quasar-ui-danx 0.3.21 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/.eslintrc.cjs +32 -30
  2. package/danx-local.sh +1 -1
  3. package/dist/danx.es.js +7490 -7520
  4. package/dist/danx.es.js.map +1 -1
  5. package/dist/danx.umd.js +5 -5
  6. package/dist/danx.umd.js.map +1 -1
  7. package/dist/style.css +1 -1
  8. package/package.json +1 -1
  9. package/src/components/ActionTable/ActionMenu.vue +1 -1
  10. package/src/components/ActionTable/ActionTable.vue +64 -45
  11. package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
  12. package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
  13. package/src/components/ActionTable/Columns/index.ts +2 -0
  14. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
  15. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
  16. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -35
  17. package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
  18. package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
  19. package/src/components/ActionTable/Form/form.d.ts +31 -0
  20. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
  21. package/src/components/ActionTable/TableSummaryRow.vue +4 -4
  22. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
  23. package/src/components/ActionTable/Toolbars/index.ts +1 -0
  24. package/src/components/ActionTable/index.ts +1 -2
  25. package/src/components/ActionTable/listControls.ts +512 -385
  26. package/src/components/ActionTable/listHelpers.ts +46 -44
  27. package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
  28. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
  29. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
  30. package/src/components/Utility/Buttons/ExportButton.vue +1 -1
  31. package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
  32. package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
  33. package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
  34. package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
  35. package/src/helpers/actions.ts +197 -187
  36. package/src/styles/general.scss +12 -11
  37. package/src/styles/quasar-reset.scss +59 -11
  38. package/src/styles/themes/danx/action-table.scss +19 -0
  39. package/src/styles/themes/danx/buttons.scss +13 -0
  40. package/src/styles/themes/danx/forms.scss +5 -0
  41. package/src/styles/themes/danx/index.scss +3 -0
  42. package/src/styles/themes/danx/panels.scss +19 -0
  43. package/src/styles/themes/danx/sidebar.scss +3 -0
  44. package/src/styles/themes/danx/toolbar.scss +3 -0
  45. package/types/index.d.ts +1 -0
  46. package/src/styles/actions.scss +0 -10
@@ -2,32 +2,34 @@ import { useDebounceFn } from "@vueuse/core";
2
2
  import { Ref, shallowRef, VNode } from "vue";
3
3
  import { FlashMessages } from "./FlashMessages";
4
4
 
5
+ export type AnyObject = { [key: string]: any };
6
+
5
7
  export type ActionTargetItem = {
6
- id: number | string;
7
- isSaving: Ref<boolean>;
8
- [key: string]: any;
8
+ id: number | string;
9
+ isSaving: Ref<boolean>;
10
+ [key: string]: any;
9
11
  };
10
12
  export type ActionTarget = ActionTargetItem[] | ActionTargetItem;
11
13
 
12
14
  export interface ActionOptions {
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;
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: object, input: any) => void;
27
+ onAction?: (action: string | null | undefined, target: object, input: any) => Promise<any>;
28
+ onBatchAction?: (action: string | null | undefined, targets: object[], 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;
31
33
  }
32
34
 
33
35
  export const activeActionVnode: Ref = shallowRef(null);
@@ -40,173 +42,181 @@ export const activeActionVnode: Ref = shallowRef(null);
40
42
  * @param {ActionOptions | null} globalOptions
41
43
  */
42
44
  export function useActions(actions: ActionOptions[], globalOptions: ActionOptions | null = null) {
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 {
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
- };
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);
51
+ }
52
+ return mappedAction;
53
+ });
54
+
55
+ /**
56
+ * Set the reactive saving state of a target
57
+ */
58
+ function setTargetSavingState(target: ActionTarget, saving: boolean) {
59
+ if (Array.isArray(target)) {
60
+ for (const t of target) {
61
+ t.isSaving.value = saving;
62
+ }
63
+ } else {
64
+ target.isSaving.value = saving;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Perform an action on a set of targets
70
+ *
71
+ * @param {string} name - can either be a string or an action object
72
+ * @param {object[]|object} target - an array of targets or a single target object
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
+ */
76
+ async function performAction(name: string | object, target: ActionTarget, 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
+ }
87
+
88
+ const vnode = action.vnode && action.vnode(target);
89
+ let result: any;
90
+
91
+ // Run the onStart handler if it exists and quit the operation if it returns false
92
+ if (action.onStart) {
93
+ if (!action.onStart(action, target, input)) {
94
+ return;
95
+ }
96
+ }
97
+
98
+ setTargetSavingState(target, true);
99
+
100
+ // If additional input is required, first render the vnode and wait for the confirm or cancel action
101
+ if (vnode) {
102
+ // If the action requires an input, we set the activeActionVnode to the input component.
103
+ // This will tell the ActionVnode to render the input component, and confirm or cancel the
104
+ // action The confirm function has the input from the component passed and will resolve the promise
105
+ // with the result of the action
106
+ result = await new Promise((resolve) => {
107
+ activeActionVnode.value = {
108
+ vnode,
109
+ confirm: async (confirmInput: any) => {
110
+ const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
111
+
112
+ // Only resolve when we have a non-error response, so we can show the error message w/o
113
+ // hiding the dialog / vnode
114
+ if (result === undefined || result === true || result?.success) {
115
+ resolve(result);
116
+ }
117
+ },
118
+ cancel: resolve
119
+ };
120
+ });
121
+
122
+ activeActionVnode.value = null;
123
+ } else {
124
+ result = await onConfirmAction(action, target, input);
125
+ }
126
+
127
+ setTargetSavingState(target, false);
128
+
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];
141
+
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])));
145
+ }
146
+ return filteredActions;
147
+ }
148
+
149
+ return {
150
+ actions: mappedActions,
151
+ filterActions,
152
+ performAction
153
+ };
152
154
  }
153
155
 
154
156
  async function onConfirmAction(action: ActionOptions, target: ActionTarget, input: any = null) {
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;
157
+ if (!action.onAction) {
158
+ throw new Error("No onAction handler found for the selected action:" + action.name);
159
+ }
160
+
161
+ let result: any;
162
+ try {
163
+ if (Array.isArray(target)) {
164
+ if (action.onBatchAction) {
165
+ result = await action.onBatchAction(action.name, target, input);
166
+ } else {
167
+ result = { error: `Action ${action.name} does not support batch actions` };
168
+ }
169
+ } else {
170
+ // If the action has an optimistic callback, we call it before the actual action to immediately
171
+ // update the UI
172
+ if (action.optimistic) {
173
+ action.optimistic(action, target, input);
174
+ }
175
+
176
+ result = await action.onAction(action.name, target, input);
177
+ }
178
+ } catch (e) {
179
+ console.error(e);
180
+ result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
181
+ }
182
+
183
+ // If there is no return value or the result marks it as successful, we show a success message
184
+ if (result === undefined || result === true || result?.success) {
185
+ if (result?.success && Array.isArray(target)) {
186
+ FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
187
+ }
188
+
189
+ if (action.onSuccess) {
190
+ action.onSuccess(result, target, input);
191
+ }
192
+ } else {
193
+ const errors = [];
194
+ if (result.errors) {
195
+ errors.push(...result.errors);
196
+ } else if (result.error) {
197
+ let message = result.error;
198
+ if (typeof result.error === "boolean") {
199
+ message = result.message;
200
+ } else if (typeof result.error === "object") {
201
+ message = result.error.message;
202
+ } else if (typeof result.error !== "string") {
203
+ message = "An unknown error occurred. Please try again later.";
204
+ }
205
+ errors.push(message);
206
+ } else {
207
+ errors.push("An unexpected error occurred. Please try again later.");
208
+ }
209
+
210
+ FlashMessages.combine("error", errors);
211
+
212
+ if (action.onError) {
213
+ action.onError(result, target, input);
214
+ }
215
+ }
216
+
217
+ if (action.onFinish) {
218
+ action.onFinish(result, target, input);
219
+ }
220
+
221
+ return result;
212
222
  }
@@ -1,18 +1,19 @@
1
1
  body {
2
- height: 100vh;
2
+ height: 100vh;
3
+ width: 100vw;
3
4
 
4
- #app,
5
- #main-layout,
6
- main {
7
- height: 100%;
8
- }
5
+ #app,
6
+ #main-layout,
7
+ main {
8
+ height: 100%;
9
+ }
9
10
  }
10
11
 
11
12
  a {
12
- cursor: pointer;
13
+ cursor: pointer;
13
14
 
14
- &.is-disabled {
15
- opacity: 0.5;
16
- cursor: not-allowed;
17
- }
15
+ &.is-disabled {
16
+ opacity: 0.5;
17
+ cursor: not-allowed;
18
+ }
18
19
  }
@@ -1,9 +1,66 @@
1
- .q-tab {
2
- text-transform: capitalize
1
+ .danx-app {
2
+ .q-tab {
3
+ text-transform: capitalize
4
+ }
5
+
6
+ .q-table__card {
7
+ background: inherit;
8
+ color: inherit;
9
+ }
10
+
11
+ .q-checkbox__inner {
12
+ color: inherit;
13
+ }
14
+
15
+ .q-toolbar {
16
+ min-height: 0;
17
+ padding: 0;
18
+ }
19
+
20
+ .q-notification__actions {
21
+ color: inherit;
22
+ }
23
+
24
+ .q-date {
25
+ min-width: 100px;
26
+
27
+ .q-date__view {
28
+ min-height: 0;
29
+ }
30
+ }
31
+
32
+ .q-field {
33
+ &.q-field--auto-height {
34
+ .q-field__control {
35
+ min-height: 0;
36
+
37
+ .q-field__native {
38
+ min-height: 0;
39
+ color: inherit;
40
+ }
41
+ }
42
+
43
+ }
44
+
45
+ &.q-field--labeled {
46
+ .q-field__control-container {
47
+ padding-top: 1.1rem;
48
+ }
49
+ }
50
+
51
+ .q-field__marginal, .q-field__input, .q-field__label {
52
+ color: inherit;
53
+ }
54
+ }
55
+ }
56
+
57
+ .q-item {
58
+ min-height: 0;
3
59
  }
4
60
 
5
61
  .q-tab-panels {
6
62
  overflow: visible;
63
+ background: inherit;
7
64
 
8
65
  .q-panel {
9
66
  overflow: visible;
@@ -21,12 +78,3 @@
21
78
  overflow-y: auto;
22
79
  }
23
80
  }
24
-
25
- .q-toolbar {
26
- min-height: 0;
27
- padding: 0;
28
- }
29
-
30
- .q-notification__actions {
31
- color: inherit;
32
- }
@@ -0,0 +1,19 @@
1
+ .dx-table-summary-tr {
2
+ @apply bg-gray-100;
3
+
4
+ &.has-selection {
5
+ @apply bg-blue-600 text-white;
6
+ }
7
+
8
+ &.is-loading {
9
+ @apply opacity-50;
10
+ }
11
+
12
+ .dx-table-summary-td {
13
+ @apply font-bold bg-gray-100 pl-5;
14
+
15
+ &.has-selection {
16
+ @apply bg-blue-600 text-white pl-4;
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ .dx-action-button {
2
+ @apply bg-slate-200 transition-all cursor-pointer rounded outline outline-transparent;
3
+
4
+ &.dx-column-action-menu:hover {
5
+ @apply bg-blue-200 outline-blue-600;
6
+ }
7
+ }
8
+
9
+ .dx-previous-next-controls {
10
+ .dx-control {
11
+ @apply bg-slate-200 border-gray-300;
12
+ }
13
+ }
@@ -0,0 +1,5 @@
1
+ .q-field {
2
+ .q-field__label {
3
+ @apply text-sm text-gray-700;
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ @import "action-table";
2
+ @import "buttons";
3
+ @import "toolbar";
@@ -0,0 +1,19 @@
1
+ .dx-panels-drawer-header {
2
+ @apply border-b;
3
+ }
4
+
5
+ .dx-panels-drawer-panels {
6
+ @apply bg-gray-100
7
+ }
8
+
9
+ .dx-panels-drawer-tabs {
10
+ @apply bg-gray-200 border-r;
11
+
12
+ .q-tab {
13
+ @apply text-left py-2.5 px-2 rounded-lg hover:bg-slate-200;
14
+
15
+ &.q-tab--active {
16
+ @apply text-white bg-blue-600;
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,3 @@
1
+ .dx-collapsable-sidebar {
2
+ @apply border-r border-gray-200
3
+ }
@@ -0,0 +1,3 @@
1
+ .dx-batch-actions {
2
+ @apply px-4 bg-slate-600 rounded text-red-500;
3
+ }
@@ -0,0 +1 @@
1
+ export * from "../src/components/ActionTable/Form/form.d.ts";
@@ -1,10 +0,0 @@
1
- .action-button {
2
- cursor: pointer;
3
- border-radius: 0.5em;
4
- transition: all 0.5s;
5
- outline: 1px solid transparent;
6
-
7
- &:hover {
8
- @apply bg-blue-200 outline outline-blue-600;
9
- }
10
- }