quasar-ui-danx 0.4.68 → 0.4.71
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/dist/danx.es.js +3104 -3089
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +44 -44
- package/dist/danx.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/actions.ts +15 -27
- package/src/helpers/request.ts +73 -28
- package/src/helpers/routes.ts +3 -2
- package/src/types/requests.d.ts +12 -2
package/package.json
CHANGED
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
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Ref } from "vue";
|
2
2
|
import { danxOptions } from "../config";
|
3
|
-
import { HttpResponse, RequestApi } from "../types";
|
3
|
+
import { ActiveRequest, HttpResponse, RequestApi } from "../types";
|
4
4
|
import { sleep } from "./utils";
|
5
5
|
|
6
6
|
/**
|
@@ -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,29 @@ 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 useMostRecentResponse = !!options?.useMostRecentResponse;
|
25
|
+
const shouldAbortPrevious = !waitOnPrevious;
|
23
26
|
const timestamp = Date.now();
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
// If there was a request with the same key made that is still active, track that here
|
29
|
+
const previousRequest = request.activeRequests[requestKey];
|
30
|
+
|
31
|
+
// Set the current active request to this one
|
32
|
+
const currentRequest: ActiveRequest = { timestamp };
|
33
|
+
request.activeRequests[requestKey] = currentRequest;
|
34
|
+
|
35
|
+
if (shouldAbortPrevious) {
|
28
36
|
// If there is already an abort controller set for this key, abort it
|
29
|
-
if (
|
30
|
-
|
37
|
+
if (previousRequest) {
|
38
|
+
previousRequest.abortController?.abort("Request was aborted due to a newer request being made");
|
31
39
|
}
|
32
40
|
|
41
|
+
const abortController = new AbortController();
|
33
42
|
// Set the new abort controller for this key
|
34
|
-
|
35
|
-
options.signal =
|
43
|
+
currentRequest.abortController = abortController;
|
44
|
+
options.signal = abortController.signal;
|
36
45
|
}
|
37
46
|
|
38
47
|
if (options.params) {
|
@@ -47,49 +56,85 @@ export const request: RequestApi = {
|
|
47
56
|
delete options.params;
|
48
57
|
}
|
49
58
|
|
50
|
-
let
|
59
|
+
let resolvePromise!: (value: any) => any;
|
60
|
+
let rejectPromise!: (reason?: any) => any;
|
61
|
+
currentRequest.requestPromise = new Promise((resolve, reject) => {
|
62
|
+
resolvePromise = resolve;
|
63
|
+
rejectPromise = reject;
|
64
|
+
});
|
65
|
+
|
66
|
+
// If there is a previous request still active, wait for it to finish before proceeding (if the waitForPrevious flag is set)
|
67
|
+
if (waitOnPrevious && previousRequest?.requestPromise) {
|
68
|
+
try {
|
69
|
+
await previousRequest.requestPromise;
|
70
|
+
} catch (e) {
|
71
|
+
// We don't care if it fails, we just need to wait for it to complete
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
// Wait to finish the request before proceeding
|
76
|
+
let response: Response;
|
51
77
|
try {
|
52
78
|
response = await fetch(request.url(url), options);
|
53
79
|
} catch (e) {
|
54
80
|
if (options.ignoreAbort && (e + "").match(/Request was aborted/)) {
|
55
|
-
|
81
|
+
const abortResponse = { abort: true };
|
82
|
+
resolvePromise(abortResponse);
|
83
|
+
return abortResponse;
|
56
84
|
}
|
85
|
+
rejectPromise(e);
|
57
86
|
throw e;
|
58
87
|
}
|
59
88
|
|
60
89
|
// Verify the app version of the client and server are matching
|
61
90
|
checkAppVersion(response);
|
62
91
|
|
63
|
-
//
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
92
|
+
// Track the most recent request (maybe another request with the same key was made after this request started)
|
93
|
+
let mostRecentRequest = request.activeRequests[requestKey];
|
94
|
+
|
95
|
+
// Always fetch the result
|
96
|
+
let responseJson = await response.json();
|
97
|
+
|
98
|
+
// Send the real JSON response to the promise in case other requests are waiting on this request result
|
99
|
+
resolvePromise(responseJson);
|
100
|
+
|
101
|
+
// If this request is not the most recent request...
|
102
|
+
if (mostRecentRequest.timestamp !== timestamp) {
|
103
|
+
// and it should be aborted but was aborted too late, return an aborted response
|
104
|
+
if (shouldAbortPrevious) {
|
105
|
+
responseJson = { abort: true };
|
106
|
+
} else if (useMostRecentResponse) {
|
107
|
+
// or if there is a more recent request, and the useMoreRecentResponse flag is set, update this response to the more recent one
|
108
|
+
do {
|
109
|
+
// Always update on each iteration to make sure we're checking the current most recent
|
110
|
+
// (maybe additional requests will be made before the current most recent finishes)
|
111
|
+
mostRecentRequest = request.activeRequests[requestKey];
|
112
|
+
responseJson = await mostRecentRequest.requestPromise;
|
113
|
+
|
114
|
+
// If the most recent request is the same as this one, break out of the loop
|
115
|
+
if (request.activeRequests[requestKey].timestamp === mostRecentRequest.timestamp) {
|
116
|
+
break;
|
117
|
+
}
|
118
|
+
} while (mostRecentRequest.timestamp !== request.activeRequests[requestKey].timestamp);
|
69
119
|
}
|
70
|
-
|
71
|
-
// Otherwise, the current is the most recent request, so we can delete the abort controller
|
72
|
-
delete request.abortControllers[abortKey];
|
73
120
|
}
|
74
121
|
|
75
|
-
const result = await response.json();
|
76
|
-
|
77
122
|
if (response.status === 401) {
|
78
123
|
const onUnauthorized = danxOptions.value.request?.onUnauthorized;
|
79
|
-
return onUnauthorized ? onUnauthorized(
|
124
|
+
return onUnauthorized ? onUnauthorized(responseJson, response) : {
|
80
125
|
error: true,
|
81
126
|
message: "Unauthorized",
|
82
|
-
...
|
127
|
+
...responseJson
|
83
128
|
};
|
84
129
|
}
|
85
130
|
|
86
131
|
if (response.status > 400) {
|
87
|
-
if (
|
88
|
-
|
132
|
+
if (responseJson.exception && !responseJson.error) {
|
133
|
+
responseJson.error = true;
|
89
134
|
}
|
90
135
|
}
|
91
136
|
|
92
|
-
return
|
137
|
+
return responseJson;
|
93
138
|
},
|
94
139
|
|
95
140
|
async poll(url: string, options, interval, fnUntil) {
|
package/src/helpers/routes.ts
CHANGED
@@ -56,7 +56,8 @@ 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
|
+
useMostRecentResponse: true,
|
60
61
|
headers: {
|
61
62
|
...options?.headers,
|
62
63
|
"X-Timestamp": Date.now().toString()
|
@@ -86,7 +87,7 @@ export function useActionRoutes(baseUrl: string, extend?: object): ListControlsR
|
|
86
87
|
batchAction(action, targets, data, options?) {
|
87
88
|
options = {
|
88
89
|
...options,
|
89
|
-
|
90
|
+
waitOnPrevious: true
|
90
91
|
};
|
91
92
|
return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data }, options);
|
92
93
|
},
|
package/src/types/requests.d.ts
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
import { AnyObject } from "./shared";
|
2
2
|
|
3
|
+
export interface ActiveRequest {
|
4
|
+
requestPromise?: Promise<any>,
|
5
|
+
abortController?: AbortController,
|
6
|
+
timestamp: number
|
7
|
+
}
|
8
|
+
|
3
9
|
export interface RequestApi {
|
4
|
-
|
10
|
+
activeRequests: {
|
11
|
+
[key: string]: ActiveRequest
|
12
|
+
};
|
5
13
|
|
6
14
|
url(url: string): string;
|
7
15
|
|
@@ -30,7 +38,9 @@ export interface RequestOptions {
|
|
30
38
|
}
|
31
39
|
|
32
40
|
export interface RequestCallOptions extends RequestInit {
|
33
|
-
|
41
|
+
requestKey?: string;
|
42
|
+
waitOnPrevious?: boolean;
|
43
|
+
useMostRecentResponse?: boolean;
|
34
44
|
ignoreAbort?: boolean;
|
35
45
|
params?: AnyObject;
|
36
46
|
}
|