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.
- package/package.json +8 -2
- package/src/components/ActionTable/ActionTable.vue +143 -0
- package/src/components/ActionTable/BatchActionMenu.vue +60 -0
- package/src/components/ActionTable/EmptyTableState.vue +33 -0
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +36 -0
- package/src/components/ActionTable/Filters/FilterGroupItem.vue +28 -0
- package/src/components/ActionTable/Filters/FilterGroupList.vue +76 -0
- package/src/components/ActionTable/Filters/FilterListToggle.vue +50 -0
- package/src/components/ActionTable/Filters/FilterableField.vue +143 -0
- package/src/components/ActionTable/Filters/index.ts +5 -0
- package/src/components/ActionTable/Form/Fields/BooleanField.vue +37 -0
- package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +46 -0
- package/src/components/ActionTable/Form/Fields/DateField.vue +59 -0
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +110 -0
- package/src/components/ActionTable/Form/Fields/DateTimeField.vue +50 -0
- package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +59 -0
- package/src/components/ActionTable/Form/Fields/EditableDiv.vue +39 -0
- package/src/components/ActionTable/Form/Fields/FieldLabel.vue +32 -0
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +78 -0
- package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +44 -0
- package/src/components/ActionTable/Form/Fields/IntegerField.vue +26 -0
- package/src/components/ActionTable/Form/Fields/LabelValueBlock.vue +22 -0
- package/src/components/ActionTable/Form/Fields/LabeledInput.vue +63 -0
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +91 -0
- package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +57 -0
- package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +39 -0
- package/src/components/ActionTable/Form/Fields/NumberField.vue +94 -0
- package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +140 -0
- package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +136 -0
- package/src/components/ActionTable/Form/Fields/SelectField.vue +318 -0
- package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +81 -0
- package/src/components/ActionTable/Form/Fields/SingleFileField.vue +78 -0
- package/src/components/ActionTable/Form/Fields/TextField.vue +82 -0
- package/src/components/ActionTable/Form/Fields/WysiwygField.vue +46 -0
- package/src/components/ActionTable/Form/Fields/index.ts +23 -0
- package/src/components/ActionTable/Form/RenderedForm.vue +76 -0
- package/src/components/ActionTable/Form/index.ts +2 -0
- package/src/components/ActionTable/RenderComponentColumn.vue +22 -0
- package/src/components/ActionTable/TableSummaryRow.vue +95 -0
- package/src/components/ActionTable/index.ts +10 -0
- package/src/components/ActionTable/listActions.ts +362 -0
- package/src/components/ActionTable/listHelpers.ts +74 -0
- package/src/components/ActionTable/tableColumns.ts +72 -0
- package/src/components/DragAndDrop/HandleDraggable.vue +29 -29
- package/src/components/DragAndDrop/ListItemDraggable.vue +10 -10
- package/src/components/DragAndDrop/index.ts +0 -1
- package/src/components/DragAndDrop/listDragAndDrop.ts +1 -1
- package/src/components/Utility/CollapsableSidebar.vue +119 -0
- package/src/components/Utility/ContentDrawer.vue +70 -0
- package/src/components/Utility/Dialogs/ConfirmDialog.vue +132 -0
- package/src/components/Utility/Dialogs/FullScreenDialog.vue +46 -0
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +105 -0
- package/src/components/Utility/Dialogs/InfoDialog.vue +92 -0
- package/src/components/Utility/Dialogs/InputDialog.vue +35 -0
- package/src/components/Utility/ImagePreview.vue +192 -0
- package/src/components/Utility/Popover/PopoverMenu.vue +64 -0
- package/src/components/Utility/Transitions/ListTransition.vue +50 -0
- package/src/components/Utility/Transitions/SlideTransition.vue +63 -0
- package/src/components/Utility/Transitions/StaggeredListTransition.vue +97 -0
- package/src/components/Utility/index.ts +11 -0
- package/src/components/index.ts +3 -0
- package/src/helpers/FileUpload.ts +295 -0
- package/src/helpers/FlashMessages.ts +79 -0
- package/src/helpers/array.ts +37 -0
- package/src/helpers/compatibility.ts +64 -0
- package/src/helpers/date.ts +5 -0
- package/src/helpers/download.ts +200 -0
- package/src/helpers/downloadPdf.ts +92 -0
- package/src/helpers/files.ts +52 -0
- package/src/helpers/formats.ts +183 -0
- package/src/helpers/http.ts +62 -0
- package/src/helpers/index.ts +12 -1
- package/src/helpers/multiFileUpload.ts +68 -0
- package/src/helpers/singleFileUpload.ts +54 -0
- package/src/helpers/storage.ts +8 -0
- package/src/index.esm.js +3 -4
- package/src/svg/FilterIcon.svg +7 -0
- package/src/svg/ImageIcon.svg +30 -0
- package/src/svg/PdfIcon.svg +21 -0
- package/src/svg/PercentIcon.svg +13 -0
- package/src/svg/TrashIcon.svg +15 -0
- package/src/svg/XIcon.svg +18 -0
- package/src/svg/index.ts +8 -0
- package/src/vendor/tinymce-config.ts +1 -0
- package/src/vue-plugin.js +7 -4
- package/tsconfig.json +14 -13
- package/src/components/DragAndDrop/Icons/index.ts +0 -2
- /package/src/{components/DragAndDrop/Icons → svg}/DragHandleDotsIcon.svg +0 -0
- /package/src/{components/DragAndDrop/Icons → svg}/DragHandleIcon.svg +0 -0
@@ -0,0 +1,318 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<q-select
|
4
|
+
ref="selectField"
|
5
|
+
v-bind="$props"
|
6
|
+
:model-value="selectedValue"
|
7
|
+
outlined
|
8
|
+
hide-dropdown-icon
|
9
|
+
dense
|
10
|
+
emit-value
|
11
|
+
:use-input="filterable"
|
12
|
+
:hide-selected="filterable && isShowing && !$props.multiple"
|
13
|
+
:input-debounce="100"
|
14
|
+
:options="filteredOptions"
|
15
|
+
option-label="label"
|
16
|
+
option-value="value"
|
17
|
+
placeholder=""
|
18
|
+
:input-class="{'is-hidden': !isShowing, [inputClass]: true}"
|
19
|
+
class="max-w-full"
|
20
|
+
@filter="onFilter"
|
21
|
+
@clear="onClear"
|
22
|
+
@popup-show="onShow"
|
23
|
+
@popup-hide="onHide"
|
24
|
+
@update:model-value="onUpdate"
|
25
|
+
>
|
26
|
+
<template #append>
|
27
|
+
<DropDownIcon
|
28
|
+
class="w-4 transition"
|
29
|
+
:class="isShowing ? 'rotate-180' : ''"
|
30
|
+
/>
|
31
|
+
</template>
|
32
|
+
<template #selected>
|
33
|
+
<div
|
34
|
+
v-if="$props.multiple"
|
35
|
+
class="flex gap-y-1 overflow-hidden"
|
36
|
+
:class="{'flex-nowrap gap-y-0': chipLimit === 1, [selectionClass]: true}"
|
37
|
+
>
|
38
|
+
<template v-if="chipOptions.length > 0">
|
39
|
+
<q-chip
|
40
|
+
v-for="chipOption in chipOptions"
|
41
|
+
:key="'selected-' + chipOption.label"
|
42
|
+
class="!mr-1"
|
43
|
+
>{{ chipOption.label }}
|
44
|
+
</q-chip>
|
45
|
+
<q-chip
|
46
|
+
v-if="selectedOptions.length > chipOptions.length"
|
47
|
+
class="!mr-1"
|
48
|
+
>
|
49
|
+
+{{ selectedOptions.length - chipOptions.length }}
|
50
|
+
</q-chip>
|
51
|
+
</template>
|
52
|
+
<template v-else>
|
53
|
+
{{ placeholder }}
|
54
|
+
</template>
|
55
|
+
</div>
|
56
|
+
<div
|
57
|
+
v-else
|
58
|
+
:class="selectionClass"
|
59
|
+
>{{ selectedLabel }}
|
60
|
+
</div>
|
61
|
+
</template>
|
62
|
+
</q-select>
|
63
|
+
</div>
|
64
|
+
</template>
|
65
|
+
<script setup>
|
66
|
+
import { ChevronDownIcon as DropDownIcon } from '@heroicons/vue/outline';
|
67
|
+
import { QSelect } from 'quasar';
|
68
|
+
import { computed, isRef, nextTick, ref } from 'vue';
|
69
|
+
|
70
|
+
const emit = defineEmits(['update:model-value', 'search']);
|
71
|
+
const props = defineProps({
|
72
|
+
...QSelect.props,
|
73
|
+
modelValue: {
|
74
|
+
type: [Array, String, Number, Object],
|
75
|
+
default: undefined
|
76
|
+
},
|
77
|
+
placeholder: {
|
78
|
+
type: String,
|
79
|
+
default: ''
|
80
|
+
},
|
81
|
+
selectionLabel: {
|
82
|
+
type: String,
|
83
|
+
default: null
|
84
|
+
},
|
85
|
+
chipLimit: {
|
86
|
+
type: Number,
|
87
|
+
default: 3
|
88
|
+
},
|
89
|
+
inputClass: {
|
90
|
+
type: String,
|
91
|
+
default: ''
|
92
|
+
},
|
93
|
+
selectionClass: {
|
94
|
+
type: String,
|
95
|
+
default: ''
|
96
|
+
},
|
97
|
+
options: {
|
98
|
+
type: Array,
|
99
|
+
default: () => []
|
100
|
+
},
|
101
|
+
filterable: Boolean,
|
102
|
+
filterFn: {
|
103
|
+
type: Function,
|
104
|
+
default: null
|
105
|
+
}
|
106
|
+
});
|
107
|
+
|
108
|
+
const selectField = ref(null);
|
109
|
+
|
110
|
+
// The filter applied to the dropdown list of options
|
111
|
+
const filter = ref(null);
|
112
|
+
// Is showing the dropdown list
|
113
|
+
const isShowing = ref(false);
|
114
|
+
|
115
|
+
/**
|
116
|
+
* The options formatted so each has a label, value and selectionLabel
|
117
|
+
* @type {ComputedRef<{selectionLabel: string, label: string, value: string|*}[]>}
|
118
|
+
*/
|
119
|
+
const computedOptions = computed(() => {
|
120
|
+
let options = props.options;
|
121
|
+
if (props.placeholder && !props.multiple && !props.filterable) {
|
122
|
+
options = [{ label: props.placeholder, value: null }, ...props.options];
|
123
|
+
}
|
124
|
+
options = options.map((o) => {
|
125
|
+
let opt = isRef(o) ? o.value : o;
|
126
|
+
return {
|
127
|
+
label: resolveLabel(opt),
|
128
|
+
value: resolveValue(opt),
|
129
|
+
selectionLabel: resolveSelectionLabel(opt)
|
130
|
+
};
|
131
|
+
});
|
132
|
+
return options;
|
133
|
+
});
|
134
|
+
|
135
|
+
const filteredOptions = computed(() => {
|
136
|
+
if (filter.value && !props.filterFn) {
|
137
|
+
return computedOptions.value.filter(o => o.label.toLocaleLowerCase().indexOf(filter.value.toLowerCase()) > -1);
|
138
|
+
} else {
|
139
|
+
return computedOptions.value;
|
140
|
+
}
|
141
|
+
});
|
142
|
+
|
143
|
+
/**
|
144
|
+
* The value selected. Basically a wrapper to better handle null values (see onUpdate function for more details)
|
145
|
+
* @type {ComputedRef<unknown>}
|
146
|
+
*/
|
147
|
+
const selectedValue = computed(() => {
|
148
|
+
if (props.multiple) {
|
149
|
+
const arrVal = Array.isArray(props.modelValue) ? props.modelValue : [];
|
150
|
+
return arrVal.map((v) => {
|
151
|
+
return v === null ? '__null__' : v;
|
152
|
+
}) || [];
|
153
|
+
} else {
|
154
|
+
return props.modelValue === null ? '__null__' : props.modelValue;
|
155
|
+
}
|
156
|
+
});
|
157
|
+
|
158
|
+
/**
|
159
|
+
* The selected options. The list of options from the computedOptions list that are selected as defined by the modelValue
|
160
|
+
* @type {ComputedRef<*>}
|
161
|
+
*/
|
162
|
+
const selectedOptions = computed(() => {
|
163
|
+
let values = selectedValue.value;
|
164
|
+
if (!props.multiple) {
|
165
|
+
values = (values || values === 0) ? [values] : [];
|
166
|
+
}
|
167
|
+
return computedOptions.value.filter((o) => {
|
168
|
+
return values.includes(o.value) || values.map(v => typeof v === 'object' && v.id).includes(o.value?.id);
|
169
|
+
});
|
170
|
+
});
|
171
|
+
|
172
|
+
/**
|
173
|
+
* The label to display in the input field of the dropdown for a selected state, non-selected state or an active filtering state.
|
174
|
+
* NOTE: Only applies to single select (not multiselect)
|
175
|
+
* @type {ComputedRef<unknown>}
|
176
|
+
*/
|
177
|
+
const selectedLabel = computed(() => {
|
178
|
+
if (props.filterable && isShowing.value) return '';
|
179
|
+
|
180
|
+
if (!selectedOptions.value || selectedOptions.value.length === 0) {
|
181
|
+
return props.placeholder || '(Select Option)';
|
182
|
+
}
|
183
|
+
return selectedOptions.value[0].selectionLabel;
|
184
|
+
});
|
185
|
+
|
186
|
+
/**
|
187
|
+
* The options to display as chips (limited by chipLimit prop)
|
188
|
+
* @type {ComputedRef<*>}
|
189
|
+
*/
|
190
|
+
const chipOptions = computed(() => {
|
191
|
+
return selectedOptions.value.slice(0, props.chipLimit);
|
192
|
+
});
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Resolve the label of the option. This will display as the text in the dropdown list
|
196
|
+
* @param option
|
197
|
+
* @returns {*|string}
|
198
|
+
*/
|
199
|
+
function resolveLabel(option) {
|
200
|
+
if (typeof option === 'string') {
|
201
|
+
return option;
|
202
|
+
}
|
203
|
+
if (typeof props.optionLabel === 'string') {
|
204
|
+
return option[props.optionLabel];
|
205
|
+
}
|
206
|
+
if (typeof props.optionLabel === 'function') {
|
207
|
+
return props.optionLabel(option);
|
208
|
+
}
|
209
|
+
return option?.label;
|
210
|
+
}
|
211
|
+
|
212
|
+
/**
|
213
|
+
* Resolve the label to display in the input field of the dropdown when an option is selected
|
214
|
+
* NOTE: Does not apply to multiselect, single select only
|
215
|
+
* @param option
|
216
|
+
* @returns {*|{default: null, type: String | StringConstructor}|string}
|
217
|
+
*/
|
218
|
+
function resolveSelectionLabel(option) {
|
219
|
+
if (typeof option === 'string') {
|
220
|
+
return option;
|
221
|
+
}
|
222
|
+
if (typeof props.selectionLabel === 'string') {
|
223
|
+
return option[props.selectionLabel];
|
224
|
+
}
|
225
|
+
if (typeof props.selectionLabel === 'function') {
|
226
|
+
return props.selectionLabel(option);
|
227
|
+
}
|
228
|
+
return option?.selectionLabel || option?.label;
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Resolve the value of the option using either defaults or the provided optionValue prop
|
233
|
+
* @param option
|
234
|
+
* @returns {string|*|string}
|
235
|
+
*/
|
236
|
+
function resolveValue(option) {
|
237
|
+
if (typeof option === 'string') {
|
238
|
+
return option;
|
239
|
+
}
|
240
|
+
let value = option.value;
|
241
|
+
if (typeof props.optionValue === 'string') {
|
242
|
+
value = option[props.optionValue];
|
243
|
+
} else if (typeof props.optionValue === 'function') {
|
244
|
+
value = props.optionValue(option);
|
245
|
+
}
|
246
|
+
// Note the __null__ special case here. See the onUpdate function for more details
|
247
|
+
return value === null ? '__null__' : value;
|
248
|
+
}
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Handle the update event from the QSelect
|
252
|
+
* NOTE: casts all null values as a special __null__ string as the null character is handled the same as undefined by QSelect.
|
253
|
+
* SelectField differentiates between these 2 to provide a null value selected state vs undefined is no value selected.
|
254
|
+
* @param value
|
255
|
+
*/
|
256
|
+
function onUpdate(value) {
|
257
|
+
if (Array.isArray(value)) {
|
258
|
+
value = value.map((v) => v === '__null__' ? null : v);
|
259
|
+
}
|
260
|
+
emit('update:model-value', value === '__null__' ? null : value);
|
261
|
+
}
|
262
|
+
|
263
|
+
/** XXX: This tells us when we should apply the filter. QSelect likes to trigger a new filter everytime you open the dropdown
|
264
|
+
* But most of the time when you open the dropdown it is already filtered and annoying to try and clear the previous filter
|
265
|
+
**/
|
266
|
+
const shouldFilter = ref(false);
|
267
|
+
|
268
|
+
/**
|
269
|
+
* Filter the options list. Do this asynchronously for the update so that the QSelect can update its internal state first, then update SelectField
|
270
|
+
* @param val
|
271
|
+
* @param update
|
272
|
+
*/
|
273
|
+
async function onFilter(val, update) {
|
274
|
+
if (!props.filterFn) {
|
275
|
+
filter.value = val;
|
276
|
+
await nextTick(update);
|
277
|
+
} else {
|
278
|
+
update();
|
279
|
+
if (shouldFilter.value === false) return;
|
280
|
+
if (val !== null && val !== filter.value) {
|
281
|
+
filter.value = val;
|
282
|
+
if (props.filterFn) {
|
283
|
+
await props.filterFn(val);
|
284
|
+
}
|
285
|
+
}
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
/**
|
290
|
+
* Clear the selected value using undefined. SelectField differentiates between null and undefined.
|
291
|
+
* See the onUpdate function for more details
|
292
|
+
*/
|
293
|
+
function onClear() {
|
294
|
+
emit('update:model-value', undefined);
|
295
|
+
}
|
296
|
+
|
297
|
+
/**
|
298
|
+
* Handle behavior when showing the dropdown
|
299
|
+
*/
|
300
|
+
function onShow() {
|
301
|
+
isShowing.value = true;
|
302
|
+
|
303
|
+
// XXX: See description on shouldFilter declaration. Only allow filtering after dropdown is ALREADY opened
|
304
|
+
shouldFilter.value = false;
|
305
|
+
nextTick(() => {
|
306
|
+
shouldFilter.value = true;
|
307
|
+
selectField.value.focus();
|
308
|
+
});
|
309
|
+
}
|
310
|
+
|
311
|
+
/**
|
312
|
+
* Handle behavior when hiding the dropdown
|
313
|
+
*/
|
314
|
+
function onHide() {
|
315
|
+
isShowing.value = false;
|
316
|
+
shouldFilter.value = false;
|
317
|
+
}
|
318
|
+
</script>
|
@@ -0,0 +1,81 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<template v-if="!loading && !options.length">
|
4
|
+
<div class="text-gray-silver">No options available</div>
|
5
|
+
</template>
|
6
|
+
<SelectField
|
7
|
+
v-model="selectedOption"
|
8
|
+
:options="options"
|
9
|
+
:label="label"
|
10
|
+
:placeholder="placeholder"
|
11
|
+
:option-value="opt => opt"
|
12
|
+
:loading="loading"
|
13
|
+
@update:model-value="onSelectOption"
|
14
|
+
/>
|
15
|
+
<div v-if="selectedOption">
|
16
|
+
<q-checkbox
|
17
|
+
v-for="child in selectedOption.children"
|
18
|
+
:key="child.id"
|
19
|
+
:model-value="selectedChildren.includes(child.id)"
|
20
|
+
:field="child"
|
21
|
+
class="mt-3"
|
22
|
+
@update:model-value="onSelectChild(child)"
|
23
|
+
>
|
24
|
+
<div>{{ child.label }}</div>
|
25
|
+
<div class="text-xs text-gray-silver">{{ child.name }}</div>
|
26
|
+
</q-checkbox>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</template>
|
30
|
+
<script setup>
|
31
|
+
import { remove } from '@ui/helpers/array';
|
32
|
+
import { ref, watch } from 'vue';
|
33
|
+
import SelectField from './SelectField';
|
34
|
+
|
35
|
+
const emit = defineEmits(['update:model-value']);
|
36
|
+
const props = defineProps({
|
37
|
+
modelValue: {
|
38
|
+
type: Array,
|
39
|
+
default: () => ([])
|
40
|
+
},
|
41
|
+
label: {
|
42
|
+
type: String,
|
43
|
+
default: 'Selection'
|
44
|
+
},
|
45
|
+
placeholder: {
|
46
|
+
type: String,
|
47
|
+
default: 'Select an option'
|
48
|
+
},
|
49
|
+
options: {
|
50
|
+
type: Array,
|
51
|
+
default: () => []
|
52
|
+
},
|
53
|
+
loading: Boolean
|
54
|
+
});
|
55
|
+
|
56
|
+
function resolveSelectedOption() {
|
57
|
+
if (props.modelValue?.length > 0) {
|
58
|
+
return props.options.find((option) => option.children.find(child => props.modelValue.includes(child.id)));
|
59
|
+
}
|
60
|
+
|
61
|
+
return null;
|
62
|
+
}
|
63
|
+
const selectedOption = ref(resolveSelectedOption());
|
64
|
+
const selectedChildren = ref(props.modelValue || []);
|
65
|
+
function onSelectChild(child) {
|
66
|
+
if (selectedChildren.value.includes(child.id)) {
|
67
|
+
selectedChildren.value = remove(selectedChildren.value, child.id);
|
68
|
+
} else {
|
69
|
+
selectedChildren.value.push(child.id);
|
70
|
+
}
|
71
|
+
emit('update:model-value', selectedChildren.value.length > 0 ? selectedChildren.value : undefined);
|
72
|
+
}
|
73
|
+
function onSelectOption() {
|
74
|
+
selectedChildren.value = [];
|
75
|
+
emit('update:model-value', undefined);
|
76
|
+
}
|
77
|
+
watch(() => props.modelValue, (value) => {
|
78
|
+
selectedOption.value = resolveSelectedOption();
|
79
|
+
selectedChildren.value = value || [];
|
80
|
+
});
|
81
|
+
</script>
|
@@ -0,0 +1,78 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
class="max-w-full relative overflow-auto"
|
4
|
+
:class="{'p-4 border rounded border-gray-medium text-center': !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 mt-2"
|
16
|
+
>
|
17
|
+
<a
|
18
|
+
class="text-blue-base"
|
19
|
+
@click="$refs.file.click()"
|
20
|
+
>Upload</a>
|
21
|
+
<a
|
22
|
+
v-if="uploadedFile"
|
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
|
+
@change="onFileSelected"
|
31
|
+
/>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<ImagePreview
|
35
|
+
v-if="!readonly || uploadedFile"
|
36
|
+
class="w-32 cursor-pointer mt-2"
|
37
|
+
:class="{'border border-dashed border-blue-base': !uploadedFile, 'mx-auto': !readonly}"
|
38
|
+
:image="uploadedFile"
|
39
|
+
downloadable
|
40
|
+
@click="!disable && $refs.file.click()"
|
41
|
+
/>
|
42
|
+
<div
|
43
|
+
v-else-if="readonly"
|
44
|
+
class="py-1"
|
45
|
+
>--
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</template>
|
49
|
+
|
50
|
+
<script setup>
|
51
|
+
import { ImagePreview } from '@ui/components';
|
52
|
+
import { useSingleFileUpload } from '@ui/helpers/singleFileUpload';
|
53
|
+
import { onMounted } from 'vue';
|
54
|
+
import FieldLabel from './FieldLabel';
|
55
|
+
|
56
|
+
const emit = defineEmits(['update:model-value']);
|
57
|
+
const props = defineProps({
|
58
|
+
modelValue: {
|
59
|
+
type: [Object, String],
|
60
|
+
default: null
|
61
|
+
},
|
62
|
+
field: {
|
63
|
+
type: Object,
|
64
|
+
required: true
|
65
|
+
},
|
66
|
+
showName: Boolean,
|
67
|
+
disable: Boolean,
|
68
|
+
readonly: Boolean
|
69
|
+
});
|
70
|
+
const { onComplete, onDrop, onFileSelected, uploadedFile, onClear } = useSingleFileUpload();
|
71
|
+
onComplete(() => emit('update:model-value', uploadedFile.value));
|
72
|
+
|
73
|
+
onMounted(() => {
|
74
|
+
if (props.modelValue) {
|
75
|
+
uploadedFile.value = props.modelValue;
|
76
|
+
}
|
77
|
+
});
|
78
|
+
</script>
|
@@ -0,0 +1,82 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<q-input
|
4
|
+
v-if="!readonly"
|
5
|
+
:data-dusk="'text-field-' + field?.id"
|
6
|
+
:data-testid="'text-field-' + field?.id"
|
7
|
+
:placeholder="field?.placeholder"
|
8
|
+
outlined
|
9
|
+
dense
|
10
|
+
:disable="disabled"
|
11
|
+
:label-slot="!noLabel"
|
12
|
+
:input-class="inputClass"
|
13
|
+
:class="parentClass"
|
14
|
+
stack-label
|
15
|
+
:type="type"
|
16
|
+
:model-value="modelValue"
|
17
|
+
:debounce="debounce"
|
18
|
+
@keydown.enter="$emit('submit')"
|
19
|
+
@update:model-value="$emit('update:model-value', $event)"
|
20
|
+
>
|
21
|
+
<template #label>
|
22
|
+
<FieldLabel
|
23
|
+
:field="field"
|
24
|
+
:label="label"
|
25
|
+
:show-name="showName"
|
26
|
+
:class="labelClass"
|
27
|
+
/>
|
28
|
+
</template>
|
29
|
+
</q-input>
|
30
|
+
<div v-if="readonly">
|
31
|
+
<LabelValueBlock
|
32
|
+
:label="label || field.label"
|
33
|
+
:value="modelValue"
|
34
|
+
/>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</template>
|
38
|
+
|
39
|
+
<script setup>
|
40
|
+
import FieldLabel from './FieldLabel';
|
41
|
+
import LabelValueBlock from './LabelValueBlock';
|
42
|
+
|
43
|
+
defineEmits(['update:model-value', 'submit']);
|
44
|
+
defineProps({
|
45
|
+
modelValue: {
|
46
|
+
type: [String, Number],
|
47
|
+
default: ''
|
48
|
+
},
|
49
|
+
field: {
|
50
|
+
type: Object,
|
51
|
+
default: null
|
52
|
+
},
|
53
|
+
type: {
|
54
|
+
type: String,
|
55
|
+
default: 'text'
|
56
|
+
},
|
57
|
+
label: {
|
58
|
+
type: String,
|
59
|
+
default: null
|
60
|
+
},
|
61
|
+
labelClass: {
|
62
|
+
type: String,
|
63
|
+
default: 'text-sm text-gray-shadow'
|
64
|
+
},
|
65
|
+
parentClass: {
|
66
|
+
type: String,
|
67
|
+
default: ''
|
68
|
+
},
|
69
|
+
inputClass: {
|
70
|
+
type: String,
|
71
|
+
default: ''
|
72
|
+
},
|
73
|
+
noLabel: Boolean,
|
74
|
+
showName: Boolean,
|
75
|
+
disabled: Boolean,
|
76
|
+
readonly: Boolean,
|
77
|
+
debounce: {
|
78
|
+
type: [String, Number],
|
79
|
+
default: 0
|
80
|
+
}
|
81
|
+
});
|
82
|
+
</script>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<FieldLabel
|
4
|
+
v-if="!noLabel"
|
5
|
+
:field="field"
|
6
|
+
:show-name="showName"
|
7
|
+
class="text-sm font-semibold text-gray-shadow block mb-2"
|
8
|
+
/>
|
9
|
+
<template v-if="readonly">
|
10
|
+
<div
|
11
|
+
class="border border-gray-300 rounded-md p-2 bg-gray-100"
|
12
|
+
v-html="modelValue"
|
13
|
+
/>
|
14
|
+
</template>
|
15
|
+
<TinyMceEditor
|
16
|
+
v-else
|
17
|
+
class="mt-2"
|
18
|
+
:api-key="apiKey"
|
19
|
+
:disabled="disable"
|
20
|
+
:model-value="modelValue"
|
21
|
+
@update:model-value="$emit('update:model-value', $event)"
|
22
|
+
/>
|
23
|
+
</div>
|
24
|
+
</template>
|
25
|
+
|
26
|
+
<script setup>
|
27
|
+
import { apiKey } from '@ui/vendor/tinymce-config';
|
28
|
+
import { default as TinyMceEditor } from '@tinymce/tinymce-vue';
|
29
|
+
import FieldLabel from './FieldLabel';
|
30
|
+
|
31
|
+
defineEmits(['update:model-value']);
|
32
|
+
defineProps({
|
33
|
+
modelValue: {
|
34
|
+
type: [String, Number],
|
35
|
+
default: null
|
36
|
+
},
|
37
|
+
field: {
|
38
|
+
type: Object,
|
39
|
+
required: true
|
40
|
+
},
|
41
|
+
noLabel: Boolean,
|
42
|
+
showName: Boolean,
|
43
|
+
disable: Boolean,
|
44
|
+
readonly: Boolean
|
45
|
+
});
|
46
|
+
</script>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
export { default as BooleanField } from "./BooleanField.vue";
|
2
|
+
export { default as ConfirmPasswordField } from "./ConfirmPasswordField.vue";
|
3
|
+
export { default as DateField } from "./DateField.vue";
|
4
|
+
export { default as DateRangeField } from "./DateRangeField.vue";
|
5
|
+
export { default as DateTimeField } from "./DateTimeField.vue";
|
6
|
+
export { default as DateTimePicker } from "./DateTimePicker.vue";
|
7
|
+
export { default as EditableDiv } from "./EditableDiv.vue";
|
8
|
+
export { default as FieldLabel } from "./FieldLabel.vue";
|
9
|
+
export { default as FileUploadButton } from "./FileUploadButton.vue";
|
10
|
+
export { default as InlineDateTimeField } from "./InlineDateTimeField.vue";
|
11
|
+
export { default as IntegerField } from "./IntegerField.vue";
|
12
|
+
export { default as LabeledInput } from "./LabeledInput.vue";
|
13
|
+
export { default as MultiFileField } from "./MultiFileField.vue";
|
14
|
+
export { default as MultiKeywordField } from "./MultiKeywordField.vue";
|
15
|
+
export { default as NewPasswordField } from "./NewPasswordField.vue";
|
16
|
+
export { default as NumberField } from "./NumberField.vue";
|
17
|
+
export { default as NumberRangeField } from "./NumberRangeField.vue";
|
18
|
+
export { default as SelectDrawer } from "./SelectDrawer.vue";
|
19
|
+
export { default as SelectField } from "./SelectField.vue";
|
20
|
+
export { default as SelectWithChildrenField } from "./SelectWithChildrenField.vue";
|
21
|
+
export { default as SingleFileField } from "./SingleFileField.vue";
|
22
|
+
export { default as TextField } from "./TextField.vue";
|
23
|
+
export { default as WysiwygField } from "./WysiwygField.vue";
|
@@ -0,0 +1,76 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="rendered-form">
|
3
|
+
<div
|
4
|
+
v-for="(field, index) in mappedFields"
|
5
|
+
:key="field.id"
|
6
|
+
:class="{ 'mt-4': index > 0 }"
|
7
|
+
>
|
8
|
+
<Component
|
9
|
+
:is="field.component"
|
10
|
+
v-model="fieldValues[field.name]"
|
11
|
+
:field="field"
|
12
|
+
:label="field.label || undefined"
|
13
|
+
:no-label="noLabel"
|
14
|
+
:show-name="showName"
|
15
|
+
:disable="disable"
|
16
|
+
:readonly="readonly"
|
17
|
+
@update:model-value="onInput(field.name, $event)"
|
18
|
+
/>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</template>
|
22
|
+
<script setup>
|
23
|
+
import { reactive } from 'vue';
|
24
|
+
import {
|
25
|
+
BooleanField,
|
26
|
+
DateField,
|
27
|
+
DateRangeField,
|
28
|
+
IntegerField,
|
29
|
+
MultiFileField,
|
30
|
+
NumberField,
|
31
|
+
SingleFileField,
|
32
|
+
TextField,
|
33
|
+
WysiwygField
|
34
|
+
} from './Fields';
|
35
|
+
|
36
|
+
const emit = defineEmits(['update:values']);
|
37
|
+
const props = defineProps({
|
38
|
+
values: {
|
39
|
+
type: Object,
|
40
|
+
default: null
|
41
|
+
},
|
42
|
+
fields: {
|
43
|
+
type: Array,
|
44
|
+
required: true
|
45
|
+
},
|
46
|
+
noLabel: Boolean,
|
47
|
+
showName: Boolean,
|
48
|
+
disable: Boolean,
|
49
|
+
readonly: Boolean
|
50
|
+
});
|
51
|
+
|
52
|
+
const FORM_FIELD_MAP = {
|
53
|
+
BOOLEAN: BooleanField,
|
54
|
+
DATE: DateField,
|
55
|
+
DATE_RANGE: DateRangeField,
|
56
|
+
INTEGER: IntegerField,
|
57
|
+
NUMBER: NumberField,
|
58
|
+
TEXT: TextField,
|
59
|
+
SINGLE_FILE: SingleFileField,
|
60
|
+
MULTI_FILE: MultiFileField,
|
61
|
+
WYSIWYG: WysiwygField
|
62
|
+
};
|
63
|
+
|
64
|
+
const mappedFields = props.fields.map((field) => ({
|
65
|
+
placeholder: `Enter ${field.label}`,
|
66
|
+
...field,
|
67
|
+
component: FORM_FIELD_MAP[field.type],
|
68
|
+
default: field.type === 'BOOLEAN' ? false : ''
|
69
|
+
}));
|
70
|
+
|
71
|
+
const fieldValues = reactive(props.values || {});
|
72
|
+
|
73
|
+
function onInput(key, value) {
|
74
|
+
emit('update:values', { ...fieldValues, [key]: value });
|
75
|
+
}
|
76
|
+
</script>
|