senaev-utils 1.0.0
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/README.md +11 -0
- package/package.json +66 -0
- package/src/reactHooks/setDeepEqualState.ts +19 -0
- package/src/reactHooks/useLocationParameter.ts +46 -0
- package/src/reactHooks/useObjectDeepChangesCounter.ts +22 -0
- package/src/reactHooks/useObservableMap/useObservableMap.ts +21 -0
- package/src/reactHooks/useRecentChangeTimer.ts +42 -0
- package/src/reactHooks/useReload.ts +17 -0
- package/src/reactHooks/useVisualizedProcessesTree.ts +202 -0
- package/src/types/Boolean.ts +9 -0
- package/src/types/DeepPartial.ts +1 -0
- package/src/types/GlobalContext.ts +8 -0
- package/src/types/HexColorString.ts +9 -0
- package/src/types/ISOLang.ts +5 -0
- package/src/types/IsEqualUnion.ts +1 -0
- package/src/types/IterableWithIndexAccess.ts +4 -0
- package/src/types/Kilobits.ts +4 -0
- package/src/types/Network/Bits.ts +4 -0
- package/src/types/Network/Bps.ts +4 -0
- package/src/types/Network/Kbps.ts +4 -0
- package/src/types/Network/Mbps.ts +4 -0
- package/src/types/Number/AngleInDegrees.ts +3 -0
- package/src/types/Number/FiniteNumber.ts +11 -0
- package/src/types/Number/Integer.ts +14 -0
- package/src/types/Number/IntegerInString.ts +13 -0
- package/src/types/Number/Number.ts +15 -0
- package/src/types/Number/Portion.ts +4 -0
- package/src/types/Number/PositiveInteger.ts +30 -0
- package/src/types/Number/PositiveNumber.ts +4 -0
- package/src/types/Number/StringUnsignedInteger.ts +4 -0
- package/src/types/Number/UnsignedFiniteNumber.ts +13 -0
- package/src/types/Number/UnsignedInteger.ts +28 -0
- package/src/types/Object/Object.ts +20 -0
- package/src/types/Percent.ts +4 -0
- package/src/types/PixelPosition.ts +9 -0
- package/src/types/PixelSize.ts +9 -0
- package/src/types/Pixels.ts +4 -0
- package/src/types/RangeTuple.ts +1 -0
- package/src/types/Size.ts +8 -0
- package/src/types/Time/DaysCount.ts +6 -0
- package/src/types/Time/Hours.ts +4 -0
- package/src/types/Time/ISODateString/ISODateString.ts +16 -0
- package/src/types/Time/Milliseconds.ts +4 -0
- package/src/types/Time/Minutes.ts +4 -0
- package/src/types/Time/Seconds.ts +5 -0
- package/src/types/Time/Time.ts +13 -0
- package/src/types/Time/Timestamp.ts +6 -0
- package/src/types/Time/UnixTimeMs.ts +6 -0
- package/src/types/Time/UnixTimeSec.ts +11 -0
- package/src/types/Time/const.ts +24 -0
- package/src/types/any/AnyAsyncFunction.ts +2 -0
- package/src/types/any/AnyFunction.ts +2 -0
- package/src/types/assertions/ANY_VALUE.ts +2 -0
- package/src/types/assertions/isNever/isNever.ts +34 -0
- package/src/types/assertions/isTrue/isTrue.ts +13 -0
- package/src/utils/AbortableProcess.ts +160 -0
- package/src/utils/Array/createArray/createArray.ts +8 -0
- package/src/utils/Array/createIntegerSequenceArray/createIntegerSequenceArray.ts +3 -0
- package/src/utils/Array/getUniqueValues/getUniqueValues.ts +3 -0
- package/src/utils/Array/separateToArrays/separateToArrays.ts +24 -0
- package/src/utils/Array/shuffleArray/shuffleArray.ts +18 -0
- package/src/utils/CircularBuffer/CircularBuffer.ts +117 -0
- package/src/utils/DOM/blurDocument.ts +3 -0
- package/src/utils/DOM/unfocusDocument.ts +3 -0
- package/src/utils/Date/dateToUnixTimeSec/dateToUnixTimeSec.ts +6 -0
- package/src/utils/Date/isoDateStringToUnixTimeMs/isoDateStringToUnixTimeMs.ts +6 -0
- package/src/utils/Date/isoDateStringToUnixTimeSec/isoDateStringToUnixTimeSec.ts +7 -0
- package/src/utils/Date/unixTimeSecToIsoDateString/unixTimeSecToIsoDateString.ts +6 -0
- package/src/utils/Deque/Deque.ts +530 -0
- package/src/utils/DeviceDetection/isTouchScreenDevice/isTouchScreenDevice.ts +16 -0
- package/src/utils/Error/rethrowError/rethrowError.ts +8 -0
- package/src/utils/Error/wrapError.ts +32 -0
- package/src/utils/Function/callFirstThenOther/callFirstThenOther.ts +24 -0
- package/src/utils/Function/callFunctions/callFunctions.ts +11 -0
- package/src/utils/Function/callTimes/callTimes.ts +5 -0
- package/src/utils/Function/isFunction.ts +3 -0
- package/src/utils/Function/noop/index.ts +4 -0
- package/src/utils/Function/once/once.ts +32 -0
- package/src/utils/Function/subscribeUpdatesWithTimeout/index.ts +44 -0
- package/src/utils/Function/throttleFunction/throttleFunction.ts +18 -0
- package/src/utils/Function/throttleWithPromiseFunction/throttleWithPromiseFunction.ts +19 -0
- package/src/utils/Function/tryCatch/tryCatch.ts +13 -0
- package/src/utils/Latch/Latch.ts +63 -0
- package/src/utils/Map/ObservableMap/ObservableMap.ts +55 -0
- package/src/utils/Map/findMapsDifferences/findMapsDifferences.ts +35 -0
- package/src/utils/Map/mapGetOrSet/mapGetOrSet.ts +7 -0
- package/src/utils/Map/mapMapToArray/mapMapToArray.ts +6 -0
- package/src/utils/Map/mapMapValues/mapMapValues.ts +9 -0
- package/src/utils/Math/createSimpleMovingAverageSeries/createSimpleMovingAverageSeries.ts +22 -0
- package/src/utils/Math/mean/mean.ts +14 -0
- package/src/utils/Math/simpleMovingAverageUsingAccFabric/simpleMovingAverageUsingAccFabric.ts +29 -0
- package/src/utils/Math/sum/sum.ts +45 -0
- package/src/utils/MinHeap/MinHeap.ts +88 -0
- package/src/utils/MinHeapUniqueNumber/MinHeapUniqueNumber.ts +105 -0
- package/src/utils/MinMaxHeap/MinMaxHeap.ts +91 -0
- package/src/utils/Number/collectIntegerSequences/collectIntegerSequences.ts +36 -0
- package/src/utils/Number/numberToFixedPrecision/numberToFixedPrecision.ts +13 -0
- package/src/utils/Object/cloneObject/cloneObject.ts +3 -0
- package/src/utils/Object/deepEqual/deepEqual.ts +45 -0
- package/src/utils/Object/deleteUndefinedProperties/index.ts +13 -0
- package/src/utils/Object/entriesToObject/entriesToObject.ts +12 -0
- package/src/utils/Object/filterObject/filterObject.ts +18 -0
- package/src/utils/Object/forOwn/forOwn.ts +12 -0
- package/src/utils/Object/getObjectEntries/getObjectEntries.ts +3 -0
- package/src/utils/Object/getObjectKeys/getObjectKeys.ts +3 -0
- package/src/utils/Object/getObjectValues/index.ts +3 -0
- package/src/utils/Object/hasOwnProperty/hasOwnProperty.ts +3 -0
- package/src/utils/Object/invertObject/InvertedObject.ts +3 -0
- package/src/utils/Object/invertObject/invertObject.ts +15 -0
- package/src/utils/Object/isEmptyObject/index.ts +5 -0
- package/src/utils/Object/isObject/index.ts +8 -0
- package/src/utils/Object/mapObjectKeys/mapObjectKeys.ts +25 -0
- package/src/utils/Object/mapObjectValues/mapObjectValues.ts +16 -0
- package/src/utils/Object/mirrorObject/ObjectKeysMirror.ts +1 -0
- package/src/utils/Object/mirrorObject/mirrorObject.ts +7 -0
- package/src/utils/Pagination/DEFAULT_PAGE.ts +1 -0
- package/src/utils/Pagination/calculatePaginationParameters.ts +29 -0
- package/src/utils/Promise/promiseAll/promiseAll.ts +6 -0
- package/src/utils/Promise/restrictParallelCalls/restrictParallelCalls.ts +18 -0
- package/src/utils/Ranges/checkRangesAdjacent/checkRangesAdjacent.ts +23 -0
- package/src/utils/Ranges/checkRangesIntersection/checkRangesIntersection.ts +8 -0
- package/src/utils/Ranges/excludeRanges/excludeRanges.ts +42 -0
- package/src/utils/Ranges/getDistanceToRange/getDistanceToRange.ts +14 -0
- package/src/utils/Ranges/isRangeWithinRanges.ts +14 -0
- package/src/utils/Ranges/mapDateRangeToISODateStringRange/mapDateRangeToISODateStringRange.ts +7 -0
- package/src/utils/Ranges/mapISODateStringRangeToUnixTimeMsRange/mapISODateStringRangeToUnixTimeMsRange.ts +8 -0
- package/src/utils/Ranges/mapRange/mapRange.ts +13 -0
- package/src/utils/Ranges/mapUnixTimeMsRangeToISODateStringRange/mapUnixTimeMsRangeToISODateStringRange.ts +8 -0
- package/src/utils/Ranges/mergeRanges/mergeRanges.ts +34 -0
- package/src/utils/Signal/Signal.ts +47 -0
- package/src/utils/Signal/SignalCallback.ts +1 -0
- package/src/utils/Signal/combineSignalsIntoNewOne/combineSignalsIntoNewOne.ts +75 -0
- package/src/utils/Signal/subscribeSignalAndCallWithCurrentValue/subscribeSignalAndCallWithCurrentValue.ts +7 -0
- package/src/utils/Signal/useSignal.ts +10 -0
- package/src/utils/String/HexString/HEX_SYMBOLS.ts +1 -0
- package/src/utils/String/HexString/HexString.ts +2 -0
- package/src/utils/String/HexString/isHexString.ts +16 -0
- package/src/utils/String/NonEmptyString/NonEmptyString.ts +22 -0
- package/src/utils/String/endsWith/index.ts +6 -0
- package/src/utils/String/isString.ts +3 -0
- package/src/utils/binarySearch/binarySearch.ts +20 -0
- package/src/utils/binarySearchClosest/binarySearchClosest.ts +43 -0
- package/src/utils/createIncrementalIntegerGenerator.ts +7 -0
- package/src/utils/createRemoteDataProcessingWindow/createRemoteDataProcessingWindow.ts +157 -0
- package/src/utils/createSimultaneousOperationsHandler/createSimultaneousOperationsHandler.ts +65 -0
- package/src/utils/encryptDecryptString/encryptDecryptString.ts +34 -0
- package/src/utils/getPrimeNumbersWithEratosthenesSieve/getPrimeNumbersWithEratosthenesSieve.ts +21 -0
- package/src/utils/isPromise/isPromise.ts +30 -0
- package/src/utils/makeAsyncFunctionCallsSimultaneous/makeAsyncFunctionCallsSimultaneous.ts +64 -0
- package/src/utils/parallelsLimitPromise/index.ts +28 -0
- package/src/utils/parallelsLimitQueue/parallelsLimitQueue.ts +69 -0
- package/src/utils/prettyStringify/index.ts +3 -0
- package/src/utils/random/fnv1a/fnv1a.ts +87 -0
- package/src/utils/random/generateHashedFloats/generateHashedFloats.ts +28 -0
- package/src/utils/random/getRandomIntegerInARange/getRandomIntegerInARange.ts +3 -0
- package/src/utils/random/getSampleIndexByStringHash/getSampleIndexByStringHash.ts +10 -0
- package/src/utils/random/randomBoolean/randomBoolean.ts +3 -0
- package/src/utils/random/randomHexString/randomHexString.ts +12 -0
- package/src/utils/requestAnimationFrame/requestAnimationFramePromise/requestAnimationFramePromise.ts +7 -0
- package/src/utils/requestAnimationFrame/requestAnimationFrameThrottle/requestAnimationFrameThrottle.ts +6 -0
- package/src/utils/retryFunctionWrapper/retryFunctionWrapper.ts +40 -0
- package/src/utils/runParallelTimescalesProcessing/runParallelTimescalesProcessing.ts +113 -0
- package/src/utils/stringifyError.ts +3 -0
- package/src/utils/timers/promiseTimeout/promiseTimeout.ts +26 -0
- package/src/utils/timers/subscribeSyntheticInterval/subscribeSyntheticInterval.ts +47 -0
- package/src/utils/waitForFunction/waitForFunction.ts +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# senaev-utils
|
|
2
|
+
|
|
3
|
+
Personal library with utils for `@senaev`.
|
|
4
|
+
|
|
5
|
+
Published as raw source files without a root entrypoint.
|
|
6
|
+
|
|
7
|
+
Import utilities directly by path, for example:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { deepEqual } from 'senaev-utils/src/utils/Object/deepEqual/deepEqual';
|
|
11
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "senaev-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Personal library with utils for @senaev",
|
|
5
|
+
"files": [
|
|
6
|
+
"src",
|
|
7
|
+
"README.md"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"lint:fix": "npx eslint . --fix",
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"test:ci": "vitest --run",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/senaev/senaev-utils.git"
|
|
19
|
+
},
|
|
20
|
+
"author": "Andrei Senaev <andrei.senaev@gmail.com>",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/senaev/senaev-utils/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/senaev/senaev-utils#readme",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@eslint/js": "9.17.0",
|
|
31
|
+
"@stylistic/eslint-plugin": "2.12.1",
|
|
32
|
+
"@testing-library/react": "^16.3.0",
|
|
33
|
+
"@types/async": "^3.2.24",
|
|
34
|
+
"@types/react": "^19.1.3",
|
|
35
|
+
"eslint": "9.17.0",
|
|
36
|
+
"eslint-config-prettier": "9.1.0",
|
|
37
|
+
"eslint-plugin-import-x": "^4.6.1",
|
|
38
|
+
"eslint-plugin-no-only-tests": "^3.3.0",
|
|
39
|
+
"eslint-plugin-prettier": "5.2.1",
|
|
40
|
+
"eslint-plugin-react": "7.37.3",
|
|
41
|
+
"jsdom": "^27.0.0",
|
|
42
|
+
"lefthook": "^1.10.10",
|
|
43
|
+
"react": "^19.1.1",
|
|
44
|
+
"react-router": "^7.9.1",
|
|
45
|
+
"typescript": "^5.8.3",
|
|
46
|
+
"typescript-eslint": "^8.32.0",
|
|
47
|
+
"vitest": "^3.1.3"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@types/node": "^24.0.0",
|
|
51
|
+
"jose": "^6.0.11",
|
|
52
|
+
"rxjs": "^7.8.2"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18",
|
|
56
|
+
"react-router": ">=7"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"react": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"react-router": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { deepEqual } from '../utils/Object/deepEqual/deepEqual';
|
|
4
|
+
|
|
5
|
+
export function setDeepEqualState<T>(defaultValue: T): [T, (nextValue: T) => void] {
|
|
6
|
+
const [
|
|
7
|
+
value,
|
|
8
|
+
setValue,
|
|
9
|
+
] = useState(defaultValue);
|
|
10
|
+
|
|
11
|
+
return [
|
|
12
|
+
value,
|
|
13
|
+
(nextValue) => {
|
|
14
|
+
if (!deepEqual(value, nextValue)) {
|
|
15
|
+
setValue(nextValue);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useLocation, useNavigate } from 'react-router';
|
|
3
|
+
|
|
4
|
+
export function useLocationParameter({
|
|
5
|
+
parameterName,
|
|
6
|
+
defaultValue,
|
|
7
|
+
}: {
|
|
8
|
+
parameterName: string;
|
|
9
|
+
defaultValue: string;
|
|
10
|
+
}): [value: string, setValue: (nextValue: string) => void] {
|
|
11
|
+
const location = useLocation();
|
|
12
|
+
const navigate = useNavigate();
|
|
13
|
+
|
|
14
|
+
// Extract the query parameter from the URL
|
|
15
|
+
const queryParams = new URLSearchParams(location.search);
|
|
16
|
+
|
|
17
|
+
const initialValue = queryParams.get(parameterName) || defaultValue;
|
|
18
|
+
|
|
19
|
+
// Set the query parameter as state
|
|
20
|
+
const [
|
|
21
|
+
stateValue,
|
|
22
|
+
setStateValue,
|
|
23
|
+
] = useState(initialValue);
|
|
24
|
+
|
|
25
|
+
// Update the query parameter in the URL when the state changes
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const params = new URLSearchParams(location.search);
|
|
28
|
+
|
|
29
|
+
if (stateValue === defaultValue) {
|
|
30
|
+
params.delete(parameterName);
|
|
31
|
+
} else {
|
|
32
|
+
params.set(parameterName, String(stateValue));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
navigate(`?${params.toString()}`, { replace: true });
|
|
36
|
+
}, [
|
|
37
|
+
stateValue,
|
|
38
|
+
location.search,
|
|
39
|
+
navigate,
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
stateValue,
|
|
44
|
+
setStateValue,
|
|
45
|
+
];
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { PositiveInteger } from '../types/Number/PositiveInteger';
|
|
4
|
+
import { deepEqual } from '../utils/Object/deepEqual/deepEqual';
|
|
5
|
+
|
|
6
|
+
import { useReload } from './useReload';
|
|
7
|
+
|
|
8
|
+
export function useObjectDeepChangesCounter(object: unknown): PositiveInteger {
|
|
9
|
+
const [
|
|
10
|
+
reload,
|
|
11
|
+
reloadIndex,
|
|
12
|
+
] = useReload();
|
|
13
|
+
|
|
14
|
+
const objectRef = useRef(object);
|
|
15
|
+
|
|
16
|
+
if (!deepEqual(object, objectRef.current)) {
|
|
17
|
+
objectRef.current = object;
|
|
18
|
+
reload();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return reloadIndex;
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { UnsignedInteger } from '../../types/Number/UnsignedInteger';
|
|
4
|
+
import { ObservableMap } from '../../utils/Map/ObservableMap/ObservableMap';
|
|
5
|
+
import { useReload } from '../useReload';
|
|
6
|
+
|
|
7
|
+
export function useObservableMap<K, V>(initialValue?: Iterable<readonly [K, V]> | null): [ObservableMap<K, V>, UnsignedInteger] {
|
|
8
|
+
const [
|
|
9
|
+
reload,
|
|
10
|
+
reloadIndex,
|
|
11
|
+
] = useReload();
|
|
12
|
+
|
|
13
|
+
const mapRef = useRef<ObservableMap<K, V>>(new ObservableMap(initialValue));
|
|
14
|
+
|
|
15
|
+
useEffect(() => mapRef.current.subscribe(reload), []);
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
mapRef.current,
|
|
19
|
+
reloadIndex,
|
|
20
|
+
];
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useRef,
|
|
4
|
+
useState,
|
|
5
|
+
} from 'react';
|
|
6
|
+
|
|
7
|
+
import { Milliseconds } from '../types/Time/Milliseconds';
|
|
8
|
+
import { AbortableProcess } from '../utils/AbortableProcess';
|
|
9
|
+
import { promiseTimeout } from '../utils/timers/promiseTimeout/promiseTimeout';
|
|
10
|
+
|
|
11
|
+
export function useRecentChangeTimer<T>(value: T, timeoutMs: Milliseconds): boolean {
|
|
12
|
+
const [
|
|
13
|
+
isRecentlyChanged,
|
|
14
|
+
setIsRecentlyChanged,
|
|
15
|
+
] = useState(true);
|
|
16
|
+
|
|
17
|
+
const lastValueRef = useRef<T>(value);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const abortable = new AbortableProcess('useRecentChangeTimer');
|
|
21
|
+
|
|
22
|
+
setIsRecentlyChanged(true);
|
|
23
|
+
promiseTimeout(timeoutMs, { abortable }).then(() => {
|
|
24
|
+
setIsRecentlyChanged(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
abortable.abort('useEffect aborted');
|
|
29
|
+
};
|
|
30
|
+
}, [
|
|
31
|
+
value,
|
|
32
|
+
timeoutMs,
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const isValueChanged = lastValueRef.current !== value;
|
|
36
|
+
|
|
37
|
+
if (isValueChanged) {
|
|
38
|
+
lastValueRef.current = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return isRecentlyChanged || isValueChanged;
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { UnsignedInteger } from '../types/Number/UnsignedInteger';
|
|
4
|
+
|
|
5
|
+
export function useReload(): [VoidFunction, UnsignedInteger] {
|
|
6
|
+
const [
|
|
7
|
+
reloadIndex,
|
|
8
|
+
setReloadIndex,
|
|
9
|
+
] = useState(0);
|
|
10
|
+
|
|
11
|
+
return [
|
|
12
|
+
() => {
|
|
13
|
+
setReloadIndex((prevIndex) => prevIndex + 1);
|
|
14
|
+
},
|
|
15
|
+
reloadIndex,
|
|
16
|
+
];
|
|
17
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
VisualizedProcessesTree,
|
|
5
|
+
VisualizedProcessesTreeChange,
|
|
6
|
+
VisualizedProcessesTreeChangeAdd, VisualizedProcessesTreeChangeDelete, VisualizedProcessesTreeChangeStatus,
|
|
7
|
+
} from '../utils/AbortableProcess';
|
|
8
|
+
import { prettyStringify } from '../utils/prettyStringify';
|
|
9
|
+
|
|
10
|
+
type Node = {
|
|
11
|
+
parentId: VisualizedProcessesTree['id'] | null;
|
|
12
|
+
node: VisualizedProcessesTree;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type AllNodesMap = Map<
|
|
16
|
+
VisualizedProcessesTree['id'],
|
|
17
|
+
Node
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
function reconstructAllNodesMap(allNodesMap: AllNodesMap, tree: VisualizedProcessesTree): void {
|
|
21
|
+
allNodesMap.clear();
|
|
22
|
+
|
|
23
|
+
const stack: {
|
|
24
|
+
node: VisualizedProcessesTree;
|
|
25
|
+
parent: VisualizedProcessesTree['id'] | null;
|
|
26
|
+
}[] = [
|
|
27
|
+
{
|
|
28
|
+
node: tree,
|
|
29
|
+
parent: null,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
while (stack.length) {
|
|
34
|
+
const { node, parent } = stack.shift()!;
|
|
35
|
+
|
|
36
|
+
allNodesMap.set(node.id, {
|
|
37
|
+
parentId: parent,
|
|
38
|
+
node,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
42
|
+
stack.push({
|
|
43
|
+
node: node.children[i],
|
|
44
|
+
parent: node.id,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function addNodeToAllNodesMap(allNodesMap: AllNodesMap, change: VisualizedProcessesTreeChangeAdd): void {
|
|
51
|
+
const {
|
|
52
|
+
id,
|
|
53
|
+
name,
|
|
54
|
+
parentId,
|
|
55
|
+
startTimestamp,
|
|
56
|
+
} = change;
|
|
57
|
+
|
|
58
|
+
const parentNode = allNodesMap.get(parentId);
|
|
59
|
+
|
|
60
|
+
if (!parentNode) {
|
|
61
|
+
throw new Error(`Parent node is not found in tree for change=[${prettyStringify(change)}]`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const newNode = {
|
|
65
|
+
id,
|
|
66
|
+
name,
|
|
67
|
+
status: undefined,
|
|
68
|
+
startTimestamp,
|
|
69
|
+
children: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
parentNode.node.children.push(newNode);
|
|
73
|
+
|
|
74
|
+
allNodesMap.set(id, {
|
|
75
|
+
parentId,
|
|
76
|
+
node: newNode,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function deleteNodeFromAllNodesMap(allNodesMap: AllNodesMap, change: VisualizedProcessesTreeChangeDelete): void {
|
|
81
|
+
const {
|
|
82
|
+
id,
|
|
83
|
+
} = change;
|
|
84
|
+
|
|
85
|
+
const node = allNodesMap.get(id);
|
|
86
|
+
|
|
87
|
+
if (!node) {
|
|
88
|
+
throw new Error(`Node is not found in tree for change=[${prettyStringify(change)}]`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { parentId } = node;
|
|
92
|
+
|
|
93
|
+
if (!parentId) {
|
|
94
|
+
throw new Error(`Cannot remove node without parent for change=[${prettyStringify(change)}]`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parentNode = allNodesMap.get(parentId);
|
|
98
|
+
|
|
99
|
+
if (!parentNode) {
|
|
100
|
+
throw new Error(`Parent node is not found in tree for change=[${prettyStringify(change)}]`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const nodeIndex = parentNode.node.children.indexOf(node.node);
|
|
104
|
+
|
|
105
|
+
if (nodeIndex === -1) {
|
|
106
|
+
throw new Error(`Child node is not found the parent node "children" array for change=[${prettyStringify(change)}]`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
parentNode.node.children.splice(nodeIndex, 1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function changeNodeStatusInAllNodesMap(allNodesMap: AllNodesMap, change: VisualizedProcessesTreeChangeStatus): void {
|
|
113
|
+
const {
|
|
114
|
+
id,
|
|
115
|
+
status,
|
|
116
|
+
} = change;
|
|
117
|
+
|
|
118
|
+
const node = allNodesMap.get(id);
|
|
119
|
+
|
|
120
|
+
if (!node) {
|
|
121
|
+
throw new Error(`Node is not found in tree for change=[${prettyStringify(change)}]`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
node.node.status = status;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getTreeForParentNodeWithId(allNodesMap: AllNodesMap, parentId: VisualizedProcessesTree['id']): VisualizedProcessesTree {
|
|
128
|
+
const parentNode = allNodesMap.get(parentId);
|
|
129
|
+
|
|
130
|
+
if (!parentNode) {
|
|
131
|
+
throw new Error(`Parent node is not found in tree for parentId=[${parentId}]`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return parentNode.node;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function useVisualizedProcessesTree(): {
|
|
138
|
+
tree: VisualizedProcessesTree | null;
|
|
139
|
+
setTree: (tree: VisualizedProcessesTree) => void;
|
|
140
|
+
handleChanges: (changes: VisualizedProcessesTreeChange[]) => void;
|
|
141
|
+
} {
|
|
142
|
+
const [
|
|
143
|
+
tree,
|
|
144
|
+
setTree,
|
|
145
|
+
] = useState<VisualizedProcessesTree | null>(null);
|
|
146
|
+
|
|
147
|
+
const allNodesMapRef = useRef<AllNodesMap>(new Map());
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
tree,
|
|
151
|
+
setTree: (nextTree: VisualizedProcessesTree) => {
|
|
152
|
+
const allNodesMap = allNodesMapRef.current;
|
|
153
|
+
|
|
154
|
+
reconstructAllNodesMap(allNodesMap, nextTree);
|
|
155
|
+
|
|
156
|
+
const parentId = nextTree.id;
|
|
157
|
+
const nextTreeToDraw = getTreeForParentNodeWithId(allNodesMap, parentId);
|
|
158
|
+
|
|
159
|
+
setTree(nextTreeToDraw);
|
|
160
|
+
},
|
|
161
|
+
handleChanges: (changes) => {
|
|
162
|
+
const allNodesMap = allNodesMapRef.current;
|
|
163
|
+
|
|
164
|
+
changes.forEach((change) => {
|
|
165
|
+
const { type } = change;
|
|
166
|
+
|
|
167
|
+
if (type === 'add') {
|
|
168
|
+
addNodeToAllNodesMap(allNodesMap, change);
|
|
169
|
+
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (type === 'delete') {
|
|
174
|
+
deleteNodeFromAllNodesMap(allNodesMap, change);
|
|
175
|
+
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (type === 'status') {
|
|
180
|
+
changeNodeStatusInAllNodesMap(allNodesMap, change);
|
|
181
|
+
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
throw new Error(`Unknown event type=[${type}] for change=[${prettyStringify(change)}]`);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
setTree((previousTree) => {
|
|
189
|
+
if (!previousTree) {
|
|
190
|
+
throw new Error('Initial tree has NOT been set');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const parentId = previousTree.id;
|
|
194
|
+
const nextTreeToDraw = getTreeForParentNodeWithId(allNodesMap, parentId);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
...nextTreeToDraw,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function isBoolean(value: unknown): value is boolean {
|
|
2
|
+
return typeof value === 'boolean';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function assertBoolean(value: unknown, errorMessage?: string): asserts value is boolean {
|
|
6
|
+
if (!isBoolean(value)) {
|
|
7
|
+
throw new Error(`assert boolean error${errorMessage ? ` errorMessage=[${errorMessage}]` : ''}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type DeepPartial<T> = T extends string ? T : { [key in keyof T]?: DeepPartial<T[key]> };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type IsEqualUnion<T, U> = [T] extends [U] ? [U] extends [T] ? true : false : false;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type FiniteNumber = number;
|
|
2
|
+
|
|
3
|
+
export function isFiniteNumber(val: unknown): val is FiniteNumber {
|
|
4
|
+
return Number.isFinite(val);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function assertFiniteNumber(val: unknown, message?: string): asserts val is FiniteNumber {
|
|
8
|
+
if (!isFiniteNumber(val)) {
|
|
9
|
+
throw new Error(`assertFiniteNumber error${message ? ` message=[${message}]` : ''}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Целое число
|
|
3
|
+
*/
|
|
4
|
+
export type Integer = number;
|
|
5
|
+
|
|
6
|
+
export function isInteger(value: unknown): value is Integer {
|
|
7
|
+
return Number.isInteger(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function assertInteger(value: unknown): asserts value is Integer {
|
|
11
|
+
if (!isInteger(value)) {
|
|
12
|
+
throw new Error(`value=[${value}] is not an integer`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isString } from '../../utils/String/isString';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Целое цисло (положительное или отрицательное), представленное строкой
|
|
5
|
+
* 💁♂️ '0'
|
|
6
|
+
* 💁♂️ '-100500'
|
|
7
|
+
* 💁♂️ '3456'
|
|
8
|
+
*/
|
|
9
|
+
export type IntegerInString = string;
|
|
10
|
+
|
|
11
|
+
export function isIntegerInString(str: unknown): boolean {
|
|
12
|
+
return isString(str) && str === parseInt(str, 10).toString(10);
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function isNumber(value: unknown): value is number {
|
|
2
|
+
return typeof value === 'number';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function assertNumber(value: unknown, message?: string): asserts value is number {
|
|
6
|
+
if (!isNumber(value)) {
|
|
7
|
+
let errorMessage = `value=[${value}] is not a number`;
|
|
8
|
+
|
|
9
|
+
if (message !== undefined) {
|
|
10
|
+
errorMessage += ` message=[${message}]`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
throw new Error(errorMessage);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PositiveNumber } from '../../types/Number/PositiveNumber';
|
|
2
|
+
|
|
3
|
+
import { Integer } from './Integer';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Целое положительное число
|
|
7
|
+
*/
|
|
8
|
+
export type PositiveInteger = PositiveNumber & Integer;
|
|
9
|
+
|
|
10
|
+
export function isPositiveInteger(value: unknown): value is PositiveInteger {
|
|
11
|
+
if (!Number.isInteger(value)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return (value as Integer) > 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function assertPositiveInteger(value: unknown, errorMessage?: string): asserts value is PositiveInteger {
|
|
19
|
+
if (!isPositiveInteger(value)) {
|
|
20
|
+
throw new Error(`value=[${value}] is not a positive integer${errorMessage ? ` errorMessage=[${errorMessage}]` : ''}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function convertAndAssertPositiveInteger(value: unknown): PositiveInteger {
|
|
25
|
+
const result = Number(value);
|
|
26
|
+
|
|
27
|
+
assertPositiveInteger(result);
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FiniteNumber, isFiniteNumber } from './FiniteNumber';
|
|
2
|
+
|
|
3
|
+
export type UnsignedFiniteNumber = FiniteNumber;
|
|
4
|
+
|
|
5
|
+
export function isUnsignedFiniteNumber(val: unknown): val is UnsignedFiniteNumber {
|
|
6
|
+
return isFiniteNumber(val) && val >= 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function assertUnsignedFiniteNumber(val: unknown, message?: string): asserts val is UnsignedFiniteNumber {
|
|
10
|
+
if (!isUnsignedFiniteNumber(val)) {
|
|
11
|
+
throw new Error(`assertUnsignedFiniteNumber error${message ? ` message=[${message}]` : ''}`);
|
|
12
|
+
}
|
|
13
|
+
}
|