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

Compare commits

...

59 Commits

Author SHA1 Message Date
d12c6f5210 chore(release): 0.5.7 2022-06-19 16:11:12 +02:00
ee623b0a0f fix: unable to add new table fields 2022-06-14 11:48:36 +02:00
c37138c6f5 ci: fix linux upload artifacts github action [skip ci] 2022-06-12 19:58:22 +02:00
e754877ee6 Merge branch 'master' of https://github.com/antares-sql/antares 2022-06-12 18:30:48 +02:00
ed38a9e7ff ci: linux upload artifacts github action 2022-06-12 18:30:44 +02:00
6bad032f0d fix(Linux): setting bar tooltip position 2022-06-11 14:26:51 +02:00
d214c1f35b fix: reload tab content on tab sort 2022-06-11 01:11:08 +02:00
77d9cac092 fix: fields sorting in table setting tabs 2022-06-10 23:29:34 +02:00
cba2ce2e37 fix: selected foreign key value not visible in the insert row modal 2022-06-10 20:02:26 +02:00
5b33419b64 fix: exception on app start setting window title 2022-06-08 14:55:29 +02:00
00242697a1 feat: dynamic app window title 2022-06-08 13:24:14 +02:00
85cec05f70 perf(Linux): title bar improvements 2022-06-08 13:04:19 +02:00
5fa8bf38e4 perf(Windows): title bar improvements 2022-06-07 18:32:37 +02:00
23acf00def fix: main process not closed after window close on some conditions 2022-06-07 09:33:03 +02:00
1c666a07d8 Merge pull request #289 from toriphes/feat/hotkeys
Feat: Hokeys to navigate between tabs and result sets
2022-06-04 15:20:08 +02:00
Giulio Ganci
49abd1ea7f feat: hotkeys to navigate inside a table resultset 2022-06-04 08:45:48 +02:00
Giulio Ganci
d3b9e08446 feat: hotkeys to navigate forward or backward between tabs 2022-06-03 18:56:19 +02:00
20b814378b chore: package.json changes 2022-06-02 12:50:05 +02:00
8ce1d1a964 chore(release): 0.5.6 2022-06-02 11:31:10 +02:00
d151c7254e build: Windows icon improvements 2022-06-02 11:17:53 +02:00
26aad519df perf: improved precision of MariaDB or MySQL auto detection 2022-06-02 09:50:16 +02:00
31b7999bba fix: empty query tab schema select if no schema selected 2022-06-02 09:46:49 +02:00
caf776bd55 fix: inline field update not working with tables missing primary key 2022-06-01 18:31:25 +02:00
a7d5e1973c fix(SQLite): unable to insert rows with TEXT fields 2022-06-01 09:56:45 +02:00
8870304c15 fix(UI): select closes clicking on scrollbar 2022-05-29 16:42:41 +02:00
34e8d3e5b1 chore(release): 0.5.5 2022-05-24 14:25:04 +02:00
6c8a36e947 refactor(UI): double click to edit field type or collation 2022-05-23 16:35:50 +02:00
99b1c1be12 Merge pull request #245 from toriphes/feat/advanced-dropdown-list
feat/advanced-dropdown-list
2022-05-23 16:31:19 +02:00
3991382153 Merge branch 'feat/advanced-dropdown-list' of https://github.com/toriphes/antares into pr/toriphes/245 2022-05-23 16:19:35 +02:00
allcontributors[bot]
3b57b7ef3b docs: update .all-contributorsrc [skip ci] 2022-05-23 16:13:51 +02:00
allcontributors[bot]
45d599ad7f docs: update README.md [skip ci] 2022-05-23 16:13:51 +02:00
9082960310 feat(translation): russian translation, closes #266 2022-05-23 16:11:54 +02:00
Giulio Ganci
5398964190 feat: added dropdown animation 2022-05-22 15:45:16 +02:00
5d5f1da97b perf(UI): max height for query text area increased 2022-05-19 09:30:43 +02:00
c95c593c74 chore: create CODE_OF_CONDUCT.md 2022-05-15 19:08:06 +02:00
c5baf2b0d3 fix: query tab content disappears reordering or closing other tabs, closes #261 2022-05-15 18:15:05 +02:00
a082514f88 fix(PostgreSQL): idle timeout disabled 2022-05-15 17:37:54 +02:00
c826888b0d fix: SSH tunnel connection error with private key, closes #260 2022-05-14 11:24:24 +02:00
b0d464952f Merge branch 'master' of https://github.com/antares-sql/antares into pr/toriphes/245 2022-05-14 09:57:09 +02:00
Giulio Ganci
7c45203636 fix(UI): BaseSelect keyboard navigation 2022-05-13 18:35:02 +02:00
Giulio Ganci
71b0736d0d fix(UI): BaseSelect style 2022-05-13 18:20:47 +02:00
Giulio Ganci
42bc9196ff feat(UI): select tab replace with BaseSelect component 2022-05-11 23:30:31 +02:00
Giulio Ganci
f7e04d6333 feat(UI): BaseSelect supports disabled options 2022-05-11 22:57:13 +02:00
9ee1b3023d build: electron-updater downgrade [skip ci] 2022-05-10 19:16:06 +02:00
79d9acb471 chore(release): 0.5.4 2022-05-10 18:13:30 +02:00
e02565c0d9 perf(UI): left alignment for numbers in result tables, closes #249 2022-05-10 15:19:47 +02:00
ff272440bd fix: unable to insert auto-generated datetime fields 2022-05-10 15:14:34 +02:00
e62f280528 fix: app blocked by BIT fields with no default, closes #256 2022-05-10 15:13:08 +02:00
6d6151814e fix: SSH tunnel not working 2022-05-10 14:44:45 +02:00
58611bf07f fix: file upload input not working 2022-05-10 14:43:08 +02:00
Giulio Ganci
2b436d8613 feat(UI): BaseSelect disabled state 2022-05-10 10:29:38 +02:00
Giulio Ganci
1869e6a148 feat(UI): BaseSelect supports option groups 2022-05-09 17:31:58 +02:00
Giulio Ganci
302c66457d feat(UI): ForeignKeySelect implements BaseSelect component 2022-05-09 11:29:25 +02:00
Giulio Ganci
0043d07708 feat(UI): BaseSelect option list scrolls automatically using up/down keys 2022-05-08 18:59:00 +02:00
Ngô Quốc Đạt
e0f85f469f Added missing translations for vi-VN 2022-05-08 16:29:48 +02:00
Giulio Ganci
a037d0cc01 feat(UI): BaseSelect in table filters 2022-05-08 13:15:39 +02:00
Giulio Ganci
5582a12bbf feat(UI): BaseSelect small variant 2022-05-08 13:14:40 +02:00
Giulio Ganci
22622df2cf feat(UI): initial BaseSelect integration 2022-05-08 09:45:37 +02:00
Giulio Ganci
745d551cc9 feat(UI): new BaseSelect component 2022-05-08 09:44:52 +02:00
71 changed files with 2383 additions and 1092 deletions

View File

@@ -165,6 +165,15 @@
"contributions": [
"translation"
]
},
{
"login": "xak666",
"name": "xaka_xak",
"avatar_url": "https://avatars.githubusercontent.com/u/38811437?v=4",
"profile": "https://github.com/xak666",
"contributions": [
"translation"
]
}
],
"contributorsPerLine": 7,

View File

@@ -1,4 +1,4 @@
name: Build/release [linux]
name: Build/release [LINUX]
on: push

View File

@@ -1,4 +1,4 @@
name: Build/release [mac]
name: Build/release [MAC]
on: push

View File

@@ -1,4 +1,4 @@
name: Build/release [windows]
name: Build/release [WINDOWS]
on: push

View File

@@ -0,0 +1,26 @@
name: Create artifact [LINUX]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: npm install & build
run: |
npm install
npm run build:local
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: linux-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -1,4 +1,4 @@
name: Test end-to-end [linux]
name: Test end-to-end [LINUX]
on: push

View File

@@ -5,7 +5,9 @@
"MySQL",
"PostgreSQL",
"SQLite",
"Windows"
"Windows",
"translation",
"Linux"
],
"svg.preview.background": "transparent"
}

View File

@@ -2,6 +2,116 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.5.7](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.7) (2022-06-19)
### Features
* added dropdown animation ([5398964](https://github.com/antares-sql/antares/commit/539896419064db9127f6a72acdbb11af2c4aa60a))
* dynamic app window title ([0024269](https://github.com/antares-sql/antares/commit/00242697a102f82dd0c731a3529c984fbdf83b3e))
* hotkeys to navigate forward or backward between tabs ([d3b9e08](https://github.com/antares-sql/antares/commit/d3b9e08446708654b3c6fad565b734d93effe683))
* hotkeys to navigate inside a table resultset ([49abd1e](https://github.com/antares-sql/antares/commit/49abd1ea7f5ec368e9a9201f8fd5b6520c4bd0a8))
* **translation:** russian translation, closes [#266](https://github.com/antares-sql/antares/issues/266) ([9082960](https://github.com/antares-sql/antares/commit/9082960310573a6e4d14bfbe82ed2eb1489f308d))
* **UI:** BaseSelect disabled state ([2b436d8](https://github.com/antares-sql/antares/commit/2b436d8613a1e3dff55d73adbddf5d2cd2452f27))
* **UI:** BaseSelect in table filters ([a037d0c](https://github.com/antares-sql/antares/commit/a037d0cc0148444e8e6c5b87c79f6ba9c2a6f0fe))
* **UI:** BaseSelect option list scrolls automatically using up/down keys ([0043d07](https://github.com/antares-sql/antares/commit/0043d077081fc49724722a5d5a74986d990c539d))
* **UI:** BaseSelect small variant ([5582a12](https://github.com/antares-sql/antares/commit/5582a12bbfade75dbcc7f9d71ada7190ed08d3c2))
* **UI:** BaseSelect supports disabled options ([f7e04d6](https://github.com/antares-sql/antares/commit/f7e04d633340a53420ce1c434e906c9434620e6e))
* **UI:** BaseSelect supports option groups ([1869e6a](https://github.com/antares-sql/antares/commit/1869e6a1482daf9381d9ac2244bf0aeffa758edc))
* **UI:** ForeignKeySelect implements BaseSelect component ([302c664](https://github.com/antares-sql/antares/commit/302c66457deeb50facf4735291640fcf48b78f66))
* **UI:** initial BaseSelect integration ([22622df](https://github.com/antares-sql/antares/commit/22622df2cfcb71054c6f6110b7ad9d4f635553dc))
* **UI:** new BaseSelect component ([745d551](https://github.com/antares-sql/antares/commit/745d551cc9253eae4e39e5d3406ceee051a7d6c1))
* **UI:** select tab replace with BaseSelect component ([42bc919](https://github.com/antares-sql/antares/commit/42bc9196ffc2f64b77f9cb42136255fc74815034))
### Bug Fixes
* empty query tab schema select if no schema selected ([31b7999](https://github.com/antares-sql/antares/commit/31b7999bba5d115913d42087614b9888bc761068))
* exception on app start setting window title ([5b33419](https://github.com/antares-sql/antares/commit/5b33419b6421d7d198a978e79e22d0a76306cdb4))
* fields sorting in table setting tabs ([77d9cac](https://github.com/antares-sql/antares/commit/77d9cac092fbb806810c3463ca066395fcab5307))
* inline field update not working with tables missing primary key ([caf776b](https://github.com/antares-sql/antares/commit/caf776bd55606c793c9763c204aa9f05d1feb27f))
* **Linux:** setting bar tooltip position ([6bad032](https://github.com/antares-sql/antares/commit/6bad032f0d1094736f651b6c06a60d2a0df36c98))
* main process not closed after window close on some conditions ([23acf00](https://github.com/antares-sql/antares/commit/23acf00def77b5662e48b84591a31760737774a7))
* **PostgreSQL:** idle timeout disabled ([a082514](https://github.com/antares-sql/antares/commit/a082514f88040c7e0ffdf4e8357bab45370a4c39))
* query tab content disappears reordering or closing other tabs, closes [#261](https://github.com/antares-sql/antares/issues/261) ([c5baf2b](https://github.com/antares-sql/antares/commit/c5baf2b0d379fdd28ee8cb907628bbfca940e2f6))
* reload tab content on tab sort ([d214c1f](https://github.com/antares-sql/antares/commit/d214c1f35ba231a8a01dbe8c0faad07d4b337752))
* selected foreign key value not visible in the insert row modal ([cba2ce2](https://github.com/antares-sql/antares/commit/cba2ce2e37cedbf0b242cc474b37bf052009ae62))
* **SQLite:** unable to insert rows with TEXT fields ([a7d5e19](https://github.com/antares-sql/antares/commit/a7d5e1973cd59d7d0ef1e74bdcf44d87fba43559))
* SSH tunnel connection error with private key, closes [#260](https://github.com/antares-sql/antares/issues/260) ([c826888](https://github.com/antares-sql/antares/commit/c826888b0dd0908958a4f727ddfa642e846269cf))
* **UI:** BaseSelect keyboard navigation ([7c45203](https://github.com/antares-sql/antares/commit/7c452036368fa0db6b9cde7c35e60a8e57bfece7))
* **UI:** BaseSelect style ([71b0736](https://github.com/antares-sql/antares/commit/71b0736d0ddbd599ab41cde0a6b0823e2bb7da2f))
* **UI:** select closes clicking on scrollbar ([8870304](https://github.com/antares-sql/antares/commit/8870304c15346257a90193807b9ae07c1393e3e2))
* unable to add new table fields ([ee623b0](https://github.com/antares-sql/antares/commit/ee623b0a0f121df0ac53d49d8be437c76ddb8539))
### Improvements
* improved precision of MariaDB or MySQL auto detection ([26aad51](https://github.com/antares-sql/antares/commit/26aad519df6ea1bbc7dffbf540193a7b2ed9ae2a))
* **Linux:** title bar improvements ([85cec05](https://github.com/antares-sql/antares/commit/85cec05f7037a1339ee223554cf127693a527aa1))
* **UI:** max height for query text area increased ([5d5f1da](https://github.com/antares-sql/antares/commit/5d5f1da97b9adfa743197d8fa0bbb6addd565a7a))
* **Windows:** title bar improvements ([5fa8bf3](https://github.com/antares-sql/antares/commit/5fa8bf38e433ef2fb31bcb893cd9e75549bd6a49))
### [0.5.6](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.6) (2022-06-02)
### Bug Fixes
* empty query tab schema select if no schema selected ([31b7999](https://github.com/antares-sql/antares/commit/31b7999bba5d115913d42087614b9888bc761068))
* inline field update not working with tables missing primary key ([caf776b](https://github.com/antares-sql/antares/commit/caf776bd55606c793c9763c204aa9f05d1feb27f))
* **SQLite:** unable to insert rows with TEXT fields ([a7d5e19](https://github.com/antares-sql/antares/commit/a7d5e1973cd59d7d0ef1e74bdcf44d87fba43559))
* **UI:** select closes clicking on scrollbar ([8870304](https://github.com/antares-sql/antares/commit/8870304c15346257a90193807b9ae07c1393e3e2))
### Improvements
* improved precision of MariaDB or MySQL auto detection ([26aad51](https://github.com/antares-sql/antares/commit/26aad519df6ea1bbc7dffbf540193a7b2ed9ae2a))
### [0.5.5](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.5) (2022-05-24)
### Features
* added dropdown animation ([5398964](https://github.com/antares-sql/antares/commit/539896419064db9127f6a72acdbb11af2c4aa60a))
* **translation:** russian translation, closes [#266](https://github.com/antares-sql/antares/issues/266) ([9082960](https://github.com/antares-sql/antares/commit/9082960310573a6e4d14bfbe82ed2eb1489f308d))
* **UI:** BaseSelect disabled state ([2b436d8](https://github.com/antares-sql/antares/commit/2b436d8613a1e3dff55d73adbddf5d2cd2452f27))
* **UI:** BaseSelect in table filters ([a037d0c](https://github.com/antares-sql/antares/commit/a037d0cc0148444e8e6c5b87c79f6ba9c2a6f0fe))
* **UI:** BaseSelect option list scrolls automatically using up/down keys ([0043d07](https://github.com/antares-sql/antares/commit/0043d077081fc49724722a5d5a74986d990c539d))
* **UI:** BaseSelect small variant ([5582a12](https://github.com/antares-sql/antares/commit/5582a12bbfade75dbcc7f9d71ada7190ed08d3c2))
* **UI:** BaseSelect supports disabled options ([f7e04d6](https://github.com/antares-sql/antares/commit/f7e04d633340a53420ce1c434e906c9434620e6e))
* **UI:** BaseSelect supports option groups ([1869e6a](https://github.com/antares-sql/antares/commit/1869e6a1482daf9381d9ac2244bf0aeffa758edc))
* **UI:** ForeignKeySelect implements BaseSelect component ([302c664](https://github.com/antares-sql/antares/commit/302c66457deeb50facf4735291640fcf48b78f66))
* **UI:** initial BaseSelect integration ([22622df](https://github.com/antares-sql/antares/commit/22622df2cfcb71054c6f6110b7ad9d4f635553dc))
* **UI:** new BaseSelect component ([745d551](https://github.com/antares-sql/antares/commit/745d551cc9253eae4e39e5d3406ceee051a7d6c1))
* **UI:** select tab replace with BaseSelect component ([42bc919](https://github.com/antares-sql/antares/commit/42bc9196ffc2f64b77f9cb42136255fc74815034))
### Bug Fixes
* **PostgreSQL:** idle timeout disabled ([a082514](https://github.com/antares-sql/antares/commit/a082514f88040c7e0ffdf4e8357bab45370a4c39))
* query tab content disappears reordering or closing other tabs, closes [#261](https://github.com/antares-sql/antares/issues/261) ([c5baf2b](https://github.com/antares-sql/antares/commit/c5baf2b0d379fdd28ee8cb907628bbfca940e2f6))
* SSH tunnel connection error with private key, closes [#260](https://github.com/antares-sql/antares/issues/260) ([c826888](https://github.com/antares-sql/antares/commit/c826888b0dd0908958a4f727ddfa642e846269cf))
* **UI:** BaseSelect keyboard navigation ([7c45203](https://github.com/antares-sql/antares/commit/7c452036368fa0db6b9cde7c35e60a8e57bfece7))
* **UI:** BaseSelect style ([71b0736](https://github.com/antares-sql/antares/commit/71b0736d0ddbd599ab41cde0a6b0823e2bb7da2f))
### Improvements
* **UI:** max height for query text area increased ([5d5f1da](https://github.com/antares-sql/antares/commit/5d5f1da97b9adfa743197d8fa0bbb6addd565a7a))
### [0.5.4](https://github.com/antares-sql/antares/compare/v0.5.3...v0.5.4) (2022-05-10)
### Bug Fixes
* app blocked by BIT fields with no default, closes [#256](https://github.com/antares-sql/antares/issues/256) ([e62f280](https://github.com/antares-sql/antares/commit/e62f280528edb0ff4550ee75038ea216e81e4f10))
* file upload input not working ([58611bf](https://github.com/antares-sql/antares/commit/58611bf07f343e6899a7446bfcd1247b0c75fc7f))
* SSH tunnel not working ([6d61518](https://github.com/antares-sql/antares/commit/6d6151814e5006935d493b9b83dbda1aa5b35391))
* unable to insert auto-generated datetime fields ([ff27244](https://github.com/antares-sql/antares/commit/ff272440bdc2a7fe699e04f8809bd5af8f9529c0))
### Improvements
* **UI:** left alignment for numbers in result tables, closes [#249](https://github.com/antares-sql/antares/issues/249) ([e02565c](https://github.com/antares-sql/antares/commit/e02565c0d9bb63efa76a79f38e3ed3586a30ad1c))
### [0.5.3](https://github.com/antares-sql/antares/compare/v0.5.2...v0.5.3) (2022-05-08)

133
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
fabio286@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -136,6 +136,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=raliqala" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fred</b></sub></a><br /><a href="#translation-fredatgithub" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</a></td>
</tr>
</table>

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.5.3",
"version": "0.5.7",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/antares-sql/antares.git",
@@ -13,7 +13,7 @@
"compile:workers": "webpack --mode=production --config webpack.workers.config.js",
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
"build": "cross-env NODE_ENV=production npm run compile",
"build:local": "npm run build && electron-builder",
"build:local": "npm run build && electron-builder --publish never",
"build:appx": "npm run build:local -- --win appx",
"rebuild:electron": "rimraf ./dist && npm run postinstall",
"release": "standard-version",
@@ -82,6 +82,12 @@
"license": "./LICENSE",
"category": "Development"
},
"nsis": {
"license": "./LICENSE",
"installerIcon": "assets/icon.ico",
"uninstallerIcon": "assets/icon.ico",
"installerHeader": "assets/icon.ico"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
},
@@ -118,7 +124,7 @@
"better-sqlite3": "~7.5.0",
"electron-log": "~4.4.1",
"electron-store": "~8.0.1",
"electron-updater": "~5.0.1",
"electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3",
"encoding": "~0.1.13",
"leaflet": "~1.7.1",

View File

@@ -1,5 +1,5 @@
import * as antares from 'common/interfaces/antares';
import fs from 'fs';
import * as fs from 'fs';
import { ipcMain } from 'electron';
import { ClientsFactory } from '../libs/ClientsFactory';
import { SslOptions } from 'mysql2';

View File

@@ -2,7 +2,7 @@ import * as antares from 'common/interfaces/antares';
import { InsertRowsParams } from 'common/interfaces/tableApis';
import { ipcMain } from 'electron';
import { faker } from '@faker-js/faker';
import moment from 'moment';
import * as moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import * as customizations from 'common/customizations';
@@ -104,8 +104,6 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `"${sqlEscaper(params.content)}"`;
break;
case 'pg':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
case 'sqlite':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
@@ -171,6 +169,8 @@ export default (connections: {[key: string]: antares.Client}) => {
}
else {
const { orgRow } = params;
delete orgRow._antares_id;
reload = true;
for (const key in orgRow) {
@@ -341,6 +341,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break;
case 'pg':
case 'sqlite':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break;
}

View File

@@ -2,7 +2,7 @@ import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore';
import * as dataTypes from 'common/data-types/mysql';
import SSH2Promise from 'ssh2-promise';
import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig';
export class MySQLClient extends AntaresCore {

View File

@@ -5,7 +5,7 @@ import * as pg from 'pg';
import * as pgAst from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore';
import * as dataTypes from 'common/data-types/postgresql';
import SSH2Promise from 'ssh2-promise';
import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -144,7 +144,11 @@ export class PostgreSQLClient extends AntaresCore {
async getConnectionPool () {
const dbConfig = await this.getDbConfig();
const pool = new pg.Pool({ ...dbConfig, max: this._poolSize });
const pool = new pg.Pool({
...dbConfig,
max: this._poolSize,
idleTimeoutMillis: 0
});
const connection = pool;
if (this._params.readonly) {

View File

@@ -1,4 +1,4 @@
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import { app, BrowserWindow, /* session, */ nativeImage, Menu, ipcMain } from 'electron';
import * as path from 'path';
import * as Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state';
@@ -7,9 +7,13 @@ import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers';
Store.initRenderer();
const persistentStore = new Store({ name: 'settings' });
const appTheme = persistentStore.get('application_theme');
const isDevelopment = process.env.NODE_ENV !== 'production';
const isMacOS = process.platform === 'darwin';
const isLinux = process.platform === 'linux';
const isWindows = process.platform === 'win32';
const gotTheLock = app.requestSingleInstanceLock();
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
@@ -19,7 +23,7 @@ let mainWindow: BrowserWindow;
let mainWindowState: windowStateKeeper.State;
async function createMainWindow () {
const icon = require('../renderer/images/logo-32.png');
const icon = require('../renderer/images/logo-64.png');
const window = new BrowserWindow({
width: mainWindowState.width,
height: mainWindowState.height,
@@ -28,15 +32,21 @@ async function createMainWindow () {
minWidth: 900,
minHeight: 550,
title: 'Antares SQL',
autoHideMenuBar: true,
icon: nativeImage.createFromDataURL(icon.default),
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
spellcheck: false
},
frame: false,
titleBarStyle: isMacOS ? 'hidden' : 'default',
autoHideMenuBar: true,
titleBarStyle: isLinux ? 'default' :'hidden',
titleBarOverlay: isWindows
? {
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000',
height: 30
}
: false,
trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined,
backgroundColor: '#1d1d1d'
});
@@ -73,10 +83,24 @@ else {
// Initialize ipcHandlers
ipcHandlers();
ipcMain.on('refresh-theme-settings', () => {
const appTheme = persistentStore.get('application_theme');
if (isWindows) {
mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000'
});
}
});
ipcMain.on('change-window-title', (event, title: string) => {
if (mainWindow) mainWindow.setTitle(title);
});
// quit application when all windows are closed
app.on('window-all-closed', () => {
// on macOS it is common for applications to stay open until the user explicitly quits
if (isMacOS) app.quit();
if (!isMacOS) app.quit();
});
app.on('activate', async () => {

View File

@@ -0,0 +1,436 @@
<template>
<div
ref="el"
class="select"
:class="{'select--open': isOpen, 'select--disabled': disabled}"
role="combobox"
:tabindex="searchable || disabled ? -1 : tabindex"
@focus="activate()"
@blur="searchable ? false : handleBlurEvent()"
@keyup.esc="deactivate()"
@keydown.self.down.prevent="moveDown()"
@keydown.self.up.prevent="moveUp"
>
<div class="select__item-text">
<input
v-if="searchable"
ref="searchInput"
class="select__search-input"
:style="searchInputStyle"
type="text"
autocomplete="off"
spellcheck="false"
:tabindex="tabindex"
:value="searchText"
@input="searchText = $event.target.value"
@focus.prevent="!isOpen ? activate() : false"
@blur.prevent="handleBlurEvent()"
@keyup.esc="deactivate()"
@keydown.down.prevent="keyArrows('down')"
@keydown.up.prevent="keyArrows('up')"
@keypress.enter.prevent.stop.self="select(filteredOptions[hightlightedIndex])"
>
<span v-if="searchable && !isOpen || !searchable">{{ currentOptionLabel }}</span>
</div>
<Transition :name="animation">
<div
v-if="isOpen"
ref="optionList"
:class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`"
@mousedown="isMouseDown = true"
@mouseup="handleMouseUpEvent()"
>
<ul class="select__list" @mousedown.prevent>
<li
v-for="(opt, index) of filteredOptions"
:key="opt.id"
:ref="(el) => optionRefs[index] = el"
:class="{
'select__item': true,
'select__group': opt.$type === 'group',
'select__option--highlight': opt.$type === 'option' && !opt.disabled && index === hightlightedIndex,
'select__option--selected': opt.$type === 'option' && isSelected(opt),
'select__option--disabled': opt.disabled
}"
@click.stop="select(opt)"
@mousemove.self="hightlightedIndex = index"
>
<slot
name="option"
:option="opt"
:index="index"
>
{{ opt.label }}
</slot>
</li>
</ul>
</div>
</Transition>
</div>
</template>
<script>
import { defineComponent, computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
export default defineComponent({
name: 'BaseSelect',
props: {
modelValue: {
type: [String, Number, Object, Boolean]
},
value: {
type: [String, Number, Object, Boolean]
},
searchable: {
type: Boolean,
default: true
},
preserveSearch: {
type: Boolean,
default: false
},
tabindex: {
type: Number,
default: 0
},
options: {
type: Array,
default: () => []
},
optionTrackBy: {
type: [String, Function],
default: 'value'
},
optionLabel: {
type: [String, Function],
default: 'label'
},
optionDisabled: {
type: Function
},
groupLabel: {
type: String
},
groupValues: {
type: String
},
closeOnSelect: {
type: Boolean,
default: true
},
animation: {
type: String,
default: 'fade-slide-down'
},
dropdownOffsets: {
type: Object,
default: () => ({ top: 10, left: 0 })
},
dropdownClass: {
type: String
},
disabled: {
type: Boolean,
default: false
}
},
emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'],
setup (props, { emit }) {
const hightlightedIndex = ref(0);
const isOpen = ref(false);
const isMouseDown = ref(false);
const internalValue = ref(props.modelValue || props.value);
const el = ref(null);
const searchInput = ref(null);
const optionList = ref(null);
const optionRefs = [];
const searchText = ref('');
const getOptionValue = (opt) => _guess('optionTrackBy', opt);
const getOptionLabel = (opt) => _guess('optionLabel', opt);
const getOptionDisabled = (opt) => _guess('optionDisabled', opt);
const _guess = (name, item) => {
const prop = props[name];
if (typeof prop === 'function')
return prop(item);
return item[prop] !== undefined ? item[prop] : item;
};
const flattenOptions = computed(() => {
return [...props.options].reduce((prev, curr) => {
if (curr[props.groupValues] && curr[props.groupValues].length) {
prev.push({
$type: 'group',
label: curr[props.groupLabel],
id: `group-${curr[props.groupLabel]}`,
count: curr[props.groupLabel].length
});
return prev.concat(curr[props.groupValues].map(el => {
const value = getOptionValue(el);
return {
$type: 'option',
label: getOptionLabel(el),
id: `option-${value}`,
disabled: getOptionDisabled(el) === true,
value,
$data: {
...el
}
};
}));
}
else {
const value = getOptionValue(curr);
prev.push({
$type: 'option',
label: getOptionLabel(curr),
id: `option-${value}`,
disabled: getOptionDisabled(curr) === true,
value,
$data: {
...curr
}
});
}
return prev;
}, []);
});
const filteredOptions = computed(() => {
const normalizedSearch = (searchText.value || '').toLowerCase().trim();
return normalizedSearch
? flattenOptions.value.filter(opt => opt.$type === 'group' || opt.label.trim().toLowerCase().indexOf(normalizedSearch) !== -1)
: flattenOptions.value;
});
const searchInputStyle = computed(() => {
if (props.searchable)
// just hide the input and give the ability to receive focus
return isOpen.value ? { with: '100%' } : { width: 0, position: 'absolute', padding: 0, margin: 0 };
return '';
});
watch(filteredOptions, (options) => {
if (hightlightedIndex.value >= options.length -1)
hightlightedIndex.value = options.length ? options.length -1 : 0;
else
hightlightedIndex.value = 0;
});
watch(() => props.modelValue, (val) => {
internalValue.value = val;
});
watch(() => props.value, (val) => {
internalValue.value = val;
});
const currentOptionLabel = computed(() =>
flattenOptions.value.find(d => d.value === internalValue.value)?.label
);
const select = (opt) => {
if (opt.$type === 'group' || opt.disabled) return;
internalValue.value = opt.value;
emit('select', opt);
emit('update:modelValue', opt.value);
emit('change', opt);
if (props.closeOnSelect)
deactivate();
};
const isSelected = (opt) => {
return internalValue.value === opt.value;
};
const activate = () => {
if (isOpen.value || props.disabled) return;
isOpen.value = true;
hightlightedIndex.value = flattenOptions.value.findIndex(el => el.value === internalValue.value) || 0;
if (props.searchable)
searchInput.value.focus();
else
el.value.focus();
nextTick(() => {
adjustListPosition();
scrollTo(optionRefs[hightlightedIndex.value]);
});
emit('open');
};
const deactivate = () => {
if (!isOpen.value) return;
isOpen.value = false;
if (props.searchable)
searchInput.value?.blur();
else
el.value?.blur();
if (!props.preserveSearch) searchText.value = '';
emit('close');
};
const adjustListPosition = () => {
const element = el.value;
let { left, top } = element.getBoundingClientRect();
const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets;
top = top + element.clientHeight + offsetTop;
const openBottom = top >= 0 && top + optionList.value.clientHeight <= window.innerHeight;
if (!openBottom) {
top -= (offsetTop * 2 + element.clientHeight);
optionList.value.style.transform = 'translate(0, -100%)';
}
optionList.value.style.left = `${left + offsetLeft}px`;
optionList.value.style.top = `${top}px`;
optionList.value.style.minWidth = `${element.clientWidth}px`;
};
const keyArrows = (direction) => {
const sum = direction === 'down' ? +1 : -1;
let index = hightlightedIndex.value + sum;
index = Math.max(0, index > filteredOptions.value.length - 1 ? filteredOptions.value.length - 1 : index);
if (filteredOptions.value[index].$type === 'group')
index=Math.max(1, index+sum);
hightlightedIndex.value = index;
const optEl = optionRefs[hightlightedIndex.value];
if (!optEl)
return;
scrollTo(optEl);
};
const scrollTo = (optEl) => {
if (!optEl) return;
const visMin = optionList.value.scrollTop;
const visMax = optionList.value.scrollTop + optionList.value.clientHeight - optEl.clientHeight;
if (optEl.offsetTop < visMin)
optionList.value.scrollTop = optEl.offsetTop;
else if (optEl.offsetTop >= visMax)
optionList.value.scrollTop = optEl.offsetTop - optionList.value.clientHeight + optEl.clientHeight;
};
const handleBlurEvent = () => {
if (isMouseDown.value) return;
deactivate();
emit('blur');
};
const handleMouseUpEvent = () => {
isMouseDown.value = false;
searchInput.value.focus();
};
const handleWheelEvent = (e) => {
if (!e.target.className.includes('select__')) deactivate();
};
onMounted(() => {
window.addEventListener('resize', adjustListPosition);
window.addEventListener('wheel', handleWheelEvent);
nextTick(() => {
// fix position when the component is created and opened at the same time
if (isOpen.value) {
setTimeout(() => {
adjustListPosition();
}, 50);
}
});
});
onUnmounted(() => {
window.removeEventListener('resize', adjustListPosition);
window.removeEventListener('wheel', handleWheelEvent);
});
return {
el,
searchInput,
searchText,
searchInputStyle,
filteredOptions,
currentOptionLabel,
activate,
deactivate,
select,
isSelected,
keyArrows,
isOpen,
isMouseDown,
hightlightedIndex,
optionList,
optionRefs,
handleBlurEvent,
handleMouseUpEvent
};
}
});
</script>
<style lang="scss" scoped>
.select {
display: block;
&:focus,
&--open {
z-index: 10;
}
&__search-input {
appearance: none;
border: none;
background: transparent;
outline: none;
color: currentColor;
max-width: 100%;
width: 100%;
}
&__list-wrapper {
cursor: pointer;
position: fixed;
display: block;
z-index: 5;
-webkit-overflow-scrolling: touch;
max-height: 240px;
overflow: auto;
left: 0;
top: 40px;
}
&__list {
list-style: none;
}
&__option {
&--disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
&--disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
</style>

View File

@@ -23,6 +23,8 @@
</template>
<script>
import { uidGen } from 'common/libs/uidGen';
export default {
name: 'BaseUploadInput',
props: {
@@ -38,12 +40,9 @@ export default {
emits: ['change', 'clear'],
data () {
return {
id: null
id: uidGen()
};
},
mounted () {
this.id = this._uid;
},
methods: {
clear () {
this.$emit('clear');

View File

@@ -1,38 +1,26 @@
<template>
<fieldset class="input-group mb-0">
<select
<BaseSelect
v-model="selectedGroup"
class="form-select"
:options="[{name: 'manual'}, ...fakerGroups]"
:option-label="(opt) => opt.name === 'manual' ? $t('message.manualValue') : $t(`faker.${opt.name}`)"
option-track-by="name"
:disabled="!isChecked"
style="flex-grow: 0;"
@change="onChange"
>
<option value="manual">
{{ $t('message.manualValue') }}
</option>
<option
v-for="group in fakerGroups"
:key="group.name"
:value="group.name"
>
{{ $t(`faker.${group.name}`) }}
</option>
</select>
<select
/>
<BaseSelect
v-if="selectedGroup !== 'manual'"
v-model="selectedMethod"
:options="fakerMethods"
:option-label="(opt) => $t(`faker.${opt.name}`)"
option-track-by="name"
class="form-select"
:disabled="!isChecked"
@change="onChange"
>
<option
v-for="method in fakerMethods"
:key="method.name"
:value="method.name"
>
{{ $t(`faker.${method.name}`) }}
</option>
</select>
/>
<ForeignKeySelect
v-else-if="foreignKeys.includes(field.name)"
ref="formInput"
@@ -52,7 +40,7 @@
>
<BaseUploadInput
v-else-if="inputProps().type === 'file'"
:value="selectedValue"
:model-value="selectedValue"
:message="$t('word.browse')"
@clear="clearValue"
@change="filesChange($event)"
@@ -66,21 +54,14 @@
:type="inputProps().type"
:disabled="!isChecked"
>
<select
<BaseSelect
v-else-if="enumArray"
v-model="selectedValue"
:options="enumArray"
class="form-select"
:disabled="!isChecked"
@change="onChange"
>
<option
v-for="val in enumArray"
:key="val"
:value="val"
>
{{ val }}
</option>
</select>
/>
<input
v-else
ref="formInput"
@@ -109,12 +90,14 @@ import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from
import BaseUploadInput from '@/components/BaseUploadInput';
import ForeignKeySelect from '@/components/ForeignKeySelect';
import FakerMethods from 'common/FakerMethods';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'FakerSelect',
components: {
ForeignKeySelect,
BaseUploadInput
BaseUploadInput,
BaseSelect
},
props: {
type: String,

View File

@@ -1,23 +1,15 @@
<template>
<select
<BaseSelect
ref="editField"
:options="foreigns"
class="form-select pl-1 pr-4"
:class="{'small-select': size === 'small'}"
:value="currentValue"
dropdown-class="select-sm"
dropdown-container=".workspace-query-results > .vscroll"
@change="onChange"
@blur="$emit('blur')"
>
<option v-if="!isValidDefault" :value="modelValue">
{{ modelValue === null ? 'NULL' : modelValue }}
</option>
<option
v-for="row in foreignList"
:key="row.foreign_column"
:value="row.foreign_column"
:selected="row.foreign_column === modelValue"
>
{{ row.foreign_column }} {{ cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '') }}
</option>
</select>
/>
</template>
<script>
@@ -26,8 +18,11 @@ import Tables from '@/ipc-api/Tables';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'ForeignKeySelect',
components: { BaseSelect },
props: {
modelValue: [String, Number],
keyUsage: Object,
@@ -47,7 +42,8 @@ export default {
},
data () {
return {
foreignList: []
foreignList: [],
currentValue: this.modelValue || null
};
},
computed: {
@@ -55,6 +51,17 @@ export default {
if (!this.foreignList.length) return true;
if (this.modelValue === null) return false;
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.modelValue.toString());
},
foreigns () {
const list = [];
if (!this.isValidDefault)
list.push({ value: this.modelValue, label: this.modelValue === null ? 'NULL' : this.modelValue });
for (const row of this.foreignList)
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${this.cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` });
return list;
}
},
async created () {
@@ -95,8 +102,8 @@ export default {
}
},
methods: {
onChange () {
this.$emit('update:modelValue', this.$refs.editField.value);
onChange (opt) {
this.$emit('update:modelValue', opt.value);
},
cutText (val) {
if (typeof val !== 'string') return val;

View File

@@ -21,6 +21,7 @@
</div>
<div class="col-9">
<input
ref="firstInput"
v-model="database.name"
class="form-input"
type="text"
@@ -35,19 +36,13 @@
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
<div class="col-9">
<select
ref="firstInput"
<BaseSelect
v-model="database.collation"
class="form-select"
>
<option
v-for="collation in collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
:options="collations"
option-label="collation"
option-track-by="collation"
/>
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
@@ -72,9 +67,13 @@ import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'ModalEditSchema',
components: {
BaseSelect
},
props: {
selectedSchema: String
},

View File

@@ -206,14 +206,11 @@
>
</div>
<div class="column col-6">
<select v-model="options.sqlInsertDivider" class="form-select">
<option value="bytes">
KiB
</option>
<option value="rows">
{{ $tc('word.row', 2) }}
</option>
</select>
<BaseSelect
v-model="options.sqlInsertDivider"
class="form-select"
:options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: $tc('word.row', 2)}]"
/>
</div>
</div>
</div>
@@ -223,14 +220,11 @@
</div>
<div class="columns">
<div class="column h5 mb-4">
<select v-model="options.outputFormat" class="form-select">
<option value="sql">
{{ $t('message.singleFile', {ext: '.sql'}) }}
</option>
<option value="sql.zip">
{{ $t('message.zipCompressedFile', {ext: '.sql'}) }}
</option>
</select>
<BaseSelect
v-model="options.outputFormat"
class="form-select"
:options="[{value: 'sql', label: $t('message.singleFile', {ext: '.sql'})}, {value: 'sql.zip', label: $t('message.zipCompressedFile', {ext: '.sql'})}]"
/>
</div>
</div>
</div>
@@ -278,9 +272,13 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import customizations from 'common/customizations';
import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'ModalExportSchema',
components: {
BaseSelect
},
props: {
selectedSchema: String
},

View File

@@ -72,99 +72,11 @@
class="tooltip tooltip-right ml-2"
:data-tooltip="$t('message.fakeDataLanguage')"
>
<select v-model="fakerLocale" class="form-select">
<option value="ar">
Arabic
</option><option value="az">
Azerbaijani
</option><option value="zh_CN">
Chinese
</option><option value="zh_TW">
Chinese (Taiwan)
</option><option value="cz">
Czech
</option><option value="nl">
Dutch
</option><option value="nl_BE">
Dutch (Belgium)
</option><option value="en">
English
</option><option value="en_AU_ocker">
English (Australia Ocker)
</option><option value="en_AU">
English (Australia)
</option><option value="en_BORK">
English (Bork)
</option><option value="en_CA">
English (Canada)
</option><option value="en_GB">
English (Great Britain)
</option><option value="en_IND">
English (India)
</option><option value="en_IE">
English (Ireland)
</option><option value="en_ZA">
English (South Africa)
</option><option value="en_US">
English (United States)
</option><option value="fa">
Farsi
</option><option value="fi">
Finnish
</option><option value="fr">
French
</option><option value="fr_CA">
French (Canada)
</option><option value="fr_CH">
French (Switzerland)
</option><option value="ge">
Georgian
</option><option value="de">
German
</option><option value="de_AT">
German (Austria)
</option><option value="de_CH">
German (Switzerland)
</option><option value="hr">
Hrvatski
</option><option value="id_ID">
Indonesia
</option><option value="it">
Italian
</option><option value="ja">
Japanese
</option><option value="ko">
Korean
</option><option value="nep">
Nepalese
</option><option value="nb_NO">
Norwegian
</option><option value="pl">
Polish
</option><option value="pt_BR">
Portuguese (Brazil)
</option><option value="pt_PT">
Portuguese (Portugal)
</option><option value="ro">
Romanian
</option><option value="ru">
Russian
</option><option value="sk">
Slovakian
</option><option value="es">
Spanish
</option><option value="es_MX">
Spanish (Mexico)
</option><option value="sv">
Swedish
</option><option value="tr">
Turkish
</option><option value="uk">
Ukrainian
</option><option value="vi">
Vietnamese
</option>
</select>
<BaseSelect
v-model="fakerLocale"
:options="locales"
class="form-select"
/>
</div>
</div>
<div class="column col-auto">
@@ -193,11 +105,13 @@ import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'ModalFakerRows',
components: {
FakerSelect
FakerSelect,
BaseSelect
},
props: {
tabUid: [String, Number],
@@ -210,14 +124,62 @@ export default {
const workspacesStore = useWorkspacesStore();
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { getWorkspace, getWorkspaceTab } = workspacesStore;
const locales = [
{ value: 'ar', label: 'Arabic' },
{ value: 'az', label: 'Azerbaijani' },
{ value: 'zh_CN', label: 'Chinese' },
{ value: 'zh_TW', label: 'Chinese (Taiwan)' },
{ value: 'cz', label: 'Czech' },
{ value: 'nl', label: 'Dutch' },
{ value: 'nl_BE', label: 'Dutch (Belgium)' },
{ value: 'en', label: 'English' },
{ value: 'en_AU_ocker', label: 'English (Australia Ocker)' },
{ value: 'en_AU', label: 'English (Australia)' },
{ value: 'en_BORK', label: 'English (Bork)' },
{ value: 'en_CA', label: 'English (Canada)' },
{ value: 'en_GB', label: 'English (Great Britain)' },
{ value: 'en_IND', label: 'English (India)' },
{ value: 'en_IE', label: 'English (Ireland)' },
{ value: 'en_ZA', label: 'English (South Africa)' },
{ value: 'en_US', label: 'English (United States)' },
{ value: 'fa', label: 'Farsi' },
{ value: 'fi', label: 'Finnish' },
{ value: 'fr', label: 'French' },
{ value: 'fr_CA', label: 'French (Canada)' },
{ value: 'fr_CH', label: 'French (Switzerland)' },
{ value: 'ge', label: 'Georgian' },
{ value: 'de', label: 'German' },
{ value: 'de_AT', label: 'German (Austria)' },
{ value: 'de_CH', label: 'German (Switzerland)' },
{ value: 'hr', label: 'Hrvatski' },
{ value: 'id_ID', label: 'Indonesia' },
{ value: 'it', label: 'Italian' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'nep', label: 'Nepalese' },
{ value: 'nb_NO', label: 'Norwegian' },
{ value: 'pl', label: 'Polish' },
{ value: 'pt_BR', label: 'Portuguese (Brazil)' },
{ value: 'pt_PT', label: 'Portuguese (Portugal)' },
{ value: 'ro', label: 'Romanian' },
{ value: 'ru', label: 'Russian' },
{ value: 'sk', label: 'Slovakian' },
{ value: 'es', label: 'Spanish' },
{ value: 'es_MX', label: 'Spanish (Mexico)' },
{ value: 'sv', label: 'Swedish' },
{ value: 'tr', label: 'Turkish' },
{ value: 'uk', label: 'Ukrainian' },
{ value: 'vi', label: 'Vietnamese' }
];
return {
addNotification,
selectedWorkspace,
getWorkspace,
getWorkspaceTab
getWorkspaceTab,
locales
};
},
data () {
@@ -271,7 +233,7 @@ export default {
else if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
else if (BIT.includes(field.type))
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = '';

View File

@@ -35,15 +35,13 @@
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
<div class="col-9">
<select v-model="database.collation" class="form-select">
<option
v-for="collation in collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<BaseSelect
v-model="database.collation"
class="form-select"
:options="collations"
option-label="collation"
option-track-by="collation"
/>
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
</div>
</div>
@@ -72,9 +70,11 @@ import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Schema from '@/ipc-api/Schema';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'ModalNewSchema',
components: { BaseSelect },
emits: ['reload', 'close'],
setup () {
const { addNotification } = useNotificationsStore();

View File

@@ -69,19 +69,14 @@
</label>
</div>
<div class="col-3 col-sm-12">
<select
<BaseSelect
v-model="localLocale"
class="form-select"
:options="locales"
option-track-by="code"
option-label="name"
@change="changeLocale(localLocale)"
>
<option
v-for="(locale, key) in locales"
:key="key"
:value="locale.code"
>
{{ locale.name }}
</option>
</select>
/>
</div>
<div class="col-4 col-sm-12 px-2 p-vcentered">
<small class="d-block" style="line-height:1.1; font-size:70%;">
@@ -97,18 +92,12 @@
</label>
</div>
<div class="col-3 col-sm-12">
<select
<BaseSelect
v-model="localPageSize"
class="form-select"
:options="pageSizes"
@change="changePageSize(+localPageSize)"
>
<option
v-for="size in pageSizes"
:key="size"
>
{{ size }}
</option>
</select>
/>
</div>
</div>
<div class="form-group column col-12 mb-0">
@@ -231,26 +220,16 @@
{{ $t('message.editorTheme') }}
</div>
<div class="column col-6 h5 mb-4">
<select
<BaseSelect
v-model="localEditorTheme"
class="form-select"
:options="editorThemes"
option-label="name"
option-track-by="code"
group-label="group"
group-values="themes"
@change="changeEditorTheme(localEditorTheme)"
>
<optgroup
v-for="group in editorThemes"
:key="group.group"
:label="group.group"
>
<option
v-for="theme in group.themes"
:key="theme.name"
:value="theme.code"
:selected="editorTheme === theme.code"
>
{{ theme.name }}
</option>
</optgroup>
</select>
/>
</div>
<div class="column col-6 mb-4">
<div class="btn-group btn-group-block">
@@ -332,13 +311,15 @@ import localesNames from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
import BaseTextEditor from '@/components/BaseTextEditor';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'ModalSettings',
components: {
ModalSettingsUpdate,
ModalSettingsChangelog,
BaseTextEditor
BaseTextEditor,
BaseSelect
},
setup () {
const applicationStore = useApplicationStore();

View File

@@ -98,6 +98,7 @@ export default {
data () {
return {
dragElement: null,
isLinux: process.platform === 'linux',
isContext: false,
isDragging: false,
contextEvent: null,
@@ -129,7 +130,9 @@ export default {
},
tooltipPosition (e) {
const el = e.target ? e.target : e;
const fromTop = window.pageYOffset + el.getBoundingClientRect().top - (el.offsetHeight / 4);
const fromTop = this.isLinux
? window.scrollY + el.getBoundingClientRect().top + (el.offsetHeight / 4)
: window.scrollY + el.getBoundingClientRect().top - (el.offsetHeight / 4)
el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`;
},
getStatusBadge (uid) {

View File

@@ -1,5 +1,9 @@
<template>
<div id="titlebar" @dblclick="toggleFullScreen">
<div
v-if="!isLinux"
id="titlebar"
@dblclick="toggleFullScreen"
>
<div class="titlebar-resizer" />
<div class="titlebar-elements">
<img
@@ -26,15 +30,16 @@
>
<i class="mdi mdi-24px mdi-refresh" />
</div>
<div
v-if="!isMacOS"
<div v-if="isWindows" style="width: 140px;" />
<!-- <div
v-if="isLinux"
class="titlebar-element"
@click="minimizeApp"
>
<i class="mdi mdi-24px mdi-minus" />
</div>
<div
v-if="!isMacOS"
v-if="isLinux"
class="titlebar-element"
@click="toggleFullScreen"
>
@@ -42,12 +47,12 @@
<i v-else class="mdi mdi-24px mdi-fullscreen" />
</div>
<div
v-if="!isMacOS"
v-if="isLinux"
class="titlebar-element close-button"
@click="closeApp"
>
<i class="mdi mdi-24px mdi-close" />
</div>
</div> -->
</div>
</div>
</template>
@@ -80,7 +85,9 @@ export default {
w: getCurrentWindow(),
isMaximized: getCurrentWindow().isMaximized(),
isDevelopment: process.env.NODE_ENV === 'development',
isMacOS: process.platform === 'darwin'
isMacOS: process.platform === 'darwin',
isWindows: process.platform === 'win32',
isLinux: process.platform === 'linux'
};
},
computed: {
@@ -95,6 +102,11 @@ export default {
return [connectionName, ...breadcrumbs].join(' • ');
}
},
watch: {
windowTitle: function (val) {
ipcRenderer.send('change-window-title', val);
}
},
created () {
window.addEventListener('resize', this.onResize);
},
@@ -171,7 +183,7 @@ export default {
height: $titlebar-height;
line-height: 0;
padding: 0 0.7rem;
opacity: 0.7;
opacity: 0.9;
transition: opacity 0.2s;
-webkit-app-region: no-drag;

View File

@@ -315,10 +315,9 @@
</template>
</Draggable>
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
<template v-for="tab of workspace.tabs">
<template v-for="tab of workspace.tabs" :key="tab.uid">
<WorkspaceTabQuery
v-if="tab.type==='query'"
:key="tab.uid"
v-if="tab.type ==='query'"
:tab-uid="tab.uid"
:tab="tab"
:is-selected="selectedTab === tab.uid"
@@ -326,7 +325,7 @@
/>
<WorkspaceTabTable
v-else-if="['temp-data', 'data'].includes(tab.type)"
:key="tab.uid"
v-once
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -336,7 +335,6 @@
/>
<WorkspaceTabNewTable
v-else-if="tab.type === 'new-table'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -345,7 +343,6 @@
/>
<WorkspaceTabPropsTable
v-else-if="tab.type === 'table-props'"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -354,7 +351,6 @@
/>
<WorkspaceTabNewView
v-else-if="tab.type === 'new-view'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -363,7 +359,6 @@
/>
<WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'"
:key="tab.uid"
:tab-uid="tab.uid"
:is-selected="selectedTab === tab.uid"
:connection="connection"
@@ -372,7 +367,6 @@
/>
<WorkspaceTabNewTrigger
v-else-if="tab.type === 'new-trigger'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -382,7 +376,6 @@
/>
<WorkspaceTabPropsTrigger
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -391,7 +384,6 @@
/>
<WorkspaceTabNewTriggerFunction
v-else-if="tab.type === 'new-trigger-function'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -401,7 +393,6 @@
/>
<WorkspaceTabPropsTriggerFunction
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -410,7 +401,6 @@
/>
<WorkspaceTabNewRoutine
v-else-if="tab.type === 'new-routine'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -420,7 +410,6 @@
/>
<WorkspaceTabPropsRoutine
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -429,7 +418,6 @@
/>
<WorkspaceTabNewFunction
v-else-if="tab.type === 'new-function'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -439,7 +427,6 @@
/>
<WorkspaceTabPropsFunction
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -448,7 +435,6 @@
/>
<WorkspaceTabNewScheduler
v-else-if="tab.type === 'new-scheduler'"
:key="tab.uid"
:tab-uid="tab.uid"
:tab="tab"
:connection="connection"
@@ -458,7 +444,6 @@
/>
<WorkspaceTabPropsScheduler
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:key="tab.uid"
:tab-uid="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
@@ -556,7 +541,9 @@ export default {
selectTab,
newTab,
removeTab,
updateTabs
updateTabs,
selectNextTab,
selectPrevTab
} = workspacesStore;
return {
@@ -568,7 +555,9 @@ export default {
selectTab,
newTab,
removeTab,
updateTabs
updateTabs,
selectNextTab,
selectPrevTab
};
},
data () {
@@ -670,6 +659,22 @@ export default {
if (currentTab)
this.closeTab(currentTab);
}
// select next tab
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowRight')
this.selectNextTab({ uid: this.connection.uid });
// select prev tab
if (e.altKey && (e.ctrlKey || e.metaKey) && e.key === 'ArrowLeft')
this.selectPrevTab({ uid: this.connection.uid });
// select tab by index (range 1-9). CTRL|CMD number
if ((e.ctrlKey || e.metaKey) && !e.altKey && e.keyCode >= 49 && e.keyCode <= 57) {
const newIndex = parseInt(e.key) - 1;
if (this.workspace.tabs[newIndex])
this.selectTab({ uid: this.connection.uid, tab: this.workspace.tabs[newIndex].uid });
}
},
openAsPermanentTab (tab) {
const permanentTabs = {

View File

@@ -50,19 +50,13 @@
<label class="form-label cut-text">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select
id="connection-client"
<BaseSelect
v-model="connection.client"
:options="clients"
option-track-by="slug"
option-label="name"
class="form-select"
>
<option
v-for="client in clients"
:key="client.slug"
:value="client.slug"
>
{{ client.name }}
</option>
</select>
/>
</div>
</div>
<div v-if="connection.client === 'pg'" class="form-group columns">
@@ -96,7 +90,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.databasePath"
:model-value="connection.databasePath"
:message="$t('word.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
@@ -211,7 +205,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.key"
:model-value="connection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
@@ -224,7 +218,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.cert"
:model-value="connection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
@@ -237,7 +231,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.ca"
:model-value="connection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
@@ -342,7 +336,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.sshKey"
:model-value="connection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
@@ -404,12 +398,14 @@ import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceAddConnectionPanel',
components: {
ModalAskCredentials,
BaseUploadInput
BaseUploadInput,
BaseSelect
},
setup () {
const { addConnection } = useConnectionsStore();
@@ -547,9 +543,9 @@ export default {
this.isTesting = false;
},
saveConnection () {
async saveConnection () {
await this.addConnection(this.connection);
this.selectWorkspace(this.connection.uid);
return this.addConnection(this.connection);
},
closeAsking () {
this.isTesting = false;

View File

@@ -50,15 +50,15 @@
<label class="form-label cut-text">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select v-model="localConnection.client" class="form-select">
<option
v-for="client in clients"
:key="client.slug"
:value="client.slug"
>
{{ client.name }}
</option>
</select>
<BaseSelect
v-model="localConnection.client"
:options="clients"
option-track-by="slug"
option-label="name"
class="form-select"
dropdown-container=".workspace .connection-panel-wrapper"
:dropdown-offsets="{top: 10}"
/>
</div>
</div>
<div v-if="connection.client === 'pg'" class="form-group columns">
@@ -92,7 +92,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.databasePath"
:model-value="localConnection.databasePath"
:message="$t('word.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
@@ -207,7 +207,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.key"
:model-value="localConnection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
@@ -220,7 +220,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.cert"
:model-value="localConnection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
@@ -233,7 +233,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.ca"
:model-value="localConnection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
@@ -330,7 +330,7 @@
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.sshKey"
:model-value="localConnection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
@@ -401,12 +401,14 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import Connection from '@/ipc-api/Connection';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceEditConnectionPanel',
components: {
ModalAskCredentials,
BaseUploadInput
BaseUploadInput,
BaseSelect
},
props: {
connection: Object

View File

@@ -57,11 +57,11 @@
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
<BaseSelect
v-model="localFunction.language"
:options="customizations.languages"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
@@ -69,27 +69,13 @@
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localFunction.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
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 class="column col-auto">
@@ -98,29 +84,16 @@
{{ $t('word.returns') }}
</label>
<div class="input-group">
<select
<BaseSelect
v-model="localFunction.returns"
class="form-select text-uppercase"
:options="[{ name: 'VOID' }, ...workspace.dataTypes]"
group-label="group"
group-values="types"
option-label="name"
option-track-by="name"
style="max-width: 150px;"
>
<option v-if="localFunction.returns === 'VOID'">
VOID
</option>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="localFunction.returns === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
/>
<input
v-if="customizations.parametersLength"
v-model="localFunction.returnsLength"
@@ -150,10 +123,11 @@
<label class="form-label">
{{ $t('message.sqlSecurity') }}
</label>
<select v-model="localFunction.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
<BaseSelect
v-model="localFunction.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.functionDataAccess" class="column col-auto">
@@ -161,12 +135,11 @@
<label class="form-label">
{{ $t('message.dataAccess') }}
</label>
<select v-model="localFunction.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
<BaseSelect
v-model="localFunction.dataAccess"
:options="['CONTAINS SQL', 'NO SQL', 'READS SQL DATA', 'MODIFIES SQL DATA']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.functionDeterministic" class="column col-auto">
@@ -210,13 +183,15 @@ import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
import Functions from '@/ipc-api/Functions';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewFunction',
components: {
BaseLoader,
QueryEditor,
WorkspaceTabPropsFunctionParamsModal
WorkspaceTabPropsFunctionParamsModal,
BaseSelect
},
props: {
tabUid: String,

View File

@@ -57,11 +57,11 @@
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localRoutine.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
<BaseSelect
v-model="localRoutine.language"
:options="customizations.languages"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
@@ -69,27 +69,13 @@
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localRoutine.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
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="column">
@@ -109,10 +95,11 @@
<label class="form-label">
{{ $t('message.sqlSecurity') }}
</label>
<select v-model="localRoutine.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
<BaseSelect
v-model="localRoutine.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.procedureDataAccess" class="column col-auto">
@@ -120,12 +107,11 @@
<label class="form-label">
{{ $t('message.dataAccess') }}
</label>
<select v-model="localRoutine.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
<BaseSelect
v-model="localRoutine.dataAccess"
:options="['CONTAINS SQL', 'NO SQL', 'READS SQL DATA', 'MODIFIES SQL DATA']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.procedureDeterministic" class="column col-auto">
@@ -170,13 +156,15 @@ import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
import Routines from '@/ipc-api/Routines';
import { storeToRefs } from 'pinia';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewRoutine',
components: {
QueryEditor,
BaseLoader,
WorkspaceTabPropsRoutineParamsModal
WorkspaceTabPropsRoutineParamsModal,
BaseSelect
},
props: {
tabUid: String,

View File

@@ -52,30 +52,13 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localScheduler.definer"
:options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalScheduler.definer">
{{ originalScheduler.definer.replaceAll('`', '') }}
</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 class="column">
@@ -149,13 +132,15 @@ import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
import Schedulers from '@/ipc-api/Schedulers';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewScheduler',
components: {
BaseLoader,
QueryEditor,
WorkspaceTabPropsSchedulerTimingModal
WorkspaceTabPropsSchedulerTimingModal,
BaseSelect
},
props: {
tabUid: String,
@@ -220,6 +205,15 @@ export default {
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
}
},
watch: {

View File

@@ -88,15 +88,13 @@
<label class="form-label">
{{ $t('word.collation') }}
</label>
<select v-model="localOptions.collation" class="form-select">
<option
v-for="collation in workspace.collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<BaseSelect
v-model="localOptions.collation"
:options="workspace.collations"
option-label="collation"
option-track-by="collation"
class="form-select"
/>
</div>
</div>
<div v-if="workspace.customizations.engines" class="column col-auto">
@@ -104,15 +102,13 @@
<label class="form-label">
{{ $t('word.engine') }}
</label>
<select v-model="localOptions.engine" class="form-select">
<option
v-for="engine in workspace.engines"
:key="engine.name"
:value="engine.name"
>
{{ engine.name }}
</option>
</select>
<BaseSelect
v-model="localOptions.engine"
class="form-select"
:options="workspace.engines"
option-label="name"
option-track-by="name"
/>
</div>
</div>
</div>
@@ -175,6 +171,7 @@ import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFie
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewTable',
@@ -183,7 +180,8 @@ export default {
WorkspaceTabPropsTableFields,
WorkspaceTabPropsTableIndexesModal,
WorkspaceTabPropsTableForeignModal,
WorkspaceTabNewTableEmptyState
WorkspaceTabNewTableEmptyState,
BaseSelect
},
props: {
tabUid: String,
@@ -247,12 +245,12 @@ export default {
},
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server')?.value || '';
return '';
},
defaultEngine () {
if (this.workspace.customizations.engines)
return this.workspace.engines.find(engine => engine.isDefault).name;
return this.workspace.engines?.find(engine => engine.isDefault)?.name || '';
return '';
},
schemaTables () {

View File

@@ -46,60 +46,43 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localTrigger.definer"
:options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalTrigger.definer">
{{ originalTrigger.definer.replaceAll('`', '') }}
</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>
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label>
<select v-model="localTrigger.table" class="form-select">
<option v-for="table in schemaTables" :key="table.name">
{{ table.name }}
</option>
</select>
<BaseSelect
v-model="localTrigger.table"
:options="schemaTables"
option-label="name"
option-track-by="name"
class="form-select"
/>
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label>
<div class="input-group">
<select v-model="localTrigger.activation" class="form-select">
<option>BEFORE</option>
<option>AFTER</option>
</select>
<select
<BaseSelect
v-model="localTrigger.activation"
:options="['BEFORE', 'AFTER']"
class="form-select"
/>
<BaseSelect
v-if="!customizations.triggerMultipleEvents"
v-model="localTrigger.event"
:options="Object.keys(localEvents)"
class="form-select"
>
<option v-for="event in Object.keys(localEvents)" :key="event">
{{ event }}
</option>
</select>
/>
<div v-if="customizations.triggerMultipleEvents" class="px-4">
<label
v-for="event in Object.keys(localEvents)"
@@ -138,12 +121,14 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
import Triggers from '@/ipc-api/Triggers';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewTrigger',
components: {
BaseLoader,
QueryEditor
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
@@ -211,6 +196,15 @@ export default {
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalTrigger.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
}
},
watch: {

View File

@@ -45,11 +45,11 @@
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
<BaseSelect
v-model="localFunction.language"
:options="customizations.triggerFunctionlanguages"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
@@ -57,27 +57,13 @@
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localFunction.definer"
:options="workspace.users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
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">
@@ -114,12 +100,14 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewTriggerFunction',
components: {
BaseLoader,
QueryEditor
QueryEditor,
BaseSelect
},
props: {
tabUid: String,

View File

@@ -46,61 +46,44 @@
<div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localView.definer"
:options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalView.definer">
{{ originalView.definer.replaceAll('`', '') }}
</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 class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<select v-model="localView.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
<BaseSelect
v-model="localView.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<select v-model="localView.algorithm" class="form-select">
<option>UNDEFINED</option>
<option>MERGE</option>
<option>TEMPTABLE</option>
</select>
<BaseSelect
v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
class="form-select"
/>
</div>
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<select v-model="localView.updateOption" class="form-select">
<option value="">
None
</option>
<option>CASCADED</option>
<option>LOCAL</option>
</select>
<BaseSelect
v-model="localView.updateOption"
:option-track-by="(user) => user.value"
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
class="form-select"
/>
</div>
</div>
</div>
@@ -127,12 +110,14 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Views from '@/ipc-api/Views';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabNewView',
components: {
BaseLoader,
QueryEditor
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
@@ -189,6 +174,15 @@ export default {
},
isDefinerInUsers () {
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalView.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
}
},
watch: {

View File

@@ -66,11 +66,11 @@
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
<BaseSelect
v-model="localFunction.language"
:options="customizations.languages"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
@@ -78,27 +78,13 @@
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localFunction.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
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 class="column col-auto">
@@ -107,32 +93,16 @@
{{ $t('word.returns') }}
</label>
<div class="input-group">
<select
<BaseSelect
v-model="localFunction.returns"
class="form-select text-uppercase"
:options="[{ name: 'VOID' }, ...workspace.dataTypes]"
group-label="group"
group-values="types"
option-label="name"
option-track-by="name"
style="max-width: 150px;"
>
<option v-if="localFunction.returns === 'VOID'">
VOID
</option>
<option v-if="!isInDataTypes">
{{ localFunction.returns }}
</option>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="localFunction.returns === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
/>
<input
v-if="customizations.parametersLength"
v-model="localFunction.returnsLength"
@@ -162,10 +132,11 @@
<label class="form-label">
{{ $t('message.sqlSecurity') }}
</label>
<select v-model="localFunction.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
<BaseSelect
v-model="localFunction.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.functionDataAccess" class="column col-auto">
@@ -173,12 +144,11 @@
<label class="form-label">
{{ $t('message.dataAccess') }}
</label>
<select v-model="localFunction.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
<BaseSelect
v-model="localFunction.dataAccess"
:options="['CONTAINS SQL', 'NO SQL', 'READS SQL DATA', 'MODIFIES SQL DATA']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.functionDeterministic" class="column col-auto">
@@ -231,6 +201,7 @@ import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
import ModalAskParameters from '@/components/ModalAskParameters';
import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsFunction',
@@ -238,7 +209,8 @@ export default {
BaseLoader,
QueryEditor,
WorkspaceTabPropsFunctionParamsModal,
ModalAskParameters
ModalAskParameters,
BaseSelect
},
props: {
tabUid: String,

View File

@@ -89,22 +89,15 @@
{{ $t('word.type') }}
</label>
<div class="column">
<select v-model="selectedParamObj.type" class="form-select text-uppercase">
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="selectedParamObj.type.toUpperCase() === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
<BaseSelect
v-model="selectedParamObj.type"
class="form-select text-uppercase"
:options="workspace.dataTypes"
group-label="group"
group-values="types"
option-label="name"
option-track-by="name"
/>
</div>
</div>
<div v-if="customizations.parametersLength" class="form-group">
@@ -174,11 +167,13 @@
<script>
import { uidGen } from 'common/libs/uidGen';
import ConfirmModal from '@/components/BaseConfirmModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsFunctionParamsModal',
components: {
ConfirmModal
ConfirmModal,
BaseSelect
},
props: {
localParameters: {

View File

@@ -66,11 +66,11 @@
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localRoutine.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
<BaseSelect
v-model="localRoutine.language"
:options="customizations.languages"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
@@ -78,27 +78,13 @@
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localRoutine.definer"
:options="[{value: '', name:$t('message.currentUser')}, ...workspace.users]"
:option-label="(user) => user.value === '' ? user.name : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
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="column">
@@ -118,10 +104,11 @@
<label class="form-label">
{{ $t('message.sqlSecurity') }}
</label>
<select v-model="localRoutine.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
<BaseSelect
v-model="localRoutine.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.procedureDataAccess" class="column col-auto">
@@ -129,12 +116,11 @@
<label class="form-label">
{{ $t('message.dataAccess') }}
</label>
<select v-model="localRoutine.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
<BaseSelect
v-model="localRoutine.dataAccess"
:options="['CONTAINS SQL', 'NO SQL', 'READS SQL DATA', 'MODIFIES SQL DATA']"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.procedureDeterministic" class="column col-auto">
@@ -187,6 +173,7 @@ import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
import ModalAskParameters from '@/components/ModalAskParameters';
import Routines from '@/ipc-api/Routines';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsRoutine',
@@ -194,7 +181,8 @@ export default {
QueryEditor,
BaseLoader,
WorkspaceTabPropsRoutineParamsModal,
ModalAskParameters
ModalAskParameters,
BaseSelect
},
props: {
tabUid: String,

View File

@@ -89,22 +89,15 @@
{{ $t('word.type') }}
</label>
<div class="column">
<select v-model="selectedParamObj.type" class="form-select text-uppercase">
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="selectedParamObj.type.toUpperCase() === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
<BaseSelect
v-model="selectedParamObj.type"
class="form-select text-uppercase"
:options="workspace.dataTypes"
group-label="group"
group-values="types"
option-label="name"
option-track-by="name"
/>
</div>
</div>
<div v-if="customizations.parametersLength" class="form-group">
@@ -174,11 +167,13 @@
<script>
import { uidGen } from 'common/libs/uidGen';
import ConfirmModal from '@/components/BaseConfirmModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsRoutineParamsModal',
components: {
ConfirmModal
ConfirmModal,
BaseSelect
},
props: {
localParameters: {

View File

@@ -51,30 +51,13 @@
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localScheduler.definer"
:options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalScheduler.definer">
{{ originalScheduler.definer.replaceAll('`', '') }}
</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 class="column">
@@ -148,13 +131,15 @@ import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
import Schedulers from '@/ipc-api/Schedulers';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsScheduler',
components: {
BaseLoader,
QueryEditor,
WorkspaceTabPropsSchedulerTimingModal
WorkspaceTabPropsSchedulerTimingModal,
BaseSelect
},
props: {
tabUid: String,
@@ -217,6 +202,15 @@ export default {
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalScheduler.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
}
},
watch: {

View File

@@ -18,14 +18,11 @@
{{ $t('word.execution') }}
</label>
<div class="column">
<select
ref="firstInput"
<BaseSelect
v-model="optionsProxy.execution"
:options="['EVERY', 'ONCE']"
class="form-select"
>
<option>EVERY</option>
<option>ONCE</option>
</select>
/>
</div>
</div>
<div v-if="optionsProxy.execution === 'EVERY'">
@@ -39,27 +36,26 @@
type="text"
@keypress="isNumberOrMinus($event)"
>
<select
<BaseSelect
v-model="optionsProxy.every[1]"
class="form-select text-uppercase"
:options="['YEAR',
'QUARTER',
'MONTH',
'WEEK',
'DAY',
'HOUR',
'MINUTE',
'SECOND',
'YEAR_MONTH',
'DAY_HOUR',
'DAY_MINUTE',
'DAY_SECOND',
'HOUR_MINUTE',
'HOUR_SECOND',
'MINUTE_SECOND']"
style="width: 0;"
>
<option>YEAR</option>
<option>QUARTER</option>
<option>MONTH</option>
<option>WEEK</option>
<option>DAY</option>
<option>HOUR</option>
<option>MINUTE</option>
<option>SECOND</option>
<option>YEAR_MONTH</option>
<option>DAY_HOUR</option>
<option>DAY_MINUTE</option>
<option>DAY_SECOND</option>
<option>HOUR_MINUTE</option>
<option>HOUR_SECOND</option>
<option>MINUTE_SECOND</option>
</select>
/>
</div>
</div>
</div>
@@ -140,11 +136,13 @@
<script>
import moment from 'moment';
import ConfirmModal from '@/components/BaseConfirmModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsSchedulerTimingModal',
components: {
ConfirmModal
ConfirmModal,
BaseSelect
},
props: {
localOptions: Object,
@@ -169,10 +167,6 @@ export default {
if (!this.optionsProxy.starts) this.optionsProxy.starts = moment().format('YYYY-MM-DD HH:mm:ss');
if (!this.optionsProxy.ends) this.optionsProxy.ends = moment().format('YYYY-MM-DD HH:mm:ss');
if (!this.optionsProxy.every.length) this.optionsProxy.every = ['1', 'DAY'];
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {

View File

@@ -101,15 +101,13 @@
<label class="form-label">
{{ $t('word.collation') }}
</label>
<select v-model="localOptions.collation" class="form-select">
<option
v-for="collation in workspace.collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
<BaseSelect
v-model="localOptions.collation"
:options="workspace.collations"
option-label="collation"
option-track-by="collation"
class="form-select"
/>
</div>
</div>
<div v-if="workspace.customizations.engines" class="column col-auto">
@@ -117,15 +115,13 @@
<label class="form-label">
{{ $t('word.engine') }}
</label>
<select v-model="localOptions.engine" class="form-select">
<option
v-for="engine in workspace.engines"
:key="engine.name"
:value="engine.name"
>
{{ engine.name }}
</option>
</select>
<BaseSelect
v-model="localOptions.engine"
class="form-select"
:options="workspace.engines"
option-label="name"
option-track-by="name"
/>
</div>
</div>
</div>
@@ -186,6 +182,7 @@ import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsTable',
@@ -193,7 +190,8 @@ export default {
BaseLoader,
WorkspaceTabPropsTableFields,
WorkspaceTabPropsTableIndexesModal,
WorkspaceTabPropsTableForeignModal
WorkspaceTabPropsTableForeignModal,
BaseSelect
},
props: {
tabUid: String,
@@ -440,15 +438,13 @@ export default {
// Fields Changes
const changes = [];
this.originalFields.forEach((originalField, oI) => {
const lI = this.localFields.findIndex(localField => localField._antares_id === originalField._antares_id);
const originalSibling = oI > 0 ? this.originalFields[oI - 1]._antares_id : false;
const localSibling = lI > 0 ? this.localFields[lI - 1]._antares_id : false;
const after = lI > 0 ? this.localFields[lI - 1].name : false;
this.localFields.forEach((field, i) => {
const originalField = this.originalFields.find(oField => oField._antares_id === field._antares_id);
if (!originalField) return;
const after = i > 0 ? this.localFields[i - 1].name : false;
const orgName = originalField.name;
if (JSON.stringify(originalField) !== JSON.stringify(this.localFields[lI]) || originalSibling !== localSibling)
if (this.localFields[lI]) changes.push({ ...this.localFields[lI], after, orgName });
changes.push({ ...field, after, orgName });
});
// OPTIONS

View File

@@ -117,19 +117,14 @@
{{ $t('message.referenceTable') }}
</label>
<div class="column">
<select
<BaseSelect
v-model="selectedForeignObj.refTable"
:options="schemaTables"
option-label="name"
option-track-by="name"
class="form-select"
@change="reloadRefFields"
>
<option
v-for="schemaTable in schemaTables"
:key="schemaTable.name"
:value="schemaTable.name"
>
{{ schemaTable.name }}
</option>
</select>
/>
</div>
</div>
<div class="form-group mb-4">
@@ -153,15 +148,11 @@
{{ $t('message.onUpdate') }}
</label>
<div class="column">
<select v-model="selectedForeignObj.onUpdate" class="form-select">
<option
v-for="action in foreignActions"
:key="action"
:value="action"
>
{{ action }}
</option>
</select>
<BaseSelect
v-model="selectedForeignObj.onUpdate"
:options="foreignActions"
class="form-select"
/>
</div>
</div>
<div class="form-group">
@@ -169,15 +160,11 @@
{{ $t('message.onDelete') }}
</label>
<div class="column">
<select v-model="selectedForeignObj.onDelete" class="form-select">
<option
v-for="action in foreignActions"
:key="action"
:value="action"
>
{{ action }}
</option>
</select>
<BaseSelect
v-model="selectedForeignObj.onDelete"
:options="foreignActions"
class="form-select"
/>
</div>
</div>
</form>
@@ -206,11 +193,13 @@ import { useNotificationsStore } from '@/stores/notifications';
import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables';
import ConfirmModal from '@/components/BaseConfirmModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsTableForeignModal',
components: {
ConfirmModal
ConfirmModal,
BaseSelect
},
props: {
localKeyUsage: Array,

View File

@@ -89,16 +89,12 @@
{{ $t('word.type') }}
</label>
<div class="column">
<select v-model="selectedIndexObj.type" class="form-select">
<option
v-for="index in indexTypes"
:key="index"
:value="index"
:disabled="index === 'PRIMARY' && hasPrimary"
>
{{ index }}
</option>
</select>
<BaseSelect
v-model="selectedIndexObj.type"
:options="indexTypes"
:option-disabled="(opt) => opt === 'PRIMARY'"
class="form-select"
/>
</div>
</div>
<div class="form-group">
@@ -140,11 +136,13 @@
<script>
import { uidGen } from 'common/libs/uidGen';
import ConfirmModal from '@/components/BaseConfirmModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsTableIndexesModal',
components: {
ConfirmModal
ConfirmModal,
BaseSelect
},
props: {
localIndexes: Array,

View File

@@ -49,35 +49,22 @@
v-if="!isInlineEditor.type"
class="cell-content text-left"
:class="typeClass(localRow.type)"
@click="editON($event, localRow.type.toUpperCase(), 'type')"
@dblclick="editON($event, localRow.type.toUpperCase(), 'type')"
>
{{ localRow.type }}
</span>
<select
<BaseSelect
v-else
ref="editField"
v-model="editingContent"
:options="types"
group-label="group"
group-values="types"
option-label="name"
option-track-by="name"
class="form-select editable-field pl-1 pr-4 small-select text-uppercase"
@blur="editOFF"
>
<option v-if="!isInDataTypes">
{{ row.type }}
</option>
<optgroup
v-for="group in dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="localRow.type === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
/>
</div>
<div
v-if="customizations.tableArray"
@@ -214,26 +201,20 @@
<span
v-if="!isInlineEditor.collation"
class="cell-content"
@click="editON($event, localRow.collation, 'collation')"
@dblclick="editON($event, localRow.collation, 'collation')"
>
{{ localRow.collation }}
</span>
<select
<BaseSelect
v-else
ref="editField"
v-model="editingContent"
:options="collations"
option-label="collation"
option-track-by="collation"
class="form-select small-select pl-1 pr-4 editable-field"
@blur="editOFF"
>
<option
v-for="collation in collations"
:key="collation.collation"
:selected="localRow.collation === collation.collation"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
/>
</template>
</div>
<ConfirmModal
@@ -347,11 +328,13 @@ import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import ConfirmModal from '@/components/BaseConfirmModal';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsTableRow',
components: {
ConfirmModal
ConfirmModal,
BaseSelect
},
props: {
row: Object,
@@ -431,6 +414,13 @@ export default {
typeNames = [...groupTypeNames, ...typeNames];
}
return typeNames.includes(this.row.type);
},
types () {
const types = [...this.dataTypes];
if (!this.isInDataTypes)
types.unshift({ name: this.row });
return types;
}
},
watch: {

View File

@@ -45,60 +45,44 @@
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localTrigger.definer"
:options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalTrigger.definer">
{{ originalTrigger.definer.replaceAll('`', '') }}
</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>
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label>
<select v-model="localTrigger.table" class="form-select">
<option v-for="table in schemaTables" :key="table.name">
{{ table.name }}
</option>
</select>
<BaseSelect
v-model="localTrigger.table"
:options="schemaTables"
option-label="name"
option-track-by="name"
class="form-select"
/>
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label>
<div class="input-group">
<select v-model="localTrigger.activation" class="form-select">
<option>BEFORE</option>
<option>AFTER</option>
</select>
<select
<BaseSelect
v-model="localTrigger.activation"
:options="['BEFORE', 'AFTER']"
class="form-select"
/>
<BaseSelect
v-if="!customizations.triggerMultipleEvents"
v-model="localTrigger.event"
:options="Object.keys(localEvents)"
class="form-select"
>
<option v-for="event in Object.keys(localEvents)" :key="event">
{{ event }}
</option>
</select>
/>
<div v-if="customizations.triggerMultipleEvents" class="px-4">
<label
v-for="event in Object.keys(localEvents)"
@@ -137,12 +121,14 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
import Triggers from '@/ipc-api/Triggers';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsTrigger',
components: {
BaseLoader,
QueryEditor
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
@@ -208,6 +194,15 @@ export default {
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalTrigger.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
}
},
watch: {

View File

@@ -32,11 +32,11 @@
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
<BaseSelect
v-model="localFunction.language"
:options="customizations.triggerFunctionlanguages"
class="form-select"
/>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
@@ -44,27 +44,13 @@
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localFunction.definer"
:options="workspace.users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
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">
@@ -110,13 +96,15 @@ import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import ModalAskParameters from '@/components/ModalAskParameters';
import Functions from '@/ipc-api/Functions';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsTriggerFunction',
components: {
BaseLoader,
QueryEditor,
ModalAskParameters
ModalAskParameters,
BaseSelect
},
props: {
tabUid: String,

View File

@@ -45,61 +45,44 @@
<div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
<BaseSelect
v-model="localView.definer"
:options="users"
:option-label="(user) => user.value === '' ? $t('message.currentUser') : `${user.name}@${user.host}`"
:option-track-by="(user) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalView.definer">
{{ originalView.definer.replaceAll('`', '') }}
</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 class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<select v-model="localView.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
<BaseSelect
v-model="localView.security"
:options="['DEFINER', 'INVOKER']"
class="form-select"
/>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<select v-model="localView.algorithm" class="form-select">
<option>UNDEFINED</option>
<option>MERGE</option>
<option>TEMPTABLE</option>
</select>
<BaseSelect
v-model="localView.algorithm"
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
class="form-select"
/>
</div>
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<select v-model="localView.updateOption" class="form-select">
<option value="">
None
</option>
<option>CASCADED</option>
<option>LOCAL</option>
</select>
<BaseSelect
v-model="localView.updateOption"
:option-track-by="(user) => user.value"
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
class="form-select"
/>
</div>
</div>
</div>
@@ -126,12 +109,14 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Views from '@/ipc-api/Views';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabPropsView',
components: {
BaseLoader,
QueryEditor
QueryEditor,
BaseSelect
},
props: {
tabUid: String,
@@ -184,6 +169,15 @@ export default {
},
isDefinerInUsers () {
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
users () {
const users = [{ value: '' }, ...this.workspace.users];
if (!this.isDefinerInUsers) {
const [name, host] = this.originalView.definer.replaceAll('`', '').split('@');
users.unshift({ name, host });
}
return users;
}
},
watch: {

View File

@@ -21,7 +21,7 @@
:height="editorHeight"
/>
<div ref="resizer" class="query-area-resizer" />
<div class="workspace-query-runner-footer">
<div ref="queryAreaFooter" class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)">
<button
@@ -115,14 +115,13 @@
</div>
<div class="input-group pr-2" :title="$t('message.commitMode')">
<i class="input-group-addon addon-sm mdi mdi-24px mdi-source-commit p-0" />
<select v-model="autocommit" class="form-select select-sm text-bold">
<option :value="true">
{{ $t('message.autoCommit') }}
</option>
<option :value="false">
{{ $t('message.manualCommit') }}
</option>
</select>
<BaseSelect
v-model="autocommit"
:options="[{value: true, label: $t('message.autoCommit')}, {value: false, label: $t('message.manualCommit')}]"
:option-label="opt => opt.label"
:option-track-by="opt => opt.value"
class="form-select select-sm text-bold"
/>
</div>
</div>
<div class="workspace-query-info">
@@ -149,14 +148,12 @@
</div>
<div class="input-group" :title="$t('word.schema')">
<i class="input-group-addon addon-sm mdi mdi-24px mdi-database" />
<select v-model="selectedSchema" class="form-select select-sm text-bold">
<option :value="null">
{{ $t('message.noSchema') }}
</option>
<option v-for="schemaName in databaseSchemas" :key="schemaName">
{{ schemaName }}
</option>
</select>
<BaseSelect
v-model="selectedSchema"
:options="[{value: null, label: $t('message.noSchema')}, ...databaseSchemas.map(el => ({label: el, value: el}))]"
class="form-select select-sm text-bold"
/>
</div>
</div>
</div>
@@ -199,6 +196,7 @@ import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState';
import ModalHistory from '@/components/ModalHistory';
import tableTabs from '@/mixins/tableTabs';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabQuery',
@@ -207,7 +205,8 @@ export default {
QueryEditor,
WorkspaceTabQueryTable,
WorkspaceTabQueryEmptyState,
ModalHistory
ModalHistory,
BaseSelect
},
mixins: [tableTabs],
props: {
@@ -255,7 +254,8 @@ export default {
durationsCount: 0,
affectedCount: null,
editorHeight: 200,
isHistoryOpen: false
isHistoryOpen: false,
debounceTimeout: null
};
},
computed: {
@@ -285,6 +285,19 @@ export default {
}
},
watch: {
query (val) {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = setTimeout(() => {
this.updateTabContent({
uid: this.connection.uid,
tab: this.tab.uid,
type: 'query',
schema: this.selectedSchema,
content: val
});
}, 200);
},
isSelected (val) {
if (val) {
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
@@ -306,6 +319,7 @@ export default {
this.selectedSchema = null;
window.addEventListener('keydown', this.onKey);
window.addEventListener('resize', this.onWindowResize);
},
mounted () {
const resizer = this.$refs.resizer;
@@ -321,6 +335,7 @@ export default {
this.runQuery(this.query);
},
beforeUnmount () {
window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener('keydown', this.onKey);
const params = {
uid: this.connection.uid,
@@ -357,13 +372,6 @@ export default {
return acc + (curr.report ? curr.report.affectedRows : 0);
}, null);
this.updateTabContent({
uid: this.connection.uid,
tab: this.tab.uid,
type: 'query',
schema: this.selectedSchema,
content: query
});
this.saveHistory(params);
if (!this.autocommit)
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: true });
@@ -412,11 +420,24 @@ export default {
},
resize (e) {
const el = this.$refs.queryEditor.$el;
let editorHeight = e.pageY - el.getBoundingClientRect().top;
if (editorHeight > 400) editorHeight = 400;
const queryFooterHeight = this.$refs.queryAreaFooter.clientHeight;
const bottom = e.pageY || this.$refs.resizer.getBoundingClientRect().bottom;
const maxHeight = window.innerHeight - 100 - queryFooterHeight;
let editorHeight = bottom - el.getBoundingClientRect().top;
if (editorHeight > maxHeight) editorHeight = maxHeight;
if (editorHeight < 50) editorHeight = 50;
this.editorHeight = editorHeight;
},
onWindowResize (e) {
const el = this.$refs.queryEditor.$el;
const queryFooterHeight = this.$refs.queryAreaFooter.clientHeight;
const bottom = e.pageY || this.$refs.resizer.getBoundingClientRect().bottom;
const maxHeight = window.innerHeight - 100 - queryFooterHeight;
const editorHeight = bottom - el.getBoundingClientRect().top;
if (editorHeight > maxHeight)
this.editorHeight = maxHeight;
},
stopResize () {
window.removeEventListener('mousemove', this.resize);
if (this.$refs.queryTable && this.results.length)
@@ -513,9 +534,8 @@ export default {
position: relative;
.query-area-resizer {
position: absolute;
height: 4px;
bottom: 40px;
margin-top: -2px;
width: 100%;
cursor: ns-resize;
z-index: 99;

View File

@@ -5,7 +5,6 @@
tabindex="0"
:style="{'height': resultsSize+'px'}"
@keyup.delete="showDeleteConfirmModal"
@keydown.ctrl.a="selectAllRows($event)"
@keydown.esc="deselectRows"
>
<TableContext
@@ -78,7 +77,11 @@
:key-usage="keyUsage"
:element-type="elementType"
:class="{'selected': selectedRows.includes(row._antares_id)}"
@select-row="selectRow($event, row._antares_id)"
:selected="selectedRows.includes(row._antares_id)"
:selected-cell="selectedRows.length === 1 && selectedRows.includes(row._antares_id) ? selectedField : null"
@start-editing="isEditingRow = true"
@stop-editing="isEditingRow = false"
@select-row="selectRow"
@update-field="updateField($event, row)"
@contextmenu="contextMenu"
/>
@@ -162,7 +165,9 @@ export default {
currentSortDir: 'asc',
resultsetIndex: 0,
scrollElement: null,
rowHeight: 23
rowHeight: 23,
selectedField: null,
isEditingRow: false
};
},
computed: {
@@ -268,9 +273,11 @@ export default {
},
mounted () {
window.addEventListener('resize', this.resizeResults);
window.addEventListener('keydown', this.onKey);
},
unmounted () {
window.removeEventListener('resize', this.resizeResults);
window.removeEventListener('keydown', this.onKey);
},
methods: {
fieldType (cKey) {
@@ -447,20 +454,23 @@ export default {
return row;
});
},
selectRow (event, row) {
if (event.ctrlKey) {
if (this.selectedRows.includes(row))
this.selectedRows = this.selectedRows.filter(el => el !== row);
selectRow (event, row, field) {
this.selectedField = field;
const selectedRowId = row._antares_id;
if (event.ctrlKey || event.metaKey) {
if (this.selectedRows.includes(selectedRowId))
this.selectedRows = this.selectedRows.filter(el => el !== selectedRowId);
else
this.selectedRows.push(row);
this.selectedRows.push(selectedRowId);
}
else if (event.shiftKey) {
if (!this.selectedRows.length)
this.selectedRows.push(row);
this.selectedRows.push(selectedRowId);
else {
const lastID = this.selectedRows.slice(-1)[0];
const lastIndex = this.sortedResults.findIndex(el => el._antares_id === lastID);
const clickedIndex = this.sortedResults.findIndex(el => el._antares_id === row);
const clickedIndex = this.sortedResults.findIndex(el => el._antares_id === selectedRowId);
if (lastIndex > clickedIndex) {
for (let i = clickedIndex; i < lastIndex; i++)
this.selectedRows.push(this.sortedResults[i]._antares_id);
@@ -472,18 +482,20 @@ export default {
}
}
else
this.selectedRows = [row];
this.selectedRows = [selectedRowId];
},
selectAllRows (e) {
if (e.target.classList.contains('editable-field')) return;
this.selectedField = 0;
this.selectedRows = this.localResults.reduce((acc, curr) => {
acc.push(curr._antares_id);
return acc;
}, []);
},
deselectRows () {
this.selectedRows = [];
if (!this.isEditingRow)
this.selectedRows = [];
},
contextMenu (event, cell) {
if (event.target.localName === 'input') return;
@@ -536,6 +548,113 @@ export default {
content: rows,
filename
});
},
onKey (e) {
if (!this.isSelected)
return;
if (this.isEditingRow)
return;
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyA' && !e.altKey)
this.selectAllRows(e);
// row naviation stuff
if ((e.code.includes('Arrow') || e.code === 'Tab') && this.sortedResults.length > 0 && !e.altKey) {
e.preventDefault();
const aviableFields= Object.keys(this.sortedResults[0]).slice(0, -1); // removes _antares_id
if (!this.selectedField)
this.selectedField = aviableFields[0];
const selectedId = this.selectedRows[0];
const selectedIndex = this.sortedResults.findIndex(row => row._antares_id === selectedId);
const selectedFieldIndex = aviableFields.findIndex(field => field === this.selectedField);
let nextIndex = 0;
let nextFieldIndex = 0;
if (selectedIndex > -1) {
switch (e.code) {
case 'ArrowDown':
nextIndex = selectedIndex + 1;
nextFieldIndex = selectedFieldIndex;
if (nextIndex > this.sortedResults.length -1)
nextIndex = this.sortedResults.length -1;
break;
case 'ArrowUp':
nextIndex = selectedIndex - 1;
nextFieldIndex = selectedFieldIndex;
if (nextIndex < 0)
nextIndex = 0;
break;
case 'ArrowRight':
nextIndex = selectedIndex;
nextFieldIndex = selectedFieldIndex + 1;
if (nextFieldIndex > aviableFields.length -1)
nextFieldIndex = 0;
break;
case 'ArrowLeft':
nextIndex = selectedIndex;
nextFieldIndex = selectedFieldIndex - 1;
if (nextFieldIndex < 0)
nextFieldIndex = aviableFields.length -1;
break;
case 'Tab':
nextIndex = selectedIndex;
if (e.shiftKey) {
nextFieldIndex = selectedFieldIndex - 1;
if (nextFieldIndex < 0)
nextFieldIndex = aviableFields.length -1;
}
else {
nextFieldIndex = selectedFieldIndex + 1;
if (nextFieldIndex > aviableFields.length -1)
nextFieldIndex = 0;
}
}
}
if (this.sortedResults[nextIndex] && nextIndex !== selectedIndex) {
this.selectedRows = [this.sortedResults[nextIndex]._antares_id];
this.$nextTick(() => this.scrollToCell(this.scrollElement.querySelector('.td.selected')));
}
if (aviableFields[nextFieldIndex] && nextFieldIndex !== selectedFieldIndex) {
this.selectedField = aviableFields[nextFieldIndex];
this.$nextTick(() => this.scrollToCell(this.scrollElement.querySelector('.td.selected')));
}
}
},
scrollToCell (el) {
if (!el) return;
const visYMin = this.scrollElement.scrollTop;
const visYMax = this.scrollElement.scrollTop + this.scrollElement.clientHeight - el.clientHeight;
const visXMin = this.scrollElement.scrollLeft;
const visXMax = this.scrollElement.scrollLeft + this.scrollElement.clientWidth - el.clientWidth;
if (el.offsetTop < visYMin)
this.scrollElement.scrollTop = el.offsetTop;
else if (el.offsetTop >= visYMax)
this.scrollElement.scrollTop = el.offsetTop - this.scrollElement.clientHeight + el.clientHeight;
if (el.offsetLeft < visXMin)
this.scrollElement.scrollLeft = el.offsetLeft;
else if (el.offsetLeft >= visXMax)
this.scrollElement.scrollLeft = el.offsetLeft - this.scrollElement.clientWidth + el.clientWidth;
}
}
};

View File

@@ -2,14 +2,15 @@
<div
class="tr"
:style="{height: itemHeight+'px'}"
@click="selectRow($event, row._antares_id)"
>
<div
v-for="(col, cKey) in row"
v-show="cKey !== '_antares_id'"
:key="cKey"
class="td p-0"
tabindex="0"
:class="{selected: selectedCell === cKey}"
@click="selectRow($event, cKey)"
@contextmenu.prevent="openContext($event, { id: row._antares_id, orgField: cKey })"
>
<template v-if="cKey !== '_antares_id'">
@@ -17,7 +18,7 @@
v-if="!isInlineEditor[cKey] && fields[cKey]"
class="cell-content"
:class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON($event, col, cKey)"
@dblclick="editON(cKey)"
>{{ cutText(typeFormat(col, fields[cKey].type.toLowerCase(), fields[cKey].length)) }}</span>
<ForeignKeySelect
v-else-if="isForeignKey(cKey)"
@@ -38,25 +39,21 @@
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
<select
<BaseSelect
v-else-if="inputProps.type === 'boolean'"
v-model="editingContent"
:options="['true', 'false']"
class="form-select small-select editable-field"
@blur="editOFF"
>
<option>true</option>
<option>false</option>
</select>
<select
/>
<BaseSelect
v-else-if="enumArray"
v-model="editingContent"
:options="enumArray"
class="form-select small-select editable-field"
dropdown-class="small-select"
@blur="editOFF"
>
<option v-for="value in enumArray" :key="value">
{{ value }}
</option>
</select>
/>
<input
v-else
ref="editField"
@@ -95,26 +92,21 @@
<label for="editorMode" class="form-label mr-2">
<b>{{ $t('word.content') }}</b>:
</label>
<select
<BaseSelect
id="editorMode"
v-model="editorMode"
:options="availableLanguages"
option-label="name"
option-track-by="slug"
class="form-select select-sm"
>
<option
v-for="language in availableLanguages"
:key="language.slug"
:value="language.slug"
>
{{ language.name }}
</option>
</select>
/>
</div>
<div class="d-flex">
<div class="p-vcentered">
<div class="mr-4">
<b>{{ $t('word.size') }}</b>: {{ editingContent ? editingContent.length : 0 }}
</div>
<div>
<div v-if="editingType">
<b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}
</div>
</div>
@@ -179,7 +171,9 @@
<b>{{ $t('word.size') }}</b>: {{ formatBytes(editingContent.length) }}<br>
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
</div>
<div><b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}</div>
<div v-if="editingType">
<b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}
</div>
</div>
<div class="mt-3">
<label>{{ $t('message.uploadFile') }}</label>
@@ -223,6 +217,7 @@ import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
import BaseMap from '@/components/BaseMap';
import ForeignKeySelect from '@/components/ForeignKeySelect';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
name: 'WorkspaceTabQueryTableRow',
@@ -230,16 +225,19 @@ export default {
ConfirmModal,
TextEditor,
ForeignKeySelect,
BaseMap
BaseMap,
BaseSelect
},
props: {
row: Object,
fields: Object,
keyUsage: Array,
itemHeight: Number,
elementType: { type: String, default: 'table' }
elementType: { type: String, default: 'table' },
selected: { type: Boolean, default: false },
selectedCell: { type: String, default: null }
},
emits: ['update-field', 'select-row', 'contextmenu'],
emits: ['update-field', 'select-row', 'contextmenu', 'start-editing', 'stop-editing'],
data () {
return {
isInlineEditor: {},
@@ -343,6 +341,9 @@ export default {
return false;
},
isBaseSelectField () {
return this.isForeignKey(this.editingField) || this.inputProps.type === 'boolean' || this.enumArray;
},
enumArray () {
if (this.fields[this.editingField] && this.fields[this.editingField].enumValues)
return this.fields[this.editingField].enumValues.replaceAll('\'', '').split(',');
@@ -369,8 +370,21 @@ export default {
this.editorMode = this.availableLanguages.find(lang => lang.id === filteredLanguages[0].languageId).slug;
})();
}
},
selected (isSelected) {
if (isSelected)
window.addEventListener('keydown', this.onKey);
else {
this.editOFF();
window.removeEventListener('keydown', this.onKey);
}
}
},
beforeUnmount () {
if (this.selected)
window.removeEventListener('keydown', this.onKey);
},
methods: {
isForeignKey (key) {
if (key.includes('.'))
@@ -389,11 +403,10 @@ export default {
bufferToBase64 (val) {
return bufferToBase64(val);
},
editON (event, content, field) {
editON (field) {
if (!this.isEditable || this.editingType === 'none') return;
window.addEventListener('keydown', this.onKey);
const content = this.row[field];
const type = this.fields[field].type.toUpperCase();
this.originalContent = this.typeFormat(content, type, this.fields[field].length);
this.editingType = type;
@@ -403,6 +416,7 @@ export default {
if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type)) {
this.isTextareaEditor = true;
this.editingContent = this.typeFormat(content, type);
this.$emit('start-editing', field);
return;
}
@@ -412,6 +426,7 @@ export default {
this.isMapModal = true;
this.editingContent = this.typeFormat(content, type);
}
this.$emit('start-editing', field);
return;
}
@@ -433,19 +448,21 @@ export default {
};
}
}
this.$emit('start-editing', field);
return;
}
// Inline editable fields
this.editingContent = this.originalContent;
this.$nextTick(() => { // Focus on input
event.target.blur();
this.$nextTick(() => document.querySelector('.editable-field').focus());
});
const obj = { [field]: true };
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
this.$nextTick(() => { // Focus on input
document.querySelector('.editable-field').focus();
});
this.$emit('start-editing', field);
},
editOFF () {
if (!this.editingField) return;
@@ -458,7 +475,13 @@ export default {
this.editingContent = this.editingContent.slice(0, -1);
}
if (this.editingContent === this.typeFormat(this.originalContent, this.editingType, this.editingLength)) return;// If not changed
// If not changed
if (this.editingContent === this.typeFormat(this.originalContent, this.editingType, this.editingLength)) {
this.editingType = null;
this.editingField = null;
this.$emit('stop-editing', this.editingField);
return;
}
content = this.editingContent;
}
@@ -479,15 +502,17 @@ export default {
content
});
this.$emit('stop-editing', this.editingField);
this.editingType = null;
this.editingField = null;
window.removeEventListener('keydown', this.onKey);
},
hideEditorModal () {
this.isTextareaEditor = false;
this.isBlobEditor = false;
this.isMapModal = false;
this.isMultiSpatial = false;
this.$emit('stop-editing', this.editingField);
},
downloadFile () {
const downloadLink = document.createElement('a');
@@ -515,8 +540,8 @@ export default {
};
this.willBeDeleted = true;
},
selectRow (event, row) {
this.$emit('select-row', event, row);
selectRow (event, field) {
this.$emit('select-row', event, this.row, field);
},
getKeyUsage (keyName) {
if (keyName.includes('.'))
@@ -530,10 +555,17 @@ export default {
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape') {
if (!this.editingField && e.key === 'Enter')
return this.editON(this.selectedCell);
if (this.editingField && e.key === 'Enter' && !this.isBaseSelectField)
return this.editOFF();
if (this.editingField && e.key === 'Escape') {
this.isInlineEditor[this.editingField] = false;
this.editingField = null;
window.removeEventListener('keydown', this.onKey);
this.$emit('stop-editing', this.editingField);
}
},
formatBytes,

View File

@@ -12,24 +12,18 @@
@change="doFilter"
><i class="form-icon" />
</label>
<select v-model="row.field" class="form-select col-auto select-sm">
<option
v-for="(item, j) of fields"
:key="j"
:value="item.name"
>
{{ item.name }}
</option>
</select>
<select v-model="row.op" class="form-select ml-2 col-auto select-sm">
<option
v-for="(operator, k) of operators"
:key="k"
:value="operator"
>
{{ operator }}
</option>
</select>
<BaseSelect
v-model="row.field"
class="form-select ml-2 col-auto select-sm"
:options="fields"
option-track-by="name"
option-label="name"
/>
<BaseSelect
v-model="row.op"
class="form-select ml-2 col-auto select-sm"
:options="operators"
/>
<div class="workspace-table-filters-row-value ml-2">
<input
v-if="!row.op.includes('NULL')"
@@ -73,8 +67,12 @@
<script>
import customizations from 'common/customizations';
import { NUMBER, FLOAT } from 'common/fieldTypes';
import BaseSelect from '@/components/BaseSelect.vue';
export default {
components: {
BaseSelect
},
props: {
fields: Array,
connClient: String

View File

@@ -12,7 +12,8 @@ const i18n = createI18n({
'de-DE': require('./de-DE'),
'vi-VN': require('./vi-VN'),
'ja-JP': require('./ja-JP'),
'zh-CN': require('./zh-CN')
'zh-CN': require('./zh-CN'),
'ru-RU': require('./ru-RU')
}
});
export default i18n;

View File

@@ -107,37 +107,37 @@ module.exports = {
changelog: 'Logs de alteração',
format: 'Formato',
sshTunnel: 'SSH túnel',
structure: 'Estrutura',
small: 'Pequeno',
medium: 'Médio',
large: 'Grande',
row: 'Linha | Linhas',
cell: 'Celula | Células',
triggerFunction: 'Gatinho de função | Gatilhos de Funções',
all: 'Todos',
duplicate: 'Duplicado',
routine: 'Rotina',
new: 'Novo',
history: 'Histórico',
select: 'Seleciomar',
passphrase: 'Palavara-Passe',
filter: 'Filtrar',
change: 'Alterar',
views: 'Visualizações',
triggers: 'Gatilhos',
routines: 'Rotinas',
functions: 'Funções',
schedulers: 'Agendadores',
includes: 'Includes',
drop: 'Drop',
completed: 'Completo',
aborted: 'Abortado',
disabled: 'Inativo',
enable: 'Ativo',
disable: 'Disable',
commit: 'Enviar',
rollback: 'Reverter',
connectionString: 'String da conexão',
structure: 'Estrutura',
small: 'Pequeno',
medium: 'Médio',
large: 'Grande',
row: 'Linha | Linhas',
cell: 'Celula | Células',
triggerFunction: 'Gatinho de função | Gatilhos de Funções',
all: 'Todos',
duplicate: 'Duplicado',
routine: 'Rotina',
new: 'Novo',
history: 'Histórico',
select: 'Seleciomar',
passphrase: 'Palavara-Passe',
filter: 'Filtrar',
change: 'Alterar',
views: 'Visualizações',
triggers: 'Gatilhos',
routines: 'Rotinas',
functions: 'Funções',
schedulers: 'Agendadores',
includes: 'Includes',
drop: 'Drop',
completed: 'Completo',
aborted: 'Abortado',
disabled: 'Inativo',
enable: 'Ativo',
disable: 'Disable',
commit: 'Enviar',
rollback: 'Reverter',
connectionString: 'String da conexão',
contributors: 'Contribuintes'
},
message: {

459
src/renderer/i18n/ru-RU.js Normal file
View File

@@ -0,0 +1,459 @@
module.exports = {
word: {
edit: 'Редактировать',
save: 'Сохранить',
close: 'Закрыть',
delete: 'Удалить',
confirm: 'Подтвердить',
cancel: 'Отмена',
send: 'Отправить',
connectionName: 'Название соединения',
client: 'Клиент',
hostName: 'Название хоста',
port: 'Порт',
user: 'Пользователь',
password: 'Пароль',
credentials: 'Полномочия',
connect: 'Подключить',
connected: 'Подключено',
disconnect: 'Отключить',
disconnected: 'Отключено',
refresh: 'Обновить',
settings: 'Настройка',
general: 'Общие',
themes: 'Темы',
update: 'Обновить',
about: 'О программе',
language: 'Язык',
version: 'Версия',
donate: 'Пожертвование',
run: 'Запуск',
schema: 'Схема',
results: 'Результаты',
size: 'Размер',
seconds: 'Секунды',
type: 'Тип',
mimeType: 'Mime-Тип',
download: 'Скачать',
add: 'Добавить',
data: 'Данные',
properties: 'Свойства',
insert: 'Вставить',
connecting: 'Соединение',
name: 'Название',
collation: 'Сопоставление',
clear: 'Очистить',
options: 'Опции',
autoRefresh: 'Авто-обновление',
indexes: 'Индексы',
foreignKeys: 'Внешние ключи',
length: 'Длина',
unsigned: 'Неподписанный',
default: 'По умолчанию',
comment: 'Комментарий',
key: 'Ключ | Ключи',
order: 'Заказ',
expression: 'Выражение',
autoIncrement: 'Авто-увеличение',
engine: 'Движок',
field: 'Поле | Поля',
approximately: 'Примерно',
total: 'Всего',
table: 'Таблица',
discard: 'Отказать',
stay: 'Оставить',
author: 'Автор',
light: 'Светлая',
dark: 'Темная',
autoCompletion: 'Авто-дополнение',
application: 'Приложение',
editor: 'Реадктор',
view: 'Просмотр',
definer: 'Определитель',
algorithm: 'Алгоритм',
trigger: 'Триггер | Триггеры',
storedRoutine: 'Сохраненная процедура | Сохраненные процедуры',
scheduler: 'Планировщик | Планировщики',
event: 'Событие',
parameters: 'Параметры',
function: 'Функция | Функции',
deterministic: 'Детерминированный',
context: 'Контекст',
export: 'Экспорт',
import: 'Импорт',
returns: 'Вернуть',
timing: 'Сроки',
state: 'Состояние',
execution: 'Выполнение',
starts: 'Начинает',
ends: 'Заканчивает',
ssl: 'SSL',
privateKey: 'Закрытый ключ',
certificate: 'Сертификат',
caCertificate: 'CA сертификат',
ciphers: 'Шифры',
upload: 'Загрузки',
browse: 'Обзор',
faker: 'Faker',
content: 'Содержимое',
cut: 'Вырезать',
copy: 'Копировать',
paste: 'Вставить',
tools: 'Инструменты',
variables: 'Переменные',
processes: 'Процессы',
database: 'База данных',
scratchpad: 'Заметки',
array: 'Массив',
changelog: 'Журнал изменений',
format: 'Формат',
sshTunnel: 'SSH туннель',
structure: 'Структура',
small: 'Малый',
medium: 'Средний',
large: 'Большой',
row: 'Строка | Строки',
cell: 'Ячейка | Ячейки',
triggerFunction: 'Функция запуска | Функции запуска',
all: 'Все',
duplicate: 'Дубликат',
routine: 'Порядок',
new: 'Новый',
history: 'История',
select: 'Выбрать',
passphrase: 'Кодовая фраза',
filter: 'Фильтр',
change: 'Изменить',
views: 'Просмотры',
triggers: 'Триггеры',
routines: 'Порядок',
functions: 'Функции',
schedulers: 'Планировщики',
includes: 'Включает',
drop: 'Сбросить',
completed: 'Завершено',
aborted: 'Aborted',
disabled: 'Прервано',
enable: 'Включить',
disable: 'Выключить',
commit: 'Подтвердить',
rollback: 'Отмена',
connectionString: 'Строка подключения',
contributors: 'Участники'
},
message: {
appWelcome: 'Приветсвуем в SQL клиенте Antares!',
appFirstStep: 'Ваш первый шаг: создать новое подключение с БД.',
addConnection: 'Добавить подключение',
createConnection: 'Создать подключение',
createNewConnection: 'Созадть новое подключение',
askCredentials: 'Спрашивать учетные данные',
testConnection: 'Тестирование подключения',
editConnection: 'Редактирование подключения',
deleteConnection: 'Удалить подключение',
deleteCorfirm: 'Подтверждаете ли вы отмену',
connectionSuccessfullyMade: 'Соединение успешно установлено!',
madeWithJS: 'Сделано с 💛 и JavaScript!',
checkForUpdates: 'Проверить обновления',
noUpdatesAvailable: 'Обновлений не найдено',
checkingForUpdate: 'Проверить обновления',
checkFailure: 'Проверка не удалась, пожалуйста, попробуйте позже',
updateAvailable: 'Доступно обновление',
downloadingUpdate: 'Скачать обновление',
updateDownloaded: 'Обновление скачано',
restartToInstall: 'Перезапустить Antares для установки',
unableEditFieldWithoutPrimary: 'Невозможно отредактировать поле без первичного ключа в наборе результатов',
editCell: 'Редактировать ячейку',
deleteRows: 'Удалить строку | Удалить {count} строк',
confirmToDeleteRows: 'Подтверждаете удаление строки? | Подтверждаете удаление {count} строк?',
notificationsTimeout: 'Тайм-аут уведомлений',
uploadFile: 'Загрузить файл',
addNewRow: 'Добавить новую строку',
numberOfInserts: 'Количество вставок',
openNewTab: 'Открыть новую вкладку',
affectedRows: 'Задействовано строк',
createNewDatabase: 'Создать новую БД',
databaseName: 'Название БД',
serverDefault: 'Сервер по умолчанию',
deleteDatabase: 'Удалить БД',
editDatabase: 'Редактировать БД',
clearChanges: 'Очистить изменения',
addNewField: 'Добавить новое поле',
manageIndexes: 'Управление индексами',
manageForeignKeys: 'Управление внешними ключами',
allowNull: 'Разрешить NULL',
zeroFill: 'Заполнить нулями',
customValue: 'Пользовательское значение',
onUpdate: 'При обновлении',
deleteField: 'Удалить поле',
createNewIndex: 'Создать новый индекс',
addToIndex: 'Добавить индекс',
createNewTable: 'Создать новую таблицу',
emptyTable: 'Пустая таблица',
deleteTable: 'Удалить таблицу',
emptyCorfirm: 'Подтверждаете очистку?',
unsavedChanges: 'Несохраненные изменения',
discardUnsavedChanges: 'У вас имеются несохраненные данные. Закрытие этой вкладки приведёт к их отмене.',
thereAreNoIndexes: 'Индексов нет',
thereAreNoForeign: 'Внешних ключей нет',
createNewForeign: 'Создать новый внешний ключ',
referenceTable: 'Ссылка на таблицу',
referenceField: 'Ссылка на поле',
foreignFields: 'Сторонние поля',
invalidDefault: 'Недопустимое значение',
onDelete: 'При удалении',
applicationTheme: 'Тема приложения',
editorTheme: 'Редактировать Тему',
wrapLongLines: 'Перенос длинных строк',
selectStatement: 'Оператор выбора',
triggerStatement: 'Оператор триггера',
sqlSecurity: 'SQL безопасность',
updateOption: 'Опции обновления',
deleteView: 'Удалить просмотр',
createNewView: 'Создать новый просмотр',
deleteTrigger: 'Удалить триггер',
createNewTrigger: 'Создать новый триггер',
currentUser: 'Текущий пользователь',
routineBody: 'Routine body',
dataAccess: 'Доступ к данным',
thereAreNoParameters: 'Там нет никаких параметров',
createNewParameter: 'Создать новый параметр',
createNewRoutine: 'Создание новой сохраненной процедуры',
deleteRoutine: 'Удаление сохраненной процедуры',
functionBody: 'Тело функции',
createNewFunction: 'Создать новую функцию',
deleteFunction: 'Удалить функцию',
schedulerBody: 'Тело планировщика',
createNewScheduler: 'Создать новый планировщик',
deleteScheduler: 'Удалить планировщик',
preserveOnCompletion: 'Сохранение по завершении',
enableSsl: 'Включить SSL',
manualValue: 'Установить значение вручную',
tableFiller: 'Фильтр таблицы',
fakeDataLanguage: 'Язык поддельных данных',
searchForElements: 'Поиск элементов',
selectAll: 'Выбрать все',
queryDuration: 'Длительность запроса',
includeBetaUpdates: 'Получать бета-версии обновлений',
setNull: 'Установить NULL',
processesList: 'Список процессов',
processInfo: 'Информация о процессе',
manageUsers: 'Управление пользователями',
createNewSchema: 'Создать новую схему',
schemaName: 'Название схемы',
editSchema: 'Редактировать схему',
deleteSchema: 'Удалить схему',
markdownSupported: 'Поддержка Markdown',
plantATree: 'Посадить дерево',
dataTabPageSize: 'Размер страницы вкладки ДАННЫЕ',
enableSsh: 'Включить SSH',
pageNumber: 'Номер страницы',
duplicateTable: 'Дубликат таблицы',
noOpenTabs: 'Открытых вкладок нет, перейдите по левой панели или:',
noSchema: 'Нет схемы',
restorePreviourSession: 'Восстановить предыдущую сессию',
runQuery: 'Запуск очереди',
thereAreNoTableFields: 'В таблице нет полей',
newTable: 'Новая таблица',
newView: 'Новый просмотр',
newTrigger: 'Новый триггер',
newRoutine: 'New routine',
newFunction: 'Новая функция',
newScheduler: 'Новый планировщик',
newTriggerFunction: 'Новая функция триггера',
thereIsNoQueriesYet: 'Пока нет никаких запросов',
searchForQueries: 'Поиск по запросам',
killProcess: 'Убить процесс',
closeTab: 'Закрыть вкладку',
exportSchema: 'Экспорт схемы',
importSchema: 'Импорт схемы',
directoryPath: 'Путь к каталогу',
newInserStmtEvery: 'New INSERT statement every',
processingTableExport: 'Обработка {table}',
fechingTableExport: 'Получение данных с {table}',
writingTableExport: 'Запись данных в {table}',
checkAllTables: 'Проверить все таблицы',
uncheckAllTables: 'Убрать со всех таблиц',
goToDownloadPage: 'Перейти на страницу для загрузки',
readOnlyMode: 'Режим только чтение',
killQuery: 'Убить запрос',
insertRow: 'Вставить строку | Вставить строки',
commitMode: 'Режим подтверждения',
autoCommit: 'Авто-подтверждение',
manualCommit: 'Ручное подтверждение',
actionSuccessful: '{action} успешно',
importQueryErrors: 'Внимание: {n} ошибка возникла | Внимание: {n} ошибок произошло',
executedQueries: '{n} запрос выполнен | {n} запросов выполнено',
ourputFormat: 'Формат вывода',
singleFile: 'Один {ext} файл',
zipCompressedFile: 'ZIP сжатие {ext} файла',
disableBlur: 'Отключить размытие',
untrustedConnection: 'Ненадежное соединение',
missingOrIncompleteTranslation: 'Отсутствующий или неполный перевод?',
findOutHowToContribute: 'Узнайте, как внести свой вклад'
},
faker: {
address: 'Адрес',
commerce: 'Коммерция',
company: 'Компания',
database: 'База данных',
date: 'Дата',
finance: 'Финансы',
git: 'Git',
hacker: 'Хакер',
internet: 'Интернет',
lorem: 'Лорем',
name: 'Имя',
music: 'Музыка',
phone: 'Телефон',
random: 'Случайный',
system: 'Система',
time: 'Время',
vehicle: 'Vehicle',
zipCode: 'Почтовый код',
zipCodeByState: 'Почтовый код города',
city: 'Город',
cityPrefix: 'Префикс города',
citySuffix: 'Суфикс города',
streetName: 'Название улицы',
streetAddress: 'Адрес улицы',
streetSuffix: 'Суфикс улицы',
streetPrefix: 'Префикс улицы',
secondaryAddress: 'Дополнительный адрес',
county: 'Округ',
country: 'Страна',
countryCode: 'Код страны',
state: 'Штат',
stateAbbr: 'Аббревиатура штата',
latitude: 'Широта',
longitude: 'Долгота',
direction: 'Направление',
cardinalDirection: 'Кардинальное направление',
ordinalDirection: 'Порядковое направление',
nearbyGPSCoordinate: 'Ближайшая GPS-координата',
timeZone: 'Часовой пояс',
color: 'Цвет',
department: 'Отдел',
productName: 'Имя продукта',
price: 'Прайс',
productAdjective: 'Product adjective',
productMaterial: 'Product material',
product: 'Продукт',
productDescription: 'Описание продукта',
suffixes: 'Суфиксы',
companyName: 'Название компании',
companySuffix: 'Суфикс компании',
catchPhrase: 'Крылатая фраза',
bs: 'BS',
catchPhraseAdjective: 'Крылатая фраза прилагательное',
catchPhraseDescriptor: 'Дескриптор крылатой фразы',
catchPhraseNoun: 'Крылатая фраза существительное',
bsAdjective: 'BS прилагательное',
bsBuzz: 'BS жужжать',
bsNoun: 'BS существительное',
column: 'Колонка',
type: 'Тип',
collation: 'Сопоставление',
engine: 'Движок',
past: 'Прошлое',
future: 'Будущее',
between: 'Между',
recent: 'Недавнее',
soon: 'Вскоре',
month: 'Месяц',
weekday: 'Будний день',
account: 'Аккаунт',
accountName: 'Имя аккаунта',
routingNumber: 'Routing number',
mask: 'Маска',
amount: 'Сумма',
transactionType: 'Тип транзакции',
currencyCode: 'Код валюты',
currencyName: 'Название валюты',
currencySymbol: 'Символ валюты',
bitcoinAddress: 'Bitcoin кошелек',
litecoinAddress: 'Litecoin кошелек',
creditCardNumber: 'Номер кредитной карты',
creditCardCVV: 'CVV код',
ethereumAddress: 'Ethereum кошелек',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Описание транзакции',
branch: 'Ветка',
commitEntry: 'Подтвердить запись',
commitMessage: 'Подтвердить сообщение',
commitSha: 'Подтвердить SHA',
shortSha: 'Короткий SHA',
abbreviation: 'Сокращение',
adjective: 'Прилагательное',
noun: 'Существительное',
verb: 'Глагол',
ingverb: 'Пословица',
phrase: 'Фраза',
avatar: 'Аватар',
email: 'Почта',
exampleEmail: 'Пример почты',
userName: 'Логин',
protocol: 'Протокол',
url: 'Url',
domainName: 'Название домена',
domainSuffix: 'Суфикс домена',
domainWord: 'Слово домена',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: 'User agent',
mac: 'Мак-адрес',
password: 'Пароль',
word: 'Слово',
words: 'Слова',
sentence: 'Предложение',
slug: 'Slug',
sentences: 'Sentences',
paragraph: 'Параграф',
paragraphs: 'Параграфы',
text: 'Текст',
lines: 'Линии',
genre: 'Жанр',
firstName: 'Фамилия',
lastName: 'Имя',
middleName: 'Среднее имя',
findName: 'Полное имя',
jobTitle: 'Название задания',
gender: 'Пол',
prefix: 'Префикс',
suffix: 'Суфикс',
title: 'Заголовок',
jobDescriptor: 'Описание задания',
jobArea: 'Область задания',
jobType: 'Тип задания',
phoneNumber: 'Номер телефона',
phoneNumberFormat: 'Формат номера',
phoneFormats: 'Формат номеров телефона',
number: 'Номер',
float: 'Дробное число',
arrayElement: 'Элемент массива',
arrayElements: 'Элементы массива',
objectElement: 'Объект элемента',
uuid: 'Uuid',
boolean: 'Логический',
image: 'Изображение',
locale: 'Локаль',
alpha: 'Альфа',
alphaNumeric: 'Буквенно-Цифровой',
hexaDecimal: 'Шестнадцатеричный',
fileName: 'Имя файла',
commonFileName: 'Общее имя файла',
mimeType: 'Mime тип',
commonFileType: 'Общий тип файло',
commonFileExt: 'Общее расширение файлов',
fileType: 'Тип файла',
fileExt: 'Расширение файла',
directoryPath: 'Путь к каталогу',
filePath: 'Путь к файлу',
semver: 'Semver',
manufacturer: 'Производитель',
model: 'Модель',
fuel: 'Топливо',
vin: 'Vin'
}
};

View File

@@ -8,5 +8,6 @@ export default {
'de-DE': 'Deutsch (Deutschland)',
'vi-VN': 'Tiếng Việt',
'ja-JP': '日本語',
'zh-CN': '简体中文'
'zh-CN': '简体中文',
'ru-RU': 'Русский'
};

View File

@@ -80,6 +80,7 @@ module.exports = {
deterministic: 'Xác định',
context: 'Context',
export: 'Xuất',
import: 'Nhập',
returns: 'Returns',
timing: 'Thời gian',
state: 'Trạng thái',
@@ -122,9 +123,23 @@ module.exports = {
select: 'Chọn',
passphrase: 'Cụm mật khẩu',
filter: 'Bộ lọc',
change: 'Thay đổi',
views: 'Xem',
triggers: 'Trình kích hoạt',
routines: 'Routines',
functions: 'Functions',
schedulers: 'Lên lịch',
includes: 'Includes',
drop: 'Drop',
completed: 'Completed',
aborted: 'Aborted',
disabled: 'Đã tắt',
enable: 'Bật',
disable: 'Tắt'
disable: 'Tắt',
commit: 'Cam kết',
rollback: 'Hoàn nguyên',
connectionString: 'Chuỗi kết nối',
contributors: 'Người đóng góp'
},
message: {
appWelcome: 'Chào bạn đến với Antares SQL Client!',
@@ -250,8 +265,32 @@ module.exports = {
searchForQueries: 'Tìm kiếm truy vấn',
killProcess: 'Huỷ quá trình',
closeTab: 'Đóng tab',
exportSchema: 'Xuất lược đồ',
importSchema: 'Nhập lược đồ',
directoryPath: 'Đường dẫn thu mục',
newInserStmtEvery: 'Câu lệnh INSERT mới mỗi',
processingTableExport: 'Đang tiến hành {table}',
fechingTableExport: 'Đang lấy dữ liệu {table}',
writingTableExport: 'Đang ghi dữ liệu {table}',
checkAllTables: 'Chọn tất cả các bảng',
uncheckAllTables: 'Bỏ chọn tất cả các bảng',
goToDownloadPage: 'Tới trang tải về',
readOnlyMode: 'Chế độ chỉ đọc'
readOnlyMode: 'Chế độ chỉ đọc',
killQuery: 'Hủy truy vấn',
insertRow: 'Chèn hàng | Chèn hàng',
commitMode: 'Chế độ cam kết',
autoCommit: 'Cam kết tự động',
manualCommit: 'Cam kết thủ công',
actionSuccessful: '{action} thành công',
importQueryErrors: 'Cảnh báo: {n} lỗi đã xảy ra | Carh báo: {n} lỗi đã xảy ra',
executedQueries: '{n} truy vấn đã chạy | {n} truy vấn đã chạy',
ourputFormat: 'Định dạng đầu ra',
singleFile: 'Một tệp {ext}',
zipCompressedFile: 'Tệp nén zip {ext}',
disableBlur: 'Tắt làm mờ',
untrustedConnection: 'Kết nối không đáng tin cậy',
missingOrIncompleteTranslation: 'Bản dịch thiếu hoặc không đầy đủ?',
findOutHowToContribute: 'Tìm hiểu cách đóng góp'
},
faker: {
address: 'Địa chỉ',

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1,14 +1,8 @@
@mixin type-colors($types) {
$numbers: ('int','tinyint','smallint','mediumint','float','double','decimal');
@each $type, $color in $types {
.type-#{$type} {
color: $color;
@if index($numbers, $type) {
text-align: right;
}
}
}
}

View File

@@ -1,3 +1,16 @@
.fade-slide-down-enter-active,
.fade-slide-down-leave-active {
transition: opacity 0.15s ease, transform 0.15s ease;
opacity: 1;
transform: translateY(0);
}
.fade-slide-down-enter-from,
.fade-slide-down-leave-to {
opacity: 0;
transform: translateY(-1.8rem);
}
.slide-fade-enter-active {
transition: all 0.3s ease;
}

View File

@@ -27,4 +27,14 @@ $titlebar-height: 1.5rem;
$settingbar-width: 3rem;
$explorebar-width: 14rem;
$footer-height: 1.5rem;
$excluding-size: $footer-height + $titlebar-height;
@function get-excluding-size(){
@if $platform == linux{
@return $footer-height;
}
@else {
@return $footer-height + $titlebar-height;
}
}
$excluding-size: get-excluding-size();

View File

@@ -87,7 +87,6 @@ option:checked {
}
}
.workspace-query-results {
overflow: auto;
white-space: nowrap;
@@ -209,17 +208,17 @@ option:checked {
}
}
}
.modal-overlay{
background: rgba( 255, 255, 255, 0.1);
box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 );
.modal-overlay {
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
}
#wrapper:not(.no-blur){
.modal-overlay{
backdrop-filter: blur( 4px );
-webkit-backdrop-filter: blur( 4px );
#wrapper:not(.no-blur) {
.modal-overlay {
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
}
@@ -297,6 +296,41 @@ option:checked {
font-size: 0.7rem;
padding: 1px 0.4rem 0;
}
&.select {
&.select--open {
border-color: $primary-color !important;
@include control-shadow();
}
}
}
.select__list {
margin: 0;
li {
margin: 0;
padding: 0.3rem 0.8rem;
.select-sm &,
.small-select & {
padding: 0.05rem 0.3rem;
}
}
}
.select__list-wrapper {
z-index: 401 !important;
border: 1px solid transparent;
border-radius: $border-radius;
box-shadow: 0px 8px 17px 0px rgba(0, 0, 0, 0.2), 0px 6px 20px 0px rgba(0, 0, 0, 0.19);
}
.select__option--selected {
background: rgba($primary-color, 0.25);
}
.select__option--highlight {
background: $primary-color;
}
.form-input[type="file"] {

View File

@@ -121,6 +121,18 @@
border-color: $primary-color;
}
.select {
&__list-wrapper {
border-color: $bg-color-gray;
background-color: $bg-color-light-dark;
}
&__group {
background: rgba($bg-color-gray, 0.65);
color: rgba($bg-color-light-gray, 0.7);
}
}
.form-input[readonly] {
background-color: $bg-color-dark;
cursor: default;
@@ -219,7 +231,8 @@
.td {
border-color: $body-bg-dark;
&:focus {
&:focus,
&.selected {
box-shadow: inset 0 0 0 2px darken($body-font-color-dark, 40%);
background-color: rgba($color: #000, $alpha: 0.3);
}
@@ -232,7 +245,7 @@
}
}
}
.connection-panel {
.panel {
background: rgba($bg-color-light-dark, 50%);
@@ -240,9 +253,7 @@
}
.bg-checkered {
background-image:
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
linear-gradient(to right, black 50%, white 50%),
background-image: 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%);
background-blend-mode: normal, difference, normal;
background-size: 2em 2em;
@@ -350,7 +361,7 @@
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: #fff, $alpha: 0.2);
background: rgba($color: #fff, $alpha: 0.1);
}
&.close-button:hover {

View File

@@ -18,6 +18,22 @@
background: #ababab;
}
.select {
&__list-wrapper {
border: #bcc3ce;
background-color: $body-bg;
}
&__group {
background: $bg-color-light-gray;
color: $unknown-color;
}
&__option--highlight {
color: $light-color;
}
}
.menu {
.menu-item a {
&:hover {
@@ -116,7 +132,7 @@
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: rgb(172, 172, 172), $alpha: 0.2);
background: rgba($color: rgb(172, 172, 172), $alpha: 0.3);
}
&.close-button:hover {
@@ -236,7 +252,8 @@
.td {
border-color: $body-bg;
&:focus {
&:focus,
&.selected {
box-shadow: inset 0 0 0 2px lighten($body-font-color, 10%);
background-color: $body-font-color-dark;
}

View File

@@ -1,4 +1,5 @@
import { defineStore, acceptHMRUpdate } from 'pinia';
import { ipcRenderer } from 'electron';
import i18n from '@/i18n';
import Store from 'electron-store';
const persistentStore = new Store({ name: 'settings' });
@@ -54,6 +55,7 @@ export const useSettingsStore = defineStore('settings', {
changeApplicationTheme (theme) {
this.applicationTheme = theme;
persistentStore.set('application_theme', this.applicationTheme);
ipcRenderer.send('refresh-theme-settings');
},
changeEditorTheme (theme) {
this.editorTheme = theme;

View File

@@ -117,13 +117,14 @@ export const useWorkspacesStore = defineStore('workspaces', {
// Check if Maria or MySQL
const isMySQL = version.name.includes('MySQL');
const isMaria = version.name.includes('Maria');
if (isMySQL && connection.client !== 'mysql') {
const connProxy = Object.assign({}, connection);
connProxy.client = 'mysql';
connectionsStore.editConnection(connProxy);
}
else if (!isMySQL && connection.client === 'mysql') {
else if (isMaria && connection.client === 'mysql') {
const connProxy = Object.assign({}, connection);
connProxy.client = 'maria';
connectionsStore.editConnection(connProxy);
@@ -609,6 +610,26 @@ export const useWorkspacesStore = defineStore('workspaces', {
: workspace
);
},
selectNextTab ({ uid }) {
const workspace = this.workspaces.find(workspace => workspace.uid === uid);
let newIndex = workspace.tabs.findIndex(tab => tab.selected || tab.uid === workspace.selectedTab) + 1;
if (newIndex > workspace.tabs.length -1)
newIndex = 0;
this.selectTab({ uid, tab: workspace.tabs[newIndex].uid });
},
selectPrevTab ({ uid }) {
const workspace = this.workspaces.find(workspace => workspace.uid === uid);
let newIndex = workspace.tabs.findIndex(tab => tab.selected || tab.uid === workspace.selectedTab) - 1;
if (newIndex < 0)
newIndex = workspace.tabs.length -1;
this.selectTab({ uid, tab: workspace.tabs[newIndex].uid });
},
updateTabs ({ uid, tabs }) {
this.workspaces = this.workspaces.map(workspace => workspace.uid === uid
? { ...workspace, tabs }

View File

@@ -21,7 +21,7 @@ test('launch app', async () => {
test('main window elements visibility', async () => {
const visibleSelectors = [
'#titlebar',
// '#titlebar',
'#window-content',
'#settingbar',
'#footer'

View File

@@ -104,7 +104,9 @@ const config = {
{
loader: 'sass-loader',
options: {
additionalData: '@import "@/scss/_variables.scss";',
additionalData: `
$platform: ${process.platform};
@import "@/scss/_variables.scss";`,
sassOptions: { quietDeps: true }
}
}