quasar-ui-danx 0.0.45 → 0.0.47

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.45",
3
+ "version": "0.0.47",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -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="$emit('action', $event)"
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, mapSortBy } from '../index';
69
+ import { ActionVnode } from '../index';
72
70
  import { ActionTableColumn, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
73
71
 
74
- defineEmits(['action', 'update:quasar-pagination', 'update:selected-rows']);
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
- :model-value="activePanel"
4
- class="overflow-y-auto bg-neutral-plus-7 h-full transition-all"
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 v-if="panel.vnode" :vnode="panel.vnode" />
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 "quasar-ui-danx";
16
+ import { RenderVnode } from '../Utility';
14
17
 
15
18
  defineProps({
16
19
  activePanel: {
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div>
3
+ <div class="text-xs font-bold">{{ label }}</div>
4
+ <div :class="{'mt-2': !dense, 'mt-1': dense, 'text-no-wrap': nowrap}">
5
+ <slot>{{ value || "-" }}</slot>
6
+ </div>
7
+ </div>
8
+ </template>
9
+ <script setup>
10
+ defineProps({
11
+ label: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ value: {
16
+ type: [String, Number],
17
+ default: "-"
18
+ },
19
+ dense: Boolean,
20
+ nowrap: Boolean
21
+ });
22
+ </script>
@@ -2,3 +2,4 @@ export { default as AddressFormat } from "./AddressFormat.vue";
2
2
  export { default as FlatListFormat } from "./FlatListFormat.vue";
3
3
  export { default as GpsCoordinatesFormat } from "./GpsCoordinatesFormat.vue";
4
4
  export { default as IconWithTextFormat } from "./IconWithTextFormat.vue";
5
+ export { default as LabelValueFormat } from "./LabelValueFormat.vue";
@@ -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 isSavingTarget = shallowRef(null);
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
- * Resolves an action by name or object, adds globalOptions and overrides any passes options
83
+ * Perform an action on a set of targets
35
84
  *
36
- * @param name
37
- * @param {any} options
38
- * @returns {any}
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 resolveAction(name, options = null) {
41
- const action = typeof name === "string" ? actions.find(a => a.name === name) : name;
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
- return { ...globalOptions, ...action, ...options };
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
- return {
50
- actions,
51
- isSavingTarget,
52
- resolveAction,
53
-
54
- /**
55
- * Filter the list of actions based on the provided filters in key-value pairs
56
- * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
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
- isSavingTarget.value = null;
134
- return result;
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