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.
Files changed (75) hide show
  1. package/dist/danx.es.js +7127 -6615
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +11 -5
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -1
  7. package/src/components/ActionTable/ActionTable.vue +28 -41
  8. package/src/components/ActionTable/Columns/ActionTableColumn.vue +19 -18
  9. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +6 -6
  10. package/src/components/ActionTable/Filters/{FilterFieldList.vue → FilterList.vue} +26 -26
  11. package/src/components/ActionTable/Filters/FilterableField.vue +28 -31
  12. package/src/components/ActionTable/Filters/index.ts +2 -2
  13. package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +71 -0
  14. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +8 -13
  15. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +48 -44
  16. package/src/components/ActionTable/Form/Fields/SelectField.vue +24 -38
  17. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +28 -33
  18. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +15 -15
  19. package/src/components/ActionTable/Form/Fields/SliderNumberField.vue +45 -0
  20. package/src/components/ActionTable/Form/Fields/TextField.vue +47 -66
  21. package/src/components/ActionTable/Form/Fields/index.ts +2 -0
  22. package/src/components/ActionTable/Form/RenderedForm.vue +50 -9
  23. package/src/components/ActionTable/Form/Utilities/MaxLengthCounter.vue +17 -0
  24. package/src/components/ActionTable/Form/Utilities/index.ts +1 -0
  25. package/src/components/ActionTable/Form/index.ts +1 -0
  26. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +16 -15
  27. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +6 -6
  28. package/src/components/ActionTable/listControls.ts +104 -166
  29. package/src/components/ActionTable/listHelpers.ts +2 -3
  30. package/src/components/ActionTable/tableColumns.ts +3 -27
  31. package/src/components/AuditHistory/AuditHistoryItemValue.vue +26 -26
  32. package/src/components/PanelsDrawer/PanelsDrawer.vue +17 -4
  33. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +6 -11
  34. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +20 -20
  35. package/src/components/Utility/Dialogs/ConfirmActionDialog.vue +39 -0
  36. package/src/components/Utility/Dialogs/ConfirmDialog.vue +10 -24
  37. package/src/components/Utility/Dialogs/DialogLayout.vue +10 -28
  38. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +42 -36
  39. package/src/components/Utility/Dialogs/index.ts +1 -0
  40. package/src/components/Utility/Files/FilePreview.vue +76 -73
  41. package/src/components/Utility/Layouts/ContentDrawer.vue +24 -31
  42. package/src/components/Utility/Tools/ActionVnode.vue +3 -3
  43. package/src/components/Utility/Tools/RenderVnode.vue +1 -1
  44. package/src/components/Utility/Transitions/MaxHeightTransition.vue +26 -0
  45. package/src/components/Utility/Transitions/index.ts +1 -0
  46. package/src/config/index.ts +36 -31
  47. package/src/helpers/FileUpload.ts +295 -297
  48. package/src/helpers/FlashMessages.ts +80 -71
  49. package/src/helpers/actions.ts +102 -82
  50. package/src/helpers/download.ts +189 -189
  51. package/src/helpers/downloadPdf.ts +55 -52
  52. package/src/helpers/formats.ts +151 -109
  53. package/src/helpers/index.ts +2 -0
  54. package/src/helpers/multiFileUpload.ts +72 -58
  55. package/src/helpers/objectStore.ts +52 -0
  56. package/src/helpers/request.ts +70 -51
  57. package/src/helpers/routes.ts +29 -0
  58. package/src/helpers/storage.ts +7 -3
  59. package/src/helpers/utils.ts +47 -29
  60. package/src/styles/quasar-reset.scss +16 -1
  61. package/src/styles/themes/danx/dialogs.scss +4 -0
  62. package/src/types/actions.d.ts +43 -0
  63. package/src/types/config.d.ts +15 -0
  64. package/src/types/controls.d.ts +99 -0
  65. package/src/types/dialogs.d.ts +32 -0
  66. package/src/types/fields.d.ts +20 -0
  67. package/src/types/files.d.ts +54 -0
  68. package/src/types/formats.d.ts +4 -0
  69. package/src/{components/ActionTable/Form/form.d.ts → types/forms.d.ts} +6 -0
  70. package/src/types/index.d.ts +12 -0
  71. package/src/types/requests.d.ts +13 -0
  72. package/src/types/shared.d.ts +15 -0
  73. package/src/types/tables.d.ts +27 -0
  74. package/types/index.d.ts +1 -1
  75. /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
- static notify: (options: QNotifyCreateOptions) => void;
6
+ static notify: (options: QNotifyCreateOptions) => void;
7
7
 
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
- };
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
- 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
- }
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
- static send(message?: string, options: QNotifyCreateOptions = {}) {
33
- if (message) {
34
- FlashMessages.notify({
35
- message,
36
- timeout: 10000,
37
- classes: "bg-gray-500 text-white",
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, "&nbsp;&nbsp;&nbsp;&nbsp;");
35
+ // Replace <SomeComponent ... > with <b>SomeComponent ... </b>
36
+ return message.replace(/<(\w+)([^>]*)>/g, "<b>$1$2</b>");
37
+ }
45
38
 
46
- static success(message?: string, options: QNotifyCreateOptions = {}) {
47
- FlashMessages.send(message, {
48
- classes: "bg-green-300 !text-green-900",
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
- static error(message?: string, options: QNotifyCreateOptions = {}) {
56
- FlashMessages.send(message, {
57
- classes: "bg-red-300 !text-red-900",
58
- icon: "error",
59
- ...options,
60
- ...danxOptions.value.flashMessages.error
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
- static warning(message?: string, options: QNotifyCreateOptions = {}) {
65
- FlashMessages.send(message, {
66
- classes: "bg-yellow-300 !text-yellow-900",
67
- icon: "warning",
68
- ...options,
69
- ...danxOptions.value.flashMessages.warning
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
- static combine(type: string, messages: string[] | { message: string, Message: string }[], options = {}) {
74
- // @ts-expect-error - type is a string
75
- const messageType = FlashMessages[type];
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
- if (typeof messageType !== "function") {
78
- throw new Error(`FlashMessages.${type} is not a function`);
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
- messageType(messages.map(m => typeof m === "string" ? m : (m.message || m.Message)).join("<br/>"), {
82
- ...options,
83
- html: true
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();
@@ -1,36 +1,9 @@
1
1
  import { useDebounceFn } from "@vueuse/core";
2
- import { Ref, shallowRef, VNode } from "vue";
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 mappedActions = actions.map(action => {
46
- const mappedAction: ActionOptions = { ...globalOptions, ...action };
47
- if (mappedAction.debounce) {
48
- mappedAction.trigger = useDebounceFn((target, input) => performAction(mappedAction, target, input, true), mappedAction.debounce);
49
- } else if (!mappedAction.trigger) {
50
- mappedAction.trigger = (target, input) => performAction(mappedAction, target, input, true);
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
- return mappedAction;
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
- * Set the reactive saving state of a target
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 setTargetSavingState(target: ActionTarget, saving: boolean) {
59
- if (Array.isArray(target)) {
60
- for (const t of target) {
61
- t.isSaving.value = saving;
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} name - can either be a string or an action object
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(name: string | object, target: ActionTarget = null, input: any = null, isTriggered = false) {
77
- const action: ActionOptions | null | undefined = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
78
- if (!action) {
79
- throw new Error(`Unknown action: ${name}`);
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
- return result;
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
- for (const filter of Object.keys(filters)) {
143
- const filterValue = filters[filter];
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
- return filteredActions;
134
+
135
+ return result;
147
136
  }
148
137
 
149
138
  return {
150
- actions: mappedActions,
151
- filterActions,
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 (Array.isArray(target)) {
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) {