@lowentry/utils 0.1.1 → 0.2.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/LeTypes.js +121 -0
- package/LeUtils.js +1829 -132
- package/index.js +2 -2
- package/package.json +1 -1
package/LeUtils.js
CHANGED
|
@@ -1,19 +1,52 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {ISSET, IS_OBJECT, STRING, INT, FLOAT, FLOAT_ANY, INT_LAX} from './LeTypes.js';
|
|
1
|
+
import {ISSET, IS_OBJECT, STRING, INT_LAX, FLOAT_LAX, INT_LAX_ANY, FLOAT_LAX_ANY} from './LeTypes.js';
|
|
3
2
|
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
6
|
+
*/
|
|
7
|
+
const checkTransactionalValue = (transactionalValue) =>
|
|
8
|
+
{
|
|
9
|
+
if(!LeUtils.isTransactionalValueValid(transactionalValue))
|
|
10
|
+
{
|
|
11
|
+
console.error('The given value is not a valid TransactionalValue:');
|
|
12
|
+
console.error(transactionalValue);
|
|
13
|
+
throw new Error('The given value is not a valid TransactionalValue');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
19
|
+
* @param {string} changeId
|
|
20
|
+
* @returns {{index:number, value:*}|null}
|
|
21
|
+
*/
|
|
22
|
+
const findTransactionalValueChange = (transactionalValue, changeId) =>
|
|
23
|
+
{
|
|
24
|
+
for(let i = 0; i < transactionalValue.changes.length; i++)
|
|
25
|
+
{
|
|
26
|
+
const change = transactionalValue.changes[i];
|
|
27
|
+
if(change.id === changeId)
|
|
10
28
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
return {index:i, value:change.value};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export const LeUtils = {
|
|
37
|
+
/**
|
|
38
|
+
* Parses the given version string, and returns an object with the major, minor, and patch numbers, as well as some comparison functions.
|
|
39
|
+
*
|
|
40
|
+
* Expects a version string such as:
|
|
41
|
+
* - "1"
|
|
42
|
+
* - "1.2"
|
|
43
|
+
* - "1.2.3"
|
|
44
|
+
* - "1.2.3 anything"
|
|
45
|
+
* - "1.2.3-anything"
|
|
46
|
+
*
|
|
47
|
+
* @param {string|*} versionString
|
|
48
|
+
* @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)}}
|
|
49
|
+
*/
|
|
17
50
|
parseVersionString:
|
|
18
51
|
(versionString) =>
|
|
19
52
|
{
|
|
@@ -104,6 +137,15 @@ export const LeUtils = {
|
|
|
104
137
|
return THIS;
|
|
105
138
|
},
|
|
106
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Returns true if the array or object contains the given value.
|
|
142
|
+
*
|
|
143
|
+
* Values are compared by casting both of them to a string.
|
|
144
|
+
*
|
|
145
|
+
* @param {array|object|Function} array
|
|
146
|
+
* @param {*} value
|
|
147
|
+
* @returns {boolean}
|
|
148
|
+
*/
|
|
107
149
|
contains:
|
|
108
150
|
(array, value) =>
|
|
109
151
|
{
|
|
@@ -124,6 +166,15 @@ export const LeUtils = {
|
|
|
124
166
|
return result;
|
|
125
167
|
},
|
|
126
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Returns true if the array or object contains the given value.
|
|
171
|
+
*
|
|
172
|
+
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
173
|
+
*
|
|
174
|
+
* @param {array|object|Function} array
|
|
175
|
+
* @param {*} value
|
|
176
|
+
* @returns {boolean}
|
|
177
|
+
*/
|
|
127
178
|
containsCaseInsensitive:
|
|
128
179
|
(array, value) =>
|
|
129
180
|
{
|
|
@@ -144,6 +195,187 @@ export const LeUtils = {
|
|
|
144
195
|
return result;
|
|
145
196
|
},
|
|
146
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Returns true if the array or object contains all the given values.
|
|
200
|
+
*
|
|
201
|
+
* Values are compared by casting both of them to a string.
|
|
202
|
+
*
|
|
203
|
+
* @param {array|object|Function} array
|
|
204
|
+
* @param {array|object|Function} values
|
|
205
|
+
* @returns {boolean}
|
|
206
|
+
*/
|
|
207
|
+
containsAll:
|
|
208
|
+
(array, values) =>
|
|
209
|
+
{
|
|
210
|
+
if(!array)
|
|
211
|
+
{
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
let result = true;
|
|
215
|
+
LeUtils.each(values, function(value)
|
|
216
|
+
{
|
|
217
|
+
if(!LeUtils.contains(array, value))
|
|
218
|
+
{
|
|
219
|
+
result = false;
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return result;
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Returns true if the array or object contains all the given values.
|
|
228
|
+
*
|
|
229
|
+
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
230
|
+
*
|
|
231
|
+
* @param {array|object|Function} array
|
|
232
|
+
* @param {array|object|Function} values
|
|
233
|
+
* @returns {boolean}
|
|
234
|
+
*/
|
|
235
|
+
containsAllCaseInsensitive:
|
|
236
|
+
(array, values) =>
|
|
237
|
+
{
|
|
238
|
+
if(!array)
|
|
239
|
+
{
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
let result = true;
|
|
243
|
+
LeUtils.each(values, function(value)
|
|
244
|
+
{
|
|
245
|
+
if(!LeUtils.containsCaseInsensitive(array, value))
|
|
246
|
+
{
|
|
247
|
+
result = false;
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
return result;
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Returns true if the array or object contains any of the given values.
|
|
256
|
+
*
|
|
257
|
+
* Values are compared by casting both of them to a string.
|
|
258
|
+
*
|
|
259
|
+
* @param {array|object|Function} array
|
|
260
|
+
* @param {array|object|Function} values
|
|
261
|
+
* @returns {boolean}
|
|
262
|
+
*/
|
|
263
|
+
containsAny:
|
|
264
|
+
(array, values) =>
|
|
265
|
+
{
|
|
266
|
+
if(!array)
|
|
267
|
+
{
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
let result = false;
|
|
271
|
+
LeUtils.each(values, function(value)
|
|
272
|
+
{
|
|
273
|
+
if(LeUtils.contains(array, value))
|
|
274
|
+
{
|
|
275
|
+
result = true;
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
return result;
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Returns true if the array or object contains any of the given values.
|
|
284
|
+
*
|
|
285
|
+
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
286
|
+
*
|
|
287
|
+
* @param {array|object|Function} array
|
|
288
|
+
* @param {array|object|Function} values
|
|
289
|
+
* @returns {boolean}
|
|
290
|
+
*/
|
|
291
|
+
containsAnyCaseInsensitive:
|
|
292
|
+
(array, values) =>
|
|
293
|
+
{
|
|
294
|
+
if(!array)
|
|
295
|
+
{
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
let result = false;
|
|
299
|
+
LeUtils.each(values, function(value)
|
|
300
|
+
{
|
|
301
|
+
if(LeUtils.containsCaseInsensitive(array, value))
|
|
302
|
+
{
|
|
303
|
+
result = true;
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
return result;
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Returns true if the array or object contains none of the given values.
|
|
312
|
+
*
|
|
313
|
+
* Values are compared by casting both of them to a string.
|
|
314
|
+
*
|
|
315
|
+
* @param {array|object|Function} array
|
|
316
|
+
* @param {array|object|Function} values
|
|
317
|
+
* @returns {boolean}
|
|
318
|
+
*/
|
|
319
|
+
containsNone:
|
|
320
|
+
(array, values) =>
|
|
321
|
+
{
|
|
322
|
+
if(!array)
|
|
323
|
+
{
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
let result = true;
|
|
327
|
+
LeUtils.each(values, function(value)
|
|
328
|
+
{
|
|
329
|
+
if(LeUtils.contains(array, value))
|
|
330
|
+
{
|
|
331
|
+
result = false;
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
return result;
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Returns true if the array or object contains none of the given values.
|
|
340
|
+
*
|
|
341
|
+
* Values are compared by casting both of them to a string, and then lowercasing them.
|
|
342
|
+
*
|
|
343
|
+
* @param {array|object|Function} array
|
|
344
|
+
* @param {array|object|Function} values
|
|
345
|
+
* @returns {boolean}
|
|
346
|
+
*/
|
|
347
|
+
containsNoneCaseInsensitive:
|
|
348
|
+
(array, values) =>
|
|
349
|
+
{
|
|
350
|
+
if(!array)
|
|
351
|
+
{
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
let result = true;
|
|
355
|
+
LeUtils.each(values, function(value)
|
|
356
|
+
{
|
|
357
|
+
if(LeUtils.containsCaseInsensitive(array, value))
|
|
358
|
+
{
|
|
359
|
+
result = false;
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
return result;
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @callback LeUtils~__eachCallback
|
|
368
|
+
* @param {*} value
|
|
369
|
+
* @param {*} index
|
|
370
|
+
*/
|
|
371
|
+
/**
|
|
372
|
+
* Loops through each element in the given array or object, and calls the callback for each element.
|
|
373
|
+
*
|
|
374
|
+
* @param {*[]|object|Function} elements
|
|
375
|
+
* @param {LeUtils~__eachCallback} callback
|
|
376
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
377
|
+
* @returns {*[]|object|Function}
|
|
378
|
+
*/
|
|
147
379
|
each:
|
|
148
380
|
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
149
381
|
{
|
|
@@ -180,6 +412,115 @@ export const LeUtils = {
|
|
|
180
412
|
return elements;
|
|
181
413
|
},
|
|
182
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Like LeUtils.each(), except that it expects an async callback.
|
|
417
|
+
*
|
|
418
|
+
* @param {*[]|object|function} elements
|
|
419
|
+
* @param {LeUtils~__eachCallback} asyncCallback
|
|
420
|
+
* @param {number} [optionalParallelCount]
|
|
421
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
422
|
+
* @returns {*[]|object|function}
|
|
423
|
+
*/
|
|
424
|
+
eachAsync:
|
|
425
|
+
(() =>
|
|
426
|
+
{
|
|
427
|
+
const eachAsyncParallel = async (elements, asyncCallback, optionalParallelCount, optionalSkipHasOwnPropertyCheck) =>
|
|
428
|
+
{
|
|
429
|
+
let promises = [];
|
|
430
|
+
let doBreak = false;
|
|
431
|
+
await LeUtils.eachAsync(elements, async (element, index) =>
|
|
432
|
+
{
|
|
433
|
+
while(promises.length > optionalParallelCount)
|
|
434
|
+
{
|
|
435
|
+
let newPromises = [];
|
|
436
|
+
LeUtils.each(promises, (promise) =>
|
|
437
|
+
{
|
|
438
|
+
if(!promise.__lowentry_utils__promise_is_done__)
|
|
439
|
+
{
|
|
440
|
+
newPromises.push(promise);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
promises = newPromises;
|
|
444
|
+
if(promises.length > optionalParallelCount)
|
|
445
|
+
{
|
|
446
|
+
await Promise.any(promises);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if(doBreak)
|
|
451
|
+
{
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const promise = (async () =>
|
|
456
|
+
{
|
|
457
|
+
if((await asyncCallback.call(element, element, index)) === false)
|
|
458
|
+
{
|
|
459
|
+
doBreak = true;
|
|
460
|
+
}
|
|
461
|
+
promise.__lowentry_utils__promise_is_done__ = true;
|
|
462
|
+
})();
|
|
463
|
+
promises.push(promise);
|
|
464
|
+
}, optionalSkipHasOwnPropertyCheck);
|
|
465
|
+
await Promise.all(promises);
|
|
466
|
+
return elements;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return async (elements, asyncCallback, parallelCount = 1, optionalSkipHasOwnPropertyCheck = false) =>
|
|
470
|
+
{
|
|
471
|
+
if((elements !== null) && (typeof elements !== 'undefined'))
|
|
472
|
+
{
|
|
473
|
+
parallelCount = INT_LAX(parallelCount);
|
|
474
|
+
if(parallelCount > 1)
|
|
475
|
+
{
|
|
476
|
+
return await eachAsyncParallel(elements, asyncCallback, parallelCount, optionalSkipHasOwnPropertyCheck);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if(Array.isArray(elements))
|
|
480
|
+
{
|
|
481
|
+
for(let index = 0; index < elements.length; index++)
|
|
482
|
+
{
|
|
483
|
+
if((await asyncCallback.call(elements[index], elements[index], index)) === false)
|
|
484
|
+
{
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else if((typeof elements === 'object') || (typeof elements === 'function'))
|
|
490
|
+
{
|
|
491
|
+
for(let index in elements)
|
|
492
|
+
{
|
|
493
|
+
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
|
|
494
|
+
{
|
|
495
|
+
if((await asyncCallback.call(elements[index], elements[index], index)) === false)
|
|
496
|
+
{
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else
|
|
503
|
+
{
|
|
504
|
+
console.warn('Executed LeUtils.eachAsync() on an invalid type: [' + (typeof elements) + ']', elements);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return elements;
|
|
508
|
+
};
|
|
509
|
+
})(),
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @callback LeUtils~__filterCallback
|
|
513
|
+
* @param {*} value
|
|
514
|
+
* @param {*} index
|
|
515
|
+
*/
|
|
516
|
+
/**
|
|
517
|
+
* Loops through the given elements, and returns a new array or object, with only the elements that didn't return false from the callback.
|
|
518
|
+
*
|
|
519
|
+
* @param {*[]|object|Function} elements
|
|
520
|
+
* @param {LeUtils~__filterCallback} callback
|
|
521
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
522
|
+
* @returns {*[]|object|Function}
|
|
523
|
+
*/
|
|
183
524
|
filter:
|
|
184
525
|
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
185
526
|
{
|
|
@@ -220,6 +561,19 @@ export const LeUtils = {
|
|
|
220
561
|
return elements;
|
|
221
562
|
},
|
|
222
563
|
|
|
564
|
+
/**
|
|
565
|
+
* @callback LeUtils~__mapCallback
|
|
566
|
+
* @param {*} value
|
|
567
|
+
* @param {*} index
|
|
568
|
+
*/
|
|
569
|
+
/**
|
|
570
|
+
* Loops through the given elements, and returns a new array or object, with the elements that were returned from the callback.
|
|
571
|
+
*
|
|
572
|
+
* @param {*[]|object|Function} elements
|
|
573
|
+
* @param {LeUtils~__mapCallback} callback
|
|
574
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
575
|
+
* @returns {*[]|object|Function}
|
|
576
|
+
*/
|
|
223
577
|
map:
|
|
224
578
|
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
225
579
|
{
|
|
@@ -254,6 +608,19 @@ export const LeUtils = {
|
|
|
254
608
|
return elements;
|
|
255
609
|
},
|
|
256
610
|
|
|
611
|
+
/**
|
|
612
|
+
* @callback LeUtils~__mapToArrayCallback
|
|
613
|
+
* @param {*} value
|
|
614
|
+
* @param {*} index
|
|
615
|
+
*/
|
|
616
|
+
/**
|
|
617
|
+
* Loops through the given elements, and returns a new array, with the elements that were returned from the callback. Always returns an array.
|
|
618
|
+
*
|
|
619
|
+
* @param {*[]|object|Function} elements
|
|
620
|
+
* @param {LeUtils~__mapToArrayCallback} callback
|
|
621
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
622
|
+
* @returns {*[]}
|
|
623
|
+
*/
|
|
257
624
|
mapToArray:
|
|
258
625
|
(elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
259
626
|
{
|
|
@@ -285,6 +652,20 @@ export const LeUtils = {
|
|
|
285
652
|
return result;
|
|
286
653
|
},
|
|
287
654
|
|
|
655
|
+
/**
|
|
656
|
+
* @callback LeUtils~__mapToArraySortedCallback
|
|
657
|
+
* @param {*} value
|
|
658
|
+
* @param {*} index
|
|
659
|
+
*/
|
|
660
|
+
/**
|
|
661
|
+
* 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.
|
|
662
|
+
*
|
|
663
|
+
* @param {*[]|object|Function} elements
|
|
664
|
+
* @param {LeUtils~__sortKeysComparatorCallback} comparator
|
|
665
|
+
* @param {LeUtils~__mapToArraySortedCallback} callback
|
|
666
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
667
|
+
* @returns {*[]}
|
|
668
|
+
*/
|
|
288
669
|
mapToArraySorted:
|
|
289
670
|
(elements, comparator, callback, optionalSkipHasOwnPropertyCheck = false) =>
|
|
290
671
|
{
|
|
@@ -297,6 +678,19 @@ export const LeUtils = {
|
|
|
297
678
|
return result;
|
|
298
679
|
},
|
|
299
680
|
|
|
681
|
+
/**
|
|
682
|
+
* @callback LeUtils~__sortKeysComparatorCallback
|
|
683
|
+
* @param {*} elementA
|
|
684
|
+
* @param {*} elementB
|
|
685
|
+
*/
|
|
686
|
+
/**
|
|
687
|
+
* 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.
|
|
688
|
+
*
|
|
689
|
+
* @param {*[]|object|Function} elements
|
|
690
|
+
* @param {LeUtils~__sortKeysComparatorCallback} comparator
|
|
691
|
+
* @param {boolean} [optionalSkipHasOwnPropertyCheck]
|
|
692
|
+
* @returns {*[]}
|
|
693
|
+
*/
|
|
300
694
|
sortKeys:
|
|
301
695
|
(elements, comparator, optionalSkipHasOwnPropertyCheck = false) =>
|
|
302
696
|
{
|
|
@@ -329,57 +723,14 @@ export const LeUtils = {
|
|
|
329
723
|
return keys;
|
|
330
724
|
},
|
|
331
725
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
{
|
|
341
|
-
return 1;
|
|
342
|
-
}
|
|
343
|
-
return 0;
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
compareNumbers:
|
|
347
|
-
(a, b) => a - b,
|
|
348
|
-
|
|
349
|
-
compareNumericStrings:
|
|
350
|
-
(a, b) =>
|
|
351
|
-
{
|
|
352
|
-
a = STRING(a).trim();
|
|
353
|
-
b = STRING(b).trim();
|
|
354
|
-
if(a.length === b.length)
|
|
355
|
-
{
|
|
356
|
-
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
|
357
|
-
}
|
|
358
|
-
return (a.length < b.length) ? -1 : 1;
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
isEmptyObject:
|
|
362
|
-
(obj) =>
|
|
363
|
-
{
|
|
364
|
-
// noinspection LoopStatementThatDoesntLoopJS
|
|
365
|
-
for(let name in obj)
|
|
366
|
-
{
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
return true;
|
|
370
|
-
},
|
|
371
|
-
|
|
372
|
-
getObjectFieldsCount:
|
|
373
|
-
(obj) =>
|
|
374
|
-
{
|
|
375
|
-
let count = 0;
|
|
376
|
-
for(let name in obj)
|
|
377
|
-
{
|
|
378
|
-
count++;
|
|
379
|
-
}
|
|
380
|
-
return count;
|
|
381
|
-
},
|
|
382
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Turns the given value(s) into a 1 dimensional array.
|
|
728
|
+
*
|
|
729
|
+
* Does the same thing as Array.flat(Infinity).
|
|
730
|
+
*
|
|
731
|
+
* @param {*} array
|
|
732
|
+
* @returns {*[]}
|
|
733
|
+
*/
|
|
383
734
|
flattenArray:
|
|
384
735
|
(() =>
|
|
385
736
|
{
|
|
@@ -411,11 +762,97 @@ export const LeUtils = {
|
|
|
411
762
|
};
|
|
412
763
|
})(),
|
|
413
764
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Compares two values. Primarily used for sorting.
|
|
767
|
+
*
|
|
768
|
+
* @param {*} a
|
|
769
|
+
* @param {*} b
|
|
770
|
+
* @returns {number}
|
|
771
|
+
*/
|
|
772
|
+
compare:
|
|
773
|
+
(a, b) => (a < b) ? -1 : ((a > b) ? 1 : 0),
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Compares two numbers. Primarily used for sorting.
|
|
777
|
+
*
|
|
778
|
+
* @param {number} a
|
|
779
|
+
* @param {number} b
|
|
780
|
+
* @returns {number}
|
|
781
|
+
*/
|
|
782
|
+
compareNumbers:
|
|
783
|
+
(a, b) => a - b,
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Compares two numeric strings. Primarily used for sorting.
|
|
787
|
+
*
|
|
788
|
+
* @param {string|number} a
|
|
789
|
+
* @param {string|number} b
|
|
790
|
+
* @returns {number}
|
|
791
|
+
*/
|
|
792
|
+
compareNumericStrings:
|
|
793
|
+
(a, b) =>
|
|
794
|
+
{
|
|
795
|
+
a = STRING(a).trim();
|
|
796
|
+
b = STRING(b).trim();
|
|
797
|
+
if(a.length === b.length)
|
|
798
|
+
{
|
|
799
|
+
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
|
800
|
+
}
|
|
801
|
+
return (a.length < b.length) ? -1 : 1;
|
|
802
|
+
},
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Returns true if the given object is empty, false otherwise.
|
|
806
|
+
*
|
|
807
|
+
* @param {object} obj
|
|
808
|
+
* @param [optionalSkipHasOwnPropertyCheck]
|
|
809
|
+
* @returns {boolean}
|
|
810
|
+
*/
|
|
811
|
+
isEmptyObject:
|
|
812
|
+
(obj, optionalSkipHasOwnPropertyCheck = false) =>
|
|
813
|
+
{
|
|
814
|
+
for(let field in obj)
|
|
815
|
+
{
|
|
816
|
+
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(obj, field))
|
|
817
|
+
{
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return true;
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Returns the number of fields in the given object.
|
|
826
|
+
*
|
|
827
|
+
* @param {object} obj
|
|
828
|
+
* @param [optionalSkipHasOwnPropertyCheck]
|
|
829
|
+
* @returns {number}
|
|
830
|
+
*/
|
|
831
|
+
getObjectFieldsCount:
|
|
832
|
+
(obj, optionalSkipHasOwnPropertyCheck = false) =>
|
|
833
|
+
{
|
|
834
|
+
let count = 0;
|
|
835
|
+
for(let field in obj)
|
|
836
|
+
{
|
|
837
|
+
if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(obj, field))
|
|
838
|
+
{
|
|
839
|
+
count++;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return count;
|
|
843
|
+
},
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Returns true if the given function is a generator function (like: "function* (){}"), returns false otherwise.
|
|
847
|
+
*
|
|
848
|
+
* @param {Function} func
|
|
849
|
+
* @returns {boolean}
|
|
850
|
+
*/
|
|
851
|
+
isGeneratorFunction:
|
|
852
|
+
(() =>
|
|
853
|
+
{
|
|
854
|
+
const GeneratorFunction = function* ()
|
|
855
|
+
{
|
|
419
856
|
}.constructor;
|
|
420
857
|
|
|
421
858
|
const AsyncGeneratorFunction = async function* ()
|
|
@@ -426,7 +863,7 @@ export const LeUtils = {
|
|
|
426
863
|
{
|
|
427
864
|
}.constructor;
|
|
428
865
|
|
|
429
|
-
const PossibleGeneratorFunctionNames = Array.from(new Set(['GeneratorFunction', 'AsyncFunction', 'AsyncGeneratorFunction', GeneratorFunction.name, GeneratorFunction.displayName, AsyncGeneratorFunction.name, AsyncGeneratorFunction.displayName])).filter(
|
|
866
|
+
const PossibleGeneratorFunctionNames = Array.from(new Set(['GeneratorFunction', 'AsyncFunction', 'AsyncGeneratorFunction', GeneratorFunction.name, GeneratorFunction.displayName, AsyncGeneratorFunction.name, AsyncGeneratorFunction.displayName])).filter((element) =>
|
|
430
867
|
{
|
|
431
868
|
return (element && (element !== RegularFunction.name) && (element !== RegularFunction.displayName));
|
|
432
869
|
});
|
|
@@ -446,10 +883,70 @@ export const LeUtils = {
|
|
|
446
883
|
};
|
|
447
884
|
})(),
|
|
448
885
|
|
|
886
|
+
/**
|
|
887
|
+
* @callback LeUtils~__setTimeoutCallback
|
|
888
|
+
* @param {number} deltaTime
|
|
889
|
+
*/
|
|
890
|
+
/**
|
|
891
|
+
* Executes the callback after the given number of milliseconds. Passes the elapsed time in seconds to the callback.
|
|
892
|
+
*
|
|
893
|
+
* To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = LeUtils.setTimeout((deltaTime)=>{}, 1000); timeoutHandler.remove();")
|
|
894
|
+
*
|
|
895
|
+
* @param {LeUtils~__setTimeoutCallback} callback ([number] deltaTime)
|
|
896
|
+
* @param {number} ms
|
|
897
|
+
* @returns {{remove:Function}}
|
|
898
|
+
*/
|
|
899
|
+
setTimeout:
|
|
900
|
+
(callback, ms) =>
|
|
901
|
+
{
|
|
902
|
+
ms = FLOAT_LAX(ms);
|
|
903
|
+
|
|
904
|
+
let lastTime = performance.now();
|
|
905
|
+
let handler = setTimeout(() =>
|
|
906
|
+
{
|
|
907
|
+
const currentTime = performance.now();
|
|
908
|
+
try
|
|
909
|
+
{
|
|
910
|
+
callback((currentTime - lastTime) / 1000);
|
|
911
|
+
}
|
|
912
|
+
catch(e)
|
|
913
|
+
{
|
|
914
|
+
console.error(e);
|
|
915
|
+
}
|
|
916
|
+
lastTime = currentTime;
|
|
917
|
+
}, ms);
|
|
918
|
+
|
|
919
|
+
return {
|
|
920
|
+
remove:
|
|
921
|
+
() =>
|
|
922
|
+
{
|
|
923
|
+
if(handler !== null)
|
|
924
|
+
{
|
|
925
|
+
clearTimeout(handler);
|
|
926
|
+
handler = null;
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
};
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* @callback LeUtils~__setIntervalCallback
|
|
934
|
+
* @param {number} deltaTime
|
|
935
|
+
*/
|
|
936
|
+
/**
|
|
937
|
+
* Executes the callback every given number of milliseconds. Passes the time difference in seconds between the last frame and now to it.
|
|
938
|
+
*
|
|
939
|
+
* To remove the interval, call remove() on the result of this function (example: "const intervalHandler = LeUtils.setInterval((deltaTime)=>{}, 1000); intervalHandler.remove();")
|
|
940
|
+
*
|
|
941
|
+
* @param {LeUtils~__setIntervalCallback} callback ([number] deltaTime)
|
|
942
|
+
* @param {number} [intervalMs]
|
|
943
|
+
* @param {boolean} [fireImmediately]
|
|
944
|
+
* @returns {{remove:Function}}
|
|
945
|
+
*/
|
|
449
946
|
setInterval:
|
|
450
|
-
(callback, intervalMs, fireImmediately) =>
|
|
947
|
+
(callback, intervalMs = 1000, fireImmediately = false) =>
|
|
451
948
|
{
|
|
452
|
-
intervalMs =
|
|
949
|
+
intervalMs = FLOAT_LAX_ANY(intervalMs, 1000);
|
|
453
950
|
|
|
454
951
|
if(fireImmediately)
|
|
455
952
|
{
|
|
@@ -466,7 +963,7 @@ export const LeUtils = {
|
|
|
466
963
|
let lastTime = performance.now();
|
|
467
964
|
let handler = setInterval(() =>
|
|
468
965
|
{
|
|
469
|
-
|
|
966
|
+
const currentTime = performance.now();
|
|
470
967
|
try
|
|
471
968
|
{
|
|
472
969
|
callback((currentTime - lastTime) / 1000);
|
|
@@ -491,34 +988,36 @@ export const LeUtils = {
|
|
|
491
988
|
};
|
|
492
989
|
},
|
|
493
990
|
|
|
494
|
-
|
|
495
|
-
|
|
991
|
+
/**
|
|
992
|
+
* @callback LeUtils~__setAnimationFrameTimeoutCallback
|
|
993
|
+
* @param {number} deltaTime
|
|
994
|
+
*/
|
|
995
|
+
/**
|
|
996
|
+
* Executes the callback after the given number of frames. Passes the elapsed time in seconds to the callback.
|
|
997
|
+
*
|
|
998
|
+
* To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = LeUtils.setAnimationFrameTimeout((deltaTime){}, 5); timeoutHandler.remove();")
|
|
999
|
+
*
|
|
1000
|
+
* @param {LeUtils~__setAnimationFrameTimeoutCallback} callback ([number] deltaTime)
|
|
1001
|
+
* @param {number} [frames]
|
|
1002
|
+
* @returns {{remove:Function}}
|
|
1003
|
+
*/
|
|
1004
|
+
setAnimationFrameTimeout:
|
|
1005
|
+
(callback, frames = 1) =>
|
|
496
1006
|
{
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if(fireImmediately)
|
|
500
|
-
{
|
|
501
|
-
try
|
|
502
|
-
{
|
|
503
|
-
callback(0);
|
|
504
|
-
}
|
|
505
|
-
catch(e)
|
|
506
|
-
{
|
|
507
|
-
console.error(e);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
1007
|
+
frames = INT_LAX_ANY(frames, 1);
|
|
510
1008
|
|
|
511
1009
|
let run = true;
|
|
512
1010
|
let requestAnimationFrameId = null;
|
|
513
1011
|
let lastTime = performance.now();
|
|
514
|
-
let frames = intervalFrames;
|
|
515
1012
|
const tick = () =>
|
|
516
1013
|
{
|
|
517
1014
|
if(run)
|
|
518
1015
|
{
|
|
519
1016
|
if(frames <= 0)
|
|
520
1017
|
{
|
|
521
|
-
|
|
1018
|
+
run = false;
|
|
1019
|
+
requestAnimationFrameId = null;
|
|
1020
|
+
const currentTime = performance.now();
|
|
522
1021
|
try
|
|
523
1022
|
{
|
|
524
1023
|
callback((currentTime - lastTime) / 1000);
|
|
@@ -528,17 +1027,13 @@ export const LeUtils = {
|
|
|
528
1027
|
console.error(e);
|
|
529
1028
|
}
|
|
530
1029
|
lastTime = currentTime;
|
|
531
|
-
|
|
1030
|
+
return;
|
|
532
1031
|
}
|
|
533
1032
|
frames--;
|
|
534
|
-
|
|
535
|
-
if(run)
|
|
536
|
-
{
|
|
537
|
-
requestAnimationFrameId = window?.requestAnimationFrame(tick);
|
|
538
|
-
}
|
|
1033
|
+
requestAnimationFrameId = (typeof window === 'undefined') ? setTimeout(tick, 1000 / 60) : requestAnimationFrame(tick);
|
|
539
1034
|
}
|
|
540
1035
|
};
|
|
541
|
-
|
|
1036
|
+
tick();
|
|
542
1037
|
|
|
543
1038
|
return {
|
|
544
1039
|
remove:
|
|
@@ -547,43 +1042,75 @@ export const LeUtils = {
|
|
|
547
1042
|
run = false;
|
|
548
1043
|
if(requestAnimationFrameId !== null)
|
|
549
1044
|
{
|
|
550
|
-
cancelAnimationFrame(requestAnimationFrameId);
|
|
1045
|
+
(typeof window === 'undefined') ? clearTimeout(requestAnimationFrameId) : cancelAnimationFrame(requestAnimationFrameId);
|
|
551
1046
|
requestAnimationFrameId = null;
|
|
552
1047
|
}
|
|
553
1048
|
},
|
|
554
1049
|
};
|
|
555
1050
|
},
|
|
556
1051
|
|
|
557
|
-
|
|
558
|
-
|
|
1052
|
+
/**
|
|
1053
|
+
* @callback LeUtils~__setAnimationFrameIntervalCallback
|
|
1054
|
+
* @param {number} deltaTime
|
|
1055
|
+
*/
|
|
1056
|
+
/**
|
|
1057
|
+
* Executes the callback every given number of frames. Passes the time difference in seconds between the last frame and now to it.
|
|
1058
|
+
*
|
|
1059
|
+
* To remove the interval, call remove() on the result of this function (example: "const intervalHandler = LeUtils.setAnimationFrameInterval((deltaTime)=>{}, 5); intervalHandler.remove();")
|
|
1060
|
+
*
|
|
1061
|
+
* @param {LeUtils~__setAnimationFrameIntervalCallback} callback ([number] deltaTime)
|
|
1062
|
+
* @param {number} [intervalFrames]
|
|
1063
|
+
* @param {boolean} [fireImmediately]
|
|
1064
|
+
* @returns {{remove:Function}}
|
|
1065
|
+
*/
|
|
1066
|
+
setAnimationFrameInterval:
|
|
1067
|
+
(callback, intervalFrames = 1, fireImmediately = false) =>
|
|
559
1068
|
{
|
|
560
|
-
|
|
1069
|
+
intervalFrames = INT_LAX_ANY(intervalFrames, 1);
|
|
1070
|
+
|
|
1071
|
+
if(fireImmediately)
|
|
1072
|
+
{
|
|
1073
|
+
try
|
|
1074
|
+
{
|
|
1075
|
+
callback(0);
|
|
1076
|
+
}
|
|
1077
|
+
catch(e)
|
|
1078
|
+
{
|
|
1079
|
+
console.error(e);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
561
1082
|
|
|
562
1083
|
let run = true;
|
|
563
1084
|
let requestAnimationFrameId = null;
|
|
1085
|
+
let lastTime = performance.now();
|
|
1086
|
+
let frames = intervalFrames;
|
|
564
1087
|
const tick = () =>
|
|
565
1088
|
{
|
|
566
1089
|
if(run)
|
|
567
1090
|
{
|
|
568
1091
|
if(frames <= 0)
|
|
569
1092
|
{
|
|
570
|
-
|
|
571
|
-
requestAnimationFrameId = null;
|
|
1093
|
+
let currentTime = performance.now();
|
|
572
1094
|
try
|
|
573
1095
|
{
|
|
574
|
-
callback();
|
|
1096
|
+
callback((currentTime - lastTime) / 1000);
|
|
575
1097
|
}
|
|
576
1098
|
catch(e)
|
|
577
1099
|
{
|
|
578
1100
|
console.error(e);
|
|
579
1101
|
}
|
|
580
|
-
|
|
1102
|
+
lastTime = currentTime;
|
|
1103
|
+
frames = intervalFrames;
|
|
581
1104
|
}
|
|
582
1105
|
frames--;
|
|
583
|
-
|
|
1106
|
+
|
|
1107
|
+
if(run)
|
|
1108
|
+
{
|
|
1109
|
+
requestAnimationFrameId = (typeof window === 'undefined') ? setTimeout(tick, 1000 / 60) : requestAnimationFrame(tick);
|
|
1110
|
+
}
|
|
584
1111
|
}
|
|
585
1112
|
};
|
|
586
|
-
tick();
|
|
1113
|
+
(typeof window === 'undefined') ? setTimeout(tick, 1000 / 60) : requestAnimationFrame(tick);
|
|
587
1114
|
|
|
588
1115
|
return {
|
|
589
1116
|
remove:
|
|
@@ -592,35 +1119,45 @@ export const LeUtils = {
|
|
|
592
1119
|
run = false;
|
|
593
1120
|
if(requestAnimationFrameId !== null)
|
|
594
1121
|
{
|
|
595
|
-
cancelAnimationFrame(requestAnimationFrameId);
|
|
1122
|
+
(typeof window === 'undefined') ? clearTimeout(requestAnimationFrameId) : cancelAnimationFrame(requestAnimationFrameId);
|
|
596
1123
|
requestAnimationFrameId = null;
|
|
597
1124
|
}
|
|
598
1125
|
},
|
|
599
1126
|
};
|
|
600
1127
|
},
|
|
601
1128
|
|
|
602
|
-
|
|
603
|
-
|
|
1129
|
+
/**
|
|
1130
|
+
* Returns a promise, which will be resolved after the given number of milliseconds.
|
|
1131
|
+
*
|
|
1132
|
+
* @param {number} ms
|
|
1133
|
+
* @returns {Promise}
|
|
1134
|
+
*/
|
|
1135
|
+
promiseTimeout:
|
|
1136
|
+
(ms) =>
|
|
604
1137
|
{
|
|
605
|
-
|
|
606
|
-
if(
|
|
1138
|
+
ms = FLOAT_LAX(ms);
|
|
1139
|
+
if(ms <= 0)
|
|
607
1140
|
{
|
|
608
|
-
return
|
|
1141
|
+
return new Promise(resolve => resolve());
|
|
609
1142
|
}
|
|
610
|
-
return
|
|
1143
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
611
1144
|
},
|
|
612
1145
|
|
|
613
|
-
|
|
614
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* Returns a promise, which will be resolved after the given number of frames.
|
|
1148
|
+
*
|
|
1149
|
+
* @param {number} frames
|
|
1150
|
+
* @returns {Promise}
|
|
1151
|
+
*/
|
|
1152
|
+
promiseAnimationFrameTimeout:
|
|
1153
|
+
(frames) =>
|
|
615
1154
|
{
|
|
616
|
-
|
|
1155
|
+
frames = INT_LAX(frames);
|
|
1156
|
+
if(frames <= 0)
|
|
617
1157
|
{
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
callback();
|
|
622
|
-
}
|
|
623
|
-
};
|
|
1158
|
+
return new Promise(resolve => resolve());
|
|
1159
|
+
}
|
|
1160
|
+
return new Promise(resolve => LeUtils.setAnimationFrameTimeout(resolve, frames));
|
|
624
1161
|
},
|
|
625
1162
|
|
|
626
1163
|
/**
|
|
@@ -631,13 +1168,19 @@ export const LeUtils = {
|
|
|
631
1168
|
* - Mobile: True
|
|
632
1169
|
* - Tablet: False
|
|
633
1170
|
* - Desktop: False
|
|
1171
|
+
*
|
|
1172
|
+
* @returns {boolean}
|
|
634
1173
|
*/
|
|
635
1174
|
platformIsMobile:
|
|
636
1175
|
() =>
|
|
637
1176
|
{
|
|
1177
|
+
if(typeof window === 'undefined')
|
|
1178
|
+
{
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
638
1181
|
// noinspection JSDeprecatedSymbols, JSUnresolvedReference
|
|
639
1182
|
/** navigator.userAgentData.mobile doesn't return the correct value on some platforms, so this is a work-around, code from: http://detectmobilebrowsers.com **/
|
|
640
|
-
const a = STRING(window
|
|
1183
|
+
const a = STRING(window.navigator?.userAgent || window.navigator?.vendor || window.opera || '');
|
|
641
1184
|
const b = a.substring(0, 4);
|
|
642
1185
|
return !!(
|
|
643
1186
|
/(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
|
|
@@ -650,24 +1193,43 @@ export const LeUtils = {
|
|
|
650
1193
|
/**
|
|
651
1194
|
* Returns true if the user has a cursor (mouse, touchpad, etc).
|
|
652
1195
|
* In this context, a cursor is defined as an input device that can hover over elements without necessarily interacting with them.
|
|
1196
|
+
*
|
|
1197
|
+
* @returns {boolean}
|
|
653
1198
|
*/
|
|
654
1199
|
platformHasCursor:
|
|
655
1200
|
() =>
|
|
656
1201
|
{
|
|
657
|
-
|
|
1202
|
+
if(typeof window === 'undefined')
|
|
1203
|
+
{
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
return !LeUtils.platformIsMobile() && !window.matchMedia('(any-hover: none)')?.matches;
|
|
658
1207
|
},
|
|
659
1208
|
|
|
660
|
-
|
|
661
|
-
|
|
1209
|
+
/**
|
|
1210
|
+
* Returns the given string, with the first character capitalized.
|
|
1211
|
+
*
|
|
1212
|
+
* @param {String} string
|
|
1213
|
+
* @returns {string}
|
|
1214
|
+
*/
|
|
1215
|
+
capitalize:
|
|
1216
|
+
(string) =>
|
|
662
1217
|
{
|
|
663
|
-
|
|
664
|
-
if(
|
|
1218
|
+
string = STRING(string).trim();
|
|
1219
|
+
if(string.length <= 0)
|
|
665
1220
|
{
|
|
666
|
-
return
|
|
1221
|
+
return string;
|
|
667
1222
|
}
|
|
668
|
-
return
|
|
1223
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
669
1224
|
},
|
|
670
1225
|
|
|
1226
|
+
/**
|
|
1227
|
+
* Returns true if the given string ends with any of the given characters or words.
|
|
1228
|
+
*
|
|
1229
|
+
* @param {string} string
|
|
1230
|
+
* @param {string|string[]} endingCharsStringOrArray
|
|
1231
|
+
* @returns {boolean}
|
|
1232
|
+
*/
|
|
671
1233
|
endsWithAny:
|
|
672
1234
|
(string, endingCharsStringOrArray) =>
|
|
673
1235
|
{
|
|
@@ -693,6 +1255,13 @@ export const LeUtils = {
|
|
|
693
1255
|
return result;
|
|
694
1256
|
},
|
|
695
1257
|
|
|
1258
|
+
/**
|
|
1259
|
+
* Returns true if the given string starts with any of the given characters or words.
|
|
1260
|
+
*
|
|
1261
|
+
* @param {string} string
|
|
1262
|
+
* @param {string|string[]} startingCharsStringOrArray
|
|
1263
|
+
* @returns {boolean}
|
|
1264
|
+
*/
|
|
696
1265
|
startsWithAny:
|
|
697
1266
|
(string, startingCharsStringOrArray) =>
|
|
698
1267
|
{
|
|
@@ -718,6 +1287,12 @@ export const LeUtils = {
|
|
|
718
1287
|
return result;
|
|
719
1288
|
},
|
|
720
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Trims the end of the given string, by removing the given characters from it.
|
|
1292
|
+
*
|
|
1293
|
+
* @param {string} string
|
|
1294
|
+
* @param {string|string[]} trimCharsStringOrArray
|
|
1295
|
+
*/
|
|
721
1296
|
trimEnd:
|
|
722
1297
|
(string, trimCharsStringOrArray) =>
|
|
723
1298
|
{
|
|
@@ -749,6 +1324,12 @@ export const LeUtils = {
|
|
|
749
1324
|
return string;
|
|
750
1325
|
},
|
|
751
1326
|
|
|
1327
|
+
/**
|
|
1328
|
+
* Trims the start of the given string, by removing the given characters from it.
|
|
1329
|
+
*
|
|
1330
|
+
* @param {string} string
|
|
1331
|
+
* @param {string|string[]} trimCharsStringOrArray
|
|
1332
|
+
*/
|
|
752
1333
|
trimStart:
|
|
753
1334
|
(string, trimCharsStringOrArray) =>
|
|
754
1335
|
{
|
|
@@ -780,10 +1361,22 @@ export const LeUtils = {
|
|
|
780
1361
|
return string;
|
|
781
1362
|
},
|
|
782
1363
|
|
|
1364
|
+
/**
|
|
1365
|
+
* Trims the start and end of the given string, by removing the given characters from it.
|
|
1366
|
+
*
|
|
1367
|
+
* @param {string} string
|
|
1368
|
+
* @param {string|string[]} trimCharsStringOrArray
|
|
1369
|
+
*/
|
|
783
1370
|
trim:
|
|
784
1371
|
(string, trimCharsStringOrArray) => LeUtils.trimEnd(LeUtils.trimStart(string, trimCharsStringOrArray), trimCharsStringOrArray),
|
|
785
1372
|
|
|
786
|
-
|
|
1373
|
+
/**
|
|
1374
|
+
* Returns the given string, trims the start and end, and makes sure it ends with a valid sentence ending character (such as !?;.).
|
|
1375
|
+
*
|
|
1376
|
+
* @param {string} sentence
|
|
1377
|
+
* @returns {string}
|
|
1378
|
+
*/
|
|
1379
|
+
purgeSentence:
|
|
787
1380
|
(sentence) =>
|
|
788
1381
|
{
|
|
789
1382
|
sentence = LeUtils.trimEnd(STRING(sentence).trim(), '.: \r\n\t');
|
|
@@ -791,6 +1384,60 @@ export const LeUtils = {
|
|
|
791
1384
|
return sentence;
|
|
792
1385
|
},
|
|
793
1386
|
|
|
1387
|
+
/**
|
|
1388
|
+
* Attempts to obtain and return an error message from the given error, regardless of what is passed to this function.
|
|
1389
|
+
*
|
|
1390
|
+
* @param {*} error
|
|
1391
|
+
* @returns {string}
|
|
1392
|
+
*/
|
|
1393
|
+
purgeErrorMessage:
|
|
1394
|
+
(error) =>
|
|
1395
|
+
{
|
|
1396
|
+
const message = STRING(((typeof error === 'string') ? error : (error.message ?? JSON.stringify(error))));
|
|
1397
|
+
const messageParts = message.split('threw an error:');
|
|
1398
|
+
return messageParts[messageParts.length - 1].trim();
|
|
1399
|
+
},
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Generates all permutations of the given names.
|
|
1403
|
+
*
|
|
1404
|
+
* For example, if you pass "foo" and "bar", it will return:
|
|
1405
|
+
* - foobar
|
|
1406
|
+
* - fooBar
|
|
1407
|
+
* - FooBar
|
|
1408
|
+
* - foo-bar
|
|
1409
|
+
* - foo_bar
|
|
1410
|
+
*
|
|
1411
|
+
* @param {string} names
|
|
1412
|
+
* @returns {string[]}
|
|
1413
|
+
*/
|
|
1414
|
+
generateNamePermutations:
|
|
1415
|
+
(...names) =>
|
|
1416
|
+
{
|
|
1417
|
+
names = LeUtils.flattenArray(names)
|
|
1418
|
+
.map(name => STRING(name).trim().toLowerCase())
|
|
1419
|
+
.filter(name => (name.length > 0));
|
|
1420
|
+
let results = [];
|
|
1421
|
+
if(names.length > 0)
|
|
1422
|
+
{
|
|
1423
|
+
results.push(names.join('')); //foobar
|
|
1424
|
+
results.push(names.map(LeUtils.capitalize).join('')); //FooBar
|
|
1425
|
+
}
|
|
1426
|
+
if(names.length > 1)
|
|
1427
|
+
{
|
|
1428
|
+
results.push([names[0]].concat(names.slice(1).map(LeUtils.capitalize)).join('')); //fooBar
|
|
1429
|
+
results.push(names.join('-')); //foo-bar
|
|
1430
|
+
results.push(names.join('_')); //foo_bar
|
|
1431
|
+
}
|
|
1432
|
+
return results;
|
|
1433
|
+
},
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Increases the given numeric string by 1, this allows you to increase a numeric string without a limit.
|
|
1437
|
+
*
|
|
1438
|
+
* @param {string} string
|
|
1439
|
+
* @returns {string}
|
|
1440
|
+
*/
|
|
794
1441
|
increaseNumericStringByOne:
|
|
795
1442
|
(string) =>
|
|
796
1443
|
{
|
|
@@ -832,6 +1479,11 @@ export const LeUtils = {
|
|
|
832
1479
|
return string;
|
|
833
1480
|
},
|
|
834
1481
|
|
|
1482
|
+
/**
|
|
1483
|
+
* Generates a string that is guaranteed to be unique (across the entire frontend).
|
|
1484
|
+
*
|
|
1485
|
+
* @returns {string}
|
|
1486
|
+
*/
|
|
835
1487
|
uniqueId:
|
|
836
1488
|
(() =>
|
|
837
1489
|
{
|
|
@@ -881,10 +1533,1055 @@ export const LeUtils = {
|
|
|
881
1533
|
};
|
|
882
1534
|
})(),
|
|
883
1535
|
|
|
1536
|
+
/**
|
|
1537
|
+
* Returns a data URL of a 1x1 transparent pixel.
|
|
1538
|
+
*
|
|
1539
|
+
* @returns {string}
|
|
1540
|
+
*/
|
|
884
1541
|
getEmptyImageSrc:
|
|
885
1542
|
() =>
|
|
886
1543
|
{
|
|
887
1544
|
// noinspection SpellCheckingInspection
|
|
888
1545
|
return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
|
889
1546
|
},
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Calculates and returns the percentage of the part and total ((part / total) * 100).
|
|
1550
|
+
*
|
|
1551
|
+
* @param {number|string} part
|
|
1552
|
+
* @param {number|string} total
|
|
1553
|
+
* @returns {number}
|
|
1554
|
+
*/
|
|
1555
|
+
getPercentage:
|
|
1556
|
+
(part, total) =>
|
|
1557
|
+
{
|
|
1558
|
+
part = FLOAT_LAX(part);
|
|
1559
|
+
total = FLOAT_LAX(total);
|
|
1560
|
+
if(total <= 0)
|
|
1561
|
+
{
|
|
1562
|
+
return 100;
|
|
1563
|
+
}
|
|
1564
|
+
return Math.max(0, Math.min(100, ((part / total) * 100)));
|
|
1565
|
+
},
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Returns the pixels of the given Image object.
|
|
1569
|
+
*
|
|
1570
|
+
* @param {HTMLImageElement} image
|
|
1571
|
+
* @returns {Uint8ClampedArray}
|
|
1572
|
+
*/
|
|
1573
|
+
getImagePixels:
|
|
1574
|
+
(image) =>
|
|
1575
|
+
{
|
|
1576
|
+
if(!document)
|
|
1577
|
+
{
|
|
1578
|
+
return new Uint8ClampedArray();
|
|
1579
|
+
}
|
|
1580
|
+
const canvas = document.createElement('canvas');
|
|
1581
|
+
document.body.appendChild(canvas);
|
|
1582
|
+
try
|
|
1583
|
+
{
|
|
1584
|
+
const ctx = canvas.getContext('2d');
|
|
1585
|
+
const width = Math.floor(image.width);
|
|
1586
|
+
const height = Math.floor(image.height);
|
|
1587
|
+
if((width <= 0) || (height <= 0))
|
|
1588
|
+
{
|
|
1589
|
+
canvas.width = 1;
|
|
1590
|
+
canvas.height = 1;
|
|
1591
|
+
}
|
|
1592
|
+
else
|
|
1593
|
+
{
|
|
1594
|
+
canvas.width = width;
|
|
1595
|
+
canvas.height = height;
|
|
1596
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
1597
|
+
}
|
|
1598
|
+
return ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
1599
|
+
}
|
|
1600
|
+
finally
|
|
1601
|
+
{
|
|
1602
|
+
canvas.parentNode.removeChild(canvas);
|
|
1603
|
+
}
|
|
1604
|
+
},
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* Returns the data URL (mimetype "image/png") of a colored version of the given Image object.
|
|
1608
|
+
*
|
|
1609
|
+
* @param {HTMLImageElement} image
|
|
1610
|
+
* @param {string} color
|
|
1611
|
+
* @returns {string}
|
|
1612
|
+
*/
|
|
1613
|
+
getColoredImage:
|
|
1614
|
+
(image, color) =>
|
|
1615
|
+
{
|
|
1616
|
+
if(!document)
|
|
1617
|
+
{
|
|
1618
|
+
return LeUtils.getEmptyImageSrc();
|
|
1619
|
+
}
|
|
1620
|
+
const canvas = document.createElement('canvas');
|
|
1621
|
+
document.body.appendChild(canvas);
|
|
1622
|
+
try
|
|
1623
|
+
{
|
|
1624
|
+
const ctx = canvas.getContext('2d');
|
|
1625
|
+
const width = Math.floor(image.width);
|
|
1626
|
+
const height = Math.floor(image.height);
|
|
1627
|
+
if((width <= 0) || (height <= 0))
|
|
1628
|
+
{
|
|
1629
|
+
canvas.width = 1;
|
|
1630
|
+
canvas.height = 1;
|
|
1631
|
+
}
|
|
1632
|
+
else
|
|
1633
|
+
{
|
|
1634
|
+
canvas.width = width;
|
|
1635
|
+
canvas.height = height;
|
|
1636
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
1637
|
+
}
|
|
1638
|
+
ctx.globalCompositeOperation = 'source-in';
|
|
1639
|
+
ctx.fillStyle = color;
|
|
1640
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1641
|
+
return canvas.toDataURL('image/png');
|
|
1642
|
+
}
|
|
1643
|
+
finally
|
|
1644
|
+
{
|
|
1645
|
+
canvas.parentNode.removeChild(canvas);
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
|
|
1649
|
+
/**
|
|
1650
|
+
* Returns the hex color of the given RGB(A).
|
|
1651
|
+
*
|
|
1652
|
+
* @param {number[]} rgb
|
|
1653
|
+
* @returns {string}
|
|
1654
|
+
*/
|
|
1655
|
+
rgbToHex:
|
|
1656
|
+
(rgb) =>
|
|
1657
|
+
{
|
|
1658
|
+
return '#' + rgb.map((x) =>
|
|
1659
|
+
{
|
|
1660
|
+
const hex = x.toString(16);
|
|
1661
|
+
return ((hex.length === 1) ? '0' + hex : hex);
|
|
1662
|
+
}).join('');
|
|
1663
|
+
},
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* Returns the RGB(A) of the given hex color.
|
|
1667
|
+
*
|
|
1668
|
+
* @param {string} hexstring
|
|
1669
|
+
* @returns {number[]}
|
|
1670
|
+
*/
|
|
1671
|
+
hexToRgb:
|
|
1672
|
+
(hexstring) =>
|
|
1673
|
+
{
|
|
1674
|
+
hexstring = hexstring.replace(/[^0-9A-F]/gi, '');
|
|
1675
|
+
const hasAlpha = ((hexstring.length === 4) || (hexstring.length === 8));
|
|
1676
|
+
while(hexstring.length < 6)
|
|
1677
|
+
{
|
|
1678
|
+
hexstring = hexstring.replace(/(.)/g, '$1$1');
|
|
1679
|
+
}
|
|
1680
|
+
const result = hexstring.match(/\w{2}/g).map((a) => parseInt(a, 16));
|
|
1681
|
+
return [
|
|
1682
|
+
result[0],
|
|
1683
|
+
result[1],
|
|
1684
|
+
result[2],
|
|
1685
|
+
...(hasAlpha ? [result[3]] : []),
|
|
1686
|
+
];
|
|
1687
|
+
},
|
|
1688
|
+
|
|
1689
|
+
/**
|
|
1690
|
+
* Returns the HSL(A) of the given RGB(A).
|
|
1691
|
+
*
|
|
1692
|
+
* @param {number[]} rgb
|
|
1693
|
+
* @returns {number[]}
|
|
1694
|
+
*/
|
|
1695
|
+
rgbToHsl:
|
|
1696
|
+
(rgb) =>
|
|
1697
|
+
{
|
|
1698
|
+
const r = rgb[0] / 255;
|
|
1699
|
+
const g = rgb[1] / 255;
|
|
1700
|
+
const b = rgb[2] / 255;
|
|
1701
|
+
const max = Math.max(r, g, b);
|
|
1702
|
+
const min = Math.min(r, g, b);
|
|
1703
|
+
let h, s, l = (max + min) / 2;
|
|
1704
|
+
if(max === min)
|
|
1705
|
+
{
|
|
1706
|
+
h = s = 0;
|
|
1707
|
+
}
|
|
1708
|
+
else
|
|
1709
|
+
{
|
|
1710
|
+
const d = max - min;
|
|
1711
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
1712
|
+
switch(max)
|
|
1713
|
+
{
|
|
1714
|
+
case r:
|
|
1715
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
1716
|
+
break;
|
|
1717
|
+
case g:
|
|
1718
|
+
h = (b - r) / d + 2;
|
|
1719
|
+
break;
|
|
1720
|
+
case b:
|
|
1721
|
+
h = (r - g) / d + 4;
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1724
|
+
h /= 6;
|
|
1725
|
+
}
|
|
1726
|
+
return [h, s, l, ...((rgb.length >= 4) ? [rgb[3] / 255] : [])];
|
|
1727
|
+
},
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* Returns the RGB(A) of the given HSL(A).
|
|
1731
|
+
*
|
|
1732
|
+
* @param {number[]} hsl
|
|
1733
|
+
* @returns {number[]}
|
|
1734
|
+
*/
|
|
1735
|
+
hslToRgb:
|
|
1736
|
+
(() =>
|
|
1737
|
+
{
|
|
1738
|
+
const hue2rgb = (p, q, t) =>
|
|
1739
|
+
{
|
|
1740
|
+
if(t < 0)
|
|
1741
|
+
{
|
|
1742
|
+
t += 1;
|
|
1743
|
+
}
|
|
1744
|
+
if(t > 1)
|
|
1745
|
+
{
|
|
1746
|
+
t -= 1;
|
|
1747
|
+
}
|
|
1748
|
+
if(t < 1 / 6)
|
|
1749
|
+
{
|
|
1750
|
+
return p + (q - p) * 6 * t;
|
|
1751
|
+
}
|
|
1752
|
+
if(t < 1 / 2)
|
|
1753
|
+
{
|
|
1754
|
+
return q;
|
|
1755
|
+
}
|
|
1756
|
+
if(t < 2 / 3)
|
|
1757
|
+
{
|
|
1758
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
1759
|
+
}
|
|
1760
|
+
return p;
|
|
1761
|
+
};
|
|
1762
|
+
return (hsl) =>
|
|
1763
|
+
{
|
|
1764
|
+
const h = hsl[0];
|
|
1765
|
+
const s = hsl[1];
|
|
1766
|
+
const l = hsl[2];
|
|
1767
|
+
let r, g, b;
|
|
1768
|
+
if(s === 0)
|
|
1769
|
+
{
|
|
1770
|
+
r = g = b = l;
|
|
1771
|
+
}
|
|
1772
|
+
else
|
|
1773
|
+
{
|
|
1774
|
+
const q = (l < 0.5) ? (l * (1 + s)) : (l + s - (l * s));
|
|
1775
|
+
const p = (2 * l) - q;
|
|
1776
|
+
r = hue2rgb(p, q, h + (1 / 3));
|
|
1777
|
+
g = hue2rgb(p, q, h);
|
|
1778
|
+
b = hue2rgb(p, q, h - (1 / 3));
|
|
1779
|
+
}
|
|
1780
|
+
return [r * 255, g * 255, b * 255, ...((hsl.length >= 4) ? [hsl[3] * 255] : [])].map((c) => Math.max(0, Math.min(255, Math.round(c))));
|
|
1781
|
+
};
|
|
1782
|
+
})(),
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Returns the LAB(A) of the given RGB(A).
|
|
1786
|
+
*
|
|
1787
|
+
* @param {number[]} rgb
|
|
1788
|
+
* @returns {number[]}
|
|
1789
|
+
*/
|
|
1790
|
+
rgbToLab:
|
|
1791
|
+
(rgb) =>
|
|
1792
|
+
{
|
|
1793
|
+
let r = rgb[0] / 255;
|
|
1794
|
+
let g = rgb[1] / 255;
|
|
1795
|
+
let b = rgb[2] / 255;
|
|
1796
|
+
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : (r / 12.92);
|
|
1797
|
+
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : (g / 12.92);
|
|
1798
|
+
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
|
1799
|
+
let x = ((r * 0.4124) + (g * 0.3576) + (b * 0.1805)) / 0.95047;
|
|
1800
|
+
let y = ((r * 0.2126) + (g * 0.7152) + (b * 0.0722));
|
|
1801
|
+
let z = ((r * 0.0193) + (g * 0.1192) + (b * 0.9505)) / 1.08883;
|
|
1802
|
+
x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
|
|
1803
|
+
y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
|
|
1804
|
+
z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
|
|
1805
|
+
return [(116 * y) - 16, 500 * (x - y), 200 * (y - z), ...((rgb.length >= 4) ? [rgb[3] / 255] : [])];
|
|
1806
|
+
},
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
* Returns the difference (calculated with DeltaE) of the LAB values of the given RGB values.
|
|
1810
|
+
*
|
|
1811
|
+
* Returns a number:
|
|
1812
|
+
*
|
|
1813
|
+
* <pre>
|
|
1814
|
+
* < 1.0 is not perceptible by human eyes
|
|
1815
|
+
* 1-2 is perceptible through close observation
|
|
1816
|
+
* 2-10 is perceptible at a glance
|
|
1817
|
+
* 11-49 is more similar than opposite
|
|
1818
|
+
* 100 is exactly the opposite color
|
|
1819
|
+
* </pre>
|
|
1820
|
+
*
|
|
1821
|
+
* @param {number[]} rgbA
|
|
1822
|
+
* @param {number[]} rgbB
|
|
1823
|
+
* @returns {number}
|
|
1824
|
+
*/
|
|
1825
|
+
getDifferenceBetweenRgb:
|
|
1826
|
+
(rgbA, rgbB) =>
|
|
1827
|
+
{
|
|
1828
|
+
const labA = LeUtils.rgbToLab(rgbA);
|
|
1829
|
+
const labB = LeUtils.rgbToLab(rgbB);
|
|
1830
|
+
return LeUtils.getDifferenceBetweenLab(labA, labB);
|
|
1831
|
+
},
|
|
1832
|
+
|
|
1833
|
+
/**
|
|
1834
|
+
* Returns the difference (calculated with DeltaE) of the given LAB values. Ignores the Alpha channel.
|
|
1835
|
+
*
|
|
1836
|
+
* Returns a number:
|
|
1837
|
+
*
|
|
1838
|
+
* <pre>
|
|
1839
|
+
* < 1.0 is not perceptible by human eyes
|
|
1840
|
+
* 1-2 is perceptible through close observation
|
|
1841
|
+
* 2-10 is perceptible at a glance
|
|
1842
|
+
* 11-49 is more similar than opposite
|
|
1843
|
+
* 100 is exactly the opposite color
|
|
1844
|
+
* </pre>
|
|
1845
|
+
*
|
|
1846
|
+
* @param {number[]} labA
|
|
1847
|
+
* @param {number[]} labB
|
|
1848
|
+
* @returns {number}
|
|
1849
|
+
*/
|
|
1850
|
+
getDifferenceBetweenLab:
|
|
1851
|
+
(labA, labB) =>
|
|
1852
|
+
{
|
|
1853
|
+
const deltaL = labA[0] - labB[0];
|
|
1854
|
+
const deltaA = labA[1] - labB[1];
|
|
1855
|
+
const deltaB = labA[2] - labB[2];
|
|
1856
|
+
const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
|
|
1857
|
+
const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
|
|
1858
|
+
const deltaC = c1 - c2;
|
|
1859
|
+
let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
|
|
1860
|
+
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
|
|
1861
|
+
const sc = 1.0 + 0.045 * c1;
|
|
1862
|
+
const sh = 1.0 + 0.015 * c1;
|
|
1863
|
+
const deltaLKlsl = deltaL / (1.0);
|
|
1864
|
+
const deltaCkcsc = deltaC / (sc);
|
|
1865
|
+
const deltaHkhsh = deltaH / (sh);
|
|
1866
|
+
const i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
|
|
1867
|
+
return (i < 0) ? 0 : Math.sqrt(i);
|
|
1868
|
+
},
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Returns the RGB(A) of the given RGB(A) values, based on the given percentage (0-100).
|
|
1872
|
+
* This allows you to define a gradient of colors to fade in between, rather than just having a start and an end color.
|
|
1873
|
+
*
|
|
1874
|
+
* Usage:
|
|
1875
|
+
*
|
|
1876
|
+
* <pre>
|
|
1877
|
+
* LeUtils.getRgbOfGradient({
|
|
1878
|
+
* 0: [255, 0, 0],
|
|
1879
|
+
* 33: [255, 255, 0],
|
|
1880
|
+
* 66: [0, 255, 0],
|
|
1881
|
+
* 100:[0, 255, 255],
|
|
1882
|
+
* }, 45.1234);
|
|
1883
|
+
* </pre>
|
|
1884
|
+
*
|
|
1885
|
+
* @param {{[percentage]: number[]}} gradient
|
|
1886
|
+
* @param {number} percentage
|
|
1887
|
+
* @returns {number[]}
|
|
1888
|
+
*/
|
|
1889
|
+
getRgbOfGradient:
|
|
1890
|
+
(gradient, percentage) =>
|
|
1891
|
+
{
|
|
1892
|
+
percentage = Math.max(0, Math.min(100, FLOAT_LAX(percentage)));
|
|
1893
|
+
|
|
1894
|
+
let closest = null;
|
|
1895
|
+
LeUtils.each(gradient, (color, percent) =>
|
|
1896
|
+
{
|
|
1897
|
+
percent = INT_LAX(percent);
|
|
1898
|
+
if(closest === null)
|
|
1899
|
+
{
|
|
1900
|
+
closest = [percent, Math.abs(percentage - percent)];
|
|
1901
|
+
}
|
|
1902
|
+
else
|
|
1903
|
+
{
|
|
1904
|
+
const difference = Math.abs(percentage - percent);
|
|
1905
|
+
if(difference < closest[1])
|
|
1906
|
+
{
|
|
1907
|
+
closest = [percent, difference];
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
if(closest === null)
|
|
1912
|
+
{
|
|
1913
|
+
return null;
|
|
1914
|
+
}
|
|
1915
|
+
closest = closest[0];
|
|
1916
|
+
|
|
1917
|
+
let higher = 99999;
|
|
1918
|
+
let lower = -99999;
|
|
1919
|
+
LeUtils.each(gradient, (color, percent) =>
|
|
1920
|
+
{
|
|
1921
|
+
percent = INT_LAX(percent);
|
|
1922
|
+
if(percent < closest)
|
|
1923
|
+
{
|
|
1924
|
+
if(percent > lower)
|
|
1925
|
+
{
|
|
1926
|
+
lower = percent;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if(percent > closest)
|
|
1930
|
+
{
|
|
1931
|
+
if(percent < higher)
|
|
1932
|
+
{
|
|
1933
|
+
higher = percent;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
if(higher === 99999)
|
|
1938
|
+
{
|
|
1939
|
+
higher = null;
|
|
1940
|
+
}
|
|
1941
|
+
if(lower === -99999)
|
|
1942
|
+
{
|
|
1943
|
+
lower = null;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
if(((higher === null) && (lower === null)) || (higher === lower))
|
|
1947
|
+
{
|
|
1948
|
+
return gradient[closest];
|
|
1949
|
+
}
|
|
1950
|
+
else if((higher !== null) && (lower !== null))
|
|
1951
|
+
{
|
|
1952
|
+
const higherDifference = Math.abs(higher - percentage);
|
|
1953
|
+
const lowerDifference = Math.abs(percentage - lower);
|
|
1954
|
+
if(higherDifference > lowerDifference)
|
|
1955
|
+
{
|
|
1956
|
+
higher = closest;
|
|
1957
|
+
}
|
|
1958
|
+
else
|
|
1959
|
+
{
|
|
1960
|
+
lower = closest;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
else if(lower === null)
|
|
1964
|
+
{
|
|
1965
|
+
lower = closest;
|
|
1966
|
+
}
|
|
1967
|
+
else
|
|
1968
|
+
{
|
|
1969
|
+
higher = closest;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
if(lower > higher)
|
|
1973
|
+
{
|
|
1974
|
+
const tmp = higher;
|
|
1975
|
+
higher = lower;
|
|
1976
|
+
lower = tmp;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
const total = (higher - lower);
|
|
1980
|
+
const part = (percentage - lower);
|
|
1981
|
+
return LeUtils.getRgbBetween(gradient[lower], gradient[higher], ((part / total) * 100));
|
|
1982
|
+
},
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* Returns the RGB(A) between the two given RGB(A) values, based on the given percentage (0-100).
|
|
1986
|
+
*
|
|
1987
|
+
* @param {number[]} startRgb
|
|
1988
|
+
* @param {number[]} endRgb
|
|
1989
|
+
* @param {number} percentage
|
|
1990
|
+
* @returns {number[]}
|
|
1991
|
+
*/
|
|
1992
|
+
getRgbBetween:
|
|
1993
|
+
(startRgb, endRgb, percentage) =>
|
|
1994
|
+
{
|
|
1995
|
+
percentage = FLOAT_LAX(percentage);
|
|
1996
|
+
const partEnd = Math.max(0, Math.min(1, (percentage / 100.0)));
|
|
1997
|
+
const partStart = (1 - partEnd);
|
|
1998
|
+
const length = Math.min(startRgb.length, endRgb.length);
|
|
1999
|
+
let result = [];
|
|
2000
|
+
for(let i = 0; i < length; i++)
|
|
2001
|
+
{
|
|
2002
|
+
result.push(Math.max(0, Math.min(255, Math.round((startRgb[i] * partStart) + (endRgb[i] * partEnd)))));
|
|
2003
|
+
}
|
|
2004
|
+
return result;
|
|
2005
|
+
},
|
|
2006
|
+
|
|
2007
|
+
/**
|
|
2008
|
+
* An implementation of the btoa function, which should work in all environments.
|
|
2009
|
+
*
|
|
2010
|
+
* @param {string} string
|
|
2011
|
+
* @returns {string}
|
|
2012
|
+
*/
|
|
2013
|
+
btoa:
|
|
2014
|
+
(string) =>
|
|
2015
|
+
{
|
|
2016
|
+
if(typeof btoa === 'function')
|
|
2017
|
+
{
|
|
2018
|
+
return btoa(string);
|
|
2019
|
+
}
|
|
2020
|
+
return Buffer.from(string).toString('base64');
|
|
2021
|
+
},
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* An implementation of the atob function, which should work in all environments.
|
|
2025
|
+
*
|
|
2026
|
+
* @param {string} base64string
|
|
2027
|
+
* @returns {string}
|
|
2028
|
+
*/
|
|
2029
|
+
atob:
|
|
2030
|
+
(base64string) =>
|
|
2031
|
+
{
|
|
2032
|
+
if(typeof atob === 'function')
|
|
2033
|
+
{
|
|
2034
|
+
return atob(base64string);
|
|
2035
|
+
}
|
|
2036
|
+
return Buffer.from(base64string, 'base64').toString();
|
|
2037
|
+
},
|
|
2038
|
+
|
|
2039
|
+
/**
|
|
2040
|
+
* Encodes a UTF-8 string into a base64 string.
|
|
2041
|
+
*
|
|
2042
|
+
* @param {string} string
|
|
2043
|
+
* @returns {string}
|
|
2044
|
+
*/
|
|
2045
|
+
utf8ToBase64:
|
|
2046
|
+
(string) =>
|
|
2047
|
+
{
|
|
2048
|
+
return LeUtils.btoa(encodeURIComponent(string).replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode(parseInt(p1, 16))));
|
|
2049
|
+
},
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* Decodes a base64 string back into a UTF-8 string.
|
|
2053
|
+
*
|
|
2054
|
+
* @param {string} base64string
|
|
2055
|
+
* @returns {string}
|
|
2056
|
+
*/
|
|
2057
|
+
base64ToUtf8:
|
|
2058
|
+
(base64string) =>
|
|
2059
|
+
{
|
|
2060
|
+
return decodeURIComponent(LeUtils.atob(base64string.trim()).split('').map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
|
|
2061
|
+
},
|
|
2062
|
+
|
|
2063
|
+
/**
|
|
2064
|
+
* Converts a base64 string into a hex string.
|
|
2065
|
+
*
|
|
2066
|
+
* @param {string} base64string
|
|
2067
|
+
* @returns {string}
|
|
2068
|
+
*/
|
|
2069
|
+
base64ToHex:
|
|
2070
|
+
(base64string) =>
|
|
2071
|
+
{
|
|
2072
|
+
return LeUtils.atob(base64string.trim()).split('').map((c) => ('0' + c.charCodeAt(0).toString(16)).slice(-2)).join('');
|
|
2073
|
+
},
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* Converts a hex string into a base64 string.
|
|
2077
|
+
*
|
|
2078
|
+
* @param {string} hexstring
|
|
2079
|
+
* @returns {string}
|
|
2080
|
+
*/
|
|
2081
|
+
hexToBase64:
|
|
2082
|
+
(hexstring) =>
|
|
2083
|
+
{
|
|
2084
|
+
return LeUtils.btoa(hexstring.replace(/[^0-9A-F]/gi, '').match(/\w{2}/g).map((a) => String.fromCharCode(parseInt(a, 16))).join(''));
|
|
2085
|
+
},
|
|
2086
|
+
|
|
2087
|
+
/**
|
|
2088
|
+
* Converts a base64 string into bytes (Uint8Array).
|
|
2089
|
+
*
|
|
2090
|
+
* @param {string} base64string
|
|
2091
|
+
* @returns {Uint8Array}
|
|
2092
|
+
*/
|
|
2093
|
+
base64ToBytes:
|
|
2094
|
+
(base64string) =>
|
|
2095
|
+
{
|
|
2096
|
+
const binary = LeUtils.atob(base64string.trim());
|
|
2097
|
+
const len = binary.length;
|
|
2098
|
+
let data = new Uint8Array(len);
|
|
2099
|
+
for(let i = 0; i < len; i++)
|
|
2100
|
+
{
|
|
2101
|
+
data[i] = binary.charCodeAt(i);
|
|
2102
|
+
}
|
|
2103
|
+
return data;
|
|
2104
|
+
},
|
|
2105
|
+
|
|
2106
|
+
/**
|
|
2107
|
+
* Converts bytes into a base64 string.
|
|
2108
|
+
*
|
|
2109
|
+
* @param {ArrayLike<number>|ArrayBufferLike} arraybuffer
|
|
2110
|
+
* @returns {string}
|
|
2111
|
+
*/
|
|
2112
|
+
bytesToBase64:
|
|
2113
|
+
(arraybuffer) =>
|
|
2114
|
+
{
|
|
2115
|
+
const bytes = new Uint8Array(arraybuffer);
|
|
2116
|
+
const len = bytes.byteLength;
|
|
2117
|
+
let binary = '';
|
|
2118
|
+
for(let i = 0; i < len; i++)
|
|
2119
|
+
{
|
|
2120
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2121
|
+
}
|
|
2122
|
+
return LeUtils.btoa(binary);
|
|
2123
|
+
},
|
|
2124
|
+
|
|
2125
|
+
/**
|
|
2126
|
+
* Downloads the given base64 string as a file.
|
|
2127
|
+
*
|
|
2128
|
+
* @param {string} base64string
|
|
2129
|
+
* @param {string} [fileName]
|
|
2130
|
+
* @param {string} [mimeType]
|
|
2131
|
+
*/
|
|
2132
|
+
downloadFile:
|
|
2133
|
+
(base64string, fileName, mimeType) =>
|
|
2134
|
+
{
|
|
2135
|
+
const link = document.createElement('a');
|
|
2136
|
+
link.setAttribute('download', (typeof fileName === 'string') ? fileName : 'file');
|
|
2137
|
+
link.href = 'data:' + mimeType + ';base64,' + base64string;
|
|
2138
|
+
link.setAttribute('target', '_blank');
|
|
2139
|
+
link.click();
|
|
2140
|
+
},
|
|
2141
|
+
|
|
2142
|
+
/**
|
|
2143
|
+
* Loads the value from the browser, returns undefined if the value doesn't exist.
|
|
2144
|
+
*
|
|
2145
|
+
* @param {string} id
|
|
2146
|
+
* @returns {*}
|
|
2147
|
+
*/
|
|
2148
|
+
localStorageGet:
|
|
2149
|
+
(id) =>
|
|
2150
|
+
{
|
|
2151
|
+
if(typeof window === 'undefined')
|
|
2152
|
+
{
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
let result = window.localStorage.getItem('LeUtils_' + id);
|
|
2156
|
+
if(typeof result !== 'string')
|
|
2157
|
+
{
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
try
|
|
2161
|
+
{
|
|
2162
|
+
result = JSON.parse(result);
|
|
2163
|
+
if(typeof result['-'] !== 'undefined')
|
|
2164
|
+
{
|
|
2165
|
+
return result['-'];
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
catch(e)
|
|
2169
|
+
{
|
|
2170
|
+
}
|
|
2171
|
+
},
|
|
2172
|
+
|
|
2173
|
+
/**
|
|
2174
|
+
* Saves the given data in the browser.
|
|
2175
|
+
*
|
|
2176
|
+
* @param {string} id
|
|
2177
|
+
* @param {*} data
|
|
2178
|
+
*/
|
|
2179
|
+
localStorageSet:
|
|
2180
|
+
(id, data) =>
|
|
2181
|
+
{
|
|
2182
|
+
if(typeof window === 'undefined')
|
|
2183
|
+
{
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if(typeof data === 'undefined')
|
|
2187
|
+
{
|
|
2188
|
+
window.localStorage.removeItem('LeUtils_' + id);
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
window.localStorage.setItem('LeUtils_' + id, JSON.stringify({'-':data}));
|
|
2192
|
+
},
|
|
2193
|
+
|
|
2194
|
+
/**
|
|
2195
|
+
* Removes the data from the browser.
|
|
2196
|
+
*
|
|
2197
|
+
* @param {string} id
|
|
2198
|
+
*/
|
|
2199
|
+
localStorageRemove:
|
|
2200
|
+
(id) =>
|
|
2201
|
+
{
|
|
2202
|
+
if(typeof window === 'undefined')
|
|
2203
|
+
{
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
window.localStorage.removeItem('LeUtils_' + id);
|
|
2207
|
+
},
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Creates and returns a new TreeSet.
|
|
2211
|
+
* A TreeSet is a set of elements, sorted by a comparator.
|
|
2212
|
+
* Binary search is used to find elements, which makes it very fast to find elements.
|
|
2213
|
+
*
|
|
2214
|
+
* The comparator is also used to determine if two values are equal to each other.
|
|
2215
|
+
* This way, you can have values that aren't the same be treated as if they are. This can be used to deal with issues such as floating point errors for example.
|
|
2216
|
+
*
|
|
2217
|
+
* @param {*[]} elements
|
|
2218
|
+
* @param {Function} comparator
|
|
2219
|
+
* @returns {{getElements: (function(): *[]), getComparator: (function(): Function), size: (function(): number), isEmpty: (function(): boolean), contains: (function(*): boolean), first: (function(): *|undefined), last: (function(): *|undefined), pollFirst: (function(): *|undefined), pollLast: (function(): *|undefined), add: function(*), addAll: function(*[]|object), getEqualValue: (function(*): (*)), getEqualValueOrAdd: (function(*): (*))}}
|
|
2220
|
+
*/
|
|
2221
|
+
createTreeSet:
|
|
2222
|
+
(elements, comparator) =>
|
|
2223
|
+
{
|
|
2224
|
+
comparator = comparator || LeUtils.compare;
|
|
2225
|
+
elements = elements || [];
|
|
2226
|
+
elements.sort(comparator);
|
|
2227
|
+
|
|
2228
|
+
/**
|
|
2229
|
+
* Performs a binary search on the elements, and returns the result.
|
|
2230
|
+
*
|
|
2231
|
+
* @param {*} value
|
|
2232
|
+
* @returns {{found: boolean, index: number, value: *|undefined}}
|
|
2233
|
+
*/
|
|
2234
|
+
const binarySearch = (value) =>
|
|
2235
|
+
{
|
|
2236
|
+
let low = 0;
|
|
2237
|
+
let high = elements.length - 1;
|
|
2238
|
+
while(low <= high)
|
|
2239
|
+
{
|
|
2240
|
+
const mid = Math.floor((low + high) / 2);
|
|
2241
|
+
const midValue = elements[mid];
|
|
2242
|
+
const cmp = comparator(midValue, value);
|
|
2243
|
+
if(cmp < 0)
|
|
2244
|
+
{
|
|
2245
|
+
low = mid + 1;
|
|
2246
|
+
}
|
|
2247
|
+
else if(cmp > 0)
|
|
2248
|
+
{
|
|
2249
|
+
high = mid - 1;
|
|
2250
|
+
}
|
|
2251
|
+
else
|
|
2252
|
+
{
|
|
2253
|
+
return {found:true, index:mid, value:midValue};
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return {found:false, index:low, value:undefined};
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
const treeSet = {
|
|
2260
|
+
/**
|
|
2261
|
+
* Returns the elements of the set.
|
|
2262
|
+
*
|
|
2263
|
+
* @returns {*[]}
|
|
2264
|
+
*/
|
|
2265
|
+
getElements:
|
|
2266
|
+
() => elements,
|
|
2267
|
+
|
|
2268
|
+
/**
|
|
2269
|
+
* Returns the comparator of the set.
|
|
2270
|
+
*
|
|
2271
|
+
* @returns {Function}
|
|
2272
|
+
*/
|
|
2273
|
+
getComparator:
|
|
2274
|
+
() => comparator,
|
|
2275
|
+
|
|
2276
|
+
/**
|
|
2277
|
+
* Returns the size of the set.
|
|
2278
|
+
*
|
|
2279
|
+
* @returns {number}
|
|
2280
|
+
*/
|
|
2281
|
+
size:
|
|
2282
|
+
() => elements.length,
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* Returns true if the set is empty, false otherwise.
|
|
2286
|
+
*
|
|
2287
|
+
* @returns {boolean}
|
|
2288
|
+
*/
|
|
2289
|
+
isEmpty:
|
|
2290
|
+
() => (elements.length <= 0),
|
|
2291
|
+
|
|
2292
|
+
/**
|
|
2293
|
+
* Returns true if the set contains a value that is equal to the given value, returns false otherwise.
|
|
2294
|
+
*
|
|
2295
|
+
* @param {*} value
|
|
2296
|
+
* @returns {boolean}
|
|
2297
|
+
*/
|
|
2298
|
+
contains:
|
|
2299
|
+
(value) => binarySearch(value).found,
|
|
2300
|
+
|
|
2301
|
+
/**
|
|
2302
|
+
* Returns the first element of the set, or undefined if it is empty.
|
|
2303
|
+
*
|
|
2304
|
+
* @returns {*|undefined}
|
|
2305
|
+
*/
|
|
2306
|
+
first:
|
|
2307
|
+
() => (elements.length > 0) ? elements[0] : undefined,
|
|
2308
|
+
|
|
2309
|
+
/**
|
|
2310
|
+
* Returns the last element of the set, or undefined if it is empty.
|
|
2311
|
+
*
|
|
2312
|
+
* @returns {*|undefined}
|
|
2313
|
+
*/
|
|
2314
|
+
last:
|
|
2315
|
+
() => (elements.length > 0) ? elements[elements.length - 1] : undefined,
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* Removes and returns the first element of the set, or undefined if it is empty.
|
|
2319
|
+
*
|
|
2320
|
+
* @returns {*|undefined}
|
|
2321
|
+
*/
|
|
2322
|
+
pollFirst:
|
|
2323
|
+
() => (elements.length > 0) ? elements.splice(0, 1)[0] : undefined,
|
|
2324
|
+
|
|
2325
|
+
/**
|
|
2326
|
+
* Removes and returns the last element of the set, or undefined if it is empty.
|
|
2327
|
+
*
|
|
2328
|
+
* @returns {*|undefined}
|
|
2329
|
+
*/
|
|
2330
|
+
pollLast:
|
|
2331
|
+
() => (elements.length > 0) ? elements.splice(elements.length - 1, 1)[0] : undefined,
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* Adds the given value to the set. Will only do so if no equal value already exists.
|
|
2335
|
+
*
|
|
2336
|
+
* @param {*} value
|
|
2337
|
+
*/
|
|
2338
|
+
add:
|
|
2339
|
+
(value) =>
|
|
2340
|
+
{
|
|
2341
|
+
const result = binarySearch(value);
|
|
2342
|
+
if(result.found)
|
|
2343
|
+
{
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
elements.splice(result.index, 0, value);
|
|
2347
|
+
},
|
|
2348
|
+
|
|
2349
|
+
/**
|
|
2350
|
+
* Adds all the given values to the set. Will only do so if no equal value already exists.
|
|
2351
|
+
*
|
|
2352
|
+
* @param {*[]|object} values
|
|
2353
|
+
*/
|
|
2354
|
+
addAll:
|
|
2355
|
+
(values) =>
|
|
2356
|
+
{
|
|
2357
|
+
LeUtils.each(values, treeSet.add);
|
|
2358
|
+
},
|
|
2359
|
+
|
|
2360
|
+
/**
|
|
2361
|
+
* Returns an equal value that's already in the tree set, or undefined if no equal values in it exist.
|
|
2362
|
+
*
|
|
2363
|
+
* @param {*} value
|
|
2364
|
+
* @returns {*|undefined}
|
|
2365
|
+
*/
|
|
2366
|
+
getEqualValue:
|
|
2367
|
+
(value) =>
|
|
2368
|
+
{
|
|
2369
|
+
const result = binarySearch(value);
|
|
2370
|
+
if(result.found)
|
|
2371
|
+
{
|
|
2372
|
+
return result.value;
|
|
2373
|
+
}
|
|
2374
|
+
return undefined;
|
|
2375
|
+
},
|
|
2376
|
+
|
|
2377
|
+
/**
|
|
2378
|
+
* Returns an equal value that's already in the tree set. If no equal values in it exist, the given value will be added and returned.
|
|
2379
|
+
*
|
|
2380
|
+
* @param {*} value
|
|
2381
|
+
* @returns {*}
|
|
2382
|
+
*/
|
|
2383
|
+
getEqualValueOrAdd:
|
|
2384
|
+
(value) =>
|
|
2385
|
+
{
|
|
2386
|
+
const result = binarySearch(value);
|
|
2387
|
+
if(result.found)
|
|
2388
|
+
{
|
|
2389
|
+
return result.value;
|
|
2390
|
+
}
|
|
2391
|
+
elements.splice(result.index, 0, value);
|
|
2392
|
+
return value;
|
|
2393
|
+
},
|
|
2394
|
+
};
|
|
2395
|
+
return treeSet;
|
|
2396
|
+
},
|
|
2397
|
+
|
|
2398
|
+
/**
|
|
2399
|
+
* @typedef {Object} LeUtils~TransactionalValue
|
|
2400
|
+
* @property {*} value
|
|
2401
|
+
* @property {{id:string, value:*}[]} changes
|
|
2402
|
+
*/
|
|
2403
|
+
/**
|
|
2404
|
+
* Creates and returns a new TransactionalValue object.
|
|
2405
|
+
* With a TransactionalValue, you can keep track of changes to a value, and commit or cancel them.
|
|
2406
|
+
*
|
|
2407
|
+
* Multiple uncommitted changes can be made at the same time, the last change will be the one that overwrites older changes.
|
|
2408
|
+
* If that change is cancelled, the previous change will be the one that overwrites older changes.
|
|
2409
|
+
* This allows you to make multiple unconfirmed changes, and confirm or cancel each of them individually at any time.
|
|
2410
|
+
*
|
|
2411
|
+
* @param {*} [value]
|
|
2412
|
+
* @returns {LeUtils~TransactionalValue}
|
|
2413
|
+
*/
|
|
2414
|
+
createTransactionalValue:
|
|
2415
|
+
(value) =>
|
|
2416
|
+
{
|
|
2417
|
+
if(typeof value === 'undefined')
|
|
2418
|
+
{
|
|
2419
|
+
value = null;
|
|
2420
|
+
}
|
|
2421
|
+
return {value:value, changes:[]};
|
|
2422
|
+
},
|
|
2423
|
+
|
|
2424
|
+
/**
|
|
2425
|
+
* Returns true if the given value is a valid TransactionalValue, returns false if it isn't.
|
|
2426
|
+
*
|
|
2427
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2428
|
+
* @returns {boolean}
|
|
2429
|
+
*/
|
|
2430
|
+
isTransactionalValueValid:
|
|
2431
|
+
(transactionalValue) =>
|
|
2432
|
+
{
|
|
2433
|
+
return ((typeof transactionalValue === 'object') && ('value' in transactionalValue) && ('changes' in transactionalValue) && Array.isArray(transactionalValue.changes));
|
|
2434
|
+
},
|
|
2435
|
+
|
|
2436
|
+
/**
|
|
2437
|
+
* Returns true if the given value is a TransactionalValue, false otherwise.
|
|
2438
|
+
*
|
|
2439
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2440
|
+
* @returns {string}
|
|
2441
|
+
*/
|
|
2442
|
+
transactionalValueToString:
|
|
2443
|
+
(transactionalValue) =>
|
|
2444
|
+
{
|
|
2445
|
+
if(!LeUtils.isTransactionalValueValid(transactionalValue))
|
|
2446
|
+
{
|
|
2447
|
+
return transactionalValue + '';
|
|
2448
|
+
}
|
|
2449
|
+
if(transactionalValue.changes.length <= 0)
|
|
2450
|
+
{
|
|
2451
|
+
return '' + transactionalValue.value;
|
|
2452
|
+
}
|
|
2453
|
+
let valuesString = '' + transactionalValue.value;
|
|
2454
|
+
for(let i = 0; i < transactionalValue.changes.length; i++)
|
|
2455
|
+
{
|
|
2456
|
+
valuesString += ' -> ' + transactionalValue.changes[i].value;
|
|
2457
|
+
}
|
|
2458
|
+
return transactionalValue.changes[transactionalValue.changes.length - 1].value + ' (' + valuesString + ')';
|
|
2459
|
+
},
|
|
2460
|
+
|
|
2461
|
+
/**
|
|
2462
|
+
* Sets the committed value of the given TransactionalValue to the given value. Clears out the previously uncommitted changes.
|
|
2463
|
+
*
|
|
2464
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2465
|
+
* @param {*} value
|
|
2466
|
+
*/
|
|
2467
|
+
transactionSetAndCommit:
|
|
2468
|
+
(transactionalValue, value) =>
|
|
2469
|
+
{
|
|
2470
|
+
checkTransactionalValue(transactionalValue);
|
|
2471
|
+
if(typeof value === 'undefined')
|
|
2472
|
+
{
|
|
2473
|
+
value = null;
|
|
2474
|
+
}
|
|
2475
|
+
transactionalValue.value = value;
|
|
2476
|
+
transactionalValue.changes = [];
|
|
2477
|
+
},
|
|
2478
|
+
|
|
2479
|
+
/**
|
|
2480
|
+
* Sets the value of the given TransactionalValue to the given value, without yet committing it, meaning it can be committed or cancelled later.
|
|
2481
|
+
* It returns the ID of the change, which can be used to commit or cancel the change later.
|
|
2482
|
+
*
|
|
2483
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2484
|
+
* @param {*} value
|
|
2485
|
+
* @returns {string}
|
|
2486
|
+
*/
|
|
2487
|
+
transactionSetWithoutCommitting:
|
|
2488
|
+
(transactionalValue, value) =>
|
|
2489
|
+
{
|
|
2490
|
+
checkTransactionalValue(transactionalValue);
|
|
2491
|
+
if(typeof value === 'undefined')
|
|
2492
|
+
{
|
|
2493
|
+
value = null;
|
|
2494
|
+
}
|
|
2495
|
+
const id = LeUtils.uniqueId();
|
|
2496
|
+
transactionalValue.changes.push({id:id, value:value});
|
|
2497
|
+
return id;
|
|
2498
|
+
},
|
|
2499
|
+
|
|
2500
|
+
/**
|
|
2501
|
+
* Commits the change with the given ID, making it the new committed value.
|
|
2502
|
+
* Returns true if the change was found and committed, returns false if it was already overwritten by a newer committed change.
|
|
2503
|
+
*
|
|
2504
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2505
|
+
* @param {string} changeId
|
|
2506
|
+
* @returns {boolean}
|
|
2507
|
+
*/
|
|
2508
|
+
transactionCommitChange:
|
|
2509
|
+
(transactionalValue, changeId) =>
|
|
2510
|
+
{
|
|
2511
|
+
checkTransactionalValue(transactionalValue);
|
|
2512
|
+
const change = findTransactionalValueChange(transactionalValue, changeId);
|
|
2513
|
+
if(change === null)
|
|
2514
|
+
{
|
|
2515
|
+
return false;
|
|
2516
|
+
}
|
|
2517
|
+
transactionalValue.value = change.value;
|
|
2518
|
+
transactionalValue.changes.splice(0, change.index + 1);
|
|
2519
|
+
return true;
|
|
2520
|
+
},
|
|
2521
|
+
|
|
2522
|
+
/**
|
|
2523
|
+
* Cancels the change with the given ID, removing it from the uncommitted changes.
|
|
2524
|
+
* Returns true if the change was found and removed, returns false if it was already overwritten by a newer committed change.
|
|
2525
|
+
*
|
|
2526
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2527
|
+
* @param {string} changeId
|
|
2528
|
+
* @returns {boolean}
|
|
2529
|
+
*/
|
|
2530
|
+
transactionCancelChange:
|
|
2531
|
+
(transactionalValue, changeId) =>
|
|
2532
|
+
{
|
|
2533
|
+
checkTransactionalValue(transactionalValue);
|
|
2534
|
+
const change = findTransactionalValueChange(transactionalValue, changeId);
|
|
2535
|
+
if(change === null)
|
|
2536
|
+
{
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
transactionalValue.changes.splice(change.index, 1);
|
|
2540
|
+
return true;
|
|
2541
|
+
},
|
|
2542
|
+
|
|
2543
|
+
/**
|
|
2544
|
+
* Returns true if the change was found, meaning it can still make a difference to the final committed value of this TransactionalValue.
|
|
2545
|
+
* 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.
|
|
2546
|
+
*
|
|
2547
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2548
|
+
* @param {string} changeId
|
|
2549
|
+
* @returns {boolean}
|
|
2550
|
+
*/
|
|
2551
|
+
transactionIsChangeRelevant:
|
|
2552
|
+
(transactionalValue, changeId) =>
|
|
2553
|
+
{
|
|
2554
|
+
checkTransactionalValue(transactionalValue);
|
|
2555
|
+
return (findTransactionalValueChange(transactionalValue, changeId) !== null);
|
|
2556
|
+
},
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* Returns the committed value of the given TransactionalValue.
|
|
2560
|
+
*
|
|
2561
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2562
|
+
* @returns {*}
|
|
2563
|
+
*/
|
|
2564
|
+
transactionGetCommittedValue:
|
|
2565
|
+
(transactionalValue) =>
|
|
2566
|
+
{
|
|
2567
|
+
checkTransactionalValue(transactionalValue);
|
|
2568
|
+
return transactionalValue.value;
|
|
2569
|
+
},
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* Returns the value (including any uncommitted changes made to it) of the given TransactionalValue.
|
|
2573
|
+
*
|
|
2574
|
+
* @param {LeUtils~TransactionalValue} transactionalValue
|
|
2575
|
+
* @returns {*}
|
|
2576
|
+
*/
|
|
2577
|
+
transactionGetValue:
|
|
2578
|
+
(transactionalValue) =>
|
|
2579
|
+
{
|
|
2580
|
+
checkTransactionalValue(transactionalValue);
|
|
2581
|
+
if(transactionalValue.changes.length <= 0)
|
|
2582
|
+
{
|
|
2583
|
+
return transactionalValue.value;
|
|
2584
|
+
}
|
|
2585
|
+
return transactionalValue.changes[transactionalValue.changes.length - 1].value;
|
|
2586
|
+
},
|
|
890
2587
|
};
|