Merge pull request #238 from antares-sql/vue3-migration

Vue3 migration
This commit is contained in:
Fabio Di Stasio 2022-04-30 17:31:02 +02:00 committed by GitHub
commit 75aa299f8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
130 changed files with 4597 additions and 3924 deletions

View File

@ -7,7 +7,7 @@
"extends": [
"standard",
"plugin:@typescript-eslint/recommended",
"plugin:vue/recommended"
"plugin:vue/vue3-recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
@ -17,6 +17,7 @@
"requireConfigFile": false
},
"plugins": [
"vue",
"@typescript-eslint"
],
"rules": {

2
.gitignore vendored
View File

@ -1,6 +1,8 @@
.DS_Store
dist
build
misc/*
!misc/.gitkeep
node_modules
thumbs.db
NOTES.md

2
.vscode/launch.json vendored
View File

@ -28,7 +28,7 @@
"sourceMaps": true,
"type": "node",
"timeout": 1000000
},
}
],
"compounds": [
{

View File

@ -2,7 +2,7 @@
Antares SQL is an application based on [Electron.js](https://www.electronjs.org/) that uses [Vue.js](https://vuejs.org/) and [Spectre.css](https://picturepan2.github.io/spectre/) as frontend frameworks.
For the build process it takes advantage of [electron-builder](https://www.electron.build/).
This application uses [Vuex](https://vuex.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
This application uses [Pinia🍍](https://pinia.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
This guide aims to provide useful information and guidelines to everyone wants to contribute with this open-source project.
For every other question related to this project please [contact me](https://github.com/Fabio286).
@ -14,7 +14,7 @@ The main files of the application are located inside `src` folder and are groupp
This folder contains small libraries, classes and objects. The purpose of `common` folder is to group together utilities used by **renderer** and **main** processes.
Noteworthy is the `customizations` folder that contains clients related customizations. Those settings are merged with `default.js` that lists every option.
Client related customizations are stored on Vuex and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
Client related customizations are stored on Pinia and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
An use case of customizations object can be the following:
@ -62,12 +62,6 @@ The command to build Antares SQL locally is `npm run build:local`.
- [Order of words in component names](https://vuejs.org/v2/style-guide/#Order-of-words-in-component-names-strongly-recommended).
- **kebab-case** in templates for property and event names.
### Vuex
- **snake_case** for state names.
- **camelCase** for getter and action names.
- **SNAKE_CASE (all caps)** for mutation names.
### Code Style
The project includes [ESlint](https://eslint.org/) and [StyleLint](https://stylelint.io/) config files with style rules. I recommend to set the lint on-save option in your code editor.

0
misc/.gitkeep Normal file
View File

View File

@ -18,8 +18,10 @@
"rebuild:electron": "rimraf ./dist && npm run postinstall",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"postinstall": "electron-builder install-app-deps",
"test": "npm run compile && node tests/app.spec.js",
"devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps && npm run devtools:install",
"test": "npm run compile && npm run test:dry",
"test:dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add",
@ -27,6 +29,9 @@
},
"author": "Fabio Di Stasio <fabio286@gmail.com>",
"main": "./dist/main.js",
"antares": {
"devtoolsId": "nhdogjmejiglipccpnnnanhbledajbpd"
},
"build": {
"appId": "com.fabio286.antares",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
@ -115,7 +120,7 @@
"electron-store": "^8.0.1",
"electron-updater": "^4.6.1",
"electron-window-state": "^5.0.3",
"faker": "^5.5.3",
"encoding": "^0.1.13",
"leaflet": "^1.7.1",
"marked": "^4.0.0",
"moment": "^2.29.1",
@ -123,24 +128,27 @@
"pg": "^8.7.1",
"pg-query-stream": "^4.2.3",
"pgsql-ast-parser": "^7.2.1",
"pinia": "^2.0.13",
"source-map-support": "^0.5.20",
"spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2",
"ssh2-promise": "^1.0.2",
"v-mask": "^2.3.0",
"vue-i18n": "^8.26.5",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
"vue": "^3.2.33",
"vue-i18n": "^9.1.9",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.15.7",
"@babel/preset-env": "^7.15.8",
"@babel/preset-typescript": "^7.16.7",
"@playwright/test": "^1.21.1",
"@types/better-sqlite3": "^7.5.0",
"@types/node": "^17.0.23",
"@types/pg": "^8.6.5",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"@vue/compiler-sfc": "^3.2.33",
"all-contributors-cli": "^6.20.0",
"babel-loader": "^8.2.3",
"chalk": "^4.1.2",
@ -148,7 +156,6 @@
"css-loader": "^6.5.0",
"electron": "^17.0.1",
"electron-builder": "^22.14.11",
"electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
@ -159,7 +166,8 @@
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "~2.4.5",
"node-loader": "^2.0.0",
"playwright": "^1.18.1",
"playwright": "^1.21.1",
"playwright-core": "^1.21.1",
"progress-webpack-plugin": "^1.0.12",
"rimraf": "^3.0.2",
"sass": "^1.42.1",
@ -172,12 +180,12 @@
"tree-kill": "^1.2.2",
"ts-loader": "^9.2.8",
"typescript": "^4.6.3",
"vue": "^2.6.14",
"unzip-crx-3": "^0.2.0",
"vue-eslint-parser": "^8.3.0",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
"vue-loader": "^16.8.3",
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0"
"webpack-dev-server": "^4.4.0",
"xvfb-maybe": "^0.2.1"
}
}

View File

@ -1,5 +1,6 @@
process.env.NODE_ENV = 'development';
// process.env.ELECTRON_ENABLE_LOGGING = true
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = false;
const chalk = require('chalk');
const electron = require('electron');
@ -19,7 +20,7 @@ let manualRestart = null;
const remoteDebugging = process.argv.includes('--remote-debug');
if (remoteDebugging) {
// disable dvtools open in electron
// disable devtools open in electron
process.env.RENDERER_REMOTE_DEBUGGING = true;
}

View File

@ -0,0 +1,48 @@
// @ts-check
const fs = require('fs');
const path = require('path');
const https = require('https');
const unzip = require('unzip-crx-3');
const { antares } = require('../package.json');
const extensionID = antares.devtoolsId;
const destFolder = path.resolve(__dirname, `../misc/${extensionID}`);
const filePath = path.resolve(__dirname, `${destFolder}${extensionID}.crx`);
const fileUrl = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${extensionID}%26uc&prodversion=32`;
const fileStream = fs.createWriteStream(filePath);
const downloadFile = url => {
return new Promise((resolve, reject) => {
const request = https.get(url);
request.on('response', response => {
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
return downloadFile(response.headers.location)
.then(resolve)
.catch(reject);
}
response.pipe(fileStream);
response.on('close', () => {
console.log('Devtools download completed!');
resolve();
});
response.on('error', reject);
});
request.on('error', reject);
request.end();
});
};
(async () => {
try {
await downloadFile(fileUrl);
await unzip(filePath, destFolder);
fs.unlinkSync(filePath);
fs.unlinkSync(`${destFolder}/package.json`);// <- Avoid to display annoyng npm script in vscode
}
catch (error) {
console.log(error);
}
})();

View File

@ -43,4 +43,4 @@ export function mimeFromHex (hex) {
}
}
}
};
}

View File

@ -5,4 +5,4 @@
*/
export function uidGen (prefix) {
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
};
}

View File

@ -10,7 +10,7 @@ export default () => {
event.returnValue = key;
});
ipcMain.handle('showOpenDialog', (event, options) => {
ipcMain.handle('show-open-dialog', (event, options) => {
return dialog.showOpenDialog(options);
});

View File

@ -135,9 +135,6 @@ export default (connections: {[key: string]: antares.Client}) => {
await connection.connect();
// TODO: temporary
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const structure = await connection.getStructure(new Set());
connections[conn.uid] = connection;

View File

@ -47,22 +47,8 @@ async function createMainWindow () {
remoteMain.enable(window.webContents);
try {
if (isDevelopment) {
const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer');
const options = {
loadExtensionOptions: { allowFileAccess: true }
};
try {
const name = await installExtension(VUEJS3_DEVTOOLS, options);
console.log(`Added Extension: ${name}`);
}
catch (err) {
console.log('An error occurred: ', err);
}
if (isDevelopment)
await window.loadURL('http://localhost:9080');
}
else {
const indexPath = path.resolve(__dirname, 'index.html');
await window.loadFile(indexPath);
@ -109,8 +95,8 @@ else {
mainWindow = await createMainWindow();
createAppMenu();
// if (isDevelopment)
// mainWindow.webContents.openDevTools();
if (isDevelopment)
mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error);
@ -120,6 +106,14 @@ else {
mainWindow.webContents.send('unhandled-exception', error);
});
});
app.on('browser-window-created', (event, window) => {
if (isDevelopment) {
const { antares } = require('../../package.json');
const extensionPath = path.resolve(__dirname, `../../misc/${antares.devtoolsId}`);
window.webContents.session.loadExtension(extensionPath, { allowFileAccess: true }).catch(console.error);
}
});
}
function createAppMenu () {

View File

@ -25,37 +25,56 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { ipcRenderer } from 'electron';
import { Menu, getCurrentWindow } from '@electron/remote';
import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar';
export default {
name: 'App',
components: {
TheTitleBar: () => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar'),
TheSettingBar: () => import(/* webpackChunkName: "TheSettingBar" */'@/components/TheSettingBar'),
TheFooter: () => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter'),
TheNotificationsBoard: () => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard'),
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
WorkspaceAddConnectionPanel: () => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
BaseTextEditor: () => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor')
TheTitleBar: defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar')),
TheSettingBar,
TheFooter: defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter')),
TheNotificationsBoard: defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard')),
Workspace: defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace')),
WorkspaceAddConnectionPanel: defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel')),
ModalSettings: defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')),
TheScratchpad: defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad')),
BaseTextEditor: defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor'))
},
data () {
return {};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
isLoading: 'application/isLoading',
isSettingModal: 'application/isSettingModal',
isScratchpad: 'application/isScratchpad',
connections: 'connections/getConnections',
applicationTheme: 'settings/getApplicationTheme',
disableBlur: 'settings/getDisableBlur',
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
})
setup () {
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const {
isLoading,
isSettingModal,
isScratchpad
} = storeToRefs(applicationStore);
const { connections } = storeToRefs(connectionsStore);
const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { checkVersionUpdate } = applicationStore;
return {
isLoading,
isSettingModal,
isScratchpad,
checkVersionUpdate,
connections,
applicationTheme,
disableBlur,
selectedWorkspace
};
},
mounted () {
ipcRenderer.send('check-for-updates');
@ -97,12 +116,6 @@ export default {
node = node.parentNode;
}
});
},
methods: {
...mapActions({
showNewConnModal: 'application/showNewConnModal',
checkVersionUpdate: 'application/checkVersionUpdate'
})
}
};
</script>

View File

@ -1,44 +1,48 @@
<template>
<div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" />
<div class="modal-container">
<div v-if="hasHeader" class="modal-header pl-2">
<div class="modal-title h6">
<slot name="header" />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasDefault" class="modal-header">
<div class="modal-title h6">
<slot />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasBody" class="modal-body">
<a
v-if="!hasHeader && !hasDefault"
class="btn btn-clear float-right"
@click="hideModal"
/>
<div class="content">
<slot name="body" />
<div class="dummy-wrapper">
<Teleport to="#window-content">
<div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" />
<div class="modal-container">
<div v-if="hasHeader" class="modal-header pl-2">
<div class="modal-title h6">
<slot name="header" />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasDefault" class="modal-header">
<div class="modal-title h6">
<slot />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasBody" class="modal-body pb-0">
<a
v-if="!hasHeader && !hasDefault"
class="btn btn-clear float-right"
@click="hideModal"
/>
<div class="content">
<slot name="body" />
</div>
</div>
<div v-if="!hideFooter" class="modal-footer">
<button
class="btn btn-primary mr-2"
@click.stop="confirmModal"
>
{{ confirmText || $t('word.confirm') }}
</button>
<button
class="btn btn-link"
@click="hideModal"
>
{{ cancelText || $t('word.cancel') }}
</button>
</div>
</div>
</div>
<div v-if="!hideFooter" class="modal-footer">
<button
class="btn btn-primary mr-2"
@click.stop="confirmModal"
>
{{ confirmText || $t('word.confirm') }}
</button>
<button
class="btn btn-link"
@click="hideModal"
>
{{ cancelText || $t('word.cancel') }}
</button>
</div>
</div>
</Teleport>
</div>
</template>
@ -58,6 +62,7 @@ export default {
confirmText: String,
cancelText: String
},
emits: ['confirm', 'hide'],
computed: {
hasHeader () {
return !!this.$slots.header;
@ -81,7 +86,7 @@ export default {
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {

View File

@ -21,6 +21,7 @@ export default {
props: {
contextEvent: MouseEvent
},
emits: ['close-context'],
data () {
return {
contextSize: null,
@ -61,7 +62,7 @@ export default {
if (this.$refs.contextContent)
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {

View File

@ -27,6 +27,7 @@ export default {
default: ''
}
},
emits: ['close'],
data () {
return {
isExpanded: false

View File

@ -11,13 +11,15 @@
<script>
import * as ace from 'ace-builds';
import { storeToRefs } from 'pinia';
import 'ace-builds/webpack-resolver';
import { mapGetters } from 'vuex';
import { useSettingsStore } from '@/stores/settings';
import { uidGen } from 'common/libs/uidGen';
export default {
name: 'BaseTextEditor',
props: {
value: String,
modelValue: String,
mode: { type: String, default: 'text' },
editorClass: { type: String, default: '' },
autoFocus: { type: Boolean, default: false },
@ -25,20 +27,30 @@ export default {
showLineNumbers: { type: Boolean, default: true },
height: { type: Number, default: 200 }
},
emits: ['update:modelValue'],
setup () {
const settingsStore = useSettingsStore();
const {
editorTheme,
editorFontSize,
autoComplete,
lineWrap
} = storeToRefs(settingsStore);
return {
editorTheme,
editorFontSize,
autoComplete,
lineWrap
};
},
data () {
return {
editor: null,
id: null
id: uidGen()
};
},
computed: {
...mapGetters({
editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
autoComplete: 'settings/getAutoComplete',
lineWrap: 'settings/getLineWrap'
})
},
watch: {
mode () {
if (this.editor)
@ -76,14 +88,11 @@ export default {
}
}
},
created () {
this.id = this._uid;
},
mounted () {
this.editor = ace.edit(`editor-${this.id}`, {
mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
value: this.value || '',
value: this.modelValue || '',
fontSize: '14px',
printMargin: false,
readOnly: this.readOnly,
@ -100,7 +109,7 @@ export default {
this.editor.session.on('change', () => {
const content = this.editor.getValue();
this.$emit('update:value', content);
this.$emit('update:modelValue', content);
});
if (this.autoFocus) {

View File

@ -22,6 +22,7 @@ export default {
default: ''
}
},
emits: ['close'],
data () {
return {
isVisible: false

View File

@ -4,10 +4,10 @@
<i class="mdi mdi-folder-open mr-1" />{{ message }}
</span>
<span class="text-ellipsis file-uploader-value">
{{ value | lastPart }}
{{ lastPart(modelValue) }}
</span>
<i
v-if="value.length"
v-if="modelValue.length"
class="file-uploader-reset mdi mdi-close"
@click.prevent="clear"
/>
@ -25,26 +25,17 @@
<script>
export default {
name: 'BaseUploadInput',
filters: {
lastPart (string) {
if (!string) return '';
string = string.split(/[/\\]+/).pop();
if (string.length >= 19)
string = `...${string.slice(-19)}`;
return string;
}
},
props: {
message: {
default: 'Browse',
type: String
},
value: {
modelValue: {
default: '',
type: String
}
},
emits: ['change', 'clear'],
data () {
return {
id: null
@ -56,6 +47,14 @@ export default {
methods: {
clear () {
this.$emit('clear');
},
lastPart (string) {
if (!string) return '';
string = string.split(/[/\\]+/).pop();
if (string.length >= 19)
string = `...${string.slice(-19)}`;
return string;
}
}
};

View File

@ -49,7 +49,7 @@ export default {
mounted () {
this.setScrollElement();
},
beforeDestroy () {
beforeUnmount () {
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
},
methods: {

View File

@ -36,8 +36,8 @@
<ForeignKeySelect
v-else-if="foreignKeys.includes(field.name)"
ref="formInput"
v-model="selectedValue"
class="form-select"
:value.sync="selectedValue"
:key-usage="getKeyUsage(field.name)"
:disabled="!isChecked"
/>
@ -105,7 +105,6 @@
</template>
<script>
import { VueMaskDirective } from 'v-mask';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import BaseUploadInput from '@/components/BaseUploadInput';
import ForeignKeySelect from '@/components/ForeignKeySelect';
@ -117,9 +116,6 @@ export default {
ForeignKeySelect,
BaseUploadInput
},
directives: {
mask: VueMaskDirective
},
props: {
type: String,
field: Object,
@ -129,6 +125,7 @@ export default {
fieldLength: Number,
fieldObj: Object
},
emits: ['update:modelValue'],
data () {
return {
localType: null,
@ -244,7 +241,7 @@ export default {
this.selectedValue = '';
},
onChange () {
this.$emit('update:value', {
this.$emit('update:modelValue', {
group: this.selectedGroup,
method: this.selectedMethod,
params: this.methodParams,

View File

@ -6,53 +6,55 @@
@change="onChange"
@blur="$emit('blur')"
>
<option v-if="!isValidDefault" :value="value">
{{ value === null ? 'NULL' : value }}
<option v-if="!isValidDefault" :value="modelValue">
{{ modelValue === null ? 'NULL' : modelValue }}
</option>
<option
v-for="row in foreignList"
:key="row.foreign_column"
:value="row.foreign_column"
:selected="row.foreign_column === value"
:selected="row.foreign_column === modelValue"
>
{{ row.foreign_column }} {{ 'foreign_description' in row ? ` - ${row.foreign_description}` : '' | cutText }}
{{ row.foreign_column }} {{ cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '') }}
</option>
</select>
</template>
<script>
import { storeToRefs } from 'pinia';
import Tables from '@/ipc-api/Tables';
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
export default {
name: 'ForeignKeySelect',
filters: {
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
}
},
props: {
value: [String, Number],
modelValue: [String, Number],
keyUsage: Object,
size: {
type: String,
default: ''
}
},
emits: ['update:modelValue', 'blur'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
return { addNotification, selectedWorkspace };
},
data () {
return {
foreignList: []
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected'
}),
isValidDefault () {
if (!this.foreignList.length) return true;
if (this.value === null) return false;
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
if (this.modelValue === null) return false;
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.modelValue.toString());
}
},
async created () {
@ -93,11 +95,12 @@ export default {
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
onChange () {
this.$emit('update:value', this.$refs.editField.value);
this.$emit('update:modelValue', this.$refs.editField.value);
},
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
}
}
};

View File

@ -1,61 +1,64 @@
<template>
<div class="modal active modal-sm">
<a class="modal-overlay" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ $t('word.credentials') }}
<Teleport to="#window-content">
<div class="modal active modal-sm">
<a class="modal-overlay" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ $t('word.credentials') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="credentials.user"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-9">
<input
v-model="credentials.password"
class="form-input"
type="password"
>
</div>
</div>
</form>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="credentials.user"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-9">
<input
v-model="credentials.password"
class="form-input"
type="password"
>
</div>
</div>
</form>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="sendCredentials">
{{ $t('word.send') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="sendCredentials">
{{ $t('word.send') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script>
export default {
name: 'ModalAskCredentials',
emits: ['close-asking', 'credentials'],
data () {
return {
credentials: {

View File

@ -36,7 +36,7 @@
class="input-group-addon field-type cut-text"
:class="typeClass(parameter.type)"
>
{{ parameter.type }} {{ parameter.length | wrapNumber }}
{{ parameter.type }} {{ wrapNumber(parameter.length) }}
</span>
</div>
</div>
@ -56,16 +56,11 @@ export default {
components: {
ConfirmModal
},
filters: {
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
},
props: {
localRoutine: Object,
client: String
},
emits: ['confirm', 'close'],
data () {
return {
values: {}
@ -83,7 +78,7 @@ export default {
this.$refs.firstInput[0].focus();
}, 20);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
@ -122,6 +117,10 @@ export default {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
}
};

View File

@ -26,10 +26,11 @@ export default {
components: {
ConfirmModal
},
emits: ['confirm', 'close'],
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {

View File

@ -1,72 +1,76 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" />
<span class="cut-text">{{ $t('message.editSchema') }}</span>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" />
<span class="cut-text">{{ $t('message.editSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label>
</div>
<div class="col-9">
<input
v-model="database.name"
class="form-input"
type="text"
required
:placeholder="$t('message.schemaName')"
readonly
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
<div class="col-9">
<select
ref="firstInput"
v-model="database.collation"
class="form-select"
>
<option
v-for="collation in collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
</form>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label>
</div>
<div class="col-9">
<input
v-model="database.name"
class="form-input"
type="text"
required
:placeholder="$t('message.schemaName')"
readonly
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
<div class="col-9">
<select
ref="firstInput"
v-model="database.collation"
class="form-select"
>
<option
v-for="collation in collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
</form>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema';
export default {
@ -74,6 +78,22 @@ export default {
props: {
selectedSchema: String
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, getDatabaseVariable } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
getDatabaseVariable
};
},
data () {
return {
database: {
@ -84,11 +104,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
collations () {
return this.getWorkspace(this.selectedWorkspace).collations;
},
@ -124,13 +139,10 @@ export default {
this.$refs.firstInput.focus();
}, 20);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async updateSchema () {
if (this.database.collation !== this.database.prevCollation) {
try {

View File

@ -1,287 +1,310 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
<span class="cut-text">{{ $t('message.exportSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="container">
<div class="columns">
<div class="col-3">
<label class="form-label">{{ $t('message.directoryPath') }}</label>
</div>
<div class="col-9">
<fieldset class="input-group">
<input
v-model="basePath"
class="form-input"
type="text"
required
readonly
:placeholder="$t('message.schemaName')"
>
<button
type="button"
class="btn btn-primary input-group-btn"
@click.prevent="openPathDialog"
>
{{ $t('word.change') }}
</button>
</fieldset>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
<span class="cut-text">{{ $t('message.exportSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="columns export-options">
<div class="column col-8 left">
<div class="columns mb-2">
<div class="column col-auto d-flex text-italic ">
<i class="mdi mdi-file-document-outline mr-2" />
{{ filename }}
<div class="modal-body pb-0">
<div class="container">
<div class="columns">
<div class="col-3">
<label class="form-label">{{ $t('message.directoryPath') }}</label>
</div>
<div class="column col-auto col-ml-auto ">
<button
class="btn btn-dark btn-sm"
:title="$t('word.refresh')"
@click="refresh"
>
<i class="mdi mdi-database-refresh" />
</button>
<button
class="btn btn-dark btn-sm"
:title="$t('message.uncheckAllTables')"
:disabled="isRefreshing"
@click="uncheckAllTables"
>
<i class="mdi mdi-file-tree-outline" />
</button>
<button
class="btn btn-dark btn-sm"
:title="$t('message.checkAllTables')"
:disabled="isRefreshing"
@click="checkAllTables"
>
<i class="mdi mdi-file-tree" />
</button>
</div>
</div>
<div class="workspace-query-results">
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr text-center">
<div class="th no-border" style="width: 50%;" />
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllTablesOption('includeStructure')"
>
<input
type="checkbox"
:indeterminate.prop="includeStructureStatus === 2"
:checked.prop="!!includeStructureStatus"
>
<i class="form-icon" />
</label>
</div>
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllTablesOption('includeContent')"
>
<input
type="checkbox"
:indeterminate.prop="includeContentStatus === 2"
:checked.prop="!!includeContentStatus"
>
<i class="form-icon" />
</label>
</div>
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllTablesOption('includeDropStatement')"
>
<input
type="checkbox"
:indeterminate.prop="includeDropStatementStatus === 2"
:checked.prop="!!includeDropStatementStatus"
>
<i class="form-icon" />
</label>
</div>
</div>
<div class="tr">
<div class="th" style="width: 50%;">
<div class="table-column-title">
<span>{{ $t('word.table') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ $t('word.structure') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ $t('word.content') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ $t('word.drop') }}</span>
</div>
</div>
</div>
</div>
<div class="tbody">
<div
v-for="item in tables"
:key="item.name"
class="tr"
<div class="col-9">
<fieldset class="input-group">
<input
v-model="basePath"
class="form-input"
type="text"
required
readonly
:placeholder="$t('message.schemaName')"
>
<div class="td">
{{ item.table }}
<button
type="button"
class="btn btn-primary input-group-btn"
@click.prevent="openPathDialog"
>
{{ $t('word.change') }}
</button>
</fieldset>
</div>
</div>
</div>
<div class="columns export-options">
<div class="column col-8 left">
<div class="columns mb-2">
<div class="column col-auto d-flex text-italic ">
<i class="mdi mdi-file-document-outline mr-2" />
{{ filename }}
</div>
<div class="column col-auto col-ml-auto ">
<button
class="btn btn-dark btn-sm"
:title="$t('word.refresh')"
@click="refresh"
>
<i class="mdi mdi-database-refresh" />
</button>
<button
class="btn btn-dark btn-sm mx-1"
:title="$t('message.uncheckAllTables')"
:disabled="isRefreshing"
@click="uncheckAllTables"
>
<i class="mdi mdi-file-tree-outline" />
</button>
<button
class="btn btn-dark btn-sm"
:title="$t('message.checkAllTables')"
:disabled="isRefreshing"
@click="checkAllTables"
>
<i class="mdi mdi-file-tree" />
</button>
</div>
</div>
<div class="workspace-query-results">
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr text-center">
<div class="th no-border" style="width: 50%;" />
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllTablesOption('includeStructure')"
>
<input
type="checkbox"
:indeterminate="includeStructureStatus === 2"
:checked="!!includeStructureStatus"
>
<i class="form-icon" />
</label>
</div>
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllTablesOption('includeContent')"
>
<input
type="checkbox"
:indeterminate="includeContentStatus === 2"
:checked="!!includeContentStatus"
>
<i class="form-icon" />
</label>
</div>
<div class="th no-border">
<label
class="form-checkbox m-0 px-2 form-inline"
@click.prevent="toggleAllTablesOption('includeDropStatement')"
>
<input
type="checkbox"
:indeterminate="includeDropStatementStatus === 2"
:checked="!!includeDropStatementStatus"
>
<i class="form-icon" />
</label>
</div>
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input
v-model="item.includeStructure"
type="checkbox"
><i class="form-icon" />
</label>
<div class="tr">
<div class="th" style="width: 50%;">
<div class="table-column-title">
<span>{{ $t('word.table') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ $t('word.structure') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ $t('word.content') }}</span>
</div>
</div>
<div class="th text-center">
<div class="table-column-title">
<span>{{ $t('word.drop') }}</span>
</div>
</div>
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input
v-model="item.includeContent"
type="checkbox"
><i class="form-icon" />
</label>
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input
v-model="item.includeDropStatement"
type="checkbox"
><i class="form-icon" />
</label>
</div>
<div class="tbody">
<div
v-for="item in tables"
:key="item.name"
class="tr"
>
<div class="td">
{{ item.table }}
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input
v-model="item.includeStructure"
type="checkbox"
><i class="form-icon" />
</label>
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input
v-model="item.includeContent"
type="checkbox"
><i class="form-icon" />
</label>
</div>
<div class="td text-center">
<label class="form-checkbox m-0 px-2 form-inline">
<input
v-model="item.includeDropStatement"
type="checkbox"
><i class="form-icon" />
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="column col-4">
<h5 class="h5">
{{ $t('word.options') }}
</h5>
<span class="h6">{{ $t('word.includes') }}:</span>
<label
v-for="(_, key) in options.includes"
:key="key"
class="form-checkbox"
>
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }}
</label>
<div v-if="customizations.exportByChunks">
<div class="h6 mt-4 mb-2">
{{ $t('message.newInserStmtEvery') }}:
<div class="column col-4">
<h5 class="h5">
{{ $t('word.options') }}
</h5>
<span class="h6">{{ $t('word.includes') }}:</span>
<label
v-for="(_, key) in options.includes"
:key="key"
class="form-checkbox"
>
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }}
</label>
<div v-if="customizations.exportByChunks">
<div class="h6 mt-4 mb-2">
{{ $t('message.newInserStmtEvery') }}:
</div>
<div class="columns">
<div class="column col-6">
<input
v-model.number="options.sqlInsertAfter"
type="number"
class="form-input"
>
</div>
<div class="column col-6">
<select v-model="options.sqlInsertDivider" class="form-select">
<option value="bytes">
KiB
</option>
<option value="rows">
{{ $tc('word.row', 2) }}
</option>
</select>
</div>
</div>
</div>
<div class="h6 mb-2 mt-4">
{{ $t('message.ourputFormat') }}:
</div>
<div class="columns">
<div class="column col-6">
<input
v-model.number="options.sqlInsertAfter"
type="number"
class="form-input"
value="250"
>
</div>
<div class="column col-6">
<select v-model="options.sqlInsertDivider" class="form-select">
<option value="bytes">
KiB
<div class="column h5 mb-4">
<select v-model="options.outputFormat" class="form-select">
<option value="sql">
{{ $t('message.singleFile', {ext: '.sql'}) }}
</option>
<option value="rows">
{{ $tc('word.row', 2) }}
<option value="sql.zip">
{{ $t('message.zipCompressedFile', {ext: '.sql'}) }}
</option>
</select>
</div>
</div>
</div>
<div class="h6 mb-2 mt-4">
{{ $t('message.ourputFormat') }}:
</div>
<div class="columns">
<div class="column h5 mb-4">
<select v-model="options.outputFormat" class="form-select">
<option value="sql">
{{ $t('message.singleFile', {ext: '.sql'}) }}
</option>
<option value="sql.zip">
{{ $t('message.zipCompressedFile', {ext: '.sql'}) }}
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer columns">
<div class="column col modal-progress-wrapper text-left">
<div v-if="progressPercentage > 0" class="export-progress">
<span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }}
</span>
<progress
class="progress d-block"
:value="progressPercentage"
max="100"
/>
<div class="modal-footer columns">
<div class="column col modal-progress-wrapper text-left">
<div v-if="progressPercentage > 0" class="export-progress">
<span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }}
</span>
<progress
class="progress d-block"
:value="progressPercentage"
max="100"
/>
</div>
</div>
<div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
<button
class="btn btn-primary mr-2"
:class="{'loading': isExporting}"
:disabled="isExporting || isRefreshing"
autofocus
@click.prevent="startExport"
>
{{ $t('word.export') }}
</button>
</div>
</div>
<div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
<button
class="btn btn-primary mr-2"
:class="{'loading': isExporting}"
:disabled="isExporting || isRefreshing"
autofocus
@click.prevent="startExport"
>
{{ $t('word.export') }}
</button>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import { ipcRenderer } from 'electron';
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import customizations from 'common/customizations';
import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema';
export default {
name: 'ModalExportSchema',
props: {
selectedSchema: String
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
getDatabaseVariable,
refreshSchema
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
getDatabaseVariable,
refreshSchema
};
},
data () {
return {
isExporting: false,
@ -299,11 +322,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
currentWorkspace () {
return this.getWorkspace(this.selectedWorkspace);
},
@ -358,20 +376,16 @@ export default {
structure.forEach(feat => {
const val = customizations[this.currentWorkspace.client][feat];
if (val)
this.$set(this.options.includes, feat, true);
this.options.includes[feat] = true;
});
ipcRenderer.on('export-progress', this.updateProgress);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
ipcRenderer.off('export-progress', this.updateProgress);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshSchema: 'workspaces/refreshSchema'
}),
async startExport () {
this.isExporting = true;
const { uid, client } = this.currentWorkspace;

View File

@ -1,192 +1,196 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $tc('message.insertRow', 2) }}</span>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $tc('message.insertRow', 2) }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">
<div
v-for="field in fields"
:key="field.name"
class="form-group"
>
<div class="col-3 col-sm-12">
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="column columns col-sm-12">
<FakerSelect
v-model="localRow[field.name]"
:type="field.type"
class="column columns pr-0"
:is-checked="!fieldsToExclude.includes(field.name)"
:foreign-keys="foreignKeys"
:key-usage="keyUsage"
:field="field"
:field-length="fieldLength(field)"
:field-obj="localRow[field.name]"
>
<span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ wrapNumber(fieldLength(field)) }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
<input
type="checkbox"
:checked="!fieldsToExclude.includes(field.name)"
@change.prevent="toggleFields($event, field)"
><i class="form-icon" />
</label>
</FakerSelect>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">
<div
v-for="field in fields"
:key="field.name"
class="form-group"
<div class="modal-footer columns">
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"
type="number"
class="form-input"
min="1"
:disabled="isInserting"
>
<div class="col-3 col-sm-12">
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="column columns col-sm-12">
<FakerSelect
:type="field.type"
class="column columns pr-0"
:is-checked="!fieldsToExclude.includes(field.name)"
:foreign-keys="foreignKeys"
:key-usage="keyUsage"
:field="field"
:field-length="fieldLength(field)"
:field-obj="localRow[field.name]"
:value.sync="localRow[field.name]"
>
<span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
<input
type="checkbox"
:checked="!fieldsToExclude.includes(field.name)"
@change.prevent="toggleFields($event, field)"
><i class="form-icon" />
</label>
</FakerSelect>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="modal-footer columns">
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"
type="number"
class="form-input"
min="1"
:disabled="isInserting"
<span class="input-group-addon">
<i class="mdi mdi-24px mdi-repeat" />
</span>
</div>
<div
v-if="hasFakes"
class="tooltip tooltip-right ml-2"
:data-tooltip="$t('message.fakeDataLanguage')"
>
<span class="input-group-addon">
<i class="mdi mdi-24px mdi-repeat" />
</span>
<select v-model="fakerLocale" class="form-select">
<option value="ar">
Arabic
</option><option value="az">
Azerbaijani
</option><option value="zh_CN">
Chinese
</option><option value="zh_TW">
Chinese (Taiwan)
</option><option value="cz">
Czech
</option><option value="nl">
Dutch
</option><option value="nl_BE">
Dutch (Belgium)
</option><option value="en">
English
</option><option value="en_AU_ocker">
English (Australia Ocker)
</option><option value="en_AU">
English (Australia)
</option><option value="en_BORK">
English (Bork)
</option><option value="en_CA">
English (Canada)
</option><option value="en_GB">
English (Great Britain)
</option><option value="en_IND">
English (India)
</option><option value="en_IE">
English (Ireland)
</option><option value="en_ZA">
English (South Africa)
</option><option value="en_US">
English (United States)
</option><option value="fa">
Farsi
</option><option value="fi">
Finnish
</option><option value="fr">
French
</option><option value="fr_CA">
French (Canada)
</option><option value="fr_CH">
French (Switzerland)
</option><option value="ge">
Georgian
</option><option value="de">
German
</option><option value="de_AT">
German (Austria)
</option><option value="de_CH">
German (Switzerland)
</option><option value="hr">
Hrvatski
</option><option value="id_ID">
Indonesia
</option><option value="it">
Italian
</option><option value="ja">
Japanese
</option><option value="ko">
Korean
</option><option value="nep">
Nepalese
</option><option value="nb_NO">
Norwegian
</option><option value="pl">
Polish
</option><option value="pt_BR">
Portuguese (Brazil)
</option><option value="pt_PT">
Portuguese (Portugal)
</option><option value="ro">
Romanian
</option><option value="ru">
Russian
</option><option value="sk">
Slovakian
</option><option value="es">
Spanish
</option><option value="es_MX">
Spanish (Mexico)
</option><option value="sv">
Swedish
</option><option value="tr">
Turkish
</option><option value="uk">
Ukrainian
</option><option value="vi">
Vietnamese
</option>
</select>
</div>
</div>
<div
v-if="hasFakes"
class="tooltip tooltip-right ml-2"
:data-tooltip="$t('message.fakeDataLanguage')"
>
<select v-model="fakerLocale" class="form-select">
<option value="ar">
Arabic
</option><option value="az">
Azerbaijani
</option><option value="zh_CN">
Chinese
</option><option value="zh_TW">
Chinese (Taiwan)
</option><option value="cz">
Czech
</option><option value="nl">
Dutch
</option><option value="nl_BE">
Dutch (Belgium)
</option><option value="en">
English
</option><option value="en_AU_ocker">
English (Australia Ocker)
</option><option value="en_AU">
English (Australia)
</option><option value="en_BORK">
English (Bork)
</option><option value="en_CA">
English (Canada)
</option><option value="en_GB">
English (Great Britain)
</option><option value="en_IND">
English (India)
</option><option value="en_IE">
English (Ireland)
</option><option value="en_ZA">
English (South Africa)
</option><option value="en_US">
English (United States)
</option><option value="fa">
Farsi
</option><option value="fi">
Finnish
</option><option value="fr">
French
</option><option value="fr_CA">
French (Canada)
</option><option value="fr_CH">
French (Switzerland)
</option><option value="ge">
Georgian
</option><option value="de">
German
</option><option value="de_AT">
German (Austria)
</option><option value="de_CH">
German (Switzerland)
</option><option value="hr">
Hrvatski
</option><option value="id_ID">
Indonesia
</option><option value="it">
Italian
</option><option value="ja">
Japanese
</option><option value="ko">
Korean
</option><option value="nep">
Nepalese
</option><option value="nb_NO">
Norwegian
</option><option value="pl">
Polish
</option><option value="pt_BR">
Portuguese (Brazil)
</option><option value="pt_PT">
Portuguese (Portugal)
</option><option value="ro">
Romanian
</option><option value="ru">
Russian
</option><option value="sk">
Slovakian
</option><option value="es">
Spanish
</option><option value="es_MX">
Spanish (Mexico)
</option><option value="sv">
Swedish
</option><option value="tr">
Turkish
</option><option value="uk">
Ukrainian
</option><option value="vi">
Vietnamese
</option>
</select>
<div class="column col-auto">
<button
class="btn btn-primary mr-2"
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ $t('word.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
<div class="column col-auto">
<button
class="btn btn-primary mr-2"
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ $t('word.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import moment from 'moment';
import { storeToRefs } from 'pinia';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect';
@ -195,17 +199,27 @@ export default {
components: {
FakerSelect
},
filters: {
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
},
props: {
tabUid: [String, Number],
fields: Array,
keyUsage: Array
},
emits: ['reload', 'hide'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, getWorkspaceTab } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
getWorkspaceTab
};
},
data () {
return {
localRow: {},
@ -216,11 +230,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getWorkspaceTab: 'workspaces/getWorkspaceTab'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
},
@ -287,13 +296,10 @@ export default {
this.localRow = { ...rowObj };
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
@ -345,8 +351,8 @@ export default {
},
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return field.charLength;
return field.length;
else if (TEXT.includes(field.type)) return Number(field.charLength);
return Number(field.length);
},
toggleFields (event, field) {
if (event.target.checked)
@ -367,6 +373,10 @@ export default {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
}
};

View File

@ -1,103 +1,107 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div 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-history mr-1" />
<span class="cut-text">{{ $t('word.history') }}: {{ connectionName }}</span>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div 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-history mr-1" />
<span class="cut-text">{{ $t('word.history') }}: {{ connectionName }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body p-0 workspace-query-results">
<div
v-if="history.length"
ref="searchForm"
class="form-group has-icon-right p-2 m-0"
>
<input
v-model="searchTerm"
class="form-input"
type="text"
:placeholder="$t('message.searchForQueries')"
<div class="modal-body p-0 workspace-query-results">
<div
v-if="history.length"
ref="searchForm"
class="form-group has-icon-right p-2 m-0"
>
<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
v-if="history.length"
ref="tableWrapper"
class="vscroll px-1 "
:style="{'height': resultsSize+'px'}"
>
<div ref="table">
<BaseVirtualScroll
ref="resultTable"
:items="filteredHistory"
:item-height="66"
:visible-height="resultsSize"
:scroll-element="scrollElement"
<input
v-model="searchTerm"
class="form-input"
type="text"
:placeholder="$t('message.searchForQueries')"
>
<template slot-scope="{ items }">
<div
v-for="query in items"
:key="query.uid"
class="tile my-2"
tabindex="0"
>
<div class="tile-icon">
<i class="mdi mdi-code-tags pr-1" />
</div>
<div class="tile-content">
<div class="tile-title">
<code
class="cut-text"
:title="query.sql"
v-html="highlightWord(query.sql)"
/>
<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
v-if="history.length"
ref="tableWrapper"
class="vscroll px-1 "
:style="{'height': resultsSize+'px'}"
>
<div ref="table">
<BaseVirtualScroll
ref="resultTable"
:items="filteredHistory"
:item-height="66"
:visible-height="resultsSize"
:scroll-element="scrollElement"
>
<template #default="{ items }">
<div
v-for="query in items"
:key="query.uid"
class="tile my-2"
tabindex="0"
>
<div class="tile-icon">
<i class="mdi mdi-code-tags pr-1" />
</div>
<div class="tile-bottom-content">
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
<div class="tile-history-buttons">
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
<i class="mdi mdi-open-in-app pr-1" /> {{ $t('word.select') }}
</button>
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
<i class="mdi mdi-content-copy pr-1" /> {{ $t('word.copy') }}
</button>
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
<i class="mdi mdi-delete-forever pr-1" /> {{ $t('word.delete') }}
</button>
<div class="tile-content">
<div class="tile-title">
<code
class="cut-text"
:title="query.sql"
v-html="highlightWord(query.sql)"
/>
</div>
<div class="tile-bottom-content">
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
<div class="tile-history-buttons">
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
<i class="mdi mdi-open-in-app pr-1" /> {{ $t('word.select') }}
</button>
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
<i class="mdi mdi-content-copy pr-1" /> {{ $t('word.copy') }}
</button>
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
<i class="mdi mdi-delete-forever pr-1" /> {{ $t('word.delete') }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
</BaseVirtualScroll>
</template>
</BaseVirtualScroll>
</div>
</div>
</div>
<div v-else class="empty">
<div class="empty-icon">
<i class="mdi mdi-history mdi-48px" />
<div v-else class="empty">
<div class="empty-icon">
<i class="mdi mdi-history mdi-48px" />
</div>
<p class="empty-title h5">
{{ $t('message.thereIsNoQueriesYet') }}
</p>
</div>
<p class="empty-title h5">
{{ $t('message.thereIsNoQueriesYet') }}
</p>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import moment from 'moment';
import { mapGetters, mapActions } from 'vuex';
import { useHistoryStore } from '@/stores/history';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
export default {
@ -108,6 +112,19 @@ export default {
props: {
connection: Object
},
emits: ['select-query', 'close'],
setup () {
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
const { getConnectionName } = useConnectionsStore();
const { addNotification } = useNotificationsStore();
return {
getHistoryByWorkspace,
deleteQueryFromHistory,
getConnectionName,
addNotification
};
},
data () {
return {
resultsSize: 1000,
@ -119,10 +136,6 @@ export default {
};
},
computed: {
...mapGetters({
getConnectionName: 'connections/getConnectionName',
getHistoryByWorkspace: 'history/getHistoryByWorkspace'
}),
connectionName () {
return this.getConnectionName(this.connection.uid);
},
@ -156,16 +169,12 @@ export default {
this.resizeResults();
window.addEventListener('resize', this.resizeResults);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey, { capture: true });
window.removeEventListener('resize', this.resizeResults);
clearInterval(this.refreshInterval);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
deleteQueryFromHistory: 'history/deleteQueryFromHistory'
}),
copyQuery (sql) {
navigator.clipboard.writeText(sql);
},

View File

@ -1,56 +1,61 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
<span class="cut-text">{{ $t('message.importSchema') }}</span>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
<span class="cut-text">{{ $t('message.importSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
{{ sqlFile }}
<div v-if="queryErrors.length > 0" class="mt-2">
<label>{{ $tc('message.importQueryErrors', queryErrors.length) }}</label>
<textarea
v-model="formattedQueryErrors"
class="form-input"
rows="5"
readonly
/>
</div>
</div>
<div class="modal-footer columns">
<div class="column col modal-progress-wrapper text-left">
<div class="import-progress">
<span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }} - {{ $tc('message.executedQueries', queryCount) }}
</span>
<progress
class="progress d-block"
:value="progressPercentage"
max="100"
<div class="modal-body pb-0">
{{ sqlFile }}
<div v-if="queryErrors.length > 0" class="mt-2">
<label>{{ $tc('message.importQueryErrors', queryErrors.length) }}</label>
<textarea
v-model="formattedQueryErrors"
class="form-input"
rows="5"
readonly
/>
</div>
</div>
<div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal">
{{ completed ? $t('word.close') : $t('word.cancel') }}
</button>
<div class="modal-footer columns">
<div class="column col modal-progress-wrapper text-left">
<div class="import-progress">
<span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }} - {{ $tc('message.executedQueries', queryCount) }}
</span>
<progress
class="progress d-block"
:value="progressPercentage"
max="100"
/>
</div>
</div>
<div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal">
{{ completed ? $t('word.close') : $t('word.cancel') }}
</button>
</div>
</div>
</div>
</div>
</div>
<Teleport to="#window-content" />
</teleport>
</template>
<script>
import { ipcRenderer } from 'electron';
import { mapActions, mapGetters } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import moment from 'moment';
import Schema from '@/ipc-api/Schema';
import { storeToRefs } from 'pinia';
export default {
name: 'ModalImportSchema',
@ -58,6 +63,22 @@ export default {
props: {
selectedSchema: String
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, refreshSchema } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshSchema
};
},
data () {
return {
sqlFile: '',
@ -70,10 +91,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
currentWorkspace () {
return this.getWorkspace(this.selectedWorkspace);
},
@ -89,16 +106,12 @@ export default {
ipcRenderer.on('import-progress', this.updateProgress);
ipcRenderer.on('query-error', this.handleQueryError);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
ipcRenderer.off('import-progress', this.updateProgress);
ipcRenderer.off('query-error', this.handleQueryError);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshSchema: 'workspaces/refreshSchema'
}),
async startImport (sqlFile) {
this.isImporting = true;
this.sqlFile = sqlFile;

View File

@ -1,76 +1,96 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewSchema') }}</span>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewSchema') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal" @submit.prevent="createSchema">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="database.name"
class="form-input"
type="text"
required
:placeholder="$t('message.schemaName')"
>
</div>
</div>
<div v-if="customizations.collations" class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
<div class="col-9">
<select v-model="database.collation" class="form-select">
<option
v-for="collation in collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
</form>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal" @submit.prevent="createSchema">
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="database.name"
class="form-input"
type="text"
required
:placeholder="$t('message.schemaName')"
>
</div>
</div>
<div v-if="customizations.collations" class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
<div class="col-9">
<select v-model="database.collation" class="form-select">
<option
v-for="collation in collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
</form>
<div class="modal-footer">
<button
class="btn btn-primary mr-2"
:class="{'loading': isLoading}"
@click.stop="createSchema"
>
{{ $t('word.add') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-primary mr-2"
:class="{'loading': isLoading}"
@click.stop="createSchema"
>
{{ $t('word.add') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema';
import { storeToRefs } from 'pinia';
export default {
name: 'ModalNewSchema',
emits: ['reload', 'close'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, getDatabaseVariable } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
getDatabaseVariable
};
},
data () {
return {
isLoading: false,
@ -81,11 +101,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
collations () {
return this.getWorkspace(this.selectedWorkspace).collations;
},
@ -103,13 +118,10 @@ export default {
this.$refs.firstInput.focus();
}, 20);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async createSchema () {
this.isLoading = true;
try {

View File

@ -1,148 +1,158 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $t('message.addNewRow') }}</span>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $t('message.addNewRow') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">
<div
v-for="(field, key) in fields"
:key="field.name"
class="form-group"
>
<div class="col-4 col-sm-12">
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="input-group col-8 col-sm-12">
<ForeignKeySelect
v-if="foreignKeys.includes(field.name)"
ref="formInput"
v-model="localRow[field.name]"
class="form-select"
:key-usage="getKeyUsage(field.name)"
:disabled="fieldsToExclude.includes(field.name)"
/>
<input
v-else-if="inputProps(field).mask"
ref="formInput"
v-model="localRow[field.name]"
v-mask="inputProps(field).mask"
class="form-input"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<input
v-else-if="inputProps(field).type === 'file'"
ref="formInput"
class="form-input"
type="file"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
@change="filesChange($event,field.name)"
>
<input
v-else-if="inputProps(field).type === 'number'"
ref="formInput"
v-model="localRow[field.name]"
class="form-input"
step="any"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<input
v-else
ref="formInput"
v-model="localRow[field.name]"
class="form-input"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<span class="input-group-addon" :class="typeCLass(field.type)">
{{ field.type }} {{ wrapNumber(fieldLength(field)) }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
<input
type="checkbox"
:checked="!field.autoIncrement"
@change.prevent="toggleFields($event, field)"
><i class="form-icon" />
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">
<div
v-for="(field, key) in fields"
:key="field.name"
class="form-group"
>
<div class="col-4 col-sm-12">
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="input-group col-8 col-sm-12">
<ForeignKeySelect
v-if="foreignKeys.includes(field.name)"
ref="formInput"
class="form-select"
:value.sync="localRow[field.name]"
:key-usage="getKeyUsage(field.name)"
:disabled="fieldsToExclude.includes(field.name)"
/>
<input
v-else-if="inputProps(field).mask"
ref="formInput"
v-model="localRow[field.name]"
v-mask="inputProps(field).mask"
class="form-input"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<input
v-else-if="inputProps(field).type === 'file'"
ref="formInput"
class="form-input"
type="file"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
@change="filesChange($event,field.name)"
>
<input
v-else-if="inputProps(field).type === 'number'"
ref="formInput"
v-model="localRow[field.name]"
class="form-input"
step="any"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<input
v-else
ref="formInput"
v-model="localRow[field.name]"
class="form-input"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<span class="input-group-addon" :class="typeCLass(field.type)">
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
<input
type="checkbox"
:checked="!field.autoIncrement"
@change.prevent="toggleFields($event, field)"
><i class="form-icon" />
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="modal-footer">
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"
type="number"
class="form-input"
min="1"
:disabled="isInserting"
>
<span class="input-group-addon">
<i class="mdi mdi-24px mdi-repeat" />
</span>
</div>
<div>
<button
class="btn btn-primary mr-2"
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ $t('word.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
<div class="modal-footer">
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"
type="number"
class="form-input"
min="1"
:disabled="isInserting"
>
<span class="input-group-addon">
<i class="mdi mdi-24px mdi-repeat" />
</span>
</div>
<div>
<button
class="btn btn-primary mr-2"
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ $t('word.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import moment from 'moment';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { VueMaskDirective } from 'v-mask';
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Tables from '@/ipc-api/Tables';
import ForeignKeySelect from '@/components/ForeignKeySelect';
import { storeToRefs } from 'pinia';
export default {
name: 'ModalNewTableRow',
components: {
ForeignKeySelect
},
directives: {
mask: VueMaskDirective
},
filters: {
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
},
props: {
tabUid: [String, Number],
fields: Array,
keyUsage: Array
},
emits: ['reload', 'hide'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, getWorkspaceTab } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
getWorkspaceTab
};
},
data () {
return {
localRow: {},
@ -152,11 +162,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getWorkspaceTab: 'workspaces/getWorkspaceTab'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
},
@ -216,13 +221,10 @@ export default {
firstSelectableInput.focus();
}, 20);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
@ -331,6 +333,10 @@ export default {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
}
};

View File

@ -1,140 +1,143 @@
<template>
<div class="modal active">
<ModalProcessesListContext
v-if="isContext"
:context-event="contextEvent"
:selected-row="selectedRow"
:selected-cell="selectedCell"
@copy-cell="copyCell"
@copy-row="copyRow"
@kill-process="killProcess"
@close-context="closeContext"
/>
<a class="modal-overlay" @click.stop="closeModal" />
<div 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-memory mr-1" />
<span class="cut-text">{{ $t('message.processesList') }}: {{ connectionName }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="processes-toolbar py-2 px-4">
<div class="workspace-query-buttons">
<div class="dropdown pr-1">
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
:class="{'loading':isQuering}"
:title="`${$t('word.refresh')} (F5)`"
@click="getProcessesList"
>
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
</button>
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
<i class="mdi mdi-24px mdi-menu-down" />
</div>
<div class="menu px-3">
<span>{{ $t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
<input
v-model="autorefreshTimer"
class="slider no-border"
type="range"
min="0"
max="15"
step="0.5"
@change="setRefreshInterval"
>
</div>
<Teleport to="#window-content">
<div class="modal active">
<ModalProcessesListContext
v-if="isContext"
:context-event="contextEvent"
:selected-row="selectedRow"
:selected-cell="selectedCell"
@copy-cell="copyCell"
@copy-row="copyRow"
@kill-process="killProcess"
@close-context="closeContext"
/>
<a class="modal-overlay" @click.stop="closeModal" />
<div 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-memory mr-1" />
<span class="cut-text">{{ $t('message.processesList') }}: {{ connectionName }}</span>
</div>
</div>
<div class="dropdown table-dropdown">
<button
:disabled="isQuering"
class="btn btn-dark btn-sm dropdown-toggle d-flex mr-0 pr-0"
tabindex="0"
>
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-menu-down" />
</button>
<ul class="menu text-left">
<li class="menu-item">
<a class="c-hand" @click="downloadTable('json')">JSON</a>
</li>
<li class="menu-item">
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
</li>
</ul>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="workspace-query-info">
<div v-if="sortedResults.length">
{{ $t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b>
</div>
</div>
</div>
<div class="modal-body py-0 workspace-query-results">
<div
ref="tableWrapper"
class="vscroll"
:style="{'height': resultsSize+'px'}"
>
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr">
<div
v-for="(field, index) in fields"
:key="index"
class="th c-hand"
<div class="processes-toolbar py-2 px-4">
<div class="workspace-query-buttons">
<div class="dropdown pr-1">
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
:class="{'loading':isQuering}"
:title="`${$t('word.refresh')} (F5)`"
@click="getProcessesList"
>
<div ref="columnResize" class="column-resizable">
<div class="table-column-title" @click="sort(field)">
<span>{{ field.toUpperCase() }}</span>
<i
v-if="currentSort === field"
class="mdi sort-icon"
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
/>
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
</button>
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
<i class="mdi mdi-24px mdi-menu-down" />
</div>
<div class="menu px-3">
<span>{{ $t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
<input
v-model="autorefreshTimer"
class="slider no-border"
type="range"
min="0"
max="15"
step="0.5"
@change="setRefreshInterval"
>
</div>
</div>
</div>
<div class="dropdown table-dropdown">
<button
:disabled="isQuering"
class="btn btn-dark btn-sm dropdown-toggle d-flex mr-0 pr-0"
tabindex="0"
>
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-menu-down" />
</button>
<ul class="menu text-left">
<li class="menu-item">
<a class="c-hand" @click="downloadTable('json')">JSON</a>
</li>
<li class="menu-item">
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
</li>
</ul>
</div>
</div>
<div class="workspace-query-info">
<div v-if="sortedResults.length">
{{ $t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b>
</div>
</div>
</div>
<div class="modal-body py-0 workspace-query-results">
<div
ref="tableWrapper"
class="vscroll"
:style="{'height': resultsSize+'px'}"
>
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr">
<div
v-for="(field, index) in fields"
:key="index"
class="th c-hand"
>
<div ref="columnResize" class="column-resizable">
<div class="table-column-title" @click="sort(field)">
<span>{{ field.toUpperCase() }}</span>
<i
v-if="currentSort === field"
class="mdi sort-icon"
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
/>
</div>
</div>
</div>
</div>
</div>
<BaseVirtualScroll
ref="resultTable"
:items="sortedResults"
:item-height="22"
class="tbody"
:visible-height="resultsSize"
:scroll-element="scrollElement"
>
<template #default="{ items }">
<ModalProcessesListRow
v-for="row in items"
:key="row.id"
class="process-row"
:row="row"
@select-row="selectRow(row.id)"
@contextmenu="contextMenu"
@stop-refresh="stopRefresh"
/>
</template>
</BaseVirtualScroll>
</div>
<BaseVirtualScroll
ref="resultTable"
:items="sortedResults"
:item-height="22"
class="tbody"
:visible-height="resultsSize"
:scroll-element="scrollElement"
>
<template slot-scope="{ items }">
<ModalProcessesListRow
v-for="row in items"
:key="row.id"
class="process-row"
:row="row"
@select-row="selectRow(row.id)"
@contextmenu="contextMenu"
@stop-refresh="stopRefresh"
/>
</template>
</BaseVirtualScroll>
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import arrayToFile from '../libs/arrayToFile';
import { useNotificationsStore } from '@/stores/notifications';
import Schema from '@/ipc-api/Schema';
import { useConnectionsStore } from '@/stores/connections';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import ModalProcessesListRow from '@/components/ModalProcessesListRow';
import ModalProcessesListContext from '@/components/ModalProcessesListContext';
@ -149,6 +152,13 @@ export default {
props: {
connection: Object
},
emits: ['close'],
setup () {
const { addNotification } = useNotificationsStore();
const { getConnectionName } = useConnectionsStore();
return { addNotification, getConnectionName };
},
data () {
return {
resultsSize: 1000,
@ -167,9 +177,6 @@ export default {
};
},
computed: {
...mapGetters({
getConnectionName: 'connections/getConnectionName'
}),
connectionName () {
return this.getConnectionName(this.connection.uid);
},
@ -203,15 +210,12 @@ export default {
this.getProcessesList();
window.addEventListener('resize', this.resizeResults);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey, { capture: true });
window.removeEventListener('resize', this.resizeResults);
clearInterval(this.refreshInterval);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async getProcessesList () {
this.isQuering = true;
@ -284,14 +288,14 @@ export default {
this.clearRefresh();
},
selectRow (row) {
this.selectedRow = row;
this.selectedRow = Number(row);
},
contextMenu (event, cell) {
if (event.target.localName === 'input') return;
this.stopRefresh();
this.selectedCell = cell;
this.selectedRow = cell.id;
this.selectedRow = Number(cell.id);
this.contextEvent = event;
this.isContext = true;
},

View File

@ -52,6 +52,7 @@ export default {
selectedRow: Number,
selectedCell: Object
},
emits: ['close-context', 'copy-cell', 'copy-row', 'kill-process'],
computed: {
},
methods: {

View File

@ -12,7 +12,7 @@
class="cell-content"
:class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`"
@dblclick="dblClick(cKey)"
>{{ col | cutText }}</span>
>{{ cutText(col) }}</span>
</div>
<ConfirmModal
v-if="isInfoModal"
@ -53,15 +53,10 @@ export default {
ConfirmModal,
TextEditor
},
filters: {
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 250 ? `${val.substring(0, 250)}[...]` : val;
}
},
props: {
row: Object
},
emits: ['select-row', 'contextmenu', 'stop-refresh'],
data () {
return {
isInlineEditor: {},
@ -95,6 +90,10 @@ export default {
this.editingField = null;
window.removeEventListener('keydown', this.onKey);
}
},
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 250 ? `${val.substring(0, 250)}[...]` : val;
}
}
};

View File

@ -1,327 +1,332 @@
<template>
<div id="settings" class="modal active">
<a class="modal-overlay c-hand" @click="closeModal" />
<div class="modal-container">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cog mr-1" />
<span class="cut-text">{{ $t('word.settings') }}</span>
<Teleport to="#window-content">
<div id="settings" class="modal active">
<a class="modal-overlay c-hand" @click="closeModal" />
<div class="modal-container">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cog mr-1" />
<span class="cut-text">{{ $t('word.settings') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
</div>
<div class="modal-body p-0">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'themes'}"
@click="selectTab('themes')"
>
<a class="tab-link">{{ $t('word.themes') }}</a>
</li>
<li
v-if="updateStatus !== 'disabled'"
class="tab-item c-hand"
:class="{'active': selectedTab === 'update'}"
@click="selectTab('update')"
>
<a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')"
>
<a class="tab-link">{{ $t('word.changelog') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'about'}"
@click="selectTab('about')"
>
<a class="tab-link">{{ $t('word.about') }}</a>
</li>
</ul>
</div>
<div v-show="selectedTab === 'general'" class="panel-body py-4">
<div class="container">
<form class="form-horizontal columns">
<div class="column col-12 h6 text-uppercase mb-1">
{{ $t('word.application') }}
</div>
<div class="column col-9 col-sm-12 mb-2">
<div class="form-group">
<div class="col-7 col-sm-12">
<label class="form-label">
<i class="mdi mdi-18px mdi-translate mr-1" />
{{ $t('word.language') }}
</label>
</div>
<div class="col-5 col-sm-12">
<select
v-model="localLocale"
class="form-select"
@change="changeLocale(localLocale)"
>
<option
v-for="(locale, key) in locales"
:key="key"
:value="locale.code"
<div class="modal-body p-0">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'themes'}"
@click="selectTab('themes')"
>
<a class="tab-link">{{ $t('word.themes') }}</a>
</li>
<li
v-if="updateStatus !== 'disabled'"
class="tab-item c-hand"
:class="{'active': selectedTab === 'update'}"
@click="selectTab('update')"
>
<a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')"
>
<a class="tab-link">{{ $t('word.changelog') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'about'}"
@click="selectTab('about')"
>
<a class="tab-link">{{ $t('word.about') }}</a>
</li>
</ul>
</div>
<div v-show="selectedTab === 'general'" class="panel-body py-4">
<div class="container">
<form class="form-horizontal columns">
<div class="column col-12 h6 text-uppercase mb-1">
{{ $t('word.application') }}
</div>
<div class="column col-9 col-sm-12 mb-2">
<div class="form-group">
<div class="col-7 col-sm-12">
<label class="form-label">
<i class="mdi mdi-18px mdi-translate mr-1" />
{{ $t('word.language') }}
</label>
</div>
<div class="col-5 col-sm-12">
<select
v-model="localLocale"
class="form-select"
@change="changeLocale(localLocale)"
>
{{ locale.name }}
</option>
</select>
<option
v-for="(locale, key) in locales"
:key="key"
:value="locale.code"
>
{{ locale.name }}
</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.dataTabPageSize') }}
</label>
</div>
<div class="col-5 col-sm-12">
<select
v-model="localPageSize"
class="form-select"
@change="changePageSize(+localPageSize)"
>
<option
v-for="size in pageSizes"
:key="size"
<div class="form-group">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.dataTabPageSize') }}
</label>
</div>
<div class="col-5 col-sm-12">
<select
v-model="localPageSize"
class="form-select"
@change="changePageSize(+localPageSize)"
>
{{ size }}
</option>
</select>
<option
v-for="size in pageSizes"
:key="size"
>
{{ size }}
</option>
</select>
</div>
</div>
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.restorePreviourSession') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleRestoreSession">
<input type="checkbox" :checked="restoreTabs">
<i class="form-icon" />
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.disableBlur') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleDisableBlur">
<input type="checkbox" :checked="disableBlur">
<i class="form-icon" />
</label>
</div>
</div>
<div class="form-group">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.notificationsTimeout') }}
</label>
</div>
<div class="col-5 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 class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.restorePreviourSession') }}
</label>
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
{{ $t('word.editor') }}
</div>
<div class="column col-9 col-sm-12">
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('word.autoCompletion') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleAutoComplete">
<input type="checkbox" :checked="selectedAutoComplete">
<i class="form-icon" />
</label>
</div>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleRestoreSession">
<input type="checkbox" :checked="restoreTabs">
<i class="form-icon" />
</label>
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.wrapLongLines') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleLineWrap">
<input type="checkbox" :checked="selectedLineWrap">
<i class="form-icon" />
</label>
</div>
</div>
</div>
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.disableBlur') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleDisableBlur">
<input type="checkbox" :checked="disableBlur">
<i class="form-icon" />
</label>
</form>
</div>
</div>
<div v-show="selectedTab === 'themes'" class="panel-body py-4">
<div class="container">
<div class="columns">
<div class="column col-12 h6 text-uppercase mb-2">
{{ $t('message.applicationTheme') }}
</div>
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'dark'}"
@click="changeApplicationTheme('dark')"
>
<img src="../images/dark.png" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name text-light">
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.dark') }}
</div>
</div>
</div>
<div class="form-group">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.notificationsTimeout') }}
</label>
</div>
<div class="col-5 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
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'light'}"
@click="changeApplicationTheme('light')"
>
<img src="../images/light.png" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name text-dark">
<i class="mdi mdi-white-balance-sunny mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.light') }}
</div>
</div>
</div>
</div>
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
{{ $t('word.editor') }}
</div>
<div class="column col-9 col-sm-12">
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('word.autoCompletion') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleAutoComplete">
<input type="checkbox" :checked="selectedAutoComplete">
<i class="form-icon" />
</label>
</div>
<div class="columns mt-4">
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ $t('message.editorTheme') }}
</div>
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.wrapLongLines') }}
</label>
</div>
<div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleLineWrap">
<input type="checkbox" :checked="selectedLineWrap">
<i class="form-icon" />
</label>
</div>
</div>
</div>
</form>
</div>
</div>
<div v-show="selectedTab === 'themes'" class="panel-body py-4">
<div class="container">
<div class="columns">
<div class="column col-12 h6 text-uppercase mb-2">
{{ $t('message.applicationTheme') }}
</div>
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'dark'}"
@click="changeApplicationTheme('dark')"
>
<img src="../images/dark.png" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name text-light">
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.dark') }}
</div>
</div>
</div>
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'light'}"
@click="changeApplicationTheme('light')"
>
<img src="../images/light.png" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name text-dark">
<i class="mdi mdi-white-balance-sunny mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.light') }}
</div>
</div>
</div>
</div>
<div class="columns mt-4">
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ $t('message.editorTheme') }}
</div>
<div class="column col-6 h5 mb-4">
<select
v-model="localEditorTheme"
class="form-select"
@change="changeEditorTheme(localEditorTheme)"
>
<optgroup
v-for="group in editorThemes"
:key="group.group"
:label="group.group"
<div class="column col-6 h5 mb-4">
<select
v-model="localEditorTheme"
class="form-select"
@change="changeEditorTheme(localEditorTheme)"
>
<option
v-for="theme in group.themes"
:key="theme.name"
:value="theme.code"
:selected="editorTheme === theme.code"
<optgroup
v-for="group in editorThemes"
:key="group.group"
:label="group.group"
>
{{ theme.name }}
</option>
</optgroup>
</select>
</div>
<div class="column col-6 mb-4">
<div class="btn-group btn-group-block">
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'small'}"
@click="changeEditorFontSize('small')"
>
{{ $t('word.small') }}
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'medium'}"
@click="changeEditorFontSize('medium')"
>
{{ $t('word.medium') }}
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'large'}"
@click="changeEditorFontSize('large')"
>
{{ $t('word.large') }}
</button>
<option
v-for="theme in group.themes"
:key="theme.name"
:value="theme.code"
:selected="editorTheme === theme.code"
>
{{ theme.name }}
</option>
</optgroup>
</select>
</div>
<div class="column col-6 mb-4">
<div class="btn-group btn-group-block">
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'small'}"
@click="changeEditorFontSize('small')"
>
{{ $t('word.small') }}
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'medium'}"
@click="changeEditorFontSize('medium')"
>
{{ $t('word.medium') }}
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'large'}"
@click="changeEditorFontSize('large')"
>
{{ $t('word.large') }}
</button>
</div>
</div>
<div class="column col-12">
<BaseTextEditor
:model-value="exampleQuery"
mode="sql"
:workspace="workspace"
:read-only="true"
:height="270"
/>
</div>
</div>
<div class="column col-12">
<BaseTextEditor
:value="exampleQuery"
mode="sql"
:workspace="workspace"
:read-only="true"
:height="270"
/>
</div>
</div>
</div>
</div>
<div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate />
</div>
<div v-show="selectedTab === 'changelog'" class="panel-body py-4">
<ModalSettingsChangelog />
</div>
<div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate />
</div>
<div v-show="selectedTab === 'changelog'" class="panel-body py-4">
<ModalSettingsChangelog />
</div>
<div v-show="selectedTab === 'about'" class="panel-body py-4">
<div class="text-center">
<img src="../images/logo.svg" width="128">
<h4>{{ appName }}</h4>
<p class="mb-2">
{{ $t('word.version') }} {{ appVersion }}<br>
<a class="c-hand" @click="openOutside('https://github.com/antares-sql/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
</p>
<div class="mb-2">
<small class="d-block text-uppercase">{{ $t('word.contributors') }}:</small>
<div class="d-block py-1">
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
<div v-show="selectedTab === 'about'" class="panel-body py-4">
<div class="text-center">
<img src="../images/logo.svg" width="128">
<h4>{{ appName }}</h4>
<p class="mb-2">
{{ $t('word.version') }} {{ appVersion }}<br>
<a class="c-hand" @click="openOutside('https://github.com/antares-sql/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
</p>
<div class="mb-2">
<small class="d-block text-uppercase">{{ $t('word.contributors') }}:</small>
<div class="d-block py-1">
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
</div>
<small>{{ $t('message.madeWithJS') }}</small>
</div>
<small>{{ $t('message.madeWithJS') }}</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { shell } from 'electron';
import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import localesNames from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
import BaseTextEditor from '@/components/BaseTextEditor';
const { shell } = require('electron');
export default {
name: 'ModalSettings',
@ -330,6 +335,79 @@ export default {
ModalSettingsChangelog,
BaseTextEditor
},
setup () {
const applicationStore = useApplicationStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const {
selectedSettingTab,
updateStatus
} = storeToRefs(applicationStore);
const {
locale: selectedLocale,
dataTabLimit: pageSize,
autoComplete: selectedAutoComplete,
lineWrap: selectedLineWrap,
notificationsTimeout,
restoreTabs,
disableBlur,
applicationTheme,
editorTheme,
editorFontSize
} = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
changeLocale,
changePageSize,
changeRestoreTabs,
changeDisableBlur,
changeAutoComplete,
changeLineWrap,
changeApplicationTheme,
changeEditorTheme,
changeEditorFontSize,
updateNotificationsTimeout
} = settingsStore;
const {
hideSettingModal,
appName,
appVersion
} = applicationStore;
const { getWorkspace } = workspacesStore;
return {
appName,
appVersion,
selectedSettingTab,
updateStatus,
closeModal: hideSettingModal,
selectedLocale,
pageSize,
selectedAutoComplete,
selectedLineWrap,
notificationsTimeout,
restoreTabs,
disableBlur,
applicationTheme,
editorTheme,
editorFontSize,
changeLocale,
changePageSize,
changeRestoreTabs,
changeDisableBlur,
changeAutoComplete,
changeLineWrap,
changeApplicationTheme,
changeEditorTheme,
changeEditorFontSize,
updateNotificationsTimeout,
selectedWorkspace,
getWorkspace
};
},
data () {
return {
appAuthor: 'Fabio Di Stasio',
@ -392,24 +470,6 @@ export default {
};
},
computed: {
...mapGetters({
appName: 'application/appName',
appVersion: 'application/appVersion',
selectedSettingTab: 'application/selectedSettingTab',
selectedLocale: 'settings/getLocale',
pageSize: 'settings/getDataTabLimit',
selectedAutoComplete: 'settings/getAutoComplete',
selectedLineWrap: 'settings/getLineWrap',
notificationsTimeout: 'settings/getNotificationsTimeout',
restoreTabs: 'settings/getRestoreTabs',
disableBlur: 'settings/getDisableBlur',
applicationTheme: 'settings/getApplicationTheme',
editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
updateStatus: 'application/getUpdateStatus',
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
locales () {
const locales = [];
for (const locale of this.$i18n.availableLocales)
@ -455,23 +515,10 @@ ORDER BY
this.selectedTab = this.selectedSettingTab;
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
closeModal: 'application/hideSettingModal',
changeLocale: 'settings/changeLocale',
changePageSize: 'settings/changePageSize',
changeRestoreTabs: 'settings/changeRestoreTabs',
changeDisableBlur: 'settings/changeDisableBlur',
changeAutoComplete: 'settings/changeAutoComplete',
changeLineWrap: 'settings/changeLineWrap',
changeApplicationTheme: 'settings/changeApplicationTheme',
changeEditorTheme: 'settings/changeEditorTheme',
changeEditorFontSize: 'settings/changeEditorFontSize',
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
}),
selectTab (tab) {
this.selectedTab = tab;
},

View File

@ -15,15 +15,19 @@
</template>
<script>
import { mapGetters } from 'vuex';
import { marked } from 'marked';
import BaseLoader from '@/components/BaseLoader';
import { useApplicationStore } from '@/stores/application';
export default {
name: 'ModalSettingsChangelog',
components: {
BaseLoader
},
setup () {
const { appVersion } = useApplicationStore();
return { appVersion };
},
data () {
return {
changelog: '',
@ -32,9 +36,6 @@ export default {
isError: false
};
},
computed: {
...mapGetters({ appVersion: 'application/appVersion' })
},
created () {
this.getChangelog();
},

View File

@ -53,17 +53,33 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { ipcRenderer, shell } from 'electron';
import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application';
import { useSettingsStore } from '@/stores/settings';
export default {
name: 'ModalSettingsUpdate',
setup () {
const applicationStore = useApplicationStore();
const settingsStore = useSettingsStore();
const {
updateStatus,
getDownloadProgress
} = storeToRefs(applicationStore);
const { allowPrerelease } = storeToRefs(settingsStore);
const { changeAllowPrerelease } = settingsStore;
return {
updateStatus,
downloadPercentage: getDownloadProgress,
allowPrerelease,
changeAllowPrerelease
};
},
computed: {
...mapGetters({
updateStatus: 'application/getUpdateStatus',
downloadPercentage: 'application/getDownloadProgress',
allowPrerelease: 'settings/getAllowPrerelease'
}),
updateMessage () {
switch (this.updateStatus) {
case 'noupdate':
@ -86,9 +102,6 @@ export default {
}
},
methods: {
...mapActions({
changeAllowPrerelease: 'settings/changeAllowPrerelease'
}),
openOutside (link) {
shell.openExternal(link);
},

View File

@ -12,13 +12,16 @@
import * as ace from 'ace-builds';
import 'ace-builds/webpack-resolver';
import '../libs/ext-language_tools';
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { uidGen } from 'common/libs/uidGen';
import { useApplicationStore } from '@/stores/application';
import { useSettingsStore } from '@/stores/settings';
import Tables from '@/ipc-api/Tables';
export default {
name: 'QueryEditor',
props: {
value: String,
modelValue: String,
workspace: Object,
isSelected: Boolean,
schema: { type: String, default: '' },
@ -26,23 +29,42 @@ export default {
readOnly: { type: Boolean, default: false },
height: { type: Number, default: 200 }
},
emits: ['update:modelValue'],
setup () {
const editor = null;
const applicationStore = useApplicationStore();
const settingsStore = useSettingsStore();
const { setBaseCompleters } = applicationStore;
const { baseCompleter } = storeToRefs(applicationStore);
const {
editorTheme,
editorFontSize,
autoComplete,
lineWrap
} = storeToRefs(settingsStore);
return {
editor,
baseCompleter,
setBaseCompleters,
editorTheme,
editorFontSize,
autoComplete,
lineWrap
};
},
data () {
return {
editor: null,
cursorPosition: 0,
fields: [],
customCompleter: [],
id: null,
id: uidGen(),
lastSchema: null
};
},
computed: {
...mapGetters({
editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
autoComplete: 'settings/getAutoComplete',
lineWrap: 'settings/getLineWrap',
baseCompleter: 'application/getBaseCompleter'
}),
tables () {
return this.workspace
? this.workspace.structure.filter(schema => schema.name === this.schema)
@ -127,11 +149,8 @@ export default {
return 'sql';
}
},
cursorPosition () {
return this.editor.session.doc.positionToIndex(this.editor.getCursorPosition());
},
lastWord () {
const charsBefore = this.value.slice(0, this.cursorPosition);
const charsBefore = this.modelValue.slice(0, this.cursorPosition);
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
return words.pop();
},
@ -155,6 +174,9 @@ export default {
}
},
watch: {
modelValue () {
this.cursorPosition = this.editor.session.doc.positionToIndex(this.editor.getCursorPosition());
},
editorTheme () {
if (this.editor)
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
@ -205,14 +227,13 @@ export default {
}
},
created () {
this.id = this._uid;
this.lastSchema = this.schema;
},
mounted () {
this.editor = ace.edit(`editor-${this.id}`, {
mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
value: this.value,
value: this.modelValue,
fontSize: '14px',
printMargin: false,
readOnly: this.readOnly
@ -263,7 +284,7 @@ export default {
this.editor.session.on('change', () => {
const content = this.editor.getValue();
this.$emit('update:value', content);
this.$emit('update:modelValue', content);
});
this.editor.on('guttermousedown', e => {
@ -294,9 +315,6 @@ export default {
}, 20);
},
methods: {
...mapActions({
setBaseCompleters: 'application/setBaseCompleter'
}),
setCustomCompleter () {
this.editor.completers.push({
getCompletions: (editor, session, pos, prefix, callback) => {

View File

@ -30,10 +30,12 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
import { storeToRefs } from 'pinia';
export default {
name: 'SettingBarContext',
@ -45,6 +47,26 @@ export default {
contextEvent: MouseEvent,
contextConnection: Object
},
emits: ['close-context'],
setup () {
const {
getConnectionName,
addConnection,
deleteConnection
} = useConnectionsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { selectWorkspace } = workspacesStore;
return {
getConnectionName,
addConnection,
deleteConnection,
selectedWorkspace,
selectWorkspace
};
},
data () {
return {
isConfirmModal: false,
@ -52,20 +74,11 @@ export default {
};
},
computed: {
...mapGetters({
getConnectionName: 'connections/getConnectionName',
selectedWorkspace: 'workspaces/getSelected'
}),
connectionName () {
return this.getConnectionName(this.contextConnection.uid);
}
},
methods: {
...mapActions({
addConnection: 'connections/addConnection',
deleteConnection: 'connections/deleteConnection',
selectWorkspace: 'workspaces/selectWorkspace'
}),
confirmDeleteConnection () {
if (this.selectedWorkspace === this.contextConnection.uid)
this.selectWorkspace();
@ -77,7 +90,7 @@ export default {
connectionCopy = {
...connectionCopy,
uid: uidGen('C'),
name: connectionCopy.name ? `${connectionCopy.name}_copy` : ''
name: connectionCopy.name ? `${connectionCopy?.name}_copy` : ''
};
this.addConnection(connectionCopy);

View File

@ -27,17 +27,30 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { useApplicationStore } from '@/stores/application';
import { useWorkspacesStore } from '@/stores/workspaces';
import { storeToRefs } from 'pinia';
const { shell } = require('electron');
export default {
name: 'TheFooter',
setup () {
const applicationStore = useApplicationStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: workspace } = storeToRefs(workspacesStore);
const { appVersion, showSettingModal } = applicationStore;
const { getWorkspace } = workspacesStore;
return {
appVersion,
showSettingModal,
workspace,
getWorkspace
};
},
computed: {
...mapGetters({
workspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
appVersion: 'application/appVersion'
}),
version () {
return this.getWorkspace(this.workspace) ? this.getWorkspace(this.workspace).version : null;
},
@ -48,9 +61,6 @@ export default {
}
},
methods: {
...mapActions({
showSettingModal: 'application/showSettingModal'
}),
openOutside (link) {
shell.openExternal(link);
}

View File

@ -4,7 +4,7 @@
@mouseenter="clearTimeouts"
@mouseleave="rearmTimeouts"
>
<transition-group name="slide-fade">
<TransitionGroup tag="div" name="slide-fade">
<BaseNotification
v-for="notification in latestNotifications"
:key="notification.uid"
@ -12,29 +12,42 @@
:status="notification.status"
@close="removeNotification(notification.uid)"
/>
</transition-group>
</TransitionGroup>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings';
import BaseNotification from '@/components/BaseNotification';
import { storeToRefs } from 'pinia';
export default {
name: 'TheNotificationsBoard',
components: {
BaseNotification
},
setup () {
const notificationsStore = useNotificationsStore();
const settingsStore = useSettingsStore();
const { removeNotification } = notificationsStore;
const { notifications } = storeToRefs(notificationsStore);
const { notificationsTimeout } = storeToRefs(settingsStore);
return {
removeNotification,
notifications,
notificationsTimeout
};
},
data () {
return {
timeouts: {}
};
},
computed: {
...mapGetters({
notifications: 'notifications/getNotifications',
notificationsTimeout: 'settings/getNotificationsTimeout'
}),
latestNotifications () {
return this.notifications.slice(0, 10);
}
@ -51,9 +64,6 @@ export default {
}
},
methods: {
...mapActions({
removeNotification: 'notifications/removeNotification'
}),
clearTimeouts () {
for (const uid in this.timeouts) {
clearTimeout(this.timeouts[uid]);

View File

@ -15,7 +15,7 @@
<div>
<div>
<TextEditor
:value.sync="localNotes"
v-model="localNotes"
editor-class="textarea-editor"
mode="markdown"
:auto-focus="true"
@ -29,9 +29,11 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
import { useScratchpadStore } from '@/stores/scratchpad';
export default {
name: 'TheScratchpad',
@ -39,17 +41,26 @@ export default {
ConfirmModal,
TextEditor
},
emits: ['hide'],
setup () {
const applicationStore = useApplicationStore();
const scratchpadStore = useScratchpadStore();
const { notes } = storeToRefs(scratchpadStore);
const { changeNotes } = scratchpadStore;
return {
notes,
hideScratchpad: applicationStore.hideScratchpad,
changeNotes
};
},
data () {
return {
localNotes: '',
debounceTimeout: null
};
},
computed: {
...mapGetters({
notes: 'scratchpad/getNotes'
})
},
watch: {
localNotes () {
clearTimeout(this.debounceTimeout);
@ -63,10 +74,6 @@ export default {
this.localNotes = this.notes;
},
methods: {
...mapActions({
hideScratchpad: 'application/hideScratchpad',
changeNotes: 'scratchpad/changeNotes'
}),
hideModal () {
this.$emit('hide');
}

View File

@ -10,22 +10,23 @@
<ul class="settingbar-elements">
<Draggable
v-model="connections"
:item-key="'uid'"
@start="isDragging = true"
@end="dragStop"
>
<li
v-for="connection in connections"
:key="connection.uid"
draggable="true"
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" class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
</li>
<template #item="{element}">
<li
:draggable="true"
class="settingbar-element btn btn-link ex-tooltip"
:class="{'selected': element.uid === selectedWorkspace}"
@click.stop="selectWorkspace(element.uid)"
@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>
</li>
</template>
</Draggable>
<li
class="settingbar-element btn btn-link ex-tooltip"
@ -55,7 +56,10 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext';
@ -65,6 +69,32 @@ export default {
Draggable,
SettingBarContext
},
setup () {
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const workspacesStore = useWorkspacesStore();
const { updateStatus } = storeToRefs(applicationStore);
const { connections: getConnections } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { showSettingModal, showScratchpad } = applicationStore;
const { getConnectionName, updateConnections } = connectionsStore;
const { getWorkspace, selectWorkspace } = workspacesStore;
return {
applicationStore,
updateStatus,
showSettingModal,
showScratchpad,
getConnections,
getConnectionName,
updateConnections,
selectedWorkspace,
getWorkspace,
selectWorkspace
};
},
data () {
return {
dragElement: null,
@ -76,13 +106,6 @@ export default {
};
},
computed: {
...mapGetters({
getConnections: 'connections/getConnections',
getConnectionName: 'connections/getConnectionName',
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
updateStatus: 'application/getUpdateStatus'
}),
connections: {
get () {
return this.getConnections;
@ -96,12 +119,6 @@ export default {
}
},
methods: {
...mapActions({
updateConnections: 'connections/updateConnections',
showSettingModal: 'application/showSettingModal',
showScratchpad: 'application/showScratchpad',
selectWorkspace: 'workspaces/selectWorkspace'
}),
contextMenu (event, connection) {
this.contextEvent = event;
this.contextConnection = connection;

View File

@ -55,10 +55,26 @@
<script>
import { ipcRenderer } from 'electron';
import { getCurrentWindow } from '@electron/remote';
import { mapGetters } from 'vuex';
import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import { storeToRefs } from 'pinia';
export default {
name: 'TheTitleBar',
setup () {
const { getConnectionName } = useConnectionsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
return {
getConnectionName,
selectedWorkspace,
getWorkspace
};
},
data () {
return {
w: getCurrentWindow(),
@ -68,11 +84,6 @@ export default {
};
},
computed: {
...mapGetters({
getConnectionName: 'connections/getConnectionName',
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
windowTitle () {
if (!this.selectedWorkspace) return '';
if (this.selectedWorkspace === 'NEW') return this.$t('message.createNewConnection');
@ -87,7 +98,7 @@ export default {
created () {
window.addEventListener('resize', this.onResize);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.onResize);
},
methods: {

View File

@ -1,264 +1,265 @@
<template>
<div v-show="isSelected" class="workspace column columns col-gapless">
<WorkspaceExploreBar
v-if="workspace.connectionStatus === 'connected'"
v-if="workspace?.connectionStatus === 'connected'"
:connection="connection"
:is-selected="isSelected"
/>
<div v-if="workspace.connectionStatus === 'connected'" class="workspace-tabs column columns col-gapless">
<div v-if="workspace?.connectionStatus === 'connected'" class="workspace-tabs column columns col-gapless">
<Draggable
ref="tabWrap"
v-model="draggableTabs"
tag="ul"
item-key="uid"
group="tabs"
class="tab tab-block column col-12"
draggable=".tab-draggable"
@mouseover.native="addWheelEvent"
@mouseover="addWheelEvent"
>
<li
v-for="(tab, i) of draggableTabs"
:key="i"
class="tab-item tab-draggable"
draggable="true"
:class="{'active': selectedTab === tab.uid}"
@mousedown.left="selectTab({uid: workspace.uid, tab: tab.uid})"
@mouseup.middle="closeTab(tab)"
>
<a
v-if="tab.type === 'query'"
class="tab-link"
:class="{'badge': tab.isChanged}"
<template #item="{element}">
<li
class="tab-item tab-draggable"
:draggable="true"
:class="{'active': selectedTab === element.uid}"
@mousedown.left="selectTab({uid: workspace.uid, tab: element.uid})"
@mouseup.middle="closeTab(element)"
>
<i class="mdi mdi-18px mdi-code-tags mr-1" />
<span>
<span>{{ tab.content || 'Query' | cutText }} #{{ tab.index }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-if="element.type === 'query'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-18px mdi-code-tags mr-1" />
<span>
<span>{{ cutText(element.content || 'Query') }} #{{ element.index }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'temp-data'"
class="tab-link"
@dblclick="openAsPermanentTab(tab)"
>
<i class="mdi mdi-18px mr-1" :class="tab.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${$t('word.data').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
<span class=" text-italic">{{ tab.elementName | cutText }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'temp-data'"
class="tab-link"
@dblclick="openAsPermanentTab(element)"
>
<i class="mdi mdi-18px mr-1" :class="element.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${$t('word.data').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
<span class=" text-italic">{{ cutText(element.elementName) }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a v-else-if="tab.type === 'data'" class="tab-link">
<i class="mdi mdi-18px mr-1" :class="tab.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${$t('word.data').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a v-else-if="element.type === 'data'" class="tab-link">
<i class="mdi mdi-18px mr-1" :class="element.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${$t('word.data').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ cutText(element.elementName) }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-table'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newTable') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-table'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newTable') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'table-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'table-props'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ cutText(element.elementName) }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'view-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.view`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'view-props'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.view`)}`">
{{ cutText(element.elementName) }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-view'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newView') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-view'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newView') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-trigger'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newTrigger') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-trigger'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newTrigger') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-routine'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newRoutine') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-routine'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newRoutine') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-function'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newFunction') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-function'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newFunction') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-trigger-function'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newTriggerFunction') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-trigger-function'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newTriggerFunction') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'new-scheduler'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newScheduler') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type === 'new-scheduler'"
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ $t('message.newScheduler') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else-if="tab.type.includes('temp-')"
class="tab-link"
:class="{'badge': tab.isChanged}"
@dblclick="openAsPermanentTab(tab)"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
<span class=" text-italic">{{ tab.elementName | cutText }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="element.type.includes('temp-')"
class="tab-link"
:class="{'badge': element.isChanged}"
@dblclick="openAsPermanentTab(element)"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
<span class=" text-italic">{{ cutText(element.elementName) }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
<a
v-else
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
</li>
<a
v-else
class="tab-link"
:class="{'badge': element.isChanged}"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`">
{{ cutText(element.elementName) }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(element)"
/>
</span>
</a>
</li>
</template>
<template #header>
<li
v-if="workspace.customizations.processesList"
@ -318,6 +319,7 @@
<WorkspaceTabQuery
v-if="tab.type==='query'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:is-selected="selectedTab === tab.uid"
:connection="connection"
@ -325,6 +327,7 @@
<WorkspaceTabTable
v-else-if="['temp-data', 'data'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:table="tab.elementName"
@ -334,6 +337,7 @@
<WorkspaceTabNewTable
v-else-if="tab.type === 'new-table'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -342,6 +346,7 @@
<WorkspaceTabPropsTable
v-else-if="tab.type === 'table-props'"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:table="tab.elementName"
@ -350,6 +355,7 @@
<WorkspaceTabNewView
v-else-if="tab.type === 'new-view'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -358,6 +364,7 @@
<WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'"
:key="tab.uid"
:tab-uid="tab.uid"
:is-selected="selectedTab === tab.uid"
:connection="connection"
:view="tab.elementName"
@ -366,6 +373,7 @@
<WorkspaceTabNewTrigger
v-else-if="tab.type === 'new-trigger'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -375,6 +383,7 @@
<WorkspaceTabPropsTrigger
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:trigger="tab.elementName"
@ -383,6 +392,7 @@
<WorkspaceTabNewTriggerFunction
v-else-if="tab.type === 'new-trigger-function'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -392,6 +402,7 @@
<WorkspaceTabPropsTriggerFunction
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:function="tab.elementName"
@ -400,6 +411,7 @@
<WorkspaceTabNewRoutine
v-else-if="tab.type === 'new-routine'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -409,6 +421,7 @@
<WorkspaceTabPropsRoutine
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:routine="tab.elementName"
@ -417,6 +430,7 @@
<WorkspaceTabNewFunction
v-else-if="tab.type === 'new-function'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -426,6 +440,7 @@
<WorkspaceTabPropsFunction
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:function="tab.elementName"
@ -434,6 +449,7 @@
<WorkspaceTabNewScheduler
v-else-if="tab.type === 'new-scheduler'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@ -443,6 +459,7 @@
<WorkspaceTabPropsScheduler
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:scheduler="tab.elementName"
@ -468,9 +485,11 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import Draggable from 'vuedraggable';
import Connection from '@/ipc-api/Connection';
import { useWorkspacesStore } from '@/stores/workspaces';
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState';
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
@ -521,18 +540,37 @@ export default {
ModalProcessesList,
ModalDiscardChanges
},
filters: {
cutText (string) {
const limit = 20;
const escapedString = string.replace(/\s{2,}/g, ' ');
if (escapedString.length > limit)
return `${escapedString.substr(0, limit)}...`;
return escapedString;
}
},
props: {
connection: Object
},
setup () {
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
addWorkspace,
connectWorkspace,
removeConnected,
selectTab,
newTab,
removeTab,
updateTabs
} = workspacesStore;
return {
selectedWorkspace,
getWorkspace,
addWorkspace,
connectWorkspace,
removeConnected,
selectTab,
newTab,
removeTab,
updateTabs
};
},
data () {
return {
hasWheelEvent: false,
@ -541,10 +579,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
@ -583,19 +617,25 @@ export default {
return false;
},
hasTools () {
return this.workspace.customizations.processesList ||
if (!this.workspace.customizations) return false;
else {
return this.workspace.customizations.processesList ||
this.workspace.customizations.usersManagement ||
this.workspace.customizations.variables;
}
}
},
watch: {
queryTabs: function (newVal, oldVal) {
if (newVal.length > oldVal.length) {
setTimeout(() => {
const scroller = this.$refs.tabWrap;
if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
}, 0);
}
queryTabs: {
handler (newVal, oldVal) {
if (newVal.length > oldVal.length) {
setTimeout(() => {
const scroller = this.$refs.tabWrap;
if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
}, 0);
}
},
deep: true
}
},
async created () {
@ -605,19 +645,10 @@ export default {
if (isInitiated)
this.connectWorkspace(this.connection);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addWorkspace: 'workspaces/addWorkspace',
connectWorkspace: 'workspaces/connectWorkspace',
removeConnected: 'workspaces/removeConnected',
selectTab: 'workspaces/selectTab',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
updateTabs: 'workspaces/updateTabs'
}),
addQueryTab () {
this.newTab({ uid: this.connection.uid, type: 'query' });
},
@ -683,6 +714,13 @@ export default {
});
this.hasWheelEvent = true;
}
},
cutText (string) {
const limit = 20;
const escapedString = string.replace(/\s{2,}/g, ' ');
if (escapedString.length > limit)
return `${escapedString.substr(0, limit)}...`;
return escapedString;
}
}
};
@ -809,48 +847,5 @@ export default {
}
}
}
.workspace-query-results {
overflow: auto;
white-space: nowrap;
.table {
width: auto;
border-collapse: separate;
.th {
position: sticky;
top: 0;
border: 2px solid;
border-left: none;
border-bottom-width: 2px;
padding: 0;
font-weight: 700;
font-size: 0.7rem;
z-index: 1;
> div {
padding: 0.1rem 0.2rem;
min-width: -webkit-fill-available;
}
}
.td {
border-right: 2px solid;
border-bottom: 2px solid;
padding: 0 0.2rem;
text-overflow: ellipsis;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
font-size: 0.7rem;
position: relative;
&:focus {
outline: none;
}
}
}
}
}
</style>

View File

@ -50,7 +50,11 @@
<label class="form-label cut-text">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select v-model="connection.client" class="form-select">
<select
id="connection-client"
v-model="connection.client"
class="form-select"
>
<option
v-for="client in clients"
:key="client.slug"
@ -363,6 +367,7 @@
</div>
<div class="panel-footer">
<button
id="connection-test"
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@ -372,6 +377,7 @@
{{ $t('message.testConnection') }}
</button>
<button
id="connection-save"
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy"
@click="saveConnection"
@ -390,10 +396,12 @@
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import { uidGen } from 'common/libs/uidGen';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
@ -403,6 +411,20 @@ export default {
ModalAskCredentials,
BaseUploadInput
},
setup () {
const { addConnection } = useConnectionsStore();
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { connectWorkspace, selectWorkspace } = workspacesStore;
return {
addConnection,
addNotification,
connectWorkspace,
selectWorkspace
};
},
data () {
return {
clients: [
@ -466,12 +488,6 @@ export default {
}, 20);
},
methods: {
...mapActions({
addConnection: 'connections/addConnection',
connectWorkspace: 'workspaces/connectWorkspace',
addNotification: 'notifications/addNotification',
selectWorkspace: 'workspaces/selectWorkspace'
}),
setDefaults () {
this.connection.user = this.customizations.defaultUser;
this.connection.port = this.customizations.defaultPort;

View File

@ -355,6 +355,7 @@
</div>
<div class="panel-footer">
<button
id="connection-test"
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@ -364,6 +365,7 @@
{{ $t('message.testConnection') }}
</button>
<button
id="connection-save"
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy || !hasChanges"
@click="saveConnection"
@ -372,6 +374,7 @@
{{ $t('word.save') }}
</button>
<button
id="connection-connect"
class="btn btn-success d-flex"
:class="{'loading': isConnecting}"
:disabled="isBusy"
@ -391,8 +394,10 @@
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Connection from '@/ipc-api/Connection';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
@ -406,6 +411,17 @@ export default {
props: {
connection: Object
},
setup () {
const { editConnection } = useConnectionsStore();
const { addNotification } = useNotificationsStore();
const { connectWorkspace } = useWorkspacesStore();
return {
editConnection,
addNotification,
connectWorkspace
};
},
data () {
return {
clients: [
@ -441,11 +457,6 @@ export default {
this.localConnection = JSON.parse(JSON.stringify(this.connection));
},
methods: {
...mapActions({
editConnection: 'connections/editConnection',
connectWorkspace: 'workspaces/connectWorkspace',
addNotification: 'notifications/addNotification'
}),
async startConnection () {
await this.saveConnection();
this.isConnecting = true;

View File

@ -25,26 +25,35 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceEmptyState',
emits: ['new-tab'],
setup () {
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const { applicationTheme } = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, changeBreadcrumbs } = workspacesStore;
return {
applicationTheme,
selectedWorkspace,
getWorkspace,
changeBreadcrumbs
};
},
computed: {
...mapGetters({
applicationTheme: 'settings/getApplicationTheme',
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
}
},
created () {
this.changeBreadcrumbs({ schema: this.workspace.breadcrumbs.schema });
},
methods: {
...mapActions({
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
})
}
};
</script>

View File

@ -115,7 +115,12 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import Tables from '@/ipc-api/Tables';
import Views from '@/ipc-api/Views';
@ -143,6 +148,45 @@ export default {
connection: Object,
isSelected: Boolean
},
setup () {
const { getConnectionName } = useConnectionsStore();
const { addNotification } = useNotificationsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const { explorebarSize } = storeToRefs(settingsStore);
const { changeExplorebarSize } = settingsStore;
const {
getWorkspace,
removeConnected: disconnectWorkspace,
refreshStructure,
changeBreadcrumbs,
selectTab,
newTab,
removeTabs,
setSearchTerm,
addLoadingElement,
removeLoadingElement
} = workspacesStore;
return {
getConnectionName,
addNotification,
explorebarSize,
changeExplorebarSize,
getWorkspace,
disconnectWorkspace,
refreshStructure,
changeBreadcrumbs,
selectTab,
newTab,
removeTabs,
setSearchTerm,
addLoadingElement,
removeLoadingElement
};
},
data () {
return {
isRefreshing: false,
@ -174,11 +218,6 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
explorebarSize: 'settings/getExplorebarSize',
getConnectionName: 'connections/getConnectionName'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
@ -222,19 +261,6 @@ export default {
});
},
methods: {
...mapActions({
disconnectWorkspace: 'workspaces/removeConnected',
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
selectTab: 'workspaces/selectTab',
newTab: 'workspaces/newTab',
removeTabs: 'workspaces/removeTabs',
setSearchTerm: 'workspaces/setSearchTerm',
addNotification: 'notifications/addNotification',
changeExplorebarSize: 'settings/changeExplorebarSize',
addLoadingElement: 'workspaces/addLoadingElement',
removeLoadingElement: 'workspaces/removeLoadingElement'
}),
async refresh () {
if (!this.isRefreshing) {
this.isRefreshing = true;

View File

@ -65,7 +65,8 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
import ModalAskParameters from '@/components/ModalAskParameters';
@ -73,6 +74,7 @@ import Triggers from '@/ipc-api/Triggers';
import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceExploreBarMiscContext',
@ -86,6 +88,33 @@ export default {
selectedMisc: Object,
selectedSchema: String
},
emits: ['close-context', 'reload'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
changeBreadcrumbs,
addLoadingElement,
removeLoadingElement,
removeTabs,
newTab
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
changeBreadcrumbs,
addLoadingElement,
removeLoadingElement,
removeTabs,
newTab
};
},
data () {
return {
isDeleteModal: false,
@ -95,10 +124,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
},
@ -122,14 +147,6 @@ export default {
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
addLoadingElement: 'workspaces/addLoadingElement',
removeLoadingElement: 'workspaces/removeLoadingElement',
removeTabs: 'workspaces/removeTabs',
newTab: 'workspaces/newTab'
}),
showDeleteModal () {
this.isDeleteModal = true;
},

View File

@ -42,8 +42,10 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceExploreBarMiscContext',
@ -55,25 +57,40 @@ export default {
selectedMisc: String,
selectedSchema: String
},
emits: [
'open-create-trigger-tab',
'open-create-routine-tab',
'open-create-function-tab',
'open-create-trigger-function-tab',
'open-create-scheduler-tab',
'close-context'
],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, changeBreadcrumbs } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
changeBreadcrumbs
};
},
data () {
return {
localElement: {}
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showDeleteModal () {
this.isDeleteModal = true;
},

View File

@ -243,8 +243,10 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import { formatBytes } from 'common/libs/formatBytes';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceExploreBarSchema',
@ -252,18 +254,45 @@ export default {
database: Object,
connection: Object
},
emits: [
'show-schema-context',
'show-table-context',
'show-misc-context',
'show-misc-folder-context'
],
setup () {
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const { applicationTheme } = storeToRefs(settingsStore);
const {
getLoadedSchemas,
getWorkspace,
getSearchTerm,
changeBreadcrumbs,
addLoadedSchema,
newTab,
refreshSchema
} = workspacesStore;
return {
applicationTheme,
getLoadedSchemas,
getWorkspace,
getSearchTerm,
changeBreadcrumbs,
addLoadedSchema,
newTab,
refreshSchema
};
},
data () {
return {
isLoading: false
};
},
computed: {
...mapGetters({
getLoadedSchemas: 'workspaces/getLoadedSchemas',
getWorkspace: 'workspaces/getWorkspace',
getSearchTerm: 'workspaces/getSearchTerm',
applicationTheme: 'settings/getApplicationTheme'
}),
searchTerm () {
return this.getSearchTerm(this.connection.uid);
},
@ -331,12 +360,6 @@ export default {
}
},
methods: {
...mapActions({
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
addLoadedSchema: 'workspaces/addLoadedSchema',
newTab: 'workspaces/newTab',
refreshSchema: 'workspaces/refreshSchema'
}),
formatBytes,
async selectSchema (schema) {
if (!this.loadedSchemas.has(schema) && !this.isLoading) {

View File

@ -124,7 +124,8 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
import ModalEditSchema from '@/components/ModalEditSchema';
@ -132,6 +133,7 @@ import ModalExportSchema from '@/components/ModalExportSchema';
import ModalImportSchema from '@/components/ModalImportSchema';
import Schema from '@/ipc-api/Schema';
import Application from '@/ipc-api/Application';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceExploreBarSchemaContext',
@ -146,6 +148,35 @@ export default {
contextEvent: MouseEvent,
selectedSchema: String
},
emits: [
'open-create-table-tab',
'open-create-view-tab',
'open-create-trigger-tab',
'open-create-routine-tab',
'open-create-function-tab',
'open-create-trigger-function-tab',
'open-create-scheduler-tab',
'close-context',
'reload'
],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
changeBreadcrumbs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
changeBreadcrumbs
};
},
data () {
return {
isDeleteModal: false,
@ -155,19 +186,11 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
openCreateTableTab () {
this.$emit('open-create-table-tab');
},

View File

@ -72,10 +72,12 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
import Tables from '@/ipc-api/Tables';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceExploreBarTableContext',
@ -88,6 +90,33 @@ export default {
selectedTable: Object,
selectedSchema: String
},
emits: ['close-context', 'duplicate-table', 'reload', 'delete-table'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
newTab,
removeTabs,
addLoadingElement,
removeLoadingElement,
changeBreadcrumbs
} = workspacesStore;
return {
addNotification,
getWorkspace,
newTab,
removeTabs,
addLoadingElement,
removeLoadingElement,
changeBreadcrumbs,
selectedWorkspace
};
},
data () {
return {
isDeleteModal: false,
@ -95,10 +124,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
},
@ -107,14 +132,6 @@ export default {
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
newTab: 'workspaces/newTab',
removeTabs: 'workspaces/removeTabs',
addLoadingElement: 'workspaces/addLoadingElement',
removeLoadingElement: 'workspaces/removeLoadingElement',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showDeleteModal () {
this.isDeleteModal = true;
},

View File

@ -185,7 +185,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localFunction.sql"
v-model="localFunction.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -203,11 +203,13 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
import Functions from '@/ipc-api/Functions';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceTabNewFunction',
@ -217,11 +219,40 @@ export default {
WorkspaceTabPropsFunctionParamsModal
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
@ -235,19 +266,12 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
@ -302,22 +326,13 @@ export default {
this.$refs.firstInput.focus();
}, 100);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;

View File

@ -145,7 +145,7 @@
v-show="isSelected"
:key="`new-${_uid}`"
ref="queryEditor"
:value.sync="localRoutine.sql"
v-model="localRoutine.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -163,11 +163,13 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
import Routines from '@/ipc-api/Routines';
import { storeToRefs } from 'pinia';
export default {
name: 'WorkspaceTabNewRoutine',
@ -177,11 +179,40 @@ export default {
WorkspaceTabPropsRoutineParamsModal
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
@ -195,19 +226,12 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
},
@ -261,22 +285,13 @@ export default {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;

View File

@ -125,7 +125,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localScheduler.sql"
v-model="localScheduler.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -142,7 +142,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
@ -156,11 +158,40 @@ export default {
WorkspaceTabPropsSchedulerTimingModal
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
@ -174,16 +205,9 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
},
@ -237,22 +261,13 @@ export default {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;

View File

@ -165,7 +165,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader';
@ -184,11 +186,42 @@ export default {
WorkspaceTabNewTableEmptyState
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
getDatabaseVariable,
refreshStructure,
setUnsavedChanges,
newTab,
renameTabs,
removeTab,
changeBreadcrumbs
} = workspacesStore;
return {
addNotification,
getWorkspace,
getDatabaseVariable,
refreshStructure,
setUnsavedChanges,
newTab,
renameTabs,
removeTab,
changeBreadcrumbs,
selectedWorkspace
};
},
data () {
return {
isLoading: false,
@ -209,17 +242,9 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
@ -276,19 +301,10 @@ export default {
this.$refs.firstInput.focus();
}, 100);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
renameTabs: 'workspaces/renameTabs',
removeTab: 'workspaces/removeTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async saveChanges () {
if (this.isSaving || !this.isValid) return;
this.isSaving = true;

View File

@ -14,7 +14,8 @@
<script>
export default {
name: 'WorkspaceTabNewTableEmptyState'
name: 'WorkspaceTabNewTableEmptyState',
emits: ['new-field']
};
</script>

View File

@ -122,7 +122,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localTrigger.sql"
v-model="localTrigger.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -132,8 +132,10 @@
</template>
<script>
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor';
import { mapGetters, mapActions } from 'vuex';
import BaseLoader from '@/components/BaseLoader';
import Triggers from '@/ipc-api/Triggers';
@ -144,11 +146,40 @@ export default {
QueryEditor
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
@ -162,16 +193,9 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
customizations () {
return this.workspace.customizations;
},
@ -226,22 +250,13 @@ export default {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
changeEvents (event) {
if (this.customizations.triggerMultipleEvents) {
this.localEvents[event] = !this.localEvents[event];

View File

@ -98,7 +98,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localFunction.sql"
v-model="localFunction.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -108,7 +108,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Functions from '@/ipc-api/Functions';
@ -120,11 +122,40 @@ export default {
QueryEditor
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
changeBreadcrumbs,
setUnsavedChanges,
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
@ -139,19 +170,12 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
@ -202,22 +226,13 @@ export default {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;

View File

@ -111,7 +111,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localView.sql"
v-model="localView.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -121,7 +121,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Views from '@/ipc-api/Views';
@ -133,11 +135,40 @@ export default {
QueryEditor
},
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
setUnsavedChanges,
changeBreadcrumbs,
newTab,
removeTab,
renameTabs
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
setUnsavedChanges,
changeBreadcrumbs,
newTab,
removeTab,
renameTabs
};
},
data () {
return {
isLoading: false,
@ -150,16 +181,9 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
},
@ -209,22 +233,13 @@ export default {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;

View File

@ -197,7 +197,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localFunction.sql"
v-model="localFunction.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -222,7 +222,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
@ -239,11 +241,38 @@ export default {
ModalAskParameters
},
props: {
tabUid: String,
connection: Object,
function: String,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
};
},
data () {
return {
isLoading: false,
@ -258,19 +287,12 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
@ -339,21 +361,13 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getFunctionData () {
if (!this.function) return;

View File

@ -181,10 +181,14 @@ export default {
ConfirmModal
},
props: {
localParameters: Array,
localParameters: {
type: Array,
default: () => []
},
func: String,
workspace: Object
},
emits: ['hide', 'parameters-update'],
data () {
return {
parametersProxy: [],
@ -215,7 +219,7 @@ export default {
this.getModalInnerHeight();
window.addEventListener('resize', this.getModalInnerHeight);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.getModalInnerHeight);
},
methods: {

View File

@ -152,9 +152,8 @@
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
<QueryEditor
v-show="isSelected"
:key="`${routine}-${_uid}`"
ref="queryEditor"
:value.sync="localRoutine.sql"
v-model="localRoutine.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -179,7 +178,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
@ -196,11 +197,38 @@ export default {
ModalAskParameters
},
props: {
tabUid: String,
connection: Object,
routine: String,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
};
},
data () {
return {
isLoading: false,
@ -215,19 +243,12 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
},
@ -284,21 +305,13 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getRoutineData () {
if (!this.routine) return;

View File

@ -181,10 +181,14 @@ export default {
ConfirmModal
},
props: {
localParameters: Array,
localParameters: {
type: Array,
default: () => []
},
routine: String,
workspace: Object
},
emits: ['parameters-update', 'hide'],
data () {
return {
parametersProxy: [],
@ -215,7 +219,7 @@ export default {
this.getModalInnerHeight();
window.addEventListener('resize', this.getModalInnerHeight);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.getModalInnerHeight);
},
methods: {

View File

@ -124,7 +124,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localScheduler.sql"
v-model="localScheduler.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -141,7 +141,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
@ -155,11 +157,38 @@ export default {
WorkspaceTabPropsSchedulerTimingModal
},
props: {
tabUid: String,
connection: Object,
scheduler: String,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
};
},
data () {
return {
isLoading: false,
@ -173,16 +202,9 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
},
@ -236,21 +258,13 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getSchedulerData () {
if (!this.scheduler) return;

View File

@ -138,22 +138,19 @@
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
import { VueMaskDirective } from 'v-mask';
import moment from 'moment';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspaceTabPropsSchedulerTimingModal',
components: {
ConfirmModal
},
directives: {
mask: VueMaskDirective
},
props: {
localOptions: Object,
workspace: Object
},
emits: ['hide', 'options-update'],
data () {
return {
optionsProxy: {},

View File

@ -177,7 +177,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader';
@ -194,11 +196,38 @@ export default {
WorkspaceTabPropsTableForeignModal
},
props: {
tabUid: String,
connection: Object,
isSelected: Boolean,
table: String,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
getDatabaseVariable,
refreshStructure,
renameTabs,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
getDatabaseVariable,
getWorkspace,
selectedWorkspace,
refreshStructure,
setUnsavedChanges,
renameTabs,
changeBreadcrumbs
};
},
data () {
return {
isLoading: false,
@ -219,17 +248,9 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
defaultEngine () {
const engine = this.getDatabaseVariable(this.connection.uid, 'default_storage_engine');
return engine ? engine.value : '';
@ -277,17 +298,10 @@ export default {
this.getFieldsData();
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
renameTabs: 'workspaces/renameTabs',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async getTableOptions (params) {
const db = this.workspace.structure.find(db => db.name === this.schema);
@ -415,7 +429,7 @@ export default {
const localIDs = this.localFields.reduce((acc, curr) => [...acc, curr._antares_id], []);
// Fields Additions
const additions = this.localFields.filter((field, i) => !originalIDs.includes(field._antares_id)).map(field => {
const additions = this.localFields.filter(field => !originalIDs.includes(field._antares_id)).map(field => {
const lI = this.localFields.findIndex(localField => localField._antares_id === field._antares_id);
const after = lI > 0 ? this.localFields[lI - 1].name : false;
return { ...field, after };

View File

@ -56,6 +56,7 @@ export default {
indexTypes: Array,
selectedField: Object
},
emits: ['close-context', 'duplicate-selected', 'delete-selected', 'add-new-index', 'add-to-index'],
computed: {
hasPrimary () {
return this.indexes.some(index => index.type === 'PRIMARY');

View File

@ -105,26 +105,29 @@
ref="resultTable"
:list="fields"
class="tbody"
item-key="_antares_id"
handle=".row-draggable"
>
<TableRow
v-for="row in fields"
:key="row._antares_id"
:row="row"
:indexes="getIndexes(row.name)"
:foreigns="getForeigns(row.name)"
:data-types="dataTypes"
:customizations="customizations"
@contextmenu="contextMenu"
@rename-field="$emit('rename-field', $event)"
/>
<template #item="{element}">
<TableRow
:row="element"
:indexes="getIndexes(element.name)"
:foreigns="getForeigns(element.name)"
:data-types="dataTypes"
:customizations="customizations"
@contextmenu="contextMenu"
@rename-field="$emit('rename-field', $event)"
/>
</template>
</Draggable>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Draggable from 'vuedraggable';
import TableRow from '@/components/WorkspaceTabPropsTableRow';
import TableContext from '@/components/WorkspaceTabPropsTableContext';
@ -147,6 +150,21 @@ export default {
schema: String,
mode: String
},
emits: ['add-new-index', 'add-to-index', 'rename-field', 'duplicate-field', 'remove-field'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace
};
},
data () {
return {
resultsSize: 1000,
@ -157,10 +175,6 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspaceTab: 'workspaces/getWorkspaceTab',
getWorkspace: 'workspaces/getWorkspace'
}),
workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
@ -195,13 +209,10 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeResults);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeResults);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
resizeResults () {
if (this.$refs.resultTable) {
const el = this.$refs.tableWrapper;

View File

@ -202,7 +202,7 @@
</template>
<script>
import { mapActions } from 'vuex';
import { useNotificationsStore } from '@/stores/notifications';
import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables';
import ConfirmModal from '@/components/BaseConfirmModal';
@ -221,6 +221,12 @@ export default {
fields: Array,
workspace: Object
},
emits: ['foreigns-update', 'hide'],
setup () {
const { addNotification } = useNotificationsStore();
return { addNotification };
},
data () {
return {
foreignProxy: [],
@ -259,13 +265,10 @@ export default {
this.getModalInnerHeight();
window.addEventListener('resize', this.getModalInnerHeight);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.getModalInnerHeight);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
confirmForeignsChange () {
this.foreignProxy = this.foreignProxy.filter(foreign =>
foreign.field &&

View File

@ -153,6 +153,7 @@ export default {
workspace: Object,
indexTypes: Array
},
emits: ['hide', 'indexes-update'],
data () {
return {
indexesProxy: [],
@ -181,7 +182,7 @@ export default {
this.getModalInnerHeight();
window.addEventListener('resize', this.getModalInnerHeight);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.getModalInnerHeight);
},
methods: {

View File

@ -343,7 +343,9 @@
</template>
<script>
import { mapGetters } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
@ -358,6 +360,21 @@ export default {
foreigns: Array,
customizations: Object
},
emits: ['contextmenu', 'rename-field'],
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace
};
},
data () {
return {
localRow: {},
@ -375,10 +392,6 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
localLength () {
const localLength = this.localRow.numLength || this.localRow.charLength || this.localRow.datePrecision || this.localRow.numPrecision || 0;
return localLength === true ? null : localLength;

View File

@ -121,7 +121,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localTrigger.sql"
v-model="localTrigger.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -131,8 +131,10 @@
</template>
<script>
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor';
import { mapGetters, mapActions } from 'vuex';
import BaseLoader from '@/components/BaseLoader';
import Triggers from '@/ipc-api/Triggers';
@ -143,11 +145,38 @@ export default {
QueryEditor
},
props: {
tabUid: String,
connection: Object,
trigger: String,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
};
},
data () {
return {
isLoading: false,
@ -161,16 +190,9 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
customizations () {
return this.workspace.customizations;
},
@ -227,21 +249,13 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getTriggerData () {
if (!this.trigger) return;

View File

@ -85,7 +85,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localFunction.sql"
v-model="localFunction.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -102,7 +102,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
@ -117,11 +119,38 @@ export default {
ModalAskParameters
},
props: {
tabUid: String,
connection: Object,
function: String,
isSelected: Boolean,
schema: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
renameTabs,
newTab,
changeBreadcrumbs,
setUnsavedChanges
};
},
data () {
return {
isLoading: false,
@ -136,19 +165,12 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
@ -204,21 +226,13 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getFunctionData () {
if (!this.function) return;

View File

@ -110,7 +110,7 @@
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localView.sql"
v-model="localView.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
@ -120,7 +120,9 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Views from '@/ipc-api/Views';
@ -132,11 +134,36 @@ export default {
QueryEditor
},
props: {
tabUid: String,
connection: Object,
isSelected: Boolean,
schema: String,
view: String
},
setup () {
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
refreshStructure,
renameTabs,
changeBreadcrumbs,
setUnsavedChanges
} = workspacesStore;
return {
addNotification,
selectedWorkspace,
getWorkspace,
refreshStructure,
renameTabs,
changeBreadcrumbs,
setUnsavedChanges
};
},
data () {
return {
isLoading: false,
@ -149,16 +176,9 @@ export default {
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
},
@ -205,20 +225,13 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
renameTabs: 'workspaces/renameTabs'
}),
async getViewData () {
if (!this.view) return;
this.isLoading = true;

View File

@ -3,18 +3,18 @@
v-show="isSelected"
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0"
@keydown.116="runQuery(query)"
@keydown.75="killTabQuery"
@keydown.ctrl.alt.87="clear"
@keydown.ctrl.66="beautify"
@keydown.ctrl.71="openHistoryModal"
@keydown.f5="runQuery(query)"
@keydown.k="killTabQuery"
@keydown.ctrl.alt.w="clear"
@keydown.ctrl.b="beautify"
@keydown.ctrl.g="openHistoryModal"
>
<div class="workspace-query-runner column col-12">
<QueryEditor
v-show="isSelected"
ref="queryEditor"
v-model="query"
:auto-focus="true"
:value.sync="query"
:workspace="workspace"
:schema="breadcrumbsSchema"
:is-selected="isSelected"
@ -187,8 +187,11 @@
</template>
<script>
import { storeToRefs } from 'pinia';
import { format } from 'sql-formatter';
import { mapGetters, mapActions } from 'vuex';
import { useHistoryStore } from '@/stores/history';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
@ -208,10 +211,36 @@ export default {
},
mixins: [tableTabs],
props: {
tabUid: String,
connection: Object,
tab: Object,
isSelected: Boolean
},
setup () {
const { getHistoryByWorkspace, saveHistory } = useHistoryStore();
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const {
getWorkspace,
changeBreadcrumbs,
updateTabContent,
setUnsavedChanges
} = workspacesStore;
return {
getHistoryByWorkspace,
saveHistory,
addNotification,
selectedWorkspace,
getWorkspace,
changeBreadcrumbs,
updateTabContent,
setUnsavedChanges
};
},
data () {
return {
query: '',
@ -230,17 +259,9 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
getHistoryByWorkspace: 'history/getHistoryByWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
breadcrumbsSchema () {
return this.workspace.breadcrumbs.schema || null;
},
@ -296,7 +317,7 @@ export default {
if (this.tab.autorun)
this.runQuery(this.query);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
const params = {
uid: this.connection.uid,
@ -305,13 +326,6 @@ export default {
Schema.destroyConnectionToCommit(params);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
updateTabContent: 'workspaces/updateTabContent',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
saveHistory: 'history/saveHistory'
}),
async runQuery (query) {
if (!query || this.isQuering) return;
this.isQuering = true;

View File

@ -4,8 +4,8 @@
class="vscroll no-outline"
tabindex="0"
:style="{'height': resultsSize+'px'}"
@keyup.46="showDeleteConfirmModal"
@keydown.ctrl.65="selectAllRows($event)"
@keyup.delete="showDeleteConfirmModal"
@keydown.ctrl.a="selectAllRows($event)"
@keydown.esc="deselectRows"
>
<TableContext
@ -68,7 +68,7 @@
:visible-height="resultsSize"
:scroll-element="scrollElement"
>
<template slot-scope="{ items }">
<template #default="{ items }">
<WorkspaceTabQueryTableRow
v-for="row in items"
:key="row._antares_id"
@ -107,14 +107,17 @@
</template>
<script>
import { storeToRefs } from 'pinia';
import { uidGen } from 'common/libs/uidGen';
import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import arrayToFile from '../libs/arrayToFile';
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow';
import TableContext from '@/components/WorkspaceTabQueryTableContext';
import ConfirmModal from '@/components/BaseConfirmModal';
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
export default {
@ -132,6 +135,20 @@ export default {
isSelected: Boolean,
elementType: { type: String, default: 'table' }
},
emits: ['update-field', 'delete-selected', 'hard-sort'],
setup () {
const { addNotification } = useNotificationsStore();
const settingsStore = useSettingsStore();
const { getWorkspace } = useWorkspacesStore();
const { dataTabLimit: pageSize } = storeToRefs(settingsStore);
return {
addNotification,
pageSize,
getWorkspace
};
},
data () {
return {
resultsSize: 0,
@ -149,10 +166,6 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
pageSize: 'settings/getDataTabLimit'
}),
workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
@ -256,13 +269,10 @@ export default {
mounted () {
window.addEventListener('resize', this.resizeResults);
},
destroyed () {
unmounted () {
window.removeEventListener('resize', this.resizeResults);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
fieldType (cKey) {
let type = 'unknown';
const field = this.fields.filter(field => field.name === cKey)[0];

View File

@ -61,6 +61,7 @@ export default {
selectedRows: Array,
selectedCell: Object
},
emits: ['show-delete-modal', 'close-context', 'set-null', 'copy-cell', 'copy-row'],
methods: {
showConfirmModal () {
this.$emit('show-delete-modal');

View File

@ -18,11 +18,11 @@
class="cell-content"
:class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON($event, col, cKey)"
>{{ col | typeFormat(fields[cKey].type.toLowerCase(), fields[cKey].length) | cutText }}</span>
>{{ cutText(typeFormat(col, fields[cKey].type.toLowerCase(), fields[cKey].length)) }}</span>
<ForeignKeySelect
v-else-if="isForeignKey(cKey)"
v-model="editingContent"
class="editable-field"
:value.sync="editingContent"
:key-usage="getKeyUsage(cKey)"
size="small"
@blur="editOFF"
@ -85,7 +85,7 @@
<div class="mb-2">
<div>
<TextEditor
:value.sync="editingContent"
v-model="editingContent"
editor-class="textarea-editor"
:mode="editorMode"
/>
@ -152,7 +152,7 @@
</template>
<template #body>
<div class="mb-2">
<transition name="jump-down">
<Transition name="jump-down">
<div v-if="contentInfo.size">
<img
v-if="isImage"
@ -173,10 +173,10 @@
</button>
</div>
</div>
</transition>
</Transition>
<div class="editor-field-info">
<div>
<b>{{ $t('word.size') }}</b>: {{ editingContent.length | formatBytes }}<br>
<b>{{ $t('word.size') }}</b>: {{ formatBytes(editingContent.length) }}<br>
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
</div>
<div><b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}</div>
@ -219,7 +219,6 @@ import {
SPATIAL,
IS_MULTI_SPATIAL
} from 'common/fieldTypes';
import { VueMaskDirective } from 'v-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
import BaseMap from '@/components/BaseMap';
@ -233,61 +232,6 @@ export default {
ForeignKeySelect,
BaseMap
},
directives: {
mask: VueMaskDirective
},
filters: {
formatBytes,
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 128 ? `${val.substring(0, 128)}[...]` : val;
},
typeFormat (val, type, precision) {
if (!val) return val;
type = type.toUpperCase();
if (DATE.includes(type))
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
if (DATETIME.includes(type)) {
if (typeof val === 'string')
return val;
let datePrecision = '';
for (let i = 0; i < precision; i++)
datePrecision += i === 0 ? '.S' : 'S';
return moment(val).isValid() ? moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`) : val;
}
if (BLOB.includes(type)) {
const buff = Buffer.from(val);
if (!buff.length) return '';
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
}
if (BIT.includes(type)) {
if (typeof val === 'number') val = [val];
const hex = Buffer.from(val).toString('hex');
const bitString = hexToBinary(hex);
return parseInt(bitString).toString().padStart(precision, '0');
}
if (ARRAY.includes(type)) {
if (Array.isArray(val))
return JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
return val;
}
if (SPATIAL.includes(type))
return val;
return typeof val === 'object' ? JSON.stringify(val) : val;
}
},
props: {
row: Object,
fields: Object,
@ -295,6 +239,7 @@ export default {
itemHeight: Number,
elementType: { type: String, default: 'table' }
},
emits: ['update-field', 'select-row', 'contextmenu'],
data () {
return {
isInlineEditor: {},
@ -449,15 +394,15 @@ export default {
window.addEventListener('keydown', this.onKey);
const type = this.fields[field].type.toUpperCase(); ;
this.originalContent = this.$options.filters.typeFormat(content, type, this.fields[field].length);
const type = this.fields[field].type.toUpperCase();
this.originalContent = this.typeFormat(content, type, this.fields[field].length);
this.editingType = type;
this.editingField = field;
this.editingLength = this.fields[field].length;
if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type)) {
this.isTextareaEditor = true;
this.editingContent = this.$options.filters.typeFormat(content, type);
this.editingContent = this.typeFormat(content, type);
return;
}
@ -465,7 +410,7 @@ export default {
if (content) {
this.isMultiSpatial = IS_MULTI_SPATIAL.includes(type);
this.isMapModal = true;
this.editingContent = this.$options.filters.typeFormat(content, type);
this.editingContent = this.typeFormat(content, type);
}
return;
}
@ -513,7 +458,7 @@ export default {
this.editingContent = this.editingContent.slice(0, -1);
}
if (this.editingContent === this.$options.filters.typeFormat(this.originalContent, this.editingType, this.editingLength)) return;// If not changed
if (this.editingContent === this.typeFormat(this.originalContent, this.editingType, this.editingLength)) return;// If not changed
content = this.editingContent;
}
@ -590,6 +535,56 @@ export default {
this.editingField = null;
window.removeEventListener('keydown', this.onKey);
}
},
formatBytes,
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 128 ? `${val.substring(0, 128)}[...]` : val;
},
typeFormat (val, type, precision) {
if (!val) return val;
type = type.toUpperCase();
if (DATE.includes(type))
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
if (DATETIME.includes(type)) {
if (typeof val === 'string')
return val;
let datePrecision = '';
for (let i = 0; i < precision; i++)
datePrecision += i === 0 ? '.S' : 'S';
return moment(val).isValid() ? moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`) : val;
}
if (BLOB.includes(type)) {
const buff = Buffer.from(val);
if (!buff.length) return '';
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
}
if (BIT.includes(type)) {
if (typeof val === 'number') val = [val];
const hex = Buffer.from(val).toString('hex');
const bitString = hexToBinary(hex);
return parseInt(bitString).toString().padStart(precision, '0');
}
if (ARRAY.includes(type)) {
if (Array.isArray(val))
return JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
return val;
}
if (SPATIAL.includes(type))
return val;
return typeof val === 'object' ? JSON.stringify(val) : val;
}
}
};

View File

@ -117,14 +117,14 @@
<i class="mdi mdi-timer-sand mdi-rotate-180 pr-1" /> <b>{{ results[0].duration / 1000 }}s</b>
</div>
<div v-if="results.length && results[0].rows">
{{ $t('word.results') }}: <b>{{ results[0].rows.length | localeString }}</b>
{{ $t('word.results') }}: <b>{{ localeString(results[0].rows.length) }}</b>
</div>
<div v-if="hasApproximately || (page > 1 && approximateCount)">
{{ $t('word.total') }}: <b
:title="!customizations.tableRealCount ? $t('word.approximately') : ''"
>
<span v-if="!customizations.tableRealCount"></span>
{{ approximateCount | localeString }}
{{ localeString(approximateCount) }}
</b>
</div>
<div class="d-flex" :title="$t('word.schema')">
@ -176,13 +176,16 @@
</template>
<script>
import { storeToRefs } from 'pinia';
import Tables from '@/ipc-api/Tables';
import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
import WorkspaceTabTableFilters from '@/components/WorkspaceTabTableFilters';
import ModalNewTableRow from '@/components/ModalNewTableRow';
import ModalFakerRows from '@/components/ModalFakerRows';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
export default {
@ -194,12 +197,6 @@ export default {
ModalNewTableRow,
ModalFakerRows
},
filters: {
localeString (val) {
if (val !== null)
return val.toLocaleString();
}
},
mixins: [tableTabs],
props: {
connection: Object,
@ -208,6 +205,24 @@ export default {
schema: String,
elementType: String
},
setup () {
const { addNotification } = useNotificationsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const { dataTabLimit: limit } = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { changeBreadcrumbs, getWorkspace } = workspacesStore;
return {
addNotification,
limit,
selectedWorkspace,
changeBreadcrumbs,
getWorkspace
};
},
data () {
return {
tabUid: 'data', // ???
@ -228,11 +243,6 @@ export default {
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
limit: 'settings/getDataTabLimit'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
@ -310,15 +320,11 @@ export default {
this.getTableData();
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
clearInterval(this.refreshInterval);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async getTableData () {
if (!this.table || !this.isSelected) return;
this.isQuering = true;
@ -335,8 +341,8 @@ export default {
table: this.table,
limit: this.limit,
page: this.page,
sortParams: this.sortParams,
where: this.filters || []
sortParams: { ...this.sortParams },
where: [...this.filters] || []
};
try { // Table data
@ -458,6 +464,10 @@ export default {
updateFilters (clausoles) {
this.filters = clausoles;
this.getTableData();
},
localeString (val) {
if (val !== null)
return val.toLocaleString();
}
}
};

View File

@ -79,6 +79,7 @@ export default {
fields: Array,
connClient: String
},
emits: ['filter-change', 'filter'],
data () {
return {
rows: [],

View File

@ -0,0 +1,64 @@
// TODO: unfinished
import Tables from '@/ipc-api/Tables';
import { ref } from 'vue';
export default function useResultTables (uid, reloadTable, addNotification) {
const tableRef = ref(null);
const isQuering = ref(false);
async function updateField (payload) {
isQuering.value = true;
const params = {
uid: uid,
...payload
};
try {
const { status, response } = await Tables.updateTableCell(params);
if (status === 'success') {
if (response.reload)// Needed for blob fields
reloadTable();
else
tableRef.applyUpdate(payload);
}
else
addNotification({ status: 'error', message: response });
}
catch (err) {
addNotification({ status: 'error', message: err.stack });
}
isQuering.value = false;
}
async function deleteSelected (payload) {
isQuering.value = true;
const params = {
uid: uid,
...payload
};
try {
const { status, response } = await Tables.deleteTableRows(params);
isQuering.value = false;
if (status === 'success')
reloadTable();
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
isQuering.value = false;
}
}
return {
tableRef,
isQuering,
updateField,
deleteSelected
};
}

View File

@ -1,9 +1,7 @@
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import { createI18n } from 'vue-i18n';
Vue.use(VueI18n);
const i18n = new VueI18n({
const i18n = createI18n({
fallbackLocale: 'en-US',
messages: {
'en-US': require('./en-US'),
'it-IT': require('./it-IT'),

View File

@ -1,20 +1,28 @@
'use strict';
import Vue from 'vue';
import { createApp } from 'vue';
import '@mdi/font/css/materialdesignicons.css';
import 'leaflet/dist/leaflet.css';
import '@/scss/main.scss';
import { VueMaskDirective } from 'v-mask';
import { useSettingsStore } from '@/stores/settings';
import App from '@/App.vue';
import store from '@/store';
import { pinia } from '@/stores';
import i18n from '@/i18n';
i18n.locale = store.state.settings.locale;
Vue.config.productionTip = false;
// https://github.com/probil/v-mask/issues/498#issuecomment-827027834
const vMaskV2 = VueMaskDirective;
const vMaskV3 = {
beforeMount: vMaskV2.bind,
updated: vMaskV2.componentUpdated,
unmounted: vMaskV2.unbind
};
new Vue({
render: h => h(App),
store,
i18n
}).$mount('#app');
createApp(App)
.directive('mask', vMaskV3)
.use(pinia)
.use(i18n)
.mount('#app');
const { locale } = useSettingsStore();
i18n.global.locale = locale;

View File

@ -1,13 +1,14 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getKey (params) {
return ipcRenderer.sendSync('get-key', params);
return ipcRenderer.sendSync('get-key', unproxify(params));
}
static showOpenDialog (options) {
return ipcRenderer.invoke('showOpenDialog', options);
return ipcRenderer.invoke('show-open-dialog', unproxify(options));
}
static getDownloadPathDirectory () {

View File

@ -1,17 +1,17 @@
'use strict';
import { ipcRenderer } from 'electron';
import connStringConstruct from '../libs/connStringDecode';
import { unproxify } from '../libs/unproxify';
export default class {
static makeTest (params) {
params = connStringConstruct(params);
return ipcRenderer.invoke('test-connection', params);
return ipcRenderer.invoke('test-connection', unproxify(params));
}
static connect (params) {
params = connStringConstruct(params);
return ipcRenderer.invoke('connect', params);
return ipcRenderer.invoke('connect', unproxify(params));
}
static checkConnection (uid) {

View File

@ -1,28 +1,29 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getFunctionInformations (params) {
return ipcRenderer.invoke('get-function-informations', params);
return ipcRenderer.invoke('get-function-informations', unproxify(params));
}
static dropFunction (params) {
return ipcRenderer.invoke('drop-function', params);
return ipcRenderer.invoke('drop-function', unproxify(params));
}
static alterFunction (params) {
return ipcRenderer.invoke('alter-function', params);
return ipcRenderer.invoke('alter-function', unproxify(params));
}
static alterTriggerFunction (params) {
return ipcRenderer.invoke('alter-trigger-function', params);
return ipcRenderer.invoke('alter-trigger-function', unproxify(params));
}
static createFunction (params) {
return ipcRenderer.invoke('create-function', params);
return ipcRenderer.invoke('create-function', unproxify(params));
}
static createTriggerFunction (params) {
return ipcRenderer.invoke('create-trigger-function', params);
return ipcRenderer.invoke('create-trigger-function', unproxify(params));
}
}

View File

@ -1,20 +1,21 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getRoutineInformations (params) {
return ipcRenderer.invoke('get-routine-informations', params);
return ipcRenderer.invoke('get-routine-informations', unproxify(params));
}
static dropRoutine (params) {
return ipcRenderer.invoke('drop-routine', params);
return ipcRenderer.invoke('drop-routine', unproxify(params));
}
static alterRoutine (params) {
return ipcRenderer.invoke('alter-routine', params);
return ipcRenderer.invoke('alter-routine', unproxify(params));
}
static createRoutine (params) {
return ipcRenderer.invoke('create-routine', params);
return ipcRenderer.invoke('create-routine', unproxify(params));
}
}

View File

@ -1,24 +1,24 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getSchedulerInformations (params) {
return ipcRenderer.invoke('get-scheduler-informations', params);
return ipcRenderer.invoke('get-scheduler-informations', unproxify(params));
}
static dropScheduler (params) {
return ipcRenderer.invoke('drop-scheduler', params);
return ipcRenderer.invoke('drop-scheduler', unproxify(params));
}
static alterScheduler (params) {
return ipcRenderer.invoke('alter-scheduler', params);
return ipcRenderer.invoke('alter-scheduler', unproxify(params));
}
static createScheduler (params) {
return ipcRenderer.invoke('create-scheduler', params);
return ipcRenderer.invoke('create-scheduler', unproxify(params));
}
static toggleScheduler (params) {
return ipcRenderer.invoke('toggle-scheduler', params);
return ipcRenderer.invoke('toggle-scheduler', unproxify(params));
}
}

View File

@ -1,25 +1,26 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static createSchema (params) {
return ipcRenderer.invoke('create-schema', params);
return ipcRenderer.invoke('create-schema', unproxify(params));
}
static updateSchema (params) {
return ipcRenderer.invoke('update-schema', params);
return ipcRenderer.invoke('update-schema', unproxify(params));
}
static getDatabaseCollation (params) {
return ipcRenderer.invoke('get-schema-collation', params);
return ipcRenderer.invoke('get-schema-collation', unproxify(params));
}
static deleteSchema (params) {
return ipcRenderer.invoke('delete-schema', params);
return ipcRenderer.invoke('delete-schema', unproxify(params));
}
static getStructure (params) {
return ipcRenderer.invoke('get-structure', params);
return ipcRenderer.invoke('get-structure', unproxify(params, false));
}
static getCollations (uid) {
@ -43,35 +44,35 @@ export default class {
}
static killProcess (params) {
return ipcRenderer.invoke('kill-process', params);
return ipcRenderer.invoke('kill-process', unproxify(params));
}
static killTabQuery (params) {
return ipcRenderer.invoke('kill-tab-query', params);
return ipcRenderer.invoke('kill-tab-query', unproxify(params));
}
static commitTab (params) {
return ipcRenderer.invoke('commit-tab', params);
return ipcRenderer.invoke('commit-tab', unproxify(params));
}
static rollbackTab (params) {
return ipcRenderer.invoke('rollback-tab', params);
return ipcRenderer.invoke('rollback-tab', unproxify(params));
}
static destroyConnectionToCommit (params) {
return ipcRenderer.invoke('destroy-connection-to-commit', params);
return ipcRenderer.invoke('destroy-connection-to-commit', unproxify(params));
}
static useSchema (params) {
return ipcRenderer.invoke('use-schema', params);
return ipcRenderer.invoke('use-schema', unproxify(params));
}
static rawQuery (params) {
return ipcRenderer.invoke('raw-query', params);
return ipcRenderer.invoke('raw-query', unproxify(params));
}
static export (params) {
return ipcRenderer.invoke('export', params);
return ipcRenderer.invoke('export', unproxify(params));
}
static abortExport () {
@ -79,7 +80,7 @@ export default class {
}
static import (params) {
return ipcRenderer.invoke('import-sql', params);
return ipcRenderer.invoke('import-sql', unproxify(params));
}
static abortImport () {

View File

@ -1,68 +1,69 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getTableColumns (params) {
return ipcRenderer.invoke('get-table-columns', params);
return ipcRenderer.invoke('get-table-columns', unproxify(params));
}
static getTableData (params) {
return ipcRenderer.invoke('get-table-data', params);
return ipcRenderer.invoke('get-table-data', unproxify(params));
}
static getTableApproximateCount (params) {
return ipcRenderer.invoke('get-table-count', params);
return ipcRenderer.invoke('get-table-count', unproxify(params));
}
static getTableOptions (params) {
return ipcRenderer.invoke('get-table-options', params);
return ipcRenderer.invoke('get-table-options', unproxify(params));
}
static getTableIndexes (params) {
return ipcRenderer.invoke('get-table-indexes', params);
return ipcRenderer.invoke('get-table-indexes', unproxify(params));
}
static getKeyUsage (params) {
return ipcRenderer.invoke('get-key-usage', params);
return ipcRenderer.invoke('get-key-usage', unproxify(params));
}
static updateTableCell (params) {
return ipcRenderer.invoke('update-table-cell', params);
return ipcRenderer.invoke('update-table-cell', unproxify(params));
}
static deleteTableRows (params) {
return ipcRenderer.invoke('delete-table-rows', params);
return ipcRenderer.invoke('delete-table-rows', unproxify(params));
}
static insertTableRows (params) {
return ipcRenderer.invoke('insert-table-rows', params);
return ipcRenderer.invoke('insert-table-rows', unproxify(params));
}
static insertTableFakeRows (params) {
return ipcRenderer.invoke('insert-table-fake-rows', params);
return ipcRenderer.invoke('insert-table-fake-rows', unproxify(params));
}
static getForeignList (params) {
return ipcRenderer.invoke('get-foreign-list', params);
return ipcRenderer.invoke('get-foreign-list', unproxify(params));
}
static createTable (params) {
return ipcRenderer.invoke('create-table', params);
return ipcRenderer.invoke('create-table', unproxify(params));
}
static alterTable (params) {
return ipcRenderer.invoke('alter-table', params);
return ipcRenderer.invoke('alter-table', unproxify(params));
}
static duplicateTable (params) {
return ipcRenderer.invoke('duplicate-table', params);
return ipcRenderer.invoke('duplicate-table', unproxify(params));
}
static truncateTable (params) {
return ipcRenderer.invoke('truncate-table', params);
return ipcRenderer.invoke('truncate-table', unproxify(params));
}
static dropTable (params) {
return ipcRenderer.invoke('drop-table', params);
return ipcRenderer.invoke('drop-table', unproxify(params));
}
}

View File

@ -1,24 +1,25 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getTriggerInformations (params) {
return ipcRenderer.invoke('get-trigger-informations', params);
return ipcRenderer.invoke('get-trigger-informations', unproxify(params));
}
static dropTrigger (params) {
return ipcRenderer.invoke('drop-trigger', params);
return ipcRenderer.invoke('drop-trigger', unproxify(params));
}
static alterTrigger (params) {
return ipcRenderer.invoke('alter-trigger', params);
return ipcRenderer.invoke('alter-trigger', unproxify(params));
}
static createTrigger (params) {
return ipcRenderer.invoke('create-trigger', params);
return ipcRenderer.invoke('create-trigger', unproxify(params));
}
static toggleTrigger (params) {
return ipcRenderer.invoke('toggle-trigger', params);
return ipcRenderer.invoke('toggle-trigger', unproxify(params));
}
}

View File

@ -1,8 +1,9 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getUsers (params) {
return ipcRenderer.invoke('get-users', params);
return ipcRenderer.invoke('get-users', unproxify(params));
}
}

View File

@ -1,20 +1,21 @@
'use strict';
import { ipcRenderer } from 'electron';
import { unproxify } from '../libs/unproxify';
export default class {
static getViewInformations (params) {
return ipcRenderer.invoke('get-view-informations', params);
return ipcRenderer.invoke('get-view-informations', unproxify(params));
}
static dropView (params) {
return ipcRenderer.invoke('drop-view', params);
return ipcRenderer.invoke('drop-view', unproxify(params));
}
static alterView (params) {
return ipcRenderer.invoke('alter-view', params);
return ipcRenderer.invoke('alter-view', unproxify(params));
}
static createView (params) {
return ipcRenderer.invoke('create-view', params);
return ipcRenderer.invoke('create-view', unproxify(params));
}
}

View File

@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-this-alias */
/* eslint-disable no-labels */
/* eslint-disable no-return-assign */
/* eslint-disable no-cond-assign */

View File

@ -0,0 +1,21 @@
import { toRaw } from 'vue';
/**
* @param {*} val
* @param {Boolean} json converts the value in JSON object (default true)
*/
export function unproxify (val, json = true) {
if (json)// JSON conversion
return JSON.parse(JSON.stringify(val));
else if (Array.isArray(val))// If array
return toRaw(val);
else if (typeof val === 'object') { // If object
const result = {};
for (const key in val)
result[key] = toRaw(val[key]);
return result;
}
else
return toRaw(val);
}

Some files were not shown because too many files have changed in this diff Show More