quasar-ui-danx 0.4.61 → 0.4.69

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.61",
3
+ "version": "0.4.69",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -175,6 +175,7 @@ const selectedOptions = computed(() => {
175
175
  if (typeof v === "object") return v.value || v.id;
176
176
  return v;
177
177
  });
178
+
178
179
  return computedOptions.value.filter((o) => {
179
180
  return comparableValues.includes(o.value);
180
181
  });
@@ -249,7 +250,7 @@ function resolveValue(option) {
249
250
  if (!option || typeof option === "string") {
250
251
  return option;
251
252
  }
252
- let value = option.value || option.id;
253
+ let value = option.value !== undefined ? option.value : option.id;
253
254
  if (typeof props.optionValue === "string") {
254
255
  value = option[props.optionValue];
255
256
  } else if (typeof props.optionValue === "function") {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="group flex items-center flex-nowrap gap-x-1 relative">
2
+ <div class="group flex items-center flex-nowrap gap-x-2 relative">
3
3
  <ShowHideButton
4
4
  v-if="selectable"
5
5
  v-model="isSelecting"
@@ -8,7 +8,6 @@
8
8
  :saving="loading"
9
9
  :class="selectClass"
10
10
  :show-icon="selectIcon || DefaultSelectIcon"
11
- class="mr-2"
12
11
  :size="size"
13
12
  >
14
13
  <QMenu
@@ -19,14 +18,14 @@
19
18
  <div>
20
19
  <div
21
20
  v-for="option in optionsPlusSelected"
22
- :key="option.id"
21
+ :key="getId(option)"
23
22
  v-ripple
24
23
  class="cursor-pointer flex items-center relative"
25
- :class="{'bg-sky-900 hover:bg-sky-800': selected?.id === option.id, 'hover:bg-slate-600': selected?.id !== option.id}"
26
- @click="selected = option"
24
+ :class="{'bg-sky-900 hover:bg-sky-800': getId(selected) === getId(option), 'hover:bg-slate-600': getId(selected) !== getId(option)}"
25
+ @click="selected = selectionType === 'object' ? option : getId(option)"
27
26
  >
28
27
  <div class="flex-grow px-4 py-2">
29
- {{ option.name }}
28
+ {{ getName(option) }}
30
29
  </div>
31
30
  <ActionButton
32
31
  v-if="deletable"
@@ -58,12 +57,12 @@
58
57
  <template v-if="selected">
59
58
  <EditableDiv
60
59
  v-if="nameEditable"
61
- :model-value="selected.name"
60
+ :model-value="getName(selected)"
62
61
  color="slate-600"
63
62
  @update:model-value="name => $emit('update', {name})"
64
63
  />
65
64
  <template v-else>
66
- {{ selected.name }}
65
+ {{ getName(selected) }}
67
66
  </template>
68
67
  </template>
69
68
  <template v-else>
@@ -104,16 +103,17 @@ import {
104
103
  FaSolidPencil as EditIcon
105
104
  } from "danx-icon";
106
105
  import { computed, ref } from "vue";
107
- import { ActionTargetItem } from "../../../../types";
106
+ import { ActionTargetItem, LabelValueItem } from "../../../../types";
108
107
  import { ShowHideButton } from "../../../Utility/Buttons";
109
108
  import { ActionButtonProps, default as ActionButton } from "../../../Utility/Buttons/ActionButton";
110
109
  import EditableDiv from "./EditableDiv";
111
110
 
112
111
  defineEmits(["create", "update", "delete"]);
113
- const selected = defineModel<ActionTargetItem | null>("selected");
112
+ const selected = defineModel<ActionTargetItem | string | null>("selected");
114
113
  const editing = defineModel<boolean>("editing");
115
114
  const props = withDefaults(defineProps<{
116
- options: ActionTargetItem[];
115
+ options: ActionTargetItem[] | LabelValueItem[];
116
+ selectionType?: "object" | "string";
117
117
  showEdit?: boolean;
118
118
  loading?: boolean;
119
119
  selectText?: string;
@@ -137,6 +137,7 @@ const props = withDefaults(defineProps<{
137
137
  editDisabled?: boolean;
138
138
  size?: ActionButtonProps["size"];
139
139
  }>(), {
140
+ selectionType: "object",
140
141
  selectText: "",
141
142
  createText: "",
142
143
  editText: "",
@@ -154,8 +155,32 @@ const props = withDefaults(defineProps<{
154
155
  const isSelecting = ref(false);
155
156
 
156
157
  // If the selected option is not in the options list, it should be added in
157
- const optionsPlusSelected = computed(() => {
158
- if (!selected.value || props.options.find((o) => o.id === selected.value?.id)) return props.options;
158
+ const optionsPlusSelected = computed<ActionTargetItem[]>(() => {
159
+ if (!selected.value || props.options.find((o) => getId(o) === getId(selected.value))) return props.options;
159
160
  return [selected.value, ...props.options];
160
161
  });
162
+
163
+ function resolveOption(option) {
164
+ if (typeof option === "object") {
165
+ return option;
166
+ }
167
+
168
+ return props.options.find((o) => {
169
+ if (typeof o === "object") {
170
+ return getId(o) === option;
171
+ }
172
+ return o === option;
173
+ });
174
+ }
175
+
176
+ function getId(option) {
177
+ option = resolveOption(option);
178
+
179
+ return option?.id || option?.value || option?.name || option;
180
+ }
181
+
182
+ function getName(option) {
183
+ option = resolveOption(option);
184
+ return option?.name || option?.label || option?.value || option;
185
+ }
161
186
  </script>
@@ -62,6 +62,7 @@ import {
62
62
  FaSolidCopy as CopyIcon,
63
63
  FaSolidFileExport as ExportIcon,
64
64
  FaSolidFileImport as ImportIcon,
65
+ FaSolidFolder as FolderIcon,
65
66
  FaSolidMinus as MinusIcon,
66
67
  FaSolidPause as PauseIcon,
67
68
  FaSolidPencil as EditIcon,
@@ -74,7 +75,7 @@ import { computed, ref } from "vue";
74
75
  import { ActionTarget, ResourceAction } from "../../../types";
75
76
 
76
77
  export interface ActionButtonProps {
77
- type?: "trash" | "create" | "edit" | "copy" | "play" | "stop" | "pause" | "refresh" | "confirm" | "cancel" | "export" | "import" | "minus";
78
+ type?: "trash" | "create" | "edit" | "copy" | "folder" | "play" | "stop" | "pause" | "refresh" | "confirm" | "cancel" | "export" | "import" | "minus";
78
79
  color?: "red" | "blue" | "sky" | "sky-invert" | "green" | "green-invert" | "lime" | "white" | "gray" | "yellow" | "orange";
79
80
  size?: "xxs" | "xs" | "sm" | "md" | "lg";
80
81
  icon?: object | string;
@@ -190,6 +191,8 @@ const typeOptions = computed(() => {
190
191
  return { icon: EditIcon };
191
192
  case "copy":
192
193
  return { icon: CopyIcon };
194
+ case "folder":
195
+ return { icon: FolderIcon };
193
196
  case "play":
194
197
  return { icon: PlayIcon };
195
198
  case "stop":
@@ -1,7 +1,7 @@
1
1
  import { useDebounceFn } from "@vueuse/core";
2
2
  import { FaSolidCopy as CopyIcon, FaSolidPencil as EditIcon, FaSolidTrash as DeleteIcon } from "danx-icon";
3
3
  import { uid } from "quasar";
4
- import { h, isReactive, Ref, shallowReactive, shallowRef } from "vue";
4
+ import { h, Ref, shallowRef } from "vue";
5
5
  import { ConfirmActionDialog, CreateNewWithNameDialog } from "../components";
6
6
  import type {
7
7
  ActionGlobalOptions,
@@ -55,39 +55,27 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionGlobal
55
55
  */
56
56
  function getAction(actionName: string, actionOptions?: Partial<ActionOptions>): ResourceAction {
57
57
  /// Resolve the action options or resource action based on the provided input
58
- let resourceAction: Partial<ResourceAction> = actions.find(a => a.name === actionName) || { name: actionName };
59
-
60
- if (actionOptions) {
61
- Object.assign(resourceAction, actionOptions);
62
- }
58
+ const baseOptions: Partial<ResourceAction> = actions.find(a => a.name === actionName) || { name: actionName };
63
59
 
64
60
  // If the action is already reactive, return it
65
- if (!isReactive(resourceAction) || !("__type" in resourceAction)) {
66
- resourceAction = storeObject({
67
- onAction: globalOptions?.routes?.applyAction,
68
- onBatchAction: globalOptions?.routes?.batchAction,
69
- onBatchSuccess: globalOptions?.controls?.clearSelectedRows,
70
- ...globalOptions,
71
- ...resourceAction,
72
- isApplying: false,
73
- __type: "__Action:" + namespace
74
- });
75
-
76
- // Splice the resourceAction in place of the action in the actions list
77
- actions.splice(actions.findIndex(a => a.name === actionName), 1, resourceAction as ResourceAction);
78
- }
79
-
80
- // Return a clone of the action so it can be modified without affecting the original
81
- const clonedAction = shallowReactive({ ...resourceAction }) as ResourceAction;
61
+ const resourceAction = storeObject({
62
+ onAction: globalOptions?.routes?.applyAction,
63
+ onBatchAction: globalOptions?.routes?.batchAction,
64
+ onBatchSuccess: globalOptions?.controls?.clearSelectedRows,
65
+ ...baseOptions,
66
+ ...actionOptions,
67
+ isApplying: false,
68
+ __type: "__Action:" + namespace
69
+ }) as ResourceAction;
82
70
 
83
71
  // Assign Trigger function if it doesn't exist
84
- if (clonedAction.debounce) {
85
- clonedAction.trigger = useDebounceFn((target, input) => performAction(clonedAction, target, input), clonedAction.debounce);
72
+ if (resourceAction.debounce) {
73
+ resourceAction.trigger = useDebounceFn((target, input) => performAction(resourceAction, target, input), resourceAction.debounce);
86
74
  } else {
87
- clonedAction.trigger = (target, input) => performAction(clonedAction, target, input);
75
+ resourceAction.trigger = (target, input) => performAction(resourceAction, target, input);
88
76
  }
89
77
 
90
- return clonedAction;
78
+ return resourceAction;
91
79
  }
92
80
 
93
81
  /**
@@ -8,7 +8,7 @@ import { sleep } from "./utils";
8
8
  * to make GET and POST requests easier w/ JSON payloads
9
9
  */
10
10
  export const request: RequestApi = {
11
- abortControllers: {},
11
+ activeRequests: {},
12
12
 
13
13
  url(url) {
14
14
  if (url.startsWith("http")) {
@@ -19,20 +19,27 @@ export const request: RequestApi = {
19
19
 
20
20
  async call(url, options) {
21
21
  options = options || {};
22
- const abortKey = options?.abortOn !== undefined ? options.abortOn : url + JSON.stringify(options.params || "");
22
+ const requestKey = options?.requestKey || url + JSON.stringify(options.params || "");
23
+ const waitOnPrevious = !!options?.waitOnPrevious;
24
+ const shouldAbort = !waitOnPrevious;
23
25
  const timestamp = Date.now();
24
26
 
25
- if (abortKey) {
26
- const abort = new AbortController();
27
- const previousAbort = request.abortControllers[abortKey];
27
+ // If there was a request with the same key made that is still active, track that here
28
+ const previousRequest = request.activeRequests[requestKey];
29
+
30
+ // Set the current active request to this one
31
+ request.activeRequests[requestKey] = { timestamp };
32
+
33
+ if (shouldAbort) {
28
34
  // If there is already an abort controller set for this key, abort it
29
- if (previousAbort) {
30
- previousAbort.abort.abort("Request was aborted due to a newer request being made");
35
+ if (previousRequest) {
36
+ previousRequest.abortController?.abort("Request was aborted due to a newer request being made");
31
37
  }
32
38
 
39
+ const abortController = new AbortController();
33
40
  // Set the new abort controller for this key
34
- request.abortControllers[abortKey] = { abort, timestamp };
35
- options.signal = abort.signal;
41
+ request.activeRequests[requestKey].abortController = abortController;
42
+ options.signal = abortController.signal;
36
43
  }
37
44
 
38
45
  if (options.params) {
@@ -49,7 +56,14 @@ export const request: RequestApi = {
49
56
 
50
57
  let response = null;
51
58
  try {
52
- response = await fetch(request.url(url), options);
59
+ // If there is a previous request still active, wait for it to finish before proceeding (if the waitForPrevious flag is set)
60
+ if (waitOnPrevious && previousRequest?.requestPromise) {
61
+ await previousRequest.requestPromise;
62
+ }
63
+
64
+ const requestPromise = fetch(request.url(url), options);
65
+ request.activeRequests[requestKey].requestPromise = requestPromise;
66
+ response = await requestPromise;
53
67
  } catch (e) {
54
68
  if (options.ignoreAbort && (e + "").match(/Request was aborted/)) {
55
69
  return { abort: true };
@@ -61,15 +75,18 @@ export const request: RequestApi = {
61
75
  checkAppVersion(response);
62
76
 
63
77
  // handle the case where the request was aborted too late, and we need to abort the response via timestamp check
64
- if (abortKey) {
78
+ if (shouldAbort) {
65
79
  // If the request was aborted too late, but there was still another request that was made after the current,
66
80
  // then abort the current request with an abort flag
67
- if (timestamp < request.abortControllers[abortKey].timestamp) {
81
+ if (timestamp < request.activeRequests[requestKey].timestamp) {
68
82
  return { abort: true };
69
83
  }
84
+ }
70
85
 
71
- // Otherwise, the current is the most recent request, so we can delete the abort controller
72
- delete request.abortControllers[abortKey];
86
+ // If this request is the active request for the requestKey, we can clear this key from active requests
87
+ if (request.activeRequests[requestKey].timestamp === timestamp) {
88
+ // Remove the request from the active requests list
89
+ delete request.activeRequests[requestKey];
73
90
  }
74
91
 
75
92
  const result = await response.json();
@@ -94,9 +111,12 @@ export const request: RequestApi = {
94
111
 
95
112
  async poll(url: string, options, interval, fnUntil) {
96
113
  let response;
114
+ if (!fnUntil) {
115
+ fnUntil = (response: HttpResponse) => !!response;
116
+ }
97
117
  do {
98
118
  response = await request.call(url, options);
99
- await sleep(interval);
119
+ await sleep(interval || 1000);
100
120
  } while (!fnUntil(response));
101
121
 
102
122
  return response;
@@ -56,11 +56,10 @@ export function useActionRoutes(baseUrl: string, extend?: object): ListControlsR
56
56
  async applyAction(action, target, data, options?) {
57
57
  options = {
58
58
  ...options,
59
- ignoreAbort: true,
59
+ waitOnPrevious: true,
60
60
  headers: {
61
61
  ...options?.headers,
62
- // @ts-expect-error This header is fine
63
- "X-Timestamp": Date.now()
62
+ "X-Timestamp": Date.now().toString()
64
63
  }
65
64
  };
66
65
  data = data || {};
@@ -87,7 +86,7 @@ export function useActionRoutes(baseUrl: string, extend?: object): ListControlsR
87
86
  batchAction(action, targets, data, options?) {
88
87
  options = {
89
88
  ...options,
90
- ignoreAbort: true
89
+ waitOnPrevious: true
91
90
  };
92
91
  return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data }, options);
93
92
  },
@@ -1,8 +1,8 @@
1
- import { FilterGroup, ListController, ListControlsRoutes } from "src/types/controls";
2
- import { FormField } from "src/types/forms";
3
- import { TableColumn } from "src/types/tables";
4
1
  import { ComputedRef, Ref, ShallowRef, VNode } from "vue";
2
+ import { FilterGroup, ListController, ListControlsRoutes } from "./controls";
3
+ import { FormField } from "./forms";
5
4
  import { AnyObject, TypedObject } from "./shared";
5
+ import { TableColumn } from "./tables";
6
6
 
7
7
  export interface ActionTargetItem extends TypedObject {
8
8
  isSaving?: boolean;
@@ -1,5 +1,5 @@
1
- import { RequestCallOptions } from "src/types/requests";
2
1
  import { ComputedRef, Ref, ShallowRef } from "vue";
2
+ import { RequestCallOptions } from "../types";
3
3
  import { ActionOptions, ActionTargetItem, ResourceAction } from "./actions";
4
4
  import { AnyObject, LabelValueItem } from "./shared";
5
5
 
@@ -1,7 +1,9 @@
1
1
  import { AnyObject } from "./shared";
2
2
 
3
3
  export interface RequestApi {
4
- abortControllers: { [key: string]: { abort: AbortController, timestamp: number } };
4
+ activeRequests: {
5
+ [key: string]: { requestPromise?: Promise<any>, abortController?: AbortController, timestamp: number }
6
+ };
5
7
 
6
8
  url(url: string): string;
7
9
 
@@ -11,7 +13,7 @@ export interface RequestApi {
11
13
 
12
14
  post(url: string, data?: object, options?: RequestCallOptions): Promise<any>;
13
15
 
14
- poll(url: string, options?: RequestCallOptions, interval: number, fnUntil: (response) => boolean): Promise<any>;
16
+ poll(url: string, options?: RequestCallOptions, interval?: number, fnUntil?: (response) => boolean): Promise<any>;
15
17
  }
16
18
 
17
19
  export interface HttpResponse {
@@ -30,7 +32,8 @@ export interface RequestOptions {
30
32
  }
31
33
 
32
34
  export interface RequestCallOptions extends RequestInit {
33
- abortOn?: string;
35
+ requestKey?: string;
36
+ waitOnPrevious?: boolean;
34
37
  ignoreAbort?: boolean;
35
38
  params?: AnyObject;
36
39
  }
package/tsconfig.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "./tsconfig.build.json",
3
- "compilerOptions": {
4
- "paths": {
5
- "vue": [
6
- "../../gpt-manager/spa/node_modules/vue"
7
- ]
8
- }
9
- }
10
- }
11
-