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:
parent
acd3310228
commit
413b56916c
src
common/libs
main
renderer
19
src/common/libs/sqlEscaper.js
Normal file
19
src/common/libs/sqlEscaper.js
Normal 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 };
|
@ -64,12 +64,12 @@ async function createMainWindow () {
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||
|
@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
export default class {
|
||||
static async getTableData (connection, schema, table) {
|
||||
return connection
|
||||
@ -9,9 +10,29 @@ export default class {
|
||||
.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
|
||||
.update({ [params.field]: `= "${params.content}"` })
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${params.id}` })
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="modal modal-sm active">
|
||||
<div class="modal active" :class="modalSizeClass">
|
||||
<a class="modal-overlay" @click="hideModal" />
|
||||
<div class="modal-container">
|
||||
<div v-if="hasHeader" class="modal-header">
|
||||
@ -45,6 +45,12 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseConfirmModal',
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
default: 'small' // small, medium, large
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasHeader () {
|
||||
return !!this.$slots.header;
|
||||
@ -54,6 +60,13 @@ export default {
|
||||
},
|
||||
hasDefault () {
|
||||
return !!this.$slots.default;
|
||||
},
|
||||
modalSizeClass () {
|
||||
if (this.size === 'small')
|
||||
return 'modal-sm';
|
||||
else if (this.size === 'large')
|
||||
return 'modal-lg';
|
||||
else return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -45,8 +45,8 @@
|
||||
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
||||
<form class="form-horizontal">
|
||||
<div class="col-6 col-sm-12">
|
||||
<div class="form-group">
|
||||
<div class="col-8 col-sm-12">
|
||||
<div class="form-group mb-4">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
<i class="material-icons md-18 mr-1">translate</i>
|
||||
@ -69,6 +69,25 @@
|
||||
</select>
|
||||
</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>
|
||||
</form>
|
||||
</div>
|
||||
@ -115,6 +134,7 @@ export default {
|
||||
return {
|
||||
isUpdate: false,
|
||||
localLocale: null,
|
||||
localTimeout: null,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
@ -123,7 +143,8 @@ export default {
|
||||
appName: 'application/appName',
|
||||
appVersion: 'application/appVersion',
|
||||
selectedSettingTab: 'application/selectedSettingTab',
|
||||
selectedLocale: 'settings/getLocale'
|
||||
selectedLocale: 'settings/getLocale',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout'
|
||||
}),
|
||||
locales () {
|
||||
const locales = [];
|
||||
@ -135,18 +156,26 @@ export default {
|
||||
},
|
||||
created () {
|
||||
this.localLocale = this.selectedLocale;
|
||||
this.localTimeout = this.notificationsTimeout;
|
||||
this.selectedTab = this.selectedSettingTab;
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
closeModal: 'application/hideSettingModal',
|
||||
changeLocale: 'settings/changeLocale'
|
||||
changeLocale: 'settings/changeLocale',
|
||||
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
|
||||
}),
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
},
|
||||
checkNotificationsTimeout () {
|
||||
if (!this.localTimeout)
|
||||
this.localTimeout = 1;
|
||||
|
||||
this.updateNotificationsTimeout(+this.localTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div id="notifications-board">
|
||||
<div
|
||||
id="notifications-board"
|
||||
@mouseenter="clearTimeouts"
|
||||
@mouseleave="rearmTimeouts"
|
||||
>
|
||||
<transition-group name="slide-fade">
|
||||
<BaseNotification
|
||||
v-for="notification in latestNotifications"
|
||||
@ -21,18 +25,51 @@ export default {
|
||||
components: {
|
||||
BaseNotification
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
timeouts: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
notifications: 'notifications/getNotifications'
|
||||
notifications: 'notifications/getNotifications',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout'
|
||||
}),
|
||||
latestNotifications () {
|
||||
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: {
|
||||
...mapActions({
|
||||
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>
|
||||
|
@ -2,15 +2,14 @@
|
||||
<div
|
||||
v-if="field !== '_id'"
|
||||
ref="cell"
|
||||
class="td"
|
||||
:class="`type-${type} p-0`"
|
||||
class="td p-0"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="$emit('contextmenu', $event)"
|
||||
>
|
||||
<span
|
||||
v-if="!isEditing"
|
||||
v-if="!isInlineEditor"
|
||||
class="cell-content px-2"
|
||||
:class="isNull(content)"
|
||||
:class="`${isNull(content)} type-${type}`"
|
||||
@dblclick="editON"
|
||||
>{{ content | typeFormat(type, precision) | cutText }}</span>
|
||||
<template v-else>
|
||||
@ -34,6 +33,29 @@
|
||||
@blur="editOFF"
|
||||
>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -42,9 +64,13 @@ import moment from 'moment';
|
||||
import { mimeFromHex, formatBytes } from 'common/libs/utilities';
|
||||
import hexToBinary from 'common/libs/hexToBinary';
|
||||
import { mask } from 'vue-the-mask';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceQueryTableCell',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
filters: {
|
||||
cutText (val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
@ -99,8 +125,9 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isEditing: false,
|
||||
localContent: ''
|
||||
isInlineEditor: false,
|
||||
isTextareaEditor: false,
|
||||
localContent: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -110,11 +137,13 @@ export default {
|
||||
case 'varchar':
|
||||
case 'text':
|
||||
case 'mediumtext':
|
||||
case 'longtext':
|
||||
return { type: 'text', mask: false };
|
||||
case 'int':
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
case 'mediumint':
|
||||
case 'bigint':
|
||||
return { type: 'number', mask: false };
|
||||
case 'date':
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
@ -141,21 +170,34 @@ export default {
|
||||
},
|
||||
editON () {
|
||||
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.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 () {
|
||||
this.isEditing = false;
|
||||
if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return;
|
||||
this.isInlineEditor = false;
|
||||
if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return;// If not changed
|
||||
|
||||
const { field, type, localContent: content } = this;
|
||||
this.$emit('updateField', { field, type, content });
|
||||
},
|
||||
hideEditorModal () {
|
||||
this.isTextareaEditor = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -176,4 +218,8 @@ export default {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.textarea-editor{
|
||||
height: 50vh!important;
|
||||
}
|
||||
</style>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isConfirmModal"
|
||||
@confirm="deleteRows()"
|
||||
@confirm="deleteRows"
|
||||
@hide="hideConfirmModal"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
|
@ -29,7 +29,9 @@ module.exports = {
|
||||
donate: 'Donate',
|
||||
run: 'Run',
|
||||
schema: 'Schema',
|
||||
results: 'Results'
|
||||
results: 'Results',
|
||||
size: 'Size',
|
||||
seconds: 'Seconds'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@ -55,7 +57,8 @@ module.exports = {
|
||||
unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset',
|
||||
editCell: 'Edit cell',
|
||||
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
|
||||
short: {
|
||||
|
@ -127,6 +127,10 @@ body {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
.input-group .input-group-addon{
|
||||
border-color: #3f3f3f;
|
||||
}
|
||||
|
||||
.menu {
|
||||
font-size: 0.7rem;
|
||||
.menu-item {
|
||||
|
@ -6,17 +6,22 @@ export default {
|
||||
strict: true,
|
||||
state: {
|
||||
locale: 'en-US',
|
||||
explorebar_size: null
|
||||
explorebar_size: null,
|
||||
notifications_timeout: 10
|
||||
},
|
||||
getters: {
|
||||
getLocale: state => state.locale,
|
||||
getExplorebarSize: state => state.explorebar_size
|
||||
getExplorebarSize: state => state.explorebar_size,
|
||||
getNotificationsTimeout: state => state.notifications_timeout
|
||||
},
|
||||
mutations: {
|
||||
SET_LOCALE (state, locale) {
|
||||
state.locale = locale;
|
||||
i18n.locale = locale;
|
||||
},
|
||||
SET_NOTIFICATIONS_TIMEOUT (state, timeout) {
|
||||
state.notifications_timeout = timeout;
|
||||
},
|
||||
SET_EXPLOREBAR_SIZE (state, size) {
|
||||
state.explorebar_size = size;
|
||||
}
|
||||
@ -25,6 +30,9 @@ export default {
|
||||
changeLocale ({ commit }, locale) {
|
||||
commit('SET_LOCALE', locale);
|
||||
},
|
||||
updateNotificationsTimeout ({ commit }, timeout) {
|
||||
commit('SET_NOTIFICATIONS_TIMEOUT', timeout);
|
||||
},
|
||||
changeExplorebarSize ({ commit }, size) {
|
||||
commit('SET_EXPLOREBAR_SIZE', size);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user