@hrnec06/react_utils 1.5.0 → 1.6.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/package.json +1 -1
- package/src/components/Debugger/Debugger.tsx +18 -68
- package/src/components/Debugger/DebuggerTerminal.tsx +15 -46
- package/src/hooks/useDefaultValue.ts +8 -0
- package/src/hooks/useEfficientRef.ts +3 -2
- package/src/hooks/useLocalStorage.ts +134 -0
- package/src/index.ts +14 -5
- package/src/hooks/useEfficientState.ts +0 -9
package/package.json
CHANGED
|
@@ -6,9 +6,13 @@ import clsx from "clsx";
|
|
|
6
6
|
import ScrollBar from "./DebuggerScrollBar";
|
|
7
7
|
import ParseValue from "./parser/DebugParser";
|
|
8
8
|
import useKeyListener from "../../hooks/useKeyListener";
|
|
9
|
-
import useUpdateEffect from "../../hooks/useUpdateEffect";
|
|
10
9
|
import useUpdatedRef from "../../hooks/useUpdatedRef";
|
|
11
10
|
import ResizeableBox from "../ResizeableBox/ResizeableBox";
|
|
11
|
+
import useNamespacedId from "../../hooks/useNamespacedId";
|
|
12
|
+
import useLocalStorage from "../../hooks/useLocalStorage";
|
|
13
|
+
import z from "zod";
|
|
14
|
+
|
|
15
|
+
const DEBUG_NAMESPACE = "debugger";
|
|
12
16
|
|
|
13
17
|
interface DebugProps {
|
|
14
18
|
value: unknown,
|
|
@@ -29,24 +33,28 @@ function Debug({
|
|
|
29
33
|
openRoot = true
|
|
30
34
|
}: DebugProps) {
|
|
31
35
|
// Config
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
36
|
+
|
|
37
|
+
const id = useNamespacedId(DEBUG_NAMESPACE);
|
|
38
|
+
|
|
39
|
+
const { LS_POS_KEY, LS_SIZE_KEY, LS_EXPAND_KEY } = useMemo(() => ({
|
|
40
|
+
LS_POS_KEY: id + ".position",
|
|
41
|
+
LS_SIZE_KEY: id + ".size",
|
|
42
|
+
LS_EXPAND_KEY: id + ".expanded"
|
|
43
|
+
}), [id]);
|
|
35
44
|
|
|
36
45
|
// Keybinds
|
|
37
46
|
const f9_pressed = useKeyListener('F9');
|
|
38
47
|
|
|
39
|
-
// Position
|
|
40
|
-
const [position, setPosition] =
|
|
48
|
+
// Position and resize
|
|
49
|
+
const [position, setPosition] = useLocalStorage<Vector2>(LS_POS_KEY, [0, 0], z.tuple([z.number(), z.number()]));
|
|
50
|
+
const [windowSize, setWindowSize] = useLocalStorage<Vector2>(LS_SIZE_KEY, [500, 500], z.tuple([z.number(), z.number()]));
|
|
51
|
+
|
|
41
52
|
const [grab, setGrab] = useState<Nullable<{
|
|
42
53
|
windowOrigin: Vector2,
|
|
43
54
|
positionOrigin: Vector2
|
|
44
55
|
}>>(null);
|
|
45
56
|
const [grabOffset, setGrabOffset] = useState<Nullable<Vector2>>(null);
|
|
46
57
|
|
|
47
|
-
// Resize
|
|
48
|
-
const [windowSize, setWindowSize] = useState<Vector2>([500, 500]);
|
|
49
|
-
|
|
50
58
|
// Scroll
|
|
51
59
|
const [scrollHeight, setScrollHeight] = useState(0);
|
|
52
60
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
@@ -98,64 +106,6 @@ function Debug({
|
|
|
98
106
|
|
|
99
107
|
const isScrollable = useMemo(() => scrollHeight > 0, [scrollHeight]);
|
|
100
108
|
|
|
101
|
-
// Load saved position
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
try {
|
|
104
|
-
const saved_position = localStorage.getItem(LS_POS_KEY);
|
|
105
|
-
if (saved_position) {
|
|
106
|
-
const [v1, v2] = saved_position.split(',');
|
|
107
|
-
|
|
108
|
-
if (v1 === undefined || v2 === undefined)
|
|
109
|
-
throw new Error('Invalid vector: ' + saved_position);
|
|
110
|
-
|
|
111
|
-
const [v1_p, v2_p] = [parseInt(v1), parseInt(v2)];
|
|
112
|
-
|
|
113
|
-
if (isNaN(v1_p) || isNaN(v2_p))
|
|
114
|
-
throw new Error('Invalid vector values: ' + saved_position);
|
|
115
|
-
|
|
116
|
-
setPosition([v1_p, v2_p]);
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
console.error(`Error loading saved position: `, error);
|
|
120
|
-
|
|
121
|
-
localStorage.removeItem(LS_POS_KEY);
|
|
122
|
-
}
|
|
123
|
-
}, []);
|
|
124
|
-
|
|
125
|
-
// Load saved size
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
try {
|
|
128
|
-
const saved_size = localStorage.getItem(LS_SIZE_KEY);
|
|
129
|
-
if (saved_size) {
|
|
130
|
-
const [v1, v2] = saved_size.split(',');
|
|
131
|
-
|
|
132
|
-
if (v1 === undefined || v2 === undefined)
|
|
133
|
-
throw new Error('Invalid vector: ' + saved_size);
|
|
134
|
-
|
|
135
|
-
const [v1_p, v2_p] = [parseInt(v1), parseInt(v2)];
|
|
136
|
-
|
|
137
|
-
if (isNaN(v1_p) || isNaN(v2_p))
|
|
138
|
-
throw new Error('Invalid vector values: ' + saved_size);
|
|
139
|
-
|
|
140
|
-
setWindowSize([v1_p, v2_p]);
|
|
141
|
-
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error(`Error loading saved size: `, error);
|
|
144
|
-
|
|
145
|
-
localStorage.removeItem(LS_POS_KEY);
|
|
146
|
-
}
|
|
147
|
-
}, []);
|
|
148
|
-
|
|
149
|
-
// Save saved position
|
|
150
|
-
useUpdateEffect(() => {
|
|
151
|
-
localStorage.setItem(LS_POS_KEY, `${position[0]},${position[1]}`);
|
|
152
|
-
}, [position]);
|
|
153
|
-
|
|
154
|
-
// Save saved size
|
|
155
|
-
useUpdateEffect(() => {
|
|
156
|
-
localStorage.setItem(LS_SIZE_KEY, `${windowSize[0]},${windowSize[1]}`);
|
|
157
|
-
}, [windowSize]);
|
|
158
|
-
|
|
159
109
|
// Reset position
|
|
160
110
|
useEffect(() => {
|
|
161
111
|
if (!f9_pressed)
|
|
@@ -291,7 +241,7 @@ function Debug({
|
|
|
291
241
|
<div
|
|
292
242
|
ref={containerRef}
|
|
293
243
|
onMouseDown={handleMouseDown}
|
|
294
|
-
className="
|
|
244
|
+
className="cursor-grab w-full"
|
|
295
245
|
style={{
|
|
296
246
|
transform: `translateY(${-(scrollProgress * scrollHeight)}px)`
|
|
297
247
|
}}
|
|
@@ -3,6 +3,7 @@ import { assert, Optional, ReactUtils } from "@hrnec06/util";
|
|
|
3
3
|
import useUpdateEffect from "../../hooks/useUpdateEffect";
|
|
4
4
|
import useNamespacedId from "../../hooks/useNamespacedId";
|
|
5
5
|
import z from "zod";
|
|
6
|
+
import useLocalStorage from "../../hooks/useLocalStorage";
|
|
6
7
|
|
|
7
8
|
const TERMINAL_NAMESPACE = "terminal";
|
|
8
9
|
|
|
@@ -73,11 +74,6 @@ class Terminal
|
|
|
73
74
|
this.history = history;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
public getLsKey(): string
|
|
77
|
-
{
|
|
78
|
-
return "terminal." + this.id;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
77
|
public clear(): Terminal
|
|
82
78
|
{
|
|
83
79
|
this.history[1]([]);
|
|
@@ -214,37 +210,6 @@ function TerminalContextProvider({
|
|
|
214
210
|
</TerminalContext.Provider>
|
|
215
211
|
)
|
|
216
212
|
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Returns the default history for a terminal
|
|
220
|
-
*/
|
|
221
|
-
function getDefaultHistory(terminal: Terminal)
|
|
222
|
-
{
|
|
223
|
-
try
|
|
224
|
-
{
|
|
225
|
-
const savedValue = localStorage.getItem(terminal.getLsKey());
|
|
226
|
-
|
|
227
|
-
const savedValueJSON = JSON.parse(savedValue ?? "[]");
|
|
228
|
-
|
|
229
|
-
const schema = z.array(z.object({
|
|
230
|
-
value: z.any(),
|
|
231
|
-
role: z.enum(LogRole),
|
|
232
|
-
type: z.enum(LogType)
|
|
233
|
-
}));
|
|
234
|
-
|
|
235
|
-
const parsed = schema.parse(savedValueJSON);
|
|
236
|
-
|
|
237
|
-
return parsed;
|
|
238
|
-
}
|
|
239
|
-
catch (error)
|
|
240
|
-
{
|
|
241
|
-
localStorage.removeItem(terminal.getLsKey());
|
|
242
|
-
console.error("Terminal data parse failed: ", error);
|
|
243
|
-
|
|
244
|
-
return [];
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
213
|
/**
|
|
249
214
|
* Recursively browses the contexts to find a matching terminal
|
|
250
215
|
*/
|
|
@@ -304,22 +269,26 @@ function useTerminal(
|
|
|
304
269
|
{
|
|
305
270
|
const id = useNamespacedId(TERMINAL_NAMESPACE);
|
|
306
271
|
|
|
307
|
-
const history =
|
|
272
|
+
const history = useLocalStorage<LogItem[]>(
|
|
273
|
+
"terminal." + id,
|
|
274
|
+
[],
|
|
275
|
+
z.array(z.object({
|
|
276
|
+
value: z.any(),
|
|
277
|
+
role: z.enum(LogRole),
|
|
278
|
+
type: z.enum(LogType)
|
|
279
|
+
}))
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// const history = useState<LogItem[]>(storage);
|
|
308
283
|
const inputHistory = useState<string[]>([]);
|
|
309
284
|
|
|
310
285
|
const terminal = useMemo(() => {
|
|
311
286
|
return new Terminal(id, name, history, inputHistory, onInput);
|
|
312
287
|
}, [name, history, onInput]);
|
|
313
288
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
history[1](def);
|
|
318
|
-
}, []);
|
|
319
|
-
|
|
320
|
-
useUpdateEffect(() => {
|
|
321
|
-
localStorage.setItem(terminal.getLsKey(), JSON.stringify(history[0]));
|
|
322
|
-
}, [history]);
|
|
289
|
+
// useUpdateEffect(() => {
|
|
290
|
+
// setStorage(history[0]);
|
|
291
|
+
// }, [history]);
|
|
323
292
|
|
|
324
293
|
return terminal;
|
|
325
294
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useMemo, useRef } from "react";
|
|
2
|
+
import useDefaultValue from "./useDefaultValue";
|
|
2
3
|
|
|
3
4
|
export default function useEfficientRef<T>(callback: () => T): React.RefObject<T>
|
|
4
5
|
{
|
|
5
|
-
const
|
|
6
|
+
const value = useDefaultValue(callback);
|
|
6
7
|
|
|
7
|
-
return useRef<T>(
|
|
8
|
+
return useRef<T>(value);
|
|
8
9
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { disposables, Listenable, Nullable, Optional, ReactUtils } from "@hrnec06/util";
|
|
2
|
+
import useSignal, { Signal } from "./useSignal";
|
|
3
|
+
import useUpdateEffect from "./useUpdateEffect";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import useDisposables from "./useDisposables";
|
|
6
|
+
import useDefaultValue from "./useDefaultValue";
|
|
7
|
+
import z from "zod";
|
|
8
|
+
|
|
9
|
+
type StateValue<T> = T | StateCallback<T>;
|
|
10
|
+
type StateCallback<T> = (value: T) => T;
|
|
11
|
+
|
|
12
|
+
function isStateCallback<T>(v: StateValue<T>): v is StateCallback<T> {
|
|
13
|
+
return typeof v === "function";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function encodeValue(value: unknown): Optional<string>
|
|
17
|
+
{
|
|
18
|
+
if (value === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
|
|
21
|
+
try
|
|
22
|
+
{
|
|
23
|
+
const encoded = JSON.stringify(value);
|
|
24
|
+
|
|
25
|
+
return encoded;
|
|
26
|
+
}
|
|
27
|
+
catch (error)
|
|
28
|
+
{
|
|
29
|
+
console.error(error);
|
|
30
|
+
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function decodeValue(value: Nullable<string>): unknown
|
|
36
|
+
{
|
|
37
|
+
if (value === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
|
|
40
|
+
try
|
|
41
|
+
{
|
|
42
|
+
const decoded = JSON.parse(value);
|
|
43
|
+
|
|
44
|
+
return decoded;
|
|
45
|
+
}
|
|
46
|
+
catch (error)
|
|
47
|
+
{
|
|
48
|
+
console.error(error);
|
|
49
|
+
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class StorageEventEmitter extends Listenable<{
|
|
55
|
+
storage: void
|
|
56
|
+
}> {
|
|
57
|
+
constructor
|
|
58
|
+
(
|
|
59
|
+
private readonly key: string
|
|
60
|
+
)
|
|
61
|
+
{
|
|
62
|
+
super();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public setLocalStorage = (value: string) => {
|
|
66
|
+
localStorage.setItem(this.key, value);
|
|
67
|
+
|
|
68
|
+
this.triggerListener("storage");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public removeLocalStorage = () => {
|
|
72
|
+
localStorage.removeItem(this.key);
|
|
73
|
+
|
|
74
|
+
this.triggerListener("storage");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public getLocalStorage = () => {
|
|
78
|
+
return localStorage.getItem(this.key);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public subscribe = (onStoreChange: () => void) => {
|
|
82
|
+
const id = this.on("storage", onStoreChange);
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
this.off("storage", id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default function useLocalStorage<T>(key: string, defaultValue: T, schema?: z.ZodType): ReactUtils.State<T>
|
|
91
|
+
{
|
|
92
|
+
const storageManager = useMemo(() => new StorageEventEmitter(key), [key]);
|
|
93
|
+
const [currentValue, setCurrentValue] = useState(defaultValue);
|
|
94
|
+
|
|
95
|
+
const store = useSyncExternalStore(
|
|
96
|
+
storageManager.subscribe,
|
|
97
|
+
storageManager.getLocalStorage,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const decoded = decodeValue(store);
|
|
102
|
+
|
|
103
|
+
if (decoded === undefined || ((schema && !schema.safeParse(decoded).success) || (typeof decoded !== typeof defaultValue)))
|
|
104
|
+
{
|
|
105
|
+
storageManager.removeLocalStorage();
|
|
106
|
+
|
|
107
|
+
setCurrentValue(defaultValue);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setCurrentValue(decoded as T);
|
|
112
|
+
}, [store]);
|
|
113
|
+
|
|
114
|
+
const setState = useCallback((value: StateValue<T>) => {
|
|
115
|
+
setCurrentValue(prevCurrentValue => {
|
|
116
|
+
const newValue = isStateCallback(value) ? value(prevCurrentValue) : value;
|
|
117
|
+
|
|
118
|
+
const encoded = encodeValue(newValue);
|
|
119
|
+
|
|
120
|
+
if (encoded === undefined)
|
|
121
|
+
{
|
|
122
|
+
storageManager.removeLocalStorage();
|
|
123
|
+
}
|
|
124
|
+
else
|
|
125
|
+
{
|
|
126
|
+
storageManager.setLocalStorage(encoded);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return newValue;
|
|
130
|
+
});
|
|
131
|
+
}, [key, currentValue]);
|
|
132
|
+
|
|
133
|
+
return [currentValue, setState];
|
|
134
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import useDefaultValue from "./hooks/useDefaultValue";
|
|
2
|
+
import useDisposables from "./hooks/useDisposables";
|
|
1
3
|
import useKeyListener from "./hooks/useKeyListener";
|
|
2
4
|
import useListener from "./hooks/useListener";
|
|
3
5
|
import useUpdatedRef from "./hooks/useUpdatedRef";
|
|
4
6
|
import useUpdateEffect from "./hooks/useUpdateEffect";
|
|
5
7
|
import useWindowSize from "./hooks/useWindowSize";
|
|
6
8
|
import useEfficientRef from "./hooks/useEfficientRef";
|
|
7
|
-
import useEfficientState from "./hooks/useEfficientState";
|
|
8
9
|
import useUUID from "./hooks/useUUID";
|
|
9
10
|
import useFlags from "./hooks/useFlags";
|
|
11
|
+
import useNamespacedId from "./hooks/useNamespacedId";
|
|
12
|
+
import useTransition from "./hooks/useTransition";
|
|
13
|
+
import useLocalStorage from "./hooks/useLocalStorage";
|
|
10
14
|
|
|
11
15
|
import useSignal, { Signal } from "./hooks/useSignal";
|
|
12
16
|
import useLazySignal, { LazySignal } from "./hooks/useLazySignal";
|
|
@@ -18,17 +22,22 @@ import Dialog from "./components/Dialog/Dialog";
|
|
|
18
22
|
|
|
19
23
|
import * as util from './lib/utils';
|
|
20
24
|
|
|
25
|
+
|
|
21
26
|
export {
|
|
22
27
|
// Hooks
|
|
28
|
+
useDefaultValue,
|
|
29
|
+
useDisposables,
|
|
30
|
+
useEfficientRef,
|
|
31
|
+
useFlags,
|
|
23
32
|
useKeyListener,
|
|
24
33
|
useListener,
|
|
34
|
+
useNamespacedId,
|
|
35
|
+
useTransition,
|
|
25
36
|
useUpdatedRef,
|
|
26
37
|
useUpdateEffect,
|
|
27
|
-
useWindowSize,
|
|
28
|
-
useEfficientRef,
|
|
29
|
-
useEfficientState,
|
|
30
38
|
useUUID,
|
|
31
|
-
|
|
39
|
+
useWindowSize,
|
|
40
|
+
useLocalStorage,
|
|
32
41
|
|
|
33
42
|
// Signals
|
|
34
43
|
useSignal,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ReactUtils } from "@hrnec06/util";
|
|
2
|
-
import { useMemo, useState } from "react";
|
|
3
|
-
|
|
4
|
-
export default function useEfficientState<T>(callback: () => T): [T, ReactUtils.SetState<T>]
|
|
5
|
-
{
|
|
6
|
-
const defaultValue = useMemo(() => callback(), []);
|
|
7
|
-
|
|
8
|
-
return useState<T>(defaultValue);
|
|
9
|
-
}
|