quasar-ui-danx 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/package.json +8 -2
  2. package/src/components/ActionTable/ActionTable.vue +143 -0
  3. package/src/components/ActionTable/BatchActionMenu.vue +60 -0
  4. package/src/components/ActionTable/EmptyTableState.vue +33 -0
  5. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +36 -0
  6. package/src/components/ActionTable/Filters/FilterGroupItem.vue +28 -0
  7. package/src/components/ActionTable/Filters/FilterGroupList.vue +76 -0
  8. package/src/components/ActionTable/Filters/FilterListToggle.vue +50 -0
  9. package/src/components/ActionTable/Filters/FilterableField.vue +143 -0
  10. package/src/components/ActionTable/Filters/index.ts +5 -0
  11. package/src/components/ActionTable/Form/Fields/BooleanField.vue +37 -0
  12. package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +46 -0
  13. package/src/components/ActionTable/Form/Fields/DateField.vue +59 -0
  14. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +110 -0
  15. package/src/components/ActionTable/Form/Fields/DateTimeField.vue +50 -0
  16. package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +59 -0
  17. package/src/components/ActionTable/Form/Fields/EditableDiv.vue +39 -0
  18. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +32 -0
  19. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +78 -0
  20. package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +44 -0
  21. package/src/components/ActionTable/Form/Fields/IntegerField.vue +26 -0
  22. package/src/components/ActionTable/Form/Fields/LabelValueBlock.vue +22 -0
  23. package/src/components/ActionTable/Form/Fields/LabeledInput.vue +63 -0
  24. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +91 -0
  25. package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +57 -0
  26. package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +39 -0
  27. package/src/components/ActionTable/Form/Fields/NumberField.vue +94 -0
  28. package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +140 -0
  29. package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +136 -0
  30. package/src/components/ActionTable/Form/Fields/SelectField.vue +318 -0
  31. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +81 -0
  32. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +78 -0
  33. package/src/components/ActionTable/Form/Fields/TextField.vue +82 -0
  34. package/src/components/ActionTable/Form/Fields/WysiwygField.vue +46 -0
  35. package/src/components/ActionTable/Form/Fields/index.ts +23 -0
  36. package/src/components/ActionTable/Form/RenderedForm.vue +76 -0
  37. package/src/components/ActionTable/Form/index.ts +2 -0
  38. package/src/components/ActionTable/RenderComponentColumn.vue +22 -0
  39. package/src/components/ActionTable/TableSummaryRow.vue +95 -0
  40. package/src/components/ActionTable/index.ts +10 -0
  41. package/src/components/ActionTable/listActions.ts +362 -0
  42. package/src/components/ActionTable/listHelpers.ts +74 -0
  43. package/src/components/ActionTable/tableColumns.ts +72 -0
  44. package/src/components/DragAndDrop/HandleDraggable.vue +29 -29
  45. package/src/components/DragAndDrop/ListItemDraggable.vue +10 -10
  46. package/src/components/DragAndDrop/index.ts +0 -1
  47. package/src/components/DragAndDrop/listDragAndDrop.ts +1 -1
  48. package/src/components/Utility/CollapsableSidebar.vue +119 -0
  49. package/src/components/Utility/ContentDrawer.vue +70 -0
  50. package/src/components/Utility/Dialogs/ConfirmDialog.vue +132 -0
  51. package/src/components/Utility/Dialogs/FullScreenDialog.vue +46 -0
  52. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +105 -0
  53. package/src/components/Utility/Dialogs/InfoDialog.vue +92 -0
  54. package/src/components/Utility/Dialogs/InputDialog.vue +35 -0
  55. package/src/components/Utility/ImagePreview.vue +192 -0
  56. package/src/components/Utility/Popover/PopoverMenu.vue +64 -0
  57. package/src/components/Utility/Transitions/ListTransition.vue +50 -0
  58. package/src/components/Utility/Transitions/SlideTransition.vue +63 -0
  59. package/src/components/Utility/Transitions/StaggeredListTransition.vue +97 -0
  60. package/src/components/Utility/index.ts +11 -0
  61. package/src/components/index.ts +3 -0
  62. package/src/helpers/FileUpload.ts +295 -0
  63. package/src/helpers/FlashMessages.ts +79 -0
  64. package/src/helpers/array.ts +37 -0
  65. package/src/helpers/compatibility.ts +64 -0
  66. package/src/helpers/date.ts +5 -0
  67. package/src/helpers/download.ts +200 -0
  68. package/src/helpers/downloadPdf.ts +92 -0
  69. package/src/helpers/files.ts +52 -0
  70. package/src/helpers/formats.ts +183 -0
  71. package/src/helpers/http.ts +62 -0
  72. package/src/helpers/index.ts +12 -1
  73. package/src/helpers/multiFileUpload.ts +68 -0
  74. package/src/helpers/singleFileUpload.ts +54 -0
  75. package/src/helpers/storage.ts +8 -0
  76. package/src/index.esm.js +3 -4
  77. package/src/svg/FilterIcon.svg +7 -0
  78. package/src/svg/ImageIcon.svg +30 -0
  79. package/src/svg/PdfIcon.svg +21 -0
  80. package/src/svg/PercentIcon.svg +13 -0
  81. package/src/svg/TrashIcon.svg +15 -0
  82. package/src/svg/XIcon.svg +18 -0
  83. package/src/svg/index.ts +8 -0
  84. package/src/vendor/tinymce-config.ts +1 -0
  85. package/src/vue-plugin.js +7 -4
  86. package/tsconfig.json +14 -13
  87. package/src/components/DragAndDrop/Icons/index.ts +0 -2
  88. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleDotsIcon.svg +0 -0
  89. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleIcon.svg +0 -0
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ v-if="label"
5
+ class="font-bold text-xs mb-2"
6
+ >
7
+ {{ label }}
8
+ </div>
9
+ <div class="flex items-center cursor-pointer">
10
+ <DateIcon class="w-5 text-blue-base" />
11
+ <div class="font-bold ml-3 hover:text-blue-base">
12
+ <template v-if="date">
13
+ {{ formattedDate }}
14
+ </template>
15
+ <template v-else>
16
+ -&nbsp;-
17
+ </template>
18
+ </div>
19
+ </div>
20
+ <q-popup-proxy>
21
+ <q-date
22
+ v-model="date"
23
+ @update:model-value="onSave"
24
+ />
25
+ </q-popup-proxy>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { CalendarIcon as DateIcon } from '@heroicons/vue/outline';
31
+ import { fDate, parseQDate } from '@ui/helpers/formats';
32
+ import { computed, ref, watch } from 'vue';
33
+
34
+ const emit = defineEmits(['update:model-value']);
35
+ const props = defineProps({
36
+ modelValue: {
37
+ type: [String, Object],
38
+ default: null
39
+ },
40
+ label: {
41
+ type: String,
42
+ default: null
43
+ }
44
+ });
45
+
46
+ const formattedDate = computed(() => {
47
+ if (props.modelValue) {
48
+ return fDate(parseQDate(props.modelValue || '0000-00-00'));
49
+ }
50
+ return null;
51
+ });
52
+
53
+ const date = ref(props.modelValue);
54
+ watch(() => props.modelValue, val => date.value = val);
55
+
56
+ function onSave() {
57
+ emit('update:model-value', date.value);
58
+ }
59
+ </script>
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ v-if="label"
5
+ class="font-bold text-xs mb-2"
6
+ >
7
+ {{ label }}
8
+ </div>
9
+ <template v-if="inline">
10
+ <q-date
11
+ v-model="dateRange"
12
+ range
13
+ class="reactive"
14
+ @update:model-value="onSave"
15
+ />
16
+ </template>
17
+ <template v-else>
18
+ <div class="flex items-center cursor-pointer">
19
+ <DateIcon class="w-5 text-blue-base" />
20
+ <div class="font-bold ml-3 hover:text-blue-base">
21
+ <template v-if="dateRangeValue">
22
+ {{ formattedDates.from }} - {{ formattedDates.to }}
23
+ </template>
24
+ <template v-else>
25
+ -&nbsp;-
26
+ </template>
27
+ </div>
28
+ </div>
29
+ <q-popup-proxy>
30
+ <q-date
31
+ v-model="dateRange"
32
+ range
33
+ @update:model-value="onSave"
34
+ />
35
+ </q-popup-proxy>
36
+ </template>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { CalendarIcon as DateIcon } from '@heroicons/vue/outline';
42
+ import { fDate, parseQDate, parseQDateTime } from '@ui/helpers/formats';
43
+ import { computed, ref, watch } from 'vue';
44
+
45
+ const emit = defineEmits(['update:model-value']);
46
+ const props = defineProps({
47
+ modelValue: {
48
+ type: Object,
49
+ default: null
50
+ },
51
+ label: {
52
+ type: String,
53
+ default: null
54
+ },
55
+ inline: Boolean,
56
+ withTime: Boolean
57
+ });
58
+
59
+ const formattedDates = computed(() => {
60
+ if (dateRangeValue.value) {
61
+ if (props.withTime) {
62
+ return {
63
+ from: fDate(parseQDateTime(dateRangeValue.value.from || '0000-00-00')),
64
+ to: fDate(parseQDateTime(dateRangeValue.value.to || '9999-12-31'))
65
+ };
66
+ }
67
+
68
+ return {
69
+ from: fDate(parseQDate(dateRangeValue.value.from || '0000-00-00')),
70
+ to: fDate(parseQDate(dateRangeValue.value.to || '9999-12-31'))
71
+ };
72
+ }
73
+ return {
74
+ from: null,
75
+ to: null
76
+ };
77
+ });
78
+
79
+ const dateRange = ref(toQDateValue(props.modelValue));
80
+ watch(() => props.modelValue, val => dateRange.value = toQDateValue(val));
81
+
82
+ function toQDateValue(val) {
83
+ if (val?.from && val?.to) {
84
+ return fDate(val.from) === fDate(val.to) ? val.from : val;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ const dateRangeValue = computed(() => {
90
+ let range = dateRange.value;
91
+
92
+ if (typeof range === 'string') {
93
+ range = {
94
+ from: range,
95
+ to: range
96
+ };
97
+ }
98
+
99
+ if (range?.from && range?.to && props.withTime) {
100
+ range.from += ' 00:00:00';
101
+ range.to += ' 23:59:59';
102
+ }
103
+
104
+ return range;
105
+ });
106
+
107
+ function onSave() {
108
+ emit('update:model-value', dateRangeValue.value);
109
+ }
110
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div>
3
+ <q-input
4
+ :model-value="fLocalizedDateTime(modelValue)"
5
+ :color="color"
6
+ class="bg-white rounded overflow-hidden px-2 w-48"
7
+ dense
8
+ readonly
9
+ @click="isShowing = true"
10
+ >
11
+ <template #append>
12
+ <q-icon name="event" class="cursor-pointer">
13
+ <q-popup-proxy v-model="isShowing">
14
+ <DateTimePicker
15
+ v-model="dateTime"
16
+ @cancel="isShowing = false"
17
+ @save="onSave"
18
+ />
19
+ </q-popup-proxy>
20
+ </q-icon>
21
+ </template>
22
+ </q-input>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup>
27
+ import { fLocalizedDateTime } from '@ui/helpers/formats';
28
+ import { ref } from 'vue';
29
+ import DateTimePicker from './DateTimePicker';
30
+
31
+ const emit = defineEmits(['update:model-value']);
32
+ const props = defineProps({
33
+ modelValue: {
34
+ type: String,
35
+ required: true
36
+ },
37
+ color: {
38
+ type: String,
39
+ default: 'blue-base'
40
+ }
41
+ });
42
+
43
+ const isShowing = ref(false);
44
+ const dateTime = ref(props.modelValue);
45
+
46
+ function onSave() {
47
+ emit('update:model-value', dateTime.value);
48
+ isShowing.value = false;
49
+ }
50
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <q-popup-proxy
3
+ :model-value="true"
4
+ cover
5
+ transition-show="scale"
6
+ transition-hide="scale"
7
+ class="bg-transparent shadow-none flex items-stretch"
8
+ >
9
+ <q-date v-model="dateTime" :mask="mask" :color="color">
10
+ <div class="flex items-center justify-center">
11
+ <div v-if="nullable" class="flex-grow">
12
+ <q-btn label="Clear" color="blue-base" flat @click="dateTime = null" />
13
+ </div>
14
+ <div>
15
+ <q-btn
16
+ label="Cancel"
17
+ color="blue-base"
18
+ flat
19
+ @click="$emit('cancel')"
20
+ />
21
+ <q-btn label="Set" color="blue-base" flat @click="$emit('save')" />
22
+ </div>
23
+ </div>
24
+ </q-date>
25
+ <q-time v-model="dateTime" :mask="mask" :color="color" class="ml-3" />
26
+ </q-popup-proxy>
27
+ </template>
28
+ <script setup>
29
+ import { dbDateTime, localizedDateTime, remoteDateTime } from '@ui/helpers/formats';
30
+ import { computed } from 'vue';
31
+
32
+ const emit = defineEmits(['update:modelValue', 'save', 'cancel', 'clear']);
33
+ const props = defineProps({
34
+ modelValue: {
35
+ type: String,
36
+ default: null
37
+ },
38
+ mask: {
39
+ type: String,
40
+ default: 'YYYY-MM-DD HH:mm'
41
+ },
42
+ color: {
43
+ type: String,
44
+ default: 'blue-base'
45
+ },
46
+ nullable: Boolean
47
+ });
48
+
49
+ const dateTime = computed({
50
+ get: () => dbDateTime(localizedDateTime(props.modelValue)),
51
+ set(value) {
52
+ const newValue = value ? dbDateTime(remoteDateTime(value)) : null;
53
+
54
+ if (newValue || props.nullable) {
55
+ emit('update:modelValue', newValue);
56
+ }
57
+ }
58
+ });
59
+ </script>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <div
3
+ contenteditable
4
+ class="inline-block hover:bg-blue-light focus:bg-blue-light transition duration-300 outline-none"
5
+ @input="onInput"
6
+ >
7
+ {{ text }}
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ import { useDebounceFn } from "@vueuse/core";
13
+ import { ref } from "vue";
14
+
15
+ const emit = defineEmits(["update:model-value", "change"]);
16
+ const props = defineProps({
17
+ modelValue: {
18
+ type: String,
19
+ required: true
20
+ },
21
+ debounceDelay: {
22
+ type: Number,
23
+ default: 500
24
+ }
25
+ });
26
+
27
+ const text = ref(props.modelValue);
28
+
29
+ const debouncedChange = useDebounceFn(() => {
30
+ emit("update:model-value", text.value);
31
+ emit("change", text.value);
32
+ }, props.debounceDelay);
33
+
34
+ function onInput(e) {
35
+ text.value = e.target.innerText;
36
+ debouncedChange();
37
+ }
38
+
39
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <span>
3
+ <slot>
4
+ {{ labelText }}
5
+ <template v-if="showName">({{ field?.name }})</template>
6
+ </slot>
7
+ <span
8
+ v-if="requiredLabel"
9
+ class="text-red-dark ml-1 text-xs bottom-1 relative"
10
+ >{{ requiredLabel }}</span>
11
+ </span>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { computed } from "vue";
16
+
17
+ const props = defineProps({
18
+ field: {
19
+ type: Object,
20
+ default: null
21
+ },
22
+ label: {
23
+ type: String,
24
+ default: null
25
+ },
26
+ showName: Boolean,
27
+ required: Boolean
28
+ });
29
+
30
+ const labelText = computed(() => props.label || props.field?.label);
31
+ const requiredLabel = computed(() => props.field?.required_group || (props.required || props.field?.required ? "*" : ""));
32
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <q-btn v-bind="$props" @click="$refs.fileUpload.click()">
3
+ <slot>
4
+ <PlusIcon class="w-5 mr-2" />
5
+ {{ text }}
6
+ </slot>
7
+
8
+ <input
9
+ ref="fileUpload"
10
+ data-testid="file-upload"
11
+ type="file"
12
+ :accept="geolocation ? 'image/*;capture=camera' : undefined"
13
+ :capture="geolocation ? 'environment' : undefined"
14
+ class="hidden"
15
+ multiple
16
+ @change="onAttachFiles"
17
+ />
18
+ </q-btn>
19
+ </template>
20
+ <script setup>
21
+ import { PlusIcon } from '@heroicons/vue/outline';
22
+ import { FileUpload } from '@ui/helpers';
23
+ import { QBtn } from 'quasar';
24
+ import { ref } from 'vue';
25
+
26
+ defineExpose({ upload });
27
+ const emit = defineEmits([
28
+ 'uploading',
29
+ 'file-progress',
30
+ 'file-complete',
31
+ 'complete'
32
+ ]);
33
+ const props = defineProps({
34
+ ...QBtn.props,
35
+ text: {
36
+ type: String,
37
+ default: 'Add File'
38
+ },
39
+ locationWaitMessage: {
40
+ type: String,
41
+ default: 'Waiting for location...'
42
+ },
43
+ geolocation: Boolean
44
+ });
45
+
46
+ const fileUpload = ref(null);
47
+
48
+ function upload() {
49
+ fileUpload.value.click();
50
+ }
51
+
52
+ /**
53
+ * Upload newly attached files and emit events for progress / completion
54
+ *
55
+ * @param files
56
+ * @returns {Promise<void>}
57
+ */
58
+ async function onAttachFiles({ target: { files } }) {
59
+ emit('uploading', files);
60
+ let fileUpload = new FileUpload(files)
61
+ .onProgress(({ file, progress }) => {
62
+ file.progress = progress;
63
+ emit('file-progress', file);
64
+ })
65
+ .onComplete(({ file, uploadedFile }) => {
66
+ emit('file-complete', { file, uploadedFile });
67
+ })
68
+ .onAllComplete(() => {
69
+ emit('complete', fileUpload.files);
70
+ });
71
+
72
+ if (props.geolocation) {
73
+ await fileUpload.resolveLocation(props.locationWaitMessage);
74
+ }
75
+
76
+ fileUpload.upload();
77
+ }
78
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <div class="inline-block">
3
+ <div
4
+ class="cursor-pointer py-2 hover:bg-blue-light flex items-center justify-end"
5
+ >
6
+ {{ fLocalizedDateTime(modelValue, { empty: 'Never' }) }}
7
+ <EditIcon class="w-4 font-bold ml-2 text-gray-base" />
8
+ <q-popup-edit
9
+ v-slot="scope"
10
+ :model-value="modelValue"
11
+ touch-position
12
+ :offset="[0, 20]"
13
+ class="bg-blue-base text-white !min-w-0"
14
+ >
15
+ <DateTimePicker
16
+ v-model="scope.value"
17
+ :nullable="nullable"
18
+ @save="onSave(scope)"
19
+ @cancel="scope.cancel"
20
+ />
21
+ </q-popup-edit>
22
+ </div>
23
+ </div>
24
+ </template>
25
+ <script setup>
26
+ import { PencilIcon as EditIcon } from '@heroicons/vue/solid';
27
+ import { fLocalizedDateTime } from '@ui/helpers/formats';
28
+ import DateTimePicker from './DateTimePicker';
29
+
30
+ const emit = defineEmits(['close', 'save', 'update:model-value']);
31
+ defineProps({
32
+ modelValue: {
33
+ type: String,
34
+ default: null
35
+ },
36
+ nullable: Boolean
37
+ });
38
+
39
+ function onSave(scope) {
40
+ emit('update:model-value', scope.value);
41
+ emit('save', scope.value);
42
+ scope.set();
43
+ }
44
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <NumberField
3
+ :field="field"
4
+ :precision="0"
5
+ :model-value="modelValue"
6
+ :show-name="showName"
7
+ @update:model-value="$emit('update:model-value', $event)"
8
+ />
9
+ </template>
10
+
11
+ <script setup>
12
+ import NumberField from './NumberField';
13
+
14
+ defineEmits(['update:model-value']);
15
+ defineProps({
16
+ modelValue: {
17
+ type: [String, Number],
18
+ required: true
19
+ },
20
+ field: {
21
+ type: Object,
22
+ required: true
23
+ },
24
+ showName: Boolean
25
+ });
26
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div>
3
+ <div class="text-xs font-bold">{{ label }}</div>
4
+ <div :class="{'mt-2': !dense, 'mt-1': dense, 'text-no-wrap': nowrap}">
5
+ <slot>{{ value || "-" }}</slot>
6
+ </div>
7
+ </div>
8
+ </template>
9
+ <script setup>
10
+ defineProps({
11
+ label: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ value: {
16
+ type: [String, Number],
17
+ default: "-"
18
+ },
19
+ dense: Boolean,
20
+ nowrap: Boolean
21
+ });
22
+ </script>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div>
3
+ <label for="first_name" class="form-label">
4
+ <slot name="label">{{ label }}</slot>
5
+ </label>
6
+ <div class="mt-1">
7
+ <q-input
8
+ :model-value="modelValue"
9
+ :error-message="error"
10
+ :error="!!error"
11
+ no-error-icon
12
+ outlined
13
+ dense
14
+ :disable="disabled"
15
+ :readonly="disabled"
16
+ :type="type"
17
+ :name="name"
18
+ :placeholder="placeholder || label"
19
+ :required="required"
20
+ :rules="rules"
21
+ :lazy-rules="!!rules"
22
+ class="w-full"
23
+ @update:model-value="$emit('update:model-value', $event)"
24
+ />
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ defineEmits(['update:model-value']);
31
+ defineProps({
32
+ type: {
33
+ type: String,
34
+ default: 'text'
35
+ },
36
+ name: {
37
+ type: String,
38
+ required: true
39
+ },
40
+ label: {
41
+ type: String,
42
+ default: null
43
+ },
44
+ placeholder: {
45
+ type: String,
46
+ default: null
47
+ },
48
+ modelValue: {
49
+ type: [String, Number],
50
+ required: true
51
+ },
52
+ error: {
53
+ type: String,
54
+ default: null
55
+ },
56
+ required: Boolean,
57
+ disabled: Boolean,
58
+ rules: {
59
+ type: Array,
60
+ default: null
61
+ }
62
+ });
63
+ </script>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <div
3
+ class="max-w-full relative overflow-auto"
4
+ :class="{'p-4 border rounded border-gray-medium': !readonly}"
5
+ @dragover.prevent
6
+ @drop.prevent="onDrop"
7
+ >
8
+ <FieldLabel
9
+ :field="field"
10
+ :show-name="showName"
11
+ class="text-sm font-semibold"
12
+ />
13
+ <div
14
+ v-if="!disable && !readonly"
15
+ class="text-sm my-2"
16
+ >
17
+ <a
18
+ class="text-blue-base"
19
+ @click="$refs.file.click()"
20
+ >Upload</a>
21
+ <a
22
+ v-if="uploadedFiles.length > 0"
23
+ class="ml-3 text-red-dark"
24
+ @click="onClear"
25
+ >Clear</a>
26
+ <input
27
+ ref="file"
28
+ class="hidden"
29
+ type="file"
30
+ multiple
31
+ @change="onFilesSelected"
32
+ />
33
+ </div>
34
+
35
+ <div class="max-w-[50em] flex items-stretch justify-start">
36
+ <ImagePreview
37
+ v-for="file in uploadedFiles"
38
+ :key="'file-upload-' + file.id"
39
+ class="w-32 m-2 cursor-pointer bg-neutral-plus-5"
40
+ :class="{'border border-dashed border-blue-base': !uploadedFiles.length}"
41
+ :image="file"
42
+ :related-files="uploadedFiles"
43
+ downloadable
44
+ :removable="!readonly && !disable"
45
+ @remove="onRemove(file)"
46
+ />
47
+ <ImagePreview
48
+ v-if="!disable && !readonly"
49
+ class="w-32 m-2 cursor-pointer border border-dashed border-blue-base"
50
+ disabled
51
+ @click="$refs.file.click()"
52
+ />
53
+ <div
54
+ v-if="readonly && uploadedFiles.length === 0"
55
+ class="p-1"
56
+ >
57
+ --
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <script setup>
64
+ import { ImagePreview } from '@ui/components';
65
+ import { useMultiFileUpload } from '@ui/helpers';
66
+ import { onMounted } from 'vue';
67
+ import FieldLabel from './FieldLabel';
68
+
69
+ const emit = defineEmits(['update:model-value']);
70
+ const props = defineProps({
71
+ modelValue: {
72
+ type: [Object, String],
73
+ default: null
74
+ },
75
+ field: {
76
+ type: Object,
77
+ required: true
78
+ },
79
+ showName: Boolean,
80
+ disable: Boolean,
81
+ readonly: Boolean
82
+ });
83
+
84
+ const { onComplete, onDrop, onFilesSelected, uploadedFiles, onClear, onRemove } = useMultiFileUpload();
85
+ onMounted(() => {
86
+ if (props.modelValue) {
87
+ uploadedFiles.value = props.modelValue;
88
+ }
89
+ });
90
+ onComplete(() => emit('update:model-value', uploadedFiles.value));
91
+ </script>