mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Merge pull request #333 from antares-sql/new-connection-management
New connections management
This commit is contained in:
94
package-lock.json
generated
94
package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@mdi/font": "~6.1.95",
|
||||
"@turf/helpers": "~6.5.0",
|
||||
"@vscode/vscode-languagedetection": "~1.0.21",
|
||||
"@vueuse/core": "~8.7.5",
|
||||
"ace-builds": "~1.4.13",
|
||||
"better-sqlite3": "~7.5.0",
|
||||
"electron-log": "~4.4.1",
|
||||
@@ -2872,6 +2873,11 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
|
||||
"integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||
@@ -3205,6 +3211,63 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.33.tgz",
|
||||
"integrity": "sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg=="
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.7.5.tgz",
|
||||
"integrity": "sha512-tqgzeZGoZcXzoit4kOGLWJibDMLp0vdm6ZO41SSUQhkhtrPhAg6dbIEPiahhUu6sZAmSYvVrZgEr5aKD51nrLA==",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.14",
|
||||
"@vueuse/metadata": "8.7.5",
|
||||
"@vueuse/shared": "8.7.5",
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.1.0",
|
||||
"vue": "^2.6.0 || ^3.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"vue": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/core/node_modules/@vueuse/shared": {
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.7.5.tgz",
|
||||
"integrity": "sha512-THXPvMBFmg6Gf6AwRn/EdTh2mhqwjGsB2Yfp374LNQSQVKRHtnJ0I42bsZTn7nuEliBxqUrGQm/lN6qUHmhJLw==",
|
||||
"dependencies": {
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.1.0",
|
||||
"vue": "^2.6.0 || ^3.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"vue": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.7.5.tgz",
|
||||
"integrity": "sha512-emJZKRQSaEnVqmlu39NpNp8iaW+bPC2kWykWoWOZMSlO/0QVEmO/rt8A5VhOEJTKLX3vwTevqbiRy9WJRwVOQg==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||
@@ -19790,6 +19853,11 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@types/web-bluetooth": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
|
||||
"integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||
@@ -20029,6 +20097,32 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.33.tgz",
|
||||
"integrity": "sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg=="
|
||||
},
|
||||
"@vueuse/core": {
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.7.5.tgz",
|
||||
"integrity": "sha512-tqgzeZGoZcXzoit4kOGLWJibDMLp0vdm6ZO41SSUQhkhtrPhAg6dbIEPiahhUu6sZAmSYvVrZgEr5aKD51nrLA==",
|
||||
"requires": {
|
||||
"@types/web-bluetooth": "^0.0.14",
|
||||
"@vueuse/metadata": "8.7.5",
|
||||
"@vueuse/shared": "8.7.5",
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/shared": {
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.7.5.tgz",
|
||||
"integrity": "sha512-THXPvMBFmg6Gf6AwRn/EdTh2mhqwjGsB2Yfp374LNQSQVKRHtnJ0I42bsZTn7nuEliBxqUrGQm/lN6qUHmhJLw==",
|
||||
"requires": {
|
||||
"vue-demi": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vueuse/metadata": {
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.7.5.tgz",
|
||||
"integrity": "sha512-emJZKRQSaEnVqmlu39NpNp8iaW+bPC2kWykWoWOZMSlO/0QVEmO/rt8A5VhOEJTKLX3vwTevqbiRy9WJRwVOQg=="
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||
|
@@ -120,6 +120,7 @@
|
||||
"@mdi/font": "~6.1.95",
|
||||
"@turf/helpers": "~6.5.0",
|
||||
"@vscode/vscode-languagedetection": "~1.0.21",
|
||||
"@vueuse/core": "~8.7.5",
|
||||
"ace-builds": "~1.4.13",
|
||||
"better-sqlite3": "~7.5.0",
|
||||
"electron-log": "~4.4.1",
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
|
||||
<TheTitleBar />
|
||||
<div id="window-content">
|
||||
<TheSettingBar />
|
||||
<TheSettingBar @show-connections-modal="isAllConnectionsModal = true" />
|
||||
<div id="main-content" class="container">
|
||||
<div class="columns col-gapless">
|
||||
<Workspace
|
||||
@@ -21,13 +21,15 @@
|
||||
<BaseTextEditor class="d-none" value="" />
|
||||
</div>
|
||||
</div>
|
||||
<ModalAllConnections v-if="isAllConnectionsModal" @close="isAllConnectionsModal = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Menu, getCurrentWindow } from '@electron/remote';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
@@ -35,98 +37,99 @@ import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import TheSettingBar from '@/components/TheSettingBar.vue';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
TheTitleBar: defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue')),
|
||||
TheSettingBar,
|
||||
TheFooter: defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue')),
|
||||
TheNotificationsBoard: defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue')),
|
||||
Workspace: defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue')),
|
||||
WorkspaceAddConnectionPanel: defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue')),
|
||||
ModalSettings: defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue')),
|
||||
TheScratchpad: defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue')),
|
||||
BaseTextEditor: defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'))
|
||||
},
|
||||
setup () {
|
||||
const applicationStore = useApplicationStore();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
isSettingModal,
|
||||
isScratchpad
|
||||
} = storeToRefs(applicationStore);
|
||||
const { connections } = storeToRefs(connectionsStore);
|
||||
const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
|
||||
const TheFooter = defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue'));
|
||||
const TheNotificationsBoard = defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue'));
|
||||
const Workspace = defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue'));
|
||||
const WorkspaceAddConnectionPanel = defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue'));
|
||||
const ModalSettings = defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue'));
|
||||
const ModalAllConnections = defineAsyncComponent(() => import(/* webpackChunkName: "ModalAllConnections" */'@/components/ModalAllConnections.vue'));
|
||||
const TheScratchpad = defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue'));
|
||||
const BaseTextEditor = defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'));
|
||||
|
||||
const { checkVersionUpdate } = applicationStore;
|
||||
const { changeApplicationTheme } = settingsStore;
|
||||
const applicationStore = useApplicationStore();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
|
||||
}, 1000);
|
||||
});
|
||||
const {
|
||||
isSettingModal,
|
||||
isScratchpad
|
||||
} = storeToRefs(applicationStore);
|
||||
const { connections } = storeToRefs(connectionsStore);
|
||||
const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isSettingModal,
|
||||
isScratchpad,
|
||||
checkVersionUpdate,
|
||||
changeApplicationTheme,
|
||||
connections,
|
||||
applicationTheme,
|
||||
disableBlur,
|
||||
selectedWorkspace
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
this.checkVersionUpdate();
|
||||
const { checkVersionUpdate } = applicationStore;
|
||||
const { changeApplicationTheme } = settingsStore;
|
||||
|
||||
const InputMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: this.$t('word.cut'),
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
label: this.$t('word.copy'),
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
label: this.$t('word.paste'),
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: this.$t('message.selectAll'),
|
||||
role: 'selectAll'
|
||||
}
|
||||
]);
|
||||
const isAllConnectionsModal: Ref<boolean> = ref(false);
|
||||
|
||||
document.body.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.code === 'Space') {
|
||||
isAllConnectionsModal.value = true;
|
||||
e.stopPropagation();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let node: any = e.target;
|
||||
|
||||
while (node) {
|
||||
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
|
||||
InputMenu.popup({ window: getCurrentWindow() });
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
window.addEventListener('keypress', onKey);
|
||||
|
||||
onMounted(() => {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
checkVersionUpdate();
|
||||
|
||||
const InputMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: t('word.cut'),
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
label: t('word.copy'),
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
label: t('word.paste'),
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: t('message.selectAll'),
|
||||
role: 'selectAll'
|
||||
}
|
||||
]);
|
||||
|
||||
document.body.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let node: any = e.target;
|
||||
|
||||
while (node) {
|
||||
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
|
||||
InputMenu.popup({ window: getCurrentWindow() });
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
353
src/renderer/components/ModalAllConnections.vue
Normal file
353
src/renderer/components/ModalAllConnections.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<Teleport to="#window-content">
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div ref="trapRef" class="modal-container p-0 pb-4">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-apps mr-1" />
|
||||
<span class="cut-text">{{ $t('message.allConnections') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body py-0">
|
||||
<div class="columns">
|
||||
<div class="connections-search column col-12 columns col-gapless">
|
||||
<div class="column col-12 mt-2">
|
||||
<div ref="searchForm" class="form-group has-icon-right p-2 m-0">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="t('message.searchForConnections')"
|
||||
@keypress.esc="searchTerm = ''"
|
||||
>
|
||||
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
|
||||
<i
|
||||
v-else
|
||||
class="form-icon c-hand mdi mdi-backspace mdi-18px pr-4"
|
||||
@click="searchTerm = ''"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TransitionGroup name="fade" :duration="{ enter: 200, leave: 200 }">
|
||||
<div
|
||||
v-for="connection in filteredConnections"
|
||||
:key="connection.uid"
|
||||
class="connection-block column col-md-6 col-lg-4 col-3 p-3"
|
||||
tabindex="0"
|
||||
@click.stop="selectConnection(connection.uid)"
|
||||
@keypress.stop.enter="selectConnection(connection.uid)"
|
||||
@mouseover="connectionHover = connection.uid"
|
||||
@mouseleave="connectionHover = null"
|
||||
>
|
||||
<div class="panel">
|
||||
<div class="panel-header p-2 text-center p-relative">
|
||||
<figure class="avatar avatar-lg pt-1 mb-1">
|
||||
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`]" />
|
||||
</figure>
|
||||
<div class="panel-title h6 text-ellipsis">
|
||||
{{ getConnectionName(connection.uid) }}
|
||||
</div>
|
||||
<div class="panel-subtitle">
|
||||
{{ clients.get(connection.client) || connection.client }}
|
||||
</div>
|
||||
<div class="all-connections-buttons p-absolute d-flex" style="top: 0; right: 0;">
|
||||
<i
|
||||
v-if="connection.isPinned"
|
||||
class="all-connections-pinned mdi mdi-18px"
|
||||
:class="connectionHover === connection.uid ? 'mdi-pin-off' : 'mdi-pin'"
|
||||
:title="t('word.unpin')"
|
||||
@click.stop="unpinConnection(connection.uid)"
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="all-connections-pin mdi mdi-18px mdi-pin mdi-rotate-45"
|
||||
:title="t('word.pin')"
|
||||
@click.stop="pinConnection(connection.uid)"
|
||||
/>
|
||||
<i
|
||||
class="all-connections-delete mdi mdi-delete mdi-18px ml-2"
|
||||
:title="t('word.delete')"
|
||||
@click.stop="askToDelete(connection)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body text-center">
|
||||
<div v-if="connection.databasePath">
|
||||
<div class="text-ellipsis" :title="connection.databasePath">
|
||||
<i class="mdi mdi-database d-inline" /> <span class="text-bold">{{
|
||||
connection.databasePath
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="text-ellipsis" :title="`${connection.host}:${connection.port}`">
|
||||
<i class="mdi mdi-server d-inline" /> <span class="text-bold">{{ connection.host
|
||||
}}:{{ connection.port }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="connection.user">
|
||||
<div class="text-ellipsis">
|
||||
<i class="mdi mdi-account d-inline" /> <span class="text-bold">{{ connection.user
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="connection.schema">
|
||||
<div class="text-ellipsis">
|
||||
<i class="mdi mdi-database d-inline" /> <span class="text-bold">{{ connection.schema
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="connection.database">
|
||||
<div class="text-ellipsis">
|
||||
<i class="mdi mdi-database d-inline" /> <span class="text-bold">{{
|
||||
connection.database
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer text-center py-0">
|
||||
<div v-if="connection.ssl" class="chip bg-success mt-2">
|
||||
<i class="mdi mdi-lock mdi-18px mr-1" />
|
||||
SSL
|
||||
</div>
|
||||
<div v-if="connection.ssh" class="chip bg-success mt-2">
|
||||
<i class="mdi mdi-console-network mdi-18px mr-1" />
|
||||
SSH
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
key="trick"
|
||||
readonly
|
||||
class="p-absolute"
|
||||
style="width: 1px; height: 1px; opacity: 0"
|
||||
type="text"
|
||||
>
|
||||
<!-- workaround for useFocusTrap $lastFocusable -->
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isConfirmModal"
|
||||
@confirm="confirmDeleteConnection"
|
||||
@hide="isConfirmModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ t('message.deleteConnection') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="mb-2">
|
||||
{{ t('message.deleteCorfirm') }} <b>{{ selectedConnectionName }}</b>?
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmModal>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { connections,
|
||||
pinnedConnections,
|
||||
lastConnections
|
||||
} = storeToRefs(connectionsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
const {
|
||||
getConnectionName,
|
||||
pinConnection,
|
||||
unpinConnection,
|
||||
deleteConnection
|
||||
} = connectionsStore;
|
||||
const { selectWorkspace } = workspacesStore;
|
||||
|
||||
const { trapRef } = useFocusTrap();
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const clients = new Map([
|
||||
['mysql', 'MySQL'],
|
||||
['maria', 'MariaDB'],
|
||||
['pg', 'PostgreSQL'],
|
||||
['sqlite', 'SQLite']
|
||||
]);
|
||||
|
||||
const searchTerm = ref('');
|
||||
const isConfirmModal = ref(false);
|
||||
const connectionHover: Ref<string> = ref(null);
|
||||
const selectedConnection: Ref<ConnectionParams> = ref(null);
|
||||
|
||||
const sortedConnections = computed(() => {
|
||||
return connections.value
|
||||
.map(c => {
|
||||
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
|
||||
return {
|
||||
...c,
|
||||
time: connTime,
|
||||
isPinned: pinnedConnections.value.has(c.uid)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.isPinned < b.isPinned) return 1;
|
||||
if (a.isPinned > b.isPinned) return -1;
|
||||
if (a.time < b.time) return 1;
|
||||
if (a.time > b.time) return -1;
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
const filteredConnections = computed(() => {
|
||||
return sortedConnections.value.filter(connection => {
|
||||
return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.databasePath?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.schema?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.user?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
String(connection.port)?.includes(searchTerm.value);
|
||||
});
|
||||
});
|
||||
|
||||
const selectedConnectionName = computed(() => getConnectionName(selectedConnection.value?.uid));
|
||||
|
||||
const closeModal = () => emit('close');
|
||||
|
||||
const selectConnection = (uid: string) => {
|
||||
selectWorkspace(uid);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const askToDelete = (connection: ConnectionParams) => {
|
||||
selectedConnection.value = connection;
|
||||
isConfirmModal.value = true;
|
||||
};
|
||||
|
||||
const confirmDeleteConnection = () => {
|
||||
if (selectedWorkspace.value === selectedConnection.value.uid)
|
||||
selectWorkspace(null);
|
||||
deleteConnection(selectedConnection.value);
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape') {
|
||||
if ((e.target as HTMLInputElement).tagName === 'INPUT' && searchTerm.value.length > 0)
|
||||
searchTerm.value = '';
|
||||
else
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vscroll {
|
||||
height: 1000px;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
.column-resizable {
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
resize: horizontal;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.table-column-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sort-icon {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
|
||||
.modal {
|
||||
align-items: flex-start;
|
||||
|
||||
.modal-container {
|
||||
max-width: 75vw;
|
||||
margin-top: 10vh;
|
||||
|
||||
.modal-body {
|
||||
height: 80vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.connections-search {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.connection-block {
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&:hover {
|
||||
.all-connections-buttons {
|
||||
|
||||
.all-connections-delete,
|
||||
.all-connections-pinned,
|
||||
.all-connections-pin {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.all-connections-buttons {
|
||||
.all-connections-pinned {
|
||||
opacity: .3;
|
||||
transition: opacity .2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.all-connections-delete,
|
||||
.all-connections-pin {
|
||||
opacity: 0;
|
||||
transition: opacity .2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -126,6 +126,19 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group column col-12 mb-0">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ t('message.disableScratchpad') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleDisableScratchpad">
|
||||
<input type="checkbox" :checked="disableScratchpad">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group column col-12">
|
||||
<div class="col-5 col-sm-12">
|
||||
<label class="form-label">
|
||||
@@ -337,6 +350,7 @@ const {
|
||||
notificationsTimeout,
|
||||
restoreTabs,
|
||||
disableBlur,
|
||||
disableScratchpad,
|
||||
applicationTheme,
|
||||
editorTheme,
|
||||
editorFontSize
|
||||
@@ -349,6 +363,7 @@ const {
|
||||
changePageSize,
|
||||
changeRestoreTabs,
|
||||
changeDisableBlur,
|
||||
changeDisableScratchpad,
|
||||
changeAutoComplete,
|
||||
changeLineWrap,
|
||||
changeApplicationTheme,
|
||||
@@ -490,6 +505,10 @@ const toggleDisableBlur = () => {
|
||||
changeDisableBlur(!disableBlur.value);
|
||||
};
|
||||
|
||||
const toggleDisableScratchpad = () => {
|
||||
changeDisableScratchpad(!disableScratchpad.value);
|
||||
};
|
||||
|
||||
const toggleAutoComplete = () => {
|
||||
changeAutoComplete(!selectedAutoComplete.value);
|
||||
};
|
||||
|
@@ -3,6 +3,20 @@
|
||||
:context-event="contextEvent"
|
||||
@close-context="$emit('close-context')"
|
||||
>
|
||||
<div
|
||||
v-if="isPinned"
|
||||
class="context-element"
|
||||
@click="unpin"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-pin-off text-light pr-1" /> {{ $t('word.unpin') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="context-element"
|
||||
@click="pin"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-pin mdi-rotate-45 text-light pr-1" /> {{ $t('word.pin') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isConnected"
|
||||
class="context-element"
|
||||
@@ -46,11 +60,18 @@ import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
const connectionsStore = useConnectionsStore();
|
||||
|
||||
const {
|
||||
getConnectionName,
|
||||
addConnection,
|
||||
deleteConnection
|
||||
} = useConnectionsStore();
|
||||
deleteConnection,
|
||||
pinConnection,
|
||||
unpinConnection
|
||||
} = connectionsStore;
|
||||
|
||||
const { pinnedConnections } = storeToRefs(connectionsStore);
|
||||
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
@@ -71,6 +92,7 @@ const isConfirmModal = ref(false);
|
||||
|
||||
const connectionName = computed(() => getConnectionName(props.contextConnection.uid));
|
||||
const isConnected = computed(() => getWorkspace(props.contextConnection.uid).connectionStatus === 'connected');
|
||||
const isPinned = computed(() => pinnedConnections.value.has(props.contextConnection.uid));
|
||||
|
||||
const confirmDeleteConnection = () => {
|
||||
if (selectedWorkspace.value === props.contextConnection.uid)
|
||||
@@ -100,6 +122,16 @@ const hideConfirmModal = () => {
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const pin = () => {
|
||||
pinConnection(props.contextConnection.uid);
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const unpin = () => {
|
||||
unpinConnection(props.contextConnection.uid);
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const disconnect = () => {
|
||||
disconnectWorkspace(props.contextConnection.uid);
|
||||
closeContext();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="settingbar">
|
||||
<div class="settingbar-top-elements">
|
||||
<div ref="sidebarConnections" class="settingbar-top-elements">
|
||||
<SettingBarContext
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
@@ -9,7 +9,7 @@
|
||||
/>
|
||||
<ul class="settingbar-elements">
|
||||
<Draggable
|
||||
v-model="connections"
|
||||
v-model="pinnedConnectionsArr"
|
||||
:item-key="'uid'"
|
||||
@start="isDragging = true"
|
||||
@end="dragStop"
|
||||
@@ -23,11 +23,40 @@
|
||||
@contextmenu.prevent="contextMenu($event, element)"
|
||||
@mouseover.self="tooltipPosition"
|
||||
>
|
||||
<i class="settingbar-element-icon dbi" :class="`dbi-${element.client} ${getStatusBadge(element.uid)}`" />
|
||||
<span v-if="!isDragging" class="ex-tooltip-content">{{ getConnectionName(element.uid) }}</span>
|
||||
<i class="settingbar-element-icon dbi" :class="[`dbi-${element.client}`, getStatusBadge(element.uid), (pinnedConnections.has(element.uid) ? 'settingbar-element-pin' : false)]" />
|
||||
<span v-if="!isDragging && !isScrolling" class="ex-tooltip-content">{{ getConnectionName(element.uid) }}</span>
|
||||
</li>
|
||||
</template>
|
||||
</Draggable>
|
||||
|
||||
<div v-if="pinnedConnectionsArr.length" class="divider" />
|
||||
|
||||
<li
|
||||
v-for="connection in unpinnedConnectionsArr"
|
||||
:key="connection.uid"
|
||||
class="settingbar-element btn btn-link ex-tooltip"
|
||||
:class="{'selected': connection.uid === selectedWorkspace}"
|
||||
@click.stop="selectWorkspace(connection.uid)"
|
||||
@contextmenu.prevent="contextMenu($event, connection)"
|
||||
@mouseover.self="tooltipPosition"
|
||||
>
|
||||
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`, getStatusBadge(connection.uid)]" />
|
||||
<span v-if="!isDragging && !isScrolling" class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="settingbar-middle-elements">
|
||||
<ul class="settingbar-elements">
|
||||
<li
|
||||
v-if="isScrollable"
|
||||
class="settingbar-element btn btn-link ex-tooltip"
|
||||
@click="emit('show-connections-modal')"
|
||||
@mouseover.self="tooltipPosition"
|
||||
>
|
||||
<i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" />
|
||||
<span class="ex-tooltip-content">{{ $t('message.allConnections') }} (CTRL+Space)</span>
|
||||
</li>
|
||||
<li
|
||||
class="settingbar-element btn btn-link ex-tooltip"
|
||||
:class="{'selected': 'NEW' === selectedWorkspace}"
|
||||
@@ -42,7 +71,11 @@
|
||||
|
||||
<div class="settingbar-bottom-elements">
|
||||
<ul class="settingbar-elements">
|
||||
<li class="settingbar-element btn btn-link ex-tooltip" @click="showScratchpad">
|
||||
<li
|
||||
v-if="!disableScratchpad"
|
||||
class="settingbar-element btn btn-link ex-tooltip"
|
||||
@click="showScratchpad"
|
||||
>
|
||||
<i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" />
|
||||
<span class="ex-tooltip-content">{{ $t('word.scratchpad') }}</span>
|
||||
</li>
|
||||
@@ -56,42 +89,70 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, Ref, computed } from 'vue';
|
||||
import { ref, Ref, computed, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import * as Draggable from 'vuedraggable';
|
||||
import SettingBarContext from '@/components/SettingBarContext.vue';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
import { useElementBounding, useScroll } from '@vueuse/core';
|
||||
|
||||
const applicationStore = useApplicationStore();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { updateStatus } = storeToRefs(applicationStore);
|
||||
const { connections: getConnections } = storeToRefs(connectionsStore);
|
||||
const { connections: storedConnections, pinnedConnections, lastConnections } = storeToRefs(connectionsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
const { disableScratchpad } = storeToRefs(settingsStore);
|
||||
|
||||
const { showSettingModal, showScratchpad } = applicationStore;
|
||||
const { getConnectionName, updateConnections } = connectionsStore;
|
||||
const { getConnectionName, updatePinnedConnections } = connectionsStore;
|
||||
const { getWorkspace, selectWorkspace } = workspacesStore;
|
||||
|
||||
const emit = defineEmits(['show-connections-modal']);
|
||||
|
||||
const isLinux = process.platform === 'linux';
|
||||
|
||||
const sidebarConnections: Ref<HTMLDivElement> = ref(null);
|
||||
const isContext: Ref<boolean> = ref(false);
|
||||
const isDragging: Ref<boolean> = ref(false);
|
||||
const isScrollable: Ref<boolean> = ref(false);
|
||||
const isScrolling = ref(useScroll(sidebarConnections)?.isScrolling);
|
||||
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||
const contextConnection: Ref<ConnectionParams> = ref(null);
|
||||
const sidebarConnectionsHeight = ref(useElementBounding(sidebarConnections)?.height);
|
||||
|
||||
const connections = computed({
|
||||
get () {
|
||||
return getConnections.value;
|
||||
},
|
||||
set (value: ConnectionParams[]) {
|
||||
updateConnections(value);
|
||||
const pinnedConnectionsArr = computed({
|
||||
get: () => [...pinnedConnections.value].map(c => storedConnections.value.find(sc => sc.uid === c)).filter(Boolean),
|
||||
set: (value: ConnectionParams[]) => {
|
||||
const pinnedUid = value.reduce((acc, curr) => {
|
||||
acc.push(curr.uid);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
updatePinnedConnections(pinnedUid);
|
||||
}
|
||||
});
|
||||
|
||||
const unpinnedConnectionsArr = computed(() => {
|
||||
return storedConnections.value
|
||||
.filter(c => !pinnedConnections.value.has(c.uid))
|
||||
.map(c => {
|
||||
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
|
||||
return { ...c, time: connTime };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.time < b.time) return 1;
|
||||
else if (a.time > b.time) return -1;
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
|
||||
|
||||
const contextMenu = (event: MouseEvent, connection: ConnectionParams) => {
|
||||
@@ -101,11 +162,14 @@ const contextMenu = (event: MouseEvent, connection: ConnectionParams) => {
|
||||
};
|
||||
|
||||
const tooltipPosition = (e: Event) => {
|
||||
const el = e.target ? e.target : e;
|
||||
const fromTop = isLinux
|
||||
? window.scrollY + (el as HTMLElement).getBoundingClientRect().top + ((el as HTMLElement).offsetHeight / 4)
|
||||
: window.scrollY + (el as HTMLElement).getBoundingClientRect().top - ((el as HTMLElement).offsetHeight / 4);
|
||||
(el as HTMLElement).querySelector<HTMLElement>('.ex-tooltip-content').style.top = `${fromTop}px`;
|
||||
const el = (e.target ? e.target : e) as unknown as HTMLElement;
|
||||
const tooltip = el.querySelector<HTMLElement>('.ex-tooltip-content');
|
||||
if (tooltip) {
|
||||
const fromTop = isLinux
|
||||
? window.scrollY + el.getBoundingClientRect().top + (el.offsetHeight / 4)
|
||||
: window.scrollY + el.getBoundingClientRect().top - (el.offsetHeight / 4);
|
||||
tooltip.style.top = `${fromTop}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (uid: string) => {
|
||||
@@ -126,13 +190,50 @@ const getStatusBadge = (uid: string) => {
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const dragStop = (e: any) => { // TODO: temp
|
||||
const dragStop = (e: any) => {
|
||||
isDragging.value = false;
|
||||
|
||||
setTimeout(() => {
|
||||
tooltipPosition(e.originalEvent.target.parentNode);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
watch(sidebarConnectionsHeight, (value) => {
|
||||
isScrollable.value = value < sidebarConnections.value.scrollHeight;
|
||||
});
|
||||
|
||||
watch(unpinnedConnectionsArr, (newVal, oldVal) => {
|
||||
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector<HTMLElement>('.settingbar-element.selected');
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const elemTop = rect.top;
|
||||
const elemBottom = rect.bottom;
|
||||
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
|
||||
|
||||
if (!isVisible) {
|
||||
element.setAttribute('tabindex', '-1');
|
||||
element.focus();
|
||||
element.removeAttribute('tabindex');
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
|
||||
watch(selectedWorkspace, (newVal, oldVal) => {
|
||||
if (newVal !== oldVal) {
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector<HTMLElement>('.settingbar-element.selected');
|
||||
if (element) {
|
||||
element.setAttribute('tabindex', '-1');
|
||||
element.focus();
|
||||
element.removeAttribute('tabindex');
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -141,7 +242,7 @@ const dragStop = (e: any) => { // TODO: temp
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
// justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
z-index: 9;
|
||||
@@ -149,7 +250,7 @@ const dragStop = (e: any) => { // TODO: temp
|
||||
.settingbar-top-elements {
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
// max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
@@ -157,8 +258,8 @@ const dragStop = (e: any) => { // TODO: temp
|
||||
}
|
||||
|
||||
.settingbar-bottom-elements {
|
||||
padding-top: 0.5rem;
|
||||
z-index: 1;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.settingbar-elements {
|
||||
@@ -214,6 +315,21 @@ const dragStop = (e: any) => { // TODO: temp
|
||||
bottom: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.settingbar-element-pin{
|
||||
margin: 0 auto;
|
||||
|
||||
&::before {
|
||||
font: normal normal normal 14px/1 "Material Design Icons";
|
||||
content: "\F0403";
|
||||
color: $body-font-color-dark;
|
||||
transform: rotate(45deg);
|
||||
opacity: .25;
|
||||
bottom: -8px;
|
||||
left: -4px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -512,7 +512,9 @@ const {
|
||||
selectTab,
|
||||
newTab,
|
||||
removeTab,
|
||||
updateTabs
|
||||
updateTabs,
|
||||
selectNextTab,
|
||||
selectPrevTab
|
||||
} = workspacesStore;
|
||||
|
||||
const props = defineProps({
|
||||
@@ -588,6 +590,22 @@ const onKey = (e: KeyboardEvent) => {
|
||||
if (currentTab)
|
||||
closeTab(currentTab);
|
||||
}
|
||||
|
||||
// select next tab
|
||||
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowRight')
|
||||
selectNextTab({ uid: props.connection.uid });
|
||||
|
||||
// select prev tab
|
||||
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowLeft')
|
||||
selectPrevTab({ uid: props.connection.uid });
|
||||
|
||||
// select tab by index (range 1-9). CTRL|CMD number
|
||||
if ((e.ctrlKey || e.metaKey) && !e.altKey && e.keyCode >= 49 && e.keyCode <= 57) {
|
||||
const newIndex = parseInt(e.key) - 1;
|
||||
|
||||
if (workspace.value.tabs[newIndex])
|
||||
selectTab({ uid: props.connection.uid, tab: workspace.value.tabs[newIndex].uid });
|
||||
}
|
||||
};
|
||||
|
||||
const openAsPermanentTab = (tab: WorkspaceTab) => {
|
||||
|
@@ -411,12 +411,12 @@ const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
||||
|
||||
const clients = ref([
|
||||
const clients = [
|
||||
{ name: 'MySQL', slug: 'mysql' },
|
||||
{ name: 'MariaDB', slug: 'maria' },
|
||||
{ name: 'PostgreSQL', slug: 'pg' },
|
||||
{ name: 'SQLite', slug: 'sqlite' }
|
||||
]);
|
||||
];
|
||||
|
||||
const connection = ref({
|
||||
name: '',
|
||||
|
@@ -424,12 +424,12 @@ const { editConnection } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const { connectWorkspace } = useWorkspacesStore();
|
||||
|
||||
const clients = ref([
|
||||
const clients = [
|
||||
{ name: 'MySQL', slug: 'mysql' },
|
||||
{ name: 'MariaDB', slug: 'maria' },
|
||||
{ name: 'PostgreSQL', slug: 'pg' },
|
||||
{ name: 'SQLite', slug: 'sqlite' }
|
||||
]);
|
||||
];
|
||||
|
||||
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||
const localConnection: Ref<ConnectionParams & { pgConnString: string }> = ref(null);
|
||||
|
@@ -125,9 +125,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Prop, Ref, ref, watch } from 'vue';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { EventInfos } from 'common/interfaces/antares';
|
||||
import { ConnectionParams, EventInfos } from 'common/interfaces/antares';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
@@ -141,7 +141,7 @@ const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
tabUid: String,
|
||||
connection: Object,
|
||||
connection: Object as Prop<ConnectionParams>,
|
||||
tab: Object,
|
||||
isSelected: Boolean,
|
||||
schema: String
|
||||
|
@@ -332,7 +332,10 @@ const addField = () => {
|
||||
collation: defaultCollation.value,
|
||||
autoIncrement: false,
|
||||
onUpdate: '',
|
||||
comment: ''
|
||||
comment: '',
|
||||
alias: '',
|
||||
tableAlias: '',
|
||||
orgTable: ''
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
|
@@ -56,7 +56,8 @@ const useFocusTrap = (args?: {disableAutofocus?: boolean}) => {
|
||||
}
|
||||
|
||||
function initFocusTrap () {
|
||||
if (!trapRef.value || isInitiated.value) return;
|
||||
if (!trapRef.value || (isInitiated.value)) return;
|
||||
|
||||
focusableElements = (trapRef.value as HTMLElement).querySelectorAll(
|
||||
focusableElementsSelector
|
||||
);
|
||||
|
@@ -139,7 +139,9 @@ module.exports = {
|
||||
commit: 'Commit',
|
||||
rollback: 'Rollback',
|
||||
connectionString: 'Connection string',
|
||||
contributors: 'Contributors'
|
||||
contributors: 'Contributors',
|
||||
pin: 'Pin',
|
||||
unpin: 'Unpin'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -291,7 +293,10 @@ module.exports = {
|
||||
untrustedConnection: 'Untrusted connection',
|
||||
missingOrIncompleteTranslation: 'Missing or incomplete translation?',
|
||||
findOutHowToContribute: 'Find out how to contribute',
|
||||
disableFKChecks: 'Disable foreigh key checks'
|
||||
disableFKChecks: 'Disable foreigh key checks',
|
||||
allConnections: 'All connections',
|
||||
searchForConnections: 'Search for connections',
|
||||
disableScratchpad: 'Disable scratchpad'
|
||||
},
|
||||
faker: {
|
||||
address: 'Address',
|
||||
|
@@ -3,6 +3,7 @@
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
|
||||
&.dbi-mysql {
|
||||
background-image: url("../images/svg/mysql.svg");
|
||||
|
@@ -368,6 +368,10 @@ option:checked {
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0.15rem 0.3rem;
|
||||
}
|
||||
|
||||
.table-dropdown {
|
||||
.menu {
|
||||
min-width: 100%;
|
||||
|
@@ -148,6 +148,10 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-top: 0.05rem solid rgba($body-font-color-dark, 0.1);
|
||||
}
|
||||
|
||||
.form-switch .form-icon::before {
|
||||
background: $bg-color-light-dark;
|
||||
}
|
||||
@@ -252,6 +256,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.connection-block{
|
||||
&:hover {
|
||||
background: $bg-color-light-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-checkered {
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
|
||||
@@ -396,9 +406,7 @@
|
||||
}
|
||||
|
||||
.settingbar-bottom-elements {
|
||||
padding-top: 0.5rem;
|
||||
background: $bg-color-light-dark;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.settingbar-elements {
|
||||
|
@@ -94,6 +94,10 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-top: 0.05rem solid rgba($body-font-color-dark, 0.1);
|
||||
}
|
||||
|
||||
.tile {
|
||||
transition: background 0.2s;
|
||||
|
||||
@@ -165,9 +169,7 @@
|
||||
}
|
||||
|
||||
.settingbar-bottom-elements {
|
||||
padding-top: 0.5rem;
|
||||
background: $bg-color-light-dark;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.settingbar-elements {
|
||||
@@ -272,6 +274,12 @@
|
||||
background: rgba($bg-color-light-gray, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.connection-block{
|
||||
&:hover {
|
||||
background: $bg-color-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.context {
|
||||
color: $body-font-color-dark;
|
||||
|
@@ -17,7 +17,9 @@ const persistentStore = new Store({
|
||||
|
||||
export const useConnectionsStore = defineStore('connections', {
|
||||
state: () => ({
|
||||
connections: persistentStore.get('connections', []) as ConnectionParams[]
|
||||
connections: persistentStore.get('connections', []) as ConnectionParams[],
|
||||
pinnedConnections: new Set([...persistentStore.get('pinnedConnections', []) as string[]]) as Set<string>,
|
||||
lastConnections: persistentStore.get('lastConnections', []) as {uid: string; time: number}[]
|
||||
}),
|
||||
getters: {
|
||||
getConnectionName: state => (uid: string) => {
|
||||
@@ -50,6 +52,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
deleteConnection (connection: ConnectionParams) {
|
||||
this.connections = (this.connections as ConnectionParams[]).filter(el => el.uid !== connection.uid);
|
||||
persistentStore.set('connections', this.connections);
|
||||
(this.pinnedConnections as Set<string>).delete(connection.uid);
|
||||
persistentStore.set('pinnedConnections', [...this.pinnedConnections]);
|
||||
},
|
||||
editConnection (connection: ConnectionParams) {
|
||||
const editedConnections = (this.connections as ConnectionParams[]).map(conn => {
|
||||
@@ -63,6 +67,28 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
updateConnections (connections: ConnectionParams[]) {
|
||||
this.connections = connections;
|
||||
persistentStore.set('connections', this.connections);
|
||||
},
|
||||
updatePinnedConnections (pinned: string[]) {
|
||||
this.pinnedConnections = new Set(pinned);
|
||||
persistentStore.set('pinnedConnections', [...this.pinnedConnections]);
|
||||
},
|
||||
pinConnection (uid: string) {
|
||||
(this.pinnedConnections as Set<string>).add(uid);
|
||||
persistentStore.set('pinnedConnections', [...this.pinnedConnections]);
|
||||
},
|
||||
unpinConnection (uid: string) {
|
||||
(this.pinnedConnections as Set<string>).delete(uid);
|
||||
persistentStore.set('pinnedConnections', [...this.pinnedConnections]);
|
||||
},
|
||||
updateLastConnection (uid: string) {
|
||||
const cIndex = (this.lastConnections as {uid: string; time: number}[]).findIndex((c) => c.uid === uid);
|
||||
|
||||
if (cIndex >= 0)
|
||||
this.lastConnections[cIndex].time = new Date().getTime();
|
||||
else
|
||||
this.lastConnections.push({ uid, time: new Date().getTime() });
|
||||
|
||||
persistentStore.set('lastConnections', this.lastConnections);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -23,7 +23,8 @@ export const useSettingsStore = defineStore('settings', {
|
||||
editorTheme: persistentStore.get('editor_theme', defaultEditorTheme) as string,
|
||||
editorFontSize: persistentStore.get('editor_font_size', 'medium') as EditorFontSize,
|
||||
restoreTabs: persistentStore.get('restore_tabs', true) as boolean,
|
||||
disableBlur: persistentStore.get('disable_blur', false) as boolean
|
||||
disableBlur: persistentStore.get('disable_blur', false) as boolean,
|
||||
disableScratchpad: persistentStore.get('disable_scratchpad', false) as boolean
|
||||
}),
|
||||
actions: {
|
||||
changeLocale (locale: string) {
|
||||
@@ -75,6 +76,10 @@ export const useSettingsStore = defineStore('settings', {
|
||||
changeDisableBlur (val: boolean) {
|
||||
this.disableBlur = val;
|
||||
persistentStore.set('disable_blur', this.disableBlur);
|
||||
},
|
||||
changeDisableScratchpad (val: boolean) {
|
||||
this.disableScratchpad = val;
|
||||
persistentStore.set('disable_scratchpad', this.disableScratchpad);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -170,6 +170,9 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
let dataTypes: TypesGroup[] = [];
|
||||
let indexTypes: string[] = [];
|
||||
let clientCustomizations: Customizations;
|
||||
const { updateLastConnection } = connectionsStore;
|
||||
|
||||
updateLastConnection(connection.uid);
|
||||
|
||||
switch (connection.client) {
|
||||
case 'mysql':
|
||||
@@ -715,44 +718,6 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
);
|
||||
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
|
||||
},
|
||||
// setTabFields ({ cUid, tUid, fields }: { cUid: string; tUid: string; fields: any }) {
|
||||
// this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
// if (workspace.uid === cUid) {
|
||||
// return {
|
||||
// ...workspace,
|
||||
// tabs: workspace.tabs.map(tab => {
|
||||
// if (tab.uid === tUid)
|
||||
// return { ...tab, fields };
|
||||
// else
|
||||
// return tab;
|
||||
// })
|
||||
// };
|
||||
// }
|
||||
// else
|
||||
// return workspace;
|
||||
// });
|
||||
|
||||
// persistentStore.set(cUid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === cUid).tabs);
|
||||
// },
|
||||
// setTabKeyUsage ({ cUid, tUid, keyUsage }: { cUid: string; tUid: string; keyUsage: any }) {
|
||||
// this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
// if (workspace.uid === cUid) {
|
||||
// return {
|
||||
// ...workspace,
|
||||
// tabs: workspace.tabs.map(tab => {
|
||||
// if (tab.uid === tUid)
|
||||
// return { ...tab, keyUsage };
|
||||
// else
|
||||
// return tab;
|
||||
// })
|
||||
// };
|
||||
// }
|
||||
// else
|
||||
// return workspace;
|
||||
// });
|
||||
|
||||
// persistentStore.set(cUid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === cUid).tabs);
|
||||
// },
|
||||
setUnsavedChanges ({ uid, tUid, isChanged }: { uid: string; tUid: string; isChanged: boolean }) {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
|
@@ -5,6 +5,7 @@
|
||||
"./src/renderer/**/*",
|
||||
"./src/common/interfaces/antares.ts"
|
||||
],
|
||||
"exclude": ["./src/renderer/libs/ext-language_tools.js"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"target": "es2021",
|
||||
|
Reference in New Issue
Block a user