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

Compare commits

..

34 Commits

Author SHA1 Message Date
227e810a43 chore(release): 0.5.6 2022-06-02 11:25:40 +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
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
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
55 changed files with 1924 additions and 977 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

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

View File

@@ -2,6 +2,75 @@
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.6](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.6) (2022-06-02)
### 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
* 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))
* **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))
* **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))
### Improvements
* improved precision of MariaDB or MySQL auto detection ([26aad51](https://github.com/antares-sql/antares/commit/26aad519df6ea1bbc7dffbf540193a7b2ed9ae2a))
* **UI:** max height for query text area increased ([5d5f1da](https://github.com/antares-sql/antares/commit/5d5f1da97b9adfa743197d8fa0bbb6addd565a7a))
### [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)

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

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.5.4",
"version": "0.5.6",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/antares-sql/antares.git",
@@ -48,6 +48,7 @@
"package.json"
],
"win": {
"icon": "assets/icon.png",
"target": [
"nsis",
"portable"
@@ -82,6 +83,12 @@
"license": "./LICENSE",
"category": "Development"
},
"nsis": {
"license": "./LICENSE",
"installerIcon": "assets/icon.ico",
"uninstallerIcon": "assets/icon.ico",
"installerHeader": "assets/icon.png"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
},
@@ -118,7 +125,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

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

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

@@ -19,7 +19,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,

View File

@@ -0,0 +1,427 @@
<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;
});
const currentOptionLabel = computed(() =>
flattenOptions.value.find(d => d.value === props.modelValue)?.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

@@ -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"
@@ -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
};
},
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 () {

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

@@ -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">
@@ -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();

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

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

@@ -38,25 +38,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,19 +91,14 @@
<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">
@@ -223,6 +214,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,7 +222,8 @@ export default {
ConfirmModal,
TextEditor,
ForeignKeySelect,
BaseMap
BaseMap,
BaseSelect
},
props: {
row: Object,

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': 'Русский'
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

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

@@ -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;
@@ -232,7 +244,7 @@
}
}
}
.connection-panel {
.panel {
background: rgba($bg-color-light-dark, 50%);
@@ -240,9 +252,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;

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 {

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