@etsoo/shared 1.2.52 → 1.2.54
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/.github/workflows/main.yml +6 -5
- package/lib/cjs/ActionResult.d.ts +1 -1
- package/lib/cjs/ActionResult.js +3 -3
- package/lib/cjs/ArrayUtils.d.ts +1 -1
- package/lib/cjs/ArrayUtils.js +4 -4
- package/lib/cjs/ColorUtils.d.ts +1 -1
- package/lib/cjs/ColorUtils.js +2 -2
- package/lib/cjs/DataTypes.d.ts +6 -6
- package/lib/cjs/DataTypes.js +50 -51
- package/lib/cjs/DateUtils.d.ts +1 -1
- package/lib/cjs/DateUtils.js +27 -28
- package/lib/cjs/DomUtils.d.ts +3 -3
- package/lib/cjs/DomUtils.js +64 -73
- package/lib/cjs/ExtendUtils.d.ts +1 -1
- package/lib/cjs/ExtendUtils.js +6 -6
- package/lib/cjs/IActionResult.d.ts +1 -1
- package/lib/cjs/NumberUtils.d.ts +1 -1
- package/lib/cjs/NumberUtils.js +9 -9
- package/lib/cjs/StorageUtils.js +2 -2
- package/lib/cjs/Utils.d.ts +4 -4
- package/lib/cjs/Utils.js +58 -62
- package/lib/cjs/index.d.ts +22 -22
- package/lib/cjs/storage/WindowStorage.d.ts +1 -1
- package/lib/cjs/types/ContentDisposition.d.ts +2 -2
- package/lib/cjs/types/ContentDisposition.js +11 -13
- package/lib/cjs/types/EColor.js +5 -7
- package/lib/cjs/types/EHistory.d.ts +3 -3
- package/lib/cjs/types/EHistory.js +4 -4
- package/lib/cjs/types/ErrorData.d.ts +1 -1
- package/lib/cjs/types/EventClass.js +1 -1
- package/lib/mjs/ActionResult.d.ts +1 -1
- package/lib/mjs/ActionResult.js +3 -3
- package/lib/mjs/ArrayUtils.d.ts +1 -1
- package/lib/mjs/ArrayUtils.js +5 -5
- package/lib/mjs/ColorUtils.d.ts +1 -1
- package/lib/mjs/ColorUtils.js +3 -3
- package/lib/mjs/DataTypes.d.ts +6 -6
- package/lib/mjs/DataTypes.js +50 -51
- package/lib/mjs/DateUtils.d.ts +1 -1
- package/lib/mjs/DateUtils.js +27 -28
- package/lib/mjs/DomUtils.d.ts +3 -3
- package/lib/mjs/DomUtils.js +67 -76
- package/lib/mjs/ExtendUtils.d.ts +1 -1
- package/lib/mjs/ExtendUtils.js +6 -6
- package/lib/mjs/IActionResult.d.ts +1 -1
- package/lib/mjs/NumberUtils.d.ts +1 -1
- package/lib/mjs/NumberUtils.js +9 -9
- package/lib/mjs/StorageUtils.js +4 -4
- package/lib/mjs/Utils.d.ts +4 -4
- package/lib/mjs/Utils.js +61 -65
- package/lib/mjs/index.d.ts +22 -22
- package/lib/mjs/index.js +22 -22
- package/lib/mjs/storage/WindowStorage.d.ts +1 -1
- package/lib/mjs/storage/WindowStorage.js +2 -2
- package/lib/mjs/types/ContentDisposition.d.ts +2 -2
- package/lib/mjs/types/ContentDisposition.js +12 -14
- package/lib/mjs/types/EColor.js +5 -7
- package/lib/mjs/types/EHistory.d.ts +3 -3
- package/lib/mjs/types/EHistory.js +5 -5
- package/lib/mjs/types/ErrorData.d.ts +1 -1
- package/lib/mjs/types/EventClass.js +1 -1
- package/package.json +61 -63
- package/src/ActionResult.ts +23 -23
- package/src/ArrayUtils.ts +164 -172
- package/src/ColorUtils.ts +80 -82
- package/src/DataTypes.ts +745 -754
- package/src/DateUtils.ts +266 -268
- package/src/DomUtils.ts +806 -831
- package/src/ExtendUtils.ts +191 -191
- package/src/IActionResult.ts +42 -42
- package/src/Keyboard.ts +258 -258
- package/src/NumberUtils.ts +135 -135
- package/src/StorageUtils.ts +117 -117
- package/src/Utils.ts +908 -930
- package/src/index.ts +22 -22
- package/src/node/Storage.ts +53 -53
- package/src/storage/IStorage.ts +62 -62
- package/src/storage/WindowStorage.ts +140 -140
- package/src/types/ContentDisposition.ts +59 -63
- package/src/types/DataError.ts +15 -15
- package/src/types/DelayedExecutorType.ts +15 -15
- package/src/types/EColor.ts +241 -248
- package/src/types/EHistory.ts +151 -151
- package/src/types/ErrorData.ts +11 -11
- package/src/types/EventClass.ts +220 -220
- package/src/types/FormData.ts +25 -25
- package/src/types/ParsedPath.ts +5 -5
- package/tsconfig.cjs.json +16 -16
- package/tsconfig.json +16 -16
- package/.eslintignore +0 -3
- package/.eslintrc.json +0 -29
- package/.prettierignore +0 -5
- package/.prettierrc +0 -6
package/src/DomUtils.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
import { DataTypes } from
|
|
3
|
-
import { DateUtils } from
|
|
4
|
-
import { Utils } from
|
|
5
|
-
import { ErrorData, ErrorType } from
|
|
6
|
-
import { FormDataFieldValue, IFormData } from
|
|
7
|
-
|
|
8
|
-
if (typeof navigator ===
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { DataTypes } from "./DataTypes";
|
|
3
|
+
import { DateUtils } from "./DateUtils";
|
|
4
|
+
import { Utils } from "./Utils";
|
|
5
|
+
import { ErrorData, ErrorType } from "./types/ErrorData";
|
|
6
|
+
import { FormDataFieldValue, IFormData } from "./types/FormData";
|
|
7
|
+
|
|
8
|
+
if (typeof navigator === "undefined") {
|
|
9
|
+
// Test mock only
|
|
10
|
+
globalThis.navigator = { language: "en-US" } as any;
|
|
11
|
+
globalThis.location = { href: "http://localhost/" } as any;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -16,33 +16,33 @@ if (typeof navigator === 'undefined') {
|
|
|
16
16
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
17
17
|
*/
|
|
18
18
|
export type UserAgentData = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Browser brands
|
|
21
|
+
*/
|
|
22
|
+
brands: {
|
|
23
|
+
brand: string;
|
|
24
|
+
version: string;
|
|
25
|
+
}[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Is mobile device
|
|
29
|
+
*/
|
|
30
|
+
mobile: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Device brand (name)
|
|
34
|
+
*/
|
|
35
|
+
device: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Platform (OS)
|
|
39
|
+
*/
|
|
40
|
+
platform: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Platform version
|
|
44
|
+
*/
|
|
45
|
+
platformVersion?: string;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -50,867 +50,842 @@ export type UserAgentData = {
|
|
|
50
50
|
* Not all methods support Node
|
|
51
51
|
*/
|
|
52
52
|
export namespace DomUtils {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Language cache parameter name
|
|
55
|
+
*/
|
|
56
|
+
export const CultureField = "culture";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Country cache parameter name
|
|
60
|
+
*/
|
|
61
|
+
export const CountryField = "country";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clear form data
|
|
65
|
+
* @param data Form data
|
|
66
|
+
* @param source Source data to match
|
|
67
|
+
* @param keepFields Fields need to be kept
|
|
68
|
+
*/
|
|
69
|
+
export function clearFormData(
|
|
70
|
+
data: IFormData,
|
|
71
|
+
source?: object,
|
|
72
|
+
keepFields?: string[]
|
|
73
|
+
) {
|
|
74
|
+
// Unique keys, FormData may have same name keys
|
|
75
|
+
const keys = new Set(data.keys());
|
|
76
|
+
|
|
77
|
+
// Remove empty key
|
|
78
|
+
const removeEmpty = (key: string) => {
|
|
79
|
+
// Need to be kept
|
|
80
|
+
if (keepFields != null && keepFields.includes(key)) return;
|
|
81
|
+
|
|
82
|
+
// Get all values
|
|
83
|
+
const formValues = data.getAll(key);
|
|
84
|
+
if (formValues.length == 1 && formValues[0] === "") {
|
|
85
|
+
// Remove empty field
|
|
86
|
+
data.delete(key);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
if (source == null) {
|
|
91
|
+
// Remove all empty strings
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
removeEmpty(key);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
const sourceKeys = Object.keys(source);
|
|
97
|
+
for (const key of sourceKeys) {
|
|
98
|
+
// Need to be kept
|
|
99
|
+
if (keepFields != null && keepFields.includes(key)) continue;
|
|
100
|
+
|
|
101
|
+
// Get all values
|
|
102
|
+
const formValues = data.getAll(key);
|
|
103
|
+
if (formValues.length > 0) {
|
|
104
|
+
// Matched
|
|
105
|
+
// Source value
|
|
106
|
+
const sourceValue = Reflect.get(source, key);
|
|
107
|
+
|
|
108
|
+
if (Array.isArray(sourceValue)) {
|
|
109
|
+
// Array, types may differ
|
|
110
|
+
if (formValues.join("`") === sourceValue.join("`")) {
|
|
111
|
+
// Equal value, remove the key
|
|
112
|
+
data.delete(key);
|
|
94
113
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Get all values
|
|
102
|
-
const formValues = data.getAll(key);
|
|
103
|
-
if (formValues.length > 0) {
|
|
104
|
-
// Matched
|
|
105
|
-
// Source value
|
|
106
|
-
const sourceValue = Reflect.get(source, key);
|
|
107
|
-
|
|
108
|
-
if (Array.isArray(sourceValue)) {
|
|
109
|
-
// Array, types may differ
|
|
110
|
-
if (formValues.join('`') === sourceValue.join('`')) {
|
|
111
|
-
// Equal value, remove the key
|
|
112
|
-
data.delete(key);
|
|
113
|
-
}
|
|
114
|
-
} else if (formValues.length == 1) {
|
|
115
|
-
// Other
|
|
116
|
-
if (formValues[0].toString() === `${sourceValue}`) {
|
|
117
|
-
// Equal value, remove the key
|
|
118
|
-
data.delete(key);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Left fields
|
|
125
|
-
for (const key of keys) {
|
|
126
|
-
// Already cleared
|
|
127
|
-
if (sourceKeys.includes(key)) continue;
|
|
128
|
-
|
|
129
|
-
// Remove empties
|
|
130
|
-
removeEmpty(key);
|
|
114
|
+
} else if (formValues.length == 1) {
|
|
115
|
+
// Other
|
|
116
|
+
if (formValues[0].toString() === `${sourceValue}`) {
|
|
117
|
+
// Equal value, remove the key
|
|
118
|
+
data.delete(key);
|
|
131
119
|
}
|
|
120
|
+
}
|
|
132
121
|
}
|
|
122
|
+
}
|
|
133
123
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
function dataAsTraveller(
|
|
139
|
-
source: IFormData | object,
|
|
140
|
-
data: object,
|
|
141
|
-
template: object,
|
|
142
|
-
keepSource: boolean,
|
|
143
|
-
isValue: boolean
|
|
144
|
-
) {
|
|
145
|
-
// Properties
|
|
146
|
-
const properties = Object.keys(template);
|
|
147
|
-
|
|
148
|
-
// Entries
|
|
149
|
-
const entries = Object.entries(
|
|
150
|
-
isFormData(source) ? formDataToObject(source) : source
|
|
151
|
-
);
|
|
124
|
+
// Left fields
|
|
125
|
+
for (const key of keys) {
|
|
126
|
+
// Already cleared
|
|
127
|
+
if (sourceKeys.includes(key)) continue;
|
|
152
128
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
properties.find(
|
|
157
|
-
(p) =>
|
|
158
|
-
p.localeCompare(key, 'en', { sensitivity: 'base' }) ===
|
|
159
|
-
0
|
|
160
|
-
) ?? (keepSource ? key : undefined);
|
|
161
|
-
if (property == null) continue;
|
|
162
|
-
|
|
163
|
-
// Template value
|
|
164
|
-
const templateValue = Reflect.get(template, property);
|
|
165
|
-
|
|
166
|
-
// Formatted value
|
|
167
|
-
let propertyValue: any;
|
|
168
|
-
|
|
169
|
-
if (templateValue == null) {
|
|
170
|
-
// Just read the source value
|
|
171
|
-
propertyValue = value;
|
|
172
|
-
} else {
|
|
173
|
-
if (isValue) {
|
|
174
|
-
// With template value
|
|
175
|
-
propertyValue = DataTypes.convert(value, templateValue);
|
|
176
|
-
} else {
|
|
177
|
-
// With template type
|
|
178
|
-
propertyValue = DataTypes.convertByType(
|
|
179
|
-
value,
|
|
180
|
-
templateValue
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Set value
|
|
186
|
-
// Object.assign(data, { [property]: propertyValue });
|
|
187
|
-
// Object.defineProperty(data, property, { value: propertyValue });
|
|
188
|
-
Reflect.set(data, property, propertyValue);
|
|
189
|
-
}
|
|
129
|
+
// Remove empties
|
|
130
|
+
removeEmpty(key);
|
|
131
|
+
}
|
|
190
132
|
}
|
|
191
133
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
134
|
+
// Return
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function dataAsTraveller(
|
|
139
|
+
source: IFormData | object,
|
|
140
|
+
data: object,
|
|
141
|
+
template: object,
|
|
142
|
+
keepSource: boolean,
|
|
143
|
+
isValue: boolean
|
|
144
|
+
) {
|
|
145
|
+
// Properties
|
|
146
|
+
const properties = Object.keys(template);
|
|
147
|
+
|
|
148
|
+
// Entries
|
|
149
|
+
const entries = Object.entries(
|
|
150
|
+
isFormData(source) ? formDataToObject(source) : source
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
for (const [key, value] of entries) {
|
|
154
|
+
// Is included or keepSource
|
|
155
|
+
const property =
|
|
156
|
+
properties.find(
|
|
157
|
+
(p) => p.localeCompare(key, "en", { sensitivity: "base" }) === 0
|
|
158
|
+
) ?? (keepSource ? key : undefined);
|
|
159
|
+
if (property == null) continue;
|
|
160
|
+
|
|
161
|
+
// Template value
|
|
162
|
+
const templateValue = Reflect.get(template, property);
|
|
163
|
+
|
|
164
|
+
// Formatted value
|
|
165
|
+
let propertyValue: any;
|
|
166
|
+
|
|
167
|
+
if (templateValue == null) {
|
|
168
|
+
// Just read the source value
|
|
169
|
+
propertyValue = value;
|
|
170
|
+
} else {
|
|
171
|
+
if (isValue) {
|
|
172
|
+
// With template value
|
|
173
|
+
propertyValue = DataTypes.convert(value, templateValue);
|
|
174
|
+
} else {
|
|
175
|
+
// With template type
|
|
176
|
+
propertyValue = DataTypes.convertByType(value, templateValue);
|
|
211
177
|
}
|
|
178
|
+
}
|
|
212
179
|
|
|
213
|
-
|
|
214
|
-
|
|
180
|
+
// Set value
|
|
181
|
+
// Object.assign(data, { [property]: propertyValue });
|
|
182
|
+
// Object.defineProperty(data, property, { value: propertyValue });
|
|
183
|
+
Reflect.set(data, property, propertyValue);
|
|
215
184
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return data;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Cast data as template format
|
|
189
|
+
* @param source Source data
|
|
190
|
+
* @param template Format template
|
|
191
|
+
* @param keepSource Keep other source properties
|
|
192
|
+
* @returns Result
|
|
193
|
+
*/
|
|
194
|
+
export function dataAs<T extends DataTypes.BasicTemplate>(
|
|
195
|
+
source: unknown,
|
|
196
|
+
template: T,
|
|
197
|
+
keepSource: boolean = false
|
|
198
|
+
): DataTypes.BasicTemplateType<T> {
|
|
199
|
+
// New data
|
|
200
|
+
// Object.create(...)
|
|
201
|
+
const data = <DataTypes.BasicTemplateType<T>>{};
|
|
202
|
+
|
|
203
|
+
if (source != null && typeof source === "object") {
|
|
204
|
+
// Travel all properties
|
|
205
|
+
dataAsTraveller(source, data, template, keepSource, false);
|
|
238
206
|
}
|
|
239
207
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// Return
|
|
282
|
-
return culture;
|
|
283
|
-
})();
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Is two dimensions equal
|
|
287
|
-
* @param d1 Dimension 1
|
|
288
|
-
* @param d2 Dimension 2
|
|
289
|
-
*/
|
|
290
|
-
export function dimensionEqual(d1?: DOMRect, d2?: DOMRect) {
|
|
291
|
-
if (d1 == null && d2 == null) {
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (d1 == null || d2 == null) {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
d1.left === d2.left &&
|
|
301
|
-
d1.top === d2.top &&
|
|
302
|
-
d1.right === d2.right &&
|
|
303
|
-
d1.bottom === d2.bottom
|
|
304
|
-
) {
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return false;
|
|
208
|
+
// Return
|
|
209
|
+
return data;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Cast data to target type
|
|
214
|
+
* @param source Source data
|
|
215
|
+
* @param template Template for generation
|
|
216
|
+
* @param keepSource Means even the template does not include the definition, still keep the item
|
|
217
|
+
* @returns Result
|
|
218
|
+
*/
|
|
219
|
+
export function dataValueAs<T extends object>(
|
|
220
|
+
source: IFormData | object,
|
|
221
|
+
templateValue: T,
|
|
222
|
+
keepSource: boolean = false
|
|
223
|
+
): Partial<T> {
|
|
224
|
+
// New data
|
|
225
|
+
// Object.create(...)
|
|
226
|
+
const data = <Partial<T>>{};
|
|
227
|
+
|
|
228
|
+
// Travel all properties
|
|
229
|
+
dataAsTraveller(source, data, templateValue, keepSource, true);
|
|
230
|
+
|
|
231
|
+
// Return
|
|
232
|
+
return data;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Current detected country
|
|
237
|
+
*/
|
|
238
|
+
export const detectedCountry = (() => {
|
|
239
|
+
// URL first, then local storage
|
|
240
|
+
let country: string | null;
|
|
241
|
+
try {
|
|
242
|
+
country =
|
|
243
|
+
new URL(location.href).searchParams.get(CountryField) ??
|
|
244
|
+
sessionStorage.getItem(CountryField) ??
|
|
245
|
+
localStorage.getItem(CountryField);
|
|
246
|
+
} catch {
|
|
247
|
+
country = null;
|
|
309
248
|
}
|
|
310
249
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (!(await verifyPermission(handle, true))) return undefined;
|
|
330
|
-
|
|
331
|
-
const stream = await handle.createWritable();
|
|
332
|
-
|
|
333
|
-
if (data instanceof Blob) {
|
|
334
|
-
data.stream().pipeTo(stream);
|
|
335
|
-
} else {
|
|
336
|
-
await data.pipeTo(stream);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return true;
|
|
340
|
-
} else {
|
|
341
|
-
const url = window.URL.createObjectURL(
|
|
342
|
-
data instanceof Blob
|
|
343
|
-
? data
|
|
344
|
-
: await new Response(data).blob()
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
const a = document.createElement('a');
|
|
348
|
-
a.style.display = 'none';
|
|
349
|
-
a.href = url;
|
|
350
|
-
if (suggestedName) a.download = suggestedName;
|
|
351
|
-
|
|
352
|
-
document.body.appendChild(a);
|
|
353
|
-
a.click();
|
|
354
|
-
a.remove();
|
|
355
|
-
|
|
356
|
-
window.URL.revokeObjectURL(url);
|
|
357
|
-
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
} catch (e) {
|
|
361
|
-
console.error('DomUtils.downloadFile with error', e);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return false;
|
|
250
|
+
// Return
|
|
251
|
+
return country;
|
|
252
|
+
})();
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Current detected culture
|
|
256
|
+
*/
|
|
257
|
+
export const detectedCulture = (() => {
|
|
258
|
+
// URL first, then local storage
|
|
259
|
+
let culture: string | null;
|
|
260
|
+
try {
|
|
261
|
+
culture =
|
|
262
|
+
new URL(location.href).searchParams.get(CultureField) ??
|
|
263
|
+
sessionStorage.getItem(CultureField) ??
|
|
264
|
+
localStorage.getItem(CultureField);
|
|
265
|
+
} catch {
|
|
266
|
+
culture = null;
|
|
365
267
|
}
|
|
366
268
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
*/
|
|
372
|
-
export async function fileToDataURL(file: File) {
|
|
373
|
-
return new Promise<string>((resolve, reject) => {
|
|
374
|
-
const reader = new FileReader();
|
|
375
|
-
reader.onerror = reject;
|
|
376
|
-
reader.onload = () => {
|
|
377
|
-
const data = reader.result;
|
|
378
|
-
if (data == null) {
|
|
379
|
-
reject();
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
resolve(data as string);
|
|
384
|
-
};
|
|
385
|
-
reader.readAsDataURL(file);
|
|
386
|
-
});
|
|
269
|
+
// Browser detected
|
|
270
|
+
if (culture == null) {
|
|
271
|
+
culture =
|
|
272
|
+
(navigator.languages && navigator.languages[0]) || navigator.language;
|
|
387
273
|
}
|
|
388
274
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return dic;
|
|
275
|
+
// Return
|
|
276
|
+
return culture;
|
|
277
|
+
})();
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Is two dimensions equal
|
|
281
|
+
* @param d1 Dimension 1
|
|
282
|
+
* @param d2 Dimension 2
|
|
283
|
+
*/
|
|
284
|
+
export function dimensionEqual(d1?: DOMRect, d2?: DOMRect) {
|
|
285
|
+
if (d1 == null && d2 == null) {
|
|
286
|
+
return true;
|
|
402
287
|
}
|
|
403
288
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
* @param data User agent data
|
|
407
|
-
* @returns Result
|
|
408
|
-
*/
|
|
409
|
-
export function isWechatClient(data?: UserAgentData | null) {
|
|
410
|
-
data ??= parseUserAgent();
|
|
411
|
-
if (!data) return false;
|
|
412
|
-
|
|
413
|
-
return data.brands.some(
|
|
414
|
-
(item) => item.brand.toLowerCase() === 'micromessenger'
|
|
415
|
-
);
|
|
289
|
+
if (d1 == null || d2 == null) {
|
|
290
|
+
return false;
|
|
416
291
|
}
|
|
417
292
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
Default
|
|
293
|
+
if (
|
|
294
|
+
d1.left === d2.left &&
|
|
295
|
+
d1.top === d2.top &&
|
|
296
|
+
d1.right === d2.right &&
|
|
297
|
+
d1.bottom === d2.bottom
|
|
298
|
+
) {
|
|
299
|
+
return true;
|
|
426
300
|
}
|
|
427
301
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Download file from API fetch response body
|
|
307
|
+
* @param data Data
|
|
308
|
+
* @param suggestedName Suggested file name
|
|
309
|
+
* @param autoDetect Auto detect, false will use link click way
|
|
310
|
+
*/
|
|
311
|
+
export async function downloadFile(
|
|
312
|
+
data: ReadableStream | Blob,
|
|
313
|
+
suggestedName?: string,
|
|
314
|
+
autoDetect: boolean = true
|
|
315
|
+
) {
|
|
316
|
+
try {
|
|
317
|
+
if (autoDetect && "showSaveFilePicker" in globalThis) {
|
|
318
|
+
// AbortError - Use dismisses the window
|
|
319
|
+
const handle = await (globalThis as any).showSaveFilePicker({
|
|
320
|
+
suggestedName
|
|
321
|
+
});
|
|
442
322
|
|
|
443
|
-
|
|
444
|
-
* Get simplified Chinese resources definition
|
|
445
|
-
* @param resources Resources
|
|
446
|
-
* @returns Result
|
|
447
|
-
*/
|
|
448
|
-
export const zhHans = <
|
|
449
|
-
T extends DataTypes.StringRecord = DataTypes.StringRecord
|
|
450
|
-
>(
|
|
451
|
-
resources: T | (() => Promise<T>)
|
|
452
|
-
): DataTypes.CultureDefinition<T> => ({
|
|
453
|
-
name: 'zh-Hans',
|
|
454
|
-
label: '简体中文',
|
|
455
|
-
resources,
|
|
456
|
-
compatibleNames: ['zh-CN', 'zh-SG']
|
|
457
|
-
});
|
|
323
|
+
if (!(await verifyPermission(handle, true))) return undefined;
|
|
458
324
|
|
|
459
|
-
|
|
460
|
-
* Get traditional Chinese resources definition
|
|
461
|
-
* @param resources Resources
|
|
462
|
-
* @returns Result
|
|
463
|
-
*/
|
|
464
|
-
export const zhHant = <
|
|
465
|
-
T extends DataTypes.StringRecord = DataTypes.StringRecord
|
|
466
|
-
>(
|
|
467
|
-
resources: T | (() => Promise<T>)
|
|
468
|
-
): DataTypes.CultureDefinition<T> => ({
|
|
469
|
-
name: 'zh-Hant',
|
|
470
|
-
label: '繁體中文',
|
|
471
|
-
resources,
|
|
472
|
-
compatibleNames: ['zh-HK', 'zh-TW', 'zh-MO']
|
|
473
|
-
});
|
|
325
|
+
const stream = await handle.createWritable();
|
|
474
326
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
*/
|
|
480
|
-
export const getCulture = <T extends DataTypes.StringRecord>(
|
|
481
|
-
items: DataTypes.CultureDefinition<T>[],
|
|
482
|
-
culture: string
|
|
483
|
-
): [DataTypes.CultureDefinition<T> | undefined, CultureMatch] => {
|
|
484
|
-
if (items.length === 0) {
|
|
485
|
-
return [undefined, CultureMatch.Exact];
|
|
327
|
+
if (data instanceof Blob) {
|
|
328
|
+
data.stream().pipeTo(stream);
|
|
329
|
+
} else {
|
|
330
|
+
await data.pipeTo(stream);
|
|
486
331
|
}
|
|
487
332
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// Compatible match
|
|
493
|
-
const compatibleMatch = items.find(
|
|
494
|
-
(item) =>
|
|
495
|
-
item.compatibleNames?.includes(culture) ||
|
|
496
|
-
culture.startsWith(item + '-')
|
|
333
|
+
return true;
|
|
334
|
+
} else {
|
|
335
|
+
const url = window.URL.createObjectURL(
|
|
336
|
+
data instanceof Blob ? data : await new Response(data).blob()
|
|
497
337
|
);
|
|
498
|
-
if (compatibleMatch) return [compatibleMatch, CultureMatch.Compatible];
|
|
499
338
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
);
|
|
505
|
-
if (samePartMatch) return [samePartMatch, CultureMatch.SamePart];
|
|
506
|
-
|
|
507
|
-
// Default
|
|
508
|
-
return [items[0], CultureMatch.Default];
|
|
509
|
-
};
|
|
339
|
+
const a = document.createElement("a");
|
|
340
|
+
a.style.display = "none";
|
|
341
|
+
a.href = url;
|
|
342
|
+
if (suggestedName) a.download = suggestedName;
|
|
510
343
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
* @returns Result
|
|
515
|
-
*/
|
|
516
|
-
export function getInputValue(input: HTMLInputElement) {
|
|
517
|
-
const type = input.type;
|
|
518
|
-
if (type === 'number' || type === 'range') {
|
|
519
|
-
const num = input.valueAsNumber;
|
|
520
|
-
if (isNaN(num)) return null;
|
|
521
|
-
return num;
|
|
522
|
-
} else if (type === 'date' || type === 'datetime-local')
|
|
523
|
-
return input.valueAsDate ?? DateUtils.parse(input.value);
|
|
524
|
-
return input.value;
|
|
525
|
-
}
|
|
344
|
+
document.body.appendChild(a);
|
|
345
|
+
a.click();
|
|
346
|
+
a.remove();
|
|
526
347
|
|
|
527
|
-
|
|
528
|
-
* Get an unique key combined with current URL
|
|
529
|
-
* @param key Key
|
|
530
|
-
*/
|
|
531
|
-
export const getLocationKey = (key: string) => `${location.href}:${key}`;
|
|
348
|
+
window.URL.revokeObjectURL(url);
|
|
532
349
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
} catch (e) {
|
|
353
|
+
console.error("DomUtils.downloadFile with error", e);
|
|
537
354
|
}
|
|
538
355
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* File to data URL
|
|
361
|
+
* @param file File
|
|
362
|
+
* @returns Data URL
|
|
363
|
+
*/
|
|
364
|
+
export async function fileToDataURL(file: File) {
|
|
365
|
+
return new Promise<string>((resolve, reject) => {
|
|
366
|
+
const reader = new FileReader();
|
|
367
|
+
reader.onerror = reject;
|
|
368
|
+
reader.onload = () => {
|
|
369
|
+
const data = reader.result;
|
|
370
|
+
if (data == null) {
|
|
371
|
+
reject();
|
|
372
|
+
return;
|
|
552
373
|
}
|
|
553
374
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
375
|
+
resolve(data as string);
|
|
376
|
+
};
|
|
377
|
+
reader.readAsDataURL(file);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Form data to object
|
|
383
|
+
* @param form Form data
|
|
384
|
+
* @returns Object
|
|
385
|
+
*/
|
|
386
|
+
export function formDataToObject(form: IFormData) {
|
|
387
|
+
const dic: Record<string, FormDataFieldValue | FormDataFieldValue[]> = {};
|
|
388
|
+
for (const key of new Set(form.keys())) {
|
|
389
|
+
const values = form.getAll(key);
|
|
390
|
+
dic[key] = values.length == 1 ? values[0] : values;
|
|
391
|
+
}
|
|
392
|
+
return dic;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Is wechat client
|
|
397
|
+
* @param data User agent data
|
|
398
|
+
* @returns Result
|
|
399
|
+
*/
|
|
400
|
+
export function isWechatClient(data?: UserAgentData | null) {
|
|
401
|
+
data ??= parseUserAgent();
|
|
402
|
+
if (!data) return false;
|
|
403
|
+
|
|
404
|
+
return data.brands.some(
|
|
405
|
+
(item) => item.brand.toLowerCase() === "micromessenger"
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Culture match case Enum
|
|
411
|
+
*/
|
|
412
|
+
export enum CultureMatch {
|
|
413
|
+
Exact,
|
|
414
|
+
Compatible,
|
|
415
|
+
SamePart,
|
|
416
|
+
Default
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get English resources definition
|
|
421
|
+
* @param resources Resources
|
|
422
|
+
* @returns Result
|
|
423
|
+
*/
|
|
424
|
+
export const en = <T extends DataTypes.StringRecord = DataTypes.StringRecord>(
|
|
425
|
+
resources: T | (() => Promise<T>)
|
|
426
|
+
): DataTypes.CultureDefinition<T> => ({
|
|
427
|
+
name: "en",
|
|
428
|
+
label: "English",
|
|
429
|
+
resources
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get simplified Chinese resources definition
|
|
434
|
+
* @param resources Resources
|
|
435
|
+
* @returns Result
|
|
436
|
+
*/
|
|
437
|
+
export const zhHans = <
|
|
438
|
+
T extends DataTypes.StringRecord = DataTypes.StringRecord
|
|
439
|
+
>(
|
|
440
|
+
resources: T | (() => Promise<T>)
|
|
441
|
+
): DataTypes.CultureDefinition<T> => ({
|
|
442
|
+
name: "zh-Hans",
|
|
443
|
+
label: "简体中文",
|
|
444
|
+
resources,
|
|
445
|
+
compatibleNames: ["zh-CN", "zh-SG"]
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get traditional Chinese resources definition
|
|
450
|
+
* @param resources Resources
|
|
451
|
+
* @returns Result
|
|
452
|
+
*/
|
|
453
|
+
export const zhHant = <
|
|
454
|
+
T extends DataTypes.StringRecord = DataTypes.StringRecord
|
|
455
|
+
>(
|
|
456
|
+
resources: T | (() => Promise<T>)
|
|
457
|
+
): DataTypes.CultureDefinition<T> => ({
|
|
458
|
+
name: "zh-Hant",
|
|
459
|
+
label: "繁體中文",
|
|
460
|
+
resources,
|
|
461
|
+
compatibleNames: ["zh-HK", "zh-TW", "zh-MO"]
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get the available culture definition
|
|
466
|
+
* @param items Available cultures
|
|
467
|
+
* @param culture Detected culture
|
|
468
|
+
*/
|
|
469
|
+
export const getCulture = <T extends DataTypes.StringRecord>(
|
|
470
|
+
items: DataTypes.CultureDefinition<T>[],
|
|
471
|
+
culture: string
|
|
472
|
+
): [DataTypes.CultureDefinition<T> | undefined, CultureMatch] => {
|
|
473
|
+
if (items.length === 0) {
|
|
474
|
+
return [undefined, CultureMatch.Exact];
|
|
563
475
|
}
|
|
564
476
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
477
|
+
// Exact match
|
|
478
|
+
const exactMatch = items.find((item) => item.name === culture);
|
|
479
|
+
if (exactMatch) return [exactMatch, CultureMatch.Exact];
|
|
480
|
+
|
|
481
|
+
// Compatible match
|
|
482
|
+
const compatibleMatch = items.find(
|
|
483
|
+
(item) =>
|
|
484
|
+
item.compatibleNames?.includes(culture) ||
|
|
485
|
+
culture.startsWith(item + "-")
|
|
486
|
+
);
|
|
487
|
+
if (compatibleMatch) return [compatibleMatch, CultureMatch.Compatible];
|
|
488
|
+
|
|
489
|
+
// Same part, like zh-CN and zh-HK
|
|
490
|
+
const samePart = culture.split("-")[0];
|
|
491
|
+
const samePartMatch = items.find((item) => item.name.startsWith(samePart));
|
|
492
|
+
if (samePartMatch) return [samePartMatch, CultureMatch.SamePart];
|
|
493
|
+
|
|
494
|
+
// Default
|
|
495
|
+
return [items[0], CultureMatch.Default];
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Get input value depending on its type
|
|
500
|
+
* @param input HTML input
|
|
501
|
+
* @returns Result
|
|
502
|
+
*/
|
|
503
|
+
export function getInputValue(input: HTMLInputElement) {
|
|
504
|
+
const type = input.type;
|
|
505
|
+
if (type === "number" || type === "range") {
|
|
506
|
+
const num = input.valueAsNumber;
|
|
507
|
+
if (isNaN(num)) return null;
|
|
508
|
+
return num;
|
|
509
|
+
} else if (type === "date" || type === "datetime-local")
|
|
510
|
+
return input.valueAsDate ?? DateUtils.parse(input.value);
|
|
511
|
+
return input.value;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Get an unique key combined with current URL
|
|
516
|
+
* @param key Key
|
|
517
|
+
*/
|
|
518
|
+
export const getLocationKey = (key: string) => `${location.href}:${key}`;
|
|
519
|
+
|
|
520
|
+
function isIterable<T>(
|
|
521
|
+
headers: Record<string, string> | Iterable<T>
|
|
522
|
+
): headers is Iterable<T> {
|
|
523
|
+
return Symbol.iterator in headers;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Convert headers to object
|
|
528
|
+
* @param headers Heaers
|
|
529
|
+
*/
|
|
530
|
+
export function headersToObject(
|
|
531
|
+
headers: HeadersInit | Iterable<[string, string]>
|
|
532
|
+
): Record<string, string> {
|
|
533
|
+
if (Array.isArray(headers)) {
|
|
534
|
+
return Object.fromEntries(headers);
|
|
581
535
|
}
|
|
582
536
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
* @param contentType Content type string
|
|
586
|
-
*/
|
|
587
|
-
export function isJSONContentType(contentType: string) {
|
|
588
|
-
if (
|
|
589
|
-
contentType &&
|
|
590
|
-
// application/problem+json
|
|
591
|
-
// application/json
|
|
592
|
-
(contentType.includes('json') ||
|
|
593
|
-
contentType.startsWith('application/javascript'))
|
|
594
|
-
)
|
|
595
|
-
return true;
|
|
596
|
-
return false;
|
|
537
|
+
if (typeof Headers === "undefined") {
|
|
538
|
+
return Object.fromEntries(Object.entries(headers));
|
|
597
539
|
}
|
|
598
540
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
* @param forms Other form data
|
|
603
|
-
* @returns Merged form data
|
|
604
|
-
*/
|
|
605
|
-
export function mergeFormData(form: IFormData, ...forms: IFormData[]) {
|
|
606
|
-
for (const newForm of forms) {
|
|
607
|
-
for (const key of new Set(newForm.keys())) {
|
|
608
|
-
form.delete(key);
|
|
609
|
-
newForm
|
|
610
|
-
.getAll(key)
|
|
611
|
-
.forEach((value) => form.append(key, value as any));
|
|
612
|
-
}
|
|
613
|
-
}
|
|
541
|
+
if (headers instanceof Headers) {
|
|
542
|
+
return Object.fromEntries(headers.entries());
|
|
543
|
+
}
|
|
614
544
|
|
|
615
|
-
|
|
545
|
+
if (isIterable(headers)) {
|
|
546
|
+
return Object.fromEntries(headers);
|
|
616
547
|
}
|
|
617
548
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
549
|
+
return headers;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Is IFormData type guard
|
|
554
|
+
* @param input Input object
|
|
555
|
+
* @returns result
|
|
556
|
+
*/
|
|
557
|
+
export function isFormData(input: unknown): input is IFormData {
|
|
558
|
+
if (
|
|
559
|
+
typeof input === "object" &&
|
|
560
|
+
input != null &&
|
|
561
|
+
"entries" in input &&
|
|
562
|
+
"getAll" in input &&
|
|
563
|
+
"keys" in input
|
|
626
564
|
) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Is JSON content type
|
|
572
|
+
* @param contentType Content type string
|
|
573
|
+
*/
|
|
574
|
+
export function isJSONContentType(contentType: string) {
|
|
575
|
+
if (
|
|
576
|
+
contentType &&
|
|
577
|
+
// application/problem+json
|
|
578
|
+
// application/json
|
|
579
|
+
(contentType.includes("json") ||
|
|
580
|
+
contentType.startsWith("application/javascript"))
|
|
581
|
+
)
|
|
582
|
+
return true;
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Merge form data to primary one
|
|
588
|
+
* @param form Primary form data
|
|
589
|
+
* @param forms Other form data
|
|
590
|
+
* @returns Merged form data
|
|
591
|
+
*/
|
|
592
|
+
export function mergeFormData(form: IFormData, ...forms: IFormData[]) {
|
|
593
|
+
for (const newForm of forms) {
|
|
594
|
+
for (const key of new Set(newForm.keys())) {
|
|
595
|
+
form.delete(key);
|
|
596
|
+
newForm.getAll(key).forEach((value) => form.append(key, value as any));
|
|
597
|
+
}
|
|
632
598
|
}
|
|
633
599
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
if (data && data.length > 1) {
|
|
667
|
-
const pfItems = data[1].split(/;\s*/);
|
|
668
|
-
|
|
669
|
-
// Platform + Version
|
|
670
|
-
const pfIndex = pfItems.findIndex((item) =>
|
|
671
|
-
platformVersionReg.test(item)
|
|
672
|
-
);
|
|
673
|
-
|
|
674
|
-
if (pfIndex !== -1) {
|
|
675
|
-
const pfParts = pfItems[pfIndex].split(/\s+/);
|
|
676
|
-
platformVersion = pfParts.pop();
|
|
677
|
-
platform = pfParts.join(' ');
|
|
678
|
-
} else {
|
|
679
|
-
const appleVersionReg =
|
|
680
|
-
/((iPhone|Mac)\s+OS(\s+\w+)?)\s+((0|\d+)(_(0|\d+)){0,3})/i;
|
|
681
|
-
|
|
682
|
-
for (let i = 0; i < pfItems.length; i++) {
|
|
683
|
-
const match = appleVersionReg.exec(pfItems[i]);
|
|
684
|
-
if (match && match.length > 4) {
|
|
685
|
-
platform = match[1];
|
|
686
|
-
platformVersion = match[4].replace(/_/g, '.');
|
|
687
|
-
|
|
688
|
-
pfItems.splice(i, 1);
|
|
689
|
-
break;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Device
|
|
695
|
-
const deviceIndex = pfItems.findIndex((item) =>
|
|
696
|
-
item.includes(' Build/')
|
|
697
|
-
);
|
|
698
|
-
if (deviceIndex === -1) {
|
|
699
|
-
const firstItem = pfItems[0];
|
|
700
|
-
if (
|
|
701
|
-
firstItem.toLowerCase() !== 'linux' &&
|
|
702
|
-
!firstItem.startsWith(platform)
|
|
703
|
-
) {
|
|
704
|
-
device = firstItem;
|
|
705
|
-
pfItems.shift();
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
device = pfItems[deviceIndex].split(' Build/')[0];
|
|
709
|
-
pfItems.splice(deviceIndex, 1);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (pl === 'mobile' || pl.startsWith('mobile/')) {
|
|
716
|
-
mobile = true;
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
600
|
+
return form;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Merge URL search parameters
|
|
605
|
+
* @param base URL search parameters
|
|
606
|
+
* @param data New simple object data to merge
|
|
607
|
+
*/
|
|
608
|
+
export function mergeURLSearchParams(
|
|
609
|
+
base: URLSearchParams,
|
|
610
|
+
data: DataTypes.SimpleObject
|
|
611
|
+
) {
|
|
612
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
613
|
+
if (value == null) return;
|
|
614
|
+
base.set(key, value.toString());
|
|
615
|
+
});
|
|
616
|
+
return base;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Parse navigator's user agent string
|
|
621
|
+
* Lightweight User-Agent string parser
|
|
622
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
|
623
|
+
* @param ua User agent string
|
|
624
|
+
* @returns User agent data
|
|
625
|
+
*/
|
|
626
|
+
export function parseUserAgent(ua?: string): UserAgentData | null {
|
|
627
|
+
ua ??= globalThis.navigator.userAgent;
|
|
628
|
+
|
|
629
|
+
if (!ua) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
719
632
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
633
|
+
const parts = ua.split(/(?!\(.*)\s+(?!\()(?![^(]*?\))/g);
|
|
634
|
+
|
|
635
|
+
let mobile = false;
|
|
636
|
+
let platform = "";
|
|
637
|
+
let platformVersion: string | undefined;
|
|
638
|
+
let device = "Desktop";
|
|
639
|
+
const brands: UserAgentData["brands"] = [];
|
|
640
|
+
|
|
641
|
+
// with the 'g' will causing failures for multiple calls
|
|
642
|
+
const platformVersionReg =
|
|
643
|
+
/^[a-zA-Z0-9-\s]+\s+(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
644
|
+
const versionReg = /^[a-zA-Z0-9]+\/(0|\d+)(\.(0|\d+)){0,3}(\(|$)/;
|
|
645
|
+
|
|
646
|
+
parts.forEach((part) => {
|
|
647
|
+
const pl = part.toLowerCase();
|
|
648
|
+
|
|
649
|
+
if (pl.startsWith("mozilla/")) {
|
|
650
|
+
const data = /\((.*)\)$/.exec(part);
|
|
651
|
+
if (data && data.length > 1) {
|
|
652
|
+
const pfItems = data[1].split(/;\s*/);
|
|
653
|
+
|
|
654
|
+
// Platform + Version
|
|
655
|
+
const pfIndex = pfItems.findIndex((item) =>
|
|
656
|
+
platformVersionReg.test(item)
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
if (pfIndex !== -1) {
|
|
660
|
+
const pfParts = pfItems[pfIndex].split(/\s+/);
|
|
661
|
+
platformVersion = pfParts.pop();
|
|
662
|
+
platform = pfParts.join(" ");
|
|
663
|
+
} else {
|
|
664
|
+
const appleVersionReg =
|
|
665
|
+
/((iPhone|Mac)\s+OS(\s+\w+)?)\s+((0|\d+)(_(0|\d+)){0,3})/i;
|
|
666
|
+
|
|
667
|
+
for (let i = 0; i < pfItems.length; i++) {
|
|
668
|
+
const match = appleVersionReg.exec(pfItems[i]);
|
|
669
|
+
if (match && match.length > 4) {
|
|
670
|
+
platform = match[1];
|
|
671
|
+
platformVersion = match[4].replace(/_/g, ".");
|
|
672
|
+
|
|
673
|
+
pfItems.splice(i, 1);
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
723
676
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Device
|
|
680
|
+
const deviceIndex = pfItems.findIndex((item) =>
|
|
681
|
+
item.includes(" Build/")
|
|
682
|
+
);
|
|
683
|
+
if (deviceIndex === -1) {
|
|
684
|
+
const firstItem = pfItems[0];
|
|
685
|
+
if (
|
|
686
|
+
firstItem.toLowerCase() !== "linux" &&
|
|
687
|
+
!firstItem.startsWith(platform)
|
|
688
|
+
) {
|
|
689
|
+
device = firstItem;
|
|
690
|
+
pfItems.shift();
|
|
736
691
|
}
|
|
692
|
+
} else {
|
|
693
|
+
device = pfItems[deviceIndex].split(" Build/")[0];
|
|
694
|
+
pfItems.splice(deviceIndex, 1);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (pl === "mobile" || pl.startsWith("mobile/")) {
|
|
701
|
+
mobile = true;
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (pl === "version" || pl.startsWith("version/")) {
|
|
706
|
+
// No process
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (versionReg.test(part)) {
|
|
711
|
+
let [brand, version] = part.split("/");
|
|
712
|
+
const pindex = version.indexOf("(");
|
|
713
|
+
if (pindex > 0) {
|
|
714
|
+
version = version.substring(0, pindex);
|
|
715
|
+
}
|
|
716
|
+
brands.push({
|
|
717
|
+
brand,
|
|
718
|
+
version: Utils.trimEnd(version, ".0")
|
|
737
719
|
});
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
});
|
|
738
723
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
724
|
+
return { mobile, platform, platformVersion, brands, device };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Set HTML element focus by name
|
|
729
|
+
* @param name Element name or first collection item
|
|
730
|
+
* @param container Container, limits the element range
|
|
731
|
+
*/
|
|
732
|
+
export function setFocus(name: string | object, container?: HTMLElement) {
|
|
733
|
+
const elementName = typeof name === "string" ? name : Object.keys(name)[0];
|
|
734
|
+
|
|
735
|
+
container ??= document.body;
|
|
736
|
+
|
|
737
|
+
const element = container.querySelector<HTMLElement>(
|
|
738
|
+
`[name="${elementName}"]`
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
if (element != null) element.focus();
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Setup frontend logging
|
|
746
|
+
* @param action Logging action
|
|
747
|
+
* @param preventDefault Is prevent default action
|
|
748
|
+
* @param window Window object
|
|
749
|
+
*/
|
|
750
|
+
export function setupLogging(
|
|
751
|
+
action: (data: ErrorData) => void | Promise<void>,
|
|
752
|
+
preventDefault?: ((type: ErrorType) => boolean) | boolean,
|
|
753
|
+
window: Window & typeof globalThis = globalThis.window
|
|
754
|
+
) {
|
|
755
|
+
// Avoid multiple setup, if there is already a handler, please set "window.onunhandledrejection = null" first
|
|
756
|
+
if (window.onunhandledrejection) return;
|
|
757
|
+
|
|
758
|
+
const errorType: ErrorType = "error";
|
|
759
|
+
const errorPD = Utils.getResult(preventDefault, errorType) ?? true;
|
|
760
|
+
window.onerror = (message, source, lineNo, colNo, error) => {
|
|
761
|
+
// Default source
|
|
762
|
+
source ||= window.location.href;
|
|
763
|
+
let data: ErrorData;
|
|
764
|
+
if (typeof message === "string") {
|
|
765
|
+
data = {
|
|
766
|
+
type: errorType,
|
|
767
|
+
message, // Share the same message with error
|
|
768
|
+
source,
|
|
769
|
+
lineNo,
|
|
770
|
+
colNo,
|
|
771
|
+
stack: error?.stack
|
|
772
|
+
};
|
|
773
|
+
} else {
|
|
774
|
+
data = {
|
|
775
|
+
type: errorType,
|
|
776
|
+
subType: message.type,
|
|
777
|
+
message: error?.message ?? `${message.currentTarget} event error`,
|
|
778
|
+
source,
|
|
779
|
+
lineNo,
|
|
780
|
+
colNo,
|
|
781
|
+
stack: error?.stack
|
|
782
|
+
};
|
|
783
|
+
}
|
|
756
784
|
|
|
757
|
-
|
|
758
|
-
}
|
|
785
|
+
action(data);
|
|
759
786
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
* @param preventDefault Is prevent default action
|
|
764
|
-
* @param window Window object
|
|
765
|
-
*/
|
|
766
|
-
export function setupLogging(
|
|
767
|
-
action: (data: ErrorData) => void | Promise<void>,
|
|
768
|
-
preventDefault?: ((type: ErrorType) => boolean) | boolean,
|
|
769
|
-
window: Window & typeof globalThis = globalThis.window
|
|
770
|
-
) {
|
|
771
|
-
// Avoid multiple setup, if there is already a handler, please set "window.onunhandledrejection = null" first
|
|
772
|
-
if (window.onunhandledrejection) return;
|
|
773
|
-
|
|
774
|
-
const errorType: ErrorType = 'error';
|
|
775
|
-
const errorPD = Utils.getResult(preventDefault, errorType) ?? true;
|
|
776
|
-
window.onerror = (message, source, lineNo, colNo, error) => {
|
|
777
|
-
// Default source
|
|
778
|
-
source ||= window.location.href;
|
|
779
|
-
let data: ErrorData;
|
|
780
|
-
if (typeof message === 'string') {
|
|
781
|
-
data = {
|
|
782
|
-
type: errorType,
|
|
783
|
-
message, // Share the same message with error
|
|
784
|
-
source,
|
|
785
|
-
lineNo,
|
|
786
|
-
colNo,
|
|
787
|
-
stack: error?.stack
|
|
788
|
-
};
|
|
789
|
-
} else {
|
|
790
|
-
data = {
|
|
791
|
-
type: errorType,
|
|
792
|
-
subType: message.type,
|
|
793
|
-
message:
|
|
794
|
-
error?.message ??
|
|
795
|
-
`${message.currentTarget} event error`,
|
|
796
|
-
source,
|
|
797
|
-
lineNo,
|
|
798
|
-
colNo,
|
|
799
|
-
stack: error?.stack
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
action(data);
|
|
787
|
+
// Return true to suppress error alert
|
|
788
|
+
return errorPD;
|
|
789
|
+
};
|
|
804
790
|
|
|
805
|
-
|
|
806
|
-
|
|
791
|
+
const rejectionType: ErrorType = "unhandledrejection";
|
|
792
|
+
const rejectionPD = Utils.getResult(preventDefault, rejectionType) ?? true;
|
|
793
|
+
window.onunhandledrejection = (event) => {
|
|
794
|
+
if (rejectionPD) event.preventDefault();
|
|
795
|
+
|
|
796
|
+
const reason = event.reason;
|
|
797
|
+
const source = window.location.href;
|
|
798
|
+
let data: ErrorData;
|
|
799
|
+
|
|
800
|
+
if (reason instanceof Error) {
|
|
801
|
+
const { name: subType, message, stack } = reason;
|
|
802
|
+
data = {
|
|
803
|
+
type: rejectionType,
|
|
804
|
+
subType,
|
|
805
|
+
message,
|
|
806
|
+
stack,
|
|
807
|
+
source
|
|
807
808
|
};
|
|
809
|
+
} else {
|
|
810
|
+
data = {
|
|
811
|
+
type: rejectionType,
|
|
812
|
+
message: typeof reason === "string" ? reason : JSON.stringify(reason),
|
|
813
|
+
source
|
|
814
|
+
};
|
|
815
|
+
}
|
|
808
816
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
Utils.getResult(preventDefault, rejectionType) ?? true;
|
|
812
|
-
window.onunhandledrejection = (event) => {
|
|
813
|
-
if (rejectionPD) event.preventDefault();
|
|
814
|
-
|
|
815
|
-
const reason = event.reason;
|
|
816
|
-
const source = window.location.href;
|
|
817
|
-
let data: ErrorData;
|
|
818
|
-
|
|
819
|
-
if (reason instanceof Error) {
|
|
820
|
-
const { name: subType, message, stack } = reason;
|
|
821
|
-
data = {
|
|
822
|
-
type: rejectionType,
|
|
823
|
-
subType,
|
|
824
|
-
message,
|
|
825
|
-
stack,
|
|
826
|
-
source
|
|
827
|
-
};
|
|
828
|
-
} else {
|
|
829
|
-
data = {
|
|
830
|
-
type: rejectionType,
|
|
831
|
-
message:
|
|
832
|
-
typeof reason === 'string'
|
|
833
|
-
? reason
|
|
834
|
-
: JSON.stringify(reason),
|
|
835
|
-
source
|
|
836
|
-
};
|
|
837
|
-
}
|
|
817
|
+
action(data);
|
|
818
|
+
};
|
|
838
819
|
|
|
839
|
-
|
|
840
|
-
|
|
820
|
+
const localConsole = (
|
|
821
|
+
type: "consoleWarn" | "consoleError",
|
|
822
|
+
orgin: (...args: any[]) => void
|
|
823
|
+
) => {
|
|
824
|
+
const consolePD = Utils.getResult(preventDefault, type) ?? false;
|
|
825
|
+
return (...args: any[]) => {
|
|
826
|
+
// Keep original action
|
|
827
|
+
if (!consolePD) orgin(...args);
|
|
828
|
+
|
|
829
|
+
const [first, ...rest] = args;
|
|
830
|
+
let message: string;
|
|
831
|
+
if (typeof first === "string") {
|
|
832
|
+
message = first;
|
|
833
|
+
} else {
|
|
834
|
+
message = JSON.stringify(first);
|
|
835
|
+
}
|
|
841
836
|
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
let message: string;
|
|
853
|
-
if (typeof first === 'string') {
|
|
854
|
-
message = first;
|
|
855
|
-
} else {
|
|
856
|
-
message = JSON.stringify(first);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const stack =
|
|
860
|
-
rest.length > 0
|
|
861
|
-
? rest.map((item) => JSON.stringify(item)).join(', ')
|
|
862
|
-
: undefined;
|
|
863
|
-
|
|
864
|
-
const data: ErrorData = {
|
|
865
|
-
type,
|
|
866
|
-
message,
|
|
867
|
-
source: window.location.href,
|
|
868
|
-
stack
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
action(data);
|
|
872
|
-
};
|
|
837
|
+
const stack =
|
|
838
|
+
rest.length > 0
|
|
839
|
+
? rest.map((item) => JSON.stringify(item)).join(", ")
|
|
840
|
+
: undefined;
|
|
841
|
+
|
|
842
|
+
const data: ErrorData = {
|
|
843
|
+
type,
|
|
844
|
+
message,
|
|
845
|
+
source: window.location.href,
|
|
846
|
+
stack
|
|
873
847
|
};
|
|
874
848
|
|
|
875
|
-
|
|
849
|
+
action(data);
|
|
850
|
+
};
|
|
851
|
+
};
|
|
876
852
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
853
|
+
window.console.warn = localConsole("consoleWarn", window.console.warn);
|
|
854
|
+
|
|
855
|
+
window.console.error = localConsole("consoleError", window.console.error);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Verify file system permission
|
|
860
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle/requestPermission
|
|
861
|
+
* @param fileHandle FileSystemHandle
|
|
862
|
+
* @param withWrite With write permission
|
|
863
|
+
* @returns Result
|
|
864
|
+
*/
|
|
865
|
+
export async function verifyPermission(
|
|
866
|
+
fileHandle: any,
|
|
867
|
+
withWrite: boolean = false
|
|
868
|
+
) {
|
|
869
|
+
if (
|
|
870
|
+
!("queryPermission" in fileHandle) ||
|
|
871
|
+
!("requestPermission" in fileHandle)
|
|
872
|
+
)
|
|
873
|
+
return false;
|
|
874
|
+
|
|
875
|
+
// FileSystemHandlePermissionDescriptor
|
|
876
|
+
const opts = { mode: withWrite ? "readwrite" : "read" };
|
|
877
|
+
|
|
878
|
+
// Check if we already have permission, if so, return true.
|
|
879
|
+
if ((await fileHandle.queryPermission(opts)) === "granted") {
|
|
880
|
+
return true;
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
* @param fileHandle FileSystemHandle
|
|
887
|
-
* @param withWrite With write permission
|
|
888
|
-
* @returns Result
|
|
889
|
-
*/
|
|
890
|
-
export async function verifyPermission(
|
|
891
|
-
fileHandle: any,
|
|
892
|
-
withWrite: boolean = false
|
|
893
|
-
) {
|
|
894
|
-
if (
|
|
895
|
-
!('queryPermission' in fileHandle) ||
|
|
896
|
-
!('requestPermission' in fileHandle)
|
|
897
|
-
)
|
|
898
|
-
return false;
|
|
899
|
-
|
|
900
|
-
// FileSystemHandlePermissionDescriptor
|
|
901
|
-
const opts = { mode: withWrite ? 'readwrite' : 'read' };
|
|
902
|
-
|
|
903
|
-
// Check if we already have permission, if so, return true.
|
|
904
|
-
if ((await fileHandle.queryPermission(opts)) === 'granted') {
|
|
905
|
-
return true;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Request permission to the file, if the user grants permission, return true.
|
|
909
|
-
if ((await fileHandle.requestPermission(opts)) === 'granted') {
|
|
910
|
-
return true;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// The user did not grant permission, return false.
|
|
914
|
-
return false;
|
|
883
|
+
// Request permission to the file, if the user grants permission, return true.
|
|
884
|
+
if ((await fileHandle.requestPermission(opts)) === "granted") {
|
|
885
|
+
return true;
|
|
915
886
|
}
|
|
887
|
+
|
|
888
|
+
// The user did not grant permission, return false.
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
916
891
|
}
|