Merge branch 'master' of https://github.com/antares-sql/antares into ts-renderer

This commit is contained in:
Fabio Di Stasio 2022-05-25 14:41:15 +02:00
commit e97401e27d
46 changed files with 1785 additions and 926 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,38 @@
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.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>

View File

@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.5.4",
"version": "0.5.5",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/antares-sql/antares.git",

View File

@ -0,0 +1,409 @@
<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 : '' }`"
>
<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 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] || 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 = () => {
deactivate();
emit('blur');
};
onMounted(() => {
window.addEventListener('resize', adjustListPosition);
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);
});
return {
el,
searchInput,
searchText,
searchInputStyle,
filteredOptions,
currentOptionLabel,
activate,
deactivate,
select,
isSelected,
keyArrows,
isOpen,
hightlightedIndex,
optionList,
optionRefs,
handleBlurEvent
};
}
});
</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: any) => 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: any) => $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"
@ -110,6 +91,7 @@ import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from
import BaseUploadInput from '@/components/BaseUploadInput.vue';
import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
import FakerMethods from 'common/FakerMethods';
import BaseSelect from '@/components/BaseSelect.vue';
const props = defineProps({
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>
@blur="$emit('blur')"
/>
</template>
<script setup lang="ts">
@ -27,6 +19,7 @@ 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';
const props = defineProps({
modelValue: [String, Number],
@ -46,6 +39,7 @@ const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const editField: Ref<HTMLSelectElement> = ref(null);
const foreignList = ref([]);
const currentValue = ref(props.modelValue);
const isValidDefault = computed(() => {
if (!foreignList.value.length) return true;
@ -53,8 +47,17 @@ const isValidDefault = computed(() => {
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue.toString());
});
const onChange = () => {
emit('update:modelValue', editField.value.value);
const foreigns = computed(() => {
const list = [];
if (!isValidDefault.value)
list.push({ value: props.modelValue, label: props.modelValue === null ? 'NULL' : props.modelValue });
for (const row of foreignList.value)
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` });
return list;
});
const onChange = (opt: any) => {
emit('update:modelValue', opt.value);
};
const cutText = (val: string) => {

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>
@ -73,6 +68,7 @@ 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';
const props = defineProps({
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>
@ -282,6 +276,7 @@ import { useWorkspacesStore } from '@/stores/workspaces';
import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema';
import { Customizations } from 'common/interfaces/customizations';
import BaseSelect from '@/components/BaseSelect.vue';
const props = defineProps({
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">
@ -195,6 +107,7 @@ import { useNotificationsStore } from '@/stores/notifications';
import { useWorkspacesStore } from '@/stores/workspaces';
import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect.vue';
import BaseSelect from '@/components/BaseSelect.vue';
const props = defineProps({
tabUid: [String, Number],
@ -222,6 +135,55 @@ const workspace = computed(() => getWorkspace(selectedWorkspace.value));
const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
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' }
];
watch(nInserts, (val) => {
if (!val || val < 1)
nInserts.value = 1;

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>
@ -73,6 +71,7 @@ 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';
const { addNotification } = useNotificationsStore();
const workspacesStore = useWorkspacesStore();

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">
@ -334,6 +313,7 @@ import { localesNames } from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate.vue';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog.vue';
import BaseTextEditor from '@/components/BaseTextEditor.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import { computed } from '@vue/reactivity';
const { t, availableLocales } = useI18n();

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: {
@ -320,6 +319,7 @@ export default {
this.selectedSchema = null;
window.addEventListener('keydown', this.onKey);
window.addEventListener('resize', this.onWindowResize);
},
mounted () {
const resizer = this.$refs.resizer;
@ -335,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,
@ -419,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)
@ -520,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;

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 const localesNames: {[key: string]: string} = {
'de-DE': 'Deutsch (Deutschland)',
'vi-VN': 'Tiếng Việt',
'ja-JP': '日本語',
'zh-CN': '简体中文'
'zh-CN': '简体中文',
'ru-RU': 'Русский'
};

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 {