Finally a virtual scroll table

This commit is contained in:
Fabio 2020-06-11 23:34:38 +02:00
parent 1c3323b537
commit 29812f7ee3
7 changed files with 208 additions and 55 deletions

5
package-lock.json generated
View File

@ -12884,6 +12884,11 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vue-virtual-scroll-list": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-2.2.9.tgz",
"integrity": "sha512-dPlvzIUUtxkaVBVea2/73sWsiTrsIpjWXd+7FWJPUEL+ME1i6LuwWNiMMqu2WVad82ONWeoXSiM5NMSDpMxYGA=="
},
"vue-virtual-scroller": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-1.0.10.tgz",

View File

@ -41,6 +41,7 @@
"spectre.css": "^0.5.8",
"vue-click-outside": "^1.1.0",
"vue-i18n": "^8.18.2",
"vue-virtual-scroll-list": "^2.2.9",
"vue-virtual-scroller": "^1.0.10",
"vuedraggable": "^2.23.2",
"vuex": "^3.4.0",

View File

@ -0,0 +1,77 @@
<template>
<div class="vscroll-holder">
<div
class="vscroll-spacer"
:style="{
opacity: 0,
clear: 'both',
height: topHeight + 'px'
}"
/>
<slot :items="visibleItems" />
<div
class="vscroll-spacer"
:style="{
opacity: 0,
clear: 'both',
height: bottomHeight + 'px'
}"
/>
</div>
</template>
<script>
// credits: https://github.com/xrado 👼
export default {
name: 'BaseVirtualScroll',
props: {
items: Array,
itemHeight: Number
},
data () {
return {
topHeight: 0,
bottomHeight: 0,
visibleItems: []
};
},
mounted () {
this._checkScrollPosition = this.checkScrollPosition.bind(this);
this.checkScrollPosition();
this.$el.addEventListener('scroll', this._checkScrollPosition);
this.$el.addEventListener('wheel', this._checkScrollPosition);
},
beforeDestroy () {
this.$el.removeEventListener('scroll', this._checkScrollPosition);
this.$el.removeEventListener('wheel', this._checkScrollPosition);
},
methods: {
checkScrollPosition (e = {}) {
var el = this.$el;
// prevent parent scroll
if ((el.scrollTop === 0 && e.deltaY < 0) || (Math.abs(el.scrollTop - (el.scrollHeight - el.clientHeight)) <= 1 && e.deltaY > 0))
e.preventDefault();
this.updateWindow(e);
},
updateWindow (e) {
const visibleItemsCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
const totalScrollHeight = this.items.length * this.itemHeight;
const scrollTop = this.$el.scrollTop;
const offset = 5;
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
this.visibleItems = this.items.slice(firstCutIndex, lastCutIndex);
this.topHeight = firstCutIndex * this.itemHeight;
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
}
}
};
</script>

View File

@ -0,0 +1,43 @@
<template>
<div>
<div
v-for="(col, cKey) in source"
:key="cKey"
class="td"
:class="fieldType(col)"
>
{{ col }}
</div>
</div>
</template>
<script>
export default {
name: 'WorkspaceQueryRow',
props: {
index: { // index of current item
type: Number
},
source: { // here is: {uid: 'unique_1', text: 'abc'}
type: Object,
default () {
return {};
}
}
},
methods: {
fieldType (col) {
let type = typeof col;
if (type === 'object')
if (col instanceof Date) type = 'date';
if (col instanceof Uint8Array) type = 'blob';
if (col === null) type = 'null';
return `type-${type}`;
}
}
};
</script>
<style>
</style>

View File

@ -22,7 +22,7 @@
</div>
</div>
</div>
<div ref="resultTable" class="workspace-query-results column col-12">
<div class="workspace-query-results column col-12">
<WorkspaceQueryTable v-if="results" :results="results" />
</div>
</div>
@ -58,12 +58,6 @@ export default {
return this.getWorkspace(this.connection.uid);
}
},
mounted () {
window.addEventListener('resize', this.resizeResults);
},
destroyed () {
window.removeEventListener('resize', this.resizeResults);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
@ -72,7 +66,6 @@ export default {
if (!this.query) return;
this.isQuering = true;
this.results = {};
this.resizeResults();
const params = {
uid: this.connection.uid,
@ -92,24 +85,6 @@ export default {
}
this.isQuering = false;
},
resizeResults (e) {
const el = this.$refs.resultTable;
const footer = document.getElementById('footer');
if (el) {
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
el.style.height = size + 'px';
}
},
fieldType (col) {
let type = typeof col;
if (type === 'object')
if (col instanceof Date) type = 'date';
if (col instanceof Uint8Array) type = 'blob';
if (col === null) type = 'null';
return `type-${type}`;
}
}
};

View File

@ -1,42 +1,80 @@
<template>
<div v-if="results" class="table table-hover">
<div class="thead">
<div class="tr">
<div
v-for="field in results.fields"
:key="field.name"
class="th"
>
{{ field.name }}
<BaseVirtualScroll
v-if="results.rows"
ref="resultTable"
:items="results.rows"
:item-height="25"
class="vscroll"
:style="{'height': resultsSize+'px'}"
>
<template slot-scope="{ items }">
<div class="table table-hover">
<div class="thead">
<div class="tr">
<div
v-for="field in results.fields"
:key="field.name"
class="th"
>
{{ field.name }}
</div>
</div>
</div>
<div class="tbody">
<div
v-for="(row, rKey) in items"
:key="rKey"
class="tr"
tabindex="0"
>
<div
v-for="(col, cKey) in row"
:key="cKey"
class="td"
:class="fieldType(col)"
>
{{ col }}
</div>
</div>
</div>
</div>
</div>
<div class="tbody">
<div
v-for="(row, rKey) in results.rows"
:key="rKey"
class="tr"
tabindex="0"
>
<div
v-for="(col, cKey) in row"
:key="cKey"
class="td"
:class="fieldType(col)"
>
{{ col }}
</div>
</div>
</div>
</div>
</template>
</BaseVirtualScroll>
</template>
<script>
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
export default {
name: 'WorkspaceQueryTable',
components: {
BaseVirtualScroll
},
props: {
results: Object
},
data () {
return {
resultsSize: 1000
};
},
computed: {
rows () {
return this.results.rows ? this.results.rows.map(item => Object.assign({}, item)) : [];
}
},
watch: {
results: function () {
if (this.$refs.resultTable)
this.resizeResults();
}
},
mounted () {
window.addEventListener('resize', this.resizeResults);
},
destroyed () {
window.removeEventListener('resize', this.resizeResults);
},
methods: {
fieldType (col) {
let type = typeof col;
@ -46,11 +84,24 @@ export default {
if (col === null) type = 'null';
return `type-${type}`;
},
resizeResults (e) {
const el = this.$refs.resultTable.$el;
const footer = document.getElementById('footer');
if (el) {
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
this.resultsSize = size;
}
}
}
};
</script>
<style>
.vscroll {
height: 1000px;
overflow: auto;
overflow-anchor: none;
}
</style>

View File

@ -2,6 +2,7 @@
import Vue from 'vue';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
import 'material-design-icons/iconfont/material-icons.css';
import '@/scss/main.scss';