quasar-ui-danx 0.4.2 → 0.4.3
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 +104 -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
@@ -3,86 +3,95 @@ import { watch } from "vue";
|
|
3
3
|
import { danxOptions } from "../config";
|
4
4
|
|
5
5
|
export class FlashMessages {
|
6
|
-
|
6
|
+
static notify: (options: QNotifyCreateOptions) => void;
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
8
|
+
static PROP_DEFINITIONS = {
|
9
|
+
successMsg: {
|
10
|
+
type: String,
|
11
|
+
default: ""
|
12
|
+
},
|
13
|
+
errorMsg: {
|
14
|
+
type: String,
|
15
|
+
default: ""
|
16
|
+
},
|
17
|
+
warningMsg: {
|
18
|
+
type: String,
|
19
|
+
default: ""
|
20
|
+
}
|
21
|
+
};
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
static enable(msgProps: { successMsg?: string, errorMsg?: string, warningMsg?: string }) {
|
24
|
+
FlashMessages.success(msgProps.successMsg);
|
25
|
+
FlashMessages.error(msgProps.errorMsg);
|
26
|
+
FlashMessages.warning(msgProps.warningMsg);
|
27
|
+
watch(() => msgProps.successMsg, msg => FlashMessages.success(msg));
|
28
|
+
watch(() => msgProps.errorMsg, msg => FlashMessages.error(msg));
|
29
|
+
watch(() => msgProps.warningMsg, msg => FlashMessages.warning(msg));
|
30
|
+
}
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
position: "top",
|
39
|
-
closeBtn: "X",
|
40
|
-
...danxOptions.value.flashMessages.default,
|
41
|
-
...options
|
42
|
-
});
|
43
|
-
}
|
44
|
-
}
|
32
|
+
static formatMessage(message: string) {
|
33
|
+
message = message.replace(/\r\n|\r|\n/g, "<br/>");
|
34
|
+
message = message.replace(/\t/g, " ");
|
35
|
+
// Replace <SomeComponent ... > with <b>SomeComponent ... </b>
|
36
|
+
return message.replace(/<(\w+)([^>]*)>/g, "<b>$1$2</b>");
|
37
|
+
}
|
45
38
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
icon: "check",
|
50
|
-
...options,
|
51
|
-
...danxOptions.value.flashMessages.success
|
52
|
-
});
|
53
|
-
}
|
39
|
+
static send(message?: string, options: QNotifyCreateOptions = {}) {
|
40
|
+
if (message) {
|
41
|
+
message = FlashMessages.formatMessage(message);
|
54
42
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
43
|
+
FlashMessages.notify({
|
44
|
+
message,
|
45
|
+
timeout: 10000,
|
46
|
+
classes: "bg-gray-500 text-white",
|
47
|
+
position: "top",
|
48
|
+
closeBtn: "X",
|
49
|
+
...danxOptions.value.flashMessages?.default,
|
50
|
+
...options
|
51
|
+
});
|
52
|
+
}
|
53
|
+
}
|
63
54
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
55
|
+
static success(message?: string, options: QNotifyCreateOptions = {}) {
|
56
|
+
FlashMessages.send(message, {
|
57
|
+
classes: "bg-green-300 !text-green-900",
|
58
|
+
icon: "check",
|
59
|
+
...options,
|
60
|
+
...danxOptions.value.flashMessages?.success
|
61
|
+
});
|
62
|
+
}
|
72
63
|
|
73
|
-
|
74
|
-
|
75
|
-
|
64
|
+
static error(message?: string, options: QNotifyCreateOptions = {}) {
|
65
|
+
FlashMessages.send(message, {
|
66
|
+
classes: "bg-red-300 !text-red-900",
|
67
|
+
icon: "error",
|
68
|
+
...options,
|
69
|
+
...danxOptions.value.flashMessages?.error
|
70
|
+
});
|
71
|
+
}
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
static warning(message?: string, options: QNotifyCreateOptions = {}) {
|
74
|
+
FlashMessages.send(message, {
|
75
|
+
classes: "bg-yellow-300 !text-yellow-900",
|
76
|
+
icon: "warning",
|
77
|
+
...options,
|
78
|
+
...danxOptions.value.flashMessages?.warning
|
79
|
+
});
|
80
|
+
}
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
static combine(type: string, messages: string[] | { message: string, Message: string }[], options = {}) {
|
83
|
+
// @ts-expect-error - type is a string
|
84
|
+
const messageType = FlashMessages[type];
|
85
|
+
|
86
|
+
if (typeof messageType !== "function") {
|
87
|
+
throw new Error(`FlashMessages.${type} is not a function`);
|
88
|
+
}
|
89
|
+
|
90
|
+
messageType(messages.map(m => typeof m === "string" ? m : (m.message || m.Message)).join("<br/>"), {
|
91
|
+
...options,
|
92
|
+
html: true
|
93
|
+
});
|
94
|
+
}
|
86
95
|
}
|
87
96
|
|
88
97
|
export const notify = new FlashMessages();
|
package/src/helpers/actions.ts
CHANGED
@@ -1,36 +1,9 @@
|
|
1
1
|
import { useDebounceFn } from "@vueuse/core";
|
2
|
-
import {
|
2
|
+
import { uid } from "quasar";
|
3
|
+
import { isReactive, Ref, shallowRef } from "vue";
|
4
|
+
import { ActionOptions, ActionTarget, AnyObject } from "../types";
|
3
5
|
import { FlashMessages } from "./FlashMessages";
|
4
|
-
|
5
|
-
export type AnyObject = { [key: string]: any };
|
6
|
-
|
7
|
-
export type ActionTargetItem = {
|
8
|
-
id: number | string;
|
9
|
-
isSaving: Ref<boolean>;
|
10
|
-
[key: string]: any;
|
11
|
-
};
|
12
|
-
export type ActionTarget = ActionTargetItem[] | ActionTargetItem | null;
|
13
|
-
|
14
|
-
export interface ActionOptions {
|
15
|
-
name?: string;
|
16
|
-
label?: string;
|
17
|
-
menu?: boolean;
|
18
|
-
batch?: boolean;
|
19
|
-
category?: string;
|
20
|
-
class?: string;
|
21
|
-
debounce?: number;
|
22
|
-
trigger?: (target: ActionTarget, input: any) => Promise<any>;
|
23
|
-
vnode?: ((target: ActionTarget) => VNode) | undefined;
|
24
|
-
enabled?: (target: object) => boolean;
|
25
|
-
batchEnabled?: (targets: object[]) => boolean;
|
26
|
-
optimistic?: (action: ActionOptions, target: ActionTargetItem | null, input: any) => void;
|
27
|
-
onAction?: (action: string | null | undefined, target: ActionTargetItem | null, input: any) => Promise<any>;
|
28
|
-
onBatchAction?: (action: string | null | undefined, targets: ActionTargetItem[], input: any) => Promise<any>;
|
29
|
-
onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
|
30
|
-
onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
|
31
|
-
onError?: (result: any, targets: ActionTarget, input: any) => any;
|
32
|
-
onFinish?: (result: any, targets: ActionTarget, input: any) => any;
|
33
|
-
}
|
6
|
+
import { storeObject } from "./objectStore";
|
34
7
|
|
35
8
|
export const activeActionVnode: Ref = shallowRef(null);
|
36
9
|
|
@@ -42,48 +15,67 @@ export const activeActionVnode: Ref = shallowRef(null);
|
|
42
15
|
* @param {ActionOptions | null} globalOptions
|
43
16
|
*/
|
44
17
|
export function useActions(actions: ActionOptions[], globalOptions: ActionOptions | null = null) {
|
45
|
-
const
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
18
|
+
const namespace = uid();
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Resolve the action object based on the provided name (or return the object if the name is already an object)
|
22
|
+
*/
|
23
|
+
function getAction(action: string | ActionOptions): ActionOptions {
|
24
|
+
if (typeof action === "string") {
|
25
|
+
action = actions.find(a => a.name === action) || { name: action };
|
51
26
|
}
|
52
|
-
|
53
|
-
|
27
|
+
|
28
|
+
// If the action is already reactive, return it
|
29
|
+
if (isReactive(action) && action.__type) return action;
|
30
|
+
|
31
|
+
action = { ...globalOptions, ...action };
|
32
|
+
|
33
|
+
// Assign Trigger function if it doesn't exist
|
34
|
+
if (!action.trigger) {
|
35
|
+
if (action.debounce) {
|
36
|
+
action.trigger = useDebounceFn((target, input) => performAction(action, target, input), action.debounce);
|
37
|
+
} else {
|
38
|
+
action.trigger = (target, input) => performAction(action, target, input);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
// Set the initial state for the action
|
43
|
+
action.isApplying = false;
|
44
|
+
|
45
|
+
return storeObject({ ...action, __type: "__Action:" + namespace });
|
46
|
+
}
|
54
47
|
|
55
48
|
/**
|
56
|
-
*
|
49
|
+
* Filter the list of actions based on the provided filters in key-value pairs
|
50
|
+
* You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
|
51
|
+
*
|
52
|
+
* @param filters
|
53
|
+
* @returns {ActionOptions[]}
|
57
54
|
*/
|
58
|
-
function
|
59
|
-
|
60
|
-
|
61
|
-
|
55
|
+
function getActions(filters?: AnyObject): ActionOptions[] {
|
56
|
+
let filteredActions = [...actions];
|
57
|
+
|
58
|
+
if (filters) {
|
59
|
+
for (const filter of Object.keys(filters)) {
|
60
|
+
const filterValue = filters[filter];
|
61
|
+
filteredActions = filteredActions.filter((a: AnyObject) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
|
62
62
|
}
|
63
|
-
} else if (target) {
|
64
|
-
target.isSaving.value = saving;
|
65
63
|
}
|
64
|
+
|
65
|
+
return filteredActions.map((a: AnyObject) => getAction(a));
|
66
66
|
}
|
67
67
|
|
68
68
|
/**
|
69
69
|
* Perform an action on a set of targets
|
70
70
|
*
|
71
|
-
* @param {string}
|
71
|
+
* @param {string} action - can either be a string or an action object
|
72
72
|
* @param {object[]|object} target - an array of targets or a single target object
|
73
73
|
* @param {any} input - The input data to pass to the action handler
|
74
|
-
* @param isTriggered - Whether the action was triggered by a trigger function
|
75
74
|
*/
|
76
|
-
async function performAction(
|
77
|
-
|
78
|
-
if
|
79
|
-
|
80
|
-
}
|
81
|
-
|
82
|
-
// We always want to call the trigger function if it exists, unless it's already been triggered
|
83
|
-
// This provides behavior like debounce and custom action resolution
|
84
|
-
if (action.trigger && !isTriggered) {
|
85
|
-
return action.trigger(target, input);
|
86
|
-
}
|
75
|
+
async function performAction(action: string | ActionOptions, target: ActionTarget = null, input: any = null) {
|
76
|
+
action = getAction(action);
|
77
|
+
// Resolve the original action, if the current action is an alias
|
78
|
+
const aliasedAction = action.alias ? getAction(action.alias) : null;
|
87
79
|
|
88
80
|
const vnode = action.vnode && action.vnode(target);
|
89
81
|
let result: any;
|
@@ -96,6 +88,11 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
|
|
96
88
|
}
|
97
89
|
|
98
90
|
setTargetSavingState(target, true);
|
91
|
+
action.isApplying = true;
|
92
|
+
|
93
|
+
if (aliasedAction) {
|
94
|
+
aliasedAction.isApplying = true;
|
95
|
+
}
|
99
96
|
|
100
97
|
// If additional input is required, first render the vnode and wait for the confirm or cancel action
|
101
98
|
if (vnode) {
|
@@ -124,56 +121,70 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
|
|
124
121
|
result = await onConfirmAction(action, target, input);
|
125
122
|
}
|
126
123
|
|
124
|
+
action.isApplying = false;
|
127
125
|
setTargetSavingState(target, false);
|
128
126
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
/**
|
133
|
-
* Filter the list of actions based on the provided filters in key-value pairs
|
134
|
-
* You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
|
135
|
-
*
|
136
|
-
* @param filters
|
137
|
-
* @returns {ActionOptions[]}
|
138
|
-
*/
|
139
|
-
function filterActions(filters: AnyObject): ActionOptions[] {
|
140
|
-
let filteredActions = [...mappedActions];
|
127
|
+
if (aliasedAction) {
|
128
|
+
aliasedAction.isApplying = false;
|
129
|
+
}
|
141
130
|
|
142
|
-
|
143
|
-
|
144
|
-
filteredActions = filteredActions.filter((a: AnyObject) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
|
131
|
+
if (result?.item) {
|
132
|
+
result.item = storeObject(result.item);
|
145
133
|
}
|
146
|
-
|
134
|
+
|
135
|
+
return result;
|
147
136
|
}
|
148
137
|
|
149
138
|
return {
|
150
|
-
|
151
|
-
|
152
|
-
performAction
|
139
|
+
getAction,
|
140
|
+
getActions
|
153
141
|
};
|
154
142
|
}
|
155
143
|
|
144
|
+
/**
|
145
|
+
* Set the reactive saving state of a target
|
146
|
+
*/
|
147
|
+
function setTargetSavingState(target: ActionTarget, saving: boolean) {
|
148
|
+
if (!target) return;
|
149
|
+
target = Array.isArray(target) ? target : [target];
|
150
|
+
for (const t of target) {
|
151
|
+
t.isSaving = saving;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Execute the confirmed action on the target (ie: calling the server, or whatever the callback function does).
|
157
|
+
*
|
158
|
+
* 1. If the action has an optimistic callback, it will be called before the actual action to immediately update the UI (non batch actions only).
|
159
|
+
* 2. Call the onBatchAction or onAction callback of the action object, depending on if the target is an array or not.
|
160
|
+
* 3. Call the onSuccess or onError callback based on the result of the action.
|
161
|
+
* 4. Call the onFinish callback of the action object.
|
162
|
+
*/
|
156
163
|
async function onConfirmAction(action: ActionOptions, target: ActionTarget, input: any = null) {
|
157
164
|
if (!action.onAction) {
|
158
165
|
throw new Error("No onAction handler found for the selected action:" + action.name);
|
159
166
|
}
|
160
167
|
|
168
|
+
const isBatch = Array.isArray(target);
|
161
169
|
let result: any;
|
170
|
+
|
162
171
|
try {
|
163
|
-
if (
|
172
|
+
if (isBatch) {
|
164
173
|
if (action.onBatchAction) {
|
165
|
-
result = await action.onBatchAction(action.name, target, input);
|
174
|
+
result = await action.onBatchAction(action.alias || action.name, target, input);
|
166
175
|
} else {
|
167
176
|
result = { error: `Action ${action.name} does not support batch actions` };
|
168
177
|
}
|
169
178
|
} else {
|
170
179
|
// If the action has an optimistic callback, we call it before the actual action to immediately
|
171
180
|
// update the UI
|
172
|
-
if (action.optimistic) {
|
181
|
+
if (typeof action.optimistic === "function") {
|
173
182
|
action.optimistic(action, target, input);
|
183
|
+
} else if (action.optimistic) {
|
184
|
+
storeObject({ ...target, ...input });
|
174
185
|
}
|
175
186
|
|
176
|
-
result = await action.onAction(action.name, target, input);
|
187
|
+
result = await action.onAction(action.alias || action.name, target, input);
|
177
188
|
}
|
178
189
|
} catch (e) {
|
179
190
|
console.error(e);
|
@@ -182,6 +193,11 @@ async function onConfirmAction(action: ActionOptions, target: ActionTarget, inpu
|
|
182
193
|
|
183
194
|
// If there is no return value or the result marks it as successful, we show a success message
|
184
195
|
if (result === undefined || result === true || result?.success) {
|
196
|
+
// Add the item to the object store if it has a type
|
197
|
+
if (result && result.item) {
|
198
|
+
result.item = storeObject(result.item);
|
199
|
+
}
|
200
|
+
|
185
201
|
if (result?.success && Array.isArray(target)) {
|
186
202
|
FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
|
187
203
|
}
|
@@ -189,6 +205,10 @@ async function onConfirmAction(action: ActionOptions, target: ActionTarget, inpu
|
|
189
205
|
if (action.onSuccess) {
|
190
206
|
action.onSuccess(result, target, input);
|
191
207
|
}
|
208
|
+
|
209
|
+
if (isBatch && action.onBatchSuccess) {
|
210
|
+
action.onBatchSuccess(result, target, input);
|
211
|
+
}
|
192
212
|
} else {
|
193
213
|
const errors = [];
|
194
214
|
if (result.errors) {
|