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

Compare commits

...

55 Commits

Author SHA1 Message Date
5c21b67b3d chore(release): 0.2.0 2021-07-03 12:29:13 +02:00
08d5b1b329 feat: contextual option to duplicate tables 2021-07-03 12:27:50 +02:00
61a42d51f5 fix(UI): contextual sub-menu alignment when close to the lower edge of the window 2021-07-03 12:01:19 +02:00
d96907ca2d fix(PostgreSQL): error opening setting tab for some stored routines 2021-07-03 11:39:57 +02:00
75bbd5f66e feat(PostgreSQL): trigger functions support 2021-07-03 11:29:14 +02:00
faa07a077c fix: fields default not correctly set in table filler 2021-07-01 19:18:57 +02:00
7ca69e51ae refactor(UI): round corners to editor autocomplete 2021-06-30 19:43:30 +02:00
d868c772b9 feat(UI): context option to copy cell or row value 2021-06-30 19:27:24 +02:00
a69bdeb20d feat(UI): resizer border mouse hover animation 2021-06-30 09:49:32 +02:00
51db2795bb refactor(UI): minor UI improvements 2021-06-29 23:31:18 +02:00
2d278aa14e fix: rows loses internal id after export 2021-06-29 23:23:11 +02:00
f74bca7bb4 perf: improved contextual menu appearance 2021-06-29 18:22:40 +02:00
978a7c5f5b feat(UI): active tab animation 2021-06-28 18:34:39 +02:00
0110756204 fix(UI): editor blinking on first connection with a dark theme 2021-06-27 12:28:55 +02:00
7df0cf8389 feat(PostgreSQL): triggers creation 2021-06-26 16:36:05 +02:00
3aef7e953e fix: vulnerability in server error toast messages 2021-06-25 09:35:20 +02:00
a975df38dd fix: unhandled exception in connection test 2021-06-25 09:29:59 +02:00
a0a025e450 perf(UI): increased application border-radius 2021-06-24 21:49:46 +02:00
7cc5e7b67f chore(release): 0.1.13 2021-06-19 13:04:11 +02:00
e2ebb04a90 feat(UI): ability to manually insert page number in DATA tabs, closes #65 2021-06-19 12:13:09 +02:00
e579f37438 feat(UI): option to change query editors font size, closes #77 2021-06-19 11:54:15 +02:00
3829b94bf7 refactor: solved deprecation warning for url.format() 2021-06-18 21:08:21 +02:00
5c8ee66f43 feat(PostgreSQL): alter trigger support 2021-06-17 22:01:18 +02:00
b77e77f3bb chore: update dependencies 2021-06-14 09:11:37 +02:00
1d5614278c ci: moved to node 14 in build actions 2021-06-14 09:11:23 +02:00
8a20befd09 fix(UI): various fixes in displaying content with small window size 2021-06-13 11:16:21 +02:00
4133fc452f fix(MySQL): pool connections not released after MySQL errors, causing endless load animation 2021-06-11 15:02:45 +02:00
690a4541f9 perf: remove comments from queries before execution 2021-06-11 14:32:51 +02:00
71910ca5c8 chore(release): 0.1.12 2021-06-09 09:03:18 +02:00
cce5adbac7 feat(PostgreSQL): trigger rename and delete 2021-06-08 09:12:43 +02:00
e6d67fcb0c Update README.md 2021-06-07 23:03:08 +02:00
e83b80afc9 chore: update README.md 2021-06-05 12:27:20 +02:00
8742fa10f0 fix: internal exceptions 2021-06-05 10:15:44 +02:00
ab54eb086f chore: update dependencies 2021-06-05 10:15:08 +02:00
308f69f12e chore: update README.md 2021-06-04 14:01:46 +02:00
2f30bafa33 Merge pull request #74 from digitalgopnik/master
feat: added translation for de-DE
2021-06-04 10:39:22 +02:00
Christian
1716077182 added translation for de-DE 2021-06-04 09:37:02 +02:00
5562e73e75 fix: page offset not reset when selected table changes 2021-06-03 11:02:24 +02:00
8a7cc2a14f fix(UI): unable to browse view's result pages 2021-06-03 10:57:57 +02:00
9ca059d979 fix(MySQL): view's data tab doesn't work with some views, closes #71 2021-06-03 10:54:59 +02:00
c9ccf786cd chore(release): 0.1.11 2021-06-02 15:47:12 +02:00
5e9c88a7fd fix(UI): prevent canc key to trigger delete modal when editing a row 2021-06-02 12:32:12 +02:00
e0e6483d1f build: update dependencies 2021-06-02 12:13:43 +02:00
66227569f4 fix: table row loses internal id after cell update 2021-06-02 11:58:34 +02:00
faa799c8ea fix(MySQL): missing schema altering tables in some conditions 2021-05-31 17:07:48 +02:00
acc1eeb094 fix: empty offset in cell update queries 2021-05-31 14:27:02 +02:00
865dad80df chore: update README.md 2021-05-30 10:54:58 +02:00
f502620779 chore(release): 0.1.10 2021-05-29 11:41:48 +02:00
f61d7eeaf4 feat: key shortcuts to change DATA tab page 2021-05-29 11:26:23 +02:00
e71c7568c0 feat: option to set DATA tab page size 2021-05-29 11:04:02 +02:00
79f033e524 feat: prev/next buttons to browse the results pages of data tab 2021-05-27 22:13:59 +02:00
310cfaa3c2 fix(MySQL): wrong schema in view data tab select, closes #71 2021-05-26 17:44:33 +02:00
6f93e1f9ab chore: AUR badge on README.md 2021-05-26 13:19:48 +02:00
04bdd085a5 fix: wrong detection of field default expressions in some cases 2021-05-25 16:54:13 +02:00
c568acae14 build: update dependencies 2021-05-25 10:09:09 +02:00
72 changed files with 2161 additions and 284 deletions

View File

@@ -17,7 +17,7 @@ jobs:
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12 node-version: 14
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1

View File

@@ -17,7 +17,7 @@ jobs:
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12 node-version: 14
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1

View File

@@ -17,7 +17,7 @@ jobs:
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12 node-version: 14
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1

View File

@@ -2,6 +2,95 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [0.2.0](https://github.com/Fabio286/antares/compare/v0.1.13...v0.2.0) (2021-07-03)
### Features
* contextual option to duplicate tables ([08d5b1b](https://github.com/Fabio286/antares/commit/08d5b1b3299adc5c53419f50892aee3b58e6fe72))
* **PostgreSQL:** trigger functions support ([75bbd5f](https://github.com/Fabio286/antares/commit/75bbd5f66e38a971482acff9dde636cf89858c8e))
* **PostgreSQL:** triggers creation ([7df0cf8](https://github.com/Fabio286/antares/commit/7df0cf83895ba784e30efa7814433cf329175364))
* **UI:** active tab animation ([978a7c5](https://github.com/Fabio286/antares/commit/978a7c5f5b314f2ba4067c86471755fdcc341b3f))
* **UI:** context option to copy cell or row value ([d868c77](https://github.com/Fabio286/antares/commit/d868c772b9e7c70e173d1f80ad8de51aa40991ef))
* **UI:** resizer border mouse hover animation ([a69bdeb](https://github.com/Fabio286/antares/commit/a69bdeb20db7d67613dace554ae37a0c5e112ec2))
### Bug Fixes
* **PostgreSQL:** error opening setting tab for some stored routines ([d96907c](https://github.com/Fabio286/antares/commit/d96907ca2de344114e3edae52843f6258468934b))
* **UI:** contextual sub-menu alignment when close to the lower edge of the window ([61a42d5](https://github.com/Fabio286/antares/commit/61a42d51f55ec3010c4ee67bad2f699ff14c9f7c))
* fields default not correctly set in table filler ([faa07a0](https://github.com/Fabio286/antares/commit/faa07a077c899af5c71124e0c20869876e57fd9a))
* rows loses internal id after export ([2d278aa](https://github.com/Fabio286/antares/commit/2d278aa14e0e5080725196538b7265ec00a7aebb))
* **UI:** editor blinking on first connection with a dark theme ([0110756](https://github.com/Fabio286/antares/commit/0110756204b79388535a74017b303e1ba41e8fd8))
* unhandled exception in connection test ([a975df3](https://github.com/Fabio286/antares/commit/a975df38dd471ab0ac8645a1021470a4600e6598))
* vulnerability in server error toast messages ([3aef7e9](https://github.com/Fabio286/antares/commit/3aef7e953ea82a9105d470cc62c68aacfc97f9d9))
### Improvements
* improved contextual menu appearance ([f74bca7](https://github.com/Fabio286/antares/commit/f74bca7bb4fc6802143446a44663fbf12319bf29))
* **UI:** increased application border-radius ([a0a025e](https://github.com/Fabio286/antares/commit/a0a025e4502141e95394ca9717b1ec65df2728c1))
### [0.1.13](https://github.com/Fabio286/antares/compare/v0.1.12...v0.1.13) (2021-06-19)
### Features
* **PostgreSQL:** alter trigger support ([5c8ee66](https://github.com/Fabio286/antares/commit/5c8ee66f432585db7bd72103fa814558bf406bcc))
* **UI:** ability to manually insert page number in DATA tabs, closes [#65](https://github.com/Fabio286/antares/issues/65) ([e2ebb04](https://github.com/Fabio286/antares/commit/e2ebb04a9047d25187889644aa625fe675de808b))
* **UI:** option to change query editors font size, closes [#77](https://github.com/Fabio286/antares/issues/77) ([e579f37](https://github.com/Fabio286/antares/commit/e579f374381b329422b646b8f7ab5acf298db981))
### Bug Fixes
* **MySQL:** pool connections not released after MySQL errors, causing endless load animation ([4133fc4](https://github.com/Fabio286/antares/commit/4133fc452fc2e961eb587590e010f4968675db7e))
* **UI:** various fixes in displaying content with small window size ([8a20bef](https://github.com/Fabio286/antares/commit/8a20befd0941cb2e6d5e29552606454bd871b092))
### Improvements
* remove comments from queries before execution ([690a454](https://github.com/Fabio286/antares/commit/690a4541f96eaf831dea07b777a421729173b654))
### [0.1.12](https://github.com/Fabio286/antares/compare/v0.1.11...v0.1.12) (2021-06-09)
### Features
* **PostgreSQL:** trigger rename and delete ([cce5adb](https://github.com/Fabio286/antares/commit/cce5adbac75170186956211b54f4db5be25aba07))
### Bug Fixes
* internal exceptions ([8742fa1](https://github.com/Fabio286/antares/commit/8742fa10f08c92a98325df50be9a7df8e36b0f2c))
* page offset not reset when selected table changes ([5562e73](https://github.com/Fabio286/antares/commit/5562e73e75c659051a49d61041d1eee9d66ff6e2))
* **MySQL:** view's data tab doesn't work with some views, closes [#71](https://github.com/Fabio286/antares/issues/71) ([9ca059d](https://github.com/Fabio286/antares/commit/9ca059d979161c0132f64db372b4ed7ecc844b2d))
* **UI:** unable to browse view's result pages ([8a7cc2a](https://github.com/Fabio286/antares/commit/8a7cc2a14fa289c2f3d9e9eb1a5435ffbbaab006))
### [0.1.11](https://github.com/Fabio286/antares/compare/v0.1.10...v0.1.11) (2021-06-02)
### Bug Fixes
* **UI:** prevent canc key to trigger delete modal when editing a row ([5e9c88a](https://github.com/Fabio286/antares/commit/5e9c88a7fd656dd69b92ba4384ca81f4e8cdd422))
* table row loses internal id after cell update ([6622756](https://github.com/Fabio286/antares/commit/66227569f4c032298e20d44c253d30109ffde0b2))
* **MySQL:** missing schema altering tables in some conditions ([faa799c](https://github.com/Fabio286/antares/commit/faa799c8ea2329e7ca1b54c9414a060bc8a2a840))
* empty offset in cell update queries ([acc1eeb](https://github.com/Fabio286/antares/commit/acc1eeb0948db35f2c9a4d85d70fb654b41b15e5))
### [0.1.10](https://github.com/Fabio286/antares/compare/v0.1.9...v0.1.10) (2021-05-29)
### Features
* key shortcuts to change DATA tab page ([f61d7ee](https://github.com/Fabio286/antares/commit/f61d7eeaf42406eb88b3285ac4606794f56e88d3))
* option to set DATA tab page size ([e71c756](https://github.com/Fabio286/antares/commit/e71c7568c09bf8ad184753d3d0f14046f1e386d0))
* prev/next buttons to browse the results pages of data tab ([79f033e](https://github.com/Fabio286/antares/commit/79f033e5245e0e744c35540d25a30b1eadc4d603))
### Bug Fixes
* **MySQL:** wrong schema in view data tab select, closes [#71](https://github.com/Fabio286/antares/issues/71) ([310cfaa](https://github.com/Fabio286/antares/commit/310cfaa3c2324b1159a46be566b6af2555dc9732))
* wrong detection of field default expressions in some cases ([04bdd08](https://github.com/Fabio286/antares/commit/04bdd085a56240273133586d23196e52341d5f27))
### [0.1.9](https://github.com/Fabio286/antares/compare/v0.1.8...v0.1.9) (2021-05-23) ### [0.1.9](https://github.com/Fabio286/antares/compare/v0.1.8...v0.1.9) (2021-05-23)

View File

@@ -14,7 +14,7 @@ Many of its current features are enough to have a pleasant user experience with
I'm actively working on it, hoping to provide cool features and fixes as soon as possible. I'm actively working on it, hoping to provide cool features and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases watch this repo on **Release only** channel. 👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
## Philosophy ## Philosophy
@@ -25,12 +25,14 @@ A modern application created with minimalism and semplicity in mind, with featur
## Download ## Download
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab) [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)** 🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## How to contribute ## How to contribute
- [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares) - 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
## Current main features ## Current main features
@@ -92,7 +94,8 @@ This is a roadmap with major features will come in near future.
**Arabic Translation** / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)] **Arabic Translation** / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
**Spanish Translation** / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)] **Spanish Translation** / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
**French Translation** / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)] **French Translation** / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)]
**Portugues (Brasil)** / [Daniel Eduardo](https://github.com/daeleduardo) [[#54](https://github.com/Fabio286/antares/pull/54)] **Portugues (Brasil)** / [Daniel Eduardo](https://github.com/daeleduardo) [[#54](https://github.com/Fabio286/antares/pull/54)]
**Deutsch (Deutschland)** / [Christian Ratz](https://github.com/digitalgopnik) [[#74](https://github.com/Fabio286/antares/pull/74)]
## Reviews ## Reviews

77
docs/aur-badge.svg Normal file
View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
style="isolation:isolate" viewBox="0 0 182 56" width="182px" height="56px">
<defs>
<clipPath id="_clipPath_tR1uglJ1Zei76xP861DY1TsjAiQWS9qF">
<rect width="182" height="56" />
</clipPath>
</defs>
<g clip-path="url(#_clipPath_tR1uglJ1Zei76xP861DY1TsjAiQWS9qF)">
<path
d="M 2.5 0.5 L 180.5 0.5 C 181.604 0.5 182.5 1.396 182.5 2.5 L 182.5 54.5 C 182.5 55.604 181.604 56.5 180.5 56.5 L 2.5 56.5 C 1.396 56.5 0.5 55.604 0.5 54.5 L 0.5 2.5 C 0.5 1.396 1.396 0.5 2.5 0.5 Z"
style="stroke:none;fill:#252525;stroke-miterlimit:10;" />
<path
d="M 2.5 0.5 L 180.5 0.5 C 181.604 0.5 182.5 1.396 182.5 2.5 L 182.5 54.5 C 182.5 55.604 181.604 56.5 180.5 56.5 L 2.5 56.5 C 1.396 56.5 0.5 55.604 0.5 54.5 L 0.5 2.5 C 0.5 1.396 1.396 0.5 2.5 0.5 Z"
style="fill:none;stroke:#CDCDCD;stroke-width:1;stroke-miterlimit:2;" />
<g>
<g>
<path
d=" M 60.898 13.777 C 58.555 13.774 56.61 14.254 55.858 14.516 L 55.083 18.697 C 55.081 18.712 58.937 17.669 60.635 17.73 C 63.447 17.831 63.706 18.805 63.656 20.119 C 63.704 20.196 62.931 18.931 60.498 18.889 C 57.43 18.836 53.098 19.976 53.104 24.608 C 53.022 29.818 56.997 31.351 59.704 31.379 C 62.138 31.335 63.279 30.458 63.904 29.988 C 64.726 29.129 65.665 28.265 66.561 27.229 C 65.714 28.77 64.978 29.835 64.213 30.651 L 64.213 31.339 L 67.913 30.716 L 67.938 20.66 C 67.901 19.237 68.754 13.791 60.898 13.777 Z M 60.367 22.533 C 61.9 22.554 63.659 23.31 63.662 25.129 C 63.669 26.784 61.589 27.674 60.235 27.66 C 58.881 27.646 57.085 26.596 57.077 24.982 C 57.103 23.54 58.771 22.496 60.367 22.533 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
<path
d=" M 70.378 14.707 L 70.352 31.36 L 74.662 30.529 L 74.67 21.087 C 74.671 19.681 76.679 18.039 79.198 18.065 C 79.733 17.097 80.738 14.625 80.983 14.062 C 75.354 14.049 75.282 15.68 74.303 16.483 C 74.293 14.952 74.3 14.033 74.3 14.033 L 70.378 14.707 L 70.378 14.707 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
<path
d=" M 94.632 16.893 C 94.591 16.873 92.385 14.312 87.949 14.292 C 83.795 14.223 79.135 15.834 79.061 22.8 C 79.097 28.925 83.537 31.318 87.973 31.365 C 92.72 31.414 94.609 28.396 94.722 28.322 C 94.156 27.83 92.034 25.728 92.034 25.728 C 92.034 25.728 90.709 27.615 88.138 27.639 C 85.566 27.664 83.331 25.651 83.299 22.844 C 83.266 20.036 85.354 18.515 88.157 18.392 C 90.584 18.392 91.984 19.959 91.984 19.959 L 94.632 16.893 L 94.632 16.893 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
<path
d=" M 100.065 8.879 L 95.996 9.835 L 96.026 31.526 L 100.034 30.802 L 100.08 20.595 C 100.089 19.524 101.628 17.88 104.2 17.933 C 106.658 17.958 107.207 19.571 107.201 19.775 L 107.272 31.592 L 111.224 30.894 L 111.239 18.363 C 111.265 17.157 108.598 14.61 104.311 14.592 C 102.273 14.596 101.145 15.057 100.571 15.397 C 99.588 16.156 98.466 16.883 97.362 17.811 C 98.382 16.501 99.239 15.595 100.075 14.921 L 100.065 8.879 L 100.065 8.879 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
</g>
<g>
<path
d=" M 114.673 9.441 L 116.508 8.982 L 116.595 30.85 L 114.73 31.168 L 114.673 9.441 L 114.673 9.441 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 119.663 15.968 L 121.271 15.252 L 121.285 30.932 L 119.731 31.253 L 119.663 15.968 L 119.663 15.968 Z M 119.28 10.314 L 120.577 9.255 L 121.655 10.454 L 120.357 11.54 L 119.28 10.314 L 119.28 10.314 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 124.296 15.682 L 126.131 15.308 L 126.14 18.586 C 126.14 18.727 127.148 14.924 132.008 15.009 C 136.727 15.035 137.499 18.688 137.473 19.507 L 137.531 31.034 L 135.922 31.384 L 135.913 19.998 C 135.932 19.665 135.178 16.853 131.843 16.843 C 128.509 16.833 126.199 19.265 126.203 20.818 L 126.229 30.848 L 124.365 31.335 L 124.296 15.682 L 124.296 15.682 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 153.547 31.117 L 151.711 31.492 L 151.703 28.214 C 151.703 28.073 150.694 31.876 145.835 31.791 C 141.116 31.765 140.344 28.112 140.37 27.293 L 140.311 15.765 L 142.261 15.372 L 142.292 26.758 C 142.292 27.069 142.665 29.947 145.999 29.957 C 149.334 29.967 151.669 27.949 151.686 24.911 L 151.662 15.928 L 153.477 15.464 L 153.547 31.117 L 153.547 31.117 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 157.144 15.553 L 155.857 16.56 L 160.792 23.018 L 155.529 30.478 L 156.894 31.492 L 161.841 24.563 L 166.948 31.656 L 168.211 30.649 L 162.738 23.065 L 167.104 16.933 L 165.762 15.797 L 161.785 21.472 L 157.144 15.553 L 157.144 15.553 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
</g>
<path
d=" M 33.112 3.879 C 30.965 9.143 29.67 12.587 27.279 17.695 C 28.745 19.248 30.544 21.058 33.466 23.101 C 30.325 21.809 28.182 20.511 26.581 19.164 C 23.521 25.549 18.728 34.643 9 52.121 C 16.645 47.707 22.572 44.986 28.095 43.948 C 27.858 42.928 27.723 41.824 27.733 40.673 L 27.742 40.428 C 27.863 35.53 30.411 31.763 33.43 32.019 C 36.448 32.274 38.794 36.455 38.673 41.353 C 38.65 42.275 38.546 43.162 38.364 43.984 C 43.828 45.053 49.691 47.767 57.233 52.121 C 55.746 49.383 54.419 46.915 53.151 44.565 C 51.154 43.017 49.072 41.003 44.823 38.822 C 47.743 39.581 49.834 40.456 51.464 41.435 C 38.575 17.439 37.532 14.251 33.112 3.879 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<g>
<path
d=" M 170.614 30.156 L 170.614 28.802 L 170.109 28.802 L 170.109 28.621 L 171.325 28.621 L 171.325 28.802 L 170.817 28.802 L 170.817 30.156 L 170.614 30.156 Z "
fill="rgb(23,147,209)" />
<path
d=" M 171.536 30.156 L 171.536 28.621 L 171.842 28.621 L 172.205 29.708 C 172.238 29.809 172.263 29.884 172.278 29.935 C 172.295 29.879 172.323 29.797 172.36 29.689 L 172.727 28.621 L 173 28.621 L 173 30.156 L 172.804 30.156 L 172.804 28.871 L 172.358 30.156 L 172.175 30.156 L 171.732 28.849 L 171.732 30.156 L 171.536 30.156 Z "
fill="rgb(23,147,209)" />
</g>
<g>
<path
d=" M 57.471 47.815 L 57.471 46.493 L 56.977 46.493 L 56.977 46.316 L 58.166 46.316 L 58.166 46.493 L 57.67 46.493 L 57.67 47.815 L 57.471 47.815 Z "
fill="rgb(23,147,209)" />
<path
d=" M 58.372 47.815 L 58.372 46.316 L 58.671 46.316 L 59.026 47.377 C 59.059 47.476 59.083 47.55 59.098 47.599 C 59.115 47.545 59.141 47.465 59.177 47.359 L 59.536 46.316 L 59.803 46.316 L 59.803 47.815 L 59.612 47.815 L 59.612 46.56 L 59.176 47.815 L 58.997 47.815 L 58.564 46.539 L 58.564 47.815 L 58.372 47.815"
fill="rgb(23,147,209)" />
</g>
</g>
<g clip-path="url(#_clipPath_NvFIpNfWUS6M4fZAtfyVzggsKR3URDoi)"><text transform="matrix(1,0,0,1,87.023,43.899)"
style="font-family:'Open Sans';font-weight:700;font-size:11px;font-style:normal;fill:#ffffff;stroke:none;">user
repository</text></g>
<defs>
<clipPath id="_clipPath_NvFIpNfWUS6M4fZAtfyVzggsKR3URDoi">
<rect x="0" y="0" width="86" height="14.98" transform="matrix(1,0,0,1,87,32.142)" />
</clipPath>
</defs>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.1.9", "version": "0.2.0",
"description": "A cross-platform easy to use SQL client.", "description": "A cross-platform easy to use SQL client.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/Fabio286/antares.git", "repository": "https://github.com/Fabio286/antares.git",
@@ -87,14 +87,14 @@
} }
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^1.1.0", "@electron/remote": "^1.2.0",
"@mdi/font": "^5.9.55", "@mdi/font": "^5.9.55",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"electron-log": "^4.3.5", "electron-log": "^4.3.5",
"electron-store": "^8.0.0", "electron-store": "^8.0.0",
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
"faker": "^5.5.3", "faker": "^5.5.3",
"marked": "^2.0.2", "marked": "^2.1.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"mysql2": "^2.2.5", "mysql2": "^2.2.5",
"pg": "^8.5.1", "pg": "^8.5.1",
@@ -108,27 +108,27 @@
"vuex": "^3.6.2" "vuex": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.14.3", "@babel/eslint-parser": "^7.14.5",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"electron": "^12.0.9", "electron": "^13.1.2",
"electron-builder": "22.10.5", "electron-builder": "^22.11.7",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-webpack": "^2.8.2", "electron-webpack": "^2.8.2",
"electron-webpack-vue": "^2.4.0", "electron-webpack-vue": "^2.4.0",
"eslint": "^7.26.0", "eslint": "^7.29.0",
"eslint-config-standard": "^16.0.2", "eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.9.0", "eslint-plugin-vue": "^7.11.1",
"sass": "^1.32.13", "sass": "^1.35.1",
"sass-loader": "^10.2.0", "sass-loader": "^10.2.0",
"standard-version": "^9.3.0", "standard-version": "^9.3.0",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.19.0", "stylelint-scss": "^3.19.0",
"vue": "^2.6.12", "vue": "^2.6.14",
"vue-template-compiler": "^2.6.12", "vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0" "webpack": "^4.46.0"
} }
} }

View File

@@ -62,8 +62,14 @@ module.exports = {
functionSql: false, functionSql: false,
functionContext: false, functionContext: false,
functionLanguage: false, functionLanguage: false,
triggerMiltipleEvents: false, triggerSql: false,
triggerStatementInCreation: false,
triggerMultipleEvents: false,
triggerTableInName: false,
triggerUpdateColumns: false, triggerUpdateColumns: false,
triggerOnlyRename: false,
triggerFunctionSql: false,
triggerFunctionlanguages: false,
parametersLength: false, parametersLength: false,
languages: false languages: false
}; };

View File

@@ -51,6 +51,7 @@ module.exports = {
procedureDataAccess: true, procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND', procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true, procedureContext: true,
triggerSql: 'BEGIN\r\n\r\nEND',
functionDeterministic: true, functionDeterministic: true,
functionDataAccess: true, functionDataAccess: true,
functionSql: 'BEGIN\r\n\r\nEND', functionSql: 'BEGIN\r\n\r\nEND',

View File

@@ -13,30 +13,40 @@ module.exports = {
// Structure // Structure
tables: true, tables: true,
views: true, views: true,
triggers: false, triggers: true,
triggerFunctions: true,
routines: true, routines: true,
functions: true, functions: true,
// Settings // Settings
tableAdd: true, tableAdd: true,
viewAdd: true, viewAdd: true,
triggerAdd: false, triggerAdd: true,
triggerFunctionAdd: true,
routineAdd: true, routineAdd: true,
functionAdd: true, functionAdd: true,
databaseEdit: false, databaseEdit: false,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
triggerSettings: false, triggerSettings: true,
triggerFunctionSettings: true,
routineSettings: true, routineSettings: true,
functionSettings: true, functionSettings: true,
indexes: true, indexes: true,
foreigns: true, foreigns: true,
nullable: true, nullable: true,
tableArray: true, tableArray: true,
procedureSql: '$BODY$\r\n\r\n$BODY$', procedureSql: '$procedure$\r\n\r\n$procedure$',
procedureContext: true, procedureContext: true,
procedureLanguage: true, procedureLanguage: true,
functionSql: '$BODY$\r\n\r\n$BODY$', functionSql: '$function$\r\n\r\n$function$',
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',
triggerFunctionlanguages: ['plpgsql'],
functionContext: true, functionContext: true,
functionLanguage: true, functionLanguage: true,
triggerSql: 'EXECUTE PROCEDURE ',
triggerStatementInCreation: true,
triggerMultipleEvents: true,
triggerTableInName: true,
triggerOnlyRename: false,
languages: ['sql', 'plpgsql', 'c', 'internal'] languages: ['sql', 'plpgsql', 'c', 'internal']
}; };

View File

@@ -1,8 +1,7 @@
'use strict'; 'use strict';
import { app, BrowserWindow, nativeImage } from 'electron'; import { app, BrowserWindow, /* session, */ nativeImage } from 'electron';
import * as path from 'path'; import * as path from 'path';
import { format as formatUrl } from 'url';
import Store from 'electron-store'; import Store from 'electron-store';
import ipcHandlers from './ipc-handlers'; import ipcHandlers from './ipc-handlers';
@@ -39,21 +38,18 @@ async function createMainWindow () {
}); });
try { try {
if (isDevelopment) { if (isDevelopment) { //
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`); await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer'); // const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer');
const toolName = await installExtension(VUEJS_DEVTOOLS); // const oldDevToolsID = session.defaultSession.getAllExtensions().find(ext => ext.name === 'Vue.js devtools').id;
console.log(toolName, 'installed'); // session.defaultSession.removeExtension(oldDevToolsID);
} // const toolName = await installExtension(VUEJS3_DEVTOOLS);
else { // console.log(toolName, 'installed');
await window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true
}));
} }
else
await window.loadURL(new URL(`file:///${path.join(__dirname, 'index.html')}`).href);
} }
catch (err) { catch (err) {
console.log(err); console.log(err);

View File

@@ -29,9 +29,8 @@ export default connections => {
params params
}); });
await connection.connect();
try { try {
await connection.connect();
await connection.select('1+1').run(); await connection.select('1+1').run();
connection.destroy(); connection.destroy();

View File

@@ -31,6 +31,16 @@ export default (connections) => {
} }
}); });
ipcMain.handle('alter-trigger-function', async (event, params) => {
try {
await connections[params.uid].alterTriggerFunction(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('create-function', async (event, params) => { ipcMain.handle('create-function', async (event, params) => {
try { try {
await connections[params.uid].createFunction(params); await connections[params.uid].createFunction(params);
@@ -40,4 +50,14 @@ export default (connections) => {
return { status: 'error', response: err.toString() }; return { status: 'error', response: err.toString() };
} }
}); });
ipcMain.handle('create-trigger-function', async (event, params) => {
try {
await connections[params.uid].createTriggerFunction(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
}; };

View File

@@ -16,13 +16,15 @@ export default (connections) => {
} }
}); });
ipcMain.handle('get-table-data', async (event, { uid, schema, table, sortParams }) => { ipcMain.handle('get-table-data', async (event, { uid, schema, table, limit, page, sortParams }) => {
try { try {
const offset = (page - 1) * limit;
const query = connections[uid] const query = connections[uid]
.select('*') .select('*')
.schema(schema) .schema(schema)
.from(table) .from(table)
.limit(1000); .limit(limit)
.offset(offset);
if (sortParams && sortParams.field && sortParams.dir) if (sortParams && sortParams.field && sortParams.dir)
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() }); query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
@@ -59,6 +61,8 @@ export default (connections) => {
}); });
ipcMain.handle('update-table-cell', async (event, params) => { ipcMain.handle('update-table-cell', async (event, params) => {
delete params.row._id;
try { // TODO: move to client classes try { // TODO: move to client classes
let escapedParam; let escapedParam;
let reload = false; let reload = false;
@@ -420,6 +424,16 @@ export default (connections) => {
} }
}); });
ipcMain.handle('duplicate-table', async (event, params) => {
try {
await connections[params.uid].duplicateTable(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('truncate-table', async (event, params) => { ipcMain.handle('truncate-table', async (event, params) => {
try { try {
await connections[params.uid].truncateTable(params); await connections[params.uid].truncateTable(params);

View File

@@ -26,6 +26,7 @@ export class AntaresCore {
groupBy: [], groupBy: [],
orderBy: [], orderBy: [],
limit: [], limit: [],
offset: [],
join: [], join: [],
update: [], update: [],
insert: [], insert: [],
@@ -109,6 +110,11 @@ export class AntaresCore {
return this; return this;
} }
offset (...args) {
this._query.offset = args;
return this;
}
/** /**
* @param {String | Array} args field = value * @param {String | Array} args field = value
* @returns * @returns
@@ -134,7 +140,7 @@ export class AntaresCore {
* @returns {Promise} * @returns {Promise}
* @memberof AntaresCore * @memberof AntaresCore
*/ */
async run (args) { run (args) {
const rawQuery = this.getSQL(); const rawQuery = this.getSQL();
this._resetQuery(); this._resetQuery();
return this.raw(rawQuery, args); return this.raw(rawQuery, args);

View File

@@ -305,13 +305,15 @@ export class MySQLClient extends AntaresCore {
.select('*') .select('*')
.schema('information_schema') .schema('information_schema')
.from('COLUMNS') .from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` }) .where({ TABLE_SCHEMA: `= '${this._schema || schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' }) .orderBy({ ORDINAL_POSITION: 'ASC' })
.run(); .run();
const { rows: fields } = await this.raw(`SHOW CREATE TABLE ${schema}.${table}`); const { rows: fields } = await this.raw(`SHOW CREATE TABLE \`${this._schema || schema}\`.\`${table}\``);
const remappedFields = fields.map(row => { const remappedFields = fields.map(row => {
if (!row['Create Table']) return false;
let n = 0; let n = 0;
return row['Create Table'] return row['Create Table']
.split('') .split('')
@@ -330,7 +332,17 @@ export class MySQLClient extends AntaresCore {
if (nameAndType[0].charAt(0) !== '`') return false; if (nameAndType[0].charAt(0) !== '`') return false;
const details = fieldArr.slice(2).join(' '); const details = fieldArr.slice(2).join(' ');
const defaultValue = details.includes('DEFAULT') ? details.match(/(?<=DEFAULT ).*?$/gs)[0] : null; let defaultValue = null;
if (details.includes('DEFAULT')) {
defaultValue = details.match(/(?<=DEFAULT ).*?$/gs)[0].split(' COMMENT')[0];
const defaultValueArr = defaultValue.split('');
if (defaultValueArr[0] === '\'') {
defaultValueArr.shift();
defaultValueArr.pop();
defaultValue = defaultValueArr.join('');
}
}
const typeAndLength = nameAndType[1].replace(')', '').split('('); const typeAndLength = nameAndType[1].replace(')', '').split('(');
return { return {
@@ -361,7 +373,7 @@ export class MySQLClient extends AntaresCore {
return { return {
name: field.COLUMN_NAME, name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(), key: field.COLUMN_KEY.toLowerCase(),
type: remappedFields[field.COLUMN_NAME].type, type: remappedFields ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE,
schema: field.TABLE_SCHEMA, schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME, table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION, numPrecision: field.NUMERIC_PRECISION,
@@ -373,7 +385,7 @@ export class MySQLClient extends AntaresCore {
unsigned: field.COLUMN_TYPE.includes('unsigned'), unsigned: field.COLUMN_TYPE.includes('unsigned'),
zerofill: field.COLUMN_TYPE.includes('zerofill'), zerofill: field.COLUMN_TYPE.includes('zerofill'),
order: field.ORDINAL_POSITION, order: field.ORDINAL_POSITION,
default: remappedFields[field.COLUMN_NAME].default, default: remappedFields ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
charset: field.CHARACTER_SET_NAME, charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME, collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'), autoIncrement: field.EXTRA.includes('auto_increment'),
@@ -579,8 +591,8 @@ export class MySQLClient extends AntaresCore {
sql: row['SQL Original Statement'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], sql: row['SQL Original Statement'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
name: row.Trigger, name: row.Trigger,
table: row['SQL Original Statement'].match(/(?<=ON `).*?(?=`)/gs)[0], table: row['SQL Original Statement'].match(/(?<=ON `).*?(?=`)/gs)[0],
event1: row['SQL Original Statement'].match(/(BEFORE|AFTER)/gs)[0], activation: row['SQL Original Statement'].match(/(BEFORE|AFTER)/gs)[0],
event2: row['SQL Original Statement'].match(/(INSERT|UPDATE|DELETE)/gs)[0] event: row['SQL Original Statement'].match(/(INSERT|UPDATE|DELETE)/gs)[0]
}; };
})[0]; })[0];
} }
@@ -625,7 +637,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createTrigger (trigger) { async createTrigger (trigger) {
const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${this._schema}\`.\`${trigger.name}\` ${trigger.event1} ${trigger.event2} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`; const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${this._schema}\`.\`${trigger.name}\` ${trigger.activation} ${trigger.event} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }
@@ -1114,7 +1126,7 @@ export class MySQLClient extends AntaresCore {
options options
} = params; } = params;
let sql = `ALTER TABLE \`${this._schema}\`.\`${table}\` `; let sql = `ALTER TABLE \`${this._schema || params.options.schema}\`.\`${table}\` `;
const alterColumns = []; const alterColumns = [];
// OPTIONS // OPTIONS
@@ -1231,6 +1243,17 @@ export class MySQLClient extends AntaresCore {
return await this.raw(sql); return await this.raw(sql);
} }
/**
* DUPLICATE TABLE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async duplicateTable (params) {
const sql = `CREATE TABLE \`${this._schema}\`.\`${params.table}_copy\` LIKE \`${this._schema}\`.\`${params.table}\``;
return await this.raw(sql);
}
/** /**
* TRUNCATE TABLE * TRUNCATE TABLE
* *
@@ -1304,7 +1327,10 @@ export class MySQLClient extends AntaresCore {
// LIMIT // LIMIT
const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : ''; const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`; // OFFSET
const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
} }
/** /**
@@ -1317,6 +1343,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async raw (sql, args) { async raw (sql, args) {
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = { args = {
@@ -1329,7 +1356,11 @@ export class MySQLClient extends AntaresCore {
const nestTables = args.nest ? '.' : false; const nestTables = args.nest ? '.' : false;
const resultsArr = []; const resultsArr = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) : [sql]; const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
.filter(Boolean)
.map(q => q.trim())
: [sql];
const isPool = typeof this._connection.getConnection === 'function'; const isPool = typeof this._connection.getConnection === 'function';
const connection = isPool ? await this._connection.getConnection() : this._connection; const connection = isPool ? await this._connection.getConnection() : this._connection;
@@ -1393,6 +1424,7 @@ export class MySQLClient extends AntaresCore {
}); });
} }
catch (err) { catch (err) {
if (isPool) connection.release();
reject(err); reject(err);
} }
@@ -1401,6 +1433,7 @@ export class MySQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response; keysArr = keysArr ? [...keysArr, ...response] : response;
} }
catch (err) { catch (err) {
if (isPool) connection.release();
reject(err); reject(err);
} }
} }
@@ -1414,7 +1447,10 @@ export class MySQLClient extends AntaresCore {
fields: remappedFields, fields: remappedFields,
keys: keysArr keys: keysArr
}); });
}).catch(reject); }).catch((err) => {
if (isPool) connection.release();
reject(err);
});
}); });
resultsArr.push({ rows, report, fields, keys, duration }); resultsArr.push({ rows, report, fields, keys, duration });

View File

@@ -215,6 +215,7 @@ export class PostgreSQLClient extends AntaresCore {
functions: [], functions: [],
procedures: [], procedures: [],
triggers: [], triggers: [],
triggerFunctions: [],
schedulers: [] schedulers: []
}; };
} }
@@ -496,17 +497,33 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async getTriggerInformations ({ schema, trigger }) { async getTriggerInformations ({ schema, trigger }) {
const sql = `SHOW CREATE TRIGGER \`${schema}\`.\`${trigger}\``; const [table, triggerName] = trigger.split('.');
const results = await this.raw(sql);
const results = await this.raw(`
SELECT event_object_schema AS table_schema,
event_object_table AS table_name,
trigger_schema,
trigger_name,
string_agg(event_manipulation, ',') AS event,
action_timing AS activation,
action_condition AS condition,
action_statement AS definition
FROM information_schema.triggers
WHERE trigger_schema = '${schema}'
AND trigger_name = '${triggerName}'
AND event_object_table = '${table}'
GROUP BY 1,2,3,4,6,7,8
ORDER BY table_schema,
table_name
`);
return results.rows.map(row => { return results.rows.map(row => {
return { return {
definer: row['SQL Original Statement'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], sql: row.definition,
sql: row['SQL Original Statement'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], name: row.trigger_name,
name: row.Trigger, table: row.table_name,
table: row['SQL Original Statement'].match(/(?<=ON `).*?(?=`)/gs)[0], event: row.event.split(','),
event1: row['SQL Original Statement'].match(/(BEFORE|AFTER)/gs)[0], activation: row.activation
event2: row['SQL Original Statement'].match(/(INSERT|UPDATE|DELETE)/gs)[0]
}; };
})[0]; })[0];
} }
@@ -519,7 +536,7 @@ export class PostgreSQLClient extends AntaresCore {
*/ */
async dropTrigger (params) { async dropTrigger (params) {
const triggerParts = params.trigger.split('.'); const triggerParts = params.trigger.split('.');
const sql = `DROP TRIGGER ${triggerParts[1]} ON ${triggerParts[0]}`; const sql = `DROP TRIGGER "${triggerParts[1]}" ON "${triggerParts[0]}"`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -536,8 +553,8 @@ export class PostgreSQLClient extends AntaresCore {
try { try {
await this.createTrigger(tempTrigger); await this.createTrigger(tempTrigger);
await this.dropTrigger({ trigger: tempTrigger.name }); await this.dropTrigger({ trigger: `${tempTrigger.table}.${tempTrigger.name}` });
await this.dropTrigger({ trigger: trigger.oldName }); await this.dropTrigger({ trigger: `${trigger.table}.${trigger.oldName}` });
await this.createTrigger(trigger); await this.createTrigger(trigger);
} }
catch (err) { catch (err) {
@@ -552,7 +569,8 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createTrigger (trigger) { async createTrigger (trigger) {
const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${trigger.name}\` ${trigger.event1} ${trigger.event2} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`; const eventsString = Array.isArray(trigger.event) ? trigger.event.join(' OR ') : trigger.event;
const sql = `CREATE TRIGGER "${trigger.name}" ${trigger.activation} ${eventsString} ON "${trigger.table}" FOR EACH ROW ${trigger.sql}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }
@@ -606,7 +624,7 @@ export class PostgreSQLClient extends AntaresCore {
const parameters = results.rows.map(row => { const parameters = results.rows.map(row => {
return { return {
name: row.parameter_name, name: row.parameter_name,
type: row.data_type.toUpperCase(), type: row.data_type ? row.data_type.toUpperCase() : '',
length: '', length: '',
context: row.parameter_mode context: row.parameter_mode
}; };
@@ -805,7 +823,7 @@ export class PostgreSQLClient extends AntaresCore {
if (this._schema !== 'public') if (this._schema !== 'public')
await this.use(this._schema); await this.use(this._schema);
const body = func.returns ? func.sql : '$BODY$\n$BODY$'; const body = func.returns ? func.sql : '$function$\n$function$';
const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters}) const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters})
RETURNS ${func.returns || 'void'} RETURNS ${func.returns || 'void'}
@@ -816,6 +834,48 @@ export class PostgreSQLClient extends AntaresCore {
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }
/**
* ALTER TRIGGER FUNCTION
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async alterTriggerFunction (params) {
const { func } = params;
if (this._schema !== 'public')
await this.use(this._schema);
const body = func.returns ? func.sql : '$function$\n$function$';
const sql = `CREATE OR REPLACE FUNCTION ${this._schema}.${func.name}()
RETURNS TRIGGER
LANGUAGE ${func.language}
AS ${body}`;
return await this.raw(sql, { split: false });
}
/**
* CREATE TRIGGER FUNCTION
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async createTriggerFunction (func) {
if (this._schema !== 'public')
await this.use(this._schema);
const body = func.returns ? func.sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$';
const sql = `CREATE FUNCTION ${this._schema}.${func.name}()
RETURNS TRIGGER
LANGUAGE ${func.language}
AS ${body}`;
return await this.raw(sql, { split: false });
}
/** /**
* SELECT * FROM pg_collation * SELECT * FROM pg_collation
* *
@@ -1066,6 +1126,17 @@ export class PostgreSQLClient extends AntaresCore {
return await this.raw(sql); return await this.raw(sql);
} }
/**
* DUPLICATE TABLE
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async duplicateTable (params) {
const sql = `CREATE TABLE ${this._schema}.${params.table}_copy (LIKE ${this._schema}.${params.table} INCLUDING ALL)`;
return await this.raw(sql);
}
/** /**
* TRUNCATE TABLE * TRUNCATE TABLE
* *
@@ -1139,7 +1210,10 @@ export class PostgreSQLClient extends AntaresCore {
// LIMIT // LIMIT
const limitRaw = selectArray.length && this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : ''; const limitRaw = selectArray.length && this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`; // OFFSET
const offsetRaw = selectArray.length && this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
} }
/** /**
@@ -1152,6 +1226,8 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async raw (sql, args) { async raw (sql, args) {
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');
args = { args = {
nest: false, nest: false,
details: false, details: false,
@@ -1164,7 +1240,11 @@ export class PostgreSQLClient extends AntaresCore {
const resultsArr = []; const resultsArr = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split ? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm) : [sql]; const queries = args.split
? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm)
.filter(Boolean)
.map(q => q.trim())
: [sql];
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder

View File

@@ -19,6 +19,7 @@
<TheScratchpad v-if="isScratchpad" /> <TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" /> <ModalSettings v-if="isSettingModal" />
<ModalDiscardChanges v-if="isUnsavedDiscardModal" /> <ModalDiscardChanges v-if="isUnsavedDiscardModal" />
<BaseTextEditor class="d-none" value="" />
</div> </div>
</div> </div>
</template> </template>
@@ -40,7 +41,8 @@ export default {
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'), ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'), ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'), TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges') ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges'),
BaseTextEditor: () => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor')
}, },
data () { data () {
return {}; return {};

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="context"> <div class="context" :class="{'bottom': isBottom}">
<a <a
class="context-overlay" class="context-overlay"
@click="close" @click="close"
@@ -23,18 +23,21 @@ export default {
}, },
data () { data () {
return { return {
contextSize: null contextSize: null,
isBottom: false
}; };
}, },
computed: { computed: {
position () { position () {
const { clientY, clientX } = this.contextEvent; const { clientY, clientX } = this.contextEvent;
let topCord = `${clientY + 5}px`; let topCord = `${clientY + 2}px`;
let leftCord = `${clientX + 5}px`; let leftCord = `${clientX + 5}px`;
if (this.contextSize) { if (this.contextSize) {
if (clientY + this.contextSize.height + 5 >= window.innerHeight) if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
topCord = `${clientY - this.contextSize.height}px`; topCord = `${clientY + 3 - this.contextSize.height}px`;
this.isBottom = true;
}
if (clientX + this.contextSize.width + 5 >= window.innerWidth) if (clientX + this.contextSize.width + 5 >= window.innerWidth)
leftCord = `${clientX - this.contextSize.width}px`; leftCord = `${clientX - this.contextSize.width}px`;
@@ -83,12 +86,21 @@ export default {
left: 0; left: 0;
bottom: 0; bottom: 0;
&:not(.bottom) .context-submenu {
top: -0.2rem;
}
&.bottom .context-submenu {
bottom: -0.2rem;
}
.context-container { .context-container {
min-width: 100px; min-width: 100px;
z-index: 10; z-index: 10;
padding: 0; padding: 0;
background: #1d1d1d; background: #1d1d1d;
border-radius: 0.1rem; border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
@@ -97,19 +109,24 @@ export default {
.context-element { .context-element {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0.2rem;
padding: 0.1rem 0.3rem; padding: 0.1rem 0.3rem;
border-radius: $border-radius;
cursor: pointer; cursor: pointer;
justify-content: space-between; justify-content: space-between;
position: relative; position: relative;
white-space: nowrap;
.context-submenu { .context-submenu {
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: opacity 0.2s; transition: opacity 0.2s;
position: absolute; position: absolute;
left: 100%; left: 100%;
top: 0;
min-width: 100px; min-width: 100px;
background: #1d1d1d;
} }
&:hover { &:hover {

View File

@@ -34,6 +34,7 @@ export default {
computed: { computed: {
...mapGetters({ ...mapGetters({
editorTheme: 'settings/getEditorTheme', editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
autoComplete: 'settings/getAutoComplete', autoComplete: 'settings/getAutoComplete',
lineWrap: 'settings/getLineWrap' lineWrap: 'settings/getLineWrap'
}) })
@@ -47,6 +48,19 @@ export default {
if (this.editor) if (this.editor)
this.editor.setTheme(`ace/theme/${this.editorTheme}`); this.editor.setTheme(`ace/theme/${this.editorTheme}`);
}, },
editorFontSize () {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (this.editor) {
this.editor.setOptions({
fontSize: sizes[this.editorFontSize]
});
}
},
autoComplete () { autoComplete () {
if (this.editor) { if (this.editor) {
this.editor.setOptions({ this.editor.setOptions({

View File

@@ -4,7 +4,7 @@
class="toast mt-2" class="toast mt-2"
:class="toastStatus.className" :class="toastStatus.className"
> >
<span class="p-vcentered text-left" v-html="`${toastStatus.iconTag} ${message}`" /> <span class="p-vcentered text-left"><i class="mdi mdi-24px mr-1" :class="toastStatus.iconName" /> {{ message }}</span>
<button class="btn btn-clear" @click="hideToast" /> <button class="btn btn-clear" @click="hideToast" />
</div> </div>
</template> </template>
@@ -30,27 +30,27 @@ export default {
computed: { computed: {
toastStatus () { toastStatus () {
let className = ''; let className = '';
let iconTag = ''; let iconName = '';
switch (this.status) { switch (this.status) {
case 'success': case 'success':
className = 'toast-success'; className = 'toast-success';
iconTag = '<i class="mdi mdi-24px mdi-check mr-1"></i>'; iconName = 'mdi-check';
break; break;
case 'error': case 'error':
className = 'toast-error'; className = 'toast-error';
iconTag = '<i class="mdi mdi-24px mdi-alert-rhombus mr-1"></i>'; iconName = 'mdi-alert-rhombus';
break; break;
case 'warning': case 'warning':
className = 'toast-warning'; className = 'toast-warning';
iconTag = '<i class="mdi mdi-24px mdi-alert mr-1"></i>'; iconName = 'mdi-alert';
break; break;
case 'primary': case 'primary':
className = 'toast-primary'; className = 'toast-primary';
iconTag = '<i class="mdi mdi-24px mdi-information-outline mr-1"></i>'; iconName = 'mdi-information-outline';
break; break;
} }
return { className, iconTag }; return { className, iconName };
} }
}, },
watch: { watch: {

View File

@@ -63,7 +63,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.file-uploader { .file-uploader {
border-radius: 0.1rem; border-radius: $border-radius;
height: 1.8rem; height: 1.8rem;
line-height: 1.2rem; line-height: 1.2rem;
display: flex; display: flex;

View File

@@ -8,7 +8,8 @@
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-play mr-1" /> {{ $t('word.parameters') }}: {{ localRoutine.name }} <i class="mdi mdi-24px mdi-play mr-1" />
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span>
</div> </div>
</template> </template>
<div slot="body"> <div slot="body">
@@ -19,10 +20,10 @@
:key="parameter._id" :key="parameter._id"
class="form-group" class="form-group"
> >
<div class="col-3"> <div class="col-4">
<label class="form-label">{{ parameter.name }}</label> <label class="form-label">{{ parameter.name }}</label>
</div> </div>
<div class="col-9"> <div class="col-8">
<div class="input-group"> <div class="input-group">
<input <input
:ref="i === 0 ? 'firstInput' : ''" :ref="i === 0 ? 'firstInput' : ''"

View File

@@ -15,18 +15,18 @@
<div class="panel-nav"> <div class="panel-nav">
<ul class="tab tab-block"> <ul class="tab tab-block">
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}" :class="{'active': selectedTab === 'general'}"
@click="selectTab('general')" @click="selectTab('general')"
> >
<a class="c-hand">{{ $t('word.general') }}</a> <a class="tab-link">{{ $t('word.general') }}</a>
</li> </li>
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}" :class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')" @click="selectTab('ssl')"
> >
<a class="c-hand">{{ $t('word.ssl') }}</a> <a class="tab-link">{{ $t('word.ssl') }}</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -5,7 +5,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editSchema') }} <i class="mdi mdi-24px mdi-database-edit mr-1" />
<span class="cut-text">{{ $t('message.editSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />

View File

@@ -5,7 +5,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.tableFiller') }} <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $t('message.tableFiller') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -251,32 +252,31 @@ export default {
else { else {
if ([...NUMBER, ...FLOAT].includes(field.type)) if ([...NUMBER, ...FLOAT].includes(field.type))
fieldDefault = Number.isNaN(+field.default) ? null : +field.default; fieldDefault = Number.isNaN(+field.default) ? null : +field.default;
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
fieldDefault = field.default fieldDefault = field.default
? field.default.includes('\'') ? field.default.includes('\'')
? field.default.split('\'')[1] ? field.default.split('\'')[1]
: field.default : field.default
: ''; : '';
} }
else if ([...TIME, ...DATE].includes(field.type))
if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default; fieldDefault = field.default;
else if (BIT.includes(field.type))
if (BIT.includes(field.type))
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', ''); fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
if (DATETIME.includes(field.type)) { if (field.default && ['current_timestamp', 'now()'].includes(field.default.toLowerCase())) {
if (field.default && field.default.toLowerCase().includes('current_timestamp')) {
let datePrecision = ''; let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++) for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S'; datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`); fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
} }
else
fieldDefault = field.default;
} }
else if (field.enumValues)
if (field.enumValues)
fieldDefault = field.enumValues.replaceAll('\'', '').split(','); fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
else
fieldDefault = field.default;
} }
rowObj[field.name] = { value: fieldDefault }; rowObj[field.name] = { value: fieldDefault };

View File

@@ -5,7 +5,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-server-plus mr-1" /> {{ $t('message.createNewConnection') }} <i class="mdi mdi-24px mdi-server-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewConnection') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click="closeModal" /> <a class="btn btn-clear c-hand" @click="closeModal" />
@@ -15,18 +16,18 @@
<div class="panel-nav"> <div class="panel-nav">
<ul class="tab tab-block"> <ul class="tab tab-block">
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}" :class="{'active': selectedTab === 'general'}"
@click="selectTab('general')" @click="selectTab('general')"
> >
<a class="c-hand">{{ $t('word.general') }}</a> <a class="tab-link">{{ $t('word.general') }}</a>
</li> </li>
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}" :class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')" @click="selectTab('ssl')"
> >
<a class="c-hand">{{ $t('word.ssl') }}</a> <a class="tab-link">{{ $t('word.ssl') }}</a>
</li> </li>
</ul> </ul>
</div> </div>
@@ -343,6 +344,7 @@ export default {
else { else {
try { try {
const res = await Connection.makeTest(this.connection); const res = await Connection.makeTest(this.connection);
console.log(res.response);
if (res.status === 'error') if (res.status === 'error')
this.toast = { status: 'error', message: res.response.message }; this.toast = { status: 'error', message: res.response.message };
else else

View File

@@ -155,8 +155,8 @@ export default {
if (this.customizations.languages) if (this.customizations.languages)
this.localFunction.language = this.customizations.languages[0]; this.localFunction.language = this.customizations.languages[0];
if (this.customizations.procedureSql) if (this.customizations.functionSql)
this.localFunction.sql = this.customizations.procedureSql; this.localFunction.sql = this.customizations.functionSql;
setTimeout(() => { setTimeout(() => {
this.$refs.firstInput.focus(); this.$refs.firstInput.focus();
}, 20); }, 20);

View File

@@ -5,7 +5,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" /> {{ $t('message.createNewSchema') }} <i class="mdi mdi-24px mdi-database-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewSchema') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />

View File

@@ -5,7 +5,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.addNewRow') }} <i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span class="cut-text">{{ $t('message.addNewRow') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />

View File

@@ -25,7 +25,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.definer" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.definer') }} {{ $t('word.definer') }}
</label> </label>
@@ -71,30 +71,56 @@
</label> </label>
<div class="column"> <div class="column">
<div class="input-group"> <div class="input-group">
<select v-model="localTrigger.event1" class="form-select"> <select v-model="localTrigger.activation" class="form-select">
<option>BEFORE</option> <option>BEFORE</option>
<option>AFTER</option> <option>AFTER</option>
</select> </select>
<select v-model="localTrigger.event2" class="form-select"> <select
<option>INSERT</option> v-if="!customizations.triggerMultipleEvents"
<option>UPDATE</option> v-model="localTrigger.event"
<option>DELETE</option> class="form-select"
>
<option v-for="event in Object.keys(localEvents)" :key="event">
{{ event }}
</option>
</select> </select>
<div v-if="customizations.triggerMultipleEvents" class="px-4">
<label
v-for="event in Object.keys(localEvents)"
:key="event"
class="form-checkbox form-inline"
@change.prevent="changeEvents(event)"
>
<input :checked="localEvents[event]" type="checkbox"><i class="form-icon" /> {{ event }}
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
</form> </form>
<div v-if="customizations.triggerStatementInCreation" class="workspace-query-results column col-12 mt-2">
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label>
<QueryEditor
ref="queryEditor"
:value.sync="localTrigger.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
/>
</div>
</div> </div>
</ConfirmModal> </ConfirmModal>
</template> </template>
<script> <script>
import ConfirmModal from '@/components/BaseConfirmModal'; import ConfirmModal from '@/components/BaseConfirmModal';
import QueryEditor from '@/components/QueryEditor';
export default { export default {
name: 'ModalNewTrigger', name: 'ModalNewTrigger',
components: { components: {
ConfirmModal ConfirmModal,
QueryEditor
}, },
props: { props: {
workspace: Object workspace: Object
@@ -103,13 +129,15 @@ export default {
return { return {
localTrigger: { localTrigger: {
definer: '', definer: '',
sql: 'BEGIN\r\n\r\nEND', sql: '',
name: '', name: '',
table: '', table: '',
event1: 'BEFORE', activation: 'BEFORE',
event2: 'INSERT' event: 'INSERT'
}, },
isOptionsChanging: false isOptionsChanging: false,
localEvents: { INSERT: false, UPDATE: false, DELETE: false },
editorHeight: 150
}; };
}, },
computed: { computed: {
@@ -122,11 +150,16 @@ export default {
.map(schema => schema.tables); .map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : []; return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
},
customizations () {
return this.workspace.customizations;
} }
}, },
mounted () { created () {
this.localTrigger.table = this.schemaTables.length ? this.schemaTables[0].name : ''; this.localTrigger.table = this.schemaTables.length ? this.schemaTables[0].name : '';
this.localTrigger.sql = this.customizations.triggerSql;
},
mounted () {
setTimeout(() => { setTimeout(() => {
this.$refs.firstInput.focus(); this.$refs.firstInput.focus();
}, 20); }, 20);
@@ -134,6 +167,16 @@ export default {
methods: { methods: {
confirmNewTrigger () { confirmNewTrigger () {
this.$emit('open-create-trigger-editor', this.localTrigger); this.$emit('open-create-trigger-editor', this.localTrigger);
},
changeEvents (event) {
if (this.customizations.triggerMultipleEvents) {
this.localEvents[event] = !this.localEvents[event];
this.localTrigger.event = [];
for (const key in this.localEvents) {
if (this.localEvents[key])
this.localTrigger.event.push(key);
}
}
} }
} }
}; };

View File

@@ -0,0 +1,132 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmNewFunction"
@hide="$emit('close')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewFunction') }}
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.name') }}
</label>
<div class="column">
<input
ref="firstInput"
v-model="localFunction.name"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
<div class="column">
<select
v-if="workspace.users.length"
v-model="localFunction.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="localFunction.comment"
class="form-input"
type="text"
>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'ModalNewTriggerFunction',
components: {
ConfirmModal
},
props: {
workspace: Object
},
data () {
return {
localFunction: {
definer: '',
sql: '',
name: '',
comment: '',
language: null
},
isOptionsChanging: false
};
},
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
if (this.customizations.triggerFunctionlanguages)
this.localFunction.language = this.customizations.triggerFunctionlanguages[0];
if (this.customizations.triggerFunctionSql)
this.localFunction.sql = this.customizations.triggerFunctionSql;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmNewFunction () {
this.$emit('open-create-function-editor', this.localFunction);
}
}
};
</script>

View File

@@ -5,7 +5,8 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-memory mr-1" /> {{ $t('message.processesList') }}: {{ connectionName }} <i class="mdi mdi-24px mdi-memory mr-1" />
<span class="cut-text">{{ $t('message.processesList') }}: {{ connectionName }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />

View File

@@ -6,7 +6,7 @@
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-cog mr-1" /> <i class="mdi mdi-24px mdi-cog mr-1" />
{{ $t('word.settings') }} <span class="cut-text">{{ $t('word.settings') }}</span>
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click="closeModal" /> <a class="btn btn-clear c-hand" @click="closeModal" />
@@ -16,40 +16,40 @@
<div class="panel-nav"> <div class="panel-nav">
<ul class="tab tab-block"> <ul class="tab tab-block">
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}" :class="{'active': selectedTab === 'general'}"
@click="selectTab('general')" @click="selectTab('general')"
> >
<a class="c-hand">{{ $t('word.general') }}</a> <a class="tab-link">{{ $t('word.general') }}</a>
</li> </li>
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'themes'}" :class="{'active': selectedTab === 'themes'}"
@click="selectTab('themes')" @click="selectTab('themes')"
> >
<a class="c-hand">{{ $t('word.themes') }}</a> <a class="tab-link">{{ $t('word.themes') }}</a>
</li> </li>
<li <li
v-if="updateStatus !== 'disabled'" v-if="updateStatus !== 'disabled'"
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'update'}" :class="{'active': selectedTab === 'update'}"
@click="selectTab('update')" @click="selectTab('update')"
> >
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a> <a class="tab-link" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
</li> </li>
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'changelog'}" :class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')" @click="selectTab('changelog')"
> >
<a class="c-hand">{{ $t('word.changelog') }}</a> <a class="tab-link">{{ $t('word.changelog') }}</a>
</li> </li>
<li <li
class="tab-item" class="tab-item c-hand"
:class="{'active': selectedTab === 'about'}" :class="{'active': selectedTab === 'about'}"
@click="selectTab('about')" @click="selectTab('about')"
> >
<a class="c-hand">{{ $t('word.about') }}</a> <a class="tab-link">{{ $t('word.about') }}</a>
</li> </li>
</ul> </ul>
</div> </div>
@@ -83,6 +83,27 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
{{ $t('message.dataTabPageSize') }}
</label>
</div>
<div class="col-6 col-sm-12">
<select
v-model="localPageSize"
class="form-select"
@change="changePageSize(+localPageSize)"
>
<option
v-for="size in pageSizes"
:key="size"
>
{{ size }}
</option>
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="col-6 col-sm-12"> <div class="col-6 col-sm-12">
<label class="form-label"> <label class="form-label">
@@ -201,6 +222,31 @@
</optgroup> </optgroup>
</select> </select>
</div> </div>
<div class="column col-6 mb-4">
<div class="btn-group btn-group-block">
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'small'}"
@click="changeEditorFontSize('small')"
>
{{ $t('word.small') }}
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'medium'}"
@click="changeEditorFontSize('medium')"
>
{{ $t('word.medium') }}
</button>
<button
class="btn btn-dark cut-text"
:class="{'active': editorFontSize === 'large'}"
@click="changeEditorFontSize('large')"
>
{{ $t('word.large') }}
</button>
</div>
</div>
<div class="column col-12"> <div class="column col-12">
<BaseTextEditor <BaseTextEditor
:value="exampleQuery" :value="exampleQuery"
@@ -257,9 +303,11 @@ export default {
data () { data () {
return { return {
localLocale: null, localLocale: null,
localPageSize: null,
localTimeout: null, localTimeout: null,
localEditorTheme: null, localEditorTheme: null,
selectedTab: 'general', selectedTab: 'general',
pageSizes: [40, 100, 250, 500, 1000],
editorThemes: [ editorThemes: [
{ {
group: this.$t('word.light'), group: this.$t('word.light'),
@@ -317,11 +365,13 @@ export default {
appVersion: 'application/appVersion', appVersion: 'application/appVersion',
selectedSettingTab: 'application/selectedSettingTab', selectedSettingTab: 'application/selectedSettingTab',
selectedLocale: 'settings/getLocale', selectedLocale: 'settings/getLocale',
pageSize: 'settings/getDataTabLimit',
selectedAutoComplete: 'settings/getAutoComplete', selectedAutoComplete: 'settings/getAutoComplete',
selectedLineWrap: 'settings/getLineWrap', selectedLineWrap: 'settings/getLineWrap',
notificationsTimeout: 'settings/getNotificationsTimeout', notificationsTimeout: 'settings/getNotificationsTimeout',
applicationTheme: 'settings/getApplicationTheme', applicationTheme: 'settings/getApplicationTheme',
editorTheme: 'settings/getEditorTheme', editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
updateStatus: 'application/getUpdateStatus', updateStatus: 'application/getUpdateStatus',
selectedWorkspace: 'workspaces/getSelected', selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace' getWorkspace: 'workspaces/getWorkspace'
@@ -359,6 +409,7 @@ ORDER BY
}, },
created () { created () {
this.localLocale = this.selectedLocale; this.localLocale = this.selectedLocale;
this.localPageSize = this.pageSize;
this.localTimeout = this.notificationsTimeout; this.localTimeout = this.notificationsTimeout;
this.localEditorTheme = this.editorTheme; this.localEditorTheme = this.editorTheme;
this.selectedTab = this.selectedSettingTab; this.selectedTab = this.selectedSettingTab;
@@ -371,10 +422,12 @@ ORDER BY
...mapActions({ ...mapActions({
closeModal: 'application/hideSettingModal', closeModal: 'application/hideSettingModal',
changeLocale: 'settings/changeLocale', changeLocale: 'settings/changeLocale',
changePageSize: 'settings/changePageSize',
changeAutoComplete: 'settings/changeAutoComplete', changeAutoComplete: 'settings/changeAutoComplete',
changeLineWrap: 'settings/changeLineWrap', changeLineWrap: 'settings/changeLineWrap',
changeApplicationTheme: 'settings/changeApplicationTheme', changeApplicationTheme: 'settings/changeApplicationTheme',
changeEditorTheme: 'settings/changeEditorTheme', changeEditorTheme: 'settings/changeEditorTheme',
changeEditorFontSize: 'settings/changeEditorFontSize',
updateNotificationsTimeout: 'settings/updateNotificationsTimeout' updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
}), }),
selectTab (tab) { selectTab (tab) {

View File

@@ -38,6 +38,7 @@ export default {
computed: { computed: {
...mapGetters({ ...mapGetters({
editorTheme: 'settings/getEditorTheme', editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
autoComplete: 'settings/getAutoComplete', autoComplete: 'settings/getAutoComplete',
lineWrap: 'settings/getLineWrap', lineWrap: 'settings/getLineWrap',
baseCompleter: 'application/getBaseCompleter' baseCompleter: 'application/getBaseCompleter'
@@ -158,6 +159,19 @@ export default {
if (this.editor) if (this.editor)
this.editor.setTheme(`ace/theme/${this.editorTheme}`); this.editor.setTheme(`ace/theme/${this.editorTheme}`);
}, },
editorFontSize () {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
if (this.editor) {
this.editor.setOptions({
fontSize: sizes[this.editorFontSize]
});
}
},
autoComplete () { autoComplete () {
if (this.editor) { if (this.editor) {
this.editor.setOptions({ this.editor.setOptions({
@@ -173,8 +187,15 @@ export default {
} }
}, },
isSelected () { isSelected () {
if (this.isSelected) if (this.isSelected) {
this.lastSchema = this.schema; this.lastSchema = this.schema;
this.editor.resize();
}
},
height () {
setTimeout(() => {
this.editor.resize();
}, 20);
}, },
lastSchema () { lastSchema () {
if (this.editor) { if (this.editor) {

View File

@@ -174,6 +174,7 @@ export default {
align-content: center; align-content: center;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
border-radius: 0;
&:hover { &:hover {
opacity: 1; opacity: 1;
@@ -181,6 +182,21 @@ export default {
&.selected { &.selected {
opacity: 1; opacity: 1;
&::before {
height: $settingbar-width;
}
}
&::before {
content: '';
height: 0;
width: 3px;
transition: height 0.2s;
background-color: $primary-color;
position: absolute;
left: 0;
border-radius: $border-radius;
} }
.settingbar-element-icon { .settingbar-element-icon {
@@ -211,7 +227,7 @@ export default {
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
font-size: 0.7rem; font-size: 0.7rem;
background: rgba(48, 55, 66, 0.95); background: rgba(48, 55, 66, 0.95);
border-radius: 0.1rem; border-radius: $border-radius;
color: #fff; color: #fff;
max-width: 320px; max-width: 320px;
pointer-events: none; pointer-events: none;

View File

@@ -127,6 +127,12 @@
:connection="connection" :connection="connection"
:function="workspace.breadcrumbs.function" :function="workspace.breadcrumbs.function"
/> />
<WorkspacePropsTabTriggerFunction
v-show="selectedTab === 'prop' && workspace.breadcrumbs.triggerFunction"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:function="workspace.breadcrumbs.triggerFunction"
/>
<WorkspacePropsTabScheduler <WorkspacePropsTabScheduler
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler" v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler"
:is-selected="selectedTab === 'prop'" :is-selected="selectedTab === 'prop'"
@@ -165,6 +171,7 @@ import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger'; import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine'; import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction'; import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler'; import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
import ModalProcessesList from '@/components/ModalProcessesList'; import ModalProcessesList from '@/components/ModalProcessesList';
@@ -179,6 +186,7 @@ export default {
WorkspacePropsTabTrigger, WorkspacePropsTabTrigger,
WorkspacePropsTabRoutine, WorkspacePropsTabRoutine,
WorkspacePropsTabFunction, WorkspacePropsTabFunction,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabScheduler, WorkspacePropsTabScheduler,
ModalProcessesList ModalProcessesList
}, },
@@ -208,6 +216,7 @@ export default {
if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true; if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true;
if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true; if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true;
if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true; if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true;
if (this.workspace.breadcrumbs.triggerFunction && this.workspace.customizations.functionSettings) return true;
if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true; if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true;
return false; return false;
}, },
@@ -219,6 +228,7 @@ export default {
this.workspace.breadcrumbs.trigger === null && this.workspace.breadcrumbs.trigger === null &&
this.workspace.breadcrumbs.procedure === null && this.workspace.breadcrumbs.procedure === null &&
this.workspace.breadcrumbs.function === null && this.workspace.breadcrumbs.function === null &&
this.workspace.breadcrumbs.triggerFunction === null &&
this.workspace.breadcrumbs.scheduler === null && this.workspace.breadcrumbs.scheduler === null &&
['data', 'prop'].includes(this.workspace.selected_tab) ['data', 'prop'].includes(this.workspace.selected_tab)
) || ) ||
@@ -362,7 +372,7 @@ export default {
min-width: 100%; min-width: 100%;
.menu-item a { .menu-item a {
border-radius: 0.1rem; border-radius: $border-radius;
color: inherit; color: inherit;
display: block; display: block;
margin: 0 -0.4rem; margin: 0 -0.4rem;

View File

@@ -90,6 +90,12 @@
@close="hideCreateFunctionModal" @close="hideCreateFunctionModal"
@open-create-function-editor="openCreateFunctionEditor" @open-create-function-editor="openCreateFunctionEditor"
/> />
<ModalNewTriggerFunction
v-if="isNewTriggerFunctionModal"
:workspace="workspace"
@close="hideCreateTriggerFunctionModal"
@open-create-function-editor="openCreateTriggerFunctionEditor"
/>
<ModalNewScheduler <ModalNewScheduler
v-if="isNewSchedulerModal" v-if="isNewSchedulerModal"
:workspace="workspace" :workspace="workspace"
@@ -106,6 +112,7 @@
@show-create-trigger-modal="showCreateTriggerModal" @show-create-trigger-modal="showCreateTriggerModal"
@show-create-routine-modal="showCreateRoutineModal" @show-create-routine-modal="showCreateRoutineModal"
@show-create-function-modal="showCreateFunctionModal" @show-create-function-modal="showCreateFunctionModal"
@show-create-trigger-function-modal="showCreateTriggerFunctionModal"
@show-create-scheduler-modal="showCreateSchedulerModal" @show-create-scheduler-modal="showCreateSchedulerModal"
@reload="refresh" @reload="refresh"
/> />
@@ -147,6 +154,7 @@ import ModalNewView from '@/components/ModalNewView';
import ModalNewTrigger from '@/components/ModalNewTrigger'; import ModalNewTrigger from '@/components/ModalNewTrigger';
import ModalNewRoutine from '@/components/ModalNewRoutine'; import ModalNewRoutine from '@/components/ModalNewRoutine';
import ModalNewFunction from '@/components/ModalNewFunction'; import ModalNewFunction from '@/components/ModalNewFunction';
import ModalNewTriggerFunction from '@/components/ModalNewTriggerFunction';
import ModalNewScheduler from '@/components/ModalNewScheduler'; import ModalNewScheduler from '@/components/ModalNewScheduler';
export default { export default {
@@ -163,6 +171,7 @@ export default {
ModalNewTrigger, ModalNewTrigger,
ModalNewRoutine, ModalNewRoutine,
ModalNewFunction, ModalNewFunction,
ModalNewTriggerFunction,
ModalNewScheduler ModalNewScheduler
}, },
props: { props: {
@@ -179,6 +188,7 @@ export default {
isNewTriggerModal: false, isNewTriggerModal: false,
isNewRoutineModal: false, isNewRoutineModal: false,
isNewFunctionModal: false, isNewFunctionModal: false,
isNewTriggerFunctionModal: false,
isNewSchedulerModal: false, isNewSchedulerModal: false,
localWidth: null, localWidth: null,
@@ -209,6 +219,9 @@ export default {
}, },
connectionName () { connectionName () {
return this.getConnectionName(this.connection.uid); return this.getConnectionName(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
} }
}, },
watch: { watch: {
@@ -363,7 +376,8 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, trigger: payload.name }); const triggerName = this.customizations.triggerTableInName ? `${payload.table}.${payload.name}` : payload.name;
this.changeBreadcrumbs({ schema: this.selectedDatabase, trigger: triggerName });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' }); this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
} }
else else
@@ -399,6 +413,13 @@ export default {
hideCreateFunctionModal () { hideCreateFunctionModal () {
this.isNewFunctionModal = false; this.isNewFunctionModal = false;
}, },
showCreateTriggerFunctionModal () {
this.closeDatabaseContext();
this.isNewTriggerFunctionModal = true;
},
hideCreateTriggerFunctionModal () {
this.isNewTriggerFunctionModal = false;
},
showCreateSchedulerModal () { showCreateSchedulerModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.isNewSchedulerModal = true; this.isNewSchedulerModal = true;
@@ -422,6 +443,22 @@ export default {
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
}, },
async openCreateTriggerFunctionEditor (payload) {
const params = {
uid: this.connection.uid,
...payload
};
const { status, response } = await Functions.createTriggerFunction(params);
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, triggerFunction: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
}
else
this.addNotification({ status: 'error', message: response });
},
async openCreateSchedulerEditor (payload) { async openCreateSchedulerEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
@@ -451,6 +488,11 @@ export default {
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
cursor: ew-resize; cursor: ew-resize;
z-index: 99; z-index: 99;
transition: background 0.2s;
&:hover {
background: rgba($primary-color, 50%);
}
} }
.workspace-explorebar { .workspace-explorebar {

View File

@@ -20,7 +20,8 @@
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ deleteMessage }} <i class="mdi mdi-24px mdi-delete mr-1" />
<span class="cut-text">{{ deleteMessage }}</span>
</div> </div>
</template> </template>
<div slot="body"> <div slot="body">
@@ -83,6 +84,7 @@ export default {
case 'procedure': case 'procedure':
return this.$t('message.deleteRoutine'); return this.$t('message.deleteRoutine');
case 'function': case 'function':
case 'triggerFunction':
return this.$t('message.deleteFunction'); return this.$t('message.deleteFunction');
case 'scheduler': case 'scheduler':
return this.$t('message.deleteScheduler'); return this.$t('message.deleteScheduler');
@@ -134,6 +136,7 @@ export default {
}); });
break; break;
case 'function': case 'function':
case 'triggerFunction':
res = await Functions.dropFunction({ res = await Functions.dropFunction({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,
func: this.selectedMisc.name func: this.selectedMisc.name

View File

@@ -93,6 +93,34 @@
</details> </details>
</div> </div>
<div v-if="filteredTriggerFunctions.length && customizations.triggerFunctions" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction}">
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
{{ $tc('word.triggerFunction', 2) }}
</summary>
<div class="accordion-body">
<div>
<ul class="menu menu-nav pt-0">
<li
v-for="(func, i) of filteredTriggerFunctions"
:key="`${func.name}-${i}`"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
@click="setBreadcrumbs({schema: database.name, triggerFunction: func.name})"
@contextmenu.prevent="showMiscContext($event, {...func, type: 'triggerFunction'})"
>
<a class="table-name">
<i class="table-icon mdi mdi-cog-clockwise mdi-18px mr-1" />
<span v-html="highlightWord(func.name)" />
</a>
</li>
</ul>
</div>
</div>
</details>
</div>
<div v-if="filteredFunctions.length && customizations.functions" class="database-misc"> <div v-if="filteredFunctions.length && customizations.functions" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}"> <summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}">
@@ -189,6 +217,11 @@ export default {
filteredFunctions () { filteredFunctions () {
return this.database.functions.filter(func => func.name.search(this.searchTerm) >= 0); return this.database.functions.filter(func => func.name.search(this.searchTerm) >= 0);
}, },
filteredTriggerFunctions () {
return this.database.triggerFunctions
? this.database.triggerFunctions.filter(func => func.name.search(this.searchTerm) >= 0)
: [];
},
filteredSchedulers () { filteredSchedulers () {
return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0); return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0);
}, },
@@ -311,7 +344,7 @@ export default {
.database-name, .database-name,
.misc-name { .misc-name {
&:hover { &:hover {
border-radius: 2px; border-radius: $border-radius;
} }
} }
@@ -320,7 +353,7 @@ export default {
position: relative; position: relative;
&:hover { &:hover {
border-radius: 2px; border-radius: $border-radius;
} }
} }

View File

@@ -42,6 +42,13 @@
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span>
</div> </div>
<div
v-if="workspace.customizations.triggerFunctionAdd"
class="context-element"
@click="showCreateTriggerFunctionModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise pr-1" /> {{ $tc('word.triggerFunction', 1) }}</span>
</div>
<div <div
v-if="workspace.customizations.schedulerAdd" v-if="workspace.customizations.schedulerAdd"
class="context-element" class="context-element"
@@ -69,7 +76,8 @@
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-remove mr-1" /> {{ $t('message.deleteSchema') }} <i class="mdi mdi-24px mdi-database-remove mr-1" />
<span class="cut-text">{{ $t('message.deleteSchema') }}</span>
</div> </div>
</template> </template>
<div slot="body"> <div slot="body">
@@ -139,6 +147,9 @@ export default {
showCreateFunctionModal () { showCreateFunctionModal () {
this.$emit('show-create-function-modal'); this.$emit('show-create-function-modal');
}, },
showCreateTriggerFunctionModal () {
this.$emit('show-create-trigger-function-modal');
},
showCreateSchedulerModal () { showCreateSchedulerModal () {
this.$emit('show-create-scheduler-modal'); this.$emit('show-create-scheduler-modal');
}, },

View File

@@ -3,6 +3,13 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="closeContext" @close-context="closeContext"
> >
<div
v-if="selectedTable.type === 'table'"
class="context-element"
@click="duplicateTable"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-multiple text-light pr-1" /> {{ $t('message.duplicateTable') }}</span>
</div>
<div <div
v-if="selectedTable.type === 'table'" v-if="selectedTable.type === 'table'"
class="context-element" class="context-element"
@@ -21,7 +28,7 @@
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-table-off mr-1" /> {{ $t('message.emptyTable') }} <i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ $t('message.emptyTable') }}</span>
</div> </div>
</template> </template>
<div slot="body"> <div slot="body">
@@ -37,7 +44,8 @@
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-table-remove mr-1" /> {{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }} <i class="mdi mdi-24px mdi-table-remove mr-1" />
<span class="cut-text">{{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }}</span>
</div> </div>
</template> </template>
<div slot="body"> <div slot="body">
@@ -104,6 +112,24 @@ export default {
closeContext () { closeContext () {
this.$emit('close-context'); this.$emit('close-context');
}, },
async duplicateTable () {
try {
const { status, response } = await Tables.duplicateTable({
uid: this.selectedWorkspace,
table: this.selectedTable.name
});
if (status === 'success') {
this.closeContext();
this.$emit('reload');
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
},
async emptyTable () { async emptyTable () {
try { try {
const { status, response } = await Tables.truncateTable({ const { status, response } = await Tables.truncateTable({

View File

@@ -8,7 +8,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-key-link mr-1" /> {{ $t('word.foreignKeys') }} "{{ table }}" <i class="mdi mdi-24px mdi-key-link mr-1" />
<span class="cut-text">{{ $t('word.foreignKeys') }} "{{ table }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -361,7 +362,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.tile { .tile {
border-radius: 2px; border-radius: $border-radius;
opacity: 0.5; opacity: 0.5;
transition: background 0.2s; transition: background 0.2s;
transition: opacity 0.2s; transition: opacity 0.2s;

View File

@@ -7,7 +7,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ localOptions.name }}" <i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ localOptions.name }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">

View File

@@ -8,7 +8,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> {{ $t('word.parameters') }} "{{ func }}" <i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span class="cut-text">{{ $t('word.parameters') }} "{{ func }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -273,7 +274,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.tile { .tile {
border-radius: 2px; border-radius: $border-radius;
opacity: 0.5; opacity: 0.5;
transition: background 0.2s; transition: background 0.2s;
transition: opacity 0.2s; transition: opacity 0.2s;

View File

@@ -8,7 +8,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" /> {{ $t('word.indexes') }} "{{ table }}" <i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span class="cut-text">{{ $t('word.indexes') }} "{{ table }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -246,7 +247,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.tile { .tile {
border-radius: 2px; border-radius: $border-radius;
opacity: 0.5; opacity: 0.5;
transition: background 0.2s; transition: background 0.2s;
transition: opacity 0.2s; transition: opacity 0.2s;

View File

@@ -7,7 +7,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ table }}" <i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ table }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -47,6 +48,7 @@
v-model="optionsProxy.autoIncrement" v-model="optionsProxy.autoIncrement"
class="form-input" class="form-input"
type="number" type="number"
:disabled="optionsProxy.autoIncrement === null"
> >
</div> </div>
</div> </div>

View File

@@ -7,7 +7,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ localOptions.name }}" <i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ localOptions.name }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">

View File

@@ -8,7 +8,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> {{ $t('word.parameters') }} "{{ routine }}" <i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span class="cut-text">{{ $t('word.parameters') }} "{{ routine }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -273,7 +274,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.tile { .tile {
border-radius: 2px; border-radius: $border-radius;
opacity: 0.5; opacity: 0.5;
transition: background 0.2s; transition: background 0.2s;
transition: opacity 0.2s; transition: opacity 0.2s;

View File

@@ -7,7 +7,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-timer mr-1" /> {{ $t('word.timing') }} "{{ localOptions.name }}" <i class="mdi mdi-24px mdi-timer mr-1" />
<span class="cut-text">{{ $t('word.timing') }} "{{ localOptions.name }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">

View File

@@ -239,11 +239,11 @@ export default {
field.defaultType = 'noval'; field.defaultType = 'noval';
else if (field.default === 'NULL') else if (field.default === 'NULL')
field.defaultType = 'null'; field.defaultType = 'null';
else if (field.default.match(/^\s*(\w+)\s*\((.*)\)$/)) else if (isNaN(+field.default) && field.default.charAt(0) !== '\'')
field.defaultType = 'expression'; field.defaultType = 'expression';
else { else {
field.defaultType = 'custom'; field.defaultType = 'custom';
if (isNaN(field.default) && !field.default.includes('\'')) if (isNaN(+field.default) && !field.default.includes('\''))
field.default = `'${field.default}'`; field.default = `'${field.default}'`;
} }

View File

@@ -137,7 +137,9 @@ export default {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction); return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
}, },
isDefinerInUsers () { isDefinerInUsers () {
return this.originalFunction ? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``) : true; return this.originalFunction
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
}, },
schemaTables () { schemaTables () {
const schemaTables = this.workspace.structure const schemaTables = this.workspace.structure

View File

@@ -37,7 +37,7 @@
> >
</div> </div>
</div> </div>
<div class="column col-auto"> <div v-if="customizations.definer" class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label> <label class="form-label">{{ $t('word.definer') }}</label>
<select <select
@@ -67,7 +67,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="columns"> <fieldset class="columns" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto"> <div class="column col-auto">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label> <label class="form-label">{{ $t('word.table') }}</label>
@@ -82,19 +82,33 @@
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label> <label class="form-label">{{ $t('word.event') }}</label>
<div class="input-group"> <div class="input-group">
<select v-model="localTrigger.event1" class="form-select"> <select v-model="localTrigger.activation" class="form-select">
<option>BEFORE</option> <option>BEFORE</option>
<option>AFTER</option> <option>AFTER</option>
</select> </select>
<select v-model="localTrigger.event2" class="form-select"> <select
<option>INSERT</option> v-if="!customizations.triggerMultipleEvents"
<option>UPDATE</option> v-model="localTrigger.event"
<option>DELETE</option> class="form-select"
>
<option v-for="event in Object.keys(localEvents)" :key="event">
{{ event }}
</option>
</select> </select>
<div v-if="customizations.triggerMultipleEvents" class="px-4">
<label
v-for="event in Object.keys(localEvents)"
:key="event"
class="form-checkbox form-inline"
@change.prevent="changeEvents(event)"
>
<input :checked="localEvents[event]" type="checkbox"><i class="form-icon" /> {{ event }}
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </fieldset>
</div> </div>
<div class="workspace-query-results column col-12 mt-2 p-relative"> <div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" /> <BaseLoader v-if="isLoading" />
@@ -136,7 +150,8 @@ export default {
localTrigger: { sql: '' }, localTrigger: { sql: '' },
lastTrigger: null, lastTrigger: null,
sqlProxy: '', sqlProxy: '',
editorHeight: 300 editorHeight: 300,
localEvents: { INSERT: false, UPDATE: false, DELETE: false }
}; };
}, },
computed: { computed: {
@@ -147,6 +162,9 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
customizations () {
return this.workspace.customizations;
},
isSelected () { isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.trigger; return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.trigger;
}, },
@@ -209,6 +227,10 @@ export default {
async getTriggerData () { async getTriggerData () {
if (!this.trigger) return; if (!this.trigger) return;
Object.keys(this.localEvents).forEach(event => {
this.localEvents[event] = false;
});
this.localTrigger = { sql: '' }; this.localTrigger = { sql: '' };
this.isLoading = true; this.isLoading = true;
@@ -224,6 +246,12 @@ export default {
this.originalTrigger = response; this.originalTrigger = response;
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger)); this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
this.sqlProxy = this.localTrigger.sql; this.sqlProxy = this.localTrigger.sql;
if (this.customizations.triggerMultipleEvents) {
this.originalTrigger.event.forEach(e => {
this.localEvents[e] = true;
});
}
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
@@ -235,6 +263,16 @@ export default {
this.resizeQueryEditor(); this.resizeQueryEditor();
this.isLoading = false; this.isLoading = false;
}, },
changeEvents (event) {
if (this.customizations.triggerMultipleEvents) {
this.localEvents[event] = !this.localEvents[event];
this.localTrigger.event = [];
for (const key in this.localEvents) {
if (this.localEvents[key])
this.localTrigger.event.push(key);
}
}
},
async saveChanges () { async saveChanges () {
if (this.isSaving) return; if (this.isSaving) return;
this.isSaving = true; this.isSaving = true;
@@ -257,7 +295,8 @@ export default {
if (oldName !== this.localTrigger.name) { if (oldName !== this.localTrigger.name) {
this.setUnsavedChanges(false); this.setUnsavedChanges(false);
this.changeBreadcrumbs({ schema: this.schema, trigger: this.localTrigger.name }); const triggerName = this.customizations.triggerTableInName ? `${this.localTrigger.table}.${this.localTrigger.name}` : this.localTrigger.name;
this.changeBreadcrumbs({ schema: this.schema, trigger: triggerName });
} }
this.getTriggerData(); this.getTriggerData();
@@ -274,6 +313,16 @@ export default {
clearChanges () { clearChanges () {
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger)); this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql); this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
Object.keys(this.localEvents).forEach(event => {
this.localEvents[event] = false;
});
if (this.customizations.triggerMultipleEvents) {
this.originalTrigger.event.forEach(e => {
this.localEvents[e] = true;
});
}
}, },
resizeQueryEditor () { resizeQueryEditor () {
if (this.$refs.queryEditor) { if (this.$refs.queryEditor) {

View File

@@ -0,0 +1,315 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localFunction.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
/>
</div>
<WorkspacePropsTriggerFunctionOptionsModal
v-if="isOptionsModal"
:local-options="localFunction"
:workspace="workspace"
@hide="hideOptionsModal"
@options-update="optionsUpdate"
/>
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localFunction"
:client="workspace.client"
@confirm="runFunction"
@close="hideAskParamsModal"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspacePropsTriggerFunctionOptionsModal from '@/components/WorkspacePropsTriggerFunctionOptionsModal';
import ModalAskParameters from '@/components/ModalAskParameters';
import Functions from '@/ipc-api/Functions';
export default {
name: 'WorkspacePropsTabTriggerFunction',
components: {
BaseLoader,
QueryEditor,
WorkspacePropsTriggerFunctionOptionsModal,
ModalAskParameters
},
props: {
connection: Object,
function: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
isOptionsModal: false,
isParamsModal: false,
isAskingParameters: false,
originalFunction: null,
localFunction: { sql: '' },
lastFunction: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.function;
},
schema () {
return this.workspace.breadcrumbs.schema;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
},
isDefinerInUsers () {
return this.originalFunction
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
}
},
watch: {
async function () {
if (this.isSelected) {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
this.lastFunction = this.function;
}
},
async isSelected (val) {
if (val && this.lastFunction !== this.function) {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
this.lastFunction = this.function;
}
},
isChanged (val) {
if (this.isSelected && this.lastFunction === this.function && this.function !== null)
this.setUnsavedChanges(val);
}
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
newTab: 'workspaces/newTab'
}),
async getFunctionData () {
if (!this.function) return;
this.isLoading = true;
this.localFunction = { sql: '' };
const params = {
uid: this.connection.uid,
schema: this.schema,
func: this.workspace.breadcrumbs.triggerFunction
};
try {
const { status, response } = await Functions.getFunctionInformations(params);
if (status === 'success') {
this.originalFunction = response;
this.originalFunction.parameters = [...this.originalFunction.parameters.map(param => {
param._id = uidGen();
return param;
})];
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
this.sqlProxy = this.localFunction.sql;
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.resizeQueryEditor();
this.isLoading = false;
},
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
func: {
...this.localFunction,
oldName: this.originalFunction.name
}
};
try {
const { status, response } = await Functions.alterTriggerFunction(params);
if (status === 'success') {
const oldName = this.originalFunction.name;
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localFunction.name) {
this.setUnsavedChanges(false);
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
}
this.getFunctionData();
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
optionsUpdate (options) {
this.localFunction = options;
},
parametersUpdate (parameters) {
this.localFunction = { ...this.localFunction, parameters };
},
runFunctionCheck () {
if (this.localFunction.parameters.length)
this.showAskParamsModal();
else
this.runFunction();
},
runFunction (params) {
if (!params) params = [];
let sql;
switch (this.connection.client) { // TODO: move in a better place
case 'maria':
case 'mysql':
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
break;
case 'pg':
sql = `SELECT ${this.originalFunction.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `SELECT ${this.originalFunction.name} ${params.join(',')}`;
break;
default:
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
}
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;
},
hideOptionsModal () {
this.isOptionsModal = false;
},
showParamsModal () {
this.isParamsModal = true;
},
hideParamsModal () {
this.isParamsModal = false;
},
showAskParamsModal () {
this.isAskingParameters = true;
},
hideAskParamsModal () {
this.isAskingParameters = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};
</script>

View File

@@ -232,7 +232,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> {{ $t('word.default') }} "{{ row.name }}" <i class="mdi mdi-24px mdi-playlist-edit mr-1" />
<span class="cut-text">{{ $t('word.default') }} "{{ row.name }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">

View File

@@ -0,0 +1,125 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmOptionsChange"
@hide="$emit('hide')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ localOptions.name }}"</span>
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div v-if="customizations.triggerFunctionlanguages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
<div class="column">
<select
v-if="workspace.users.length"
v-model="optionsProxy.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="optionsProxy.comment"
class="form-input"
type="text"
>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsTriggerFunctionOptionsModal',
components: {
ConfirmModal
},
props: {
localOptions: Object,
workspace: Object
},
data () {
return {
optionsProxy: {},
isOptionsChanging: false
};
},
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
},
isInDataTypes () {
let typeNames = [];
for (const group of this.workspace.dataTypes) {
typeNames = group.types.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
}
return typeNames.includes(this.localOptions.returns);
}
},
created () {
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
if (!this.isTableNameValid)
this.optionsProxy.name = this.localOptions.name;
this.$emit('options-update', this.optionsProxy);
}
}
};
</script>

View File

@@ -31,25 +31,6 @@
<span>{{ $t('word.run') }}</span> <span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play" /> <i class="mdi mdi-24px mdi-play" />
</button> </button>
<div class="dropdown export-dropdown pr-2">
<button
:disabled="!results.length || isQuering"
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0"
>
<span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" />
</button>
<ul class="menu text-left">
<li class="menu-item">
<a class="c-hand" @click="downloadTable('json')">JSON</a>
</li>
<li class="menu-item">
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
</li>
</ul>
</div>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:disabled="!query || isQuering" :disabled="!query || isQuering"
@@ -68,6 +49,28 @@
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep pl-1" /> <i class="mdi mdi-24px mdi-delete-sweep pl-1" />
</button> </button>
<div class="divider-vert py-3" />
<div class="dropdown table-dropdown pr-2">
<button
:disabled="!results.length || isQuering"
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0"
>
<span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" />
</button>
<ul class="menu text-left">
<li class="menu-item">
<a class="c-hand" @click="downloadTable('json')">JSON</a>
</li>
<li class="menu-item">
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
</li>
</ul>
</div>
</div> </div>
<div class="workspace-query-info"> <div class="workspace-query-info">
<div <div
@@ -281,11 +284,16 @@ export default {
.query-area-resizer { .query-area-resizer {
position: absolute; position: absolute;
height: 5px; height: 4px;
bottom: 40px; bottom: 40px;
width: 100%; width: 100%;
cursor: ns-resize; cursor: ns-resize;
z-index: 99; z-index: 99;
transition: background 0.2s;
&:hover {
background: rgba($primary-color, 50%);
}
} }
.workspace-query-runner-footer { .workspace-query-runner-footer {
@@ -319,10 +327,4 @@ export default {
min-height: 200px; min-height: 200px;
} }
} }
.export-dropdown {
.menu {
min-width: 100%;
}
}
</style> </style>

View File

@@ -12,6 +12,8 @@
:selected-rows="selectedRows" :selected-rows="selectedRows"
@show-delete-modal="showDeleteConfirmModal" @show-delete-modal="showDeleteConfirmModal"
@set-null="setNull" @set-null="setNull"
@copy-cell="copyCell"
@copy-row="copyRow"
@close-context="closeContext" @close-context="closeContext"
/> />
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs"> <ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
@@ -85,7 +87,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }} <i class="mdi mdi-24px mdi-delete mr-1" />
<span class="cut-text">{{ $tc('message.deleteRows', selectedRows.length) }}</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -139,7 +142,8 @@ export default {
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
getWorkspace: 'workspaces/getWorkspace' getWorkspace: 'workspaces/getWorkspace',
pageSize: 'settings/getDataTabLimit'
}), }),
workspaceSchema () { workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema; return this.getWorkspace(this.connUid).breadcrumbs.schema;
@@ -157,7 +161,7 @@ export default {
return this.fields.every(field => field.name); return this.fields.every(field => field.name);
}, },
isHardSort () { isHardSort () {
return this.mode === 'table' && this.localResults.length === 1000; return this.mode === 'table' && this.localResults.length === this.pageSize;
}, },
sortedResults () { sortedResults () {
if (this.currentSort && !this.isHardSort) { if (this.currentSort && !this.isHardSort) {
@@ -345,7 +349,9 @@ export default {
closeContext () { closeContext () {
this.isContext = false; this.isContext = false;
}, },
showDeleteConfirmModal () { showDeleteConfirmModal (e) {
if (e && e.path && ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.path[0].tagName))
return;
this.isDeleteConfirmModal = true; this.isDeleteConfirmModal = true;
}, },
hideDeleteConfirmModal () { hideDeleteConfirmModal () {
@@ -381,6 +387,22 @@ export default {
}; };
this.$emit('update-field', params); this.$emit('update-field', params);
}, },
copyCell () {
const row = this.localResults.find(row => this.selectedRows.includes(row._id));
const cellName = Object.keys(row).find(prop => [
this.selectedCell.field,
`${this.fields[0].table}.${this.selectedCell.field}`,
`${this.fields[0].tableAlias}.${this.selectedCell.field}`
].includes(prop));
const valueToCopy = row[cellName];
navigator.clipboard.writeText(valueToCopy);
},
copyRow () {
const row = this.localResults.find(row => this.selectedRows.includes(row._id));
const rowToCopy = JSON.parse(JSON.stringify(row));
delete rowToCopy._id;
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
},
applyUpdate (params) { applyUpdate (params) {
const { primary, id, field, table, content } = params; const { primary, id, field, table, content } = params;
@@ -461,7 +483,7 @@ export default {
downloadTable (format, filename) { downloadTable (format, filename) {
if (!this.sortedResults) return; if (!this.sortedResults) return;
const rows = [...this.sortedResults].map(row => { const rows = JSON.parse(JSON.stringify(this.sortedResults)).map(row => {
delete row._id; delete row._id;
return row; return row;
}); });

View File

@@ -3,6 +3,30 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="closeContext" @close-context="closeContext"
> >
<div v-if="selectedRows.length === 1" class="context-element">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ $t('word.copy') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu">
<div
v-if="selectedRows.length === 1"
class="context-element"
@click="copyCell"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ $tc('word.cell', 1) }}
</span>
</div>
<div
v-if="selectedRows.length === 1"
class="context-element"
@click="copyRow"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ $tc('word.row', 1) }}
</span>
</div>
</div>
</div>
<div <div
v-if="selectedRows.length === 1" v-if="selectedRows.length === 1"
class="context-element" class="context-element"
@@ -44,6 +68,14 @@ export default {
setNull () { setNull () {
this.$emit('set-null'); this.$emit('set-null');
this.closeContext(); this.closeContext();
},
copyCell () {
this.$emit('copy-cell');
this.closeContext();
},
copyRow () {
this.$emit('copy-row');
this.closeContext();
} }
} }
}; };

View File

@@ -10,7 +10,7 @@
> >
<template v-if="cKey !== '_id'"> <template v-if="cKey !== '_id'">
<span <span
v-if="!isInlineEditor[cKey]" v-if="!isInlineEditor[cKey] && fields[cKey]"
class="cell-content px-2" class="cell-content px-2"
:class="`${isNull(col)} ${typeClass(fields[cKey].type)}`" :class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON($event, col, cKey)" @dblclick="editON($event, col, cKey)"
@@ -74,7 +74,7 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> {{ $t('word.edit') }} "{{ editingField }}" <i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -138,7 +138,8 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> {{ $t('word.edit') }} "{{ editingField }}" <i class="mdi mdi-24px mdi-playlist-edit mr-1" />
<span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@@ -348,7 +349,7 @@ export default {
return false; return false;
}, },
enumArray () { enumArray () {
if (this.fields[this.editingField].enumValues) if (this.fields[this.editingField] && this.fields[this.editingField].enumValues)
return this.fields[this.editingField].enumValues.replaceAll('\'', '').split(','); return this.fields[this.editingField].enumValues.replaceAll('\'', '').split(',');
return false; return false;
} }

View File

@@ -32,6 +32,44 @@
</div> </div>
</div> </div>
</div> </div>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || page === 1"
title="CTRL+ᐊ"
@click="pageChange('prev')"
>
<i class="mdi mdi-24px mdi-skip-previous" />
</button>
<div class="dropdown" :class="{'active': isPageMenu}">
<div @click="openPageMenu">
<div class="btn btn-dark btn-sm mr-0 no-radius dropdown-toggle text-bold px-3">
{{ page }}
</div>
<div class="menu px-3">
<span>{{ $t('message.pageNumber') }}</span>
<input
ref="pageSelect"
v-model="pageProxy"
type="number"
min="1"
class="form-input"
@blur="setPageNumber"
>
</div>
</div>
</div>
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (results.length && results[0].rows.length < limit)"
title="CTRL+ᐅ"
@click="pageChange('next')"
>
<i class="mdi mdi-24px mdi-skip-next" />
</button>
</div>
<div class="divider-vert py-3" />
<button <button
v-if="isTable" v-if="isTable"
@@ -43,7 +81,7 @@
<i class="mdi mdi-24px mdi-playlist-plus ml-1" /> <i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button> </button>
<div class="dropdown export-dropdown pr-2"> <div class="dropdown table-dropdown pr-2">
<button <button
:disabled="isQuering" :disabled="isQuering"
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0" class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
@@ -72,10 +110,10 @@
<i class="mdi mdi-timer-sand mdi-rotate-180 pr-1" /> <b>{{ results[0].duration / 1000 }}s</b> <i class="mdi mdi-timer-sand mdi-rotate-180 pr-1" /> <b>{{ results[0].duration / 1000 }}s</b>
</div> </div>
<div v-if="results.length && results[0].rows"> <div v-if="results.length && results[0].rows">
{{ $t('word.results') }}: <b>{{ results[0].rows.length.toLocaleString() }}</b> {{ $t('word.results') }}: <b>{{ results[0].rows.length | localeString }}</b>
</div> </div>
<div v-if="hasApproximately"> <div v-if="hasApproximately || (page > 1 && tableInfo.rows)">
{{ $t('word.total') }}: <b>{{ tableInfo.rows.toLocaleString() }}</b> <small>({{ $t('word.approximately') }})</small> {{ $t('word.total') }}: <b>{{ tableInfo.rows | localeString }}</b> <small>({{ $t('word.approximately') }})</small>
</div> </div>
<div v-if="workspace.breadcrumbs.database"> <div v-if="workspace.breadcrumbs.database">
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b> {{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
@@ -134,6 +172,12 @@ export default {
ModalNewTableRow, ModalNewTableRow,
ModalFakerRows ModalFakerRows
}, },
filters: {
localeString (val) {
if (val)
return val.toLocaleString();
}
},
mixins: [tableTabs], mixins: [tableTabs],
props: { props: {
connection: Object, connection: Object,
@@ -143,19 +187,23 @@ export default {
return { return {
tabUid: 'data', tabUid: 'data',
isQuering: false, isQuering: false,
isPageMenu: false,
results: [], results: [],
lastTable: null, lastTable: null,
isAddModal: false, isAddModal: false,
isFakerModal: false, isFakerModal: false,
autorefreshTimer: 0, autorefreshTimer: 0,
refreshInterval: null, refreshInterval: null,
sortParams: {} sortParams: {},
page: 1,
pageProxy: 1
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
getWorkspace: 'workspaces/getWorkspace', getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected' selectedWorkspace: 'workspaces/getSelected',
limit: 'settings/getDataTabLimit'
}), }),
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
@@ -184,19 +232,26 @@ export default {
return this.results.length && return this.results.length &&
this.results[0].rows && this.results[0].rows &&
this.tableInfo && this.tableInfo &&
this.results[0].rows.length === 1000 && this.results[0].rows.length === this.limit &&
this.results[0].rows.length < this.tableInfo.rows; this.results[0].rows.length < this.tableInfo.rows;
} }
}, },
watch: { watch: {
table () { table () {
if (this.isSelected) { if (this.isSelected) {
this.page = 1;
this.sortParams = {}; this.sortParams = {};
this.getTableData(); this.getTableData();
this.lastTable = this.table; this.lastTable = this.table;
this.$refs.queryTable.resetSort(); this.$refs.queryTable.resetSort();
} }
}, },
page (val, oldVal) {
if (val && val > 0 && val !== oldVal) {
this.pageProxy = this.page;
this.getTableData();
}
},
isSelected (val) { isSelected (val) {
if (val && this.lastTable !== this.table) { if (val && this.lastTable !== this.table) {
this.getTableData(); this.getTableData();
@@ -228,6 +283,8 @@ export default {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.schema, schema: this.schema,
table: this.workspace.breadcrumbs.table || this.workspace.breadcrumbs.view, table: this.workspace.breadcrumbs.table || this.workspace.breadcrumbs.view,
limit: this.limit,
page: this.page,
sortParams sortParams
}; };
@@ -255,6 +312,33 @@ export default {
this.sortParams = sortParams; this.sortParams = sortParams;
this.getTableData(sortParams); this.getTableData(sortParams);
}, },
openPageMenu () {
if (this.isQuering) return;
this.isPageMenu = true;
if (this.isPageMenu)
setTimeout(() => this.$refs.pageSelect.focus(), 20);
},
setPageNumber () {
this.isPageMenu = false;
if (this.pageProxy > 0)
this.page = this.pageProxy;
else
this.pageProxy = this.page;
},
pageChange (direction) {
if (this.isQuering) return;
if (direction === 'next' && (this.results.length && this.results[0].rows.length === this.limit)) {
if (!this.page)
this.page = 2;
else
this.page++;
}
else if (direction === 'prev' && this.page > 1)
this.page--;
},
showAddModal () { showAddModal () {
this.isAddModal = true; this.isAddModal = true;
}, },
@@ -273,6 +357,13 @@ export default {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'F5') if (e.key === 'F5')
this.reloadTable(); this.reloadTable();
if (e.ctrlKey) {
if (e.key === 'ArrowRight')
this.pageChange('next');
if (e.key === 'ArrowLeft')
this.pageChange('prev');
}
} }
}, },
setRefreshInterval () { setRefreshInterval () {
@@ -292,10 +383,3 @@ export default {
} }
}; };
</script> </script>
<style lang="scss" scoped>
.export-dropdown {
.menu {
min-width: 100%;
}
}
</style>

379
src/renderer/i18n/de-DE.js Normal file
View File

@@ -0,0 +1,379 @@
module.exports = {
word: {
edit: 'Bearbeiten',
save: 'Speichern',
close: 'Schließen',
delete: 'Löschen',
confirm: 'Bestätigen',
cancel: 'Abbrechen',
send: 'Senden',
connectionName: 'Verbindungsname',
client: 'Client',
hostName: 'Hostname',
port: 'Port',
user: 'Nutzer',
password: 'Kennwort',
credentials: 'Zugangsdaten',
connect: 'Verbinden',
connected: 'Verbunden',
disconnect: 'Trennen',
disconnected: 'Getrennt',
refresh: 'Aktualisieren',
settings: 'Einstellungen',
general: 'Allgemein',
themes: 'Designs',
update: 'Aktualisierung',
about: 'Über',
language: 'Sprache',
version: 'Version',
donate: 'Spenden',
run: 'Ausführen',
schema: 'Schema',
results: 'Ergebnisse',
size: 'Größe',
seconds: 'Sekunden',
type: 'Typ',
mimeType: 'Mime-Type',
download: 'Herunterladen',
add: 'Hinzufügen',
data: 'Daten',
properties: 'Eigenschaften',
insert: 'Einfügen',
connecting: 'Verbinden',
name: 'Name',
collation: 'Kollation',
clear: 'Leeren',
options: 'Optionen',
autoRefresh: 'Auto-Aktualisierung',
indexes: 'Indizes',
foreignKeys: 'Fremdschlüssel',
length: 'Länge',
unsigned: 'Unsigniert',
default: 'Standard',
comment: 'Kommentar',
key: 'Schlüssel',
order: 'Sortierung',
expression: 'Ausdruck',
autoIncrement: 'Auto Inkrement',
engine: 'Engine',
field: 'Feld | Felder',
approximately: 'Ungefähr',
total: 'Gesamt',
table: 'Tabelle',
discard: 'Verwerfen',
stay: 'Warten',
author: 'Autor',
light: 'Hell',
dark: 'Dunkel',
autoCompletion: 'Auto-Vervollständigung',
application: 'Anwendung',
editor: 'Editor',
view: 'Ansicht',
definer: 'Bestimmer',
algorithm: 'Algorithmus',
trigger: 'Auslöser',
storedRoutine: 'Gespeicherte Routine | Gespeicherte Routinen',
scheduler: 'Zeitplaner',
event: 'Ereignis',
parameters: 'Parameter',
function: 'Funktion | Funktionen',
deterministic: 'Deterministisch',
context: 'Kontext',
export: 'Export',
returns: 'Rückgabe',
timing: 'Dauer',
state: 'Stand',
execution: 'Ausführung',
starts: 'Startet',
ends: 'Endet',
ssl: 'SSL',
privateKey: 'Privater Schlüssel',
certificate: 'Zertifikat',
caCertificate: 'CA Zertifikat',
ciphers: 'Chiffren',
upload: 'Hochladen',
browse: 'Suchen',
faker: 'Faker',
content: 'Inhalt',
cut: 'Ausschneiden',
copy: 'Kopieren',
paste: 'Einfügen',
tools: 'Tools',
variables: 'Variablen',
processes: 'Prozesse',
database: 'Datenbank',
scratchpad: 'Scratchpad',
array: 'Array',
format: 'Formatierung'
},
message: {
appWelcome: 'Willkommen im Antares SQL Client!',
appFirstStep: 'Dein erster Schritt: Erstelle eine neue Datenbankverbindung.',
addConnection: 'Verbindung hinzufügen',
createConnection: 'Verbindung erstellen',
createNewConnection: 'Neue Verbindung erstellen',
askCredentials: 'Frage nach Zugangsdaten',
testConnection: 'Verbindung testen',
editConnection: 'Verbindung bearbeiten',
deleteConnection: 'Verbindung löschen',
deleteCorfirm: 'Bestätige den Abbruch von',
connectionSuccessfullyMade: 'Verbindung erfolgreich erstellt!',
madeWithJS: 'Mit 💛 und JavaScript gemacht!',
checkForUpdates: 'Suche nach Aktualisierungen',
noUpdatesAvailable: 'Keine Aktualisierungen verfügbar',
checkingForUpdate: 'Suche nach Aktualisierungen',
checkFailure: 'Suche fehlgeschlagen, bitte versuche es später noch einmal',
updateAvailable: 'Aktualisierung verfügbar',
downloadingUpdate: 'Aktualisierung wird heruntergeladen',
updateDownloaded: 'Aktualisierung heruntergeladen',
restartToInstall: 'Starte Antares neu für die Installation',
unableEditFieldWithoutPrimary: 'Feld kann ohne Primärschlüssel in Ergebnisliste nicht bearbeitet werden',
editCell: 'Zelle bearbeiten',
deleteRows: 'Zeile löschen | Lösche {count} Zeilen',
confirmToDeleteRows: 'Eine Zeile wirklich löschen? | {count} Zeilen wirklich löschen?',
notificationsTimeout: 'Timeout für Benachrichtigungen',
uploadFile: 'Datei hochladen',
addNewRow: 'Neue Zeile hinzufügen',
numberOfInserts: 'Anzahl der eingefügten Zeilen',
openNewTab: 'Öffne einen neuen Tab',
affectedRows: 'Betroffene Zeilen',
createNewDatabase: 'Erstelle ein neue Datenbank',
databaseName: 'Datenbankname',
serverDefault: 'Server default',
deleteDatabase: 'Datenbank löschen',
editDatabase: 'Datenbank bearbeiten',
clearChanges: 'Änderungen leeren',
addNewField: 'Neues Feld hinzufügen',
manageIndexes: 'Indizes verwalten',
manageForeignKeys: 'Fremdschlüssel verwalten',
allowNull: 'Erlaube NULL',
zeroFill: 'Nullauffüllung',
customValue: 'Spezifischer Wert',
onUpdate: 'Bei Aktualisierung',
deleteField: 'Feld löschen',
createNewIndex: 'Neuen Index erstellen',
addToIndex: 'Zum Index hinzufügen',
createNewTable: 'Neue Tabelle erstellen',
emptyTable: 'Tabelle leeren',
deleteTable: 'Tabelle löschen',
emptyCorfirm: 'Wirklich leeren?',
unsavedChanges: 'Ungespeicherte Änderungen',
discardUnsavedChanges: 'Du hast ungespeicherte Änderungen. Wenn du den Tab verlässt, werden diese Änderungen verworfen.',
thereAreNoIndexes: 'Es gibt keine Indizes',
thereAreNoForeign: 'Es gibt keine Fremdschlüssel',
createNewForeign: 'Neuen Fremdschlüssel erstellen',
referenceTable: 'Referenztabelle',
referenceField: 'Referenzfeld',
foreignFields: 'Fremdfelder',
invalidDefault: 'Ungültiger Standard',
onDelete: 'Bei Löschung',
applicationTheme: 'Anwendungsdesign',
editorTheme: 'Editordesign',
wrapLongLines: 'Lange Zeilen umbrechen',
selectStatement: 'Select-Anweisung',
triggerStatement: 'Trigger-Anweisung',
sqlSecurity: 'SQL-Sicherheit',
updateOption: 'Aktualisierungsoption',
deleteView: 'View löschen',
createNewView: 'Neue View erstellen',
deleteTrigger: 'Trigger löschen',
createNewTrigger: 'Neuen Trigger erstellen',
currentUser: 'Aktueller Nutzer',
routineBody: 'Routineninhalt',
dataAccess: 'Datenzugriff',
thereAreNoParameters: 'Es gibt keine Parameter',
createNewParameter: 'Neue Parameter erstellen',
createNewRoutine: 'Neue Routine erstellen',
deleteRoutine: 'Routine löschen',
functionBody: 'Funktionsinhalt',
createNewFunction: 'Neue Funktion erstellen',
deleteFunction: 'Funktion löschen',
schedulerBody: 'Zeitplaner-Inhalt',
createNewScheduler: 'Neuen Zeitplaner erstellen',
deleteScheduler: 'Zeitplaner löschen',
preserveOnCompletion: 'Bei Vervollständigung erhalten',
enableSsl: 'Aktiviere SSL',
manualValue: 'Manueller Wert',
tableFiller: 'Tabellenfüller',
fakeDataLanguage: 'Fingierte Datensprache',
searchForElements: 'Suche nach Elemente',
selectAll: 'Alle auswählen',
queryDuration: 'Dauer der Abfrage',
includeBetaUpdates: 'Beta-Aktualisierungen berücksichtigen',
setNull: 'Setze NULL',
processesList: 'Prozessliste',
processInfo: 'Prozessinformationen',
manageUsers: 'Benutzer verwalten',
createNewSchema: 'Neues Schema erstellen',
schemaName: 'Schemaname',
editSchema: 'Schema bearbeiten',
deleteSchema: 'Schema löschen',
markdownSupported: 'Unterstützt Markdown',
plantATree: 'Pflanze einen Baum',
dataTabPageSize: 'Einträge pro Tab / Seite'
},
faker: {
address: 'Adresse',
commerce: 'Handel',
company: 'Firma',
database: 'Datenbank',
date: 'Datum',
finance: 'Finanzen',
git: 'Git',
hacker: 'Hacker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Name',
music: 'Musik',
phone: 'Telefon',
random: 'Zufällig',
system: 'System',
time: 'Zeit',
vehicle: 'Fahrzeug',
zipCode: 'Postleitzahl',
zipCodeByState: 'Postleitzahl nach Stadt',
city: 'Stadt',
cityPrefix: 'Stadtpräfix',
citySuffix: 'Stadtzusatz',
streetName: 'Straßenname',
streetAddress: 'Anschrift',
streetSuffix: 'Straßenzusatz',
streetPrefix: 'Straßenpräfix',
secondaryAddress: 'Zweite Anschrift',
county: 'Landkreis',
country: 'Land',
countryCode: 'Ländercode',
state: 'Bundesland',
stateAbbr: 'Bundeslandkürzel',
latitude: 'Breitengrad',
longitude: 'Längengrad',
direction: 'Richtung',
cardinalDirection: 'Himmelsrichtung',
ordinalDirection: 'Nebenhimmelsrichtung',
nearbyGPSCoordinate: 'Nächste GPS-Koordinate',
timeZone: 'Zeitzone',
color: 'Farbe',
department: 'Abteilung',
productName: 'Produktname',
price: 'Preis',
productAdjective: 'Produkteigenschaft',
productMaterial: 'Produktmaterial',
product: 'Produkt',
productDescription: 'Produktbeschreibung',
suffixes: 'Zusätze',
companyName: 'Firmenname',
companySuffix: 'Firmenzusatz',
catchPhrase: 'Slogan',
bs: 'BS',
catchPhraseAdjective: 'Sloganeigenschaft',
catchPhraseDescriptor: 'Sloganbeschreibung',
catchPhraseNoun: 'Slogannomen',
bsAdjective: 'BS-Eigenschaft',
bsBuzz: 'BS-Klatsch',
bsNoun: 'BS-Nomen',
column: 'Spalte',
type: 'Typ',
collation: 'Kollation',
engine: 'Engine',
past: 'Vergangenheit',
future: 'Zukunft',
between: 'Zwischen',
recent: 'Kürzlich',
soon: 'Bald',
month: 'Monat',
weekday: 'Wochentag',
account: 'Konto',
accountName: 'Kontoname',
routingNumber: 'Bankleitzahl',
mask: 'Maske',
amount: 'Wert',
transactionType: 'Vorgangstyp',
currencyCode: 'Währungscode',
currencyName: 'Währungsname',
currencySymbol: 'Währungssymbol',
bitcoinAddress: 'Bitcoin-Adresse',
litecoinAddress: 'Litecoin-Adresse',
creditCardNumber: 'Kreditkartennummer',
creditCardCVV: 'Kartenprüfnummer',
ethereumAddress: 'Ethereum-Adresse',
iban: 'IBAN',
bic: 'BIC',
transactionDescription: 'Vorgangsbeschreibung',
branch: 'Zweig',
commitEntry: 'Commit-Eintrag',
commitMessage: 'Commit-Nachricht',
commitSha: 'Commit-SHA',
shortSha: 'Kurzer SHA',
abbreviation: 'Kürzel',
adjective: 'Adjektiv',
noun: 'Nomen',
verb: 'Verb',
ingverb: 'Ingverb',
phrase: 'Phrase',
avatar: 'Avatar',
email: 'E-Mail',
exampleEmail: 'Beispiel-E-Mail',
userName: 'Benutzername',
protocol: 'Protokoll',
url: 'Url',
domainName: 'Domainname',
domainSuffix: 'Domainzusatz',
domainWord: 'Domainwort',
ip: 'IP',
ipv6: 'IPv6',
userAgent: 'Browserkennung',
mac: 'Mac',
password: 'Kennwort',
word: 'Wort',
words: 'Wörter',
sentence: 'Satz',
slug: 'Slug',
sentences: 'Sätze',
paragraph: 'Paragraph',
paragraphs: 'Paragraphen',
text: 'Text',
lines: 'Zeilen',
genre: 'Genre',
firstName: 'Vorname',
lastName: 'Nachname',
middleName: 'Zweitname',
findName: 'Vollständiger Name',
jobTitle: 'Berufsbezeichnung',
gender: 'Geschlecht',
prefix: 'Präfix',
suffix: 'Zusatz',
title: 'Titel',
jobDescriptor: 'Berufsbeschreibung',
jobArea: 'Berufsfeld',
jobType: 'Anstellungsart',
phoneNumber: 'Telefonnummer',
phoneNumberFormat: 'Telefonnummerformat',
phoneFormats: 'Telefonnummerformate',
number: 'Nummer',
float: 'Gleitkommazahl',
arrayElement: 'Array-Element',
arrayElements: 'Array-Elemente',
objectElement: 'Object-Element',
uuid: 'Uuid',
boolean: 'Boolean',
image: 'Grafik',
locale: 'Sprachumgebung',
alpha: 'Alpha',
alphaNumeric: 'Alphanumerisch',
hexaDecimal: 'Hexadezimal',
fileName: 'Dateiname',
commonFileName: 'Allgemeiner Dateiname',
mimeType: 'Mimetype',
commonFileType: 'Allgemeiner Dateityp',
commonFileExt: 'Allgemeine Dateiendung',
fileType: 'Dateityp',
fileExt: 'Dateiendung',
directoryPath: 'Verzeichnispfad',
filePath: 'Dateipfad',
semver: 'Semver',
manufacturer: 'Hersteller',
model: 'Modell',
fuel: 'Treibstoff',
vin: 'Wein'
}
};

View File

@@ -105,7 +105,14 @@ module.exports = {
scratchpad: 'Scratchpad', scratchpad: 'Scratchpad',
array: 'Array', array: 'Array',
changelog: 'Changelog', changelog: 'Changelog',
format: 'Format' format: 'Format',
structure: 'Structure',
small: 'Small',
medium: 'Medium',
large: 'Large',
row: 'Row | Rows',
cell: 'Cell | Cells',
triggerFunction: 'Trigger function | Trigger functions'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@@ -210,7 +217,10 @@ module.exports = {
editSchema: 'Edit schema', editSchema: 'Edit schema',
deleteSchema: 'Delete schema', deleteSchema: 'Delete schema',
markdownSupported: 'Markdown supported', markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree' plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size',
pageNumber: 'Page number',
duplicateTable: 'Duplicate table'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@@ -10,7 +10,8 @@ const i18n = new VueI18n({
'ar-SA': require('./ar-SA'), 'ar-SA': require('./ar-SA'),
'es-ES': require('./es-ES'), 'es-ES': require('./es-ES'),
'fr-FR': require('./fr-FR'), 'fr-FR': require('./fr-FR'),
'pt-BR': require('./pt-BR') 'pt-BR': require('./pt-BR'),
'de-DE': require('./de-DE')
} }
}); });
export default i18n; export default i18n;

View File

@@ -4,5 +4,6 @@ export default {
'ar-SA': 'العربية', 'ar-SA': 'العربية',
'es-ES': 'Español', 'es-ES': 'Español',
'fr-FR': 'Français', 'fr-FR': 'Français',
'pt-BR': 'Português (Brasil)' 'pt-BR': 'Português (Brasil)',
'de-DE': 'Deutsch (Deutschland)'
}; };

View File

@@ -14,7 +14,15 @@ export default class {
return ipcRenderer.invoke('alter-function', params); return ipcRenderer.invoke('alter-function', params);
} }
static alterTriggerFunction (params) {
return ipcRenderer.invoke('alter-trigger-function', params);
}
static createFunction (params) { static createFunction (params) {
return ipcRenderer.invoke('create-function', params); return ipcRenderer.invoke('create-function', params);
} }
static createTriggerFunction (params) {
return ipcRenderer.invoke('create-trigger-function', params);
}
} }

View File

@@ -19,7 +19,6 @@ export default class {
} }
static updateTableCell (params) { static updateTableCell (params) {
delete params.row._id;
return ipcRenderer.invoke('update-table-cell', params); return ipcRenderer.invoke('update-table-cell', params);
} }
@@ -47,6 +46,10 @@ export default class {
return ipcRenderer.invoke('alter-table', params); return ipcRenderer.invoke('alter-table', params);
} }
static duplicateTable (params) {
return ipcRenderer.invoke('duplicate-table', params);
}
static truncateTable (params) { static truncateTable (params) {
return ipcRenderer.invoke('truncate-table', params); return ipcRenderer.invoke('truncate-table', params);
} }

View File

@@ -22,6 +22,7 @@ $enum-color: goldenrod;
$unknown-color: gray; $unknown-color: gray;
/* Sizes */ /* Sizes */
$border-radius: 0.3rem;
$titlebar-height: 1.5rem; $titlebar-height: 1.5rem;
$settingbar-width: 3rem; $settingbar-width: 3rem;
$explorebar-width: 14rem; $explorebar-width: 14rem;

View File

@@ -34,12 +34,22 @@ body {
outline: none !important; outline: none !important;
} }
.no-radius {
border-radius: 0 !important;
}
.no-border { .no-border {
outline: none !important; outline: none !important;
border: none !important; border: none !important;
box-shadow: none !important; box-shadow: none !important;
} }
.cut-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.workspace-tabs { .workspace-tabs {
align-content: baseline; align-content: baseline;
@@ -103,7 +113,7 @@ body {
.modal-container, .modal-container,
.modal-sm .modal-container { .modal-sm .modal-container {
padding: 0; padding: 0;
border-radius: 3px; border-radius: $border-radius;
.modal-header { .modal-header {
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
@@ -111,13 +121,46 @@ body {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-radius: 3px 3px 0 0; border-radius: $border-radius $border-radius 0 0;
.modal-title {
overflow: hidden;
}
} }
} }
} }
.tab { .tab {
.tab-item { .tab-item {
position: relative;
display: flex;
justify-content: center;
.tab-link {
min-width: 0;
transition: color 0.2s;
}
&.active {
.tab-link {
border-color: transparent;
}
&::after {
width: 100%;
}
}
&::after {
content: "";
height: 2px;
width: 0;
transition: width 0.2s;
background-color: $primary-color;
position: absolute;
bottom: 0;
}
.btn-clear { .btn-clear {
margin-top: -0.1rem; margin-top: -0.1rem;
font-size: 0.6rem; font-size: 0.6rem;
@@ -185,9 +228,47 @@ body {
max-height: 5000rem !important; max-height: 5000rem !important;
} }
.btn-group {
flex-wrap: nowrap;
}
.btn.loading { .btn.loading {
> .mdi, > .mdi,
> span { > span {
visibility: hidden; visibility: hidden;
} }
} }
.table-dropdown {
.menu {
min-width: 100%;
padding: 0;
.menu-item {
padding: 0;
> a {
margin: 0.2rem;
padding: 0.1rem 0.3rem;
&:hover {
color: inherit;
}
}
}
}
}
// Ace Editor
.ace_editor {
&.ace_autocomplete {
border-radius: $border-radius;
.ace_marker-layer {
.ace_active-line,
.ace_line-hover {
border-radius: $border-radius;
}
}
}
}

View File

@@ -58,6 +58,10 @@
&:hover { &:hover {
background: $bg-color-gray; background: $bg-color-gray;
} }
&.active {
background-color: $primary-color;
}
} }
} }
@@ -211,10 +215,8 @@
} }
.bg-checkered { .bg-checkered {
background-image: background-image: linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)), linear-gradient(to right, black 50%, white 50%), linear-gradient(to bottom, black 50%, white 50%);
linear-gradient(to right, black 50%, white 50%),
linear-gradient(to bottom, black 50%, white 50%);
background-blend-mode: normal, difference, normal; background-blend-mode: normal, difference, normal;
background-size: 2em 2em; background-size: 2em 2em;
} }
@@ -233,7 +235,7 @@
} }
&:hover { &:hover {
background: $primary-color; background: rgba($light-color, 15%);
} }
} }
} }
@@ -264,7 +266,7 @@
} }
.editor-col { .editor-col {
border-left: 2px solid $bg-color-light-dark; border-left: 0.05rem solid rgba($bg-color-light-dark, 60%);
} }
.table { .table {
@@ -361,26 +363,6 @@
margin: 0; margin: 0;
.settingbar-element { .settingbar-element {
height: $settingbar-width;
width: 100%;
margin: 0;
border-left: 3px solid transparent;
opacity: 0.5;
transition: opacity 0.2s;
display: flex;
align-content: center;
justify-content: center;
flex-direction: column;
&:hover {
opacity: 1;
}
&.selected {
border-left-color: $body-font-color-dark;
opacity: 1;
}
.settingbar-element-icon { .settingbar-element-icon {
&.badge::after { &.badge::after {
bottom: -10px; bottom: -10px;

View File

@@ -62,6 +62,10 @@
&:hover { &:hover {
background: $bg-color-gray; background: $bg-color-gray;
} }
&.active {
background-color: $primary-color;
}
} }
} }
@@ -94,7 +98,7 @@
} }
.editor-col { .editor-col {
border-left: 2px solid darken($bg-color-light-gray, 15%); border-left: 0.05rem solid darken($bg-color-light-gray, 15%);
} }
.file-uploader { .file-uploader {
@@ -143,26 +147,6 @@
margin: 0; margin: 0;
.settingbar-element { .settingbar-element {
height: $settingbar-width;
width: 100%;
margin: 0;
border-left: 3px solid transparent;
opacity: 0.5;
transition: opacity 0.2s;
display: flex;
align-content: center;
justify-content: center;
flex-direction: column;
&:hover {
opacity: 1;
}
&.selected {
border-left-color: $body-font-color-dark;
opacity: 1;
}
.settingbar-element-icon { .settingbar-element-icon {
&.badge::after { &.badge::after {
bottom: -10px; bottom: -10px;
@@ -235,7 +219,7 @@
} }
&:hover { &:hover {
background: $primary-color; background: rgba($light-color, 15%);
} }
} }
} }

View File

@@ -11,20 +11,24 @@ export default {
allow_prerelease: persistentStore.get('allow_prerelease', true), allow_prerelease: persistentStore.get('allow_prerelease', true),
explorebar_size: persistentStore.get('explorebar_size', null), explorebar_size: persistentStore.get('explorebar_size', null),
notifications_timeout: persistentStore.get('notifications_timeout', 5), notifications_timeout: persistentStore.get('notifications_timeout', 5),
data_tab_limit: persistentStore.get('data_tab_limit', 1000),
auto_complete: persistentStore.get('auto_complete', true), auto_complete: persistentStore.get('auto_complete', true),
line_wrap: persistentStore.get('line_wrap', true), line_wrap: persistentStore.get('line_wrap', true),
application_theme: persistentStore.get('application_theme', 'dark'), application_theme: persistentStore.get('application_theme', 'dark'),
editor_theme: persistentStore.get('editor_theme', 'twilight') editor_theme: persistentStore.get('editor_theme', 'twilight'),
editor_font_size: persistentStore.get('editor_font_size', 'medium')
}, },
getters: { getters: {
getLocale: state => state.locale, getLocale: state => state.locale,
getDataTabLimit: state => state.data_tab_limit,
getAllowPrerelease: state => state.allow_prerelease, getAllowPrerelease: state => state.allow_prerelease,
getExplorebarSize: state => state.explorebar_size, getExplorebarSize: state => state.explorebar_size,
getNotificationsTimeout: state => state.notifications_timeout, getNotificationsTimeout: state => state.notifications_timeout,
getAutoComplete: state => state.auto_complete, getAutoComplete: state => state.auto_complete,
getLineWrap: state => state.line_wrap, getLineWrap: state => state.line_wrap,
getApplicationTheme: state => state.application_theme, getApplicationTheme: state => state.application_theme,
getEditorTheme: state => state.editor_theme getEditorTheme: state => state.editor_theme,
getEditorFontSize: state => state.editor_font_size
}, },
mutations: { mutations: {
SET_LOCALE (state, locale) { SET_LOCALE (state, locale) {
@@ -32,6 +36,10 @@ export default {
i18n.locale = locale; i18n.locale = locale;
persistentStore.set('locale', state.locale); persistentStore.set('locale', state.locale);
}, },
SET_DATA_TAB_LIMIT (state, limit) {
state.data_tab_limit = limit;
persistentStore.set('data_tab_limit', state.data_tab_limit);
},
SET_ALLOW_PRERELEASE (state, allow) { SET_ALLOW_PRERELEASE (state, allow) {
state.allow_prerelease = allow; state.allow_prerelease = allow;
persistentStore.set('allow_prerelease', state.allow_prerelease); persistentStore.set('allow_prerelease', state.allow_prerelease);
@@ -59,12 +67,19 @@ export default {
SET_EDITOR_THEME (state, theme) { SET_EDITOR_THEME (state, theme) {
state.editor_theme = theme; state.editor_theme = theme;
persistentStore.set('editor_theme', state.editor_theme); persistentStore.set('editor_theme', state.editor_theme);
},
SET_EDITOR_FONT_SIZE (state, size) {
state.editor_font_size = size;
persistentStore.set('editor_font_size', state.editor_font_size);
} }
}, },
actions: { actions: {
changeLocale ({ commit }, locale) { changeLocale ({ commit }, locale) {
commit('SET_LOCALE', locale); commit('SET_LOCALE', locale);
}, },
changePageSize ({ commit }, limit) {
commit('SET_DATA_TAB_LIMIT', limit);
},
changeAllowPrerelease ({ commit }, allow) { changeAllowPrerelease ({ commit }, allow) {
commit('SET_ALLOW_PRERELEASE', allow); commit('SET_ALLOW_PRERELEASE', allow);
}, },
@@ -85,6 +100,9 @@ export default {
}, },
changeEditorTheme ({ commit }, theme) { changeEditorTheme ({ commit }, theme) {
commit('SET_EDITOR_THEME', theme); commit('SET_EDITOR_THEME', theme);
},
changeEditorFontSize ({ commit }, size) {
commit('SET_EDITOR_FONT_SIZE', size);
} }
} }
}; };