antares/src/renderer/components/WorkspaceQueryTable.vue

408 lines
13 KiB
Vue
Raw Normal View History

2020-06-10 19:29:10 +02:00
<template>
<div
ref="tableWrapper"
class="vscroll"
:style="{'height': resultsSize+'px'}"
>
<TableContext
v-if="isContext"
:context-event="contextEvent"
2020-07-22 18:30:52 +02:00
:selected-rows="selectedRows"
@delete-selected="deleteSelected"
2020-08-12 18:11:48 +02:00
@close-context="isContext = false"
/>
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
<li
v-for="(result, index) in resultsWithRows"
:key="index"
class="tab-item"
:class="{'active': resultsetIndex === index}"
@click="selectResultset(index)"
>
<a>{{ result.fields ? result.fields[0].table : '' }} ({{ result.rows.length }})</a>
</li>
</ul>
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr">
<div
v-for="(field, index) in fields"
:key="index"
class="th c-hand"
:title="`${field.type} ${fieldLength(field) ? `(${fieldLength(field)})` : ''}`"
>
<div ref="columnResize" class="column-resizable">
<div class="table-column-title" @click="sort(field.name)">
<i
v-if="field.key"
class="mdi mdi-key column-key c-help"
:class="`key-${field.key}`"
:title="keyName(field.key)"
/>
<span>{{ field.alias || field.name }}</span>
<i
v-if="currentSort === field.name || currentSort === `${field.table}.${field.name}`"
class="mdi sort-icon"
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
/>
2020-06-16 18:01:22 +02:00
</div>
2020-06-11 23:34:38 +02:00
</div>
</div>
2020-06-10 19:29:10 +02:00
</div>
</div>
<BaseVirtualScroll
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
ref="resultTable"
:items="sortedResults"
:item-height="22"
class="tbody"
:visible-height="resultsSize"
:scroll-element="scrollElement"
>
<template slot-scope="{ items }">
<WorkspaceQueryTableRow
v-for="row in items"
:key="row._id"
:row="row"
:fields="fields"
:key-usage="keyUsage"
class="tr"
:class="{'selected': selectedRows.includes(row._id)}"
@select-row="selectRow($event, row._id)"
@update-field="updateField($event, getPrimaryValue(row))"
@contextmenu="contextMenu"
/>
</template>
</BaseVirtualScroll>
</div>
</div>
2020-06-10 19:29:10 +02:00
</template>
<script>
import { uidGen } from 'common/libs/uidGen';
import arrayToFile from '../libs/arrayToFile';
import { LONG_TEXT, BLOB } from 'common/fieldTypes';
2020-06-11 23:34:38 +02:00
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
import TableContext from '@/components/WorkspaceQueryTableContext';
import { mapActions, mapGetters } 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,
WorkspaceQueryTableRow,
TableContext
2020-06-18 19:01:09 +02:00
},
2020-06-10 19:29:10 +02:00
props: {
results: Array,
tabUid: [String, Number],
connUid: String,
mode: String
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,
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',
resultsetIndex: 0,
scrollElement: null
2020-06-11 23:34:38 +02:00
};
},
2020-06-26 18:14:16 +02:00
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace'
}),
workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
2020-06-26 18:14:16 +02:00
primaryField () {
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
2020-07-24 13:26:56 +02:00
},
2020-12-07 17:51:48 +01:00
isHardSort () {
return this.mode === 'table' && this.localResults.length === 1000;
},
2020-07-24 13:26:56 +02:00
sortedResults () {
2020-12-07 17:51:48 +01:00
if (this.currentSort && !this.isHardSort) {
2020-07-24 13:26:56 +02:00
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;
},
resultsWithRows () {
return this.results.filter(result => result.rows);
},
fields () {
return this.resultsWithRows.length ? this.resultsWithRows[this.resultsetIndex].fields : [];
},
keyUsage () {
return this.resultsWithRows.length ? this.resultsWithRows[this.resultsetIndex].keys : [];
2020-06-26 18:14:16 +02:00
}
},
2020-06-16 18:01:22 +02:00
watch: {
results () {
this.setLocalResults();
this.resultsetIndex = 0;
},
resultsetIndex () {
this.setLocalResults();
2020-06-11 23:34:38 +02:00
}
},
2020-06-12 18:10:45 +02:00
updated () {
if (this.$refs.table)
2020-07-24 13:26:56 +02:00
this.refreshScroller();
if (this.$refs.tableWrapper)
this.scrollElement = this.$refs.tableWrapper;
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.datePrecision;
2020-07-05 16:06:56 +02:00
return length;
},
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
2021-01-21 18:14:37 +01:00
return field.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;
}
},
getTable (index) {
if (this.resultsWithRows[index] && this.resultsWithRows[index].fields && this.resultsWithRows[index].fields.length)
return this.resultsWithRows[index].fields[0].table;
return '';
},
getSchema (index) {
if (this.resultsWithRows[index] && this.resultsWithRows[index].fields && this.resultsWithRows[index].fields.length)
return this.resultsWithRows[index].fields[0].schema;
return this.workspaceSchema;
},
getPrimaryValue (row) {
const primaryFieldName = Object.keys(row).find(prop => [
this.primaryField.alias,
this.primaryField.name,
`${this.primaryField.table}.${this.primaryField.alias}`,
`${this.primaryField.table}.${this.primaryField.name}`,
`${this.primaryField.tableAlias}.${this.primaryField.alias}`,
`${this.primaryField.tableAlias}.${this.primaryField.name}`
].includes(prop));
return row[primaryFieldName];
},
setLocalResults () {
2020-11-13 12:39:40 +01:00
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
return { ...item, _id: uidGen() };
})
: [];
},
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.tableWrapper;
2020-06-11 23:34:38 +02:00
2020-06-12 18:10:45 +02:00
if (el) {
const footer = document.getElementById('footer');
2020-06-12 18:10:45 +02:00
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,
schema: this.getSchema(this.resultsetIndex),
table: this.getTable(this.resultsetIndex),
2020-06-27 15:14:08 +02:00
id,
2020-07-22 18:30:52 +02:00
...payload
2020-06-27 15:14:08 +02:00
};
this.$emit('update-field', params);
2020-06-27 15:14:08 +02:00
}
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] ||
row[`${this.primaryField.table}.${this.primaryField.name}`] ||
row[`${this.primaryField.tableAlias}.${this.primaryField.name}`]
);
2020-07-23 19:10:14 +02:00
const params = {
primary: this.primaryField.name,
schema: this.getSchema(this.resultsetIndex),
table: this.getTable(this.resultsetIndex),
2020-07-23 19:10:14 +02:00
rows: rowIDs
};
this.$emit('delete-selected', params);
2020-07-23 19:10:14 +02:00
}
},
2020-06-28 15:31:16 +02:00
applyUpdate (params) {
const { primary, id, field, table, content } = params;
2020-06-28 15:31:16 +02:00
this.localResults = this.localResults.map(row => {
if (row[primary] === id)// only fieldName
2020-06-28 15:31:16 +02:00
row[field] = content;
else if (row[`${table}.${primary}`] === id)// table.fieldName
row[`${table}.${field}`] = content;
2020-06-28 15:31:16 +02:00
return row;
});
},
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];
this.contextEvent = event;
this.isContext = true;
2020-07-24 13:26:56 +02:00
},
sort (field) {
if (this.mode === 'query')
field = `${this.getTable(this.resultsetIndex)}.${field}`;
2020-07-24 13:26:56 +02:00
if (field === this.currentSort) {
if (this.currentSortDir === 'asc')
this.currentSortDir = 'desc';
else
this.resetSort();
}
else {
this.currentSortDir = 'asc';
this.currentSort = field;
}
2020-12-07 17:51:48 +01:00
if (this.isHardSort)
this.$emit('hard-sort', { field: this.currentSort, dir: this.currentSortDir });
2020-07-24 13:26:56 +02:00
},
resetSort () {
this.currentSort = '';
this.currentSortDir = 'asc';
},
selectResultset (index) {
this.resultsetIndex = index;
},
downloadTable (format, filename) {
if (!this.sortedResults) return;
2021-01-14 18:11:36 +01:00
const rows = [...this.sortedResults].map(row => {
delete row._id;
return row;
});
arrayToFile({
type: format,
content: rows,
filename
});
2020-06-10 19:29:10 +02:00
}
}
};
</script>
<style lang="scss" scoped>
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
}
.result-tabs {
background: transparent !important;
margin: 0;
}
2020-06-10 19:29:10 +02:00
</style>