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/.eslintrc.cjs +1 -1
- package/dist/danx.es.js +4991 -4997
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +77 -77
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/Form/Fields/SelectField.vue +2 -1
- package/src/components/ActionTable/Form/Fields/SelectionMenuField.vue +38 -13
- package/src/components/Utility/Buttons/ActionButton.vue +4 -1
- package/src/helpers/actions.ts +15 -27
- package/src/helpers/request.ts +35 -15
- package/src/helpers/routes.ts +3 -4
- package/src/types/actions.d.ts +3 -3
- package/src/types/controls.d.ts +1 -1
- package/src/types/requests.d.ts +6 -3
- package/tsconfig.json +0 -11
package/package.json
CHANGED
@@ -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
|
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-
|
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
|
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
|
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
|
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
|
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
|
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
|
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":
|
package/src/helpers/actions.ts
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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 (
|
85
|
-
|
72
|
+
if (resourceAction.debounce) {
|
73
|
+
resourceAction.trigger = useDebounceFn((target, input) => performAction(resourceAction, target, input), resourceAction.debounce);
|
86
74
|
} else {
|
87
|
-
|
75
|
+
resourceAction.trigger = (target, input) => performAction(resourceAction, target, input);
|
88
76
|
}
|
89
77
|
|
90
|
-
return
|
78
|
+
return resourceAction;
|
91
79
|
}
|
92
80
|
|
93
81
|
/**
|
package/src/helpers/request.ts
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
26
|
-
|
27
|
-
|
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 (
|
30
|
-
|
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.
|
35
|
-
options.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
|
-
|
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 (
|
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.
|
81
|
+
if (timestamp < request.activeRequests[requestKey].timestamp) {
|
68
82
|
return { abort: true };
|
69
83
|
}
|
84
|
+
}
|
70
85
|
|
71
|
-
|
72
|
-
|
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;
|
package/src/helpers/routes.ts
CHANGED
@@ -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
|
-
|
59
|
+
waitOnPrevious: true,
|
60
60
|
headers: {
|
61
61
|
...options?.headers,
|
62
|
-
|
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
|
-
|
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
|
},
|
package/src/types/actions.d.ts
CHANGED
@@ -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;
|
package/src/types/controls.d.ts
CHANGED
@@ -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
|
|
package/src/types/requests.d.ts
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import { AnyObject } from "./shared";
|
2
2
|
|
3
3
|
export interface RequestApi {
|
4
|
-
|
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
|
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
|
-
|
35
|
+
requestKey?: string;
|
36
|
+
waitOnPrevious?: boolean;
|
34
37
|
ignoreAbort?: boolean;
|
35
38
|
params?: AnyObject;
|
36
39
|
}
|