quasar-ui-danx 0.3.47 → 0.3.49
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 +2237 -2202
- 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/Form/Fields/SelectField.vue +140 -138
- 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/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
|
}
|
package/src/helpers/request.ts
CHANGED
@@ -1,50 +1,56 @@
|
|
1
1
|
import { ref, Ref } from "vue";
|
2
2
|
|
3
3
|
interface RequestOptions {
|
4
|
-
|
4
|
+
baseUrl: string;
|
5
5
|
}
|
6
6
|
|
7
7
|
const requestOptions: Ref<RequestOptions> = ref({
|
8
|
-
|
8
|
+
baseUrl: ""
|
9
9
|
});
|
10
10
|
/**
|
11
11
|
* A simple request helper that wraps the fetch API
|
12
12
|
* to make GET and POST requests easier w/ JSON payloads
|
13
13
|
*/
|
14
14
|
export const request = {
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
configure(options: RequestOptions) {
|
16
|
+
requestOptions.value = options;
|
17
|
+
},
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
url(url: string) {
|
20
|
+
if (url.startsWith("http")) {
|
21
|
+
return url;
|
22
|
+
}
|
23
|
+
return requestOptions.value.baseUrl + url;
|
24
|
+
},
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
async get(url: string, options = {}): Promise<object> {
|
27
|
+
return fetch(request.url(url), {
|
28
|
+
method: "get",
|
29
|
+
headers: {
|
30
|
+
Accept: "application/json",
|
31
|
+
"Content-Type": "application/json"
|
32
|
+
},
|
33
|
+
...options
|
34
|
+
}).then((r) => r.json());
|
35
|
+
},
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
async post(url: string, data = {}, options = {}) {
|
38
|
+
let body = "";
|
39
|
+
try {
|
40
|
+
body = JSON.stringify(data);
|
41
|
+
} catch (e) {
|
42
|
+
// fail silently
|
43
|
+
}
|
44
|
+
return fetch(request.url(url), {
|
45
|
+
method: "post",
|
46
|
+
body,
|
47
|
+
headers: {
|
48
|
+
Accept: "application/json",
|
49
|
+
"Content-Type": "application/json"
|
50
|
+
},
|
51
|
+
...options
|
52
|
+
}).then((r) => r.json());
|
53
|
+
}
|
48
54
|
};
|
49
55
|
|
50
56
|
/**
|
@@ -55,25 +61,25 @@ export const request = {
|
|
55
61
|
*
|
56
62
|
*/
|
57
63
|
export async function fetchResourceListWithSelected(fetchFn: (filter: object) => Promise<any[]>, list: Ref, id: string, filter: object): Promise<void> {
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
64
|
+
// First make sure we have the selected record, so we can always add it to the list
|
65
|
+
let selectedResource;
|
66
|
+
if (id) {
|
67
|
+
selectedResource = list.value.find((c: { id: string }) => c.id === id) || (await fetchFn({ id }))[0];
|
68
|
+
}
|
63
69
|
|
64
|
-
|
65
|
-
|
70
|
+
// Get the filtered campaign list
|
71
|
+
list.value = await fetchFn(filter);
|
66
72
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
73
|
+
// If our selected campaign is not in the filtered list, add it
|
74
|
+
if (selectedResource && !list.value.find((c: { id: string }) => c.id === id)) {
|
75
|
+
list.value.push(selectedResource);
|
76
|
+
}
|
71
77
|
}
|
72
78
|
|
73
79
|
/**
|
74
80
|
* Returns the value of the URL parameter (if it is set)
|
75
81
|
*/
|
76
82
|
export function getUrlParam(key: string, url?: string) {
|
77
|
-
|
78
|
-
|
83
|
+
const params = new URLSearchParams(url?.replace(/.*\?/, "") || window.location.search);
|
84
|
+
return params.get(key);
|
79
85
|
}
|