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

Compare commits

..

156 Commits

Author SHA1 Message Date
fe9817bf31 chore(release): 0.7.4 2023-02-10 09:26:50 +01:00
ddb7ead083 fix: tables sort in sidebar 2023-02-10 09:26:17 +01:00
9d8c21244b chore(release): 0.7.3 2023-02-09 11:50:16 +01:00
d934ae1e6c fix(SQLite): triggers disappear after editing related table, fixes #523 2023-02-04 13:51:02 +01:00
68f8d48064 chore: minor ts changes 2023-02-04 12:10:09 +01:00
e7e491340a feat(SQLite): added support to INTEGER UNSIGNED 2023-02-04 11:38:30 +01:00
e8447e5655 fix: select of table type stuck when editing an unknown type 2023-02-04 11:34:53 +01:00
6decba316c fix: longtext edit modal opens when it shouldn't, fixes #524 2023-02-04 09:36:29 +01:00
2b5e1e7b39 fix(SQLite): error with integer timestamp fields 2023-02-04 09:07:41 +01:00
a124f04661 Merge pull request #529 from antares-sql/dependabot/npm_and_yarn/mdi/font-7.1.96
build(deps): bump @mdi/font from 7.0.96 to 7.1.96
2023-02-02 16:45:38 +01:00
dependabot[bot]
3ee4ab4d84 build(deps): bump @mdi/font from 7.0.96 to 7.1.96
Bumps [@mdi/font](https://github.com/Templarian/MaterialDesign-Webfont) from 7.0.96 to 7.1.96.
- [Release notes](https://github.com/Templarian/MaterialDesign-Webfont/releases)
- [Commits](https://github.com/Templarian/MaterialDesign-Webfont/compare/v7.0.96...v7.1.96)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 19:05:41 +00:00
174aa3c508 chore(release): 0.7.2 2023-01-22 17:16:04 +01:00
729edd40a6 build: update electron 2023-01-22 15:07:47 +01:00
c7ab3b77a2 fix: allow comments in queies, fixes #519 2023-01-19 14:32:37 +01:00
5624b3b2d7 Merge pull request #520 from dyaskur/master
add copy shortcut and default copy type setting
2023-01-16 18:16:13 +01:00
4c16d8c61f refactor: results table setting section 2023-01-16 18:15:49 +01:00
Dyas🍌 Yaskur🍎
9aca89477f feat: add copy shortcut and default copy type setting 2023-01-16 04:49:44 +07:00
0e80e823d0 fix(SQLite): exception saving tables without INT fields length 2023-01-09 10:02:28 +01:00
ff4bc6c39b feat: connection info icons in footer 2023-01-05 17:21:22 +01:00
b4e1e9ac26 Merge branch 'master' of https://github.com/antares-sql/antares 2023-01-02 09:21:03 +01:00
f177c7f1f1 style: minor refactor 2023-01-02 09:21:01 +01:00
6a67c27915 Merge pull request #406 from antares-sql/dependabot/npm_and_yarn/electron-store-8.1.0
build(deps): bump electron-store from 8.0.2 to 8.1.0
2023-01-02 09:19:15 +01:00
374cedba2b Merge pull request #510 from antares-sql/dependabot/npm_and_yarn/pinia-2.0.28
build(deps): bump pinia from 2.0.27 to 2.0.28
2023-01-02 09:18:55 +01:00
dependabot[bot]
de8097c297 build(deps): bump pinia from 2.0.27 to 2.0.28
Bumps [pinia](https://github.com/vuejs/pinia) from 2.0.27 to 2.0.28.
- [Release notes](https://github.com/vuejs/pinia/releases)
- [Commits](https://github.com/vuejs/pinia/compare/pinia@2.0.27...pinia@2.0.28)

---
updated-dependencies:
- dependency-name: pinia
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-01 19:02:46 +00:00
6fa430adf6 fix: ssh connection closed after idle time, fixes #425 2022-12-29 13:04:20 +01:00
0e7b93c2df chore(release): 0.7.1 2022-12-23 09:25:50 +01:00
843c15e428 fix(MySQL): not every connection gets read-only option 2022-12-23 09:24:05 +01:00
2982b6cb96 ci: change macos build target to macos-11 2022-12-22 22:22:17 +01:00
c08946e932 fix: context submenu outside view when near the edge, fixes #506 2022-12-22 11:27:13 +01:00
55c1604e7f refactor(UI): hide selected connection bar when folder preview 2022-12-21 13:16:56 +01:00
d64fbbad0f Merge pull request #505 from antares-sql/all-contributors/add-dyaskur
docs: add dyaskur as a contributor for code
2022-12-20 21:30:13 +01:00
allcontributors[bot]
c88d734bc0 docs: update .all-contributorsrc [skip ci] 2022-12-20 20:29:48 +00:00
allcontributors[bot]
99d94ea92c docs: update README.md [skip ci] 2022-12-20 20:29:47 +00:00
e4f620c5a1 Merge pull request #500 from dyaskur/add_copy_as_table_rows
feat: Copy rows as html table, so we can paste it to spreadsheet
2022-12-20 21:28:54 +01:00
99f9a9e188 refactor: replace string "Table" with localization 2022-12-20 21:26:33 +01:00
Dyas🍌 Yaskur🍎
2236c8fe75 set copy plain text delimiter to tab 2022-12-19 19:07:31 +07:00
b9a097e2f5 Merge pull request #502 from antares-sql/all-contributors/add-dyaskur
docs: add dyaskur as a contributor for translation
2022-12-19 09:00:17 +01:00
allcontributors[bot]
966446afd6 docs: update .all-contributorsrc [skip ci] 2022-12-19 07:59:59 +00:00
allcontributors[bot]
b681adc632 docs: update README.md [skip ci] 2022-12-19 07:59:58 +00:00
15d0158993 Merge pull request #498 from dyaskur/indonesian-language-translation
Add new language: Bahasa Indonesia
2022-12-19 08:58:25 +01:00
e76d324810 chore: minor change in e2e tests 2022-12-19 08:54:20 +01:00
Dyas🍌 Yaskur🍎
897795ddbb fix: bahasa indonesia typos 2022-12-19 07:34:50 +07:00
Dyas🍌 Yaskur🍎
c32f463ea5 feat: Copy rows as html table, so we can paste it to spreadsheet 2022-12-19 05:33:50 +07:00
Dyas🍌 Yaskur🍎
25e1ba4384 Add new language: Bahasa Indonesia 2022-12-19 04:36:10 +07:00
dependabot[bot]
a71ae05c6f build(deps): bump electron-store from 8.0.2 to 8.1.0
Bumps [electron-store](https://github.com/sindresorhus/electron-store) from 8.0.2 to 8.1.0.
- [Release notes](https://github.com/sindresorhus/electron-store/releases)
- [Commits](https://github.com/sindresorhus/electron-store/compare/v8.0.2...v8.1.0)

---
updated-dependencies:
- dependency-name: electron-store
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-18 15:01:12 +00:00
1bd26ceaa6 feat: option to disable selected query execution, closes #477 2022-12-18 15:59:58 +01:00
56a0361ed2 Merge pull request #493 from antares-sql/all-contributors/add-brdtheo
docs: add brdtheo as a contributor for translation
2022-12-12 09:14:39 +01:00
allcontributors[bot]
c0dd3e0941 docs: update .all-contributorsrc [skip ci] 2022-12-12 08:14:23 +00:00
allcontributors[bot]
33ab5d7491 docs: update README.md [skip ci] 2022-12-12 08:14:22 +00:00
72e6a23fd6 Merge pull request #491 from brdtheo/i18n-fix-french-translation
Fix french translation
2022-12-12 09:11:08 +01:00
brdtheo
fd129a2ad1 fix(i18n): add missing keys for french translation 2022-12-07 22:45:44 +01:00
2c63cbc4e8 chore: update dependencies 2022-12-06 12:56:36 +01:00
c9a33936a0 ci: test target os change 2022-12-03 17:53:16 +01:00
a6bdf69a28 fix: connection default icon not change after client change 2022-12-03 17:53:00 +01:00
dd971d70e0 fix(UI): white background dragging connections inside folder on Linux 2022-12-02 09:38:40 +01:00
669d7e8d4d fix: white background dragging connections or tabs on Linux, fixes #486 2022-12-01 15:38:19 +01:00
02f204a01d Merge pull request #485 from SmileYzn/patch-1
Update pt-BR.ts
2022-11-30 17:50:00 +01:00
Cleverson
023d7aa92d Update pt-BR.ts 2022-11-30 13:45:23 -03:00
4ca40c07d6 ci: ubuntu 20.04 as linux actions target 2022-11-30 15:57:48 +01:00
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
124 changed files with 10445 additions and 12769 deletions

View File

@@ -174,6 +174,25 @@
"contributions": [ "contributions": [
"translation" "translation"
] ]
},
{
"login": "brdtheo",
"name": "Théo Billardey",
"avatar_url": "https://avatars.githubusercontent.com/u/48206778?v=4",
"profile": "https://codepen.io/theo-billardey",
"contributions": [
"translation"
]
},
{
"login": "dyaskur",
"name": "Muhammad Dyas Yaskur",
"avatar_url": "https://avatars.githubusercontent.com/u/9539970?v=4",
"profile": "http://yaskur.net",
"contributions": [
"translation",
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -11,7 +11,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-11, ubuntu-20.04, windows-latest]
steps: steps:
- name: Check out Git repository - name: Check out Git repository

View File

@@ -5,7 +5,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

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

View File

@@ -1,4 +1,4 @@
name: Test end-to-end [LINUX] name: Test end-to-end [WINDOWS]
on: push on: push
@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [windows-latest]
steps: steps:
- name: Check out Git repository - name: Check out Git repository

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,187 @@
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.4](https://github.com/antares-sql/antares/compare/v0.7.3...v0.7.4) (2023-02-10)
### Bug Fixes
* tables sort in sidebar ([ddb7ead](https://github.com/antares-sql/antares/commit/ddb7ead0832708713ba4bb2717661b8b93a47e3f))
### [0.7.3](https://github.com/antares-sql/antares/compare/v0.7.2...v0.7.3) (2023-02-09)
### Features
* **SQLite:** added support to INTEGER UNSIGNED ([e7e4913](https://github.com/antares-sql/antares/commit/e7e491340a037b64d6d8e538376415779d54332e))
### Bug Fixes
* longtext edit modal opens when it shouldn't, fixes [#524](https://github.com/antares-sql/antares/issues/524) ([6decba3](https://github.com/antares-sql/antares/commit/6decba316ca46106520cb4dba44409ceb4a4af75))
* select of table type stuck when editing an unknown type ([e8447e5](https://github.com/antares-sql/antares/commit/e8447e56551871a200517bdaa747ae215ad83cf4))
* **SQLite:** error with integer timestamp fields ([2b5e1e7](https://github.com/antares-sql/antares/commit/2b5e1e7b39c25f536b6139a4d01b9f7f17069ea8))
* **SQLite:** triggers disappear after editing related table, fixes [#523](https://github.com/antares-sql/antares/issues/523) ([d934ae1](https://github.com/antares-sql/antares/commit/d934ae1e6c0747698b4973d9cad217379076a6cf))
### [0.7.2](https://github.com/antares-sql/antares/compare/v0.7.1...v0.7.2) (2023-01-22)
### Features
* add copy shortcut and default copy type setting ([9aca894](https://github.com/antares-sql/antares/commit/9aca89477f1fd7b7f55f1e5c290d495c46f61d8e))
* connection info icons in footer ([ff4bc6c](https://github.com/antares-sql/antares/commit/ff4bc6c39b05a827cebde84466814cf246908208))
### Bug Fixes
* allow comments in queies, fixes [#519](https://github.com/antares-sql/antares/issues/519) ([c7ab3b7](https://github.com/antares-sql/antares/commit/c7ab3b77a22e85cee6fb93064eaad5a8e8ad9fd2))
* **SQLite:** exception saving tables without INT fields length ([0e80e82](https://github.com/antares-sql/antares/commit/0e80e823d059dfe24995b5848d88cc84235e6275))
* ssh connection closed after idle time, fixes [#425](https://github.com/antares-sql/antares/issues/425) ([6fa430a](https://github.com/antares-sql/antares/commit/6fa430adf68013a9d0a093031f56dd741bdc0299))
### [0.7.1](https://github.com/antares-sql/antares/compare/v0.7.0...v0.7.1) (2022-12-23)
### Features
* Copy rows as html table, so we can paste it to spreadsheet ([c32f463](https://github.com/antares-sql/antares/commit/c32f463ea5ac3f54cba32929f77442f1e0ba934a))
* option to disable selected query execution, closes [#477](https://github.com/antares-sql/antares/issues/477) ([1bd26ce](https://github.com/antares-sql/antares/commit/1bd26ceaa68fe66f26c76b3b60fa6eeccea91729))
### Bug Fixes
* bahasa indonesia typos ([897795d](https://github.com/antares-sql/antares/commit/897795ddbb4ade2652b0471f18288b8b3aaf0eb9))
* connection default icon not change after client change ([a6bdf69](https://github.com/antares-sql/antares/commit/a6bdf69a281c8614c41274b6dc2f3563aa89c57e))
* context submenu outside view when near the edge, fixes [#506](https://github.com/antares-sql/antares/issues/506) ([c08946e](https://github.com/antares-sql/antares/commit/c08946e932884e5f0253df2545f98315ab7e5219))
* **i18n:** add missing keys for french translation ([fd129a2](https://github.com/antares-sql/antares/commit/fd129a2ad1c3401372c9172b38f4406254d134df))
* **MySQL:** not every connection gets read-only option ([843c15e](https://github.com/antares-sql/antares/commit/843c15e428c4a0412f19a93ab05d2fcbb60da09b))
* **UI:** white background dragging connections inside folder on Linux ([dd971d7](https://github.com/antares-sql/antares/commit/dd971d70e04faf0d5b239586b12e4a9a42407433))
* white background dragging connections or tabs on Linux, fixes [#486](https://github.com/antares-sql/antares/issues/486) ([669d7e8](https://github.com/antares-sql/antares/commit/669d7e8d4d062ed5bdafe1d5cde8ec51a2f68b26))
## [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
@@ -115,30 +116,34 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tr> <tbody>
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Documentation">📖</a></td> <tr>
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=toriphes" title="Code">💻</a></td> <td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt="Fabio Di Stasio"/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td> <td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt="Giulio Ganci"/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=toriphes" title="Code">💻</a></td>
<td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td> <td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt="Christian Ratz"/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td> <td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt="Giuseppe Gigliotti"/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt="Mohd-PH"/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt="hongkfui"/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
</tr> <td align="center"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt="Robin"/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td>
<tr> </tr>
<td align="center"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td> <tr>
<td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt="Daniel Eduardo"/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td> <td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt="Ngô Quốc Đạt"/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td>
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td> <td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt="Isamu Sugiura"/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=kilianstallz" title="Code">💻</a></td> <td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt="Riccardo Sacchetto"/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=wenj91" title="Code">💻</a></td> <td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt="Kilian Stallinger"/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=kilianstallz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt=""/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt="文杰"/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=wenj91" title="Code">💻</a></td>
</tr> <td align="center"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt="goYou"/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td>
<tr> </tr>
<td align="center"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=raliqala" title="Code">💻</a></td> <tr>
<td align="center"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt="Topollo"/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=raliqala" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fred</b></sub></a><br /><a href="#translation-fredatgithub" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt="Cleverson"/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt="fred"/><br /><sub><b>fred</b></sub></a><br /><a href="#translation-fredatgithub" title="Translation">🌍</a></td>
</tr> <td align="center"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt="xaka_xak"/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</a></td>
<td align="center"><a href="https://codepen.io/theo-billardey"><img src="https://avatars.githubusercontent.com/u/48206778?v=4?s=100" width="100px;" alt="Théo Billardey"/><br /><sub><b>Théo Billardey</b></sub></a><br /><a href="#translation-brdtheo" title="Translation">🌍</a></td>
<td align="center"><a href="http://yaskur.net"><img src="https://avatars.githubusercontent.com/u/9539970?v=4?s=100" width="100px;" alt="Muhammad Dyas Yaskur"/><br /><sub><b>Muhammad Dyas Yaskur</b></sub></a><br /><a href="#translation-dyaskur" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=dyaskur" title="Code">💻</a></td>
</tr>
</tbody>
</table> </table>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

16183
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.4",
"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",
@@ -121,44 +121,47 @@
"dependencies": { "dependencies": {
"@electron/remote": "~2.0.1", "@electron/remote": "~2.0.1",
"@faker-js/faker": "~6.1.2", "@faker-js/faker": "~6.1.2",
"@mdi/font": "~7.0.96", "@mdi/font": "~7.1.96",
"@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.14.0",
"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.1.0",
"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.4",
"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.28",
"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": "~12.0.3",
"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.45",
"vue-i18n": "~9.2.0", "vue-i18n": "~9.2.2",
"vuedraggable": "~4.1.0" "vuedraggable": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "~7.15.7", "@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8", "@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7", "@babel/preset-typescript": "~7.16.7",
"@playwright/test": "~1.21.1", "@playwright/test": "~1.28.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",
"@types/ssh2": "~1.11.6",
"@typescript-eslint/eslint-plugin": "~5.18.0", "@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0", "@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33", "@vue/compiler-sfc": "~3.2.33",
@@ -167,8 +170,8 @@
"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": "~22.0.3",
"electron-builder": "~23.0.3", "electron-builder": "~22.10.3",
"eslint": "~7.32.0", "eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3", "eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2", "eslint-plugin-import": "~2.24.2",
@@ -179,17 +182,19 @@
"html-webpack-plugin": "~5.5.0", "html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5", "mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0", "node-loader": "~2.0.0",
"playwright": "~1.21.1", "playwright": "~1.28.1",
"playwright-core": "~1.21.1", "playwright-core": "~1.28.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",
@@ -198,7 +203,12 @@
"vue-loader": "~16.8.3", "vue-loader": "~16.8.3",
"webpack": "~5.72.0", "webpack": "~5.72.0",
"webpack-cli": "~4.9.1", "webpack-cli": "~4.9.1",
"webpack-dev-server": "~4.4.0", "webpack-dev-server": "~4.11.1",
"xvfb-maybe": "~0.2.1" "xvfb-maybe": "~0.2.1"
},
"overrides": {
"ssh2-promise": {
"ssh2": "github:Fabio286/ssh2"
}
} }
} }

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

@@ -6,7 +6,7 @@ export default [
types: [ types: [
{ {
name: 'INT', name: 'INT',
length: true, length: 10,
collation: false, collation: false,
unsigned: true, unsigned: true,
zerofill: true zerofill: true
@@ -18,6 +18,13 @@ export default [
unsigned: true, unsigned: true,
zerofill: true zerofill: true
}, },
{
name: 'INTEGER UNSIGNED',
length: true,
collation: false,
unsigned: true,
zerofill: true
},
{ {
name: 'BIGINT', name: 'BIGINT',
length: true, length: true,

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
@@ -84,13 +85,11 @@ export interface TableInfos {
name: string; name: string;
type: string; type: string;
rows: number; rows: number;
created: Date;
updated: Date;
engine: string; engine: string;
comment: string; comment: string;
size: number | false; size: number | false;
autoIncrement: number;
collation: string; collation: string;
autoIncrement?: boolean;
} }
export type TableOptions = Partial<TableInfos>; export type TableOptions = Partial<TableInfos>;

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,69 +1,138 @@
export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string | number; context?: 'tab' }} = {
'run-or-reload': { l18n: 'message.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'message.openNewTab', context: 'tab' },
'close-tab': { l18n: 'message.closeTab', context: 'tab' },
'format-query': { l18n: 'message.formatQuery', context: 'tab' },
'kill-query': { l18n: 'message.killQuery', context: 'tab' },
'query-history': { l18n: 'message.queryHistory', context: 'tab' },
'clear-query': { l18n: 'message.clearQuery', context: 'tab' },
'next-tab': { l18n: 'message.nextTab' },
'prev-tab': { l18n: 'message.previousTab' },
'open-all-connections': { l18n: 'message.openAllConnections' },
'open-filter': { l18n: 'message.openFilter' },
'next-page': { l18n: 'message.nextResultsPage' },
'prev-page': { l18n: 'message.previousResultsPage' },
'toggle-console': { l18n: 'message.toggleConsole' },
'save-content': { l18n: 'message.saveContent' },
'create-connection': { l18n: 'message.createNewConnection' },
'open-settings': { l18n: 'message.openSettings' },
'open-scratchpad': { l18n: 'message.openScratchpad' }
};
interface ShortcutRecord { interface ShortcutRecord {
event: string; event: string;
keys: Electron.Accelerator[]; keys: Electron.Accelerator[] | string[];
description: string; /** Needed for default shortcuts */
os: NodeJS.Platform[]; 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',
event: `select-tab-${i}`, l18nParam: i
keys: [`CommandOrControl+${i}`], };
description: `Select tab number ${i}`,
os: ['darwin', 'linux', 'win32'] shortcuts.push({
}); event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`],
os: ['darwin', 'linux', 'win32']
});
} }
export { shortcuts, ShortcutRecord }; export { shortcuts, ShortcutRecord };

View File

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

View File

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

View File

@@ -62,7 +62,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-structure', async (event, params) => { ipcMain.handle('get-structure', async (event, params) => {
try { try {
const structure = await connections[params.uid].getStructure( const structure: unknown = await connections[params.uid].getStructure(
params.schemas params.schemas
); );
@@ -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 };
} }
@@ -160,8 +160,7 @@ export default (connections: {[key: string]: antares.Client}) => {
details: true, details: true,
schema, schema,
tabUid, tabUid,
autocommit, autocommit
comments: false
}); });
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,7 +193,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof orgRow[key] === 'string') if (typeof orgRow[key] === 'string')
orgRow[key] = `'${orgRow[key]}'`; orgRow[key] = `'${orgRow[key]}'`;
orgRow[key] = `= ${orgRow[key]}`; if (orgRow[key] === null)
orgRow[key] = `IS ${orgRow[key]}`;
else
orgRow[key] = `= ${orgRow[key]}`;
} }
await connections[params.uid] await connections[params.uid]
@@ -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

@@ -147,7 +147,14 @@ export class MySQLClient extends AntaresCore {
if (this._params.ssh) { if (this._params.ssh) {
try { try {
this._ssh = new SSH2Promise({ ...this._params.ssh }); if (this._params.ssh.password === '') delete this._params.ssh.password;
if (this._params.ssh.passphrase === '') delete this._params.ssh.passphrase;
this._ssh = new SSH2Promise({
...this._params.ssh,
keepaliveInterval: 30*60*1000,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});
const tunnel = await this._ssh.addTunnel({ const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host, remoteAddr: this._params.host,
@@ -225,6 +232,9 @@ export class MySQLClient extends AntaresCore {
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`); await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`);
if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY');
connection.on('connection', conn => { connection.on('connection', conn => {
if (this._params.readonly) if (this._params.readonly)
conn.query('SET SESSION TRANSACTION READ ONLY'); conn.query('SET SESSION TRANSACTION READ ONLY');
@@ -244,25 +254,15 @@ export class MySQLClient extends AntaresCore {
async getStructure (schemas: Set<string>) { async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface ShowTableResult { interface ShowTableResult {
Db?: string; TABLE_SCHEMA?: string;
Name: string; TABLE_NAME: string;
Engine: string; TABLE_TYPE: string;
Version: number; TABLE_ROWS: number;
Row_format: string; ENGINE: string;
Rows: number; DATA_LENGTH: number;
Avg_row_length: number; INDEX_LENGTH: number;
Data_length: number; TABLE_COLLATION: string;
Max_data_length: number; TABLE_COMMENT: string;
Index_length: number;
Data_free: number;
Auto_increment: number;
Create_time: Date;
Update_time: Date;
Check_time?: number;
Collation: string;
Checksum?: number;
Create_options: string;
Comment: string;
} }
interface ShowTriggersResult { interface ShowTriggersResult {
@@ -299,10 +299,25 @@ export class MySQLClient extends AntaresCore {
for (const db of filteredDatabases) { for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue; if (!schemas.has(db.Database)) continue;
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`SHOW TABLE STATUS FROM \`${db.Database}\``); let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`
SELECT
TABLE_NAME,
TABLE_TYPE,
ENGINE,
DATA_LENGTH,
INDEX_LENGTH,
TABLE_COMMENT,
TABLE_COLLATION,
CREATE_TIME,
UPDATE_TIME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = "${db.Database}"
ORDER BY TABLE_NAME
`);
if (tables.length) { if (tables.length) {
tables = tables.map(table => { tables = tables.map(table => {
table.Db = db.Database; table.TABLE_SCHEMA = db.Database;
return table; return table;
}); });
tablesArr.push(...tables); tablesArr.push(...tables);
@@ -321,9 +336,9 @@ export class MySQLClient extends AntaresCore {
return filteredDatabases.map(db => { return filteredDatabases.map(db => {
if (schemas.has(db.Database)) { if (schemas.has(db.Database)) {
// TABLES // TABLES
const remappedTables: antares.TableInfos[] = tablesArr.filter(table => table.Db === db.Database).map(table => { const remappedTables: antares.TableInfos[] = tablesArr.filter(table => table.TABLE_SCHEMA === db.Database).map(table => {
let tableType; let tableType;
switch (table.Comment) { switch (table.TABLE_TYPE) {
case 'VIEW': case 'VIEW':
tableType = 'view'; tableType = 'view';
break; break;
@@ -332,20 +347,17 @@ export class MySQLClient extends AntaresCore {
break; break;
} }
const tableSize = Number(table.Data_length) + Number(table.Index_length); const tableSize = Number(table.DATA_LENGTH) + Number(table.INDEX_LENGTH);
schemaSize += tableSize; schemaSize += tableSize;
return { return {
name: table.Name, name: table.TABLE_NAME,
type: tableType, type: tableType,
rows: table.Rows, rows: table.TABLE_ROWS,
created: table.Create_time, engine: table.ENGINE,
updated: table.Update_time, comment: table.TABLE_COMMENT,
engine: table.Engine,
comment: table.Comment,
size: tableSize, size: tableSize,
autoIncrement: table.Auto_increment, collation: table.TABLE_COLLATION
collation: table.Collation
}; };
}); });
@@ -469,7 +481,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 +957,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 +1605,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;
@@ -1598,7 +1616,7 @@ export class MySQLClient extends AntaresCore {
if (!field || Array.isArray(field)) if (!field || Array.isArray(field))
return undefined; return undefined;
const type = this._getType(field); const type = this._getType(field as undefined);
return { return {
name: field.orgName, name: field.orgName,

View File

@@ -162,7 +162,11 @@ export class PostgreSQLClient extends AntaresCore {
if (this._params.ssh) { if (this._params.ssh) {
try { try {
this._ssh = new SSH2Promise({ ...this._params.ssh }); this._ssh = new SSH2Promise({
...this._params.ssh,
keepaliveInterval: 30*60*1000,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});
const tunnel = await this._ssh.addTunnel({ const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host, remoteAddr: this._params.host,
@@ -540,11 +544,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 +1426,8 @@ export class PostgreSQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
try { try {
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query }); const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });

View File

@@ -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: ''
}); });
} }
@@ -348,6 +344,20 @@ export class SQLiteClient extends AntaresCore {
const tmpName = `Antares_${table}_tmp`; const tmpName = `Antares_${table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`); await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`);
// Get table triggers before drop
const { rows: triggers } = await this.raw(`SELECT * FROM "${schema}".sqlite_master WHERE type='trigger' AND tbl_name = '${table}'`);
const remappedTriggers = triggers.map((row) => {
return {
schema,
sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
name: row.name,
table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0],
activation: row.sql.match(/(BEFORE|AFTER)/gs)[0],
event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0]
};
});
await this.dropTable(params); await this.dropTable(params);
const createTableParams = { const createTableParams = {
@@ -380,6 +390,11 @@ export class SQLiteClient extends AntaresCore {
await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`); await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`);
await this.dropTable({ schema: schema, table: tmpName }); await this.dropTable({ schema: schema, table: tmpName });
// Recreates triggers
for (const trigger of remappedTriggers)
await this.createTrigger(trigger);
await this.raw('PRAGMA foreign_keys = 1'); await this.raw('PRAGMA foreign_keys = 1');
await this.raw('COMMIT'); await this.raw('COMMIT');
} }
@@ -628,7 +643,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
@@ -675,8 +691,8 @@ export class SQLiteClient extends AntaresCore {
if ([...TIME, ...DATETIME].includes(parsedType)) { if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryAllResult.find(res => res[field.name] !== null); const firstNotNull = queryAllResult.find(res => res[field.name] !== null);
if (firstNotNull && firstNotNull[field.name].includes('.')) if (firstNotNull && String(firstNotNull[field.name]).includes('.'))
length = firstNotNull[field.name].split('.').pop().length; length = String(firstNotNull[field.name]).split('.').pop().length;
} }
return { return {

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';
@@ -38,6 +37,7 @@ async function createMainWindow () {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
devTools: isDevelopment,
spellcheck: false spellcheck: false
}, },
autoHideMenuBar: true, autoHideMenuBar: true,
@@ -86,7 +86,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 +124,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 +143,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 +178,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

@@ -1,5 +1,5 @@
<template> <template>
<div class="context" :class="{'bottom': isBottom}"> <div class="context" :class="[{ 'bottom': isBottom }, { 'right': isRight }]">
<a <a
class="context-overlay" class="context-overlay"
@click="close" @click="close"
@@ -19,9 +19,10 @@
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue'; import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
const contextContent: Ref<HTMLDivElement> = ref(null); const contextContent: Ref<HTMLDivElement> = ref(null);
const contextSize: Ref<DOMRect> = ref(null); const contextSize: Ref<{height: number; width: number; subHeight?: number; subWidth?: number}> = ref(null);
const isBottom: Ref<boolean> = ref(false); const isBottom: Ref<boolean> = ref(false);
const props = defineProps<{contextEvent: MouseEvent}>(); const isRight: Ref<boolean> = ref(false);
const props = defineProps<{ contextEvent: MouseEvent }>();
const emit = defineEmits(['close-context']); const emit = defineEmits(['close-context']);
const position = computed(() => { const position = computed(() => {
@@ -39,8 +40,16 @@ const position = computed(() => {
isBottom.value = true; isBottom.value = true;
} }
if (clientX + contextSize.value.width + 5 >= window.innerWidth) if (clientY + contextSize.value.subHeight + contextSize.value.height >= window.innerHeight)
isBottom.value = true;
if (clientX + contextSize.value.width + 5 >= window.innerWidth) {
leftCord = `${clientX - contextSize.value.width}px`; leftCord = `${clientX - contextSize.value.width}px`;
isRight.value = true;
}
if (clientX + contextSize.value.subWidth + contextSize.value.width >= window.innerWidth)
isRight.value = true;
} }
} }
@@ -62,8 +71,20 @@ const onKey = (e: KeyboardEvent) => {
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (contextContent.value) if (contextContent.value) {
contextSize.value = contextContent.value.getBoundingClientRect(); contextSize.value = contextContent.value.getBoundingClientRect();
const submenus = contextContent.value.querySelectorAll<HTMLDivElement>('.context-submenu');
for (const submenu of submenus) {
const submenuSize = submenu.getBoundingClientRect();
if (!contextSize.value.subHeight || submenuSize.height > contextSize.value.subHeight)
contextSize.value.subHeight = submenuSize.height;
if (!contextSize.value.subWidth || submenuSize.width > contextSize.value.subWidth)
contextSize.value.subWidth = submenuSize.width;
}
}
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -73,88 +94,94 @@ onBeforeUnmount(() => {
<style lang="scss"> <style lang="scss">
.context { .context {
display: flex; display: flex;
font-size: 16px; font-size: 16px;
z-index: 400; z-index: 400;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
position: fixed; position: fixed;
height: 100vh; height: 100vh;
right: 0; right: 0;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
&:not(.bottom) .context-submenu { &:not(.bottom) .context-submenu {
top: -0.2rem; top: -0.2rem;
} }
&.bottom .context-submenu { &.bottom .context-submenu {
bottom: -0.2rem; bottom: -0.2rem;
} }
.context-container { &:not(.right) .context-submenu {
min-width: 100px; left: 100%;
z-index: 10; }
padding: 0;
background: #1d1d1d;
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
display: flex;
flex-direction: column;
position: absolute;
pointer-events: initial;
.context-element { &.right .context-submenu {
display: flex; right: 100%;
align-items: center; }
margin: 0.2rem;
padding: 0.1rem 0.3rem; .context-container {
min-width: 100px;
z-index: 10;
padding: 0;
background: #1d1d1d;
border-radius: $border-radius; border-radius: $border-radius;
cursor: pointer; border: 1px solid $bg-color-light-dark;
justify-content: space-between; display: flex;
position: relative; flex-direction: column;
white-space: nowrap; position: absolute;
pointer-events: initial;
.context-submenu { .context-element {
border-radius: $border-radius; display: flex;
border: 1px solid $bg-color-light-dark; align-items: center;
opacity: 0; margin: 0.2rem;
visibility: hidden; padding: 0.1rem 0.3rem;
transition: opacity 0.2s; border-radius: $border-radius;
position: absolute; cursor: pointer;
left: 100%; justify-content: space-between;
min-width: 100px; position: relative;
background: #1d1d1d; white-space: nowrap;
.context-submenu {
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s;
position: absolute;
min-width: 100px;
background: #1d1d1d;
}
&:hover {
.context-submenu {
display: block;
visibility: visible;
opacity: 1;
}
}
} }
}
&:hover { .context-overlay {
.context-submenu { background: transparent;
display: block; bottom: 0;
visibility: visible; cursor: default;
opacity: 1; display: block;
} left: 0;
} position: absolute;
} right: 0;
} top: 0;
}
.context-overlay {
background: transparent;
bottom: 0;
cursor: default;
display: block;
left: 0;
position: absolute;
right: 0;
top: 0;
}
} }
.disabled { .disabled {
pointer-events: none; pointer-events: none;
filter: grayscale(100%); filter: grayscale(100%);
opacity: 0.5; opacity: 0.5;
} }
</style> </style>

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)) {
return { type: 'number', mask: false }; if (IS_BIGINT.includes(props.type))
return { type: 'text', mask: false };
else
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')"
@@ -112,7 +99,7 @@
</div> </div>
<div class="panel-footer text-center py-0"> <div class="panel-footer text-center py-0">
<div v-if="connection.ssl" class="chip bg-success mt-2"> <div v-if="connection.ssl" class="chip bg-success mt-2">
<i class="mdi mdi-lock mdi-18px mr-1" /> <i class="mdi mdi-shield-key mdi-18px mr-1" />
SSL SSL
</div> </div>
<div v-if="connection.ssh" class="chip bg-success mt-2"> <div v-if="connection.ssh" class="chip bg-success mt-2">
@@ -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"
@@ -79,7 +86,7 @@
/> />
</div> </div>
<div class="col-4 col-sm-12 px-2 p-vcentered"> <div class="col-4 col-sm-12 px-2 p-vcentered">
<small class="d-block" style="line-height: 1.1; font-size: 70%;"> <small class="d-block" :style="'line-height: 1.1; font-size: 70%;'">
{{ t('message.missingOrIncompleteTranslation') }}<br> {{ t('message.missingOrIncompleteTranslation') }}<br>
<a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('message.findOutHowToContribute') }}</a> <a class="text-bold c-hand" @click="openOutside('https://github.com/antares-sql/antares/wiki/Translate-Antares')">{{ t('message.findOutHowToContribute') }}</a>
</small> </small>
@@ -189,6 +196,41 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.executeSelectedQuery') }}
</label>
</div>
<div class="col-3 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleExecuteSelected">
<input type="checkbox" :checked="selectedExecuteSelected">
<i class="form-icon" />
</label>
</div>
</div>
</div>
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
{{ t('word.resultsTable') }}
</div>
<div class="column col-12 col-sm-12 columns">
<div class="form-group column col-12">
<div class="col-5 col-sm-12">
<label class="form-label">
{{ t('message.defaultCopyType') }}
</label>
</div>
<div class="col-3 col-sm-12">
<BaseSelect
v-model="defaultCopyType"
class="form-select"
:options="copyTypes"
option-track-by="code"
option-label="name"
@change="changeDefaultCopyType(defaultCopyType)"
/>
</div>
</div>
</div> </div>
</form> </form>
</div> </div>
@@ -232,7 +274,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 +286,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 +345,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 +392,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';
@@ -347,6 +414,8 @@ const {
dataTabLimit: pageSize, dataTabLimit: pageSize,
autoComplete: selectedAutoComplete, autoComplete: selectedAutoComplete,
lineWrap: selectedLineWrap, lineWrap: selectedLineWrap,
executeSelected: selectedExecuteSelected,
defaultCopyType: selectedCopyType,
notificationsTimeout, notificationsTimeout,
restoreTabs, restoreTabs,
disableBlur, disableBlur,
@@ -366,10 +435,12 @@ const {
changeDisableScratchpad, changeDisableScratchpad,
changeAutoComplete, changeAutoComplete,
changeLineWrap, changeLineWrap,
changeExecuteSelected,
changeApplicationTheme, changeApplicationTheme,
changeEditorTheme, changeEditorTheme,
changeEditorFontSize, changeEditorFontSize,
updateNotificationsTimeout updateNotificationsTimeout,
changeDefaultCopyType
} = settingsStore; } = settingsStore;
const { const {
hideSettingModal: closeModal, hideSettingModal: closeModal,
@@ -384,7 +455,30 @@ 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 defaultCopyType: Ref<string> = ref(null);
const localPageSize: Ref<number> = ref(null);
const localTimeout: Ref<number> = ref(null);
const localEditorTheme: Ref<string> = ref(null);
const selectedTab: Ref<string> = ref('general');
const editorThemes = computed(() => [
{ {
group: t('word.light'), group: t('word.light'),
themes: [ themes: [
@@ -432,28 +526,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 = [];
@@ -463,6 +536,14 @@ const locales = computed(() => {
return locales; return locales;
}); });
const copyTypes = computed(() => [
{ code: 'cell', name: t('word.cell') },
{ code: 'html', name: t('word.table') },
{ code: 'json', name: 'JSON' },
{ code: 'csv', name: 'CSV' },
{ code: 'sql', name: 'SQL insert' }
]);
const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value)); const hasUpdates = computed(() => ['available', 'downloading', 'downloaded', 'link'].includes(updateStatus.value));
const workspace = computed(() => { const workspace = computed(() => {
@@ -517,7 +598,12 @@ const toggleLineWrap = () => {
changeLineWrap(!selectedLineWrap.value); changeLineWrap(!selectedLineWrap.value);
}; };
const toggleExecuteSelected = () => {
changeExecuteSelected(!selectedExecuteSelected.value);
};
localLocale.value = selectedLocale.value; localLocale.value = selectedLocale.value;
defaultCopyType.value = selectedCopyType.value;
localPageSize.value = pageSize.value as number; localPageSize.value = pageSize.value as number;
localTimeout.value = notificationsTimeout.value as number; localTimeout.value = notificationsTimeout.value as number;
localEditorTheme.value = editorTheme.value as string; localEditorTheme.value = editorTheme.value as string;

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;
setCustomCompleter(); setTimeout(() => {
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 && coveredElement !== element.uid }"
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,451 @@
<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;
&.ghost {
background: $bg-color-light-dark;
&.selected::before {
height: 0;
position: absolute;
}
}
&.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,18 +1,34 @@
<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">
<i class="mdi mdi-18px mdi-database mr-1" /> <i class="mdi mdi-18px mdi-database mr-1" />
<small>{{ versionString }}</small> <small>{{ versionString }}</small>
</li> </li>
<li v-if="connectionInfos && connectionInfos.readonly" class="footer-element">
<i class="mdi mdi-18px mdi-lock mr-1" />
<small>{{ t('message.readOnlyMode') }}</small>
</li>
<li v-if="connectionInfos && connectionInfos.ssl" class="footer-element">
<i class="mdi mdi-18px mdi-shield-key mr-1" />
<small>SSL</small>
</li>
<li v-if="connectionInfos && connectionInfos.ssh" class="footer-element">
<i class="mdi mdi-18px mdi-console-network mr-1" />
<small>SSH</small>
</li>
</ul> </ul>
</div> </div>
<div class="footer-right-elements"> <div class="footer-right-elements">
<ul class="footer-elements"> <ul class="footer-elements">
<li <li
v-if="workspace?.connectionStatus === 'connected' " v-if="workspace?.connectionStatus === 'connected'"
class="footer-element footer-link" class="footer-element footer-link"
@click="toggleConsole()" @click="toggleConsole()"
> >
@@ -50,6 +66,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 +79,20 @@ 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, getConnectionByUid } = connectionsStore;
const workspace = computed(() => getWorkspace(workspaceUid.value)); const workspace = computed(() => getWorkspace(workspaceUid.value));
const footerColor = computed(() => getConnectionFolder(workspaceUid.value)?.color || '#E36929');
const connectionInfos = computed(() => getConnectionByUid(workspaceUid.value));
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"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" /> <div class="settingbar-element-icon-wrapper">
<span class="ex-tooltip-content">{{ t('message.allConnections') }} (Shift+CTRL+Space)</span> <i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" />
</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"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" /> <div class="settingbar-element-icon-wrapper">
<span class="ex-tooltip-content">{{ t('message.addConnection') }}</span> <i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
</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,137 +150,143 @@ 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;
flex-direction: column; flex-direction: column;
// justify-content: space-between; // justify-content: space-between;
align-items: center; align-items: center;
padding: 0; padding: 0;
z-index: 9; z-index: 9;
.settingbar-top-elements { .settingbar-top-elements {
overflow-x: hidden; overflow-x: hidden;
overflow-y: overlay; overflow-y: overlay;
// max-height: calc((100vh - 3.5rem) - #{$excluding-size}); // max-height: calc((100vh - 3.5rem) - #{$excluding-size});
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 3px; width: 3px;
} }
} }
.settingbar-bottom-elements { .settingbar-bottom-elements {
z-index: 1; z-index: 1;
margin-top: auto; margin-top: auto;
} }
.settingbar-elements { .settingbar-elements {
list-style: none; list-style: none;
text-align: center; text-align: center;
width: $settingbar-width; width: $settingbar-width;
padding: 0; padding: 0;
margin: 0; margin: 0;
li {
margin: 0;
background: $bg-color-light-dark;
&.ghost {
border-radius: $border-radius;
.settingbar-element {
&.selected::before {
height: 0;
width: 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;
} }
&.selected { &.selected {
opacity: 1; opacity: 1;
&::before { &::before {
height: $settingbar-width; height: calc(#{$settingbar-width} - 0.4rem);
} }
} }
&::before { &::before {
content: ""; content: "";
height: 0; height: 0;
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;
background-color: $primary-color; background-color: 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%;
&.badge::after { .settingbar-element-icon {
bottom: -10px; &.badge::after {
right: 0; top: 10px;
position: absolute; right: -6px;
} position: absolute;
}
&.badge-update::after { &.badge-update::after {
bottom: initial; bottom: initial;
} }
} }
.settingbar-element-pin { .settingbar-element-name {
margin: 0 auto; 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;
}
&::before { .settingbar-element-pin {// <- Dead
font: normal normal normal 14px/1 "Material Design Icons"; margin: 0 auto;
content: "\F0403";
color: $body-font-color-dark; &::before {
transform: rotate(45deg); font: normal normal normal 14px/1 "Material Design Icons";
opacity: 0.25; content: "\F0403";
bottom: -8px; color: $body-font-color-dark;
left: -4px; transform: rotate(45deg);
position: absolute; opacity: 0.25;
} top: -8px;
} left: -10px;
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>
@@ -45,7 +45,7 @@ const workspace = computed(() => {
changeBreadcrumbs({ schema: workspace.value.breadcrumbs.schema }); changeBreadcrumbs({ schema: workspace.value.breadcrumbs.schema });
</script> </script>
<style scoped> <style lang="scss" scoped>
.empty { .empty {
height: 100%; height: 100%;
border-radius: 0; border-radius: 0;

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 saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -341,19 +337,19 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@@ -7,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 saveChanges();
if (isChanged.value)
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 saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -298,19 +294,19 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged || !isValid" :disabled="!isChanged || !isValid"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -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();
@@ -311,9 +312,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,
@@ -420,14 +422,10 @@ const foreignsUpdate = (foreigns: TableForeign[]) => {
localKeyUsage.value = foreigns; localKeyUsage.value = foreigns;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -447,18 +445,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 saveChanges();
if (isChanged.value)
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 saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
originalFunction.value = { originalFunction.value = {
@@ -237,12 +233,12 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
@@ -255,6 +251,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -115,6 +114,7 @@ import BaseLoader from '@/components/BaseLoader.vue';
import QueryEditor from '@/components/QueryEditor.vue'; import QueryEditor from '@/components/QueryEditor.vue';
import Views from '@/ipc-api/Views'; import Views from '@/ipc-api/Views';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -216,14 +216,10 @@ const resizeQueryEditor = () => {
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
@@ -259,12 +255,12 @@ setTimeout(() => {
resizeQueryEditor(); resizeQueryEditor();
}, 50); }, 50);
window.addEventListener('keydown', onKey);
onMounted(() => { onMounted(() => {
if (props.isSelected) if (props.isSelected)
changeBreadcrumbs({ schema: props.schema }); changeBreadcrumbs({ schema: props.schema });
ipcRenderer.on('save-content', saveContentListener);
setTimeout(() => { setTimeout(() => {
firstInput.value.focus(); firstInput.value.focus();
}, 100); }, 100);
@@ -277,7 +273,7 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -207,6 +206,7 @@ import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { AlterFunctionParams, FunctionInfos, FunctionParam } from 'common/interfaces/antares'; import { AlterFunctionParams, FunctionInfos, FunctionParam } from 'common/interfaces/antares';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -407,14 +407,10 @@ const hideAskParamsModal = () => {
isAskingParameters.value = false; isAskingParameters.value = false;
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -457,11 +453,12 @@ watch(consoleHeight, () => {
(async () => { (async () => {
await getFunctionData(); await getFunctionData();
queryEditor.value.editor.session.setValue(localFunction.value.sql); queryEditor.value.editor.session.setValue(localFunction.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -469,6 +466,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -179,6 +178,7 @@ import BaseLoader from '@/components/BaseLoader.vue';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue'; import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal.vue';
import ModalAskParameters from '@/components/ModalAskParameters.vue'; import ModalAskParameters from '@/components/ModalAskParameters.vue';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -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 saveChanges();
if (isChanged.value)
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 saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -345,11 +341,12 @@ watch(consoleHeight, () => {
(async () => { (async () => {
await getSchedulerData(); await getSchedulerData();
queryEditor.value.editor.session.setValue(localScheduler.value.sql); queryEditor.value.editor.session.setValue(localScheduler.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -357,7 +354,7 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -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,11 +324,13 @@ 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
acc[curr.name] = acc[curr.name] || []; .filter((index: TableIndex) => index.type !== 'FOREIGN KEY')
acc[curr.name].push(curr); .reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => {
return acc; acc[curr.name] = acc[curr.name] || [];
}, {}); acc[curr.name].push(curr);
return acc;
}, {});
originalIndexes.value = Object.keys(indexesObj).map(index => { originalIndexes.value = Object.keys(indexesObj).map(index => {
return { return {
@@ -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 saveChanges();
if (isChanged.value)
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"
/> />
@@ -409,7 +410,7 @@ const types = computed(() => {
const types = [...props.dataTypes]; const types = [...props.dataTypes];
if (!isInDataTypes.value) if (!isInDataTypes.value)
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(types as any).unshift({ name: props.row }); (types as any).unshift({ name: props.row.type });
return types; return types;
}); });
@@ -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 saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -359,11 +355,12 @@ watch(consoleHeight, () => {
(async () => { (async () => {
await getTriggerData(); await getTriggerData();
queryEditor.value.editor.session.setValue(localTrigger.value.sql); queryEditor.value.editor.session.setValue(localTrigger.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -371,6 +368,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" /> <i class="mdi mdi-24px mdi-content-save mr-1" />
@@ -93,6 +92,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { AlterFunctionParams, TriggerFunctionInfos } from 'common/interfaces/antares'; import { AlterFunctionParams, TriggerFunctionInfos } from 'common/interfaces/antares';
import { ipcRenderer } from 'electron';
const { t } = useI18n(); const { t } = useI18n();
@@ -223,14 +223,10 @@ const resizeQueryEditor = () => {
} }
}; };
const onKey = (e: KeyboardEvent) => { const saveContentListener = () => {
if (props.isSelected) { const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
e.stopPropagation(); if (props.isSelected && !hasModalOpen && isChanged.value)
if (e.ctrlKey && e.key === 's') { // CTRL + S saveChanges();
if (isChanged.value)
saveChanges();
}
}
}; };
watch(() => props.schema, async () => { watch(() => props.schema, async () => {
@@ -264,11 +260,12 @@ watch(isChanged, (val) => {
(async () => { (async () => {
await getFunctionData(); await getFunctionData();
queryEditor.value.editor.session.setValue(localFunction.value.sql); queryEditor.value.editor.session.setValue(localFunction.value.sql);
window.addEventListener('keydown', onKey);
})(); })();
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeQueryEditor); window.addEventListener('resize', resizeQueryEditor);
ipcRenderer.on('save-content', saveContentListener);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -276,6 +273,6 @@ onUnmounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey); ipcRenderer.removeListener('save-content', saveContentListener);
}); });
</script> </script>

View File

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

View File

@@ -3,11 +3,6 @@
v-show="isSelected" v-show="isSelected"
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0" class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0" tabindex="0"
@keydown.f5="runQuery(query)"
@keydown.k="killTabQuery"
@keydown.ctrl.alt.w="clear"
@keydown.ctrl.b="beautify"
@keydown.ctrl.g="openHistoryModal"
> >
<div class="workspace-query-runner column col-12"> <div class="workspace-query-runner column col-12">
<QueryEditor <QueryEditor
@@ -40,7 +35,6 @@
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:class="{'loading':isQuering}" :class="{'loading':isQuering}"
:disabled="!query" :disabled="!query"
title="F5"
@click="runQuery(query)" @click="runQuery(query)"
> >
<i class="mdi mdi-24px mdi-play pr-1" /> <i class="mdi mdi-24px mdi-play pr-1" />
@@ -68,7 +62,6 @@
<button <button
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:disabled="!query || isQuering" :disabled="!query || isQuering"
title="CTRL+W"
@click="clear()" @click="clear()"
> >
<i class="mdi mdi-24px mdi-delete-sweep pr-1" /> <i class="mdi mdi-24px mdi-delete-sweep pr-1" />
@@ -80,7 +73,6 @@
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="!query || isQuering" :disabled="!query || isQuering"
title="CTRL+B"
@click="beautify()" @click="beautify()"
> >
<i class="mdi mdi-24px mdi-brush pr-1" /> <i class="mdi mdi-24px mdi-brush pr-1" />
@@ -89,7 +81,6 @@
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="isQuering" :disabled="isQuering"
title="CTRL+G"
@click="openHistoryModal()" @click="openHistoryModal()"
> >
<i class="mdi mdi-24px mdi-history pr-1" /> <i class="mdi mdi-24px mdi-history pr-1" />
@@ -206,6 +197,8 @@ 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';
import { useSettingsStore } from '@/stores/settings';
const { t } = useI18n(); const { t } = useI18n();
@@ -230,6 +223,7 @@ const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const { consoleHeight } = storeToRefs(useConsoleStore()); const { consoleHeight } = storeToRefs(useConsoleStore());
const { executeSelected } = storeToRefs(useSettingsStore());
const { const {
getWorkspace, getWorkspace,
@@ -298,8 +292,10 @@ const runQuery = async (query: string) => {
if (!query || isQuering.value) return; if (!query || isQuering.value) return;
isQuering.value = true; isQuering.value = true;
const selectedQuery = queryEditor.value.editor.getSelectedText(); if (executeSelected.value) {
if (selectedQuery) query = selectedQuery; const selectedQuery = queryEditor.value.editor.getSelectedText();
if (selectedQuery) query = selectedQuery;
}
clearTabData(); clearTabData();
queryTable.value.resetSort(); queryTable.value.resetSort();
@@ -427,7 +423,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 +496,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 +550,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();
@@ -140,7 +142,7 @@ const settingsStore = useSettingsStore();
const consoleStore = useConsoleStore(); const consoleStore = useConsoleStore();
const { getWorkspace } = useWorkspacesStore(); const { getWorkspace } = useWorkspacesStore();
const { dataTabLimit: pageSize } = storeToRefs(settingsStore); const { dataTabLimit: pageSize, defaultCopyType } = storeToRefs(settingsStore);
const { consoleHeight } = storeToRefs(consoleStore); const { consoleHeight } = storeToRefs(consoleStore);
@@ -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),
@@ -461,6 +465,89 @@ const copyRow = (format: string) => {
navigator.clipboard.writeText(csv.join('\n')); navigator.clipboard.writeText(csv.join('\n'));
} }
else if (format === 'html') {
const arrayContent = new Array<string[]>();
if (!Array.isArray(contentToCopy)) contentToCopy = [contentToCopy];
for (const row of contentToCopy)
arrayContent.push(Object.values(row));
const htmlContent = createHtmlTable(arrayContent);
const htmlBlob = new Blob([htmlContent.outerHTML], { type: 'text/html' });
const textBlob = new Blob([arrayContent.map(row => row.join('\t')).join('\n')], { type: 'text/plain' });
const data = [new ClipboardItem({
'text/plain': textBlob,
'text/html': htmlBlob
})];
navigator.clipboard.write(data);
}
};
const createHtmlTable = (tableData: Array<string[]>) => {
const table = document.createElement('table');
const tableBody = document.createElement('tbody');
tableData.forEach(function (rowData: Array<string>) {
const row = document.createElement('tr');
rowData.forEach(function (cellData: string) {
const cell = document.createElement('td');
cell.appendChild(document.createTextNode(cellData));
row.appendChild(cell);
});
tableBody.appendChild(row);
});
table.appendChild(tableBody);
return table;
};
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 = () => {
@@ -529,11 +616,11 @@ const deselectRows = (e: Event) => {
if (!isDeleteConfirmModal.value) if (!isDeleteConfirmModal.value)
selectedRows.value = []; selectedRows.value = [];
selectedField.value = null;
if (e.type === 'blur') if (e.type === 'blur')
hasFocus.value = false; hasFocus.value = false;
} }
selectedField.value = null;
}; };
const contextMenu = (event: MouseEvent, cell: any) => { const contextMenu = (event: MouseEvent, cell: any) => {
@@ -610,8 +697,20 @@ const onKey = async (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyA' && !e.altKey) if ((e.ctrlKey || e.metaKey) && e.code === 'KeyA' && !e.altKey)
selectAllRows(e); selectAllRows(e);
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyC' && !e.altKey) {
const copyType = defaultCopyType.value;
if (selectedRows.value.length >= 1) {
if (selectedRows.value.length === 1 && copyType === 'cell')
await navigator.clipboard.writeText(scrollElement.value.querySelector('.td.selected').innerText);
else if (selectedRows.value.length > 1 && copyType === 'cell')
copyRow('html');
else
copyRow(copyType);
}
}
// 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

@@ -16,6 +16,11 @@
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }} <i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
</span> </span>
</div> </div>
<div class="context-element" @click="copyRow('html')">
<span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} ({{ t('word.table') }})
</span>
</div>
<div class="context-element" @click="copyRow('json')"> <div class="context-element" @click="copyRow('json')">
<span class="d-flex"> <span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (JSON) <i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (JSON)
@@ -42,6 +47,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 +90,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 +110,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 +193,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)) {
return { type: 'number', mask: false }; if (IS_BIGINT.includes(editingType.value))
return { type: 'text', mask: false };
else
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,14 @@ 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',
resultsTable: 'Results table'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@@ -298,7 +305,37 @@ 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',
executeSelectedQuery: 'Execute selected query',
defaultCopyType: 'Default copy type'
}, },
faker: { faker: {
address: 'Address', address: 'Address',
@@ -364,6 +401,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

@@ -13,23 +13,23 @@ export const frFR = {
port: 'Port', port: 'Port',
user: 'Utilisateur', user: 'Utilisateur',
password: 'Mot de passe', password: 'Mot de passe',
credentials: 'Credentials', credentials: 'Identifiants',
connect: 'Connexion', connect: 'Connexion',
connected: 'Connecté', connected: 'Connecté',
disconnect: 'Déconnection', disconnect: 'Déconnection',
disconnected: 'Déconnecté', disconnected: 'Déconnecté',
refresh: 'Rafrechir', refresh: 'Rafrchir',
settings: 'Paramètres', settings: 'Paramètres',
general: 'Général', general: 'Général',
themes: 'Thèmes', themes: 'Thèmes',
update: 'Mise à jour', update: 'Mise à jour',
about: 'A propos de', about: 'À propos de',
language: 'Langage', language: 'Langage',
version: 'Version', version: 'Version',
donate: 'Faire une donation', donate: 'Faire une donation',
run: 'Lancer', run: 'Éxécuter',
schema: 'Schéma', schema: 'Schéma',
results: 'Resultats', results: 'Résultats',
size: 'Taille', size: 'Taille',
seconds: 'Secondes', seconds: 'Secondes',
type: 'Type', type: 'Type',
@@ -44,14 +44,14 @@ export const frFR = {
collation: 'Langage', collation: 'Langage',
clear: 'Effacer', clear: 'Effacer',
options: 'Options', options: 'Options',
autoRefresh: 'Auto-rafraichissemnt', autoRefresh: 'Auto-rafraîchissemnt',
indexes: 'Indexs', indexes: 'Indexs',
foreignKeys: 'Clefs étrangères', foreignKeys: 'Clés étrangères',
length: 'Longueur', length: 'Longueur',
unsigned: 'non signé', unsigned: 'non signé',
default: 'Par défault', default: 'Par défaut',
comment: 'Commentaire', comment: 'Commentaire',
key: 'Clef | Clefs', key: 'Clé | Clés',
order: 'Ordre', order: 'Ordre',
expression: 'Expression', expression: 'Expression',
autoIncrement: 'Auto Incrémentation', autoIncrement: 'Auto Incrémentation',
@@ -63,11 +63,11 @@ export const frFR = {
discard: 'Jeter', discard: 'Jeter',
stay: 'Rester', stay: 'Rester',
author: 'Auteur', author: 'Auteur',
light: 'Léger', light: 'Clair',
dark: 'Sombre', dark: 'Sombre',
autoCompletion: 'Auto Completion', autoCompletion: 'Prédiction Automatique',
application: 'Application', application: 'Application',
editor: 'Editor', editor: 'Éditeur',
view: 'Vue', view: 'Vue',
definer: 'Définisseur', definer: 'Définisseur',
algorithme: 'Algorithme', algorithme: 'Algorithme',
@@ -83,10 +83,10 @@ export const frFR = {
import: 'Import', import: 'Import',
returns: 'Retourne', returns: 'Retourne',
timing: 'Temps', timing: 'Temps',
state: 'Etat', state: 'État',
execution: 'Exécution', execution: 'Éxécution',
starts: 'Commence', starts: 'Commence',
ends: 'Fini', ends: 'Termine',
ssl: 'SSL', ssl: 'SSL',
privateKey: 'Clé privée', privateKey: 'Clé privée',
certificate: 'Certificat', certificate: 'Certificat',
@@ -95,7 +95,7 @@ export const frFR = {
upload: 'Téléverser', upload: 'Téléverser',
browse: 'Naviguer', browse: 'Naviguer',
faker: 'Imposteur', faker: 'Imposteur',
sshTunnel: 'SSH tunnel', sshTunnel: 'Tunnel SSH',
content: 'Contenu', content: 'Contenu',
cut: 'Couper', cut: 'Couper',
copy: 'Copier', copy: 'Copier',
@@ -116,7 +116,7 @@ export const frFR = {
cell: 'Cellule | Cellules', cell: 'Cellule | Cellules',
triggerFunction: 'Fonction de déclenchement | Fonctions de déclenchement', triggerFunction: 'Fonction de déclenchement | Fonctions de déclenchement',
all: 'Tout', all: 'Tout',
duplicate: 'Double', duplicate: 'Dupliquer',
routine: 'Routine', routine: 'Routine',
new: 'Nouveau', new: 'Nouveau',
history: 'Passé', history: 'Passé',
@@ -139,7 +139,8 @@ export const frFR = {
commit: 'Appliquer', commit: 'Appliquer',
rollback: 'Retour arrière', rollback: 'Retour arrière',
connectionString: 'Chaîne de connexion', connectionString: 'Chaîne de connexion',
contributors: 'Contributeurs' contributors: 'Contributeurs',
shortcuts: 'Raccourcis'
}, },
message: { message: {
appWelcome: 'Bienvenue dans le client SQL Antares!', appWelcome: 'Bienvenue dans le client SQL Antares!',
@@ -179,8 +180,8 @@ export const frFR = {
editDatabase: 'Editer la base de données', editDatabase: 'Editer la base de données',
clearChanges: 'Effacer les modifications', clearChanges: 'Effacer les modifications',
addNewField: 'Ajouter un nouveau champ', addNewField: 'Ajouter un nouveau champ',
manageIndexes: 'Gère les index', manageIndexes: 'Gérer les index',
manageForeignKeys: 'Gère les clés étrangères', manageForeignKeys: 'Gérer les clés étrangères',
allowNull: 'Autoriser NULL', allowNull: 'Autoriser NULL',
zeroFill: 'Remplissage avec zéro', zeroFill: 'Remplissage avec zéro',
customValue: 'Valeur personnalisée', customValue: 'Valeur personnalisée',
@@ -208,11 +209,11 @@ export const frFR = {
selectStatement: 'Sélectionner un état', selectStatement: 'Sélectionner un état',
triggerStatement: 'Déclencher un état', triggerStatement: 'Déclencher un état',
sqlSecurity: 'Sécurité SQL', sqlSecurity: 'Sécurité SQL',
updateOption: 'Mide à jour d\'une option', updateOption: 'Mise à jour d\'une option',
deleteView: 'Effacer une vue', deleteView: 'Effacer une vue',
createNewView: 'Créer une nouvelle vue', createNewView: 'Créer une nouvelle vue',
deleteTrigger: 'Effacer un déclencheur', deleteTrigger: 'Effacer un déclencheur',
createNewTrigger: 'Cr"er un nouveau déclencheur', createNewTrigger: 'Créer un nouveau déclencheur',
currentUser: 'utilisateur courant', currentUser: 'utilisateur courant',
routineBody: 'Corps de la routine', routineBody: 'Corps de la routine',
dataAccess: 'Accès aux données', dataAccess: 'Accès aux données',
@@ -232,7 +233,7 @@ export const frFR = {
tableFiller: 'Remplisseur de table', tableFiller: 'Remplisseur de table',
fakeDataLanguage: 'Language de données fausses', fakeDataLanguage: 'Language de données fausses',
searchForElements: 'Rechercher des éléments', searchForElements: 'Rechercher des éléments',
selectAll: 'Tout séélectionner', selectAll: 'Tout sélectionner',
queryDuration: 'Temps de requêtage', queryDuration: 'Temps de requêtage',
includeBetaUpdates: 'Inclure les mises à jour beta', includeBetaUpdates: 'Inclure les mises à jour beta',
setNull: 'Définir comme NULL', setNull: 'Définir comme NULL',
@@ -287,7 +288,10 @@ export const frFR = {
ourputFormat: 'Format de sortie', ourputFormat: 'Format de sortie',
singleFile: 'Seul fichier avec l\'extension {ext}', singleFile: 'Seul fichier avec l\'extension {ext}',
zipCompressedFile: 'Fichier compréssé avec l\'extension {ext}', zipCompressedFile: 'Fichier compréssé avec l\'extension {ext}',
disableBlur: 'Désactiver le floue' disableBlur: 'Désactiver le floue',
missingOrIncompleteTranslation: 'Traduction manquante ou incomplète?',
findOutHowToContribute: 'Trouver comment contribuer',
disableScratchpad: 'Désactiver le bloc-notes'
}, },
faker: { faker: {
address: 'Adresse', address: 'Adresse',

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