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

Compare commits

..

158 Commits

Author SHA1 Message Date
df6625af49 chore(release): 0.7.7 2023-03-10 18:12:26 +01:00
59846e6ff4 fix(MySQL): missing table information in table setting tab 2023-03-09 09:07:57 +01:00
63fece2a1b fix(Linux): remove app menu shown pressing ALT key 2023-03-06 18:29:53 +01:00
9ef475ec3f chore: package.json changes 2023-03-06 09:38:51 +01:00
3546c57e39 fix: unable to set string fields default values starting with 0 2023-03-02 18:09:16 +01:00
8730be02af ci: minor change 2023-03-02 09:49:59 +01:00
b925ff9c01 fix(Linux): remove app menu shown pressing ALT key 2023-03-02 09:07:10 +01:00
2c46269cf2 fix: hide table size tooltip if disabled 2023-03-02 09:06:28 +01:00
23d8467154 chore(release): 0.7.6 2023-02-27 10:44:53 +01:00
d1297a0085 fix: error with import/export tools, fixes #541 2023-02-27 10:43:51 +01:00
6bd8667f43 Merge pull request #540 from jimcat8/cn_main
Supplement the key-value pair and translate its value.
2023-02-27 10:07:46 +01:00
tianci li
c9093a36b3 Word error 2023-02-27 12:14:20 +08:00
tianci li
b6dbfe1564 Supplement the key-value pair and translate its value. 2023-02-27 11:59:29 +08:00
06b0090480 chore(release): 0.7.5 2023-02-26 17:29:31 +01:00
629ce63329 fix: single quotes not properly escaped for random generated content 2023-02-26 17:02:41 +01:00
313e7407eb feat(MySQL): option, disabled by default, to enable table size indicators on sidebar 2023-02-23 08:51:33 +01:00
4458177688 fix(MariaDB): exception with event_scheduler DISABLED with MariaDB 10, fixes #535 2023-02-21 18:07:29 +01:00
ea7865a086 Merge pull request #533 from antares-sql/all-contributors/add-jimcat8
docs: add jimcat8 as a contributor for translation
2023-02-19 10:43:34 +01:00
allcontributors[bot]
ca900f3dcf docs: update .all-contributorsrc [skip ci] 2023-02-19 09:43:23 +00:00
allcontributors[bot]
4f6f28c6e6 docs: update README.md [skip ci] 2023-02-19 09:43:22 +00:00
8caac0f21d Merge pull request #532 from jimcat8/cn_main
Updated zh-CN translation text
2023-02-19 10:43:15 +01:00
tianci li
cacfc1c2eb review 2023-02-19 14:21:42 +08:00
tianci li
2f30c0d42f Updated zh-CN translation text 2023-02-19 14:07:49 +08:00
b1fbc43ab2 fix: unable to import after a failed import, fixes #515 2023-02-13 18:06:55 +01:00
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
99 changed files with 9453 additions and 11248 deletions

View File

@@ -174,6 +174,34 @@
"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"
]
},
{
"login": "jimcat8",
"name": "tianci li",
"avatar_url": "https://avatars.githubusercontent.com/u/86754294?v=4",
"profile": "https://github.com/jimcat8",
"contributions": [
"translation"
]
} }
], ],
"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
@@ -14,11 +14,12 @@ jobs:
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: npm install & build - name: Install dependencies
run: | run: npm i
npm install
npm run build - name: "Build"
run: npm run build
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

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

View File

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

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,194 @@
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.7](https://github.com/antares-sql/antares/compare/v0.7.6...v0.7.7) (2023-03-10)
### Bug Fixes
* hide table size tooltip if disabled ([2c46269](https://github.com/antares-sql/antares/commit/2c46269cf262248d3f5644b7c5b89d5d317a89a4))
* **Linux:** remove app menu shown pressing ALT key ([63fece2](https://github.com/antares-sql/antares/commit/63fece2a1b6b09f61ae416f7c3a7b51ee0a53d0e))
* **Linux:** remove app menu shown pressing ALT key ([b925ff9](https://github.com/antares-sql/antares/commit/b925ff9c01afcc727e5d70bafb079d468ed1eede))
* **MySQL:** missing table information in table setting tab ([59846e6](https://github.com/antares-sql/antares/commit/59846e6ff47591ec8dc22403c3be0e70e0f3ccfd))
* unable to set string fields default values starting with 0 ([3546c57](https://github.com/antares-sql/antares/commit/3546c57e398be7b2e226bb8e93e8fc0fb8bab40a))
### [0.7.6](https://github.com/antares-sql/antares/compare/v0.7.5...v0.7.6) (2023-02-27)
### Bug Fixes
* error with import/export tools, fixes [#541](https://github.com/antares-sql/antares/issues/541) ([d1297a0](https://github.com/antares-sql/antares/commit/d1297a0085fd54967817816efaeed5770a887bbf))
### [0.7.5](https://github.com/antares-sql/antares/compare/v0.7.4...v0.7.5) (2023-02-26)
### Features
* **MySQL:** option, disabled by default, to enable table size indicators on sidebar ([313e740](https://github.com/antares-sql/antares/commit/313e7407eb1afe5d19ac49ee4b308950b48cafa8))
### Bug Fixes
* **MariaDB:** exception with event_scheduler DISABLED with MariaDB 10, fixes [#535](https://github.com/antares-sql/antares/issues/535) ([4458177](https://github.com/antares-sql/antares/commit/445817768863616ea7340c8bf62472197b73bd6e))
* single quotes not properly escaped for random generated content ([629ce63](https://github.com/antares-sql/antares/commit/629ce633294c862266db9e27ffa5c154e8fc416c))
* unable to import after a failed import, fixes [#515](https://github.com/antares-sql/antares/issues/515) ([b1fbc43](https://github.com/antares-sql/antares/commit/b1fbc43ab2f39827cb85ac7d21ac889ffc2f4c64))
### [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) ### [0.5.15](https://github.com/antares-sql/antares/compare/v0.5.14...v0.5.15) (2022-08-17)

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,35 @@ 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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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" valign="top" width="14.28%"><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>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jimcat8"><img src="https://avatars.githubusercontent.com/u/86754294?v=4?s=100" width="100px;" alt="tianci li"/><br /><sub><b>tianci li</b></sub></a><br /><a href="#translation-jimcat8" title="Translation">🌍</a></td>
</tr>
</tbody>
</table> </table>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

14683
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.15", "version": "0.7.7",
"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,8 +182,8 @@
"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", "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",
@@ -200,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

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

@@ -140,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'] },

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

@@ -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,4 +1,4 @@
import { app, ipcMain, dialog, BrowserWindow } from 'electron'; import { app, ipcMain, dialog } from 'electron';
import { ShortcutRegister } from '../libs/ShortcutRegister'; import { ShortcutRegister } from '../libs/ShortcutRegister';
export default () => { export default () => {
@@ -15,26 +15,22 @@ export default () => {
}); });
ipcMain.handle('resotre-default-shortcuts', () => { ipcMain.handle('resotre-default-shortcuts', () => {
const mainWindow = BrowserWindow.getAllWindows()[0]; const shortCutRegister = ShortcutRegister.getInstance();
const shortCutRegister = ShortcutRegister.getInstance({ mainWindow });
shortCutRegister.restoreDefaults(); shortCutRegister.restoreDefaults();
}); });
ipcMain.handle('reload-shortcuts', () => { ipcMain.handle('reload-shortcuts', () => {
const mainWindow = BrowserWindow.getAllWindows()[0]; const shortCutRegister = ShortcutRegister.getInstance();
const shortCutRegister = ShortcutRegister.getInstance({ mainWindow });
shortCutRegister.reload(); shortCutRegister.reload();
}); });
ipcMain.handle('update-shortcuts', (event, shortcuts) => { ipcMain.handle('update-shortcuts', (event, shortcuts) => {
const mainWindow = BrowserWindow.getAllWindows()[0]; const shortCutRegister = ShortcutRegister.getInstance();
const shortCutRegister = ShortcutRegister.getInstance({ mainWindow });
shortCutRegister.updateShortcuts(shortcuts); shortCutRegister.updateShortcuts(shortcuts);
}); });
ipcMain.handle('unregister-shortcuts', () => { ipcMain.handle('unregister-shortcuts', () => {
const mainWindow = BrowserWindow.getAllWindows()[0]; const shortCutRegister = ShortcutRegister.getInstance();
const shortCutRegister = ShortcutRegister.getInstance({ mainWindow });
shortCutRegister.unregister(); 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;
} }
@@ -331,7 +352,18 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof fakeValue === 'string') { if (typeof fakeValue === 'string') {
if (params.row[key].length) if (params.row[key].length)
fakeValue = fakeValue.substring(0, params.row[key].length); fakeValue = fakeValue.substring(0, params.row[key].length);
fakeValue = `'${sqlEscaper(fakeValue)}'`;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fakeValue = `'${sqlEscaper(fakeValue)}'`;
break;
case 'pg':
case 'sqlite':
case 'firebird':
fakeValue = `'${fakeValue.replaceAll('\'', '\'\'')}'`;
break;
}
} }
else if ([...DATE, ...DATETIME].includes(type)) else if ([...DATE, ...DATETIME].includes(type))
fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`; fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;
@@ -367,7 +399,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

@@ -1,4 +1,4 @@
import { BrowserWindow, globalShortcut } from 'electron'; import { BrowserWindow, globalShortcut, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
import * as Store from 'electron-store'; import * as Store from 'electron-store';
import { ShortcutRecord, shortcuts } from 'common/shortcuts'; import { ShortcutRecord, shortcuts } from 'common/shortcuts';
@@ -6,19 +6,34 @@ const shortcutsStore = new Store({ name: 'shortcuts' });
const isDevelopment = process.env.NODE_ENV !== 'production'; const isDevelopment = process.env.NODE_ENV !== 'production';
const defaultShortcuts = shortcuts.filter(s => s.os.includes(process.platform)); 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 { export class ShortcutRegister {
private _shortcuts: ShortcutRecord[]; private _shortcuts: ShortcutRecord[];
private _mainWindow: BrowserWindow; private _mainWindow: BrowserWindow;
private _menu: Menu;
private _menuTemplate: OsMenu;
private _mode: ShortcutMode;
private static _instance: ShortcutRegister; private static _instance: ShortcutRegister;
private constructor (args: { mainWindow: BrowserWindow }) { private constructor (args: { mainWindow: BrowserWindow; menuTemplate?: OsMenu; mode: ShortcutMode }) {
this._mainWindow = args.mainWindow; this._mainWindow = args.mainWindow;
this._menuTemplate = args.menuTemplate || {};
this._mode = args.mode;
this.shortcuts = shortcutsStore.get('shortcuts', defaultShortcuts) as ShortcutRecord[]; this.shortcuts = shortcutsStore.get('shortcuts', defaultShortcuts) as ShortcutRecord[];
} }
public static getInstance (args: { mainWindow: BrowserWindow }) { public static getInstance (args?: { mainWindow?: BrowserWindow; menuTemplate?: OsMenu; mode?: ShortcutMode }) {
if (!ShortcutRegister._instance) if (!ShortcutRegister._instance && args.menuTemplate !== undefined && args.mode !== undefined) {
ShortcutRegister._instance = new ShortcutRegister(args); ShortcutRegister._instance = new ShortcutRegister({
mainWindow: args.mainWindow,
menuTemplate: args.menuTemplate,
mode: args.mode
});
}
return ShortcutRegister._instance; return ShortcutRegister._instance;
} }
@@ -33,24 +48,74 @@ export class ShortcutRegister {
} }
init () { 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) { for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) { if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) { for (const key of shortcut.keys) {
try { try {
globalShortcut.register(key, () => { this._menu.append(new MenuItem({
this._mainWindow.webContents.send(shortcut.event); label: '.',
if (isDevelopment) console.log('EVENT:', shortcut); 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) { catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults(); this.restoreDefaults();
throw error; throw error;
} }
} }
} }
} }
}
this._mainWindow.webContents.send('update-shortcuts', this.shortcuts); 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 () { reload () {
@@ -69,6 +134,6 @@ export class ShortcutRegister {
} }
unregister () { unregister () {
globalShortcut.unregisterAll(); 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 {
@@ -290,22 +290,69 @@ export class MySQLClient extends AntaresCore {
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS'); const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS'); const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`'); // eslint-disable-next-line @typescript-eslint/no-explicit-any
let schedulers: any[] = [];
try { // Avoid exception with event_scheduler DISABLED with MariaDB 10
const { rows } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
schedulers = rows;
}
catch (err) {
console.log(err);
}
const tablesArr: ShowTableResult[] = []; const tablesArr: ShowTableResult[] = [];
const triggersArr: ShowTriggersResult[] = []; const triggersArr: ShowTriggersResult[] = [];
let schemaSize = 0; let schemaSize = 0;
const Store = require('electron-store');
Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
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}\``); const showTableSize = settingsStore.get('show_table_size', false);
if (tables.length) {
tables = tables.map(table => { if (showTableSize) {
table.Db = db.Database; let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`
return table; SELECT
}); TABLE_NAME,
tablesArr.push(...tables); 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) {
tables = tables.map(table => {
table.TABLE_SCHEMA = db.Database;
return table;
});
tablesArr.push(...tables);
}
}
else {
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`SHOW FULL TABLES FROM \`${db.Database}\``);
if (tables.length) {
tables = tables.map(table => {
const [name, type] = Object.values(table);
table.TABLE_SCHEMA = db.Database;
table.TABLE_NAME = name;
table.TABLE_TYPE = type;
return table;
});
tablesArr.push(...tables);
}
} }
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SHOW TRIGGERS FROM \`${db.Database}\``); let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SHOW TRIGGERS FROM \`${db.Database}\``);
@@ -321,9 +368,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 +379,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 +513,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 +989,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 +1637,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 +1648,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,14 @@
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 { ShortcutRegister } from './libs/ShortcutRegister'; import { OsMenu, ShortcutRegister } from './libs/ShortcutRegister';
Store.initRenderer(); Store.initRenderer();
const settingsStore = new Store({ name: 'settings' }); const settingsStore = new Store({ name: 'settings' });
let shortCutRegister: ShortcutRegister;
const appTheme = settingsStore.get('application_theme'); const appTheme = settingsStore.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';
@@ -39,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,
@@ -122,31 +121,11 @@ else {
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
createAppMenu(); createAppMenu();
shortCutRegister = ShortcutRegister.getInstance({ mainWindow });
if (isWindows) if (isWindows)
mainWindow.show(); mainWindow.show();
if (isDevelopment) // if (isDevelopment)
mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
app.on('browser-window-focus', () => {
// Send registered shortcut events to window
shortCutRegister.init();
if (isDevelopment) { // Dev shortcuts
globalShortcut.register('Shift+CommandOrControl+F5', () => {
mainWindow.reload();
});
globalShortcut.register('Shift+CommandOrControl+F12', () => {
mainWindow.webContents.openDevTools();
});
}
});
app.on('browser-window-blur', () => {
shortCutRegister.unregister();
});
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
@@ -167,10 +146,8 @@ else {
} }
function createAppMenu () { function createAppMenu () {
let menu: Electron.Menu = null; const menuTemplate: OsMenu = {
darwin: [
if (isMacOS) {
menu = Menu.buildFromTemplate([
{ {
label: app.name, label: app.name,
submenu: [ submenu: [
@@ -201,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

@@ -6,72 +6,106 @@ import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory'; import { ClientsFactory } from '../libs/ClientsFactory';
import MySQLImporter from '../libs/importers/sql/MySQLlImporter'; import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter'; import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ImportOptions } from 'common/interfaces/importer';
let importer: antares.Importer; let importer: antares.Importer;
process.on('message', async ({ type, dbConfig, options }) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
process.on('message', async ({ type, dbConfig, options }: {
type: string;
dbConfig: mysql.ConnectionOptions & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
| pg.ClientConfig & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
| { databasePath: string; readonly: boolean };
options: ImportOptions;
}) => {
if (type === 'init') { if (type === 'init') {
const connection = await ClientsFactory.getClient({ try {
client: options.type, const connection = await ClientsFactory.getClient({
params: { client: options.type,
...dbConfig, params: {
schema: options.schema ...dbConfig,
}, schema: options.schema
poolSize: 1 },
}) as MySQLClient | PostgreSQLClient; poolSize: 1
}) as MySQLClient | PostgreSQLClient;
const pool = await connection.getConnectionPool(); const pool = await connection.getConnectionPool();
switch (options.type) { switch (options.type) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
importer = new MySQLImporter(pool as unknown as mysql.Pool, options); importer = new MySQLImporter(pool as unknown as mysql.Pool, options);
break; break;
case 'pg': case 'pg':
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options); importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options);
break; break;
default: default:
process.send({
type: 'error',
payload: `"${options.type}" importer not aviable`
});
return;
}
importer.once('error', err => {
console.error(err);
process.send({ process.send({
type: 'error', type: 'error',
payload: `"${options.type}" importer not aviable` payload: err.toString()
}); });
return; });
}
importer.once('error', err => { importer.once('end', () => {
process.send({
type: 'end',
payload: { cancelled: importer.isCancelled }
});
});
importer.once('cancel', () => {
process.send({ type: 'cancel' });
});
importer.on('progress', state => {
process.send({
type: 'import-progress',
payload: state
});
});
importer.on('query-error', state => {
process.send({
type: 'query-error',
payload: state
});
});
importer.run();
}
catch (err) {
console.error(err); console.error(err);
process.send({ process.send({
type: 'error', type: 'error',
payload: err.toString() payload: err.toString()
}); });
}); }
importer.once('end', () => {
process.send({
type: 'end',
payload: { cancelled: importer.isCancelled }
});
});
importer.once('cancel', () => {
process.send({ type: 'cancel' });
});
importer.on('progress', state => {
process.send({
type: 'import-progress',
payload: state
});
});
importer.on('query-error', state => {
process.send({
type: 'query-error',
payload: state
});
});
importer.run();
} }
else if (type === 'cancel') else if (type === 'cancel')
importer.cancel(); importer.cancel();
}); });
process.on('uncaughtException', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});
process.on('unhandledRejection', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});

View File

@@ -130,6 +130,12 @@ onMounted(() => {
node = node.parentNode; node = node.parentNode;
} }
}); });
document.addEventListener('keydown', e => {
if (e.altKey && e.key === 'Alt') { // Prevent Alt key to trigger hidden shortcut menu
e.preventDefault();
}
});
}); });
</script> </script>

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

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

@@ -86,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>
@@ -120,6 +120,24 @@
</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.showTableSize') }}
</label>
</div>
<div class="col-1 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleShowTableSize">
<input type="checkbox" :checked="showTableSize">
<i class="form-icon" />
</label>
</div>
<div class="col-6 col-sm-12 px-2 p-vcentered">
<small class="d-block" :style="'line-height: 1.1; font-size: 70%;'">
{{ t('message.showTableSizeDescription') }}
</small>
</div>
</div>
<div class="form-group column col-12 mb-0"> <div class="form-group column col-12 mb-0">
<div class="col-5 col-sm-12"> <div class="col-5 col-sm-12">
<label class="form-label"> <label class="form-label">
@@ -196,6 +214,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>
@@ -239,7 +292,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"
@@ -251,28 +304,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>
@@ -358,8 +432,11 @@ const {
dataTabLimit: pageSize, dataTabLimit: pageSize,
autoComplete: selectedAutoComplete, autoComplete: selectedAutoComplete,
lineWrap: selectedLineWrap, lineWrap: selectedLineWrap,
executeSelected: selectedExecuteSelected,
defaultCopyType: selectedCopyType,
notificationsTimeout, notificationsTimeout,
restoreTabs, restoreTabs,
showTableSize,
disableBlur, disableBlur,
disableScratchpad, disableScratchpad,
applicationTheme, applicationTheme,
@@ -377,10 +454,13 @@ const {
changeDisableScratchpad, changeDisableScratchpad,
changeAutoComplete, changeAutoComplete,
changeLineWrap, changeLineWrap,
changeExecuteSelected,
changeApplicationTheme, changeApplicationTheme,
changeEditorTheme, changeEditorTheme,
changeEditorFontSize, changeEditorFontSize,
updateNotificationsTimeout updateNotificationsTimeout,
changeDefaultCopyType,
changeShowTableSize
} = settingsStore; } = settingsStore;
const { const {
hideSettingModal: closeModal, hideSettingModal: closeModal,
@@ -395,7 +475,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: [
@@ -443,28 +546,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 = [];
@@ -474,6 +556,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(() => {
@@ -512,6 +602,10 @@ const toggleRestoreSession = () => {
changeRestoreTabs(!restoreTabs.value); changeRestoreTabs(!restoreTabs.value);
}; };
const toggleShowTableSize = () => {
changeShowTableSize(!showTableSize.value);
};
const toggleDisableBlur = () => { const toggleDisableBlur = () => {
changeDisableBlur(!disableBlur.value); changeDisableBlur(!disableBlur.value);
}; };
@@ -528,7 +622,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

@@ -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,47 +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"
:class="{ 'selected': element.uid === selectedWorkspace }"
:title="getConnectionName(element.uid)"
@click.stop="selectWorkspace(element.uid)"
@contextmenu.prevent="contextMenu($event, element)"
>
<i
class="settingbar-element-icon dbi"
:class="[`dbi-${element.client}`, getStatusBadge(element.uid), (pinnedConnections.has(element.uid) ? 'settingbar-element-pin' : false)]"
/>
<small class="settingbar-element-name">{{ getConnectionName(element.uid) }}</small>
</li>
</template>
</Draggable>
<div v-if="pinnedConnectionsArr.length" class="divider" />
<li
v-for="connection in unpinnedConnectionsArr"
:key="connection.uid"
class="settingbar-element btn btn-link"
:class="{ 'selected': connection.uid === selectedWorkspace }"
:title="getConnectionName(connection.uid)"
@click.stop="selectWorkspace(connection.uid)"
@contextmenu.prevent="contextMenu($event, connection)"
>
<i
class="settingbar-element-icon dbi"
:class="[`dbi-${connection.client}`, getStatusBadge(connection.uid)]"
/>
<small class="settingbar-element-name">{{ getConnectionName(connection.uid) }}</small>
</li>
</ul> </ul>
</div> </div>
@@ -56,19 +19,31 @@
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<li <li
v-if="isScrollable" v-if="isScrollable"
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('message.allConnections')
}"
class="settingbar-element btn btn-link" class="settingbar-element btn btn-link"
:title="t('message.allConnections')"
@click="emit('show-connections-modal')" @click="emit('show-connections-modal')"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" /> <div class="settingbar-element-icon-wrapper">
<i class="settingbar-element-icon mdi mdi-24px mdi-dots-horizontal text-light" />
</div>
</li> </li>
<li <li
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('message.addConnection')
}"
class="settingbar-element btn btn-link" class="settingbar-element btn btn-link"
:class="{ 'selected': 'NEW' === selectedWorkspace }" :class="{ 'selected': 'NEW' === selectedWorkspace }"
:title="t('message.addConnection')"
@click="selectWorkspace('NEW')" @click="selectWorkspace('NEW')"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" /> <div class="settingbar-element-icon-wrapper">
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
</div>
</li> </li>
</ul> </ul>
</div> </div>
@@ -77,15 +52,23 @@
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<li <li
v-if="!disableScratchpad" v-if="!disableScratchpad"
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('word.scratchpad')
}"
class="settingbar-element btn btn-link" class="settingbar-element btn btn-link"
:title="t('word.scratchpad')"
@click="showScratchpad" @click="showScratchpad"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" /> <i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" />
</li> </li>
<li <li
v-tooltip="{
strategy: 'fixed',
placement: 'right',
content: t('word.settings')
}"
class="settingbar-element btn btn-link" class="settingbar-element btn btn-link"
:title="t('word.settings')"
@click="showSettingModal('general')" @click="showSettingModal('general')"
> >
<i <i
@@ -102,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 } 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();
@@ -119,121 +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 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(() => {
@@ -246,6 +150,9 @@ watch(selectedWorkspace, (newVal, oldVal) => {
}, 150); }, 150);
} }
}); });
if (!connectionsArr.value.length)
initConnectionsOrder();
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -281,6 +188,22 @@ watch(selectedWorkspace, (newVal, oldVal) => {
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%;
@@ -289,7 +212,7 @@ watch(selectedWorkspace, (newVal, oldVal) => {
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; position: relative;
@@ -302,7 +225,7 @@ watch(selectedWorkspace, (newVal, oldVal) => {
opacity: 1; opacity: 1;
&::before { &::before {
height: $settingbar-width; height: calc(#{$settingbar-width} - 0.4rem);
} }
} }
@@ -311,53 +234,56 @@ watch(selectedWorkspace, (newVal, oldVal) => {
height: 0; height: 0;
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;
background-color: $primary-color; background-color: rgba($color: #fff, $alpha: 0.8);
border-radius: $border-radius; border-radius: $border-radius;
} }
.settingbar-element-icon { .settingbar-element-icon-wrapper{
margin: 0 auto; display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
&.badge::after { .settingbar-element-icon {
top: 5px; &.badge::after {
right: -4px; top: 10px;
position: absolute; right: -6px;
position: absolute;
}
&.badge-update::after {
bottom: initial;
}
} }
&.badge-update::after { .settingbar-element-name {
bottom: initial; font-size: 65%;
max-width: 90%;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: calc($settingbar-width - 15px);
line-height: 1.1;
color: rgba($body-font-color-dark, 0.8);
text-align: center;
} }
}
.settingbar-element-name { .settingbar-element-pin {// <- Dead
font-size: 65%; margin: 0 auto;
bottom: 5px;
left: 7px;
position: absolute;
font-style: normal;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: calc($settingbar-width - 15px);
text-align: left;
line-height: 1.1;
color: rgba($body-font-color-dark, 0.8);
text-align: center;
}
.settingbar-element-pin { &::before {
margin: 0 auto; font: normal normal normal 14px/1 "Material Design Icons";
content: "\F0403";
&::before { color: $body-font-color-dark;
font: normal normal normal 14px/1 "Material Design Icons"; transform: rotate(45deg);
content: "\F0403"; opacity: 0.25;
color: $body-font-color-dark; top: -8px;
transform: rotate(45deg); left: -10px;
opacity: 0.25; position: absolute;
top: -8px; }
left: -10px;
position: absolute;
} }
} }
} }

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({

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

@@ -217,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);
@@ -258,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)}"
@@ -43,7 +42,7 @@
<span v-html="highlightWord(table.name)" /> <span v-html="highlightWord(table.name)" />
</a> </a>
<div <div
v-if="table.type === 'table' && table.size !== false" v-if="table.type === 'table' && table.size !== false && !isNaN(table.size)"
class="table-size tooltip tooltip-left mr-1" class="table-size tooltip tooltip-left mr-1"
:data-tooltip="formatBytes(table.size)" :data-tooltip="formatBytes(table.size)"
> >
@@ -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

@@ -291,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: '',

View File

@@ -90,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"
@@ -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,

View File

@@ -275,7 +275,13 @@ const saveContentListener = () => {
}; };
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) => {

View File

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

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

@@ -103,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"
@@ -186,6 +187,7 @@ import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFie
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'; import { ipcRenderer } from 'electron';
import { useSettingsStore } from '@/stores/settings';
const { t } = useI18n(); const { t } = useI18n();
@@ -199,8 +201,10 @@ const props = defineProps({
const { addNotification } = useNotificationsStore(); const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore(); const workspacesStore = useWorkspacesStore();
const settingsStore = useSettingsStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { showTableSize } = settingsStore;
const { const {
getWorkspace, getWorkspace,
@@ -256,7 +260,7 @@ const isChanged = computed(() => {
const getTableOptions = async (params: {uid: string; schema: string; table: string}) => { const getTableOptions = async (params: {uid: string; schema: string; table: string}) => {
const db = workspace.value.structure.find(db => db.name === props.schema); const db = workspace.value.structure.find(db => db.name === props.schema);
if (db && db.tables.length && props.table) if (db && db.tables.length && props.table && showTableSize)
tableOptions.value = db.tables.find(table => table.name === props.table); tableOptions.value = db.tables.find(table => table.name === props.table);
else { else {
const { status, response } = await Tables.getTableOptions(params); const { status, response } = await Tables.getTableOptions(params);
@@ -300,7 +304,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 +327,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 +535,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,

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\'';
} }
@@ -531,7 +532,10 @@ const editOFF = () => {
break; break;
case 'custom': case 'custom':
localRow.value.autoIncrement = false; localRow.value.autoIncrement = false;
localRow.value.default = Number.isNaN(+defaultValue.value.custom) ? `'${defaultValue.value.custom}'` : defaultValue.value.custom; if (fieldType.value.group === 'string')
localRow.value.default = `'${defaultValue.value.custom}'`;
else
localRow.value.default = defaultValue.value.custom;
break; break;
case 'expression': case 'expression':
localRow.value.autoIncrement = false; localRow.value.autoIncrement = false;

View File

@@ -198,6 +198,7 @@ import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyStat
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 { ipcRenderer } from 'electron';
import { useSettingsStore } from '@/stores/settings';
const { t } = useI18n(); const { t } = useI18n();
@@ -222,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,
@@ -290,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();

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

@@ -143,7 +143,13 @@ export const enUS = {
pin: 'Pin', pin: 'Pin',
unpin: 'Unpin', unpin: 'Unpin',
console: 'Console', console: 'Console',
shortcuts: 'Shortcuts' 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!',
@@ -286,7 +292,7 @@ export const enUS = {
autoCommit: 'Auto commit', autoCommit: 'Auto commit',
manualCommit: 'Manual commit', manualCommit: 'Manual commit',
actionSuccessful: '{action} successful', actionSuccessful: '{action} successful',
importQueryErrors: 'Warning: {n} error has accurrend | Warning: {n} errors occurred', importQueryErrors: 'Warning: {n} error has occurrend | Warning: {n} errors occurred',
executedQueries: '{n} query executed | {n} queries executed', executedQueries: '{n} query executed | {n} queries executed',
ourputFormat: 'Output format', ourputFormat: 'Output format',
singleFile: 'Single {ext} file', singleFile: 'Single {ext} file',
@@ -322,7 +328,16 @@ export const enUS = {
clearQuery: 'Clear query', clearQuery: 'Clear query',
openFilter: 'Open filter', openFilter: 'Open filter',
nextResultsPage: 'Next results page', nextResultsPage: 'Next results page',
previousResultsPage: 'Previous 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',
showTableSize: 'Show table size in sidebar',
showTableSizeDescription: 'MySQL/MariaDB only. Enable this option may affects performance on schema with many tables.'
}, },
faker: { faker: {
address: 'Address', address: 'Address',
@@ -388,6 +403,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',

503
src/renderer/i18n/id-ID.ts Normal file
View File

@@ -0,0 +1,503 @@
export const idID = {
word: {
edit: 'Ubah',
save: 'Simpan',
close: 'Tutup',
delete: 'Hapus',
confirm: 'Iya',
cancel: 'Batal',
send: 'Kirim',
connectionName: 'Nama Koneksi',
client: 'Klien',
hostName: 'Nama Host',
port: 'Port',
user: 'Pengguna',
password: 'Sandi',
credentials: 'Kredensial',
connect: 'Menyambung',
connected: 'Tersambung',
disconnect: 'Putuskan',
disconnected: 'Terputus',
refresh: 'Segarkan',
settings: 'Pengaturan',
general: 'Umum',
themes: 'Tema',
update: 'Memperbarui',
about: 'Tentang',
language: 'Bahasa',
version: 'Versi',
donate: 'Donasi',
run: 'Jalankan',
schema: 'Skema',
results: 'Hasil',
size: 'Ukuran',
seconds: 'Detik',
type: 'Jenis',
mimeType: 'Tipe Mime',
download: 'Unduh',
add: 'Tambahkan',
data: 'Data',
properties: 'Properti',
insert: 'Masukan',
connecting: 'Menghubungkan',
name: 'Nama',
collation: 'Kolasi',
clear: 'Jernih',
options: 'Pilihan',
autoRefresh: 'Segarkan otomatis',
indexes: 'Indeks',
foreignKeys: 'Foreign Key',
length: 'Panjangnya',
unsigned: 'Unsigned',
default: 'Bawaan',
comment: 'Komentar',
key: 'Key | Keys',
order: 'Urutan',
expression: 'Ekspresi',
autoIncrement: 'Inkremen Otomatis',
engine: 'Mesin',
field: 'Bidang | Fields',
approximately: 'Sekitar',
total: 'Total',
table: 'Tabel',
discard: 'Membuang',
stay: 'Tinggal',
author: 'Pengarang',
light: 'Terang',
dark: 'Gelap',
autoCompletion: 'Penyelesaian Otomatis',
application: 'Aplikasi',
editor: 'Editor',
view: 'Melihat',
definer: 'Definisi',
algorithm: 'Algoritma',
trigger: 'Trigger | Trigger',
storedRoutine: 'Rutin tersimpan | Rutin tersimpan',
scheduler: 'Penjadwal | Penjadwal',
event: 'Peristiwa',
parameters: 'Parameter',
function: 'Fungsi | Fungsi',
deterministic: 'Deterministik',
context: 'Konteks',
export: 'Ekspor',
import: 'Impor',
returns: 'Pengembalian',
timing: 'Pengaturan waktu',
state: 'Keadaan',
execution: 'Eksekusi',
starts: 'Mulai',
ends: 'Berakhir',
ssl: 'SSL',
privateKey: 'Kunci pribadi',
certificate: 'Sertifikat',
caCertificate: 'Sertifikat CA',
ciphers: 'Chipher',
upload: 'Mengunggah',
browse: 'Jelajahi',
faker: 'Pemalsu',
content: 'Isi',
cut: 'Potong',
copy: 'Salin',
paste: 'Tempel',
tools: 'Peralatan',
variables: 'Variabel',
processes: 'Proses',
database: 'Basis data',
scratchpad: 'Papan penggaris',
array: 'Array',
changelog: 'Changelog',
format: 'Format',
sshTunnel: 'Tunel SSH',
structure: 'Struktur',
small: 'Kecil',
medium: 'Sedang',
large: 'Besar',
row: 'Baris | Baris',
cell: 'Sel | Sel',
triggerFunction: 'Fungsi trigger | Fungsi trigger',
all: 'Semua',
duplicate: 'Duplikat',
routine: 'Rutin',
new: 'Baru',
history: 'Histori',
select: 'Pilih',
passphrase: 'Frasa sandi',
filter: 'Saring',
change: 'Mengubah',
views: 'Tampilan',
triggers: 'Pemicu',
routines: 'Rutinitas',
functions: 'Fungsi',
schedulers: 'Penjadwal',
includes: 'Termasuk',
drop: 'Menjatuhkan',
completed: 'Selesai',
aborted: 'Dibatalkan',
disabled: 'Dinonaaktivkan',
enable: 'Aktifkan',
disable: 'Nonaktifkan',
commit: 'Komit',
rollback: 'Kembalikan',
connectionString: 'Rangkaian sambungan',
contributors: 'Kontributor',
pin: 'Pin',
unpin: 'Unpin',
console: 'Konsol',
shortcuts: 'Shortcut',
folder: 'Folder | Folder',
appearence: 'Appearence',
color: 'Color',
label: 'Label',
icon: 'Icon'
},
message: {
appWelcome: 'Selamat datang di Antares SQL Client!',
appFirstStep: 'Langkah pertama Anda: buat koneksi database baru.',
addConnection: 'Tambahkan koneksi',
createConnection: 'Buat koneksi',
createNewConnection: 'Buat koneksi baru',
askCredentials: 'Mintalah kredensial',
testConnection: 'Tes koneksi',
editConnection: 'Mengedit koneksi',
deleteConnection: 'Hapus koneksi',
deleteCorfirm: 'Apakah Anda mengkonfirmasi pembatalan',
connectionSuccessfullyMade: 'Sambungan berhasil dibuat!',
madeWithJS: 'Dibuat dengan 💛 dan JavaScript!',
checkForUpdates: 'Periksa pembaruan',
noUpdatesAvailable: 'Tidak ada pembaruan yang tersedia',
checkingForUpdate: 'Memeriksa pembaruan',
checkFailure: 'Pemeriksaan gagal, coba lagi nanti',
updateAvailable: 'Pembaruan tersedia',
downloadingUpdate: 'Mengunduh pembaruan',
updateDownloaded: 'Pembaruan diunduh',
restartToInstall: 'Mulai ulang Antares untuk menginstal',
unableEditFieldWithoutPrimary: 'Tidak dapat mengedit bidang tanpa kunci utama di kumpulan hasil',
editCell: 'Mengedit sel',
deleteRows: 'Hapus baris | Hapus {count} baris',
confirmToDeleteRows: 'Apakah Anda mengonfirmasi untuk menghapus satu baris? | Apakah Anda mengonfirmasi untuk menghapus {count} baris?',
notificationsTimeout: 'Batas waktu pemberitahuan',
uploadFile: 'Unggah data',
addNewRow: 'Tambahkan baris baru',
numberOfInserts: 'Jumlah sisipan',
openNewTab: 'Buka tab baru',
affectedRows: 'Baris yang terpengaruh',
createNewDatabase: 'Buat Basis Data baru',
databaseName: 'Nama basis data',
serverDefault: 'Standar server',
deleteDatabase: 'Hapus basis data',
editDatabase: 'Ubah basis data',
clearChanges: 'Hapus perubahan',
addNewField: 'Tambahkan bidang baru',
manageIndexes: 'Kelola indeks',
manageForeignKeys: 'Mengelola kunci asing',
allowNull: 'Izinkan NULL',
zeroFill: 'Isi nol',
customValue: 'Nilai khusus',
onUpdate: 'Sedang diperbarui',
deleteField: 'Hapus bidang',
createNewIndex: 'Buat indeks baru',
addToIndex: 'Tambahkan ke index',
createNewTable: 'Buat tabel baru',
emptyTable: 'Kosongkan Tabel',
deleteTable: 'Hapus tabel',
emptyCorfirm: 'Apakah Anda mengkonfirmasi untuk mengosongkan',
unsavedChanges: 'Perubahan belum disimpan',
discardUnsavedChanges: 'Anda memiliki beberapa perubahan yang belum disimpan. Dengan menutup tab ini akan membuang perubahan',
thereAreNoIndexes: 'Tidak ada indeks',
thereAreNoForeign: 'Tidak ada kunci asing',
createNewForeign: 'Buat kunci asing baru',
referenceTable: 'Ref. table',
referenceField: 'Ref. field',
foreignFields: 'Bidang asing',
invalidDefault: 'Standar tidak valid',
onDelete: 'Saat hapus',
applicationTheme: 'Tema Aplikasi',
editorTheme: 'Tema Editor',
wrapLongLines: 'Bungkus garis panjang',
selectStatement: 'Pilih pernyataan',
triggerStatement: 'Pernyataan pemicu',
sqlSecurity: 'keamanan SQL',
updateOption: 'Opsi pembaruan',
deleteView: 'Hapus tampilan',
createNewView: 'Buat tampilan baru',
deleteTrigger: 'Hapus pemicu',
createNewTrigger: 'Buat pemicu baru',
currentUser: 'Pengguna saat ini',
routineBody: 'Badan rutin',
dataAccess: 'Akses data',
thereAreNoParameters: 'Tidak ada parameter',
createNewParameter: 'Buat parameter baru',
createNewRoutine: 'Buat rutin tersimpan baru',
deleteRoutine: 'Hapus rutinitas yang tersimpan',
functionBody: 'Fungsi tubuh',
createNewFunction: 'Buat fungsi baru',
deleteFunction: 'Hapus fungsi',
schedulerBody: 'badan Penjadwal',
createNewScheduler: 'Buat penjadwal baru',
deleteScheduler: 'Hapus penjadwal',
preserveOnCompletion: 'Pertahankan saat selesai',
enableSsl: 'Aktifkan SSL',
manualValue: 'Nilai manual',
tableFiller: 'Pengisi Tabel',
fakeDataLanguage: 'Bahasa data palsu',
searchForElements: 'Cari elemen',
selectAll: 'Pilih Semua',
queryDuration: 'Durasi kueri',
includeBetaUpdates: 'Sertakan pembaruan beta',
setNull: 'Tetapkan NULL',
processesList: 'Daftar proses',
processInfo: 'Info proses',
manageUsers: 'Kelola pengguna',
createNewSchema: 'Buat skema baru',
schemaName: 'Nama skema',
editSchema: 'Edit skema',
deleteSchema: 'Hapus skema',
markdownSupported: 'Markdown didukung',
plantATree: 'Menanam pohon',
dataTabPageSize: 'Ukuran halaman tab DATA',
enableSsh: 'Aktifkan SSH',
pageNumber: 'Nomor halaman',
duplicateTable: 'Duplikat Tabel',
noOpenTabs: 'Tidak ada tab terbuka, navigasikan di bilah kiri atau:',
noSchema: 'Tidak ada skema',
restorePreviourSession: 'Kembalikan sesi sebelumnya',
runQuery: 'Jalankan kueri',
thereAreNoTableFields: 'Tidak ada bidang tabel',
newTable: 'Tabel baru',
newView: 'Pemandangan baru',
newTrigger: 'Pemicu baru',
newRoutine: 'Rutinitas baru',
newFunction: 'Fungsi baru',
newScheduler: 'Penjadwal baru',
newTriggerFunction: 'Fungsi pemicu baru',
thereIsNoQueriesYet: 'Belum ada kueri',
searchForQueries: 'Telusuri kueri',
killProcess: 'Membunuh proses',
closeTab: 'Tutup tab',
exportSchema: 'Skema ekspor',
importSchema: 'Skema impor',
directoryPath: 'Jalur direktori',
newInserStmtEvery: 'Pernyataan INSERT baru setiap',
processingTableExport: 'Memproses {table}',
fechingTableExport: 'Mengambil data {table}',
writingTableExport: 'Menulis data {table} ',
checkAllTables: 'Periksa semua tabel',
uncheckAllTables: 'Hapus centang semua tabel',
goToDownloadPage: 'Buka halaman unduh',
readOnlyMode: 'Mode hanya baca',
killQuery: 'Bunuh kueri',
insertRow: 'Sisipkan baris | Sisipkan baris',
commitMode: 'Mode komit',
autoCommit: 'Komit otomatis',
manualCommit: 'Komit manual',
actionSuccessful: '{aksi} berhasil',
importQueryErrors: 'Peringatan: {n} kesalahan telah terjadi | Peringatan: {n} kesalahan telah terjadi',
executedQueries: '{n} permintaan dieksekusi | {n} permintaan dieksekusi',
ourputFormat: 'Format keluaran',
singleFile: 'File {ext} tunggal',
zipCompressedFile: 'File {ext} terkompresi ZIP',
disableBlur: 'Nonaktifkan buram',
untrustedConnection: 'Koneksi tidak tepercaya',
missingOrIncompleteTranslation: 'Terjemahan hilang atau tidak lengkap?',
findOutHowToContribute: 'Cari tahu cara berkontribusi',
disableFKChecks: 'Nonaktifkan pemeriksaan kunci asing',
allConnections: 'Semua koneksi',
searchForConnections: 'Cari koneksi',
disableScratchpad: 'Nonaktifkan papan tulis',
reportABug: 'Laporkan bug',
nextTab: 'Tab setelahnya',
previousTab: 'Tab sebelumnya',
selectTabNumber: 'Pilih nomor tab {param}',
toggleConsole: 'Toggle console',
addShortcut: 'Tambah shortcut',
editShortcut: 'Ubah shortcut',
deleteShortcut: 'Hapus shortcut',
restoreDefaults: 'Kembalikan ke default',
restoreDefaultsQuestion: 'Apakah kamu yakin untuk dikembalikan seperti setelan awal?',
registerAShortcut: 'Daftarkan shortcut',
invalidShortcutMessage: 'Kombinasi salah, lanjutkan mengetik',
shortcutAlreadyExists: 'Shortcut sudah ada',
saveContent: 'Simpan content',
openAllConnections: 'Buka semua koneksi',
openSettings: 'Buka Pengaturan',
openScratchpad: 'Buka scratchpad',
runOrReload: 'Jalankan atau reload',
formatQuery: 'Format query',
queryHistory: 'Histori query',
clearQuery: 'Bersihkan query',
openFilter: 'Buka filter',
nextResultsPage: 'Next results page',
previousResultsPage: 'Previous results page',
fillCell: 'Isi sel',
editFolder: 'Ubah folder',
folderName: 'Nama folder',
deleteFolder: 'Hapus folder',
editConnectionAppearence: 'Ubah connection appearence',
executeSelectedQuery: 'Eksekusi query yang dipilih',
defaultCopyType: 'Jenis salin default'
},
faker: {
address: 'Alamat',
commerce: 'Perdagangan',
company: 'Perusahaan',
database: 'Basis data',
date: 'Tanggal',
finance: 'Keuangan',
git: 'Git',
hacker: 'Peretas',
internet: 'Internet',
lorem: 'Lorem',
name: 'Nama',
music: 'Musik',
phone: 'Telepon',
random: 'Acak',
system: 'Sistem',
time: 'Waktu',
vehicle: 'Kendaraan',
zipCode: 'Kode Pos',
zipCodeByState: 'Kode pos menurut negara bagian',
city: 'Kota',
cityPrefix: 'Awalan kota',
citySuffix: 'Akhiran kota',
streetName: 'nama jalan',
streetAddress: 'alamat jalan',
streetSuffix: 'Akhiran jalan',
streetPrefix: 'Awalan jalan',
secondaryAddress: 'Alamat sekunder',
county: 'daerah',
country: 'Negara',
countryCode: 'Kode negara',
state: 'Negara',
stateAbbr: 'Singkatan negara',
latitude: 'Garis Lintang',
longitude: 'Garis bujur',
direction: 'Arah',
cardinalDirection: 'Arah mata angin',
ordinalDirection: 'Arah ordinal',
nearbyGPSCoordinate: 'Koordinat GPS terdekat',
timeZone: 'Zona waktu',
color: 'Warna',
department: 'Departemen',
productName: 'Nama Produk',
price: 'Harga',
productAdjective: 'Kata sifat produk',
productMaterial: 'Bahan produk',
product: 'Produk',
productDescription: 'Deskripsi Produk',
suffixes: 'Akhiran',
companyName: 'Nama Perusahaan',
companySuffix: 'Akhiran perusahaan',
catchPhrase: 'Menangkap frase',
bs: 'BS',
catchPhraseAdjective: 'Menangkap frase kata sifat',
catchPhraseDescriptor: 'Menangkap frase deskriptor',
catchPhraseNoun: 'Menangkap frase kata benda',
bsAdjective: 'kata sifat BS',
bsBuzz: 'BS berdengung',
bsNoun: 'kata benda BS',
column: 'Kolom',
type: 'Jenis',
collation: 'Pemeriksaan',
engine: 'Mesin',
past: 'Masa lalu',
future: 'Masa depan',
between: 'Di antara',
recent: 'Terkini',
soon: 'Segera',
month: 'Bulan',
weekday: 'Hari kerja',
account: 'Akun',
accountName: 'Nama akun',
routingNumber: 'Nomor perutean',
mask: 'Masker',
amount: 'Jumlah',
transactionType: 'Tipe transaksi',
currencyCode: 'Kode mata uang',
currencyName: 'Nama mata uang',
currencySymbol: 'Simbol mata uang',
bitcoinAddress: 'Alamat Bitcoin',
litecoinAddress: 'Alamat Litecoin',
creditCardNumber: 'Nomor kartu kredit',
creditCardCVV: 'CVV kartu kredit',
ethereumAddress: 'Alamat Ethereum',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Deskripsi transaksi',
branch: 'Cabang',
commitEntry: 'Lakukan entri',
commitMessage: 'Pesan komit',
commitSha: 'Lakukan SHA',
shortSha: 'SHA pendek',
abbreviation: 'Singkatan',
adjective: 'Kata sifat',
noun: 'Kata benda',
verb: 'Kata kerja',
ingverb: 'Ingverb',
phrase: 'Frasa',
avatar: 'Avatar',
email: 'Email',
exampleEmail: 'Contoh email',
userName: 'Username',
protocol: 'Protokol',
url: 'Url',
domainName: 'Nama domain',
domainSuffix: 'Akhiran domain',
domainWord: 'Kata domain',
ip: 'IP',
ipv6: 'IPv6',
userAgent: 'Agen pengguna',
mac: 'Mac',
password: 'Kata sandi',
word: 'Kata',
words: 'Kata-kata',
sentence: 'Kalimat',
slug: 'Siput',
sentences: 'Kalimat-kalimat',
paragraph: 'Gugus kalimat',
paragraphs: 'Paragraf',
text: 'Teks',
lines: 'Garis',
genre: 'Aliran',
firstName: 'Nama depan',
lastName: 'Nama keluarga',
middleName: 'Nama tengah',
findName: 'Nama lengkap',
jobTitle: 'Judul pekerjaan',
gender: 'Jenis kelamin',
prefix: 'Awalan',
suffix: 'Akhiran',
title: 'Judul',
jobDescriptor: 'Deskriptor pekerjaan',
jobArea: 'Bidang pekerjaan',
jobType: 'Jenis pekerjaan',
phoneNumber: 'Nomor telepon',
phoneNumberFormat: 'Format nomor telepon',
phoneFormats: 'Format telepon',
number: 'Nomor',
float: 'Mengambang',
arrayElement: 'Elemen array',
arrayElements: 'Elemen2 array',
objectElement: 'Elemen objek',
uuid: 'Uuid',
boolean: 'Boolean',
image: 'Gambar',
locale: 'Lokal',
alpha: 'Alfa',
alphaNumeric: 'Alfanumerik',
hexaDecimal: 'Heksadesimal',
fileName: 'Nama file',
commonFileName: 'Nama file umum',
mimeType: 'Jenis mime',
commonFileType: 'Jenis file umum',
commonFileExt: 'Ekstensi file umum',
fileType: 'Jenis file',
fileExt: 'Ekstensi file',
directoryPath: 'Jalur direktori',
filePath: 'Jalur file',
semver: 'Semver',
manufacturer: 'Pabrikan',
model: 'Model',
fuel: 'Fuel',
vin: 'Vin'
}
};

View File

@@ -10,6 +10,7 @@ import { viVN } from './vi-VN';
import { jaJP } from './ja-JP'; import { jaJP } from './ja-JP';
import { zhCN } from './zh-CN'; import { zhCN } from './zh-CN';
import { ruRU } from './ru-RU'; import { ruRU } from './ru-RU';
import { idID } from './id-ID';
const messages = { const messages = {
'en-US': enUS, 'en-US': enUS,
@@ -22,7 +23,8 @@ const messages = {
'vi-VN': viVN, 'vi-VN': viVN,
'ja-JP': jaJP, 'ja-JP': jaJP,
'zh-CN': zhCN, 'zh-CN': zhCN,
'ru-RU': ruRU 'ru-RU': ruRU,
'id-ID': idID
}; };
type NestedPartial<T> = { type NestedPartial<T> = {

View File

@@ -138,7 +138,16 @@ export const ptBR = {
commit: 'Enviar', commit: 'Enviar',
rollback: 'Reverter', rollback: 'Reverter',
connectionString: 'String da conexão', connectionString: 'String da conexão',
contributors: 'Contribuintes' contributors: 'Contribuintes',
pin: 'Fixar',
unpin: 'Desafixar',
console: 'Console',
shortcuts: 'Atalhos',
folder: 'Pasta | Pastas',
appearence: 'Aparência',
color: 'Cor',
label: 'Rótulo',
icon: 'Icone'
}, },
message: { message: {
appWelcome: 'Bem vindo ao Antares SQL Client!', appWelcome: 'Bem vindo ao Antares SQL Client!',
@@ -293,7 +302,35 @@ export const ptBR = {
allConnections: 'Todas as conexões', allConnections: 'Todas as conexões',
searchForConnections: 'Procurar por conexões', searchForConnections: 'Procurar por conexões',
disableScratchpad: 'Desativar bloco de notas', disableScratchpad: 'Desativar bloco de notas',
reportABug: 'Reportar um problema' reportABug: 'Reportar um problema',
nextTab: 'Próxima guia',
previousTab: 'Guia anterior',
selectTabNumber: 'Selecionar guia número {param}',
toggleConsole: 'Alterar console',
addShortcut: 'Adicionar Atalho',
editShortcut: 'Editar atalho',
deleteShortcut: 'Delete shortcut',
restoreDefaults: 'Restaurar padrões',
restoreDefaultsQuestion: 'Você confirma que quer restaurar os valores padrões?',
registerAShortcut: 'Registrar um atalho',
invalidShortcutMessage: 'Combinação inválida, continue digitando',
shortcutAlreadyExists: 'O atalho já existe',
saveContent: 'Salvar conteúdo',
openAllConnections: 'Abrir todas as conexões',
openSettings: 'Abrir Configurações',
openScratchpad: 'Abrir scratchpad',
runOrReload: 'Executar ou recarregar',
formatQuery: 'Formatar consulta',
queryHistory: 'Histórico de consulta',
clearQuery: 'Limpar consulta',
openFilter: 'Abrir Filtro',
nextResultsPage: 'Próxima página de resultados',
previousResultsPage: 'Página de resultados anterior',
fillCell: 'Preencher Célula',
editFolder: 'Editar Pasta',
folderName: 'Nome da pasta',
deleteFolder: 'Apagar Pasta',
editConnectionAppearence: 'Editar aparência da conexão'
}, },
faker: { faker: {
address: 'Endereço', address: 'Endereço',

View File

@@ -9,5 +9,6 @@ export const localesNames: {[key: string]: string} = {
'vi-VN': 'Tiếng Việt', 'vi-VN': 'Tiếng Việt',
'ja-JP': '日本語', 'ja-JP': '日本語',
'zh-CN': '简体中文', 'zh-CN': '简体中文',
'ru-RU': 'Русский' 'ru-RU': 'Русский',
'id-ID': 'Bahasa Indonesia'
}; };

View File

@@ -8,7 +8,7 @@ export const zhCN = {
cancel: '取消', cancel: '取消',
send: '发送', send: '发送',
connectionName: '连接名称', connectionName: '连接名称',
client: 'Client', client: '数据库类型',
hostName: '主机名', hostName: '主机名',
port: '端口', port: '端口',
user: '用户', user: '用户',
@@ -17,10 +17,10 @@ export const zhCN = {
connect: '连接', connect: '连接',
connected: '已连接', connected: '已连接',
disconnect: '断开连接', disconnect: '断开连接',
disconnected: '断开', disconnected: '断开连接',
refresh: '刷新', refresh: '刷新',
settings: '设置', settings: '设置',
general: '一般', general: '常规',
themes: '主题', themes: '主题',
update: '更新', update: '更新',
about: '关于', about: '关于',
@@ -28,7 +28,7 @@ export const zhCN = {
version: '版本', version: '版本',
donate: '捐赠', donate: '捐赠',
run: '运行', run: '运行',
schema: 'schema', schema: '模式(schema)',
results: '结果', results: '结果',
size: '尺寸', size: '尺寸',
seconds: '秒', seconds: '秒',
@@ -51,38 +51,38 @@ export const zhCN = {
unsigned: '无符号', unsigned: '无符号',
default: '默认', default: '默认',
comment: '注释', comment: '注释',
key: 'Key | Keys', key: '键位 | 键位',
order: 'Order', order: '排序',
expression: '表达式', expression: '表达式',
autoIncrement: '自动增量', autoIncrement: '自动增量',
engine: 'Engine', engine: '引擎',
field: 'Field | 字段', field: '字段 | 字段',
approximately: '大约', approximately: '大约',
total: '总计', total: '总计',
table: '表', table: '表',
discard: '弃', discard: '弃',
stay: '等待', stay: '等待',
author: '作者', author: '作者',
light: 'Light', light: '明亮',
dark: 'Dark', dark: '暗黑',
autoCompletion: '自动完成', autoCompletion: '自动完成',
application: '应用程序', application: '应用程序',
editor: '编辑器', editor: '编辑器',
view: '视图', view: '视图',
definer: '定义者', definer: '定义者',
algorithm: 'Algorithm', algorithm: '算法',
trigger: 'Trigger | 触发器', trigger: '触发器 | 触发器',
storedRoutine: 'Stored routine | 存储例程', storedRoutine: '存储例程 | 存储例程',
scheduler: 'Scheduler | 调度器', scheduler: '调度器 | 调度器',
event: '事件', event: '事件',
parameters: '参数', parameters: '参数',
function: 'Function | 函数', function: '函数 | 函数',
deterministic: 'Deterministic', deterministic: '确定的',
context: '上下文', context: '上下文',
export: '导出', export: '导出',
import: '导入', import: '导入',
returns: '返回', returns: '返回',
timing: '定时', timing: '定时',
state: '状态', state: '状态',
execution: '执行', execution: '执行',
starts: '开始', starts: '开始',
@@ -91,10 +91,10 @@ export const zhCN = {
privateKey: '私钥', privateKey: '私钥',
certificate: '证书', certificate: '证书',
caCertificate: 'CA 证书', caCertificate: 'CA 证书',
ciphers: 'Ciphers', ciphers: '密码',
upload: '上传', upload: '上传',
browse: '浏览', browse: '浏览',
faker: 'Faker', faker: '伪造者',
content: '内容', content: '内容',
cut: '剪切', cut: '剪切',
copy: '复制', copy: '复制',
@@ -103,25 +103,25 @@ export const zhCN = {
variables: '变量', variables: '变量',
processes: '进程', processes: '进程',
database: '数据库', database: '数据库',
scratchpad: 'Scratchpad', scratchpad: '草稿栏',
array: '数组', array: '数组',
changelog: '更日志', changelog: '更日志',
format: '格式', format: '格式',
sshTunnel: 'SSH 隧道', sshTunnel: 'SSH 隧道',
structure: '结构', structure: '结构',
small: '小', small: '小',
medium: '中', medium: '中',
large: '大', large: '大',
row: 'Row | 行', row: ' | 行',
cell: 'Cell | 单元格', cell: '单元格 | 单元格',
triggerFunction: 'Trigger function | 触发函数', triggerFunction: '触发函数 | 触发函数',
all: '全部', all: '全部',
duplicate: '重复', duplicate: '重复',
routine: '例程', routine: '例程',
new: 'New', new: '',
history: '历史记录', history: '历史',
select: '选择', select: '选择',
passphrase: '密码', passphrase: '密码短语',
filter: '过滤器', filter: '过滤器',
change: '变更', change: '变更',
views: '视图', views: '视图',
@@ -129,131 +129,139 @@ export const zhCN = {
routines: '例程', routines: '例程',
functions: '函数', functions: '函数',
schedulers: '调度器', schedulers: '调度器',
includes: '包', includes: '包',
drop: '按下([使]掉下)', drop: '下降',
completed: '完成', completed: '完成',
aborted: '中止', aborted: '中止',
disabled: '禁用', disabled: '禁用',
enable: '启用', enable: '启用',
disable: '是否禁用', disable: '禁用',
commit: '提交', commit: '提交',
rollback: '回滚', rollback: '回滚',
connectionString: '连接字符串', connectionString: '连接字符串',
contributors: '贡献者', contributors: '贡献者',
pin: 'Pin', pin: '固定',
unpin: 'Unpin' unpin: '取消固定',
console: '控制台',
shortcuts: '快捷键',
folder: '文件夹 | 文件夹',
appearence: '外观',
color: '颜色',
label: '标签',
icon: '图标',
resultsTable: '结果表'
}, },
message: { message: {
appWelcome: '欢迎来到Antares SQL Client!', appWelcome: '欢迎来到Antares SQL Client!',
appFirstStep: '你的第一步: 创建一个新的数据库连接.', appFirstStep: '你的第一步: 创建一个新的数据库连接.',
addConnection: '添加连接', addConnection: '添加连接',
createConnection: '创建连接', createConnection: '创建连接',
createNewConnection: '创建新连接', createNewConnection: '创建新连接',
askCredentials: '询问凭', askCredentials: '询问凭',
testConnection: '测试连接', testConnection: '测试连接',
editConnection: '编辑连接', editConnection: '编辑连接',
deleteConnection: '删除连接', deleteConnection: '删除连接',
deleteCorfirm: '您是否确认取消', deleteCorfirm: '您是否确认取消',
connectionSuccessfullyMade: '连接成功建立!', connectionSuccessfullyMade: '连接成功!',
madeWithJS: '用💛和JavaScript制造!', madeWithJS: '使用 💛 和 JavaScript 制作!',
checkForUpdates: '检查更新', checkForUpdates: '检查更新',
noUpdatesAvailable: '没有可用更新', noUpdatesAvailable: '可用更新',
checkingForUpdate: '正在检查更新', checkingForUpdate: '正在检查更新',
checkFailure: '检查失败,请稍后再试', checkFailure: '检查失败,请稍后再试',
updateAvailable: '可用更新', updateAvailable: '可用更新',
downloadingUpdate: '正在下载更新', downloadingUpdate: '正在下载更新',
updateDownloaded: '更新已下载', updateDownloaded: '已下载更新',
restartToInstall: '重启Antares完成更新', restartToInstall: '重启 Antares 以进行安装',
unableEditFieldWithoutPrimary: '无法编辑一个在结果集中没有主键的字段', unableEditFieldWithoutPrimary: '无法编辑结果集中一个没有主键的字段',
editCell: '编辑单元格', editCell: '编辑单元格',
deleteRows: '删除行 | 删除{count}行', deleteRows: '删除行 | 删除 {count} 行',
confirmToDeleteRows: '是否确认要删除一行? | 您是否确认要删除{count}行?', confirmToDeleteRows: '是否确认要删除一行? | 您是否确认要删除 {count} 行?',
notificationsTimeout: '通知超时', notificationsTimeout: '通知超时',
uploadFile: '上传文件', uploadFile: '上传文件',
addNewRow: '添加新行', addNewRow: '添加新行',
numberOfInserts: '插入的数量', numberOfInserts: '插入的数量',
openNewTab: '打开一个新标签', openNewTab: '打开一个新标签',
affectedRows: '受影响的行', affectedRows: '受影响的行',
createNewDatabase: '创建新数据库', createNewDatabase: '创建新数据库',
databaseName: '数据库名称', databaseName: '数据库名称',
serverDefault: '默认服务器', serverDefault: '默认服务器',
deleteDatabase: '删除数据库', deleteDatabase: '删除数据库',
editDatabase: '编辑数据库', editDatabase: '编辑数据库',
clearChanges: '清除变化', clearChanges: '清除更改',
addNewField: '添加新字段', addNewField: '添加新字段',
manageIndexes: '管理索引', manageIndexes: '管理索引',
manageForeignKeys: '管理外键', manageForeignKeys: '管理外键',
allowNull: '允许NULL', allowNull: '允许 NULL',
zeroFill: '填充', zeroFill: '填充',
customValue: '自定义值', customValue: '自定义值',
onUpdate: '在更新', onUpdate: '在更新',
deleteField: '删除字段', deleteField: '删除字段',
createNewIndex: '创建新索引', createNewIndex: '创建新索引',
addToIndex: '添加到索引', addToIndex: '添加到索引',
createNewTable: '创建新表', createNewTable: '创建新表',
emptyTable: '清空表', emptyTable: '清空表',
deleteTable: '删除表', deleteTable: '删除表',
emptyCorfirm: '是否确认清空', emptyCorfirm: '是否确认清空',
unsavedChanges: '未保存的更改', unsavedChanges: '未保存的更改',
discardUnsavedChanges: '有一些未保存的修改。关闭这个标签,这些变化将被丢弃.', discardUnsavedChanges: '有一些未保存的更改, 关闭此标签将放弃这些更改.',
thereAreNoIndexes: '没有索引', thereAreNoIndexes: '没有索引',
thereAreNoForeign: '没有外键', thereAreNoForeign: '没有外键',
createNewForeign: '创建新外键', createNewForeign: '创建新外键',
referenceTable: '参考表', referenceTable: '参考表',
referenceField: '参考字段', referenceField: '参考字段',
foreignFields: '外键字段', foreignFields: '外键字段',
invalidDefault: '无效的默认值', invalidDefault: '无效的默认值',
onDelete: '在删除', onDelete: '在删除',
applicationTheme: '应用主题', applicationTheme: '应用程序主题',
editorTheme: '编辑器主题', editorTheme: '编辑器主题',
wrapLongLines: '超出换行显示', wrapLongLines: '将长行换行显示',
selectStatement: '选择语句', selectStatement: '选择语句',
triggerStatement: '触发器语句', triggerStatement: '触发器语句',
sqlSecurity: 'SQL安全', sqlSecurity: 'SQL 安全',
updateOption: '更新选项', updateOption: '更新选项',
deleteView: '删除视图', deleteView: '删除视图',
createNewView: '创建新视图', createNewView: '创建新视图',
deleteTrigger: '删除触发器', deleteTrigger: '删除触发器',
createNewTrigger: '创建新触发器', createNewTrigger: '创建新触发器',
currentUser: '当前用户', currentUser: '当前用户',
routineBody: '例程主体', routineBody: '例程主体',
dataAccess: '数据访问', dataAccess: '数据访问',
thereAreNoParameters: '没有参数', thereAreNoParameters: '没有参数',
createNewParameter: '创建新参数', createNewParameter: '创建新参数',
createNewRoutine: '创建新例程', createNewRoutine: '创建新存储例程',
deleteRoutine: '删除例程', deleteRoutine: '删除存储例程',
functionBody: '函数体', functionBody: '函数体',
createNewFunction: '创建新函数', createNewFunction: '创建新函数',
deleteFunction: '删除函数', deleteFunction: '删除函数',
schedulerBody: '调度器主体', schedulerBody: '调度器主体',
createNewScheduler: '创建新调度器', createNewScheduler: '创建新调度器',
deleteScheduler: '删除调度器', deleteScheduler: '删除调度器',
preserveOnCompletion: '完成时保存', preserveOnCompletion: '完成时保存',
enableSsl: '启用SSL', enableSsl: '启用 SSL',
manualValue: '手动值', manualValue: '手动值',
tableFiller: '表填充器', tableFiller: '表填充器',
fakeDataLanguage: '伪造的数据语言', fakeDataLanguage: '伪造的数据语言',
searchForElements: '搜索元素', searchForElements: '搜索元素',
selectAll: '选择所有', selectAll: '选择全部',
queryDuration: '查询时间', queryDuration: '查询时间',
includeBetaUpdates: '包测试版更新', includeBetaUpdates: '包测试版更新',
setNull: '设置NULL', setNull: '设置 NULL',
processesList: '进程列表', processesList: '进程列表',
processInfo: '进程信息', processInfo: '进程信息',
manageUsers: '管理用户', manageUsers: '管理用户',
createNewSchema: '创建新模式', createNewSchema: '创建新模式(schema)',
schemaName: '模式名称', schemaName: '模式名称',
editSchema: '编辑模式', editSchema: '编辑模式',
deleteSchema: '删除模式', deleteSchema: '删除模式',
markdownSupported: '支持Markdown', markdownSupported: '支持 Markdown',
plantATree: '种植一棵树', plantATree: '种植一棵树',
dataTabPageSize: '数据标签的页面大小', dataTabPageSize: '数据标签的页面大小',
enableSsh: '启用SSH', enableSsh: '启用 SSH',
pageNumber: '页数', pageNumber: '页数',
duplicateTable: '重复的表', duplicateTable: '重复的表',
noOpenTabs: '没有打开的标签,在左栏导航或:', noOpenTabs: '没有打开的标签, 请在左侧栏上导航或:',
noSchema: '没有模式', noSchema: '没有模式',
restorePreviourSession: '恢复以前的会话', restorePreviourSession: '恢复上一个会话',
runQuery: '运行查询', runQuery: '运行查询',
thereAreNoTableFields: '没有表的字段', thereAreNoTableFields: '没有表的字段',
newTable: '新表', newTable: '新表',
@@ -263,40 +271,73 @@ export const zhCN = {
newFunction: '新函数', newFunction: '新函数',
newScheduler: '新调度器', newScheduler: '新调度器',
newTriggerFunction: '新触发函数', newTriggerFunction: '新触发函数',
thereIsNoQueriesYet: '还没有查询', thereIsNoQueriesYet: '目前还没有任何查询',
searchForQueries: '搜索查询', searchForQueries: '搜索查询',
killProcess: '杀死进程', killProcess: '终止进程',
closeTab: '关闭标签', closeTab: '关闭标签',
exportSchema: '导出 schema', exportSchema: '导出模式(schema)',
importSchema: '导入 schema', importSchema: '导入模式(schema)',
directoryPath: '目录路径', directoryPath: '目录路径',
newInserStmtEvery: '新的 INSERT 语句每个', newInserStmtEvery: '每条新的 INSERT 语句',
processingTableExport: '处理 {table}', processingTableExport: '处理 {table}',
fechingTableExport: '正在 {table} 获取数据', fechingTableExport: '正在获取 {table} 数据',
writingTableExport: '正在 {table} 数据写入中', writingTableExport: '正在写入 {table} 数据',
checkAllTables: '检查所有表', checkAllTables: '勾选所有表',
uncheckAllTables: '取消选中所有表', uncheckAllTables: '不勾选所有表',
goToDownloadPage: '转到下载页面', goToDownloadPage: '转到下载页面',
readOnlyMode: '只读模式', readOnlyMode: '只读模式',
killQuery: '停止查询', killQuery: '取消查询',
insertRow: '插入行 | 插入多行', insertRow: '插入行 | 插入多行',
commitMode: '提交模式', commitMode: '提交模式',
autoCommit: '自动提交', autoCommit: '自动提交',
manualCommit: '手动提交', manualCommit: '手动提交',
actionSuccessful: '{action} 成功', actionSuccessful: '{action} 成功',
importQueryErrors: '警告: {n} 错误已发生 | 警告: 发生 {n} 个错误', importQueryErrors: '警告: 发生了 {n} 错误 | 警告: 发生 {n} 个错误',
executedQueries: '{n} 个查询已执行 | {n} 个查询已执行', executedQueries: '{n} 个查询已执行 | {n} 个查询已执行',
ourputFormat: '输出格式', ourputFormat: '输出格式',
singleFile: '单个 {ext} 文件', singleFile: '单个 {ext} 文件',
zipCompressedFile: 'ZIP 压缩 {ext} 文件', zipCompressedFile: 'ZIP 压缩 {ext} 文件',
disableBlur: '禁用模糊', disableBlur: '禁用模糊',
untrustedConnection: '不受信任的连接', untrustedConnection: '不受信任的连接',
missingOrIncompleteTranslation: '翻译缺失或不完整?', missingOrIncompleteTranslation: '缺失或不完整的翻译?',
findOutHowToContribute: '找出如何贡献', findOutHowToContribute: '了解如何做出贡献',
disableFKChecks: '禁用外键检查', disableFKChecks: '禁用外键检查',
allConnections: '所有连接', allConnections: '所有连接',
searchForConnections: '搜索连接', searchForConnections: '搜索连接',
disableScratchpad: '禁用暂存器' disableScratchpad: '禁用草稿栏',
reportABug: '报告错误',
nextTab: '下一个标签',
previousTab: '上一个标签',
selectTabNumber: '选择标签编号 {param}',
toggleConsole: '切换控制台',
addShortcut: '添加快捷键',
editShortcut: '编辑快捷键',
deleteShortcut: '删除快捷键',
restoreDefaults: '恢复默认值',
restoreDefaultsQuestion: '是否确认恢复默认值?',
registerAShortcut: '注册快捷键',
invalidShortcutMessage: '无效组合,请继续输入',
shortcutAlreadyExists: '快捷键已经存在',
saveContent: '保存内容',
openAllConnections: '打开全部连接',
openSettings: '打开设置',
openScratchpad: '打开草稿栏',
runOrReload: '运行或重新加载',
formatQuery: '格式查询',
queryHistory: '查询历史',
clearQuery: '清除查询',
openFilter: '打开过滤器',
nextResultsPage: '下一个结果页',
previousResultsPage: '上一个结果页',
fillCell: '填充单元格',
editFolder: '编辑文件夹',
folderName: '文件夹名称',
deleteFolder: '删除文件夹',
editConnectionAppearence: '编辑连接的外观',
executeSelectedQuery: '执行所选查询',
defaultCopyType: '默认复制类型',
showTableSize: '在侧边栏显示表大小',
showTableSizeDescription: '仅限 MySQL/MariaDB. 启用此选项可能会影响许多表的模式(schema)的性能.'
}, },
faker: { faker: {
address: '地址', address: '地址',
@@ -306,8 +347,8 @@ export const zhCN = {
date: '日期', date: '日期',
finance: '财务', finance: '财务',
git: 'Git', git: 'Git',
hacker: 'Hacker', hacker: '黑客',
internet: 'Internet', internet: '互联网',
lorem: 'Lorem', lorem: 'Lorem',
name: '姓名', name: '姓名',
music: '音乐', music: '音乐',
@@ -317,26 +358,26 @@ export const zhCN = {
time: '时间', time: '时间',
vehicle: '车辆', vehicle: '车辆',
zipCode: '邮政编码', zipCode: '邮政编码',
zipCodeByState: '州的邮', zipCodeByState: '州的邮政编码',
city: '城市', city: '城市',
cityPrefix: '城市前缀', cityPrefix: '城市前缀',
citySuffix: '城市后缀', citySuffix: '城市后缀',
streetName: '街道名称', streetName: '街道名称',
streetAddress: '街道地址', streetAddress: '街道地址',
streetSuffix: '街道缀', streetSuffix: '街道缀',
streetPrefix: '街道缀', streetPrefix: '街道缀',
secondaryAddress: '次要地址', secondaryAddress: '次要地址',
county: '县', county: '县',
country: '国家', country: '国家',
countryCode: '国家代码', countryCode: '国家代码',
state: '州', state: '州',
stateAbbr: '州的缩写', stateAbbr: '州简称',
latitude: '纬度', latitude: '纬度',
longitude: '经度', longitude: '经度',
direction: '方向', direction: '方向',
cardinalDirection: 'Cardinal direction', cardinalDirection: '方位',
ordinalDirection: 'Ordinal direction', ordinalDirection: '顺序方向',
nearbyGPSCoordinate: '附近的GPS坐标', nearbyGPSCoordinate: '附近的 GPS 坐标',
timeZone: '时区', timeZone: '时区',
color: '颜色', color: '颜色',
department: '部门', department: '部门',
@@ -345,23 +386,24 @@ export const zhCN = {
productAdjective: '产品形容词', productAdjective: '产品形容词',
productMaterial: '产品材料', productMaterial: '产品材料',
product: '产品', product: '产品',
productDescription: '产品描述', productDescription: '产品说明',
suffixes: '后缀', suffixes: '后缀',
companyName: '公司名称', companyName: '公司名称',
companySuffix: '公司后缀', companySuffix: '公司后缀',
catchPhrase: 'Catch phrase', catchPhrase: '流行语',
bs: 'BS', bs: 'BS',
catchPhraseAdjective: 'Catch phrase adjective', catchPhraseAdjective: '流行语形容词',
catchPhraseDescriptor: 'Catch phrase descriptor', catchPhraseDescriptor: '流行语说明',
catchPhraseNoun: 'Catch phrase noun', catchPhraseNoun: '流行语名词',
bsAdjective: 'BS adjective', bsAdjective: 'BS 形容词',
bsBuzz: 'BS buzz', bsBuzz: 'BS 嗡嗡声',
bsNoun: 'BS noun', bsNoun: 'BS 名称',
column: '列', column: '列',
type: '类型', type: '类型',
collation: '校对', collation: '排序规则',
engine: 'Engine', engine: '引擎',
past: '过去', past: '过去',
now: '现在',
future: '未来', future: '未来',
between: '之间', between: '之间',
recent: '最近', recent: '最近',
@@ -370,7 +412,7 @@ export const zhCN = {
weekday: '工作日', weekday: '工作日',
account: '账户', account: '账户',
accountName: '账户名称', accountName: '账户名称',
routingNumber: '路由号', routingNumber: '路由号',
mask: '掩码', mask: '掩码',
amount: '金额', amount: '金额',
transactionType: '交易类型', transactionType: '交易类型',
@@ -389,29 +431,29 @@ export const zhCN = {
commitEntry: '提交条目', commitEntry: '提交条目',
commitMessage: '提交信息', commitMessage: '提交信息',
commitSha: '提交 SHA', commitSha: '提交 SHA',
shortSha: 'Short SHA', shortSha: '短的 SHA',
abbreviation: '缩写', abbreviation: '缩写',
adjective: '形容词', adjective: '形容词',
noun: '名词', noun: '名词',
verb: '动词', verb: '动词',
ingverb: 'Ingverb', ingverb: '英式动词',
phrase: '短语', phrase: '短语',
avatar: '头像', avatar: '头像',
email: '电子邮箱', email: '电子邮箱',
exampleEmail: '电子邮件例子', exampleEmail: '示例电子邮件',
userName: '用户名', userName: '用户名',
protocol: '协议', protocol: '协议',
url: 'Url', url: '统一资源定位地址',
domainName: 'Domin name', domainName: '域名',
domainSuffix: '域名后缀', domainSuffix: '域名后缀',
domainWord: 'Domain word', domainWord: '域词',
ip: 'Ip', ip: 'Ip',
ipv6: 'Ipv6', ipv6: 'Ipv6',
userAgent: 'User agent', userAgent: '用户代理',
mac: 'Mac', mac: 'Mac',
password: '密码', password: '密码',
word: 'Word', word: '单词',
words: 'Words', words: '单词',
sentence: '句子', sentence: '句子',
slug: 'Slug', slug: 'Slug',
sentences: '句子', sentences: '句子',
@@ -419,7 +461,7 @@ export const zhCN = {
paragraphs: '段落', paragraphs: '段落',
text: '文本', text: '文本',
lines: '行', lines: '行',
genre: 'Genre', genre: '类型',
firstName: '名', firstName: '名',
lastName: '姓氏', lastName: '姓氏',
middleName: '中间名', middleName: '中间名',
@@ -428,30 +470,30 @@ export const zhCN = {
gender: '性别', gender: '性别',
prefix: '前缀', prefix: '前缀',
suffix: '后缀', suffix: '后缀',
title: '标题', title: '头衔',
jobDescriptor: '工作描述', jobDescriptor: '工作描述',
jobArea: '工作领域', jobArea: '工作领域',
jobType: '工作类型', jobType: '工作类型',
phoneNumber: '电话号码', phoneNumber: '电话号码',
phoneNumberFormat: '电话号码格式', phoneNumberFormat: '电话号码格式',
phoneFormats: '电话格式', phoneFormats: '电话格式',
number: 'Number', number: '号码',
float: 'Float', float: 'Float',
arrayElement: '数组元素', arrayElement: '数组元素',
arrayElements: '数组元素', arrayElements: '数组元素',
objectElement: '对象元素', objectElement: '对象元素',
uuid: 'Uuid', uuid: 'Uuid',
boolean: 'Boolean', boolean: '布尔型',
image: 'Image', image: '镜像',
locale: 'Locale', locale: '区域',
alpha: 'Alpha', alpha: '阿尔法',
alphaNumeric: 'Alphanumeric', alphaNumeric: 'α 数字',
hexaDecimal: 'Hexadecimal', hexaDecimal: '十六进制',
fileName: '文件名', fileName: '文件名',
commonFileName: '普通文件名', commonFileName: '普通文件名',
mimeType: 'MIME类型', mimeType: 'MIME类型',
commonFileType: '常见文件类型', commonFileType: '常见文件类型',
commonFileExt: '常见文件扩展名', commonFileExt: '常见文件扩展名',
fileType: '文件类型', fileType: '文件类型',
fileExt: '文件扩展名', fileExt: '文件扩展名',
directoryPath: '目录路径', directoryPath: '目录路径',

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -3,7 +3,9 @@ import { ipcRenderer } from 'electron';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import { VueMaskDirective } from 'v-mask'; import { VueMaskDirective } from 'v-mask';
import * as FloatingVue from 'floating-vue';
import '@mdi/font/css/materialdesignicons.css'; import '@mdi/font/css/materialdesignicons.css';
import 'floating-vue/dist/style.css';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import '@/scss/main.scss'; import '@/scss/main.scss';
@@ -27,6 +29,7 @@ createApp(App)
.directive('mask', vMaskV3) .directive('mask', vMaskV3)
.use(createPinia()) .use(createPinia())
.use(i18n) .use(i18n)
.use(FloatingVue)
.mount('#app'); .mount('#app');
const { locale } = useSettingsStore(); const { locale } = useSettingsStore();

View File

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

View File

@@ -26,6 +26,8 @@
"macaddr8": $string-color, "macaddr8": $string-color,
"uuid": $string-color, "uuid": $string-color,
"regproc": $string-color, "regproc": $string-color,
"blob-text": $string-color,
"int": $number-color, "int": $number-color,
"tinyint": $number-color, "tinyint": $number-color,
"smallint": $number-color, "smallint": $number-color,
@@ -36,6 +38,7 @@
"bigint": $number-color, "bigint": $number-color,
"newdecimal": $number-color, "newdecimal": $number-color,
"integer": $number-color, "integer": $number-color,
"integer_unsigned": $number-color,
"numeric": $number-color, "numeric": $number-color,
"smallserial": $number-color, "smallserial": $number-color,
"serial": $number-color, "serial": $number-color,
@@ -44,6 +47,7 @@
"double_precision": $number-color, "double_precision": $number-color,
"oid": $number-color, "oid": $number-color,
"xid": $number-color, "xid": $number-color,
"money": $number-color, "money": $number-color,
"number": $number-color, "number": $number-color,
"datetime": $date-color, "datetime": $date-color,
@@ -54,20 +58,26 @@
"timestamp": $date-color, "timestamp": $date-color,
"timestamp_without_time_zone": $date-color, "timestamp_without_time_zone": $date-color,
"timestamp_with_time_zone": $date-color, "timestamp_with_time_zone": $date-color,
"bit": $bit-color, "bit": $bit-color,
"bit_varying": $bit-color, "bit_varying": $bit-color,
"binary": $blob-color, "binary": $blob-color,
"char-binary": $blob-color,
"varbinary": $blob-color, "varbinary": $blob-color,
"blob": $blob-color, "blob": $blob-color,
"tinyblob": $blob-color, "tinyblob": $blob-color,
"mediumblob": $blob-color, "mediumblob": $blob-color,
"medium_blob": $blob-color, "medium_blob": $blob-color,
"longblob": $blob-color, "longblob": $blob-color,
"long_blob": $blob-color,
"bytea": $blob-color, "bytea": $blob-color,
"enum": $enum-color, "enum": $enum-color,
"set": $enum-color, "set": $enum-color,
"bool": $enum-color, "bool": $enum-color,
"boolean": $enum-color, "boolean": $enum-color,
"interval": $array-color, "interval": $array-color,
"array": $array-color, "array": $array-color,
"anyarray": $array-color, "anyarray": $array-color,
@@ -84,6 +94,7 @@
"geomcollection": $array-color, "geomcollection": $array-color,
"geometrycollection": $array-color, "geometrycollection": $array-color,
"aclitem": $array-color, "aclitem": $array-color,
"unknown": $unknown-color, "unknown": $unknown-color,
) )
); );

View File

@@ -25,6 +25,10 @@
background-image: url("../images/svg/sqlite.svg"); background-image: url("../images/svg/sqlite.svg");
} }
&.dbi-firebird {
background-image: url("../images/svg/firebird.svg");
}
&.dbi-oracledb { &.dbi-oracledb {
background-image: url("../images/svg/oracledb.svg"); background-image: url("../images/svg/oracledb.svg");
} }

View File

@@ -21,6 +21,10 @@
color: limegreen; color: limegreen;
} }
&.key-fk {
color: chocolate;
}
&.key-FULLTEXT { &.key-FULLTEXT {
color: mediumvioletred; color: mediumvioletred;
} }

View File

@@ -43,6 +43,14 @@
animation: jump-down-in 0.2s reverse; animation: jump-down-in 0.2s reverse;
} }
.flip-list-move {
transition: transform 0.5s;
}
.no-move {
transition: transform 0s;
}
.pulse { .pulse {
animation-name: pulse; animation-name: pulse;
animation-duration: 2s; animation-duration: 2s;

View File

@@ -109,7 +109,8 @@ option:checked {
> div { > div {
padding: 0.1rem 0.2rem; padding: 0.1rem 0.2rem;
min-width: fill-available; /* stylelint-disable-next-line value-no-vendor-prefix */
min-width: -webkit-fill-available;
} }
} }

View File

@@ -209,6 +209,8 @@
background: $bg-color-light-dark; background: $bg-color-light-dark;
.tab-item { .tab-item {
background: $bg-color-light-dark;
> a { > a {
color: $body-font-color-dark; color: $body-font-color-dark;
} }

View File

@@ -2,8 +2,19 @@ import { defineStore } from 'pinia';
import * as Store from 'electron-store'; import * as Store from 'electron-store';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { ConnectionParams } from 'common/interfaces/antares'; import { ConnectionParams } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen';
const key = localStorage.getItem('key'); const key = localStorage.getItem('key');
export interface SidebarElement {
isFolder: boolean;
uid: string;
client?: string;
connections?: string[];
color?: string;
name?: string;
icon?: null | string;
}
if (!key) if (!key)
localStorage.setItem('key', crypto.randomBytes(16).toString('hex')); localStorage.setItem('key', crypto.randomBytes(16).toString('hex'));
else else
@@ -18,67 +29,175 @@ const persistentStore = new Store({
export const useConnectionsStore = defineStore('connections', { export const useConnectionsStore = defineStore('connections', {
state: () => ({ state: () => ({
connections: persistentStore.get('connections', []) as ConnectionParams[], connections: persistentStore.get('connections', []) as ConnectionParams[],
pinnedConnections: new Set([...persistentStore.get('pinnedConnections', []) as string[]]) as Set<string>, lastConnections: persistentStore.get('lastConnections', []) as {uid: string; time: number}[],
lastConnections: persistentStore.get('lastConnections', []) as {uid: string; time: number}[] connectionsOrder: persistentStore.get('connectionsOrder', []) as SidebarElement[]
}), }),
getters: { getters: {
getConnectionByUid: state => (uid:string) => state.connections.find(connection => connection.uid === uid),
getConnectionName: state => (uid: string) => { getConnectionName: state => (uid: string) => {
const connection = state.connections.filter(connection => connection.uid === uid)[0]; const connection = state.connections.find(connection => connection.uid === uid);
let connectionName = ''; let connectionName = '';
if (connection) {
if (connection.name)
connectionName = connection.name;
else if (connection.ask)
connectionName = `${connection.host}:${connection.port}`;
else if (connection.databasePath) {
let string = connection.databasePath.split(/[/\\]+/).pop();
if (connection.name) if (string.length >= 30)
connectionName = connection.name; string = `...${string.slice(-30)}`;
else if (connection.ask)
connectionName = `${connection.host}:${connection.port}`;
else if (connection.databasePath) {
let string = connection.databasePath.split(/[/\\]+/).pop();
if (string.length >= 30) connectionName = string;
string = `...${string.slice(-30)}`; }
else
connectionName = string; connectionName = `${connection.user + '@'}${connection.host}:${connection.port}`;
} }
else
connectionName = `${connection.user + '@'}${connection.host}:${connection.port}`;
return connectionName; return connectionName;
} },
getConnectionOrderByUid: state => (uid:string) => state.connectionsOrder
.find(connection => connection.uid === uid),
getFolders: state => state.connectionsOrder.filter(conn => conn.isFolder),
getConnectionFolder: state => (uid:string) => state.connectionsOrder
.find(folder => folder.isFolder && folder.connections.includes(uid))
}, },
actions: { actions: {
addConnection (connection: ConnectionParams) { addConnection (connection: ConnectionParams) {
this.connections.push(connection); this.connections.push(connection);
persistentStore.set('connections', this.connections); persistentStore.set('connections', this.connections);
this.connectionsOrder.push({
isFolder: false,
uid: connection.uid,
client: connection.client,
icon: null,
name: null
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
}, },
deleteConnection (connection: ConnectionParams) { addFolder (params: {after: string; connections: [string, string]}) {
this.connections = (this.connections as ConnectionParams[]).filter(el => el.uid !== connection.uid); const index = this.connectionsOrder.findIndex((conn: SidebarElement) => conn.uid === params.after);
this.connectionsOrder.splice(index, 0, {
isFolder: true,
uid: uidGen('F'),
name: '',
color: '#E36929',
connections: params.connections
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
addToFolder (params: {folder: string; connection: string}) {
this.connectionsOrder = this.connectionsOrder.map((conn: SidebarElement) => {
if (conn.uid === params.folder)
conn.connections.push(params.connection);
return conn;
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
this.clearEmptyFolders();
},
deleteConnection (connection: SidebarElement | ConnectionParams) {
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).map(el => { // Removes connection from folders
if (el.isFolder && el.connections.includes(connection.uid))
el.connections = el.connections.filter(uid => uid !== connection.uid);
return el;
});
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => el.uid !== connection.uid);
this.connections = (this.connections as SidebarElement[]).filter(el => el.uid !== connection.uid);
persistentStore.set('connections', this.connections); persistentStore.set('connections', this.connections);
(this.pinnedConnections as Set<string>).delete(connection.uid); this.clearEmptyFolders();
persistentStore.set('pinnedConnections', [...this.pinnedConnections]);
}, },
editConnection (connection: ConnectionParams) { editConnection (connection: ConnectionParams) {
const editedConnections = (this.connections as ConnectionParams[]).map(conn => { const editedConnections = (this.connections as ConnectionParams[]).map(conn => {
if (conn.uid === connection.uid) return connection; if (conn.uid === connection.uid) return connection;
return conn; return conn;
}); });
this.connections = editedConnections; this.connections = editedConnections;
this.selected_conection = {};
persistentStore.set('connections', this.connections); persistentStore.set('connections', this.connections);
const editedConnectionsOrder = (this.connectionsOrder as SidebarElement[]).map(conn => {
if (conn.uid === connection.uid) {
return {
isFolder: false,
uid: connection.uid,
client: connection.client,
icon: conn.icon,
name: conn.name
};
}
return conn;
});
this.connectionsOrder = editedConnectionsOrder;
persistentStore.set('connectionsOrder', this.connectionsOrder);
}, },
updateConnections (connections: ConnectionParams[]) { updateConnections (connections: ConnectionParams[]) {
this.connections = connections; this.connections = connections;
persistentStore.set('connections', this.connections); persistentStore.set('connections', this.connections);
}, },
updatePinnedConnections (pinned: string[]) { initConnectionsOrder () {
this.pinnedConnections = new Set(pinned); this.connectionsOrder = (this.connections as ConnectionParams[]).map<SidebarElement>(conn => {
persistentStore.set('pinnedConnections', [...this.pinnedConnections]); return {
isFolder: false,
uid: conn.uid,
client: conn.client,
icon: null,
name: null
};
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
}, },
pinConnection (uid: string) { updateConnectionsOrder (connections: SidebarElement[]) {
(this.pinnedConnections as Set<string>).add(uid); const invalidElements = connections.reduce<{index: number; uid: string}[]>((acc, curr, i) => {
persistentStore.set('pinnedConnections', [...this.pinnedConnections]); if (typeof curr === 'string')
acc.push({ index: i, uid: curr });
return acc;
}, []);
if (invalidElements.length) {
invalidElements.forEach(el => {
let connIndex = connections.findIndex(conn => conn.uid === el.uid);
const conn = connections[connIndex];
if (connIndex === -1) return;
connections.splice(el.index, 1, { // Move to new position
isFolder: false,
client: conn.client,
uid: conn.uid,
icon: conn.icon,
name: conn.name
});
connIndex = connections.findIndex((conn, i) => conn.uid === el.uid && i !== el.index);
connections.splice(connIndex, 1);// Delete old object
});
}
// Clear empty folders
const emptyFolders = connections.reduce<string[]>((acc, curr) => {
if (curr.connections && curr.connections.length === 0)
acc.push(curr.uid);
return acc;
}, []);
connections = connections.filter(el => !emptyFolders.includes(el.uid));
this.connectionsOrder = connections;
persistentStore.set('connectionsOrder', this.connectionsOrder);
}, },
unpinConnection (uid: string) { updateConnectionOrder (element: SidebarElement) {
(this.pinnedConnections as Set<string>).delete(uid); this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).map(el => {
persistentStore.set('pinnedConnections', [...this.pinnedConnections]); if (el.uid === element.uid)
el = element;
return el;
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
}, },
updateLastConnection (uid: string) { updateLastConnection (uid: string) {
const cIndex = (this.lastConnections as {uid: string; time: number}[]).findIndex((c) => c.uid === uid); const cIndex = (this.lastConnections as {uid: string; time: number}[]).findIndex((c) => c.uid === uid);
@@ -89,6 +208,17 @@ export const useConnectionsStore = defineStore('connections', {
this.lastConnections.push({ uid, time: new Date().getTime() }); this.lastConnections.push({ uid, time: new Date().getTime() });
persistentStore.set('lastConnections', this.lastConnections); persistentStore.set('lastConnections', this.lastConnections);
},
clearEmptyFolders () {
// Clear empty folders
const emptyFolders = (this.connectionsOrder as SidebarElement[]).reduce<string[]>((acc, curr) => {
if (curr.connections && curr.connections.length === 0)
acc.push(curr.uid);
return acc;
}, []);
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => !emptyFolders.includes(el.uid));
persistentStore.set('connectionsOrder', this.connectionsOrder);
} }
} }
}); });

View File

@@ -10,7 +10,7 @@ const isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)');
const defaultAppTheme = isDarkTheme.matches ? 'dark' : 'light'; const defaultAppTheme = isDarkTheme.matches ? 'dark' : 'light';
const defaultEditorTheme = isDarkTheme.matches ? 'twilight' : 'sqlserver'; const defaultEditorTheme = isDarkTheme.matches ? 'twilight' : 'sqlserver';
export type EditorFontSize = 'small' | 'medium' | 'large'; export type EditorFontSize = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';
export type ApplicationTheme = 'light' | 'dark'; export type ApplicationTheme = 'light' | 'dark';
export const useSettingsStore = defineStore('settings', { export const useSettingsStore = defineStore('settings', {
@@ -19,16 +19,19 @@ export const useSettingsStore = defineStore('settings', {
allowPrerelease: settingsStore.get('allow_prerelease', true) as boolean, allowPrerelease: settingsStore.get('allow_prerelease', true) as boolean,
explorebarSize: settingsStore.get('explorebar_size', null) as number, explorebarSize: settingsStore.get('explorebar_size', null) as number,
notificationsTimeout: settingsStore.get('notifications_timeout', 5) as number, notificationsTimeout: settingsStore.get('notifications_timeout', 5) as number,
showTableSize: settingsStore.get('show_table_size', false) as boolean,
dataTabLimit: settingsStore.get('data_tab_limit', 1000) as number, dataTabLimit: settingsStore.get('data_tab_limit', 1000) as number,
autoComplete: settingsStore.get('auto_complete', true) as boolean, autoComplete: settingsStore.get('auto_complete', true) as boolean,
lineWrap: settingsStore.get('line_wrap', true) as boolean, lineWrap: settingsStore.get('line_wrap', true) as boolean,
executeSelected: settingsStore.get('execute_selected', true) as boolean,
applicationTheme: settingsStore.get('application_theme', defaultAppTheme) as ApplicationTheme, applicationTheme: settingsStore.get('application_theme', defaultAppTheme) as ApplicationTheme,
editorTheme: settingsStore.get('editor_theme', defaultEditorTheme) as string, editorTheme: settingsStore.get('editor_theme', defaultEditorTheme) as string,
editorFontSize: settingsStore.get('editor_font_size', 'medium') as EditorFontSize, editorFontSize: settingsStore.get('editor_font_size', 'medium') as EditorFontSize,
restoreTabs: settingsStore.get('restore_tabs', true) as boolean, restoreTabs: settingsStore.get('restore_tabs', true) as boolean,
disableBlur: settingsStore.get('disable_blur', false) as boolean, disableBlur: settingsStore.get('disable_blur', false) as boolean,
disableScratchpad: settingsStore.get('disable_scratchpad', false) as boolean, disableScratchpad: settingsStore.get('disable_scratchpad', false) as boolean,
shortcuts: shortcutsStore.get('shortcuts', []) as ShortcutRecord[] shortcuts: shortcutsStore.get('shortcuts', []) as ShortcutRecord[],
defaultCopyType: settingsStore.get('default_copy_type', 'cell') as string
}), }),
actions: { actions: {
changeLocale (locale: AvailableLocale) { changeLocale (locale: AvailableLocale) {
@@ -48,6 +51,10 @@ export const useSettingsStore = defineStore('settings', {
this.notificationsTimeout = timeout; this.notificationsTimeout = timeout;
settingsStore.set('notifications_timeout', this.notificationsTimeout); settingsStore.set('notifications_timeout', this.notificationsTimeout);
}, },
changeShowTableSize (show: boolean) {
this.showTableSize = show;
settingsStore.set('show_table_size', this.showTableSize);
},
changeExplorebarSize (size: number) { changeExplorebarSize (size: number) {
this.explorebarSize = size; this.explorebarSize = size;
settingsStore.set('explorebar_size', this.explorebarSize); settingsStore.set('explorebar_size', this.explorebarSize);
@@ -60,6 +67,10 @@ export const useSettingsStore = defineStore('settings', {
this.lineWrap = val; this.lineWrap = val;
settingsStore.set('line_wrap', this.lineWrap); settingsStore.set('line_wrap', this.lineWrap);
}, },
changeExecuteSelected (val: boolean) {
this.executeSelected = val;
settingsStore.set('execute_selected', this.executeSelected);
},
changeApplicationTheme (theme: string) { changeApplicationTheme (theme: string) {
this.applicationTheme = theme; this.applicationTheme = theme;
settingsStore.set('application_theme', this.applicationTheme); settingsStore.set('application_theme', this.applicationTheme);
@@ -87,6 +98,10 @@ export const useSettingsStore = defineStore('settings', {
}, },
updateShortcuts (shortcuts: ShortcutRecord[]) { updateShortcuts (shortcuts: ShortcutRecord[]) {
this.shortcuts = shortcuts; this.shortcuts = shortcuts;
},
changeDefaultCopyType (type: string) {
this.defaultCopyType = type;
settingsStore.set('default_copy_type', this.defaultCopyType);
} }
} }
}); });

View File

@@ -176,8 +176,6 @@ export const useWorkspacesStore = defineStore('workspaces', {
: workspace); : workspace);
} }
else { else {
let dataTypes: TypesGroup[] = [];
let indexTypes: string[] = [];
let clientCustomizations: Customizations; let clientCustomizations: Customizations;
const { updateLastConnection } = connectionsStore; const { updateLastConnection } = connectionsStore;
@@ -186,21 +184,20 @@ export const useWorkspacesStore = defineStore('workspaces', {
switch (connection.client) { switch (connection.client) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
dataTypes = require('common/data-types/mysql').default;
indexTypes = require('common/index-types/mysql').default;
clientCustomizations = customizations.mysql; clientCustomizations = customizations.mysql;
break; break;
case 'pg': case 'pg':
dataTypes = require('common/data-types/postgresql').default;
indexTypes = require('common/index-types/postgresql').default;
clientCustomizations = customizations.pg; clientCustomizations = customizations.pg;
break; break;
case 'sqlite': case 'sqlite':
dataTypes = require('common/data-types/sqlite').default;
indexTypes = require('common/index-types/sqlite').default;
clientCustomizations = customizations.sqlite; clientCustomizations = customizations.sqlite;
break; break;
case 'firebird':
clientCustomizations = customizations.firebird;
break;
} }
const dataTypes = clientCustomizations.dataTypes;
const indexTypes = clientCustomizations.indexTypes;
const { status, response: version } = await Schema.getVersion(connection.uid); const { status, response: version } = await Schema.getVersion(connection.uid);

View File

@@ -1,2 +1,80 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
declare module '@/App.vue'; declare module '@/App.vue';
declare module 'v-mask'; declare module 'v-mask';
declare module 'vuedraggable' {// <- to export as default
const draggableComponent: import('vue').DefineComponent<{
list: {
type: ArrayConstructor;
required: boolean;
default: any;
};
modelValue: {
type: ArrayConstructor;
required: boolean;
default: any;
};
itemKey: {
type: (FunctionConstructor | StringConstructor)[];
required: boolean;
};
clone: {
type: FunctionConstructor;
default: (original: any) => any;
};
tag: {
type: StringConstructor;
default: string;
};
move: {
type: FunctionConstructor;
default: any;
};
componentData: {
type: ObjectConstructor;
required: boolean;
default: any;
};
}, unknown, {
error: boolean;
}, {
realList(): any;
getKey(): any;
}, {
getUnderlyingVm(domElement: any): any;
getUnderlyingPotencialDraggableComponent(htmElement: any): any;
emitChanges(evt: any): void;
alterList(onList: any): void;
spliceList(): void;
updatePosition(oldIndex: any, newIndex: any): void;
getRelatedContextFromMoveEvent({ to, related }: {
to: any;
related: any;
}): any;
getVmIndexFromDomIndex(domIndex: any): any;
onDragStart(evt: any): void;
onDragAdd(evt: any): void;
onDragRemove(evt: any): void;
onDragUpdate(evt: any): void;
computeFutureIndex(relatedContext: any, evt: any): any;
onDragMove(evt: any, originalEvent: any): any;
onDragEnd(): void;
}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, any[], any, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<{
move: Function;
tag: string;
clone: Function;
list: unknown[];
modelValue: unknown[];
componentData: Record<string, any>;
} & {
itemKey?: string | Function;
}>, {
move: Function;
tag: string;
clone: Function;
list: unknown[];
modelValue: unknown[];
componentData: Record<string, any>;
}>;
export = draggableComponent;
}