quasar-ui-danx 0.0.37 → 0.0.39

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -1,49 +1,43 @@
1
1
  <template>
2
2
  <PopoverMenu
3
3
  class="px-2 flex action-button"
4
- :items="activeItems"
5
- :disabled="targets.length === 0"
6
- :tooltip="targets.length === 0 ? tooltip : null"
7
- :loading="isSaving"
4
+ :items="activeActions"
5
+ :disabled="!hasTarget"
6
+ :tooltip="!hasTarget ? tooltip : null"
7
+ :loading="loading"
8
8
  :loading-component="loadingComponent"
9
- @action-item="onAction"
9
+ @action-item="$emit('action', $event)"
10
10
  />
11
11
  </template>
12
12
  <script setup>
13
- import { computed, ref } from 'vue';
14
- import { performAction } from '../../helpers';
13
+ import { computed } from 'vue';
15
14
  import { PopoverMenu } from '../Utility';
16
15
 
17
16
  const emit = defineEmits(['action']);
18
17
  const props = defineProps({
19
- items: {
18
+ actions: {
20
19
  type: Array,
21
20
  required: true
22
21
  },
23
- targets: {
24
- type: Array,
22
+ target: {
23
+ type: [Array, Object],
25
24
  required: true
26
25
  },
27
26
  tooltip: {
28
27
  type: String,
29
28
  default: 'First select records to perform a batch action'
30
29
  },
30
+ loading: Boolean,
31
31
  loadingComponent: {
32
32
  type: [Function, Object],
33
33
  default: undefined
34
34
  }
35
35
  });
36
36
 
37
- const activeItems = computed(() => props.items.filter(item => {
38
- if (item.enabled === undefined) return true;
39
- return typeof item.enabled === 'function' ? !!item.enabled(props.targets?.[0] ?? null, props.targets) : !!item.enabled;
40
- }));
37
+ const hasTarget = computed(() => !!props.target?.length);
41
38
 
42
- const isSaving = ref(false);
43
- async function onAction(item) {
44
- emit('action', item);
45
- isSaving.value = true;
46
- await performAction(item, props.targets);
47
- isSaving.value = false;
48
- }
39
+ const activeActions = computed(() => props.actions.filter(action => {
40
+ if (action.enabled === undefined) return true;
41
+ return typeof action.enabled === 'function' ? !!action.enabled(props.target) : !!action.enabled;
42
+ }));
49
43
  </script>
@@ -75,9 +75,10 @@
75
75
  </div>
76
76
  <div v-if="rowProps.col.actions" class="flex-grow flex justify-end pl-2">
77
77
  <ActionMenu
78
- :items="rowProps.col.actions"
79
- :targets="[rowProps.row]"
80
- @action="(action) => $emit('action', {action: action, row: rowProps.row})"
78
+ :actions="rowProps.col.actions"
79
+ :target="rowProps.row"
80
+ :loading="isSavingItem?.id === rowProps.row.id"
81
+ @action="(action) => $emit('action', {action, target: rowProps.row})"
81
82
  />
82
83
  </div>
83
84
  </component>
@@ -112,6 +113,10 @@ const props = defineProps({
112
113
  type: Object,
113
114
  required: true
114
115
  },
116
+ isSavingItem: {
117
+ type: Object,
118
+ default: null
119
+ },
115
120
  isLoadingList: Boolean,
116
121
  pagedItems: {
117
122
  type: Object,
@@ -6,15 +6,5 @@
6
6
  <slot name="filters" />
7
7
  <slot />
8
8
  </div>
9
- <ActionPerformerTool
10
- v-if="activeAction"
11
- :targets="actionTargets"
12
- :action="activeAction"
13
- @done="clearAction"
14
- />
15
9
  </div>
16
10
  </template>
17
- <script setup>
18
- import { actionTargets, activeAction, clearAction } from '../../../helpers';
19
- import { ActionPerformerTool } from '../../Utility';
20
- </script>
@@ -1,7 +1,7 @@
1
1
  export * from "./Filters";
2
2
  export * from "./Form";
3
3
  export * from "./Layouts";
4
- export * from "./listActions";
4
+ export * from "./listControls";
5
5
  export * from "./listHelpers";
6
6
  export * from "./tableColumns";
7
7
  export { default as ActionMenu } from "./ActionMenu.vue";
@@ -7,20 +7,17 @@ interface ListActionsOptions {
7
7
  summaryRoute?: Function;
8
8
  filterFieldOptionsRoute?: Function;
9
9
  moreRoute?: Function;
10
- applyActionRoute?: Function;
11
10
  itemDetailsRoute?: Function;
12
11
  urlPattern?: RegExp;
13
12
  filterDefaults?: Record<string, any>;
14
13
  refreshFilters?: boolean;
15
14
  }
16
15
 
17
-
18
- export function useListActions(name: string, {
16
+ export function useListControls(name: string, {
19
17
  listRoute,
20
18
  summaryRoute = null,
21
19
  filterFieldOptionsRoute = null,
22
20
  moreRoute = null,
23
- applyActionRoute = null,
24
21
  itemDetailsRoute = null,
25
22
  refreshFilters = false,
26
23
  urlPattern = null,
@@ -225,33 +222,6 @@ export function useListActions(name: string, {
225
222
  setItem(PAGE_SETTINGS_KEY, settings);
226
223
  }
227
224
 
228
- /**
229
- * Applies an action to an item.
230
- */
231
- const isApplyingActionToItem = ref(null);
232
- let actionResultCount = 0;
233
-
234
- async function applyAction(item, input, itemData = {}) {
235
- isApplyingActionToItem.value = item;
236
- const resultNumber = ++actionResultCount;
237
- setItemInPagedList({ ...item, ...input, ...itemData });
238
- const result = await applyActionRoute(item, input);
239
- if (result.success) {
240
- // Only render the most recent campaign changes
241
- if (resultNumber !== actionResultCount) return;
242
-
243
- // Update the updated item in the previously loaded list if it exists
244
- setItemInPagedList(result.item);
245
-
246
- // Update the active item if it is the same as the updated item
247
- if (activeItem.value?.id === result.item.id) {
248
- activeItem.value = { ...activeItem.value, ...result.item };
249
- }
250
- }
251
- isApplyingActionToItem.value = null;
252
- return result;
253
- }
254
-
255
225
  // The active ad for viewing / editing
256
226
  const activeItem = ref(null);
257
227
  // Controls the active panel (ie: tab) if rendering a panels drawer or similar
@@ -355,7 +325,6 @@ export function useListActions(name: string, {
355
325
  isLoadingSummary,
356
326
  pager,
357
327
  quasarPagination,
358
- isApplyingActionToItem,
359
328
  activeItem,
360
329
  activePanel,
361
330
 
@@ -366,9 +335,9 @@ export function useListActions(name: string, {
366
335
  loadList,
367
336
  loadMore,
368
337
  refreshAll,
369
- applyAction,
370
338
  getNextItem,
371
339
  openItemForm,
372
- applyFilterFromUrl
340
+ applyFilterFromUrl,
341
+ setItemInPagedList
373
342
  };
374
343
  }
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <div>
3
+ <RenderComponent
4
+ v-if="activeActionInput"
5
+ :component="activeActionInput.component"
6
+ :is-saving="isSaving"
7
+ @confirm="activeActionInput.confirm"
8
+ @close="activeActionInput.cancel"
9
+ />
10
+ </div>
11
+ </template>
12
+ <script setup>
13
+ import RenderComponent from 'src/components/Utility/Tools/RenderComponent';
14
+ import { activeActionInput } from '../../../helpers';
15
+
16
+ defineProps({
17
+ isSaving: Boolean
18
+
19
+ });
20
+ const emit = defineEmits(['result']);
21
+ </script>
@@ -1,2 +1,2 @@
1
- export { default as ActionPerformerTool } from "./ActionPerformerTool.vue";
1
+ export { default as ActionInputComponent } from "./ActionInputComponent.vue";
2
2
  export { default as RenderComponent } from "./RenderComponent.vue";
@@ -0,0 +1,169 @@
1
+ import { ref, shallowRef } from "vue";
2
+ import { FlashMessages } from "./index";
3
+
4
+ interface ActionOptions {
5
+ name: string;
6
+ label: string;
7
+ menu?: boolean;
8
+ batch?: boolean;
9
+ inputComponent?: (target: object[] | object) => any;
10
+ enabled?: (target: object) => boolean;
11
+ onAction?: (action: string | null, target: object, input: any) => any;
12
+ onBatchAction?: (action: string | null, targets: object[], input: any) => any;
13
+ onFinish?: (action: string | null, targets: object, input: any) => void;
14
+ }
15
+
16
+ export const activeActionInput = shallowRef(null);
17
+
18
+ /**
19
+ * Hook to perform an action on a set of targets
20
+ * This helper allows you to perform actions by name on a set of targets using a provided list of actions
21
+ *
22
+ * @param actions
23
+ * @param {ActionOptions} globalOptions
24
+ */
25
+ export function useActions(actions: ActionOptions[], globalOptions: ActionOptions = null) {
26
+ const activeAction = ref(null);
27
+ const activeTarget = ref(null);
28
+
29
+ /**
30
+ * Resolves an action by name or object, adds globalOptions and overrides any passes options
31
+ *
32
+ * @param name
33
+ * @param {any} options
34
+ * @returns {any}
35
+ */
36
+ function resolveAction(name, options = null) {
37
+ const action = typeof name === "string" ? actions.find(a => a.name === name) : name;
38
+ if (!action) {
39
+ throw new Error(`Unknown action: ${name}`);
40
+ }
41
+
42
+ return { ...globalOptions, ...action, ...options };
43
+ }
44
+
45
+ return {
46
+ actions,
47
+ activeAction,
48
+ activeTarget,
49
+ resolveAction,
50
+
51
+ /**
52
+ * Filter the list of actions based on the provided filters in key-value pairs
53
+ * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
54
+ *
55
+ * @param filters
56
+ * @returns {ActionOptions[]}
57
+ */
58
+ filterActions(filters: object) {
59
+ let filteredActions = [...actions];
60
+
61
+ for (const filter of Object.keys(filters)) {
62
+ const filterValue = filters[filter];
63
+ filteredActions = filteredActions.filter(a => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
64
+ }
65
+ return filteredActions;
66
+ },
67
+
68
+ /**
69
+ * Applies an action to an item.
70
+ */
71
+ async applyAction(item, input, itemData = {}) {
72
+ isSavingItem.value = item;
73
+
74
+ setItemInPagedList({ ...item, ...input, ...itemData });
75
+ const result = await applyActionRoute(item, input);
76
+ if (result.success) {
77
+ // Only render the most recent campaign changes
78
+ if (resultNumber !== actionResultCount) return;
79
+
80
+ // Update the updated item in the previously loaded list if it exists
81
+ setItemInPagedList(result.item);
82
+
83
+ // Update the active item if it is the same as the updated item
84
+ if (activeItem.value?.id === result.item.id) {
85
+ activeItem.value = { ...activeItem.value, ...result.item };
86
+ }
87
+ }
88
+ isSavingItem.value = null;
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 component = action.inputComponent && action.inputComponent(target);
102
+
103
+ if (component) {
104
+ input = await new Promise((resolve, reject) => {
105
+ activeActionInput.value = {
106
+ component,
107
+ confirm: resolve,
108
+ cancel: reject
109
+ };
110
+ });
111
+ activeActionInput.value = null;
112
+ }
113
+
114
+ return await onConfirmAction(action, target, input);
115
+ }
116
+ };
117
+ }
118
+
119
+ async function onConfirmAction(action, target, input) {
120
+ if (!action.onAction) {
121
+ throw new Error("No onAction handler found for the selected action:" + action.name);
122
+ }
123
+
124
+ let result: any;
125
+ try {
126
+ if (Array.isArray(target)) {
127
+ result = await action.onBatchAction(action.name, target, input);
128
+ } else {
129
+ result = await action.onAction(action.name, target, input);
130
+ }
131
+ } catch (e) {
132
+ console.error(e);
133
+ result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
134
+ }
135
+
136
+ // If there is no return value or the result marks it as successful, we show a success message
137
+ if (result === undefined || result?.success) {
138
+
139
+ if (result?.success) {
140
+ FlashMessages.success(`The update was successful`);
141
+ }
142
+
143
+ if (action.onSuccess) {
144
+ await action.onSuccess(result, target, input);
145
+ }
146
+
147
+ } else {
148
+ const errors = [];
149
+ if (result.errors) {
150
+ errors.push(...result.errors);
151
+ } else if (result.error) {
152
+ errors.push(typeof result.error === "string" ? result.error : result.error.message);
153
+ } else {
154
+ errors.push("An unknown error occurred. Please try again later.");
155
+ }
156
+
157
+ FlashMessages.combine("error", errors);
158
+
159
+ if (action.onError) {
160
+ await action.onError(result, target, input);
161
+ }
162
+ }
163
+
164
+ if (action.onFinish) {
165
+ await action.onFinish(result, target, input);
166
+ }
167
+
168
+ return result;
169
+ }
@@ -9,7 +9,7 @@ export * from "./FlashMessages";
9
9
  export * from "./formats";
10
10
  export * from "./http";
11
11
  export * from "./multiFileUpload";
12
- export * from "./performAction";
12
+ export * from "./actions";
13
13
  export * from "./singleFileUpload";
14
14
  export * from "./storage";
15
15
  export * from "./utils";
@@ -1,88 +0,0 @@
1
- <template>
2
- <div>
3
- <Component
4
- v-if="confirmDialog"
5
- :is="confirmDialog.is"
6
- v-bind="confirmDialog.props"
7
- :is-saving="isSaving"
8
- @confirm="onConfirmAction"
9
- @close="$emit('done')"
10
- />
11
- </div>
12
- </template>
13
- <script setup>
14
- import { onMounted, ref, shallowRef } from 'vue';
15
- import { FlashMessages } from '../../../helpers';
16
-
17
- const emit = defineEmits(['done']);
18
- const props = defineProps({
19
- action: {
20
- type: Object,
21
- required: true
22
- },
23
- targets: {
24
- type: Array,
25
- required: true
26
- }
27
- });
28
-
29
- const confirmDialog = shallowRef(props.action.confirmDialog ? props.action.confirmDialog(props.targets) : null);
30
- const isSaving = ref(null);
31
-
32
- onMounted(async () => {
33
- // If there is no dialog, we auto-confirm the action
34
- if (!confirmDialog.value) {
35
- await onConfirmAction();
36
- }
37
- });
38
-
39
- async function onConfirmAction(input) {
40
- if (!props.action.onAction) {
41
- throw new Error('No onAction handler found for the selected action:' + props.action.name);
42
- }
43
-
44
- isSaving.value = true;
45
- let result;
46
- try {
47
- result = await props.action.onAction(props.targets, input);
48
- } catch (e) {
49
- console.error(e);
50
- result = { error: `An error occurred while performing the action ${props.action.label}. Please try again later.` };
51
- }
52
-
53
- isSaving.value = false;
54
-
55
- // If there is no return value or the result marks it as successful, we show a success message
56
- if (result === undefined || result?.success) {
57
-
58
- if (result?.success) {
59
- FlashMessages.success(`The update was successful`);
60
- }
61
-
62
- if (props.action.onSuccess) {
63
- await props.action.onSuccess(result, props.targets, input);
64
- }
65
-
66
- emit('done');
67
- } else {
68
- const errors = [];
69
- if (result.errors) {
70
- errors.push(...result.errors);
71
- } else if (result.error) {
72
- errors.push(typeof result.error === 'string' ? result.error : result.error.message);
73
- } else {
74
- errors.push('An unknown error occurred. Please try again later.');
75
- }
76
-
77
- FlashMessages.combine('error', errors);
78
-
79
- if (props.action.onError) {
80
- await props.action.onError(result, props.targets, input);
81
- }
82
- }
83
-
84
- if (props.action.onFinish) {
85
- await props.action.onFinish(result, props.targets, input);
86
- }
87
- }
88
- </script>
@@ -1,73 +0,0 @@
1
- import { ref } from "vue";
2
- import { waitForRef } from "./index";
3
-
4
- export const activeAction = ref(null);
5
- export const actionTargets = ref([]);
6
-
7
- /**
8
- * Hook to perform an action on a set of targets
9
- * This helper allows you to perform actions by name on a set of targets using a provided list of actions
10
- *
11
- * @param actions
12
- * @returns {{performAction(name, targets): Promise<void>}}
13
- */
14
- export function usePerformAction(actions: any[]) {
15
-
16
- function filterActions(filters) {
17
- let filteredActions = [...actions];
18
-
19
- for (const filter of Object.keys(filters)) {
20
- const filterValue = filters[filter];
21
- filteredActions = filteredActions.filter(a => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
22
- }
23
- return filteredActions;
24
- }
25
-
26
- return {
27
- /**
28
- * Perform an action on a set of targets
29
- *
30
- * @param name - can either be a string or an action object
31
- * @param targets - an array of targets (or a single target object)
32
- * @param options
33
- * @returns {Promise<void>}
34
- */
35
- async performAction(name, targets, options = {}) {
36
- const action = typeof name === "string" ? actions.find(a => a.name === name) : name;
37
- if (!action) {
38
- throw new Error(`Unknown action: ${name}`);
39
- }
40
- targets = Array.isArray(targets) ? targets : [targets];
41
-
42
- await performAction({ ...action, ...options }, targets);
43
- },
44
-
45
- actions,
46
- filterActions,
47
- };
48
- }
49
-
50
- /**
51
- * Perform an action on a set of targets
52
- *
53
- * NOTE: This function and variables should be used w/ the ActionPerformerTool - make sure to use a Layout that has
54
- * rendered this component so the actions will be performed
55
- *
56
- * @param action
57
- * @param targets
58
- * @returns {Promise<void>}
59
- */
60
- export async function performAction(action: any, targets: any[]): Promise<void> {
61
- activeAction.value = action;
62
- actionTargets.value = targets;
63
- await waitForRef(activeAction, null);
64
- }
65
-
66
- /**
67
- * Clear the active action and targets - (note: this will tear down any dialogs / rendered components) triggered by the
68
- * ActionPerformerTool
69
- */
70
- export function clearAction() {
71
- activeAction.value = null;
72
- actionTargets.value = [];
73
- }