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

Compare commits

..

97 Commits

Author SHA1 Message Date
cf5247bf35 chore(release): 0.7.0 2022-11-30 13:22:26 +01:00
19db29663b refactor(UI): minor change 2022-11-30 13:09:07 +01:00
d010d5aa8f fix(UI): wrong copnnection icons color with light theme 2022-11-30 09:15:15 +01:00
0a1f50a9b9 fix: missing sidebar data after update 2022-11-29 18:18:56 +01:00
e7da5a7040 build(deps): update better-sqlite3 to 8.0.0 2022-11-29 15:19:23 +01:00
2d126d521c refactor: minor improvements 2022-11-29 14:25:30 +01:00
0fca70ebec fix(UI): folder to folder drag glitches 2022-11-29 13:19:41 +01:00
36358584fd Merge pull request #481 from antares-sql/new-sidebar
New connections sidebar
2022-11-28 15:19:24 +01:00
672896414e feat(UI): new settimgbar tooltips 2022-11-28 15:11:29 +01:00
b06bafe06c fix: deletion of connections inside folder 2022-11-28 13:06:01 +01:00
4fe9dfc4d7 feat(UI): footer color based on folder color 2022-11-28 11:56:02 +01:00
7af178a1e4 fix: wrong position moving elements outside folder 2022-11-28 09:42:18 +01:00
212b2bdba9 feat(UI): connections customization 2022-11-27 17:52:32 +01:00
72accb7b0e feat(UI): folders customization 2022-11-26 11:21:47 +01:00
321b387083 refactor(UI): various improvements on sidebar 2022-11-24 16:40:04 +01:00
ece6c6401d feat(UI): folders implementation 2022-11-23 17:52:08 +01:00
5a028a4ea2 refactor: changes to implement folders 2022-11-21 20:19:02 +01:00
83f9b12be0 chore(release): 0.6.0 2022-11-18 14:32:43 +01:00
1c1403f586 fix: incomplete list of collations, fixes #478 2022-11-18 11:30:10 +01:00
038cf68253 Merge pull request #472 from antares-sql/firebirdsql-support
Firebird SQL support
2022-11-17 16:19:59 +01:00
3580faebba chore: update README.md 2022-11-17 16:17:36 +01:00
ae312efbbc feat(Firebird SQL): procedure add/edit/delete support 2022-11-17 15:27:39 +01:00
8e422e3f07 feat(Firebird SQL): trigger add/edit/delete support 2022-11-16 15:16:12 +01:00
7d1967a609 feat(Firebird SQL): view add/edit/delete support 2022-11-16 12:00:12 +01:00
7ff8e2149e fix(Firebird SQL): connection pool issue 2022-11-16 10:12:44 +01:00
1b5cc315dd feat(Firebird SQL): table add/edit/delete support 2022-11-15 16:46:12 +01:00
27566c1dfa feat(Firebird SQL): manual commit mode 2022-11-10 15:52:31 +01:00
03777a2ea3 refactor(Firebird SQL): improve fields metadata detection 2022-11-09 17:41:31 +01:00
d91251d7cb build(deps): update better-sqlite to 7.6.2 2022-11-09 10:48:01 +01:00
0827a04d61 feat(Firebird SQL): support to blob fields 2022-11-08 15:53:21 +01:00
2c8509ff41 feat(Firebird SQL): support to indexes and foreign keys 2022-11-08 14:05:54 +01:00
76df6319c2 feat(Firebird SQL): connections pool 2022-11-07 09:49:36 +01:00
e6f6a022d1 feat: support to text blob fields 2022-11-05 10:22:12 +01:00
95bb41e9db feat(Firebird SQL): display table content and query results 2022-11-04 16:31:10 +01:00
7ab84bde57 initial firebird commit 2022-11-02 14:18:50 +01:00
d190a2dd61 fix: loss of precision updating BIGINT values, fixes #467 2022-10-26 12:26:09 +02:00
d8a298fd20 chore(release): 0.5.19 2022-10-22 14:54:15 +02:00
369622d5af Merge pull request #464 from antares-sql/context-cell-filler
Context menu option to fill table cells
2022-10-20 11:49:43 +02:00
a40d722d7c refactor: remove unnecessary console.log 2022-10-20 11:37:23 +02:00
440f74dfc1 fix: app stuck inserting a random value if field length high 2022-10-20 11:25:23 +02:00
8621ca5333 fix: unable to edit text fields if value is NULL, fixes #466 2022-10-20 10:55:30 +02:00
24edc82b1b feat: uuid fill for string cells 2022-10-19 10:40:56 +02:00
0a2124f2c2 feat: context menu option to fill cell with random values 2022-10-19 01:12:07 +02:00
a8521317a5 Merge branch 'master' of https://github.com/antares-sql/antares into context-cell-filler 2022-10-18 08:50:39 +02:00
88408da745 fix: error joining tables with different schema 2022-10-17 13:55:48 +02:00
d52b7af297 fix(SQLite): save boolean as integer to improve compativility, fixes #463 2022-10-17 12:06:22 +02:00
e4a4696dd3 chore(release): 0.5.18 2022-10-14 15:03:49 +02:00
f0255c0065 Merge pull request #455 from antares-sql/dependabot/npm_and_yarn/sql-formatter-11.0.2
build(deps): bump sql-formatter from 8.2.0 to 11.0.2
2022-10-11 10:36:00 +02:00
9991173685 Merge branch 'master' of https://github.com/antares-sql/antares into dependabot/npm_and_yarn/sql-formatter-11.0.2 2022-10-11 10:29:55 +02:00
dependabot[bot]
874dc6298b build(deps): bump sql-formatter from 8.2.0 to 11.0.2
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 8.2.0 to 11.0.2.
- [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/v8.2.0...v11.0.2)

---
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-10-11 08:12:16 +00:00
5b1b8cf4cc build: update dependencies 2022-10-11 10:11:11 +02:00
a521274d01 feat(PostgreSQL): UUID random generation option on UUID fields, closes #424 2022-10-10 18:08:28 +02:00
86e4c1d58f Merge branch 'master' of https://github.com/antares-sql/antares 2022-10-04 10:12:19 +02:00
ba5a1b68ab fix: trackpad horizontal scroll on tabs not working properly 2022-10-04 10:12:16 +02:00
2c45bce1ee Merge pull request #448 from SmileYzn/master
Update pt-BR.ts
2022-10-01 22:22:34 +02:00
Cleverson
2e05fa4bdc Update pt-BR.ts 2022-09-30 14:43:06 -03:00
dd9c089d27 fix: auto-scroll on sidebar not working, fixes #447 2022-09-29 09:27:29 +02:00
e6955550fa refactor: minor changes in table content context 2022-09-29 09:17:47 +02:00
fe39c1d388 chore(release): 0.5.17 2022-09-22 09:34:28 +02:00
d114f8a651 feat: added more editor font sizes, closes #440 2022-09-21 10:33:44 +02:00
84168d1d75 fix: editor font size doesn't change on new tabs, fixes #442 2022-09-15 19:03:18 +02:00
498a9b48e2 fix: empty definer when editing a view, fixes #437 2022-09-15 18:58:19 +02:00
01f607cd40 fix: "run or reload" shortcut triggers on all connections open 2022-09-08 12:01:57 +02:00
efe134a059 fix: cant run procedures with parameters from leftbar 2022-09-07 18:18:15 +02:00
40cb4dd98d chore: minor changes on macos artifacts action 2022-08-27 09:27:07 +02:00
a142d3c4d7 fix(MacOS): empty options on macos menubar 2022-08-27 09:04:11 +02:00
89da957a49 ci: github action for macos artifacts 2022-08-27 09:02:09 +02:00
05c2f9836c chore(release): 0.5.16 2022-08-26 18:49:51 +02:00
ebc325ae0c fix: issue updating datetime cells with null value, closes #423 2022-08-26 18:48:26 +02:00
39326eb52e fix: unable to set null or delete rows without primary key 2022-08-26 18:31:47 +02:00
df681147aa fix: ts exceptions 2022-08-24 10:23:03 +02:00
ffc645ba5e fix: CTRL+Right/Left not working on text editor, closes #427 2022-08-24 10:04:25 +02:00
1fb1205319 chore: update README.md 2022-08-20 09:18:46 +02:00
c90ab0e880 fix(UI): wrong position of fields resizable area 2022-08-19 16:54:56 +02:00
9dc700e13e fix(UI): editor themes group not visible in select element 2022-08-18 16:10:29 +02:00
6950d0ce5a chore(release): 0.5.15 2022-08-17 16:21:37 +02:00
4df14c3693 feat: dynamic shortcut suggestions on empty query tabs 2022-08-17 16:20:36 +02:00
c05be8304f perf(translation): updated italian translation 2022-08-17 15:29:12 +02:00
31c575dad9 Merge pull request #405 from antares-sql/custom-shortcuts
Shortcuts customization
2022-08-17 11:12:49 +02:00
040657d5ca Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-17 11:05:09 +02:00
5043fafa93 feat: added more events in shortcuts setting 2022-08-17 10:00:23 +02:00
8eb127e458 feat: ability to edit shortcuts 2022-08-16 18:03:38 +02:00
d044a02cb7 feat: ability to add new shortcuts 2022-08-16 13:10:20 +02:00
8cb2c197c8 chore: suppress some stylelint warns 2022-08-15 18:14:51 +02:00
c50d17e82b fix: startup exception 2022-08-15 18:13:53 +02:00
Cleverson
7c186d2dee Update pt-BR.ts 2022-08-12 14:59:22 +02:00
0f219cf9b7 perf: improved keypress detector 2022-08-12 12:40:35 +02:00
75c5a34095 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-11 11:22:25 +02:00
48877534d1 feat(UI): connection name on left bar, closes #382 #414 2022-08-11 11:14:43 +02:00
c22413fde9 feat: delete shortcuts and restore defaults 2022-08-10 17:59:59 +02:00
77ab8d8a03 build: minor change in ts config 2022-08-09 17:28:33 +02:00
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
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
da25823868 Merge branch 'master' of https://github.com/antares-sql/antares into custom-shortcuts 2022-08-05 17:08:48 +02:00
e29d86b409 refactor: shortcuts registration via ShortcutRegister class 2022-08-05 12:07:19 +02:00
114 changed files with 7140 additions and 4346 deletions

View File

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

View File

@@ -1,6 +1,7 @@
{ {
"extends": [ "extends": [
"stylelint-config-standard" "stylelint-config-standard",
"stylelint-config-recommended-vue"
], ],
"fix": true, "fix": true,
"formatter": "verbose", "formatter": "verbose",

2
.vscode/launch.json vendored
View File

@@ -5,7 +5,6 @@
"name": "Electron: Main", "name": "Electron: Main",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"port": 9222, "port": 9222,
"protocol": "inspector",
"request": "attach", "request": "attach",
"sourceMaps": true, "sourceMaps": true,
"type": "node", "type": "node",
@@ -23,7 +22,6 @@
"name": "Electron: Worker", "name": "Electron: Worker",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"port": 9224, "port": 9224,
"protocol": "inspector",
"request": "attach", "request": "attach",
"sourceMaps": true, "sourceMaps": true,
"type": "node", "type": "node",

View File

@@ -5,9 +5,12 @@
"MySQL", "MySQL",
"PostgreSQL", "PostgreSQL",
"SQLite", "SQLite",
"Firebird SQL",
"Windows", "Windows",
"translation", "translation",
"Linux" "Linux",
"MacOS",
"deps"
], ],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -2,6 +2,131 @@
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.7.0](https://github.com/antares-sql/antares/compare/v0.6.0...v0.7.0) (2022-11-30)
### Features
* **UI:** connections customization ([212b2bd](https://github.com/antares-sql/antares/commit/212b2bdba933db1af22967a057d64f41c96b9930))
* **UI:** folders customization ([72accb7](https://github.com/antares-sql/antares/commit/72accb7b0e62977dde2c82312aee5b9d025e5182))
* **UI:** folders implementation ([ece6c64](https://github.com/antares-sql/antares/commit/ece6c6401def4a9b42280f23efaa038b9ad98de8))
* **UI:** footer color based on folder color ([4fe9dfc](https://github.com/antares-sql/antares/commit/4fe9dfc4d7ca19a798b8a8a74d979ab88aebeaac))
* **UI:** new settimgbar tooltips ([6728964](https://github.com/antares-sql/antares/commit/672896414e901635c83ca037663e355a31ce013b))
### Bug Fixes
* deletion of connections inside folder ([b06bafe](https://github.com/antares-sql/antares/commit/b06bafe06c060233ebe901979b9fc4455a25cb89))
* missing sidebar data after update ([0a1f50a](https://github.com/antares-sql/antares/commit/0a1f50a9b92c9705784b93f14be40a01a78cb0da))
* **UI:** folder to folder drag glitches ([0fca70e](https://github.com/antares-sql/antares/commit/0fca70ebec1af3d594db4e1ce159e52e12224850))
* **UI:** wrong copnnection icons color with light theme ([d010d5a](https://github.com/antares-sql/antares/commit/d010d5aa8f07a5def1183567ee767fd144696ed3))
* wrong position moving elements outside folder ([7af178a](https://github.com/antares-sql/antares/commit/7af178a1e400876e6c45dbe31a198a12d29227a8))
## [0.6.0](https://github.com/antares-sql/antares/compare/v0.5.19...v0.6.0) (2022-11-18)
### Features
* **Firebird SQL:** connections pool ([76df631](https://github.com/antares-sql/antares/commit/76df6319c242ea42f93d4e5d811d96ec2c282aa8))
* **Firebird SQL:** display table content and query results ([95bb41e](https://github.com/antares-sql/antares/commit/95bb41e9db255a780aae1ae32ce4a53ee3bab20e))
* **Firebird SQL:** manual commit mode ([27566c1](https://github.com/antares-sql/antares/commit/27566c1dfae55f72d3f870c50410e5ecda256037))
* **Firebird SQL:** procedure add/edit/delete support ([ae312ef](https://github.com/antares-sql/antares/commit/ae312efbbc3a9941380477b9849bdd8edc5b9fbf))
* **Firebird SQL:** support to blob fields ([0827a04](https://github.com/antares-sql/antares/commit/0827a04d61af75b4366394e5f0289df461d02c98))
* **Firebird SQL:** support to indexes and foreign keys ([2c8509f](https://github.com/antares-sql/antares/commit/2c8509ff4173fbebeff92ab472a37edd3d80a2ac))
* **Firebird SQL:** table add/edit/delete support ([1b5cc31](https://github.com/antares-sql/antares/commit/1b5cc315dddca6b753fb6fe6e196e29441ffed79))
* **Firebird SQL:** trigger add/edit/delete support ([8e422e3](https://github.com/antares-sql/antares/commit/8e422e3f07323f388523621a05f0403a87f19e47))
* **Firebird SQL:** view add/edit/delete support ([7d1967a](https://github.com/antares-sql/antares/commit/7d1967a60977b2ce1095a37b7135f429a83f163d))
* support to text blob fields ([e6f6a02](https://github.com/antares-sql/antares/commit/e6f6a022d1a5bbc3f5303f635a2115813601c61a))
### Bug Fixes
* **Firebird SQL:** connection pool issue ([7ff8e21](https://github.com/antares-sql/antares/commit/7ff8e2149ef911a235b4a1dcc329775af1d2a72b))
* incomplete list of collations, fixes [#478](https://github.com/antares-sql/antares/issues/478) ([1c1403f](https://github.com/antares-sql/antares/commit/1c1403f58641f7b5f8a7c29fc430673ffa88f969))
* loss of precision updating BIGINT values, fixes [#467](https://github.com/antares-sql/antares/issues/467) ([d190a2d](https://github.com/antares-sql/antares/commit/d190a2dd61040d1748dfb97403f9d56015d938fe))
### [0.5.19](https://github.com/antares-sql/antares/compare/v0.5.18...v0.5.19) (2022-10-22)
### Features
* context menu option to fill cell with random values ([0a2124f](https://github.com/antares-sql/antares/commit/0a2124f2c2bdadc7c753db11d1e29f8acb9ddcac))
* uuid fill for string cells ([24edc82](https://github.com/antares-sql/antares/commit/24edc82b1be7299a09df18611b2a0ba361a6b46f))
### Bug Fixes
* app stuck inserting a random value if field length high ([440f74d](https://github.com/antares-sql/antares/commit/440f74dfc1f4942ba585b9bdae7517fe6ab04a81))
* error joining tables with different schema ([88408da](https://github.com/antares-sql/antares/commit/88408da745e45c70de977bc4270e5f61825be65f))
* **SQLite:** save boolean as integer to improve compativility, fixes [#463](https://github.com/antares-sql/antares/issues/463) ([d52b7af](https://github.com/antares-sql/antares/commit/d52b7af2978bc8beafd2d22078c72abb62e9e532))
* unable to edit text fields if value is NULL, fixes [#466](https://github.com/antares-sql/antares/issues/466) ([8621ca5](https://github.com/antares-sql/antares/commit/8621ca5333b5c51dc7a2ea1fcc0c5ec7f752a00a))
### [0.5.18](https://github.com/antares-sql/antares/compare/v0.5.17...v0.5.18) (2022-10-14)
### Features
* **PostgreSQL:** UUID random generation option on UUID fields, closes [#424](https://github.com/antares-sql/antares/issues/424) ([a521274](https://github.com/antares-sql/antares/commit/a521274d01031c1411bbbb136369802d43368089))
### Bug Fixes
* auto-scroll on sidebar not working, fixes [#447](https://github.com/antares-sql/antares/issues/447) ([dd9c089](https://github.com/antares-sql/antares/commit/dd9c089d27c61ab76d49887c7de2849cbb6e88a6))
* trackpad horizontal scroll on tabs not working properly ([ba5a1b6](https://github.com/antares-sql/antares/commit/ba5a1b68ab2d56777a5c94eede26e9bded5819e6))
### [0.5.17](https://github.com/antares-sql/antares/compare/v0.5.16...v0.5.17) (2022-09-22)
### Features
* added more editor font sizes, closes [#440](https://github.com/antares-sql/antares/issues/440) ([d114f8a](https://github.com/antares-sql/antares/commit/d114f8a65164f702b23175095e6fc2b021e0e038))
### Bug Fixes
* "run or reload" shortcut triggers on all connections open ([01f607c](https://github.com/antares-sql/antares/commit/01f607cd40c18ab0f9761b2a05705a966aaae43a))
* cant run procedures with parameters from leftbar ([efe134a](https://github.com/antares-sql/antares/commit/efe134a059700ca87333dc6e66166d6ec8d289e8))
* editor font size doesn't change on new tabs, fixes [#442](https://github.com/antares-sql/antares/issues/442) ([84168d1](https://github.com/antares-sql/antares/commit/84168d1d75460acc2c844bfece7d85f0c977e74c))
* empty definer when editing a view, fixes [#437](https://github.com/antares-sql/antares/issues/437) ([498a9b4](https://github.com/antares-sql/antares/commit/498a9b48e25ee061960f5f649c953cdaf6ff1a58))
* **MacOS:** empty options on macos menubar ([a142d3c](https://github.com/antares-sql/antares/commit/a142d3c4d77e31375dfbea148eec54ce1f635192))
### [0.5.16](https://github.com/antares-sql/antares/compare/v0.5.15...v0.5.16) (2022-08-26)
### Bug Fixes
* CTRL+Right/Left not working on text editor, closes [#427](https://github.com/antares-sql/antares/issues/427) ([ffc645b](https://github.com/antares-sql/antares/commit/ffc645ba5efb1c52670096e4f8c7f992b7335dae))
* issue updating datetime cells with null value, closes [#423](https://github.com/antares-sql/antares/issues/423) ([ebc325a](https://github.com/antares-sql/antares/commit/ebc325ae0c656dca2eb8f7544ab271beaee9b47e))
* ts exceptions ([df68114](https://github.com/antares-sql/antares/commit/df681147aaf0bfca69f3ffdc474cc1846541b1d8))
* **UI:** editor themes group not visible in select element ([9dc700e](https://github.com/antares-sql/antares/commit/9dc700e13ea65bb8c6feac4ff4ffeadd32053614))
* **UI:** wrong position of fields resizable area ([c90ab0e](https://github.com/antares-sql/antares/commit/c90ab0e8807ff30a9ab58e9aa3515cf427dd6e86))
* unable to set null or delete rows without primary key ([39326eb](https://github.com/antares-sql/antares/commit/39326eb52e038728b5419d4a8de8024c7ead3002))
### [0.5.15](https://github.com/antares-sql/antares/compare/v0.5.14...v0.5.15) (2022-08-17)
### Features
* ability to add new shortcuts ([d044a02](https://github.com/antares-sql/antares/commit/d044a02cb79a9d06aadc34cdbf6e81da84360559))
* ability to edit shortcuts ([8eb127e](https://github.com/antares-sql/antares/commit/8eb127e45838bc01ba12f0740fec077fcd975532))
* added more events in shortcuts setting ([5043faf](https://github.com/antares-sql/antares/commit/5043fafa934844ebc2f59cabcec830c6a4d5ca8e))
* delete shortcuts and restore defaults ([c22413f](https://github.com/antares-sql/antares/commit/c22413fde9dfe5501a5f220070cfe552a318c70b))
* dynamic shortcut suggestions on empty query tabs ([4df14c3](https://github.com/antares-sql/antares/commit/4df14c3693955bd7801b4b99103fca85f00f3e8c))
* list of available shortcuts in settings window ([44bb75b](https://github.com/antares-sql/antares/commit/44bb75bc60d7d31bbd99a9ba57f30fd354f7581c))
* **UI:** connection name on left bar, closes [#382](https://github.com/antares-sql/antares/issues/382) [#414](https://github.com/antares-sql/antares/issues/414) ([4887753](https://github.com/antares-sql/antares/commit/48877534d1a41d351b267c0dab925046ca984179))
* **UI:** shortcuts setting UI improved ([49b63bc](https://github.com/antares-sql/antares/commit/49b63bc6f28fc6031e6a892d0a48cd35ae2f26cd))
### Bug Fixes
* startup exception ([c50d17e](https://github.com/antares-sql/antares/commit/c50d17e82b7fd337d4037ddf646cd1a8fc765bae))
### Improvements
* improved keypress detector ([0f219cf](https://github.com/antares-sql/antares/commit/0f219cf9b796b4369c609fb0e8e3b84346a30b07))
* **translation:** updated italian translation ([c05be83](https://github.com/antares-sql/antares/commit/c05be8304f3cf299cf338f67c00184305e022919))
### [0.5.14](https://github.com/antares-sql/antares/compare/v0.5.13...v0.5.14) (2022-08-09) ### [0.5.14](https://github.com/antares-sql/antares/compare/v0.5.13...v0.5.14) (2022-08-09)

View File

@@ -12,7 +12,7 @@
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
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, SQLite and Firebird SQL.
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.
@@ -34,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.
@@ -83,8 +84,8 @@ This is a roadmap with major features will come in near future.
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL - [x] PostgreSQL
- [x] SQLite - [x] SQLite
- [ ] MSSQL - [x] Firebird SQL
- [ ] OracleDB - [ ] SQL Server
- [ ] More... - [ ] More...
### Operating Systems ### Operating Systems

5688
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.14", "version": "0.7.0",
"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",
@@ -125,28 +125,30 @@
"@turf/helpers": "~6.5.0", "@turf/helpers": "~6.5.0",
"@vueuse/core": "~8.7.5", "@vueuse/core": "~8.7.5",
"ace-builds": "~1.8.1", "ace-builds": "~1.8.1",
"better-sqlite3": "~7.5.1", "better-sqlite3": "~8.0.0",
"electron-log": "~4.4.1", "electron-log": "~4.4.1",
"electron-store": "~8.0.1", "electron-store": "~8.0.1",
"electron-updater": "~4.6.5", "electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3", "electron-window-state": "~5.0.3",
"encoding": "~0.1.13", "encoding": "~0.1.13",
"floating-vue": "~2.0.0-beta.20",
"leaflet": "~1.7.1", "leaflet": "~1.7.1",
"marked": "~4.0.0", "marked": "~4.0.19",
"moment": "~2.29.4", "moment": "~2.29.4",
"mysql2": "~2.3.2", "mysql2": "~2.3.2",
"node-firebird": "~1.1.3",
"pg": "~8.7.1", "pg": "~8.7.1",
"pg-connection-string": "~2.5.0", "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.23",
"source-map-support": "~0.5.20", "source-map-support": "~0.5.20",
"spectre.css": "~0.5.9", "spectre.css": "~0.5.9",
"sql-formatter": "~8.2.0", "sql-formatter": "~11.0.2",
"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.40",
"vue-i18n": "~9.2.0", "vue-i18n": "~9.2.2",
"vuedraggable": "~4.1.0" "vuedraggable": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -156,7 +158,7 @@
"@playwright/test": "~1.21.1", "@playwright/test": "~1.21.1",
"@types/better-sqlite3": "~7.5.0", "@types/better-sqlite3": "~7.5.0",
"@types/leaflet": "~1.7.9", "@types/leaflet": "~1.7.9",
"@types/marked": "~4.0.3", "@types/marked": "~4.0.7",
"@types/node": "~17.0.23", "@types/node": "~17.0.23",
"@types/pg": "~8.6.5", "@types/pg": "~8.6.5",
"@typescript-eslint/eslint-plugin": "~5.18.0", "@typescript-eslint/eslint-plugin": "~5.18.0",
@@ -167,7 +169,7 @@
"chalk": "~4.1.2", "chalk": "~4.1.2",
"cross-env": "~7.0.2", "cross-env": "~7.0.2",
"css-loader": "~6.5.0", "css-loader": "~6.5.0",
"electron": "~19.0.5", "electron": "~19.1.2",
"electron-builder": "~23.0.3", "electron-builder": "~23.0.3",
"eslint": "~7.32.0", "eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3", "eslint-config-standard": "~16.0.3",
@@ -181,15 +183,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 [
@@ -139,7 +140,7 @@ export default class {
{ name: 'arrayElement', group: 'random', types: ['string'] }, { name: 'arrayElement', group: 'random', types: ['string'] },
{ name: 'arrayElements', group: 'random', types: ['string'] }, { name: 'arrayElements', group: 'random', types: ['string'] },
{ name: 'objectElement', group: 'random', types: ['string'] }, { name: 'objectElement', group: 'random', types: ['string'] },
{ name: 'uuid', group: 'random', types: ['string'] }, { name: 'uuid', group: 'random', types: ['string', 'uuid'] },
{ name: 'boolean', group: 'random', types: ['string'] }, { name: 'boolean', group: 'random', types: ['string'] },
{ name: 'word', group: 'random', types: ['string'] }, { name: 'word', group: 'random', types: ['string'] },
{ name: 'words', group: 'random', types: ['string'] }, { name: 'words', group: 'random', types: ['string'] },
@@ -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

@@ -1,10 +1,14 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
// Everything OFF
export const defaults: Customizations = { export const defaults: Customizations = {
// Defaults // Defaults
defaultPort: null, defaultPort: null,
defaultUser: null, defaultUser: null,
defaultDatabase: null, defaultDatabase: null,
dataTypes: [],
indexTypes: [],
foreignActions: [],
// Core // Core
database: false, database: false,
collations: false, collations: false,
@@ -45,9 +49,9 @@ export const defaults: Customizations = {
exportByChunks: false, exportByChunks: false,
schemaImport: false, schemaImport: false,
tableSettings: false, tableSettings: false,
tableOptions: false,
tableArray: false, tableArray: false,
tableRealCount: false, tableRealCount: false,
tableDuplicate: false,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
triggerFunctionSettings: false, triggerFunctionSettings: false,
@@ -73,6 +77,7 @@ export const defaults: Customizations = {
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: null, procedureSql: null,
procedureContext: false, procedureContext: false,
procedureContextValues: [],
procedureLanguage: false, procedureLanguage: false,
functionDeterministic: false, functionDeterministic: false,
functionDataAccess: false, functionDataAccess: false,

View File

@@ -0,0 +1,63 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import firebirdTypes from '../data-types/firebird';
export const customizations: Customizations = {
...defaults,
// Defaults
defaultPort: 3050,
defaultUser: 'SYSDBA',
defaultDatabase: null,
dataTypes: firebirdTypes,
indexTypes: [
'PRIMARY',
// 'CHECK',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'NO ACTION',
'CASCADE',
'SET NULL',
'SET DEFAULT'
],
// Core
database: true,
collations: false,
engines: false,
connectionSchema: false,
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,
variables: false,
// Structure
schemas: false,
tables: true,
views: true,
triggers: true,
routines: true,
functions: false,
// Settings
elementsWrapper: '"',
stringsWrapper: '\'',
tableAdd: true,
tableSettings: true,
tableRealCount: true,
viewAdd: true,
viewSettings: true,
triggerAdd: true,
triggerMultipleEvents: true,
triggerSql: 'BEGIN\r\n\r\nEND',
routineAdd: true,
procedureContext: true,
procedureContextValues: ['IN', 'OUT'],
procedureSql: 'BEGIN\r\n\r\nEND',
parametersLength: true,
indexes: true,
foreigns: true,
nullable: true
};

View File

@@ -1,16 +1,19 @@
import * as mysql from 'common/customizations/mysql'; import * as mysql from 'common/customizations/mysql';
import * as postgresql from 'common/customizations/postgresql'; import * as postgresql from 'common/customizations/postgresql';
import * as sqlite from 'common/customizations/sqlite'; import * as sqlite from 'common/customizations/sqlite';
import * as firebird from 'common/customizations/firebird';
import { Customizations } from 'common/interfaces/customizations'; import { Customizations } from 'common/interfaces/customizations';
export default { export default {
maria: mysql.customizations, maria: mysql.customizations,
mysql: mysql.customizations, mysql: mysql.customizations,
pg: postgresql.customizations, pg: postgresql.customizations,
sqlite: sqlite.customizations sqlite: sqlite.customizations,
firebird: firebird.customizations
} as { } as {
maria: Customizations; maria: Customizations;
mysql: Customizations; mysql: Customizations;
pg: Customizations; pg: Customizations;
sqlite: Customizations; sqlite: Customizations;
firebird: Customizations;
}; };

View File

@@ -1,5 +1,6 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import mysqlTypes from '../data-types/mysql';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
@@ -7,6 +8,19 @@ export const customizations: Customizations = {
defaultPort: 3306, defaultPort: 3306,
defaultUser: 'root', defaultUser: 'root',
defaultDatabase: null, defaultDatabase: null,
dataTypes: mysqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
connectionSchema: true, connectionSchema: true,
collations: true, collations: true,
@@ -29,6 +43,7 @@ export const customizations: Customizations = {
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: true, tableAdd: true,
tableTruncateDisableFKCheck: true, tableTruncateDisableFKCheck: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
routineAdd: true, routineAdd: true,
@@ -51,7 +66,6 @@ export const customizations: Customizations = {
unsigned: true, unsigned: true,
nullable: true, nullable: true,
zerofill: true, zerofill: true,
tableOptions: true,
autoIncrement: true, autoIncrement: true,
comment: true, comment: true,
collation: true, collation: true,
@@ -64,6 +78,7 @@ export const customizations: Customizations = {
procedureDataAccess: true, procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND', procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
triggerSql: 'BEGIN\r\n\r\nEND', triggerSql: 'BEGIN\r\n\r\nEND',
functionDeterministic: true, functionDeterministic: true,
functionDataAccess: true, functionDataAccess: true,

View File

@@ -1,5 +1,6 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import postgresqlTypes from '../data-types/postgresql';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
@@ -7,6 +8,18 @@ export const customizations: Customizations = {
defaultPort: 5432, defaultPort: 5432,
defaultUser: 'postgres', defaultUser: 'postgres',
defaultDatabase: 'postgres', defaultDatabase: 'postgres',
dataTypes: postgresqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
database: true, database: true,
sslConnection: true, sslConnection: true,
@@ -26,6 +39,7 @@ export const customizations: Customizations = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
@@ -47,6 +61,7 @@ export const customizations: Customizations = {
tableArray: true, tableArray: true,
procedureSql: '$procedure$\r\n\r\n$procedure$', procedureSql: '$procedure$\r\n\r\n$procedure$',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
procedureLanguage: true, procedureLanguage: true,
functionSql: '$function$\r\n\r\n$function$', functionSql: '$function$\r\n\r\n$function$',
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$', triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',

View File

@@ -1,8 +1,21 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import sqliteTypes from '../data-types/sqlite';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
dataTypes: sqliteTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
fileConnection: true, fileConnection: true,
// Structure // Structure
@@ -14,6 +27,7 @@ export const customizations: Customizations = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
schemaEdit: false, schemaEdit: false,

View File

@@ -0,0 +1,136 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
{
group: 'integer',
types: [
{
name: 'SMALLINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'INTEGER',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'BIGINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
}
]
},
{
group: 'float',
types: [
{
name: 'DECIMAL',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'NUMERIC',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'FLOAT',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'DOUBLE PRECISION',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'string',
types: [
{
name: 'CHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'VARCHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'BLOB-TEXT',
length: false,
collation: true,
unsigned: false,
zerofill: false
}
]
},
{
group: 'binary',
types: [
{
name: 'BLOB',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'CHAR-BINARY',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'time',
types: [
{
name: 'DATE',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIME',
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIMESTAMP',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
}
] as TypesGroup[];

View File

@@ -10,7 +10,8 @@ export const LONG_TEXT = [
'MEDIUMTEXT', 'MEDIUMTEXT',
'LONGTEXT', 'LONGTEXT',
'JSON', 'JSON',
'VARBINARY' 'VARBINARY',
'BLOB-TEXT'
]; ];
export const ARRAY = [ export const ARRAY = [
@@ -29,14 +30,14 @@ export const NUMBER = [
'SMALLINT', 'SMALLINT',
'MEDIUMINT', 'MEDIUMINT',
'BIGINT', 'BIGINT',
'DECIMAL',
'NUMERIC', 'NUMERIC',
'INTEGER', 'INTEGER',
'SMALLSERIAL', 'SMALLSERIAL',
'SERIAL', 'SERIAL',
'BIGSERIAL', 'BIGSERIAL',
'OID', 'OID',
'XID' 'XID',
'INT64'
]; ];
export const FLOAT = [ export const FLOAT = [
@@ -48,6 +49,12 @@ export const FLOAT = [
'MONEY' 'MONEY'
]; ];
export const IS_BIGINT = [
'BIGINT',
'BIGSERIAL',
'DOUBLE PRECISION'
];
export const BOOLEAN = [ export const BOOLEAN = [
'BOOL', 'BOOL',
'BOOLEAN' 'BOOLEAN'
@@ -78,7 +85,9 @@ export const BLOB = [
'TINYBLOB', 'TINYBLOB',
'MEDIUMBLOB', 'MEDIUMBLOB',
'LONGBLOB', 'LONGBLOB',
'BYTEA' 'LONG_BLOB',
'BYTEA',
'CHAR-BINARY'
]; ];
export const BIT = [ export const BIT = [
@@ -86,6 +95,14 @@ export const BIT = [
'BIT VARYING' 'BIT VARYING'
]; ];
export const BINARY = [
'BINARY'
];
export const UUID = [
'UUID'
];
export const SPATIAL = [ export const SPATIAL = [
'POINT', 'POINT',
'LINESTRING', 'LINESTRING',

View File

@@ -1,6 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
];

View File

@@ -1,5 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -1,5 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -8,9 +8,10 @@ import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { MySQLClient } from '../../main/libs/clients/MySQLClient'; import { MySQLClient } from '../../main/libs/clients/MySQLClient';
import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient';
import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; import { SQLiteClient } from '../../main/libs/clients/SQLiteClient';
import { FirebirdSQLClient } from 'src/main/libs/clients/FirebirdSQLClient';
export type Client = MySQLClient | PostgreSQLClient | SQLiteClient export type Client = MySQLClient | PostgreSQLClient | SQLiteClient | FirebirdSQLClient
export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' | 'firebird'
export type Exporter = MysqlExporter | PostgreSQLExporter export type Exporter = MysqlExporter | PostgreSQLExporter
export type Importer = MySQLImporter | PostgreSQLImporter export type Importer = MySQLImporter | PostgreSQLImporter

View File

@@ -1,8 +1,13 @@
import { TypesGroup } from './antares';
export interface Customizations { export interface Customizations {
// Defaults // Defaults
defaultPort?: number; defaultPort?: number;
defaultUser?: string; defaultUser?: string;
defaultDatabase?: string; defaultDatabase?: string;
dataTypes?: TypesGroup[];
indexTypes?: string[];
foreignActions?: string[];
// Core // Core
database?: boolean; database?: boolean;
collations?: boolean; collations?: boolean;
@@ -30,7 +35,7 @@ export interface Customizations {
stringsWrapper: string; stringsWrapper: string;
tableAdd?: boolean; tableAdd?: boolean;
tableSettings?: boolean; tableSettings?: boolean;
tableOptions?: boolean; tableDuplicate?: boolean;
tableArray?: boolean; tableArray?: boolean;
tableRealCount?: boolean; tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean; tableTruncateDisableFKCheck?: boolean;
@@ -71,6 +76,7 @@ export interface Customizations {
procedureDataAccess?: boolean; procedureDataAccess?: boolean;
procedureSql?: string; procedureSql?: string;
procedureContext?: boolean; procedureContext?: boolean;
procedureContextValues?: string[];
procedureLanguage?: boolean; procedureLanguage?: boolean;
functionDeterministic?: boolean; functionDeterministic?: boolean;
functionDataAccess?: boolean; functionDataAccess?: boolean;

View File

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

View File

@@ -176,7 +176,7 @@ function isMD (str: string) {
} }
export function langDetector (str: string) { export function langDetector (str: string) {
if (!str.trim().length) if (!str || !str.trim().length)
return 'text'; return 'text';
if (isJSON(str)) if (isJSON(str))
return 'json'; return 'json';

View File

@@ -1,3 +1,3 @@
export function uidGen (prefix?: string) { export function uidGen (prefix?: string) {
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase(); return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substring(2, 11).toUpperCase();
} }

View File

@@ -1,67 +1,136 @@
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[]; 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'] os: ['darwin', 'linux', 'win32']
}, },
{ {
event: 'close-tab', event: 'close-tab',
keys: ['CommandOrControl+W'], keys: ['CommandOrControl+W'],
description: 'Close tab',
os: ['darwin', 'linux', 'win32'] 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'] 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'] os: ['darwin', 'win32']
}, },
{ {
event: 'next-tab', event: 'next-tab',
keys: ['CommandOrControl+PageDown'], keys: ['CommandOrControl+PageDown'],
description: 'Next tab', os: ['linux', 'win32']
os: ['linux']
}, },
{ {
event: 'prev-tab', event: 'prev-tab',
keys: ['CommandOrControl+PageUp'], keys: ['CommandOrControl+PageUp'],
description: 'Previous tab', os: ['linux', 'win32']
os: ['linux']
}, },
{ {
event: 'open-connections-modal', 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'] 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'] 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',
l18nParam: i
};
shortcuts.push({
event: `select-tab-${i}`, event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`], keys: [`CommandOrControl+${i}`],
description: `Select tab number ${i}`,
os: ['darwin', 'linux', 'win32'] os: ['darwin', 'linux', 'win32']
}); });
} }

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

@@ -61,7 +61,11 @@ export default (connections: {[key: string]: antares.Client}) => {
}); });
await connection.connect(); await connection.connect();
if (conn.client === 'firebird')
connection.raw('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') FROM rdb$database');
else
await connection.select('1+1').run(); await connection.select('1+1').run();
connection.destroy(); connection.destroy();
return { status: 'success' }; return { status: 'success' };

View File

@@ -97,7 +97,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-engines', async (event, uid) => { ipcMain.handle('get-engines', async (event, uid) => {
try { try {
const result = await connections[uid].getEngines(); const result: unknown = await connections[uid].getEngines();
return { status: 'success', response: result }; return { status: 'success', response: result };
} }

View File

@@ -5,7 +5,7 @@ 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/sqlUtils'; 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, BOOLEAN } from 'common/fieldTypes';
import customizations from 'common/customizations'; import customizations from 'common/customizations';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: {[key: string]: antares.Client}) => {
@@ -105,6 +105,7 @@ export default (connections: {[key: string]: antares.Client}) => {
break; break;
case 'pg': case 'pg':
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@@ -124,6 +125,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `0x${fileBlob.toString('hex')}`; escapedParam = `0x${fileBlob.toString('hex')}`;
break; break;
case 'pg': case 'pg':
case 'firebird':
fileBlob = fs.readFileSync(params.content); fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`; escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break; break;
@@ -141,6 +143,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = '\'\''; escapedParam = '\'\'';
break; break;
case 'pg': case 'pg':
case 'firebird':
escapedParam = 'decode(\'\', \'hex\')'; escapedParam = 'decode(\'\', \'hex\')';
break; break;
case 'sqlite': case 'sqlite':
@@ -153,6 +156,19 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `b'${sqlEscaper(params.content)}'`; escapedParam = `b'${sqlEscaper(params.content)}'`;
reload = true; reload = true;
} }
else if (BOOLEAN.includes(params.type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
case 'pg':
case 'firebird':
escapedParam = params.content;
break;
case 'sqlite':
escapedParam = Number(params.content === 'true');
break;
}
}
else if (params.content === null) else if (params.content === null)
escapedParam = 'NULL'; escapedParam = 'NULL';
else else
@@ -177,6 +193,9 @@ 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]}'`;
if (orgRow[key] === null)
orgRow[key] = `IS ${orgRow[key]}`;
else
orgRow[key] = `= ${orgRow[key]}`; orgRow[key] = `= ${orgRow[key]}`;
} }
@@ -208,10 +227,11 @@ export default (connections: {[key: string]: antares.Client}) => {
}).join(','); }).join(',');
try { try {
const result = await connections[params.uid] const result: unknown = await connections[params.uid]
.schema(params.schema) .schema(params.schema)
.delete(params.table) .delete(params.table)
.where({ [params.primary]: `IN (${idString})` }) .where({ [params.primary]: `IN (${idString})` })
.limit(params.rows.length)
.run(); .run();
return { status: 'success', response: result }; return { status: 'success', response: result };
@@ -270,6 +290,7 @@ export default (connections: {[key: string]: antares.Client}) => {
break; break;
case 'pg': case 'pg':
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@@ -367,7 +388,20 @@ export default (connections: {[key: string]: antares.Client}) => {
if (description) if (description)
query.select(`LEFT(${description}, 20) AS foreign_description`); query.select(`LEFT(${description}, 20) AS foreign_description`);
const results = await query.run(); const results = await query.run<{[key: string]: string}>();
const parsedResults: {[key: string]: string}[] = [];
for (const row of results.rows) {
const remappedRow: {[key: string]: string} = {};
for (const key in row)
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
parsedResults.push(remappedRow);
}
results.rows = parsedResults;
return { status: 'success', response: results }; return { status: 'success', response: results };
} }

View File

@@ -16,7 +16,7 @@ const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
/** /**
* As Simple As Possible Query Builder Core * As Simple As Possible Query Builder Core
*/ */
export class AntaresCore { export abstract class AntaresCore {
_client: antares.ClientCode; _client: antares.ClientCode;
protected _cUid: string protected _cUid: string
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};

View File

@@ -2,6 +2,7 @@ import * as antares from 'common/interfaces/antares';
import { MySQLClient } from './clients/MySQLClient'; import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient'; import { PostgreSQLClient } from './clients/PostgreSQLClient';
import { SQLiteClient } from './clients/SQLiteClient'; import { SQLiteClient } from './clients/SQLiteClient';
import { FirebirdSQLClient } from './clients/FirebirdSQLClient';
export class ClientsFactory { export class ClientsFactory {
static getClient (args: antares.ClientParams) { static getClient (args: antares.ClientParams) {
@@ -13,6 +14,8 @@ export class ClientsFactory {
return new PostgreSQLClient(args); return new PostgreSQLClient(args);
case 'sqlite': case 'sqlite':
return new SQLiteClient(args); return new SQLiteClient(args);
case 'firebird':
return new FirebirdSQLClient(args);
default: default:
throw new Error(`Unknown database client: ${args.client}`); throw new Error(`Unknown database client: ${args.client}`);
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -469,7 +469,12 @@ export class MySQLClient extends AntaresCore {
.orderBy({ ORDINAL_POSITION: 'ASC' }) .orderBy({ ORDINAL_POSITION: 'ASC' })
.run<TableColumnsResult>(); .run<TableColumnsResult>();
const { rows: fields } = await this.raw<antares.QueryResult<CreateTableResult>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``); let fields: CreateTableResult[] = [];
try {
const { rows } = await this.raw<antares.QueryResult<CreateTableResult>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
fields = rows;
}
catch (_) {}
const remappedFields = fields.map(row => { const remappedFields = fields.map(row => {
if (!row['Create Table']) return false; if (!row['Create Table']) return false;
@@ -940,7 +945,7 @@ export class MySQLClient extends AntaresCore {
return { return {
algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
definer: viewInfo[0].DEFINER, definer: viewInfo[0].DEFINER.split('@').map((str: string) => `\`${str}\``).join('@'),
security: viewInfo[0].SECURITY_TYPE, security: viewInfo[0].SECURITY_TYPE,
updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION, updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION,
sql: viewInfo[0].VIEW_DEFINITION, sql: viewInfo[0].VIEW_DEFINITION,
@@ -1588,7 +1593,8 @@ export class MySQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
connection.query({ sql: query, nestTables }).then(async ([response, fields]) => { connection.query({ sql: query, nestTables }).then(async ([response, fields]) => {
timeStop = new Date(); timeStop = new Date();
const queryResult = response; const queryResult = response;

View File

@@ -540,11 +540,7 @@ export class PostgreSQLClient extends AntaresCore {
return { return {
name: row.constraint_name, name: row.constraint_name,
column: row.column_name, column: row.column_name,
indexType: null as null, type: row.constraint_type
type: row.constraint_type,
cardinality: null as null,
comment: '',
indexComment: ''
}; };
}); });
} }
@@ -1426,7 +1422,8 @@ export class PostgreSQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
try { try {
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query }); const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });

View File

@@ -217,11 +217,7 @@ export class SQLiteClient extends AntaresCore {
remappedIndexes.push({ remappedIndexes.push({
name: 'PRIMARY', name: 'PRIMARY',
column: key.name, column: key.name,
indexType: null as never, type: 'PRIMARY'
type: 'PRIMARY',
cardinality: null as never,
comment: '',
indexComment: ''
}); });
} }
@@ -628,7 +624,8 @@ export class SQLiteClient extends AntaresCore {
let timeStop; let timeStop;
const keysArr: antares.QueryForeign[] = []; const keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
let queryRunResult: sqlite.RunResult; let queryRunResult: sqlite.RunResult;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -1,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',
@@ -124,8 +123,8 @@ else {
if (isWindows) if (isWindows)
mainWindow.show(); mainWindow.show();
if (isDevelopment) // if (isDevelopment)
mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
@@ -143,40 +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) {
if (shortcut.os.includes(process.platform)) {
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: [
@@ -207,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

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

@@ -68,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']);
@@ -90,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

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

View File

@@ -7,7 +7,7 @@
: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;'"
@change="onChange" @change="onChange"
/> />
@@ -87,7 +87,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, PropType, Ref, ref, watch } from 'vue'; import { computed, PropType, Ref, ref, watch } from 'vue';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT, UUID, IS_BIGINT } from 'common/fieldTypes';
import BaseUploadInput from '@/components/BaseUploadInput.vue'; 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';
@@ -126,6 +126,8 @@ const fakerGroups = computed(() => {
localType.value = 'datetime'; localType.value = 'datetime';
else if (TIME.includes(props.type)) else if (TIME.includes(props.type))
localType.value = 'time'; localType.value = 'time';
else if (UUID.includes(props.type))
localType.value = 'uuid';
else else
localType.value = 'none'; localType.value = 'none';
@@ -144,8 +146,12 @@ const inputProps = () => {
if ([...TEXT, ...LONG_TEXT].includes(props.type)) if ([...TEXT, ...LONG_TEXT].includes(props.type))
return { type: 'text', mask: false }; return { type: 'text', mask: false };
if ([...NUMBER, ...FLOAT].includes(props.type)) if ([...NUMBER, ...FLOAT].includes(props.type)) {
if (IS_BIGINT.includes(props.type))
return { type: 'text', mask: false };
else
return { type: 'number', mask: false }; return { type: 'number', mask: false };
}
if (TIME.includes(props.type)) { if (TIME.includes(props.type)) {
let timeMask = '##:##:##'; let timeMask = '##:##:##';

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

@@ -55,20 +55,7 @@
<div class="panel-subtitle"> <div class="panel-subtitle">
{{ clients.get(connection.client) || connection.client }} {{ clients.get(connection.client) || connection.client }}
</div> </div>
<div class="all-connections-buttons p-absolute d-flex" style="top: 0; right: 0;"> <div class="all-connections-buttons p-absolute d-flex" :style="'top: 0; right: 0;'">
<i
v-if="connection.isPinned"
class="all-connections-pinned mdi mdi-18px"
:class="connectionHover === connection.uid ? 'mdi-pin-off' : 'mdi-pin'"
:title="t('word.unpin')"
@click.stop="unpinConnection(connection.uid)"
/>
<i
v-else
class="all-connections-pin mdi mdi-18px mdi-pin mdi-rotate-45"
:title="t('word.pin')"
@click.stop="pinConnection(connection.uid)"
/>
<i <i
class="all-connections-delete mdi mdi-delete mdi-18px ml-2" class="all-connections-delete mdi mdi-delete mdi-18px ml-2"
:title="t('word.delete')" :title="t('word.delete')"
@@ -126,7 +113,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 -->
@@ -171,15 +158,12 @@ const connectionsStore = useConnectionsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { connections, const { connections,
pinnedConnections,
lastConnections lastConnections
} = storeToRefs(connectionsStore); } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { const {
getConnectionName, getConnectionName,
pinConnection,
unpinConnection,
deleteConnection deleteConnection
} = connectionsStore; } = connectionsStore;
const { selectWorkspace } = workspacesStore; const { selectWorkspace } = workspacesStore;
@@ -206,13 +190,10 @@ const sortedConnections = computed(() => {
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0; const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
return { return {
...c, ...c,
time: connTime, time: connTime
isPinned: pinnedConnections.value.has(c.uid)
}; };
}) })
.sort((a, b) => { .sort((a, b) => {
if (a.isPinned < b.isPinned) return 1;
if (a.isPinned > b.isPinned) return -1;
if (a.time < b.time) return 1; if (a.time < b.time) return 1;
if (a.time > b.time) return -1; if (a.time > b.time) return -1;
return 0; return 0;

View File

@@ -0,0 +1,195 @@
<template>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div ref="trapRef" class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-brush-variant mr-1" />
<span class="cut-text">{{ t('message.editConnectionAppearence') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group mb-4">
<div class="col-3">
<label class="form-label">{{ t('word.label') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="localConnection.name"
class="form-input"
type="text"
:placeholder="getConnectionName(localConnection.uid)"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.icon') }}</label>
</div>
<div class="col-9 icons-wrapper">
<div
v-for="icon in icons"
:key="icon.name"
class="icon-box"
:title="icon.name"
:class="[icon.code ? `mdi ${icon.code} mdi-36px` : `dbi dbi-${connection.client}`, {'selected': localConnection.icon === icon.code}]"
@click="localConnection.icon = icon.code"
/>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearence">
{{ t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
import { useFocusTrap } from '@/composables/useFocusTrap';
import { useI18n } from 'vue-i18n';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { unproxify } from '@/libs/unproxify';
const connectionsStore = useConnectionsStore();
const { t } = useI18n();
const props = defineProps({
connection: {
type: Object as PropType<SidebarElement>,
required: true
}
});
const emit = defineEmits(['close']);
const { updateConnectionOrder, getConnectionName } = connectionsStore;
const icons = [
{ name: 'default', code: null },
// Symbols
{ name: 'account-group', code: 'mdi-account-group-outline' },
{ name: 'cloud', code: 'mdi-cloud-outline' },
{ name: 'key-chain', code: 'mdi-key-chain-variant' },
{ name: 'lightning-bolt', code: 'mdi-lightning-bolt' },
{ name: 'map-marker', code: 'mdi-map-marker-radius-outline' },
{ name: 'api', code: 'mdi-api' },
{ name: 'chart-line', code: 'mdi-chart-line' },
{ name: 'chat', code: 'mdi-chat-outline' },
{ name: 'bug', code: 'mdi-bug-outline' },
{ name: 'shield', code: 'mdi-shield-outline' },
{ name: 'cart', code: 'mdi-cart-variant' },
{ name: 'bank', code: 'mdi-bank-outline' },
{ name: 'receipt', code: 'mdi-receipt-text-outline' },
{ name: 'raspberry-pi', code: 'mdi-raspberry-pi' },
{ name: 'book', code: 'mdi-book-outline' },
{ name: 'web', code: 'mdi-web' },
{ name: 'multimedia', code: 'mdi-multimedia' },
{ name: 'qr-code', code: 'mdi-qrcode' },
{ name: 'flask', code: 'mdi-flask-outline' },
{ name: 'memory', code: 'mdi-memory' },
{ name: 'cube', code: 'mdi-cube-outline' },
{ name: 'weather', code: 'mdi-weather-partly-snowy-rainy' },
{ name: 'controller', code: 'mdi-controller' },
{ name: 'home-group', code: 'mdi-home-group' },
// Vehicles
{ name: 'truck', code: 'mdi-truck-outline' },
{ name: 'car', code: 'mdi-car' },
{ name: 'motorbike', code: 'mdi-atv' },
{ name: 'train', code: 'mdi-train' },
{ name: 'airplane', code: 'mdi-airplane' },
{ name: 'ferry', code: 'mdi-ferry' },
// Brand
{ name: 'docker', code: 'mdi-docker' },
{ name: 'open-source', code: 'mdi-open-source-initiative' },
{ name: 'aws', code: 'mdi-aws' },
{ name: 'google-cloud', code: 'mdi-google-cloud' },
{ name: 'microsoft-azure', code: 'mdi-microsoft-azure' },
{ name: 'linux', code: 'mdi-linux' },
{ name: 'microsoft-windows', code: 'mdi-microsoft-windows' },
{ name: 'apple', code: 'mdi-apple' },
{ name: 'android', code: 'mdi-android' }
];
const { trapRef } = useFocusTrap();
const firstInput: Ref<HTMLInputElement> = ref(null);
const localConnection: Ref<SidebarElement> = ref(unproxify(props.connection));
const editFolderAppearence = () => {
updateConnectionOrder(localConnection.value);
closeModal();
};
const closeModal = () => emit('close');
const onKey =(e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style scoped lang="scss">
.modal-container {
max-width: 360px;
}
.icons-wrapper{
display: grid;
grid-template-columns: repeat(auto-fill, 40px);
gap: 5px;
.icon-box {
height: 40px;
width: 40px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.selected {
outline: 2px solid $primary-color;
border-radius: 8px;
}
}
}
.theme-light {
.icons-wrapper {
.dbi {
filter: invert(100%) opacity(.8);
&.selected {
outline-color: #1c96d6;
}
}
}
}
</style>

View File

@@ -40,6 +40,7 @@
v-model="database.collation" v-model="database.collation"
class="form-select" class="form-select"
:options="collations" :options="collations"
:max-visible-options="1000"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
/> />
@@ -163,7 +164,7 @@ onBeforeUnmount(() => {
</script> </script>
<style scoped> <style scoped lang="scss">
.modal-container { .modal-container {
max-width: 360px; max-width: 360px;
} }

View File

@@ -0,0 +1,152 @@
<template>
<Teleport to="#window-content">
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div ref="trapRef" class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-folder-edit mr-1" />
<span class="cut-text">{{ t('message.editFolder') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group mb-4">
<div class="col-3">
<label class="form-label">{{ t('word.name') }}</label>
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="localFolder.name"
class="form-input"
type="text"
required
:placeholder="t('message.folderName')"
>
</div>
</div>
<div class="form-group">
<div class="col-3">
<label class="form-label">{{ t('word.color') }}</label>
</div>
<div class="col-9 color-wrapper">
<div
v-for="color in colorPalette"
:key="color.name"
class="color-box"
:title="color.name"
:style="`background-color: ${color.hex}`"
@click="localFolder.color = color.hex"
>
<i v-if="localFolder.color === color.hex" class="mdi mdi-check" />
</div>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="editFolderAppearence">
{{ t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ t('word.close') }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
import { useFocusTrap } from '@/composables/useFocusTrap';
import { useI18n } from 'vue-i18n';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { unproxify } from '@/libs/unproxify';
const connectionsStore = useConnectionsStore();
const { t } = useI18n();
const props = defineProps({
folder: {
type: Object as PropType<SidebarElement>,
required: true
}
});
const emit = defineEmits(['close']);
const { updateConnectionOrder } = connectionsStore;
const colorPalette = [
{ name: 'default', hex: '#E36929' },
{ name: 'grape-fruit', hex: '#ED5565' },
{ name: 'rose', hex: '#E3242B' },
{ name: 'fire', hex: '#FDA50F' },
{ name: 'sunflower', hex: '#FFCE54' },
{ name: 'moss', hex: '#8A985E' },
{ name: 'grass', hex: '#6DCD05' },
{ name: 'emerald', hex: '#038835' },
{ name: 'mint', hex: '#48CFAD' },
{ name: 'aqua', hex: '#4FC1E9' },
{ name: 'royal-lblue', hex: '#4169E1' },
{ name: 'blue-jeans', hex: '#5D9CEC' },
{ name: 'stone', hex: '#59788E' },
{ name: 'lavander', hex: '#AC92EC' },
{ name: 'pink-rose', hex: '#EC87C0' },
{ name: 'smoke', hex: '#BEBDB8' },
{ name: 'slate', hex: '#757C88' }
];
const { trapRef } = useFocusTrap();
const firstInput: Ref<HTMLInputElement> = ref(null);
const localFolder: Ref<SidebarElement> = ref(unproxify(props.folder));
const editFolderAppearence = () => {
updateConnectionOrder(localFolder.value);
closeModal();
};
const closeModal = () => emit('close');
const onKey =(e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
closeModal();
};
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style scoped lang="scss">
.modal-container {
max-width: 360px;
}
.color-wrapper{
display: grid;
grid-template-columns: repeat(auto-fill, 20px);
gap: 5px;
.color-box {
height: 20px;
width: 20px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
</style>

View File

@@ -39,6 +39,7 @@
v-model="database.collation" v-model="database.collation"
class="form-select" class="form-select"
:options="collations" :options="collations"
:max-visible-options="1000"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
/> />
@@ -142,7 +143,7 @@ onBeforeUnmount(() => {
}); });
</script> </script>
<style scoped> <style scoped lang="scss">
.modal-container { .modal-container {
max-width: 360px; max-width: 360px;
} }

View File

@@ -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" />
@@ -135,6 +135,7 @@
<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 { exportRows } from '../libs/exportRows'; import { exportRows } from '../libs/exportRows';
import { useNotificationsStore } from '@/stores/notifications'; import { useNotificationsStore } from '@/stores/notifications';
@@ -326,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(() => {
@@ -348,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

@@ -30,6 +30,13 @@
> >
<a class="tab-link">{{ t('word.themes') }}</a> <a class="tab-link">{{ t('word.themes') }}</a>
</li> </li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'shortcuts'}"
@click="selectTab('shortcuts')"
>
<a class="tab-link">{{ t('word.shortcuts') }}</a>
</li>
<li <li
v-if="updateStatus !== 'disabled'" v-if="updateStatus !== 'disabled'"
class="tab-item c-hand" class="tab-item c-hand"
@@ -232,7 +239,7 @@
<div class="column col-12 h6 text-uppercase mb-2 mt-4"> <div class="column col-12 h6 text-uppercase mb-2 mt-4">
{{ t('message.editorTheme') }} {{ t('message.editorTheme') }}
</div> </div>
<div class="column col-6 h5 mb-4"> <div class="column col-5 h5 mb-4">
<BaseSelect <BaseSelect
v-model="localEditorTheme" v-model="localEditorTheme"
class="form-select" class="form-select"
@@ -244,28 +251,49 @@
@change="changeEditorTheme(localEditorTheme)" @change="changeEditorTheme(localEditorTheme)"
/> />
</div> </div>
<div class="column col-6 mb-4"> <div class="column col-7 mb-4">
<div class="btn-group btn-group-block"> <div class="btn-group btn-group-block">
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xsmall'}"
@click="changeEditorFontSize('xsmall')"
>
10px
</button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'small'}" :class="{'active': editorFontSize === 'small'}"
@click="changeEditorFontSize('small')" @click="changeEditorFontSize('small')"
> >
{{ t('word.small') }} 12px
</button> </button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'medium'}" :class="{'active': editorFontSize === 'medium'}"
@click="changeEditorFontSize('medium')" @click="changeEditorFontSize('medium')"
> >
{{ t('word.medium') }} 14px
</button> </button>
<button <button
class="btn btn-dark cut-text" class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'large'}" :class="{'active': editorFontSize === 'large'}"
@click="changeEditorFontSize('large')" @click="changeEditorFontSize('large')"
> >
{{ t('word.large') }} 16px
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xlarge'}"
@click="changeEditorFontSize('xlarge')"
>
18px
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'xxlarge'}"
@click="changeEditorFontSize('xxlarge')"
>
20px
</button> </button>
</div> </div>
</div> </div>
@@ -282,6 +310,9 @@
</div> </div>
</div> </div>
<div v-show="selectedTab === 'shortcuts'" class="panel-body py-4">
<ModalSettingsShortcuts />
</div>
<div v-show="selectedTab === 'update'" class="panel-body py-4"> <div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate /> <ModalSettingsUpdate />
</div> </div>
@@ -326,6 +357,7 @@ 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 { AvailableLocale } from '@/i18n'; import { AvailableLocale } from '@/i18n';
@@ -384,7 +416,29 @@ const contributors = process.env.APP_CONTRIBUTORS;
const appLogo = require('../images/logo.svg'); const appLogo = require('../images/logo.svg');
const darkPreview = require('../images/dark.png'); const darkPreview = require('../images/dark.png');
const lightPreview = require('../images/light.png'); const lightPreview = require('../images/light.png');
const editorThemes= [ const exampleQuery = `-- This is an example
SELECT
employee.id,
employee.first_name,
employee.last_name,
SUM(DATEDIFF("SECOND", call.start, call.end)) AS call_duration
FROM call
INNER JOIN employee ON call.employee_id = employee.id
GROUP BY
employee.id,
employee.first_name,
employee.last_name
ORDER BY
employee.id ASC;
`;
const localLocale: Ref<AvailableLocale> = ref(null);
const localPageSize: Ref<number> = ref(null);
const localTimeout: Ref<number> = ref(null);
const localEditorTheme: Ref<string> = ref(null);
const selectedTab: Ref<string> = ref('general');
const editorThemes = computed(() => [
{ {
group: t('word.light'), group: t('word.light'),
themes: [ themes: [
@@ -432,28 +486,7 @@ 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<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 locales = computed(() => { const locales = computed(() => {
const locales = []; const locales = [];

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

@@ -34,6 +34,15 @@ const {
lineWrap lineWrap
} = storeToRefs(settingsStore); } = storeToRefs(settingsStore);
const sizes = {
xsmall: '10px',
small: '12px',
medium: '14px',
large: '16px',
xlarge: '18px',
xxlarge: '20px'
};
const props = defineProps({ const props = defineProps({
modelValue: String, modelValue: String,
workspace: Object as Prop<Workspace>, workspace: Object as Prop<Workspace>,
@@ -231,7 +240,9 @@ watch(() => tablesInQuery.value.length, () => {
}); });
fields.value = localFields; fields.value = localFields;
setTimeout(() => {
setCustomCompleter(); setCustomCompleter();
}, 100);
}); });
watch(editorTheme, () => { watch(editorTheme, () => {
@@ -240,12 +251,6 @@ watch(editorTheme, () => {
}); });
watch(editorFontSize, () => { watch(editorFontSize, () => {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (editor.value) { if (editor.value) {
editor.value.setOptions({ editor.value.setOptions({
fontSize: sizes[editorFontSize.value] fontSize: sizes[editorFontSize.value]
@@ -305,7 +310,8 @@ onMounted(() => {
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
wrap: lineWrap.value, wrap: lineWrap.value,
enableSnippets: true, enableSnippets: true,
enableLiveAutocompletion: autoComplete.value enableLiveAutocompletion: autoComplete.value,
fontSize: sizes[editorFontSize.value]
}); });
if (!baseCompleter.value.length) if (!baseCompleter.value.length)

View File

@@ -0,0 +1,218 @@
<template>
<Draggable
:list="localList"
item-key="'uid'"
ghost-class="ghost"
:group="{ name: 'connections', pull: 'clone' }"
:swap-threshold="0.3"
@start="emit('start', $event)"
@end="emit('end', $event)"
@change="emit('update:modelValue', localList)"
>
<template #item="{ element }">
<li
v-if="element.isFolder || !folderedConnections.includes(element.uid)"
:draggable="true"
:class="{'folder': element.isFolder}"
@dragstart="draggedElement = element.uid"
@dragend="dragEnd"
@contextmenu.prevent="emit('context', $event, element)"
>
<div
v-if="!element.isFolder && !folderedConnections.includes(element.uid)"
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: getConnectionName(element.uid)
}"
class="settingbar-element btn btn-link"
:class="{ 'selected': element.uid === selectedWorkspace }"
placement="right"
strategy="fixed"
@click.stop="selectWorkspace(element.uid)"
>
<!-- Creates a new folder -->
<SettingBarConnections
v-if="draggedElement && !foldersUid.includes(draggedElement)"
class="drag-area"
:class="[{'folder-preview': coveredElement === element.uid && draggedElement !== coveredElement}]"
:list="dummyNested"
@dragenter="coveredElement = element.uid"
@dragleave="coveredElement = false"
@change="createFolder"
/>
<i v-if="coveredElement === element.uid && draggedElement !== coveredElement" class="settingbar-element-icon mdi mdi-folder-plus mdi-36px" />
<template v-else>
<div class="settingbar-element-icon-wrapper">
<i
class="settingbar-element-icon dbi"
:class="[element.icon ? `mdi ${element.icon} mdi-36px`: `dbi-${element.client}`, getStatusBadge(element.uid)]"
/>
<small class="settingbar-element-name">{{ element.name || getConnectionName(element.uid) }}</small>
</div>
</template>
</div>
<SettingBarConnectionsFolder
v-else-if="element.isFolder"
:key="`${element.uid}-${element.connections.length}`"
:folder="element"
:covered-element="coveredElement"
:dragged-element="draggedElement"
:folder-drag="folderDrag"
@select-workspace="selectWorkspace"
@covered="coveredElement = element.uid"
@uncovered="coveredElement = false"
@folder-sort="emit('update:modelValue', localList)"
@folder-drag="folderDrag = $event"
@context="emit('context', $event.event, $event.content)"
/>
</li>
</template>
</Draggable>
</template>
<script setup lang="ts">
import { computed, PropType, Ref, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import * as Draggable from 'vuedraggable';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
const workspacesStore = useWorkspacesStore();
const connectionsStore = useConnectionsStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getFolders: folders } = storeToRefs(connectionsStore);
const { getWorkspace, selectWorkspace } = workspacesStore;
const { getConnectionName, addFolder } = connectionsStore;
const props = defineProps({
modelValue: {
type: Array as PropType<SidebarElement[]>,
default: () => []
}
});
const emit = defineEmits(['start', 'end', 'move', 'context', 'update:modelValue']);
const localList = ref(props.modelValue);
const dummyNested = ref([]);
const draggedElement: Ref<string | false> = ref(false);
const coveredElement: Ref<string | false> = ref(false);
const folderDrag = ref(false);
const foldersUid = computed(() => folders.value.reduce<string[]>((acc, curr) => {
acc.push(curr.uid);
return acc;
}, []));
const folderedConnections = computed(() => {
return folders.value.reduce<string[]>((acc, curr) => {
acc = [...acc, ...curr.connections];
return acc;
}, []);
});
const dragEnd = () => {
coveredElement.value = false;
draggedElement.value = false;
};
const createFolder = ({ added }: {added: { element: SidebarElement }}) => {
if (typeof coveredElement.value === 'string' && !added.element.isFolder) {
// Create folder
addFolder({
after: coveredElement.value,
connections: [coveredElement.value, added.element.uid]
});
coveredElement.value = false;
}
};
const getStatusBadge = (uid: string) => {
if (getWorkspace(uid)) {
const status = getWorkspace(uid).connectionStatus;
switch (status) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
};
watch(() => dummyNested.value.length, () => {
dummyNested.value = [];
});
watch(() => props.modelValue, (value) => {
localList.value = value;
folderDrag.value = false;// Prenent some edge cases
});
</script>
<style lang="scss">
.drag-area {
background-color: transparent;
z-index: 10;
position: absolute;
left: 20px;
top: 20px;
right: 20px;
bottom: 20px;
transition: all .2s;
&.folder-preview {
border: 2px dotted;
border-radius: 15px;
left: 5px;
top: 5px;
right: 5px;
bottom: 5px;
}
li {
display: none!important;
}
}
.ghost:not(.folder) {
height: $settingbar-width;
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
position: relative;
transition: opacity .2s;
.settingbar-element {
border-radius: 15px!important;
background: rgba($color: #fff, $alpha: 0.1);
.settingbar-element-name {
position: absolute;
bottom: 5px;
left: 3px!important;
text-align: center!important;
font-size: 65%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
}
}
}
.settingbar-element-icon {
display: flex;
color: $body-font-color-dark;
}
</style>

View File

@@ -0,0 +1,443 @@
<template>
<div
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: folder.name,
disabled: isOpen || !folder.name
}"
class="settingbar-element folder btn btn-link p-1"
:class="[{ 'selected-inside': hasSelectedInside && !isOpen }]"
:style="isOpen ? `height: auto; opacity: 1;` : null"
>
<Draggable
class="folder-container"
:item-key="((item: string) => localList.indexOf(item))"
:class="[{'opened': isOpen}]"
:style="[
`background: ${folder.color};`,
isOpen ? `max-height: ${60*(folder.connections.length+1)}px` : 'max-height: 60px',
!isOpen || folderDrag ? `overflow: hidden;` : ''
]"
:list="localList"
ghost-class="ghost"
:group="{ name: 'connections', put: folderDrag ? undefined : 'clone' }"
@start="dragStart"
@end="dragStop"
>
<template #header>
<div
v-if="!isOpen"
class="folder-overlay"
@click="openFolder"
@contextmenu.stop="emit('context', {event: $event, content: folder})"
/>
<div
v-if="isOpen"
class="folder-icon"
:style="`color: ${folder.color};`"
@click="closeFolder"
>
<i class="folder-icon-open mdi mdi-folder-open mdi-36px" />
<i class="folder-icon-close mdi mdi-folder mdi-36px" />
</div>
</template>
<template #item="{ element }">
<div
:key="element"
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: getConnectionName(element)
}"
class="folder-element"
:class="{ 'selected': element === selectedWorkspace }"
@click="emit('select-workspace', element)"
@contextmenu.stop="emit('context', {event: $event, content: getConnectionOrderByUid(element)})"
>
<i
class="folder-element-icon dbi"
:class="[getConnectionOrderByUid(element)?.icon ? `mdi ${getConnectionOrderByUid(element).icon}`: `dbi-${getConnectionOrderByUid(element)?.client}`, getStatusBadge(element)]"
/>
<small v-if="isOpen" class="folder-element-name">{{ getConnectionOrderByUid(element)?.name || getConnectionName(element) }}</small>
</div>
</template>
</Draggable>
<SettingBarConnections
v-if="draggedElement && !foldersUid.includes(draggedElement)"
class="drag-area"
:class="[{'folder-preview': coveredElement === folder.uid && draggedElement !== coveredElement}]"
:list="dummyNested"
:swap-threshold="1"
@dragenter="emit('covered')"
@dragleave="emit('uncovered')"
@change="addIntoFolder"
/>
</div>
</template>
<script setup lang="ts">
import { computed, PropType, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import * as Draggable from 'vuedraggable';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces';
import SettingBarConnections from '@/components/SettingBarConnections.vue';
const workspacesStore = useWorkspacesStore();
const connectionsStore = useConnectionsStore();
const { getFolders: folders } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace } = workspacesStore;
const { getConnectionOrderByUid, getConnectionName, addToFolder } = connectionsStore;
const foldersOpened = JSON.parse(localStorage.getItem('opened-folders')) || [];
const props = defineProps({
folder: {
type: Object as PropType<SidebarElement>,
required: true
},
folderDrag: {
type: Boolean,
default: false
},
draggedElement: {
type: [String, Boolean] as PropType<string | false>,
required: true
},
coveredElement: {
type: [String, Boolean] as PropType<string | false>,
required: true
}
});
const emit = defineEmits(['context', 'covered', 'uncovered', 'select-workspace', 'folder-sort', 'folder-drag']);
const isOpen = ref(foldersOpened.includes(props.folder.uid));
const localList = ref(props.folder.connections);
const dummyNested = ref([]);
const hasSelectedInside = computed(() => localList.value.includes(selectedWorkspace.value));
const foldersUid = computed(() => folders.value.reduce<string[]>((acc, curr) => {
acc.push(curr.uid);
return acc;
}, []));
const addIntoFolder = ({ added }: {added: { element: SidebarElement }}) => {
if (typeof props.coveredElement === 'string' && !added.element.isFolder) {
addToFolder({
folder: props.coveredElement,
connection: added.element.uid
});
emit('uncovered');
}
};
const getStatusBadge = (uid: string) => {
if (getWorkspace(uid)) {
const status = getWorkspace(uid).connectionStatus;
switch (status) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
};
const openFolder = () => {
isOpen.value = true;
const opened: string[] = JSON.parse(localStorage.getItem('opened-folders')) || [];
opened.push(props.folder.uid);
localStorage.setItem('opened-folders', JSON.stringify(opened));
};
const closeFolder = () => {
isOpen.value = false;
let opened: string[] = JSON.parse(localStorage.getItem('opened-folders')) || [];
opened = opened.filter(uid => uid !== props.folder.uid);
localStorage.setItem('opened-folders', JSON.stringify(opened));
};
const dragStart = () => {
emit('folder-drag', true);
};
const dragStop = () => {
emit('folder-drag', false);
};
watch(() => dummyNested.value.length, () => {
dummyNested.value = [];
});
emit('folder-sort');// To apply changes on component key change
</script>
<style lang="scss" scoped>
.folder {
position: relative;
&.selected-inside {
opacity: 1!important;
&::after {
height: 2.5rem;
position: absolute;
}
}
&::after {
content: "";
height: 0;
width: 3px;
transition: height 0.2s;
background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius;
position: absolute;
left: 0;
}
}
.folder-container {
display: grid;
grid-template-columns: auto auto;
grid-template-rows: 50%;
gap: 4px;
padding: 4px;
height: 100%;
width: 100%;
border-radius: 15px;
transition: background .3s;
color: $body-font-color-dark;
&::before {
content: "";
height: 0;
width: 3px;
transition: height 0.2s;
background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius;
position: absolute;
left: -11px;
}
.folder-element-icon {
margin: 0 auto;
&.badge::after {
top: 10px;
right: 0px;
position: absolute;
display: none;
}
&.badge-update::after {
bottom: initial;
}
}
&.opened {
gap: 4px 6px;
grid-template-columns: auto;
grid-template-rows: auto;
background: rgba($color: #fff, $alpha: 0.1)!important;
transition: max-height .1s;
.folder-element {
opacity: .6;
height: 2.5rem;
max-width: initial;
max-height: initial;
background: transparent;
&.selected {
opacity: 1;
&::before {
height: 2.5rem;
position: absolute;
}
}
&::before {
content: "";
height: 0;
width: 3px;
transition: height 0.2s;
background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius;
position: absolute;
left: -11px;
}
.folder-element-icon {
margin: 0 auto;
font-size: 36px;
display: flex;
&.badge::after {
top: 14px;
right: -4px;
position: absolute;
display: block;
}
&.badge-update::after {
bottom: initial;
}
}
}
.folder-icon {
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 3px;
position: relative;
transition: opacity .2s;
.folder-icon-open {
display: block;
}
.folder-icon-close {
display: none;
}
&:hover {
opacity: 1;
.folder-icon-open {
display: none;
}
.folder-icon-close {
display: block;
}
}
}
}
.folder-overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
}
.folder-element {
display: flex;
align-items: flex-start;
justify-content: center;
max-width: 23px;
max-height: 23px;
margin-bottom: 3px;
position: relative;
transition: opacity .2s;
background: $bg-color-light-dark;
border-radius: 8px;
&.ghost {
margin:0 ;
margin-bottom: 3px;
height: 2.5rem;
}
&:hover, &.selected {
opacity: 1;
}
.folder-element-icon {
transition: margin .2s;
}
.folder-element-name {
position: absolute;
max-width: 90%;
bottom: 0;
font-size: 65%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
transition: bottom .2s;
}
}
&:not(.opened){
.folder-element {
.folder-element-icon {
width: 21px;
height: 21px;
font-size: 16px;
}
}
}
}
.ghost {
border-radius: 15px!important;
background: rgba($color: #fff, $alpha: 0.1);
&.folder-element {
height: $settingbar-width;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
margin: 4px;
position: relative;
transition: opacity .2s;
&:hover, &.selected {
opacity: 1;
}
.folder-element-icon {
margin: 0 auto;
font-size: 36px;
display: flex;
align-items: center;
justify-content: center;
&.badge::after {
top: 5px;
right: -4px;
position: absolute;
}
&.badge-update::after {
bottom: initial;
}
}
.folder-element-name {
position: absolute;
max-width: 90%;
bottom: 5px;
text-align: center;
font-size: 65%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
}
}
}
</style>

View File

@@ -3,20 +3,6 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="$emit('close-context')" @close-context="$emit('close-context')"
> >
<div
v-if="isPinned"
class="context-element"
@click="unpin"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-pin-off text-light pr-1" /> {{ t('word.unpin') }}</span>
</div>
<div
v-else
class="context-element"
@click="pin"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-pin mdi-rotate-45 text-light pr-1" /> {{ t('word.pin') }}</span>
</div>
<div <div
v-if="isConnected" v-if="isConnected"
class="context-element" class="context-element"
@@ -24,9 +10,16 @@
> >
<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
v-if="!contextConnection.isFolder"
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.stop="showAppearenceModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-brush-variant text-light pr-1" /> {{ t('word.appearence') }}</span>
</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>
@@ -38,7 +31,7 @@
> >
<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 mr-1" :class="[contextConnection.isFolder ? 'mdi-folder-remove' : 'mdi-server-remove']" /> {{ t(contextConnection.isFolder ? 'message.deleteFolder' : 'message.deleteConnection') }}
</div> </div>
</template> </template>
<template #body> <template #body>
@@ -47,6 +40,16 @@
</div> </div>
</template> </template>
</ConfirmModal> </ConfirmModal>
<ModalFolderAppearence
v-if="isFolderEdit"
:folder="contextConnection"
@close="hideAppearenceModal"
/>
<ModalConnectionAppearence
v-if="isConnectionEdit"
:connection="contextConnection"
@close="hideAppearenceModal"
/>
</BaseContextMenu> </BaseContextMenu>
</template> </template>
@@ -55,26 +58,24 @@ 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 { useI18n } from 'vue-i18n';
import { useConnectionsStore } from '@/stores/connections'; import { SidebarElement, 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 ModalFolderAppearence from '@/components/ModalFolderAppearence.vue';
import ModalConnectionAppearence from '@/components/ModalConnectionAppearence.vue';
const { t } = useI18n(); const { t } = useI18n();
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
const { const {
getConnectionByUid,
getConnectionName, getConnectionName,
addConnection, addConnection,
deleteConnection, deleteConnection
pinConnection,
unpinConnection
} = connectionsStore; } = connectionsStore;
const { pinnedConnections } = storeToRefs(connectionsStore);
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
@@ -86,16 +87,17 @@ const {
const props = defineProps({ const props = defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,
contextConnection: Object as Prop<ConnectionParams> contextConnection: Object as Prop<SidebarElement>
}); });
const emit = defineEmits(['close-context']); const emit = defineEmits(['close-context']);
const isConfirmModal = ref(false); const isConfirmModal = ref(false);
const isFolderEdit = ref(false);
const isConnectionEdit = ref(false);
const connectionName = computed(() => getConnectionName(props.contextConnection.uid)); const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('word.folder', 1));
const isConnected = computed(() => getWorkspace(props.contextConnection.uid).connectionStatus === 'connected'); const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected');
const isPinned = computed(() => pinnedConnections.value.has(props.contextConnection.uid));
const confirmDeleteConnection = () => { const confirmDeleteConnection = () => {
if (selectedWorkspace.value === props.contextConnection.uid) if (selectedWorkspace.value === props.contextConnection.uid)
@@ -105,7 +107,7 @@ const confirmDeleteConnection = () => {
}; };
const duplicateConnection = () => { const duplicateConnection = () => {
let connectionCopy = Object.assign({}, props.contextConnection); let connectionCopy = getConnectionByUid(props.contextConnection.uid);
connectionCopy = { connectionCopy = {
...connectionCopy, ...connectionCopy,
uid: uidGen('C'), uid: uidGen('C'),
@@ -116,6 +118,19 @@ const duplicateConnection = () => {
closeContext(); closeContext();
}; };
const showAppearenceModal = () => {
if (props.contextConnection.isFolder)
isFolderEdit.value = true;
else
isConnectionEdit.value = true;
};
const hideAppearenceModal = () => {
isConnectionEdit.value = false;
isFolderEdit.value = false;
closeContext();
};
const showConfirmModal = () => { const showConfirmModal = () => {
isConfirmModal.value = true; isConfirmModal.value = true;
}; };
@@ -125,16 +140,6 @@ const hideConfirmModal = () => {
closeContext(); closeContext();
}; };
const pin = () => {
pinConnection(props.contextConnection.uid);
closeContext();
};
const unpin = () => {
unpinConnection(props.contextConnection.uid);
closeContext();
};
const disconnect = () => { const disconnect = () => {
disconnectWorkspace(props.contextConnection.uid); disconnectWorkspace(props.contextConnection.uid);
closeContext(); closeContext();

View File

@@ -1,5 +1,9 @@
<template> <template>
<div id="footer" class="text-light"> <div
id="footer"
:class="[lightColors.includes(footerColor) ? 'text-dark' : 'text-light']"
:style="`background-color: ${footerColor};`"
>
<div class="footer-left-elements"> <div class="footer-left-elements">
<ul class="footer-elements"> <ul class="footer-elements">
<li class="footer-element"> <li class="footer-element">
@@ -50,6 +54,7 @@ import { useApplicationStore } from '@/stores/application';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { computed, ComputedRef } from 'vue'; import { computed, ComputedRef } from 'vue';
import { useConsoleStore } from '@/stores/console'; import { useConsoleStore } from '@/stores/console';
import { useConnectionsStore } from '@/stores/connections';
const { t } = useI18n(); const { t } = useI18n();
@@ -62,14 +67,19 @@ interface DatabaseInfos {// TODO: temp
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const connectionsStore = useConnectionsStore();
const lightColors = ['#FFCE54', '#FDA50F', '#BEBDB8', '#48CFAD'];
const { getSelected: workspaceUid } = storeToRefs(workspacesStore); const { getSelected: workspaceUid } = storeToRefs(workspacesStore);
const { toggleConsole } = useConsoleStore(); const { toggleConsole } = useConsoleStore();
const { showSettingModal } = applicationStore; const { showSettingModal } = applicationStore;
const { getWorkspace } = workspacesStore; const { getWorkspace } = workspacesStore;
const { getConnectionFolder } = connectionsStore;
const workspace = computed(() => getWorkspace(workspaceUid.value)); const workspace = computed(() => getWorkspace(workspaceUid.value));
const footerColor = computed(() => getConnectionFolder(workspaceUid.value)?.color || '#E36929');
const version: ComputedRef<DatabaseInfos> = computed(() => { const version: ComputedRef<DatabaseInfos> = computed(() => {
return getWorkspace(workspaceUid.value) ? workspace.value.version : null; return getWorkspace(workspaceUid.value) ? workspace.value.version : null;
}); });

View File

@@ -8,41 +8,10 @@
@close-context="isContext = false" @close-context="isContext = false"
/> />
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<Draggable <SettingBarConnections
v-model="pinnedConnectionsArr" v-model="connectionsArr"
:item-key="'uid'" @context="contextMenu"
@start="isDragging = true" />
@end="dragStop"
>
<template #item="{element}">
<li
:draggable="true"
class="settingbar-element btn btn-link ex-tooltip"
:class="{'selected': element.uid === selectedWorkspace}"
@click.stop="selectWorkspace(element.uid)"
@contextmenu.prevent="contextMenu($event, element)"
@mouseover.self="tooltipPosition"
>
<i class="settingbar-element-icon dbi" :class="[`dbi-${element.client}`, getStatusBadge(element.uid), (pinnedConnections.has(element.uid) ? 'settingbar-element-pin' : false)]" />
<span v-if="!isDragging && !isScrolling" class="ex-tooltip-content">{{ getConnectionName(element.uid) }}</span>
</li>
</template>
</Draggable>
<div v-if="pinnedConnectionsArr.length" class="divider" />
<li
v-for="connection in unpinnedConnectionsArr"
:key="connection.uid"
class="settingbar-element btn btn-link ex-tooltip"
:class="{'selected': connection.uid === selectedWorkspace}"
@click.stop="selectWorkspace(connection.uid)"
@contextmenu.prevent="contextMenu($event, connection)"
@mouseover.self="tooltipPosition"
>
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`, getStatusBadge(connection.uid)]" />
<span v-if="!isDragging && !isScrolling" class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
</li>
</ul> </ul>
</div> </div>
@@ -50,21 +19,31 @@
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<li <li
v-if="isScrollable" v-if="isScrollable"
class="settingbar-element btn btn-link ex-tooltip" v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('message.allConnections')
}"
class="settingbar-element btn btn-link"
@click="emit('show-connections-modal')" @click="emit('show-connections-modal')"
@mouseover.self="tooltipPosition"
> >
<div class="settingbar-element-icon-wrapper">
<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> </div>
</li> </li>
<li <li
class="settingbar-element btn btn-link ex-tooltip" v-tooltip="{
:class="{'selected': 'NEW' === selectedWorkspace}" strategy: 'fixed',
placement: 'right',
content: t('message.addConnection')
}"
class="settingbar-element btn btn-link"
:class="{ 'selected': 'NEW' === selectedWorkspace }"
@click="selectWorkspace('NEW')" @click="selectWorkspace('NEW')"
@mouseover.self="tooltipPosition"
> >
<div class="settingbar-element-icon-wrapper">
<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> </div>
</li> </li>
</ul> </ul>
</div> </div>
@@ -73,15 +52,29 @@
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<li <li
v-if="!disableScratchpad" v-if="!disableScratchpad"
class="settingbar-element btn btn-link ex-tooltip" v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('word.scratchpad')
}"
class="settingbar-element btn btn-link"
@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}" /> v-tooltip="{
<span class="ex-tooltip-content">{{ t('word.settings') }}</span> strategy: 'fixed',
placement: 'right',
content: t('word.settings')
}"
class="settingbar-element btn btn-link"
@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>
@@ -92,16 +85,16 @@
import { ref, Ref, computed, watch } from 'vue'; import { ref, Ref, computed, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore, SidebarElement } from '@/stores/connections';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import * as Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext.vue'; import SettingBarContext from '@/components/SettingBarContext.vue';
import { ConnectionParams } from 'common/interfaces/antares'; import SettingBarConnections from '@/components/SettingBarConnections.vue';
import { useElementBounding, useScroll } from '@vueuse/core'; import { useElementBounding } from '@vueuse/core';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();
localStorage.setItem('opened-folders', '[]');
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
@@ -109,122 +102,42 @@ const workspacesStore = useWorkspacesStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const { updateStatus } = storeToRefs(applicationStore); const { updateStatus } = storeToRefs(applicationStore);
const { connections: storedConnections, pinnedConnections, lastConnections } = storeToRefs(connectionsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { connectionsOrder } = storeToRefs(connectionsStore);
const { disableScratchpad } = storeToRefs(settingsStore); const { disableScratchpad } = storeToRefs(settingsStore);
const { showSettingModal, showScratchpad } = applicationStore; const { showSettingModal, showScratchpad } = applicationStore;
const { getConnectionName, updatePinnedConnections } = connectionsStore; const { updateConnectionsOrder, initConnectionsOrder } = connectionsStore;
const { getWorkspace, selectWorkspace } = workspacesStore; const { selectWorkspace } = workspacesStore;
const emit = defineEmits(['show-connections-modal']); const emit = defineEmits(['show-connections-modal']);
const isLinux = process.platform === 'linux';
const sidebarConnections: Ref<HTMLDivElement> = ref(null); 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 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<SidebarElement> = ref(null);
const sidebarConnectionsHeight = ref(useElementBounding(sidebarConnections)?.height); const sidebarConnectionsHeight = ref(useElementBounding(sidebarConnections)?.height);
const pinnedConnectionsArr = computed({ const connectionsArr = computed({
get: () => [...pinnedConnections.value].map(c => storedConnections.value.find(sc => sc.uid === c)).filter(Boolean), get: () => connectionsOrder.value,
set: (value: ConnectionParams[]) => { set: (value: SidebarElement[]) => {
const pinnedUid = value.reduce((acc, curr) => { updateConnectionsOrder(value);
acc.push(curr.uid);
return acc;
}, []);
updatePinnedConnections(pinnedUid);
} }
}); });
const unpinnedConnectionsArr = computed(() => {
return storedConnections.value
.filter(c => !pinnedConnections.value.has(c.uid))
.map(c => {
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
return { ...c, time: connTime };
})
.sort((a, b) => {
if (a.time < b.time) return 1;
else if (a.time > b.time) return -1;
return 0;
});
});
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value)); const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
const contextMenu = (event: MouseEvent, connection: ConnectionParams) => { const contextMenu = (event: MouseEvent, connection: SidebarElement) => {
contextEvent.value = event; contextEvent.value = event;
contextConnection.value = connection; contextConnection.value = connection;
isContext.value = true; isContext.value = true;
}; };
const tooltipPosition = (e: Event) => {
const el = (e.target ? e.target : e) as unknown as HTMLElement;
const tooltip = el.querySelector<HTMLElement>('.ex-tooltip-content');
if (tooltip) {
const fromTop = isLinux
? window.scrollY + el.getBoundingClientRect().top + (el.offsetHeight / 4)
: window.scrollY + el.getBoundingClientRect().top - (el.offsetHeight / 4);
tooltip.style.top = `${fromTop}px`;
}
};
const getStatusBadge = (uid: string) => {
if (getWorkspace(uid)) {
const status = getWorkspace(uid).connectionStatus;
switch (status) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dragStop = (e: any) => {
isDragging.value = false;
setTimeout(() => {
tooltipPosition(e.originalEvent.target.parentNode);
}, 200);
};
watch(sidebarConnectionsHeight, (value) => { watch(sidebarConnectionsHeight, (value) => {
isScrollable.value = value < sidebarConnections.value.scrollHeight; isScrollable.value = value < sidebarConnections.value.scrollHeight;
}); });
watch(unpinnedConnectionsArr, (newVal, oldVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
setTimeout(() => {
const element = document.querySelector<HTMLElement>('.settingbar-element.selected');
if (element) {
const rect = element.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
if (!isVisible) {
element.setAttribute('tabindex', '-1');
element.focus();
element.removeAttribute('tabindex');
}
}
}, 50);
}
});
watch(selectedWorkspace, (newVal, oldVal) => { watch(selectedWorkspace, (newVal, oldVal) => {
if (newVal !== oldVal) { if (newVal !== oldVal) {
setTimeout(() => { setTimeout(() => {
@@ -237,10 +150,13 @@ watch(selectedWorkspace, (newVal, oldVal) => {
}, 150); }, 150);
} }
}); });
if (!connectionsArr.value.length)
initConnectionsOrder();
</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;
@@ -272,17 +188,22 @@ watch(selectedWorkspace, (newVal, oldVal) => {
padding: 0; padding: 0;
margin: 0; margin: 0;
li {
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: center;
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
position: relative;
&:hover { &:hover {
opacity: 1; opacity: 1;
@@ -292,7 +213,7 @@ watch(selectedWorkspace, (newVal, oldVal) => {
opacity: 1; opacity: 1;
&::before { &::before {
height: $settingbar-width; height: calc(#{$settingbar-width} - 0.4rem);
} }
} }
@@ -301,16 +222,21 @@ watch(selectedWorkspace, (newVal, oldVal) => {
height: 0; height: 0;
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;
background-color: $primary-color; background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius; border-radius: $border-radius;
} }
.settingbar-element-icon { .settingbar-element-icon-wrapper{
margin: 0 auto; display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
.settingbar-element-icon {
&.badge::after { &.badge::after {
bottom: -10px; top: 10px;
right: 0; right: -6px;
position: absolute; position: absolute;
} }
@@ -319,7 +245,21 @@ watch(selectedWorkspace, (newVal, oldVal) => {
} }
} }
.settingbar-element-pin { .settingbar-element-name {
font-size: 65%;
max-width: 90%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: calc($settingbar-width - 15px);
line-height: 1.1;
color: rgba($body-font-color-dark, 0.8);
text-align: center;
}
.settingbar-element-pin {// <- Dead
margin: 0 auto; margin: 0 auto;
&::before { &::before {
@@ -328,46 +268,13 @@ watch(selectedWorkspace, (newVal, oldVal) => {
color: $body-font-color-dark; color: $body-font-color-dark;
transform: rotate(45deg); transform: rotate(45deg);
opacity: 0.25; opacity: 0.25;
bottom: -8px; top: -8px;
left: -4px; left: -10px;
position: absolute; 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

@@ -30,7 +30,7 @@
> >
<i class="mdi mdi-24px mdi-refresh" /> <i class="mdi mdi-24px mdi-refresh" />
</div> </div>
<div v-if="isWindows" style="width: 140px;" /> <div v-if="isWindows" :style="'width: 140px;'" />
</div> </div>
</div> </div>
</template> </template>
@@ -67,7 +67,7 @@ const windowTitle = computed(() => {
const connectionName = getConnectionName(selectedWorkspace.value); const connectionName = getConnectionName(selectedWorkspace.value);
const workspace = getWorkspace(selectedWorkspace.value); const workspace = getWorkspace(selectedWorkspace.value);
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb) || [workspace.client]; const breadcrumbs = workspace ? Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb) || [workspace.client] : [];
return [connectionName, ...breadcrumbs].join(' • '); return [connectionName, ...breadcrumbs].join(' • ');
}); });

View File

@@ -326,7 +326,7 @@
v-if="tab.type ==='query'" v-if="tab.type ==='query'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:connection="connection" :connection="connection"
/> />
<WorkspaceTabTable <WorkspaceTabTable
@@ -334,7 +334,7 @@
v-once v-once
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:table="tab.elementName" :table="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
:element-type="tab.elementType" :element-type="tab.elementType"
@@ -344,14 +344,14 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:schema="tab.schema" :schema="tab.schema"
/> />
<WorkspaceTabPropsTable <WorkspaceTabPropsTable
v-else-if="tab.type === 'table-props'" v-else-if="tab.type === 'table-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:table="tab.elementName" :table="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -360,13 +360,13 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:schema="tab.schema" :schema="tab.schema"
/> />
<WorkspaceTabPropsView <WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'" v-else-if="tab.type === 'view-props'"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:connection="connection" :connection="connection"
:view="tab.elementName" :view="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
@@ -376,7 +376,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -384,7 +384,7 @@
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)" v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -393,7 +393,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -401,7 +401,7 @@
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)" v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:function="tab.elementName" :function="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -410,7 +410,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -418,7 +418,7 @@
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)" v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:routine="tab.elementName" :routine="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -427,7 +427,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -435,7 +435,7 @@
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)" v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:function="tab.elementName" :function="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -444,7 +444,7 @@
:tab-uid="tab.uid" :tab-uid="tab.uid"
:tab="tab" :tab="tab"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:trigger="tab.elementName" :trigger="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -452,7 +452,7 @@
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)" v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:tab-uid="tab.uid" :tab-uid="tab.uid"
:connection="connection" :connection="connection"
:is-selected="selectedTab === tab.uid" :is-selected="selectedTab === tab.uid && isSelected"
:scheduler="tab.elementName" :scheduler="tab.elementName"
:schema="tab.schema" :schema="tab.schema"
/> />
@@ -638,6 +638,8 @@ const hideProcessesModal = () => {
const addWheelEvent = () => { const addWheelEvent = () => {
if (!hasWheelEvent.value) { if (!hasWheelEvent.value) {
tabWrap.value.$el.addEventListener('wheel', (e: WheelEvent) => { tabWrap.value.$el.addEventListener('wheel', (e: WheelEvent) => {
if (e.deltaX !== 0) return; // If trackpad horizontal scroll
if (e.deltaY > 0) tabWrap.value.$el.scrollLeft += 50; if (e.deltaY > 0) tabWrap.value.$el.scrollLeft += 50;
else tabWrap.value.$el.scrollLeft -= 50; else tabWrap.value.$el.scrollLeft -= 50;
}); });

View File

@@ -415,7 +415,8 @@ const clients = [
{ name: 'MySQL', slug: 'mysql' }, { name: 'MySQL', slug: 'mysql' },
{ name: 'MariaDB', slug: 'maria' }, { name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' }, { name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' } { name: 'SQLite', slug: 'sqlite' },
{ name: 'Firebird SQL (experimental)', slug: 'firebird' }
]; ];
const connection = ref({ const connection = ref({
@@ -553,7 +554,7 @@ 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>

View File

@@ -428,7 +428,8 @@ const clients = [
{ name: 'MySQL', slug: 'mysql' }, { name: 'MySQL', slug: 'mysql' },
{ name: 'MariaDB', slug: 'maria' }, { name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' }, { name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' } { name: 'SQLite', slug: 'sqlite' },
{ name: 'Firebird SQL (experimental)', slug: 'firebird' }
]; ];
const firstInput: Ref<HTMLInputElement> = ref(null); const firstInput: Ref<HTMLInputElement> = ref(null);

View File

@@ -7,7 +7,7 @@
{{ t('message.noOpenTabs') }} {{ t('message.noOpenTabs') }}
</p> </p>
<div class="empty-action"> <div class="empty-action">
<button class="btn btn-gray d-flex" @click="emit('new-tab')"> <button class="btn btn-primary d-flex" @click="emit('new-tab')">
<i class="mdi mdi-24px mdi-tab-plus mr-2" /> <i class="mdi mdi-24px mdi-tab-plus mr-2" />
{{ t('message.openNewTab') }} {{ t('message.openNewTab') }}
</button> </button>

View File

@@ -188,8 +188,6 @@ const deleteMisc = async () => {
break; break;
} }
console.log(res);
const { status, response } = res; const { status, response } = res;
if (status === 'success') { if (status === 'success') {
@@ -219,7 +217,7 @@ const runElementCheck = () => {
}; };
const runElement = (params: string[]) => { const runElement = (params: string[]) => {
if (props.selectedMisc.type === 'procedure') if (['procedure', 'routine'].includes(props.selectedMisc.type))
runRoutine(params); runRoutine(params);
else if (props.selectedMisc.type === 'function') else if (props.selectedMisc.type === 'function')
runFunction(params); runFunction(params);
@@ -260,6 +258,9 @@ const runRoutine = (params?: string[]) => {
case 'pg': case 'pg':
sql = `CALL ${localElement.value.name}(${params.join(',')})`; sql = `CALL ${localElement.value.name}(${params.join(',')})`;
break; break;
case 'firebird':
sql = `EXECUTE PROCEDURE "${localElement.value.name}"(${params.join(',')})`;
break;
// case 'mssql': // case 'mssql':
// sql = `EXEC ${localElement.value.name} ${params.join(',')}`; // sql = `EXEC ${localElement.value.name} ${params.join(',')}`;
// break; // break;

View File

@@ -25,7 +25,6 @@
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="table of filteredTables" v-for="table of filteredTables"
:ref="breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name) ? 'explorebarSelected' : ''"
:key="table.name" :key="table.name"
class="menu-item" class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}" :class="{'selected': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
@@ -69,7 +68,6 @@
<li <li
v-for="trigger of filteredTriggers" v-for="trigger of filteredTriggers"
:key="trigger.name" :key="trigger.name"
:ref="breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name ? 'explorebarSelected' : ''"
class="menu-item" class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}" :class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
@mousedown.left="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})" @mousedown.left="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
@@ -111,7 +109,6 @@
<li <li
v-for="(routine, i) of filteredProcedures" v-for="(routine, i) of filteredProcedures"
:key="`${routine.name}-${i}`" :key="`${routine.name}-${i}`"
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name ? 'explorebarSelected' : ''"
class="menu-item" class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name}" :class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === routine.name}"
@mousedown.left="selectMisc({schema: database.name, misc: routine, type: 'routine'})" @mousedown.left="selectMisc({schema: database.name, misc: routine, type: 'routine'})"
@@ -145,7 +142,6 @@
<li <li
v-for="(func, i) of filteredTriggerFunctions" v-for="(func, i) of filteredTriggerFunctions"
:key="`${func.name}-${i}`" :key="`${func.name}-${i}`"
:ref="breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name ? 'explorebarSelected' : ''"
class="menu-item" class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}" :class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})" @mousedown.left="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
@@ -179,7 +175,6 @@
<li <li
v-for="(func, i) of filteredFunctions" v-for="(func, i) of filteredFunctions"
:key="`${func.name}-${i}`" :key="`${func.name}-${i}`"
:ref="breadcrumbs.schema === database.name && breadcrumbs.function === func.name ? 'explorebarSelected' : ''"
class="menu-item" class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}" :class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'function'})" @mousedown.left="selectMisc({schema: database.name, misc: func, type: 'function'})"
@@ -213,7 +208,6 @@
<li <li
v-for="scheduler of filteredSchedulers" v-for="scheduler of filteredSchedulers"
:key="scheduler.name" :key="scheduler.name"
:ref="breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name ? 'explorebarSelected' : ''"
class="menu-item" class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}" :class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
@mousedown.left="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})" @mousedown.left="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
@@ -281,7 +275,6 @@ const {
} = workspacesStore; } = workspacesStore;
const schemaAccordion: Ref<HTMLDetailsElement> = ref(null); const schemaAccordion: Ref<HTMLDetailsElement> = ref(null);
const explorebarSelected: Ref<HTMLElement[]> = ref(null);
const isLoading = ref(false); const isLoading = ref(false);
const searchTerm = computed(() => { const searchTerm = computed(() => {
@@ -340,7 +333,8 @@ const maxSize = computed(() => {
watch(breadcrumbs, (newVal, oldVal) => { watch(breadcrumbs, (newVal, oldVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
setTimeout(() => { setTimeout(() => {
const element = explorebarSelected.value ? explorebarSelected.value[0] : null; const element = document.querySelector<HTMLElement>('.workspace-explorebar-database .selected');
if (element) { if (element) {
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
const elemTop = rect.top; const elemTop = rect.top;
@@ -353,7 +347,7 @@ watch(breadcrumbs, (newVal, oldVal) => {
element.removeAttribute('tabindex'); element.removeAttribute('tabindex');
} }
} }
}, 50); }, 100);
} }
}); });

View File

@@ -18,7 +18,7 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span>
</div> </div>
<div <div
v-if="selectedTable && selectedTable.type === 'table'" v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
class="context-element" class="context-element"
@click="duplicateTable" @click="duplicateTable"
> >

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
if (isChanged.value)
saveChanges(); 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,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" />
@@ -161,6 +160,7 @@ import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsR
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 { useI18n } from 'vue-i18n';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -272,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
if (isChanged.value)
saveChanges(); saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -295,7 +291,7 @@ watch(consoleHeight, () => {
}); });
originalRoutine.value = { originalRoutine.value = {
sql: customizations.value.functionSql, sql: customizations.value.procedureSql,
language: customizations.value.languages ? customizations.value.languages[0] : null, language: customizations.value.languages ? customizations.value.languages[0] : null,
name: '', name: '',
definer: '', definer: '',
@@ -313,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
if (isChanged.value)
saveChanges(); 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" />
@@ -91,6 +90,7 @@
<BaseSelect <BaseSelect
v-model="localOptions.collation" v-model="localOptions.collation"
:options="workspace.collations" :options="workspace.collations"
:max-visible-options="1000"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
class="form-select" class="form-select"
@@ -175,6 +175,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 +421,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
if (isChanged.value)
saveChanges(); saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -447,18 +444,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,18 +268,20 @@ 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
if (isChanged.value)
saveChanges(); saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema }); if (val) {
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
resizeQueryEditor();
}, 50);
}
}); });
watch(isChanged, (val) => { watch(isChanged, (val) => {
@@ -305,12 +307,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 +325,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
if (isChanged.value)
saveChanges(); 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
if (isChanged.value)
saveChanges(); 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
if (isChanged.value)
saveChanges(); 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();
@@ -351,6 +351,9 @@ const runRoutine = (params?: string[]) => {
case 'pg': case 'pg':
sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`; sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`;
break; break;
case 'firebird':
sql = `EXECUTE PROCEDURE "${originalRoutine.value.name}"(${params.join(',')})`;
break;
case 'mssql': case 'mssql':
sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`; sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`;
break; break;
@@ -377,14 +380,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
if (isChanged.value)
saveChanges(); saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -427,11 +426,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 +439,7 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -118,29 +118,17 @@
{{ t('word.context') }} {{ t('word.context') }}
</label> </label>
<div class="column"> <div class="column">
<label class="form-radio"> <label
v-for="condext in customizations.procedureContextValues"
:key="condext"
class="form-radio"
>
<input <input
v-model="selectedParamObj.context" v-model="selectedParamObj.context"
type="radio" type="radio"
name="context" name="context"
value="IN" :value="condext"
> <i class="form-icon" /> IN > <i class="form-icon" /> {{ condext }}
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="OUT"
name="context"
> <i class="form-icon" /> OUT
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="INOUT"
name="context"
> <i class="form-icon" /> INOUT
</label> </label>
</div> </div>
</div> </div>

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
if (isChanged.value)
saveChanges(); 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" />
@@ -104,6 +103,7 @@
<BaseSelect <BaseSelect
v-model="localOptions.collation" v-model="localOptions.collation"
:options="workspace.collations" :options="workspace.collations"
:max-visible-options="1000"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
class="form-select" class="form-select"
@@ -173,7 +173,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 +186,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();
@@ -300,7 +301,7 @@ const getFieldsData = async () => {
field.defaultType = 'noval'; field.defaultType = 'noval';
else if (field.default === 'NULL') else if (field.default === 'NULL')
field.defaultType = 'null'; field.defaultType = 'null';
else if (isNaN(+field.default) && field.default.charAt(0) !== '\'') else if (typeof field.default === 'string' && isNaN(+field.default) && field.default.charAt(0) !== '\'')
field.defaultType = 'expression'; field.defaultType = 'expression';
else { else {
field.defaultType = 'custom'; field.defaultType = 'custom';
@@ -323,7 +324,9 @@ const getFieldsData = async () => {
const { status, response } = await Tables.getTableIndexes(params); const { status, response } = await Tables.getTableIndexes(params);
if (status === 'success') { if (status === 'success') {
const indexesObj = response.reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => { const indexesObj = response
.filter((index: TableIndex) => index.type !== 'FOREIGN KEY')
.reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => {
acc[curr.name] = acc[curr.name] || []; acc[curr.name] = acc[curr.name] || [];
acc[curr.name].push(curr); acc[curr.name].push(curr);
return acc; return acc;
@@ -529,9 +532,10 @@ const clearChanges = () => {
}; };
const addField = () => { const addField = () => {
const uid = uidGen();
localFields.value.push({ localFields.value.push({
_antares_id: uidGen(), _antares_id: uid,
name: `${t('word.field', 1)}_${++newFieldsCounter.value}`, name: `${t('word.field', 1)}_${uid.substring(0, 4)}`,
key: '', key: '',
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
type: (workspace.value.dataTypes[0] as any).types[0].name, type: (workspace.value.dataTypes[0] as any).types[0].name,
@@ -646,14 +650,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
if (isChanged.value)
saveChanges(); saveChanges();
}
}
}; };
watch(() => props.schema, () => { watch(() => props.schema, () => {
@@ -684,9 +684,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

@@ -113,7 +113,7 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label col-3 pt-0"> <label class="form-label col-3">
{{ t('message.referenceTable') }} {{ t('message.referenceTable') }}
</label> </label>
<div class="column"> <div class="column">
@@ -219,13 +219,8 @@ const foreignProxy = ref([]);
const selectedForeignID = ref(''); const selectedForeignID = ref('');
const modalInnerHeight = ref(400); const modalInnerHeight = ref(400);
const refFields = ref({} as {[key: string]: TableField[]}); const refFields = ref({} as {[key: string]: TableField[]});
const foreignActions = [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
];
const foreignActions = computed(() => props.workspace.customizations.foreignActions);
const selectedForeignObj = computed(() => foreignProxy.value.find(foreign => foreign._antares_id === selectedForeignID.value)); const selectedForeignObj = computed(() => foreignProxy.value.find(foreign => foreign._antares_id === selectedForeignID.value));
const isChanged = computed(() => JSON.stringify(props.localKeyUsage) !== JSON.stringify(foreignProxy.value)); const isChanged = computed(() => JSON.stringify(props.localKeyUsage) !== JSON.stringify(foreignProxy.value));
@@ -254,16 +249,17 @@ const getModalInnerHeight = () => {
}; };
const addForeign = () => { const addForeign = () => {
const uid = uidGen();
foreignProxy.value = [...foreignProxy.value, { foreignProxy.value = [...foreignProxy.value, {
_antares_id: uidGen(), _antares_id: uid,
constraintName: `FK_${uidGen()}`, constraintName: `FK_${uid.substring(0, 4)}`,
refSchema: props.schema, refSchema: props.schema,
table: props.table, table: props.table,
refTable: '', refTable: '',
field: '', field: '',
refField: '', refField: '',
onUpdate: foreignActions[0], onUpdate: foreignActions.value[0],
onDelete: foreignActions[0] onDelete: foreignActions.value[0]
}]; }];
if (foreignProxy.value.length === 1) if (foreignProxy.value.length === 1)
@@ -271,6 +267,7 @@ const addForeign = () => {
setTimeout(() => { setTimeout(() => {
indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60; indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60;
selectedForeignID.value = uid;
}, 20); }, 20);
}; };

View File

@@ -92,7 +92,7 @@
<BaseSelect <BaseSelect
v-model="selectedIndexObj.type" v-model="selectedIndexObj.type"
:options="indexTypes" :options="indexTypes"
:option-disabled="(opt: any) => opt === 'PRIMARY'" :option-disabled="(opt: any) => opt === 'PRIMARY' && hasPrimary"
class="form-select" class="form-select"
/> />
</div> </div>
@@ -160,6 +160,7 @@ const modalInnerHeight = ref(400);
const selectedIndexObj = computed(() => indexesProxy.value.find(index => index._antares_id === selectedIndexID.value)); const selectedIndexObj = computed(() => indexesProxy.value.find(index => index._antares_id === selectedIndexID.value));
const isChanged = computed(() => JSON.stringify(props.localIndexes) !== JSON.stringify(indexesProxy.value)); const isChanged = computed(() => JSON.stringify(props.localIndexes) !== JSON.stringify(indexesProxy.value));
const hasPrimary = computed(() => indexesProxy.value.some(index => ['PRIMARY', 'PRIMARY KEY'].includes(index.type)));
const confirmIndexesChange = () => { const confirmIndexesChange = () => {
indexesProxy.value = indexesProxy.value.filter(index => index.fields.length); indexesProxy.value = indexesProxy.value.filter(index => index.fields.length);
@@ -179,15 +180,12 @@ const getModalInnerHeight = () => {
}; };
const addIndex = () => { const addIndex = () => {
const uid = uidGen();
indexesProxy.value = [...indexesProxy.value, { indexesProxy.value = [...indexesProxy.value, {
_antares_id: uidGen(), _antares_id: uid,
name: 'NEW_INDEX', name: `INDEX_${uid.substring(0, 4)}`,
fields: [], fields: [],
type: 'INDEX', type: props.workspace.customizations.primaryAsIndex ? props.indexTypes[0] : props.indexTypes[1]
comment: '',
indexType: 'BTREE',
indexComment: '',
cardinality: 0
}]; }];
if (indexesProxy.value.length === 1) if (indexesProxy.value.length === 1)
@@ -195,6 +193,7 @@ const addIndex = () => {
setTimeout(() => { setTimeout(() => {
indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60; indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60;
selectedIndexID.value = uid;
}, 20); }, 20);
}; };

View File

@@ -212,6 +212,7 @@
:options="collations" :options="collations"
option-label="collation" option-label="collation"
option-track-by="collation" option-track-by="collation"
:max-visible-options="1000"
class="form-select small-select pl-1 pr-4 editable-field" class="form-select small-select pl-1 pr-4 editable-field"
@blur="editOFF" @blur="editOFF"
/> />
@@ -501,8 +502,8 @@ const editOFF = () => {
localRow.value.enumValues = ''; localRow.value.enumValues = '';
if (fieldType.value.length) { if (fieldType.value.length) {
if (['integer', 'float', 'binary', 'spatial'].includes(fieldType.value.group)) localRow.value.numLength = 11; if (['integer', 'float', 'binary', 'spatial'].includes(fieldType.value.group)) localRow.value.numLength = 10;
if (['string'].includes(fieldType.value.group)) localRow.value.charLength = 15; if (['string'].includes(fieldType.value.group)) localRow.value.charLength = 20;
if (['time'].includes(fieldType.value.group)) localRow.value.datePrecision = 0; if (['time'].includes(fieldType.value.group)) localRow.value.datePrecision = 0;
if (['other'].includes(fieldType.value.group)) localRow.value.enumValues = '\'valA\',\'valB\''; if (['other'].includes(fieldType.value.group)) localRow.value.enumValues = '\'valA\',\'valB\'';
} }

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
if (isChanged.value)
saveChanges(); 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
if (isChanged.value)
saveChanges(); 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
if (isChanged.value)
saveChanges(); 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" />
@@ -206,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();
@@ -427,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);
} }
}; };
@@ -499,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();
@@ -518,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

@@ -18,6 +18,7 @@
@show-delete-modal="showDeleteConfirmModal" @show-delete-modal="showDeleteConfirmModal"
@set-null="setNull" @set-null="setNull"
@copy-cell="copyCell" @copy-cell="copyCell"
@fill-cell="fillCell"
@copy-row="copyRow" @copy-row="copyRow"
@duplicate-row="duplicateRow" @duplicate-row="duplicateRow"
@close-context="closeContext" @close-context="closeContext"
@@ -122,7 +123,7 @@ 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 { exportRows } from '../libs/exportRows'; import { exportRows } from '../libs/exportRows';
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, BLOB, DATE, DATETIME, TIME } 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';
@@ -133,6 +134,7 @@ 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 { jsonToSqlInsert } from 'common/libs/sqlUtils';
import { unproxify } from '@/libs/unproxify'; import { unproxify } from '@/libs/unproxify';
import faker from '@faker-js/faker';
const { t } = useI18n(); const { t } = useI18n();
@@ -268,6 +270,8 @@ const keyName = (key: string) => {
return 'UNIQUE'; return 'UNIQUE';
case 'mul': case 'mul':
return 'INDEX'; return 'INDEX';
case 'fk':
return 'REFERENCES';
default: default:
return 'UNKNOWN ' + key; return 'UNKNOWN ' + key;
} }
@@ -377,7 +381,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
@@ -389,7 +393,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),
@@ -463,6 +467,53 @@ const copyRow = (format: string) => {
} }
}; };
const fillCell = (event: { name: string; group: string; type: string }) => {
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
let fakeValue;
let datePrecision = '';
if (['datetime', 'time'].includes(event.group)) {
for (let i = 0; i < selectedCell.value.length; i++)
datePrecision += i === 0 ? '.S' : 'S';
}
if (event.group === 'custom') {
if (event.type === 'time' && event.name === 'now')
fakeValue = moment().format(`HH:mm:ss${datePrecision}`);
else if (event.type === 'time' && event.name === 'random')
fakeValue = moment(faker.date.recent()).format(`HH:mm:ss${datePrecision}`);
else if (event.type === 'datetime' && event.name === 'now')
fakeValue = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
else {
fakeValue = (faker as any)[event.group][event.name]();
if (['string', 'number'].includes(typeof fakeValue)) {
if (typeof fakeValue === 'number')
fakeValue = String(fakeValue);
if (selectedCell.value.length)
fakeValue = fakeValue.substring(0, selectedCell.value.length < 1024 ? Number(selectedCell.value.length) : 1024);
}
else if ([...DATE, ...DATETIME].includes(selectedCell.value.type))
fakeValue = moment(fakeValue).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
else if (TIME.includes(selectedCell.value.type))
fakeValue = moment(fakeValue).format(`HH:mm:ss${datePrecision}`);
}
const params = {
primary: primaryField.value?.name,
schema: getSchema(resultsetIndex.value),
table: getTable(resultsetIndex.value),
id: getPrimaryValue(row),
row,
orgRow: row,
field: selectedCell.value.field,
content: fakeValue
};
emit('update-field', params);
};
const duplicateRow = () => { 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 rowToDuplicate = JSON.parse(JSON.stringify(row)); const rowToDuplicate = JSON.parse(JSON.stringify(row));
@@ -611,7 +662,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

@@ -42,6 +42,27 @@
<i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('word.duplicate') }} <i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('word.duplicate') }}
</span> </span>
</div> </div>
<div
v-if="selectedRows.length === 1 && selectedCell.isEditable && mode === 'table' && fakerGroup"
class="context-element"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-auto-fix text-light pr-1" /> {{ t('message.fillCell') }}
</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu">
<div
v-for="method in fakerMethods[fakerGroup]"
:key="method.name"
class="context-element"
@click="fillCell(method)"
>
<span class="d-flex">
{{ t(`faker.${method.name}`) }}
</span>
</div>
</div>
</div>
<div <div
v-if="selectedRows.length === 1 && selectedCell.isEditable" v-if="selectedRows.length === 1 && selectedCell.isEditable"
class="context-element" class="context-element"
@@ -64,13 +85,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Prop } from 'vue'; import { computed, 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';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, UUID } from 'common/fieldTypes';
const { t } = useI18n(); const { t } = useI18n();
defineProps({ const props = defineProps({
contextEvent: MouseEvent, contextEvent: MouseEvent,
selectedRows: Array, selectedRows: Array,
selectedCell: Object, selectedCell: Object,
@@ -83,9 +105,62 @@ const emit = defineEmits([
'set-null', 'set-null',
'copy-cell', 'copy-cell',
'copy-row', 'copy-row',
'duplicate-row' 'duplicate-row',
'fill-cell'
]); ]);
const fakerMethods = {
string: [
{ name: 'word', group: 'lorem' },
{ name: 'text', group: 'lorem' },
{ name: 'firstName', group: 'name' },
{ name: 'lastName', group: 'name' },
{ name: 'jobTitle', group: 'name' },
{ name: 'phoneNumber', group: 'phone' },
{ name: 'exampleEmail', group: 'internet' },
{ name: 'ip', group: 'internet' },
{ name: 'domainName', group: 'internet' },
{ name: 'color', group: 'internet' },
{ name: 'uuid', group: 'random' }
],
number: [
{ name: 'number', group: 'random' }
],
float: [
{ name: 'float', group: 'random' },
{ name: 'amount', group: 'finance' }
],
datetime: [
{ name: 'now', group: 'custom' },
{ name: 'past', group: 'date' },
{ name: 'future', group: 'date' }
],
time: [
{ name: 'now', group: 'custom' },
{ name: 'random', group: 'custom' }
],
uuid: [
{ name: 'uuid', group: 'random' }
]
};
const fakerGroup = computed(() => {
if ([...TEXT, ...LONG_TEXT].includes(props.selectedCell.type))
return 'string';
else if (NUMBER.includes(props.selectedCell.type))
return 'number';
else if (FLOAT.includes(props.selectedCell.type))
return 'float';
else if ([...DATE, ...DATETIME].includes(props.selectedCell.type))
return 'datetime';
else if (TIME.includes(props.selectedCell.type))
return 'time';
else if (UUID.includes(props.selectedCell.type))
return 'uuid';
else
return false;
});
const showConfirmModal = () => { const showConfirmModal = () => {
emit('show-delete-modal'); emit('show-delete-modal');
}; };
@@ -113,4 +188,9 @@ const duplicateRow = () => {
emit('duplicate-row'); emit('duplicate-row');
closeContext(); closeContext();
}; };
const fillCell = (method: {name: string; group: string}) => {
emit('fill-cell', { ...method, type: fakerGroup.value });
closeContext();
};
</script> </script>

View File

@@ -11,7 +11,12 @@
:class="{selected: selectedCell === cKey}" :class="{selected: selectedCell === cKey}"
@click="selectRow($event, cKey)" @click="selectRow($event, cKey)"
@contextmenu.prevent="openContext($event, { id: row._antares_id, orgField: cKey })" @contextmenu.prevent="openContext($event, {
id: row._antares_id,
orgField: cKey,
type: fields[cKey].type,
length: fields[cKey].charLength || fields[cKey].length
})"
> >
<template v-if="cKey !== '_antares_id'"> <template v-if="cKey !== '_antares_id'">
<span <span
@@ -212,9 +217,11 @@ import {
DATETIME, DATETIME,
BLOB, BLOB,
BIT, BIT,
BINARY,
HAS_TIMEZONE, HAS_TIMEZONE,
SPATIAL, SPATIAL,
IS_MULTI_SPATIAL IS_MULTI_SPATIAL,
IS_BIGINT
} from 'common/fieldTypes'; } from 'common/fieldTypes';
import ConfirmModal from '@/components/BaseConfirmModal.vue'; import ConfirmModal from '@/components/BaseConfirmModal.vue';
import TextEditor from '@/components/BaseTextEditor.vue'; import TextEditor from '@/components/BaseTextEditor.vue';
@@ -275,8 +282,12 @@ const inputProps = computed(() => {
if ([...TEXT, ...LONG_TEXT].includes(editingType.value)) if ([...TEXT, ...LONG_TEXT].includes(editingType.value))
return { type: 'text', mask: false }; return { type: 'text', mask: false };
if ([...NUMBER, ...FLOAT].includes(editingType.value)) if ([...NUMBER, ...FLOAT].includes(editingType.value)) {
if (IS_BIGINT.includes(editingType.value))
return { type: 'text', mask: false };
else
return { type: 'number', mask: false }; return { type: 'number', mask: false };
}
if (TIME.includes(editingType.value)) { if (TIME.includes(editingType.value)) {
let timeMask = '##:##:##'; let timeMask = '##:##:##';
@@ -379,6 +390,9 @@ const editON = async (field: string) => {
const content = props.row[field]; const content = props.row[field];
const type = props.fields[field].type.toUpperCase(); const type = props.fields[field].type.toUpperCase();
if (BINARY.includes(type)) return;
originalContent.value = typeFormat(content, type, props.fields[field].length); originalContent.value = typeFormat(content, type, props.fields[field].length);
editingType.value = type; editingType.value = type;
editingField.value = field; editingField.value = field;
@@ -442,7 +456,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);
} }
@@ -526,7 +540,14 @@ const getKeyUsage = (keyName: string) => {
return props.keyUsage.find(key => key.field === keyName); return props.keyUsage.find(key => key.field === keyName);
}; };
const openContext = (event: MouseEvent, payload: { id: string; field?: string; orgField: string; isEditable?: boolean }) => { const openContext = (event: MouseEvent, payload: {
id: string;
field?: string;
orgField: string;
isEditable?: boolean;
type: string;
length: number | false;
}) => {
payload.field = props.fields[payload.orgField].name;// Ensures field name only payload.field = props.fields[payload.orgField].name;// Ensures field name only
payload.isEditable = isEditable.value; payload.isEditable = isEditable.value;
emit('contextmenu', event, payload); emit('contextmenu', event, payload);
@@ -582,6 +603,9 @@ const typeFormat = (val: string | number | Date | number[], type: string, precis
return parseInt(bitString).toString().padStart(Number(precision), '0'); return parseInt(bitString).toString().padStart(Number(precision), '0');
} }
if (BINARY.includes(type))
return Buffer.from(val as number[]).toString('hex');
if (ARRAY.includes(type)) { if (ARRAY.includes(type)) {
if (Array.isArray(val)) if (Array.isArray(val))
return JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}'); return JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');

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"
> >
@@ -190,6 +190,7 @@ 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 { useFilters } from '@/composables/useFilters';
import { ipcRenderer } from 'electron';
const { localeString } = useFilters(); const { localeString } = useFilters();
@@ -342,7 +343,6 @@ const pageChange = (direction: 'prev' | 'next') => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const showFakerModal = (row?: any) => { const showFakerModal = (row?: any) => {
console.log(row);
if (isQuering.value) return; if (isQuering.value) return;
isFakerModal.value = true; isFakerModal.value = true;
rowToDuplicate.value = row; rowToDuplicate.value = row;
@@ -353,23 +353,6 @@ const hideFakerModal = () => {
rowToDuplicate.value = null; 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')
isSearch.value = !isSearch.value;
}
}
};
const setRefreshInterval = () => { const setRefreshInterval = () => {
if (refreshInterval.value) if (refreshInterval.value)
clearInterval(refreshInterval.value); clearInterval(refreshInterval.value);
@@ -401,6 +384,30 @@ const updateFilters = (clausoles: TableFilterClausole[]) => {
getTableData(); getTableData();
}; };
const reloadListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
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(() => {
return results.value.length && return results.value.length &&
results.value[0].rows && results.value[0].rows &&
@@ -457,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

@@ -28,11 +28,24 @@ export function useFilters () {
return `(${num})`; 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 { return {
cutText, cutText,
formatDate, formatDate,
wrapNumber, wrapNumber,
lastPart, lastPart,
localeString localeString,
parseKeys
}; };
} }

View File

@@ -142,7 +142,13 @@ export const enUS = {
contributors: 'Contributors', contributors: 'Contributors',
pin: 'Pin', pin: 'Pin',
unpin: 'Unpin', unpin: 'Unpin',
console: 'Console' console: 'Console',
shortcuts: 'Shortcuts',
folder: 'Folder | Folders',
appearence: 'Appearence',
color: 'Color',
label: 'Label',
icon: 'Icon'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@@ -298,7 +304,35 @@ export const enUS = {
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',
fillCell: 'Fill cell',
editFolder: 'Edit folder',
folderName: 'Folder name',
deleteFolder: 'Delete folder',
editConnectionAppearence: 'Edit connection appearence'
}, },
faker: { faker: {
address: 'Address', address: 'Address',
@@ -364,6 +398,7 @@ export const enUS = {
collation: 'Collation', collation: 'Collation',
engine: 'Engine', engine: 'Engine',
past: 'Past', past: 'Past',
now: 'Now',
future: 'Future', future: 'Future',
between: 'Between', between: 'Between',
recent: 'Recent', recent: 'Recent',

View File

@@ -135,7 +135,15 @@ export const itIT = {
aborted: 'Annullato', aborted: 'Annullato',
disabled: 'Disabilitato', disabled: 'Disabilitato',
enable: 'Abilita', enable: 'Abilita',
disable: 'Disabilita' disable: 'Disabilita',
commit: 'Commit',
rollback: 'Rollback',
connectionString: 'Connection string',
contributors: 'Contributori',
pin: 'Fissa',
unpin: 'Sgancia',
console: 'Console',
shortcuts: 'Scorciatoie'
}, },
message: { message: {
appWelcome: 'Benvenuto in Antares SQL Client!', appWelcome: 'Benvenuto in Antares SQL Client!',
@@ -248,15 +256,6 @@ export const itIT = {
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:', noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
noSchema: 'Nessuno schema', noSchema: 'Nessuno schema',
restorePreviourSession: 'Ripristina sessione precedente', restorePreviourSession: 'Ripristina sessione precedente',
exportSchema: 'Esporta schema',
importSchema: 'Importa schema',
directoryPath: 'Percorso directory',
newInserStmtEvery: 'Nuova istruzione INSERT ogni',
processingTableExport: 'Processo {table}',
fechingTableExport: 'Ricavo i dati {table}',
writingTableExport: 'Scrittura dati {table}',
checkAllTables: 'Seleziona tutte le tabelle',
uncheckAllTables: 'Deseleziona tutte le tabelle',
runQuery: 'Esegui query', runQuery: 'Esegui query',
thereAreNoTableFields: 'Non ci sono campi della tabella', thereAreNoTableFields: 'Non ci sono campi della tabella',
newTable: 'Nuova tabella', newTable: 'Nuova tabella',
@@ -270,10 +269,60 @@ export const itIT = {
searchForQueries: 'Cerca query', searchForQueries: 'Cerca query',
killProcess: 'Uccidi processo', killProcess: 'Uccidi processo',
closeTab: 'Chiudi tab', closeTab: 'Chiudi tab',
exportSchema: 'Esporta schema',
importSchema: 'Importa schema',
directoryPath: 'Percorso directory',
newInserStmtEvery: 'Nuova istruzione INSERT ogni',
processingTableExport: 'Processo {table}',
fechingTableExport: 'Ricavo i dati {table}',
writingTableExport: 'Scrittura dati {table}',
checkAllTables: 'Seleziona tutte le tabelle',
uncheckAllTables: 'Deseleziona tutte le tabelle',
goToDownloadPage: 'Vai alla pagina di download', goToDownloadPage: 'Vai alla pagina di download',
readOnlyMode: 'Modalità sola lettura', readOnlyMode: 'Modalità sola lettura',
killQuery: 'Interrompi query',
insertRow: 'Inserisci riga | Inserisci righe',
commitMode: 'Modalità commit',
autoCommit: 'Auto commit',
manualCommit: 'Commit manuale',
actionSuccessful: '{action} riuscito',
importQueryErrors: 'Attenzione: si è verificato un errore | Attenzione si sono verificati {n} errori', importQueryErrors: 'Attenzione: si è verificato un errore | Attenzione si sono verificati {n} errori',
executedQueries: '{n} query eseguite | {n} query eseguite' executedQueries: '{n} query eseguite | {n} query eseguite',
ourputFormat: 'Formato output',
singleFile: 'Singolo file {ext}',
zipCompressedFile: 'File {ext} zippato',
disableBlur: 'Disabilita sfocatura',
untrustedConnection: 'Connessione non affidabile',
missingOrIncompleteTranslation: 'Traduzione mancante o incompleta?',
findOutHowToContribute: 'Scopri come contribuire',
disableFKChecks: 'DIsabilita controllo foreigh key',
allConnections: 'Tutte le connessioni',
searchForConnections: 'Cerca una connessione',
disableScratchpad: 'Disabilita scratchpad',
reportABug: 'Segnala un bug',
nextTab: 'Prossima tab',
previousTab: 'Tab precedente',
selectTabNumber: 'Seleziona tab numero {param}',
toggleConsole: 'Attiva/disattiva console',
addShortcut: 'Aggiungi scorciatoia',
editShortcut: 'Modifica scorciatoia',
deleteShortcut: 'Cancella scorciatoia',
restoreDefaults: 'Ripristina predefiniti',
restoreDefaultsQuestion: 'Confermi di ripristinare i valori predefiniti?',
registerAShortcut: 'Registra una scorciatoia',
invalidShortcutMessage: 'Combinazione non valida, continua a digitare',
shortcutAlreadyExists: 'Scorciatoia esistente',
saveContent: 'Salva contenuto',
openAllConnections: 'Apri tutte le connessioni',
openSettings: 'Apri le impostazioni',
openScratchpad: 'Apri lo scratchpad',
runOrReload: 'Esegui o ricarica',
formatQuery: 'Formatta query',
queryHistory: 'Cronologia query',
clearQuery: 'Pulisci query',
openFilter: 'Apri il filtro',
nextResultsPage: 'Prossima pagina risultati',
previousResultsPage: 'Pagina risultati precedente'
}, },
faker: { faker: {
address: 'Indirizzo', address: 'Indirizzo',

View File

@@ -134,11 +134,15 @@ export const ptBR = {
aborted: 'Abortado', aborted: 'Abortado',
disabled: 'Inativo', disabled: 'Inativo',
enable: 'Ativo', enable: 'Ativo',
disable: 'Disable', disable: 'Desativar',
commit: 'Enviar', commit: 'Enviar',
rollback: 'Reverter', rollback: 'Reverter',
connectionString: 'String da conexão', connectionString: 'String da conexão',
contributors: 'Contribuintes' contributors: 'Contribuintes',
pin: 'Fixar',
unpin: 'Desafixar',
console: 'Console',
shortcuts: 'Atalhos'
}, },
message: { message: {
appWelcome: 'Bem vindo ao Antares SQL Client!', appWelcome: 'Bem vindo ao Antares SQL Client!',
@@ -285,7 +289,38 @@ export const ptBR = {
ourputFormat: 'Formato da saída', ourputFormat: 'Formato da saída',
singleFile: 'Arquivo {ext} único', singleFile: 'Arquivo {ext} único',
zipCompressedFile: 'Arquivo compactado {ext} ZIP', zipCompressedFile: 'Arquivo compactado {ext} ZIP',
disableBlur: 'Desabilitar Blur' disableBlur: 'Desabilitar Blur',
untrustedConnection: 'Conexão insegura',
missingOrIncompleteTranslation: 'Tradução incorreta ou incompleta?',
findOutHowToContribute: 'Saiba como contribuir',
disableFKChecks: 'Desativar verificação de chave estrangeira',
allConnections: 'Todas as conexões',
searchForConnections: 'Procurar por conexões',
disableScratchpad: 'Desativar bloco de notas',
reportABug: 'Reportar um problema',
nextTab: 'Próxima guia',
previousTab: 'Guia anterior',
selectTabNumber: 'Selecionar guia número {param}',
toggleConsole: 'Alterar console',
addShortcut: 'Adicionar Atalho',
editShortcut: 'Editar atalho',
deleteShortcut: 'Delete shortcut',
restoreDefaults: 'Restaurar padrões',
restoreDefaultsQuestion: 'Você confirma que quer restaurar os valores padrões?',
registerAShortcut: 'Registrar um atalho',
invalidShortcutMessage: 'Combinação inválida, continue digitando',
shortcutAlreadyExists: 'O atalho já existe',
saveContent: 'Salvar conteúdo',
openAllConnections: 'Abrir todas as conexões',
openSettings: 'Abrir Configurações',
openScratchpad: 'Abrir scratchpad',
runOrReload: 'Executar ou recarregar',
formatQuery: 'Formatar consulta',
queryHistory: 'Histórico de consulta',
clearQuery: 'Limpar consulta',
openFilter: 'Abrir Filtro',
nextResultsPage: 'Próxima página de resultados',
previousResultsPage: 'Página de resultados anterior'
}, },
faker: { faker: {
address: 'Endereço', address: 'Endereço',

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -3,7 +3,9 @@ import { ipcRenderer } from 'electron';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import { VueMaskDirective } from 'v-mask'; import { VueMaskDirective } from 'v-mask';
import * as FloatingVue from 'floating-vue';
import '@mdi/font/css/materialdesignicons.css'; import '@mdi/font/css/materialdesignicons.css';
import 'floating-vue/dist/style.css';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import '@/scss/main.scss'; import '@/scss/main.scss';
@@ -27,6 +29,7 @@ createApp(App)
.directive('mask', vMaskV3) .directive('mask', vMaskV3)
.use(createPinia()) .use(createPinia())
.use(i18n) .use(i18n)
.use(FloatingVue)
.mount('#app'); .mount('#app');
const { locale } = useSettingsStore(); const { locale } = useSettingsStore();
@@ -42,6 +45,10 @@ ipcRenderer.on('query-log', (event, logRecord) => {
useConsoleStore().putLog(logRecord); useConsoleStore().putLog(logRecord);
}); });
ipcRenderer.on('toggle-console', () => {
useConsoleStore().toggleConsole();
});
// IPC app updates // IPC app updates
ipcRenderer.on('checking-for-update', () => { ipcRenderer.on('checking-for-update', () => {
useApplicationStore().updateStatus = 'checking'; useApplicationStore().updateStatus = 'checking';
@@ -85,3 +92,7 @@ ipcRenderer.on('open-updates-preferences', () => {
useApplicationStore().showSettingModal('update'); useApplicationStore().showSettingModal('update');
ipcRenderer.send('check-for-updates'); ipcRenderer.send('check-for-updates');
}); });
ipcRenderer.on('update-shortcuts', (event, shortcuts) => {
useSettingsStore().updateShortcuts(shortcuts);
});

View File

@@ -1,3 +1,4 @@
import { ShortcutRecord } from 'common/shortcuts';
import { ipcRenderer, OpenDialogOptions, OpenDialogReturnValue } from 'electron'; import { ipcRenderer, OpenDialogOptions, OpenDialogReturnValue } from 'electron';
import { unproxify } from '../libs/unproxify'; import { unproxify } from '../libs/unproxify';
@@ -9,4 +10,20 @@ export default class {
static getDownloadPathDirectory (): Promise<string> { static getDownloadPathDirectory (): Promise<string> {
return ipcRenderer.invoke('get-download-dir-path'); return ipcRenderer.invoke('get-download-dir-path');
} }
static reloadShortcuts () {
return ipcRenderer.invoke('reload-shortcuts');
}
static updateShortcuts (shortcuts: ShortcutRecord[]) {
return ipcRenderer.invoke('update-shortcuts', unproxify(shortcuts));
}
static restoreDefaultShortcuts () {
return ipcRenderer.invoke('resotre-default-shortcuts');
}
static unregisterShortcuts () {
return ipcRenderer.invoke('unregister-shortcuts');
}
} }

View File

@@ -0,0 +1,4 @@
export const getContrast = (hexcolor: string) => {
if (!hexcolor) return '';
return (parseInt(hexcolor.replace('#', ''), 16) > 0xffffff / 2) ? 'dark' : 'light';
};

View File

@@ -1,4 +1,3 @@
@mixin type-colors($types) { @mixin type-colors($types) {
@each $type, $color in $types { @each $type, $color in $types {
.type-#{$type} { .type-#{$type} {
@@ -27,6 +26,8 @@
"macaddr8": $string-color, "macaddr8": $string-color,
"uuid": $string-color, "uuid": $string-color,
"regproc": $string-color, "regproc": $string-color,
"blob-text": $string-color,
"int": $number-color, "int": $number-color,
"tinyint": $number-color, "tinyint": $number-color,
"smallint": $number-color, "smallint": $number-color,
@@ -45,6 +46,7 @@
"double_precision": $number-color, "double_precision": $number-color,
"oid": $number-color, "oid": $number-color,
"xid": $number-color, "xid": $number-color,
"money": $number-color, "money": $number-color,
"number": $number-color, "number": $number-color,
"datetime": $date-color, "datetime": $date-color,
@@ -55,20 +57,26 @@
"timestamp": $date-color, "timestamp": $date-color,
"timestamp_without_time_zone": $date-color, "timestamp_without_time_zone": $date-color,
"timestamp_with_time_zone": $date-color, "timestamp_with_time_zone": $date-color,
"bit": $bit-color, "bit": $bit-color,
"bit_varying": $bit-color, "bit_varying": $bit-color,
"binary": $blob-color, "binary": $blob-color,
"char-binary": $blob-color,
"varbinary": $blob-color, "varbinary": $blob-color,
"blob": $blob-color, "blob": $blob-color,
"tinyblob": $blob-color, "tinyblob": $blob-color,
"mediumblob": $blob-color, "mediumblob": $blob-color,
"medium_blob": $blob-color, "medium_blob": $blob-color,
"longblob": $blob-color, "longblob": $blob-color,
"long_blob": $blob-color,
"bytea": $blob-color, "bytea": $blob-color,
"enum": $enum-color, "enum": $enum-color,
"set": $enum-color, "set": $enum-color,
"bool": $enum-color, "bool": $enum-color,
"boolean": $enum-color, "boolean": $enum-color,
"interval": $array-color, "interval": $array-color,
"array": $array-color, "array": $array-color,
"anyarray": $array-color, "anyarray": $array-color,
@@ -85,6 +93,7 @@
"geomcollection": $array-color, "geomcollection": $array-color,
"geometrycollection": $array-color, "geometrycollection": $array-color,
"aclitem": $array-color, "aclitem": $array-color,
"unknown": $unknown-color, "unknown": $unknown-color,
) )
); );

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