1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

Compare commits

...

113 Commits

Author SHA1 Message Date
fe39c1d388 chore(release): 0.5.17 2022-09-22 09:34:28 +02:00
d114f8a651 feat: added more editor font sizes, closes #440 2022-09-21 10:33:44 +02:00
84168d1d75 fix: editor font size doesn't change on new tabs, fixes #442 2022-09-15 19:03:18 +02:00
498a9b48e2 fix: empty definer when editing a view, fixes #437 2022-09-15 18:58:19 +02:00
01f607cd40 fix: "run or reload" shortcut triggers on all connections open 2022-09-08 12:01:57 +02:00
efe134a059 fix: cant run procedures with parameters from leftbar 2022-09-07 18:18:15 +02:00
40cb4dd98d chore: minor changes on macos artifacts action 2022-08-27 09:27:07 +02:00
a142d3c4d7 fix(MacOS): empty options on macos menubar 2022-08-27 09:04:11 +02:00
89da957a49 ci: github action for macos artifacts 2022-08-27 09:02:09 +02:00
05c2f9836c chore(release): 0.5.16 2022-08-26 18:49:51 +02:00
ebc325ae0c fix: issue updating datetime cells with null value, closes #423 2022-08-26 18:48:26 +02:00
39326eb52e fix: unable to set null or delete rows without primary key 2022-08-26 18:31:47 +02:00
df681147aa fix: ts exceptions 2022-08-24 10:23:03 +02:00
ffc645ba5e fix: CTRL+Right/Left not working on text editor, closes #427 2022-08-24 10:04:25 +02:00
1fb1205319 chore: update README.md 2022-08-20 09:18:46 +02:00
c90ab0e880 fix(UI): wrong position of fields resizable area 2022-08-19 16:54:56 +02:00
9dc700e13e fix(UI): editor themes group not visible in select element 2022-08-18 16:10:29 +02:00
6950d0ce5a chore(release): 0.5.15 2022-08-17 16:21:37 +02:00
4df14c3693 feat: dynamic shortcut suggestions on empty query tabs 2022-08-17 16:20:36 +02:00
c05be8304f perf(translation): updated italian translation 2022-08-17 15:29:12 +02:00
31c575dad9 Merge pull request #405 from antares-sql/custom-shortcuts
Shortcuts customization
2022-08-17 11:12:49 +02:00
040657d5ca Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-17 11:05:09 +02:00
5043fafa93 feat: added more events in shortcuts setting 2022-08-17 10:00:23 +02:00
8eb127e458 feat: ability to edit shortcuts 2022-08-16 18:03:38 +02:00
d044a02cb7 feat: ability to add new shortcuts 2022-08-16 13:10:20 +02:00
8cb2c197c8 chore: suppress some stylelint warns 2022-08-15 18:14:51 +02:00
c50d17e82b fix: startup exception 2022-08-15 18:13:53 +02:00
Cleverson
7c186d2dee Update pt-BR.ts 2022-08-12 14:59:22 +02:00
0f219cf9b7 perf: improved keypress detector 2022-08-12 12:40:35 +02:00
75c5a34095 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-11 11:22:25 +02:00
48877534d1 feat(UI): connection name on left bar, closes #382 #414 2022-08-11 11:14:43 +02:00
c22413fde9 feat: delete shortcuts and restore defaults 2022-08-10 17:59:59 +02:00
77ab8d8a03 build: minor change in ts config 2022-08-09 17:28:33 +02:00
4386c6ab95 chore(release): 0.5.14 2022-08-09 16:21:55 +02:00
19205e0736 style: general lint fix 2022-08-09 16:18:21 +02:00
4fc4ddd1d6 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-09 16:10:26 +02:00
49b63bc6f2 feat(UI): shortcuts setting UI improved 2022-08-09 16:10:08 +02:00
44eb507a12 fix: unable to open settingbar context menu 2022-08-09 16:04:27 +02:00
1590ffaff0 build: icons for linux builds 2022-08-09 10:48:53 +02:00
3c1bae540f chore(release): 0.5.13 2022-08-09 09:13:11 +02:00
44bb75bc60 feat: list of available shortcuts in settings window 2022-08-08 16:44:40 +02:00
8bb5bb93cf Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-08 11:46:08 +02:00
f64a12a8e9 fix(MySQL): error with ANSI sql_mode 2022-08-07 19:00:12 +02:00
da25823868 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-05 17:08:48 +02:00
a9fcfd57ec refactor: improved vue-i18n implementation 2022-08-05 17:03:16 +02:00
e2307341f3 Merge pull request #397 from antares-sql/dependabot/npm_and_yarn/vue-i18n-9.2.0
build(deps): bump vue-i18n from 9.1.10 to 9.2.0
2022-08-05 13:17:06 +02:00
09a372e96d refactor: vue-i18n ts improvements 2022-08-05 13:06:08 +02:00
f4da28cca0 refactor: vue-i18n ts improvements 2022-08-05 12:57:56 +02:00
89745b7391 Merge pull request #398 from antares-sql/dependabot/npm_and_yarn/mdi/font-7.0.96
build(deps): bump @mdi/font from 6.9.96 to 7.0.96
2022-08-05 12:26:44 +02:00
104b7c928b fix: set legacy: false 2022-08-05 12:25:14 +02:00
dependabot[bot]
427360d826 build(deps): bump sql-formatter from 4.0.2 to 8.2.0
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 4.0.2 to 8.2.0.
- [Release notes](https://github.com/sql-formatter-org/sql-formatter/releases)
- [Changelog](https://github.com/sql-formatter-org/sql-formatter/blob/master/.release-it.json)
- [Commits](https://github.com/sql-formatter-org/sql-formatter/compare/v4.0.2...v8.2.0)

---
updated-dependencies:
- dependency-name: sql-formatter
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-05 12:18:53 +02:00
e29d86b409 refactor: shortcuts registration via ShortcutRegister class 2022-08-05 12:07:19 +02:00
Giulio Ganci
0bfa14e1c9 feat: new macos icon 2022-08-03 12:57:17 +02:00
88ba55ec02 build: added deb arm targets 2022-08-03 11:33:26 +02:00
aaff4cf4fe refactor: filter functions as composable 2022-08-03 11:10:16 +02:00
35c54aee84 chore: update README.md 2022-08-02 13:17:13 +02:00
be2e9b21f5 chore: update dependabot.yml 2022-08-02 10:55:19 +02:00
2262278393 Merge branch 'master' of https://github.com/antares-sql/antares 2022-08-02 10:51:43 +02:00
531e17889a chore: update dependabot.yml 2022-08-02 10:51:40 +02:00
a07ed58004 chore: update CONTRIBUTING.md 2022-08-02 10:41:05 +02:00
00dc59a76d ci: remove old ci configs 2022-08-02 10:37:27 +02:00
2f883bfeb2 ci: new ci config 2022-08-02 10:10:20 +02:00
7ff16fccce ci: minor change 2022-08-01 22:32:19 +02:00
dependabot[bot]
3625fbc1b0 build(deps): bump @mdi/font from 6.9.96 to 7.0.96
Bumps [@mdi/font](https://github.com/Templarian/MaterialDesign-Webfont) from 6.9.96 to 7.0.96.
- [Release notes](https://github.com/Templarian/MaterialDesign-Webfont/releases)
- [Commits](https://github.com/Templarian/MaterialDesign-Webfont/compare/v6.9.96...v7.0.96)

---
updated-dependencies:
- dependency-name: "@mdi/font"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 19:10:35 +00:00
dependabot[bot]
deee0d637b build(deps): bump vue-i18n from 9.1.10 to 9.2.0
Bumps [vue-i18n](https://github.com/intlify/vue-i18n-next/tree/HEAD/packages/vue-i18n) from 9.1.10 to 9.2.0.
- [Release notes](https://github.com/intlify/vue-i18n-next/releases)
- [Changelog](https://github.com/intlify/vue-i18n-next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n-next/commits/v9.2.0/packages/vue-i18n)

---
updated-dependencies:
- dependency-name: vue-i18n
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 19:09:53 +00:00
8c6950cebd refactor: minor refactors 2022-08-01 18:13:14 +02:00
46167e4473 build: general ci config update 2022-08-01 18:06:57 +02:00
c32a4415d1 Update README.md 2022-07-31 10:43:32 +02:00
1c3d7aa30b feat: copy row as CSV, closes #394 2022-07-29 18:53:39 +02:00
664d18efc1 chore: update README.md 2022-07-26 17:21:43 +02:00
cc941dfc04 chore(release): 0.5.12 2022-07-26 14:59:06 +02:00
1d151e9349 fix: error on schema export 2022-07-26 13:33:57 +02:00
addd9fba28 build(deps): bump ace-builds from 1.4.14 to 1.8.1 2022-07-25 15:25:58 +02:00
a00c19d300 fix: prevent ctrl+a in console 2022-07-25 15:20:15 +02:00
9551afbd2d feat: ability to copy multiple selected rows 2022-07-25 14:56:00 +02:00
1ead76c028 fix: missing defaults on insert row window 2022-07-25 13:09:41 +02:00
d3da15aa13 feat: copy row as SQL INSERT 2022-07-25 12:42:22 +02:00
f3b5de38c4 feat: export table content as SQL INSERT 2022-07-25 12:19:58 +02:00
Askar Kanturin
d4b6d2e9d1 changed readme 2022-07-23 10:20:36 +02:00
Askar Kanturin
e2c106e4e0 fixed typo in readme
i'm no native speaker, but i feel this is a typo
2022-07-23 10:20:36 +02:00
eb60899e6e fix(MySQL): missing quoted identifier for column names in table filter, closes #380 2022-07-22 10:08:33 +02:00
1d367d468d Merge branch 'master' of https://github.com/antares-sql/antares 2022-07-21 11:01:13 +02:00
8ecaedbf6c fix: disable ctrl+alt+(left/right) shortcut on linux 2022-07-21 11:01:10 +02:00
dependabot[bot]
dd1eebd4ec build(deps): bump terser from 5.13.1 to 5.14.2
Bumps [terser](https://github.com/terser/terser) from 5.13.1 to 5.14.2.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-21 08:43:24 +02:00
8c83b3f144 fix: missing table on insert new records on session restored tabs 2022-07-20 10:41:44 +02:00
985e5d3527 feat: context menu option to duplicate a table row 2022-07-19 17:48:51 +02:00
78902639eb feat: execute selected query 2022-07-19 15:02:17 +02:00
cb038b374a fix: issue with logger on import/export 2022-07-19 10:10:24 +02:00
eafdb1cc3d chore(release): 0.5.11 2022-07-19 08:14:47 +02:00
91e0630513 fix: unable to edit table fields content on tables with datetime fields 2022-07-19 08:08:51 +02:00
bf768c3800 fix: filter persists switching temporary table tabs 2022-07-18 18:29:12 +02:00
0b1aa3dd29 fix: console events disabled in production 2022-07-18 17:21:34 +02:00
ec75f9546a chore(release): 0.5.10 2022-07-18 14:44:30 +02:00
9bc9adb7cf fix: exception on QueryEditor with null modelValue 2022-07-18 14:43:38 +02:00
b4d14d98db refactor: improved console with light theme 2022-07-18 11:58:22 +02:00
c21bd6075c feat: context menu to copy queries from console 2022-07-18 11:58:22 +02:00
44647f5b55 feat: open/close console on single connection 2022-07-18 11:58:22 +02:00
3f9e6d85ca perf: improved resize of text editor resizing console height 2022-07-18 11:58:22 +02:00
6a6f43a718 feat: initial console implementation 2022-07-18 11:58:22 +02:00
f12a04b052 feat: ipc event channel to send logs to renderer 2022-07-18 11:58:22 +02:00
abf829867e feat: Ctrl+PgUp & Ctrl+PgDn to navigate between tabs 2022-07-14 19:30:34 +02:00
b71f04e5aa feat: field names suggestion for tables in the query 2022-07-14 16:09:04 +02:00
7725fafe85 fix: unable to delete by select all in left bar search, closes #368 2022-07-13 18:50:16 +02:00
ed3d35f131 fix(Linux): ctrl+space shortcut not working 2022-07-13 18:25:42 +02:00
e0946f04f7 fix: unable to update data on tables missing primary or unique key 2022-07-13 09:30:30 +02:00
0891e7be8c refactor: disabled autofocus for scheduler timing modal 2022-07-11 11:35:30 +02:00
f312cf5f85 perf(UI): improved visibility of explore bar tooltips 2022-07-11 09:56:33 +02:00
05e0d310ec Merge pull request #363 from goYou/master
feat: Update zh-CN.ts
2022-07-10 09:13:35 +02:00
goYou
1e0b2b4cae Update zh-CN.ts 2022-07-10 14:49:30 +08:00
5ee728cfe4 Merge pull request #361 from antares-sql/dependabot/npm_and_yarn/moment-2.29.4
build(deps): bump moment from 2.29.3 to 2.29.4
2022-07-09 12:41:20 +02:00
a91fa8ff54 fix: fields content language detection not working properly 2022-07-09 12:39:44 +02:00
dependabot[bot]
2c13433900 build(deps): bump moment from 2.29.3 to 2.29.4
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.3...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-09 10:38:14 +00:00
dcc2a4c51c refactor: replaced @vscode/vscode-languagedetection with custom language detection 2022-07-09 12:37:30 +02:00
132 changed files with 9306 additions and 8684 deletions

View File

@@ -6,6 +6,8 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" - package-ecosystem: "npm"
allow:
- dependency-type: "production"
directory: "/" directory: "/"
schedule: schedule:
interval: "monthly" interval: "monthly"

View File

@@ -1,29 +0,0 @@
name: Build/release [LINUX]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@@ -1,29 +0,0 @@
name: Build/release [MAC]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@@ -1,29 +0,0 @@
name: Build/release [WINDOWS]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-2019]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

37
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build & release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- name: Release
uses: ncipollo/release-action@v1
with:
artifacts: "build/*.AppImage,build/*.yml,build/*.deb,build/*.dmg,build/*.blockmap,build/*.zip,build/*.exe"
allowUpdates: true
draft: true
generateReleaseNotes: true

View File

@@ -10,10 +10,15 @@ jobs:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: npm install & build - name: npm install & build
run: | run: |
npm install npm install
npm run build:local npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -0,0 +1,31 @@
name: Create artifact [MAC]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: macos-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: npm install & build
run: |
npm install
npm run build
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: macos-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -12,12 +12,12 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Install Node.js, NPM and Yarn - name: Install Node.js
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: 14 node-version: 16
- name: Install dependencies - name: Install dependencies
run: npm i run: npm i

1
.nvmrc
View File

@@ -1 +0,0 @@
v16.13.0

View File

@@ -1,6 +1,7 @@
{ {
"extends": [ "extends": [
"stylelint-config-standard" "stylelint-config-standard",
"stylelint-config-recommended-vue"
], ],
"fix": true, "fix": true,
"formatter": "verbose", "formatter": "verbose",
@@ -10,6 +11,7 @@
"rules": { "rules": {
"at-rule-no-unknown": null, "at-rule-no-unknown": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"font-family-no-missing-generic-family-keyword": null,
"declaration-colon-newline-after": "always-multi-line" "declaration-colon-newline-after": "always-multi-line"
}, },
"syntax": "scss" "syntax": "scss"

View File

@@ -7,7 +7,8 @@
"SQLite", "SQLite",
"Windows", "Windows",
"translation", "translation",
"Linux" "Linux",
"MacOS"
], ],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -2,6 +2,138 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.5.17](https://github.com/antares-sql/antares/compare/v0.5.16...v0.5.17) (2022-09-22)
### Features
* added more editor font sizes, closes [#440](https://github.com/antares-sql/antares/issues/440) ([d114f8a](https://github.com/antares-sql/antares/commit/d114f8a65164f702b23175095e6fc2b021e0e038))
### Bug Fixes
* "run or reload" shortcut triggers on all connections open ([01f607c](https://github.com/antares-sql/antares/commit/01f607cd40c18ab0f9761b2a05705a966aaae43a))
* cant run procedures with parameters from leftbar ([efe134a](https://github.com/antares-sql/antares/commit/efe134a059700ca87333dc6e66166d6ec8d289e8))
* editor font size doesn't change on new tabs, fixes [#442](https://github.com/antares-sql/antares/issues/442) ([84168d1](https://github.com/antares-sql/antares/commit/84168d1d75460acc2c844bfece7d85f0c977e74c))
* empty definer when editing a view, fixes [#437](https://github.com/antares-sql/antares/issues/437) ([498a9b4](https://github.com/antares-sql/antares/commit/498a9b48e25ee061960f5f649c953cdaf6ff1a58))
* **MacOS:** empty options on macos menubar ([a142d3c](https://github.com/antares-sql/antares/commit/a142d3c4d77e31375dfbea148eec54ce1f635192))
### [0.5.16](https://github.com/antares-sql/antares/compare/v0.5.15...v0.5.16) (2022-08-26)
### Bug Fixes
* CTRL+Right/Left not working on text editor, closes [#427](https://github.com/antares-sql/antares/issues/427) ([ffc645b](https://github.com/antares-sql/antares/commit/ffc645ba5efb1c52670096e4f8c7f992b7335dae))
* issue updating datetime cells with null value, closes [#423](https://github.com/antares-sql/antares/issues/423) ([ebc325a](https://github.com/antares-sql/antares/commit/ebc325ae0c656dca2eb8f7544ab271beaee9b47e))
* ts exceptions ([df68114](https://github.com/antares-sql/antares/commit/df681147aaf0bfca69f3ffdc474cc1846541b1d8))
* **UI:** editor themes group not visible in select element ([9dc700e](https://github.com/antares-sql/antares/commit/9dc700e13ea65bb8c6feac4ff4ffeadd32053614))
* **UI:** wrong position of fields resizable area ([c90ab0e](https://github.com/antares-sql/antares/commit/c90ab0e8807ff30a9ab58e9aa3515cf427dd6e86))
* unable to set null or delete rows without primary key ([39326eb](https://github.com/antares-sql/antares/commit/39326eb52e038728b5419d4a8de8024c7ead3002))
### [0.5.15](https://github.com/antares-sql/antares/compare/v0.5.14...v0.5.15) (2022-08-17)
### Features
* ability to add new shortcuts ([d044a02](https://github.com/antares-sql/antares/commit/d044a02cb79a9d06aadc34cdbf6e81da84360559))
* ability to edit shortcuts ([8eb127e](https://github.com/antares-sql/antares/commit/8eb127e45838bc01ba12f0740fec077fcd975532))
* added more events in shortcuts setting ([5043faf](https://github.com/antares-sql/antares/commit/5043fafa934844ebc2f59cabcec830c6a4d5ca8e))
* delete shortcuts and restore defaults ([c22413f](https://github.com/antares-sql/antares/commit/c22413fde9dfe5501a5f220070cfe552a318c70b))
* dynamic shortcut suggestions on empty query tabs ([4df14c3](https://github.com/antares-sql/antares/commit/4df14c3693955bd7801b4b99103fca85f00f3e8c))
* list of available shortcuts in settings window ([44bb75b](https://github.com/antares-sql/antares/commit/44bb75bc60d7d31bbd99a9ba57f30fd354f7581c))
* **UI:** connection name on left bar, closes [#382](https://github.com/antares-sql/antares/issues/382) [#414](https://github.com/antares-sql/antares/issues/414) ([4887753](https://github.com/antares-sql/antares/commit/48877534d1a41d351b267c0dab925046ca984179))
* **UI:** shortcuts setting UI improved ([49b63bc](https://github.com/antares-sql/antares/commit/49b63bc6f28fc6031e6a892d0a48cd35ae2f26cd))
### Bug Fixes
* startup exception ([c50d17e](https://github.com/antares-sql/antares/commit/c50d17e82b7fd337d4037ddf646cd1a8fc765bae))
### Improvements
* improved keypress detector ([0f219cf](https://github.com/antares-sql/antares/commit/0f219cf9b796b4369c609fb0e8e3b84346a30b07))
* **translation:** updated italian translation ([c05be83](https://github.com/antares-sql/antares/commit/c05be8304f3cf299cf338f67c00184305e022919))
### [0.5.14](https://github.com/antares-sql/antares/compare/v0.5.13...v0.5.14) (2022-08-09)
### Bug Fixes
* unable to open settingbar context menu ([44eb507](https://github.com/antares-sql/antares/commit/44eb507a12bad028a4fa8a8bb0f6442a3e8dde91))
### [0.5.13](https://github.com/antares-sql/antares/compare/v0.5.12...v0.5.13) (2022-08-09)
### Features
* copy row as CSV, closes [#394](https://github.com/antares-sql/antares/issues/394) ([1c3d7aa](https://github.com/antares-sql/antares/commit/1c3d7aa30bb9c2bd900a764ee6b97960729e9263))
* new macos icon ([0bfa14e](https://github.com/antares-sql/antares/commit/0bfa14e1c90320578597df030941530b670a4131))
### Bug Fixes
* **MySQL:** error with ANSI sql_mode ([f64a12a](https://github.com/antares-sql/antares/commit/f64a12a8e9c5f764c3a692f1a032736e008058b5))
* set legacy: false ([104b7c9](https://github.com/antares-sql/antares/commit/104b7c928b9c2abfc056880f16c606a0b1fa7c67))
### [0.5.12](https://github.com/antares-sql/antares/compare/v0.5.11...v0.5.12) (2022-07-26)
### Features
* ability to copy multiple selected rows ([9551afb](https://github.com/antares-sql/antares/commit/9551afbd2d7e525c81f28e98e788b92609ce9de4))
* context menu option to duplicate a table row ([985e5d3](https://github.com/antares-sql/antares/commit/985e5d352793d1b3e1981d004b6f494bfbb049bf))
* copy row as SQL INSERT ([d3da15a](https://github.com/antares-sql/antares/commit/d3da15aa1377dcba73927047563f1d0c2d1284ca))
* execute selected query ([7890263](https://github.com/antares-sql/antares/commit/78902639ebb29a8c53f8aa0d2045c74e0646febc))
* export table content as SQL INSERT ([f3b5de3](https://github.com/antares-sql/antares/commit/f3b5de38c4abfd2c1d738e179fc22e6c8b6f9080))
### Bug Fixes
* disable ctrl+alt+(left/right) shortcut on linux ([8ecaedb](https://github.com/antares-sql/antares/commit/8ecaedbf6c2fc0dc56ff2177a87dd6ede74bdd22))
* error on schema export ([1d151e9](https://github.com/antares-sql/antares/commit/1d151e9349fd97576ccd8ef7f88ca789a1f28b65))
* issue with logger on import/export ([cb038b3](https://github.com/antares-sql/antares/commit/cb038b374a4fe85ad569e42eee7af123c925e775))
* missing defaults on insert row window ([1ead76c](https://github.com/antares-sql/antares/commit/1ead76c02889f48bd91cae702820b082ca2ff54b))
* missing table on insert new records on session restored tabs ([8c83b3f](https://github.com/antares-sql/antares/commit/8c83b3f1447354ec63b2a308db05ad4d54659aa7))
* **MySQL:** missing quoted identifier for column names in table filter, closes [#380](https://github.com/antares-sql/antares/issues/380) ([eb60899](https://github.com/antares-sql/antares/commit/eb60899e6e17879c79a7ee7108061e9aca8596f7))
* prevent ctrl+a in console ([a00c19d](https://github.com/antares-sql/antares/commit/a00c19d3003cd43d3ee6e3132728122bb2b24c97))
### [0.5.11](https://github.com/antares-sql/antares/compare/v0.5.10...v0.5.11) (2022-07-19)
### Bug Fixes
* console events disabled in production ([0b1aa3d](https://github.com/antares-sql/antares/commit/0b1aa3dd299db641df3d4c56c7ee56a187fc3ab3))
* filter persists switching temporary table tabs ([bf768c3](https://github.com/antares-sql/antares/commit/bf768c380087b65604b5b571a9858a7f07bd681d))
* unable to edit table fields content on tables with datetime fields ([91e0630](https://github.com/antares-sql/antares/commit/91e06305133c97ea02dcfdc4e739a4b0a7e7049d))
### [0.5.10](https://github.com/antares-sql/antares/compare/v0.5.9...v0.5.10) (2022-07-18)
### Features
* context menu to copy queries from console ([c21bd60](https://github.com/antares-sql/antares/commit/c21bd6075c1203607c05e45b76233d57e3008190))
* Ctrl+PgUp & Ctrl+PgDn to navigate between tabs ([abf8298](https://github.com/antares-sql/antares/commit/abf829867e567354e534cff3e02a6d43f4c7a262))
* field names suggestion for tables in the query ([b71f04e](https://github.com/antares-sql/antares/commit/b71f04e5aa3c37eaa160dfbc76d1b84789e3543e))
* initial console implementation ([6a6f43a](https://github.com/antares-sql/antares/commit/6a6f43a718561e0abd2cb89048b7fe45d08736ae))
* ipc event channel to send logs to renderer ([f12a04b](https://github.com/antares-sql/antares/commit/f12a04b0524f1172334c89afeb27675c19ff68d2))
* open/close console on single connection ([44647f5](https://github.com/antares-sql/antares/commit/44647f5b5508965bf5a7264add89e175c725e877))
### Bug Fixes
* exception on QueryEditor with null modelValue ([9bc9adb](https://github.com/antares-sql/antares/commit/9bc9adb7cff19b86a99491d968485a4cd7b47b99))
* fields content language detection not working properly ([a91fa8f](https://github.com/antares-sql/antares/commit/a91fa8ff54bbf1f8475666efd3a268a3a4f07f0c))
* **Linux:** ctrl+space shortcut not working ([ed3d35f](https://github.com/antares-sql/antares/commit/ed3d35f1319a1e2edcb8104f2045a71b9e9754a2))
* unable to delete by select all in left bar search, closes [#368](https://github.com/antares-sql/antares/issues/368) ([7725faf](https://github.com/antares-sql/antares/commit/7725fafe852479720fa619ced0970f2fa0099191))
* unable to update data on tables missing primary or unique key ([e0946f0](https://github.com/antares-sql/antares/commit/e0946f04f792d25c187ea56d4714bdacc016ada3))
### Improvements
* improved resize of text editor resizing console height ([3f9e6d8](https://github.com/antares-sql/antares/commit/3f9e6d85ca445eea1028effa32418eee4980f87d))
* **UI:** improved visibility of explore bar tooltips ([f312cf5](https://github.com/antares-sql/antares/commit/f312cf5f855deddd562c26d1835f78d16499b93b))
### [0.5.9](https://github.com/antares-sql/antares/compare/v0.5.8...v0.5.9) (2022-07-06) ### [0.5.9](https://github.com/antares-sql/antares/compare/v0.5.8...v0.5.9) (2022-07-06)

View File

@@ -44,8 +44,7 @@ In this folder is located the structure of Vue frontend application.
## Build ## Build
The command to build Antares SQL locally is `npm run build:local`. The command to build Antares SQL locally is `npm run build`.
`build` command (without `:local`) is used exclusively by the GitHub Action.
## Conventions ## Conventions

View File

@@ -13,12 +13,13 @@ Antares is an SQL client based on [Electron.js](https://github.com/electron/elec
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite. **At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite.
At the moment, however, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it. However, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter. 👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
🗳️ Poll: **[Which is the main OS you use Antares on?](https://github.com/antares-sql/antares/discussions/379)**
## Current key features ## Current key features
@@ -33,6 +34,7 @@ We are actively working on it, hoping to provide new cool features, improvements
- SSH tunnel support. - SSH tunnel support.
- Manual commit mode. - Manual commit mode.
- Import and export database dumps. - Import and export database dumps.
- Customizable keyboard shortcuts.
- Dark and light theme. - Dark and light theme.
- Editor themes. - Editor themes.
@@ -40,20 +42,20 @@ We are actively working on it, hoping to provide new cool features, improvements
Why are we developing an SQL client when there are a lot of them on the market? Why are we developing an SQL client when there are a lot of them on the market?
The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem. The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first. A modern application created with minimalism and simplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenues; productivity comes first.
## Installation ## Installation
Based on your operating system you can have one or more distribution formats to choose based on your preferences. Based on your operating system you can have one or more distribution formats to choose based on your preferences.
Since Antares SQL is a free software we haven't a budget to spend in annual licenses or certificates. This can result that on some platforms you need some additional passages to install this app. Since Antares SQL is a free software we don't have a budget to spend on annual licenses or certificates. This can result that on some platforms you might need to put in some additional work to install this app.
### Linux ### Linux
On Linux you can simply download and run `.AppImage` distributions, install from Snap Store or from AUR. On Linux you can simply download and run the `.AppImage` distribution, install from Snap Store, from AUR or from our [PPA repository](https://github.com/antares-sql/antares-ppa).
### Windows ### Windows
On Windows you can choose between Microsoft Store and download `.exe` distribution. The latter lacks of a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt. On Windows you can choose between downloading the app from Microsoft Store or downloading the `.exe` from our [website](https://antares-sql.app/downloads) or [this github repo](https://github.com/Fabio286/antares/releases/latest). Distributions that are not from Microsoft Store are not signed with a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
### MacOS ### MacOS
@@ -61,7 +63,7 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
## Download ## Download
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab) [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [<img src="https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png" style="height: 56px">](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)** 🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## Coming soon ## Coming soon

Binary file not shown.

BIN
assets/linux/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/linux/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

BIN
assets/linux/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/linux/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/linux/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

13157
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.5.9", "version": "0.5.17",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/antares-sql/antares.git", "repository": "https://github.com/antares-sql/antares.git",
@@ -12,14 +12,13 @@
"compile:main": "webpack --mode=production --config webpack.main.config.js", "compile:main": "webpack --mode=production --config webpack.main.config.js",
"compile:workers": "webpack --mode=production --config webpack.workers.config.js", "compile:workers": "webpack --mode=production --config webpack.workers.config.js",
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js", "compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
"build": "cross-env NODE_ENV=production npm run compile", "build": "cross-env NODE_ENV=production npm run compile && electron-builder --publish never",
"build:local": "npm run build && electron-builder --publish never", "build:appx": "npm run build -- --win appx",
"build:appx": "npm run build:local -- --win appx", "rebuild:electron": "rimraf ./dist && npm run postinstall && npm run devtools:install",
"rebuild:electron": "rimraf ./dist && npm run postinstall",
"release": "standard-version", "release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha", "release:pre": "npm run release -- --prerelease alpha",
"devtools:install": "node scripts/devtoolsInstaller", "devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps && npm run devtools:install", "postinstall": "electron-builder install-app-deps",
"test:e2e": "npm run compile && npm run test:e2e-dry", "test:e2e": "npm run compile && npm run test:e2e-dry",
"test:e2e-dry": "xvfb-maybe -- playwright test", "test:e2e-dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"", "lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
@@ -65,7 +64,11 @@
"target": [ "target": [
{ {
"target": "deb", "target": "deb",
"arch": "x64" "arch": [
"x64",
"armv7l",
"arm64"
]
}, },
{ {
"target": "AppImage", "target": "AppImage",
@@ -76,6 +79,7 @@
] ]
} }
], ],
"icon": "assets/linux",
"category": "Development" "category": "Development"
}, },
"appImage": { "appImage": {
@@ -117,11 +121,10 @@
"dependencies": { "dependencies": {
"@electron/remote": "~2.0.1", "@electron/remote": "~2.0.1",
"@faker-js/faker": "~6.1.2", "@faker-js/faker": "~6.1.2",
"@mdi/font": "~6.9.96", "@mdi/font": "~7.0.96",
"@turf/helpers": "~6.5.0", "@turf/helpers": "~6.5.0",
"@vscode/vscode-languagedetection": "~1.0.21",
"@vueuse/core": "~8.7.5", "@vueuse/core": "~8.7.5",
"ace-builds": "~1.4.13", "ace-builds": "~1.8.1",
"better-sqlite3": "~7.5.1", "better-sqlite3": "~7.5.1",
"electron-log": "~4.4.1", "electron-log": "~4.4.1",
"electron-store": "~8.0.1", "electron-store": "~8.0.1",
@@ -130,19 +133,20 @@
"encoding": "~0.1.13", "encoding": "~0.1.13",
"leaflet": "~1.7.1", "leaflet": "~1.7.1",
"marked": "~4.0.0", "marked": "~4.0.0",
"moment": "~2.29.1", "moment": "~2.29.4",
"mysql2": "~2.3.2", "mysql2": "~2.3.2",
"pg": "~8.7.1", "pg": "~8.7.1",
"pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1", "pgsql-ast-parser": "~7.2.1",
"pinia": "~2.0.13", "pinia": "~2.0.13",
"source-map-support": "~0.5.20", "source-map-support": "~0.5.20",
"spectre.css": "~0.5.9", "spectre.css": "~0.5.9",
"sql-formatter": "~4.0.2", "sql-formatter": "~8.2.0",
"ssh2-promise": "~1.0.2", "ssh2-promise": "~1.0.2",
"v-mask": "~2.3.0", "v-mask": "~2.3.0",
"vue": "~3.2.37", "vue": "~3.2.37",
"vue-i18n": "~9.1.9", "vue-i18n": "~9.2.0",
"vuedraggable": "~4.1.0" "vuedraggable": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -177,15 +181,17 @@
"node-loader": "~2.0.0", "node-loader": "~2.0.0",
"playwright": "~1.21.1", "playwright": "~1.21.1",
"playwright-core": "~1.21.1", "playwright-core": "~1.21.1",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12", "progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2", "rimraf": "~3.0.2",
"sass": "~1.42.1", "sass": "~1.42.1",
"sass-loader": "~12.3.0", "sass-loader": "~12.3.0",
"standard-version": "~9.3.1", "standard-version": "~9.3.1",
"style-loader": "~3.3.1", "style-loader": "~3.3.1",
"stylelint": "~13.13.1", "stylelint": "~14.9.1",
"stylelint-config-standard": "~22.0.0", "stylelint-config-recommended-vue": "~1.4.0",
"stylelint-scss": "~3.21.0", "stylelint-config-standard": "~26.0.0",
"stylelint-scss": "~4.3.0",
"tree-kill": "~1.2.2", "tree-kill": "~1.2.2",
"ts-loader": "~9.2.8", "ts-loader": "~9.2.8",
"typescript": "~4.6.3", "typescript": "~4.6.3",

View File

@@ -114,7 +114,6 @@ function startRenderer (callback) {
}); });
const server = new WebpackDevServer(compiler, { const server = new WebpackDevServer(compiler, {
hot: true,
port: 9080, port: 9080,
client: { client: {
overlay: true, overlay: true,

View File

@@ -1,4 +1,5 @@
// @ts-check // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export default class { export default class {
static get _methods () { static get _methods () {
return [ return [
@@ -180,7 +181,7 @@ export default class {
acc[curr.group] = new Set(curr.types); acc[curr.group] = new Set(curr.types);
return acc; return acc;
}, {}); }, {} as any);
const groupsArr = []; const groupsArr = [];
@@ -198,12 +199,12 @@ export default class {
}); });
} }
static getGroupsByType (type) { static getGroupsByType (type: string) {
if (!type) return []; if (!type) return [];
return this.getGroups().filter(group => group.types.includes(type)); return this.getGroups().filter(group => group.types.includes(type));
} }
static getMethods ({ type, group }) { static getMethods ({ type, group }: {type: string; group: string}) {
return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => { return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => {
if (a.name < b.name) if (a.name < b.name)
return -1; return -1;

View File

@@ -25,7 +25,7 @@ export const customizations: Customizations = {
functions: true, functions: true,
schedulers: true, schedulers: true,
// Settings // Settings
elementsWrapper: '', elementsWrapper: '`',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: true, tableAdd: true,
tableTruncateDisableFKCheck: true, tableTruncateDisableFKCheck: true,

View File

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

View File

@@ -1,5 +1,5 @@
export function bufferToBase64 (buf: Buffer) { export function bufferToBase64 (buf: Buffer) {
const binstr = Array.prototype.map.call(buf, ch => { const binstr = Array.prototype.map.call(buf, (ch: number) => {
return String.fromCharCode(ch); return String.fromCharCode(ch);
}).join(''); }).join('');
return Buffer.from(binstr, 'binary').toString('base64'); return Buffer.from(binstr, 'binary').toString('base64');

View File

@@ -0,0 +1,192 @@
function isJSON (str: string) {
try {
if (!['{', '['].includes(str.trim()[0]))
return false;
JSON.parse(str);
return true;
}
catch (_) {
return false;
}
}
function isHTML (str: string) {
const tags = [
'a',
'abbr',
'address',
'area',
'article',
'aside',
'audio',
'b',
'base',
'bdi',
'bdo',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'col',
'colgroup',
'data',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'div',
'dl',
'dt',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'label',
'legend',
'li',
'link',
'main',
'map',
'mark',
'math',
'menu',
'menuitem',
'meta',
'meter',
'nav',
'noscript',
'object',
'ol',
'optgroup',
'option',
'output',
'p',
'param',
'picture',
'pre',
'progress',
'q',
'rb',
'rp',
'rt',
'rtc',
'ruby',
's',
'samp',
'script',
'section',
'select',
'slot',
'small',
'source',
'span',
'strong',
'style',
'sub',
'summary',
'sup',
'svg',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'track',
'u',
'ul',
'var',
'video',
'wbr'
];
const doc = new DOMParser().parseFromString(str, 'text/html');
const lowerStr = str.toLowerCase();
if (Array.from(doc.body.childNodes).some(node => node.nodeType === 1))
return tags.some((tag) => lowerStr.includes(`<${tag}>`));
return false;
}
function isSVG (str: string) {
const doc = new DOMParser().parseFromString(str, 'text/xml');
const lowerStr = str.toLowerCase();
const errorNode = doc.querySelector('parsererror');
if (!errorNode)
return lowerStr.includes('<svg');
return false;
}
function isXML (str: string) {
const doc = new DOMParser().parseFromString(str, 'text/xml');
const errorNode = doc.querySelector('parsererror');
return !errorNode;
}
function isMD (str: string) {
const mdChecks = [
'# ',
'`',
'- ',
'+ ',
'* ',
'1. ',
'**',
'__',
'~~',
'>> ',
'](http',
'![',
'[ ]',
'[x]'
];
return mdChecks.some((tag) => str.includes(tag));
}
export function langDetector (str: string) {
if (!str.trim().length)
return 'text';
if (isJSON(str))
return 'json';
if (isHTML(str))
return 'html';
if (isSVG(str))
return 'svg';
if (isXML(str))
return 'xml';
if (isMD(str))
return 'markdown';
return 'text';
}

View File

@@ -1,14 +0,0 @@
/* eslint-disable no-useless-escape */
// eslint-disable-next-line no-control-regex
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
function sqlEscaper (string: string) {
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
}
export { sqlEscaper };

162
src/common/libs/sqlUtils.ts Normal file
View File

@@ -0,0 +1,162 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-useless-escape */
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import customizations from '../customizations';
import { ClientCode } from '../interfaces/antares';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary, { HexChar } from './hexToBinary';
import { getArrayDepth } from './getArrayDepth';
/**
* Escapes a string fo SQL use
*
* @param { String } string
* @returns { String } Escaped string
*/
export const sqlEscaper = (string: string): string => {
// eslint-disable-next-line no-control-regex
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
};
export const objectToGeoJSON = (val: any) => {
if (Array.isArray(val)) {
if (getArrayDepth(val) === 1)
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(val.map(arr => arr.reduce((acc: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([val.x, val.y]);
};
export const escapeAndQuote = (val: string, client: ClientCode) => {
const { stringsWrapper: sw } = customizations[client];
// eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
'\0': '\\0',
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\r': '\\r',
'\x1a': '\\Z',
'"': '\\"',
'\'': '\\\'',
'\\': '\\\\'
};
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
let escapedVal = '';
let match;
while ((match = CHARS_TO_ESCAPE.exec(val))) {
escapedVal += val.slice(chunkIndex, match.index) + CHARS_ESCAPE_MAP[match[0]];
chunkIndex = CHARS_TO_ESCAPE.lastIndex;
}
if (chunkIndex === 0)
return `${sw}${val}${sw}`;
if (chunkIndex < val.length)
return `${sw}${escapedVal + val.slice(chunkIndex)}${sw}`;
return `${sw}${escapedVal}${sw}`;
};
export const valueToSqlString = (args: {
val: any;
client: ClientCode;
field: {type: string; datePrecision: number};
}): string => {
let parsedValue;
const { val, client, field } = args;
const { stringsWrapper: sw } = customizations[client];
if (val === null)
parsedValue = 'NULL';
else if (DATE.includes(field.type)) {
parsedValue = moment(val).isValid()
? escapeAndQuote(moment(val).format('YYYY-MM-DD'), client)
: val;
}
else if (DATETIME.includes(field.type)) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
parsedValue = moment(val).isValid()
? escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`), client)
: escapeAndQuote(val, client);
}
else if ('isArray' in field) {
let localVal;
if (Array.isArray(val))
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
else
localVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
parsedValue = `'${localVal}'`;
}
else if (TEXT_SEARCH.includes(field.type))
parsedValue = `'${val.replaceAll('\'', '\'\'')}'`;
else if (BIT.includes(field.type))
parsedValue = `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(field.type)) {
if (['mysql', 'maria'].includes(client))
parsedValue = `X'${val.toString('hex').toUpperCase()}'`;
else if (client === 'pg')
parsedValue = `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
}
else if (NUMBER.includes(field.type))
parsedValue = val;
else if (FLOAT.includes(field.type))
parsedValue = parseFloat(val);
else if (SPATIAL.includes(field.type)) {
let geoJson;
if (IS_MULTI_SPATIAL.includes(field.type)) {
const features = [];
for (const element of val)
features.push(objectToGeoJSON(element));
geoJson = {
type: 'FeatureCollection',
features
};
}
else
geoJson = objectToGeoJSON(val);
parsedValue = `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
}
else if (val === '') parsedValue = `${sw}${sw}`;
else {
parsedValue = typeof val === 'string'
? escapeAndQuote(val, client)
: typeof val === 'object'
? escapeAndQuote(JSON.stringify(val), client)
: val;
}
return parsedValue;
};
export const jsonToSqlInsert = (args: {
json: { [key: string]: any};
client: ClientCode;
fields: { [key: string]: {type: string; datePrecision: number}};
table: string;
}) => {
const { client, json, fields, table } = args;
const { elementsWrapper: ew } = customizations[client];
const fieldNames = Object.keys(json).map(key => `${ew}${key}${ew}`);
const values = Object.keys(json).map(key => (
valueToSqlString({ val: json[key], client, field: fields[key] })
));
return `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES (${values.join(', ')});`;
};

138
src/common/shortcuts.ts Normal file
View File

@@ -0,0 +1,138 @@
export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string | number; context?: 'tab' }} = {
'run-or-reload': { l18n: 'message.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'message.openNewTab', context: 'tab' },
'close-tab': { l18n: 'message.closeTab', context: 'tab' },
'format-query': { l18n: 'message.formatQuery', context: 'tab' },
'kill-query': { l18n: 'message.killQuery', context: 'tab' },
'query-history': { l18n: 'message.queryHistory', context: 'tab' },
'clear-query': { l18n: 'message.clearQuery', context: 'tab' },
'next-tab': { l18n: 'message.nextTab' },
'prev-tab': { l18n: 'message.previousTab' },
'open-all-connections': { l18n: 'message.openAllConnections' },
'open-filter': { l18n: 'message.openFilter' },
'next-page': { l18n: 'message.nextResultsPage' },
'prev-page': { l18n: 'message.previousResultsPage' },
'toggle-console': { l18n: 'message.toggleConsole' },
'save-content': { l18n: 'message.saveContent' },
'create-connection': { l18n: 'message.createNewConnection' },
'open-settings': { l18n: 'message.openSettings' },
'open-scratchpad': { l18n: 'message.openScratchpad' }
};
interface ShortcutRecord {
event: string;
keys: Electron.Accelerator[] | string[];
/** Needed for default shortcuts */
os: NodeJS.Platform[];
}
/**
* Default shortcuts
*/
const shortcuts: ShortcutRecord[] = [
{
event: 'run-or-reload',
keys: ['F5'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'save-content',
keys: ['CommandOrControl+S'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'kill-query',
keys: ['CommandOrControl+K'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'format-query',
keys: ['CommandOrControl+B'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'clear-query',
keys: ['CommandOrControl+Alt+W'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'query-history',
keys: ['CommandOrControl+G'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'open-new-tab',
keys: ['CommandOrControl+T'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'close-tab',
keys: ['CommandOrControl+W'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'next-tab',
keys: ['Alt+CommandOrControl+Right'],
os: ['darwin', 'win32']
},
{
event: 'prev-tab',
keys: ['Alt+CommandOrControl+Left'],
os: ['darwin', 'win32']
},
{
event: 'next-tab',
keys: ['CommandOrControl+PageDown'],
os: ['linux', 'win32']
},
{
event: 'prev-tab',
keys: ['CommandOrControl+PageUp'],
os: ['linux', 'win32']
},
{
event: 'open-filter',
keys: ['CommandOrControl+F'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'next-page',
keys: ['CommandOrControl+Right'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'prev-page',
keys: ['CommandOrControl+Left'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'open-all-connections',
keys: ['Shift+CommandOrControl+Space'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+F12'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32']
}
];
for (let i = 1; i <= 9; i++) {
shortcutEvents[`select-tab-${i}`] = {
l18n: 'message.selectTabNumber',
l18nParam: i
};
shortcuts.push({
event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`],
os: ['darwin', 'linux', 'win32']
});
}
export { shortcuts, ShortcutRecord };

View File

@@ -1,4 +1,5 @@
import { app, ipcMain, dialog } from 'electron'; import { app, ipcMain, dialog } from 'electron';
import { ShortcutRegister } from '../libs/ShortcutRegister';
export default () => { export default () => {
ipcMain.on('close-app', () => { ipcMain.on('close-app', () => {
@@ -12,4 +13,24 @@ export default () => {
ipcMain.handle('get-download-dir-path', () => { ipcMain.handle('get-download-dir-path', () => {
return app.getPath('downloads'); return app.getPath('downloads');
}); });
ipcMain.handle('resotre-default-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.restoreDefaults();
});
ipcMain.handle('reload-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.reload();
});
ipcMain.handle('update-shortcuts', (event, shortcuts) => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.updateShortcuts(shortcuts);
});
ipcMain.handle('unregister-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.unregister();
});
}; };

View File

@@ -55,6 +55,7 @@ export default (connections: {[key: string]: antares.Client}) => {
try { try {
const connection = await ClientsFactory.getClient({ const connection = await ClientsFactory.getClient({
uid: conn.uid,
client: conn.client, client: conn.client,
params params
}); });
@@ -128,6 +129,7 @@ export default (connections: {[key: string]: antares.Client}) => {
try { try {
const connection = ClientsFactory.getClient({ const connection = ClientsFactory.getClient({
uid: conn.uid,
client: conn.client, client: conn.client,
params, params,
poolSize: 5 poolSize: 5

View File

@@ -4,7 +4,7 @@ import { InsertRowsParams } from 'common/interfaces/tableApis';
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import * as moment from 'moment'; import * as moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper'; import { sqlEscaper } from 'common/libs/sqlUtils';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import customizations from 'common/customizations'; import customizations from 'common/customizations';
@@ -177,7 +177,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof orgRow[key] === 'string') if (typeof orgRow[key] === 'string')
orgRow[key] = `'${orgRow[key]}'`; orgRow[key] = `'${orgRow[key]}'`;
orgRow[key] = `= ${orgRow[key]}`; if (orgRow[key] === null)
orgRow[key] = `IS ${orgRow[key]}`;
else
orgRow[key] = `= ${orgRow[key]}`;
} }
await connections[params.uid] await connections[params.uid]

View File

@@ -3,10 +3,14 @@ import mysql from 'mysql2/promise';
import * as pg from 'pg'; import * as pg from 'pg';
import SSH2Promise from 'ssh2-promise'; import SSH2Promise from 'ssh2-promise';
const queryLogger = (sql: string) => { const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
// Remove comments, newlines and multiple spaces // Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
console.log(escapedSql); if (process.type !== undefined) {
const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
}
if (process.env.NODE_ENV === 'development') console.log(escapedSql);
}; };
/** /**
@@ -14,15 +18,17 @@ const queryLogger = (sql: string) => {
*/ */
export class AntaresCore { export class AntaresCore {
_client: antares.ClientCode; _client: antares.ClientCode;
protected _cUid: string
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};
protected _poolSize: number; protected _poolSize: number;
protected _ssh?: SSH2Promise; protected _ssh?: SSH2Promise;
protected _logger: (sql: string) => void; protected _logger: (args: {sql: string; cUid: string}) => void;
protected _queryDefaults: antares.QueryBuilderObject; protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject; protected _query: antares.QueryBuilderObject;
constructor (args: antares.ClientParams) { constructor (args: antares.ClientParams) {
this._client = args.client; this._client = args.client;
this._cUid = args.uid;
this._params = args.params; this._params = args.params;
this._poolSize = args.poolSize || undefined; this._poolSize = args.poolSize || undefined;
this._logger = args.logger || queryLogger; this._logger = args.logger || queryLogger;

View File

@@ -0,0 +1,139 @@
import { BrowserWindow, globalShortcut, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
import * as Store from 'electron-store';
import { ShortcutRecord, shortcuts } from 'common/shortcuts';
const shortcutsStore = new Store({ name: 'shortcuts' });
const isDevelopment = process.env.NODE_ENV !== 'production';
const defaultShortcuts = shortcuts.filter(s => s.os.includes(process.platform));
export type ShortcutMode = 'local' | 'global'
export type OsMenu = {
[key in NodeJS.Platform]?: MenuItemConstructorOptions[];
};
export class ShortcutRegister {
private _shortcuts: ShortcutRecord[];
private _mainWindow: BrowserWindow;
private _menu: Menu;
private _menuTemplate: OsMenu;
private _mode: ShortcutMode;
private static _instance: ShortcutRegister;
private constructor (args: { mainWindow: BrowserWindow; menuTemplate?: OsMenu; mode: ShortcutMode }) {
this._mainWindow = args.mainWindow;
this._menuTemplate = args.menuTemplate || {};
this._mode = args.mode;
this.shortcuts = shortcutsStore.get('shortcuts', defaultShortcuts) as ShortcutRecord[];
}
public static getInstance (args?: { mainWindow?: BrowserWindow; menuTemplate?: OsMenu; mode?: ShortcutMode }) {
if (!ShortcutRegister._instance && args.menuTemplate !== undefined && args.mode !== undefined) {
ShortcutRegister._instance = new ShortcutRegister({
mainWindow: args.mainWindow,
menuTemplate: args.menuTemplate,
mode: args.mode
});
}
return ShortcutRegister._instance;
}
get shortcuts () {
return this._shortcuts;
}
private set shortcuts (value: ShortcutRecord[]) {
this._shortcuts = value;
shortcutsStore.set('shortcuts', value);
}
init () {
this._mainWindow.webContents.send('update-shortcuts', this.shortcuts);
this.buildBaseMenu();
if (this._mode === 'global')
this.setGlobalShortcuts();
else if (this._mode === 'local')
this.setLocalShortcuts();
else
throw new Error(`Unknown mode "${this._mode}"`);
Menu.setApplicationMenu(this._menu);
}
private buildBaseMenu () {
if (Object.keys(this._menuTemplate).includes(process.platform))
this._menu = Menu.buildFromTemplate(this._menuTemplate[process.platform]);
else
this._menu = new Menu();
}
private setLocalShortcuts () {
for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) {
try {
this._menu.append(new MenuItem({
label: 'Shortcuts',
visible: false,
submenu: [{
label: String(key),
accelerator: key,
visible: false,
click: () => {
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
}
}]
}));
}
catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults();
throw error;
}
}
}
}
}
private setGlobalShortcuts () {
for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) {
try {
globalShortcut.register(key, () => {
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('GLOBAL EVENT:', shortcut);
});
}
catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults();
throw error;
}
}
}
}
}
reload () {
this.unregister();
this.init();
}
updateShortcuts (shortcuts: ShortcutRecord[]) {
this.shortcuts = shortcuts;
this.reload();
}
restoreDefaults () {
this.shortcuts = defaultShortcuts;
this.reload();
}
unregister () {
if (this._mode === 'global') globalShortcut.unregisterAll();
}
}

View File

@@ -192,14 +192,14 @@ export class MySQLClient extends AntaresCore {
// ANSI_QUOTES check // ANSI_QUOTES check
const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode = response[0]?.Value?.split(','); const sqlMode: string[] = response[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES'); const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
if (this._params.readonly) if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY'); await connection.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
return connection; return connection;
} }
@@ -219,18 +219,18 @@ export class MySQLClient extends AntaresCore {
// ANSI_QUOTES check // ANSI_QUOTES check
const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode = res[0]?.Value?.split(','); const sqlMode: string[] = res[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES'); const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
connection.on('connection', conn => { connection.on('connection', conn => {
if (this._params.readonly) if (this._params.readonly)
conn.query('SET SESSION TRANSACTION READ ONLY'); conn.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes) if (hasAnsiQuotes)
conn.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); conn.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
}); });
return connection; return connection;
@@ -940,7 +940,7 @@ export class MySQLClient extends AntaresCore {
return { return {
algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
definer: viewInfo[0].DEFINER, definer: viewInfo[0].DEFINER.split('@').map((str: string) => `\`${str}\``).join('@'),
security: viewInfo[0].SECURITY_TYPE, security: viewInfo[0].SECURITY_TYPE,
updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION, updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION,
sql: viewInfo[0].VIEW_DEFINITION, sql: viewInfo[0].VIEW_DEFINITION,
@@ -1397,7 +1397,7 @@ export class MySQLClient extends AntaresCore {
} }
async getVersion () { async getVersion () {
const sql = 'SHOW VARIABLES LIKE "%vers%"'; const sql = 'SHOW VARIABLES LIKE \'%vers%\'';
const { rows } = await this.raw(sql); const { rows } = await this.raw(sql);
return rows.reduce((acc, curr) => { return rows.reduce((acc, curr) => {
@@ -1536,7 +1536,7 @@ export class MySQLClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
if (process.env.NODE_ENV === 'development') this._logger(sql); this._logger({ cUid: this._cUid, sql });
args = { args = {
nest: false, nest: false,
@@ -1588,7 +1588,8 @@ export class MySQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
connection.query({ sql: query, nestTables }).then(async ([response, fields]) => { connection.query({ sql: query, nestTables }).then(async ([response, fields]) => {
timeStop = new Date(); timeStop = new Date();
const queryResult = response; const queryResult = response;

View File

@@ -1,6 +1,5 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2'; import * as mysql from 'mysql2';
import { builtinsTypes } from 'pg-types';
import * as pg from 'pg'; import * as pg from 'pg';
import * as pgAst from 'pgsql-ast-parser'; import * as pgAst from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
@@ -19,6 +18,68 @@ pg.types.setTypeParser(1114, pgToString); // timestamp
pg.types.setTypeParser(1184, pgToString); // timestamptz pg.types.setTypeParser(1184, pgToString); // timestamptz
pg.types.setTypeParser(1266, pgToString); // timetz pg.types.setTypeParser(1266, pgToString); // timetz
// from pg-types
type builtinsTypes =
'BOOL' |
'BYTEA' |
'CHAR' |
'INT8' |
'INT2' |
'INT4' |
'REGPROC' |
'TEXT' |
'OID' |
'TID' |
'XID' |
'CID' |
'JSON' |
'XML' |
'PG_NODE_TREE' |
'SMGR' |
'PATH' |
'POLYGON' |
'CIDR' |
'FLOAT4' |
'FLOAT8' |
'ABSTIME' |
'RELTIME' |
'TINTERVAL' |
'CIRCLE' |
'MACADDR8' |
'MONEY' |
'MACADDR' |
'INET' |
'ACLITEM' |
'BPCHAR' |
'VARCHAR' |
'DATE' |
'TIME' |
'TIMESTAMP' |
'TIMESTAMPTZ' |
'INTERVAL' |
'TIMETZ' |
'BIT' |
'VARBIT' |
'NUMERIC' |
'REFCURSOR' |
'REGPROCEDURE' |
'REGOPER' |
'REGOPERATOR' |
'REGCLASS' |
'REGTYPE' |
'UUID' |
'TXID_SNAPSHOT' |
'PG_LSN' |
'PG_NDISTINCT' |
'PG_DEPENDENCIES' |
'TSVECTOR' |
'TSQUERY' |
'GTSVECTOR' |
'REGCONFIG' |
'REGDICTIONARY' |
'JSONB' |
'REGNAMESPACE' |
'REGROLE';
export class PostgreSQLClient extends AntaresCore { export class PostgreSQLClient extends AntaresCore {
private _schema?: string; private _schema?: string;
private _runningConnections: Map<string, number>; private _runningConnections: Map<string, number>;
@@ -1314,7 +1375,7 @@ export class PostgreSQLClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
if (process.env.NODE_ENV === 'development') this._logger(sql); this._logger({ cUid: this._cUid, sql });
args = { args = {
nest: false, nest: false,
@@ -1365,7 +1426,8 @@ export class PostgreSQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
try { try {
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query }); const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });

View File

@@ -586,7 +586,7 @@ export class SQLiteClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder this._logger({ cUid: this._cUid, sql });// TODO: replace BLOB content with a placeholder
args = { args = {
nest: false, nest: false,
@@ -628,7 +628,8 @@ export class SQLiteClient extends AntaresCore {
let timeStop; let timeStop;
const keysArr: antares.QueryForeign[] = []; const keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
let queryRunResult: sqlite.RunResult; let queryRunResult: sqlite.RunResult;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -1,12 +1,8 @@
import * as exporter from 'common/interfaces/exporter'; import * as exporter from 'common/interfaces/exporter';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import { SqlExporter } from './SqlExporter'; import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
import { getArrayDepth } from 'common/libs/getArrayDepth';
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import { MySQLClient } from '../../clients/MySQLClient'; import { MySQLClient } from '../../clients/MySQLClient';
import { valueToSqlString } from 'common/libs/sqlUtils';
export default class MysqlExporter extends SqlExporter { export default class MysqlExporter extends SqlExporter {
protected _client: MySQLClient; protected _client: MySQLClient;
@@ -122,54 +118,7 @@ ${footer}
const column = notGeneratedColumns[i]; const column = notGeneratedColumns[i];
const val = row[column.name]; const val = row[column.name];
if (val === null) sqlInsertString += 'NULL'; sqlInsertString += valueToSqlString({ val, client: 'mysql', field: column });
else if (DATE.includes(column.type)) {
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
: val;
}
else if (DATETIME.includes(column.type)) {
let datePrecision = '';
for (let i = 0; i < column.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
: this.escapeAndQuote(val);
}
else if (BIT.includes(column.type))
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(column.type))
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
else if (NUMBER.includes(column.type))
sqlInsertString += val;
else if (FLOAT.includes(column.type))
sqlInsertString += parseFloat(val);
else if (SPATIAL.includes(column.type)) {
let geoJson;
if (IS_MULTI_SPATIAL.includes(column.type)) {
const features = [];
for (const element of val)
features.push(this._getGeoJSON(element));
geoJson = {
type: 'FeatureCollection',
features
};
}
else
geoJson = this._getGeoJSON(val);
sqlInsertString += `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
}
else if (val === '') sqlInsertString += '\'\'';
else {
sqlInsertString += typeof val === 'string'
? this.escapeAndQuote(val)
: typeof val === 'object'
? this.escapeAndQuote(JSON.stringify(val))
: val;
}
if (parseInt(i) !== notGeneratedColumns.length - 1) if (parseInt(i) !== notGeneratedColumns.length - 1)
sqlInsertString += ', '; sqlInsertString += ', ';
@@ -435,17 +384,4 @@ CREATE TABLE \`${view.Name}\`(
return `'${escapedVal}'`; return `'${escapedVal}'`;
} }
/* eslint-disable @typescript-eslint/no-explicit-any */
_getGeoJSON (val: any) {
if (Array.isArray(val)) {
if (getArrayDepth(val) === 1)
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(val.map(arr => arr.reduce((acc: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([val.x, val.y]);
}
/* eslint-enable @typescript-eslint/no-explicit-any */
} }

View File

@@ -1,13 +1,11 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as exporter from 'common/interfaces/exporter'; import * as exporter from 'common/interfaces/exporter';
import { SqlExporter } from './SqlExporter'; import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
import * as moment from 'moment';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import * as QueryStream from 'pg-query-stream'; import * as QueryStream from 'pg-query-stream';
import { PostgreSQLClient } from '../../clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
import { valueToSqlString } from 'common/libs/sqlUtils';
export default class PostgreSQLExporter extends SqlExporter { export default class PostgreSQLExporter extends SqlExporter {
constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) { constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
@@ -223,47 +221,7 @@ SET row_security = off;\n\n\n`;
const column = columns[i]; const column = columns[i];
const val = row[column.name]; const val = row[column.name];
if (val === null) sqlInsertString += 'NULL'; sqlInsertString += valueToSqlString({ val, client: 'pg', field: column });
else if (DATE.includes(column.type)) {
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
: val;
}
else if (DATETIME.includes(column.type)) {
let datePrecision = '';
for (let i = 0; i < column.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
sqlInsertString += moment(val).isValid()
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
: this.escapeAndQuote(val);
}
else if ('isArray' in column) {
let parsedVal;
if (Array.isArray(val))
parsedVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
else
parsedVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
sqlInsertString += `'${parsedVal}'`;
}
else if (TEXT_SEARCH.includes(column.type))
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
else if (BIT.includes(column.type))
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(column.type))
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
else if (NUMBER.includes(column.type))
sqlInsertString += val;
else if (FLOAT.includes(column.type))
sqlInsertString += parseFloat(val);
else if (val === '') sqlInsertString += '\'\'';
else {
sqlInsertString += typeof val === 'string'
? this.escapeAndQuote(val)
: typeof val === 'object'
? this.escapeAndQuote(JSON.stringify(val))
: val;
}
if (parseInt(i) !== columns.length - 1) if (parseInt(i) !== columns.length - 1)
sqlInsertString += ', '; sqlInsertString += ', ';

View File

@@ -1,15 +1,15 @@
import { app, BrowserWindow, /* session, */ nativeImage, Menu, ipcMain } from 'electron'; import { app, BrowserWindow, nativeImage, ipcMain } from 'electron';
import * as path from 'path'; import * as path from 'path';
import * as Store from 'electron-store'; import * as Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state'; import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main'; import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers'; import ipcHandlers from './ipc-handlers';
import { OsMenu, ShortcutRegister } from './libs/ShortcutRegister';
Store.initRenderer(); Store.initRenderer();
const persistentStore = new Store({ name: 'settings' }); const settingsStore = new Store({ name: 'settings' });
const appTheme = settingsStore.get('application_theme');
const appTheme = persistentStore.get('application_theme');
const isDevelopment = process.env.NODE_ENV !== 'production'; const isDevelopment = process.env.NODE_ENV !== 'production';
const isMacOS = process.platform === 'darwin'; const isMacOS = process.platform === 'darwin';
const isLinux = process.platform === 'linux'; const isLinux = process.platform === 'linux';
@@ -85,7 +85,7 @@ else {
ipcHandlers(); ipcHandlers();
ipcMain.on('refresh-theme-settings', () => { ipcMain.on('refresh-theme-settings', () => {
const appTheme = persistentStore.get('application_theme'); const appTheme = settingsStore.get('application_theme');
if (isWindows && mainWindow) { if (isWindows && mainWindow) {
mainWindow.setTitleBarOverlay({ mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff', color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
@@ -145,10 +145,8 @@ else {
} }
function createAppMenu () { function createAppMenu () {
let menu: Electron.Menu = null; const menuTemplate: OsMenu = {
darwin: [
if (isMacOS) {
menu = Menu.buildFromTemplate([
{ {
label: app.name, label: app.name,
submenu: [ submenu: [
@@ -179,10 +177,11 @@ function createAppMenu () {
{ {
role: 'windowMenu' role: 'windowMenu'
} }
]); ]
} };
Menu.setApplicationMenu(menu); const shortCutRegister = ShortcutRegister.getInstance({ mainWindow, menuTemplate, mode: 'local' });
shortCutRegister.init();
} }
function saveWindowState () { function saveWindowState () {

View File

@@ -10,7 +10,7 @@
:key="connection.uid" :key="connection.uid"
:connection="connection" :connection="connection"
/> />
<div class="connection-panel-wrapper"> <div class="connection-panel-wrapper p-relative">
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" /> <WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div> </div>
</div> </div>
@@ -26,7 +26,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineAsyncComponent, onBeforeUnmount, onMounted, Ref, ref } from 'vue'; import { defineAsyncComponent, onMounted, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -67,24 +67,29 @@ const { changeApplicationTheme } = settingsStore;
const isAllConnectionsModal: Ref<boolean> = ref(false); const isAllConnectionsModal: Ref<boolean> = ref(false);
const onKey = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey) {
if (e.code === 'Space') {
isAllConnectionsModal.value = true;
e.stopPropagation();
}
}
};
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => { setTimeout(() => {
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
}, 1000); }, 1000);
}); });
window.addEventListener('keypress', onKey);
onMounted(() => { onMounted(() => {
ipcRenderer.on('open-all-connections', () => {
isAllConnectionsModal.value = true;
});
ipcRenderer.on('open-scratchpad', () => {
isScratchpad.value = true;
});
ipcRenderer.on('open-settings', () => {
isSettingModal.value = true;
});
ipcRenderer.on('create-connection', () => {
workspacesStore.selectWorkspace('NEW');
});
ipcRenderer.send('check-for-updates'); ipcRenderer.send('check-for-updates');
checkVersionUpdate(); checkVersionUpdate();
@@ -126,10 +131,6 @@ onMounted(() => {
} }
}); });
}); });
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -162,7 +163,7 @@ onBeforeUnmount(() => {
.connection-panel-wrapper { .connection-panel-wrapper {
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
width: 100%; width: 100%;
padding-top: 15vh; padding-top: 10vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;

View File

@@ -31,13 +31,13 @@
class="btn btn-primary mr-2" class="btn btn-primary mr-2"
@click.stop="confirmModal" @click.stop="confirmModal"
> >
{{ confirmText || $t('word.confirm') }} {{ confirmText || t('word.confirm') }}
</button> </button>
<button <button
class="btn btn-link" class="btn btn-link"
@click="hideModal" @click="hideModal"
> >
{{ cancelText || $t('word.cancel') }} {{ cancelText || t('word.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -49,6 +49,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { computed, onBeforeUnmount, PropType, useSlots } from 'vue'; import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
size: { size: {
@@ -65,6 +68,10 @@ const props = defineProps({
disableAutofocus: { disableAutofocus: {
type: Boolean, type: Boolean,
default: false default: false
},
closeOnConfirm: {
type: Boolean,
default: true
} }
}); });
const emit = defineEmits(['confirm', 'hide']); const emit = defineEmits(['confirm', 'hide']);
@@ -87,7 +94,7 @@ const modalSizeClass = computed(() => {
const confirmModal = () => { const confirmModal = () => {
emit('confirm'); emit('confirm');
hideModal(); if (props.closeOnConfirm) hideModal();
}; };
const hideModal = () => { const hideModal = () => {

View File

@@ -431,6 +431,12 @@ export default defineComponent({
width: 100%; width: 100%;
} }
&__item-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&__list-wrapper { &__list-wrapper {
cursor: pointer; cursor: pointer;
position: fixed; position: fixed;

View File

@@ -51,14 +51,17 @@ watch(editorTheme, () => {
watch(editorFontSize, () => { watch(editorFontSize, () => {
const sizes = { const sizes = {
small: 12, xsmall: '10px',
medium: 14, small: '12px',
large: 16 medium: '14px',
large: '16px',
xlarge: '18px',
xxlarge: '20px'
}; };
if (editor) { if (editor) {
editor.setOptions({ editor.setOptions({
fontSize: sizes[editorFontSize.value as undefined as 'small' | 'medium' | 'large'] fontSize: sizes[editorFontSize.value]
}); });
} }
}); });

View File

@@ -4,7 +4,7 @@
<i class="mdi mdi-folder-open mr-1" />{{ message }} <i class="mdi mdi-folder-open mr-1" />{{ message }}
</span> </span>
<span class="text-ellipsis file-uploader-value"> <span class="text-ellipsis file-uploader-value">
{{ lastPart(modelValue) }} {{ lastPart(modelValue, 19) }}
</span> </span>
<i <i
v-if="modelValue" v-if="modelValue"
@@ -24,6 +24,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useFilters } from '@/composables/useFilters';
const { lastPart } = useFilters();
defineProps({ defineProps({
message: { message: {
@@ -43,15 +46,6 @@ const id = uidGen();
const clear = () => { const clear = () => {
emit('clear'); emit('clear');
}; };
const lastPart = (string: string) => {
if (!string) return '';
string = string.split(/[/\\]+/).pop();
if (string.length >= 19)
string = `...${string.slice(-19)}`;
return string;
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -4,7 +4,7 @@
v-model="selectedGroup" v-model="selectedGroup"
class="form-select" class="form-select"
:options="[{name: 'manual'}, ...fakerGroups]" :options="[{name: 'manual'}, ...fakerGroups]"
:option-label="(opt: any) => opt.name === 'manual' ? $t('message.manualValue') : $t(`faker.${opt.name}`)" :option-label="(opt: any) => opt.name === 'manual' ? t('message.manualValue') : t(`faker.${opt.name}`)"
option-track-by="name" option-track-by="name"
:disabled="!isChecked" :disabled="!isChecked"
style="flex-grow: 0;" style="flex-grow: 0;"
@@ -15,7 +15,7 @@
v-if="selectedGroup !== 'manual'" v-if="selectedGroup !== 'manual'"
v-model="selectedMethod" v-model="selectedMethod"
:options="fakerMethods" :options="fakerMethods"
:option-label="(opt: any) => $t(`faker.${opt.name}`)" :option-label="(opt: any) => t(`faker.${opt.name}`)"
option-track-by="name" option-track-by="name"
class="form-select" class="form-select"
:disabled="!isChecked" :disabled="!isChecked"
@@ -41,7 +41,7 @@
<BaseUploadInput <BaseUploadInput
v-else-if="inputProps().type === 'file'" v-else-if="inputProps().type === 'file'"
:model-value="selectedValue" :model-value="selectedValue"
:message="$t('word.browse')" :message="t('word.browse')"
@clear="clearValue" @clear="clearValue"
@change="filesChange($event)" @change="filesChange($event)"
/> />
@@ -92,6 +92,9 @@ import BaseUploadInput from '@/components/BaseUploadInput.vue';
import ForeignKeySelect from '@/components/ForeignKeySelect.vue'; import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
import FakerMethods from 'common/FakerMethods'; import FakerMethods from 'common/FakerMethods';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
type: String, type: String,

View File

@@ -13,7 +13,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, Ref, ref } from 'vue'; import { computed, Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
@@ -21,6 +21,7 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import { TEXT, LONG_TEXT } from 'common/fieldTypes'; import { TEXT, LONG_TEXT } from 'common/fieldTypes';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { TableField } from 'common/interfaces/antares'; import { TableField } from 'common/interfaces/antares';
import { useFilters } from '@/composables/useFilters';
const props = defineProps({ const props = defineProps({
modelValue: [String, Number], modelValue: [String, Number],
@@ -35,17 +36,18 @@ const emit = defineEmits(['update:modelValue', 'blur']);
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { cutText } = useFilters();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const editField: Ref<HTMLSelectElement> = ref(null); const editField: Ref<HTMLSelectElement> = ref(null);
const foreignList = ref([]); const foreignList = ref([]);
const currentValue = ref(props.modelValue); const currentValue = ref(null);
const isValidDefault = computed(() => { const isValidDefault = computed(() => {
if (!foreignList.value.length) return true; if (!foreignList.value.length) return true;
if (props.modelValue === null) return false; if (props.modelValue === null) return false;
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue.toString()); return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue?.toString());
}); });
const foreigns = computed(() => { const foreigns = computed(() => {
@@ -53,7 +55,7 @@ const foreigns = computed(() => {
if (!isValidDefault.value) if (!isValidDefault.value)
list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue }); list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue });
for (const row of foreignList.value) for (const row of foreignList.value)
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` }); list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '', 15)}` });
return list; return list;
}); });
@@ -61,10 +63,9 @@ const onChange = (opt: HTMLSelectElement) => {
emit('update:modelValue', opt.value); emit('update:modelValue', opt.value);
}; };
const cutText = (val: string) => { watch(() => props.modelValue, () => {
if (typeof val !== 'string') return val; currentValue.value = props.modelValue;
return val.length > 15 ? `${val.substring(0, 15)}...` : val; });
};
let foreignDesc: string | false; let foreignDesc: string | false;
const params = { const params = {

View File

@@ -0,0 +1,122 @@
<template>
<div class="form-group has-icon-right m-0">
<input
class="form-input"
type="text"
:value="pressedKeys"
:placeholder="t('message.registerAShortcut')"
@focus="isFocus = true"
@blur="isFocus = false"
@keydown.prevent.stop="onKey"
>
<i class="form-icon mdi mdi-keyboard-outline mdi-24px" />
</div>
</template>
<script setup lang="ts">
import { computed, PropType, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import Application from '@/ipc-api/Application';
const { t } = useI18n();
const emit = defineEmits(['update:modelValue']);
const isMacOS = process.platform === 'darwin';
const props = defineProps({
modelValue: String as PropType<string | Electron.Accelerator>
});
const isFocus = ref(false);
const keyboardEvent: Ref<KeyboardEvent> = ref(null);
const pressedKeys = computed(() => {
const keys: string[] = [];
const singleKeysToIgnore = ['Dead', 'Backspace', 'ArrotLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
const specialKeys = ['Control', 'Alt', 'AltGraph', 'Shift', 'Meta', 'CapsLock', 'ContextMenu', 'Escape'];
const keysFromCode = ['Space', 'Minus', 'Equal', 'Slash', 'Quote', 'Semicolon', 'Comma', 'Period', 'Backslash', 'BracketLeft', 'BracketRight'];
if (props.modelValue && !keyboardEvent.value)
return props.modelValue;
else if (keyboardEvent.value) {
if (keyboardEvent.value.altKey)
keys.push('Alt');
if (keyboardEvent.value.ctrlKey)
keys.push('Control');
if (keyboardEvent.value.metaKey && isMacOS)
keys.push('Command');
if (keyboardEvent.value.shiftKey && keys.length)
keys.push('Shift');
if (keyboardEvent.value.code) {
if (keys.length === 0 && (keyboardEvent.value.key.length === 1 || singleKeysToIgnore.includes(keyboardEvent.value.key)))
return t('message.invalidShortcutMessage');
else if (!specialKeys.includes(keyboardEvent.value.key)) {
if (keyboardEvent.value.key === 'Dead') {
keys.push(keyboardEvent.value.code
.replace('Digit', '')
.replace('Key', '')
.replace('Quote', '\'')
.replace('Backquote', '`'));
}
else if (keysFromCode.includes(keyboardEvent.value.code) || keyboardEvent.value.code.includes('Digit')) {
keys.push(keyboardEvent.value.code
.replace('Quote', '\'')
.replace('Semicolon', ';')
.replace('Slash', '/')
.replace('Backslash', '\\')
.replace('BracketLeft', '[')
.replace('BracketRight', ']')
.replace('Comma', ',')
.replace('Period', '.')
.replace('Minus', '-')
.replace('Equal', '=')
.replace('Digit', '')
.replace('Key', ''));
}
else {
keys.push(keyboardEvent.value.key.length === 1
? keyboardEvent.value.key.toUpperCase()
: keyboardEvent.value.key
.replace('Arrow', '')
);
}
}
else
return t('message.invalidShortcutMessage');
}
}
return keys.join('+');
});
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
e.preventDefault();
keyboardEvent.value = e;
};
watch(pressedKeys, (value) => {
if (value !== t('message.invalidShortcutMessage'))
emit('update:modelValue', pressedKeys.value);
});
watch(isFocus, (val) => {
if (val)
Application.unregisterShortcuts();
else
Application.reloadShortcuts();
});
</script>
<style lang="scss" scoped>
.has-icon-right {
.form-input {
padding-right: 1.8rem;
overflow: hidden;
text-overflow: ellipsis;
caret-color: transparent;
}
.form-icon {
right: 0.4rem;
}
}
</style>

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-apps mr-1" /> <i class="mdi mdi-24px mdi-apps mr-1" />
<span class="cut-text">{{ $t('message.allConnections') }}</span> <span class="cut-text">{{ t('message.allConnections') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -126,7 +126,7 @@
key="trick" key="trick"
readonly readonly
class="p-absolute" class="p-absolute"
style="width: 1px; height: 1px; opacity: 0" style="width: 1px; height: 1px; opacity: 0;"
type="text" type="text"
> >
<!-- workaround for useFocusTrap $lastFocusable --> <!-- workaround for useFocusTrap $lastFocusable -->
@@ -270,89 +270,87 @@ onBeforeUnmount(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.vscroll { .vscroll {
height: 1000px; height: 1000px;
overflow: auto; overflow: auto;
overflow-anchor: none; overflow-anchor: none;
} }
.column-resizable { .column-resizable {
&:hover,
&:hover, &:active {
&:active { resize: horizontal;
resize: horizontal; overflow: hidden;
overflow: hidden; }
}
} }
.table-column-title { .table-column-title {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.sort-icon { .sort-icon {
font-size: 0.7rem; font-size: 0.7rem;
line-height: 1; line-height: 1;
margin-left: 0.2rem; margin-left: 0.2rem;
} }
.modal { .modal {
align-items: flex-start; align-items: flex-start;
.modal-container { .modal-container {
max-width: 75vw; max-width: 75vw;
margin-top: 10vh; margin-top: 10vh;
.modal-body { .modal-body {
height: 80vh; height: 80vh;
} }
} }
} }
.connections-search { .connections-search {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
} }
.connection-block { .connection-block {
cursor: pointer; cursor: pointer;
transition: all .2s; transition: all 0.2s;
border-radius: $border-radius; border-radius: $border-radius;
outline: none; outline: none;
&:focus { &:focus {
box-shadow: 0 0 3px .1rem rgba($primary-color, 80%); box-shadow: 0 0 3px 0.1rem rgba($primary-color, 80%);
} }
&:hover {
.all-connections-buttons {
.all-connections-delete,
.all-connections-pinned,
.all-connections-pin {
opacity: .5;
}
}
}
.all-connections-buttons {
.all-connections-pinned {
opacity: .3;
transition: opacity .2s;
&:hover {
opacity: 1;
}
}
&:hover {
.all-connections-buttons {
.all-connections-delete, .all-connections-delete,
.all-connections-pinned,
.all-connections-pin { .all-connections-pin {
opacity: 0; opacity: 0.5;
transition: opacity .2s;
&:hover {
opacity: 1;
}
} }
} }
}
.all-connections-buttons {
.all-connections-pinned {
opacity: 0.3;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
.all-connections-delete,
.all-connections-pin {
opacity: 0;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
} }
</style> </style>

View File

@@ -6,7 +6,7 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ $t('word.credentials') }} <i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ t('word.credentials') }}
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -16,7 +16,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.user') }}</label> <label class="form-label">{{ t('word.user') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -29,7 +29,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.password') }}</label> <label class="form-label">{{ t('word.password') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -44,10 +44,10 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="sendCredentials"> <button class="btn btn-primary mr-2" @click.stop="sendCredentials">
{{ $t('word.send') }} {{ t('word.send') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -58,6 +58,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { trapRef } = useFocusTrap(); const { trapRef } = useFocusTrap();

View File

@@ -1,7 +1,7 @@
<template> <template>
<ConfirmModal <ConfirmModal
:confirm-text="$t('word.run')" :confirm-text="t('word.run')"
:cancel-text="$t('word.cancel')" :cancel-text="t('word.cancel')"
size="400" size="400"
@confirm="runRoutine" @confirm="runRoutine"
@hide="closeModal" @hide="closeModal"
@@ -9,7 +9,7 @@
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-play mr-1" /> <i class="mdi mdi-24px mdi-play mr-1" />
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span> <span class="cut-text">{{ t('word.parameters') }}: {{ localRoutine.name }}</span>
</div> </div>
</template> </template>
<template #body> <template #body>
@@ -52,6 +52,12 @@ import { computed, PropType, Ref, ref } from 'vue';
import { NUMBER, FLOAT } from 'common/fieldTypes'; import { NUMBER, FLOAT } from 'common/fieldTypes';
import { FunctionInfos, RoutineInfos } from 'common/interfaces/antares'; import { FunctionInfos, RoutineInfos } from 'common/interfaces/antares';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { wrapNumber } = useFilters();
const props = defineProps({ const props = defineProps({
localRoutine: Object as PropType<RoutineInfos | FunctionInfos>, localRoutine: Object as PropType<RoutineInfos | FunctionInfos>,
@@ -106,11 +112,6 @@ const onKey = (e: KeyboardEvent) => {
closeModal(); closeModal();
}; };
const wrapNumber = (num: number) => {
if (!num) return '';
return `(${num})`;
};
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
setTimeout(() => { setTimeout(() => {

View File

@@ -1,18 +1,18 @@
<template> <template>
<ConfirmModal <ConfirmModal
:confirm-text="$t('word.discard')" :confirm-text="t('word.discard')"
:cancel-text="$t('word.stay')" :cancel-text="t('word.stay')"
@confirm="emit('confirm')" @confirm="emit('confirm')"
@hide="emit('close')" @hide="emit('close')"
> >
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }} <i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ t('message.unsavedChanges') }}
</div> </div>
</template> </template>
<template #body> <template #body>
<div> <div>
{{ $t('message.discardUnsavedChanges') }} {{ t('message.discardUnsavedChanges') }}
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -21,6 +21,9 @@
<script setup lang="ts"> <script setup lang="ts">
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { onBeforeUnmount } from 'vue'; import { onBeforeUnmount } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const emit = defineEmits(['confirm', 'close']); const emit = defineEmits(['confirm', 'close']);

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" /> <i class="mdi mdi-24px mdi-database-edit mr-1" />
<span class="cut-text">{{ $t('message.editSchema') }}</span> <span class="cut-text">{{ t('message.editSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -26,14 +26,14 @@
class="form-input" class="form-input"
type="text" type="text"
required required
:placeholder="$t('message.schemaName')" :placeholder="t('message.schemaName')"
readonly readonly
> >
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label> <label class="form-label">{{ t('word.collation') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<BaseSelect <BaseSelect
@@ -43,7 +43,7 @@
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
/> />
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small> <small>{{ t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div> </div>
</div> </div>
</form> </form>
@@ -51,10 +51,10 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema"> <button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }} {{ t('word.update') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -70,6 +70,9 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
selectedSchema: String selectedSchema: String

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" /> <i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
<span class="cut-text">{{ $t('message.exportSchema') }}</span> <span class="cut-text">{{ t('message.exportSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -16,7 +16,7 @@
<div class="container"> <div class="container">
<div class="columns"> <div class="columns">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('message.directoryPath') }}</label> <label class="form-label">{{ t('message.directoryPath') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<fieldset class="input-group"> <fieldset class="input-group">
@@ -26,14 +26,14 @@
type="text" type="text"
required required
readonly readonly
:placeholder="$t('message.schemaName')" :placeholder="t('message.schemaName')"
> >
<button <button
type="button" type="button"
class="btn btn-primary input-group-btn" class="btn btn-primary input-group-btn"
@click.prevent="openPathDialog" @click.prevent="openPathDialog"
> >
{{ $t('word.change') }} {{ t('word.change') }}
</button> </button>
</fieldset> </fieldset>
</div> </div>
@@ -51,14 +51,14 @@
<div class="column col-auto col-ml-auto "> <div class="column col-auto col-ml-auto ">
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('word.refresh')" :title="t('word.refresh')"
@click="refresh" @click="refresh"
> >
<i class="mdi mdi-database-refresh" /> <i class="mdi mdi-database-refresh" />
</button> </button>
<button <button
class="btn btn-dark btn-sm mx-1" class="btn btn-dark btn-sm mx-1"
:title="$t('message.uncheckAllTables')" :title="t('message.uncheckAllTables')"
:disabled="isRefreshing" :disabled="isRefreshing"
@click="uncheckAllTables" @click="uncheckAllTables"
> >
@@ -66,7 +66,7 @@
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.checkAllTables')" :title="t('message.checkAllTables')"
:disabled="isRefreshing" :disabled="isRefreshing"
@click="checkAllTables" @click="checkAllTables"
> >
@@ -122,22 +122,22 @@
<div class="tr"> <div class="tr">
<div class="th" style="width: 50%;"> <div class="th" style="width: 50%;">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.table') }}</span> <span>{{ t('word.table') }}</span>
</div> </div>
</div> </div>
<div class="th text-center"> <div class="th text-center">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.structure') }}</span> <span>{{ t('word.structure') }}</span>
</div> </div>
</div> </div>
<div class="th text-center"> <div class="th text-center">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.content') }}</span> <span>{{ t('word.content') }}</span>
</div> </div>
</div> </div>
<div class="th text-center"> <div class="th text-center">
<div class="table-column-title"> <div class="table-column-title">
<span>{{ $t('word.drop') }}</span> <span>{{ t('word.drop') }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -183,19 +183,19 @@
</div> </div>
<div class="column col-4"> <div class="column col-4">
<h5 class="h5"> <h5 class="h5">
{{ $t('word.options') }} {{ t('word.options') }}
</h5> </h5>
<span class="h6">{{ $t('word.includes') }}:</span> <span class="h6">{{ t('word.includes') }}:</span>
<label <label
v-for="(_, key) in options.includes" v-for="(_, key) in options.includes"
:key="key" :key="key"
class="form-checkbox" class="form-checkbox"
> >
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $tc(`word.${key}`, 2) }} <input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ t(`word.${key}`, 2) }}
</label> </label>
<div v-if="clientCustoms.exportByChunks"> <div v-if="clientCustoms.exportByChunks">
<div class="h6 mt-4 mb-2"> <div class="h6 mt-4 mb-2">
{{ $t('message.newInserStmtEvery') }}: {{ t('message.newInserStmtEvery') }}:
</div> </div>
<div class="columns"> <div class="columns">
<div class="column col-6"> <div class="column col-6">
@@ -209,21 +209,21 @@
<BaseSelect <BaseSelect
v-model="options.sqlInsertDivider" v-model="options.sqlInsertDivider"
class="form-select" class="form-select"
:options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: $tc('word.row', 2)}]" :options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: t('word.row', 2)}]"
/> />
</div> </div>
</div> </div>
</div> </div>
<div class="h6 mb-2 mt-4"> <div class="h6 mb-2 mt-4">
{{ $t('message.ourputFormat') }}: {{ t('message.ourputFormat') }}:
</div> </div>
<div class="columns"> <div class="columns">
<div class="column h5 mb-4"> <div class="column h5 mb-4">
<BaseSelect <BaseSelect
v-model="options.outputFormat" v-model="options.outputFormat"
class="form-select" class="form-select"
:options="[{value: 'sql', label: $t('message.singleFile', {ext: '.sql'})}, {value: 'sql.zip', label: $t('message.zipCompressedFile', {ext: '.sql'})}]" :options="[{value: 'sql', label: t('message.singleFile', {ext: '.sql'})}, {value: 'sql.zip', label: t('message.zipCompressedFile', {ext: '.sql'})}]"
/> />
</div> </div>
</div> </div>
@@ -245,7 +245,7 @@
</div> </div>
<div class="column col-auto px-0"> <div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
<button <button
class="btn btn-primary mr-2" class="btn btn-primary mr-2"
@@ -254,7 +254,7 @@
autofocus autofocus
@click.prevent="startExport" @click.prevent="startExport"
> >
{{ $t('word.export') }} {{ t('word.export') }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $tc('message.insertRow', 2) }}</span> <span class="cut-text">{{ t('message.insertRow', 2) }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -39,7 +39,7 @@
<span class="input-group-addon field-type" :class="typeClass(field.type)"> <span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ wrapNumber(fieldLength(field)) }} {{ field.type }} {{ wrapNumber(fieldLength(field)) }}
</span> </span>
<label class="form-checkbox ml-3" :title="$t('word.insert')"> <label class="form-checkbox ml-3" :title="t('word.insert')">
<input <input
type="checkbox" type="checkbox"
:checked="!fieldsToExclude.includes(field.name)" :checked="!fieldsToExclude.includes(field.name)"
@@ -55,7 +55,7 @@
</div> </div>
<div class="modal-footer columns"> <div class="modal-footer columns">
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'"> <div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')"> <div class="input-group tooltip tooltip-right" :data-tooltip="t('message.numberOfInserts')">
<input <input
v-model="nInserts" v-model="nInserts"
type="number" type="number"
@@ -70,7 +70,7 @@
<div <div
v-if="hasFakes" v-if="hasFakes"
class="tooltip tooltip-right ml-2" class="tooltip tooltip-right ml-2"
:data-tooltip="$t('message.fakeDataLanguage')" :data-tooltip="t('message.fakeDataLanguage')"
> >
<BaseSelect <BaseSelect
v-model="fakerLocale" v-model="fakerLocale"
@@ -85,10 +85,10 @@
:class="{'loading': isInserting}" :class="{'loading': isInserting}"
@click.stop="insertRows" @click.stop="insertRows"
> >
{{ $t('word.insert') }} {{ t('word.insert') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -109,10 +109,20 @@ import { useFocusTrap } from '@/composables/useFocusTrap';
import Tables from '@/ipc-api/Tables'; import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect.vue'; import FakerSelect from '@/components/FakerSelect.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { wrapNumber } = useFilters();
const props = defineProps({ const props = defineProps({
tabUid: [String, Number], tabUid: [String, Number],
schema: String,
table: String,
fields: Array as Prop<TableField[]>, fields: Array as Prop<TableField[]>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rowToDuplicate: Object as Prop<any>,
keyUsage: Array as Prop<TableForeign[]> keyUsage: Array as Prop<TableForeign[]>
}); });
@@ -123,8 +133,6 @@ const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
const { trapRef } = useFocusTrap({ disableAutofocus: true }); const { trapRef } = useFocusTrap({ disableAutofocus: true });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -134,7 +142,6 @@ const nInserts = ref(1);
const isInserting = ref(false); const isInserting = ref(false);
const fakerLocale = ref('en'); const fakerLocale = ref('en');
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
const foreignKeys = computed(() => props.keyUsage.map(key => key.field)); const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual')); const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
@@ -220,8 +227,8 @@ const insertRows = async () => {
try { try {
const { status, response } = await Tables.insertTableFakeRows({ const { status, response } = await Tables.insertTableFakeRows({
uid: selectedWorkspace.value, uid: selectedWorkspace.value,
schema: workspace.value.breadcrumbs.schema, schema: props.schema,
table: workspace.value.breadcrumbs.table, table: props.table,
row: rowToInsert, row: rowToInsert,
repeat: nInserts.value, repeat: nInserts.value,
fields: fieldTypes, fields: fieldTypes,
@@ -266,11 +273,6 @@ const onKey = (e: KeyboardEvent) => {
closeModal(); closeModal();
}; };
const wrapNumber = (num: number) => {
if (!num) return '';
return `(${num})`;
};
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
@@ -284,44 +286,57 @@ onMounted(() => {
const rowObj: {[key: string]: unknown} = {}; const rowObj: {[key: string]: unknown} = {};
for (const field of props.fields) { if (!props.rowToDuplicate) {
let fieldDefault; // Set default values
for (const field of props.fields) {
let fieldDefault;
if (field.default === 'NULL') fieldDefault = null; if (field.default === 'NULL') fieldDefault = null;
else { else {
if ([...NUMBER, ...FLOAT].includes(field.type)) if ([...NUMBER, ...FLOAT].includes(field.type))
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', ''); fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) { else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
fieldDefault = field.default fieldDefault = field.default
? field.default.includes('\'') ? field.default.includes('\'')
? field.default.split('\'')[1] ? field.default.split('\'')[1]
: field.default : field.default
: ''; : '';
}
else if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
else if (BIT.includes(field.type))
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
} }
else if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
else if (BIT.includes(field.type))
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
else
fieldDefault = field.default;
}
else if (field.enumValues)
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
else else
fieldDefault = field.default; fieldDefault = field.default;
} }
else if (field.enumValues)
fieldDefault = field.enumValues.replaceAll('\'', '').split(','); rowObj[field.name] = { value: fieldDefault };
else
fieldDefault = field.default; if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
} }
}
else {
// Set values to duplicate
for (const field of props.fields) {
if (typeof props.rowToDuplicate[field.name] !== 'object')
rowObj[field.name] = { value: props.rowToDuplicate[field.name] };
rowObj[field.name] = { value: fieldDefault }; if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields }
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
} }
localRow.value = { ...rowObj }; localRow.value = { ...rowObj };

View File

@@ -100,14 +100,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue'; import { Component, computed, ComputedRef, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import * as moment from 'moment';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { HistoryRecord, useHistoryStore } from '@/stores/history'; import { HistoryRecord, useHistoryStore } from '@/stores/history';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import { useFilters } from '@/composables/useFilters';
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue'; import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
const { t } = useI18n(); const { t } = useI18n();
const { formatDate } = useFilters();
const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore(); const { getHistoryByWorkspace, deleteQueryFromHistory } = useHistoryStore();
const { getConnectionName } = useConnectionsStore(); const { getConnectionName } = useConnectionsStore();
@@ -164,7 +165,6 @@ const resizeResults = () => {
} }
}; };
const formatDate = (date: Date) => moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
const refreshScroller = () => resizeResults(); const refreshScroller = () => resizeResults();
const closeModal = () => emit('close'); const closeModal = () => emit('close');

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" /> <i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
<span class="cut-text">{{ $t('message.importSchema') }}</span> <span class="cut-text">{{ t('message.importSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -15,7 +15,7 @@
<div class="modal-body pb-0"> <div class="modal-body pb-0">
{{ sqlFile }} {{ sqlFile }}
<div v-if="queryErrors.length > 0" class="mt-2"> <div v-if="queryErrors.length > 0" class="mt-2">
<label>{{ $tc('message.importQueryErrors', queryErrors.length) }}</label> <label>{{ t('message.importQueryErrors', queryErrors.length) }}</label>
<textarea <textarea
v-model="formattedQueryErrors" v-model="formattedQueryErrors"
class="form-input" class="form-input"
@@ -28,7 +28,7 @@
<div class="column col modal-progress-wrapper text-left"> <div class="column col modal-progress-wrapper text-left">
<div class="import-progress"> <div class="import-progress">
<span class="progress-status"> <span class="progress-status">
{{ progressPercentage }}% - {{ progressStatus }} - {{ $tc('message.executedQueries', queryCount) }} {{ progressPercentage }}% - {{ progressStatus }} - {{ t('message.executedQueries', queryCount) }}
</span> </span>
<progress <progress
class="progress d-block" class="progress d-block"
@@ -39,7 +39,7 @@
</div> </div>
<div class="column col-auto px-0"> <div class="column col-auto px-0">
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ completed ? $t('word.close') : $t('word.cancel') }} {{ completed ? t('word.close') : t('word.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -57,8 +57,8 @@ import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import { useI18n } from 'vue-i18n';
import { ImportState } from 'common/interfaces/importer'; import { ImportState } from 'common/interfaces/importer';
import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -7,7 +7,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" /> <i class="mdi mdi-24px mdi-database-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewSchema') }}</span> <span class="cut-text">{{ t('message.createNewSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -17,7 +17,7 @@
<form class="form-horizontal" @submit.prevent="createSchema"> <form class="form-horizontal" @submit.prevent="createSchema">
<div class="form-group"> <div class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.name') }}</label> <label class="form-label">{{ t('word.name') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<input <input
@@ -26,13 +26,13 @@
class="form-input" class="form-input"
type="text" type="text"
required required
:placeholder="$t('message.schemaName')" :placeholder="t('message.schemaName')"
> >
</div> </div>
</div> </div>
<div v-if="customizations.collations" class="form-group"> <div v-if="customizations.collations" class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label> <label class="form-label">{{ t('word.collation') }}</label>
</div> </div>
<div class="col-9"> <div class="col-9">
<BaseSelect <BaseSelect
@@ -42,7 +42,7 @@
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
/> />
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small> <small>{{ t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div> </div>
</div> </div>
</form> </form>
@@ -54,10 +54,10 @@
:class="{'loading': isLoading}" :class="{'loading': isLoading}"
@click.stop="createSchema" @click.stop="createSchema"
> >
{{ $t('word.add') }} {{ t('word.add') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }} {{ t('word.close') }}
</button> </button>
</div> </div>
</div> </div>
@@ -73,6 +73,9 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();

View File

@@ -17,7 +17,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-memory mr-1" /> <i class="mdi mdi-24px mdi-memory mr-1" />
<span class="cut-text">{{ $t('message.processesList') }}: {{ connectionName }}</span> <span class="cut-text">{{ t('message.processesList') }}: {{ connectionName }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -29,7 +29,7 @@
<button <button
class="btn btn-dark btn-sm mr-0 pr-1 d-flex" class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
:class="{'loading':isQuering}" :class="{'loading':isQuering}"
:title="`${$t('word.refresh')} (F5)`" :title="`${t('word.refresh')}`"
@click="getProcessesList" @click="getProcessesList"
> >
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" /> <i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
@@ -39,7 +39,7 @@
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</div> </div>
<div class="menu px-3"> <div class="menu px-3">
<span>{{ $t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span> <span>{{ t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
<input <input
v-model="autorefreshTimer" v-model="autorefreshTimer"
class="slider no-border" class="slider no-border"
@@ -59,7 +59,7 @@
tabindex="0" tabindex="0"
> >
<i class="mdi mdi-24px mdi-file-export mr-1" /> <i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span> <span>{{ t('word.export') }}</span>
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</button> </button>
<ul class="menu text-left"> <ul class="menu text-left">
@@ -74,7 +74,7 @@
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div v-if="sortedResults.length"> <div v-if="sortedResults.length">
{{ $t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b> {{ t('word.processes') }}: <b>{{ sortedResults.length.toLocaleString() }}</b>
</div> </div>
</div> </div>
</div> </div>
@@ -135,8 +135,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue';
import { ipcRenderer } from 'electron';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { arrayToFile } from '../libs/arrayToFile'; import { exportRows } from '../libs/exportRows';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useFocusTrap } from '@/composables/useFocusTrap'; import { useFocusTrap } from '@/composables/useFocusTrap';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
@@ -144,6 +145,9 @@ import { useConnectionsStore } from '@/stores/connections';
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue'; import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue'; import ModalProcessesListRow from '@/components/ModalProcessesListRow.vue';
import ModalProcessesListContext from '@/components/ModalProcessesListContext.vue'; import ModalProcessesListContext from '@/components/ModalProcessesListContext.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const { getConnectionName } = useConnectionsStore(); const { getConnectionName } = useConnectionsStore();
@@ -312,10 +316,10 @@ const closeModal = () => emit('close');
const downloadTable = (format: 'csv' | 'json') => { const downloadTable = (format: 'csv' | 'json') => {
if (!sortedResults.value) return; if (!sortedResults.value) return;
arrayToFile({ exportRows({
type: format, type: format,
content: sortedResults.value, content: sortedResults.value,
filename: 'processes' table: 'processes'
}); });
}; };
@@ -323,10 +327,10 @@ const onKey = (e:KeyboardEvent) => {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') if (e.key === 'Escape')
closeModal(); closeModal();
if (e.key === 'F5')
getProcessesList();
}; };
ipcRenderer.on('run-or-reload', getProcessesList);
window.addEventListener('keydown', onKey, { capture: true }); window.addEventListener('keydown', onKey, { capture: true });
onMounted(() => { onMounted(() => {
@@ -345,6 +349,8 @@ onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey, { capture: true }); window.removeEventListener('keydown', onKey, { capture: true });
window.removeEventListener('resize', resizeResults); window.removeEventListener('resize', resizeResults);
clearInterval(refreshInterval.value); clearInterval(refreshInterval.value);
ipcRenderer.removeListener('run-or-reload', getProcessesList);
}); });
defineExpose({ refreshScroller }); defineExpose({ refreshScroller });

View File

@@ -4,7 +4,7 @@
@close-context="closeContext" @close-context="closeContext"
> >
<div v-if="props.selectedRow" class="context-element"> <div v-if="props.selectedRow" class="context-element">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ $t('word.copy') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" /> <i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu"> <div class="context-submenu">
<div <div
@@ -13,7 +13,7 @@
@click="copyCell" @click="copyCell"
> >
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ $tc('word.cell', 1) }} <i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
</span> </span>
</div> </div>
<div <div
@@ -22,7 +22,7 @@
@click="copyRow" @click="copyRow"
> >
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ $tc('word.row', 1) }} <i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', 1) }}
</span> </span>
</div> </div>
</div> </div>
@@ -33,7 +33,7 @@
@click="killProcess" @click="killProcess"
> >
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-close-circle-outline text-light pr-1" /> {{ $t('message.killProcess') }} <i class="mdi mdi-18px mdi-close-circle-outline text-light pr-1" /> {{ t('message.killProcess') }}
</span> </span>
</div> </div>
</BaseContextMenu> </BaseContextMenu>
@@ -41,6 +41,9 @@
<script setup lang="ts"> <script setup lang="ts">
import BaseContextMenu from '@/components/BaseContextMenu.vue'; import BaseContextMenu from '@/components/BaseContextMenu.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,

View File

@@ -12,19 +12,19 @@
class="cell-content" class="cell-content"
:class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`" :class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`"
@dblclick="dblClick(cKey)" @dblclick="dblClick(cKey)"
>{{ cutText(col) }}</span> >{{ cutText(col, 250) }}</span>
</div> </div>
<ConfirmModal <ConfirmModal
v-if="isInfoModal" v-if="isInfoModal"
:confirm-text="$t('word.update')" :confirm-text="t('word.update')"
:cancel-text="$t('word.close')" :cancel-text="t('word.close')"
size="medium" size="medium"
:hide-footer="true" :hide-footer="true"
@hide="hideInfoModal" @hide="hideInfoModal"
> >
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ $t('message.processInfo') }} <i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ t('message.processInfo') }}
</div> </div>
</template> </template>
<template #body> <template #body>
@@ -48,6 +48,12 @@
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import TextEditor from '@/components/BaseTextEditor.vue'; import TextEditor from '@/components/BaseTextEditor.vue';
import { useFilters } from '@/composables/useFilters';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { cutText } = useFilters();
const props = defineProps({ const props = defineProps({
row: Object row: Object
@@ -79,11 +85,6 @@ const dblClick = (col: string) => {
isInfoModal.value = true; isInfoModal.value = true;
}; };
const cutText = (val: string | number) => {
if (typeof val !== 'string') return val;
return val.length > 250 ? `${val.substring(0, 250)}[...]` : val;
};
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -30,6 +30,13 @@
> >
<a class="tab-link">{{ t('word.themes') }}</a> <a class="tab-link">{{ t('word.themes') }}</a>
</li> </li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'shortcuts'}"
@click="selectTab('shortcuts')"
>
<a class="tab-link">{{ t('word.shortcuts') }}</a>
</li>
<li <li
v-if="updateStatus !== 'disabled'" v-if="updateStatus !== 'disabled'"
class="tab-item c-hand" class="tab-item c-hand"
@@ -232,7 +239,7 @@
<div class="column col-12 h6 text-uppercase mb-2 mt-4"> <div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ t('message.editorTheme') }} {{ t('message.editorTheme') }}
</div> </div>
<div class="column col-6 h5 mb-4"> <div class="column col-5 h5 mb-4">
<BaseSelect <BaseSelect
v-model="localEditorTheme" v-model="localEditorTheme"
class="form-select" class="form-select"
@@ -244,28 +251,49 @@
@change="changeEditorTheme(localEditorTheme)" @change="changeEditorTheme(localEditorTheme)"
/> />
</div> </div>
<div class="column col-6 mb-4"> <div class="column col-7 mb-4">
<div class="btn-group btn-group-block"> <div class="btn-group btn-group-block">
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xsmall'}"
@click="changeEditorFontSize('xsmall')"
>
10px
</button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'small'}" :class="{'active': editorFontSize === 'small'}"
@click="changeEditorFontSize('small')" @click="changeEditorFontSize('small')"
> >
{{ t('word.small') }} 12px
</button> </button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'medium'}" :class="{'active': editorFontSize === 'medium'}"
@click="changeEditorFontSize('medium')" @click="changeEditorFontSize('medium')"
> >
{{ t('word.medium') }} 14px
</button> </button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'large'}" :class="{'active': editorFontSize === 'large'}"
@click="changeEditorFontSize('large')" @click="changeEditorFontSize('large')"
> >
{{ t('word.large') }} 16px
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xlarge'}"
@click="changeEditorFontSize('xlarge')"
>
18px
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xxlarge'}"
@click="changeEditorFontSize('xxlarge')"
>
20px
</button> </button>
</div> </div>
</div> </div>
@@ -282,6 +310,9 @@
</div> </div>
</div> </div>
<div v-show="selectedTab === 'shortcuts'" class="panel-body py-4">
<ModalSettingsShortcuts />
</div>
<div v-show="selectedTab === 'update'" class="panel-body py-4"> <div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate /> <ModalSettingsUpdate />
</div> </div>
@@ -315,7 +346,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, Ref, ref } from 'vue'; import { onBeforeUnmount, Ref, ref, computed } from 'vue';
import { shell } from 'electron'; import { shell } from 'electron';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -326,11 +357,12 @@ import { useFocusTrap } from '@/composables/useFocusTrap';
import { localesNames } from '@/i18n/supported-locales'; import { localesNames } from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue'; import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue'; import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue';
import ModalSettingsShortcuts from '@/components/ModalSettingsShortcuts.vue';
import BaseTextEditor from '@/components/BaseTextEditor.vue'; import BaseTextEditor from '@/components/BaseTextEditor.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { computed } from '@vue/reactivity'; import { AvailableLocale } from '@/i18n';
const { t, availableLocales } = useI18n(); const { t } = useI18n();
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@@ -384,7 +416,29 @@ const contributors = process.env.APP_CONTRIBUTORS;
const appLogo = require('../images/logo.svg'); const appLogo = require('../images/logo.svg');
const darkPreview = require('../images/dark.png'); const darkPreview = require('../images/dark.png');
const lightPreview = require('../images/light.png'); const lightPreview = require('../images/light.png');
const editorThemes= [ const exampleQuery = `-- This is an example
SELECT
employee.id,
employee.first_name,
employee.last_name,
SUM(DATEDIFF("SECOND", call.start, call.end)) AS call_duration
FROM call
INNER JOIN employee ON call.employee_id = employee.id
GROUP BY
employee.id,
employee.first_name,
employee.last_name
ORDER BY
employee.id ASC;
`;
const localLocale: Ref<AvailableLocale> = ref(null);
const localPageSize: Ref<number> = ref(null);
const localTimeout: Ref<number> = ref(null);
const localEditorTheme: Ref<string> = ref(null);
const selectedTab: Ref<string> = ref('general');
const editorThemes = computed(() => [
{ {
group: t('word.light'), group: t('word.light'),
themes: [ themes: [
@@ -432,32 +486,11 @@ const editorThemes= [
{ code: 'vibrant_ink', name: 'Vibrant Ink' } { code: 'vibrant_ink', name: 'Vibrant Ink' }
] ]
} }
]; ]);
const exampleQuery = `-- This is an example
SELECT
employee.id,
employee.first_name,
employee.last_name,
SUM(DATEDIFF("SECOND", call.start, call.end)) AS call_duration
FROM call
INNER JOIN employee ON call.employee_id = employee.id
GROUP BY
employee.id,
employee.first_name,
employee.last_name
ORDER BY
employee.id ASC;
`;
const localLocale: Ref<string> = ref(null);
const localPageSize: Ref<number> = ref(null);
const localTimeout: Ref<number> = ref(null);
const localEditorTheme: Ref<string> = ref(null);
const selectedTab: Ref<string> = ref('general');
const locales = computed(() => { const locales = computed(() => {
const locales = []; const locales = [];
for (const locale of availableLocales) for (const locale of Object.keys(localesNames))
locales.push({ code: locale, name: localesNames[locale] }); locales.push({ code: locale, name: localesNames[locale] });
return locales; return locales;
@@ -517,7 +550,7 @@ const toggleLineWrap = () => {
changeLineWrap(!selectedLineWrap.value); changeLineWrap(!selectedLineWrap.value);
}; };
localLocale.value = selectedLocale.value as string; localLocale.value = selectedLocale.value;
localPageSize.value = pageSize.value as number; localPageSize.value = pageSize.value as number;
localTimeout.value = notificationsTimeout.value as number; localTimeout.value = notificationsTimeout.value as number;
localEditorTheme.value = editorTheme.value as string; localEditorTheme.value = editorTheme.value as string;
@@ -538,6 +571,12 @@ onBeforeUnmount(() => {
.modal-body { .modal-body {
overflow: hidden; overflow: hidden;
.tab-link {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.panel-body { .panel-body {
min-height: calc(25vh - 70px); min-height: calc(25vh - 70px);
max-height: 65vh; max-height: 65vh;

View File

@@ -0,0 +1,313 @@
<template>
<div class="p-relative">
<div class="shortcuts-tools pb-2 px-2">
<button class="btn btn-dark btn-sm d-flex ml-2" @click="showAddModal">
<i class="mdi mdi-24px mdi-plus mr-1" /><span>{{ t('message.addShortcut') }}</span>
</button>
<button class="btn btn-dark btn-sm d-flex ml-2" @click="isConfirmRestoreModal = true">
<i class="mdi mdi-24px mdi-undo mr-1" /><span>{{ t('message.restoreDefaults') }}</span>
</button>
</div>
<div class="container workspace-query-results">
<div class="table table-hover">
<div class="thead">
<div class="tr text-uppercase">
<div class="th no-border">
<div>
{{ t('word.event') }}
</div>
</div>
<div class="th no-border" style="width: 100%;">
<div>
{{ t('word.key', 2) }}
</div>
</div>
<div class="th no-border" />
</div>
</div>
<div class="tbody">
<div
v-for="(shortcut, i) in shortcuts"
:key="i"
class="tr"
tabindex="0"
>
<div class="td py-1">
{{ t(shortcutEvents[shortcut.event].l18n, {param: shortcutEvents[shortcut.event].l18nParam}) }}
</div>
<div
class="td py-1"
style="border-right: 0;"
v-html="parseKeys(shortcut.keys)"
/>
<div class="td py-1 pr-2">
<button class="shortcut-button btn btn-link btn-sm d-flex p-0 px-1 mr-2" @click="showEditModal({...shortcut, index: i})">
<span>{{ t('word.edit') }}</span><i class="mdi mdi-pencil ml-1" />
</button>
<button class="shortcut-button btn btn-link btn-sm d-flex p-0 px-1" @click="showDeleteModal(shortcut)">
<span>{{ t('word.delete') }}</span><i class="mdi mdi-delete-outline ml-1" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<Teleport to="#window-content">
<ConfirmModal
v-if="isConfirmAddModal"
:disable-autofocus="true"
:confirm-text="t('word.save')"
:close-on-confirm="false"
@confirm="addShortcut"
@hide="closeAddModal"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ t('message.addShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<BaseSelect
v-model="shortcutToAdd.event"
class="form-select"
:options="eventOptions"
/>
</div>
</div>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.key', 2) }}</label>
<KeyPressDetector v-model="typedShortcut" />
</div>
</div>
<small v-if="doesShortcutExists" class="text-warning">{{ t('message.shortcutAlreadyExists') }}</small>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmEditModal"
:disable-autofocus="true"
:confirm-text="t('word.save')"
:close-on-confirm="false"
@confirm="editShortcut"
@hide="closeEditModal"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ t('message.editShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.event') }}</label>
<BaseSelect
v-model="shortcutToEdit.event"
class="form-select"
:options="eventOptions"
:disabled="true"
/>
</div>
</div>
<div class="mb-2">
<div class="form-group">
<label class="form-label">{{ t('word.key', 2) }}</label>
<KeyPressDetector v-model="shortcutToEdit.keys[0]" />
</div>
</div>
<small v-if="doesShortcutExists" class="text-warning">{{ t('message.shortcutAlreadyExists') }}</small>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmDeleteModal"
:disable-autofocus="true"
@confirm="deleteShortcut"
@hide="isConfirmDeleteModal = false"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ t('message.deleteShortcut') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.deleteCorfirm') }} <b>{{ t(shortcutEvents[shortcutToDelete.event].l18n, {param: shortcutEvents[shortcutToDelete.event].l18nParam}) }} (<span v-html="parseKeys(shortcutToDelete.keys)" />)</b>?
</div>
</template>
</ConfirmModal>
<ConfirmModal
v-if="isConfirmRestoreModal"
:disable-autofocus="true"
@confirm="restoreDefaults"
@hide="isConfirmRestoreModal = false"
>
<template #header>
<div class="d-flex">
<i class="mdi mdi-24px mdi-undo mr-1" /> {{ t('message.restoreDefaults') }}
</div>
</template>
<template #body>
<div class="mb-2">
{{ t('message.restoreDefaultsQuestion') }}
</div>
</template>
</ConfirmModal>
</Teleport>
</template>
<script setup lang="ts">
import { Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useSettingsStore } from '@/stores/settings';
import KeyPressDetector from './KeyPressDetector.vue';
import Application from '@/ipc-api/Application';
import { shortcutEvents, ShortcutRecord } from 'common/shortcuts';
import ConfirmModal from '@/components/BaseConfirmModal.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import { computed } from '@vue/reactivity';
import { useFilters } from '@/composables/useFilters';
const { parseKeys } = useFilters();
const { t } = useI18n();
const isMacOS = process.platform === 'darwin';
const isConfirmRestoreModal = ref(false);
const isConfirmAddModal = ref(false);
const isConfirmEditModal = ref(false);
const isConfirmDeleteModal = ref(false);
const doesShortcutExists = ref(false);
const shortcutToAdd: Ref<ShortcutRecord> = ref({ event: undefined, keys: [], os: [process.platform] });
const shortcutToEdit: Ref<ShortcutRecord & { index: number }> = ref(null);
const shortcutToDelete: Ref<ShortcutRecord> = ref(null);
const typedShortcut = ref('');
const settingsStore = useSettingsStore();
const { shortcuts } = storeToRefs(settingsStore);
const eventOptions = computed(() => {
return Object.keys(shortcutEvents)
.map(key => {
return { value: key, label: t(shortcutEvents[key].l18n, { param: shortcutEvents[key].l18nParam }) };
})
.sort((a, b) => {
if (a.label < b.label) return -1;
if (a.label > b.label) return 1;
return 0;
});
});
const restoreDefaults = () => {
isConfirmRestoreModal.value = false;
return Application.restoreDefaultShortcuts();
};
const showAddModal = () => {
shortcutToAdd.value.event = eventOptions.value[0].value;
isConfirmAddModal.value = true;
};
const closeAddModal = () => {
typedShortcut.value = '';
doesShortcutExists.value = false;
shortcutToAdd.value = { event: undefined, keys: [], os: [process.platform] };
isConfirmAddModal.value = false;
};
const showEditModal = (shortcut: ShortcutRecord & { index: number }) => {
shortcut = {
...shortcut,
keys: [shortcut.keys[0].replaceAll('CommandOrControl', isMacOS ? 'Command' : 'Control')]
};
shortcutToEdit.value = shortcut;
isConfirmEditModal.value = true;
};
const editShortcut = () => {
const index = shortcutToEdit.value.index;
delete shortcutToEdit.value.index;
shortcutToEdit.value.index = undefined;
shortcuts.value[index] = shortcutToEdit.value;
isConfirmEditModal.value = false;
return Application.updateShortcuts(shortcuts.value);
};
const closeEditModal = () => {
typedShortcut.value = '';
doesShortcutExists.value = false;
shortcutToEdit.value = null;
isConfirmEditModal.value = false;
};
const addShortcut = () => {
if (!typedShortcut.value.length || doesShortcutExists.value) return;
shortcutToAdd.value.keys = [typedShortcut.value.replaceAll(isMacOS ? 'Command' : 'Control', 'CommandOrControl')];
const filteredShortcuts = [shortcutToAdd.value, ...shortcuts.value];
isConfirmAddModal.value = false;
return Application.updateShortcuts(filteredShortcuts);
};
const showDeleteModal = (shortcut: ShortcutRecord) => {
isConfirmDeleteModal.value = true;
shortcutToDelete.value = shortcut;
};
const deleteShortcut = () => {
const filteredShortcuts = shortcuts.value.filter(s => (
shortcutToDelete.value.keys.toString() !== s.keys.toString()
));
isConfirmDeleteModal.value = false;
return Application.updateShortcuts(filteredShortcuts);
};
watch(typedShortcut, () => {
doesShortcutExists.value = shortcuts.value.some(s => (
s.keys.some(k => (
k.replaceAll('CommandOrControl', isMacOS ? 'Command' : 'Control') === typedShortcut.value
))
));
});
</script>
<style lang="scss" scoped>
.table {
.tr {
.td {
border-right: 3px solid;
border-bottom: 3px solid;
}
&:hover {
.shortcut-button {
opacity: 1;
}
}
.shortcut-button {
font-size: 0.7rem;
height: 1rem;
line-height: 1rem;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0;
}
}
}
.shortcuts-tools {
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -26,27 +26,27 @@
:class="{'loading': updateStatus === 'checking'}" :class="{'loading': updateStatus === 'checking'}"
@click="checkForUpdates" @click="checkForUpdates"
> >
{{ $t('message.checkForUpdates') }} {{ t('message.checkForUpdates') }}
</button> </button>
<button <button
v-else-if="updateStatus === 'downloaded'" v-else-if="updateStatus === 'downloaded'"
class="btn btn-primary" class="btn btn-primary"
@click="restartToUpdate" @click="restartToUpdate"
> >
{{ $t('message.restartToInstall') }} {{ t('message.restartToInstall') }}
</button> </button>
<button <button
v-else-if="updateStatus === 'link'" v-else-if="updateStatus === 'link'"
class="btn btn-primary" class="btn btn-primary"
@click="openOutside('https://antares-sql.app/download.html')" @click="openOutside('https://antares-sql.app/download.html')"
> >
{{ $t('message.goToDownloadPage') }} {{ t('message.goToDownloadPage') }}
</button> </button>
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<label class="form-switch d-inline-block disabled" @click.prevent="toggleAllowPrerelease"> <label class="form-switch d-inline-block disabled" @click.prevent="toggleAllowPrerelease">
<input type="checkbox" :checked="allowPrerelease"> <input type="checkbox" :checked="allowPrerelease">
<i class="form-icon" /> {{ $t('message.includeBetaUpdates') }} <i class="form-icon" /> {{ t('message.includeBetaUpdates') }}
</label> </label>
</div> </div>
</div> </div>

View File

@@ -34,6 +34,15 @@ const {
lineWrap lineWrap
} = storeToRefs(settingsStore); } = storeToRefs(settingsStore);
const sizes = {
xsmall: '10px',
small: '12px',
medium: '14px',
large: '16px',
xlarge: '18px',
xxlarge: '20px'
};
const props = defineProps({ const props = defineProps({
modelValue: String, modelValue: String,
workspace: Object as Prop<Workspace>, workspace: Object as Prop<Workspace>,
@@ -47,10 +56,11 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const cursorPosition = ref(0); const cursorPosition = ref(0);
const fields = ref([]); const lastTableFields = ref([]);
const customCompleter = ref([]); const customCompleter = ref([]);
const id = ref(uidGen()); const id = ref(uidGen());
const lastSchema: Ref<string> = ref(null); const lastSchema: Ref<string> = ref(null);
const fields: Ref<{name: string; type: string}[]> = ref([]);
const tables = computed(() => { const tables = computed(() => {
return props.workspace return props.workspace
@@ -61,13 +71,27 @@ const tables = computed(() => {
}, []).map(table => { }, []).map(table => {
return { return {
name: table.name as string, name: table.name as string,
type: table.type as string, type: table.type as string
fields: []
}; };
}) })
: []; : [];
}); });
const tablesInQuery = computed(() => {
if (!props.modelValue) return [];
const words = props.modelValue
.replaceAll(/[.'"`]/g, ' ')
.split(' ')
.filter(Boolean);
const includedTables = tables.value.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, [] as string[]).filter((t) => words.includes(t));
return includedTables;
});
const triggers = computed(() => { const triggers = computed(() => {
return props.workspace return props.workspace
? props.workspace.structure.filter(schema => schema.name === props.schema) ? props.workspace.structure.filter(schema => schema.name === props.schema)
@@ -150,11 +174,11 @@ const lastWord = computed(() => {
const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value)); const isLastWordATable = computed(() => /\w+\.\w*/gm.test(lastWord.value));
const fieldsCompleter = computed(() => { const tableFieldsCompleter = computed(() => {
return { return {
getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => { getCompletions: (editor: never, session: never, pos: never, prefix: never, callback: (err: null, response: ace.Ace.Completion[]) => void) => {
const completions: ace.Ace.Completion[] = []; const completions: ace.Ace.Completion[] = [];
fields.value.forEach(field => { lastTableFields.value.forEach(field => {
completions.push({ completions.push({
value: field, value: field,
meta: 'column', meta: 'column',
@@ -175,7 +199,8 @@ const setCustomCompleter = () => {
...triggers.value, ...triggers.value,
...procedures.value, ...procedures.value,
...functions.value, ...functions.value,
...schedulers.value ...schedulers.value,
...fields.value
].forEach(el => { ].forEach(el => {
completions.push({ completions.push({
value: el.name, value: el.name,
@@ -195,18 +220,35 @@ watch(() => props.modelValue, () => {
cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition()); cursorPosition.value = (editor.value.session as any).doc.positionToIndex(editor.value.getCursorPosition());
}); });
watch(() => tablesInQuery.value.length, () => {
const localFields: {name: string; type: string}[] = [];
tablesInQuery.value.forEach(async table => {
const params = {
uid: props.workspace.uid,
schema: props.schema,
table: table
};
const { response } = await Tables.getTableColumns(params);
response.forEach((field: { name: string }) => {
localFields.push({
name: field.name,
type: 'column'
});
});
});
fields.value = localFields;
setCustomCompleter();
});
watch(editorTheme, () => { watch(editorTheme, () => {
if (editor.value) if (editor.value)
editor.value.setTheme(`ace/theme/${editorTheme.value}`); editor.value.setTheme(`ace/theme/${editorTheme.value}`);
}); });
watch(editorFontSize, () => { watch(editorFontSize, () => {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (editor.value) { if (editor.value) {
editor.value.setOptions({ editor.value.setOptions({
fontSize: sizes[editorFontSize.value] fontSize: sizes[editorFontSize.value]
@@ -266,7 +308,8 @@ onMounted(() => {
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
wrap: lineWrap.value, wrap: lineWrap.value,
enableSnippets: true, enableSnippets: true,
enableLiveAutocompletion: autoComplete.value enableLiveAutocompletion: autoComplete.value,
fontSize: sizes[editorFontSize.value]
}); });
if (!baseCompleter.value.length) if (!baseCompleter.value.length)
@@ -289,8 +332,8 @@ onMounted(() => {
Tables.getTableColumns(params).then(res => { Tables.getTableColumns(params).then(res => {
if (res.response.length) if (res.response.length)
fields.value = res.response.map((field: { name: string }) => field.name); lastTableFields.value = res.response.map((field: { name: string }) => field.name);
editor.value.completers = [fieldsCompleter.value]; editor.value.completers = [tableFieldsCompleter.value];
editor.value.execCommand('startAutocomplete'); editor.value.execCommand('startAutocomplete');
}).catch(console.log); }).catch(console.log);
} }

View File

@@ -8,27 +8,27 @@
class="context-element" class="context-element"
@click="unpin" @click="unpin"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-pin-off text-light pr-1" /> {{ $t('word.unpin') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-pin-off text-light pr-1" /> {{ t('word.unpin') }}</span>
</div> </div>
<div <div
v-else v-else
class="context-element" class="context-element"
@click="pin" @click="pin"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-pin mdi-rotate-45 text-light pr-1" /> {{ $t('word.pin') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-pin mdi-rotate-45 text-light pr-1" /> {{ t('word.pin') }}</span>
</div> </div>
<div <div
v-if="isConnected" v-if="isConnected"
class="context-element" class="context-element"
@click="disconnect" @click="disconnect"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-power text-light pr-1" /> {{ $t('word.disconnect') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-power text-light pr-1" /> {{ t('word.disconnect') }}</span>
</div> </div>
<div class="context-element" @click="duplicateConnection"> <div class="context-element" @click="duplicateConnection">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ $t('word.duplicate') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('word.duplicate') }}</span>
</div> </div>
<div class="context-element" @click="showConfirmModal"> <div class="context-element" @click="showConfirmModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ t('word.delete') }}</span>
</div> </div>
<ConfirmModal <ConfirmModal
@@ -38,12 +38,12 @@
> >
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }} <i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ t('message.deleteConnection') }}
</div> </div>
</template> </template>
<template #body> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>? {{ t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -54,12 +54,15 @@
import { computed, Prop, ref } from 'vue'; import { computed, Prop, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useI18n } from 'vue-i18n';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseContextMenu from '@/components/BaseContextMenu.vue'; import BaseContextMenu from '@/components/BaseContextMenu.vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
const { t } = useI18n();
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
const { const {

View File

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

View File

@@ -1,14 +1,14 @@
<template> <template>
<ConfirmModal <ConfirmModal
:confirm-text="$t('word.update')" :confirm-text="t('word.update')"
:cancel-text="$t('word.close')" :cancel-text="t('word.close')"
size="large" size="large"
:hide-footer="true" :hide-footer="true"
@hide="hideScratchpad" @hide="hideScratchpad"
> >
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }} <i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ t('word.scratchpad') }}
</div> </div>
</template> </template>
<template #body> <template #body>
@@ -22,7 +22,7 @@
:show-line-numbers="false" :show-line-numbers="false"
/> />
</div> </div>
<small class="text-gray">{{ $t('message.markdownSupported') }}</small> <small class="text-gray">{{ t('message.markdownSupported') }}</small>
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -35,6 +35,9 @@ import { useApplicationStore } from '@/stores/application';
import { useScratchpadStore } from '@/stores/scratchpad'; import { useScratchpadStore } from '@/stores/scratchpad';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import TextEditor from '@/components/BaseTextEditor.vue'; import TextEditor from '@/components/BaseTextEditor.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const scratchpadStore = useScratchpadStore(); const scratchpadStore = useScratchpadStore();

View File

@@ -14,17 +14,20 @@
@start="isDragging = true" @start="isDragging = true"
@end="dragStop" @end="dragStop"
> >
<template #item="{element}"> <template #item="{ element }">
<li <li
:draggable="true" :draggable="true"
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link"
:class="{'selected': element.uid === selectedWorkspace}" :class="{ 'selected': element.uid === selectedWorkspace }"
:title="getConnectionName(element.uid)"
@click.stop="selectWorkspace(element.uid)" @click.stop="selectWorkspace(element.uid)"
@contextmenu.prevent="contextMenu($event, element)" @contextmenu.prevent="contextMenu($event, element)"
@mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon dbi" :class="[`dbi-${element.client}`, getStatusBadge(element.uid), (pinnedConnections.has(element.uid) ? 'settingbar-element-pin' : false)]" /> <i
<span v-if="!isDragging && !isScrolling" class="ex-tooltip-content">{{ getConnectionName(element.uid) }}</span> class="settingbar-element-icon dbi"
:class="[`dbi-${element.client}`, getStatusBadge(element.uid), (pinnedConnections.has(element.uid) ? 'settingbar-element-pin' : false)]"
/>
<small class="settingbar-element-name">{{ getConnectionName(element.uid) }}</small>
</li> </li>
</template> </template>
</Draggable> </Draggable>
@@ -34,14 +37,17 @@
<li <li
v-for="connection in unpinnedConnectionsArr" v-for="connection in unpinnedConnectionsArr"
:key="connection.uid" :key="connection.uid"
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link"
:class="{'selected': connection.uid === selectedWorkspace}" :class="{ 'selected': connection.uid === selectedWorkspace }"
:title="getConnectionName(connection.uid)"
@click.stop="selectWorkspace(connection.uid)" @click.stop="selectWorkspace(connection.uid)"
@contextmenu.prevent="contextMenu($event, connection)" @contextmenu.prevent="contextMenu($event, connection)"
@mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`, getStatusBadge(connection.uid)]" /> <i
<span v-if="!isDragging && !isScrolling" class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span> class="settingbar-element-icon dbi"
:class="[`dbi-${connection.client}`, getStatusBadge(connection.uid)]"
/>
<small class="settingbar-element-name">{{ getConnectionName(connection.uid) }}</small>
</li> </li>
</ul> </ul>
</div> </div>
@@ -50,21 +56,19 @@
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<li <li
v-if="isScrollable" v-if="isScrollable"
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link"
:title="t('message.allConnections')"
@click="emit('show-connections-modal')" @click="emit('show-connections-modal')"
@mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" /> <i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" />
<span class="ex-tooltip-content">{{ $t('message.allConnections') }} (CTRL+Space)</span>
</li> </li>
<li <li
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link"
:class="{'selected': 'NEW' === selectedWorkspace}" :class="{ 'selected': 'NEW' === selectedWorkspace }"
:title="t('message.addConnection')"
@click="selectWorkspace('NEW')" @click="selectWorkspace('NEW')"
@mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" /> <i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
<span class="ex-tooltip-content">{{ $t('message.addConnection') }}</span>
</li> </li>
</ul> </ul>
</div> </div>
@@ -73,15 +77,21 @@
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<li <li
v-if="!disableScratchpad" v-if="!disableScratchpad"
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link"
:title="t('word.scratchpad')"
@click="showScratchpad" @click="showScratchpad"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" /> <i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" />
<span class="ex-tooltip-content">{{ $t('word.scratchpad') }}</span>
</li> </li>
<li class="settingbar-element btn btn-link ex-tooltip" @click="showSettingModal('general')"> <li
<i class="settingbar-element-icon mdi mdi-24px mdi-cog text-light" :class="{' badge badge-update': hasUpdates}" /> class="settingbar-element btn btn-link"
<span class="ex-tooltip-content">{{ $t('word.settings') }}</span> :title="t('word.settings')"
@click="showSettingModal('general')"
>
<i
class="settingbar-element-icon mdi mdi-24px mdi-cog text-light"
:class="{ ' badge badge-update': hasUpdates }"
/>
</li> </li>
</ul> </ul>
</div> </div>
@@ -98,7 +108,10 @@ import { useSettingsStore } from '@/stores/settings';
import * as Draggable from 'vuedraggable'; import * as Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext.vue'; import SettingBarContext from '@/components/SettingBarContext.vue';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { useElementBounding, useScroll } from '@vueuse/core'; import { useElementBounding } from '@vueuse/core';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
@@ -122,7 +135,6 @@ const sidebarConnections: Ref<HTMLDivElement> = ref(null);
const isContext: Ref<boolean> = ref(false); const isContext: Ref<boolean> = ref(false);
const isDragging: Ref<boolean> = ref(false); const isDragging: Ref<boolean> = ref(false);
const isScrollable: Ref<boolean> = ref(false); const isScrollable: Ref<boolean> = ref(false);
const isScrolling = ref(useScroll(sidebarConnections)?.isScrolling);
const contextEvent: Ref<MouseEvent> = ref(null); const contextEvent: Ref<MouseEvent> = ref(null);
const contextConnection: Ref<ConnectionParams> = ref(null); const contextConnection: Ref<ConnectionParams> = ref(null);
const sidebarConnectionsHeight = ref(useElementBounding(sidebarConnections)?.height); const sidebarConnectionsHeight = ref(useElementBounding(sidebarConnections)?.height);
@@ -237,32 +249,32 @@ watch(selectedWorkspace, (newVal, oldVal) => {
</script> </script>
<style lang="scss"> <style lang="scss">
#settingbar { #settingbar {
width: $settingbar-width; width: $settingbar-width;
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// justify-content: space-between; // justify-content: space-between;
align-items: center; align-items: center;
padding: 0; padding: 0;
z-index: 9; z-index: 9;
.settingbar-top-elements { .settingbar-top-elements {
overflow-x: hidden; overflow-x: hidden;
overflow-y: overlay; overflow-y: overlay;
// max-height: calc((100vh - 3.5rem) - #{$excluding-size}); // max-height: calc((100vh - 3.5rem) - #{$excluding-size});
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 3px; width: 3px;
} }
} }
.settingbar-bottom-elements { .settingbar-bottom-elements {
z-index: 1; z-index: 1;
margin-top: auto; margin-top: auto;
} }
.settingbar-elements { .settingbar-elements {
list-style: none; list-style: none;
text-align: center; text-align: center;
width: $settingbar-width; width: $settingbar-width;
@@ -270,101 +282,85 @@ watch(selectedWorkspace, (newVal, oldVal) => {
margin: 0; margin: 0;
.settingbar-element { .settingbar-element {
height: $settingbar-width; height: $settingbar-width;
width: 100%; width: 100%;
margin: 0; margin: 0;
opacity: 0.5; opacity: 0.6;
transition: opacity 0.2s; transition: opacity 0.2s;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
position: relative;
&:hover { &:hover {
opacity: 1; opacity: 1;
} }
&.selected { &.selected {
opacity: 1; opacity: 1;
&::before { &::before {
height: $settingbar-width; height: $settingbar-width;
} }
} }
&::before { &::before {
content: ""; content: "";
height: 0; height: 0;
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;
background-color: $primary-color; background-color: $primary-color;
border-radius: $border-radius; border-radius: $border-radius;
} }
.settingbar-element-icon { .settingbar-element-icon {
margin: 0 auto; margin: 0 auto;
&.badge::after { &.badge::after {
bottom: -10px; top: 5px;
right: 0; right: -4px;
position: absolute;
}
&.badge-update::after {
bottom: initial;
}
}
.settingbar-element-name {
font-size: 65%;
bottom: 5px;
left: 7px;
position: absolute; position: absolute;
} font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: calc($settingbar-width - 15px);
text-align: left;
line-height: 1.1;
color: rgba($body-font-color-dark, 0.8);
text-align: center;
}
&.badge-update::after { .settingbar-element-pin {
bottom: initial; margin: 0 auto;
}
}
.settingbar-element-pin{ &::before {
margin: 0 auto; font: normal normal normal 14px/1 "Material Design Icons";
content: "\F0403";
&::before { color: $body-font-color-dark;
font: normal normal normal 14px/1 "Material Design Icons"; transform: rotate(45deg);
content: "\F0403"; opacity: 0.25;
color: $body-font-color-dark; top: -8px;
transform: rotate(45deg); left: -10px;
opacity: .25; position: absolute;
bottom: -8px; }
left: -4px; }
position: absolute;
}
}
} }
} }
} }
.ex-tooltip {// Because both overflow-x: visible and overflow-y:auto are evil!!!
.ex-tooltip-content {
z-index: 999;
visibility: hidden;
opacity: 0;
display: block;
position: absolute;
text-align: center;
margin: 0 0 0 calc(#{$settingbar-width} - 5px);
left: 0;
padding: 0.2rem 0.4rem;
font-size: 0.7rem;
background: rgba(48, 55, 66, 0.95);
border-radius: $border-radius;
color: #fff;
max-width: 320px;
pointer-events: none;
text-overflow: ellipsis;
overflow: hidden;
transition: opacity 0.2s;
}
&.sortable-chosen {
.ex-tooltip-content {
opacity: 0 !important;
}
}
&:hover:not(.selected) .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
}
</style> </style>

View File

@@ -31,10 +31,10 @@
> >
<i class="mdi mdi-18px mdi-code-tags mr-1" /> <i class="mdi mdi-18px mdi-code-tags mr-1" />
<span> <span>
<span>{{ cutText(element.content || 'Query') }} #{{ element.index }}</span> <span>{{ cutText(element.content || 'Query', 20, true) }} #{{ element.index }}</span>
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -47,11 +47,11 @@
@dblclick="openAsPermanentTab(element)" @dblclick="openAsPermanentTab(element)"
> >
<i class="mdi mdi-18px mr-1" :class="element.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" /> <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 :title="`${t('word.data').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<span class=" text-italic">{{ cutText(element.elementName) }}</span> <span class=" text-italic">{{ cutText(element.elementName, 20, true) }}</span>
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -60,11 +60,11 @@
<a v-else-if="element.type === 'data'" class="tab-link"> <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'" /> <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 :title="`${t('word.data').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ cutText(element.elementName) }} {{ cutText(element.elementName, 20, true) }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -77,11 +77,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newTable') }} {{ t('message.newTable') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -94,11 +94,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" /> <i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.settings').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ cutText(element.elementName) }} {{ cutText(element.elementName, 20, true) }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -111,11 +111,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" /> <i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.view`)}`"> <span :title="`${t('word.settings').toUpperCase()}: ${t(`word.view`)}`">
{{ cutText(element.elementName) }} {{ cutText(element.elementName, 20, true) }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -128,11 +128,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newView') }} {{ t('message.newView') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -145,11 +145,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newTrigger') }} {{ t('message.newTrigger') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -162,11 +162,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newRoutine') }} {{ t('message.newRoutine') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -179,11 +179,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newFunction') }} {{ t('message.newFunction') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -196,11 +196,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newTriggerFunction') }} {{ t('message.newTriggerFunction') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -213,11 +213,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" /> <i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.new').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ $t('message.newScheduler') }} {{ t('message.newScheduler') }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -231,11 +231,11 @@
@dblclick="openAsPermanentTab(element)" @dblclick="openAsPermanentTab(element)"
> >
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" /> <i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.settings').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
<span class=" text-italic">{{ cutText(element.elementName) }}</span> <span class=" text-italic">{{ cutText(element.elementName, 20, true) }}</span>
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -248,11 +248,11 @@
:class="{'badge': element.isChanged}" :class="{'badge': element.isChanged}"
> >
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" /> <i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${element.elementType}`)}`"> <span :title="`${t('word.settings').toUpperCase()}: ${t(`word.${element.elementType}`)}`">
{{ cutText(element.elementName) }} {{ cutText(element.elementName, 20, true) }}
<span <span
class="btn btn-clear" class="btn btn-clear"
:title="$t('word.close')" :title="t('word.close')"
@mousedown.left.stop @mousedown.left.stop
@click.stop="closeTab(element)" @click.stop="closeTab(element)"
/> />
@@ -268,7 +268,7 @@
<a <a
class="tab-link workspace-tools-link dropdown-toggle" class="tab-link workspace-tools-link dropdown-toggle"
tabindex="0" tabindex="0"
:title="$t('word.tools')" :title="t('word.tools')"
> >
<i class="mdi mdi-24px mdi-tools" /> <i class="mdi mdi-24px mdi-tools" />
</a> </a>
@@ -276,7 +276,13 @@
<li class="menu-item"> <li class="menu-item">
<a class="c-hand p-vcentered" @click="showProcessesModal"> <a class="c-hand p-vcentered" @click="showProcessesModal">
<i class="mdi mdi-memory mr-1 tool-icon" /> <i class="mdi mdi-memory mr-1 tool-icon" />
<span>{{ $t('message.processesList') }}</span> <span>{{ t('message.processesList') }}</span>
</a>
</li>
<li class="menu-item">
<a class="c-hand p-vcentered" @click="toggleConsole">
<i class="mdi mdi-console-line mr-1 tool-icon" />
<span>{{ t('word.console') }}</span>
</a> </a>
</li> </li>
<li <li
@@ -286,7 +292,7 @@
> >
<a class="c-hand p-vcentered disabled"> <a class="c-hand p-vcentered disabled">
<i class="mdi mdi-shape mr-1 tool-icon" /> <i class="mdi mdi-shape mr-1 tool-icon" />
<span>{{ $t('word.variables') }}</span> <span>{{ t('word.variables') }}</span>
</a> </a>
</li> </li>
<li <li
@@ -296,7 +302,7 @@
> >
<a class="c-hand p-vcentered disabled"> <a class="c-hand p-vcentered disabled">
<i class="mdi mdi-account-group mr-1 tool-icon" /> <i class="mdi mdi-account-group mr-1 tool-icon" />
<span>{{ $t('message.manageUsers') }}</span> <span>{{ t('message.manageUsers') }}</span>
</a> </a>
</li> </li>
</ul> </ul>
@@ -306,7 +312,7 @@
<li class="tab-item"> <li class="tab-item">
<a <a
class="tab-add" class="tab-add"
:title="$t('message.openNewTab')" :title="t('message.openNewTab')"
@click="addQueryTab" @click="addQueryTab"
> >
<i class="mdi mdi-24px mdi-plus" /> <i class="mdi mdi-24px mdi-plus" />
@@ -320,7 +326,7 @@
v-if="tab.type ==='query'" v-if="tab.type ==='query'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:connection="connection" :connection="connection"
/> />
<WorkspaceTabTable <WorkspaceTabTable
@@ -328,7 +334,7 @@
v-once v-once
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:table="tab.elementName" :table="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
:element-type="tab.elementType" :element-type="tab.elementType"
@@ -338,14 +344,14 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:schema="tab.schema" :schema="tab.schema"
/> />
<WorkspaceTabPropsTable <WorkspaceTabPropsTable
v-else-if="tab.type === 'table-props'" v-else-if="tab.type === 'table-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:table="tab.elementName" :table="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -354,13 +360,13 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:schema="tab.schema" :schema="tab.schema"
/> />
<WorkspaceTabPropsView <WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'" v-else-if="tab.type === 'view-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:connection="connection" :connection="connection"
:view="tab.elementName" :view="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
@@ -370,7 +376,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -378,7 +384,7 @@
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)" v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -387,7 +393,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -395,7 +401,7 @@
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)" v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:function="tab.elementName" :function="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -404,7 +410,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -412,7 +418,7 @@
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)" v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:routine="tab.elementName" :routine="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -421,7 +427,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -429,7 +435,7 @@
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)" v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:function="tab.elementName" :function="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -438,7 +444,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -446,13 +452,14 @@
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)" v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:scheduler="tab.elementName" :scheduler="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
</template> </template>
<WorkspaceQueryConsole v-if="isConsoleOpen(workspace.uid)" :uid="workspace.uid" />
</div> </div>
<div v-else class="connection-panel-wrapper"> <div v-else class="connection-panel-wrapper p-relative">
<WorkspaceEditConnectionPanel :connection="connection" /> <WorkspaceEditConnectionPanel :connection="connection" />
</div> </div>
<ModalProcessesList <ModalProcessesList
@@ -470,18 +477,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeUnmount, Prop, ref, watch } from 'vue'; import { ipcRenderer } from 'electron';
import { computed, onMounted, Prop, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import * as Draggable from 'vuedraggable'; import * as Draggable from 'vuedraggable';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces'; import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { useFilters } from '@/composables/useFilters';
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState.vue'; import WorkspaceEmptyState from '@/components/WorkspaceEmptyState.vue';
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar.vue'; import WorkspaceExploreBar from '@/components/WorkspaceExploreBar.vue';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel.vue'; import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel.vue';
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery.vue'; import WorkspaceTabQuery from '@/components/WorkspaceTabQuery.vue';
import WorkspaceTabTable from '@/components/WorkspaceTabTable.vue'; import WorkspaceTabTable from '@/components/WorkspaceTabTable.vue';
import WorkspaceQueryConsole from '@/components/WorkspaceQueryConsole.vue';
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable.vue'; import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable.vue';
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView.vue'; import WorkspaceTabNewView from '@/components/WorkspaceTabNewView.vue';
@@ -500,7 +511,11 @@ import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction.vu
import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler.vue'; import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler.vue';
import ModalProcessesList from '@/components/ModalProcessesList.vue'; import ModalProcessesList from '@/components/ModalProcessesList.vue';
import ModalDiscardChanges from '@/components/ModalDiscardChanges.vue'; import ModalDiscardChanges from '@/components/ModalDiscardChanges.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const { cutText } = useFilters();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -517,6 +532,11 @@ const {
selectPrevTab selectPrevTab
} = workspacesStore; } = workspacesStore;
const consoleStore = useConsoleStore();
const { isConsoleOpen } = storeToRefs(consoleStore);
const { toggleConsole } = consoleStore;
const props = defineProps({ const props = defineProps({
connection: Object as Prop<ConnectionParams> connection: Object as Prop<ConnectionParams>
}); });
@@ -568,46 +588,13 @@ watch(queryTabs, (newVal, oldVal) => {
}); });
const addQueryTab = () => { const addQueryTab = () => {
newTab({ uid: props.connection.uid, type: 'query' }); newTab({ uid: props.connection.uid, type: 'query', schema: workspace.value.breadcrumbs.schema });
}; };
const getSelectedTab = () => { const getSelectedTab = () => {
return workspace.value.tabs.find(tab => tab.uid === selectedTab.value); return workspace.value.tabs.find(tab => tab.uid === selectedTab.value);
}; };
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (!isSelected.value)
return;
if ((e.ctrlKey || e.metaKey) && e.key === 't' && !e.altKey) { // CTRL|Command + t
addQueryTab();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'w' && !e.altKey) { // CTRL|Command + w
const currentTab = getSelectedTab();
if (currentTab)
closeTab(currentTab);
}
// select next tab
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowRight')
selectNextTab({ uid: props.connection.uid });
// select prev tab
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowLeft')
selectPrevTab({ uid: props.connection.uid });
// select tab by index (range 1-9). CTRL|CMD number
if ((e.ctrlKey || e.metaKey) && !e.altKey && e.keyCode >= 49 && e.keyCode <= 57) {
const newIndex = parseInt(e.key) - 1;
if (workspace.value.tabs[newIndex])
selectTab({ uid: props.connection.uid, tab: workspace.value.tabs[newIndex].uid });
}
};
const openAsPermanentTab = (tab: WorkspaceTab) => { const openAsPermanentTab = (tab: WorkspaceTab) => {
const permanentTabs = { const permanentTabs = {
table: 'data', table: 'data',
@@ -658,24 +645,43 @@ const addWheelEvent = () => {
} }
}; };
const cutText = (string: string) => {
const limit = 20;
const escapedString = string.replace(/\s{2,}/g, ' ');
if (escapedString.length > limit)
return `${escapedString.substr(0, limit)}...`;
return escapedString;
};
(async () => { (async () => {
window.addEventListener('keydown', onKey);
await addWorkspace(props.connection.uid); await addWorkspace(props.connection.uid);
const isInitiated = await Connection.checkConnection(props.connection.uid); const isInitiated = await Connection.checkConnection(props.connection.uid);
if (isInitiated) if (isInitiated)
connectWorkspace(props.connection); connectWorkspace(props.connection);
})(); })();
onBeforeUnmount(() => { onMounted(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.on('open-new-tab', () => {
if (!isSelected.value) return;
addQueryTab();
});
ipcRenderer.on('close-tab', () => {
if (!isSelected.value) return;
const currentTab = getSelectedTab();
if (currentTab)
closeTab(currentTab);
});
ipcRenderer.on('next-tab', () => {
if (!isSelected.value) return;
selectNextTab({ uid: props.connection.uid });
});
ipcRenderer.on('prev-tab', () => {
if (!isSelected.value) return;
selectPrevTab({ uid: props.connection.uid });
});
for (let i = 1; i <= 9; i++) {
ipcRenderer.on(`select-tab-${i}`, () => {
if (!isSelected.value) return;
if (workspace.value.tabs[i-1])
selectTab({ uid: props.connection.uid, tab: workspace.value.tabs[i-1].uid });
});
}
}); });
</script> </script>
@@ -685,8 +691,9 @@ onBeforeUnmount(() => {
margin: 0; margin: 0;
.workspace-tabs { .workspace-tabs {
overflow: hidden; overflow-y: hidden;
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
position: relative;
.tab-block { .tab-block {
margin-top: 0; margin-top: 0;

View File

@@ -553,14 +553,15 @@ setDefaults();
setTimeout(() => { setTimeout(() => {
if (firstInput.value) firstInput.value.focus(); if (firstInput.value) firstInput.value.focus();
}, 20); }, 200);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.connection-panel { .connection-panel {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 1rem; margin-bottom: 0.5rem;
margin-top: 1.5rem;
.panel { .panel {
min-width: 450px; min-width: 450px;

View File

@@ -551,7 +551,8 @@ localConnection.value = JSON.parse(JSON.stringify(props.connection));
.connection-panel { .connection-panel {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 1rem; margin-bottom: 0.5rem;
margin-top: 1.5rem;
.panel { .panel {
min-width: 450px; min-width: 450px;

View File

@@ -15,18 +15,18 @@
<i <i
v-if="customizations.schemas" v-if="customizations.schemas"
class="mdi mdi-18px mdi-database-plus c-hand mr-2" class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="$t('message.createNewSchema')" :title="t('message.createNewSchema')"
@click="showNewDBModal" @click="showNewDBModal"
/> />
<i <i
class="mdi mdi-18px mdi-refresh c-hand mr-2" class="mdi mdi-18px mdi-refresh c-hand mr-2"
:class="{'rotate':isRefreshing}" :class="{'rotate':isRefreshing}"
:title="$t('word.refresh')" :title="t('word.refresh')"
@click="refresh" @click="refresh"
/> />
<i <i
class="mdi mdi-18px mdi-power c-hand" class="mdi mdi-18px mdi-power c-hand"
:title="$t('word.disconnect')" :title="t('word.disconnect')"
@click="disconnectWorkspace(connection.uid)" @click="disconnectWorkspace(connection.uid)"
/> />
</span> </span>
@@ -38,7 +38,7 @@
v-model="searchTerm" v-model="searchTerm"
class="form-input input-sm" class="form-input input-sm"
type="text" type="text"
:placeholder="$t('message.searchForElements')" :placeholder="t('message.searchForElements')"
> >
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px" /> <i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px" />
<i <i
@@ -133,6 +133,9 @@ import TableContext from '@/components/WorkspaceExploreBarTableContext.vue';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext.vue'; import MiscContext from '@/components/WorkspaceExploreBarMiscContext.vue';
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext.vue'; import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext.vue';
import ModalNewSchema from '@/components/ModalNewSchema.vue'; import ModalNewSchema from '@/components/ModalNewSchema.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
connection: Object, connection: Object,
@@ -235,17 +238,7 @@ const refresh = async () => {
} }
}; };
const explorebarSearch = (e: KeyboardEvent) => { const explorebarSearch = () => {
if (e.code === 'Backspace') {
e.preventDefault();
if (searchTerm.value.length)
searchTerm.value = searchTerm.value.slice(0, -1);
else
return;
}
else if (e.key.length > 1)// Prevent non-alphanumerics
return;
searchInput.value.focus(); searchInput.value.focus();
}; };

View File

@@ -8,7 +8,7 @@
class="context-element" class="context-element"
@click="runElementCheck" @click="runElementCheck"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.run') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('word.run') }}</span>
</div> </div>
<div <div
v-if="selectedMisc.type === 'trigger' && customizations.triggerEnableDisable" v-if="selectedMisc.type === 'trigger' && customizations.triggerEnableDisable"
@@ -16,10 +16,10 @@
@click="toggleTrigger" @click="toggleTrigger"
> >
<span v-if="!selectedMisc.enabled" class="d-flex"> <span v-if="!selectedMisc.enabled" class="d-flex">
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.enable') }} <i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('word.enable') }}
</span> </span>
<span v-else class="d-flex"> <span v-else class="d-flex">
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ $t('word.disable') }} <i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ t('word.disable') }}
</span> </span>
</div> </div>
<div <div
@@ -28,14 +28,14 @@
@click="toggleScheduler" @click="toggleScheduler"
> >
<span v-if="!selectedMisc.enabled" class="d-flex"> <span v-if="!selectedMisc.enabled" class="d-flex">
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.enable') }} <i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ t('word.enable') }}
</span> </span>
<span v-else class="d-flex"> <span v-else class="d-flex">
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ $t('word.disable') }} <i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ t('word.disable') }}
</span> </span>
</div> </div>
<div class="context-element" @click="showDeleteModal"> <div class="context-element" @click="showDeleteModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ $t('word.delete') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ t('word.delete') }}</span>
</div> </div>
<ConfirmModal <ConfirmModal
v-if="isDeleteModal" v-if="isDeleteModal"
@@ -50,7 +50,7 @@
</template> </template>
<template #body> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"? {{ t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"?
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -188,8 +188,6 @@ const deleteMisc = async () => {
break; break;
} }
console.log(res);
const { status, response } = res; const { status, response } = res;
if (status === 'success') { if (status === 'success') {
@@ -219,7 +217,7 @@ const runElementCheck = () => {
}; };
const runElement = (params: string[]) => { const runElement = (params: string[]) => {
if (props.selectedMisc.type === 'procedure') if (['procedure', 'routine'].includes(props.selectedMisc.type))
runRoutine(params); runRoutine(params);
else if (props.selectedMisc.type === 'function') else if (props.selectedMisc.type === 'function')
runFunction(params); runFunction(params);

View File

@@ -580,7 +580,7 @@ defineExpose({ selectSchema, schemaAccordion });
transition: opacity 0.2s; transition: opacity 0.2s;
&:hover { &:hover {
opacity: 0.8; opacity: 1;
} }
&::after { &::after {

View File

@@ -4,7 +4,7 @@
@close-context="closeContext" @close-context="closeContext"
> >
<div class="context-element"> <div class="context-element">
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ t('word.add') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" /> <i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu"> <div class="context-submenu">
<div <div
@@ -12,49 +12,49 @@
class="context-element" class="context-element"
@click="openCreateTableTab" @click="openCreateTableTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ t('word.table') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.viewAdd" v-if="workspace.customizations.viewAdd"
class="context-element" class="context-element"
@click="openCreateViewTab" @click="openCreateViewTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ t('word.view') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.triggerAdd" v-if="workspace.customizations.triggerAdd"
class="context-element" class="context-element"
@click="openCreateTriggerTab" @click="openCreateTriggerTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $tc('word.trigger', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ t('word.trigger', 1) }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.routineAdd" v-if="workspace.customizations.routineAdd"
class="context-element" class="context-element"
@click="openCreateRoutineTab" @click="openCreateRoutineTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ $tc('word.storedRoutine', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ t('word.storedRoutine', 1) }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.functionAdd" v-if="workspace.customizations.functionAdd"
class="context-element" class="context-element"
@click="openCreateFunctionTab" @click="openCreateFunctionTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ t('word.function', 1) }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.triggerFunctionAdd" v-if="workspace.customizations.triggerFunctionAdd"
class="context-element" class="context-element"
@click="openCreateTriggerFunctionTab" @click="openCreateTriggerFunctionTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise pr-1" /> {{ $tc('word.triggerFunction', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise pr-1" /> {{ t('word.triggerFunction', 1) }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schedulerAdd" v-if="workspace.customizations.schedulerAdd"
class="context-element" class="context-element"
@click="openCreateSchedulerTab" @click="openCreateSchedulerTab"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $tc('word.scheduler', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ t('word.scheduler', 1) }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -63,28 +63,28 @@
class="context-element" class="context-element"
@click="showExportSchemaModal" @click="showExportSchemaModal"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-down text-light pr-1" /> {{ $t('word.export') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-down text-light pr-1" /> {{ t('word.export') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schemaImport" v-if="workspace.customizations.schemaImport"
class="context-element" class="context-element"
@click="initImport" @click="initImport"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-up text-light pr-1" /> {{ $t('word.import') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-up text-light pr-1" /> {{ t('word.import') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schemaEdit" v-if="workspace.customizations.schemaEdit"
class="context-element" class="context-element"
@click="showEditModal" @click="showEditModal"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ t('word.edit') }}</span>
</div> </div>
<div <div
v-if="workspace.customizations.schemaDrop" v-if="workspace.customizations.schemaDrop"
class="context-element" class="context-element"
@click="showDeleteModal" @click="showDeleteModal"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ $t('word.delete') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ t('word.delete') }}</span>
</div> </div>
<ConfirmModal <ConfirmModal
@@ -95,12 +95,12 @@
<template #header> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-remove mr-1" /> <i class="mdi mdi-24px mdi-database-remove mr-1" />
<span class="cut-text">{{ $t('message.deleteSchema') }}</span> <span class="cut-text">{{ t('message.deleteSchema') }}</span>
</div> </div>
</template> </template>
<template #body> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"? {{ t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"?
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
@@ -135,6 +135,9 @@ import ModalExportSchema from '@/components/ModalExportSchema.vue';
import ModalImportSchema from '@/components/ModalImportSchema.vue'; import ModalImportSchema from '@/components/ModalImportSchema.vue';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import Application from '@/ipc-api/Application'; import Application from '@/ipc-api/Application';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,

View File

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

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -179,6 +178,8 @@
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
@@ -187,6 +188,7 @@ import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabProps
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { FunctionInfos, FunctionParam } from 'common/interfaces/antares'; import { FunctionInfos, FunctionParam } from 'common/interfaces/antares';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -200,6 +202,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -274,8 +277,12 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
@@ -293,14 +300,10 @@ const hideParamsModal = () => {
isParamsModal.value = false; isParamsModal.value = false;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -311,6 +314,10 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
originalFunction.value = { originalFunction.value = {
sql: customizations.value.functionSql, sql: customizations.value.functionSql,
language: customizations.value.languages ? customizations.value.languages[0] : null, language: customizations.value.languages ? customizations.value.languages[0] : null,
@@ -330,19 +337,19 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@@ -7,31 +7,30 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ t('word.save') }}</span>
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" /> <i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ t('word.clear') }}</span>
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showParamsModal"> <button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> <i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span> <span>{{ t('word.parameters') }}</span>
</button> </button>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
@@ -42,7 +41,7 @@
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.name') }} {{ t('word.name') }}
</label> </label>
<input <input
ref="firstInput" ref="firstInput"
@@ -55,7 +54,7 @@
<div v-if="customizations.languages" class="column col-auto"> <div v-if="customizations.languages" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.language') }} {{ t('word.language') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localRoutine.language" v-model="localRoutine.language"
@@ -67,11 +66,11 @@
<div v-if="customizations.definer" class="column col-auto"> <div v-if="customizations.definer" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.definer') }} {{ t('word.definer') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localRoutine.definer" v-model="localRoutine.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]" :options="[{value: '', name:t('message.currentUser')}, ...workspace.users]"
:option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`" :option-label="(user: any) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``" :option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select" class="form-select"
@@ -81,7 +80,7 @@
<div v-if="customizations.comment" class="column"> <div v-if="customizations.comment" class="column">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('word.comment') }} {{ t('word.comment') }}
</label> </label>
<input <input
v-model="localRoutine.comment" v-model="localRoutine.comment"
@@ -93,7 +92,7 @@
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('message.sqlSecurity') }} {{ t('message.sqlSecurity') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localRoutine.security" v-model="localRoutine.security"
@@ -105,7 +104,7 @@
<div v-if="customizations.procedureDataAccess" class="column col-auto"> <div v-if="customizations.procedureDataAccess" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
{{ $t('message.dataAccess') }} {{ t('message.dataAccess') }}
</label> </label>
<BaseSelect <BaseSelect
v-model="localRoutine.dataAccess" v-model="localRoutine.dataAccess"
@@ -118,7 +117,7 @@
<div class="form-group"> <div class="form-group">
<label class="form-label d-invisible">.</label> <label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline">
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }} <input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ t('word.deterministic') }}
</label> </label>
</div> </div>
</div> </div>
@@ -126,7 +125,7 @@
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label> <label class="form-label ml-2">{{ t('message.routineBody') }}</label>
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
ref="queryEditor" ref="queryEditor"
@@ -151,6 +150,8 @@
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import Routines from '@/ipc-api/Routines'; import Routines from '@/ipc-api/Routines';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
@@ -158,6 +159,10 @@ import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue'; import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { FunctionParam } from 'common/interfaces/antares'; import { FunctionParam } from 'common/interfaces/antares';
import { useI18n } from 'vue-i18n';
import { ipcRenderer } from 'electron';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
tabUid: String, tabUid: String,
@@ -169,6 +174,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -243,8 +249,12 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
@@ -262,14 +272,10 @@ const hideParamsModal = () => {
isParamsModal.value = false; isParamsModal.value = false;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -280,6 +286,10 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
originalRoutine.value = { originalRoutine.value = {
sql: customizations.value.functionSql, sql: customizations.value.functionSql,
language: customizations.value.languages ? customizations.value.languages[0] : null, language: customizations.value.languages ? customizations.value.languages[0] : null,
@@ -299,19 +309,19 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -129,6 +128,8 @@ import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Prop, Ref
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { ConnectionParams, EventInfos } from 'common/interfaces/antares'; import { ConnectionParams, EventInfos } from 'common/interfaces/antares';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
@@ -136,6 +137,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue'; import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -149,6 +151,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -233,8 +236,12 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
@@ -252,14 +259,10 @@ const timingUpdate = (options: EventInfos) => {
localScheduler.value = options; localScheduler.value = options;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -270,6 +273,10 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
originalScheduler.value = { originalScheduler.value = {
definer: '', definer: '',
sql: 'BEGIN\r\n\r\nEND', sql: 'BEGIN\r\n\r\nEND',
@@ -287,19 +294,19 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged || !isValid" :disabled="!isChanged || !isValid"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -175,6 +174,7 @@ import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTa
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue'; import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares'; import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -420,14 +420,10 @@ const foreignsUpdate = (foreigns: TableForeign[]) => {
localKeyUsage.value = foreigns; localKeyUsage.value = foreigns;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -447,18 +443,19 @@ tableOptions.value = {
}; };
localOptions.value = JSON.parse(JSON.stringify(tableOptions.value)); localOptions.value = JSON.parse(JSON.stringify(tableOptions.value));
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -30,7 +29,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="container"> <div class="px-2">
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
@@ -118,12 +117,15 @@
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import Triggers from '@/ipc-api/Triggers'; import Triggers from '@/ipc-api/Triggers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -137,6 +139,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -254,21 +257,21 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -288,18 +291,22 @@ originalTrigger.value = {
name: '' name: ''
}; };
watch(consoleHeight, () => {
resizeQueryEditor();
});
localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value)); localTrigger.value = JSON.parse(JSON.stringify(originalTrigger.value));
setTimeout(() => { setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
@@ -312,6 +319,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -99,10 +98,13 @@ import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -116,6 +118,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -189,21 +192,21 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
originalFunction.value = { originalFunction.value = {
@@ -212,18 +215,30 @@ originalFunction.value = {
name: '' name: ''
}; };
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
});
watch(consoleHeight, () => {
resizeQueryEditor();
});
localFunction.value = JSON.parse(JSON.stringify(originalFunction.value)); localFunction.value = JSON.parse(JSON.stringify(originalFunction.value));
setTimeout(() => { setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
@@ -236,14 +251,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
});
watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema });
});
watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -107,12 +106,15 @@
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import Views from '@/ipc-api/Views'; import Views from '@/ipc-api/Views';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -126,6 +128,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -202,21 +205,21 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -233,6 +236,10 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
originalView.value = { originalView.value = {
algorithm: 'UNDEFINED', algorithm: 'UNDEFINED',
definer: '', definer: '',
@@ -248,12 +255,12 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
@@ -266,7 +273,7 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -194,6 +193,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
@@ -205,6 +206,7 @@ import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { AlterFunctionParams, FunctionInfos, FunctionParam } from 'common/interfaces/antares'; import { AlterFunctionParams, FunctionInfos, FunctionParam } from 'common/interfaces/antares';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -218,6 +220,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -344,8 +347,12 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
@@ -400,14 +407,10 @@ const hideAskParamsModal = () => {
isAskingParameters.value = false; isAskingParameters.value = false;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -443,14 +446,19 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
(async () => { (async () => {
await getFunctionData(); await getFunctionData();
queryEditor.value.editor.session.setValue(localFunction.value.sql); queryEditor.value.editor.session.setValue(localFunction.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -458,6 +466,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -168,6 +167,8 @@ import { Component, computed, onUnmounted, onBeforeUnmount, onMounted, Ref, ref,
import { AlterRoutineParams, FunctionParam, RoutineInfos } from 'common/interfaces/antares'; import { AlterRoutineParams, FunctionParam, RoutineInfos } from 'common/interfaces/antares';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
@@ -177,6 +178,7 @@ import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue'; import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
import ModalAskParameters from '@/components/ModalAskParameters.vue'; import ModalAskParameters from '@/components/ModalAskParameters.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -190,6 +192,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -316,8 +319,12 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
@@ -370,14 +377,10 @@ const hideAskParamsModal = () => {
isAskingParameters.value = false; isAskingParameters.value = false;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -413,14 +416,19 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
(async () => { (async () => {
await getRoutineData(); await getRoutineData();
queryEditor.value.editor.session.setValue(localRoutine.value.sql); queryEditor.value.editor.session.setValue(localRoutine.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -428,7 +436,7 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -127,6 +126,8 @@ import { AlterEventParams, EventInfos } from 'common/interfaces/antares';
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
@@ -134,6 +135,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue'; import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -147,6 +149,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -269,8 +272,12 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
@@ -288,14 +295,10 @@ const timingUpdate = (options: EventInfos) => {
localScheduler.value = options; localScheduler.value = options;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -331,14 +334,19 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
(async () => { (async () => {
await getSchedulerData(); await getSchedulerData();
queryEditor.value.editor.session.setValue(localScheduler.value.sql); queryEditor.value.editor.session.setValue(localScheduler.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -346,7 +354,7 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -2,6 +2,7 @@
<ConfirmModal <ConfirmModal
:confirm-text="t('word.confirm')" :confirm-text="t('word.confirm')"
size="400" size="400"
:disable-autofocus="true"
@confirm="confirmOptionsChange" @confirm="confirmOptionsChange"
@hide="$emit('hide')" @hide="$emit('hide')"
> >

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -173,7 +172,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, onBeforeUnmount, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
import { AlterTableParams, TableField, TableForeign, TableIndex, TableInfos, TableOptions } from 'common/interfaces/antares'; import { AlterTableParams, TableField, TableForeign, TableIndex, TableInfos, TableOptions } from 'common/interfaces/antares';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -186,6 +185,7 @@ import BaseSelect from '@/components/BaseSelect.vue';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue'; import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue'; import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue'; import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -646,14 +646,10 @@ const foreignsUpdate = (foreigns: TableForeign[]) => {
localKeyUsage.value = foreigns; localKeyUsage.value = foreigns;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, () => { watch(() => props.schema, () => {
@@ -684,9 +680,12 @@ watch(isChanged, (val) => {
}); });
getFieldsData(); getFieldsData();
window.addEventListener('keydown', onKey);
onMounted(() => {
ipcRenderer.on('save-content', saveContentListener);
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -126,8 +126,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { Component, computed, onMounted, onUnmounted, onUpdated, Prop, ref, Ref, watch } from 'vue'; import { Component, computed, onMounted, onUnmounted, onUpdated, Prop, ref, Ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console';
import * as Draggable from 'vuedraggable'; import * as Draggable from 'vuedraggable';
import TableRow from '@/components/WorkspaceTabPropsTableRow.vue'; import TableRow from '@/components/WorkspaceTabPropsTableRow.vue';
import TableContext from '@/components/WorkspaceTabPropsTableContext.vue'; import TableContext from '@/components/WorkspaceTabPropsTableContext.vue';
@@ -150,9 +152,12 @@ const props = defineProps({
const emit = defineEmits(['add-new-index', 'add-to-index', 'rename-field', 'duplicate-field', 'remove-field']); const emit = defineEmits(['add-new-index', 'add-to-index', 'rename-field', 'duplicate-field', 'remove-field']);
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const consoleStore = useConsoleStore();
const { getWorkspace } = workspacesStore; const { getWorkspace } = workspacesStore;
const { consoleHeight } = storeToRefs(consoleStore);
const tableWrapper: Ref<HTMLDivElement> = ref(null); const tableWrapper: Ref<HTMLDivElement> = ref(null);
const propTable: Ref<HTMLDivElement> = ref(null); const propTable: Ref<HTMLDivElement> = ref(null);
const resultTable: Ref<Component> = ref(null); const resultTable: Ref<Component> = ref(null);
@@ -172,8 +177,13 @@ const resizeResults = () => {
const el = tableWrapper.value; const el = tableWrapper.value;
if (el) { if (el) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - el.getBoundingClientRect().top - sizeToSubtract;
resultsSize.value = size; resultsSize.value = size;
} }
} }
@@ -216,6 +226,10 @@ watch(fieldsLength, () => {
refreshScroller(); refreshScroller();
}); });
watch(consoleHeight, () => {
resizeResults();
});
onUpdated(() => { onUpdated(() => {
if (propTable.value) if (propTable.value)
refreshScroller(); refreshScroller();

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -30,7 +29,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="container"> <div class="px-2">
<div class="columns"> <div class="columns">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
@@ -118,12 +117,15 @@
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useConsoleStore } from '@/stores/console';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import Triggers from '@/ipc-api/Triggers'; import Triggers from '@/ipc-api/Triggers';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
type TriggerEventName = 'INSERT' | 'UPDATE' | 'DELETE' type TriggerEventName = 'INSERT' | 'UPDATE' | 'DELETE'
@@ -139,6 +141,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -304,21 +307,21 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -345,14 +348,19 @@ watch(isChanged, (val) => {
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val }); setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
(async () => { (async () => {
await getTriggerData(); await getTriggerData();
queryEditor.value.editor.session.setValue(localTrigger.value.sql); queryEditor.value.editor.session.setValue(localTrigger.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -360,6 +368,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -84,13 +83,16 @@
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { AlterFunctionParams, TriggerFunctionInfos } from 'common/interfaces/antares'; import { AlterFunctionParams, TriggerFunctionInfos } from 'common/interfaces/antares';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -104,6 +106,7 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
@@ -209,21 +212,21 @@ const clearChanges = () => {
const resizeQueryEditor = () => { const resizeQueryEditor = () => {
if (queryEditor.value) { if (queryEditor.value) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
editorHeight.value = size; editorHeight.value = size;
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -242,6 +245,10 @@ watch(() => props.function, async () => {
} }
}); });
watch(consoleHeight, () => {
resizeQueryEditor();
});
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema }); if (val) changeBreadcrumbs({ schema: props.schema });
}); });
@@ -253,11 +260,12 @@ watch(isChanged, (val) => {
(async () => { (async () => {
await getFunctionData(); await getFunctionData();
queryEditor.value.editor.session.setValue(localFunction.value.sql); queryEditor.value.editor.session.setValue(localFunction.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -265,6 +273,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -112,6 +111,7 @@ import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import Views from '@/ipc-api/Views'; import Views from '@/ipc-api/Views';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -245,14 +245,10 @@ const resizeQueryEditor = () => {
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -288,11 +284,12 @@ watch(isChanged, (val) => {
(async () => { (async () => {
await getViewData(); await getViewData();
queryEditor.value.editor.session.setValue(localView.value.sql); queryEditor.value.editor.session.setValue(localView.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -300,6 +297,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -3,15 +3,11 @@
v-show="isSelected" v-show="isSelected"
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0" class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0" tabindex="0"
@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"> <div class="workspace-query-runner column col-12">
<QueryEditor <QueryEditor
v-show="isSelected" v-show="isSelected"
id="query-editor"
ref="queryEditor" ref="queryEditor"
v-model="query" v-model="query"
:auto-focus="true" :auto-focus="true"
@@ -39,7 +35,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:class="{'loading':isQuering}" :class="{'loading':isQuering}"
:disabled="!query" :disabled="!query"
title="F5"
@click="runQuery(query)" @click="runQuery(query)"
> >
<i class="mdi mdi-24px mdi-play pr-1" /> <i class="mdi mdi-24px mdi-play pr-1" />
@@ -67,7 +62,6 @@
<button <button
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:disabled="!query || isQuering" :disabled="!query || isQuering"
title="CTRL+W"
@click="clear()" @click="clear()"
> >
<i class="mdi mdi-24px mdi-delete-sweep pr-1" /> <i class="mdi mdi-24px mdi-delete-sweep pr-1" />
@@ -79,7 +73,6 @@
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="!query || isQuering" :disabled="!query || isQuering"
title="CTRL+B"
@click="beautify()" @click="beautify()"
> >
<i class="mdi mdi-24px mdi-brush pr-1" /> <i class="mdi mdi-24px mdi-brush pr-1" />
@@ -88,7 +81,6 @@
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="isQuering" :disabled="isQuering"
title="CTRL+G"
@click="openHistoryModal()" @click="openHistoryModal()"
> >
<i class="mdi mdi-24px mdi-history pr-1" /> <i class="mdi mdi-24px mdi-history pr-1" />
@@ -111,6 +103,9 @@
<li class="menu-item"> <li class="menu-item">
<a class="c-hand" @click="downloadTable('csv')">CSV</a> <a class="c-hand" @click="downloadTable('csv')">CSV</a>
</li> </li>
<li class="menu-item">
<a class="c-hand" @click="downloadTable('sql')">SQL INSERT</a>
</li>
</ul> </ul>
</div> </div>
<div class="input-group pr-2" :title="t('message.commitMode')"> <div class="input-group pr-2" :title="t('message.commitMode')">
@@ -187,19 +182,22 @@
import { Component, computed, onBeforeUnmount, onMounted, Prop, ref, Ref, watch } from 'vue'; import { Component, computed, onBeforeUnmount, onMounted, Prop, ref, Ref, watch } from 'vue';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { format } from 'sql-formatter'; import { format } from 'sql-formatter';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { useHistoryStore } from '@/stores/history'; import { useHistoryStore } from '@/stores/history';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useResultTables } from '@/composables/useResultTables';
import { useConsoleStore } from '@/stores/console';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import BaseLoader from '@/components/BaseLoader.vue'; import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue'; import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue';
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue'; import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue';
import ModalHistory from '@/components/ModalHistory.vue'; import ModalHistory from '@/components/ModalHistory.vue';
import { useResultTables } from '@/composables/useResultTables';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -223,6 +221,8 @@ const { saveHistory } = useHistoryStore();
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore());
const { const {
getWorkspace, getWorkspace,
changeBreadcrumbs, changeBreadcrumbs,
@@ -289,6 +289,10 @@ watch(selectedSchema, () => {
const runQuery = async (query: string) => { const runQuery = async (query: string) => {
if (!query || isQuering.value) return; if (!query || isQuering.value) return;
isQuering.value = true; isQuering.value = true;
const selectedQuery = queryEditor.value.editor.getSelectedText();
if (selectedQuery) query = selectedQuery;
clearTabData(); clearTabData();
queryTable.value.resetSort(); queryTable.value.resetSort();
@@ -365,14 +369,17 @@ const resize = (e: MouseEvent) => {
const el = queryEditor.value.$el; const el = queryEditor.value.$el;
const queryFooterHeight = queryAreaFooter.value.clientHeight; const queryFooterHeight = queryAreaFooter.value.clientHeight;
const bottom = e.pageY || resizer.value.getBoundingClientRect().bottom; const bottom = e.pageY || resizer.value.getBoundingClientRect().bottom;
const maxHeight = window.innerHeight - 100 - queryFooterHeight; const maxHeight = window.innerHeight - 100 - queryFooterHeight - consoleHeight.value;
let localEditorHeight = bottom - el.getBoundingClientRect().top; let localEditorHeight = bottom - el.getBoundingClientRect().top;
if (localEditorHeight > maxHeight) localEditorHeight = maxHeight; if (localEditorHeight > maxHeight) localEditorHeight = maxHeight;
if (localEditorHeight < 50) localEditorHeight = 50; if (localEditorHeight < 50) localEditorHeight = 50;
editorHeight.value = localEditorHeight; editorHeight.value = localEditorHeight;
}; };
const resizeResults = () => queryTable.value.resizeResults();
const onWindowResize = (e: MouseEvent) => { const onWindowResize = (e: MouseEvent) => {
if (!queryEditor.value) return;
const el = queryEditor.value.$el; const el = queryEditor.value.$el;
const queryFooterHeight = queryAreaFooter.value.clientHeight; const queryFooterHeight = queryAreaFooter.value.clientHeight;
const bottom = e.pageY || resizer.value.getBoundingClientRect().bottom; const bottom = e.pageY || resizer.value.getBoundingClientRect().bottom;
@@ -386,7 +393,7 @@ const onWindowResize = (e: MouseEvent) => {
const stopResize = () => { const stopResize = () => {
window.removeEventListener('mousemove', resize); window.removeEventListener('mousemove', resize);
if (queryTable.value && results.value.length) if (queryTable.value && results.value.length)
queryTable.value.resizeResults(); resizeResults();
if (queryEditor.value) if (queryEditor.value)
queryEditor.value.editor.resize(); queryEditor.value.editor.resize();
@@ -412,7 +419,8 @@ const beautify = () => {
const formattedQuery = format(query.value, { const formattedQuery = format(query.value, {
language, language,
uppercase: true uppercase: true
}); // eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
queryEditor.value.editor.session.setValue(formattedQuery); queryEditor.value.editor.session.setValue(formattedQuery);
} }
}; };
@@ -434,7 +442,7 @@ const clear = () => {
clearTabData(); clearTabData();
}; };
const downloadTable = (format: 'csv' | 'json') => { const downloadTable = (format: 'csv' | 'json' | 'sql') => {
queryTable.value.downloadTable(format, `${props.tab.type}-${props.tab.index}`); queryTable.value.downloadTable(format, `${props.tab.type}-${props.tab.index}`);
}; };
@@ -476,18 +484,55 @@ const rollbackTab = async () => {
isQuering.value = false; isQuering.value = false;
}; };
defineExpose({ resizeResults });
query.value = props.tab.content as string; query.value = props.tab.content as string;
selectedSchema.value = props.tab.schema || breadcrumbsSchema.value; selectedSchema.value = props.tab.schema || breadcrumbsSchema.value;
if (!databaseSchemas.value.includes(selectedSchema.value)) if (!databaseSchemas.value.includes(selectedSchema.value))
selectedSchema.value = null; selectedSchema.value = null;
// window.addEventListener('keydown', onKey);
window.addEventListener('resize', onWindowResize); window.addEventListener('resize', onWindowResize);
const reloadListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
runQuery(query.value);
};
const formatListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
beautify();
};
const killQueryListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
killTabQuery();
};
const clearQueryListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
clear();
};
const historyListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
openHistoryModal();
};
onMounted(() => { onMounted(() => {
const localResizer = resizer.value; const localResizer = resizer.value;
ipcRenderer.on('run-or-reload', reloadListener);
ipcRenderer.on('format-query', formatListener);
ipcRenderer.on('kill-query', killQueryListener);
ipcRenderer.on('clear-query', clearQueryListener);
ipcRenderer.on('query-history', historyListener);
localResizer.addEventListener('mousedown', (e: MouseEvent) => { localResizer.addEventListener('mousedown', (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
@@ -501,12 +546,17 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', onWindowResize); window.removeEventListener('resize', onWindowResize);
// window.removeEventListener('keydown', onKey);
const params = { const params = {
uid: props.connection.uid, uid: props.connection.uid,
tabUid: props.tab.uid tabUid: props.tab.uid
}; };
Schema.destroyConnectionToCommit(params); Schema.destroyConnectionToCommit(params);
ipcRenderer.removeListener('run-or-reload', reloadListener);
ipcRenderer.removeListener('format-query', formatListener);
ipcRenderer.removeListener('kill-query', killQueryListener);
ipcRenderer.removeListener('clear-query', clearQueryListener);
ipcRenderer.removeListener('query-history', historyListener);
}); });
</script> </script>

View File

@@ -2,49 +2,21 @@
<div class="container"> <div class="container">
<div class="columns"> <div class="columns">
<div class="column col-16 text-right"> <div class="column col-16 text-right">
<div class="mb-4"> <div
{{ t('message.runQuery') }} v-for="(shortcut, i) in tabShortcuts"
</div> :key="i"
<div v-if="customizations.cancelQueries" class="mb-4"> class="mb-4"
{{ t('message.killQuery') }} >
</div> {{ t(shortcutEvents[shortcut.event].l18n, {param: shortcutEvents[shortcut.event].l18nParam}) }}
<div class="mb-4">
{{ t('word.format') }}
</div>
<div class="mb-4">
{{ t('word.clear') }}
</div>
<div class="mb-4">
{{ t('word.history') }}
</div>
<div class="mb-4">
{{ t('message.openNewTab') }}
</div>
<div class="mb-4">
{{ t('message.closeTab') }}
</div> </div>
</div> </div>
<div class="column col-16"> <div class="column col-16">
<div class="mb-4"> <div
<code>F5</code> v-for="(shortcut, i) in tabShortcuts"
</div> :key="i"
<div v-if="customizations.cancelQueries" class="mb-4"> class="mb-4"
<code>CTRL</code> + <code>K</code> >
</div> <span v-html="parseKeys(shortcut.keys)" />
<div class="mb-4">
<code>CTRL</code> + <code>B</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>ALT</code> + <code>W</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>G</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>T</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>W</code>
</div> </div>
</div> </div>
</div> </div>
@@ -52,12 +24,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSettingsStore } from '@/stores/settings';
import { shortcutEvents } from 'common/shortcuts';
import { useFilters } from '@/composables/useFilters';
const { parseKeys } = useFilters();
const { t } = useI18n(); const { t } = useI18n();
defineProps({ const settingsStore = useSettingsStore();
customizations: Object const { shortcuts } = storeToRefs(settingsStore);
const tabShortcuts = computed(() => {
return shortcuts.value.filter(s => shortcutEvents[s.event].context === 'tab');
}); });
</script> </script>

View File

@@ -14,10 +14,12 @@
:context-event="contextEvent" :context-event="contextEvent"
:selected-rows="selectedRows" :selected-rows="selectedRows"
:selected-cell="selectedCell" :selected-cell="selectedCell"
:mode="mode"
@show-delete-modal="showDeleteConfirmModal" @show-delete-modal="showDeleteConfirmModal"
@set-null="setNull" @set-null="setNull"
@copy-cell="copyCell" @copy-cell="copyCell"
@copy-row="copyRow" @copy-row="copyRow"
@duplicate-row="duplicateRow"
@close-context="closeContext" @close-context="closeContext"
/> />
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs"> <ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
@@ -118,33 +120,44 @@ import { storeToRefs } from 'pinia';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { arrayToFile } from '../libs/arrayToFile'; import { useConsoleStore } from '@/stores/console';
import { exportRows } from '../libs/exportRows';
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue'; import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow.vue'; import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow.vue';
import TableContext from '@/components/WorkspaceTabQueryTableContext.vue'; import TableContext from '@/components/WorkspaceTabQueryTableContext.vue';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import moment from 'moment'; import * as moment from 'moment';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { TableField, QueryResult } from 'common/interfaces/antares'; import { TableField, QueryResult } from 'common/interfaces/antares';
import { TableUpdateParams } from 'common/interfaces/tableApis'; import { TableUpdateParams } from 'common/interfaces/tableApis';
import { jsonToSqlInsert } from 'common/libs/sqlUtils';
import { unproxify } from '@/libs/unproxify';
const { t } = useI18n(); const { t } = useI18n();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const consoleStore = useConsoleStore();
const { getWorkspace } = useWorkspacesStore(); const { getWorkspace } = useWorkspacesStore();
const { dataTabLimit: pageSize } = storeToRefs(settingsStore); const { dataTabLimit: pageSize } = storeToRefs(settingsStore);
const { consoleHeight } = storeToRefs(consoleStore);
const props = defineProps({ const props = defineProps({
results: Array as Prop<QueryResult[]>, results: Array as Prop<QueryResult[]>,
connUid: String, connUid: String,
mode: String, mode: String as Prop<'table' | 'query'>,
isSelected: Boolean, isSelected: Boolean,
elementType: { type: String, default: 'table' } elementType: { type: String, default: 'table' }
}); });
const emit = defineEmits(['update-field', 'delete-selected', 'hard-sort']); const emit = defineEmits([
'update-field',
'delete-selected',
'hard-sort',
'duplicate-row'
]);
const resultTable: Ref<Component & {updateWindow: () => void}> = ref(null); const resultTable: Ref<Component & {updateWindow: () => void}> = ref(null);
const tableWrapper: Ref<HTMLDivElement> = ref(null); const tableWrapper: Ref<HTMLDivElement> = ref(null);
@@ -166,6 +179,7 @@ const selectedField = ref(null);
const isEditingRow = ref(false); const isEditingRow = ref(false);
const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema); const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema);
const workspaceClient = computed(() => getWorkspace(props.connUid).client);
const primaryField = computed(() => { const primaryField = computed(() => {
const primaryFields = fields.value.filter(field => field.key === 'pri'); const primaryFields = fields.value.filter(field => field.key === 'pri');
@@ -272,6 +286,8 @@ const getSchema = (index: number) => {
}; };
const getPrimaryValue = (row: any) => { const getPrimaryValue = (row: any) => {
if (!primaryField.value) return null;
const primaryFieldName = Object.keys(row).find(prop => [ const primaryFieldName = Object.keys(row).find(prop => [
primaryField.value.alias, primaryField.value.alias,
primaryField.value.name, primaryField.value.name,
@@ -296,8 +312,13 @@ const resizeResults = () => {
const el = tableWrapper.value; const el = tableWrapper.value;
if (el) { if (el) {
let sizeToSubtract = 0;
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight; if (footer) sizeToSubtract += footer.offsetHeight;
sizeToSubtract += consoleHeight.value;
const size = window.innerHeight - el.getBoundingClientRect().top - sizeToSubtract;
resultsSize.value = size; resultsSize.value = size;
} }
resultTable.value.updateWindow(); resultTable.value.updateWindow();
@@ -321,7 +342,7 @@ const updateField = (payload: { field: string; type: string; content: any }, row
}); });
const params = { const params = {
primary: primaryField.value.name, primary: primaryField.value?.name,
schema: getSchema(resultsetIndex.value), schema: getSchema(resultsetIndex.value),
table: getTable(resultsetIndex.value), table: getTable(resultsetIndex.value),
id: getPrimaryValue(orgRow), id: getPrimaryValue(orgRow),
@@ -356,7 +377,7 @@ const deleteSelected = () => {
}); });
const params = { const params = {
primary: primaryField.value.name, primary: primaryField.value?.name,
schema: getSchema(resultsetIndex.value), schema: getSchema(resultsetIndex.value),
table: getTable(resultsetIndex.value), table: getTable(resultsetIndex.value),
rows rows
@@ -368,7 +389,7 @@ const setNull = () => {
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id)); const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
const params = { const params = {
primary: primaryField.value.name, primary: primaryField.value?.name,
schema: getSchema(resultsetIndex.value), schema: getSchema(resultsetIndex.value),
table: getTable(resultsetIndex.value), table: getTable(resultsetIndex.value),
id: getPrimaryValue(row), id: getPrimaryValue(row),
@@ -394,11 +415,59 @@ const copyCell = () => {
navigator.clipboard.writeText(valueToCopy); navigator.clipboard.writeText(valueToCopy);
}; };
const copyRow = () => { const copyRow = (format: string) => {
let contentToCopy;
if (selectedRows.value.length === 1) {
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
const rowToCopy = unproxify(row);
delete rowToCopy._antares_id;
contentToCopy = rowToCopy;
}
else {
contentToCopy = unproxify(localResults.value).filter((row: any) => selectedRows.value.includes(row._antares_id)).map((row: any) => {
delete row._antares_id;
return row;
});
}
if (format === 'json')
navigator.clipboard.writeText(JSON.stringify(contentToCopy));
else if (format === 'sql') {
const sqlInserts = [];
if (!Array.isArray(contentToCopy)) contentToCopy = [contentToCopy];
for (const row of contentToCopy) {
sqlInserts.push(jsonToSqlInsert({
json: row,
client: workspaceClient.value,
fields: fieldsObj.value as {
[key: string]: {type: string; datePrecision: number};
},
table: getTable(resultsetIndex.value)
}));
}
navigator.clipboard.writeText(sqlInserts.join('\n'));
}
else if (format === 'csv') {
const csv = [];
if (!Array.isArray(contentToCopy)) contentToCopy = [contentToCopy];
if (contentToCopy.length)
csv.push(Object.keys(contentToCopy[0]).join(';'));
for (const row of contentToCopy)
csv.push(Object.values(row).map(col => typeof col === 'string' ? `"${col}"` : col).join(';'));
navigator.clipboard.writeText(csv.join('\n'));
}
};
const duplicateRow = () => {
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id)); const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
const rowToCopy = JSON.parse(JSON.stringify(row)); const rowToDuplicate = JSON.parse(JSON.stringify(row));
delete rowToCopy._antares_id; delete rowToDuplicate._antares_id;
navigator.clipboard.writeText(JSON.stringify(rowToCopy)); emit('duplicate-row', rowToDuplicate);
}; };
const applyUpdate = (params: TableUpdateParams) => { const applyUpdate = (params: TableUpdateParams) => {
@@ -509,7 +578,7 @@ const selectResultset = (index: number) => {
resultsetIndex.value = index; resultsetIndex.value = index;
}; };
const downloadTable = (format: 'csv' | 'json', filename: string) => { const downloadTable = (format: 'csv' | 'json' | 'sql', table: string) => {
if (!sortedResults.value) return; if (!sortedResults.value) return;
const rows = JSON.parse(JSON.stringify(sortedResults.value)).map((row: any) => { const rows = JSON.parse(JSON.stringify(sortedResults.value)).map((row: any) => {
@@ -517,10 +586,14 @@ const downloadTable = (format: 'csv' | 'json', filename: string) => {
return row; return row;
}); });
arrayToFile({ exportRows({
type: format, type: format,
content: rows, content: rows,
filename fields: fieldsObj.value as {
[key: string]: {type: string; datePrecision: number};
},
client: workspaceClient.value,
table
}); });
}; };
@@ -538,7 +611,7 @@ const onKey = async (e: KeyboardEvent) => {
selectAllRows(e); selectAllRows(e);
// row navigation stuff // row navigation stuff
if ((e.code.includes('Arrow') || e.code === 'Tab') && sortedResults.value.length > 0 && !e.altKey) { if (!(e.ctrlKey || e.metaKey) && (e.code.includes('Arrow') || e.code === 'Tab') && sortedResults.value.length > 0 && !e.altKey) {
e.preventDefault(); e.preventDefault();
const aviableFields= Object.keys(sortedResults.value[0]).slice(0, -1); // removes _antares_id const aviableFields= Object.keys(sortedResults.value[0]).slice(0, -1); // removes _antares_id
@@ -662,6 +735,10 @@ watch(() => props.isSelected, async (val) => {
} }
}); });
watch(consoleHeight, () => {
resizeResults();
});
onUpdated(() => { onUpdated(() => {
if (table.value) if (table.value)
refreshScroller(); refreshScroller();

View File

@@ -3,7 +3,7 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="closeContext" @close-context="closeContext"
> >
<div v-if="selectedRows.length === 1" class="context-element"> <div class="context-element">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" /> <i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu"> <div class="context-submenu">
@@ -16,17 +16,32 @@
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }} <i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
</span> </span>
</div> </div>
<div <div class="context-element" @click="copyRow('json')">
v-if="selectedRows.length === 1"
class="context-element"
@click="copyRow"
>
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', 1) }} <i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (JSON)
</span>
</div>
<div class="context-element" @click="copyRow('csv')">
<span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (CSV)
</span>
</div>
<div class="context-element" @click="copyRow('sql')">
<span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (SQL INSERT)
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div
v-if="selectedRows.length === 1 && selectedCell.isEditable && mode === 'table'"
class="context-element"
@click="duplicateRow"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('word.duplicate') }}
</span>
</div>
<div <div
v-if="selectedRows.length === 1 && selectedCell.isEditable" v-if="selectedRows.length === 1 && selectedCell.isEditable"
class="context-element" class="context-element"
@@ -49,6 +64,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Prop } from 'vue';
import BaseContextMenu from '@/components/BaseContextMenu.vue'; import BaseContextMenu from '@/components/BaseContextMenu.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -57,10 +73,18 @@ const { t } = useI18n();
defineProps({ defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,
selectedRows: Array, selectedRows: Array,
selectedCell: Object selectedCell: Object,
mode: String as Prop<'table' | 'query'>
}); });
const emit = defineEmits(['show-delete-modal', 'close-context', 'set-null', 'copy-cell', 'copy-row']); const emit = defineEmits([
'show-delete-modal',
'close-context',
'set-null',
'copy-cell',
'copy-row',
'duplicate-row'
]);
const showConfirmModal = () => { const showConfirmModal = () => {
emit('show-delete-modal'); emit('show-delete-modal');
@@ -80,8 +104,13 @@ const copyCell = () => {
closeContext(); closeContext();
}; };
const copyRow = () => { const copyRow = (format: string) => {
emit('copy-row'); emit('copy-row', format);
closeContext();
};
const duplicateRow = () => {
emit('duplicate-row');
closeContext(); closeContext();
}; };
</script> </script>

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