1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-03-01 01:47:41 +01:00

Notifications timeout, large text editor

This commit is contained in:
Fabio 2020-07-30 19:12:29 +02:00
parent acd3310228
commit 413b56916c
11 changed files with 213 additions and 33 deletions

View File

@ -0,0 +1,19 @@
/* eslint-disable no-useless-escape */
// eslint-disable-next-line no-control-regex
const regex = new RegExp(/[\0\x08\x09\x1a\n\r"'\\\%]/g);
/**
* Escapes a string
*
* @param {String} string
* @returns {String}
*/
function sqlEscaper (string) {
return string.replace(regex, (char) => {
var m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '"', '\\', '\\\\', '%'];
var r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\'\'', '""', '\\\\', '\\\\\\\\', '\\%'];
return r[m.indexOf(char)];
});
}
export { sqlEscaper };

View File

@ -64,12 +64,12 @@ async function createMainWindow () {
}); });
}); });
// Initialize ipcHandlers
ipcHandlers();
return window; return window;
}; };
// Initialize ipcHandlers
ipcHandlers();
// quit application when all windows are closed // quit application when all windows are closed
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// on macOS it is common for applications to stay open until the user explicitly quits // on macOS it is common for applications to stay open until the user explicitly quits

View File

@ -1,4 +1,5 @@
'use strict'; 'use strict';
import { sqlEscaper } from 'common/libs/sqlEscaper';
export default class { export default class {
static async getTableData (connection, schema, table) { static async getTableData (connection, schema, table) {
return connection return connection
@ -9,9 +10,29 @@ export default class {
.run(); .run();
} }
static async updateTableCell (connection, params) { // TODO: Handle different field types static async updateTableCell (connection, params) {
let escapedParam;
switch (params.type) {
case 'int':
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'bigint':
escapedParam = params.content;
break;
case 'char':
case 'varchar':
case 'text':
case 'mediumtext':
case 'longtext':
escapedParam = `"${sqlEscaper(params.content)}"`;
break;
default:
escapedParam = `"${sqlEscaper(params.content)}"`;
break;
}
return connection return connection
.update({ [params.field]: `= "${params.content}"` }) .update({ [params.field]: `= ${escapedParam}` })
.schema(params.schema) .schema(params.schema)
.from(params.table) .from(params.table)
.where({ [params.primary]: `= ${params.id}` }) .where({ [params.primary]: `= ${params.id}` })

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="modal modal-sm active"> <div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" /> <a class="modal-overlay" @click="hideModal" />
<div class="modal-container"> <div class="modal-container">
<div v-if="hasHeader" class="modal-header"> <div v-if="hasHeader" class="modal-header">
@ -45,6 +45,12 @@
<script> <script>
export default { export default {
name: 'BaseConfirmModal', name: 'BaseConfirmModal',
props: {
size: {
type: String,
default: 'small' // small, medium, large
}
},
computed: { computed: {
hasHeader () { hasHeader () {
return !!this.$slots.header; return !!this.$slots.header;
@ -54,6 +60,13 @@ export default {
}, },
hasDefault () { hasDefault () {
return !!this.$slots.default; return !!this.$slots.default;
},
modalSizeClass () {
if (this.size === 'small')
return 'modal-sm';
else if (this.size === 'large')
return 'modal-lg';
else return '';
} }
}, },
methods: { methods: {

View File

@ -45,8 +45,8 @@
<div v-if="selectedTab === 'general'" class="panel-body py-4"> <div v-if="selectedTab === 'general'" class="panel-body py-4">
<form class="form-horizontal"> <form class="form-horizontal">
<div class="col-6 col-sm-12"> <div class="col-8 col-sm-12">
<div class="form-group"> <div class="form-group mb-4">
<div class="col-6 col-sm-12"> <div class="col-6 col-sm-12">
<label class="form-label"> <label class="form-label">
<i class="material-icons md-18 mr-1">translate</i> <i class="material-icons md-18 mr-1">translate</i>
@ -69,6 +69,25 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
{{ $t('message.notificationsTimeout') }}:
</label>
</div>
<div class="col-6 col-sm-12">
<div class="input-group">
<input
v-model="localTimeout"
class="form-input"
type="number"
min="1"
@focusout="checkNotificationsTimeout"
>
<span class="input-group-addon">{{ $t('word.seconds') }}</span>
</div>
</div>
</div>
</div> </div>
</form> </form>
</div> </div>
@ -115,6 +134,7 @@ export default {
return { return {
isUpdate: false, isUpdate: false,
localLocale: null, localLocale: null,
localTimeout: null,
selectedTab: 'general' selectedTab: 'general'
}; };
}, },
@ -123,7 +143,8 @@ export default {
appName: 'application/appName', appName: 'application/appName',
appVersion: 'application/appVersion', appVersion: 'application/appVersion',
selectedSettingTab: 'application/selectedSettingTab', selectedSettingTab: 'application/selectedSettingTab',
selectedLocale: 'settings/getLocale' selectedLocale: 'settings/getLocale',
notificationsTimeout: 'settings/getNotificationsTimeout'
}), }),
locales () { locales () {
const locales = []; const locales = [];
@ -135,18 +156,26 @@ export default {
}, },
created () { created () {
this.localLocale = this.selectedLocale; this.localLocale = this.selectedLocale;
this.localTimeout = this.notificationsTimeout;
this.selectedTab = this.selectedSettingTab; this.selectedTab = this.selectedSettingTab;
}, },
methods: { methods: {
...mapActions({ ...mapActions({
closeModal: 'application/hideSettingModal', closeModal: 'application/hideSettingModal',
changeLocale: 'settings/changeLocale' changeLocale: 'settings/changeLocale',
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
}), }),
selectTab (tab) { selectTab (tab) {
this.selectedTab = tab; this.selectedTab = tab;
}, },
openOutside (link) { openOutside (link) {
shell.openExternal(link); shell.openExternal(link);
},
checkNotificationsTimeout () {
if (!this.localTimeout)
this.localTimeout = 1;
this.updateNotificationsTimeout(+this.localTimeout);
} }
} }
}; };

View File

@ -1,5 +1,9 @@
<template> <template>
<div id="notifications-board"> <div
id="notifications-board"
@mouseenter="clearTimeouts"
@mouseleave="rearmTimeouts"
>
<transition-group name="slide-fade"> <transition-group name="slide-fade">
<BaseNotification <BaseNotification
v-for="notification in latestNotifications" v-for="notification in latestNotifications"
@ -21,18 +25,51 @@ export default {
components: { components: {
BaseNotification BaseNotification
}, },
data () {
return {
timeouts: {}
};
},
computed: { computed: {
...mapGetters({ ...mapGetters({
notifications: 'notifications/getNotifications' notifications: 'notifications/getNotifications',
notificationsTimeout: 'settings/getNotificationsTimeout'
}), }),
latestNotifications () { latestNotifications () {
return this.notifications.slice(0, 10); return this.notifications.slice(0, 10);
} }
}, },
watch: {
notifications: {
deep: true,
handler: function (notification) {
if (notification.length) {
this.timeouts[notification[0].uid] = setTimeout(() => {
this.removeNotification(notification[0].uid);
delete this.timeouts[notification.uid];
}, this.notificationsTimeout * 1000);
}
}
}
},
methods: { methods: {
...mapActions({ ...mapActions({
removeNotification: 'notifications/removeNotification' removeNotification: 'notifications/removeNotification'
}) }),
clearTimeouts () {
for (const uid in this.timeouts) {
clearTimeout(this.timeouts[uid]);
delete this.timeouts[uid];
}
},
rearmTimeouts () {
for (const notification of this.notifications) {
this.timeouts[notification.uid] = setTimeout(() => {
this.removeNotification(notification.uid);
delete this.timeouts[notification.uid];
}, this.notificationsTimeout * 1000);
}
}
} }
}; };
</script> </script>

View File

@ -2,15 +2,14 @@
<div <div
v-if="field !== '_id'" v-if="field !== '_id'"
ref="cell" ref="cell"
class="td" class="td p-0"
:class="`type-${type} p-0`"
tabindex="0" tabindex="0"
@contextmenu.prevent="$emit('contextmenu', $event)" @contextmenu.prevent="$emit('contextmenu', $event)"
> >
<span <span
v-if="!isEditing" v-if="!isInlineEditor"
class="cell-content px-2" class="cell-content px-2"
:class="isNull(content)" :class="`${isNull(content)} type-${type}`"
@dblclick="editON" @dblclick="editON"
>{{ content | typeFormat(type, precision) | cutText }}</span> >{{ content | typeFormat(type, precision) | cutText }}</span>
<template v-else> <template v-else>
@ -34,6 +33,29 @@
@blur="editOFF" @blur="editOFF"
> >
</template> </template>
<ConfirmModal
v-if="isTextareaEditor"
size="medium"
@confirm="editOFF"
@hide="hideEditorModal"
>
<template :slot="'header'">
{{ $t('word.edit') }} "{{ field }}"
</template>
<div :slot="'body'">
<div class="mb-2">
<div>
<textarea
v-model="localContent"
class="form-input textarea-editor"
/>
</div>
<div class="pt-2">
<b>{{ $t('word.size') }}</b>: {{ localContent.length }}
</div>
</div>
</div>
</ConfirmModal>
</div> </div>
</template> </template>
@ -42,9 +64,13 @@ import moment from 'moment';
import { mimeFromHex, formatBytes } from 'common/libs/utilities'; import { mimeFromHex, formatBytes } from 'common/libs/utilities';
import hexToBinary from 'common/libs/hexToBinary'; import hexToBinary from 'common/libs/hexToBinary';
import { mask } from 'vue-the-mask'; import { mask } from 'vue-the-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
export default { export default {
name: 'WorkspaceQueryTableCell', name: 'WorkspaceQueryTableCell',
components: {
ConfirmModal
},
filters: { filters: {
cutText (val) { cutText (val) {
if (typeof val !== 'string') return val; if (typeof val !== 'string') return val;
@ -99,8 +125,9 @@ export default {
}, },
data () { data () {
return { return {
isEditing: false, isInlineEditor: false,
localContent: '' isTextareaEditor: false,
localContent: null
}; };
}, },
computed: { computed: {
@ -110,11 +137,13 @@ export default {
case 'varchar': case 'varchar':
case 'text': case 'text':
case 'mediumtext': case 'mediumtext':
case 'longtext':
return { type: 'text', mask: false }; return { type: 'text', mask: false };
case 'int': case 'int':
case 'tinyint': case 'tinyint':
case 'smallint': case 'smallint':
case 'mediumint': case 'mediumint':
case 'bigint':
return { type: 'number', mask: false }; return { type: 'number', mask: false };
case 'date': case 'date':
return { type: 'text', mask: '####-##-##' }; return { type: 'text', mask: '####-##-##' };
@ -141,21 +170,34 @@ export default {
}, },
editON () { editON () {
if (['file'].includes(this.inputProps.type)) return;// TODO: remove temporary file block if (['file'].includes(this.inputProps.type)) return;// TODO: remove temporary file block
this.$nextTick(() => {
this.$refs.cell.blur();
this.$nextTick(() => this.$refs.editField.focus());
});
this.localContent = this.$options.filters.typeFormat(this.content, this.type); this.localContent = this.$options.filters.typeFormat(this.content, this.type);
this.isEditing = true;
switch (this.type) {
case 'text':
case 'mediumtext':
case 'longtext':
this.isTextareaEditor = true;
break;
default:// Inline editable fields
this.$nextTick(() => { // Focus on input
this.$refs.cell.blur();
this.$nextTick(() => this.$refs.editField.focus());
});
this.isInlineEditor = true;
break;
}
}, },
editOFF () { editOFF () {
this.isEditing = false; this.isInlineEditor = false;
if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return; if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return;// If not changed
const { field, type, localContent: content } = this; const { field, type, localContent: content } = this;
this.$emit('updateField', { field, type, content }); this.$emit('updateField', { field, type, content });
},
hideEditorModal () {
this.isTextareaEditor = false;
} }
} }
}; };
@ -176,4 +218,8 @@ export default {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
} }
.textarea-editor{
height: 50vh!important;
}
</style> </style>

View File

@ -9,7 +9,7 @@
<ConfirmModal <ConfirmModal
v-if="isConfirmModal" v-if="isConfirmModal"
@confirm="deleteRows()" @confirm="deleteRows"
@hide="hideConfirmModal" @hide="hideConfirmModal"
> >
<template :slot="'header'"> <template :slot="'header'">

View File

@ -29,7 +29,9 @@ module.exports = {
donate: 'Donate', donate: 'Donate',
run: 'Run', run: 'Run',
schema: 'Schema', schema: 'Schema',
results: 'Results' results: 'Results',
size: 'Size',
seconds: 'Seconds'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@ -55,7 +57,8 @@ module.exports = {
unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset', unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset',
editCell: 'Edit cell', editCell: 'Edit cell',
deleteRows: 'Delete row | Delete {count} rows', deleteRows: 'Delete row | Delete {count} rows',
confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?' confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?',
notificationsTimeout: 'Notifications timeout'
}, },
// Date and Time // Date and Time
short: { short: {

View File

@ -127,6 +127,10 @@ body {
border-color: $primary-color; border-color: $primary-color;
} }
.input-group .input-group-addon{
border-color: #3f3f3f;
}
.menu { .menu {
font-size: 0.7rem; font-size: 0.7rem;
.menu-item { .menu-item {

View File

@ -6,17 +6,22 @@ export default {
strict: true, strict: true,
state: { state: {
locale: 'en-US', locale: 'en-US',
explorebar_size: null explorebar_size: null,
notifications_timeout: 10
}, },
getters: { getters: {
getLocale: state => state.locale, getLocale: state => state.locale,
getExplorebarSize: state => state.explorebar_size getExplorebarSize: state => state.explorebar_size,
getNotificationsTimeout: state => state.notifications_timeout
}, },
mutations: { mutations: {
SET_LOCALE (state, locale) { SET_LOCALE (state, locale) {
state.locale = locale; state.locale = locale;
i18n.locale = locale; i18n.locale = locale;
}, },
SET_NOTIFICATIONS_TIMEOUT (state, timeout) {
state.notifications_timeout = timeout;
},
SET_EXPLOREBAR_SIZE (state, size) { SET_EXPLOREBAR_SIZE (state, size) {
state.explorebar_size = size; state.explorebar_size = size;
} }
@ -25,6 +30,9 @@ export default {
changeLocale ({ commit }, locale) { changeLocale ({ commit }, locale) {
commit('SET_LOCALE', locale); commit('SET_LOCALE', locale);
}, },
updateNotificationsTimeout ({ commit }, timeout) {
commit('SET_NOTIFICATIONS_TIMEOUT', timeout);
},
changeExplorebarSize ({ commit }, size) { changeExplorebarSize ({ commit }, size) {
commit('SET_EXPLOREBAR_SIZE', size); commit('SET_EXPLOREBAR_SIZE', size);
} }