2020-06-10 19:29:10 +02:00
|
|
|
<template>
|
2020-07-10 19:51:36 +02:00
|
|
|
<div>
|
|
|
|
<TableContext
|
|
|
|
v-if="isContext"
|
|
|
|
:context-event="contextEvent"
|
2020-07-22 18:30:52 +02:00
|
|
|
:selected-rows="selectedRows"
|
2020-07-23 19:10:14 +02:00
|
|
|
@deleteSelected="deleteSelected"
|
2020-07-10 19:51:36 +02:00
|
|
|
@closeContext="isContext = false"
|
|
|
|
/>
|
|
|
|
<BaseVirtualScroll
|
|
|
|
v-if="results.rows"
|
|
|
|
ref="resultTable"
|
2020-07-24 13:26:56 +02:00
|
|
|
:items="sortedResults"
|
2020-07-10 19:51:36 +02:00
|
|
|
: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 fields"
|
|
|
|
:key="field.name"
|
2020-07-24 13:26:56 +02:00
|
|
|
class="th c-hand"
|
2020-07-10 19:51:36 +02:00
|
|
|
>
|
2020-07-24 13:26:56 +02:00
|
|
|
<div ref="columnResize" class="column-resizable">
|
|
|
|
<div class="table-column-title" @click="sort(field.name)">
|
|
|
|
<i
|
|
|
|
v-if="field.key"
|
|
|
|
class="material-icons column-key c-help"
|
|
|
|
:class="`key-${field.key}`"
|
|
|
|
:title="keyName(field.key)"
|
|
|
|
>vpn_key</i>
|
|
|
|
<span>{{ field.name }}</span>
|
|
|
|
<i v-if="currentSort === field.name" class="material-icons sort-icon">{{ currentSortDir === 'asc' ? 'arrow_upward':'arrow_downward' }}</i>
|
|
|
|
</div>
|
2020-07-10 19:51:36 +02:00
|
|
|
</div>
|
2020-06-16 18:01:22 +02:00
|
|
|
</div>
|
2020-06-11 23:34:38 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-07-10 19:51:36 +02:00
|
|
|
<div class="tbody">
|
|
|
|
<div
|
|
|
|
v-for="row in items"
|
|
|
|
:key="row._id"
|
|
|
|
class="tr"
|
2020-07-22 18:30:52 +02:00
|
|
|
:class="{'selected': selectedRows.includes(row._id)}"
|
|
|
|
@click="selectRow($event, row._id)"
|
2020-07-10 19:51:36 +02:00
|
|
|
>
|
|
|
|
<WorkspaceQueryTableCell
|
|
|
|
v-for="(col, cKey) in row"
|
|
|
|
:key="cKey"
|
|
|
|
:content="col"
|
|
|
|
:field="cKey"
|
|
|
|
:precision="fieldPrecision(cKey)"
|
|
|
|
:type="fieldType(cKey)"
|
|
|
|
@updateField="updateField($event, row[primaryField.name])"
|
2020-07-22 18:30:52 +02:00
|
|
|
@contextmenu="contextMenu($event, {id: row._id, field: cKey})"
|
2020-07-10 19:51:36 +02:00
|
|
|
/>
|
|
|
|
</div>
|
2020-06-11 23:34:38 +02:00
|
|
|
</div>
|
2020-06-10 19:29:10 +02:00
|
|
|
</div>
|
2020-07-10 19:51:36 +02:00
|
|
|
</template>
|
|
|
|
</BaseVirtualScroll>
|
|
|
|
</div>
|
2020-06-10 19:29:10 +02:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2020-08-04 17:54:19 +02:00
|
|
|
import { uidGen } from 'common/libs/uidGen';
|
2020-06-11 23:34:38 +02:00
|
|
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
2020-06-26 18:14:16 +02:00
|
|
|
import WorkspaceQueryTableCell from '@/components/WorkspaceQueryTableCell';
|
2020-07-10 19:51:36 +02:00
|
|
|
import TableContext from '@/components/WorkspaceQueryTableContext';
|
2020-06-26 18:14:16 +02:00
|
|
|
import { mapActions } from 'vuex';
|
2020-06-11 23:34:38 +02:00
|
|
|
|
2020-06-10 19:29:10 +02:00
|
|
|
export default {
|
|
|
|
name: 'WorkspaceQueryTable',
|
2020-06-11 23:34:38 +02:00
|
|
|
components: {
|
2020-06-26 18:14:16 +02:00
|
|
|
BaseVirtualScroll,
|
2020-07-10 19:51:36 +02:00
|
|
|
WorkspaceQueryTableCell,
|
|
|
|
TableContext
|
2020-06-18 19:01:09 +02:00
|
|
|
},
|
2020-06-10 19:29:10 +02:00
|
|
|
props: {
|
2020-06-16 18:01:22 +02:00
|
|
|
results: Object,
|
|
|
|
fields: Array
|
2020-06-10 19:29:10 +02:00
|
|
|
},
|
2020-06-11 23:34:38 +02:00
|
|
|
data () {
|
|
|
|
return {
|
2020-06-16 18:01:22 +02:00
|
|
|
resultsSize: 1000,
|
2020-07-10 19:51:36 +02:00
|
|
|
localResults: [],
|
|
|
|
isContext: false,
|
2020-07-22 18:30:52 +02:00
|
|
|
contextEvent: null,
|
|
|
|
selectedCell: null,
|
2020-07-24 13:26:56 +02:00
|
|
|
selectedRows: [],
|
|
|
|
currentSort: '',
|
|
|
|
currentSortDir: 'asc'
|
2020-06-11 23:34:38 +02:00
|
|
|
};
|
|
|
|
},
|
2020-06-26 18:14:16 +02:00
|
|
|
computed: {
|
|
|
|
primaryField () {
|
|
|
|
return this.fields.filter(field => field.key === 'pri')[0] || false;
|
2020-07-24 13:26:56 +02:00
|
|
|
},
|
|
|
|
sortedResults () {
|
|
|
|
if (this.currentSort) {
|
|
|
|
return [...this.localResults].sort((a, b) => {
|
|
|
|
let modifier = 1;
|
|
|
|
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
|
|
|
const valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort];
|
|
|
|
if (this.currentSortDir === 'desc') modifier = -1;
|
|
|
|
if (valA < valB) return -1 * modifier;
|
|
|
|
if (valA > valB) return 1 * modifier;
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return this.localResults;
|
2020-06-26 18:14:16 +02:00
|
|
|
}
|
|
|
|
},
|
2020-06-16 18:01:22 +02:00
|
|
|
watch: {
|
|
|
|
results () {
|
2020-07-24 13:26:56 +02:00
|
|
|
this.resetSort();
|
2020-06-16 18:01:22 +02:00
|
|
|
this.localResults = this.results.rows ? this.results.rows.map(item => {
|
2020-06-12 18:10:45 +02:00
|
|
|
return { ...item, _id: uidGen() };
|
|
|
|
}) : [];
|
2020-06-11 23:34:38 +02:00
|
|
|
}
|
|
|
|
},
|
2020-06-12 18:10:45 +02:00
|
|
|
updated () {
|
|
|
|
if (this.$refs.resultTable)
|
2020-07-24 13:26:56 +02:00
|
|
|
this.refreshScroller();
|
2020-06-11 23:34:38 +02:00
|
|
|
},
|
|
|
|
mounted () {
|
|
|
|
window.addEventListener('resize', this.resizeResults);
|
|
|
|
},
|
|
|
|
destroyed () {
|
|
|
|
window.removeEventListener('resize', this.resizeResults);
|
|
|
|
},
|
2020-06-10 19:29:10 +02:00
|
|
|
methods: {
|
2020-06-26 18:14:16 +02:00
|
|
|
...mapActions({
|
|
|
|
addNotification: 'notifications/addNotification'
|
|
|
|
}),
|
2020-06-18 19:01:09 +02:00
|
|
|
fieldType (cKey) {
|
|
|
|
let type = 'unknown';
|
|
|
|
const field = this.fields.filter(field => field.name === cKey)[0];
|
|
|
|
if (field)
|
|
|
|
type = field.type;
|
2020-06-10 19:29:10 +02:00
|
|
|
|
2020-06-18 19:01:09 +02:00
|
|
|
return type;
|
|
|
|
},
|
2020-07-05 16:06:56 +02:00
|
|
|
fieldPrecision (cKey) {
|
|
|
|
let length = 0;
|
|
|
|
const field = this.fields.filter(field => field.name === cKey)[0];
|
|
|
|
if (field)
|
|
|
|
length = field.precision;
|
|
|
|
|
|
|
|
return length;
|
|
|
|
},
|
2020-06-16 18:01:22 +02:00
|
|
|
keyName (key) {
|
|
|
|
switch (key) {
|
|
|
|
case 'pri':
|
|
|
|
return 'PRIMARY';
|
|
|
|
case 'uni':
|
|
|
|
return 'UNIQUE';
|
|
|
|
case 'mul':
|
|
|
|
return 'INDEX';
|
|
|
|
default:
|
|
|
|
return 'UNKNOWN ' + key;
|
|
|
|
}
|
|
|
|
},
|
2020-07-23 19:10:14 +02:00
|
|
|
resizeResults () {
|
2020-06-12 18:10:45 +02:00
|
|
|
if (this.$refs.resultTable) {
|
|
|
|
const el = this.$refs.resultTable.$el;
|
|
|
|
const footer = document.getElementById('footer');
|
2020-06-11 23:34:38 +02:00
|
|
|
|
2020-06-12 18:10:45 +02:00
|
|
|
if (el) {
|
|
|
|
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
|
|
|
this.resultsSize = size;
|
|
|
|
}
|
2020-07-23 19:10:14 +02:00
|
|
|
this.$refs.resultTable.updateWindow();
|
2020-06-11 23:34:38 +02:00
|
|
|
}
|
2020-06-26 18:14:16 +02:00
|
|
|
},
|
2020-07-23 19:10:14 +02:00
|
|
|
refreshScroller () {
|
|
|
|
this.resizeResults();
|
|
|
|
},
|
2020-07-22 18:30:52 +02:00
|
|
|
updateField (payload, id) {
|
2020-06-26 18:14:16 +02:00
|
|
|
if (!this.primaryField)
|
|
|
|
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
|
2020-06-27 15:14:08 +02:00
|
|
|
else {
|
|
|
|
const params = {
|
|
|
|
primary: this.primaryField.name,
|
|
|
|
id,
|
2020-07-22 18:30:52 +02:00
|
|
|
...payload
|
2020-06-27 15:14:08 +02:00
|
|
|
};
|
|
|
|
this.$emit('updateField', params);
|
|
|
|
}
|
2020-06-28 15:31:16 +02:00
|
|
|
},
|
2020-07-23 19:10:14 +02:00
|
|
|
deleteSelected () {
|
|
|
|
if (!this.primaryField)
|
|
|
|
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
|
|
|
|
else {
|
|
|
|
const rowIDs = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => row[this.primaryField.name]);
|
|
|
|
const params = {
|
|
|
|
primary: this.primaryField.name,
|
|
|
|
rows: rowIDs
|
|
|
|
};
|
|
|
|
this.$emit('deleteSelected', params);
|
|
|
|
}
|
|
|
|
},
|
2020-06-28 15:31:16 +02:00
|
|
|
applyUpdate (params) {
|
|
|
|
const { primary, id, field, content } = params;
|
|
|
|
this.localResults = this.localResults.map(row => {
|
|
|
|
if (row[primary] === id)
|
|
|
|
row[field] = content;
|
|
|
|
|
|
|
|
return row;
|
|
|
|
});
|
2020-07-10 19:51:36 +02:00
|
|
|
},
|
2020-07-22 18:30:52 +02:00
|
|
|
selectRow (event, row) {
|
|
|
|
if (event.ctrlKey) {
|
|
|
|
if (this.selectedRows.includes(row))
|
|
|
|
this.selectedRows = this.selectedRows.filter(el => el !== row);
|
|
|
|
else
|
|
|
|
this.selectedRows.push(row);
|
|
|
|
}
|
|
|
|
else if (event.shiftKey) {
|
|
|
|
if (!this.selectedRows.length)
|
|
|
|
this.selectedRows.push(row);
|
|
|
|
else {
|
|
|
|
const lastID = this.selectedRows.slice(-1)[0];
|
|
|
|
const lastIndex = this.localResults.findIndex(el => el._id === lastID);
|
|
|
|
const clickedIndex = this.localResults.findIndex(el => el._id === row);
|
|
|
|
if (lastIndex > clickedIndex) {
|
|
|
|
for (let i = clickedIndex; i < lastIndex; i++)
|
|
|
|
this.selectedRows.push(this.localResults[i]._id);
|
|
|
|
}
|
|
|
|
else if (lastIndex < clickedIndex) {
|
|
|
|
for (let i = clickedIndex; i > lastIndex; i--)
|
|
|
|
this.selectedRows.push(this.localResults[i]._id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
this.selectedRows = [row];
|
|
|
|
},
|
|
|
|
contextMenu (event, cell) {
|
|
|
|
this.selectedCell = cell;
|
|
|
|
if (!this.selectedRows.includes(cell.id))
|
|
|
|
this.selectedRows = [cell.id];
|
2020-07-10 19:51:36 +02:00
|
|
|
this.contextEvent = event;
|
|
|
|
this.isContext = true;
|
2020-07-24 13:26:56 +02:00
|
|
|
},
|
|
|
|
sort (field) {
|
|
|
|
if (field === this.currentSort) {
|
|
|
|
if (this.currentSortDir === 'asc')
|
|
|
|
this.currentSortDir = 'desc';
|
|
|
|
else
|
|
|
|
this.resetSort();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.currentSortDir = 'asc';
|
|
|
|
this.currentSort = field;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
resetSort () {
|
|
|
|
this.currentSort = '';
|
|
|
|
this.currentSortDir = 'asc';
|
2020-06-10 19:29:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
2020-06-16 18:01:22 +02:00
|
|
|
<style lang="scss">
|
2020-06-11 23:34:38 +02:00
|
|
|
.vscroll {
|
2020-07-31 18:16:28 +02:00
|
|
|
height: 1000px;
|
|
|
|
overflow: auto;
|
|
|
|
overflow-anchor: none;
|
2020-06-11 23:34:38 +02:00
|
|
|
}
|
2020-06-16 18:01:22 +02:00
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
.column-resizable {
|
|
|
|
&:hover,
|
|
|
|
&:active {
|
|
|
|
resize: horizontal;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
2020-07-24 13:26:56 +02:00
|
|
|
}
|
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
.table-column-title {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
2020-06-16 18:01:22 +02:00
|
|
|
}
|
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
.sort-icon {
|
|
|
|
font-size: 0.7rem;
|
|
|
|
line-height: 1;
|
|
|
|
margin-left: 0.2rem;
|
2020-07-24 13:26:56 +02:00
|
|
|
}
|
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
.column-key {
|
|
|
|
transform: rotate(90deg);
|
|
|
|
font-size: 0.7rem;
|
|
|
|
line-height: 1.5;
|
|
|
|
margin-right: 0.2rem;
|
2020-06-16 18:01:22 +02:00
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
&.key-pri {
|
|
|
|
color: goldenrod;
|
|
|
|
}
|
2020-06-16 18:01:22 +02:00
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
&.key-uni {
|
|
|
|
color: deepskyblue;
|
|
|
|
}
|
2020-06-16 18:01:22 +02:00
|
|
|
|
2020-07-31 18:16:28 +02:00
|
|
|
&.key-mul {
|
|
|
|
color: palegreen;
|
|
|
|
}
|
2020-06-16 18:01:22 +02:00
|
|
|
}
|
2020-06-10 19:29:10 +02:00
|
|
|
</style>
|