@lowentry/utils 1.25.3 → 2.0.2
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/LICENSE +23 -0
- package/README.md +0 -2
- package/package.json +52 -53
- package/api-extractor.json +0 -43
- package/index.d.ts +0 -454
- package/index.js +0 -4160
- package/index.js.map +0 -1
- package/src/LeTypes.js +0 -254
- package/src/LeUtils.js +0 -3611
- package/src/classes/EventEmitter.js +0 -124
- package/src/classes/LinkedList.js +0 -145
- package/src/classes/SerializableMap.js +0 -17
- package/src/classes/TreeSet.js +0 -235
- package/src/index.js +0 -6
- package/tsconfig.d.ts +0 -1
- package/tsconfig.json +0 -39
package/src/LeUtils.js
DELETED
|
@@ -1,3611 +0,0 @@
|
|
|
1
|
-
import CloneDeep from 'clone-deep';
|
|
2
|
-
import {ISSET, IS_OBJECT, IS_ARRAY, STRING, INT_LAX, FLOAT_LAX, INT_LAX_ANY, FLOAT_LAX_ANY, ARRAY} from './LeTypes.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
7
|
-
*/
|
|
8
|
-
const checkTransactionalValue = (transactionalValue) =>
|
|
9
|
-
{
|
|
10
|
-
if(!LeUtils.isTransactionalValueValid(transactionalValue))
|
|
11
|
-
{
|
|
12
|
-
console.error('The given value is not a valid TransactionalValue:');
|
|
13
|
-
console.error(transactionalValue);
|
|
14
|
-
throw new Error('The given value is not a valid TransactionalValue');
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
20
|
-
* @param {string} changeId
|
|
21
|
-
* @returns {{index:number, value:*}|null}
|
|
22
|
-
*/
|
|
23
|
-
const findTransactionalValueChange = (transactionalValue, changeId) =>
|
|
24
|
-
{
|
|
25
|
-
for(let i = 0; i < transactionalValue.changes.length; i++)
|
|
26
|
-
{
|
|
27
|
-
const change = transactionalValue.changes[i];
|
|
28
|
-
if(change.id === changeId)
|
|
29
|
-
{
|
|
30
|
-
return {index:i, value:change.value};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export const LeUtils = {
|
|
38
|
-
/**
|
|
39
|
-
* A deep equals implementation.
|
|
40
|
-
*
|
|
41
|
-
* @param {*} a The value to compare.
|
|
42
|
-
* @param {*} b The other value to compare.
|
|
43
|
-
* @returns {boolean} Returns true if the values are equivalent.
|
|
44
|
-
*/
|
|
45
|
-
equals:
|
|
46
|
-
(a, b) =>
|
|
47
|
-
{
|
|
48
|
-
if(a === b)
|
|
49
|
-
{
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if(a && b && typeof a == 'object' && typeof b == 'object')
|
|
54
|
-
{
|
|
55
|
-
if(a.constructor !== b.constructor)
|
|
56
|
-
{
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if(Array.isArray(a))
|
|
61
|
-
{
|
|
62
|
-
const length = a.length;
|
|
63
|
-
if(length != b.length)
|
|
64
|
-
{
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
for(let i = length; i-- !== 0;)
|
|
68
|
-
{
|
|
69
|
-
if(!LeUtils.equals(a[i], b[i]))
|
|
70
|
-
{
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
if((a instanceof Map) && (b instanceof Map))
|
|
77
|
-
{
|
|
78
|
-
if(a.size !== b.size)
|
|
79
|
-
{
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
for(let i of a.entries())
|
|
83
|
-
{
|
|
84
|
-
if(!b.has(i[0]))
|
|
85
|
-
{
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
for(let i of a.entries())
|
|
90
|
-
{
|
|
91
|
-
if(!LeUtils.equals(i[1], b.get(i[0])))
|
|
92
|
-
{
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
if((a instanceof Set) && (b instanceof Set))
|
|
99
|
-
{
|
|
100
|
-
if(a.size !== b.size)
|
|
101
|
-
{
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
for(let i of a.entries())
|
|
105
|
-
{
|
|
106
|
-
if(!b.has(i[0]))
|
|
107
|
-
{
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
if(ArrayBuffer.isView(a) && ArrayBuffer.isView(b))
|
|
114
|
-
{
|
|
115
|
-
if(('length' in a) && ('length' in b) && (typeof a.length === 'number') && (typeof b.length === 'number'))
|
|
116
|
-
{
|
|
117
|
-
if(a.length != b.length)
|
|
118
|
-
{
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
for(let i = a.length; i-- !== 0;)
|
|
122
|
-
{
|
|
123
|
-
if(a[i] !== b[i])
|
|
124
|
-
{
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
if(('byteLength' in a) && ('byteLength' in b) && (typeof a.byteLength === 'number') && (typeof b.byteLength === 'number') && ('getUint8' in a) && ('getUint8' in b) && (typeof a.getUint8 === 'function') && (typeof b.getUint8 === 'function'))
|
|
131
|
-
{
|
|
132
|
-
if(a.byteLength !== b.byteLength)
|
|
133
|
-
{
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
for(let i = a.byteLength; i-- !== 0;)
|
|
137
|
-
{
|
|
138
|
-
if(a.getUint8(i) !== b.getUint8(i))
|
|
139
|
-
{
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if(a.constructor === RegExp)
|
|
149
|
-
{
|
|
150
|
-
return a.source === b.source && a.flags === b.flags;
|
|
151
|
-
}
|
|
152
|
-
if(a.valueOf !== Object.prototype.valueOf)
|
|
153
|
-
{
|
|
154
|
-
return a.valueOf() === b.valueOf();
|
|
155
|
-
}
|
|
156
|
-
if(a.toString !== Object.prototype.toString)
|
|
157
|
-
{
|
|
158
|
-
return a.toString() === b.toString();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if(a.constructor && (a.constructor !== Object) && (a.constructor !== Array) && (Object.getPrototypeOf(a) !== Object.prototype))
|
|
162
|
-
{
|
|
163
|
-
if(typeof a.equals === 'function')
|
|
164
|
-
{
|
|
165
|
-
return a.equals(b);
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const keys = Object.keys(a);
|
|
171
|
-
const length = keys.length;
|
|
172
|
-
if(length !== Object.keys(b).length)
|
|
173
|
-
{
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
for(let i = length; i-- !== 0;)
|
|
177
|
-
{
|
|
178
|
-
if(!Object.prototype.hasOwnProperty.call(b, keys[i]))
|
|
179
|
-
{
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
for(let i = length; i-- !== 0;)
|
|
184
|
-
{
|
|
185
|
-
const key = keys[i];
|
|
186
|
-
if((key === '_owner') && a.$$typeof)
|
|
187
|
-
{
|
|
188
|
-
// React-specific: avoid traversing _owner, it contains circular references, and is not needed when comparing the actual element
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if(!LeUtils.equals(a[key], b[key]))
|
|
192
|
-
{
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// true if both are NaN, false otherwise
|
|
200
|
-
return ((a !== a) && (b !== b));
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Performs a deep equality comparison between two collections (objects, maps, arrays, etc), sorting on the keys before comparing them.
|
|
205
|
-
*
|
|
206
|
-
* This is useful for comparing objects that have the same properties, but in a different order, and/or in a different collection type (like Maps vs Objects).
|
|
207
|
-
*
|
|
208
|
-
* @param {*} elementsA The elements to compare.
|
|
209
|
-
* @param {*} elementsB The other elements to compare.
|
|
210
|
-
* @param {string[]} [ignoreKeys=[]] An array of keys to ignore when comparing the elements. This is useful for ignoring properties that are not relevant for the comparison.
|
|
211
|
-
* @return {boolean} Returns true if the given values are equivalent, ignoring the order of properties.
|
|
212
|
-
*/
|
|
213
|
-
equalsMapLike:
|
|
214
|
-
(() =>
|
|
215
|
-
{
|
|
216
|
-
const sortKeyValueArrays = (pairA, pairB) => LeUtils.compare(pairA[0], pairB[0]);
|
|
217
|
-
return (elementsA, elementsB, ignoreKeys = []) =>
|
|
218
|
-
{
|
|
219
|
-
elementsA = LeUtils.mapToArray(elementsA, (value, key) => [key, value]).sort(sortKeyValueArrays);
|
|
220
|
-
elementsB = LeUtils.mapToArray(elementsB, (value, key) => [key, value]).sort(sortKeyValueArrays);
|
|
221
|
-
ignoreKeys = (typeof ignoreKeys === 'string') ? ARRAY(ignoreKeys) : LeUtils.mapToArray(ignoreKeys);
|
|
222
|
-
|
|
223
|
-
let indexA = 0;
|
|
224
|
-
let indexB = 0;
|
|
225
|
-
while((indexA < elementsA.length) && (indexB < elementsB.length))
|
|
226
|
-
{
|
|
227
|
-
const [mapKey, mapValue] = elementsA[indexA];
|
|
228
|
-
const [ownMapKey, ownMapValue] = elementsB[indexB];
|
|
229
|
-
|
|
230
|
-
const ignoreKeysIncludesMapKey = ignoreKeys.includes(mapKey);
|
|
231
|
-
const ignoreKeysIncludesOwnMapKey = ignoreKeys.includes(ownMapKey);
|
|
232
|
-
if(ignoreKeysIncludesMapKey)
|
|
233
|
-
{
|
|
234
|
-
indexA++;
|
|
235
|
-
if(ignoreKeysIncludesOwnMapKey)
|
|
236
|
-
{
|
|
237
|
-
indexB++;
|
|
238
|
-
}
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
else if(ignoreKeysIncludesOwnMapKey)
|
|
242
|
-
{
|
|
243
|
-
indexB++;
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if(!LeUtils.equals(mapKey, ownMapKey) || !LeUtils.equals(mapValue, ownMapValue))
|
|
248
|
-
{
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
indexA++;
|
|
252
|
-
indexB++;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
while((indexA < elementsA.length) && ignoreKeys.includes(elementsA[indexA][0]))
|
|
256
|
-
{
|
|
257
|
-
indexA++;
|
|
258
|
-
}
|
|
259
|
-
if(indexA < elementsA.length)
|
|
260
|
-
{
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
while((indexB < elementsB.length) && ignoreKeys.includes(elementsB[indexB][0]))
|
|
265
|
-
{
|
|
266
|
-
indexB++;
|
|
267
|
-
}
|
|
268
|
-
return (indexB >= elementsB.length);
|
|
269
|
-
};
|
|
270
|
-
})(),
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Returns a deep copy of the given value.
|
|
274
|
-
*
|
|
275
|
-
* @param {*} value
|
|
276
|
-
* @returns {*}
|
|
277
|
-
*/
|
|
278
|
-
clone:
|
|
279
|
-
(value) => CloneDeep(value, true),
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Executes the given callback when the document is ready.
|
|
283
|
-
*
|
|
284
|
-
* @param {Function} callback
|
|
285
|
-
* @returns {{remove:Function}}
|
|
286
|
-
*/
|
|
287
|
-
onDomReady:
|
|
288
|
-
(callback) =>
|
|
289
|
-
{
|
|
290
|
-
if(!globalThis?.document || !globalThis?.document?.addEventListener || !globalThis?.document?.removeEventListener)
|
|
291
|
-
{
|
|
292
|
-
// no document, so we can't wait for it to be ready
|
|
293
|
-
console.warn('LeUtils.onDomReady() was called, but there is no document available. This might happen if the code is executed in a non-browser environment.');
|
|
294
|
-
return {
|
|
295
|
-
remove:() =>
|
|
296
|
-
{
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if((globalThis.document.readyState === 'interactive') || (globalThis.document.readyState === 'complete'))
|
|
302
|
-
{
|
|
303
|
-
return LeUtils.setTimeout(() => callback(), 0);
|
|
304
|
-
}
|
|
305
|
-
else
|
|
306
|
-
{
|
|
307
|
-
let listening = true;
|
|
308
|
-
const callbackWrapper = () =>
|
|
309
|
-
{
|
|
310
|
-
if(listening)
|
|
311
|
-
{
|
|
312
|
-
listening = false;
|
|
313
|
-
globalThis.document.removeEventListener('DOMContentLoaded', callbackWrapper);
|
|
314
|
-
callback();
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
globalThis.document.addEventListener('DOMContentLoaded', callbackWrapper);
|
|
319
|
-
|
|
320
|
-
return {
|
|
321
|
-
remove:
|
|
322
|
-
() =>
|
|
323
|
-
{
|
|
324
|
-
if(listening)
|
|
325
|
-
{
|
|
326
|
-
listening = false;
|
|
327
|
-
globalThis.document.removeEventListener('DOMContentLoaded', callbackWrapper);
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Parses the given version string, and returns an object with the major, minor, and patch numbers, as well as some comparison functions.
|
|
336
|
-
*
|
|
337
|
-
* Expects a version string such as:
|
|
338
|
-
* - "1"
|
|
339
|
-
* - "1.2"
|
|
340
|
-
* - "1.2.3"
|
|
341
|
-
* - "1.2.3 anything"
|
|
342
|
-
* - "1.2.3-anything"
|
|
343
|
-
*
|
|
344
|
-
* @param {string|*} versionString
|
|
345
|
-
* @returns {{major: (number), minor: (number), patch: (number), toString: (function(): string), equals: (function(string|*): boolean), smallerThan: (function(string|*): boolean), smallerThanOrEquals: (function(string|*): boolean), largerThan: (function(string|*): boolean), largerThanOrEquals: (function(string|*): boolean)}}
|
|
346
|
-
*/
|
|
347
|
-
parseVersionString:
|
|
348
|
-
(versionString) =>
|
|
349
|
-
{
|
|
350
|
-
if(IS_OBJECT(versionString) && ISSET(versionString?.major) && ISSET(versionString?.minor) && ISSET(versionString?.patch))
|
|
351
|
-
{
|
|
352
|
-
return versionString;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
versionString = STRING(versionString).trim();
|
|
356
|
-
const partsVersion = versionString.split(' ')[0].split('-')[0].split('.');
|
|
357
|
-
const major = INT_LAX(partsVersion[0]);
|
|
358
|
-
const minor = INT_LAX(partsVersion[1]);
|
|
359
|
-
const patch = INT_LAX(partsVersion[2]);
|
|
360
|
-
|
|
361
|
-
const THIS = {
|
|
362
|
-
major:major,
|
|
363
|
-
minor:minor,
|
|
364
|
-
patch:patch,
|
|
365
|
-
|
|
366
|
-
toString:
|
|
367
|
-
() => major + '.' + minor + '.' + patch,
|
|
368
|
-
|
|
369
|
-
equals:
|
|
370
|
-
(otherVersion) =>
|
|
371
|
-
{
|
|
372
|
-
otherVersion = LeUtils.parseVersionString(otherVersion);
|
|
373
|
-
return (major === otherVersion.major) && (minor === otherVersion.minor) && (patch === otherVersion.patch);
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
largerThan:
|
|
377
|
-
(otherVersion) =>
|
|
378
|
-
{
|
|
379
|
-
otherVersion = LeUtils.parseVersionString(otherVersion);
|
|
380
|
-
|
|
381
|
-
if(major > otherVersion.major)
|
|
382
|
-
{
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
if(major < otherVersion.major)
|
|
386
|
-
{
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if(minor > otherVersion.minor)
|
|
391
|
-
{
|
|
392
|
-
return true;
|
|
393
|
-
}
|
|
394
|
-
if(minor < otherVersion.minor)
|
|
395
|
-
{
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return (patch > otherVersion.patch);
|
|
400
|
-
},
|
|
401
|
-
|
|
402
|
-
largerThanOrEquals:
|
|
403
|
-
(otherVersion) =>
|
|
404
|
-
{
|
|
405
|
-
otherVersion = LeUtils.parseVersionString(otherVersion);
|
|
406
|
-
|
|
407
|
-
if(major > otherVersion.major)
|
|
408
|
-
{
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
if(major < otherVersion.major)
|
|
412
|
-
{
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if(minor > otherVersion.minor)
|
|
417
|
-
{
|
|
418
|
-
return true;
|
|
419
|
-
}
|
|
420
|
-
if(minor < otherVersion.minor)
|
|
421
|
-
{
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return (patch >= otherVersion.patch);
|
|
426
|
-
},
|
|
427
|
-
|
|
428
|
-
smallerThan:
|
|
429
|
-
(otherVersion) => !THIS.largerThanOrEquals(otherVersion),
|
|
430
|
-
|
|
431
|
-
smallerThanOrEquals:
|
|
432
|
-
(otherVersion) => !THIS.largerThan(otherVersion),
|
|
433
|
-
};
|
|
434
|
-
return THIS;
|
|
435
|
-
},
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Returns true if the array or object contains the given value.
|
|
439
|
-
*
|
|
440
|
-
* Values are compared by casting both of them to a string.
|
|
441
|
-
*
|
|
442
|
-
* @param {*[]|Object|Function} array
|
|
443
|
-
* @param {*} value
|
|
444
|
-
* @returns {boolean}
|
|
445
|
-
*/
|
|
446
|
-
contains:
|
|
447
|
-
(array, value) =>
|
|
448
|
-
{
|
|
449
|
-
if(!array)
|
|
450
|
-
{
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
let result = false;
|
|
454
|
-
value = STRING(value);
|
|
455
|
-
LeUtils.each(array, (val) =>
|
|
456
|
-
{
|
|
457
|
-
if(STRING(val) === value)
|
|
458
|
-
{
|
|
459
|
-
result = true;
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
return result;
|
|
464
|
-
},
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Returns true if the array or object contains the given value.
|
|
468
|
-
*
|
|
469
|
-
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
470
|
-
*
|
|
471
|
-
* @param {*[]|Object|Function} array
|
|
472
|
-
* @param {*} value
|
|
473
|
-
* @returns {boolean}
|
|
474
|
-
*/
|
|
475
|
-
containsCaseInsensitive:
|
|
476
|
-
(array, value) =>
|
|
477
|
-
{
|
|
478
|
-
if(!array)
|
|
479
|
-
{
|
|
480
|
-
return false;
|
|
481
|
-
}
|
|
482
|
-
let result = false;
|
|
483
|
-
value = STRING(value).toLowerCase();
|
|
484
|
-
LeUtils.each(array, (val) =>
|
|
485
|
-
{
|
|
486
|
-
if(STRING(val).toLowerCase() === value)
|
|
487
|
-
{
|
|
488
|
-
result = true;
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
return result;
|
|
493
|
-
},
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Returns true if the array or object contains all the given values.
|
|
497
|
-
*
|
|
498
|
-
* Values are compared by casting both of them to a string.
|
|
499
|
-
*
|
|
500
|
-
* @param {*[]|Object|Function} array
|
|
501
|
-
* @param {*[]|Object|Function} values
|
|
502
|
-
* @returns {boolean}
|
|
503
|
-
*/
|
|
504
|
-
containsAll:
|
|
505
|
-
(array, values) =>
|
|
506
|
-
{
|
|
507
|
-
if(!array)
|
|
508
|
-
{
|
|
509
|
-
return false;
|
|
510
|
-
}
|
|
511
|
-
let result = true;
|
|
512
|
-
LeUtils.each(values, (value) =>
|
|
513
|
-
{
|
|
514
|
-
if(!LeUtils.contains(array, value))
|
|
515
|
-
{
|
|
516
|
-
result = false;
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
return result;
|
|
521
|
-
},
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Returns true if the array or object contains all the given values.
|
|
525
|
-
*
|
|
526
|
-
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
527
|
-
*
|
|
528
|
-
* @param {*[]|Object|Function} array
|
|
529
|
-
* @param {*[]|Object|Function} values
|
|
530
|
-
* @returns {boolean}
|
|
531
|
-
*/
|
|
532
|
-
containsAllCaseInsensitive:
|
|
533
|
-
(array, values) =>
|
|
534
|
-
{
|
|
535
|
-
if(!array)
|
|
536
|
-
{
|
|
537
|
-
return false;
|
|
538
|
-
}
|
|
539
|
-
let result = true;
|
|
540
|
-
LeUtils.each(values, (value) =>
|
|
541
|
-
{
|
|
542
|
-
if(!LeUtils.containsCaseInsensitive(array, value))
|
|
543
|
-
{
|
|
544
|
-
result = false;
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
return result;
|
|
549
|
-
},
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Returns true if the array or object contains any of the given values.
|
|
553
|
-
*
|
|
554
|
-
* Values are compared by casting both of them to a string.
|
|
555
|
-
*
|
|
556
|
-
* @param {*[]|Object|Function} array
|
|
557
|
-
* @param {*[]|Object|Function} values
|
|
558
|
-
* @returns {boolean}
|
|
559
|
-
*/
|
|
560
|
-
containsAny:
|
|
561
|
-
(array, values) =>
|
|
562
|
-
{
|
|
563
|
-
if(!array)
|
|
564
|
-
{
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
let result = false;
|
|
568
|
-
LeUtils.each(values, (value) =>
|
|
569
|
-
{
|
|
570
|
-
if(LeUtils.contains(array, value))
|
|
571
|
-
{
|
|
572
|
-
result = true;
|
|
573
|
-
return false;
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
return result;
|
|
577
|
-
},
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Returns true if the array or object contains any of the given values.
|
|
581
|
-
*
|
|
582
|
-
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
583
|
-
*
|
|
584
|
-
* @param {*[]|Object|Function} array
|
|
585
|
-
* @param {*[]|Object|Function} values
|
|
586
|
-
* @returns {boolean}
|
|
587
|
-
*/
|
|
588
|
-
containsAnyCaseInsensitive:
|
|
589
|
-
(array, values) =>
|
|
590
|
-
{
|
|
591
|
-
if(!array)
|
|
592
|
-
{
|
|
593
|
-
return false;
|
|
594
|
-
}
|
|
595
|
-
let result = false;
|
|
596
|
-
LeUtils.each(values, (value) =>
|
|
597
|
-
{
|
|
598
|
-
if(LeUtils.containsCaseInsensitive(array, value))
|
|
599
|
-
{
|
|
600
|
-
result = true;
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
return result;
|
|
605
|
-
},
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Returns true if the array or object contains none of the given values.
|
|
609
|
-
*
|
|
610
|
-
* Values are compared by casting both of them to a string.
|
|
611
|
-
*
|
|
612
|
-
* @param {*[]|Object|Function} array
|
|
613
|
-
* @param {*[]|Object|Function} values
|
|
614
|
-
* @returns {boolean}
|
|
615
|
-
*/
|
|
616
|
-
containsNone:
|
|
617
|
-
(array, values) =>
|
|
618
|
-
{
|
|
619
|
-
if(!array)
|
|
620
|
-
{
|
|
621
|
-
return true;
|
|
622
|
-
}
|
|
623
|
-
let result = true;
|
|
624
|
-
LeUtils.each(values, (value) =>
|
|
625
|
-
{
|
|
626
|
-
if(LeUtils.contains(array, value))
|
|
627
|
-
{
|
|
628
|
-
result = false;
|
|
629
|
-
return false;
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
return result;
|
|
633
|
-
},
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Returns true if the array or object contains none of the given values.
|
|
637
|
-
*
|
|
638
|
-
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
639
|
-
*
|
|
640
|
-
* @param {*[]|Object|Function} array
|
|
641
|
-
* @param {*[]|Object|Function} values
|
|
642
|
-
* @returns {boolean}
|
|
643
|
-
*/
|
|
644
|
-
containsNoneCaseInsensitive:
|
|
645
|
-
(array, values) =>
|
|
646
|
-
{
|
|
647
|
-
if(!array)
|
|
648
|
-
{
|
|
649
|
-
return true;
|
|
650
|
-
}
|
|
651
|
-
let result = true;
|
|
652
|
-
LeUtils.each(values, (value) =>
|
|
653
|
-
{
|
|
654
|
-
if(LeUtils.containsCaseInsensitive(array, value))
|
|
655
|
-
{
|
|
656
|
-
result = false;
|
|
657
|
-
return false;
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
return result;
|
|
661
|
-
},
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Finds the first element in the given array or object that returns true from the callback, and returns an object with the index and value.
|
|
665
|
-
*
|
|
666
|
-
* @param {*[]|Object|Function} elements
|
|
667
|
-
* @param {(value:*, index:*) => boolean|void} callback
|
|
668
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
669
|
-
* @returns {{index:*, value:*}|null}
|
|
670
|
-
*/
|
|
671
|
-
findIndexValue:
|
|
672
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
673
|
-
{
|
|
674
|
-
let result = null;
|
|
675
|
-
LeUtils.each(elements, (value, index) =>
|
|
676
|
-
{
|
|
677
|
-
if(callback.call(elements[index], elements[index], index))
|
|
678
|
-
{
|
|
679
|
-
result = {index, value};
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
683
|
-
return result;
|
|
684
|
-
},
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Finds the first element in the given array or object that returns true from the callback, and returns the index.
|
|
688
|
-
*
|
|
689
|
-
* @param {*[]|Object|Function} elements
|
|
690
|
-
* @param {(value:*, index:*) => boolean|void} callback
|
|
691
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
692
|
-
* @returns {*|null}
|
|
693
|
-
*/
|
|
694
|
-
findIndex:
|
|
695
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) => LeUtils.findIndexValue(elements, callback, optionalSkipHasOwnPropertyCheck)?.index ?? null,
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Finds the first element in the given array or object that returns true from the callback, and returns the value.
|
|
699
|
-
*
|
|
700
|
-
* @param {*[]|Object|Function} elements
|
|
701
|
-
* @param {(value:*, index:*) => boolean|void} callback
|
|
702
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
703
|
-
* @returns {*|null}
|
|
704
|
-
*/
|
|
705
|
-
find:
|
|
706
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) => LeUtils.findIndexValue(elements, callback, optionalSkipHasOwnPropertyCheck)?.value ?? null,
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Returns the value at the given index in the given elements.
|
|
710
|
-
*
|
|
711
|
-
* @param {*} elements
|
|
712
|
-
* @param {*} index
|
|
713
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
714
|
-
* @returns {*}
|
|
715
|
-
*/
|
|
716
|
-
getValueAtIndex:
|
|
717
|
-
(elements, index, optionalSkipHasOwnPropertyCheck = false) =>
|
|
718
|
-
{
|
|
719
|
-
if((elements === null) || (typeof elements === 'undefined'))
|
|
720
|
-
{
|
|
721
|
-
return undefined;
|
|
722
|
-
}
|
|
723
|
-
if(Array.isArray(elements))
|
|
724
|
-
{
|
|
725
|
-
return elements[index];
|
|
726
|
-
}
|
|
727
|
-
if((typeof elements === 'object') && (elements?.constructor === Object))
|
|
728
|
-
{
|
|
729
|
-
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
|
|
730
|
-
{
|
|
731
|
-
return elements[index];
|
|
732
|
-
}
|
|
733
|
-
return undefined;
|
|
734
|
-
}
|
|
735
|
-
if(elements instanceof Map)
|
|
736
|
-
{
|
|
737
|
-
return elements.get(index);
|
|
738
|
-
}
|
|
739
|
-
if(elements instanceof Set)
|
|
740
|
-
{
|
|
741
|
-
return index;
|
|
742
|
-
}
|
|
743
|
-
if(typeof elements !== 'string')
|
|
744
|
-
{
|
|
745
|
-
if(ArrayBuffer.isView(elements) && !(elements instanceof DataView))
|
|
746
|
-
{
|
|
747
|
-
return elements[index];
|
|
748
|
-
}
|
|
749
|
-
if(typeof elements?.[Symbol.iterator] === 'function')
|
|
750
|
-
{
|
|
751
|
-
let i = 0;
|
|
752
|
-
for(const value of elements)
|
|
753
|
-
{
|
|
754
|
-
if(i === index)
|
|
755
|
-
{
|
|
756
|
-
return value;
|
|
757
|
-
}
|
|
758
|
-
i++;
|
|
759
|
-
}
|
|
760
|
-
return undefined;
|
|
761
|
-
}
|
|
762
|
-
if(typeof elements?.forEach === 'function')
|
|
763
|
-
{
|
|
764
|
-
let result = undefined;
|
|
765
|
-
let shouldContinue = true;
|
|
766
|
-
elements.forEach((value, i) =>
|
|
767
|
-
{
|
|
768
|
-
if(shouldContinue)
|
|
769
|
-
{
|
|
770
|
-
if(i === index)
|
|
771
|
-
{
|
|
772
|
-
result = value;
|
|
773
|
-
shouldContinue = false;
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
return result;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if((typeof elements === 'object') || (typeof elements === 'function'))
|
|
781
|
-
{
|
|
782
|
-
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
|
|
783
|
-
{
|
|
784
|
-
return elements[index];
|
|
785
|
-
}
|
|
786
|
-
return undefined;
|
|
787
|
-
}
|
|
788
|
-
return undefined;
|
|
789
|
-
},
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* Checks if the given elements can be iterated over using LeUtils.each().
|
|
793
|
-
*
|
|
794
|
-
* @param {*} elements
|
|
795
|
-
* @returns {boolean}
|
|
796
|
-
*/
|
|
797
|
-
supportsEach:
|
|
798
|
-
(elements) =>
|
|
799
|
-
{
|
|
800
|
-
if((elements === null) || (typeof elements === 'undefined') || (typeof elements === 'string'))
|
|
801
|
-
{
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
804
|
-
return !!(
|
|
805
|
-
(Array.isArray(elements))
|
|
806
|
-
|| ((typeof elements === 'object') && (elements?.constructor === Object))
|
|
807
|
-
|| (typeof elements?.[Symbol.iterator] === 'function')
|
|
808
|
-
|| (typeof elements?.forEach === 'function')
|
|
809
|
-
|| ((typeof elements === 'object') || (typeof elements === 'function'))
|
|
810
|
-
);
|
|
811
|
-
},
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Returns an iterator that iterates over each element in the given array or object, yielding an array with the value and the index/key.
|
|
815
|
-
*
|
|
816
|
-
* @param {*} elements
|
|
817
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
818
|
-
* @yields {[key:*, value:*]}
|
|
819
|
-
*/
|
|
820
|
-
eachIterator:
|
|
821
|
-
function* (elements, optionalSkipHasOwnPropertyCheck = false)
|
|
822
|
-
{
|
|
823
|
-
if((elements === null) || (typeof elements === 'undefined'))
|
|
824
|
-
{
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
|
-
if(Array.isArray(elements))
|
|
828
|
-
{
|
|
829
|
-
for(let i = 0; i < elements.length; i++)
|
|
830
|
-
{
|
|
831
|
-
yield [elements[i], i];
|
|
832
|
-
}
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
if((typeof elements === 'object') && (elements?.constructor === Object))
|
|
836
|
-
{
|
|
837
|
-
for(const i in elements)
|
|
838
|
-
{
|
|
839
|
-
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, i))
|
|
840
|
-
{
|
|
841
|
-
yield [elements[i], i];
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
if(elements instanceof Map)
|
|
847
|
-
{
|
|
848
|
-
for(const [i, value] of elements)
|
|
849
|
-
{
|
|
850
|
-
yield [value, i];
|
|
851
|
-
}
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
if(elements instanceof Set)
|
|
855
|
-
{
|
|
856
|
-
for(const value of elements)
|
|
857
|
-
{
|
|
858
|
-
yield [value, value];
|
|
859
|
-
}
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
if(typeof elements !== 'string')
|
|
863
|
-
{
|
|
864
|
-
if(typeof elements?.[Symbol.iterator] === 'function')
|
|
865
|
-
{
|
|
866
|
-
let i = 0;
|
|
867
|
-
for(const value of elements)
|
|
868
|
-
{
|
|
869
|
-
yield [value, i];
|
|
870
|
-
i++;
|
|
871
|
-
}
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
if(typeof elements?.forEach === 'function')
|
|
875
|
-
{
|
|
876
|
-
const buffer = [];
|
|
877
|
-
elements.forEach((value, i) =>
|
|
878
|
-
{
|
|
879
|
-
buffer.push([value, i]);
|
|
880
|
-
});
|
|
881
|
-
for(const entry of buffer)
|
|
882
|
-
{
|
|
883
|
-
yield entry;
|
|
884
|
-
}
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
if((typeof elements === 'object') || (typeof elements === 'function'))
|
|
889
|
-
{
|
|
890
|
-
for(const i in elements)
|
|
891
|
-
{
|
|
892
|
-
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, i))
|
|
893
|
-
{
|
|
894
|
-
yield [elements[i], i];
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
console.warn('Executed LeUtils.eachIterator() on an invalid type: [' + (typeof elements) + ']', elements);
|
|
900
|
-
},
|
|
901
|
-
|
|
902
|
-
/**
|
|
903
|
-
* Loops through each element in the given array or object, and calls the callback for each element.
|
|
904
|
-
*
|
|
905
|
-
* @param {*} elements
|
|
906
|
-
* @param {(value:*, index?:*) => boolean|void} callback
|
|
907
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
908
|
-
* @returns {*}
|
|
909
|
-
*/
|
|
910
|
-
each:
|
|
911
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
912
|
-
{
|
|
913
|
-
for(const [value, key] of LeUtils.eachIterator(elements, optionalSkipHasOwnPropertyCheck))
|
|
914
|
-
{
|
|
915
|
-
if(callback.call(value, value, key) === false)
|
|
916
|
-
{
|
|
917
|
-
break;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return elements;
|
|
921
|
-
},
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
* Like LeUtils.each(), except that it expects an async callback.
|
|
925
|
-
*
|
|
926
|
-
* @param {*} elements
|
|
927
|
-
* @param {(value:*, index?:*) => Promise<boolean|undefined>} asyncCallback
|
|
928
|
-
* @param {number} [optionalParallelCount]
|
|
929
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
930
|
-
* @returns {Promise<*>}
|
|
931
|
-
*/
|
|
932
|
-
eachAsync:
|
|
933
|
-
(() =>
|
|
934
|
-
{
|
|
935
|
-
/**
|
|
936
|
-
* Instead of waiting for every promise individually, this function will queue up multiple promises at once, then wait for any of them to finish, before adding more, until it has looped through all elements.
|
|
937
|
-
* Then, at the end, it will wait for the remaining promises to finish.
|
|
938
|
-
*/
|
|
939
|
-
const eachAsyncParallel = async (elements, asyncCallback, optionalParallelCount, optionalSkipHasOwnPropertyCheck) =>
|
|
940
|
-
{
|
|
941
|
-
const runningPromises = new Set();
|
|
942
|
-
let doBreak = false;
|
|
943
|
-
await LeUtils.eachAsync(elements, async (value, index) =>// loop through each element
|
|
944
|
-
{
|
|
945
|
-
if(doBreak)
|
|
946
|
-
{
|
|
947
|
-
return false;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// if no spot is available, wait for one to finish
|
|
951
|
-
while(runningPromises.size >= optionalParallelCount)
|
|
952
|
-
{
|
|
953
|
-
await Promise.race(runningPromises);
|
|
954
|
-
if(doBreak)
|
|
955
|
-
{
|
|
956
|
-
return false;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// process this element, by creating a promise, and adding it to the queue
|
|
961
|
-
const promise = (async () =>
|
|
962
|
-
{
|
|
963
|
-
if((await asyncCallback.call(value, value, index)) === false)
|
|
964
|
-
{
|
|
965
|
-
doBreak = true;
|
|
966
|
-
}
|
|
967
|
-
})();
|
|
968
|
-
runningPromises.add(promise);
|
|
969
|
-
promise.finally(() =>
|
|
970
|
-
{
|
|
971
|
-
runningPromises.delete(promise);
|
|
972
|
-
});
|
|
973
|
-
}, 1, optionalSkipHasOwnPropertyCheck);
|
|
974
|
-
await Promise.all(runningPromises);
|
|
975
|
-
return elements;
|
|
976
|
-
};
|
|
977
|
-
|
|
978
|
-
return async (elements, asyncCallback, parallelCount = 1, optionalSkipHasOwnPropertyCheck = false) =>
|
|
979
|
-
{
|
|
980
|
-
if((elements !== null) && (typeof elements !== 'undefined'))
|
|
981
|
-
{
|
|
982
|
-
parallelCount = INT_LAX(parallelCount);
|
|
983
|
-
if(parallelCount > 1)
|
|
984
|
-
{
|
|
985
|
-
return await eachAsyncParallel(elements, asyncCallback, parallelCount, optionalSkipHasOwnPropertyCheck);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
for(const [value, key] of LeUtils.eachIterator(elements, optionalSkipHasOwnPropertyCheck))
|
|
989
|
-
{
|
|
990
|
-
if((await asyncCallback.call(value, value, key)) === false)
|
|
991
|
-
{
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
return elements;
|
|
997
|
-
};
|
|
998
|
-
})(),
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* Returns an empty simplified collection (array, object, or Map), based on the given elements.
|
|
1002
|
-
*
|
|
1003
|
-
* Usage:
|
|
1004
|
-
*
|
|
1005
|
-
* ```js
|
|
1006
|
-
* const [success, collection, add] = LeUtils.getEmptySimplifiedCollection(elements);
|
|
1007
|
-
* ```
|
|
1008
|
-
*
|
|
1009
|
-
* @param {*} elements
|
|
1010
|
-
* @returns {[boolean, *[]|Object|Map, (value:*,index:*)=>void]}
|
|
1011
|
-
*/
|
|
1012
|
-
getEmptySimplifiedCollection:
|
|
1013
|
-
(elements) =>
|
|
1014
|
-
{
|
|
1015
|
-
if((elements === null) || (typeof elements === 'undefined'))
|
|
1016
|
-
{
|
|
1017
|
-
return [false, [], (value, index) =>
|
|
1018
|
-
{
|
|
1019
|
-
}];
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
let collection = null;
|
|
1023
|
-
let add = null;
|
|
1024
|
-
if(Array.isArray(elements))
|
|
1025
|
-
{
|
|
1026
|
-
collection = [];
|
|
1027
|
-
add = (value, index) =>
|
|
1028
|
-
{
|
|
1029
|
-
collection.push(value);
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
else if((typeof elements === 'object') && (elements?.constructor === Object))
|
|
1033
|
-
{
|
|
1034
|
-
collection = {};
|
|
1035
|
-
add = (value, index) =>
|
|
1036
|
-
{
|
|
1037
|
-
collection[index] = value;
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
else if(elements instanceof Map)
|
|
1041
|
-
{
|
|
1042
|
-
collection = new Map();
|
|
1043
|
-
add = (value, index) =>
|
|
1044
|
-
{
|
|
1045
|
-
collection.set(index, value);
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
else if((typeof elements !== 'string') && ((typeof elements?.[Symbol.iterator] === 'function') || (typeof elements?.forEach === 'function')))
|
|
1049
|
-
{
|
|
1050
|
-
collection = [];
|
|
1051
|
-
add = (value, index) =>
|
|
1052
|
-
{
|
|
1053
|
-
collection.push(value);
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
else if((typeof elements === 'object') || (typeof elements === 'function'))
|
|
1057
|
-
{
|
|
1058
|
-
collection = {};
|
|
1059
|
-
add = (value, index) =>
|
|
1060
|
-
{
|
|
1061
|
-
collection[index] = value;
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
else
|
|
1065
|
-
{
|
|
1066
|
-
console.warn('Executed LeUtils.getEmptySimplifiedCollection() on an invalid type: [' + (typeof elements) + ']', elements);
|
|
1067
|
-
return [false, [], (value, index) =>
|
|
1068
|
-
{
|
|
1069
|
-
}];
|
|
1070
|
-
}
|
|
1071
|
-
return [true, collection, add];
|
|
1072
|
-
},
|
|
1073
|
-
|
|
1074
|
-
/**
|
|
1075
|
-
* Loops through the given elements, and returns a new collection, with only the elements that returned true (or a value equals to true) from the callback.
|
|
1076
|
-
* If no callback is given, it will return all elements that are of a true value (for example, values that are: not null, not undefined, not false, not 0, not an empty string, not an empty array, not an empty object).
|
|
1077
|
-
*
|
|
1078
|
-
* @param {*} elements
|
|
1079
|
-
* @param {(value:*, index:*) => boolean|undefined} [callback]
|
|
1080
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
1081
|
-
* @returns {*}
|
|
1082
|
-
*/
|
|
1083
|
-
filter:
|
|
1084
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1085
|
-
{
|
|
1086
|
-
const [success, collection, add] = LeUtils.getEmptySimplifiedCollection(elements);
|
|
1087
|
-
if(!success)
|
|
1088
|
-
{
|
|
1089
|
-
return elements;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
LeUtils.each(elements, (value, index) =>
|
|
1093
|
-
{
|
|
1094
|
-
if(!callback)
|
|
1095
|
-
{
|
|
1096
|
-
if(value)
|
|
1097
|
-
{
|
|
1098
|
-
add(value, index);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
else if(callback.call(value, value, index))
|
|
1102
|
-
{
|
|
1103
|
-
add(value, index);
|
|
1104
|
-
}
|
|
1105
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
1106
|
-
return collection;
|
|
1107
|
-
},
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
* Loops through the given elements, and returns a new collection, with the elements that were returned from the callback.
|
|
1111
|
-
*
|
|
1112
|
-
* @param {*} elements
|
|
1113
|
-
* @param {(value:*, index:*) => *} [callback]
|
|
1114
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
1115
|
-
* @returns {*}
|
|
1116
|
-
*/
|
|
1117
|
-
map:
|
|
1118
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1119
|
-
{
|
|
1120
|
-
const [success, collection, add] = LeUtils.getEmptySimplifiedCollection(elements);
|
|
1121
|
-
if(!success)
|
|
1122
|
-
{
|
|
1123
|
-
return elements;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
LeUtils.each(elements, (value, index) =>
|
|
1127
|
-
{
|
|
1128
|
-
if(!callback)
|
|
1129
|
-
{
|
|
1130
|
-
add(value, index);
|
|
1131
|
-
}
|
|
1132
|
-
else
|
|
1133
|
-
{
|
|
1134
|
-
add(callback.call(value, value, index), index);
|
|
1135
|
-
}
|
|
1136
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
1137
|
-
return collection;
|
|
1138
|
-
},
|
|
1139
|
-
|
|
1140
|
-
/**
|
|
1141
|
-
* Loops through the given elements, and returns a new array, with the elements that were returned from the callback. Always returns an array.
|
|
1142
|
-
*
|
|
1143
|
-
* @param {*} elements
|
|
1144
|
-
* @param {(value:*, index:*) => *} [callback]
|
|
1145
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
1146
|
-
* @returns {*[]}
|
|
1147
|
-
*/
|
|
1148
|
-
mapToArray:
|
|
1149
|
-
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1150
|
-
{
|
|
1151
|
-
let result = [];
|
|
1152
|
-
LeUtils.each(elements, (value, index) =>
|
|
1153
|
-
{
|
|
1154
|
-
if(!callback)
|
|
1155
|
-
{
|
|
1156
|
-
result.push(value);
|
|
1157
|
-
}
|
|
1158
|
-
else
|
|
1159
|
-
{
|
|
1160
|
-
result.push(callback.call(value, value, index));
|
|
1161
|
-
}
|
|
1162
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
1163
|
-
return result;
|
|
1164
|
-
},
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Loops through the given elements, and returns a new array, with the elements that were returned from the callback. The elements will be sorted by the result from the given comparator. Always returns an array.
|
|
1168
|
-
*
|
|
1169
|
-
* @param {*} elements
|
|
1170
|
-
* @param {(valueA:*, valueB:*) => number} comparator
|
|
1171
|
-
* @param {(value:*, index:*) => *} [callback]
|
|
1172
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
1173
|
-
* @returns {*[]}
|
|
1174
|
-
*/
|
|
1175
|
-
mapToArraySorted:
|
|
1176
|
-
(elements, comparator, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1177
|
-
{
|
|
1178
|
-
const keys = LeUtils.sortKeys(elements, comparator, optionalSkipHasOwnPropertyCheck);
|
|
1179
|
-
let result = [];
|
|
1180
|
-
for(const key of keys)
|
|
1181
|
-
{
|
|
1182
|
-
const value = LeUtils.getValueAtIndex(elements, key, optionalSkipHasOwnPropertyCheck);
|
|
1183
|
-
if(!callback)
|
|
1184
|
-
{
|
|
1185
|
-
result.push(value);
|
|
1186
|
-
}
|
|
1187
|
-
else
|
|
1188
|
-
{
|
|
1189
|
-
result.push(callback.call(value, value, key));
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
return result;
|
|
1193
|
-
},
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Loops through the given elements, and returns a new array, with the keys from the given elements, sorted by the result from the given comparator. Always returns an array.
|
|
1197
|
-
*
|
|
1198
|
-
* @param {*} elements
|
|
1199
|
-
* @param {(valueA:*, valueB:*) => number} comparator
|
|
1200
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
1201
|
-
* @returns {*[]}
|
|
1202
|
-
*/
|
|
1203
|
-
sortKeys:
|
|
1204
|
-
(elements, comparator, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1205
|
-
{
|
|
1206
|
-
let keys = [];
|
|
1207
|
-
LeUtils.each(elements, (value, index) =>
|
|
1208
|
-
{
|
|
1209
|
-
keys.push(index);
|
|
1210
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
1211
|
-
|
|
1212
|
-
keys.sort((a, b) => comparator(LeUtils.getValueAtIndex(elements, a, optionalSkipHasOwnPropertyCheck), LeUtils.getValueAtIndex(elements, b, optionalSkipHasOwnPropertyCheck)));
|
|
1213
|
-
return keys;
|
|
1214
|
-
},
|
|
1215
|
-
|
|
1216
|
-
/**
|
|
1217
|
-
* Turns the given value(s) into a 1 dimensional array.
|
|
1218
|
-
*
|
|
1219
|
-
* Does the same thing as Array.flat(Infinity).
|
|
1220
|
-
*
|
|
1221
|
-
* @param {*} array
|
|
1222
|
-
* @returns {*[]}
|
|
1223
|
-
*/
|
|
1224
|
-
flattenArray:
|
|
1225
|
-
(() =>
|
|
1226
|
-
{
|
|
1227
|
-
const flattenArrayRecursive = (result, array) =>
|
|
1228
|
-
{
|
|
1229
|
-
if(!Array.isArray(array))
|
|
1230
|
-
{
|
|
1231
|
-
result.push(array);
|
|
1232
|
-
return;
|
|
1233
|
-
}
|
|
1234
|
-
array.forEach((entry) =>
|
|
1235
|
-
{
|
|
1236
|
-
flattenArrayRecursive(result, entry);
|
|
1237
|
-
});
|
|
1238
|
-
};
|
|
1239
|
-
|
|
1240
|
-
return (array) =>
|
|
1241
|
-
{
|
|
1242
|
-
if(!Array.isArray(array))
|
|
1243
|
-
{
|
|
1244
|
-
return [array];
|
|
1245
|
-
}
|
|
1246
|
-
let result = [];
|
|
1247
|
-
array.forEach((entry) =>
|
|
1248
|
-
{
|
|
1249
|
-
flattenArrayRecursive(result, entry);
|
|
1250
|
-
});
|
|
1251
|
-
return result;
|
|
1252
|
-
};
|
|
1253
|
-
})(),
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* Turns the given value(s) into a 1 dimensional array.
|
|
1257
|
-
*
|
|
1258
|
-
* Compared to LeUtils.flattenArray(), this function also supports objects, Maps, Sets, and other iterable objects.
|
|
1259
|
-
*
|
|
1260
|
-
* @param {*} elements
|
|
1261
|
-
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
1262
|
-
* @returns {*[]}
|
|
1263
|
-
*/
|
|
1264
|
-
flattenToArray:
|
|
1265
|
-
(() =>
|
|
1266
|
-
{
|
|
1267
|
-
const flattenToArrayRecursive = (result, elements, optionalSkipHasOwnPropertyCheck) =>
|
|
1268
|
-
{
|
|
1269
|
-
if(!LeUtils.supportsEach(elements))
|
|
1270
|
-
{
|
|
1271
|
-
result.push(elements);
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
LeUtils.each(elements, entry =>
|
|
1275
|
-
{
|
|
1276
|
-
flattenToArrayRecursive(result, entry, optionalSkipHasOwnPropertyCheck);
|
|
1277
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
1278
|
-
};
|
|
1279
|
-
|
|
1280
|
-
return (elements, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1281
|
-
{
|
|
1282
|
-
if(!LeUtils.supportsEach(elements))
|
|
1283
|
-
{
|
|
1284
|
-
return [elements];
|
|
1285
|
-
}
|
|
1286
|
-
let result = [];
|
|
1287
|
-
LeUtils.each(elements, entry =>
|
|
1288
|
-
{
|
|
1289
|
-
flattenToArrayRecursive(result, entry, optionalSkipHasOwnPropertyCheck);
|
|
1290
|
-
}, optionalSkipHasOwnPropertyCheck);
|
|
1291
|
-
return result;
|
|
1292
|
-
};
|
|
1293
|
-
})(),
|
|
1294
|
-
|
|
1295
|
-
/**
|
|
1296
|
-
* Compares two values. Primarily used for sorting.
|
|
1297
|
-
*
|
|
1298
|
-
* @param {*} a
|
|
1299
|
-
* @param {*} b
|
|
1300
|
-
* @returns {number}
|
|
1301
|
-
*/
|
|
1302
|
-
compare:
|
|
1303
|
-
(a, b) => (a < b) ? -1 : ((a > b) ? 1 : 0),
|
|
1304
|
-
|
|
1305
|
-
/**
|
|
1306
|
-
* Compares two numbers. Primarily used for sorting.
|
|
1307
|
-
*
|
|
1308
|
-
* @param {number} a
|
|
1309
|
-
* @param {number} b
|
|
1310
|
-
* @returns {number}
|
|
1311
|
-
*/
|
|
1312
|
-
compareNumbers:
|
|
1313
|
-
(a, b) => a - b,
|
|
1314
|
-
|
|
1315
|
-
/**
|
|
1316
|
-
* Compares two numeric strings. Primarily used for sorting.
|
|
1317
|
-
*
|
|
1318
|
-
* @param {string|number} a
|
|
1319
|
-
* @param {string|number} b
|
|
1320
|
-
* @returns {number}
|
|
1321
|
-
*/
|
|
1322
|
-
compareNumericStrings:
|
|
1323
|
-
(a, b) =>
|
|
1324
|
-
{
|
|
1325
|
-
const aParts = STRING(a).split('.');
|
|
1326
|
-
const bParts = STRING(b).split('.');
|
|
1327
|
-
for(let i = 0; i < Math.min(aParts.length, bParts.length); i++)
|
|
1328
|
-
{
|
|
1329
|
-
a = aParts[i].trim();
|
|
1330
|
-
b = bParts[i].trim();
|
|
1331
|
-
if(a.length !== b.length)
|
|
1332
|
-
{
|
|
1333
|
-
return (a.length < b.length) ? -1 : 1;
|
|
1334
|
-
}
|
|
1335
|
-
if(a !== b)
|
|
1336
|
-
{
|
|
1337
|
-
return (a < b) ? -1 : 1;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
if(aParts.length !== bParts.length)
|
|
1341
|
-
{
|
|
1342
|
-
return (aParts.length < bParts.length) ? -1 : 1;
|
|
1343
|
-
}
|
|
1344
|
-
return 0;
|
|
1345
|
-
},
|
|
1346
|
-
|
|
1347
|
-
/**
|
|
1348
|
-
* Compares two strings in a natural way, meaning that it will compare numbers in the strings as actual numbers.
|
|
1349
|
-
*
|
|
1350
|
-
* This will correctly sort numeric parts so that "file5.txt" comes before "file10.txt", as well as that "file/5/test.txt" comes before "file/10/test.txt".
|
|
1351
|
-
*
|
|
1352
|
-
* @param {string} a
|
|
1353
|
-
* @param {string} b
|
|
1354
|
-
* @returns {number}
|
|
1355
|
-
*/
|
|
1356
|
-
compareNaturalStrings:
|
|
1357
|
-
(a, b) =>
|
|
1358
|
-
{
|
|
1359
|
-
const re = /(\d+|\D+)/g; // split into runs of digits or non-digits
|
|
1360
|
-
const aTokens = a.match(re) ?? [];
|
|
1361
|
-
const bTokens = b.match(re) ?? [];
|
|
1362
|
-
|
|
1363
|
-
const len = Math.min(aTokens.length, bTokens.length);
|
|
1364
|
-
for(let i = 0; i < len; i++)
|
|
1365
|
-
{
|
|
1366
|
-
const x = aTokens[i];
|
|
1367
|
-
const y = bTokens[i];
|
|
1368
|
-
if(x === y)
|
|
1369
|
-
{
|
|
1370
|
-
continue;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
// if both are numbers, compare as numbers
|
|
1374
|
-
const nx = parseInt(x, 10);
|
|
1375
|
-
const ny = parseInt(y, 10);
|
|
1376
|
-
if(!isNaN(nx) && !isNaN(ny))
|
|
1377
|
-
{
|
|
1378
|
-
return nx - ny;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// otherwise compare lexically
|
|
1382
|
-
return x < y ? -1 : 1;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
return aTokens.length - bTokens.length;
|
|
1386
|
-
},
|
|
1387
|
-
|
|
1388
|
-
/**
|
|
1389
|
-
* Compares two strings generated by LeUtils.timestamp(). Primarily used for sorting.
|
|
1390
|
-
*
|
|
1391
|
-
* @param {string} a
|
|
1392
|
-
* @param {string} b
|
|
1393
|
-
* @returns {number}
|
|
1394
|
-
*/
|
|
1395
|
-
compareTimestampStrings:
|
|
1396
|
-
(a, b) =>
|
|
1397
|
-
{
|
|
1398
|
-
a = LeUtils.base64ToHex(STRING(a).replaceAll('-', '+').replaceAll('_', '/'));
|
|
1399
|
-
b = LeUtils.base64ToHex(STRING(b).replaceAll('-', '+').replaceAll('_', '/'));
|
|
1400
|
-
return LeUtils.compare(a, b);
|
|
1401
|
-
},
|
|
1402
|
-
|
|
1403
|
-
/**
|
|
1404
|
-
* Returns true if the given object is empty, false otherwise.
|
|
1405
|
-
*
|
|
1406
|
-
* @param {Object} obj
|
|
1407
|
-
* @param [optionalSkipHasOwnPropertyCheck]
|
|
1408
|
-
* @returns {boolean}
|
|
1409
|
-
*/
|
|
1410
|
-
isEmptyObject:
|
|
1411
|
-
(obj, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1412
|
-
{
|
|
1413
|
-
for(let field in obj)
|
|
1414
|
-
{
|
|
1415
|
-
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(obj, field))
|
|
1416
|
-
{
|
|
1417
|
-
return false;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
return true;
|
|
1421
|
-
},
|
|
1422
|
-
|
|
1423
|
-
/**
|
|
1424
|
-
* Returns the number of fields in the given object.
|
|
1425
|
-
*
|
|
1426
|
-
* @param {Object} obj
|
|
1427
|
-
* @param [optionalSkipHasOwnPropertyCheck]
|
|
1428
|
-
* @returns {number}
|
|
1429
|
-
*/
|
|
1430
|
-
getObjectFieldsCount:
|
|
1431
|
-
(obj, optionalSkipHasOwnPropertyCheck = false) =>
|
|
1432
|
-
{
|
|
1433
|
-
let count = 0;
|
|
1434
|
-
for(let field in obj)
|
|
1435
|
-
{
|
|
1436
|
-
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(obj, field))
|
|
1437
|
-
{
|
|
1438
|
-
count++;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
return count;
|
|
1442
|
-
},
|
|
1443
|
-
|
|
1444
|
-
/**
|
|
1445
|
-
* Returns true if the given function is a generator function (like: "function* (){}"), returns false otherwise.
|
|
1446
|
-
*
|
|
1447
|
-
* @param {Function} func
|
|
1448
|
-
* @returns {boolean}
|
|
1449
|
-
*/
|
|
1450
|
-
isGeneratorFunction:
|
|
1451
|
-
(() =>
|
|
1452
|
-
{
|
|
1453
|
-
const GeneratorFunction = function* ()
|
|
1454
|
-
{
|
|
1455
|
-
}.constructor;
|
|
1456
|
-
|
|
1457
|
-
const AsyncGeneratorFunction = async function* ()
|
|
1458
|
-
{
|
|
1459
|
-
}.constructor;
|
|
1460
|
-
|
|
1461
|
-
const RegularFunction = function()
|
|
1462
|
-
{
|
|
1463
|
-
}.constructor;
|
|
1464
|
-
|
|
1465
|
-
const PossibleGeneratorFunctionNames = Array.from(new Set(['GeneratorFunction', 'AsyncFunction', 'AsyncGeneratorFunction', GeneratorFunction.name, AsyncGeneratorFunction.name])).filter((element) =>
|
|
1466
|
-
{
|
|
1467
|
-
return (element && (element !== RegularFunction.name));
|
|
1468
|
-
});
|
|
1469
|
-
|
|
1470
|
-
return (func) =>
|
|
1471
|
-
{
|
|
1472
|
-
if(!func)
|
|
1473
|
-
{
|
|
1474
|
-
return false;
|
|
1475
|
-
}
|
|
1476
|
-
const constructor = func.constructor;
|
|
1477
|
-
if(!constructor)
|
|
1478
|
-
{
|
|
1479
|
-
return false;
|
|
1480
|
-
}
|
|
1481
|
-
// noinspection JSUnresolvedVariable
|
|
1482
|
-
return ((constructor.name && PossibleGeneratorFunctionNames.includes(constructor.name)) || (constructor.displayName && PossibleGeneratorFunctionNames.includes(constructor.displayName)));
|
|
1483
|
-
};
|
|
1484
|
-
})(),
|
|
1485
|
-
|
|
1486
|
-
/**
|
|
1487
|
-
* Executes the callback after the given number of milliseconds. Passes the elapsed time in seconds to the callback.
|
|
1488
|
-
*
|
|
1489
|
-
* To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = LeUtils.setTimeout((deltaTime)=>{}, 1000); timeoutHandler.remove();")
|
|
1490
|
-
*
|
|
1491
|
-
* @param {(deltaTime:number) => *} callback
|
|
1492
|
-
* @param {number} ms
|
|
1493
|
-
* @returns {{remove:Function}}
|
|
1494
|
-
*/
|
|
1495
|
-
setTimeout:
|
|
1496
|
-
(callback, ms) =>
|
|
1497
|
-
{
|
|
1498
|
-
if(!globalThis?.setTimeout || !globalThis?.clearTimeout)
|
|
1499
|
-
{
|
|
1500
|
-
console.warn('LeUtils.setTimeout() called in an environment without globalThis.setTimeout, returning a no-op handler.');
|
|
1501
|
-
return {
|
|
1502
|
-
remove:() =>
|
|
1503
|
-
{
|
|
1504
|
-
},
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
ms = FLOAT_LAX(ms);
|
|
1509
|
-
|
|
1510
|
-
let lastTime = globalThis?.performance?.now?.() ?? 0;
|
|
1511
|
-
/** @type {number|null} */
|
|
1512
|
-
let handler = globalThis.setTimeout(() =>
|
|
1513
|
-
{
|
|
1514
|
-
const currentTime = globalThis?.performance?.now?.() ?? 0;
|
|
1515
|
-
try
|
|
1516
|
-
{
|
|
1517
|
-
callback((currentTime - lastTime) / 1000);
|
|
1518
|
-
}
|
|
1519
|
-
catch(e)
|
|
1520
|
-
{
|
|
1521
|
-
console.error(e);
|
|
1522
|
-
}
|
|
1523
|
-
lastTime = currentTime;
|
|
1524
|
-
}, ms);
|
|
1525
|
-
|
|
1526
|
-
return {
|
|
1527
|
-
remove:
|
|
1528
|
-
() =>
|
|
1529
|
-
{
|
|
1530
|
-
if(handler !== null)
|
|
1531
|
-
{
|
|
1532
|
-
globalThis.clearTimeout(handler);
|
|
1533
|
-
handler = null;
|
|
1534
|
-
}
|
|
1535
|
-
},
|
|
1536
|
-
};
|
|
1537
|
-
},
|
|
1538
|
-
|
|
1539
|
-
/**
|
|
1540
|
-
* Executes the callback every given number of milliseconds. Passes the time difference in seconds between the last frame and now to it.
|
|
1541
|
-
*
|
|
1542
|
-
* To remove the interval, call remove() on the result of this function (example: "const intervalHandler = LeUtils.setInterval((deltaTime)=>{}, 1000); intervalHandler.remove();")
|
|
1543
|
-
*
|
|
1544
|
-
* @param {(deltaTime:number) => *} callback
|
|
1545
|
-
* @param {number} [intervalMs]
|
|
1546
|
-
* @param {boolean} [fireImmediately]
|
|
1547
|
-
* @returns {{remove:Function}}
|
|
1548
|
-
*/
|
|
1549
|
-
setInterval:
|
|
1550
|
-
(callback, intervalMs = 1000, fireImmediately = false) =>
|
|
1551
|
-
{
|
|
1552
|
-
intervalMs = FLOAT_LAX_ANY(intervalMs, 1000);
|
|
1553
|
-
|
|
1554
|
-
if(fireImmediately)
|
|
1555
|
-
{
|
|
1556
|
-
try
|
|
1557
|
-
{
|
|
1558
|
-
callback(0);
|
|
1559
|
-
}
|
|
1560
|
-
catch(e)
|
|
1561
|
-
{
|
|
1562
|
-
console.error(e);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
if(!globalThis?.setInterval || !globalThis?.clearInterval)
|
|
1567
|
-
{
|
|
1568
|
-
console.warn('LeUtils.setInterval() called in an environment without globalThis.setInterval, returning a no-op handler.');
|
|
1569
|
-
return {
|
|
1570
|
-
remove:() =>
|
|
1571
|
-
{
|
|
1572
|
-
},
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
let lastTime = globalThis?.performance?.now?.() ?? 0;
|
|
1577
|
-
/** @type {number|null} */
|
|
1578
|
-
let handler = globalThis.setInterval(() =>
|
|
1579
|
-
{
|
|
1580
|
-
const currentTime = globalThis?.performance?.now?.() ?? 0;
|
|
1581
|
-
try
|
|
1582
|
-
{
|
|
1583
|
-
callback((currentTime - lastTime) / 1000);
|
|
1584
|
-
}
|
|
1585
|
-
catch(e)
|
|
1586
|
-
{
|
|
1587
|
-
console.error(e);
|
|
1588
|
-
}
|
|
1589
|
-
lastTime = currentTime;
|
|
1590
|
-
}, intervalMs);
|
|
1591
|
-
|
|
1592
|
-
return {
|
|
1593
|
-
remove:
|
|
1594
|
-
() =>
|
|
1595
|
-
{
|
|
1596
|
-
if(handler !== null)
|
|
1597
|
-
{
|
|
1598
|
-
globalThis.clearInterval(handler);
|
|
1599
|
-
handler = null;
|
|
1600
|
-
}
|
|
1601
|
-
},
|
|
1602
|
-
};
|
|
1603
|
-
},
|
|
1604
|
-
|
|
1605
|
-
/**
|
|
1606
|
-
* Executes the callback after the given number of frames. Passes the elapsed time in seconds to the callback.
|
|
1607
|
-
*
|
|
1608
|
-
* To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = LeUtils.setAnimationFrameTimeout((deltaTime){}, 5); timeoutHandler.remove();")
|
|
1609
|
-
*
|
|
1610
|
-
* @param {(deltaTime:number) => *} callback
|
|
1611
|
-
* @param {number} [frames]
|
|
1612
|
-
* @returns {{remove:Function}}
|
|
1613
|
-
*/
|
|
1614
|
-
setAnimationFrameTimeout:
|
|
1615
|
-
(callback, frames = 1) =>
|
|
1616
|
-
{
|
|
1617
|
-
if(!globalThis?.requestAnimationFrame || !globalThis?.cancelAnimationFrame)
|
|
1618
|
-
{
|
|
1619
|
-
console.warn('LeUtils.setAnimationFrameTimeout() called in an environment without globalThis.requestAnimationFrame, returning a no-op handler.');
|
|
1620
|
-
return {
|
|
1621
|
-
remove:() =>
|
|
1622
|
-
{
|
|
1623
|
-
},
|
|
1624
|
-
};
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
frames = INT_LAX_ANY(frames, 1);
|
|
1628
|
-
|
|
1629
|
-
let run = true;
|
|
1630
|
-
let requestAnimationFrameId = null;
|
|
1631
|
-
let lastTime = globalThis?.performance?.now?.() ?? 0;
|
|
1632
|
-
const tick = () =>
|
|
1633
|
-
{
|
|
1634
|
-
if(run)
|
|
1635
|
-
{
|
|
1636
|
-
if(frames <= 0)
|
|
1637
|
-
{
|
|
1638
|
-
run = false;
|
|
1639
|
-
requestAnimationFrameId = null;
|
|
1640
|
-
const currentTime = globalThis?.performance?.now?.() ?? 0;
|
|
1641
|
-
try
|
|
1642
|
-
{
|
|
1643
|
-
callback((currentTime - lastTime) / 1000);
|
|
1644
|
-
}
|
|
1645
|
-
catch(e)
|
|
1646
|
-
{
|
|
1647
|
-
console.error(e);
|
|
1648
|
-
}
|
|
1649
|
-
lastTime = currentTime;
|
|
1650
|
-
return;
|
|
1651
|
-
}
|
|
1652
|
-
frames--;
|
|
1653
|
-
requestAnimationFrameId = globalThis.requestAnimationFrame(tick);
|
|
1654
|
-
}
|
|
1655
|
-
};
|
|
1656
|
-
tick();
|
|
1657
|
-
|
|
1658
|
-
return {
|
|
1659
|
-
remove:
|
|
1660
|
-
() =>
|
|
1661
|
-
{
|
|
1662
|
-
run = false;
|
|
1663
|
-
if(requestAnimationFrameId !== null)
|
|
1664
|
-
{
|
|
1665
|
-
globalThis.cancelAnimationFrame(requestAnimationFrameId);
|
|
1666
|
-
requestAnimationFrameId = null;
|
|
1667
|
-
}
|
|
1668
|
-
},
|
|
1669
|
-
};
|
|
1670
|
-
},
|
|
1671
|
-
|
|
1672
|
-
/**
|
|
1673
|
-
* Executes the callback every given number of frames. Passes the time difference in seconds between the last frame and now to it.
|
|
1674
|
-
*
|
|
1675
|
-
* To remove the interval, call remove() on the result of this function (example: "const intervalHandler = LeUtils.setAnimationFrameInterval((deltaTime)=>{}, 5); intervalHandler.remove();")
|
|
1676
|
-
*
|
|
1677
|
-
* @param {(deltaTime:number) => *} callback
|
|
1678
|
-
* @param {number} [intervalFrames]
|
|
1679
|
-
* @param {boolean} [fireImmediately]
|
|
1680
|
-
* @returns {{remove:Function}}
|
|
1681
|
-
*/
|
|
1682
|
-
setAnimationFrameInterval:
|
|
1683
|
-
(callback, intervalFrames = 1, fireImmediately = false) =>
|
|
1684
|
-
{
|
|
1685
|
-
intervalFrames = INT_LAX_ANY(intervalFrames, 1);
|
|
1686
|
-
|
|
1687
|
-
if(fireImmediately)
|
|
1688
|
-
{
|
|
1689
|
-
try
|
|
1690
|
-
{
|
|
1691
|
-
callback(0);
|
|
1692
|
-
}
|
|
1693
|
-
catch(e)
|
|
1694
|
-
{
|
|
1695
|
-
console.error(e);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
if(!globalThis?.requestAnimationFrame || !globalThis?.cancelAnimationFrame)
|
|
1700
|
-
{
|
|
1701
|
-
console.warn('LeUtils.setAnimationFrameInterval() called in an environment without globalThis.requestAnimationFrame, returning a no-op handler.');
|
|
1702
|
-
return {
|
|
1703
|
-
remove:() =>
|
|
1704
|
-
{
|
|
1705
|
-
},
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
let run = true;
|
|
1710
|
-
let requestAnimationFrameId = null;
|
|
1711
|
-
let lastTimestamp = 0;
|
|
1712
|
-
let totalTime = 0;
|
|
1713
|
-
let frames = intervalFrames;
|
|
1714
|
-
const tick = (timestamp) =>
|
|
1715
|
-
{
|
|
1716
|
-
if(run)
|
|
1717
|
-
{
|
|
1718
|
-
if(lastTimestamp === 0)
|
|
1719
|
-
{
|
|
1720
|
-
lastTimestamp = timestamp;
|
|
1721
|
-
}
|
|
1722
|
-
totalTime += (timestamp - lastTimestamp);
|
|
1723
|
-
lastTimestamp = timestamp;
|
|
1724
|
-
|
|
1725
|
-
frames--;
|
|
1726
|
-
if(frames <= 0)
|
|
1727
|
-
{
|
|
1728
|
-
try
|
|
1729
|
-
{
|
|
1730
|
-
callback(totalTime / 1000);
|
|
1731
|
-
}
|
|
1732
|
-
catch(e)
|
|
1733
|
-
{
|
|
1734
|
-
console.error(e);
|
|
1735
|
-
}
|
|
1736
|
-
totalTime = 0;
|
|
1737
|
-
frames = intervalFrames;
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
if(run)
|
|
1741
|
-
{
|
|
1742
|
-
requestAnimationFrameId = globalThis.requestAnimationFrame(tick);
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
};
|
|
1746
|
-
globalThis.requestAnimationFrame(tick);
|
|
1747
|
-
|
|
1748
|
-
return {
|
|
1749
|
-
remove:
|
|
1750
|
-
() =>
|
|
1751
|
-
{
|
|
1752
|
-
run = false;
|
|
1753
|
-
if(requestAnimationFrameId !== null)
|
|
1754
|
-
{
|
|
1755
|
-
globalThis.cancelAnimationFrame(requestAnimationFrameId);
|
|
1756
|
-
requestAnimationFrameId = null;
|
|
1757
|
-
}
|
|
1758
|
-
},
|
|
1759
|
-
};
|
|
1760
|
-
},
|
|
1761
|
-
|
|
1762
|
-
/**
|
|
1763
|
-
* Returns a promise, which will be resolved after the given number of milliseconds.
|
|
1764
|
-
*
|
|
1765
|
-
* @param {number} ms
|
|
1766
|
-
* @returns {Promise<number>}
|
|
1767
|
-
*/
|
|
1768
|
-
promiseTimeout:
|
|
1769
|
-
(ms) =>
|
|
1770
|
-
{
|
|
1771
|
-
ms = FLOAT_LAX(ms);
|
|
1772
|
-
if(ms <= 0)
|
|
1773
|
-
{
|
|
1774
|
-
return new Promise(resolve => resolve(0));
|
|
1775
|
-
}
|
|
1776
|
-
return new Promise(resolve => LeUtils.setTimeout(resolve, ms));
|
|
1777
|
-
},
|
|
1778
|
-
|
|
1779
|
-
/**
|
|
1780
|
-
* Returns a promise, which will be resolved after the given number of frames.
|
|
1781
|
-
*
|
|
1782
|
-
* @param {number} frames
|
|
1783
|
-
* @returns {Promise<number>}
|
|
1784
|
-
*/
|
|
1785
|
-
promiseAnimationFrameTimeout:
|
|
1786
|
-
(frames) =>
|
|
1787
|
-
{
|
|
1788
|
-
frames = INT_LAX(frames);
|
|
1789
|
-
if(frames <= 0)
|
|
1790
|
-
{
|
|
1791
|
-
return new Promise(resolve => resolve(0));
|
|
1792
|
-
}
|
|
1793
|
-
return new Promise(resolve => LeUtils.setAnimationFrameTimeout(resolve, frames));
|
|
1794
|
-
},
|
|
1795
|
-
|
|
1796
|
-
/**
|
|
1797
|
-
* Allows you to do a fetch, with built-in retry and abort functionality.
|
|
1798
|
-
*
|
|
1799
|
-
* @param {string} url
|
|
1800
|
-
* @param {{retries?:number|null, delay?:number|((attempt:number)=>number)|null}|Object|null} [options]
|
|
1801
|
-
* @returns {{then:Function, catch:Function, finally:Function, remove:Function, isRemoved:Function}}
|
|
1802
|
-
*/
|
|
1803
|
-
fetch:
|
|
1804
|
-
(url, options) =>
|
|
1805
|
-
{
|
|
1806
|
-
let currentRetries = 0;
|
|
1807
|
-
const retries = INT_LAX(options?.retries);
|
|
1808
|
-
|
|
1809
|
-
let controllerAborted = false;
|
|
1810
|
-
let controller = null;
|
|
1811
|
-
if(globalThis?.AbortController)
|
|
1812
|
-
{
|
|
1813
|
-
controller = new AbortController();
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
let promise = (async () =>
|
|
1817
|
-
{
|
|
1818
|
-
const attemptFetch = async () =>
|
|
1819
|
-
{
|
|
1820
|
-
if(controllerAborted || controller?.signal?.aborted)
|
|
1821
|
-
{
|
|
1822
|
-
throw new Error('Aborted');
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
try
|
|
1826
|
-
{
|
|
1827
|
-
const response = await fetch(url, {
|
|
1828
|
-
signal:controller?.signal,
|
|
1829
|
-
...(options ?? {}),
|
|
1830
|
-
retries:undefined,
|
|
1831
|
-
delay: undefined,
|
|
1832
|
-
});
|
|
1833
|
-
if(!response.ok)
|
|
1834
|
-
{
|
|
1835
|
-
throw new Error('Network request failed: ' + response.status + ' ' + response.statusText);
|
|
1836
|
-
}
|
|
1837
|
-
return response;
|
|
1838
|
-
}
|
|
1839
|
-
catch(error)
|
|
1840
|
-
{
|
|
1841
|
-
if(controllerAborted || controller?.signal?.aborted)
|
|
1842
|
-
{
|
|
1843
|
-
throw new Error('Aborted');
|
|
1844
|
-
}
|
|
1845
|
-
if(currentRetries >= retries)
|
|
1846
|
-
{
|
|
1847
|
-
throw error;
|
|
1848
|
-
}
|
|
1849
|
-
currentRetries++;
|
|
1850
|
-
await LeUtils.promiseTimeout((typeof options?.delay === 'function') ? INT_LAX_ANY(options?.delay(currentRetries), 500) : (INT_LAX_ANY(options?.delay, 500)));
|
|
1851
|
-
return await attemptFetch();
|
|
1852
|
-
}
|
|
1853
|
-
};
|
|
1854
|
-
return await attemptFetch();
|
|
1855
|
-
})();
|
|
1856
|
-
|
|
1857
|
-
let result = {};
|
|
1858
|
-
result.then = (...args) =>
|
|
1859
|
-
{
|
|
1860
|
-
promise = promise.then(...args);
|
|
1861
|
-
return result;
|
|
1862
|
-
};
|
|
1863
|
-
result.catch = (...args) =>
|
|
1864
|
-
{
|
|
1865
|
-
promise = promise.catch(...args);
|
|
1866
|
-
return result;
|
|
1867
|
-
};
|
|
1868
|
-
result.finally = (...args) =>
|
|
1869
|
-
{
|
|
1870
|
-
promise = promise.finally(...args);
|
|
1871
|
-
return result;
|
|
1872
|
-
};
|
|
1873
|
-
result.remove = (...args) =>
|
|
1874
|
-
{
|
|
1875
|
-
controllerAborted = true;
|
|
1876
|
-
if(controller)
|
|
1877
|
-
{
|
|
1878
|
-
controller.abort(...args);
|
|
1879
|
-
}
|
|
1880
|
-
return result;
|
|
1881
|
-
};
|
|
1882
|
-
result.isRemoved = () => (controllerAborted || !!controller?.signal?.aborted);
|
|
1883
|
-
return result;
|
|
1884
|
-
},
|
|
1885
|
-
|
|
1886
|
-
/**
|
|
1887
|
-
* Allows you to do a fetch, with built-in retry functionality. Caches on the requested URL, so that the same URL will not be fetched multiple times.
|
|
1888
|
-
*
|
|
1889
|
-
* @param {string} url
|
|
1890
|
-
* @param {{retries?:number|null, delay?:number|((attempt:number)=>number)|null, [verify]:((data:*,response:*)=>void)|null}|null} [options]
|
|
1891
|
-
* @param {((response:*)=>*)|null} [responseFunction] A function that will be called with the response object, and should return the data to be cached.
|
|
1892
|
-
* @returns {Promise<*>}
|
|
1893
|
-
*/
|
|
1894
|
-
cachedFetch:
|
|
1895
|
-
(() =>
|
|
1896
|
-
{
|
|
1897
|
-
const cache = new Map();
|
|
1898
|
-
return async (url, options, responseFunction) =>
|
|
1899
|
-
{
|
|
1900
|
-
if(cache.has(url))
|
|
1901
|
-
{
|
|
1902
|
-
const result = cache.get(url);
|
|
1903
|
-
if(result.data)
|
|
1904
|
-
{
|
|
1905
|
-
return result.data;
|
|
1906
|
-
}
|
|
1907
|
-
if(result.promise)
|
|
1908
|
-
{
|
|
1909
|
-
return await result.promise;
|
|
1910
|
-
}
|
|
1911
|
-
if(result.error)
|
|
1912
|
-
{
|
|
1913
|
-
throw result.error;
|
|
1914
|
-
}
|
|
1915
|
-
console.warn('Failed to use the cachedFetch cache, for URL: ', url, ', it is in an unexpected state: ', result);
|
|
1916
|
-
return null;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
const promise = LeUtils.fetch(url, options)
|
|
1920
|
-
.then(async response =>
|
|
1921
|
-
{
|
|
1922
|
-
const data = responseFunction ? (await responseFunction(response)) : response;
|
|
1923
|
-
if(typeof options?.verify === 'function')
|
|
1924
|
-
{
|
|
1925
|
-
await options.verify(data, response);
|
|
1926
|
-
}
|
|
1927
|
-
return data;
|
|
1928
|
-
})
|
|
1929
|
-
.then(data =>
|
|
1930
|
-
{
|
|
1931
|
-
cache.set(url, {data});
|
|
1932
|
-
return data;
|
|
1933
|
-
})
|
|
1934
|
-
.catch(error =>
|
|
1935
|
-
{
|
|
1936
|
-
cache.set(url, {error});
|
|
1937
|
-
console.error('Failed to fetch: ', error);
|
|
1938
|
-
throw error;
|
|
1939
|
-
});
|
|
1940
|
-
if(!cache.has(url))
|
|
1941
|
-
{
|
|
1942
|
-
cache.set(url, {promise});
|
|
1943
|
-
}
|
|
1944
|
-
return await promise;
|
|
1945
|
-
};
|
|
1946
|
-
})(),
|
|
1947
|
-
|
|
1948
|
-
/**
|
|
1949
|
-
* Returns true if the user is on a smartphone device (mobile).
|
|
1950
|
-
* Will return false if the user is on a tablet or on a desktop.
|
|
1951
|
-
*
|
|
1952
|
-
* In short:
|
|
1953
|
-
* - Mobile: True
|
|
1954
|
-
* - Tablet: False
|
|
1955
|
-
* - Desktop: False
|
|
1956
|
-
*
|
|
1957
|
-
* @returns {boolean}
|
|
1958
|
-
*/
|
|
1959
|
-
platformIsMobile:
|
|
1960
|
-
() =>
|
|
1961
|
-
{
|
|
1962
|
-
// noinspection JSDeprecatedSymbols, JSUnresolvedReference
|
|
1963
|
-
/** navigator.userAgentData.mobile doesn't return the correct value on some platforms, so this is a work-around, code from: http://detectmobilebrowsers.com **/
|
|
1964
|
-
const a = STRING(globalThis?.navigator?.userAgent || globalThis?.navigator?.vendor || globalThis?.opera || '');
|
|
1965
|
-
const b = a.substring(0, 4);
|
|
1966
|
-
return !!(
|
|
1967
|
-
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series([46])0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i
|
|
1968
|
-
.test(a) ||
|
|
1969
|
-
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br([ev])w|bumb|bw-([nu])|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do([cp])o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly([-_])|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-([mpt])|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c([- _agpst])|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac([ \-/])|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja([tv])a|jbro|jemu|jigs|kddi|keji|kgt([ /])|klon|kpt |kwc-|kyo([ck])|le(no|xi)|lg( g|\/([klu])|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t([- ov])|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30([02])|n50([025])|n7(0([01])|10)|ne(([cm])-|on|tf|wf|wg|wt)|nok([6i])|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan([adt])|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c([-01])|47|mc|nd|ri)|sgh-|shar|sie([-m])|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel([im])|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c([- ])|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i
|
|
1970
|
-
.test(b)
|
|
1971
|
-
);
|
|
1972
|
-
},
|
|
1973
|
-
|
|
1974
|
-
/**
|
|
1975
|
-
* Returns true if the user has a cursor (mouse, touchpad, etc).
|
|
1976
|
-
* In this context, a cursor is defined as an input device that can hover over elements without necessarily interacting with them.
|
|
1977
|
-
*
|
|
1978
|
-
* @returns {boolean}
|
|
1979
|
-
*/
|
|
1980
|
-
platformHasCursor:
|
|
1981
|
-
() =>
|
|
1982
|
-
{
|
|
1983
|
-
return !LeUtils.platformIsMobile() && !globalThis?.matchMedia?.('(any-hover: none)')?.matches;
|
|
1984
|
-
},
|
|
1985
|
-
|
|
1986
|
-
/**
|
|
1987
|
-
* Returns the given string, with the first character capitalized.
|
|
1988
|
-
*
|
|
1989
|
-
* @param {String} string
|
|
1990
|
-
* @returns {string}
|
|
1991
|
-
*/
|
|
1992
|
-
capitalize:
|
|
1993
|
-
(string) =>
|
|
1994
|
-
{
|
|
1995
|
-
string = STRING(string).trim();
|
|
1996
|
-
if(string.length <= 0)
|
|
1997
|
-
{
|
|
1998
|
-
return string;
|
|
1999
|
-
}
|
|
2000
|
-
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
2001
|
-
},
|
|
2002
|
-
|
|
2003
|
-
/**
|
|
2004
|
-
* Returns true if the given string ends with any of the given characters or words.
|
|
2005
|
-
*
|
|
2006
|
-
* @param {string} string
|
|
2007
|
-
* @param {string|string[]} endingCharsStringOrArray
|
|
2008
|
-
* @returns {boolean}
|
|
2009
|
-
*/
|
|
2010
|
-
endsWithAny:
|
|
2011
|
-
(string, endingCharsStringOrArray) =>
|
|
2012
|
-
{
|
|
2013
|
-
string = STRING(string);
|
|
2014
|
-
let endingCharsArray;
|
|
2015
|
-
if(Array.isArray(endingCharsStringOrArray))
|
|
2016
|
-
{
|
|
2017
|
-
endingCharsArray = endingCharsStringOrArray;
|
|
2018
|
-
}
|
|
2019
|
-
else
|
|
2020
|
-
{
|
|
2021
|
-
endingCharsArray = STRING(endingCharsStringOrArray).split('');
|
|
2022
|
-
}
|
|
2023
|
-
let result = false;
|
|
2024
|
-
LeUtils.each(endingCharsArray, (chars) =>
|
|
2025
|
-
{
|
|
2026
|
-
if(string.endsWith(STRING(chars)))
|
|
2027
|
-
{
|
|
2028
|
-
result = true;
|
|
2029
|
-
return false;
|
|
2030
|
-
}
|
|
2031
|
-
});
|
|
2032
|
-
return result;
|
|
2033
|
-
},
|
|
2034
|
-
|
|
2035
|
-
/**
|
|
2036
|
-
* Returns true if the given string starts with any of the given characters or words.
|
|
2037
|
-
*
|
|
2038
|
-
* @param {string} string
|
|
2039
|
-
* @param {string|string[]} startingCharsStringOrArray
|
|
2040
|
-
* @returns {boolean}
|
|
2041
|
-
*/
|
|
2042
|
-
startsWithAny:
|
|
2043
|
-
(string, startingCharsStringOrArray) =>
|
|
2044
|
-
{
|
|
2045
|
-
string = STRING(string);
|
|
2046
|
-
let startingCharsArray;
|
|
2047
|
-
if(Array.isArray(startingCharsStringOrArray))
|
|
2048
|
-
{
|
|
2049
|
-
startingCharsArray = startingCharsStringOrArray;
|
|
2050
|
-
}
|
|
2051
|
-
else
|
|
2052
|
-
{
|
|
2053
|
-
startingCharsArray = STRING(startingCharsStringOrArray).split('');
|
|
2054
|
-
}
|
|
2055
|
-
let result = false;
|
|
2056
|
-
LeUtils.each(startingCharsArray, (chars) =>
|
|
2057
|
-
{
|
|
2058
|
-
if(string.startsWith(STRING(chars)))
|
|
2059
|
-
{
|
|
2060
|
-
result = true;
|
|
2061
|
-
return false;
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
return result;
|
|
2065
|
-
},
|
|
2066
|
-
|
|
2067
|
-
/**
|
|
2068
|
-
* Trims the end of the given string, by removing the given characters from it.
|
|
2069
|
-
*
|
|
2070
|
-
* @param {string} string
|
|
2071
|
-
* @param {string|string[]} trimCharsStringOrArray
|
|
2072
|
-
*/
|
|
2073
|
-
trimEnd:
|
|
2074
|
-
(string, trimCharsStringOrArray) =>
|
|
2075
|
-
{
|
|
2076
|
-
string = STRING(string);
|
|
2077
|
-
let endingCharsArray;
|
|
2078
|
-
if(Array.isArray(trimCharsStringOrArray))
|
|
2079
|
-
{
|
|
2080
|
-
endingCharsArray = trimCharsStringOrArray;
|
|
2081
|
-
}
|
|
2082
|
-
else
|
|
2083
|
-
{
|
|
2084
|
-
endingCharsArray = STRING(trimCharsStringOrArray).split('');
|
|
2085
|
-
}
|
|
2086
|
-
const trimChars = (chars) =>
|
|
2087
|
-
{
|
|
2088
|
-
chars = STRING(chars);
|
|
2089
|
-
if(string.endsWith(chars))
|
|
2090
|
-
{
|
|
2091
|
-
string = string.substring(0, string.length - chars.length);
|
|
2092
|
-
run = true;
|
|
2093
|
-
}
|
|
2094
|
-
};
|
|
2095
|
-
let run = true;
|
|
2096
|
-
while(run)
|
|
2097
|
-
{
|
|
2098
|
-
run = false;
|
|
2099
|
-
LeUtils.each(endingCharsArray, trimChars);
|
|
2100
|
-
}
|
|
2101
|
-
return string;
|
|
2102
|
-
},
|
|
2103
|
-
|
|
2104
|
-
/**
|
|
2105
|
-
* Trims the start of the given string, by removing the given characters from it.
|
|
2106
|
-
*
|
|
2107
|
-
* @param {string} string
|
|
2108
|
-
* @param {string|string[]} trimCharsStringOrArray
|
|
2109
|
-
*/
|
|
2110
|
-
trimStart:
|
|
2111
|
-
(string, trimCharsStringOrArray) =>
|
|
2112
|
-
{
|
|
2113
|
-
string = STRING(string);
|
|
2114
|
-
let startingCharsArray;
|
|
2115
|
-
if(Array.isArray(trimCharsStringOrArray))
|
|
2116
|
-
{
|
|
2117
|
-
startingCharsArray = trimCharsStringOrArray;
|
|
2118
|
-
}
|
|
2119
|
-
else
|
|
2120
|
-
{
|
|
2121
|
-
startingCharsArray = STRING(trimCharsStringOrArray).split('');
|
|
2122
|
-
}
|
|
2123
|
-
const trimChars = (chars) =>
|
|
2124
|
-
{
|
|
2125
|
-
chars = STRING(chars);
|
|
2126
|
-
if(string.startsWith(chars))
|
|
2127
|
-
{
|
|
2128
|
-
string = string.substring(chars.length);
|
|
2129
|
-
run = true;
|
|
2130
|
-
}
|
|
2131
|
-
};
|
|
2132
|
-
let run = true;
|
|
2133
|
-
while(run)
|
|
2134
|
-
{
|
|
2135
|
-
run = false;
|
|
2136
|
-
LeUtils.each(startingCharsArray, trimChars);
|
|
2137
|
-
}
|
|
2138
|
-
return string;
|
|
2139
|
-
},
|
|
2140
|
-
|
|
2141
|
-
/**
|
|
2142
|
-
* Trims the start and end of the given string, by removing the given characters from it.
|
|
2143
|
-
*
|
|
2144
|
-
* @param {string} string
|
|
2145
|
-
* @param {string|string[]} trimCharsStringOrArray
|
|
2146
|
-
*/
|
|
2147
|
-
trim:
|
|
2148
|
-
(string, trimCharsStringOrArray) => LeUtils.trimEnd(LeUtils.trimStart(string, trimCharsStringOrArray), trimCharsStringOrArray),
|
|
2149
|
-
|
|
2150
|
-
/**
|
|
2151
|
-
* Returns the given string, trims the start and end, and makes sure it ends with a valid sentence ending character (such as !?;.).
|
|
2152
|
-
*
|
|
2153
|
-
* @param {string} sentence
|
|
2154
|
-
* @returns {string}
|
|
2155
|
-
*/
|
|
2156
|
-
purgeSentence:
|
|
2157
|
-
(sentence) =>
|
|
2158
|
-
{
|
|
2159
|
-
sentence = LeUtils.trimEnd(STRING(sentence).trim(), '.: \r\n\t');
|
|
2160
|
-
sentence += (LeUtils.endsWithAny(sentence, '!?;') ? '' : '.');
|
|
2161
|
-
return sentence;
|
|
2162
|
-
},
|
|
2163
|
-
|
|
2164
|
-
/**
|
|
2165
|
-
* Attempts to obtain and return an error message from the given error, regardless of what is passed to this function.
|
|
2166
|
-
*
|
|
2167
|
-
* @param {*} error
|
|
2168
|
-
* @returns {string}
|
|
2169
|
-
*/
|
|
2170
|
-
purgeErrorMessage:
|
|
2171
|
-
(error) =>
|
|
2172
|
-
{
|
|
2173
|
-
if(error?.message)
|
|
2174
|
-
{
|
|
2175
|
-
error = error.message;
|
|
2176
|
-
}
|
|
2177
|
-
if(typeof error === 'string')
|
|
2178
|
-
{
|
|
2179
|
-
const errorParts = error.split('threw an error:');
|
|
2180
|
-
error = errorParts[errorParts.length - 1];
|
|
2181
|
-
}
|
|
2182
|
-
else
|
|
2183
|
-
{
|
|
2184
|
-
try
|
|
2185
|
-
{
|
|
2186
|
-
error = JSON.stringify(error);
|
|
2187
|
-
}
|
|
2188
|
-
catch(e)
|
|
2189
|
-
{
|
|
2190
|
-
error = 'An unknown error occurred';
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
return error.trim();
|
|
2194
|
-
},
|
|
2195
|
-
|
|
2196
|
-
/**
|
|
2197
|
-
* Generates all permutations of the given names.
|
|
2198
|
-
*
|
|
2199
|
-
* For example, if you pass "foo" and "bar", it will return:
|
|
2200
|
-
* - foobar
|
|
2201
|
-
* - fooBar
|
|
2202
|
-
* - FooBar
|
|
2203
|
-
* - foo-bar
|
|
2204
|
-
* - foo_bar
|
|
2205
|
-
*
|
|
2206
|
-
* @param {string[]} names
|
|
2207
|
-
* @returns {string[]}
|
|
2208
|
-
*/
|
|
2209
|
-
generateNamePermutations:
|
|
2210
|
-
(...names) =>
|
|
2211
|
-
{
|
|
2212
|
-
names = LeUtils.flattenToArray(names)
|
|
2213
|
-
.map(name => STRING(name).trim().toLowerCase())
|
|
2214
|
-
.filter(name => (name.length > 0));
|
|
2215
|
-
let results = [];
|
|
2216
|
-
if(names.length > 0)
|
|
2217
|
-
{
|
|
2218
|
-
results.push(names.join('')); //foobar
|
|
2219
|
-
results.push(names.map(LeUtils.capitalize).join('')); //FooBar
|
|
2220
|
-
}
|
|
2221
|
-
if(names.length > 1)
|
|
2222
|
-
{
|
|
2223
|
-
results.push([names[0]].concat(names.slice(1).map(LeUtils.capitalize)).join('')); //fooBar
|
|
2224
|
-
results.push(names.join('-')); //foo-bar
|
|
2225
|
-
results.push(names.join('_')); //foo_bar
|
|
2226
|
-
}
|
|
2227
|
-
return results;
|
|
2228
|
-
},
|
|
2229
|
-
|
|
2230
|
-
/**
|
|
2231
|
-
* Increases the given numeric string by 1, this allows you to increase a numeric string without a limit.
|
|
2232
|
-
*
|
|
2233
|
-
* @param {string} string
|
|
2234
|
-
* @returns {string}
|
|
2235
|
-
*/
|
|
2236
|
-
increaseNumericStringByOne:
|
|
2237
|
-
(string) =>
|
|
2238
|
-
{
|
|
2239
|
-
if(typeof string !== 'string')
|
|
2240
|
-
{
|
|
2241
|
-
string = '' + string;
|
|
2242
|
-
for(let i = string.length - 1; i >= 0; i--)
|
|
2243
|
-
{
|
|
2244
|
-
const c = string.charAt(i);
|
|
2245
|
-
if((c < '0') || (c > '9'))
|
|
2246
|
-
{
|
|
2247
|
-
return '1';
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
if(string === '')
|
|
2252
|
-
{
|
|
2253
|
-
return '1';
|
|
2254
|
-
}
|
|
2255
|
-
for(let i = string.length - 1; i >= 0; i--)
|
|
2256
|
-
{
|
|
2257
|
-
let c = string.charAt(i);
|
|
2258
|
-
if((c < '0') || (c > '9'))
|
|
2259
|
-
{
|
|
2260
|
-
return '1';
|
|
2261
|
-
}
|
|
2262
|
-
if(c < '9')
|
|
2263
|
-
{
|
|
2264
|
-
c = String.fromCharCode(c.charCodeAt(0) + 1);
|
|
2265
|
-
string = string.substring(0, i) + c + string.substring(i + 1);// string[i] = (char + 1);
|
|
2266
|
-
break;
|
|
2267
|
-
}
|
|
2268
|
-
string = string.substring(0, i) + '0' + string.substring(i + 1);// string[i] = '0';
|
|
2269
|
-
}
|
|
2270
|
-
if(string.charAt(0) === '0')
|
|
2271
|
-
{
|
|
2272
|
-
string = '1' + string;
|
|
2273
|
-
}
|
|
2274
|
-
return string;
|
|
2275
|
-
},
|
|
2276
|
-
|
|
2277
|
-
/**
|
|
2278
|
-
* Generates a base64 string (with +/ replaced by -_) that is guaranteed to be unique.
|
|
2279
|
-
*
|
|
2280
|
-
* @returns {string}
|
|
2281
|
-
*/
|
|
2282
|
-
uniqueId:
|
|
2283
|
-
(() =>
|
|
2284
|
-
{
|
|
2285
|
-
let previousUniqueIdsTime = null;
|
|
2286
|
-
let previousUniqueIds = new Map();
|
|
2287
|
-
|
|
2288
|
-
const numberToBytes = (number) =>
|
|
2289
|
-
{
|
|
2290
|
-
const size = (number === 0) ? 0 : Math.ceil((Math.floor(Math.log2(number)) + 1) / 8);
|
|
2291
|
-
const bytes = new Uint8ClampedArray(size);
|
|
2292
|
-
let x = number;
|
|
2293
|
-
for(let i = (size - 1); i >= 0; i--)
|
|
2294
|
-
{
|
|
2295
|
-
const rightByte = x & 0xff;
|
|
2296
|
-
bytes[i] = rightByte;
|
|
2297
|
-
x = Math.floor(x / 0x100);
|
|
2298
|
-
}
|
|
2299
|
-
return bytes;
|
|
2300
|
-
};
|
|
2301
|
-
|
|
2302
|
-
const generateUniqueId = () =>
|
|
2303
|
-
{
|
|
2304
|
-
let now;
|
|
2305
|
-
try
|
|
2306
|
-
{
|
|
2307
|
-
// noinspection JSDeprecatedSymbols
|
|
2308
|
-
now = (globalThis?.performance?.timeOrigin || globalThis?.performance?.timing?.navigationStart || 0) + (globalThis?.performance?.now?.() ?? 0);
|
|
2309
|
-
}
|
|
2310
|
-
catch(e)
|
|
2311
|
-
{
|
|
2312
|
-
}
|
|
2313
|
-
now = now || (Date.now ? Date.now() : (new Date()).getTime());
|
|
2314
|
-
now = Math.round(now);
|
|
2315
|
-
const nowBytes = numberToBytes(now);
|
|
2316
|
-
|
|
2317
|
-
let uuid = null;
|
|
2318
|
-
try
|
|
2319
|
-
{
|
|
2320
|
-
uuid = crypto?.randomUUID();
|
|
2321
|
-
}
|
|
2322
|
-
catch(e)
|
|
2323
|
-
{
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
if(uuid)
|
|
2327
|
-
{
|
|
2328
|
-
uuid = LeUtils.base64ToBytes(LeUtils.hexToBase64(uuid));
|
|
2329
|
-
}
|
|
2330
|
-
else
|
|
2331
|
-
{
|
|
2332
|
-
const bytesChunkA = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
|
|
2333
|
-
const bytesChunkB = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
|
|
2334
|
-
const bytesChunkC = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
|
|
2335
|
-
const bytesChunkD = numberToBytes((Math.random() + ' ').substring(2, 12).padEnd(10, '0'));
|
|
2336
|
-
uuid = new Uint8Array(bytesChunkA.length + bytesChunkB.length + bytesChunkC.length + bytesChunkD.length);
|
|
2337
|
-
uuid.set(bytesChunkA, 0);
|
|
2338
|
-
uuid.set(bytesChunkB, bytesChunkA.length);
|
|
2339
|
-
uuid.set(bytesChunkC, bytesChunkA.length + bytesChunkB.length);
|
|
2340
|
-
uuid.set(bytesChunkD, bytesChunkA.length + bytesChunkB.length + bytesChunkC.length);
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
const bytes = new Uint8Array(nowBytes.length + uuid.length);
|
|
2344
|
-
bytes.set(nowBytes, 0);
|
|
2345
|
-
bytes.set(uuid, nowBytes.length);
|
|
2346
|
-
uuid = LeUtils.bytesToBase64(bytes).replaceAll('=', '').replaceAll('+', '-').replaceAll('/', '_');
|
|
2347
|
-
|
|
2348
|
-
return {
|
|
2349
|
-
time:now,
|
|
2350
|
-
id: uuid,
|
|
2351
|
-
};
|
|
2352
|
-
};
|
|
2353
|
-
|
|
2354
|
-
return () =>
|
|
2355
|
-
{
|
|
2356
|
-
while(true)
|
|
2357
|
-
{
|
|
2358
|
-
const result = generateUniqueId();
|
|
2359
|
-
if(previousUniqueIdsTime !== result.time)
|
|
2360
|
-
{
|
|
2361
|
-
previousUniqueIdsTime = result.time;
|
|
2362
|
-
previousUniqueIds.clear();
|
|
2363
|
-
previousUniqueIds.set(result.id, true);
|
|
2364
|
-
return result.id;
|
|
2365
|
-
}
|
|
2366
|
-
else if(previousUniqueIds.get(result.id) !== true)
|
|
2367
|
-
{
|
|
2368
|
-
previousUniqueIds.set(result.id, true);
|
|
2369
|
-
return result.id;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
};
|
|
2373
|
-
})(),
|
|
2374
|
-
|
|
2375
|
-
/**
|
|
2376
|
-
* Generates a base64 string (with +/ replaced by -_) of the current time (in milliseconds since 1970).
|
|
2377
|
-
*
|
|
2378
|
-
* @param {number} [now] Optional time to use instead of the current time. If not set, the current time will be used.
|
|
2379
|
-
* @returns {string}
|
|
2380
|
-
*/
|
|
2381
|
-
timestamp:
|
|
2382
|
-
(() =>
|
|
2383
|
-
{
|
|
2384
|
-
const numberToBytes = (number) =>
|
|
2385
|
-
{
|
|
2386
|
-
const size = (number === 0) ? 0 : Math.ceil((Math.floor(Math.log2(number)) + 1) / 8);
|
|
2387
|
-
const bytes = new Uint8ClampedArray(size);
|
|
2388
|
-
let x = number;
|
|
2389
|
-
for(let i = (size - 1); i >= 0; i--)
|
|
2390
|
-
{
|
|
2391
|
-
const rightByte = x & 0xff;
|
|
2392
|
-
bytes[i] = rightByte;
|
|
2393
|
-
x = Math.floor(x / 0x100);
|
|
2394
|
-
}
|
|
2395
|
-
return bytes;
|
|
2396
|
-
};
|
|
2397
|
-
|
|
2398
|
-
return (/** @type {number|null|undefined} */ now = null) =>
|
|
2399
|
-
{
|
|
2400
|
-
if(ISSET(now))
|
|
2401
|
-
{
|
|
2402
|
-
now = FLOAT_LAX(now);
|
|
2403
|
-
}
|
|
2404
|
-
else
|
|
2405
|
-
{
|
|
2406
|
-
try
|
|
2407
|
-
{
|
|
2408
|
-
// noinspection JSDeprecatedSymbols
|
|
2409
|
-
now = (performance?.timeOrigin || performance?.timing?.navigationStart || 0) + (performance?.now?.() ?? 0);
|
|
2410
|
-
}
|
|
2411
|
-
catch(e)
|
|
2412
|
-
{
|
|
2413
|
-
}
|
|
2414
|
-
now = now || (Date.now ? Date.now() : (new Date()).getTime());
|
|
2415
|
-
}
|
|
2416
|
-
now = Math.round(now);
|
|
2417
|
-
const nowBytes = numberToBytes(now);
|
|
2418
|
-
|
|
2419
|
-
return LeUtils.bytesToBase64(nowBytes).replaceAll('=', '').replaceAll('+', '-').replaceAll('/', '_');
|
|
2420
|
-
};
|
|
2421
|
-
})(),
|
|
2422
|
-
|
|
2423
|
-
/**
|
|
2424
|
-
* Returns a data URL of a 1x1 transparent pixel.
|
|
2425
|
-
*
|
|
2426
|
-
* @returns {string}
|
|
2427
|
-
*/
|
|
2428
|
-
getEmptyImageSrc:
|
|
2429
|
-
() =>
|
|
2430
|
-
{
|
|
2431
|
-
// noinspection SpellCheckingInspection
|
|
2432
|
-
return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
|
2433
|
-
},
|
|
2434
|
-
|
|
2435
|
-
/**
|
|
2436
|
-
* Calculates and returns the percentage of the part and total ((part / total) * 100).
|
|
2437
|
-
*
|
|
2438
|
-
* @param {number|string} part
|
|
2439
|
-
* @param {number|string} total
|
|
2440
|
-
* @returns {number}
|
|
2441
|
-
*/
|
|
2442
|
-
getPercentage:
|
|
2443
|
-
(part, total) =>
|
|
2444
|
-
{
|
|
2445
|
-
part = FLOAT_LAX(part);
|
|
2446
|
-
total = FLOAT_LAX(total);
|
|
2447
|
-
if(total === 0)
|
|
2448
|
-
{
|
|
2449
|
-
return 100;
|
|
2450
|
-
}
|
|
2451
|
-
return Math.max(0, Math.min(100, ((part / total) * 100)));
|
|
2452
|
-
},
|
|
2453
|
-
|
|
2454
|
-
/**
|
|
2455
|
-
* Returns the pixels of the given Image object.
|
|
2456
|
-
*
|
|
2457
|
-
* @param {HTMLImageElement} image
|
|
2458
|
-
* @returns {Uint8ClampedArray}
|
|
2459
|
-
*/
|
|
2460
|
-
getImagePixels:
|
|
2461
|
-
(image) =>
|
|
2462
|
-
{
|
|
2463
|
-
if(!globalThis?.document?.createElement || !globalThis?.document?.body?.appendChild)
|
|
2464
|
-
{
|
|
2465
|
-
console.warn('LeUtils.getImagePixels: Document is not available, returning empty pixels.');
|
|
2466
|
-
return new Uint8ClampedArray();
|
|
2467
|
-
}
|
|
2468
|
-
const canvas = globalThis.document.createElement('canvas');
|
|
2469
|
-
globalThis.document.body.appendChild(canvas);
|
|
2470
|
-
try
|
|
2471
|
-
{
|
|
2472
|
-
const ctx = canvas.getContext('2d');
|
|
2473
|
-
const width = Math.floor(image.width);
|
|
2474
|
-
const height = Math.floor(image.height);
|
|
2475
|
-
if(!ctx || (width <= 0) || (height <= 0))
|
|
2476
|
-
{
|
|
2477
|
-
return new Uint8ClampedArray();
|
|
2478
|
-
}
|
|
2479
|
-
canvas.width = width;
|
|
2480
|
-
canvas.height = height;
|
|
2481
|
-
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
2482
|
-
return ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
2483
|
-
}
|
|
2484
|
-
finally
|
|
2485
|
-
{
|
|
2486
|
-
canvas?.parentNode?.removeChild(canvas);
|
|
2487
|
-
}
|
|
2488
|
-
},
|
|
2489
|
-
|
|
2490
|
-
/**
|
|
2491
|
-
* Returns the data URL (mimetype "image/png") of a colored version of the given Image object.
|
|
2492
|
-
*
|
|
2493
|
-
* @param {HTMLImageElement} image
|
|
2494
|
-
* @param {string} color
|
|
2495
|
-
* @returns {string}
|
|
2496
|
-
*/
|
|
2497
|
-
getColoredImage:
|
|
2498
|
-
(image, color) =>
|
|
2499
|
-
{
|
|
2500
|
-
if(!globalThis?.document?.createElement || !globalThis?.document?.body?.appendChild)
|
|
2501
|
-
{
|
|
2502
|
-
console.warn('LeUtils.getColoredImage: Document is not available, returning empty image src.');
|
|
2503
|
-
return LeUtils.getEmptyImageSrc();
|
|
2504
|
-
}
|
|
2505
|
-
const canvas = globalThis.document.createElement('canvas');
|
|
2506
|
-
globalThis.document.body.appendChild(canvas);
|
|
2507
|
-
try
|
|
2508
|
-
{
|
|
2509
|
-
const ctx = canvas.getContext('2d');
|
|
2510
|
-
const width = Math.floor(image.width);
|
|
2511
|
-
const height = Math.floor(image.height);
|
|
2512
|
-
if(!ctx || (width <= 0) || (height <= 0))
|
|
2513
|
-
{
|
|
2514
|
-
return LeUtils.getEmptyImageSrc();
|
|
2515
|
-
}
|
|
2516
|
-
canvas.width = width;
|
|
2517
|
-
canvas.height = height;
|
|
2518
|
-
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
2519
|
-
ctx.globalCompositeOperation = 'source-in';
|
|
2520
|
-
ctx.fillStyle = color;
|
|
2521
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
2522
|
-
return canvas.toDataURL('image/png');
|
|
2523
|
-
}
|
|
2524
|
-
finally
|
|
2525
|
-
{
|
|
2526
|
-
canvas?.parentNode?.removeChild(canvas);
|
|
2527
|
-
}
|
|
2528
|
-
},
|
|
2529
|
-
|
|
2530
|
-
/**
|
|
2531
|
-
* Returns the hex color of the given RGB(A).
|
|
2532
|
-
*
|
|
2533
|
-
* @param {number[]} rgb
|
|
2534
|
-
* @returns {string}
|
|
2535
|
-
*/
|
|
2536
|
-
rgbToHex:
|
|
2537
|
-
(rgb) =>
|
|
2538
|
-
{
|
|
2539
|
-
return '#' + rgb.map((x) =>
|
|
2540
|
-
{
|
|
2541
|
-
const hex = x.toString(16);
|
|
2542
|
-
return ((hex.length === 1) ? '0' + hex : hex);
|
|
2543
|
-
}).join('');
|
|
2544
|
-
},
|
|
2545
|
-
|
|
2546
|
-
/**
|
|
2547
|
-
* Returns the RGB(A) of the given hex color.
|
|
2548
|
-
*
|
|
2549
|
-
* @param {string} hexstring
|
|
2550
|
-
* @returns {number[]}
|
|
2551
|
-
*/
|
|
2552
|
-
hexToRgb:
|
|
2553
|
-
(hexstring) =>
|
|
2554
|
-
{
|
|
2555
|
-
const initialHexstring = hexstring;
|
|
2556
|
-
hexstring = hexstring.replace(/[^0-9A-F]/gi, '');
|
|
2557
|
-
const hasAlpha = ((hexstring.length === 4) || (hexstring.length === 8));
|
|
2558
|
-
while(hexstring.length < 6)
|
|
2559
|
-
{
|
|
2560
|
-
hexstring = hexstring.replace(/(.)/g, '$1$1');
|
|
2561
|
-
}
|
|
2562
|
-
const result = hexstring.match(/\w{2}/g)?.map((a) => parseInt(a, 16));
|
|
2563
|
-
if(!result || (result.length < 3))
|
|
2564
|
-
{
|
|
2565
|
-
throw new Error('Invalid hex color: "' + hexstring + '" (was given "' + initialHexstring + '")');
|
|
2566
|
-
}
|
|
2567
|
-
return [
|
|
2568
|
-
result[0],
|
|
2569
|
-
result[1],
|
|
2570
|
-
result[2],
|
|
2571
|
-
...(hasAlpha ? [result[3]] : []),
|
|
2572
|
-
];
|
|
2573
|
-
},
|
|
2574
|
-
|
|
2575
|
-
/**
|
|
2576
|
-
* Returns the HSL(A) of the given RGB(A).
|
|
2577
|
-
*
|
|
2578
|
-
* @param {number[]} rgb
|
|
2579
|
-
* @returns {number[]}
|
|
2580
|
-
*/
|
|
2581
|
-
rgbToHsl:
|
|
2582
|
-
(rgb) =>
|
|
2583
|
-
{
|
|
2584
|
-
const r = rgb[0] / 255;
|
|
2585
|
-
const g = rgb[1] / 255;
|
|
2586
|
-
const b = rgb[2] / 255;
|
|
2587
|
-
const max = Math.max(r, g, b);
|
|
2588
|
-
const min = Math.min(r, g, b);
|
|
2589
|
-
let h = 0;
|
|
2590
|
-
let s = 0;
|
|
2591
|
-
let l = (max + min) / 2;
|
|
2592
|
-
if(max !== min)
|
|
2593
|
-
{
|
|
2594
|
-
const d = max - min;
|
|
2595
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2596
|
-
switch(max)
|
|
2597
|
-
{
|
|
2598
|
-
case r:
|
|
2599
|
-
h = (g - b) / d + (g < b ? 6 : 0);
|
|
2600
|
-
break;
|
|
2601
|
-
case g:
|
|
2602
|
-
h = (b - r) / d + 2;
|
|
2603
|
-
break;
|
|
2604
|
-
case b:
|
|
2605
|
-
h = (r - g) / d + 4;
|
|
2606
|
-
break;
|
|
2607
|
-
}
|
|
2608
|
-
h /= 6;
|
|
2609
|
-
}
|
|
2610
|
-
return [h, s, l, ...((rgb.length >= 4) ? [rgb[3] / 255] : [])];
|
|
2611
|
-
},
|
|
2612
|
-
|
|
2613
|
-
/**
|
|
2614
|
-
* Returns the RGB(A) of the given HSL(A).
|
|
2615
|
-
*
|
|
2616
|
-
* @param {number[]} hsl
|
|
2617
|
-
* @returns {number[]}
|
|
2618
|
-
*/
|
|
2619
|
-
hslToRgb:
|
|
2620
|
-
(() =>
|
|
2621
|
-
{
|
|
2622
|
-
const hue2rgb = (p, q, t) =>
|
|
2623
|
-
{
|
|
2624
|
-
if(t < 0)
|
|
2625
|
-
{
|
|
2626
|
-
t += 1;
|
|
2627
|
-
}
|
|
2628
|
-
if(t > 1)
|
|
2629
|
-
{
|
|
2630
|
-
t -= 1;
|
|
2631
|
-
}
|
|
2632
|
-
if(t < 1 / 6)
|
|
2633
|
-
{
|
|
2634
|
-
return p + (q - p) * 6 * t;
|
|
2635
|
-
}
|
|
2636
|
-
if(t < 1 / 2)
|
|
2637
|
-
{
|
|
2638
|
-
return q;
|
|
2639
|
-
}
|
|
2640
|
-
if(t < 2 / 3)
|
|
2641
|
-
{
|
|
2642
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
|
2643
|
-
}
|
|
2644
|
-
return p;
|
|
2645
|
-
};
|
|
2646
|
-
return (hsl) =>
|
|
2647
|
-
{
|
|
2648
|
-
const h = hsl[0];
|
|
2649
|
-
const s = hsl[1];
|
|
2650
|
-
const l = hsl[2];
|
|
2651
|
-
let r = 1;
|
|
2652
|
-
let g = 1;
|
|
2653
|
-
let b = 1;
|
|
2654
|
-
if(s !== 0)
|
|
2655
|
-
{
|
|
2656
|
-
const q = (l < 0.5) ? (l * (1 + s)) : (l + s - (l * s));
|
|
2657
|
-
const p = (2 * l) - q;
|
|
2658
|
-
r = hue2rgb(p, q, h + (1 / 3));
|
|
2659
|
-
g = hue2rgb(p, q, h);
|
|
2660
|
-
b = hue2rgb(p, q, h - (1 / 3));
|
|
2661
|
-
}
|
|
2662
|
-
return [r * 255, g * 255, b * 255, ...((hsl.length >= 4) ? [hsl[3] * 255] : [])].map((c) => Math.max(0, Math.min(255, Math.round(c))));
|
|
2663
|
-
};
|
|
2664
|
-
})(),
|
|
2665
|
-
|
|
2666
|
-
/**
|
|
2667
|
-
* Returns the LAB(A) of the given RGB(A).
|
|
2668
|
-
*
|
|
2669
|
-
* @param {number[]} rgb
|
|
2670
|
-
* @returns {number[]}
|
|
2671
|
-
*/
|
|
2672
|
-
rgbToLab:
|
|
2673
|
-
(rgb) =>
|
|
2674
|
-
{
|
|
2675
|
-
let r = rgb[0] / 255;
|
|
2676
|
-
let g = rgb[1] / 255;
|
|
2677
|
-
let b = rgb[2] / 255;
|
|
2678
|
-
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : (r / 12.92);
|
|
2679
|
-
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : (g / 12.92);
|
|
2680
|
-
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
|
2681
|
-
let x = ((r * 0.4124) + (g * 0.3576) + (b * 0.1805)) / 0.95047;
|
|
2682
|
-
let y = ((r * 0.2126) + (g * 0.7152) + (b * 0.0722));
|
|
2683
|
-
let z = ((r * 0.0193) + (g * 0.1192) + (b * 0.9505)) / 1.08883;
|
|
2684
|
-
x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
|
|
2685
|
-
y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
|
|
2686
|
-
z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
|
|
2687
|
-
return [(116 * y) - 16, 500 * (x - y), 200 * (y - z), ...((rgb.length >= 4) ? [rgb[3] / 255] : [])];
|
|
2688
|
-
},
|
|
2689
|
-
|
|
2690
|
-
/**
|
|
2691
|
-
* Returns the difference (calculated with DeltaE) of the LAB values of the given RGB values.
|
|
2692
|
-
*
|
|
2693
|
-
* Returns a number:
|
|
2694
|
-
*
|
|
2695
|
-
* ```js
|
|
2696
|
-
* < 1 is not perceptible by human eyes
|
|
2697
|
-
* 1-2 is perceptible through close observation
|
|
2698
|
-
* 2-10 is perceptible at a glance
|
|
2699
|
-
* 11-49 is more similar than opposite
|
|
2700
|
-
* 100 is exactly the opposite color
|
|
2701
|
-
* ```
|
|
2702
|
-
*
|
|
2703
|
-
* @param {number[]} rgbA
|
|
2704
|
-
* @param {number[]} rgbB
|
|
2705
|
-
* @returns {number}
|
|
2706
|
-
*/
|
|
2707
|
-
getDifferenceBetweenRgb:
|
|
2708
|
-
(rgbA, rgbB) =>
|
|
2709
|
-
{
|
|
2710
|
-
const labA = LeUtils.rgbToLab(rgbA);
|
|
2711
|
-
const labB = LeUtils.rgbToLab(rgbB);
|
|
2712
|
-
return LeUtils.getDifferenceBetweenLab(labA, labB);
|
|
2713
|
-
},
|
|
2714
|
-
|
|
2715
|
-
/**
|
|
2716
|
-
* Returns the difference (calculated with DeltaE) of the given LAB values. Ignores the Alpha channel.
|
|
2717
|
-
*
|
|
2718
|
-
* Returns a number:
|
|
2719
|
-
*
|
|
2720
|
-
* ```js
|
|
2721
|
-
* < 1 is not perceptible by human eyes
|
|
2722
|
-
* 1-2 is perceptible through close observation
|
|
2723
|
-
* 2-10 is perceptible at a glance
|
|
2724
|
-
* 11-49 is more similar than opposite
|
|
2725
|
-
* 100 is exactly the opposite color
|
|
2726
|
-
* ```
|
|
2727
|
-
*
|
|
2728
|
-
* @param {number[]} labA
|
|
2729
|
-
* @param {number[]} labB
|
|
2730
|
-
* @returns {number}
|
|
2731
|
-
*/
|
|
2732
|
-
getDifferenceBetweenLab:
|
|
2733
|
-
(labA, labB) =>
|
|
2734
|
-
{
|
|
2735
|
-
const deltaL = labA[0] - labB[0];
|
|
2736
|
-
const deltaA = labA[1] - labB[1];
|
|
2737
|
-
const deltaB = labA[2] - labB[2];
|
|
2738
|
-
const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
|
|
2739
|
-
const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
|
|
2740
|
-
const deltaC = c1 - c2;
|
|
2741
|
-
let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
|
|
2742
|
-
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
|
|
2743
|
-
const sc = 1.0 + 0.045 * c1;
|
|
2744
|
-
const sh = 1.0 + 0.015 * c1;
|
|
2745
|
-
const deltaLKlsl = deltaL / (1.0);
|
|
2746
|
-
const deltaCkcsc = deltaC / (sc);
|
|
2747
|
-
const deltaHkhsh = deltaH / (sh);
|
|
2748
|
-
const i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
|
|
2749
|
-
return (i < 0) ? 0 : Math.sqrt(i);
|
|
2750
|
-
},
|
|
2751
|
-
|
|
2752
|
-
/**
|
|
2753
|
-
* Returns the RGB(A) of the given RGB(A) values, based on the given percentage (0-100).
|
|
2754
|
-
* This allows you to define a gradient of colors to fade in between, rather than just having a start and an end color.
|
|
2755
|
-
*
|
|
2756
|
-
* Usage:
|
|
2757
|
-
*
|
|
2758
|
-
* ```js
|
|
2759
|
-
* LeUtils.getRgbOfGradient({
|
|
2760
|
-
* 0: [255, 0, 0],
|
|
2761
|
-
* 33: [255, 255, 0],
|
|
2762
|
-
* 66: [0, 255, 0],
|
|
2763
|
-
* 100:[0, 255, 255],
|
|
2764
|
-
* }, 45.1234);
|
|
2765
|
-
* ```
|
|
2766
|
-
*
|
|
2767
|
-
* @param {{percentage?:number[]}} gradient
|
|
2768
|
-
* @param {number} percentage
|
|
2769
|
-
* @returns {number[]}
|
|
2770
|
-
*/
|
|
2771
|
-
getRgbOfGradient:
|
|
2772
|
-
(gradient, percentage) =>
|
|
2773
|
-
{
|
|
2774
|
-
percentage = Math.max(0, Math.min(100, FLOAT_LAX(percentage)));
|
|
2775
|
-
|
|
2776
|
-
let closest = null;
|
|
2777
|
-
LeUtils.each(gradient, (color, percent) =>
|
|
2778
|
-
{
|
|
2779
|
-
percent = INT_LAX(percent);
|
|
2780
|
-
if(closest === null)
|
|
2781
|
-
{
|
|
2782
|
-
closest = [percent, Math.abs(percentage - percent)];
|
|
2783
|
-
}
|
|
2784
|
-
else
|
|
2785
|
-
{
|
|
2786
|
-
const difference = Math.abs(percentage - percent);
|
|
2787
|
-
if(difference < closest[1])
|
|
2788
|
-
{
|
|
2789
|
-
closest = [percent, difference];
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
});
|
|
2793
|
-
if(closest === null)
|
|
2794
|
-
{
|
|
2795
|
-
return [0, 0, 0];
|
|
2796
|
-
}
|
|
2797
|
-
closest = closest[0];
|
|
2798
|
-
|
|
2799
|
-
const HIGHER = 99999;
|
|
2800
|
-
const LOWER = -99999;
|
|
2801
|
-
let higher = HIGHER;
|
|
2802
|
-
let lower = LOWER;
|
|
2803
|
-
LeUtils.each(gradient, (color, percent) =>
|
|
2804
|
-
{
|
|
2805
|
-
percent = INT_LAX(percent);
|
|
2806
|
-
if(percent < closest)
|
|
2807
|
-
{
|
|
2808
|
-
if(percent > lower)
|
|
2809
|
-
{
|
|
2810
|
-
lower = percent;
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
if(percent > closest)
|
|
2814
|
-
{
|
|
2815
|
-
if(percent < higher)
|
|
2816
|
-
{
|
|
2817
|
-
higher = percent;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
});
|
|
2821
|
-
|
|
2822
|
-
if(((higher === HIGHER) && (lower === LOWER)) || (higher === lower))
|
|
2823
|
-
{
|
|
2824
|
-
return gradient[closest];
|
|
2825
|
-
}
|
|
2826
|
-
else if((higher !== HIGHER) && (lower !== LOWER))
|
|
2827
|
-
{
|
|
2828
|
-
const higherDifference = Math.abs(higher - percentage);
|
|
2829
|
-
const lowerDifference = Math.abs(percentage - lower);
|
|
2830
|
-
if(higherDifference > lowerDifference)
|
|
2831
|
-
{
|
|
2832
|
-
higher = closest;
|
|
2833
|
-
}
|
|
2834
|
-
else
|
|
2835
|
-
{
|
|
2836
|
-
lower = closest;
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
else if(lower === LOWER)
|
|
2840
|
-
{
|
|
2841
|
-
lower = closest;
|
|
2842
|
-
}
|
|
2843
|
-
else
|
|
2844
|
-
{
|
|
2845
|
-
higher = closest;
|
|
2846
|
-
}
|
|
2847
|
-
|
|
2848
|
-
if(lower > higher)
|
|
2849
|
-
{
|
|
2850
|
-
const tmp = higher;
|
|
2851
|
-
higher = lower;
|
|
2852
|
-
lower = tmp;
|
|
2853
|
-
}
|
|
2854
|
-
|
|
2855
|
-
const total = (higher - lower);
|
|
2856
|
-
const part = (percentage - lower);
|
|
2857
|
-
return LeUtils.getRgbBetween(gradient[lower], gradient[higher], ((part / total) * 100));
|
|
2858
|
-
},
|
|
2859
|
-
|
|
2860
|
-
/**
|
|
2861
|
-
* Returns the RGB(A) between the two given RGB(A) values, based on the given percentage (0-100).
|
|
2862
|
-
*
|
|
2863
|
-
* @param {number[]} startRgb
|
|
2864
|
-
* @param {number[]} endRgb
|
|
2865
|
-
* @param {number} percentage
|
|
2866
|
-
* @returns {number[]}
|
|
2867
|
-
*/
|
|
2868
|
-
getRgbBetween:
|
|
2869
|
-
(startRgb, endRgb, percentage) =>
|
|
2870
|
-
{
|
|
2871
|
-
percentage = FLOAT_LAX(percentage);
|
|
2872
|
-
const partEnd = Math.max(0, Math.min(1, (percentage / 100.0)));
|
|
2873
|
-
const partStart = (1 - partEnd);
|
|
2874
|
-
const length = Math.min(startRgb.length, endRgb.length);
|
|
2875
|
-
let result = [];
|
|
2876
|
-
for(let i = 0; i < length; i++)
|
|
2877
|
-
{
|
|
2878
|
-
result.push(Math.max(0, Math.min(255, Math.round((startRgb[i] * partStart) + (endRgb[i] * partEnd)))));
|
|
2879
|
-
}
|
|
2880
|
-
return result;
|
|
2881
|
-
},
|
|
2882
|
-
|
|
2883
|
-
/**
|
|
2884
|
-
* An implementation of the btoa function, which should work in all environments.
|
|
2885
|
-
*
|
|
2886
|
-
* @param {string} string
|
|
2887
|
-
* @returns {string}
|
|
2888
|
-
*/
|
|
2889
|
-
btoa:
|
|
2890
|
-
(string) =>
|
|
2891
|
-
{
|
|
2892
|
-
if(typeof globalThis?.btoa === 'function')
|
|
2893
|
-
{
|
|
2894
|
-
return globalThis.btoa(string);
|
|
2895
|
-
}
|
|
2896
|
-
if(typeof globalThis?.Buffer?.from === 'function')
|
|
2897
|
-
{
|
|
2898
|
-
return globalThis.Buffer.from(string).toString('base64');
|
|
2899
|
-
}
|
|
2900
|
-
throw new Error('LeUtils.btoa: No btoa implementation found in this environment.');
|
|
2901
|
-
},
|
|
2902
|
-
|
|
2903
|
-
/**
|
|
2904
|
-
* An implementation of the atob function, which should work in all environments.
|
|
2905
|
-
*
|
|
2906
|
-
* @param {string} base64string
|
|
2907
|
-
* @returns {string}
|
|
2908
|
-
*/
|
|
2909
|
-
atob:
|
|
2910
|
-
(base64string) =>
|
|
2911
|
-
{
|
|
2912
|
-
if(typeof globalThis?.atob === 'function')
|
|
2913
|
-
{
|
|
2914
|
-
return globalThis.atob(base64string);
|
|
2915
|
-
}
|
|
2916
|
-
if(typeof globalThis?.Buffer?.from === 'function')
|
|
2917
|
-
{
|
|
2918
|
-
return globalThis.Buffer.from(base64string, 'base64').toString();
|
|
2919
|
-
}
|
|
2920
|
-
throw new Error('LeUtils.atob: No atob implementation found in this environment.');
|
|
2921
|
-
},
|
|
2922
|
-
|
|
2923
|
-
/**
|
|
2924
|
-
* Encodes a UTF-8 string into a base64 string.
|
|
2925
|
-
*
|
|
2926
|
-
* @param {string} string
|
|
2927
|
-
* @returns {string}
|
|
2928
|
-
*/
|
|
2929
|
-
utf8ToBase64:
|
|
2930
|
-
(string) =>
|
|
2931
|
-
{
|
|
2932
|
-
return LeUtils.btoa(encodeURIComponent(string).replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode(parseInt(p1, 16))));
|
|
2933
|
-
},
|
|
2934
|
-
|
|
2935
|
-
/**
|
|
2936
|
-
* Decodes a base64 string back into a UTF-8 string.
|
|
2937
|
-
*
|
|
2938
|
-
* @param {string} base64string
|
|
2939
|
-
* @returns {string}
|
|
2940
|
-
*/
|
|
2941
|
-
base64ToUtf8:
|
|
2942
|
-
(base64string) =>
|
|
2943
|
-
{
|
|
2944
|
-
return decodeURIComponent(LeUtils.atob(base64string.trim()).split('').map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
|
|
2945
|
-
},
|
|
2946
|
-
|
|
2947
|
-
/**
|
|
2948
|
-
* Converts a base64 string into a hex string.
|
|
2949
|
-
*
|
|
2950
|
-
* @param {string} base64string
|
|
2951
|
-
* @returns {string}
|
|
2952
|
-
*/
|
|
2953
|
-
base64ToHex:
|
|
2954
|
-
(base64string) =>
|
|
2955
|
-
{
|
|
2956
|
-
return LeUtils.atob(base64string.trim()).split('').map((c) => ('0' + c.charCodeAt(0).toString(16)).slice(-2)).join('');
|
|
2957
|
-
},
|
|
2958
|
-
|
|
2959
|
-
/**
|
|
2960
|
-
* Converts a hex string into a base64 string.
|
|
2961
|
-
*
|
|
2962
|
-
* @param {string} hexstring
|
|
2963
|
-
* @returns {string}
|
|
2964
|
-
*/
|
|
2965
|
-
hexToBase64:
|
|
2966
|
-
(hexstring) =>
|
|
2967
|
-
{
|
|
2968
|
-
const hexResult = hexstring.replace(/[^0-9A-F]/gi, '').match(/\w{2}/g)?.map((a) => String.fromCharCode(parseInt(a, 16)))?.join('');
|
|
2969
|
-
if(!hexResult)
|
|
2970
|
-
{
|
|
2971
|
-
throw new Error('Invalid hex string: "' + hexstring + '"');
|
|
2972
|
-
}
|
|
2973
|
-
return LeUtils.btoa(hexResult);
|
|
2974
|
-
},
|
|
2975
|
-
|
|
2976
|
-
/**
|
|
2977
|
-
* Converts a base64 string into bytes (Uint8Array).
|
|
2978
|
-
*
|
|
2979
|
-
* @param {string} base64string
|
|
2980
|
-
* @returns {Uint8Array}
|
|
2981
|
-
*/
|
|
2982
|
-
base64ToBytes:
|
|
2983
|
-
(base64string) =>
|
|
2984
|
-
{
|
|
2985
|
-
const binary = LeUtils.atob(base64string.trim());
|
|
2986
|
-
const len = binary.length;
|
|
2987
|
-
let data = new Uint8Array(len);
|
|
2988
|
-
for(let i = 0; i < len; i++)
|
|
2989
|
-
{
|
|
2990
|
-
data[i] = binary.charCodeAt(i);
|
|
2991
|
-
}
|
|
2992
|
-
return data;
|
|
2993
|
-
},
|
|
2994
|
-
|
|
2995
|
-
/**
|
|
2996
|
-
* Converts bytes into a base64 string.
|
|
2997
|
-
*
|
|
2998
|
-
* @param {ArrayLike<number>|ArrayBuffer} arraybuffer
|
|
2999
|
-
* @returns {string}
|
|
3000
|
-
*/
|
|
3001
|
-
bytesToBase64:
|
|
3002
|
-
(arraybuffer) =>
|
|
3003
|
-
{
|
|
3004
|
-
const bytes = new Uint8Array(arraybuffer);
|
|
3005
|
-
const len = bytes.byteLength;
|
|
3006
|
-
let binary = '';
|
|
3007
|
-
for(let i = 0; i < len; i++)
|
|
3008
|
-
{
|
|
3009
|
-
binary += String.fromCharCode(bytes[i]);
|
|
3010
|
-
}
|
|
3011
|
-
return LeUtils.btoa(binary);
|
|
3012
|
-
},
|
|
3013
|
-
|
|
3014
|
-
/**
|
|
3015
|
-
* Downloads the given base64 string as a file.
|
|
3016
|
-
*
|
|
3017
|
-
* @param {string} base64string
|
|
3018
|
-
* @param {string} [fileName]
|
|
3019
|
-
* @param {string} [mimeType]
|
|
3020
|
-
*/
|
|
3021
|
-
downloadFile:
|
|
3022
|
-
(base64string, fileName, mimeType) =>
|
|
3023
|
-
{
|
|
3024
|
-
if(!globalThis?.document?.createElement)
|
|
3025
|
-
{
|
|
3026
|
-
console.warn('LeUtils.downloadFile: Document is not available, cannot download file.');
|
|
3027
|
-
return;
|
|
3028
|
-
}
|
|
3029
|
-
const link = globalThis.document.createElement('a');
|
|
3030
|
-
link.setAttribute('download', (typeof fileName === 'string') ? fileName : 'file');
|
|
3031
|
-
link.href = 'data:' + mimeType + ';base64,' + base64string;
|
|
3032
|
-
link.setAttribute('target', '_blank');
|
|
3033
|
-
link.click();
|
|
3034
|
-
},
|
|
3035
|
-
|
|
3036
|
-
/**
|
|
3037
|
-
* Loads the value from the browser, returns undefined if the value doesn't exist.
|
|
3038
|
-
*
|
|
3039
|
-
* @param {string} id
|
|
3040
|
-
* @returns {*}
|
|
3041
|
-
*/
|
|
3042
|
-
localStorageGet:
|
|
3043
|
-
(id) =>
|
|
3044
|
-
{
|
|
3045
|
-
if(!globalThis?.localStorage?.getItem)
|
|
3046
|
-
{
|
|
3047
|
-
console.warn('LeUtils.localStorageGet: LocalStorage is not available, returning undefined.');
|
|
3048
|
-
return;
|
|
3049
|
-
}
|
|
3050
|
-
let result = globalThis.localStorage.getItem('LeUtils_' + id);
|
|
3051
|
-
if(typeof result !== 'string')
|
|
3052
|
-
{
|
|
3053
|
-
return;
|
|
3054
|
-
}
|
|
3055
|
-
try
|
|
3056
|
-
{
|
|
3057
|
-
return JSON.parse(result)?.['-'];
|
|
3058
|
-
}
|
|
3059
|
-
catch(e)
|
|
3060
|
-
{
|
|
3061
|
-
}
|
|
3062
|
-
},
|
|
3063
|
-
|
|
3064
|
-
/**
|
|
3065
|
-
* Saves the given data in the browser.
|
|
3066
|
-
*
|
|
3067
|
-
* @param {string} id
|
|
3068
|
-
* @param {*} data
|
|
3069
|
-
*/
|
|
3070
|
-
localStorageSet:
|
|
3071
|
-
(id, data) =>
|
|
3072
|
-
{
|
|
3073
|
-
if(!globalThis?.localStorage?.setItem)
|
|
3074
|
-
{
|
|
3075
|
-
console.warn('LeUtils.localStorageSet: LocalStorage is not available, cannot save data.');
|
|
3076
|
-
return;
|
|
3077
|
-
}
|
|
3078
|
-
if(typeof data === 'undefined')
|
|
3079
|
-
{
|
|
3080
|
-
globalThis.localStorage.removeItem('LeUtils_' + id);
|
|
3081
|
-
return;
|
|
3082
|
-
}
|
|
3083
|
-
globalThis.localStorage.setItem('LeUtils_' + id, JSON.stringify({'-':data}));
|
|
3084
|
-
},
|
|
3085
|
-
|
|
3086
|
-
/**
|
|
3087
|
-
* Removes the data from the browser.
|
|
3088
|
-
*
|
|
3089
|
-
* @param {string} id
|
|
3090
|
-
*/
|
|
3091
|
-
localStorageRemove:
|
|
3092
|
-
(id) =>
|
|
3093
|
-
{
|
|
3094
|
-
if(!globalThis?.localStorage?.removeItem)
|
|
3095
|
-
{
|
|
3096
|
-
console.warn('LeUtils.localStorageRemove: LocalStorage is not available, cannot remove data.');
|
|
3097
|
-
return;
|
|
3098
|
-
}
|
|
3099
|
-
globalThis.localStorage.removeItem('LeUtils_' + id);
|
|
3100
|
-
},
|
|
3101
|
-
|
|
3102
|
-
/**
|
|
3103
|
-
* Returns whether the current hostname (globalThis.location.hostname) is private (such as localhost, 192.168.1.1, etc).
|
|
3104
|
-
* This can be used to determine if the app is running in a development environment or not.
|
|
3105
|
-
*
|
|
3106
|
-
* Only "localhost" and IPv4 addresses are supported. IPv6 addresses will always return false.
|
|
3107
|
-
*
|
|
3108
|
-
* @returns {boolean}
|
|
3109
|
-
*/
|
|
3110
|
-
isCurrentHostPrivate:
|
|
3111
|
-
(() =>
|
|
3112
|
-
{
|
|
3113
|
-
let lastHostname = null;
|
|
3114
|
-
let lastResult = false;
|
|
3115
|
-
|
|
3116
|
-
return () =>
|
|
3117
|
-
{
|
|
3118
|
-
if(!globalThis?.location?.hostname)
|
|
3119
|
-
{
|
|
3120
|
-
console.warn('LeUtils.isCurrentHostPrivate: No location.hostname found, returning false.');
|
|
3121
|
-
return false;
|
|
3122
|
-
}
|
|
3123
|
-
const hostname = globalThis.location.hostname;
|
|
3124
|
-
if(lastHostname === hostname)
|
|
3125
|
-
{
|
|
3126
|
-
return lastResult;
|
|
3127
|
-
}
|
|
3128
|
-
lastHostname = hostname;
|
|
3129
|
-
lastResult = LeUtils.isGivenHostPrivate(lastHostname);
|
|
3130
|
-
return lastResult;
|
|
3131
|
-
};
|
|
3132
|
-
})(),
|
|
3133
|
-
|
|
3134
|
-
/**
|
|
3135
|
-
* Returns true if the given hostname is private (such as localhost, 192.168.1.1, etc).
|
|
3136
|
-
*
|
|
3137
|
-
* Only "localhost" and IPv4 addresses are supported. IPv6 addresses will always return false.
|
|
3138
|
-
*
|
|
3139
|
-
* @param {string} host
|
|
3140
|
-
* @returns {boolean}
|
|
3141
|
-
*/
|
|
3142
|
-
isGivenHostPrivate:
|
|
3143
|
-
(host) =>
|
|
3144
|
-
{
|
|
3145
|
-
host = STRING(host).trim();
|
|
3146
|
-
if(!host)
|
|
3147
|
-
{
|
|
3148
|
-
return false;
|
|
3149
|
-
}
|
|
3150
|
-
try
|
|
3151
|
-
{
|
|
3152
|
-
host = (new URL(host)).hostname;
|
|
3153
|
-
}
|
|
3154
|
-
catch(e)
|
|
3155
|
-
{
|
|
3156
|
-
host = host.split(':')[0];
|
|
3157
|
-
}
|
|
3158
|
-
host = STRING(host).trim().toLowerCase();
|
|
3159
|
-
|
|
3160
|
-
if((host === 'localhost') || (host === '127.0.0.1'))
|
|
3161
|
-
{
|
|
3162
|
-
return true;
|
|
3163
|
-
}
|
|
3164
|
-
if(!/^(\d{1,3}\.){3}\d{1,3}$/.test(host))
|
|
3165
|
-
{
|
|
3166
|
-
return false;
|
|
3167
|
-
}
|
|
3168
|
-
const parts = host.split('.');
|
|
3169
|
-
return (parts[0] === '10') || // 10.0.0.0 - 10.255.255.255
|
|
3170
|
-
((parts[0] === '172') && ((parseInt(parts[1], 10) >= 16) && (parseInt(parts[1], 10) <= 31))) || // 172.16.0.0 - 172.31.255.255
|
|
3171
|
-
((parts[0] === '192') && (parts[1] === '168')); // 192.168.0.0 - 192.168.255.255
|
|
3172
|
-
},
|
|
3173
|
-
|
|
3174
|
-
/**
|
|
3175
|
-
* @typedef {Object} LeUtils_TransactionalValue
|
|
3176
|
-
* @property {*} value
|
|
3177
|
-
* @property {{id:string, value:*}[]} changes
|
|
3178
|
-
*/
|
|
3179
|
-
/**
|
|
3180
|
-
* Creates and returns a new TransactionalValue object.
|
|
3181
|
-
* With a TransactionalValue, you can keep track of changes to a value, and commit or cancel them.
|
|
3182
|
-
*
|
|
3183
|
-
* Multiple uncommitted changes can be made at the same time, the last change will be the one that overwrites older changes.
|
|
3184
|
-
* If that change is cancelled, the previous change will be the one that overwrites older changes.
|
|
3185
|
-
* This allows you to make multiple unconfirmed changes, and confirm or cancel each of them individually at any time.
|
|
3186
|
-
*
|
|
3187
|
-
* @param {*} [value]
|
|
3188
|
-
* @returns {LeUtils_TransactionalValue}
|
|
3189
|
-
*/
|
|
3190
|
-
createTransactionalValue:
|
|
3191
|
-
(value) =>
|
|
3192
|
-
{
|
|
3193
|
-
if(typeof value === 'undefined')
|
|
3194
|
-
{
|
|
3195
|
-
value = null;
|
|
3196
|
-
}
|
|
3197
|
-
return {value:value, changes:[]};
|
|
3198
|
-
},
|
|
3199
|
-
|
|
3200
|
-
/**
|
|
3201
|
-
* Returns true if the given value is a valid TransactionalValue, returns false if it isn't.
|
|
3202
|
-
*
|
|
3203
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3204
|
-
* @returns {boolean}
|
|
3205
|
-
*/
|
|
3206
|
-
isTransactionalValueValid:
|
|
3207
|
-
(transactionalValue) =>
|
|
3208
|
-
{
|
|
3209
|
-
return ((typeof transactionalValue === 'object') && ('value' in transactionalValue) && ('changes' in transactionalValue) && Array.isArray(transactionalValue.changes));
|
|
3210
|
-
},
|
|
3211
|
-
|
|
3212
|
-
/**
|
|
3213
|
-
* Returns true if the given value is a TransactionalValue, false otherwise.
|
|
3214
|
-
*
|
|
3215
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3216
|
-
* @returns {string}
|
|
3217
|
-
*/
|
|
3218
|
-
transactionalValueToString:
|
|
3219
|
-
(transactionalValue) =>
|
|
3220
|
-
{
|
|
3221
|
-
if(!LeUtils.isTransactionalValueValid(transactionalValue))
|
|
3222
|
-
{
|
|
3223
|
-
return transactionalValue + '';
|
|
3224
|
-
}
|
|
3225
|
-
if(transactionalValue.changes.length <= 0)
|
|
3226
|
-
{
|
|
3227
|
-
return '' + transactionalValue.value;
|
|
3228
|
-
}
|
|
3229
|
-
let valuesString = '' + transactionalValue.value;
|
|
3230
|
-
for(let i = 0; i < transactionalValue.changes.length; i++)
|
|
3231
|
-
{
|
|
3232
|
-
valuesString += ' -> ' + transactionalValue.changes[i].value;
|
|
3233
|
-
}
|
|
3234
|
-
return transactionalValue.changes[transactionalValue.changes.length - 1].value + ' (' + valuesString + ')';
|
|
3235
|
-
},
|
|
3236
|
-
|
|
3237
|
-
/**
|
|
3238
|
-
* Sets the committed value of the given TransactionalValue to the given value. Clears out the previously uncommitted changes.
|
|
3239
|
-
*
|
|
3240
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3241
|
-
* @param {*} value
|
|
3242
|
-
*/
|
|
3243
|
-
transactionSetAndCommit:
|
|
3244
|
-
(transactionalValue, value) =>
|
|
3245
|
-
{
|
|
3246
|
-
checkTransactionalValue(transactionalValue);
|
|
3247
|
-
if(typeof value === 'undefined')
|
|
3248
|
-
{
|
|
3249
|
-
value = null;
|
|
3250
|
-
}
|
|
3251
|
-
transactionalValue.value = value;
|
|
3252
|
-
transactionalValue.changes = [];
|
|
3253
|
-
},
|
|
3254
|
-
|
|
3255
|
-
/**
|
|
3256
|
-
* Sets the value of the given TransactionalValue to the given value, without yet committing it, meaning it can be committed or cancelled later.
|
|
3257
|
-
* It returns the ID of the change, which can be used to commit or cancel the change later.
|
|
3258
|
-
*
|
|
3259
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3260
|
-
* @param {*} value
|
|
3261
|
-
* @returns {string}
|
|
3262
|
-
*/
|
|
3263
|
-
transactionSetWithoutCommitting:
|
|
3264
|
-
(transactionalValue, value) =>
|
|
3265
|
-
{
|
|
3266
|
-
checkTransactionalValue(transactionalValue);
|
|
3267
|
-
if(typeof value === 'undefined')
|
|
3268
|
-
{
|
|
3269
|
-
value = null;
|
|
3270
|
-
}
|
|
3271
|
-
const id = LeUtils.uniqueId();
|
|
3272
|
-
transactionalValue.changes.push({id:id, value:value});
|
|
3273
|
-
return id;
|
|
3274
|
-
},
|
|
3275
|
-
|
|
3276
|
-
/**
|
|
3277
|
-
* Commits the change with the given ID, making it the new committed value.
|
|
3278
|
-
* Returns true if the change was found and committed, returns false if it was already overwritten by a newer committed change.
|
|
3279
|
-
*
|
|
3280
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3281
|
-
* @param {string} changeId
|
|
3282
|
-
* @returns {boolean}
|
|
3283
|
-
*/
|
|
3284
|
-
transactionCommitChange:
|
|
3285
|
-
(transactionalValue, changeId) =>
|
|
3286
|
-
{
|
|
3287
|
-
checkTransactionalValue(transactionalValue);
|
|
3288
|
-
const change = findTransactionalValueChange(transactionalValue, changeId);
|
|
3289
|
-
if(change === null)
|
|
3290
|
-
{
|
|
3291
|
-
return false;
|
|
3292
|
-
}
|
|
3293
|
-
transactionalValue.value = change.value;
|
|
3294
|
-
transactionalValue.changes.splice(0, change.index + 1);
|
|
3295
|
-
return true;
|
|
3296
|
-
},
|
|
3297
|
-
|
|
3298
|
-
/**
|
|
3299
|
-
* Cancels the change with the given ID, removing it from the uncommitted changes.
|
|
3300
|
-
* Returns true if the change was found and removed, returns false if it was already overwritten by a newer committed change.
|
|
3301
|
-
*
|
|
3302
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3303
|
-
* @param {string} changeId
|
|
3304
|
-
* @returns {boolean}
|
|
3305
|
-
*/
|
|
3306
|
-
transactionCancelChange:
|
|
3307
|
-
(transactionalValue, changeId) =>
|
|
3308
|
-
{
|
|
3309
|
-
checkTransactionalValue(transactionalValue);
|
|
3310
|
-
const change = findTransactionalValueChange(transactionalValue, changeId);
|
|
3311
|
-
if(change === null)
|
|
3312
|
-
{
|
|
3313
|
-
return false;
|
|
3314
|
-
}
|
|
3315
|
-
transactionalValue.changes.splice(change.index, 1);
|
|
3316
|
-
return true;
|
|
3317
|
-
},
|
|
3318
|
-
|
|
3319
|
-
/**
|
|
3320
|
-
* Returns true if the change was found, meaning it can still make a difference to the final committed value of this TransactionalValue.
|
|
3321
|
-
* Returns false if it was already overwritten by a newer committed change, meaning that this change can no longer make a difference to the final committed value of this TransactionalValue.
|
|
3322
|
-
*
|
|
3323
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3324
|
-
* @param {string} changeId
|
|
3325
|
-
* @returns {boolean}
|
|
3326
|
-
*/
|
|
3327
|
-
transactionIsChangeRelevant:
|
|
3328
|
-
(transactionalValue, changeId) =>
|
|
3329
|
-
{
|
|
3330
|
-
checkTransactionalValue(transactionalValue);
|
|
3331
|
-
return (findTransactionalValueChange(transactionalValue, changeId) !== null);
|
|
3332
|
-
},
|
|
3333
|
-
|
|
3334
|
-
/**
|
|
3335
|
-
* Returns the committed value of the given TransactionalValue.
|
|
3336
|
-
*
|
|
3337
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3338
|
-
* @returns {*}
|
|
3339
|
-
*/
|
|
3340
|
-
transactionGetCommittedValue:
|
|
3341
|
-
(transactionalValue) =>
|
|
3342
|
-
{
|
|
3343
|
-
checkTransactionalValue(transactionalValue);
|
|
3344
|
-
return transactionalValue.value;
|
|
3345
|
-
},
|
|
3346
|
-
|
|
3347
|
-
/**
|
|
3348
|
-
* Returns the value (including any uncommitted changes made to it) of the given TransactionalValue.
|
|
3349
|
-
*
|
|
3350
|
-
* @param {LeUtils_TransactionalValue} transactionalValue
|
|
3351
|
-
* @returns {*}
|
|
3352
|
-
*/
|
|
3353
|
-
transactionGetValue:
|
|
3354
|
-
(transactionalValue) =>
|
|
3355
|
-
{
|
|
3356
|
-
checkTransactionalValue(transactionalValue);
|
|
3357
|
-
if(transactionalValue.changes.length <= 0)
|
|
3358
|
-
{
|
|
3359
|
-
return transactionalValue.value;
|
|
3360
|
-
}
|
|
3361
|
-
return transactionalValue.changes[transactionalValue.changes.length - 1].value;
|
|
3362
|
-
},
|
|
3363
|
-
|
|
3364
|
-
/**
|
|
3365
|
-
* Creates a worker thread. Workers have to be stored at /workers/{workerName}.worker.js for this to work.
|
|
3366
|
-
*
|
|
3367
|
-
* Example of a worker file:
|
|
3368
|
-
*
|
|
3369
|
-
* ```js
|
|
3370
|
-
* onmessage = (message) =>
|
|
3371
|
-
* {
|
|
3372
|
-
* postMessage({
|
|
3373
|
-
* ...message.data,
|
|
3374
|
-
* results: ['...some expensive calculation involving message.data...'],
|
|
3375
|
-
* });
|
|
3376
|
-
* };
|
|
3377
|
-
* ```
|
|
3378
|
-
*
|
|
3379
|
-
* Usage:
|
|
3380
|
-
*
|
|
3381
|
-
* ```js
|
|
3382
|
-
* const {results} = await (async () =>
|
|
3383
|
-
* {
|
|
3384
|
-
* try
|
|
3385
|
-
* {
|
|
3386
|
-
* return await LeUtils.sendWorkerMessage('my-worker', {someData:[1, 2, 3, 4, 5]});
|
|
3387
|
-
* }
|
|
3388
|
-
* catch(error)
|
|
3389
|
-
* {
|
|
3390
|
-
* console.error('MyWorker: ', error);
|
|
3391
|
-
* return {results:[]};
|
|
3392
|
-
* }
|
|
3393
|
-
* })();
|
|
3394
|
-
* ```
|
|
3395
|
-
*
|
|
3396
|
-
* or, if you want more control over the number of threads you have (the above example will only create 1 thread per worker):
|
|
3397
|
-
*
|
|
3398
|
-
* ```js
|
|
3399
|
-
* const myWorker1 = LeUtils.createWorkerThread('my-worker'); // creates a thread, you can create multiple worker threads of the same worker, to run multiple instances in parallel
|
|
3400
|
-
* const myWorker2 = LeUtils.createWorkerThread('my-worker'); // same worker, another thread
|
|
3401
|
-
* const {results} = await (async () =>
|
|
3402
|
-
* {
|
|
3403
|
-
* try
|
|
3404
|
-
* {
|
|
3405
|
-
* return await myWorker1.sendMessage({someData:[1, 2, 3, 4, 5]});
|
|
3406
|
-
* }
|
|
3407
|
-
* catch(error)
|
|
3408
|
-
* {
|
|
3409
|
-
* console.error('MyWorker: ', error);
|
|
3410
|
-
* return {results:[]};
|
|
3411
|
-
* }
|
|
3412
|
-
* })();
|
|
3413
|
-
* ```
|
|
3414
|
-
*
|
|
3415
|
-
* @param {string} name
|
|
3416
|
-
* @returns {{worker:Worker|null, sendMessage:(data:Object,options:{timeout:number|undefined}|undefined)=>Promise<Object>}}
|
|
3417
|
-
*/
|
|
3418
|
-
createWorkerThread:
|
|
3419
|
-
(name) =>
|
|
3420
|
-
{
|
|
3421
|
-
if(!globalThis?.Worker)
|
|
3422
|
-
{
|
|
3423
|
-
console.warn('LeUtils.createWorkerThread: Workers are not supported in this environment, returning a dummy worker.');
|
|
3424
|
-
return {
|
|
3425
|
-
worker: null,
|
|
3426
|
-
sendMessage:(data, options) => new Promise((resolve, reject) =>
|
|
3427
|
-
{
|
|
3428
|
-
reject('Workers are not supported in this environment');
|
|
3429
|
-
}),
|
|
3430
|
-
};
|
|
3431
|
-
}
|
|
3432
|
-
|
|
3433
|
-
const worker = new globalThis.Worker('/workers/' + name + '.worker.js');
|
|
3434
|
-
let listeners = new Map();
|
|
3435
|
-
|
|
3436
|
-
const addListener = (id, callback) =>
|
|
3437
|
-
{
|
|
3438
|
-
listeners.set(id, callback);
|
|
3439
|
-
};
|
|
3440
|
-
|
|
3441
|
-
const removeListener = (id) =>
|
|
3442
|
-
{
|
|
3443
|
-
listeners.delete(id);
|
|
3444
|
-
};
|
|
3445
|
-
|
|
3446
|
-
const sendMessage = (data, options) =>
|
|
3447
|
-
{
|
|
3448
|
-
return new Promise((resolve, reject) =>
|
|
3449
|
-
{
|
|
3450
|
-
const id = LeUtils.uniqueId();
|
|
3451
|
-
addListener(id, resolve);
|
|
3452
|
-
setTimeout(() =>
|
|
3453
|
-
{
|
|
3454
|
-
removeListener(id);
|
|
3455
|
-
reject('timeout');
|
|
3456
|
-
}, options?.timeout ?? 10000);
|
|
3457
|
-
|
|
3458
|
-
worker.postMessage({
|
|
3459
|
-
id,
|
|
3460
|
-
...data,
|
|
3461
|
-
});
|
|
3462
|
-
});
|
|
3463
|
-
};
|
|
3464
|
-
|
|
3465
|
-
worker.onerror = (error) =>
|
|
3466
|
-
{
|
|
3467
|
-
console.error('Worker ' + name + ':', error);
|
|
3468
|
-
};
|
|
3469
|
-
worker.onmessage = (message) =>
|
|
3470
|
-
{
|
|
3471
|
-
const data = message.data;
|
|
3472
|
-
if(data?.id)
|
|
3473
|
-
{
|
|
3474
|
-
const callback = listeners.get(data.id);
|
|
3475
|
-
if(callback)
|
|
3476
|
-
{
|
|
3477
|
-
removeListener(data.id);
|
|
3478
|
-
callback(data);
|
|
3479
|
-
}
|
|
3480
|
-
}
|
|
3481
|
-
};
|
|
3482
|
-
|
|
3483
|
-
return {worker, sendMessage};
|
|
3484
|
-
},
|
|
3485
|
-
|
|
3486
|
-
/**
|
|
3487
|
-
* Sends a message to the given worker. Creates a worker thread for this worker if it doesn't exist yet.
|
|
3488
|
-
*
|
|
3489
|
-
* See {@link LeUtils#createWorkerThread} for more info on how to use workers.
|
|
3490
|
-
*
|
|
3491
|
-
* @param {string} workerName
|
|
3492
|
-
* @param {Object} data
|
|
3493
|
-
* @param {{timeout:number|undefined}} [options]
|
|
3494
|
-
* @returns {Promise<Object>}
|
|
3495
|
-
*/
|
|
3496
|
-
sendWorkerMessage:
|
|
3497
|
-
(() =>
|
|
3498
|
-
{
|
|
3499
|
-
const workers = new Map();
|
|
3500
|
-
return (workerName, data, options) =>
|
|
3501
|
-
{
|
|
3502
|
-
if(!workers.has(workerName))
|
|
3503
|
-
{
|
|
3504
|
-
workers.set(workerName, LeUtils.createWorkerThread(workerName));
|
|
3505
|
-
}
|
|
3506
|
-
return workers.get(workerName).sendMessage(data, options);
|
|
3507
|
-
};
|
|
3508
|
-
})(),
|
|
3509
|
-
|
|
3510
|
-
/**
|
|
3511
|
-
* Purges the given email address, returning an empty string if it's invalid.
|
|
3512
|
-
*
|
|
3513
|
-
* @param {string} email
|
|
3514
|
-
* @returns {string}
|
|
3515
|
-
*/
|
|
3516
|
-
purgeEmail:
|
|
3517
|
-
(email) =>
|
|
3518
|
-
{
|
|
3519
|
-
email = STRING(email).trim().toLowerCase().replace(/\s/g, '');
|
|
3520
|
-
if(!email.includes('@') || !email.includes('.'))
|
|
3521
|
-
{
|
|
3522
|
-
return '';
|
|
3523
|
-
}
|
|
3524
|
-
return email;
|
|
3525
|
-
},
|
|
3526
|
-
|
|
3527
|
-
/**
|
|
3528
|
-
* Returns true if the focus is effectively clear, meaning that the user is not typing in an input field.
|
|
3529
|
-
*
|
|
3530
|
-
* @returns {boolean}
|
|
3531
|
-
*/
|
|
3532
|
-
isFocusClear:(() =>
|
|
3533
|
-
{
|
|
3534
|
-
const inputTypes = ['text', 'search', 'email', 'number', 'password', 'tel', 'time', 'url', 'week', 'month', 'date', 'datetime-local'];
|
|
3535
|
-
return () => !((globalThis?.document?.activeElement?.tagName?.toLowerCase() === 'input') && inputTypes.includes(globalThis?.document?.activeElement?.getAttribute('type')?.toLowerCase() ?? ''));
|
|
3536
|
-
})(),
|
|
3537
|
-
|
|
3538
|
-
/**
|
|
3539
|
-
* Returns the user's locale. Returns 'en-US' if it can't be determined.
|
|
3540
|
-
*
|
|
3541
|
-
* @returns {string}
|
|
3542
|
-
*/
|
|
3543
|
-
getUserLocale:(() =>
|
|
3544
|
-
{
|
|
3545
|
-
let userLocale = null;
|
|
3546
|
-
return () =>
|
|
3547
|
-
{
|
|
3548
|
-
if(userLocale === null)
|
|
3549
|
-
{
|
|
3550
|
-
userLocale = (() =>
|
|
3551
|
-
{
|
|
3552
|
-
let locales = globalThis?.navigator?.languages ?? [];
|
|
3553
|
-
if(!IS_ARRAY(locales) || (locales.length <= 0))
|
|
3554
|
-
{
|
|
3555
|
-
return 'en-US';
|
|
3556
|
-
}
|
|
3557
|
-
locales = locales.filter(locale => ((typeof locale === 'string') && locale.includes('-') && (locale.toLowerCase() !== 'en-us')));
|
|
3558
|
-
if(locales.length <= 0)
|
|
3559
|
-
{
|
|
3560
|
-
return 'en-US';
|
|
3561
|
-
}
|
|
3562
|
-
const localesNoEnglish = locales.filter(locale => !locale.toLowerCase().startsWith('en-'));
|
|
3563
|
-
if(localesNoEnglish.length <= 0)
|
|
3564
|
-
{
|
|
3565
|
-
return locales[0];
|
|
3566
|
-
}
|
|
3567
|
-
return localesNoEnglish[0];
|
|
3568
|
-
})();
|
|
3569
|
-
}
|
|
3570
|
-
return userLocale;
|
|
3571
|
-
};
|
|
3572
|
-
})(),
|
|
3573
|
-
|
|
3574
|
-
/**
|
|
3575
|
-
* Returns the user's locale date format. Always returns YYYY MM DD, with the character in between depending on the user's locale. Returns 'YYYY/MM/DD' if the user's locale can't be determined.
|
|
3576
|
-
*
|
|
3577
|
-
* @returns {string}
|
|
3578
|
-
*/
|
|
3579
|
-
getUserLocaleDateFormat:(() =>
|
|
3580
|
-
{
|
|
3581
|
-
let userLocaleDateFormat = null;
|
|
3582
|
-
return () =>
|
|
3583
|
-
{
|
|
3584
|
-
if(userLocaleDateFormat === null)
|
|
3585
|
-
{
|
|
3586
|
-
userLocaleDateFormat = (() =>
|
|
3587
|
-
{
|
|
3588
|
-
let char = '/';
|
|
3589
|
-
if(globalThis?.Intl?.DateTimeFormat)
|
|
3590
|
-
{
|
|
3591
|
-
const formattedDate = new globalThis.Intl.DateTimeFormat(LeUtils.getUserLocale()).format();
|
|
3592
|
-
if(formattedDate.includes('-'))
|
|
3593
|
-
{
|
|
3594
|
-
char = '-';
|
|
3595
|
-
}
|
|
3596
|
-
else if(formattedDate.includes('. '))
|
|
3597
|
-
{
|
|
3598
|
-
char = '.';
|
|
3599
|
-
}
|
|
3600
|
-
else if(formattedDate.includes('.'))
|
|
3601
|
-
{
|
|
3602
|
-
char = '.';
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
return 'YYYY' + char + 'MM' + char + 'DD';
|
|
3606
|
-
})();
|
|
3607
|
-
}
|
|
3608
|
-
return userLocaleDateFormat;
|
|
3609
|
-
};
|
|
3610
|
-
})(),
|
|
3611
|
-
};
|