quasar-ui-danx 0.3.47 → 0.3.48
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 +2135 -2102
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +4 -4
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +28 -25
- package/src/components/ActionTable/listControls.ts +4 -4
- package/src/components/Utility/Dialogs/InfoDialog.vue +29 -27
- package/src/helpers/actions.ts +187 -187
- package/src/helpers/request.ts +51 -45
package/package.json
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
class="text-sm font-semibold"
|
12
12
|
/>
|
13
13
|
<div
|
14
|
-
v-if="!disable && !readonly"
|
14
|
+
v-if="!disable && !readonly && !hideControls"
|
15
15
|
class="text-sm my-2"
|
16
16
|
>
|
17
17
|
<a
|
@@ -23,20 +23,21 @@
|
|
23
23
|
class="ml-3 text-red-900"
|
24
24
|
@click="clearUploadedFiles"
|
25
25
|
>Clear</a>
|
26
|
-
<input
|
27
|
-
ref="file"
|
28
|
-
class="hidden"
|
29
|
-
type="file"
|
30
|
-
multiple
|
31
|
-
@change="onFilesSelected"
|
32
|
-
>
|
33
26
|
</div>
|
34
27
|
|
28
|
+
<input
|
29
|
+
ref="file"
|
30
|
+
class="hidden"
|
31
|
+
type="file"
|
32
|
+
multiple
|
33
|
+
@change="onFilesSelected"
|
34
|
+
>
|
35
|
+
|
35
36
|
<div class="max-w-[50em] flex items-stretch justify-start">
|
36
37
|
<FilePreview
|
37
38
|
v-for="file in uploadedFiles"
|
38
39
|
:key="'file-upload-' + file.id"
|
39
|
-
class="w-32 m-2 cursor-pointer bg-gray-200"
|
40
|
+
class="w-32 h-32 m-2 cursor-pointer bg-gray-200"
|
40
41
|
:class="{'border border-dashed border-blue-600': !uploadedFiles.length}"
|
41
42
|
:image="file"
|
42
43
|
:related-files="uploadedFiles"
|
@@ -46,7 +47,7 @@
|
|
46
47
|
/>
|
47
48
|
<FilePreview
|
48
49
|
v-if="!disable && !readonly"
|
49
|
-
class="w-32 m-2 cursor-pointer border border-dashed border-blue-600"
|
50
|
+
class="w-32 h-32 m-2 cursor-pointer border border-dashed border-blue-600"
|
50
51
|
disabled
|
51
52
|
@click="$refs.file.click()"
|
52
53
|
/>
|
@@ -61,31 +62,33 @@
|
|
61
62
|
</template>
|
62
63
|
|
63
64
|
<script setup>
|
64
|
-
import { onMounted } from "vue";
|
65
|
+
import { onMounted, watch } from "vue";
|
65
66
|
import { useMultiFileUpload } from "../../../../helpers";
|
66
67
|
import { FilePreview } from "../../../Utility";
|
67
68
|
import FieldLabel from "./FieldLabel";
|
68
69
|
|
69
70
|
const emit = defineEmits(["update:model-value"]);
|
70
71
|
const props = defineProps({
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
72
|
+
modelValue: {
|
73
|
+
type: [Object, String],
|
74
|
+
default: null
|
75
|
+
},
|
76
|
+
field: {
|
77
|
+
type: Object,
|
78
|
+
required: true
|
79
|
+
},
|
80
|
+
showName: Boolean,
|
81
|
+
disable: Boolean,
|
82
|
+
readonly: Boolean,
|
83
|
+
hideControls: Boolean
|
82
84
|
});
|
83
85
|
|
84
86
|
const { onComplete, onDrop, onFilesSelected, uploadedFiles, clearUploadedFiles, onRemove } = useMultiFileUpload();
|
85
87
|
onMounted(() => {
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
if (props.modelValue) {
|
89
|
+
uploadedFiles.value = props.modelValue;
|
90
|
+
}
|
89
91
|
});
|
92
|
+
watch(() => props.modelValue, () => uploadedFiles.value = props.modelValue);
|
90
93
|
onComplete(() => emit("update:model-value", uploadedFiles.value));
|
91
94
|
</script>
|
@@ -156,7 +156,7 @@ export function useListControls({
|
|
156
156
|
}
|
157
157
|
|
158
158
|
// Add a reactive isSaving property to each item (for performance reasons in checking saving state)
|
159
|
-
data = data
|
159
|
+
data = data?.map((item: any) => {
|
160
160
|
// We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
|
161
161
|
// components, and therefore reactivity is not responding to the new isSaving state
|
162
162
|
const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
|
@@ -182,7 +182,7 @@ export function useListControls({
|
|
182
182
|
const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
|
183
183
|
setPagedItems({
|
184
184
|
data,
|
185
|
-
meta: { total: pagedItems.value
|
185
|
+
meta: { total: pagedItems.value?.meta?.total || 0 }
|
186
186
|
});
|
187
187
|
|
188
188
|
// Update the active item as well if it is set
|
@@ -205,8 +205,8 @@ export function useListControls({
|
|
205
205
|
|
206
206
|
if (newItems && newItems.length > 0) {
|
207
207
|
setPagedItems({
|
208
|
-
data: [...pagedItems.value
|
209
|
-
meta: { total: pagedItems.value
|
208
|
+
data: [...pagedItems.value?.data || [], ...newItems],
|
209
|
+
meta: { total: pagedItems.value?.meta?.total || 0 }
|
210
210
|
});
|
211
211
|
return true;
|
212
212
|
}
|
@@ -34,6 +34,7 @@
|
|
34
34
|
<slot>{{ content }}</slot>
|
35
35
|
</QCardSection>
|
36
36
|
<div
|
37
|
+
v-if="!hideButtons"
|
37
38
|
class="flex items-center justify-center px-6 py-4 border-t border-gray-300"
|
38
39
|
>
|
39
40
|
<div class="flex-grow text-right">
|
@@ -61,35 +62,36 @@ import { XIcon as CloseIcon } from "@heroicons/vue/outline";
|
|
61
62
|
|
62
63
|
const emit = defineEmits(["update:model-value", "close"]);
|
63
64
|
defineProps({
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
65
|
+
modelValue: { type: [Boolean, Object], default: true },
|
66
|
+
title: {
|
67
|
+
type: String,
|
68
|
+
default: ""
|
69
|
+
},
|
70
|
+
titleClass: {
|
71
|
+
type: String,
|
72
|
+
default: ""
|
73
|
+
},
|
74
|
+
subtitle: {
|
75
|
+
type: String,
|
76
|
+
default: ""
|
77
|
+
},
|
78
|
+
content: {
|
79
|
+
type: String,
|
80
|
+
default: ""
|
81
|
+
},
|
82
|
+
backdropDismiss: Boolean,
|
83
|
+
hideButtons: Boolean,
|
84
|
+
maximized: Boolean,
|
85
|
+
fullWidth: Boolean,
|
86
|
+
fullHeight: Boolean,
|
87
|
+
doneText: {
|
88
|
+
type: String,
|
89
|
+
default: "Done"
|
90
|
+
}
|
89
91
|
});
|
90
92
|
|
91
93
|
function onClose() {
|
92
|
-
|
93
|
-
|
94
|
+
emit("update:model-value", false);
|
95
|
+
emit("close");
|
94
96
|
}
|
95
97
|
</script>
|
package/src/helpers/actions.ts
CHANGED
@@ -3,31 +3,31 @@ import { Ref, shallowRef, VNode } from "vue";
|
|
3
3
|
import { FlashMessages } from "./FlashMessages";
|
4
4
|
|
5
5
|
export type ActionTargetItem = {
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
id: number | string;
|
7
|
+
isSaving: Ref<boolean>;
|
8
|
+
[key: string]: any;
|
9
9
|
};
|
10
10
|
export type ActionTarget = ActionTargetItem[] | ActionTargetItem;
|
11
11
|
|
12
12
|
export interface ActionOptions {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
13
|
+
name?: string;
|
14
|
+
label?: string;
|
15
|
+
menu?: boolean;
|
16
|
+
batch?: boolean;
|
17
|
+
category?: string;
|
18
|
+
class?: string;
|
19
|
+
debounce?: number;
|
20
|
+
trigger?: (target: ActionTarget, input: any) => Promise<any>;
|
21
|
+
vnode?: (target: ActionTarget) => VNode;
|
22
|
+
enabled?: (target: object) => boolean;
|
23
|
+
batchEnabled?: (targets: object[]) => boolean;
|
24
|
+
optimistic?: (action: ActionOptions, target: object, input: any) => void;
|
25
|
+
onAction?: (action: string | null | undefined, target: object, input: any) => Promise<any>;
|
26
|
+
onBatchAction?: (action: string | null | undefined, targets: object[], input: any) => Promise<any>;
|
27
|
+
onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
|
28
|
+
onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
|
29
|
+
onError?: (result: any, targets: ActionTarget, input: any) => any;
|
30
|
+
onFinish?: (result: any, targets: ActionTarget, input: any) => any;
|
31
31
|
}
|
32
32
|
|
33
33
|
export const activeActionVnode: Ref = shallowRef(null);
|
@@ -40,173 +40,173 @@ export const activeActionVnode: Ref = shallowRef(null);
|
|
40
40
|
* @param {ActionOptions | null} globalOptions
|
41
41
|
*/
|
42
42
|
export function useActions(actions: ActionOptions[], globalOptions: ActionOptions | null = null) {
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
43
|
+
const mappedActions = actions.map(action => {
|
44
|
+
const mappedAction: ActionOptions = { ...globalOptions, ...action };
|
45
|
+
if (mappedAction.debounce) {
|
46
|
+
mappedAction.trigger = useDebounceFn((target, input) => performAction(mappedAction, target, input, true), mappedAction.debounce);
|
47
|
+
} else if (!mappedAction.trigger) {
|
48
|
+
mappedAction.trigger = (target, input) => performAction(mappedAction, target, input, true);
|
49
|
+
}
|
50
|
+
return mappedAction;
|
51
|
+
});
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Set the reactive saving state of a target
|
55
|
+
*/
|
56
|
+
function setTargetSavingState(target: ActionTarget, saving: boolean) {
|
57
|
+
if (Array.isArray(target)) {
|
58
|
+
for (const t of target) {
|
59
|
+
t.isSaving.value = saving;
|
60
|
+
}
|
61
|
+
} else if (target.isSaving) {
|
62
|
+
target.isSaving.value = saving;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Perform an action on a set of targets
|
68
|
+
*
|
69
|
+
* @param {string} name - can either be a string or an action object
|
70
|
+
* @param {object[]|object} target - an array of targets or a single target object
|
71
|
+
* @param {any} input - The input data to pass to the action handler
|
72
|
+
* @param isTriggered - Whether the action was triggered by a trigger function
|
73
|
+
*/
|
74
|
+
async function performAction(name: string | object, target: ActionTarget, input: any = null, isTriggered = false) {
|
75
|
+
const action: ActionOptions | null | undefined = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
|
76
|
+
if (!action) {
|
77
|
+
throw new Error(`Unknown action: ${name}`);
|
78
|
+
}
|
79
|
+
|
80
|
+
// We always want to call the trigger function if it exists, unless it's already been triggered
|
81
|
+
// This provides behavior like debounce and custom action resolution
|
82
|
+
if (action.trigger && !isTriggered) {
|
83
|
+
return action.trigger(target, input);
|
84
|
+
}
|
85
|
+
|
86
|
+
const vnode = action.vnode && action.vnode(target);
|
87
|
+
let result: any;
|
88
|
+
|
89
|
+
// Run the onStart handler if it exists and quit the operation if it returns false
|
90
|
+
if (action.onStart) {
|
91
|
+
if (!action.onStart(action, target, input)) {
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
setTargetSavingState(target, true);
|
97
|
+
|
98
|
+
// If additional input is required, first render the vnode and wait for the confirm or cancel action
|
99
|
+
if (vnode) {
|
100
|
+
// If the action requires an input, we set the activeActionVnode to the input component.
|
101
|
+
// This will tell the ActionVnode to render the input component, and confirm or cancel the
|
102
|
+
// action The confirm function has the input from the component passed and will resolve the promise
|
103
|
+
// with the result of the action
|
104
|
+
result = await new Promise((resolve, reject) => {
|
105
|
+
activeActionVnode.value = {
|
106
|
+
vnode,
|
107
|
+
confirm: async (confirmInput: any) => {
|
108
|
+
const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
|
109
|
+
|
110
|
+
// Only resolve when we have a non-error response, so we can show the error message w/o
|
111
|
+
// hiding the dialog / vnode
|
112
|
+
if (result === undefined || result === true || result?.success) {
|
113
|
+
resolve(result);
|
114
|
+
}
|
115
|
+
},
|
116
|
+
cancel: resolve
|
117
|
+
};
|
118
|
+
});
|
119
|
+
|
120
|
+
activeActionVnode.value = null;
|
121
|
+
} else {
|
122
|
+
result = await onConfirmAction(action, target, input);
|
123
|
+
}
|
124
|
+
|
125
|
+
setTargetSavingState(target, false);
|
126
|
+
|
127
|
+
return result;
|
128
|
+
}
|
129
|
+
|
130
|
+
/**
|
131
|
+
* Filter the list of actions based on the provided filters in key-value pairs
|
132
|
+
* You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
|
133
|
+
*
|
134
|
+
* @param filters
|
135
|
+
* @returns {ActionOptions[]}
|
136
|
+
*/
|
137
|
+
function filterActions(filters: object): ActionOptions[] {
|
138
|
+
let filteredActions = [...mappedActions];
|
139
|
+
|
140
|
+
for (const filter of Object.keys(filters)) {
|
141
|
+
const filterValue = filters[filter];
|
142
|
+
filteredActions = filteredActions.filter((a: object) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
|
143
|
+
}
|
144
|
+
return filteredActions;
|
145
|
+
}
|
146
|
+
|
147
|
+
return {
|
148
|
+
actions: mappedActions,
|
149
|
+
filterActions,
|
150
|
+
performAction
|
151
|
+
};
|
152
152
|
}
|
153
153
|
|
154
154
|
async function onConfirmAction(action: ActionOptions, target: ActionTarget, input: any = null) {
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
155
|
+
if (!action.onAction) {
|
156
|
+
throw new Error("No onAction handler found for the selected action:" + action.name);
|
157
|
+
}
|
158
|
+
|
159
|
+
let result: any;
|
160
|
+
try {
|
161
|
+
if (Array.isArray(target)) {
|
162
|
+
if (action.onBatchAction) {
|
163
|
+
result = await action.onBatchAction(action.name, target, input);
|
164
|
+
} else {
|
165
|
+
result = { error: `Action ${action.name} does not support batch actions` };
|
166
|
+
}
|
167
|
+
} else {
|
168
|
+
// If the action has an optimistic callback, we call it before the actual action to immediately
|
169
|
+
// update the UI
|
170
|
+
if (action.optimistic) {
|
171
|
+
action.optimistic(action, target, input);
|
172
|
+
}
|
173
|
+
|
174
|
+
result = await action.onAction(action.name, target, input);
|
175
|
+
}
|
176
|
+
} catch (e) {
|
177
|
+
console.error(e);
|
178
|
+
result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
|
179
|
+
}
|
180
|
+
|
181
|
+
// If there is no return value or the result marks it as successful, we show a success message
|
182
|
+
if (result === undefined || result === true || result?.success) {
|
183
|
+
if (result?.success && Array.isArray(target)) {
|
184
|
+
FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
|
185
|
+
}
|
186
|
+
|
187
|
+
if (action.onSuccess) {
|
188
|
+
action.onSuccess(result, target, input);
|
189
|
+
}
|
190
|
+
} else {
|
191
|
+
const errors = [];
|
192
|
+
if (result.errors) {
|
193
|
+
errors.push(...result.errors);
|
194
|
+
} else if (result.error) {
|
195
|
+
errors.push(typeof result.error === "string" ? result.error : result.error.message);
|
196
|
+
} else {
|
197
|
+
errors.push("An unknown error occurred. Please try again later.");
|
198
|
+
}
|
199
|
+
|
200
|
+
FlashMessages.combine("error", errors);
|
201
|
+
|
202
|
+
if (action.onError) {
|
203
|
+
action.onError(result, target, input);
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
if (action.onFinish) {
|
208
|
+
action.onFinish(result, target, input);
|
209
|
+
}
|
210
|
+
|
211
|
+
return result;
|
212
212
|
}
|