1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-02-01 09:56:54 +01:00

feat(UI): ForeignKeySelect implements BaseSelect component

This commit is contained in:
Giulio Ganci 2022-05-09 11:29:25 +02:00
parent 0043d07708
commit 302c66457d
6 changed files with 147 additions and 100 deletions

View File

@ -6,7 +6,7 @@
role="combobox"
:tabindex="searchable ? -1 : tabindex"
@focus="activate()"
@blur="searchable ? false : deactivate()"
@blur="searchable ? false : handleBlurEvent()"
@keyup.esc="deactivate()"
@keydown.self.down.prevent="moveDown()"
@keydown.self.up.prevent="moveUp"
@ -23,8 +23,8 @@
:tabindex="tabindex"
:value="searchText"
@input="searchText = $event.target.value"
@focus.prevent="activate()"
@blur.prevent="deactivate()"
@focus.prevent="!isOpen ? activate() : false"
@blur.prevent="handleBlurEvent()"
@keyup.esc="deactivate()"
@keydown.down.prevent="keyArrows('down')"
@keydown.up.prevent="keyArrows('up')"
@ -32,38 +32,43 @@
>
<span v-if="searchable && !isOpen || !searchable">{{ currentOptionLabel }}</span>
</div>
<div
<Teleport
v-if="isOpen"
ref="optionList"
class="select__list-wrapper"
ref="teleportEl"
:to="dropdownContainer"
>
<ul class="select__list" @mousedown.prevent>
<li
v-for="(opt, index) of filteredOptions"
:key="getOptionValue(opt)"
:ref="(el) => optionRefs[index] = el"
:class="{
'select__option--highlight': index === hightlightedIndex,
'select__option--selected': isSelected(opt)
}"
@click.stop="select(opt)"
@mouseenter.self="hightlightedIndex = index"
>
<slot
name="option"
:option="opt"
:index="index"
<div
ref="optionList"
:class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`"
>
<ul class="select__list" @mousedown.prevent>
<li
v-for="(opt, index) of filteredOptions"
:key="getOptionValue(opt)"
:ref="(el) => optionRefs[index] = el"
:class="{
'select__option--highlight': index === hightlightedIndex,
'select__option--selected': isSelected(opt)
}"
@click.stop="select(opt)"
@mouseenter.self="hightlightedIndex = index"
>
{{ getOptionLabel(opt) }}
</slot>
</li>
</ul>
</div>
<slot
name="option"
:option="opt"
:index="index"
>
{{ getOptionLabel(opt) }}
</slot>
</li>
</ul>
</div>
</Teleport>
</div>
</template>
<script>
import { defineComponent, computed, ref, watch } from 'vue';
import { defineComponent, computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
export default defineComponent({
name: 'BaseSelect',
@ -71,6 +76,9 @@ export default defineComponent({
modelValue: {
type: [String, Number, Object]
},
value: {
type: [String, Number, Object]
},
searchable: {
type: Boolean,
default: true
@ -89,21 +97,35 @@ export default defineComponent({
},
optionTrackBy: {
type: [String, Function],
default: (opt) => opt.id || opt.value
default: () => (opt) => {
for (const guess of ['id', 'value']) if (opt[guess]) return guess;
}
},
optionLabel: {
type: [String, Function],
default: (opt) => opt.label
default: () => (opt) => opt.label ? 'label' : undefined
},
closeOnSelect: {
type: Boolean,
default: true
},
dropdownContainer: {
type: String,
default: '#main-content'
},
dropdownOffsets: {
type: Object,
default: () => ({ top: 10, left: 0 })
},
dropdownClass: {
type: String
}
},
emits: ['select', 'open', 'close', 'update:modelValue'],
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);
@ -134,12 +156,12 @@ export default defineComponent({
});
const getOptionValue = (opt) => {
const key = typeof props.optionTrackBy === 'function' ? props.optionTrackBy() : props.optionTrackBy;
const key = typeof props.optionTrackBy === 'function' ? props.optionTrackBy(opt) : props.optionTrackBy;
return key ? opt[key] : opt;
};
const getOptionLabel = (opt) => {
const key = typeof props.optionLabel === 'function' ? props.optionLabel() : props.optionLabel;
const key = typeof props.optionLabel === 'function' ? props.optionLabel(opt) : props.optionLabel;
return key ? opt[key] : opt;
};
@ -153,19 +175,22 @@ export default defineComponent({
});
const select = (opt) => {
internalValue.value = opt;
emit('select', opt);
emit('update:modelValue', getOptionValue(opt));
emit('change', opt);
if (props.closeOnSelect)
deactivate();
};
const isSelected = (opt) => {
return props.modelValue === getOptionValue(opt);
return internalValue.value === getOptionValue(opt);
};
const activate = () => {
if (isOpen.value) return;
isOpen.value = true;
if (props.searchable)
searchInput.value.focus();
@ -173,14 +198,13 @@ export default defineComponent({
else
el.value.focus();
isOpen.value = true;
nextTick(() => adjustListPosition());
emit('open');
};
const deactivate = () => {
if (!isOpen.value) return;
isOpen.value = false;
if (props.searchable)
@ -194,6 +218,16 @@ export default defineComponent({
emit('close');
};
const adjustListPosition = () => {
const element = el.value;
const { left, top } = element.getBoundingClientRect();
const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets;
optionList.value.style.left = `${left + offsetLeft}px`;
optionList.value.style.top = `${top + element.clientHeight + offsetTop}px`;
optionList.value.style.minWidth = `${element.clientWidth}px`;
};
const keyArrows = (direction) => {
const sum = direction === 'down' ? +1 : -1;
const index = hightlightedIndex.value + sum;
@ -211,6 +245,18 @@ export default defineComponent({
optionList.value.scrollTop = optEl.offsetTop - optionList.value.clientHeight + optEl.clientHeight;
};
const handleBlurEvent = () => {
deactivate();
emit('blur');
};
onMounted(() => {
window.addEventListener('resize', adjustListPosition);
});
onUnmounted(() => {
window.removeEventListener('resize', adjustListPosition);
});
return {
el,
searchInput,
@ -228,7 +274,8 @@ export default defineComponent({
isOpen,
hightlightedIndex,
optionList,
optionRefs
optionRefs,
handleBlurEvent
};
}
});
@ -236,7 +283,7 @@ export default defineComponent({
<style lang="scss" scoped>
.select {
position: relative;
display: block;
&__search-input {
@ -248,10 +295,9 @@ export default defineComponent({
}
&__list-wrapper {
position: absolute;
position: fixed;
display: block;
width: 100%;
z-index: 50;
z-index: 5;
-webkit-overflow-scrolling: touch;
max-height: 240px;
overflow: auto;

View File

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

View File

@ -56,6 +56,8 @@
option-track-by="slug"
option-label="name"
class="form-select"
dropdown-container=".workspace .connection-panel-wrapper"
:dropdown-offsets="{top: 10}"
/>
</div>
</div>

View File

@ -302,39 +302,35 @@ option:checked {
border-color: $primary-color !important;
@include control-shadow();
}
}
}
.select__list-wrapper {
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__list {
margin: 0;
li {
margin: 0;
padding: 0.3rem 0.8rem;
.select__list {
margin: 0;
li {
margin: 0;
padding: 0.3rem 0.8rem;
}
}
&.select-sm {
.select__list {
li {
padding: 0.05rem 0.3rem;
}
}
}
.select__option--selected {
background: rgba($primary-color, 0.25);
}
.select__option--highlight {
background: $primary-color;
.select-sm & {
padding: 0.05rem 0.3rem;
}
}
}
.select__list-wrapper {
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"] {
overflow: hidden;
}

View File

@ -121,12 +121,10 @@
border-color: $primary-color;
}
.form-select {
.select {
&__list-wrapper {
border-color: $bg-color-gray;
background-color: $bg-color-light-dark;
}
.select {
&__list-wrapper {
border-color: $bg-color-gray;
background-color: $bg-color-light-dark;
}
}

View File

@ -18,16 +18,14 @@
background: #ababab;
}
.form-select {
.select {
&__list-wrapper {
border: #bcc3ce;
background-color: $body-bg;
}
.select {
&__list-wrapper {
border: #bcc3ce;
background-color: $body-bg;
}
&__option--highlight {
color: $light-color;
}
&__option--highlight {
color: $light-color;
}
}