feat: initial console implementation

This commit is contained in:
Fabio Di Stasio 2022-07-16 12:01:37 +02:00
parent f12a04b052
commit 6a6f43a718
14 changed files with 278 additions and 20 deletions

View File

@ -25,7 +25,7 @@ export interface IpcResponse<T = any> {
*/
export interface ClientParams {
client: ClientCode;
uid: string;
uid?: string;
params:
mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
| pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}

View File

@ -29,6 +29,11 @@ const shortcuts: ShortcutRecord[] = [
event: 'open-connections-modal',
keys: ['Shift+CommandOrControl+Space'],
description: 'Show all connections'
},
{
event: 'toggle-console',
keys: ['CommandOrControl+F12', 'CommandOrControl+`'],
description: 'Toggle console'
}
];

View File

@ -8,7 +8,7 @@ const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
// Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
const mainWindow = webContents.fromId(1);
mainWindow.send('query-log', { cUid, sql: escapedSql });
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
console.log(escapedSql);
};

View File

@ -155,9 +155,11 @@ else {
}
}
// Main process shortcuts
if (isDevelopment) {
globalShortcut.register('CommandOrControl+F12', () => {
if (isDevelopment) { // Dev shortcuts
globalShortcut.register('Shift+CommandOrControl+F5', () => {
mainWindow.reload();
});
globalShortcut.register('Shift+CommandOrControl+F12', () => {
mainWindow.webContents.openDevTools();
});
}

View File

@ -71,10 +71,6 @@ ipcRenderer.on('open-connections-modal', () => {
isAllConnectionsModal.value = true;
});
ipcRenderer.on('query-log', (e, sql: string) => {
console.log(sql);
});
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
@ -155,10 +151,10 @@ onMounted(() => {
.connection-panel-wrapper {
height: calc(100vh - #{$excluding-size});
width: 100%;
padding-top: 15vh;
padding-top: 2rem;
display: flex;
justify-content: center;
align-items: flex-start;
align-items: center;
overflow: auto;
}
}

View File

@ -11,14 +11,30 @@
<div class="footer-right-elements">
<ul class="footer-elements">
<li
v-if="workspace !== 'NEW'"
class="footer-element footer-link"
@click="toggleConsole"
>
<i class="mdi mdi-18px mdi-console-line mr-1" />
<small>{{ t('word.console') }}</small>
</li>
<li class="footer-element footer-link" @click="openOutside('https://www.paypal.com/paypalme/fabiodistasio')">
<i class="mdi mdi-18px mdi-coffee mr-1" />
<small>{{ $t('word.donate') }}</small>
<small>{{ t('word.donate') }}</small>
</li>
<li class="footer-element footer-link" @click="openOutside('https://github.com/antares-sql/antares/issues')">
<li
class="footer-element footer-link"
:title="t('message.reportABug')"
@click="openOutside('https://github.com/antares-sql/antares/issues')"
>
<i class="mdi mdi-18px mdi-bug" />
</li>
<li class="footer-element footer-link" @click="showSettingModal('about')">
<li
class="footer-element footer-link"
:title="t('word.about')"
@click="showSettingModal('about')"
>
<i class="mdi mdi-18px mdi-information-outline" />
</li>
</ul>
@ -29,9 +45,13 @@
<script setup lang="ts">
import { shell } from 'electron';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useApplicationStore } from '@/stores/application';
import { useWorkspacesStore } from '@/stores/workspaces';
import { computed, ComputedRef } from 'vue';
import { useConsoleStore } from '@/stores/console';
const { t } = useI18n();
interface DatabaseInfos {// TODO: temp
name: string;
@ -44,6 +64,7 @@ const applicationStore = useApplicationStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: workspace } = storeToRefs(workspacesStore);
const { toggleConsole } = useConsoleStore();
const { showSettingModal } = applicationStore;
const { getWorkspace } = workspacesStore;

View File

@ -451,9 +451,11 @@
:schema="tab.schema"
/>
</template>
<WorkspaceQueryConsole v-if="isConsoleOpen" :uid="workspace.uid" />
</div>
<div v-else class="connection-panel-wrapper">
<div v-else class="connection-panel-wrapper p-relative">
<WorkspaceEditConnectionPanel :connection="connection" />
<WorkspaceQueryConsole v-if="isConsoleOpen" :uid="workspace.uid" />
</div>
<ModalProcessesList
v-if="isProcessesModal"
@ -476,6 +478,7 @@ import { storeToRefs } from 'pinia';
import * as Draggable from 'vuedraggable';
import Connection from '@/ipc-api/Connection';
import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console';
import { ConnectionParams } from 'common/interfaces/antares';
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState.vue';
@ -483,6 +486,7 @@ import WorkspaceExploreBar from '@/components/WorkspaceExploreBar.vue';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel.vue';
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery.vue';
import WorkspaceTabTable from '@/components/WorkspaceTabTable.vue';
import WorkspaceQueryConsole from '@/components/WorkspaceQueryConsole.vue';
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable.vue';
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView.vue';
@ -518,6 +522,8 @@ const {
selectPrevTab
} = workspacesStore;
const { isConsoleOpen } = storeToRefs(useConsoleStore());
const props = defineProps({
connection: Object as Prop<ConnectionParams>
});
@ -680,8 +686,9 @@ onMounted(() => {
margin: 0;
.workspace-tabs {
overflow: hidden;
overflow-y: hidden;
height: calc(100vh - #{$excluding-size});
position: relative;
.tab-block {
margin-top: 0;

View File

@ -0,0 +1,147 @@
<template>
<div
ref="wrapper"
class="query-console-wrapper"
@mouseenter="isHover = true"
@mouseleave="isHover = false"
>
<div ref="resizer" class="query-console-resizer" />
<div
id="query-console"
ref="queryConsole"
class="query-console column col-12"
:style="{height: localHeight ? localHeight+'px' : ''}"
>
<div class="query-console-header">
<div>{{ t('word.console') }}</div>
<i class="mdi mdi-close c-hand" @click="resizeConsole(0)" />
</div>
<div ref="queryConsoleBody" class="query-console-body">
<div
v-for="(wLog, i) in workspaceLogs"
:key="i"
class="query-console-log"
>
<span class="type-datetime">{{ moment(wLog.date).format('YYYY-MM-DD HH:mm:ss') }}</span>: <span class="type-string">{{ wLog.sql }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, Ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import * as moment from 'moment';
import { useConsoleStore } from '@/stores/console';
import { storeToRefs } from 'pinia';
const { t } = useI18n();
const consoleStore = useConsoleStore();
const { resizeConsole, getLogsByWorkspace } = consoleStore;
const { consoleHeight } = storeToRefs(consoleStore);
const props = defineProps({
uid: String
});
const wrapper: Ref<HTMLInputElement> = ref(null);
const queryConsole: Ref<HTMLInputElement> = ref(null);
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
const resizer: Ref<HTMLInputElement> = ref(null);
const localHeight = ref(250);
const isHover = ref(false);
const resize = (e: MouseEvent) => {
const el = queryConsole.value;
let consoleHeight = el.getBoundingClientRect().bottom - e.pageY;
if (consoleHeight > 400) consoleHeight = 400;
localHeight.value = consoleHeight;
};
const workspaceLogs = computed(() => {
return getLogsByWorkspace(props.uid);
});
const stopResize = () => {
if (localHeight.value < 0) localHeight.value = 0;
resizeConsole(localHeight.value);
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResize);
};
watch(workspaceLogs, async () => {
if (!isHover.value) {
await nextTick();
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
}
});
onMounted(() => {
localHeight.value = consoleHeight.value;
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
});
onMounted(() => {
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault();
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
});
});
</script>
<style lang="scss" scoped>
.query-console-wrapper{
width: 100%;
z-index: 9;
margin-top: auto;
position: absolute;
bottom: 0;
.query-console-resizer{
height: 4px;
top: -1px;
width: 100%;
cursor: ns-resize;
position: absolute;
z-index: 99;
transition: background 0.2s;
&:hover {
background: rgba($primary-color, 50%);
}
}
.query-console {
padding: 0;
padding-bottom: $footer-height;
.query-console-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px;
font-weight: 700;
}
.query-console-body {
overflow: auto;
display: flex;
flex-direction: column;
max-height: 100%;
padding: 0 6px;
.query-console-log {
padding: 1px 3px;
user-select: text;
border-radius: $border-radius;
&:hover {
background: $bg-color-gray;
}
}
}
}
}
</style>

View File

@ -372,6 +372,8 @@ const resize = (e: MouseEvent) => {
editorHeight.value = localEditorHeight;
};
const resizeResults = () => queryTable.value.resizeResults();
const onWindowResize = (e: MouseEvent) => {
const el = queryEditor.value.$el;
const queryFooterHeight = queryAreaFooter.value.clientHeight;
@ -386,7 +388,7 @@ const onWindowResize = (e: MouseEvent) => {
const stopResize = () => {
window.removeEventListener('mousemove', resize);
if (queryTable.value && results.value.length)
queryTable.value.resizeResults();
resizeResults();
if (queryEditor.value)
queryEditor.value.editor.resize();
@ -476,6 +478,8 @@ const rollbackTab = async () => {
isQuering.value = false;
};
defineExpose({ resizeResults });
query.value = props.tab.content as string;
selectedSchema.value = props.tab.schema || breadcrumbsSchema.value;

View File

@ -118,6 +118,7 @@ import { storeToRefs } from 'pinia';
import { uidGen } from 'common/libs/uidGen';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console';
import { arrayToFile } from '../libs/arrayToFile';
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
@ -132,10 +133,13 @@ import { TableUpdateParams } from 'common/interfaces/tableApis';
const { t } = useI18n();
const settingsStore = useSettingsStore();
const consoleStore = useConsoleStore();
const { getWorkspace } = useWorkspacesStore();
const { dataTabLimit: pageSize } = storeToRefs(settingsStore);
const { consoleHeight } = storeToRefs(consoleStore);
const props = defineProps({
results: Array as Prop<QueryResult[]>,
connUid: String,
@ -298,8 +302,13 @@ const resizeResults = () => {
const el = tableWrapper.value;
if (el) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer');
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - el.getBoundingClientRect().top - sizeToSubtract;
resultsSize.value = size;
}
resultTable.value.updateWindow();
@ -664,6 +673,10 @@ watch(() => props.isSelected, async (val) => {
}
});
watch(consoleHeight, () => {
resizeResults();
});
onUpdated(() => {
if (table.value)
refreshScroller();

View File

@ -141,7 +141,8 @@ module.exports = {
connectionString: 'Connection string',
contributors: 'Contributors',
pin: 'Pin',
unpin: 'Unpin'
unpin: 'Unpin',
console: 'Console'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@ -296,7 +297,8 @@ module.exports = {
disableFKChecks: 'Disable foreigh key checks',
allConnections: 'All connections',
searchForConnections: 'Search for connections',
disableScratchpad: 'Disable scratchpad'
disableScratchpad: 'Disable scratchpad',
reportABug: 'Report a bug'
},
faker: {
address: 'Address',

View File

@ -10,6 +10,7 @@ import { VueMaskDirective } from 'v-mask';
import { useApplicationStore } from '@/stores/application';
import { useSettingsStore } from '@/stores/settings';
import { useNotificationsStore } from '@/stores/notifications';
import { useConsoleStore } from '@/stores/console';
import App from '@/App.vue';
import i18n from '@/i18n';
@ -36,6 +37,11 @@ ipcRenderer.on('unhandled-exception', (event, error) => {
useNotificationsStore().addNotification({ status: 'error', message: error.message });
});
// IPC query logs
ipcRenderer.on('query-log', (event, logRecord) => {
useConsoleStore().putLog(logRecord);
});
// IPC app updates
ipcRenderer.on('checking-for-update', () => {
useApplicationStore().updateStatus = 'checking';

View File

@ -305,6 +305,11 @@
}
}
.query-console {
border-top: 1px solid #444;
background-color: $bg-color-dark;
}
.tile {
transition: background 0.2s;

View File

@ -0,0 +1,50 @@
import { ipcRenderer } from 'electron';
import { defineStore } from 'pinia';
const logsSize = 1000;
export interface ConsoleRecord {
cUid: string;
sql: string;
date: Date;
}
export const useConsoleStore = defineStore('console', {
state: () => ({
records: [] as ConsoleRecord[],
consoleHeight: 0,
isConsoleOpen: false
}),
getters: {
getLogsByWorkspace: state => (uid: string) => state.records.filter(r => r.cUid === uid)
},
actions: {
putLog (record: ConsoleRecord) {
this.records.push(record);
if (this.records.length > logsSize)
this.records = this.records.slice(0, logsSize);
},
resizeConsole (height: number) {
if (height < 30) {
this.consoleHeight = 0;
this.isConsoleOpen = false;
}
else
this.consoleHeight = height;
},
toggleConsole () {
if (this.isConsoleOpen) {
this.isConsoleOpen = false;
this.consoleHeight = 0;
}
else {
this.isConsoleOpen = true;
this.consoleHeight = 250;
}
}
}
});
ipcRenderer.on('toggle-console', () => {
useConsoleStore().toggleConsole();
});