mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Finally a virtual scroll table
This commit is contained in:
5
package-lock.json
generated
5
package-lock.json
generated
@ -12884,6 +12884,11 @@
|
|||||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||||
"dev": true
|
"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": {
|
"vue-virtual-scroller": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-1.0.10.tgz",
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"spectre.css": "^0.5.8",
|
"spectre.css": "^0.5.8",
|
||||||
"vue-click-outside": "^1.1.0",
|
"vue-click-outside": "^1.1.0",
|
||||||
"vue-i18n": "^8.18.2",
|
"vue-i18n": "^8.18.2",
|
||||||
|
"vue-virtual-scroll-list": "^2.2.9",
|
||||||
"vue-virtual-scroller": "^1.0.10",
|
"vue-virtual-scroller": "^1.0.10",
|
||||||
"vuedraggable": "^2.23.2",
|
"vuedraggable": "^2.23.2",
|
||||||
"vuex": "^3.4.0",
|
"vuex": "^3.4.0",
|
||||||
|
77
src/renderer/components/BaseVirtualScroll.vue
Normal file
77
src/renderer/components/BaseVirtualScroll.vue
Normal 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>
|
43
src/renderer/components/WorkspaceQueryRow.vue
Normal file
43
src/renderer/components/WorkspaceQueryRow.vue
Normal 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>
|
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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" />
|
<WorkspaceQueryTable v-if="results" :results="results" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -58,12 +58,6 @@ export default {
|
|||||||
return this.getWorkspace(this.connection.uid);
|
return this.getWorkspace(this.connection.uid);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
|
||||||
window.addEventListener('resize', this.resizeResults);
|
|
||||||
},
|
|
||||||
destroyed () {
|
|
||||||
window.removeEventListener('resize', this.resizeResults);
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification'
|
addNotification: 'notifications/addNotification'
|
||||||
@ -72,7 +66,6 @@ export default {
|
|||||||
if (!this.query) return;
|
if (!this.query) return;
|
||||||
this.isQuering = true;
|
this.isQuering = true;
|
||||||
this.results = {};
|
this.results = {};
|
||||||
this.resizeResults();
|
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
@ -92,24 +85,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.isQuering = false;
|
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}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,42 +1,80 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="results" class="table table-hover">
|
<BaseVirtualScroll
|
||||||
<div class="thead">
|
v-if="results.rows"
|
||||||
<div class="tr">
|
ref="resultTable"
|
||||||
<div
|
:items="results.rows"
|
||||||
v-for="field in results.fields"
|
:item-height="25"
|
||||||
:key="field.name"
|
class="vscroll"
|
||||||
class="th"
|
:style="{'height': resultsSize+'px'}"
|
||||||
>
|
>
|
||||||
{{ field.name }}
|
<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>
|
||||||
</div>
|
</template>
|
||||||
<div class="tbody">
|
</BaseVirtualScroll>
|
||||||
<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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceQueryTable',
|
name: 'WorkspaceQueryTable',
|
||||||
|
components: {
|
||||||
|
BaseVirtualScroll
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
results: Object
|
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: {
|
methods: {
|
||||||
fieldType (col) {
|
fieldType (col) {
|
||||||
let type = typeof col;
|
let type = typeof col;
|
||||||
@ -46,11 +84,24 @@ export default {
|
|||||||
if (col === null) type = 'null';
|
if (col === null) type = 'null';
|
||||||
|
|
||||||
return `type-${type}`;
|
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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.vscroll {
|
||||||
|
height: 1000px;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-anchor: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
||||||
import 'material-design-icons/iconfont/material-icons.css';
|
import 'material-design-icons/iconfont/material-icons.css';
|
||||||
import '@/scss/main.scss';
|
import '@/scss/main.scss';
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user