quasar-ui-danx 0.3.22 → 0.4.1
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/.eslintrc.cjs +32 -30
- package/danx-local.sh +1 -1
- package/dist/danx.es.js +7490 -7519
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +5 -5
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +1 -1
- package/src/components/ActionTable/ActionTable.vue +64 -45
- package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
- package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
- package/src/components/ActionTable/Columns/index.ts +2 -0
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -34
- package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
- package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
- package/src/components/ActionTable/Form/form.d.ts +31 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
- package/src/components/ActionTable/TableSummaryRow.vue +4 -4
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
- package/src/components/ActionTable/Toolbars/index.ts +1 -0
- package/src/components/ActionTable/index.ts +1 -2
- package/src/components/ActionTable/listControls.ts +512 -385
- package/src/components/ActionTable/listHelpers.ts +46 -44
- package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
- package/src/components/Utility/Buttons/ExportButton.vue +1 -1
- package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
- package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
- package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
- package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
- package/src/helpers/actions.ts +197 -187
- package/src/styles/general.scss +12 -11
- package/src/styles/quasar-reset.scss +59 -11
- package/src/styles/themes/danx/action-table.scss +19 -0
- package/src/styles/themes/danx/buttons.scss +13 -0
- package/src/styles/themes/danx/forms.scss +5 -0
- package/src/styles/themes/danx/index.scss +3 -0
- package/src/styles/themes/danx/panels.scss +19 -0
- package/src/styles/themes/danx/sidebar.scss +3 -0
- package/src/styles/themes/danx/toolbar.scss +3 -0
- package/types/index.d.ts +1 -0
- package/src/styles/actions.scss +0 -10
package/src/helpers/actions.ts
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
}
|
package/src/styles/general.scss
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
body {
|
2
|
-
|
2
|
+
height: 100vh;
|
3
|
+
width: 100vw;
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
#app,
|
6
|
+
#main-layout,
|
7
|
+
main {
|
8
|
+
height: 100%;
|
9
|
+
}
|
9
10
|
}
|
10
11
|
|
11
12
|
a {
|
12
|
-
|
13
|
+
cursor: pointer;
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
&.is-disabled {
|
16
|
+
opacity: 0.5;
|
17
|
+
cursor: not-allowed;
|
18
|
+
}
|
18
19
|
}
|
@@ -1,9 +1,66 @@
|
|
1
|
-
.
|
2
|
-
|
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,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
|
+
}
|
package/types/index.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export * from "../src/components/ActionTable/Form/form.d.ts";
|