|
1 | 1 | <template>
|
2 |
| - <v-text-field |
3 |
| - variant="underlined" |
4 |
| - :disabled="state === State.LOADING" |
5 |
| - v-model="inputValue" |
6 |
| - :label="label" |
7 |
| - persistent-hint |
8 |
| - :hint="error" |
9 |
| - :error="!!error" |
10 |
| - @blur="submit()" |
| 2 | + <VTextField v-model="inputValue" |
| 3 | + variant="underlined" |
| 4 | + :disabled="state === State.LOADING || !!disabled" |
| 5 | + :label="label" |
| 6 | + persistent-hint |
| 7 | + :hint="!!error ? error: (helpText || '')" |
| 8 | + :error="!!error" |
| 9 | + :type="calculatedInputType" |
| 10 | + @blur="submit()" |
11 | 11 | >
|
12 |
| - <template v-if="state === State.SUCCESS" v-slot:append> |
13 |
| - <v-icon color="success">mdi-check</v-icon> |
| 12 | + <template v-if="inputType === 'password'" v-slot:append-inner> |
| 13 | + <VIcon v-if="showPlainText" @click="togglePlaintext">mdi-eye-off</VIcon> |
| 14 | + <VIcon v-else @click="togglePlaintext">mdi-eye</VIcon> |
14 | 15 | </template>
|
15 |
| - <template v-else-if="state === State.LOADING" v-slot:append> |
16 |
| - <v-progress-circular indeterminate></v-progress-circular> |
| 16 | + <template v-if="state !== State.READY" v-slot:append> |
| 17 | + <VIcon v-if="state === State.ERROR" |
| 18 | + color="error" |
| 19 | + class="mr-1" |
| 20 | + > |
| 21 | + mdi-alert-circle-outline |
| 22 | + </VIcon> |
| 23 | + <VIcon v-if="state === State.SUCCESS" color="success">mdi-check</VIcon> |
| 24 | + <VProgressCircular v-if="state===State.LOADING" indeterminate /> |
17 | 25 | </template>
|
18 |
| - <template v-else-if="state === State.ERROR" v-slot:append> |
19 |
| - <v-icon color="error" class="mr-1">mdi-alert-circle-outline</v-icon> |
20 |
| - </template> |
21 |
| - </v-text-field> |
| 26 | + </VTextField> |
22 | 27 | </template>
|
23 | 28 |
|
24 | 29 |
|
25 | 30 | <script lang="ts" setup>
|
26 |
| -import {ref} from "vue"; |
| 31 | +import {computed, ref} from "vue"; |
| 32 | +import {SettingType} from "@/types/ISettings"; |
27 | 33 |
|
| 34 | +/* eslint-disable */ |
28 | 35 | enum State {
|
29 | 36 | LOADING,
|
30 | 37 | READY,
|
31 | 38 | SUCCESS,
|
32 | 39 | ERROR,
|
33 | 40 | }
|
| 41 | +/* eslint-enable */ |
34 | 42 |
|
35 | 43 | interface IProps {
|
36 | 44 | onSubmit: Function;
|
37 | 45 | name: string;
|
38 | 46 | label: string;
|
39 | 47 | initialValue: string|undefined;
|
| 48 | + inputType: "text"|"password"; |
| 49 | + settingType: SettingType; |
| 50 | + helpText?: string; |
| 51 | + disabled?: boolean; |
40 | 52 | }
|
41 | 53 |
|
42 | 54 | const props = defineProps<IProps>();
|
| 55 | +const emit = defineEmits(["savedValueUpdated"]); |
43 | 56 |
|
44 |
| -const state = ref<State>(State.READY) |
| 57 | +const state = ref<State>(State.READY); |
45 | 58 | const inputValue = ref(props.initialValue);
|
46 | 59 | const lastSubmittedValue = ref(props.initialValue);
|
47 | 60 | const error = ref("");
|
| 61 | +const showPlainText = ref(props.inputType === "text"); |
48 | 62 |
|
49 | 63 | async function submit() {
|
50 |
| - if (inputValue.value === lastSubmittedValue.value) { |
| 64 | + // Do not submit: |
| 65 | + // - in error state |
| 66 | + // - same value as most recent saved value |
| 67 | + // - empty value |
| 68 | + if (inputValue.value === "") { |
| 69 | + state.value = State.ERROR; |
| 70 | + error.value = "This field is required"; |
| 71 | + return; |
| 72 | + } |
| 73 | +
|
| 74 | + if (state.value !== State.ERROR && !error.value && inputValue.value === lastSubmittedValue.value) { |
51 | 75 | return;
|
52 | 76 | }
|
53 | 77 |
|
54 | 78 | state.value = State.LOADING;
|
55 | 79 | lastSubmittedValue.value = inputValue.value;
|
56 | 80 |
|
57 |
| - const returnValue = await props.onSubmit(props.name, inputValue.value); |
| 81 | + const returnValue = await props.onSubmit(props.name, inputValue.value, props.settingType); |
58 | 82 |
|
59 | 83 | if (typeof returnValue === "boolean" && returnValue) {
|
60 | 84 | state.value = State.SUCCESS;
|
| 85 | + error.value = ""; |
| 86 | + emit("savedValueUpdated"); |
61 | 87 | } else {
|
62 | 88 | state.value = State.ERROR;
|
63 | 89 |
|
64 | 90 | if (typeof returnValue === "string") {
|
65 | 91 | error.value = returnValue;
|
66 | 92 | } else {
|
67 |
| - error.value = "Save error" |
| 93 | + error.value = "Save error"; |
68 | 94 | }
|
69 | 95 | }
|
70 | 96 | }
|
71 | 97 |
|
| 98 | +const calculatedInputType = computed(() => { |
| 99 | + if (props.inputType === "password" && !showPlainText.value) { |
| 100 | + return "password"; |
| 101 | + } |
| 102 | +
|
| 103 | + return "text"; |
| 104 | +}); |
| 105 | +
|
| 106 | +function togglePlaintext() { |
| 107 | + showPlainText.value = !showPlainText.value; |
| 108 | +} |
| 109 | +
|
72 | 110 | </script>
|
73 | 111 |
|
74 | 112 | <style scoped>
|
|
0 commit comments