@3dsource/utils 1.0.9 → 1.0.10
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/eslint.config.js +37 -0
- package/ng-package.json +7 -0
- package/package.json +4 -14
- package/{lib/color/CMYKtoRGB.d.ts → src/lib/color/CMYKtoRGB.ts} +11 -1
- package/src/lib/color/HEXtoRGB.ts +9 -0
- package/src/lib/color/HSVtoRGB.ts +82 -0
- package/{lib/color/RGBtoCMYK.d.ts → src/lib/color/RGBtoCMYK.ts} +31 -1
- package/src/lib/color/RGBtoHEX.ts +17 -0
- package/src/lib/color/RGBtoHSV.ts +53 -0
- package/src/lib/color/hsv.ts +14 -0
- package/src/lib/color/max.ts +18 -0
- package/src/lib/color/min.ts +18 -0
- package/src/lib/color/overlay.ts +25 -0
- package/{lib/color/rgb.d.ts → src/lib/color/rgb.ts} +3 -1
- package/src/lib/color/sub.ts +18 -0
- package/src/lib/color/subtract.ts +27 -0
- package/src/lib/color/sum.ts +19 -0
- package/{lib/color/toRGB.d.ts → src/lib/color/toRGB.ts} +7 -1
- package/src/lib/color/toRGBA.ts +8 -0
- package/src/lib/constants/color-codes.constant.ts +9 -0
- package/src/lib/csv/CSV2Array.ts +66 -0
- package/src/lib/csv/CSV2Records.ts +56 -0
- package/src/lib/csv/ObjectToCSV.ts +21 -0
- package/src/lib/dev/dev3d.ts +1 -0
- package/src/lib/dev/logger.ts +94 -0
- package/src/lib/dev/timeToString.ts +16 -0
- package/src/lib/filenaming/cleanupFileName.ts +18 -0
- package/src/lib/filenaming/makePath.ts +5 -0
- package/src/lib/filenaming/normalizePath.ts +9 -0
- package/src/lib/geom/expandOverRectangle.ts +17 -0
- package/src/lib/geom/fitIntoRectangle.ts +43 -0
- package/{lib/geom/interfaces/area.interface.d.ts → src/lib/geom/interfaces/area.interface.ts} +2 -1
- package/{lib/geom/interfaces/rect.interface.d.ts → src/lib/geom/interfaces/rect.interface.ts} +2 -2
- package/src/lib/geom/interfaces/size.interface.ts +4 -0
- package/src/lib/geom/interfaces//321/201oords.interface.ts +4 -0
- package/src/lib/helpers/BatchLoader.ts +243 -0
- package/src/lib/helpers/KeyboardNumericCode.ts +118 -0
- package/src/lib/helpers/debounce.ts +10 -0
- package/src/lib/helpers/generate-uuid.ts +5 -0
- package/{lib/helpers/index.d.ts → src/lib/helpers/index.ts} +1 -0
- package/src/lib/helpers/save-to-local-drive.ts +31 -0
- package/src/lib/helpers/serialize.ts +11 -0
- package/src/lib/helpers/short-hash.ts +20 -0
- package/src/lib/helpers/signal.ts +46 -0
- package/src/lib/helpers/sleep.ts +3 -0
- package/src/lib/helpers/trimLastSlashFromUrl.ts +9 -0
- package/src/lib/image/SaveImage.ts +65 -0
- package/src/lib/image/getCanvasCached.ts +16 -0
- package/src/lib/image/getSnapshot.ts +99 -0
- package/src/lib/image/loadImage.ts +13 -0
- package/src/lib/interfaces/image-output.ts +8 -0
- package/{lib/interfaces/load-args-tmp.interface.d.ts → src/lib/interfaces/load-args-tmp.interface.ts} +2 -1
- package/src/lib/interfaces/load-args.interface.ts +15 -0
- package/{lib/math/baseSortedIndex.d.ts → src/lib/math/baseSortedIndex.ts} +19 -1
- package/{lib/math/calculateMedian.d.ts → src/lib/math/calculateMedian.ts} +17 -1
- package/{lib/math/circularIndex.d.ts → src/lib/math/circularIndex.ts} +5 -1
- package/src/lib/math/clampf.ts +14 -0
- package/src/lib/math/degrees.ts +7 -0
- package/{lib/math/floatCompare.d.ts → src/lib/math/floatCompare.ts} +30 -3
- package/{lib/math/inverseLerp.d.ts → src/lib/math/inverseLerp.ts} +22 -1
- package/src/lib/math/lerp.ts +12 -0
- package/{lib/mutex/Mutex.d.ts → src/lib/mutex/Mutex.ts} +15 -8
- package/{lib/mutex/Semaphore.d.ts → src/lib/mutex/Semaphore.ts} +27 -7
- package/src/lib/mutex/TaskRunner.ts +26 -0
- package/src/lib/predicates/operators.ts +75 -0
- package/{lib/predicates/textForSearch.d.ts → src/lib/predicates/textForSearch.ts} +17 -1
- package/{lib/predicates/where.d.ts → src/lib/predicates/where.ts} +32 -2
- package/src/lib/rxjs/leadingTrailingDebounceTime.ts +86 -0
- package/{lib/rxjs/smoothTransition.d.ts → src/lib/rxjs/smoothTransition.ts} +19 -1
- package/src/lib/rxjs/tapLog.ts +13 -0
- package/src/lib/strings/pad.ts +18 -0
- package/tsconfig.lib.json +13 -0
- package/tsconfig.lib.prod.json +11 -0
- package/fesm2022/3dsource-utils.mjs +0 -1886
- package/fesm2022/3dsource-utils.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/lib/color/HEXtoRGB.d.ts +0 -5
- package/lib/color/HSVtoRGB.d.ts +0 -21
- package/lib/color/RGBtoHEX.d.ts +0 -1
- package/lib/color/RGBtoHSV.d.ts +0 -19
- package/lib/color/hsv.d.ts +0 -1
- package/lib/color/max.d.ts +0 -1
- package/lib/color/min.d.ts +0 -1
- package/lib/color/overlay.d.ts +0 -1
- package/lib/color/sub.d.ts +0 -1
- package/lib/color/subtract.d.ts +0 -1
- package/lib/color/sum.d.ts +0 -1
- package/lib/color/toRGBA.d.ts +0 -1
- package/lib/constants/color-codes.constant.d.ts +0 -9
- package/lib/csv/CSV2Array.d.ts +0 -1
- package/lib/csv/CSV2Records.d.ts +0 -1
- package/lib/csv/ObjectToCSV.d.ts +0 -1
- package/lib/dev/dev3d.d.ts +0 -1
- package/lib/dev/logger.d.ts +0 -11
- package/lib/dev/timeToString.d.ts +0 -5
- package/lib/filenaming/cleanupFileName.d.ts +0 -1
- package/lib/filenaming/makePath.d.ts +0 -1
- package/lib/filenaming/normalizePath.d.ts +0 -1
- package/lib/geom/expandOverRectangle.d.ts +0 -2
- package/lib/geom/fitIntoRectangle.d.ts +0 -2
- package/lib/geom/interfaces/size.interface.d.ts +0 -4
- package/lib/geom/interfaces//321/201oords.interface.d.ts +0 -4
- package/lib/helpers/BatchLoader.d.ts +0 -47
- package/lib/helpers/KeyboardNumericCode.d.ts +0 -103
- package/lib/helpers/debounce.d.ts +0 -1
- package/lib/helpers/generate-uuid.d.ts +0 -1
- package/lib/helpers/save-to-local-drive.d.ts +0 -1
- package/lib/helpers/serialize.d.ts +0 -1
- package/lib/helpers/signal.d.ts +0 -23
- package/lib/helpers/sleep.d.ts +0 -1
- package/lib/helpers/trimLastSlashFromUrl.d.ts +0 -1
- package/lib/image/SaveImage.d.ts +0 -18
- package/lib/image/getCanvasCached.d.ts +0 -4
- package/lib/image/getSnapshot.d.ts +0 -2
- package/lib/image/loadImage.d.ts +0 -1
- package/lib/interfaces/image-output.d.ts +0 -7
- package/lib/interfaces/load-args.interface.d.ts +0 -15
- package/lib/math/clampf.d.ts +0 -8
- package/lib/math/degrees.d.ts +0 -2
- package/lib/math/lerp.d.ts +0 -7
- package/lib/mutex/TaskRunner.d.ts +0 -5
- package/lib/predicates/operators.d.ts +0 -32
- package/lib/rxjs/leadingTrailingDebounceTime.d.ts +0 -15
- package/lib/rxjs/tapLog.d.ts +0 -2
- package/lib/strings/pad.d.ts +0 -8
- /package/{lib/color/index.d.ts → src/lib/color/index.ts} +0 -0
- /package/{lib/constants/index.d.ts → src/lib/constants/index.ts} +0 -0
- /package/{lib/csv/index.d.ts → src/lib/csv/index.ts} +0 -0
- /package/{lib/dev/index.d.ts → src/lib/dev/index.ts} +0 -0
- /package/{lib/filenaming/index.d.ts → src/lib/filenaming/index.ts} +0 -0
- /package/{lib/geom/index.d.ts → src/lib/geom/index.ts} +0 -0
- /package/{lib/geom/interfaces/index.d.ts → src/lib/geom/interfaces/index.ts} +0 -0
- /package/{lib/image/index.d.ts → src/lib/image/index.ts} +0 -0
- /package/{lib/interfaces/index.d.ts → src/lib/interfaces/index.ts} +0 -0
- /package/{lib/math/index.d.ts → src/lib/math/index.ts} +0 -0
- /package/{lib/mutex/index.d.ts → src/lib/mutex/index.ts} +0 -0
- /package/{lib/predicates/index.d.ts → src/lib/predicates/index.ts} +0 -0
- /package/{lib/rxjs/index.d.ts → src/lib/rxjs/index.ts} +0 -0
- /package/{lib/strings/index.d.ts → src/lib/strings/index.ts} +0 -0
- /package/{public-api.d.ts → src/public-api.ts} +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { filter, map } from 'rxjs/operators';
|
|
4
|
+
|
|
5
|
+
interface BroadcastEvent {
|
|
6
|
+
key: any;
|
|
7
|
+
data?: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SingleTone Private class.
|
|
12
|
+
* Event-Driving pattern for sending signals from component to component.
|
|
13
|
+
*/
|
|
14
|
+
class SignalClass {
|
|
15
|
+
private static eventBus: Subject<BroadcastEvent>;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
if (SignalClass.eventBus) {
|
|
19
|
+
throw new Error(`Check that 'SignalClass' is not instantiated again!`);
|
|
20
|
+
}
|
|
21
|
+
SignalClass.eventBus = new Subject<BroadcastEvent>();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* It is not recommended to use this class directly.
|
|
26
|
+
* use the sendSignal functions instead.
|
|
27
|
+
* those wrappers are more convenient and provide a better type safety.
|
|
28
|
+
*/
|
|
29
|
+
broadcast<K, D>(key: K, data?: D) {
|
|
30
|
+
SignalClass.eventBus.next({ key, data });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* It is not recommended to use this class directly.
|
|
35
|
+
* use the fromSignal functions instead.
|
|
36
|
+
* those wrappers are more convenient and provide a better type safety.
|
|
37
|
+
*/
|
|
38
|
+
on<K, D = any>(key: K): Observable<D> {
|
|
39
|
+
return SignalClass.eventBus.asObservable().pipe(
|
|
40
|
+
filter((event) => event.key === key),
|
|
41
|
+
map((event) => event.data),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Signal = new SignalClass();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getSnapshot } from './getSnapshot';
|
|
2
|
+
import type { ImageOutputValues } from '../interfaces';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* USE DownloadImage instead!
|
|
6
|
+
* @deprecated use DownloadImage instead!
|
|
7
|
+
* @param imageSource
|
|
8
|
+
* @param name
|
|
9
|
+
* @param width
|
|
10
|
+
* @param format
|
|
11
|
+
*/
|
|
12
|
+
function SaveImage(
|
|
13
|
+
imageSource:
|
|
14
|
+
| HTMLImageElement
|
|
15
|
+
| HTMLCanvasElement
|
|
16
|
+
| HTMLVideoElement
|
|
17
|
+
| ImageBitmap
|
|
18
|
+
| string,
|
|
19
|
+
name: string,
|
|
20
|
+
width?: number,
|
|
21
|
+
format?: string | ImageOutputValues,
|
|
22
|
+
): any {
|
|
23
|
+
let out;
|
|
24
|
+
const image: any = imageSource;
|
|
25
|
+
|
|
26
|
+
if (typeof image !== 'string') {
|
|
27
|
+
out = getSnapshot(image, width, format as ImageOutputValues);
|
|
28
|
+
} else {
|
|
29
|
+
out = image;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if ((navigator as any).msSaveBlob) {
|
|
33
|
+
// IE10+
|
|
34
|
+
try {
|
|
35
|
+
const blob = image.msToBlob();
|
|
36
|
+
return (navigator as any).msSaveBlob(blob, name);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn(e);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const uri = out;
|
|
42
|
+
const link = document.createElement('a');
|
|
43
|
+
link.download = name;
|
|
44
|
+
link.href = uri as string;
|
|
45
|
+
|
|
46
|
+
document.body.appendChild(link);
|
|
47
|
+
if (link.click) {
|
|
48
|
+
link.click();
|
|
49
|
+
} else {
|
|
50
|
+
const event = document.createEvent('MouseEvents');
|
|
51
|
+
(event as any).initMouseEvent('click', true, true, window);
|
|
52
|
+
link.dispatchEvent(event);
|
|
53
|
+
}
|
|
54
|
+
document.body.removeChild(link);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param imageSource
|
|
60
|
+
* @param string name
|
|
61
|
+
* @param number width
|
|
62
|
+
* @param string format
|
|
63
|
+
*/
|
|
64
|
+
const DownloadImage = SaveImage;
|
|
65
|
+
export { SaveImage, DownloadImage };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const canvasCacheObj: any = {};
|
|
2
|
+
|
|
3
|
+
export function getCanvasCached(id: string): {
|
|
4
|
+
canvas: HTMLCanvasElement;
|
|
5
|
+
ctx: CanvasRenderingContext2D;
|
|
6
|
+
} {
|
|
7
|
+
if (!canvasCacheObj[id]) {
|
|
8
|
+
const canvas = document.createElement('canvas');
|
|
9
|
+
canvasCacheObj[id] = {
|
|
10
|
+
canvas,
|
|
11
|
+
ctx: canvas.getContext('2d'),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return canvasCacheObj[id];
|
|
16
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { fitIntoRectangle } from '../geom';
|
|
2
|
+
import type { ImageOutputValues } from '../interfaces';
|
|
3
|
+
|
|
4
|
+
function getSnapshot2(
|
|
5
|
+
imageElement:
|
|
6
|
+
| HTMLImageElement
|
|
7
|
+
| HTMLCanvasElement
|
|
8
|
+
| HTMLVideoElement
|
|
9
|
+
| ImageBitmap,
|
|
10
|
+
maxWidth = 1024,
|
|
11
|
+
output: ImageOutputValues = 'image/png',
|
|
12
|
+
quality = 0.9,
|
|
13
|
+
): HTMLCanvasElement | string {
|
|
14
|
+
const drawableImage = imageElement as any;
|
|
15
|
+
const canvasHelper: HTMLCanvasElement = document.createElement('canvas');
|
|
16
|
+
const drawCTX: CanvasRenderingContext2D = canvasHelper.getContext(
|
|
17
|
+
'2d',
|
|
18
|
+
) as CanvasRenderingContext2D;
|
|
19
|
+
|
|
20
|
+
const prop = fitIntoRectangle(
|
|
21
|
+
{
|
|
22
|
+
x: 0,
|
|
23
|
+
y: 0,
|
|
24
|
+
w: drawableImage.naturalWidth || drawableImage.width,
|
|
25
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
x: 0,
|
|
29
|
+
y: 0,
|
|
30
|
+
w: maxWidth,
|
|
31
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const width = prop.w;
|
|
36
|
+
const height = prop.h;
|
|
37
|
+
canvasHelper.width = width;
|
|
38
|
+
canvasHelper.height = height;
|
|
39
|
+
|
|
40
|
+
drawCTX.save();
|
|
41
|
+
drawCTX.clearRect(0, 0, width, height);
|
|
42
|
+
drawCTX.drawImage(drawableImage, 0, 0, width, height);
|
|
43
|
+
drawCTX.restore();
|
|
44
|
+
|
|
45
|
+
if (output === 'canvas') {
|
|
46
|
+
return canvasHelper;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return canvasHelper.toDataURL(output, quality);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getSnapshot(
|
|
53
|
+
imageElement:
|
|
54
|
+
| HTMLImageElement
|
|
55
|
+
| HTMLCanvasElement
|
|
56
|
+
| HTMLVideoElement
|
|
57
|
+
| ImageBitmap,
|
|
58
|
+
maxWidth = 1024,
|
|
59
|
+
output: ImageOutputValues = 'image/png',
|
|
60
|
+
quality = 0.9,
|
|
61
|
+
): HTMLCanvasElement | string {
|
|
62
|
+
const drawableImage = imageElement as any;
|
|
63
|
+
const canvasHelper: HTMLCanvasElement = document.createElement('canvas');
|
|
64
|
+
const drawCTX: CanvasRenderingContext2D = canvasHelper.getContext(
|
|
65
|
+
'2d',
|
|
66
|
+
) as CanvasRenderingContext2D;
|
|
67
|
+
|
|
68
|
+
const prop = fitIntoRectangle(
|
|
69
|
+
{
|
|
70
|
+
x: 0,
|
|
71
|
+
y: 0,
|
|
72
|
+
w: drawableImage.naturalWidth || drawableImage.width,
|
|
73
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
x: 0,
|
|
77
|
+
y: 0,
|
|
78
|
+
w: (drawableImage.naturalWidth || drawableImage.width) / 2,
|
|
79
|
+
h: drawableImage.naturalHeight || drawableImage.height,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const width = prop.w;
|
|
84
|
+
const height = prop.h;
|
|
85
|
+
canvasHelper.width = width;
|
|
86
|
+
canvasHelper.height = height;
|
|
87
|
+
|
|
88
|
+
drawCTX.save();
|
|
89
|
+
drawCTX.clearRect(0, 0, width, height);
|
|
90
|
+
drawCTX.drawImage(drawableImage, 0, 0, width, height);
|
|
91
|
+
|
|
92
|
+
drawCTX.restore();
|
|
93
|
+
|
|
94
|
+
if (width > maxWidth * 2) {
|
|
95
|
+
return getSnapshot(canvasHelper, maxWidth, output, quality);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return getSnapshot2(drawableImage, maxWidth, output, quality);
|
|
99
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function loadImage(url: string): Promise<HTMLImageElement> {
|
|
2
|
+
return new Promise((resolve: any, reject) => {
|
|
3
|
+
const img = new Image();
|
|
4
|
+
img.crossOrigin = 'anonymous';
|
|
5
|
+
img.onload = () => {
|
|
6
|
+
setTimeout(() => resolve(img), 1);
|
|
7
|
+
};
|
|
8
|
+
img.onerror = () => {
|
|
9
|
+
setTimeout(() => reject(null), 1);
|
|
10
|
+
};
|
|
11
|
+
img.src = url;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface LoadArgs {
|
|
2
|
+
order: number;
|
|
3
|
+
extra: any;
|
|
4
|
+
item?: HTMLImageElement;
|
|
5
|
+
useXhr?: boolean;
|
|
6
|
+
path: string;
|
|
7
|
+
onLoadEnd: (data: LoadArgs) => void;
|
|
8
|
+
onLoadStart: (data: ProgressEvent) => void;
|
|
9
|
+
onProgress: (data: ProgressEvent) => void;
|
|
10
|
+
id?: string;
|
|
11
|
+
img?: HTMLImageElement;
|
|
12
|
+
error?: boolean;
|
|
13
|
+
fallBack?: string;
|
|
14
|
+
resolutionId: number | string | null;
|
|
15
|
+
}
|
|
@@ -22,4 +22,22 @@
|
|
|
22
22
|
* // returns 1
|
|
23
23
|
* baseSortedIndex([1, 2, 3], 2);
|
|
24
24
|
*/
|
|
25
|
-
export
|
|
25
|
+
export function baseSortedIndex(array: number[], value: number): number {
|
|
26
|
+
const MAX_ARRAY_LENGTH = 4294967295;
|
|
27
|
+
const HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
|
|
28
|
+
let low = 0;
|
|
29
|
+
let high = array?.length || low;
|
|
30
|
+
|
|
31
|
+
if (high <= HALF_MAX_ARRAY_LENGTH) {
|
|
32
|
+
while (low < high) {
|
|
33
|
+
const mid = (low + high) >>> 1;
|
|
34
|
+
const computed = array[mid];
|
|
35
|
+
if (computed !== null && computed < value) {
|
|
36
|
+
low = mid + 1;
|
|
37
|
+
} else {
|
|
38
|
+
high = mid;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return high;
|
|
43
|
+
}
|
|
@@ -14,4 +14,20 @@
|
|
|
14
14
|
* console.log(median); // Output: 2.5
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export
|
|
17
|
+
export function calculateMedian(numbers: number[]): number {
|
|
18
|
+
if (numbers.length === 0) return 0;
|
|
19
|
+
|
|
20
|
+
// Sort the array in ascending order
|
|
21
|
+
const sorted = [...numbers].sort((a, b) => a - b);
|
|
22
|
+
|
|
23
|
+
const length = sorted.length;
|
|
24
|
+
const middle = Math.floor(length / 2);
|
|
25
|
+
|
|
26
|
+
// If the length is even, return the average of the two middle numbers
|
|
27
|
+
if (length % 2 === 0) {
|
|
28
|
+
return (sorted[middle - 1] + sorted[middle]) / 2;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If the length is odd, return the middle number
|
|
32
|
+
return sorted[middle];
|
|
33
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { clampf } from './clampf';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Computes a circular index within a given range.
|
|
3
5
|
*
|
|
@@ -32,4 +34,6 @@
|
|
|
32
34
|
* const index5 = circularIndex(1, 1);
|
|
33
35
|
* console.log(index5); // Output: 0
|
|
34
36
|
*/
|
|
35
|
-
export
|
|
37
|
+
export function circularIndex(index: number, totalItems: number): number {
|
|
38
|
+
return clampf(0, totalItems - 1, (totalItems * 2 + index) % totalItems);
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clamps value between min and max;
|
|
3
|
+
* @param min minValue
|
|
4
|
+
* @param max maxValue
|
|
5
|
+
* @param value inputValue
|
|
6
|
+
* @returns number
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function clampf(min: number, max: number, value: number): number {
|
|
10
|
+
if (min > max) {
|
|
11
|
+
return min;
|
|
12
|
+
}
|
|
13
|
+
return Math.min(max, Math.max(min, value));
|
|
14
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small epsilon value used for floating-point comparisons.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The value of `EPSILON` is `0.01`. This can be overridden in the comparison functions if needed.
|
|
6
|
+
*/
|
|
7
|
+
const EPSILON = 0.01;
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Checks if floating-point number `A` is less than `B` considering a small margin of error.
|
|
3
11
|
*
|
|
@@ -11,7 +19,15 @@
|
|
|
11
19
|
* const result = fpIsALessThanB(0.0034, 0.0066); // true
|
|
12
20
|
* ```
|
|
13
21
|
*/
|
|
14
|
-
export
|
|
22
|
+
export function fpIsALessThanB(
|
|
23
|
+
A: number,
|
|
24
|
+
B: number,
|
|
25
|
+
Epsilon?: number,
|
|
26
|
+
): boolean {
|
|
27
|
+
Epsilon = Epsilon || EPSILON;
|
|
28
|
+
return A - B < Epsilon && Math.abs(A - B) > Epsilon;
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
/**
|
|
16
32
|
* Checks if floating-point number `A` is greater than `B` considering a small margin of error.
|
|
17
33
|
*
|
|
@@ -25,7 +41,15 @@ export declare function fpIsALessThanB(A: number, B: number, Epsilon?: number):
|
|
|
25
41
|
* const result = fpIsAGreaterThanB(0.0066, 0.0034); // true
|
|
26
42
|
* ```
|
|
27
43
|
*/
|
|
28
|
-
export
|
|
44
|
+
export function fpIsAGreaterThanB(
|
|
45
|
+
A: number,
|
|
46
|
+
B: number,
|
|
47
|
+
Epsilon?: number,
|
|
48
|
+
): boolean {
|
|
49
|
+
Epsilon = Epsilon || EPSILON;
|
|
50
|
+
return A - B > Epsilon && Math.abs(A - B) > Epsilon;
|
|
51
|
+
}
|
|
52
|
+
|
|
29
53
|
/**
|
|
30
54
|
* Checks if floating-point number `A` is approximately the same as `B` considering a small margin of error.
|
|
31
55
|
*
|
|
@@ -39,4 +63,7 @@ export declare function fpIsAGreaterThanB(A: number, B: number, Epsilon?: number
|
|
|
39
63
|
* const result = fpIsASameAsB(0.005, 0.0051); // true
|
|
40
64
|
* ```
|
|
41
65
|
*/
|
|
42
|
-
export
|
|
66
|
+
export function fpIsASameAsB(A: number, B: number, Epsilon?: number): boolean {
|
|
67
|
+
Epsilon = Epsilon || EPSILON;
|
|
68
|
+
return Math.abs(A - B) < Epsilon;
|
|
69
|
+
}
|
|
@@ -14,4 +14,25 @@
|
|
|
14
14
|
* If `clampToNormal` is true, the result is clamped between 0 and 1.
|
|
15
15
|
* If the difference between `min` and `max` is 0, returns 1 to avoid division by zero.
|
|
16
16
|
*/
|
|
17
|
-
export
|
|
17
|
+
export function inverseLerp(
|
|
18
|
+
min: number,
|
|
19
|
+
max: number,
|
|
20
|
+
value: number,
|
|
21
|
+
clampToNormal = false,
|
|
22
|
+
): number {
|
|
23
|
+
if (clampToNormal) {
|
|
24
|
+
value = Math.min(max, value);
|
|
25
|
+
value = Math.max(min, value);
|
|
26
|
+
}
|
|
27
|
+
const difference = max - min;
|
|
28
|
+
|
|
29
|
+
// Avoid JS division error
|
|
30
|
+
if (difference === 0) {
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
const result = (value - min) / difference;
|
|
34
|
+
if (clampToNormal) {
|
|
35
|
+
return Math.min(1, Math.max(0, result));
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { clampf } from './clampf';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param min minimums
|
|
5
|
+
* @param max maximums
|
|
6
|
+
* @param value current float value in 0-1 range
|
|
7
|
+
* @returns clamped value
|
|
8
|
+
*/
|
|
9
|
+
export function lerp(min: number, max: number, value: number): number {
|
|
10
|
+
value = clampf(0, 1, value);
|
|
11
|
+
return value * (max - min) + min;
|
|
12
|
+
}
|
|
@@ -32,12 +32,19 @@
|
|
|
32
32
|
* [LOG]: "Task 4 acquired mutex"
|
|
33
33
|
* [LOG]: "Task 4 releasing mutex"
|
|
34
34
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
export class Mutex {
|
|
36
|
+
private mutex = Promise.resolve();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Acquires the mutex, returning a promise that resolves when the mutex is acquired.
|
|
40
|
+
* The returned promise provides a release function that should be called to release the mutex.
|
|
41
|
+
* @returns A promise that resolves with a release function.
|
|
42
|
+
*/
|
|
43
|
+
async acquire(): Promise<() => void> {
|
|
44
|
+
let release: () => void;
|
|
45
|
+
const previous = this.mutex;
|
|
46
|
+
this.mutex = new Promise<void>((resolve) => (release = resolve));
|
|
47
|
+
await previous;
|
|
48
|
+
return release!;
|
|
49
|
+
}
|
|
43
50
|
}
|
|
@@ -32,11 +32,31 @@
|
|
|
32
32
|
* [LOG]: "Task 4 releasing semaphore"
|
|
33
33
|
* [LOG]: "Task 3 releasing semaphore"
|
|
34
34
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
export class Semaphore {
|
|
36
|
+
private tasks: (() => void)[] = [];
|
|
37
|
+
private currentCount = 0;
|
|
38
|
+
|
|
39
|
+
constructor(private readonly maxConcurrency: number) {}
|
|
40
|
+
|
|
41
|
+
public acquire(): Promise<void> {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
if (this.currentCount < this.maxConcurrency) {
|
|
44
|
+
this.currentCount++;
|
|
45
|
+
resolve();
|
|
46
|
+
} else {
|
|
47
|
+
this.tasks.push(resolve);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public release(): void {
|
|
53
|
+
if (this.tasks.length > 0) {
|
|
54
|
+
const nextTask = this.tasks.shift();
|
|
55
|
+
if (nextTask) {
|
|
56
|
+
nextTask();
|
|
57
|
+
}
|
|
58
|
+
} else if (this.currentCount > 0) {
|
|
59
|
+
this.currentCount--;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
42
62
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
import { finalize, from, switchMap } from 'rxjs';
|
|
3
|
+
import { Logger } from '../dev';
|
|
4
|
+
import { Mutex } from './Mutex';
|
|
5
|
+
|
|
6
|
+
export class TaskRunner {
|
|
7
|
+
private mutex = new Mutex();
|
|
8
|
+
|
|
9
|
+
runTask(name: string, task: () => Observable<string | null>) {
|
|
10
|
+
// Log that the task is waiting to acquire the mutex
|
|
11
|
+
Logger.log(`${name} waiting to acquire mutex`);
|
|
12
|
+
|
|
13
|
+
return from(this.mutex.acquire()).pipe(
|
|
14
|
+
switchMap((release) => {
|
|
15
|
+
// Log that the mutex has been acquired
|
|
16
|
+
Logger.log(`${name} acquired mutex`);
|
|
17
|
+
return task().pipe(
|
|
18
|
+
finalize(() => {
|
|
19
|
+
Logger.log(`${name} releasing mutex`);
|
|
20
|
+
release();
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
type TruthyTypesOf<T> = T extends false | '' | 0 | null | undefined ? never : T;
|
|
2
|
+
type FalsyTypesOf<T> = T extends false | '' | 0 | null | undefined ? T : never;
|
|
3
|
+
type EmptyTypesOf<T> = T extends undefined | null | object | '' ? T : never;
|
|
4
|
+
type NonEmptyTypesOf<T> = T extends undefined | null | object | '' ? never : T;
|
|
5
|
+
/**
|
|
6
|
+
* The Boolean object represents a truth value: true or false.
|
|
7
|
+
* @param value
|
|
8
|
+
* @constructor
|
|
9
|
+
* Returns:boolean
|
|
10
|
+
*/
|
|
11
|
+
export const Truthy: <T>(value: T) => value is TruthyTypesOf<T> = <T>(
|
|
12
|
+
value?: T,
|
|
13
|
+
): value is TruthyTypesOf<T> =>
|
|
14
|
+
!!value && value !== 'false' && value !== 'undefined' && value !== 'null';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The Boolean object represents an INVERSE truth value: true or false.
|
|
18
|
+
* @param value
|
|
19
|
+
* @constructor
|
|
20
|
+
* Returns:boolean
|
|
21
|
+
*/
|
|
22
|
+
export const Falsy: <T>(value: T) => value is FalsyTypesOf<T> = <T>(
|
|
23
|
+
value?: T,
|
|
24
|
+
): value is FalsyTypesOf<T> => !Truthy(value);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Checks if a value is empty.
|
|
28
|
+
*
|
|
29
|
+
* @param {any} value - The value to check.
|
|
30
|
+
* @return {boolean} Returns true if the value is empty, otherwise false.
|
|
31
|
+
*/
|
|
32
|
+
export const IsEmpty: <T>(value: T) => value is EmptyTypesOf<T> = <T>(
|
|
33
|
+
value: T,
|
|
34
|
+
): value is EmptyTypesOf<T> => {
|
|
35
|
+
return (
|
|
36
|
+
value === undefined ||
|
|
37
|
+
value === null ||
|
|
38
|
+
(typeof value === 'object' && Object.keys(value).length === 0) ||
|
|
39
|
+
(typeof value === 'string' && value.trim().length === 0)
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const NotEmpty: <T>(value: T) => value is NonEmptyTypesOf<T> = <T>(
|
|
44
|
+
value: T,
|
|
45
|
+
): value is NonEmptyTypesOf<T> => {
|
|
46
|
+
return !IsEmpty(value);
|
|
47
|
+
};
|
|
48
|
+
export const isDefined = <T>(arg: T | null | undefined): arg is T =>
|
|
49
|
+
arg !== null && arg !== undefined;
|
|
50
|
+
|
|
51
|
+
export const isNotDefined = <T>(
|
|
52
|
+
arg: T | null | undefined,
|
|
53
|
+
): arg is null | undefined => arg === null || arg === undefined;
|
|
54
|
+
|
|
55
|
+
export const isJSON = (str: string): boolean => {
|
|
56
|
+
try {
|
|
57
|
+
JSON.parse(str);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn(e);
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const isEmpty = (value: any) =>
|
|
68
|
+
value === undefined ||
|
|
69
|
+
value === null ||
|
|
70
|
+
(typeof value === 'object' && Object.keys(value).length === 0) ||
|
|
71
|
+
(typeof value === 'string' && value.trim().length === 0);
|
|
72
|
+
|
|
73
|
+
export function isAllValuesTruthy<T extends string, K>(value: Record<T, K>) {
|
|
74
|
+
return Object.values(value).every(Truthy);
|
|
75
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cache: Record<string, unknown> = {};
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Converts a given value to a string suitable for search comparisons.
|
|
3
5
|
* The resulting string is in lowercase, with consecutive spaces replaced by underscores.
|
|
@@ -15,4 +17,18 @@
|
|
|
15
17
|
* retrieved from the cache or freshly computed and then cached.
|
|
16
18
|
*
|
|
17
19
|
*/
|
|
18
|
-
export
|
|
20
|
+
export function textForSearch(value: unknown, caseSensitive = false): string {
|
|
21
|
+
const key = `${value}_${caseSensitive}`;
|
|
22
|
+
return (cache[key] ||
|
|
23
|
+
(() => {
|
|
24
|
+
let result = String(value).trim().replace(/ +/gi, '_');
|
|
25
|
+
|
|
26
|
+
if (!caseSensitive) {
|
|
27
|
+
result = result.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cache[key] = result;
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
})()) as string;
|
|
34
|
+
}
|