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

Compare commits

...

83 Commits

Author SHA1 Message Date
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
121 changed files with 22044 additions and 4635 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

@@ -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

@@ -2,6 +2,95 @@
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.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) ### [0.5.10](https://github.com/antares-sql/antares/compare/v0.5.9...v0.5.10) (2022-07-18)

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

22881
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.10", "version": "0.5.16",
"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,10 +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",
"@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",
@@ -132,16 +136,17 @@
"moment": "~2.29.4", "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": {
@@ -176,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

@@ -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

@@ -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(', ')});`;
};

View File

@@ -1,49 +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 { interface ShortcutRecord {
event: string; event: string;
keys: Electron.Accelerator[]; keys: Electron.Accelerator[] | string[];
description: string; /** Needed for default shortcuts */
os: NodeJS.Platform[];
} }
/**
* Default shortcuts
*/
const shortcuts: ShortcutRecord[] = [ 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', event: 'open-new-tab',
keys: ['CommandOrControl+T'], keys: ['CommandOrControl+T'],
description: 'Open a new query tab' os: ['darwin', 'linux', 'win32']
}, },
{ {
event: 'close-tab', event: 'close-tab',
keys: ['CommandOrControl+W'], keys: ['CommandOrControl+W'],
description: 'Close tab' os: ['darwin', 'linux', 'win32']
}, },
{ {
event: 'next-tab', event: 'next-tab',
keys: ['Alt+CommandOrControl+Right', 'CommandOrControl+PageDown'], keys: ['Alt+CommandOrControl+Right'],
description: 'Next tab' os: ['darwin', 'win32']
}, },
{ {
event: 'prev-tab', event: 'prev-tab',
keys: ['Alt+CommandOrControl+Left', 'CommandOrControl+PageUp'], keys: ['Alt+CommandOrControl+Left'],
description: 'Previous tab' os: ['darwin', 'win32']
}, },
{ {
event: 'open-connections-modal', 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'], keys: ['Shift+CommandOrControl+Space'],
description: 'Show all connections' os: ['darwin', 'linux', 'win32']
}, },
{ {
event: 'toggle-console', event: 'toggle-console',
keys: ['CommandOrControl+F12', 'CommandOrControl+`'], keys: ['CommandOrControl+F12'],
description: 'Toggle console' os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32']
} }
]; ];
for (let i = 1; i <= 9; i++) { for (let i = 1; i <= 9; i++) {
shortcuts.push( shortcutEvents[`select-tab-${i}`] = {
{ l18n: 'message.selectTabNumber',
event: `select-tab-${i}`, l18nParam: i
keys: [`CommandOrControl+${i}`], };
description: `Select tab number ${i}`
}); shortcuts.push({
event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`],
os: ['darwin', 'linux', 'win32']
});
} }
export { shortcuts }; 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

@@ -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

@@ -1,5 +1,4 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import { webContents } from 'electron';
import mysql from 'mysql2/promise'; 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';
@@ -7,9 +6,11 @@ import SSH2Promise from 'ssh2-promise';
const queryLogger = ({ sql, cUid }: {sql: string; cUid: 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, ' ');
const mainWindow = webContents.fromId(1); if (process.type !== undefined) {
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() }); const mainWindow = require('electron').webContents.fromId(1);
console.log(escapedSql); mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
}
if (process.env.NODE_ENV === 'development') console.log(escapedSql);
}; };
/** /**

View File

@@ -0,0 +1,138 @@
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',
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;
@@ -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({ cUid: this._cUid, sql }); this._logger({ cUid: this._cUid, sql });
args = { args = {
nest: false, nest: false,

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({ cUid: this._cUid, sql }); this._logger({ cUid: this._cUid, sql });
args = { args = {
nest: false, nest: false,

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({ cUid: this._cUid, 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,

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,16 +1,15 @@
import { app, BrowserWindow, globalShortcut, 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 { shortcuts } from 'common/shortcuts'; 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';
@@ -86,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',
@@ -143,38 +142,11 @@ else {
window.webContents.session.loadExtension(extensionPath, { allowFileAccess: true }).catch(console.error); window.webContents.session.loadExtension(extensionPath, { allowFileAccess: true }).catch(console.error);
} }
}); });
app.on('browser-window-focus', () => {
// Send registered shortcut events to window
for (const shortcut of shortcuts) {
for (const key of shortcut.keys) {
globalShortcut.register(key, () => {
mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('EVENT:', shortcut);
});
}
}
if (isDevelopment) { // Dev shortcuts
globalShortcut.register('Shift+CommandOrControl+F5', () => {
mainWindow.reload();
});
globalShortcut.register('Shift+CommandOrControl+F12', () => {
mainWindow.webContents.openDevTools();
});
}
});
app.on('browser-window-blur', () => {
globalShortcut.unregisterAll();
});
} }
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: [
@@ -205,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>
@@ -67,10 +67,6 @@ const { changeApplicationTheme } = settingsStore;
const isAllConnectionsModal: Ref<boolean> = ref(false); const isAllConnectionsModal: Ref<boolean> = ref(false);
ipcRenderer.on('open-connections-modal', () => {
isAllConnectionsModal.value = true;
});
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
@@ -78,6 +74,22 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
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();

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

@@ -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"
@@ -282,6 +289,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 +325,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 +336,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 +395,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 +465,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 +529,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 +550,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

@@ -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

@@ -12,7 +12,7 @@
<div class="footer-right-elements"> <div class="footer-right-elements">
<ul class="footer-elements"> <ul class="footer-elements">
<li <li
v-if="workspace.connectionStatus === 'connected' " v-if="workspace?.connectionStatus === 'connected' "
class="footer-element footer-link" class="footer-element footer-link"
@click="toggleConsole()" @click="toggleConsole()"
> >

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') }} (Shift+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,13 +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> </a>
</li> </li>
<li class="menu-item"> <li class="menu-item">
<a class="c-hand p-vcentered" @click="toggleConsole"> <a class="c-hand p-vcentered" @click="toggleConsole">
<i class="mdi mdi-console-line mr-1 tool-icon" /> <i class="mdi mdi-console-line mr-1 tool-icon" />
<span>{{ $t('word.console') }}</span> <span>{{ t('word.console') }}</span>
</a> </a>
</li> </li>
<li <li
@@ -292,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
@@ -302,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>
@@ -312,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" />
@@ -485,6 +485,7 @@ import Connection from '@/ipc-api/Connection';
import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces'; import { useWorkspacesStore, WorkspaceTab } from '@/stores/workspaces';
import { useConsoleStore } from '@/stores/console'; 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';
@@ -510,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);
@@ -640,14 +645,6 @@ 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 () => {
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);

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,7 @@ 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: .5rem; margin-bottom: 0.5rem;
margin-top: 1.5rem; margin-top: 1.5rem;
.panel { .panel {

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,

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') {

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

@@ -35,7 +35,7 @@
@close-context="isContext = false" @close-context="isContext = false"
> >
<div class="context-element" @click="copyQuery"> <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> <span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
</div> </div>
</BaseContextMenu> </BaseContextMenu>
</template> </template>
@@ -119,59 +119,62 @@ onMounted(() => {
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.query-console-wrapper{ .query-console-wrapper {
width: 100%; width: 100%;
z-index: 9; z-index: 9;
margin-top: auto; margin-top: auto;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
.query-console-resizer{ .query-console-resizer {
height: 4px; height: 4px;
top: -1px; top: -1px;
width: 100%; width: 100%;
cursor: ns-resize; cursor: ns-resize;
position: absolute; position: absolute;
z-index: 99; z-index: 99;
transition: background 0.2s; transition: background 0.2s;
&:hover { &:hover {
background: rgba($primary-color, 50%); 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;
}
}
} }
} }
}
.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: .8;
font-weight: 700;
user-select: text;
}
}
}
}
} }
</style> </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" />
@@ -189,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();
@@ -300,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) => {
@@ -341,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"
@@ -160,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,
@@ -269,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) => {
@@ -310,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" />
@@ -138,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();
@@ -259,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) => {
@@ -298,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" />
@@ -126,6 +125,7 @@ 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();
@@ -268,14 +268,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.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -305,12 +301,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);
@@ -323,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" />
@@ -105,6 +104,7 @@ 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();
@@ -203,14 +203,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();
}
}
}; };
originalFunction.value = { originalFunction.value = {
@@ -237,12 +233,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);
@@ -255,6 +251,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" />
@@ -115,6 +114,7 @@ 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();
@@ -216,14 +216,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.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -259,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);
@@ -277,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" />
@@ -207,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();
@@ -407,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 () => {
@@ -457,11 +453,12 @@ watch(consoleHeight, () => {
(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(() => {
@@ -469,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" />
@@ -179,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();
@@ -377,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 () => {
@@ -427,11 +423,12 @@ watch(consoleHeight, () => {
(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(() => {
@@ -439,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" />
@@ -136,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();
@@ -295,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 () => {
@@ -345,11 +341,12 @@ watch(consoleHeight, () => {
(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(() => {
@@ -357,7 +354,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" />
@@ -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

@@ -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" />
@@ -126,6 +125,7 @@ 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'
@@ -318,14 +318,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 () => {
@@ -359,11 +355,12 @@ watch(consoleHeight, () => {
(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(() => {
@@ -371,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" />
@@ -93,6 +92,7 @@ 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();
@@ -223,14 +223,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 () => {
@@ -264,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(() => {
@@ -276,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,11 +3,6 @@
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
@@ -40,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" />
@@ -68,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" />
@@ -80,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" />
@@ -89,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" />
@@ -112,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')">
@@ -203,6 +197,7 @@ 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 BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -294,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();
@@ -420,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);
} }
}; };
@@ -442,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}`);
}; };
@@ -492,12 +492,47 @@ 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();
@@ -511,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">
@@ -119,16 +121,18 @@ 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 { useConsoleStore } from '@/stores/console'; import { useConsoleStore } from '@/stores/console';
import { arrayToFile } from '../libs/arrayToFile'; 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();
@@ -143,12 +147,17 @@ 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);
@@ -170,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');
@@ -367,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
@@ -379,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),
@@ -405,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) => {
@@ -520,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) => {
@@ -528,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
}); });
}; };
@@ -549,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

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>

View File

@@ -19,7 +19,7 @@
class="cell-content" class="cell-content"
:class="`${isNull(col)} ${typeClass(fields[cKey].type)}`" :class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON(cKey)" @dblclick="editON(cKey)"
>{{ cutText(typeFormat(col, fields[cKey].type.toLowerCase(), fields[cKey].length) as string) }}</span> >{{ cutText(typeFormat(col, fields[cKey].type.toLowerCase(), fields[cKey].length) as string, 250) }}</span>
<ForeignKeySelect <ForeignKeySelect
v-else-if="isForeignKey(cKey)" v-else-if="isForeignKey(cKey)"
v-model="editingContent" v-model="editingContent"
@@ -221,9 +221,11 @@ import TextEditor from '@/components/BaseTextEditor.vue';
import BaseMap from '@/components/BaseMap.vue'; import BaseMap from '@/components/BaseMap.vue';
import ForeignKeySelect from '@/components/ForeignKeySelect.vue'; import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useFilters } from '@/composables/useFilters';
import { QueryForeign, TableField } from 'common/interfaces/antares'; import { QueryForeign, TableField } from 'common/interfaces/antares';
const { t } = useI18n(); const { t } = useI18n();
const { cutText } = useFilters();
const props = defineProps({ const props = defineProps({
row: Object, row: Object,
@@ -440,7 +442,7 @@ const editOFF = () => {
let content; let content;
if (!BLOB.includes(editingType.value)) { if (!BLOB.includes(editingType.value)) {
if ([...DATETIME, ...TIME].includes(editingType.value)) { if ([...DATETIME, ...TIME].includes(editingType.value)) {
if (editingContent.value.substring(editingContent.value.length - 1) === '.') if (editingContent.value !== null && editingContent.value.substring(editingContent.value.length - 1) === '.')
editingContent.value = editingContent.value.slice(0, -1); editingContent.value = editingContent.value.slice(0, -1);
} }
@@ -546,11 +548,6 @@ const onKey = (e: KeyboardEvent) => {
} }
}; };
const cutText = (val: string) => {
if (typeof val !== 'string') return val;
return val.length > 128 ? `${val.substring(0, 128)}[...]` : val;
};
const typeFormat = (val: string | number | Date | number[], type: string, precision?: number | false) => { const typeFormat = (val: string | number | Date | number[], type: string, precision?: number | false) => {
if (!val) return val; if (!val) return val;

View File

@@ -8,7 +8,7 @@
<button <button
class="btn btn-dark btn-sm mr-0 pr-1" class="btn btn-dark btn-sm mr-0 pr-1"
:class="{'loading':isQuering}" :class="{'loading':isQuering}"
:title="`${t('word.refresh')} (F5)`" :title="`${t('word.refresh')}`"
@click="reloadTable" @click="reloadTable"
> >
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" /> <i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
@@ -35,7 +35,7 @@
<button <button
class="btn btn-dark btn-sm mr-0" class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || page === 1" :disabled="isQuering || page === 1"
title="CTRL+ᐊ" :title="t('message.previousResultsPage')"
@click="pageChange('prev')" @click="pageChange('prev')"
> >
<i class="mdi mdi-24px mdi-skip-previous" /> <i class="mdi mdi-24px mdi-skip-previous" />
@@ -61,7 +61,7 @@
<button <button
class="btn btn-dark btn-sm mr-0" class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (results.length && results[0].rows.length < limit)" :disabled="isQuering || (results.length && results[0].rows.length < limit)"
title="CTRL+ᐅ" :title="t('message.nextResultsPage')"
@click="pageChange('next')" @click="pageChange('next')"
> >
<i class="mdi mdi-24px mdi-skip-next" /> <i class="mdi mdi-24px mdi-skip-next" />
@@ -72,7 +72,7 @@
<button <button
class="btn btn-sm" class="btn btn-sm"
:title="`${t('word.filter')} (CTRL+F)`" :title="t('word.filter')"
:class="{'btn-primary': isSearch, 'btn-dark': !isSearch}" :class="{'btn-primary': isSearch, 'btn-dark': !isSearch}"
@click="isSearch = !isSearch" @click="isSearch = !isSearch"
> >
@@ -82,10 +82,10 @@
v-if="isTable" v-if="isTable"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="isQuering" :disabled="isQuering"
@click="showFakerModal" @click="showFakerModal()"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $tc('message.insertRow', 2) }}</span> <span>{{ t('message.insertRow', 2) }}</span>
</button> </button>
<div class="dropdown table-dropdown pr-2"> <div class="dropdown table-dropdown pr-2">
@@ -105,6 +105,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> </div>
@@ -153,14 +156,18 @@
:element-type="elementType" :element-type="elementType"
@update-field="updateField" @update-field="updateField"
@delete-selected="deleteSelected" @delete-selected="deleteSelected"
@duplicate-row="showFakerModal"
@hard-sort="hardSort" @hard-sort="hardSort"
/> />
</div> </div>
<ModalFakerRows <ModalFakerRows
v-if="isFakerModal" v-if="isFakerModal"
:fields="fields" :fields="fields"
:row-to-duplicate="rowToDuplicate"
:key-usage="keyUsage" :key-usage="keyUsage"
:tab-uid="tabUid" :tab-uid="tabUid"
:schema="schema"
:table="table"
@hide="hideFakerModal" @hide="hideFakerModal"
@reload="reloadTable" @reload="reloadTable"
/> />
@@ -182,6 +189,10 @@ import WorkspaceTabTableFilters from '@/components/WorkspaceTabTableFilters.vue'
import ModalFakerRows from '@/components/ModalFakerRows.vue'; import ModalFakerRows from '@/components/ModalFakerRows.vue';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { TableFilterClausole } from 'common/interfaces/tableApis'; import { TableFilterClausole } from 'common/interfaces/tableApis';
import { useFilters } from '@/composables/useFilters';
import { ipcRenderer } from 'electron';
const { localeString } = useFilters();
const { t } = useI18n(); const { t } = useI18n();
@@ -224,6 +235,7 @@ const filters = ref([]);
const page = ref(1); const page = ref(1);
const pageProxy = ref(1); const pageProxy = ref(1);
const approximateCount = ref(0); const approximateCount = ref(0);
const rowToDuplicate = ref(null);
const workspace = computed(() => { const workspace = computed(() => {
return getWorkspace(props.connection.uid); return getWorkspace(props.connection.uid);
@@ -234,7 +246,7 @@ const customizations = computed(() => {
}); });
const isTable = computed(() => { const isTable = computed(() => {
return !!workspace.value.breadcrumbs.table; return !workspace.value.breadcrumbs.view;
}); });
const fields = computed(() => { const fields = computed(() => {
@@ -329,30 +341,16 @@ const pageChange = (direction: 'prev' | 'next') => {
page.value--; page.value--;
}; };
const showFakerModal = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const showFakerModal = (row?: any) => {
if (isQuering.value) return; if (isQuering.value) return;
isFakerModal.value = true; isFakerModal.value = true;
rowToDuplicate.value = row;
}; };
const hideFakerModal = () => { const hideFakerModal = () => {
isFakerModal.value = false; isFakerModal.value = false;
}; rowToDuplicate.value = null;
const onKey = (e: KeyboardEvent) => {
if (props.isSelected) {
e.stopPropagation();
if (e.key === 'F5')
reloadTable();
if (e.ctrlKey || e.metaKey) {
if (e.key === 'ArrowRight')
pageChange('next');
if (e.key === 'ArrowLeft')
pageChange('prev');
if (e.key === 'f') // f
isSearch.value = !isSearch.value;
}
}
}; };
const setRefreshInterval = () => { const setRefreshInterval = () => {
@@ -367,7 +365,7 @@ const setRefreshInterval = () => {
} }
}; };
const downloadTable = (format: 'csv' | 'json') => { const downloadTable = (format: 'csv' | 'json' | 'sql') => {
queryTable.value.downloadTable(format, props.table); queryTable.value.downloadTable(format, props.table);
}; };
@@ -386,9 +384,28 @@ const updateFilters = (clausoles: TableFilterClausole[]) => {
getTableData(); getTableData();
}; };
const localeString = (val: number | null) => { const reloadListener = () => {
if (val !== null) const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
return val.toLocaleString(); if (props.isSelected && !hasModalOpen)
reloadTable();
};
const openFilterListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
isSearch.value = !isSearch.value;
};
const nextPageListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
pageChange('next');
};
const prevPageListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen)
pageChange('prev');
}; };
const hasApproximately = computed(() => { const hasApproximately = computed(() => {
@@ -412,6 +429,8 @@ watch(() => props.schema, () => {
watch(() => props.table, () => { watch(() => props.table, () => {
if (props.isSelected) { if (props.isSelected) {
page.value = 1; page.value = 1;
filters.value = [];
isSearch.value = false;
approximateCount.value = 0; approximateCount.value = 0;
sortParams.value = {} as { field: string; dir: 'asc' | 'desc'}; sortParams.value = {} as { field: string; dir: 'asc' | 'desc'};
getTableData(); getTableData();
@@ -445,10 +464,17 @@ watch(isSearch, (val) => {
}); });
getTableData(); getTableData();
window.addEventListener('keydown', onKey);
ipcRenderer.on('run-or-reload', reloadListener);
ipcRenderer.on('open-filter', openFilterListener);
ipcRenderer.on('next-page', nextPageListener);
ipcRenderer.on('prev-page', prevPageListener);
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
clearInterval(refreshInterval.value); clearInterval(refreshInterval.value);
ipcRenderer.removeListener('run-or-reload', reloadListener);
ipcRenderer.removeListener('open-filter', openFilterListener);
ipcRenderer.removeListener('next-page', nextPageListener);
ipcRenderer.removeListener('prev-page', prevPageListener);
}); });
</script> </script>

View File

@@ -51,7 +51,7 @@
class="btn btn-sm btn-primary mr-0 ml-2" class="btn btn-sm btn-primary mr-0 ml-2"
type="submit" type="submit"
> >
{{ $t('word.filter') }} {{ t('word.filter') }}
</button> </button>
<button <button
class="btn btn-sm btn-dark mr-0 ml-2" class="btn btn-sm btn-dark mr-0 ml-2"
@@ -71,6 +71,9 @@ import customizations from 'common/customizations';
import { NUMBER, FLOAT } from 'common/fieldTypes'; import { NUMBER, FLOAT } from 'common/fieldTypes';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { TableFilterClausole } from 'common/interfaces/tableApis'; import { TableFilterClausole } from 'common/interfaces/tableApis';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
fields: Array as Prop<TableField[]>, fields: Array as Prop<TableField[]>,
@@ -97,7 +100,9 @@ const removeRow = (i: number) => {
}; };
const doFilter = () => { const doFilter = () => {
const clausoles = rows.value.filter(el => el.active).map(el => createClausole(el)); const clausoles = rows.value
.filter(el => el.active)
.map(el => createClausole(el));
emit('filter', clausoles); emit('filter', clausoles);
}; };

View File

@@ -0,0 +1,51 @@
import * as moment from 'moment';
export function useFilters () {
const cutText = (string: string, length: number, escape?: boolean) => {
if (typeof string !== 'string') return string;
if (escape) string = string.replace(/\s{2,}/g, ' ');
return string.length > length ? `${string.substring(0, length)}...` : string;
};
const lastPart = (string: string, length: number) => {
if (!string) return '';
string = string.split(/[/\\]+/).pop();
if (string.length >= length)
string = `...${string.slice(-length)}`;
return string;
};
const formatDate = (date: Date) => moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
const localeString = (number: number | null) => {
if (number !== null)
return number.toLocaleString();
};
const wrapNumber = (num: number) => {
if (!num) return '';
return `(${num})`;
};
const parseKeys = (keys: {[key: number]: string}[]) => {
const isMacOS = process.platform === 'darwin';
return (keys as string[]).map(k => (
k.split('+')
.map(sk => (
`<code class="text-bold">${sk}</code>`
)))
.join('+')
.replaceAll('CommandOrControl', isMacOS ? 'Command' : 'Control')
).join(', ');
};
return {
cutText,
formatDate,
wrapNumber,
lastPart,
localeString,
parseKeys
};
}

View File

@@ -1,4 +1,4 @@
module.exports = { export const arSA = {
word: { word: {
edit: 'تعديل', edit: 'تعديل',
save: 'حفظ', save: 'حفظ',

View File

@@ -1,4 +1,4 @@
module.exports = { export const deDE = {
word: { word: {
edit: 'Bearbeiten', edit: 'Bearbeiten',
save: 'Speichern', save: 'Speichern',

View File

@@ -1,4 +1,4 @@
module.exports = { export const enUS = {
word: { word: {
edit: 'Edit', edit: 'Edit',
save: 'Save', save: 'Save',
@@ -142,7 +142,8 @@ module.exports = {
contributors: 'Contributors', contributors: 'Contributors',
pin: 'Pin', pin: 'Pin',
unpin: 'Unpin', unpin: 'Unpin',
console: 'Console' console: 'Console',
shortcuts: 'Shortcuts'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@@ -298,7 +299,30 @@ module.exports = {
allConnections: 'All connections', allConnections: 'All connections',
searchForConnections: 'Search for connections', searchForConnections: 'Search for connections',
disableScratchpad: 'Disable scratchpad', disableScratchpad: 'Disable scratchpad',
reportABug: 'Report a bug' reportABug: 'Report a bug',
nextTab: 'Next tab',
previousTab: 'Previous tab',
selectTabNumber: 'Select tab number {param}',
toggleConsole: 'Toggle console',
addShortcut: 'Add shortcut',
editShortcut: 'Edit shortcut',
deleteShortcut: 'Delete shortcut',
restoreDefaults: 'Restore defaults',
restoreDefaultsQuestion: 'Do you confirm to restore default values?',
registerAShortcut: 'Register a shortcut',
invalidShortcutMessage: 'Invalid combination, continue to type',
shortcutAlreadyExists: 'Shortcut already exists',
saveContent: 'Save content',
openAllConnections: 'Open all connections',
openSettings: 'Open settings',
openScratchpad: 'Open scratchpad',
runOrReload: 'Run or reload',
formatQuery: 'Format query',
queryHistory: 'Query history',
clearQuery: 'Clear query',
openFilter: 'Open filter',
nextResultsPage: 'Next results page',
previousResultsPage: 'Previous results page'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@@ -1,4 +1,4 @@
module.exports = { export const esES = {
word: { word: {
edit: 'Editar', edit: 'Editar',
save: 'Guardar', save: 'Guardar',

View File

@@ -1,4 +1,4 @@
module.exports = { export const frFR = {
word: { word: {
edit: 'Editer', edit: 'Editer',
save: 'Sauver', save: 'Sauver',

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