@hrnec06/react_utils 1.5.1 → 1.7.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/ContextMenu/ContextMenu.tsx +235 -0
- package/src/components/ContextMenu/ContextMenu.types.ts +17 -0
- package/src/components/ContextMenu/ContextMenuCtx.tsx +33 -0
- package/src/components/ContextMenu/ContextMenuItem.tsx +147 -0
- package/src/components/ContextMenu/ContextMenuRoot.tsx +28 -0
- package/src/components/ContextMenu/ContextMenuSection.tsx +28 -0
- package/src/components/Debugger/Debugger.tsx +86 -84
- package/src/components/Debugger/DebuggerTerminal.tsx +18 -49
- package/src/components/Debugger/parser/DebugParser.tsx +121 -14
- package/src/components/Debugger/parser/DebugTerminal.tsx +24 -7
- package/src/components/Debugger/parser/ValueArray.tsx +22 -7
- package/src/components/Debugger/parser/ValueBoolean.tsx +15 -4
- package/src/components/Debugger/parser/ValueConstant.tsx +21 -0
- package/src/components/Debugger/parser/ValueFunction.tsx +17 -5
- package/src/components/Debugger/parser/ValueNumber.tsx +14 -4
- package/src/components/Debugger/parser/ValueObject.tsx +61 -17
- package/src/components/Debugger/parser/ValueString.tsx +13 -4
- package/src/components/ResizeableBox/ResizeableBox.tsx +1 -1
- package/src/hooks/useDebounce.ts +26 -0
- package/src/hooks/useDefaultValue.ts +8 -0
- package/src/hooks/useEfficientRef.ts +3 -2
- package/src/hooks/useEvent.ts +15 -0
- package/src/hooks/useLatestRef.ts +12 -0
- package/src/hooks/useLocalStorage.ts +134 -0
- package/src/hooks/useSyncRef.ts +17 -0
- package/src/hooks/useTransition.ts +2 -1
- package/src/index.ts +16 -5
- package/src/lib/errors/ContextError.ts +11 -0
- package/src/hooks/useEfficientState.ts +0 -9
- package/src/hooks/useUpdatedRef.ts +0 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { minmax, Nullable, Vector2 } from "@hrnec06/util";
|
|
1
|
+
import { matchMouseEvent, minmax, MouseButton, Nullable, Vector2 } from "@hrnec06/util";
|
|
2
2
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import {DebuggerContext} from "./DebuggerContext";
|
|
@@ -6,12 +6,49 @@ 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
|
|
10
|
-
import useUpdatedRef from "../../hooks/useUpdatedRef";
|
|
9
|
+
import useUpdatedRef from "../../hooks/useLatestRef";
|
|
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
|
+
import useDefaultValue from "../../hooks/useDefaultValue";
|
|
15
|
+
|
|
16
|
+
const DEBUG_NAMESPACE = "debugger";
|
|
17
|
+
|
|
18
|
+
function useDebuggerID(_customID?: string): string
|
|
19
|
+
{
|
|
20
|
+
const customID = useDefaultValue(_customID);
|
|
21
|
+
|
|
22
|
+
if (customID)
|
|
23
|
+
return customID;
|
|
24
|
+
|
|
25
|
+
return useNamespacedId(DEBUG_NAMESPACE);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface DebuggerLabelProps {
|
|
29
|
+
label: string
|
|
30
|
+
}
|
|
31
|
+
function DebuggerLabel({ label }: DebuggerLabelProps)
|
|
32
|
+
{
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className={clsx(
|
|
36
|
+
"px-3 py-1",
|
|
37
|
+
"text font-semibold text-white bg-[#181818]"
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
{label}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
12
44
|
|
|
13
45
|
interface DebugProps {
|
|
14
46
|
value: unknown,
|
|
47
|
+
/**
|
|
48
|
+
* Assign a persistant ID if saved position breaks because of remounts.
|
|
49
|
+
*/
|
|
50
|
+
id?: string,
|
|
51
|
+
label?: string,
|
|
15
52
|
openPaths?: string[],
|
|
16
53
|
excludePaths?: string[],
|
|
17
54
|
size?: 'normal' | 'big' | 'tiny',
|
|
@@ -21,6 +58,8 @@ interface DebugProps {
|
|
|
21
58
|
}
|
|
22
59
|
function Debug({
|
|
23
60
|
value,
|
|
61
|
+
id: customID,
|
|
62
|
+
label,
|
|
24
63
|
openPaths,
|
|
25
64
|
excludePaths,
|
|
26
65
|
size = 'normal',
|
|
@@ -29,24 +68,28 @@ function Debug({
|
|
|
29
68
|
openRoot = true
|
|
30
69
|
}: DebugProps) {
|
|
31
70
|
// Config
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
71
|
+
|
|
72
|
+
const id = useDebuggerID(customID);
|
|
73
|
+
|
|
74
|
+
const { LS_POS_KEY, LS_SIZE_KEY, LS_EXPAND_KEY } = useMemo(() => ({
|
|
75
|
+
LS_POS_KEY: id + ".position",
|
|
76
|
+
LS_SIZE_KEY: id + ".size",
|
|
77
|
+
LS_EXPAND_KEY: id + ".expanded"
|
|
78
|
+
}), [id]);
|
|
35
79
|
|
|
36
80
|
// Keybinds
|
|
37
81
|
const f9_pressed = useKeyListener('F9');
|
|
38
82
|
|
|
39
|
-
// Position
|
|
40
|
-
const [position, setPosition] =
|
|
83
|
+
// Position and resize
|
|
84
|
+
const [position, setPosition] = useLocalStorage<Vector2>(LS_POS_KEY, [0, 0], z.tuple([z.number(), z.number()]));
|
|
85
|
+
const [windowSize, setWindowSize] = useLocalStorage<Vector2>(LS_SIZE_KEY, [500, 500], z.tuple([z.number(), z.number()]));
|
|
86
|
+
|
|
41
87
|
const [grab, setGrab] = useState<Nullable<{
|
|
42
88
|
windowOrigin: Vector2,
|
|
43
89
|
positionOrigin: Vector2
|
|
44
90
|
}>>(null);
|
|
45
91
|
const [grabOffset, setGrabOffset] = useState<Nullable<Vector2>>(null);
|
|
46
92
|
|
|
47
|
-
// Resize
|
|
48
|
-
const [windowSize, setWindowSize] = useState<Vector2>([500, 500]);
|
|
49
|
-
|
|
50
93
|
// Scroll
|
|
51
94
|
const [scrollHeight, setScrollHeight] = useState(0);
|
|
52
95
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
@@ -98,64 +141,6 @@ function Debug({
|
|
|
98
141
|
|
|
99
142
|
const isScrollable = useMemo(() => scrollHeight > 0, [scrollHeight]);
|
|
100
143
|
|
|
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
144
|
// Reset position
|
|
160
145
|
useEffect(() => {
|
|
161
146
|
if (!f9_pressed)
|
|
@@ -196,6 +181,8 @@ function Debug({
|
|
|
196
181
|
|
|
197
182
|
// Handlers
|
|
198
183
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
184
|
+
if (!matchMouseEvent(e.nativeEvent, MouseButton.Left)) return;
|
|
185
|
+
|
|
199
186
|
e.preventDefault();
|
|
200
187
|
|
|
201
188
|
setGrab({
|
|
@@ -263,7 +250,7 @@ function Debug({
|
|
|
263
250
|
}
|
|
264
251
|
}}
|
|
265
252
|
>
|
|
266
|
-
<div className="fixed pointer-events-none w-full h-full left-0 top-0 overflow-hidden z-
|
|
253
|
+
<div className="fixed pointer-events-none w-full h-full left-0 top-0 overflow-hidden z-500">
|
|
267
254
|
<div
|
|
268
255
|
className={clsx(
|
|
269
256
|
"absolute font-jetbrains pointer-events-auto",
|
|
@@ -285,25 +272,40 @@ function Debug({
|
|
|
285
272
|
$onResize={handleResize}
|
|
286
273
|
>
|
|
287
274
|
<div
|
|
288
|
-
|
|
289
|
-
className=
|
|
275
|
+
// onMouseDown={handleMouseDown}
|
|
276
|
+
className={clsx(
|
|
277
|
+
"flex flex-col w-full h-full",
|
|
278
|
+
"bg-[#1f1f1f] border border-[#4b4b4b] shadow-lg rounded-md"
|
|
279
|
+
)}
|
|
290
280
|
>
|
|
281
|
+
{label && (
|
|
282
|
+
<DebuggerLabel
|
|
283
|
+
label={label}
|
|
284
|
+
/>
|
|
285
|
+
)}
|
|
286
|
+
|
|
291
287
|
<div
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
className=" cursor-grab w-full"
|
|
295
|
-
style={{
|
|
296
|
-
transform: `translateY(${-(scrollProgress * scrollHeight)}px)`
|
|
297
|
-
}}
|
|
288
|
+
onWheel={handleWheel}
|
|
289
|
+
className="w-full h-full overflow-hidden flex"
|
|
298
290
|
>
|
|
299
|
-
<div
|
|
300
|
-
|
|
291
|
+
<div
|
|
292
|
+
ref={containerRef}
|
|
293
|
+
onMouseDown={handleMouseDown}
|
|
294
|
+
className="cursor-grab w-full"
|
|
295
|
+
style={{
|
|
296
|
+
transform: `translateY(${-(scrollProgress * scrollHeight)}px)`
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
<div className="p-3 pl-5">
|
|
300
|
+
<ParseValue value={value} />
|
|
301
|
+
</div>
|
|
301
302
|
</div>
|
|
303
|
+
<ScrollBar
|
|
304
|
+
containerHeight={containerHeight}
|
|
305
|
+
scrollHeight={scrollHeight}
|
|
306
|
+
/>
|
|
302
307
|
</div>
|
|
303
|
-
|
|
304
|
-
containerHeight={containerHeight}
|
|
305
|
-
scrollHeight={scrollHeight}
|
|
306
|
-
/>
|
|
308
|
+
|
|
307
309
|
</div>
|
|
308
310
|
</ResizeableBox>
|
|
309
311
|
</div>
|
|
@@ -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 =
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
);
|
|
313
281
|
|
|
314
|
-
|
|
315
|
-
|
|
282
|
+
// const history = useState<LogItem[]>(storage);
|
|
283
|
+
const inputHistory = useState<string[]>([]);
|
|
316
284
|
|
|
317
|
-
|
|
318
|
-
|
|
285
|
+
const terminal = useMemo(() =>
|
|
286
|
+
new Terminal(id, name, history, inputHistory, onInput)
|
|
287
|
+
, [name, history, onInput]);
|
|
319
288
|
|
|
320
|
-
useUpdateEffect(() => {
|
|
321
|
-
|
|
322
|
-
}, [history]);
|
|
289
|
+
// useUpdateEffect(() => {
|
|
290
|
+
// setStorage(history[0]);
|
|
291
|
+
// }, [history]);
|
|
323
292
|
|
|
324
293
|
return terminal;
|
|
325
294
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { forwardRef, RefObject, useDeferredValue, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
1
2
|
import useDebugger from "../DebuggerContext";
|
|
2
3
|
import { matchPath } from "../DebuggerLogic";
|
|
3
4
|
import { Terminal } from "../DebuggerTerminal";
|
|
@@ -5,46 +6,152 @@ import DebugTerminal from "./DebugTerminal";
|
|
|
5
6
|
import ValueArray from "./ValueArray";
|
|
6
7
|
import ValueBoolean from "./ValueBoolean";
|
|
7
8
|
import ValueFunction from "./ValueFunction";
|
|
8
|
-
import ValueKeyword from "./ValueKeyword";
|
|
9
9
|
import ValueNumber from "./ValueNumber";
|
|
10
10
|
import ValueObject from "./ValueObject";
|
|
11
11
|
import ValueString from "./ValueString";
|
|
12
12
|
import ValueSymbol from "./ValueSymbol";
|
|
13
|
+
import { Nullable } from "@hrnec06/util";
|
|
14
|
+
import ContextMenu from "../../ContextMenu/ContextMenu";
|
|
15
|
+
import ValueConstant from "./ValueConstant";
|
|
16
|
+
import useSyncRef from "../../../hooks/useSyncRef";
|
|
17
|
+
import CTXM from "../../ContextMenu/ContextMenu.types";
|
|
18
|
+
import clsx from "clsx";
|
|
19
|
+
import useDebounce from "../../../hooks/useDebounce";
|
|
20
|
+
import { useCTXMListener } from "../../ContextMenu/ContextMenuCtx";
|
|
13
21
|
|
|
14
|
-
interface
|
|
22
|
+
export interface ParseValueRef {
|
|
23
|
+
copyValue: string,
|
|
24
|
+
name?: string,
|
|
25
|
+
menu?: CTXM.ContextMenuSection,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ClassifyProps {
|
|
29
|
+
ref: React.Ref<ParseValueRef>,
|
|
15
30
|
value: unknown,
|
|
16
31
|
path?: string[]
|
|
17
32
|
}
|
|
18
|
-
|
|
33
|
+
function Classify({
|
|
34
|
+
ref,
|
|
19
35
|
value,
|
|
20
36
|
path = []
|
|
21
|
-
}:
|
|
37
|
+
}: ClassifyProps) {
|
|
22
38
|
const debug = useDebugger();
|
|
23
39
|
|
|
24
40
|
switch (typeof value) {
|
|
25
|
-
case 'string': return (<ValueString value={value} />);
|
|
41
|
+
case 'string': return (<ValueString ref={ref} value={value} />);
|
|
26
42
|
case 'bigint':
|
|
27
|
-
case 'number': return (<ValueNumber value={value} />);
|
|
28
|
-
case 'boolean': return (<ValueBoolean value={value} />);
|
|
29
|
-
case 'undefined': return (<
|
|
30
|
-
case 'function': return (<ValueFunction value={value} />);
|
|
43
|
+
case 'number': return (<ValueNumber ref={ref} value={value} />);
|
|
44
|
+
case 'boolean': return (<ValueBoolean ref={ref} value={value} />);
|
|
45
|
+
case 'undefined': return (<ValueConstant ref={ref} value="undefined" />)
|
|
46
|
+
case 'function': return (<ValueFunction ref={ref} value={value} />);
|
|
31
47
|
case 'symbol': return (<ValueSymbol value={value} />)
|
|
32
48
|
case 'object': {
|
|
33
49
|
if (value === null)
|
|
34
|
-
return (<
|
|
50
|
+
return (<ValueConstant ref={ref} value={"null"} />)
|
|
35
51
|
|
|
36
52
|
const isRootObject = path.length === 0;
|
|
37
53
|
|
|
38
54
|
const shouldBeExpanded = (isRootObject && debug.options.openRoot) || (matchPath(path, debug.paths.open) && !matchPath(path, debug.paths.exclude));
|
|
39
55
|
|
|
40
56
|
if (value instanceof Terminal)
|
|
41
|
-
return <DebugTerminal terminal={value} path={path} defaultExpanded={shouldBeExpanded} />
|
|
57
|
+
return <DebugTerminal ref={ref} terminal={value} path={path} defaultExpanded={shouldBeExpanded} />
|
|
42
58
|
|
|
43
59
|
if (Array.isArray(value)) {
|
|
44
|
-
return <ValueArray value={value} path={path} defaultExpanded={shouldBeExpanded} />
|
|
60
|
+
return <ValueArray ref={ref} value={value} path={path} defaultExpanded={shouldBeExpanded} />
|
|
45
61
|
}
|
|
46
62
|
|
|
47
|
-
return <ValueObject value={value} path={path} defaultExpanded={shouldBeExpanded} />
|
|
63
|
+
return <ValueObject ref={ref} value={value} path={path} defaultExpanded={shouldBeExpanded} />
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
|
-
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface ParseValueProps {
|
|
69
|
+
value: unknown,
|
|
70
|
+
path?: string[],
|
|
71
|
+
/**
|
|
72
|
+
* Children to be put before the parsed value
|
|
73
|
+
*/
|
|
74
|
+
children?: React.ReactNode
|
|
75
|
+
}
|
|
76
|
+
function ParseValue({
|
|
77
|
+
value,
|
|
78
|
+
path = [],
|
|
79
|
+
children
|
|
80
|
+
}: ParseValueProps)
|
|
81
|
+
{
|
|
82
|
+
const valueRef = useRef<ParseValueRef>(null);
|
|
83
|
+
const [localValue, setLocalValue] = useState<Nullable<ParseValueRef>>(null);
|
|
84
|
+
const syncedValueRef = useSyncRef(valueRef, setLocalValue);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<DebugCTXMProvider
|
|
88
|
+
path={path}
|
|
89
|
+
ref={localValue}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
|
|
93
|
+
<Classify
|
|
94
|
+
ref={syncedValueRef}
|
|
95
|
+
value={value}
|
|
96
|
+
path={path}
|
|
97
|
+
/>
|
|
98
|
+
</DebugCTXMProvider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface DebugCTXMProviderProps extends React.PropsWithChildren {
|
|
103
|
+
path: string[],
|
|
104
|
+
ref: Nullable<ParseValueRef>
|
|
105
|
+
}
|
|
106
|
+
export function DebugCTXMProvider({
|
|
107
|
+
path,
|
|
108
|
+
ref,
|
|
109
|
+
children
|
|
110
|
+
}: DebugCTXMProviderProps)
|
|
111
|
+
{
|
|
112
|
+
const ctxmLabel = useMemo(() => {
|
|
113
|
+
if (!path.length)
|
|
114
|
+
return "root";
|
|
115
|
+
|
|
116
|
+
return path.join('.');
|
|
117
|
+
}, [path]);
|
|
118
|
+
|
|
119
|
+
const handleCopy = ref === null
|
|
120
|
+
? undefined
|
|
121
|
+
: () => {
|
|
122
|
+
if (!ref) return;
|
|
123
|
+
|
|
124
|
+
navigator.clipboard.writeText(ref.copyValue);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<ContextMenu
|
|
129
|
+
className={clsx(
|
|
130
|
+
"inline align-middle h-full",
|
|
131
|
+
"outline-sky-500 outline-offset-2",
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
$autoGenerateWrapper
|
|
135
|
+
$id={ctxmLabel}
|
|
136
|
+
$inherit={(menu) => ({
|
|
137
|
+
parent: menu.id,
|
|
138
|
+
children: menu.menu
|
|
139
|
+
})}
|
|
140
|
+
$menu={[
|
|
141
|
+
[
|
|
142
|
+
{
|
|
143
|
+
id: 'copy',
|
|
144
|
+
label: "Copy",
|
|
145
|
+
description: ref?.name,
|
|
146
|
+
onClick: handleCopy
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
ref?.menu ?? null
|
|
150
|
+
]}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</ContextMenu>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default ParseValue;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createContext, useContext, useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import { createContext, forwardRef, useContext, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { LogItem, LogRole, LogType, Terminal } from "../DebuggerTerminal"
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import { KeyboardButton, matchBool, matchKeyEvent, matchMouseEvent, MouseButton, Optional } from "@hrnec06/util";
|
|
5
|
-
import ParseValue from "./DebugParser";
|
|
5
|
+
import ParseValue, { ParseValueRef } from "./DebugParser";
|
|
6
6
|
import { ChevronRight, CircleAlert, SendIcon, TerminalIcon, Trash, TriangleAlert } from "lucide-react";
|
|
7
7
|
import ResizeableBox from "../../ResizeableBox/ResizeableBox";
|
|
8
8
|
import { Chevron_Toggle } from "../DebuggerSymbols";
|
|
@@ -28,10 +28,25 @@ interface DebugTerminalProps {
|
|
|
28
28
|
path: string[],
|
|
29
29
|
defaultExpanded?: boolean
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const DebugTerminal = forwardRef<ParseValueRef, DebugTerminalProps>(({
|
|
32
|
+
terminal,
|
|
33
|
+
path,
|
|
34
|
+
defaultExpanded
|
|
35
|
+
}, ref) => {
|
|
33
36
|
const [showing, setShowing] = useState(!!defaultExpanded);
|
|
34
37
|
|
|
38
|
+
useImperativeHandle(ref, () => ({
|
|
39
|
+
name: `Terminal ${terminal.name}`,
|
|
40
|
+
copyValue: terminal.getLog().reduce((prev, curr) => prev + `${curr.role === LogRole.Input ? '> ' : `${LogType[curr.type]}: `}${JSON.stringify(curr.value)}` + "\n", ""),
|
|
41
|
+
menu: [
|
|
42
|
+
{
|
|
43
|
+
id: "terminal-clear",
|
|
44
|
+
label: "Clear",
|
|
45
|
+
onClick: () => terminal.clear(),
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}), [terminal.getLog()]);
|
|
49
|
+
|
|
35
50
|
const handleToggle = (value: boolean) => {
|
|
36
51
|
setShowing(value);
|
|
37
52
|
}
|
|
@@ -60,8 +75,8 @@ export default function DebugTerminal({ terminal, path, defaultExpanded }: Debug
|
|
|
60
75
|
|
|
61
76
|
<TerminalMain show={showing} />
|
|
62
77
|
</LocalTerminalContext.Provider>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
78
|
+
);
|
|
79
|
+
});
|
|
65
80
|
|
|
66
81
|
interface TerminalMainProps {
|
|
67
82
|
show: boolean
|
|
@@ -338,4 +353,6 @@ function TerminalInput()
|
|
|
338
353
|
</button>
|
|
339
354
|
</div>
|
|
340
355
|
)
|
|
341
|
-
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export default DebugTerminal;
|
|
@@ -1,23 +1,37 @@
|
|
|
1
|
-
import React, { useLayoutEffect, useMemo, useState } from "react";
|
|
1
|
+
import React, { forwardRef, useImperativeHandle, useLayoutEffect, useMemo, useState } from "react";
|
|
2
2
|
import useDebugger from "../DebuggerContext";
|
|
3
3
|
import { Char_Bracket, Char_Comma, Chevron_Toggle } from "../DebuggerSymbols";
|
|
4
4
|
import ParseValueSimple from "./DebugParserSimple";
|
|
5
|
-
import ParseValue from "./DebugParser";
|
|
5
|
+
import ParseValue, { ParseValueRef } from "./DebugParser";
|
|
6
6
|
|
|
7
7
|
interface ValueArrayProps {
|
|
8
8
|
value: unknown[],
|
|
9
9
|
path: string[],
|
|
10
10
|
defaultExpanded?: boolean
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const ValueArray = forwardRef<ParseValueRef, ValueArrayProps>(({
|
|
13
13
|
value,
|
|
14
14
|
path,
|
|
15
15
|
defaultExpanded = false
|
|
16
|
-
}
|
|
16
|
+
}, ref) => {
|
|
17
17
|
const debug = useDebugger();
|
|
18
18
|
|
|
19
19
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
20
20
|
|
|
21
|
+
useImperativeHandle(ref, () => ({
|
|
22
|
+
name: `Array (${value.length})`,
|
|
23
|
+
copyValue: JSON.stringify(value),
|
|
24
|
+
menu: [
|
|
25
|
+
{
|
|
26
|
+
id: "open-toggle",
|
|
27
|
+
label: expanded ? "Collapse" : "Expand",
|
|
28
|
+
onClick: () => {
|
|
29
|
+
setExpanded(!expanded);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}), [value, expanded]);
|
|
34
|
+
|
|
21
35
|
const children = useMemo(() => {
|
|
22
36
|
const children: React.ReactNode[] = [];
|
|
23
37
|
|
|
@@ -106,7 +120,6 @@ export default function ValueArray({
|
|
|
106
120
|
onClick={() => setExpanded(true)}
|
|
107
121
|
>
|
|
108
122
|
<Char_Bracket text="[" />
|
|
109
|
-
{/* <span className="text-zinc-500">...</span> */}
|
|
110
123
|
{collapsedPreview}
|
|
111
124
|
<Char_Bracket text="]" />
|
|
112
125
|
</div>
|
|
@@ -114,7 +127,7 @@ export default function ValueArray({
|
|
|
114
127
|
}
|
|
115
128
|
</>
|
|
116
129
|
);
|
|
117
|
-
}
|
|
130
|
+
});
|
|
118
131
|
|
|
119
132
|
interface CompactArrayItemProps {
|
|
120
133
|
value: unknown,
|
|
@@ -140,4 +153,6 @@ function CompactArrayItem({
|
|
|
140
153
|
{isLast && (<Char_Comma />)}
|
|
141
154
|
</>
|
|
142
155
|
)
|
|
143
|
-
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default ValueArray;
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
+
import { forwardRef, useImperativeHandle } from "react"
|
|
1
2
|
import ValueKeyword from "./ValueKeyword"
|
|
3
|
+
import { ParseValueRef } from "./DebugParser";
|
|
2
4
|
|
|
3
5
|
interface ValueBooleanProps {
|
|
4
6
|
value: boolean
|
|
5
7
|
}
|
|
6
|
-
|
|
8
|
+
const ValueBoolean = forwardRef<ParseValueRef, ValueBooleanProps>(({
|
|
7
9
|
value
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
}, ref) => {
|
|
11
|
+
useImperativeHandle(ref, () => ({
|
|
12
|
+
name: "Boolean",
|
|
13
|
+
copyValue: value ? "true" : "false"
|
|
14
|
+
}), [value]);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<ValueKeyword value={value ? 'true' : 'false'} />
|
|
18
|
+
)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export default ValueBoolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { forwardRef, useImperativeHandle } from "react";
|
|
2
|
+
import ValueKeyword from "./ValueKeyword";
|
|
3
|
+
import { ParseValueRef } from "./DebugParser";
|
|
4
|
+
|
|
5
|
+
interface ValueConstantProps {
|
|
6
|
+
value: string
|
|
7
|
+
}
|
|
8
|
+
const ValueConstant = forwardRef<ParseValueRef, ValueConstantProps>(({
|
|
9
|
+
value
|
|
10
|
+
}, ref) => {
|
|
11
|
+
useImperativeHandle(ref, () => ({
|
|
12
|
+
name: value,
|
|
13
|
+
copyValue: value
|
|
14
|
+
}), [value]);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<ValueKeyword value={value} />
|
|
18
|
+
)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export default ValueConstant;
|