quasar-ui-danx 0.0.46 → 0.0.47
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/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +13 -4
- package/src/components/ActionTable/ActionTable.vue +2 -17
- package/src/components/ActionTable/ActionTableColumn.vue +1 -5
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +7 -4
- package/src/helpers/actions.ts +111 -94
package/package.json
CHANGED
@@ -4,16 +4,15 @@
|
|
4
4
|
:items="activeActions"
|
5
5
|
:disabled="!hasTarget"
|
6
6
|
:tooltip="!hasTarget ? tooltip : null"
|
7
|
-
:loading="loading"
|
7
|
+
:loading="isSaving || loading"
|
8
8
|
:loading-component="loadingComponent"
|
9
|
-
@action-item="
|
9
|
+
@action-item="onAction"
|
10
10
|
/>
|
11
11
|
</template>
|
12
12
|
<script setup>
|
13
|
-
import { computed } from 'vue';
|
13
|
+
import { computed, ref } from 'vue';
|
14
14
|
import { PopoverMenu } from '../Utility';
|
15
15
|
|
16
|
-
const emit = defineEmits(['action']);
|
17
16
|
const props = defineProps({
|
18
17
|
actions: {
|
19
18
|
type: Array,
|
@@ -43,4 +42,14 @@ const activeActions = computed(() => props.actions.filter(action => {
|
|
43
42
|
|
44
43
|
return action.enabled ? action.enabled(props.target) : true;
|
45
44
|
}));
|
45
|
+
|
46
|
+
const isSaving = ref(false);
|
47
|
+
async function onAction(action) {
|
48
|
+
if (!action.trigger) {
|
49
|
+
throw new Error('Action must have a trigger function! Make sure you are using useActions() or implement your own trigger function.');
|
50
|
+
}
|
51
|
+
isSaving.value = true;
|
52
|
+
await action.trigger(props.target);
|
53
|
+
isSaving.value = false;
|
54
|
+
}
|
46
55
|
</script>
|
@@ -51,8 +51,6 @@
|
|
51
51
|
<ActionTableColumn
|
52
52
|
:row-props="rowProps"
|
53
53
|
:settings="columnSettings[rowProps.col.name]"
|
54
|
-
:is-saving="isSavingRow(rowProps.row)"
|
55
|
-
@action="$emit('action', $event, rowProps.row)"
|
56
54
|
>
|
57
55
|
<slot />
|
58
56
|
</ActionTableColumn>
|
@@ -68,10 +66,10 @@ import { ref } from 'vue';
|
|
68
66
|
import { getItem, setItem } from '../../helpers';
|
69
67
|
import { DragHandleIcon as RowResizeIcon } from '../../svg';
|
70
68
|
import { HandleDraggable } from '../DragAndDrop';
|
71
|
-
import { ActionVnode
|
69
|
+
import { ActionVnode } from '../index';
|
72
70
|
import { ActionTableColumn, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
|
73
71
|
|
74
|
-
defineEmits(['
|
72
|
+
defineEmits(['update:quasar-pagination', 'update:selected-rows']);
|
75
73
|
const props = defineProps({
|
76
74
|
name: {
|
77
75
|
type: String,
|
@@ -89,10 +87,6 @@ const props = defineProps({
|
|
89
87
|
type: Object,
|
90
88
|
required: true
|
91
89
|
},
|
92
|
-
isSavingTarget: {
|
93
|
-
type: Object,
|
94
|
-
default: null
|
95
|
-
},
|
96
90
|
isLoadingList: Boolean,
|
97
91
|
pagedItems: {
|
98
92
|
type: Object,
|
@@ -126,15 +120,6 @@ function onResizeColumn(column, val) {
|
|
126
120
|
};
|
127
121
|
setItem(COLUMN_SETTINGS_KEY, columnSettings.value);
|
128
122
|
}
|
129
|
-
|
130
|
-
function isSavingRow(row) {
|
131
|
-
if (!props.isSavingTarget) return false;
|
132
|
-
|
133
|
-
if (Array.isArray(props.isSavingTarget)) {
|
134
|
-
return !!props.isSavingTarget.find(t => t.id === row.id);
|
135
|
-
}
|
136
|
-
return props.isSavingTarget.id === row.id;
|
137
|
-
}
|
138
123
|
</script>
|
139
124
|
|
140
125
|
<style lang="scss" scoped>
|
@@ -30,8 +30,6 @@
|
|
30
30
|
<ActionMenu
|
31
31
|
:actions="column.actions"
|
32
32
|
:target="row"
|
33
|
-
:loading="isSaving"
|
34
|
-
@action="$emit('action', $event)"
|
35
33
|
/>
|
36
34
|
</div>
|
37
35
|
</div>
|
@@ -42,7 +40,6 @@ import { computed } from 'vue';
|
|
42
40
|
import { RenderVnode } from '../Utility';
|
43
41
|
import { ActionMenu } from './index';
|
44
42
|
|
45
|
-
defineEmits(['action']);
|
46
43
|
const props = defineProps({
|
47
44
|
rowProps: {
|
48
45
|
type: Object,
|
@@ -51,8 +48,7 @@ const props = defineProps({
|
|
51
48
|
settings: {
|
52
49
|
type: Object,
|
53
50
|
default: null
|
54
|
-
}
|
55
|
-
isSaving: Boolean
|
51
|
+
}
|
56
52
|
});
|
57
53
|
|
58
54
|
const row = computed(() => props.rowProps.row);
|
@@ -1,16 +1,19 @@
|
|
1
1
|
<template>
|
2
2
|
<QTabPanels
|
3
|
-
|
4
|
-
|
3
|
+
:model-value="activePanel"
|
4
|
+
class="overflow-y-auto bg-neutral-plus-7 h-full transition-all"
|
5
5
|
>
|
6
6
|
<QTabPanel v-for="panel in panels" :key="panel.name" :name="panel.name">
|
7
|
-
<RenderVnode
|
7
|
+
<RenderVnode
|
8
|
+
v-if="panel.vnode"
|
9
|
+
:vnode="panel.vnode"
|
10
|
+
/>
|
8
11
|
</QTabPanel>
|
9
12
|
</QTabPanels>
|
10
13
|
</template>
|
11
14
|
|
12
15
|
<script setup>
|
13
|
-
import { RenderVnode } from
|
16
|
+
import { RenderVnode } from '../Utility';
|
14
17
|
|
15
18
|
defineProps({
|
16
19
|
activePanel: {
|
package/src/helpers/actions.ts
CHANGED
@@ -1,6 +1,27 @@
|
|
1
|
-
import { shallowRef, VNode } from "vue";
|
1
|
+
import { ref, shallowRef, VNode } from "vue";
|
2
2
|
import { FlashMessages } from "./index";
|
3
3
|
|
4
|
+
/**
|
5
|
+
* TODO: HOW TO INTEGRATE optimistic updates and single item updates?
|
6
|
+
*/
|
7
|
+
async function applyAction(item, input, itemData = {}) {
|
8
|
+
setItemInPagedList({ ...item, ...input, ...itemData });
|
9
|
+
const result = await applyActionRoute(item, input);
|
10
|
+
if (result.success) {
|
11
|
+
// Only render the most recent campaign changes
|
12
|
+
if (resultNumber !== actionResultCount) return;
|
13
|
+
|
14
|
+
// Update the updated item in the previously loaded list if it exists
|
15
|
+
setItemInPagedList(result.item);
|
16
|
+
|
17
|
+
// Update the active item if it is the same as the updated item
|
18
|
+
if (activeItem.value?.id === result.item.id) {
|
19
|
+
activeItem.value = { ...activeItem.value, ...result.item };
|
20
|
+
}
|
21
|
+
}
|
22
|
+
return result;
|
23
|
+
}
|
24
|
+
|
4
25
|
interface ActionOptions {
|
5
26
|
name?: string;
|
6
27
|
label?: string;
|
@@ -8,6 +29,8 @@ interface ActionOptions {
|
|
8
29
|
batch?: boolean;
|
9
30
|
category?: string;
|
10
31
|
class?: string;
|
32
|
+
trigger?: (target: object[] | object, input: any) => Promise<any>;
|
33
|
+
activeTarget?: any;
|
11
34
|
vnode?: (target: object[] | object) => VNode;
|
12
35
|
enabled?: (target: object) => boolean;
|
13
36
|
batchEnabled?: (targets: object[]) => boolean;
|
@@ -28,111 +51,105 @@ export const activeActionVnode = shallowRef(null);
|
|
28
51
|
* @param {ActionOptions} globalOptions
|
29
52
|
*/
|
30
53
|
export function useActions(actions: ActionOptions[], globalOptions: ActionOptions = null) {
|
31
|
-
const
|
54
|
+
const mappedActions = actions.map(action => {
|
55
|
+
if (!action.trigger) {
|
56
|
+
action.trigger = (target, input) => performAction(action, target, input);
|
57
|
+
action.activeTarget = ref(null);
|
58
|
+
}
|
59
|
+
return { ...globalOptions, ...action };
|
60
|
+
});
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Check if the provided target is currently being saved by any of the actions
|
64
|
+
*/
|
65
|
+
function isSavingTarget(target: any): boolean {
|
66
|
+
if (!target) return false;
|
67
|
+
|
68
|
+
for (const action of mappedActions) {
|
69
|
+
const activeTargets = Array.isArray(action.activeTarget.value) ? action.activeTarget.value : [action.activeTarget.value];
|
70
|
+
if (activeTargets.length === 0) continue;
|
71
|
+
|
72
|
+
for (const activeTarget of activeTargets) {
|
73
|
+
if (activeTarget === target || (activeTarget.id && activeTarget.id === target.id)) {
|
74
|
+
return true;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
return false;
|
80
|
+
}
|
32
81
|
|
33
82
|
/**
|
34
|
-
*
|
83
|
+
* Perform an action on a set of targets
|
35
84
|
*
|
36
|
-
* @param name
|
37
|
-
* @param {
|
38
|
-
* @
|
85
|
+
* @param {string} name - can either be a string or an action object
|
86
|
+
* @param {object[]|object} target - an array of targets or a single target object
|
87
|
+
* @param {any} input - The input data to pass to the action handler
|
39
88
|
*/
|
40
|
-
function
|
41
|
-
const action = typeof name === "string" ?
|
89
|
+
async function performAction(name: string | object, target: object[] | object, input: any = null) {
|
90
|
+
const action: ActionOptions = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
|
42
91
|
if (!action) {
|
43
92
|
throw new Error(`Unknown action: ${name}`);
|
44
93
|
}
|
45
94
|
|
46
|
-
|
95
|
+
const vnode = action.vnode && action.vnode(target);
|
96
|
+
let result: any;
|
97
|
+
|
98
|
+
action.activeTarget.value = target;
|
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, reject) => {
|
107
|
+
activeActionVnode.value = {
|
108
|
+
vnode,
|
109
|
+
confirm: async (input: any) => {
|
110
|
+
const result = await onConfirmAction(action, target, input);
|
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
|
+
action.activeTarget.value = null;
|
128
|
+
return result;
|
47
129
|
}
|
48
130
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
* @param filters
|
59
|
-
* @returns {ActionOptions[]}
|
60
|
-
*/
|
61
|
-
filterActions(filters: object) {
|
62
|
-
let filteredActions = [...actions];
|
63
|
-
|
64
|
-
for (const filter of Object.keys(filters)) {
|
65
|
-
const filterValue = filters[filter];
|
66
|
-
filteredActions = filteredActions.filter(a => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
|
67
|
-
}
|
68
|
-
return filteredActions;
|
69
|
-
},
|
70
|
-
|
71
|
-
/**
|
72
|
-
* TODO: HOW TO INTEGRATE optimistic updates and single item updates?
|
73
|
-
*/
|
74
|
-
async applyAction(item, input, itemData = {}) {
|
75
|
-
setItemInPagedList({ ...item, ...input, ...itemData });
|
76
|
-
const result = await applyActionRoute(item, input);
|
77
|
-
if (result.success) {
|
78
|
-
// Only render the most recent campaign changes
|
79
|
-
if (resultNumber !== actionResultCount) return;
|
80
|
-
|
81
|
-
// Update the updated item in the previously loaded list if it exists
|
82
|
-
setItemInPagedList(result.item);
|
83
|
-
|
84
|
-
// Update the active item if it is the same as the updated item
|
85
|
-
if (activeItem.value?.id === result.item.id) {
|
86
|
-
activeItem.value = { ...activeItem.value, ...result.item };
|
87
|
-
}
|
88
|
-
}
|
89
|
-
return result;
|
90
|
-
},
|
91
|
-
|
92
|
-
/**
|
93
|
-
* Perform an action on a set of targets
|
94
|
-
*
|
95
|
-
* @param {string} name - can either be a string or an action object
|
96
|
-
* @param {object[]|object} target - an array of targets or a single target object
|
97
|
-
* @param {any} input
|
98
|
-
*/
|
99
|
-
async performAction(name: string | object, target: object[] | object, input: any = null) {
|
100
|
-
const action = resolveAction(name);
|
101
|
-
const vnode = action.vnode && action.vnode(target);
|
102
|
-
let result = null;
|
103
|
-
|
104
|
-
isSavingTarget.value = target;
|
105
|
-
|
106
|
-
// If additional input is required, first render the vnode and wait for the confirm or cancel action
|
107
|
-
if (vnode) {
|
108
|
-
// If the action requires an input, we set the activeActionVnode to the input component.
|
109
|
-
// This will tell the ActionVnode to render the input component, and confirm or cancel the
|
110
|
-
// action The confirm function has the input from the component passed and will resolve the promise
|
111
|
-
// with the result of the action
|
112
|
-
result = await new Promise((resolve, reject) => {
|
113
|
-
activeActionVnode.value = {
|
114
|
-
vnode,
|
115
|
-
confirm: async input => {
|
116
|
-
const result = await onConfirmAction(action, target, input);
|
117
|
-
|
118
|
-
// Only resolve when we have a non-error response, so we can show the error message w/o
|
119
|
-
// hiding the dialog / vnode
|
120
|
-
if (result === undefined || result === true || result?.success) {
|
121
|
-
resolve(result);
|
122
|
-
}
|
123
|
-
},
|
124
|
-
cancel: resolve
|
125
|
-
};
|
126
|
-
});
|
127
|
-
|
128
|
-
activeActionVnode.value = null;
|
129
|
-
} else {
|
130
|
-
result = await onConfirmAction(action, target, input);
|
131
|
-
}
|
131
|
+
/**
|
132
|
+
* Filter the list of actions based on the provided filters in key-value pairs
|
133
|
+
* You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
|
134
|
+
*
|
135
|
+
* @param filters
|
136
|
+
* @returns {ActionOptions[]}
|
137
|
+
*/
|
138
|
+
function filterActions(filters: object): ActionOptions[] {
|
139
|
+
let filteredActions = [...mappedActions];
|
132
140
|
|
133
|
-
|
134
|
-
|
141
|
+
for (const filter of Object.keys(filters)) {
|
142
|
+
const filterValue = filters[filter];
|
143
|
+
filteredActions = filteredActions.filter(a => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
|
135
144
|
}
|
145
|
+
return filteredActions;
|
146
|
+
}
|
147
|
+
|
148
|
+
return {
|
149
|
+
actions: mappedActions,
|
150
|
+
isSavingTarget,
|
151
|
+
filterActions,
|
152
|
+
performAction
|
136
153
|
};
|
137
154
|
}
|
138
155
|
|