facilmap/frontend/src/lib/components/edit-type-dialog/edit-type-dropdown-dialog.vue

373 wiersze
12 KiB
Vue

<script setup lang="ts">
import type { CRU, Field, FieldOptionUpdate, FieldUpdate, Type } from "facilmap-types";
import { canControl, formatCheckboxValue, mergeObject } from "facilmap-utils";
import { getUniqueId } from "../../utils/utils";
import { cloneDeep, isEqual } from "lodash-es";
import ColourPicker from "../ui/colour-picker.vue";
import Draggable from "vuedraggable";
import Icon from "../ui/icon.vue";
import ModalDialog from "../ui/modal-dialog.vue";
import ShapePicker from "../ui/shape-picker.vue";
import SizePicker from "../ui/size-picker.vue";
import SymbolPicker from "../ui/symbol-picker.vue";
import WidthPicker from "../ui/width-picker.vue";
import { useToasts } from "../ui/toasts/toasts.vue";
import { computed, ref, watch } from "vue";
import { showConfirm } from "../ui/alert.vue";
import { injectContextRequired } from "../facil-map-context-provider/facil-map-context-provider.vue";
import ValidatedField from "../ui/validated-form/validated-field.vue";
import StrokePicker from "../ui/stroke-picker.vue";
import { useI18n } from "../../utils/i18n";
function getControlNumber(type: Type<CRU.READ | CRU.CREATE_VALIDATED>, field: FieldUpdate): number {
return [
field.controlColour,
...(type.type == "marker" ? [
field.controlSize,
field.controlSymbol,
field.controlShape
] : []),
...(type.type == "line" ? [
field.controlWidth,
field.controlStroke
] : [])
].filter((v) => v).length;
}
const context = injectContextRequired();
const toasts = useToasts();
const i18n = useI18n();
const props = defineProps<{
type: Type<CRU.READ | CRU.CREATE_VALIDATED>;
field: Field;
}>();
const emit = defineEmits<{
"update:field": [field: Field];
hidden: [];
}>();
const id = getUniqueId("fm-edit-type-dropdown-dialog");
const modalRef = ref<InstanceType<typeof ModalDialog>>();
const initialField = computed(() => {
const field: FieldUpdate = cloneDeep(props.field);
if(field.type == 'checkbox') {
if(!field.options || field.options.length != 2) {
field.options = [
{ value: '' },
{ value: field.name }
]
}
// Convert legacy format
if(field.options[0].value == "0")
field.options[0].value = "";
if(field.options[1].value == "1")
field.options[1].value = field.name;
}
for(let option of (field.options || []))
option.oldValue = option.value;
return field;
});
const fieldValue = ref(cloneDeep(initialField.value));
watch(() => props.field, (newField, oldField) => {
if (fieldValue.value) {
if (newField == null) {
modalRef.value?.modal.hide();
// TODO: Show message
} else {
mergeObject(oldField, newField, fieldValue.value);
}
}
}, { deep: true });
const isModified = computed(() => !isEqual(fieldValue.value, initialField.value));
const resolvedCanControl = computed(() => canControl(props.type, props.field));
function addOption(): void {
if(fieldValue.value.options == null)
fieldValue.value.options = [ ];
fieldValue.value.options!.push({ value: "" });
}
async function deleteOption(option: FieldOptionUpdate): Promise<void> {
if (!await showConfirm({
title: i18n.t("edit-type-dropdown-dialog.delete-option-title"),
message: i18n.t("edit-type-dropdown-dialog.delete-option-message", { value: option.value }),
variant: "danger",
okLabel: i18n.t("edit-type-dropdown-dialog.delete-option-button")
}))
return;
var idx = fieldValue.value.options!.indexOf(option);
if(idx != -1)
fieldValue.value.options!.splice(idx, 1);
}
function save(): void {
toasts.hideToast(`fm${context.id}-edit-type-dropdown-error`);
emit("update:field", fieldValue.value);
modalRef.value?.modal.hide();
}
const controlNumber = computed(() => getControlNumber(props.type, fieldValue.value));
const columns = computed(() => controlNumber.value + (fieldValue.value.type === "checkbox" ? 2 : 3));
function validateOptionValue(value: string): string | undefined {
if (fieldValue.value.type !== "checkbox" && fieldValue.value.options!.filter((op) => op.value === value).length > 1) {
return i18n.t("edit-type-dropdown-dialog.unique-value-error");
}
}
const formValidationError = computed(() => {
if (controlNumber.value > 0 && (fieldValue.value.options?.length ?? 0) === 0) {
return i18n.t("edit-type-dropdown-dialog.no-options-error");
} else {
return undefined;
}
});
const typeInterpolation = computed(() => {
if (props.type.type === "marker") {
return i18n.t("edit-type-dropdown-dialog.control-interpolation-marker");
} else {
return i18n.t("edit-type-dropdown-dialog.control-interpolation-line");
}
});
</script>
<template>
<ModalDialog
:title="fieldValue.type === 'checkbox' ? i18n.t('edit-type-dropdown-dialog.title-checkbox') : i18n.t('edit-type-dropdown-dialog.title-dropdown')"
class="fm-edit-type-dropdown"
:isModified="isModified"
@submit="save()"
@hidden="emit('hidden')"
:size="fieldValue && controlNumber > 2 ? 'xl' : 'lg'"
:okLabel="isModified ? i18n.t('edit-type-dropdown-dialog.ok-button') : undefined"
:formValidationError="formValidationError"
ref="modalRef"
>
<div class="row mb-3">
<label class="col-sm-3 col-form-label">{{i18n.t("edit-type-dropdown-dialog.control")}}</label>
<div class="col-sm-9">
<div class="form-check fm-form-check-with-label">
<input
:id="`${id}-control-colour`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlColour"
:disabled="!resolvedCanControl.includes('colour')"
/>
<label
class="form-check-label"
:for="`${id}-control-colour`"
>
{{i18n.t("edit-type-dropdown-dialog.control-colour", { type: typeInterpolation })}}
</label>
</div>
<div v-if="type.type == 'marker'" class="form-check">
<input
:id="`${id}-control-size`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlSize"
:disabled="!resolvedCanControl.includes('size')"
/>
<label
class="form-check-label"
:for="`${id}-control-size`"
>
{{i18n.t("edit-type-dropdown-dialog.control-size", { type: typeInterpolation })}}
</label>
</div>
<div v-if="type.type == 'marker'" class="form-check">
<input
:id="`${id}-control-symbol`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlSymbol"
:disabled="!resolvedCanControl.includes('symbol')"
/>
<label
class="form-check-label"
:for="`${id}-control-symbol`"
>
{{i18n.t("edit-type-dropdown-dialog.control-icon", { type: typeInterpolation })}}
</label>
</div>
<div v-if="type.type == 'marker'" class="form-check">
<input
:id="`${id}-control-shape`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlShape"
:disabled="!resolvedCanControl.includes('shape')"
/>
<label
class="form-check-label"
:for="`${id}-control-shape`"
>
{{i18n.t("edit-type-dropdown-dialog.control-shape", { type: typeInterpolation })}}
</label>
</div>
<div v-if="type.type == 'line'" class="form-check">
<input
:id="`${id}-control-width`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlWidth"
:disabled="!resolvedCanControl.includes('width')"
/>
<label
class="form-check-label"
:for="`${id}-control-width`"
>
{{i18n.t("edit-type-dropdown-dialog.control-width", { type: typeInterpolation })}}
</label>
</div>
<div v-if="type.type == 'line'" class="form-check">
<input
:id="`${id}-control-stroke`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlStroke"
:disabled="!resolvedCanControl.includes('stroke')"
/>
<label
class="form-check-label"
:for="`${id}-control-stroke`"
>
{{i18n.t("edit-type-dropdown-dialog.control-stroke", { type: typeInterpolation })}}
</label>
</div>
</div>
</div>
<table v-if="fieldValue.type != 'checkbox' || controlNumber > 0" class="table table-striped table-hover">
<thead>
<tr>
<th>{{i18n.t("edit-type-dropdown-dialog.option")}}</th>
<th v-if="fieldValue.type == 'checkbox'">{{i18n.t("edit-type-dropdown-dialog.label")}}</th>
<th v-if="fieldValue.controlColour">{{i18n.t("edit-type-dropdown-dialog.colour")}}</th>
<th v-if="fieldValue.controlSize">{{i18n.t("edit-type-dropdown-dialog.size")}}</th>
<th v-if="fieldValue.controlSymbol">{{i18n.t("edit-type-dropdown-dialog.icon")}}</th>
<th v-if="fieldValue.controlShape">{{i18n.t("edit-type-dropdown-dialog.shape")}}</th>
<th v-if="fieldValue.controlWidth">{{i18n.t("edit-type-dropdown-dialog.width")}}</th>
<th v-if="fieldValue.controlStroke">{{i18n.t("edit-type-dropdown-dialog.stroke")}}</th>
<th v-if="fieldValue.type != 'checkbox'"></th>
<th v-if="fieldValue.type != 'checkbox'" class="move"></th>
</tr>
</thead>
<Draggable
v-model="fieldValue.options"
tag="tbody"
handle=".fm-drag-handle"
:itemKey="(option: any) => fieldValue.options!.indexOf(option)"
>
<template #item="{ element: option, index: idx }">
<tr>
<td v-if="fieldValue.type == 'checkbox'">
<strong>{{formatCheckboxValue(idx === 0 ? "0" : "1")}}</strong>
</td>
<ValidatedField
tag="td"
class="field position-relative"
:value="option.value"
:validators="[validateOptionValue]"
>
<template #default="slotProps">
<input
class="form-control"
v-model="option.value"
:ref="slotProps.inputRef"
/>
<div class="invalid-tooltip">
{{slotProps.validationError}}
</div>
</template>
</ValidatedField>
<td v-if="fieldValue.controlColour" class="field">
<ColourPicker
:modelValue="option.colour ?? type.defaultColour"
@update:modelValue="option.colour = $event"
></ColourPicker>
</td>
<td v-if="fieldValue.controlSize" class="field">
<SizePicker
:modelValue="option.size ?? type.defaultSize"
@update:modelValue="option.size = $event"
class="fm-custom-range-with-label"
></SizePicker>
</td>
<td v-if="fieldValue.controlSymbol" class="field">
<SymbolPicker
:modelValue="option.symbol ?? type.defaultSymbol"
@update:modelValue="option.symbol = $event"
></SymbolPicker>
</td>
<td v-if="fieldValue.controlShape" class="field">
<ShapePicker
:modelValue="option.shape ?? type.defaultShape"
@update:modelValue="option.shape = $event"
></ShapePicker>
</td>
<td v-if="fieldValue.controlWidth" class="field">
<WidthPicker
:modelValue="option.width ?? type.defaultWidth"
@update:modelValue="option.width = $event"
class="fm-custom-range-with-label"
></WidthPicker>
</td>
<td v-if="fieldValue.controlStroke" class="field">
<StrokePicker
:modelValue="option.stroke ?? type.defaultStroke"
@update:modelValue="option.stroke = $event"
></StrokePicker>
</td>
<td v-if="fieldValue.type != 'checkbox'" class="td-buttons">
<button type="button" class="btn btn-secondary" @click="deleteOption(option)"><Icon icon="minus" :alt="i18n.t('edit-type-dropdown-dialog.option-remove')"></Icon></button>
</td>
<td v-if="fieldValue.type != 'checkbox'" class="td-buttons">
<button type="button" class="btn btn-secondary fm-drag-handle"><Icon icon="resize-vertical" :alt="i18n.t('edit-type-dropdown-dialog.option-reorder')"></Icon></button>
</td>
</tr>
</template>
</Draggable>
<tfoot v-if="fieldValue.type != 'checkbox'">
<tr>
<td :colspan="columns">
<button type="button" class="btn btn-secondary" @click="addOption()"><Icon icon="plus" :alt="i18n.t('edit-type-dropdown-dialog.option-add')"></Icon></button>
</td>
</tr>
</tfoot>
</table>
<div class="fm-form-invalid-feedback" v-if="formValidationError">
{{formValidationError}}
</div>
</ModalDialog>
</template>
<style lang="scss">
.fm-edit-type-dropdown {
td.field {
min-width: 10rem;
}
}
</style>