quasar-ui-danx 0.4.2 → 0.4.4
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/dist/danx.es.js +7127 -6615
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +11 -5
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -1
- package/src/components/ActionTable/ActionTable.vue +28 -41
- package/src/components/ActionTable/Columns/ActionTableColumn.vue +19 -18
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +6 -6
- package/src/components/ActionTable/Filters/{FilterFieldList.vue → FilterList.vue} +26 -26
- package/src/components/ActionTable/Filters/FilterableField.vue +28 -31
- package/src/components/ActionTable/Filters/index.ts +2 -2
- package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +71 -0
- package/src/components/ActionTable/Form/Fields/FieldLabel.vue +8 -13
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +48 -44
- package/src/components/ActionTable/Form/Fields/SelectField.vue +24 -38
- package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +28 -33
- package/src/components/ActionTable/Form/Fields/SingleFileField.vue +15 -15
- package/src/components/ActionTable/Form/Fields/SliderNumberField.vue +45 -0
- package/src/components/ActionTable/Form/Fields/TextField.vue +47 -66
- package/src/components/ActionTable/Form/Fields/index.ts +2 -0
- package/src/components/ActionTable/Form/RenderedForm.vue +50 -9
- package/src/components/ActionTable/Form/Utilities/MaxLengthCounter.vue +17 -0
- package/src/components/ActionTable/Form/Utilities/index.ts +1 -0
- package/src/components/ActionTable/Form/index.ts +1 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +16 -15
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +6 -6
- package/src/components/ActionTable/listControls.ts +106 -166
- package/src/components/ActionTable/listHelpers.ts +2 -3
- package/src/components/ActionTable/tableColumns.ts +3 -27
- package/src/components/AuditHistory/AuditHistoryItemValue.vue +26 -26
- package/src/components/PanelsDrawer/PanelsDrawer.vue +17 -4
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +6 -11
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +20 -20
- package/src/components/Utility/Dialogs/ConfirmActionDialog.vue +39 -0
- package/src/components/Utility/Dialogs/ConfirmDialog.vue +10 -24
- package/src/components/Utility/Dialogs/DialogLayout.vue +10 -28
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +42 -36
- package/src/components/Utility/Dialogs/index.ts +1 -0
- package/src/components/Utility/Files/FilePreview.vue +76 -73
- package/src/components/Utility/Layouts/ContentDrawer.vue +24 -31
- package/src/components/Utility/Tools/ActionVnode.vue +3 -3
- package/src/components/Utility/Tools/RenderVnode.vue +1 -1
- package/src/components/Utility/Transitions/MaxHeightTransition.vue +26 -0
- package/src/components/Utility/Transitions/index.ts +1 -0
- package/src/config/index.ts +36 -31
- package/src/helpers/FileUpload.ts +295 -297
- package/src/helpers/FlashMessages.ts +80 -71
- package/src/helpers/actions.ts +102 -82
- package/src/helpers/download.ts +189 -189
- package/src/helpers/downloadPdf.ts +55 -52
- package/src/helpers/formats.ts +151 -109
- package/src/helpers/index.ts +2 -0
- package/src/helpers/multiFileUpload.ts +72 -58
- package/src/helpers/objectStore.ts +52 -0
- package/src/helpers/request.ts +70 -51
- package/src/helpers/routes.ts +29 -0
- package/src/helpers/storage.ts +7 -3
- package/src/helpers/utils.ts +47 -29
- package/src/styles/quasar-reset.scss +16 -1
- package/src/styles/themes/danx/dialogs.scss +4 -0
- package/src/types/actions.d.ts +43 -0
- package/src/types/config.d.ts +15 -0
- package/src/types/controls.d.ts +99 -0
- package/src/types/dialogs.d.ts +32 -0
- package/src/types/fields.d.ts +20 -0
- package/src/types/files.d.ts +54 -0
- package/src/types/formats.d.ts +4 -0
- package/src/{components/ActionTable/Form/form.d.ts → types/forms.d.ts} +6 -0
- package/src/types/index.d.ts +12 -0
- package/src/types/requests.d.ts +13 -0
- package/src/types/shared.d.ts +15 -0
- package/src/types/tables.d.ts +27 -0
- package/types/index.d.ts +1 -1
- /package/src/components/ActionTable/Filters/{FilterFieldItem.vue → FilterItem.vue} +0 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "quasar-ui-danx",
|
3
|
-
"version": "0.4.
|
3
|
+
"version": "0.4.4",
|
4
4
|
"author": "Dan <dan@flytedesk.com>",
|
5
5
|
"description": "DanX Vue / Quasar component library",
|
6
6
|
"license": "MIT",
|
@@ -19,6 +19,7 @@
|
|
19
19
|
},
|
20
20
|
"devDependencies": {
|
21
21
|
"@quasar/extras": "^1.16.4",
|
22
|
+
"@types/luxon": "^3.4.2",
|
22
23
|
"@types/node": "^20.12.7",
|
23
24
|
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
24
25
|
"@typescript-eslint/parser": "^7.6.0",
|
@@ -48,6 +49,7 @@
|
|
48
49
|
"@heroicons/vue": "v1",
|
49
50
|
"@tinymce/tinymce-vue": "^5.1.1",
|
50
51
|
"@vueuse/core": "^10.7.2",
|
52
|
+
"danx-icon": "^1.0.2",
|
51
53
|
"exifreader": "^4.21.1",
|
52
54
|
"gsap": "^3.12.5",
|
53
55
|
"luxon": "^3.4.4"
|
@@ -62,10 +62,11 @@
|
|
62
62
|
</div>
|
63
63
|
</template>
|
64
64
|
|
65
|
-
<script setup>
|
65
|
+
<script setup lang="ts">
|
66
66
|
import { QTable } from "quasar";
|
67
67
|
import { computed, ref } from "vue";
|
68
68
|
import { getItem, setItem } from "../../helpers";
|
69
|
+
import { ActionTargetItem, ListControlsPagination, TableColumn } from "../../types";
|
69
70
|
import { ActionVnode } from "../Utility";
|
70
71
|
import { ActionTableColumn, ActionTableHeaderColumn } from "./Columns";
|
71
72
|
import EmptyTableState from "./EmptyTableState.vue";
|
@@ -73,50 +74,36 @@ import { mapSortBy, registerStickyScrolling } from "./listHelpers";
|
|
73
74
|
import TableSummaryRow from "./TableSummaryRow.vue";
|
74
75
|
|
75
76
|
defineEmits(["update:selected-rows", "update:pagination"]);
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
loadingList: Boolean,
|
98
|
-
loadingSummary: Boolean,
|
99
|
-
pagedItems: {
|
100
|
-
type: Object,
|
101
|
-
default: null
|
102
|
-
},
|
103
|
-
summary: {
|
104
|
-
type: Object,
|
105
|
-
default: null
|
106
|
-
},
|
107
|
-
columns: {
|
108
|
-
type: Array,
|
109
|
-
required: true
|
110
|
-
},
|
111
|
-
rowsPerPageOptions: {
|
112
|
-
type: Array,
|
113
|
-
default: () => [10, 25, 50, 100]
|
114
|
-
}
|
77
|
+
|
78
|
+
export interface Props {
|
79
|
+
name: string;
|
80
|
+
label: string;
|
81
|
+
color?: string;
|
82
|
+
selectedRows: ActionTargetItem[];
|
83
|
+
pagination: ListControlsPagination;
|
84
|
+
loadingList?: boolean;
|
85
|
+
loadingSummary?: boolean;
|
86
|
+
pagedItems?: any;
|
87
|
+
summary: any;
|
88
|
+
columns: TableColumn[];
|
89
|
+
rowsPerPageOptions?: number[];
|
90
|
+
}
|
91
|
+
|
92
|
+
const props = withDefaults(defineProps<Props>(), {
|
93
|
+
color: "",
|
94
|
+
pagedItems: null,
|
95
|
+
summary: null,
|
96
|
+
loadingSummary: false,
|
97
|
+
rowsPerPageOptions: () => [10, 25, 50, 100]
|
115
98
|
});
|
99
|
+
|
116
100
|
const actionTable = ref(null);
|
117
101
|
registerStickyScrolling(actionTable);
|
118
102
|
|
119
|
-
const tableColumns = computed(() => props.columns.map((column) => ({
|
103
|
+
const tableColumns = computed<TableColumn[]>(() => props.columns.map((column) => ({
|
104
|
+
...column,
|
105
|
+
field: column.field || column.name
|
106
|
+
})));
|
120
107
|
const hasData = computed(() => props.pagedItems?.data?.length);
|
121
108
|
const COLUMN_SETTINGS_KEY = `column-settings-${props.name}`;
|
122
109
|
const columnSettings = ref(getItem(COLUMN_SETTINGS_KEY) || {});
|
@@ -13,6 +13,7 @@
|
|
13
13
|
<a
|
14
14
|
v-if="column.onClick"
|
15
15
|
:class="column.innerClass"
|
16
|
+
class="dx-column-link"
|
16
17
|
@click="column.onClick(row)"
|
17
18
|
>
|
18
19
|
<RenderVnode
|
@@ -62,14 +63,14 @@ import ActionMenu from "../ActionMenu";
|
|
62
63
|
import { TitleColumnFormat } from "./";
|
63
64
|
|
64
65
|
const props = defineProps({
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
rowProps: {
|
67
|
+
type: Object,
|
68
|
+
required: true
|
69
|
+
},
|
70
|
+
settings: {
|
71
|
+
type: Object,
|
72
|
+
default: null
|
73
|
+
}
|
73
74
|
});
|
74
75
|
|
75
76
|
const row = computed(() => props.rowProps.row);
|
@@ -78,18 +79,18 @@ const value = computed(() => props.rowProps.value);
|
|
78
79
|
const isSaving = computed(() => row.value.isSaving?.value);
|
79
80
|
|
80
81
|
const columnStyle = computed(() => {
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
const width = props.settings?.width || column.value.width;
|
83
|
+
return {
|
84
|
+
width: width ? `${width}px` : undefined,
|
85
|
+
minWidth: column.value.minWidth ? `${column.value.minWidth}px` : undefined
|
86
|
+
};
|
86
87
|
});
|
87
88
|
|
88
89
|
const columnClass = computed(() => ({
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
[column.value.class || ""]: true,
|
91
|
+
"is-saving": isSaving.value,
|
92
|
+
"justify-end": column.value.align === "right",
|
93
|
+
"justify-center": column.value.align === "center",
|
94
|
+
"justify-start": column.value.align === "left"
|
94
95
|
}));
|
95
96
|
</script>
|
@@ -8,17 +8,17 @@
|
|
8
8
|
:name="name"
|
9
9
|
@update:collapse="$emit('update:show-filters', !$event)"
|
10
10
|
>
|
11
|
-
<
|
12
|
-
:filter="activeFilter"
|
13
|
-
:
|
11
|
+
<FilterList
|
12
|
+
:active-filter="activeFilter"
|
13
|
+
:filters="filters"
|
14
14
|
@update:filter="$emit('update:active-filter', $event)"
|
15
15
|
/>
|
16
16
|
</CollapsableSidebar>
|
17
17
|
</template>
|
18
18
|
<script setup lang="ts">
|
19
|
-
import {
|
20
|
-
import { FilterFieldList } from ".";
|
19
|
+
import { FilterGroup, ListControlsFilter } from "../../../types";
|
21
20
|
import { CollapsableSidebar } from "../../Utility";
|
21
|
+
import FilterList from "./FilterList";
|
22
22
|
|
23
23
|
defineEmits(["update:active-filter", "update:show-filters"]);
|
24
24
|
|
@@ -28,7 +28,7 @@ export interface Props {
|
|
28
28
|
activeFilter: ListControlsFilter,
|
29
29
|
minWidth?: string,
|
30
30
|
maxWidth?: string,
|
31
|
-
filters?:
|
31
|
+
filters?: FilterGroup[]
|
32
32
|
}
|
33
33
|
|
34
34
|
withDefaults(defineProps<Props>(), {
|
@@ -2,14 +2,14 @@
|
|
2
2
|
<QList>
|
3
3
|
<div class="px-4 py-2 max-w-full">
|
4
4
|
<template
|
5
|
-
v-for="(group, index) in
|
5
|
+
v-for="(group, index) in filters"
|
6
6
|
:key="'group-' + group.name"
|
7
7
|
>
|
8
8
|
<template v-if="group.flat">
|
9
9
|
<FilterableField
|
10
10
|
v-for="field in group.fields"
|
11
11
|
:key="'field-' + field.name"
|
12
|
-
:model-value="field.calcValue ? field.calcValue(
|
12
|
+
:model-value="field.calcValue ? field.calcValue(activeFilter) : activeFilter[field.name]"
|
13
13
|
:field="field"
|
14
14
|
:loading="loading"
|
15
15
|
class="mb-4"
|
@@ -17,7 +17,7 @@
|
|
17
17
|
/>
|
18
18
|
</template>
|
19
19
|
|
20
|
-
<
|
20
|
+
<FilterItem
|
21
21
|
v-else
|
22
22
|
:name="group.name"
|
23
23
|
:count="activeCountByGroup[group.name]"
|
@@ -25,16 +25,16 @@
|
|
25
25
|
<FilterableField
|
26
26
|
v-for="field in group.fields"
|
27
27
|
:key="'field-' + field.name"
|
28
|
-
:model-value="field.calcValue ? field.calcValue(
|
28
|
+
:model-value="field.calcValue ? field.calcValue(activeFilter) : activeFilter[field.name]"
|
29
29
|
:field="field"
|
30
30
|
:loading="loading"
|
31
31
|
class="mb-4"
|
32
32
|
@update:model-value="updateFilter(field, $event)"
|
33
33
|
/>
|
34
|
-
</
|
34
|
+
</FilterItem>
|
35
35
|
|
36
36
|
<QSeparator
|
37
|
-
v-if="index < (
|
37
|
+
v-if="index < (filters.length - 1)"
|
38
38
|
class="my-2"
|
39
39
|
/>
|
40
40
|
</template>
|
@@ -44,33 +44,33 @@
|
|
44
44
|
<script setup>
|
45
45
|
import { computed } from "vue";
|
46
46
|
import FilterableField from "./FilterableField";
|
47
|
-
import
|
47
|
+
import FilterItem from "./FilterItem";
|
48
48
|
|
49
49
|
const emit = defineEmits(["update:filter"]);
|
50
50
|
const props = defineProps({
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
filters: {
|
52
|
+
type: Array,
|
53
|
+
required: true
|
54
|
+
},
|
55
|
+
activeFilter: {
|
56
|
+
type: Object,
|
57
|
+
required: true
|
58
|
+
},
|
59
|
+
loading: Boolean
|
60
60
|
});
|
61
61
|
|
62
62
|
const activeCountByGroup = computed(() => {
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
const activeCountByGroup = {};
|
64
|
+
for (const group of props.filters) {
|
65
|
+
activeCountByGroup[group.name] = group.fields.filter(field => props.activeFilter[field.name] !== undefined).length;
|
66
|
+
}
|
67
|
+
return activeCountByGroup;
|
68
68
|
});
|
69
69
|
function updateFilter(field, value) {
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
70
|
+
let activeFilter = { [field.name]: value };
|
71
|
+
if (field.filterBy) {
|
72
|
+
activeFilter = field.filterBy(value);
|
73
|
+
}
|
74
|
+
emit("update:filter", { ...props.activeFilter, ...activeFilter });
|
75
75
|
}
|
76
76
|
</script>
|
@@ -5,6 +5,7 @@
|
|
5
5
|
v-if="field.options?.length > 0 || loading"
|
6
6
|
:model-value="modelValue"
|
7
7
|
:options="field.options"
|
8
|
+
:clearable="field.clearable === undefined ? true : field.clearable"
|
8
9
|
multiple
|
9
10
|
:loading="loading"
|
10
11
|
:chip-limit="1"
|
@@ -30,6 +31,7 @@
|
|
30
31
|
v-else-if="field.type === 'single-select'"
|
31
32
|
:model-value="modelValue"
|
32
33
|
:options="field.options"
|
34
|
+
:clearable="field.clearable === undefined ? true : field.clearable"
|
33
35
|
:placeholder="field.placeholder"
|
34
36
|
:loading="loading"
|
35
37
|
:label="field.label"
|
@@ -46,7 +48,7 @@
|
|
46
48
|
v-else-if="field.type === 'date-range'"
|
47
49
|
:model-value="modelValue"
|
48
50
|
:label="field.label"
|
49
|
-
:inline="field.inline"
|
51
|
+
:inline="!!field.inline"
|
50
52
|
with-time
|
51
53
|
class="mt-2 reactive"
|
52
54
|
@update:model-value="onUpdate"
|
@@ -106,43 +108,38 @@
|
|
106
108
|
</template>
|
107
109
|
</div>
|
108
110
|
</template>
|
109
|
-
<script setup>
|
111
|
+
<script setup lang="ts">
|
112
|
+
import { FormField } from "../../../types";
|
110
113
|
import {
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
BooleanField,
|
115
|
+
DateField,
|
116
|
+
DateRangeField,
|
117
|
+
MultiKeywordField,
|
118
|
+
NumberRangeField,
|
119
|
+
SelectField,
|
120
|
+
SelectWithChildrenField
|
118
121
|
} from "../Form/Fields";
|
119
122
|
|
120
123
|
const emit = defineEmits(["update:model-value"]);
|
121
|
-
const props = defineProps
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
modelValue: {
|
127
|
-
type: [String, Array, Number, Object, Boolean],
|
128
|
-
default: undefined
|
129
|
-
},
|
130
|
-
loading: Boolean
|
131
|
-
});
|
124
|
+
const props = defineProps<{
|
125
|
+
field: FormField;
|
126
|
+
modelValue?: any;
|
127
|
+
loading?: boolean;
|
128
|
+
}>();
|
132
129
|
|
133
130
|
function onUpdate(val) {
|
134
|
-
|
131
|
+
let newVal = val || undefined;
|
135
132
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
133
|
+
switch (props.field.type) {
|
134
|
+
case "multi-select":
|
135
|
+
newVal = (val && val.length > 0) ? val : undefined;
|
136
|
+
break;
|
137
|
+
case "single-select":
|
138
|
+
case "boolean":
|
139
|
+
newVal = val === null ? undefined : val;
|
140
|
+
break;
|
141
|
+
}
|
145
142
|
|
146
|
-
|
143
|
+
emit("update:model-value", newVal);
|
147
144
|
}
|
148
145
|
</script>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
export { default as CollapsableFiltersSidebar } from "./CollapsableFiltersSidebar.vue";
|
2
2
|
export { default as FilterableField } from "./FilterableField.vue";
|
3
|
-
export { default as
|
4
|
-
export { default as
|
3
|
+
export { default as FilterItem } from "./FilterItem.vue";
|
4
|
+
export { default as FilterList } from "./FilterList.vue";
|
5
5
|
export { default as FilterListToggle } from "./FilterListToggle.vue";
|
6
6
|
export { default as FilterToolbarLayout } from "./FilterToolbarLayout.vue";
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
class="danx-edit-on-click-text-field flex flex-nowrap items-center rounded overflow-ellipsis"
|
4
|
+
:class="{[props.class]: true, 'is-readonly': readonly, 'cursor-pointer': !isEditing && !readonly}"
|
5
|
+
@click="onEdit"
|
6
|
+
>
|
7
|
+
<div
|
8
|
+
ref="editableBox"
|
9
|
+
:contenteditable="!readonly && isEditing"
|
10
|
+
class="flex-grow p-2 rounded outline-none border-none"
|
11
|
+
:class="{[editingClass]: isEditing}"
|
12
|
+
@input="text = $event.target.innerText"
|
13
|
+
>
|
14
|
+
{{ text }}
|
15
|
+
</div>
|
16
|
+
<div v-if="!readonly">
|
17
|
+
<QBtn
|
18
|
+
v-if="isEditing"
|
19
|
+
@click.stop="isEditing = false"
|
20
|
+
>
|
21
|
+
<DoneIcon class="w-4" />
|
22
|
+
</QBtn>
|
23
|
+
<QBtn
|
24
|
+
v-else
|
25
|
+
class="edit-icon"
|
26
|
+
>
|
27
|
+
<EditIcon class="w-4" />
|
28
|
+
</QBtn>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</template>
|
32
|
+
<script lang="ts" setup>
|
33
|
+
import { FaSolidCheck as DoneIcon, FaSolidPencil as EditIcon } from "danx-icon";
|
34
|
+
import { nextTick, ref } from "vue";
|
35
|
+
|
36
|
+
export interface Props {
|
37
|
+
class?: "hover:bg-slate-300",
|
38
|
+
editingClass?: "bg-slate-500";
|
39
|
+
readonly?: boolean;
|
40
|
+
}
|
41
|
+
|
42
|
+
const editableBox = ref<HTMLElement | null>(null);
|
43
|
+
const text = defineModel({ type: String });
|
44
|
+
const props = defineProps<Props>();
|
45
|
+
const isEditing = ref(false);
|
46
|
+
function onEdit() {
|
47
|
+
if (props.readonly) return;
|
48
|
+
isEditing.value = true;
|
49
|
+
nextTick(() => {
|
50
|
+
editableBox.value?.focus();
|
51
|
+
});
|
52
|
+
}
|
53
|
+
|
54
|
+
</script>
|
55
|
+
|
56
|
+
<style lang="scss" scoped>
|
57
|
+
.danx-edit-on-click-text-field {
|
58
|
+
@apply transition-all;
|
59
|
+
|
60
|
+
.edit-icon {
|
61
|
+
@apply opacity-0 transition-all;
|
62
|
+
|
63
|
+
}
|
64
|
+
|
65
|
+
&:hover {
|
66
|
+
.edit-icon {
|
67
|
+
@apply opacity-100;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
</style>
|
@@ -11,21 +11,16 @@
|
|
11
11
|
</span>
|
12
12
|
</template>
|
13
13
|
|
14
|
-
<script setup>
|
14
|
+
<script setup lang="ts">
|
15
|
+
import { FormField } from "src/types";
|
15
16
|
import { computed } from "vue";
|
16
17
|
|
17
|
-
const props = defineProps
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
type: String,
|
24
|
-
default: null
|
25
|
-
},
|
26
|
-
showName: Boolean,
|
27
|
-
required: Boolean
|
28
|
-
});
|
18
|
+
const props = defineProps<{
|
19
|
+
field?: FormField;
|
20
|
+
label?: string;
|
21
|
+
showName?: boolean;
|
22
|
+
required?: boolean;
|
23
|
+
}>();
|
29
24
|
|
30
25
|
const labelText = computed(() => props.label || props.field?.label);
|
31
26
|
const requiredLabel = computed(() => props.field?.required_group || (props.required || props.field?.required ? "*" : ""));
|
@@ -1,55 +1,62 @@
|
|
1
1
|
<template>
|
2
2
|
<div
|
3
3
|
class="max-w-full relative overflow-auto"
|
4
|
-
:class="{'p-
|
4
|
+
:class="{'p-2': !readonly}"
|
5
5
|
@dragover.prevent
|
6
6
|
@drop.prevent="onDrop"
|
7
7
|
>
|
8
8
|
<FieldLabel
|
9
9
|
:field="field"
|
10
|
+
:label="label"
|
10
11
|
:show-name="showName"
|
11
12
|
class="text-sm font-semibold"
|
12
13
|
/>
|
13
|
-
|
14
|
+
|
15
|
+
<input
|
14
16
|
v-if="!disable && !readonly"
|
15
|
-
|
17
|
+
ref="file"
|
18
|
+
class="hidden"
|
19
|
+
type="file"
|
20
|
+
multiple
|
21
|
+
@change="onFilesSelected"
|
16
22
|
>
|
17
|
-
<a
|
18
|
-
class="text-blue-600"
|
19
|
-
@click="$refs.file.click()"
|
20
|
-
>Upload</a>
|
21
|
-
<a
|
22
|
-
v-if="uploadedFiles.length > 0"
|
23
|
-
class="ml-3 text-red-900"
|
24
|
-
@click="clearUploadedFiles"
|
25
|
-
>Clear</a>
|
26
|
-
<input
|
27
|
-
ref="file"
|
28
|
-
class="hidden"
|
29
|
-
type="file"
|
30
|
-
multiple
|
31
|
-
@change="onFilesSelected"
|
32
|
-
>
|
33
|
-
</div>
|
34
23
|
|
35
24
|
<div class="max-w-[50em] flex items-stretch justify-start">
|
36
25
|
<FilePreview
|
37
26
|
v-for="file in uploadedFiles"
|
38
27
|
:key="'file-upload-' + file.id"
|
39
|
-
class="w-32 m-2 cursor-pointer bg-gray-200"
|
28
|
+
class="w-32 h-32 m-2 cursor-pointer bg-gray-200"
|
40
29
|
:class="{'border border-dashed border-blue-600': !uploadedFiles.length}"
|
41
|
-
:
|
30
|
+
:file="file"
|
42
31
|
:related-files="uploadedFiles"
|
43
32
|
downloadable
|
44
33
|
:removable="!readonly && !disable"
|
45
34
|
@remove="onRemove(file)"
|
46
35
|
/>
|
47
|
-
<
|
36
|
+
<div
|
48
37
|
v-if="!disable && !readonly"
|
49
|
-
class="w-32 m-2
|
50
|
-
|
51
|
-
|
52
|
-
|
38
|
+
class="dx-add-remove-files w-32 h-32 m-2 rounded-2xl flex flex-col flex-nowrap items-center overflow-hidden cursor-pointer"
|
39
|
+
>
|
40
|
+
<div
|
41
|
+
class="dx-add-file flex-grow p-1 pt-3 flex justify-center items-center bg-green-200 text-green-700 w-full hover:bg-green-100"
|
42
|
+
@click="$refs.file.click()"
|
43
|
+
>
|
44
|
+
<div>
|
45
|
+
<AddFileIcon class="w-10 m-auto" />
|
46
|
+
<div class="mt-1 text-center">
|
47
|
+
Add
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
<div
|
52
|
+
v-if="uploadedFiles.length > 0"
|
53
|
+
class="dx-remove-file flex items-center flex-nowrap p-2 bg-red-200 text-red-800 hover:bg-red-100 w-full justify-center text-xs"
|
54
|
+
@click="clearUploadedFiles"
|
55
|
+
>
|
56
|
+
<RemoveFileIcon class="mr-2 w-3" />
|
57
|
+
Remove All
|
58
|
+
</div>
|
59
|
+
</div>
|
53
60
|
<div
|
54
61
|
v-if="readonly && uploadedFiles.length === 0"
|
55
62
|
class="p-1"
|
@@ -60,32 +67,29 @@
|
|
60
67
|
</div>
|
61
68
|
</template>
|
62
69
|
|
63
|
-
<script setup>
|
70
|
+
<script setup lang="ts">
|
64
71
|
import { onMounted } from "vue";
|
65
72
|
import { useMultiFileUpload } from "../../../../helpers";
|
73
|
+
import { ImageIcon as AddFileIcon, TrashIcon as RemoveFileIcon } from "../../../../svg";
|
74
|
+
import { FormField, UploadedFile } from "../../../../types";
|
66
75
|
import { FilePreview } from "../../../Utility";
|
67
76
|
import FieldLabel from "./FieldLabel";
|
68
77
|
|
69
78
|
const emit = defineEmits(["update:model-value"]);
|
70
|
-
const props = defineProps
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
},
|
79
|
-
showName: Boolean,
|
80
|
-
disable: Boolean,
|
81
|
-
readonly: Boolean
|
82
|
-
});
|
79
|
+
const props = defineProps<{
|
80
|
+
modelValue?: UploadedFile[];
|
81
|
+
field?: FormField;
|
82
|
+
label?: string;
|
83
|
+
showName?: boolean;
|
84
|
+
disable?: boolean;
|
85
|
+
readonly?: boolean;
|
86
|
+
}>();
|
83
87
|
|
84
88
|
const { onComplete, onDrop, onFilesSelected, uploadedFiles, clearUploadedFiles, onRemove } = useMultiFileUpload();
|
85
89
|
onMounted(() => {
|
86
|
-
|
87
|
-
|
88
|
-
|
90
|
+
if (props.modelValue) {
|
91
|
+
uploadedFiles.value = props.modelValue;
|
92
|
+
}
|
89
93
|
});
|
90
94
|
onComplete(() => emit("update:model-value", uploadedFiles.value));
|
91
95
|
</script>
|