Results table improvements

This commit is contained in:
Fabio 2020-07-24 13:26:56 +02:00
parent fdf5bef5ad
commit 60132c94a1
16 changed files with 266 additions and 213 deletions

View File

@ -1,11 +1,11 @@
import connection from './connection';
import structure from './structure';
import tables from './tables';
import updates from './updates';
const connections = {};
export default () => {
connection(connections);
structure(connections);
tables(connections);
updates();
};

View File

@ -1,6 +1,6 @@
import { ipcMain } from 'electron';
import InformationSchema from '../models/InformationSchema';
import Generic from '../models/Generic';
import Tables from '../models/Tables';
// TODO: remap objects based on client
@ -17,7 +17,7 @@ export default (connections) => {
ipcMain.handle('getTableData', async (event, { uid, schema, table }) => {
try {
const result = await Generic.getTableData(connections[uid], schema, table);
const result = await Tables.getTableData(connections[uid], schema, table);
return { status: 'success', response: result };
}
catch (err) {
@ -27,7 +27,7 @@ export default (connections) => {
ipcMain.handle('updateTableCell', async (event, params) => {
try {
const result = await Generic.updateTableCell(connections[params.uid], params);
const result = await Tables.updateTableCell(connections[params.uid], params);
return { status: 'success', response: result };
}
catch (err) {
@ -37,7 +37,7 @@ export default (connections) => {
ipcMain.handle('deleteTableRows', async (event, params) => {
try {
const result = await Generic.deleteTableRows(connections[params.uid], params);
const result = await Tables.deleteTableRows(connections[params.uid], params);
return { status: 'success', response: result };
}
catch (err) {

View File

@ -11,30 +11,4 @@ export default class {
}
return connection.raw(query);
}
static async getTableData (connection, schema, table) {
return connection
.select('*')
.schema(schema)
.from(table)
.limit(1000)
.run();
}
static async updateTableCell (connection, params) { // TODO: Handle different field types
return connection
.update({ [params.field]: `= "${params.content}"` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${params.id}` })
.run();
}
static async deleteTableRows (connection, params) {
return connection
.schema(params.schema)
.delete(params.table)
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
.run();
}
}

28
src/main/models/Tables.js Normal file
View File

@ -0,0 +1,28 @@
'use strict';
export default class {
static async getTableData (connection, schema, table) {
return connection
.select('*')
.schema(schema)
.from(table)
.limit(1000)
.run();
}
static async updateTableCell (connection, params) { // TODO: Handle different field types
return connection
.update({ [params.field]: `= "${params.content}"` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${params.id}` })
.run();
}
static async deleteTableRows (connection, params) {
return connection
.schema(params.schema)
.delete(params.table)
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
.run();
}
}

View File

@ -152,9 +152,14 @@ export default {
border-left: none;
border-bottom-width: 2px;
border-color: $bg-color-light;
padding: .1rem .4rem;
padding: 0;
font-weight: 700;
font-size: .7rem;
> div {
padding: .1rem .4rem;
min-width: -webkit-fill-available;
}
}
.td{

View File

@ -43,7 +43,7 @@
<script>
import Connection from '@/ipc-api/Connection';
import Structure from '@/ipc-api/Structure';
import Tables from '@/ipc-api/Tables';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import { mapGetters, mapActions } from 'vuex';
@ -132,7 +132,7 @@ export default {
table: this.table
};
const { status, response } = await Structure.getTableColumns(params);
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success')
this.fields = response.rows;
else

View File

@ -10,7 +10,7 @@
<BaseVirtualScroll
v-if="results.rows"
ref="resultTable"
:items="localResults"
:items="sortedResults"
:item-height="25"
class="vscroll"
:style="{'height': resultsSize+'px'}"
@ -22,16 +22,19 @@
<div
v-for="field in fields"
:key="field.name"
class="th"
class="th c-hand"
>
<div class="table-column-title">
<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>
<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>
</div>
</div>
</div>
@ -87,16 +90,34 @@ export default {
isContext: false,
contextEvent: null,
selectedCell: null,
selectedRows: []
selectedRows: [],
currentSort: '',
currentSortDir: 'asc'
};
},
computed: {
primaryField () {
return this.fields.filter(field => field.key === 'pri')[0] || false;
},
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;
}
},
watch: {
results () {
this.resetSort();
this.localResults = this.results.rows ? this.results.rows.map(item => {
return { ...item, _id: uidGen() };
}) : [];
@ -104,7 +125,7 @@ export default {
},
updated () {
if (this.$refs.resultTable)
this.resizeResults();
this.refreshScroller();
},
mounted () {
window.addEventListener('resize', this.resizeResults);
@ -225,6 +246,22 @@ export default {
this.selectedRows = [cell.id];
this.contextEvent = event;
this.isContext = true;
},
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';
}
}
};
@ -237,11 +274,25 @@ export default {
overflow-anchor: none;
}
.column-resizable{
&:hover,
&:active{
resize: horizontal;
overflow: hidden;
}
}
.table-column-title{
display: flex;
align-items: center;
}
.sort-icon{
font-size: .7rem;
line-height: 1;
margin-left: .2rem;
}
.column-key{
transform: rotate(90deg);
font-size: .7rem;

View File

@ -48,7 +48,7 @@ export default {
filters: {
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 50 ? `${val.substring(0, 50)}[...]` : val;
return val.length > 128 ? `${val.substring(0, 128)}[...]` : val;
},
typeFormat (val, type, precision) {
if (!val) return val;
@ -167,14 +167,12 @@ export default {
border: none;
line-height: 1;
width: 100%;
max-width: 200px;
}
.cell-content{
display: block;
min-height: .8rem;
text-overflow: ellipsis;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
}

View File

@ -40,7 +40,7 @@
</template>
<script>
import Structure from '@/ipc-api/Structure';
import Tables from '@/ipc-api/Tables';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
@ -85,13 +85,13 @@ export default {
}
},
watch: {
table: function () {
table () {
if (this.isSelected) {
this.getTableData();
this.lastTable = this.table;
}
},
isSelected: function (val) {
isSelected (val) {
if (val && this.lastTable !== this.table) {
this.getTableData();
this.lastTable = this.table;
@ -117,7 +117,7 @@ export default {
};
try {
const { status, response } = await Structure.getTableColumns(params);
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success')
this.fields = response.rows;
else
@ -128,7 +128,7 @@ export default {
}
try {
const { status, response } = await Structure.getTableData(params);
const { status, response } = await Tables.getTableData(params);
if (status === 'success')
this.results = response;

View File

@ -1,4 +1,4 @@
import Structure from '@/ipc-api/Structure';
import Tables from '@/ipc-api/Tables';
export default {
methods: {
@ -6,12 +6,12 @@ export default {
const params = {
uid: this.connection.uid,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
table: this.table,
...payload
};
try {
const { status, response } = await Structure.updateTableCell(params);
const { status, response } = await Tables.updateTableCell(params);
if (status === 'success')
this.$refs.queryTable.applyUpdate(payload);
else
@ -30,7 +30,7 @@ export default {
};
try {
const { status, response } = await Structure.deleteTableRows(params);
const { status, response } = await Tables.deleteTableRows(params);
if (status === 'success') {
const { primary, rows } = params;
this.results = { ...this.results, rows: this.results.rows.filter(row => !rows.includes(row[primary])) };

View File

@ -3,44 +3,41 @@
.type-#{$type} {
color: $color;
@if $type == 'number'{
@if $type == "number" {
text-align: right;
}
}
}
}
@include type-colors((
"char": seagreen,
"varchar": seagreen,
"text": seagreen,
"mediumtext": seagreen,
"longtext": seagreen,
@include type-colors(
(
"char": seagreen,
"varchar": seagreen,
"text": seagreen,
"mediumtext": seagreen,
"longtext": seagreen,
"int": cornflowerblue,
"tinyint": cornflowerblue,
"smallint": cornflowerblue,
"mediumint": cornflowerblue,
"bigint": cornflowerblue,
"datetime": coral,
"date": coral,
"time": coral,
"timestamp": coral,
"bit": lightskyblue,
"blob": darkorchid,
"mediumblob": darkorchid,
"longblob": darkorchid,
"unknown": gray,
)
);
"int": cornflowerblue,
"tinyint": cornflowerblue,
"smallint": cornflowerblue,
"mediumint": cornflowerblue,
"bigint": cornflowerblue,
"datetime": coral,
"date": coral,
"time": coral,
"timestamp": coral,
"bit": lightskyblue,
"blob": darkorchid,
"mediumblob": darkorchid,
"longblob": darkorchid,
"unknown": gray,
));
.is-null{
.is-null {
color: gray;
&::after{
content: 'NULL';
&::after {
content: "NULL";
}
}
}

View File

@ -1,26 +1,26 @@
.dbi{
.dbi {
display: inline-block;
width: 42px;
height: 42px;
background-size: cover;
&.dbi-mysql{
background-image: url('../images/svg/mysql.svg');
&.dbi-mysql {
background-image: url("../images/svg/mysql.svg");
}
&.dbi-maria{
background-image: url('../images/svg/mariadb.svg');
&.dbi-maria {
background-image: url("../images/svg/mariadb.svg");
}
&.dbi-mssql{
background-image: url('../images/svg/mssql.svg');
&.dbi-mssql {
background-image: url("../images/svg/mssql.svg");
}
&.dbi-pg{
background-image: url('../images/svg/pg.svg');
&.dbi-pg {
background-image: url("../images/svg/pg.svg");
}
&.dbi-oracledb{
background-image: url('../images/svg/oracledb.svg');
&.dbi-oracledb {
background-image: url("../images/svg/oracledb.svg");
}
}
}

View File

@ -1,68 +1,69 @@
.table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
display: table;
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
&,
&.table-striped {
.tbody {
.tr {
&.selected{
background: #333!important;
}
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-dark;
}
}
}
}
// Scollable tables
&.table-scroll {
display: block;
overflow-x: auto;
padding-bottom: .75rem;
white-space: nowrap;
}
border-collapse: collapse;
border-spacing: 0;
width: 100%;
display: table;
table-layout: fixed;
.thead{
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: #333 !important;
}
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-dark;
}
}
}
}
// Scollable tables
&.table-scroll {
display: block;
overflow-x: auto;
padding-bottom: 0.75rem;
white-space: nowrap;
}
.thead {
display: table-header-group;
}
.tbody{
}
.tbody {
display: table-row-group;
}
}
.tr{
.tr {
display: table-row;
}
}
.td,
.th {
border-bottom: $border-width solid $border-color;
padding: $unit-3 $unit-2;
display: table-cell;
}
.th {
border-bottom-width: $border-width-lg;
}
}
.td,
.th {
border-bottom: $border-width solid $border-color;
padding: $unit-3 $unit-2;
display: table-cell;
}
.th {
border-bottom-width: $border-width-lg;
}
}

View File

@ -1,10 +1,11 @@
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}

View File

@ -1,4 +1,3 @@
@import "~spectre.css/src/variables";
@import "variables";
@import "transitions";
@ -9,7 +8,7 @@
@import "~spectre.css/src/spectre";
@import "~spectre.css/src/spectre-exp";
body{
body {
user-select: none;
}
@ -19,21 +18,21 @@ body{
@include padding-variant(3, $unit-3);
@include padding-variant(4, $unit-4);
.btn.btn-gray{
.btn.btn-gray {
color: #fff;
background: $bg-color-gray;
&:hover{
&:hover {
background: $bg-color;
}
}
.p-vcentered{
display: flex!important;
.p-vcentered {
display: flex !important;
align-items: center;
}
.c-help{
.c-help {
cursor: help;
}
@ -42,109 +41,108 @@ body{
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: $bg-color-light;
background: $bg-color-light;
}
::-webkit-scrollbar-thumb {
background: rgba($color: #FFF, $alpha: .5);
::-webkit-scrollbar-thumb {
background: rgba($color: #fff, $alpha: 0.5);
&:hover {
background: rgba($color: #FFF, $alpha: 1);
background: rgba($color: #fff, $alpha: 1);
}
}
// Animations
@keyframes rotation {
from {
transform: rotate(0deg);
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
transform: rotate(359deg);
}
}
}
.rotate {
animation: rotation .8s infinite linear;
animation: rotation 0.8s infinite linear;
}
/*Override*/
.modal{
.modal {
.modal-overlay,
&.active .modal-overlay{
&.active .modal-overlay {
background: rgba(255, 255, 255, 0.15);
}
.modal-sm .modal-container,
.modal-container{
.modal-container {
box-shadow: 0 0 1px 0px #000;
padding: 0;
background: $bg-color;
.modal-header{
padding: .4rem .8rem;
.modal-header {
padding: 0.4rem 0.8rem;
text-transform: uppercase;
background: $bg-color-gray;
display: flex;
justify-content: space-between;
align-items: center;
color: #FFF;
color: #fff;
}
}
}
.tab{
.tab {
border-color: #272727;
}
.panel{
.panel {
border: none;
}
.badge{
.badge {
&[data-badge],
&:not([data-badge]){
&:not([data-badge]) {
&::after {
box-shadow: none;
}
}
}
.form-select{
.form-select {
cursor: pointer;
}
.form-select,
.form-select:not([multiple]):not([size]),
.form-input,
.form-checkbox .form-icon,
.form-radio .form-icon{
.form-checkbox .form-icon,
.form-radio .form-icon {
border-color: $bg-color-light;
background: $bg-color-gray;
}
.form-select:not([multiple]):not([size]):focus{
.form-select:not([multiple]):not([size]):focus {
border-color: $primary-color;
}
.menu{
font-size: .7rem;
.menu {
font-size: 0.7rem;
.menu-item {
+ .menu-item{
+ .menu-item {
margin-top: 0;
}
}
}
}
.accordion-body {
max-height: 500rem!important;
max-height: 500rem !important;
}
.btn.loading {
> .material-icons,
> span{
> span {
visibility: hidden;
}
}
}