@3dsource/utils 1.0.4 → 1.0.6
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/fesm2022/3dsource-utils.mjs +1812 -0
- package/fesm2022/3dsource-utils.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/{src/lib/color/CMYKtoRGB.ts → lib/color/CMYKtoRGB.d.ts} +1 -11
- package/lib/color/HEXtoRGB.d.ts +5 -0
- package/lib/color/HSVtoRGB.d.ts +21 -0
- package/{src/lib/color/RGBtoCMYK.ts → lib/color/RGBtoCMYK.d.ts} +1 -31
- package/lib/color/RGBtoHEX.d.ts +1 -0
- package/lib/color/RGBtoHSV.d.ts +19 -0
- package/lib/color/hsv.d.ts +1 -0
- package/lib/color/max.d.ts +1 -0
- package/lib/color/min.d.ts +1 -0
- package/lib/color/overlay.d.ts +1 -0
- package/{src/lib/color/rgb.ts → lib/color/rgb.d.ts} +1 -3
- package/lib/color/sub.d.ts +1 -0
- package/lib/color/subtract.d.ts +1 -0
- package/lib/color/sum.d.ts +1 -0
- package/{src/lib/color/toRGB.ts → lib/color/toRGB.d.ts} +1 -7
- package/lib/color/toRGBA.d.ts +1 -0
- package/lib/constants/color-codes.constant.d.ts +9 -0
- package/lib/csv/CSV2Array.d.ts +1 -0
- package/lib/csv/CSV2Records.d.ts +1 -0
- package/lib/csv/ObjectToCSV.d.ts +1 -0
- package/lib/dev/dev3d.d.ts +1 -0
- package/lib/dev/logger.d.ts +11 -0
- package/lib/dev/timeToString.d.ts +5 -0
- package/lib/filenaming/cleanupFileName.d.ts +1 -0
- package/lib/filenaming/makePath.d.ts +1 -0
- package/lib/filenaming/normalizePath.d.ts +1 -0
- package/lib/geom/expandOverRectangle.d.ts +2 -0
- package/lib/geom/fitIntoRectangle.d.ts +2 -0
- package/{src/lib/geom/interfaces/area.interface.ts → lib/geom/interfaces/area.interface.d.ts} +1 -2
- package/{src/lib/geom/interfaces/rect.interface.ts → lib/geom/interfaces/rect.interface.d.ts} +2 -2
- package/lib/geom/interfaces/size.interface.d.ts +4 -0
- package/lib/geom/interfaces//321/201oords.interface.d.ts +4 -0
- package/lib/helpers/BatchLoader.d.ts +47 -0
- package/lib/helpers/KeyboardNumericCode.d.ts +103 -0
- package/lib/helpers/serialize.d.ts +1 -0
- package/lib/helpers/sleep.d.ts +1 -0
- package/lib/helpers/trimLastSlashFromUrl.d.ts +1 -0
- package/lib/image/SaveImage.d.ts +18 -0
- package/lib/image/getCanvasCached.d.ts +4 -0
- package/lib/image/getSnapshot.d.ts +2 -0
- package/lib/image/loadImage.d.ts +1 -0
- package/lib/interfaces/image-output.d.ts +7 -0
- package/{src/lib/interfaces/load-args-tmp.interface.ts → lib/interfaces/load-args-tmp.interface.d.ts} +1 -2
- package/lib/interfaces/load-args.interface.d.ts +15 -0
- package/{src/lib/math/baseSortedIndex.ts → lib/math/baseSortedIndex.d.ts} +1 -19
- package/{src/lib/math/calculateMedian.ts → lib/math/calculateMedian.d.ts} +1 -17
- package/{src/lib/math/circularIndex.ts → lib/math/circularIndex.d.ts} +1 -5
- package/lib/math/clampf.d.ts +8 -0
- package/lib/math/degrees.d.ts +2 -0
- package/{src/lib/math/floatCompare.ts → lib/math/floatCompare.d.ts} +3 -30
- package/{src/lib/math/inverseLerp.ts → lib/math/inverseLerp.d.ts} +1 -22
- package/lib/math/lerp.d.ts +7 -0
- package/{src/lib/mutex/Mutex.ts → lib/mutex/Mutex.d.ts} +8 -15
- package/{src/lib/mutex/Semaphore.ts → lib/mutex/Semaphore.d.ts} +7 -27
- package/lib/mutex/TaskRunner.d.ts +5 -0
- package/{src/lib/predicates/index.ts → lib/predicates/index.d.ts} +1 -1
- package/lib/predicates/operators.d.ts +32 -0
- package/{src/lib/predicates/textForSearch.ts → lib/predicates/textForSearch.d.ts} +1 -17
- package/{src/lib/predicates/where.ts → lib/predicates/where.d.ts} +2 -32
- package/lib/rxjs/leadingTrailingDebounceTime.d.ts +15 -0
- package/{src/lib/rxjs/smoothTransition.ts → lib/rxjs/smoothTransition.d.ts} +1 -19
- package/lib/rxjs/tapLog.d.ts +2 -0
- package/lib/strings/pad.d.ts +8 -0
- package/package.json +13 -2
- package/eslint.config.js +0 -37
- package/ng-package.json +0 -7
- package/src/lib/color/HEXtoRGB.ts +0 -9
- package/src/lib/color/HSVtoRGB.ts +0 -82
- package/src/lib/color/RGBtoHEX.ts +0 -17
- package/src/lib/color/RGBtoHSV.ts +0 -53
- package/src/lib/color/hsv.ts +0 -14
- package/src/lib/color/max.ts +0 -18
- package/src/lib/color/min.ts +0 -18
- package/src/lib/color/overlay.ts +0 -25
- package/src/lib/color/sub.ts +0 -18
- package/src/lib/color/subtract.ts +0 -27
- package/src/lib/color/sum.ts +0 -19
- package/src/lib/color/toRGBA.ts +0 -8
- package/src/lib/constants/color-codes.constant.ts +0 -9
- package/src/lib/csv/CSV2Array.ts +0 -66
- package/src/lib/csv/CSV2Records.ts +0 -56
- package/src/lib/csv/ObjectToCSV.ts +0 -21
- package/src/lib/dev/dev3d.ts +0 -1
- package/src/lib/dev/logger.ts +0 -94
- package/src/lib/dev/timeToString.ts +0 -16
- package/src/lib/filenaming/cleanupFileName.ts +0 -18
- package/src/lib/filenaming/makePath.ts +0 -5
- package/src/lib/filenaming/normalizePath.ts +0 -9
- package/src/lib/geom/expandOverRectangle.ts +0 -17
- package/src/lib/geom/fitIntoRectangle.ts +0 -43
- package/src/lib/geom/interfaces/size.interface.ts +0 -4
- package/src/lib/geom/interfaces//321/201oords.interface.ts +0 -4
- package/src/lib/helpers/BatchLoader.ts +0 -243
- package/src/lib/helpers/KeyboardNumericCode.ts +0 -118
- package/src/lib/helpers/serialize.ts +0 -11
- package/src/lib/helpers/sleep.ts +0 -3
- package/src/lib/helpers/trimLastSlashFromUrl.ts +0 -9
- package/src/lib/image/SaveImage.ts +0 -65
- package/src/lib/image/getCanvasCached.ts +0 -16
- package/src/lib/image/getSnapshot.ts +0 -99
- package/src/lib/image/loadImage.ts +0 -13
- package/src/lib/interfaces/image-output.ts +0 -8
- package/src/lib/interfaces/load-args.interface.ts +0 -15
- package/src/lib/math/clampf.ts +0 -14
- package/src/lib/math/degrees.ts +0 -7
- package/src/lib/math/lerp.ts +0 -12
- package/src/lib/mutex/TaskRunner.ts +0 -26
- package/src/lib/predicates/BooleanPredictors.ts +0 -47
- package/src/lib/rxjs/leadingTrailingDebounceTime.ts +0 -86
- package/src/lib/rxjs/tapLog.ts +0 -13
- package/src/lib/strings/pad.ts +0 -18
- package/tsconfig.lib.json +0 -13
- package/tsconfig.lib.prod.json +0 -11
- /package/{src/lib/color/index.ts → lib/color/index.d.ts} +0 -0
- /package/{src/lib/constants/index.ts → lib/constants/index.d.ts} +0 -0
- /package/{src/lib/csv/index.ts → lib/csv/index.d.ts} +0 -0
- /package/{src/lib/dev/index.ts → lib/dev/index.d.ts} +0 -0
- /package/{src/lib/filenaming/index.ts → lib/filenaming/index.d.ts} +0 -0
- /package/{src/lib/geom/index.ts → lib/geom/index.d.ts} +0 -0
- /package/{src/lib/geom/interfaces/index.ts → lib/geom/interfaces/index.d.ts} +0 -0
- /package/{src/lib/helpers/index.ts → lib/helpers/index.d.ts} +0 -0
- /package/{src/lib/image/index.ts → lib/image/index.d.ts} +0 -0
- /package/{src/lib/interfaces/index.ts → lib/interfaces/index.d.ts} +0 -0
- /package/{src/lib/math/index.ts → lib/math/index.d.ts} +0 -0
- /package/{src/lib/mutex/index.ts → lib/mutex/index.d.ts} +0 -0
- /package/{src/lib/rxjs/index.ts → lib/rxjs/index.d.ts} +0 -0
- /package/{src/lib/strings/index.ts → lib/strings/index.d.ts} +0 -0
- /package/{src/public-api.ts → public-api.d.ts} +0 -0
|
@@ -0,0 +1,1812 @@
|
|
|
1
|
+
import { from, switchMap, finalize, asyncScheduler, Observable, timer, animationFrameScheduler, interval } from 'rxjs';
|
|
2
|
+
import { map, distinctUntilChanged, tap } from 'rxjs/operators';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* RGB from each of the CMYK values to determine a return as an any[].
|
|
6
|
+
* CMYK values are as follows.
|
|
7
|
+
* C - a number between 0 and 255 representing cyan
|
|
8
|
+
* M - number between 0 and 255 representing magenta
|
|
9
|
+
* Y - number between 0 and 255 representing yellow
|
|
10
|
+
* K - number between 0 and 255 representing black
|
|
11
|
+
*
|
|
12
|
+
**/
|
|
13
|
+
function CMYKtoRGB(c, m, y, k) {
|
|
14
|
+
c = 255 - c;
|
|
15
|
+
m = 255 - m;
|
|
16
|
+
y = 255 - y;
|
|
17
|
+
k = 255 - k;
|
|
18
|
+
return [
|
|
19
|
+
((255 - c) * (255 - k)) / 255,
|
|
20
|
+
((255 - m) * (255 - k)) / 255,
|
|
21
|
+
((255 - y) * (255 - k)) / 255,
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function HEXtoRGB(color) {
|
|
26
|
+
if (typeof color === 'string') {
|
|
27
|
+
color = parseInt(color.replace('#', ''), 16);
|
|
28
|
+
}
|
|
29
|
+
const r = (color >> 16) & 255;
|
|
30
|
+
const g = (color >> 8) & 255;
|
|
31
|
+
const b = color & 255;
|
|
32
|
+
return { r: r, g: g, b: b };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Values calculated from each RGB * RGB color value.
|
|
37
|
+
* @ Param r the red (R) indicating the number (0-255)
|
|
38
|
+
* @ Param g green (G) indicates the number (0-255)
|
|
39
|
+
* @ Param b blue (B) shows the number (0-255)
|
|
40
|
+
* @ Return obtained from the RGB color value for each indicating the number
|
|
41
|
+
**/
|
|
42
|
+
function rgb(r, g, b) {
|
|
43
|
+
return (r << 16) | (g << 8) | b;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* eslint-disable prefer-const */
|
|
47
|
+
/**
|
|
48
|
+
* HSV from each of the RGB values to determine a return as an any[].
|
|
49
|
+
* RGB values are as follows.
|
|
50
|
+
* R - a number from 0 to 255
|
|
51
|
+
* G - a number from 0 to 255
|
|
52
|
+
* B - a number from 0 to 255
|
|
53
|
+
*
|
|
54
|
+
* HSV values are as follows.
|
|
55
|
+
* H - a number between 360-0
|
|
56
|
+
* S - number between 0 and 1.0
|
|
57
|
+
* V - number between 0 and 1.0
|
|
58
|
+
*
|
|
59
|
+
* H is replaced with equivalent numbers in the range of the 360-0 that is out of range.
|
|
60
|
+
* Cannot compute, including alpha.
|
|
61
|
+
*
|
|
62
|
+
* @ Param h hue (Hue) number that indicates (to 360-0)
|
|
63
|
+
* @ Param s the saturation (Saturation) shows the number (0.0 to 1.0)
|
|
64
|
+
* @ Param v lightness (Value) indicates the number (0.0 to 1.0)
|
|
65
|
+
* @ Return RGB values into an any[] of [R, G, B]
|
|
66
|
+
**/
|
|
67
|
+
function HSVtoRGB(h, s, v) {
|
|
68
|
+
let r = 0, g = 0, b = 0;
|
|
69
|
+
let i, x, y, z;
|
|
70
|
+
if (s < 0) {
|
|
71
|
+
s = 0;
|
|
72
|
+
}
|
|
73
|
+
if (s > 1) {
|
|
74
|
+
s = 1;
|
|
75
|
+
}
|
|
76
|
+
if (v < 0) {
|
|
77
|
+
v = 0;
|
|
78
|
+
}
|
|
79
|
+
if (v > 1) {
|
|
80
|
+
v = 1;
|
|
81
|
+
}
|
|
82
|
+
h = h % 360;
|
|
83
|
+
if (h < 0) {
|
|
84
|
+
h += 360;
|
|
85
|
+
}
|
|
86
|
+
h /= 60;
|
|
87
|
+
i = h >> 0;
|
|
88
|
+
x = v * (1 - s);
|
|
89
|
+
y = v * (1 - s * (h - i));
|
|
90
|
+
z = v * (1 - s * (1 - h + i));
|
|
91
|
+
switch (i) {
|
|
92
|
+
case 0:
|
|
93
|
+
r = v;
|
|
94
|
+
g = z;
|
|
95
|
+
b = x;
|
|
96
|
+
break;
|
|
97
|
+
case 1:
|
|
98
|
+
r = y;
|
|
99
|
+
g = v;
|
|
100
|
+
b = x;
|
|
101
|
+
break;
|
|
102
|
+
case 2:
|
|
103
|
+
r = x;
|
|
104
|
+
g = v;
|
|
105
|
+
b = z;
|
|
106
|
+
break;
|
|
107
|
+
case 3:
|
|
108
|
+
r = x;
|
|
109
|
+
g = y;
|
|
110
|
+
b = v;
|
|
111
|
+
break;
|
|
112
|
+
case 4:
|
|
113
|
+
r = z;
|
|
114
|
+
g = x;
|
|
115
|
+
b = v;
|
|
116
|
+
break;
|
|
117
|
+
case 5:
|
|
118
|
+
r = v;
|
|
119
|
+
g = x;
|
|
120
|
+
b = y;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
return [(r * 255) >> 0, (g * 255) >> 0, (b * 255) >> 0];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* eslint-disable prefer-spread */
|
|
127
|
+
/**
|
|
128
|
+
* HSV calculated from the numbers of each RGB color value.
|
|
129
|
+
* @ Param h hue (Hue) number that indicates (to 360-0)
|
|
130
|
+
* @ Param s the saturation (Saturation) shows the number (0.0 to 1.0)
|
|
131
|
+
* @ Param v lightness (Value) indicates the number (0.0 to 1.0)
|
|
132
|
+
* @ Return obtained from the RGB color value for each indicating the number
|
|
133
|
+
**/
|
|
134
|
+
function hsv(h, s, v) {
|
|
135
|
+
return rgb.apply(null, HSVtoRGB(h, s, v));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* RGB figures show (0x000000 0xFFFFFF up from) the
|
|
140
|
+
* R, G, B returns an any[] divided into a number from 0 to 255, respectively.
|
|
141
|
+
*
|
|
142
|
+
* @ Param rgb numbers show (0x000000 0xFFFFFF up from)
|
|
143
|
+
* @ Return any[] indicates the value of each color [R, G, B]
|
|
144
|
+
**/
|
|
145
|
+
function toRGB(rgb) {
|
|
146
|
+
const r = (rgb >> 16) & 0xff;
|
|
147
|
+
const g = (rgb >> 8) & 0xff;
|
|
148
|
+
const b = rgb & 0xff;
|
|
149
|
+
return [r, g, b];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Comparison (light).
|
|
154
|
+
* 2 RGB single number that indicates (0x000000 0xFFFFFF up from) to compare,
|
|
155
|
+
* RGB values combined with higher returns to their numbers.
|
|
156
|
+
* @ Param col1 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
157
|
+
* @ Param col2 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
158
|
+
* @ Return comparison (light) value
|
|
159
|
+
**/
|
|
160
|
+
function max(col1, col2) {
|
|
161
|
+
const c1 = toRGB(col1);
|
|
162
|
+
const c2 = toRGB(col2);
|
|
163
|
+
const r = Math.max(c1[0], c2[0]);
|
|
164
|
+
const g = Math.max(c1[1], c2[1]);
|
|
165
|
+
const b = Math.max(c1[2], c2[2]);
|
|
166
|
+
return (r << 16) | (g << 8) | b;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Comparison (dark).
|
|
171
|
+
* 2 RGB single numbers that indicate (0x000000 0xFFFFFF up from) to compare,
|
|
172
|
+
* RGB lower combined returns a numeric value for each number.
|
|
173
|
+
* @ Param col1 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
174
|
+
* @ Param col2 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
175
|
+
* @ Return comparison (dark) values
|
|
176
|
+
**/
|
|
177
|
+
function min(col1, col2) {
|
|
178
|
+
const c1 = toRGB(col1);
|
|
179
|
+
const c2 = toRGB(col2);
|
|
180
|
+
const r = Math.min(c1[0], c2[0]);
|
|
181
|
+
const g = Math.min(c1[1], c2[1]);
|
|
182
|
+
const b = Math.min(c1[2], c2[2]);
|
|
183
|
+
return (r << 16) | (g << 8) | b;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
*
|
|
188
|
+
There are two part of formula:
|
|
189
|
+
First part: If Lower Layer Value > 127.5, then do the following -
|
|
190
|
+
Value Unit = (255-Lower Layer Value)/127.5
|
|
191
|
+
Min Value = Lower Layer Value - (255-Lower Layer Value)
|
|
192
|
+
Overlay = (Upper Layer Value * Value Unit) + Min Value
|
|
193
|
+
Second part: If Lower Layer Value < 127.5, then do the following -
|
|
194
|
+
Value Unit=Lower Layer Value/127.5
|
|
195
|
+
|
|
196
|
+
Overlay = Upper Layer Value * Value Unit
|
|
197
|
+
* @param {number} col1
|
|
198
|
+
* @param {number} col2
|
|
199
|
+
* @return {number}
|
|
200
|
+
*/
|
|
201
|
+
function overlay(col1, col2) {
|
|
202
|
+
const c1 = toRGB(col1);
|
|
203
|
+
const c2 = toRGB(col2);
|
|
204
|
+
const r = Math.max(c1[0], c2[0]);
|
|
205
|
+
const g = Math.max(c1[1], c2[1]);
|
|
206
|
+
const b = Math.max(c1[2], c2[2]);
|
|
207
|
+
return (r << 16) | (g << 8) | b;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* RGB from the respective figures, HSV sequences in terms of returns.
|
|
212
|
+
* RGB values are as follows.
|
|
213
|
+
* R - a number from 0 to 255
|
|
214
|
+
* G - a number from 0 to 255
|
|
215
|
+
* B - a number from 0 to 255
|
|
216
|
+
*
|
|
217
|
+
* CMYK values are as follows.
|
|
218
|
+
* C - a number between 0 and 255 representing cyan
|
|
219
|
+
* M - number between 0 and 255 representing magenta
|
|
220
|
+
* Y - number between 0 and 255 representing yellow
|
|
221
|
+
* K - number between 0 and 255 representing black
|
|
222
|
+
*
|
|
223
|
+
* Cannot compute, including alpha.
|
|
224
|
+
* @ Param r the red (R) indicating the number (0x00 to 0xFF to)
|
|
225
|
+
* @ Param g green (G) indicates the number (0x00 to 0xFF to)
|
|
226
|
+
* @ Param b blue (B) shows the number (0x00 to 0xFF to)
|
|
227
|
+
* @ Return CMYK values into an any[] of [H, S, V]
|
|
228
|
+
**/
|
|
229
|
+
function RGBtoCMYK(r, g, b) {
|
|
230
|
+
let c = 0;
|
|
231
|
+
let m = 0;
|
|
232
|
+
let y = 0;
|
|
233
|
+
let k = 0;
|
|
234
|
+
c = 255 - r;
|
|
235
|
+
m = 255 - g;
|
|
236
|
+
y = 255 - b;
|
|
237
|
+
k = 255;
|
|
238
|
+
if (c < k) {
|
|
239
|
+
k = c;
|
|
240
|
+
}
|
|
241
|
+
if (m < k) {
|
|
242
|
+
k = m;
|
|
243
|
+
}
|
|
244
|
+
if (y < k) {
|
|
245
|
+
k = y;
|
|
246
|
+
}
|
|
247
|
+
if (k === 255) {
|
|
248
|
+
c = 0;
|
|
249
|
+
m = 0;
|
|
250
|
+
y = 0;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
c = Math.round((255 * (c - k)) / (255 - k));
|
|
254
|
+
m = Math.round((255 * (m - k)) / (255 - k));
|
|
255
|
+
y = Math.round((255 * (y - k)) / (255 - k));
|
|
256
|
+
}
|
|
257
|
+
return [c, m, y, k];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function RGBtoHEX(r, g, b) {
|
|
261
|
+
let redColor = r.toString(16);
|
|
262
|
+
let greenColor = g.toString(16);
|
|
263
|
+
let blackColor = b.toString(16);
|
|
264
|
+
if (redColor.length === 1) {
|
|
265
|
+
redColor = '0' + redColor;
|
|
266
|
+
}
|
|
267
|
+
if (greenColor.length === 1) {
|
|
268
|
+
greenColor = '0' + greenColor;
|
|
269
|
+
}
|
|
270
|
+
if (blackColor.length === 1) {
|
|
271
|
+
blackColor = '0' + blackColor;
|
|
272
|
+
}
|
|
273
|
+
return (redColor + greenColor + blackColor).toUpperCase();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* RGB from the respective figures, HSV sequences in terms of returns.
|
|
278
|
+
* RGB values are as follows.
|
|
279
|
+
* R - a number from 0 to 255
|
|
280
|
+
* G - a number from 0 to 255
|
|
281
|
+
* B - a number from 0 to 255
|
|
282
|
+
*
|
|
283
|
+
* HSV values are as follows.
|
|
284
|
+
* H - a number between 360-0
|
|
285
|
+
* S - number between 0 and 1.0
|
|
286
|
+
* V - number between 0 and 1.0
|
|
287
|
+
*
|
|
288
|
+
* Cannot compute, including alpha.
|
|
289
|
+
* @ Param r the red (R) indicating the number (0x00 to 0xFF to)
|
|
290
|
+
* @ Param g green (G) indicates the number (0x00 to 0xFF to)
|
|
291
|
+
* @ Param b blue (B) shows the number (0x00 to 0xFF to)
|
|
292
|
+
* @ Return HSV values into an any[] of [H, S, V]
|
|
293
|
+
**/
|
|
294
|
+
function RGBtoHSV(r, g, b) {
|
|
295
|
+
r /= 255;
|
|
296
|
+
g /= 255;
|
|
297
|
+
b /= 255;
|
|
298
|
+
let h = 0, s = 0, v = 0;
|
|
299
|
+
let x, y;
|
|
300
|
+
if (r >= g)
|
|
301
|
+
x = r;
|
|
302
|
+
else
|
|
303
|
+
x = g;
|
|
304
|
+
if (b > x)
|
|
305
|
+
x = b;
|
|
306
|
+
if (r <= g)
|
|
307
|
+
y = r;
|
|
308
|
+
else
|
|
309
|
+
y = g;
|
|
310
|
+
if (b < y)
|
|
311
|
+
y = b;
|
|
312
|
+
v = x;
|
|
313
|
+
const c = x - y;
|
|
314
|
+
if (x === 0)
|
|
315
|
+
s = 0;
|
|
316
|
+
else
|
|
317
|
+
s = c / x;
|
|
318
|
+
if (s !== 0) {
|
|
319
|
+
if (r === x) {
|
|
320
|
+
h = (g - b) / c;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
if (g === x) {
|
|
324
|
+
h = 2 + (b - r) / c;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
if (b === x) {
|
|
328
|
+
h = 4 + (r - g) / c;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
h = h * 60;
|
|
333
|
+
if (h < 0)
|
|
334
|
+
h = h + 360;
|
|
335
|
+
}
|
|
336
|
+
return [h, s, v];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Subtractive.
|
|
341
|
+
* 2 RGB single numbers that indicate (0x000000 0xFFFFFF up from) Return the value
|
|
342
|
+
* of the subtractive color.
|
|
343
|
+
* @ Param col1 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
344
|
+
* @ Param col2 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
345
|
+
* @ Return the subtractive
|
|
346
|
+
**/
|
|
347
|
+
function sub(col1, col2) {
|
|
348
|
+
const c1 = toRGB(col1);
|
|
349
|
+
const c2 = toRGB(col2);
|
|
350
|
+
const r = Math.max(c1[0] - c2[0], 0);
|
|
351
|
+
const g = Math.max(c1[1] - c2[1], 0);
|
|
352
|
+
const b = Math.max(c1[2] - c2[2], 0);
|
|
353
|
+
return (r << 16) | (g << 8) | b;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Subtraction.
|
|
358
|
+
* 2 RGB single number that indicates (0x000000 0xFFFFFF up from) is subtracted
|
|
359
|
+
* from the return numbers.
|
|
360
|
+
* @ Param col1 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
361
|
+
* @ Param col2 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
362
|
+
* @ Return value subtracted Blend
|
|
363
|
+
**/
|
|
364
|
+
function subtract(col1, col2) {
|
|
365
|
+
const colA = toRGB(col1);
|
|
366
|
+
const colB = toRGB(col2);
|
|
367
|
+
const r = Math.max(Math.max(colB[0] - (256 - colA[0]), colA[0] - (256 - colB[0])), 0);
|
|
368
|
+
const g = Math.max(Math.max(colB[1] - (256 - colA[1]), colA[1] - (256 - colB[1])), 0);
|
|
369
|
+
const b = Math.max(Math.max(colB[2] - (256 - colA[2]), colA[2] - (256 - colB[2])), 0);
|
|
370
|
+
return (r << 16) | (g << 8) | b;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Additive color.
|
|
375
|
+
* 2 RGB single numbers that indicate (0x000000 0xFFFFFF up from) Return the value
|
|
376
|
+
* of the additive mixture.
|
|
377
|
+
* @ Param col1 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
378
|
+
* @ Param col2 RGB numbers show (0x000000 0xFFFFFF up from)
|
|
379
|
+
* @ Return the additive color
|
|
380
|
+
**/
|
|
381
|
+
function sum(col1, col2) {
|
|
382
|
+
const c1 = toRGB(col1);
|
|
383
|
+
const c2 = toRGB(col2);
|
|
384
|
+
const r = Math.min(c1[0] + c2[0], 255);
|
|
385
|
+
const g = Math.min(c1[1] + c2[1], 255);
|
|
386
|
+
const b = Math.min(c1[2] + c2[2], 255);
|
|
387
|
+
return (r << 16) | (g << 8) | b;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function toRGBA(rgba) {
|
|
391
|
+
const a = (rgba >> 24) & 0xff;
|
|
392
|
+
const r = (rgba >> 16) & 0xff;
|
|
393
|
+
const g = (rgba >> 8) & 0xff;
|
|
394
|
+
const b = rgba & 0xff;
|
|
395
|
+
return [r, g, b, a];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const COLOR_CODES = {
|
|
399
|
+
TO_UNREAL: ['#ffffff', '#005912', `UNREAL <-`],
|
|
400
|
+
TAP_LOG: ['#ffff00', '#1976d2', ' [TAPLOG]'],
|
|
401
|
+
FROM_UNREAL: ['#ffff00', '#898989', 'UNREAL ->'],
|
|
402
|
+
FROM_UNREAL_ERROR: ['#ffffff', '#ff0000', 'UNREAL ERROR ->'],
|
|
403
|
+
FROM_CIRRUS: ['#ffffff', '#ef702b', 'CIRRUS ->'],
|
|
404
|
+
FROM_CIRRUS_ERROR: ['#ffffff', '#ff0000', 'CIRRUS ->'],
|
|
405
|
+
TO_CIRRUS: ['#ffffff', '#3c9738', 'CIRRUS <-'],
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
function CSV2Array(strData, strDelimiter) {
|
|
409
|
+
// Check to see if the delimiter is defined. If not,
|
|
410
|
+
// then default to comma.
|
|
411
|
+
strDelimiter = strDelimiter || ',';
|
|
412
|
+
// Create a regular expression to parse the CSV values.
|
|
413
|
+
const objPattern = new RegExp(
|
|
414
|
+
// Delimiters.
|
|
415
|
+
'(\\' +
|
|
416
|
+
strDelimiter +
|
|
417
|
+
'|\\r?\\n|\\r|^)' +
|
|
418
|
+
// Quoted fields.
|
|
419
|
+
'(?:"([^"]*(?:""[^"]*)*)"|' +
|
|
420
|
+
// Standard fields.
|
|
421
|
+
'([^"\\' +
|
|
422
|
+
strDelimiter +
|
|
423
|
+
'\\r\\n]*))', 'gi');
|
|
424
|
+
// Create an array to hold our data. Give the array
|
|
425
|
+
// a default empty first row.
|
|
426
|
+
const arrData = [[]];
|
|
427
|
+
// Create an array to hold our individual pattern
|
|
428
|
+
// matching groups.
|
|
429
|
+
let arrMatches = null;
|
|
430
|
+
// Keep looping over the regular expression matches
|
|
431
|
+
// until we can no longer find a match.
|
|
432
|
+
while ((arrMatches = objPattern.exec(strData))) {
|
|
433
|
+
// Get the delimiter that was found.
|
|
434
|
+
const strMatchedDelimiter = arrMatches[1];
|
|
435
|
+
// Check to see if the given delimiter has a length
|
|
436
|
+
// (is not the start of string) and if it matches
|
|
437
|
+
// field delimiter. If idKey does not, then we know
|
|
438
|
+
// that this delimiter is a row delimiter.
|
|
439
|
+
if (strMatchedDelimiter.length && strMatchedDelimiter !== strDelimiter) {
|
|
440
|
+
// Since we have reached a new row of data,
|
|
441
|
+
// add an empty row to our data array.
|
|
442
|
+
arrData.push([]);
|
|
443
|
+
}
|
|
444
|
+
let strMatchedValue;
|
|
445
|
+
// Now that we have our delimiter out of the way,
|
|
446
|
+
// let's check to see which kind of value we
|
|
447
|
+
// captured (quoted or unquoted).
|
|
448
|
+
if (arrMatches[2]) {
|
|
449
|
+
// We found a quoted value. When we capture
|
|
450
|
+
// this value, unescape any double quotes.
|
|
451
|
+
strMatchedValue = arrMatches[2].replace(new RegExp('""', 'g'), '"');
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
// We found a non-quoted value.
|
|
455
|
+
strMatchedValue = arrMatches[3];
|
|
456
|
+
}
|
|
457
|
+
// Now that we have our value string, let's add
|
|
458
|
+
// it to the data array.
|
|
459
|
+
arrData[arrData.length - 1].push(strMatchedValue);
|
|
460
|
+
}
|
|
461
|
+
// Return the parsed data.
|
|
462
|
+
return arrData;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const booleanRegex = /^(true|false)$/i;
|
|
466
|
+
const trueRegex = /^(true)$/i;
|
|
467
|
+
const jsonRegex = /([\d.]+)|(\[])|(\[.+])|({.+})/i;
|
|
468
|
+
const zipObject = (props, values) => props.reduce((obj, prop, index) => ((obj[prop] = values[index]), obj), {});
|
|
469
|
+
const toHead = (input) => {
|
|
470
|
+
return String(input).trim();
|
|
471
|
+
};
|
|
472
|
+
const toCorrectString = (input) => {
|
|
473
|
+
if (typeof input === 'string') {
|
|
474
|
+
if (input.match(jsonRegex)) {
|
|
475
|
+
// optimization for use try-catch only after regex match. much faster than without regex
|
|
476
|
+
try {
|
|
477
|
+
return JSON.parse(input);
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
/* empty */
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (input.match(booleanRegex)) {
|
|
484
|
+
return !!input.match(trueRegex);
|
|
485
|
+
}
|
|
486
|
+
return input.trim();
|
|
487
|
+
}
|
|
488
|
+
else if (typeof input === 'number') {
|
|
489
|
+
return input;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
function CSV2Records(csv, useRowAsHead = 0, strDelimiter) {
|
|
493
|
+
const arr = CSV2Array(csv, strDelimiter);
|
|
494
|
+
const total = arr.length;
|
|
495
|
+
const keys = arr[useRowAsHead].map(toHead);
|
|
496
|
+
const data = [];
|
|
497
|
+
let tmpSKU = 0;
|
|
498
|
+
for (let i = useRowAsHead + 1; i < total; i++) {
|
|
499
|
+
const obj = zipObject(keys, arr[i].map(toCorrectString));
|
|
500
|
+
data.push(obj);
|
|
501
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
502
|
+
tmpSKU++;
|
|
503
|
+
}
|
|
504
|
+
return data.filter(Boolean);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function ObjectToCSV(records) {
|
|
508
|
+
if (records.length === 0) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const uniqueFields = Array.from(records.reduce((fields, record) => {
|
|
512
|
+
Object.keys(record).forEach((field) => fields.add(field));
|
|
513
|
+
return fields;
|
|
514
|
+
}, new Set()));
|
|
515
|
+
const csvHeader = uniqueFields.join(',') + '\n';
|
|
516
|
+
const csvRows = records
|
|
517
|
+
.map((record) => uniqueFields.map((field) => `"${record[field] || ''}"`).join(','))
|
|
518
|
+
.join('\n');
|
|
519
|
+
return csvHeader + csvRows;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const isLocalhost = !!window.location.href.match(/localhost/gi);
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Adds required number of symbols before input
|
|
526
|
+
* @param input input string
|
|
527
|
+
* @param size amount of total symbols
|
|
528
|
+
* @param symbol filler
|
|
529
|
+
* @returns string
|
|
530
|
+
*/
|
|
531
|
+
function pad(input, size, symbol = '0') {
|
|
532
|
+
let s = input + '';
|
|
533
|
+
while (s.length < size) {
|
|
534
|
+
s = symbol + s;
|
|
535
|
+
}
|
|
536
|
+
return s.slice(-size);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Create a string of the form 'HOURS.MINUTES.SECONDS.MILLISECONDS'.
|
|
541
|
+
*/
|
|
542
|
+
function timeToString() {
|
|
543
|
+
const date = new Date();
|
|
544
|
+
return `${pad(date.getHours(), 2)}:${pad(date.getMinutes(), 2)}:${pad(date.getSeconds(), 2)}.${pad(date.getMilliseconds(), 3)}`;
|
|
545
|
+
}
|
|
546
|
+
function timeUTCToString() {
|
|
547
|
+
const date = new Date();
|
|
548
|
+
return `${pad(date.getUTCHours(), 2)}:${pad(date.getUTCMinutes(), 2)}:${pad(date.getUTCSeconds(), 2)}.${pad(date.getUTCMilliseconds(), 3)}`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* The Boolean object represents a truth value: true or false.
|
|
553
|
+
* @param value
|
|
554
|
+
* @constructor
|
|
555
|
+
* Returns:boolean
|
|
556
|
+
*/
|
|
557
|
+
const Truthy = (value) => !!value && value !== 'false' && value !== 'undefined' && value !== 'null';
|
|
558
|
+
/**
|
|
559
|
+
* The Boolean object represents an INVERSE truth value: true or false.
|
|
560
|
+
* @param value
|
|
561
|
+
* @constructor
|
|
562
|
+
* Returns:boolean
|
|
563
|
+
*/
|
|
564
|
+
const Falsy = (value) => !Truthy(value);
|
|
565
|
+
/**
|
|
566
|
+
* Checks if a value is empty.
|
|
567
|
+
*
|
|
568
|
+
* @param {any} value - The value to check.
|
|
569
|
+
* @return {boolean} Returns true if the value is empty, otherwise false.
|
|
570
|
+
*/
|
|
571
|
+
const IsEmpty = (value) => {
|
|
572
|
+
return (value === undefined ||
|
|
573
|
+
value === null ||
|
|
574
|
+
(typeof value === 'object' && Object.keys(value).length === 0) ||
|
|
575
|
+
(typeof value === 'string' && value.trim().length === 0));
|
|
576
|
+
};
|
|
577
|
+
const NotEmpty = (value) => {
|
|
578
|
+
return !IsEmpty(value);
|
|
579
|
+
};
|
|
580
|
+
const isDefined = (arg) => arg !== null && arg !== undefined;
|
|
581
|
+
const isNotDefined = (arg) => arg === null || arg === undefined;
|
|
582
|
+
const isJSON = (str) => {
|
|
583
|
+
try {
|
|
584
|
+
JSON.parse(str);
|
|
585
|
+
}
|
|
586
|
+
catch (e) {
|
|
587
|
+
console.warn(e);
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
};
|
|
592
|
+
const isEmpty = (value) => value === undefined ||
|
|
593
|
+
value === null ||
|
|
594
|
+
(typeof value === 'object' && Object.keys(value).length === 0) ||
|
|
595
|
+
(typeof value === 'string' && value.trim().length === 0);
|
|
596
|
+
function isAllValuesTruthy(value) {
|
|
597
|
+
return Object.values(value).every(Truthy);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const cache = {};
|
|
601
|
+
/**
|
|
602
|
+
* Converts a given value to a string suitable for search comparisons.
|
|
603
|
+
* The resulting string is in lowercase, with consecutive spaces replaced by underscores.
|
|
604
|
+
* The function uses a cache to improve performance by storing and reusing the results of previous conversions.
|
|
605
|
+
*
|
|
606
|
+
* @param {any} value - The value to be converted to a search-friendly string.
|
|
607
|
+
* It can be of any type, but it will be coerced to a string if not already one.
|
|
608
|
+
*
|
|
609
|
+
* @param {boolean} caseSensitive - A flag indicating whether the comparison should be case-sensitive.
|
|
610
|
+
* If set to true, the function will perform case-sensitive comparisons, preserving the original casing of input strings.
|
|
611
|
+
* If set to false (default), the function will convert the input strings to lowercase for case-insensitive comparisons
|
|
612
|
+
*
|
|
613
|
+
* @returns {string} The processed string, which is trimmed, converted to lowercase (unless caseSensitive is true),
|
|
614
|
+
* and has all sequences of spaces replaced with underscores. This string is either
|
|
615
|
+
* retrieved from the cache or freshly computed and then cached.
|
|
616
|
+
*
|
|
617
|
+
*/
|
|
618
|
+
function textForSearch(value, caseSensitive = false) {
|
|
619
|
+
const key = `${value}_${caseSensitive}`;
|
|
620
|
+
return (cache[key] ||
|
|
621
|
+
(() => {
|
|
622
|
+
let result = String(value).trim().replace(/ +/gi, '_');
|
|
623
|
+
if (!caseSensitive) {
|
|
624
|
+
result = result.toLowerCase();
|
|
625
|
+
}
|
|
626
|
+
cache[key] = result;
|
|
627
|
+
return result;
|
|
628
|
+
})());
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Creates a predicate function to filter records from a collection based on specified criteria.
|
|
633
|
+
*
|
|
634
|
+
* The `where` function accepts a criteria object with key-value pairs, indicating the fields
|
|
635
|
+
* and their expected values for filtering. It returns a predicate function that filters a collection
|
|
636
|
+
* of records. This predicate checks if records match the criteria using a case-insensitive comparison,
|
|
637
|
+
* making it particularly useful in conjunction with the `textForSearch` function, which standardizes
|
|
638
|
+
* strings for such comparisons.
|
|
639
|
+
*
|
|
640
|
+
* Each criterion value is first processed with `textForSearch` to normalize spacing and case,
|
|
641
|
+
* ensuring consistent and predictable filtering. The comparison between each record's field value
|
|
642
|
+
* and the criterion value is thus case-insensitive and ignores extra spaces.
|
|
643
|
+
*
|
|
644
|
+
* @param {Partial<Record<K, T[K]>>} criteria - An object representing the filtering criteria.
|
|
645
|
+
* Keys correspond to the record's fields to be filtered, and values are the expected values for those fields,
|
|
646
|
+
* processed in a case-insensitive manner. Undefined values are ignored.
|
|
647
|
+
*
|
|
648
|
+
* @param {boolean} caseSensitive - A flag indicating whether the comparison should be case-sensitive.
|
|
649
|
+
* If set to true, the function will perform case-sensitive comparisons, preserving the original casing of input strings.
|
|
650
|
+
* If set to false (default), the function will convert the input strings to lowercase for case-insensitive comparisons
|
|
651
|
+
*
|
|
652
|
+
* @returns {(record: T) => boolean} A predicate function that takes a record of type `T`
|
|
653
|
+
* and returns `true` if the record's field values match all criteria, otherwise `false`.
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* // Define a collection of items
|
|
657
|
+
* const items = [
|
|
658
|
+
* { name: "Apple", category: "Fruit" },
|
|
659
|
+
* { name: "Carrot", category: "Vegetable" },
|
|
660
|
+
* { name: "Banana", category: "Fruit" }
|
|
661
|
+
* ];
|
|
662
|
+
*
|
|
663
|
+
* // Create a predicate to find fruits, using a case-insensitive comparison
|
|
664
|
+
* const isFruit = where({ category: "fruit" }); // 'fruit' will match 'Fruit' in items
|
|
665
|
+
*
|
|
666
|
+
* // Filter the collection with the predicate
|
|
667
|
+
* const fruits = items.filter(isFruit);
|
|
668
|
+
* // Output: [{ name: "Apple", category: "Fruit" }, { name: "Banana", category: "Fruit" }]
|
|
669
|
+
*
|
|
670
|
+
* @typeparam T - Specifies the type of records in the collection to be filtered. It extends `object`,
|
|
671
|
+
* ensuring the function is only used with object types. This generic approach allows for flexible usage
|
|
672
|
+
* with various record structures.
|
|
673
|
+
*/
|
|
674
|
+
function where(criteria, caseSensitive = false) {
|
|
675
|
+
const preComputedEntries = Object.entries(criteria)
|
|
676
|
+
.filter(([, value]) => value !== undefined)
|
|
677
|
+
.map(([key, value]) => [key, textForSearch(value, caseSensitive)]);
|
|
678
|
+
return (record) => {
|
|
679
|
+
return (preComputedEntries.length > 0 &&
|
|
680
|
+
preComputedEntries.every(([key, criterionValue]) => {
|
|
681
|
+
return (textForSearch(record[key] ?? '', caseSensitive) ===
|
|
682
|
+
criterionValue);
|
|
683
|
+
}));
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function whereNot(criteria, caseSensitive = false) {
|
|
687
|
+
const whereFunc = where(criteria, caseSensitive);
|
|
688
|
+
return (record) => {
|
|
689
|
+
return !whereFunc(record);
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
class Logger {
|
|
694
|
+
static { this.isDevMode = Truthy(localStorage.getItem('devMode')); }
|
|
695
|
+
static colored(color, background, ...args) {
|
|
696
|
+
if (!Logger.isDevMode) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const [first, ...rest] = args;
|
|
700
|
+
console.log.apply(console, [
|
|
701
|
+
`%c ${timeUTCToString()} ${first} `,
|
|
702
|
+
`color: ${color}; background: ${background};`,
|
|
703
|
+
...rest,
|
|
704
|
+
]);
|
|
705
|
+
}
|
|
706
|
+
static log(...args) {
|
|
707
|
+
if (!Logger.isDevMode) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
console.log.apply(console, [
|
|
711
|
+
`%c ${timeUTCToString()} [ LOG ] `,
|
|
712
|
+
'color: black; background: #80ff80;',
|
|
713
|
+
...args,
|
|
714
|
+
]);
|
|
715
|
+
}
|
|
716
|
+
static warn(...args) {
|
|
717
|
+
if (!Logger.isDevMode) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
console.warn.apply(console, [
|
|
721
|
+
`%c ${timeUTCToString()} WARN `,
|
|
722
|
+
'color: black; background: #ffd500;',
|
|
723
|
+
...args,
|
|
724
|
+
]);
|
|
725
|
+
}
|
|
726
|
+
static info(...args) {
|
|
727
|
+
if (!Logger.isDevMode) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
console.log.apply(console, [
|
|
731
|
+
`%c ${timeUTCToString()} [ INFO ] `,
|
|
732
|
+
'color: white; background: blue;',
|
|
733
|
+
...args,
|
|
734
|
+
]);
|
|
735
|
+
}
|
|
736
|
+
static error(...args) {
|
|
737
|
+
if (!Logger.isDevMode) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
console.error.apply(console, [
|
|
741
|
+
`%c ${timeUTCToString()} ERROR `,
|
|
742
|
+
'color: white; background: red;',
|
|
743
|
+
...args,
|
|
744
|
+
]);
|
|
745
|
+
}
|
|
746
|
+
static table(...args) {
|
|
747
|
+
if (!Logger.isDevMode) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
// eslint-disable-next-line no-console
|
|
751
|
+
console.table.apply(console, [args]);
|
|
752
|
+
}
|
|
753
|
+
static time(...args) {
|
|
754
|
+
if (!Logger.isDevMode) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
// eslint-disable-next-line no-console,prefer-spread
|
|
758
|
+
console.time.apply(console, args);
|
|
759
|
+
}
|
|
760
|
+
static timeEnd(...args) {
|
|
761
|
+
if (!Logger.isDevMode) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
// eslint-disable-next-line no-console,prefer-spread
|
|
765
|
+
console.timeEnd.apply(console, args);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function cleanupFileName(value) {
|
|
770
|
+
if (!value) {
|
|
771
|
+
return value;
|
|
772
|
+
}
|
|
773
|
+
value = value
|
|
774
|
+
.toString()
|
|
775
|
+
.replace(/undefined/g, '')
|
|
776
|
+
.trim()
|
|
777
|
+
.replace(/\//g, '-')
|
|
778
|
+
.replace(/[-]+/g, '-')
|
|
779
|
+
.replace(/[^\w^.-]+/gi, '_')
|
|
780
|
+
.replace(/[\^]+/gi, '_');
|
|
781
|
+
value = value.split('_').filter(Boolean).join('_').toLowerCase();
|
|
782
|
+
return value;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function makePath(...value) {
|
|
786
|
+
return [...value].map((el) => cleanupFileName(el)).join('/');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function normalizePath(value) {
|
|
790
|
+
const re = new RegExp('[/\\\\]+', 'g');
|
|
791
|
+
return value
|
|
792
|
+
.split(re)
|
|
793
|
+
.map((el) => cleanupFileName(el))
|
|
794
|
+
.join('/');
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function expandOverRectangle(objectRect, areaRect) {
|
|
798
|
+
const ratio = objectRect.w / objectRect.h;
|
|
799
|
+
const result = { x: 0, y: 0, w: 0, h: 0, scale: 1 };
|
|
800
|
+
if (areaRect.w / ratio < areaRect.h) {
|
|
801
|
+
result.w = areaRect.h * ratio;
|
|
802
|
+
result.h = areaRect.h;
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
result.w = areaRect.w;
|
|
806
|
+
result.h = areaRect.w / ratio;
|
|
807
|
+
}
|
|
808
|
+
result.x = areaRect.x + (areaRect.w - result.w) / 2;
|
|
809
|
+
result.y = areaRect.y + (areaRect.h - result.h) / 2;
|
|
810
|
+
result.scale = result.w / objectRect.w;
|
|
811
|
+
return result;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function fitIntoRectangle(objectRect, areaRect, round = true, fixedRatio = null) {
|
|
815
|
+
const result = { x: 0, y: 0, w: 0, h: 0, scale: 1 };
|
|
816
|
+
const ratio = objectRect.h / objectRect.w;
|
|
817
|
+
if (objectRect.h > objectRect.w) {
|
|
818
|
+
result.h = areaRect.h;
|
|
819
|
+
result.w = areaRect.h / ratio;
|
|
820
|
+
if (result.w > areaRect.w) {
|
|
821
|
+
result.w = areaRect.w;
|
|
822
|
+
result.h = areaRect.w * ratio;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
result.w = areaRect.w;
|
|
827
|
+
result.h = areaRect.w * ratio;
|
|
828
|
+
if (result.h > areaRect.h) {
|
|
829
|
+
result.h = areaRect.h;
|
|
830
|
+
result.w = areaRect.h / ratio;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (fixedRatio) {
|
|
834
|
+
result.w = objectRect.w * fixedRatio;
|
|
835
|
+
result.h = objectRect.h * fixedRatio;
|
|
836
|
+
}
|
|
837
|
+
const wOut = round ? Math.floor(result.w) : result.w;
|
|
838
|
+
const hOut = round ? Math.floor(result.h) : result.h;
|
|
839
|
+
return {
|
|
840
|
+
x: areaRect.x + (areaRect.w - wOut) / 2,
|
|
841
|
+
y: areaRect.y + (areaRect.h - hOut) / 2,
|
|
842
|
+
w: wOut,
|
|
843
|
+
h: hOut,
|
|
844
|
+
scale: wOut / objectRect.w,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const canvasCacheObj = {};
|
|
849
|
+
function getCanvasCached(id) {
|
|
850
|
+
if (!canvasCacheObj[id]) {
|
|
851
|
+
const canvas = document.createElement('canvas');
|
|
852
|
+
canvasCacheObj[id] = {
|
|
853
|
+
canvas,
|
|
854
|
+
ctx: canvas.getContext('2d'),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
return canvasCacheObj[id];
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function getSnapshot2(imageElement, maxWidth = 1024, output = 'image/png', quality = 0.9) {
|
|
861
|
+
const drawableImage = imageElement;
|
|
862
|
+
const canvasHelper = document.createElement('canvas');
|
|
863
|
+
const drawCTX = canvasHelper.getContext('2d');
|
|
864
|
+
const prop = fitIntoRectangle({
|
|
865
|
+
x: 0,
|
|
866
|
+
y: 0,
|
|
867
|
+
w: drawableImage.naturalWidth || drawableImage.width,
|
|
868
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
869
|
+
}, {
|
|
870
|
+
x: 0,
|
|
871
|
+
y: 0,
|
|
872
|
+
w: maxWidth,
|
|
873
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
874
|
+
});
|
|
875
|
+
const width = prop.w;
|
|
876
|
+
const height = prop.h;
|
|
877
|
+
canvasHelper.width = width;
|
|
878
|
+
canvasHelper.height = height;
|
|
879
|
+
drawCTX.save();
|
|
880
|
+
drawCTX.clearRect(0, 0, width, height);
|
|
881
|
+
drawCTX.drawImage(drawableImage, 0, 0, width, height);
|
|
882
|
+
drawCTX.restore();
|
|
883
|
+
if (output === 'canvas') {
|
|
884
|
+
return canvasHelper;
|
|
885
|
+
}
|
|
886
|
+
return canvasHelper.toDataURL(output, quality);
|
|
887
|
+
}
|
|
888
|
+
function getSnapshot(imageElement, maxWidth = 1024, output = 'image/png', quality = 0.9) {
|
|
889
|
+
const drawableImage = imageElement;
|
|
890
|
+
const canvasHelper = document.createElement('canvas');
|
|
891
|
+
const drawCTX = canvasHelper.getContext('2d');
|
|
892
|
+
const prop = fitIntoRectangle({
|
|
893
|
+
x: 0,
|
|
894
|
+
y: 0,
|
|
895
|
+
w: drawableImage.naturalWidth || drawableImage.width,
|
|
896
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
897
|
+
}, {
|
|
898
|
+
x: 0,
|
|
899
|
+
y: 0,
|
|
900
|
+
w: (drawableImage.naturalWidth || drawableImage.width) / 2,
|
|
901
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
902
|
+
});
|
|
903
|
+
const width = prop.w;
|
|
904
|
+
const height = prop.h;
|
|
905
|
+
canvasHelper.width = width;
|
|
906
|
+
canvasHelper.height = height;
|
|
907
|
+
drawCTX.save();
|
|
908
|
+
drawCTX.clearRect(0, 0, width, height);
|
|
909
|
+
drawCTX.drawImage(drawableImage, 0, 0, width, height);
|
|
910
|
+
drawCTX.restore();
|
|
911
|
+
if (width > maxWidth * 2) {
|
|
912
|
+
return getSnapshot(canvasHelper, maxWidth, output, quality);
|
|
913
|
+
}
|
|
914
|
+
return getSnapshot2(drawableImage, maxWidth, output, quality);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function loadImage(url) {
|
|
918
|
+
return new Promise((resolve, reject) => {
|
|
919
|
+
const img = new Image();
|
|
920
|
+
img.crossOrigin = 'anonymous';
|
|
921
|
+
img.onload = () => {
|
|
922
|
+
setTimeout(() => resolve(img), 1);
|
|
923
|
+
};
|
|
924
|
+
img.onerror = () => {
|
|
925
|
+
setTimeout(() => reject(null), 1);
|
|
926
|
+
};
|
|
927
|
+
img.src = url;
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* USE DownloadImage instead!
|
|
933
|
+
* @deprecated use DownloadImage instead!
|
|
934
|
+
* @param imageSource
|
|
935
|
+
* @param name
|
|
936
|
+
* @param width
|
|
937
|
+
* @param format
|
|
938
|
+
*/
|
|
939
|
+
function SaveImage(imageSource, name, width, format) {
|
|
940
|
+
let out;
|
|
941
|
+
const image = imageSource;
|
|
942
|
+
if (typeof image !== 'string') {
|
|
943
|
+
out = getSnapshot(image, width, format);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
out = image;
|
|
947
|
+
}
|
|
948
|
+
if (navigator.msSaveBlob) {
|
|
949
|
+
// IE10+
|
|
950
|
+
try {
|
|
951
|
+
const blob = image.msToBlob();
|
|
952
|
+
return navigator.msSaveBlob(blob, name);
|
|
953
|
+
}
|
|
954
|
+
catch (e) {
|
|
955
|
+
console.warn(e);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
const uri = out;
|
|
960
|
+
const link = document.createElement('a');
|
|
961
|
+
link.download = name;
|
|
962
|
+
link.href = uri;
|
|
963
|
+
document.body.appendChild(link);
|
|
964
|
+
if (link.click) {
|
|
965
|
+
link.click();
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
const event = document.createEvent('MouseEvents');
|
|
969
|
+
event.initMouseEvent('click', true, true, window);
|
|
970
|
+
link.dispatchEvent(event);
|
|
971
|
+
}
|
|
972
|
+
document.body.removeChild(link);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* @param imageSource
|
|
977
|
+
* @param string name
|
|
978
|
+
* @param number width
|
|
979
|
+
* @param string format
|
|
980
|
+
*/
|
|
981
|
+
const DownloadImage = SaveImage;
|
|
982
|
+
|
|
983
|
+
class BatchLoader {
|
|
984
|
+
constructor() {
|
|
985
|
+
this.maximumSlotsNumber = 1;
|
|
986
|
+
this.queue = [];
|
|
987
|
+
this.slots = [];
|
|
988
|
+
}
|
|
989
|
+
clear() {
|
|
990
|
+
this.queue.forEach((item) => {
|
|
991
|
+
try {
|
|
992
|
+
if (item.img) {
|
|
993
|
+
item.img.src = '';
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
catch (e) {
|
|
997
|
+
console.warn(e);
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
item.xhr?.abort();
|
|
1001
|
+
}
|
|
1002
|
+
catch (e) {
|
|
1003
|
+
console.warn(e);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
this.slots.forEach((item) => {
|
|
1007
|
+
try {
|
|
1008
|
+
if (item.img) {
|
|
1009
|
+
item.img.src = '';
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
catch (e) {
|
|
1013
|
+
console.warn(e);
|
|
1014
|
+
}
|
|
1015
|
+
try {
|
|
1016
|
+
item.xhr?.abort();
|
|
1017
|
+
}
|
|
1018
|
+
catch (e) {
|
|
1019
|
+
console.warn(e);
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
this.slots.length = 0;
|
|
1023
|
+
this.queue.length = 0;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* new settings
|
|
1027
|
+
* you must pass new settings
|
|
1028
|
+
* @param sett
|
|
1029
|
+
*/
|
|
1030
|
+
setSettings(sett) {
|
|
1031
|
+
this.maximumSlotsNumber =
|
|
1032
|
+
sett.maximumSlotsNumber !== undefined
|
|
1033
|
+
? sett.maximumSlotsNumber
|
|
1034
|
+
: this.maximumSlotsNumber;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* execute with a chosen file
|
|
1038
|
+
* @param id
|
|
1039
|
+
* @param func
|
|
1040
|
+
*/
|
|
1041
|
+
withItem(id, func) {
|
|
1042
|
+
let item = this.getById(id);
|
|
1043
|
+
if (!item) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
item = func(item);
|
|
1047
|
+
this.queue.sort(this.sortByOrder);
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
remove(id) {
|
|
1051
|
+
this.queue = this.queue.filter((arg) => arg.id !== id);
|
|
1052
|
+
return this.queue;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* executes when all files are downloaded
|
|
1056
|
+
* @param cb
|
|
1057
|
+
*/
|
|
1058
|
+
whenDone(cb) {
|
|
1059
|
+
this._whenDone = cb;
|
|
1060
|
+
}
|
|
1061
|
+
// order {number} lower will execute first, default *100*
|
|
1062
|
+
load(args) {
|
|
1063
|
+
const out = args
|
|
1064
|
+
.filter((arg) => !!arg.path)
|
|
1065
|
+
.map((arg) => ({
|
|
1066
|
+
path: arg.path,
|
|
1067
|
+
id: this.add(arg),
|
|
1068
|
+
}));
|
|
1069
|
+
this.queue.sort(this.sortByOrder);
|
|
1070
|
+
this.checkSlots();
|
|
1071
|
+
return out;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* operates with one file
|
|
1075
|
+
* @param item
|
|
1076
|
+
* @private
|
|
1077
|
+
*/
|
|
1078
|
+
add(item) {
|
|
1079
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1080
|
+
const that = this;
|
|
1081
|
+
const temp = {
|
|
1082
|
+
id: item.id,
|
|
1083
|
+
resolutionId: null,
|
|
1084
|
+
useXhr: item.useXhr,
|
|
1085
|
+
order: item.order !== undefined ? item.order : 100,
|
|
1086
|
+
fallBack: item.fallBack,
|
|
1087
|
+
path: item.path,
|
|
1088
|
+
onLoadEnd: item.onLoadEnd,
|
|
1089
|
+
onProgress: item.onProgress,
|
|
1090
|
+
onLoadStart: item.onLoadStart,
|
|
1091
|
+
extra: item.extra,
|
|
1092
|
+
};
|
|
1093
|
+
let retryCount = 1;
|
|
1094
|
+
if (temp.useXhr) {
|
|
1095
|
+
temp.xhr = this.getXmlHttp();
|
|
1096
|
+
temp.xhr.onloadstart = (data) => item.onLoadStart(data);
|
|
1097
|
+
temp.xhr.onprogress = (data) => item.onProgress(data);
|
|
1098
|
+
temp.xhr.onreadystatechange = async function (data) {
|
|
1099
|
+
if (this.readyState === 1 && this.status === 0) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (this.readyState === 4 &&
|
|
1103
|
+
(this.status === 404 || this.status === 0)) {
|
|
1104
|
+
temp.error = true;
|
|
1105
|
+
that.loadEnd(temp);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (this.readyState === 4 && this.status === 200) {
|
|
1109
|
+
const urlCreator = window.URL;
|
|
1110
|
+
const imageUrl = urlCreator.createObjectURL(data.target.response);
|
|
1111
|
+
temp.img = await loadImage(imageUrl);
|
|
1112
|
+
that.loadEnd(temp);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
temp.img = new Image();
|
|
1119
|
+
temp.img.crossOrigin = 'anonymous';
|
|
1120
|
+
temp.img.onload = () => {
|
|
1121
|
+
that.loadEnd(temp);
|
|
1122
|
+
};
|
|
1123
|
+
temp.img.onerror = () => {
|
|
1124
|
+
if (temp.fallBack && retryCount-- > 0) {
|
|
1125
|
+
console.warn('Fallback used =>', temp.path, '=>', temp.fallBack);
|
|
1126
|
+
if (temp.img) {
|
|
1127
|
+
temp.img.onload = null;
|
|
1128
|
+
temp.img.src = temp.fallBack;
|
|
1129
|
+
}
|
|
1130
|
+
that.loadEnd(temp);
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
temp.error = true;
|
|
1134
|
+
that.loadEnd(temp);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
this.queue.push(temp);
|
|
1139
|
+
return temp.id;
|
|
1140
|
+
}
|
|
1141
|
+
sortByOrder(a, b) {
|
|
1142
|
+
return a.order > b.order ? 1 : a.order < b.order ? -1 : 0;
|
|
1143
|
+
}
|
|
1144
|
+
loadEnd(item) {
|
|
1145
|
+
if (item.onLoadEnd !== undefined) {
|
|
1146
|
+
for (let i = 0, len = this.slots.length; i < len; i += 1) {
|
|
1147
|
+
if (this.slots[i].id === item.id) {
|
|
1148
|
+
this.slots.splice(i, 1);
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
item.onLoadEnd(item);
|
|
1153
|
+
}
|
|
1154
|
+
this.checkSlots();
|
|
1155
|
+
}
|
|
1156
|
+
checkSlots() {
|
|
1157
|
+
while ((this.slots.length < this.maximumSlotsNumber ||
|
|
1158
|
+
this.maximumSlotsNumber === -1) &&
|
|
1159
|
+
this.queue.length) {
|
|
1160
|
+
this.slots.push(this.queue.shift());
|
|
1161
|
+
const slot = this.slots[this.slots.length - 1];
|
|
1162
|
+
if (slot.useXhr) {
|
|
1163
|
+
slot.xhr?.open('GET', slot.path, true);
|
|
1164
|
+
slot.xhr?.send(null);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
if (slot.img) {
|
|
1168
|
+
slot.img.src = slot.path;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (this.slots.length === 0 &&
|
|
1173
|
+
this.queue.length === 0 &&
|
|
1174
|
+
this._whenDone !== undefined) {
|
|
1175
|
+
this._whenDone();
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* get Item By id for interaction with files in this.queue
|
|
1180
|
+
* @param id
|
|
1181
|
+
* @private
|
|
1182
|
+
*/
|
|
1183
|
+
getById(id) {
|
|
1184
|
+
for (let i = 0, len = this.queue.length; i < len; i += 1) {
|
|
1185
|
+
if (this.queue[i].id === id) {
|
|
1186
|
+
return this.queue[i];
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
getXmlHttp() {
|
|
1192
|
+
let xhr = null;
|
|
1193
|
+
try {
|
|
1194
|
+
xhr = new XMLHttpRequest();
|
|
1195
|
+
xhr.responseType = 'blob';
|
|
1196
|
+
}
|
|
1197
|
+
catch (e) {
|
|
1198
|
+
console.warn(e);
|
|
1199
|
+
}
|
|
1200
|
+
return xhr;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const KeyboardNumericCode = {
|
|
1205
|
+
Backspace: 8,
|
|
1206
|
+
Tab: 9,
|
|
1207
|
+
Numpad5: 12,
|
|
1208
|
+
NumpadEnter: 13,
|
|
1209
|
+
ShiftRight: 16,
|
|
1210
|
+
ControlRight: 17,
|
|
1211
|
+
ControlLeft: 17,
|
|
1212
|
+
AltRight: 18,
|
|
1213
|
+
Escape: 27,
|
|
1214
|
+
Space: 32,
|
|
1215
|
+
Numpad9: 33,
|
|
1216
|
+
Numpad3: 34,
|
|
1217
|
+
Numpad1: 35,
|
|
1218
|
+
Numpad7: 36,
|
|
1219
|
+
Numpad4: 37,
|
|
1220
|
+
Numpad8: 38,
|
|
1221
|
+
Numpad6: 39,
|
|
1222
|
+
Numpad2: 40,
|
|
1223
|
+
Numpad0: 45,
|
|
1224
|
+
NumpadDecimal: 46,
|
|
1225
|
+
Digit0: 48,
|
|
1226
|
+
Digit1: 49,
|
|
1227
|
+
Digit2: 50,
|
|
1228
|
+
Digit3: 51,
|
|
1229
|
+
Digit4: 52,
|
|
1230
|
+
Digit5: 53,
|
|
1231
|
+
Digit6: 54,
|
|
1232
|
+
Digit7: 55,
|
|
1233
|
+
Digit8: 56,
|
|
1234
|
+
Digit9: 57,
|
|
1235
|
+
KeyA: 65,
|
|
1236
|
+
KeyB: 66,
|
|
1237
|
+
KeyC: 67,
|
|
1238
|
+
KeyD: 68,
|
|
1239
|
+
KeyE: 69,
|
|
1240
|
+
KeyF: 70,
|
|
1241
|
+
KeyG: 71,
|
|
1242
|
+
KeyH: 72,
|
|
1243
|
+
KeyI: 73,
|
|
1244
|
+
KeyJ: 74,
|
|
1245
|
+
KeyK: 75,
|
|
1246
|
+
KeyL: 76,
|
|
1247
|
+
KeyM: 77,
|
|
1248
|
+
KeyN: 78,
|
|
1249
|
+
KeyO: 79,
|
|
1250
|
+
KeyP: 80,
|
|
1251
|
+
KeyQ: 81,
|
|
1252
|
+
KeyR: 82,
|
|
1253
|
+
KeyS: 83,
|
|
1254
|
+
KeyT: 84,
|
|
1255
|
+
KeyU: 85,
|
|
1256
|
+
KeyV: 86,
|
|
1257
|
+
KeyW: 87,
|
|
1258
|
+
KeyX: 88,
|
|
1259
|
+
KeyY: 89,
|
|
1260
|
+
KeyZ: 90,
|
|
1261
|
+
MetaLeft: 91,
|
|
1262
|
+
ContextMenu: 93,
|
|
1263
|
+
NumpadMultiply: 106,
|
|
1264
|
+
NumpadAdd: 107,
|
|
1265
|
+
NumpadSubtract: 109,
|
|
1266
|
+
NumpadDivide: 111,
|
|
1267
|
+
F1: 112,
|
|
1268
|
+
F2: 113,
|
|
1269
|
+
F3: 114,
|
|
1270
|
+
F4: 115,
|
|
1271
|
+
F5: 116,
|
|
1272
|
+
F6: 117,
|
|
1273
|
+
F7: 118,
|
|
1274
|
+
F8: 119,
|
|
1275
|
+
F9: 120,
|
|
1276
|
+
F10: 121,
|
|
1277
|
+
F11: 122,
|
|
1278
|
+
F12: 123,
|
|
1279
|
+
NumLock: 144,
|
|
1280
|
+
ScrollLock: 145,
|
|
1281
|
+
Semicolon: 186,
|
|
1282
|
+
Equal: 187,
|
|
1283
|
+
Comma: 188,
|
|
1284
|
+
Minus: 189,
|
|
1285
|
+
Period: 190,
|
|
1286
|
+
Slash: 191,
|
|
1287
|
+
Backquote: 192,
|
|
1288
|
+
BracketLeft: 219,
|
|
1289
|
+
Backslash: 220,
|
|
1290
|
+
BracketRight: 221,
|
|
1291
|
+
Quote: 222,
|
|
1292
|
+
IntlBackslash: 226,
|
|
1293
|
+
ArrowUp: 38,
|
|
1294
|
+
ArrowDown: 40,
|
|
1295
|
+
ArrowLeft: 37,
|
|
1296
|
+
ArrowRight: 39,
|
|
1297
|
+
};
|
|
1298
|
+
const InvertedKeyMapValues = {};
|
|
1299
|
+
//For use for InvertedKeyMap[32] will return 'Space' and so on
|
|
1300
|
+
const InvertedKeyMap = InvertedKeyMapValues;
|
|
1301
|
+
const KeyboardStringCode = {};
|
|
1302
|
+
Object.entries(KeyboardNumericCode).forEach(
|
|
1303
|
+
// @ts-expect-error @typescript-eslint/ban-ts-comment
|
|
1304
|
+
([key, value]) => {
|
|
1305
|
+
InvertedKeyMapValues[value] = key;
|
|
1306
|
+
// @ts-expect-error @typescript-eslint/ban-ts-comment
|
|
1307
|
+
KeyboardStringCode[key] = key;
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
function serialize(obj) {
|
|
1311
|
+
const str = [];
|
|
1312
|
+
for (const p in obj) {
|
|
1313
|
+
if (Object.prototype.hasOwnProperty.call(obj, p)) {
|
|
1314
|
+
str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return str.join('&');
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function sleep(time) {
|
|
1321
|
+
return new Promise((r) => setTimeout(r, time));
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function trimLastSlashFromUrl(baseUrl) {
|
|
1325
|
+
if (!baseUrl) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
else if (baseUrl[baseUrl.length - 1] === '/') {
|
|
1329
|
+
return baseUrl.substring(0, baseUrl.length - 1);
|
|
1330
|
+
}
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const ImageOutput = {
|
|
1335
|
+
CANVAS: 'canvas',
|
|
1336
|
+
PNG: 'image/png',
|
|
1337
|
+
WEBP: 'image/webp',
|
|
1338
|
+
JPG: 'image/jpeg',
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* Calculates the index at which the `value` should be inserted into the `array` to maintain its
|
|
1343
|
+
* sorted order. This function performs a binary search with a complexity of O(log n), making it
|
|
1344
|
+
* efficient for large datasets. It assumes that the input array is sorted in ascending order and
|
|
1345
|
+
* contains comparable elements.
|
|
1346
|
+
*
|
|
1347
|
+
* Note: The function is designed to handle arrays with lengths up to a maximum of `HALF_MAX_ARRAY_LENGTH`
|
|
1348
|
+
* to ensure the search operation remains within JavaScript's maximum array index limit.
|
|
1349
|
+
*
|
|
1350
|
+
* @param {any[]} array The sorted array into which the value should be inserted. The array elements
|
|
1351
|
+
* should be comparable with the `value` using less-than and greater-than operations.
|
|
1352
|
+
* @param {number} value The value to insert into the array. This function assumes that `array` is sorted
|
|
1353
|
+
* in ascending order and will find the correct position for `value` accordingly.
|
|
1354
|
+
* @returns {number} The index at which the `value` should be inserted into `array` to maintain its sorted order.
|
|
1355
|
+
* If the `array` is empty or if `value` is less than or equal to all elements in `array`,
|
|
1356
|
+
* returns 0. If `value` is greater than all elements in `array`, returns the length of `array`.
|
|
1357
|
+
* @example
|
|
1358
|
+
* // returns 2
|
|
1359
|
+
* baseSortedIndex([1, 3, 4], 3);
|
|
1360
|
+
*
|
|
1361
|
+
* @example
|
|
1362
|
+
* // returns 1
|
|
1363
|
+
* baseSortedIndex([1, 2, 3], 2);
|
|
1364
|
+
*/
|
|
1365
|
+
function baseSortedIndex(array, value) {
|
|
1366
|
+
const MAX_ARRAY_LENGTH = 4294967295;
|
|
1367
|
+
const HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
|
|
1368
|
+
let low = 0;
|
|
1369
|
+
let high = array?.length || low;
|
|
1370
|
+
if (high <= HALF_MAX_ARRAY_LENGTH) {
|
|
1371
|
+
while (low < high) {
|
|
1372
|
+
const mid = (low + high) >>> 1;
|
|
1373
|
+
const computed = array[mid];
|
|
1374
|
+
if (computed !== null && computed < value) {
|
|
1375
|
+
low = mid + 1;
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
high = mid;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return high;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Calculates the median of an array of numbers.
|
|
1387
|
+
*
|
|
1388
|
+
* The median is the middle value of a sorted list of numbers. If the list has an even number
|
|
1389
|
+
* of elements, the median is the average of the two middle numbers.
|
|
1390
|
+
*
|
|
1391
|
+
* @param numbers - An array of numbers for which the median will be calculated.
|
|
1392
|
+
* @returns The median value as a number, or `null` if the array is empty.
|
|
1393
|
+
*
|
|
1394
|
+
* @example
|
|
1395
|
+
* ```typescript
|
|
1396
|
+
* const numbers = [3, 1, 4, 2];
|
|
1397
|
+
* const median = calculateMedian(numbers);
|
|
1398
|
+
* console.log(median); // Output: 2.5
|
|
1399
|
+
* ```
|
|
1400
|
+
*/
|
|
1401
|
+
function calculateMedian(numbers) {
|
|
1402
|
+
if (numbers.length === 0)
|
|
1403
|
+
return 0;
|
|
1404
|
+
// Sort the array in ascending order
|
|
1405
|
+
const sorted = [...numbers].sort((a, b) => a - b);
|
|
1406
|
+
const length = sorted.length;
|
|
1407
|
+
const middle = Math.floor(length / 2);
|
|
1408
|
+
// If the length is even, return the average of the two middle numbers
|
|
1409
|
+
if (length % 2 === 0) {
|
|
1410
|
+
return (sorted[middle - 1] + sorted[middle]) / 2;
|
|
1411
|
+
}
|
|
1412
|
+
// If the length is odd, return the middle number
|
|
1413
|
+
return sorted[middle];
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Clamps value between min and max;
|
|
1418
|
+
* @param min minValue
|
|
1419
|
+
* @param max maxValue
|
|
1420
|
+
* @param value inputValue
|
|
1421
|
+
* @returns number
|
|
1422
|
+
*/
|
|
1423
|
+
function clampf(min, max, value) {
|
|
1424
|
+
if (min > max) {
|
|
1425
|
+
return min;
|
|
1426
|
+
}
|
|
1427
|
+
return Math.min(max, Math.max(min, value));
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Computes a circular index within a given range.
|
|
1432
|
+
*
|
|
1433
|
+
* This function ensures that the index wraps around within the range of 0 to `totalItems - 1`.
|
|
1434
|
+
* It uses the `clampf` function to clamp the result within the valid range.
|
|
1435
|
+
*
|
|
1436
|
+
* @param index - The current index to be wrapped.
|
|
1437
|
+
* @param totalItems - The total number of items in the range.
|
|
1438
|
+
* @returns The wrapped index within the range of 0 to `totalItems - 1`.
|
|
1439
|
+
*
|
|
1440
|
+
* @example
|
|
1441
|
+
* ```typescript
|
|
1442
|
+
* import { circularIndex } from './circularIndex';
|
|
1443
|
+
*
|
|
1444
|
+
* // Example 1: Basic usage
|
|
1445
|
+
* const index1 = circularIndex(5, 5);
|
|
1446
|
+
* console.log(index1); // Output: 0
|
|
1447
|
+
*
|
|
1448
|
+
* // Example 2: Index greater than totalItems
|
|
1449
|
+
* const index2 = circularIndex(6, 5);
|
|
1450
|
+
* console.log(index2); // Output: 1
|
|
1451
|
+
*
|
|
1452
|
+
* // Example 3: Negative index
|
|
1453
|
+
* const index3 = circularIndex(-2, 5);
|
|
1454
|
+
* console.log(index3); // Output: 3
|
|
1455
|
+
*
|
|
1456
|
+
* // Example 4: Large Negative index
|
|
1457
|
+
* const index4 = circularIndex(-6, 5);
|
|
1458
|
+
* console.log(index4); // Output: 4
|
|
1459
|
+
*
|
|
1460
|
+
* // Example 5: Index equal to totalItems
|
|
1461
|
+
* const index5 = circularIndex(1, 1);
|
|
1462
|
+
* console.log(index5); // Output: 0
|
|
1463
|
+
*/
|
|
1464
|
+
function circularIndex(index, totalItems) {
|
|
1465
|
+
return clampf(0, totalItems - 1, (totalItems * 2 + index) % totalItems);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function degreesToRadians(degrees) {
|
|
1469
|
+
return degrees * (Math.PI / 180);
|
|
1470
|
+
}
|
|
1471
|
+
function radiansToDegrees(radians) {
|
|
1472
|
+
return radians * (180 / Math.PI);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* A small epsilon value used for floating-point comparisons.
|
|
1477
|
+
*
|
|
1478
|
+
* @remarks
|
|
1479
|
+
* The value of `EPSILON` is `0.01`. This can be overridden in the comparison functions if needed.
|
|
1480
|
+
*/
|
|
1481
|
+
const EPSILON = 0.01;
|
|
1482
|
+
/**
|
|
1483
|
+
* Checks if floating-point number `A` is less than `B` considering a small margin of error.
|
|
1484
|
+
*
|
|
1485
|
+
* @param A - The first floating-point number to compare.
|
|
1486
|
+
* @param B - The second floating-point number to compare.
|
|
1487
|
+
* @param Epsilon - Optional custom epsilon value for comparison. Defaults to `EPSILON` if not provided.
|
|
1488
|
+
* @returns `true` if `A` is less than `B` considering the epsilon margin, otherwise `false`.
|
|
1489
|
+
*
|
|
1490
|
+
* @example
|
|
1491
|
+
* ```
|
|
1492
|
+
* const result = fpIsALessThanB(0.0034, 0.0066); // true
|
|
1493
|
+
* ```
|
|
1494
|
+
*/
|
|
1495
|
+
function fpIsALessThanB(A, B, Epsilon) {
|
|
1496
|
+
Epsilon = Epsilon || EPSILON;
|
|
1497
|
+
return A - B < Epsilon && Math.abs(A - B) > Epsilon;
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Checks if floating-point number `A` is greater than `B` considering a small margin of error.
|
|
1501
|
+
*
|
|
1502
|
+
* @param A - The first floating-point number to compare.
|
|
1503
|
+
* @param B - The second floating-point number to compare.
|
|
1504
|
+
* @param Epsilon - Optional custom epsilon value for comparison. Defaults to `EPSILON` if not provided.
|
|
1505
|
+
* @returns `true` if `A` is greater than `B` considering the epsilon margin, otherwise `false`.
|
|
1506
|
+
*
|
|
1507
|
+
* @example
|
|
1508
|
+
* ```
|
|
1509
|
+
* const result = fpIsAGreaterThanB(0.0066, 0.0034); // true
|
|
1510
|
+
* ```
|
|
1511
|
+
*/
|
|
1512
|
+
function fpIsAGreaterThanB(A, B, Epsilon) {
|
|
1513
|
+
Epsilon = Epsilon || EPSILON;
|
|
1514
|
+
return A - B > Epsilon && Math.abs(A - B) > Epsilon;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Checks if floating-point number `A` is approximately the same as `B` considering a small margin of error.
|
|
1518
|
+
*
|
|
1519
|
+
* @param A - The first floating-point number to compare.
|
|
1520
|
+
* @param B - The second floating-point number to compare.
|
|
1521
|
+
* @param Epsilon - Optional custom epsilon value for comparison. Defaults to `EPSILON` if not provided.
|
|
1522
|
+
* @returns `true` if `A` is approximately equal to `B` considering the epsilon margin, otherwise `false`.
|
|
1523
|
+
*
|
|
1524
|
+
* @example
|
|
1525
|
+
* ```
|
|
1526
|
+
* const result = fpIsASameAsB(0.005, 0.0051); // true
|
|
1527
|
+
* ```
|
|
1528
|
+
*/
|
|
1529
|
+
function fpIsASameAsB(A, B, Epsilon) {
|
|
1530
|
+
Epsilon = Epsilon || EPSILON;
|
|
1531
|
+
return Math.abs(A - B) < Epsilon;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Performs an inverse linear interpolation.
|
|
1536
|
+
*
|
|
1537
|
+
* Given a range defined by `min` and `max`, this function calculates the
|
|
1538
|
+
* interpolation factor (or percentage) of `value` within that range. Optionally,
|
|
1539
|
+
* it can clamp the result to be between 0 and 1 to ensure the return value is
|
|
1540
|
+
* within the range of a standard lerp operation.
|
|
1541
|
+
*
|
|
1542
|
+
* @param {number} min - The minimum value of the interpolation range.
|
|
1543
|
+
* @param {number} max - The maximum value of the interpolation range.
|
|
1544
|
+
* @param {number} value - The value to interpolate.
|
|
1545
|
+
* @param {boolean} [clampToNormal=false] - Whether to clamp the result between 0 and 1.
|
|
1546
|
+
* @returns {number} The interpolation factor of `value` between `min` and `max`.
|
|
1547
|
+
* If `clampToNormal` is true, the result is clamped between 0 and 1.
|
|
1548
|
+
* If the difference between `min` and `max` is 0, returns 1 to avoid division by zero.
|
|
1549
|
+
*/
|
|
1550
|
+
function inverseLerp(min, max, value, clampToNormal = false) {
|
|
1551
|
+
if (clampToNormal) {
|
|
1552
|
+
value = Math.min(max, value);
|
|
1553
|
+
value = Math.max(min, value);
|
|
1554
|
+
}
|
|
1555
|
+
const difference = max - min;
|
|
1556
|
+
// Avoid JS division error
|
|
1557
|
+
if (difference === 0) {
|
|
1558
|
+
return 1;
|
|
1559
|
+
}
|
|
1560
|
+
const result = (value - min) / difference;
|
|
1561
|
+
if (clampToNormal) {
|
|
1562
|
+
return Math.min(1, Math.max(0, result));
|
|
1563
|
+
}
|
|
1564
|
+
return result;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* @param min minimums
|
|
1569
|
+
* @param max maximums
|
|
1570
|
+
* @param value current float value in 0-1 range
|
|
1571
|
+
* @returns clamped value
|
|
1572
|
+
*/
|
|
1573
|
+
function lerp(min, max, value) {
|
|
1574
|
+
value = clampf(0, 1, value);
|
|
1575
|
+
return value * (max - min) + min;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* A Mutex class to handle mutual exclusion locks, ensuring that only one asynchronous task can execute a critical section at a time.
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* const mutex = new Mutex();
|
|
1583
|
+
*
|
|
1584
|
+
* async function task(name: string, duration: number) {
|
|
1585
|
+
* console.log(`${name} waiting to acquire mutex`);
|
|
1586
|
+
* const release = await mutex.acquire();
|
|
1587
|
+
* console.log(`${name} acquired mutex`);
|
|
1588
|
+
* await new Promise(resolve => setTimeout(resolve, duration));
|
|
1589
|
+
* console.log(`${name} releasing mutex`);
|
|
1590
|
+
* release();
|
|
1591
|
+
* }
|
|
1592
|
+
*
|
|
1593
|
+
* task('Task 1', 1000);
|
|
1594
|
+
* task('Task 2', 2000);
|
|
1595
|
+
* task('Task 3', 1500);
|
|
1596
|
+
* task('Task 4', 500);
|
|
1597
|
+
*
|
|
1598
|
+
* Outputs
|
|
1599
|
+
* [LOG]: "Task 1 waiting to acquire mutex"
|
|
1600
|
+
* [LOG]: "Task 2 waiting to acquire mutex"
|
|
1601
|
+
* [LOG]: "Task 3 waiting to acquire mutex"
|
|
1602
|
+
* [LOG]: "Task 4 waiting to acquire mutex"
|
|
1603
|
+
* [LOG]: "Task 1 acquired mutex"
|
|
1604
|
+
* [LOG]: "Task 1 releasing mutex"
|
|
1605
|
+
* [LOG]: "Task 2 acquired mutex"
|
|
1606
|
+
* [LOG]: "Task 2 releasing mutex"
|
|
1607
|
+
* [LOG]: "Task 3 acquired mutex"
|
|
1608
|
+
* [LOG]: "Task 3 releasing mutex"
|
|
1609
|
+
* [LOG]: "Task 4 acquired mutex"
|
|
1610
|
+
* [LOG]: "Task 4 releasing mutex"
|
|
1611
|
+
*/
|
|
1612
|
+
class Mutex {
|
|
1613
|
+
constructor() {
|
|
1614
|
+
this.mutex = Promise.resolve();
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Acquires the mutex, returning a promise that resolves when the mutex is acquired.
|
|
1618
|
+
* The returned promise provides a release function that should be called to release the mutex.
|
|
1619
|
+
* @returns A promise that resolves with a release function.
|
|
1620
|
+
*/
|
|
1621
|
+
async acquire() {
|
|
1622
|
+
let release;
|
|
1623
|
+
const previous = this.mutex;
|
|
1624
|
+
this.mutex = new Promise((resolve) => (release = resolve));
|
|
1625
|
+
await previous;
|
|
1626
|
+
return release;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* A Semaphore controls access to a resource that can handle a limited number of concurrent operations.
|
|
1632
|
+
*
|
|
1633
|
+
* @example
|
|
1634
|
+
* const semaphore = new Semaphore(2);
|
|
1635
|
+
*
|
|
1636
|
+
* async function task(name: string, duration: number) {
|
|
1637
|
+
* console.log(`${name} waiting to acquire semaphore`);
|
|
1638
|
+
* await semaphore.acquire();
|
|
1639
|
+
* console.log(`${name} acquired semaphore`);
|
|
1640
|
+
* await new Promise(resolve => setTimeout(resolve, duration));
|
|
1641
|
+
* console.log(`${name} releasing semaphore`);
|
|
1642
|
+
* semaphore.release();
|
|
1643
|
+
* }
|
|
1644
|
+
*
|
|
1645
|
+
* task('Task 1', 1000);
|
|
1646
|
+
* task('Task 2', 2000);
|
|
1647
|
+
* task('Task 3', 1500);
|
|
1648
|
+
* task('Task 4', 500);
|
|
1649
|
+
*
|
|
1650
|
+
* Outputs
|
|
1651
|
+
* [LOG]: "Task 1 waiting to acquire semaphore"
|
|
1652
|
+
* [LOG]: "Task 2 waiting to acquire semaphore"
|
|
1653
|
+
* [LOG]: "Task 3 waiting to acquire semaphore"
|
|
1654
|
+
* [LOG]: "Task 4 waiting to acquire semaphore"
|
|
1655
|
+
* [LOG]: "Task 1 acquired semaphore"
|
|
1656
|
+
* [LOG]: "Task 2 acquired semaphore"
|
|
1657
|
+
* [LOG]: "Task 1 releasing semaphore"
|
|
1658
|
+
* [LOG]: "Task 3 acquired semaphore"
|
|
1659
|
+
* [LOG]: "Task 2 releasing semaphore"
|
|
1660
|
+
* [LOG]: "Task 4 acquired semaphore"
|
|
1661
|
+
* [LOG]: "Task 4 releasing semaphore"
|
|
1662
|
+
* [LOG]: "Task 3 releasing semaphore"
|
|
1663
|
+
*/
|
|
1664
|
+
class Semaphore {
|
|
1665
|
+
constructor(maxConcurrency) {
|
|
1666
|
+
this.maxConcurrency = maxConcurrency;
|
|
1667
|
+
this.tasks = [];
|
|
1668
|
+
this.currentCount = 0;
|
|
1669
|
+
}
|
|
1670
|
+
acquire() {
|
|
1671
|
+
return new Promise((resolve) => {
|
|
1672
|
+
if (this.currentCount < this.maxConcurrency) {
|
|
1673
|
+
this.currentCount++;
|
|
1674
|
+
resolve();
|
|
1675
|
+
}
|
|
1676
|
+
else {
|
|
1677
|
+
this.tasks.push(resolve);
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
release() {
|
|
1682
|
+
if (this.tasks.length > 0) {
|
|
1683
|
+
const nextTask = this.tasks.shift();
|
|
1684
|
+
if (nextTask) {
|
|
1685
|
+
nextTask();
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
else if (this.currentCount > 0) {
|
|
1689
|
+
this.currentCount--;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
class TaskRunner {
|
|
1695
|
+
constructor() {
|
|
1696
|
+
this.mutex = new Mutex();
|
|
1697
|
+
}
|
|
1698
|
+
runTask(name, task) {
|
|
1699
|
+
// Log that the task is waiting to acquire the mutex
|
|
1700
|
+
Logger.log(`${name} waiting to acquire mutex`);
|
|
1701
|
+
return from(this.mutex.acquire()).pipe(switchMap((release) => {
|
|
1702
|
+
// Log that the mutex has been acquired
|
|
1703
|
+
Logger.log(`${name} acquired mutex`);
|
|
1704
|
+
return task().pipe(finalize(() => {
|
|
1705
|
+
Logger.log(`${name} releasing mutex`);
|
|
1706
|
+
release();
|
|
1707
|
+
}));
|
|
1708
|
+
}));
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
/**
|
|
1713
|
+
* Emits the most recent value emitted by the source Observable after a
|
|
1714
|
+
* specified time span has passed without another source emission.
|
|
1715
|
+
*
|
|
1716
|
+
* This is a combination of `debounceTime` and `auditTime` operators.
|
|
1717
|
+
*
|
|
1718
|
+
* The leading value is emitted immediately, and trailing values are debounced.
|
|
1719
|
+
*
|
|
1720
|
+
* @param dueTime The time to wait before emitting the last value.
|
|
1721
|
+
* @param scheduler The scheduler to use for the timeout.
|
|
1722
|
+
* @returns A function that returns an Observable that mirrors the source Observable, but applies the specified debouncing.
|
|
1723
|
+
*/
|
|
1724
|
+
function leadingTrailingDebounceTime(dueTime, scheduler = asyncScheduler) {
|
|
1725
|
+
return (source) => new Observable((subscriber) => {
|
|
1726
|
+
let hasValue = false;
|
|
1727
|
+
let lastValue = null;
|
|
1728
|
+
let timerSubscription = null;
|
|
1729
|
+
let leading = true; // Controls whether we emit immediately.
|
|
1730
|
+
const clearTimer = () => {
|
|
1731
|
+
if (timerSubscription) {
|
|
1732
|
+
timerSubscription.unsubscribe();
|
|
1733
|
+
timerSubscription = null;
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
const resetValues = () => {
|
|
1737
|
+
hasValue = false;
|
|
1738
|
+
lastValue = null;
|
|
1739
|
+
leading = true; // Reset so the next new value is immediate.
|
|
1740
|
+
};
|
|
1741
|
+
const emitLastValue = () => {
|
|
1742
|
+
if (hasValue && lastValue !== null) {
|
|
1743
|
+
subscriber.next(lastValue);
|
|
1744
|
+
resetValues();
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
const sourceSubscription = source.subscribe({
|
|
1748
|
+
next: (value) => {
|
|
1749
|
+
// If we are "leading", emit immediately and switch leading off
|
|
1750
|
+
if (leading) {
|
|
1751
|
+
subscriber.next(value);
|
|
1752
|
+
leading = false;
|
|
1753
|
+
timerSubscription = timer(dueTime, scheduler).subscribe(() => {
|
|
1754
|
+
resetValues();
|
|
1755
|
+
clearTimer();
|
|
1756
|
+
});
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
// Otherwise, save the value and start (or restart) timer
|
|
1760
|
+
hasValue = true;
|
|
1761
|
+
lastValue = value;
|
|
1762
|
+
clearTimer();
|
|
1763
|
+
timerSubscription = timer(dueTime, scheduler).subscribe(() => {
|
|
1764
|
+
emitLastValue();
|
|
1765
|
+
});
|
|
1766
|
+
},
|
|
1767
|
+
error: (err) => {
|
|
1768
|
+
clearTimer();
|
|
1769
|
+
subscriber.error(err);
|
|
1770
|
+
},
|
|
1771
|
+
complete: () => {
|
|
1772
|
+
// If there is a pending emission, emit it before completing
|
|
1773
|
+
clearTimer();
|
|
1774
|
+
emitLastValue();
|
|
1775
|
+
subscriber.complete();
|
|
1776
|
+
},
|
|
1777
|
+
});
|
|
1778
|
+
// Cleanup logic
|
|
1779
|
+
return () => {
|
|
1780
|
+
clearTimer();
|
|
1781
|
+
sourceSubscription.unsubscribe();
|
|
1782
|
+
};
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* Creates an observable that emits values transitioning smoothly from the start value to the end value over a specified duration.
|
|
1788
|
+
* The transition is performed using linear interpolation (lerp) and clamped to the range [0, 1].
|
|
1789
|
+
*
|
|
1790
|
+
* @param {number} start - The starting value of the transition.
|
|
1791
|
+
* @param {number} end - The ending value of the transition.
|
|
1792
|
+
* @param {number} duration - The duration of the transition in milliseconds.
|
|
1793
|
+
* @returns {Observable<number>} An observable that emits the interpolated values from start to end over the specified duration.
|
|
1794
|
+
*/
|
|
1795
|
+
const smoothTransition = (start, end, duration) => {
|
|
1796
|
+
const startTime = animationFrameScheduler.now();
|
|
1797
|
+
return interval(0, animationFrameScheduler).pipe(map(() => {
|
|
1798
|
+
const elapsed = (animationFrameScheduler.now() - startTime) / duration;
|
|
1799
|
+
return clampf(0, 1, elapsed);
|
|
1800
|
+
}), distinctUntilChanged(), map((fraction) => lerp(start, end, fraction)));
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
const tapLog = (text, ...args) => (source) => source.pipe(tap((data) => {
|
|
1804
|
+
Logger.colored(...COLOR_CODES.TAP_LOG, text, data, ...args);
|
|
1805
|
+
}));
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* Generated bundle index. Do not edit.
|
|
1809
|
+
*/
|
|
1810
|
+
|
|
1811
|
+
export { BatchLoader, CMYKtoRGB, COLOR_CODES, CSV2Array, CSV2Records, DownloadImage, Falsy, HEXtoRGB, HSVtoRGB, ImageOutput, InvertedKeyMap, IsEmpty, KeyboardNumericCode, KeyboardStringCode, Logger, Mutex, NotEmpty, ObjectToCSV, RGBtoCMYK, RGBtoHEX, RGBtoHSV, SaveImage, Semaphore, TaskRunner, Truthy, baseSortedIndex, calculateMedian, circularIndex, clampf, cleanupFileName, degreesToRadians, expandOverRectangle, fitIntoRectangle, fpIsAGreaterThanB, fpIsALessThanB, fpIsASameAsB, getCanvasCached, getSnapshot, hsv, inverseLerp, isAllValuesTruthy, isDefined, isEmpty, isJSON, isLocalhost, isNotDefined, leadingTrailingDebounceTime, lerp, loadImage, makePath, max, min, normalizePath, overlay, pad, radiansToDegrees, rgb, serialize, sleep, smoothTransition, sub, subtract, sum, tapLog, textForSearch, timeToString, timeUTCToString, toRGB, toRGBA, trimLastSlashFromUrl, where, whereNot };
|
|
1812
|
+
//# sourceMappingURL=3dsource-utils.mjs.map
|