quasar-ui-danx 0.0.38 → 0.0.40

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.38",
3
+ "version": "0.0.40",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -2,16 +2,15 @@
2
2
  <PopoverMenu
3
3
  class="px-2 flex action-button"
4
4
  :items="activeActions"
5
- :disabled="targets.length === 0"
6
- :tooltip="targets.length === 0 ? tooltip : null"
7
- :loading="loading || isSaving"
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']);
@@ -20,8 +19,8 @@ const props = defineProps({
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: {
@@ -35,16 +34,10 @@ const props = defineProps({
35
34
  }
36
35
  });
37
36
 
37
+ const hasTarget = computed(() => !!props.target?.length);
38
+
38
39
  const activeActions = computed(() => props.actions.filter(action => {
39
40
  if (action.enabled === undefined) return true;
40
- return typeof action.enabled === 'function' ? !!action.enabled(props.targets?.[0] ?? null, props.targets) : !!action.enabled;
41
+ return typeof action.enabled === 'function' ? !!action.enabled(props.target) : !!action.enabled;
41
42
  }));
42
-
43
- const isSaving = ref(false);
44
- async function onAction(action) {
45
- emit('action', action);
46
- isSaving.value = true;
47
- await performAction(action, props.targets);
48
- isSaving.value = false;
49
- }
50
43
  </script>
@@ -76,14 +76,17 @@
76
76
  <div v-if="rowProps.col.actions" class="flex-grow flex justify-end pl-2">
77
77
  <ActionMenu
78
78
  :actions="rowProps.col.actions"
79
- :targets="[rowProps.row]"
79
+ :target="rowProps.row"
80
80
  :loading="isSavingItem?.id === rowProps.row.id"
81
- @action="(action) => $emit('action', {action: action, row: rowProps.row})"
81
+ @action="(action) => $emit('action', {action, target: rowProps.row})"
82
82
  />
83
83
  </div>
84
84
  </component>
85
85
  </q-td>
86
86
  </template>
87
+ <template #bottom>
88
+ <ActionInputComponent />
89
+ </template>
87
90
  </q-table>
88
91
  </template>
89
92
 
@@ -92,7 +95,7 @@ import { ref } from 'vue';
92
95
  import { getItem, setItem } from '../../helpers';
93
96
  import { DragHandleIcon as RowResizeIcon } from '../../svg';
94
97
  import { HandleDraggable } from '../DragAndDrop';
95
- import { mapSortBy, RenderComponent } from '../index';
98
+ import { ActionInputComponent, mapSortBy, RenderComponent } from '../index';
96
99
  import { ActionMenu, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
97
100
 
98
101
  defineEmits(['action', 'filter', 'update:quasar-pagination', 'update:selected-rows']);
@@ -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 isSavingItem = ref(null);
232
- let actionResultCount = 0;
233
-
234
- async function applyAction(item, input, itemData = {}) {
235
- isSavingItem.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
- isSavingItem.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
- isSavingItem,
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,23 @@
1
+ <template>
2
+ <div>
3
+ <RenderComponent
4
+ v-if="activeActionInput"
5
+ :component="activeActionInput.component"
6
+ :is-saving="isSaving"
7
+ @confirm="onConfirm"
8
+ @close="activeActionInput.cancel"
9
+ />
10
+ </div>
11
+ </template>
12
+ <script setup>
13
+ import { ref } from 'vue';
14
+ import { activeActionInput } from '../../../helpers';
15
+ import RenderComponent from './RenderComponent';
16
+
17
+ const isSaving = ref(false);
18
+ async function onConfirm(input) {
19
+ isSaving.value = true;
20
+ await activeActionInput.value.confirm(input);
21
+ isSaving.value = false;
22
+ }
23
+ </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,180 @@
1
+ import { 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) => Promise<any>;
12
+ onBatchAction?: (action: string | null, targets: object[], input: any) => Promise<any>;
13
+ onSuccess?: (action: string | null, targets: object, input: any) => any;
14
+ onError?: (action: string | null, targets: object, input: any) => any;
15
+ onFinish?: (action: string | null, targets: object, input: any) => any;
16
+ }
17
+
18
+ export const activeActionInput = shallowRef(null);
19
+
20
+ /**
21
+ * Hook to perform an action on a set of targets
22
+ * This helper allows you to perform actions by name on a set of targets using a provided list of actions
23
+ *
24
+ * @param actions
25
+ * @param {ActionOptions} globalOptions
26
+ */
27
+ export function useActions(actions: ActionOptions[], globalOptions: ActionOptions = null) {
28
+ const isSavingTarget = shallowRef(null);
29
+
30
+ /**
31
+ * Resolves an action by name or object, adds globalOptions and overrides any passes options
32
+ *
33
+ * @param name
34
+ * @param {any} options
35
+ * @returns {any}
36
+ */
37
+ function resolveAction(name, options = null) {
38
+ const action = typeof name === "string" ? actions.find(a => a.name === name) : name;
39
+ if (!action) {
40
+ throw new Error(`Unknown action: ${name}`);
41
+ }
42
+
43
+ return { ...globalOptions, ...action, ...options };
44
+ }
45
+
46
+ return {
47
+ actions,
48
+ isSavingTarget,
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
+ * TODO: HOW TO INTEGRATE optimistic updates and single item updates?
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
+ let result = null;
103
+
104
+ isSavingTarget.value = target;
105
+
106
+ // If no component input is required, we can directly perform the action
107
+ if (component) {
108
+ // If the action requires an input, we set the activeActionInput to the input component.
109
+ // This will tell the ActionInputComponent 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
+ activeActionInput.value = {
114
+ component,
115
+ confirm: async () => resolve(await onConfirmAction(action, target, input)),
116
+ cancel: reject
117
+ };
118
+ });
119
+ activeActionInput.value = null;
120
+ } else {
121
+ result = await onConfirmAction(action, target, input);
122
+ }
123
+
124
+ isSavingTarget.value = null;
125
+ return result;
126
+ }
127
+ };
128
+ }
129
+
130
+ async function onConfirmAction(action: ActionOptions, target: object[] | object, input: any = null) {
131
+ if (!action.onAction) {
132
+ throw new Error("No onAction handler found for the selected action:" + action.name);
133
+ }
134
+
135
+ let result: any;
136
+ try {
137
+ if (Array.isArray(target)) {
138
+ result = await action.onBatchAction(action.name, target, input);
139
+ } else {
140
+ result = await action.onAction(action.name, target, input);
141
+ }
142
+ } catch (e) {
143
+ console.error(e);
144
+ result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
145
+ }
146
+
147
+ // If there is no return value or the result marks it as successful, we show a success message
148
+ if (result === undefined || result?.success) {
149
+
150
+ if (result?.success) {
151
+ FlashMessages.success(`The update was successful`);
152
+ }
153
+
154
+ if (action.onSuccess) {
155
+ action.onSuccess(result, target, input);
156
+ }
157
+
158
+ } else {
159
+ const errors = [];
160
+ if (result.errors) {
161
+ errors.push(...result.errors);
162
+ } else if (result.error) {
163
+ errors.push(typeof result.error === "string" ? result.error : result.error.message);
164
+ } else {
165
+ errors.push("An unknown error occurred. Please try again later.");
166
+ }
167
+
168
+ FlashMessages.combine("error", errors);
169
+
170
+ if (action.onError) {
171
+ action.onError(result, target, input);
172
+ }
173
+ }
174
+
175
+ if (action.onFinish) {
176
+ action.onFinish(result, target, input);
177
+ }
178
+
179
+ return result;
180
+ }
@@ -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
- }