@hrnec06/react_utils 1.6.0 → 1.7.1
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 +1 -0
- package/package.json +2 -2
- 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 +70 -18
- package/src/components/Debugger/DebuggerTerminal.tsx +3 -3
- package/src/components/Debugger/parser/DebugParser.tsx +117 -14
- package/src/components/Debugger/parser/DebugTerminal.tsx +41 -7
- package/src/components/Debugger/parser/ValueArray.tsx +31 -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 +69 -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/useEvent.ts +15 -0
- package/src/hooks/useLatestRef.ts +12 -0
- package/src/hooks/useSyncRef.ts +17 -0
- package/src/hooks/useTransition.ts +2 -1
- package/src/index.ts +12 -3
- package/src/lib/errors/ContextError.ts +11 -0
- package/src/hooks/useUpdatedRef.ts +0 -12
|
@@ -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,148 @@ 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 || ref.copyValue === undefined)
|
|
120
|
+
? undefined
|
|
121
|
+
: () => navigator.clipboard.writeText(ref.copyValue!);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<ContextMenu
|
|
125
|
+
className={clsx(
|
|
126
|
+
"inline align-middle h-full",
|
|
127
|
+
"outline-sky-500 outline-offset-2",
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
$autoGenerateWrapper
|
|
131
|
+
$id={ctxmLabel}
|
|
132
|
+
$inherit={(menu) => ({
|
|
133
|
+
parent: menu.id,
|
|
134
|
+
children: menu.menu
|
|
135
|
+
})}
|
|
136
|
+
$menu={[
|
|
137
|
+
[
|
|
138
|
+
{
|
|
139
|
+
id: 'copy',
|
|
140
|
+
label: "Copy",
|
|
141
|
+
description: ref?.name,
|
|
142
|
+
onClick: handleCopy
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
ref?.menu ?? null
|
|
146
|
+
]}
|
|
147
|
+
>
|
|
148
|
+
{children}
|
|
149
|
+
</ContextMenu>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
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,42 @@ 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
|
+
const logToString = (log: LogItem) => {
|
|
40
|
+
let value: string;
|
|
41
|
+
|
|
42
|
+
try
|
|
43
|
+
{
|
|
44
|
+
value = JSON.stringify(log.value);
|
|
45
|
+
}
|
|
46
|
+
catch (error)
|
|
47
|
+
{
|
|
48
|
+
value = `Error parsing value (${typeof log.value})`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return `${log.role === LogRole.Input ? '> ' : `${LogType[log.type]}: `}${value}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: `Terminal ${terminal.name}`,
|
|
56
|
+
copyValue: terminal.getLog().reduce((prev, curr) => prev + logToString(curr) + "\n", ""),
|
|
57
|
+
menu: [
|
|
58
|
+
{
|
|
59
|
+
id: "terminal-clear",
|
|
60
|
+
label: "Clear",
|
|
61
|
+
onClick: () => terminal.clear(),
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}, [terminal.getLog()]);
|
|
66
|
+
|
|
35
67
|
const handleToggle = (value: boolean) => {
|
|
36
68
|
setShowing(value);
|
|
37
69
|
}
|
|
@@ -60,8 +92,8 @@ export default function DebugTerminal({ terminal, path, defaultExpanded }: Debug
|
|
|
60
92
|
|
|
61
93
|
<TerminalMain show={showing} />
|
|
62
94
|
</LocalTerminalContext.Provider>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
65
97
|
|
|
66
98
|
interface TerminalMainProps {
|
|
67
99
|
show: boolean
|
|
@@ -338,4 +370,6 @@ function TerminalInput()
|
|
|
338
370
|
</button>
|
|
339
371
|
</div>
|
|
340
372
|
)
|
|
341
|
-
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export default DebugTerminal;
|
|
@@ -1,23 +1,46 @@
|
|
|
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
|
+
import { Optional } from "@hrnec06/util";
|
|
6
7
|
|
|
7
8
|
interface ValueArrayProps {
|
|
8
9
|
value: unknown[],
|
|
9
10
|
path: string[],
|
|
10
11
|
defaultExpanded?: boolean
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
const ValueArray = forwardRef<ParseValueRef, ValueArrayProps>(({
|
|
13
14
|
value,
|
|
14
15
|
path,
|
|
15
16
|
defaultExpanded = false
|
|
16
|
-
}
|
|
17
|
+
}, ref) => {
|
|
17
18
|
const debug = useDebugger();
|
|
18
19
|
|
|
19
20
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
20
21
|
|
|
22
|
+
useImperativeHandle(ref, () => {
|
|
23
|
+
let copyValue: Optional<string> = undefined;
|
|
24
|
+
try
|
|
25
|
+
{
|
|
26
|
+
copyValue = JSON.stringify(value);
|
|
27
|
+
} catch {}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
name: `Array (${value.length})`,
|
|
31
|
+
copyValue: copyValue,
|
|
32
|
+
menu: [
|
|
33
|
+
{
|
|
34
|
+
id: "open-toggle",
|
|
35
|
+
label: expanded ? "Collapse" : "Expand",
|
|
36
|
+
onClick: () => {
|
|
37
|
+
setExpanded(!expanded);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
}, [value, expanded]);
|
|
43
|
+
|
|
21
44
|
const children = useMemo(() => {
|
|
22
45
|
const children: React.ReactNode[] = [];
|
|
23
46
|
|
|
@@ -106,7 +129,6 @@ export default function ValueArray({
|
|
|
106
129
|
onClick={() => setExpanded(true)}
|
|
107
130
|
>
|
|
108
131
|
<Char_Bracket text="[" />
|
|
109
|
-
{/* <span className="text-zinc-500">...</span> */}
|
|
110
132
|
{collapsedPreview}
|
|
111
133
|
<Char_Bracket text="]" />
|
|
112
134
|
</div>
|
|
@@ -114,7 +136,7 @@ export default function ValueArray({
|
|
|
114
136
|
}
|
|
115
137
|
</>
|
|
116
138
|
);
|
|
117
|
-
}
|
|
139
|
+
});
|
|
118
140
|
|
|
119
141
|
interface CompactArrayItemProps {
|
|
120
142
|
value: unknown,
|
|
@@ -140,4 +162,6 @@ function CompactArrayItem({
|
|
|
140
162
|
{isLast && (<Char_Comma />)}
|
|
141
163
|
</>
|
|
142
164
|
)
|
|
143
|
-
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
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;
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import { forwardRef, useImperativeHandle } from "react";
|
|
2
|
+
import { ParseValueRef } from "./DebugParser";
|
|
3
|
+
|
|
1
4
|
interface ValueFunctionProps {
|
|
2
5
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
3
6
|
value: Function
|
|
4
7
|
}
|
|
5
|
-
|
|
8
|
+
const ValueFunction = forwardRef<ParseValueRef, ValueFunctionProps>(({
|
|
6
9
|
value
|
|
7
|
-
}
|
|
10
|
+
}, ref) => {
|
|
11
|
+
useImperativeHandle(ref, () => ({
|
|
12
|
+
name: "Function",
|
|
13
|
+
copyValue: value.toString()
|
|
14
|
+
}), [value]);
|
|
15
|
+
|
|
8
16
|
const argsMatch = value.toString().match(/^[^(]*\(\s*([^)]*)\)/);
|
|
9
17
|
let args: string = '';
|
|
10
18
|
|
|
@@ -12,14 +20,18 @@ export default function ValueFunction({
|
|
|
12
20
|
args = argsMatch[1]!;
|
|
13
21
|
}
|
|
14
22
|
|
|
23
|
+
const displayValue = `${value.name}(${args})`;
|
|
24
|
+
|
|
15
25
|
return (
|
|
16
26
|
<>
|
|
17
27
|
<span className="text-[#f2824a]">
|
|
18
28
|
{'ƒ '}
|
|
19
29
|
</span>
|
|
20
30
|
<span className="text-white">
|
|
21
|
-
{
|
|
31
|
+
{displayValue}
|
|
22
32
|
</span>
|
|
23
33
|
</>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default ValueFunction;
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import { forwardRef, useImperativeHandle } from "react"
|
|
2
|
+
import { ParseValueRef } from "./DebugParser";
|
|
3
|
+
|
|
1
4
|
interface ValueNumberProps {
|
|
2
5
|
value: number | bigint | string
|
|
3
6
|
}
|
|
4
|
-
|
|
7
|
+
const ValueNumber = forwardRef<ParseValueRef, ValueNumberProps>(({
|
|
5
8
|
value
|
|
6
|
-
}
|
|
9
|
+
}, ref) => {
|
|
10
|
+
useImperativeHandle(ref, () => ({
|
|
11
|
+
name: "Number",
|
|
12
|
+
copyValue: value.toString()
|
|
13
|
+
}), [value]);
|
|
14
|
+
|
|
7
15
|
return (
|
|
8
16
|
<span className="text-[#a7ce9b]">
|
|
9
17
|
{value}
|
|
10
18
|
</span>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export default ValueNumber;
|
|
@@ -1,25 +1,49 @@
|
|
|
1
|
-
import React, { useLayoutEffect, useMemo, useState } from "react";
|
|
1
|
+
import React, { forwardRef, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import useDebugger from "../DebuggerContext";
|
|
3
|
-
import { entries, keys } from "@hrnec06/util";
|
|
3
|
+
import { entries, keys, Nullable, Optional } from "@hrnec06/util";
|
|
4
4
|
import { Char_Bracket, Char_Colon, Char_Comma, Chevron_Toggle } from "../DebuggerSymbols";
|
|
5
|
-
import ParseValue from "./DebugParser";
|
|
5
|
+
import ParseValue, { DebugCTXMProvider, ParseValueRef } from "./DebugParser";
|
|
6
|
+
import useSyncRef from "../../../hooks/useSyncRef";
|
|
7
|
+
import { useCTXMListener } from "../../ContextMenu/ContextMenuCtx";
|
|
6
8
|
|
|
7
9
|
interface ValueObjectProps {
|
|
8
10
|
value: object,
|
|
9
11
|
path: string[],
|
|
10
12
|
defaultExpanded?: boolean
|
|
11
13
|
}
|
|
12
|
-
|
|
14
|
+
const ValueObject = forwardRef<ParseValueRef, ValueObjectProps>(({
|
|
13
15
|
value,
|
|
14
16
|
path,
|
|
15
17
|
defaultExpanded = false
|
|
16
|
-
}
|
|
18
|
+
}, ref) => {
|
|
17
19
|
const debug = useDebugger();
|
|
18
20
|
|
|
19
21
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
20
22
|
|
|
21
23
|
const objectEntries = entries(value);
|
|
22
24
|
|
|
25
|
+
useImperativeHandle(ref, () => {
|
|
26
|
+
let copyValue: Optional<string> = undefined;
|
|
27
|
+
try
|
|
28
|
+
{
|
|
29
|
+
copyValue = JSON.stringify(value);
|
|
30
|
+
} catch {}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
name: `Object {${objectEntries.length}}`,
|
|
34
|
+
copyValue: copyValue,
|
|
35
|
+
menu: [
|
|
36
|
+
{
|
|
37
|
+
id: "open-toggle",
|
|
38
|
+
label: expanded ? "Collapse" : "Expand",
|
|
39
|
+
onClick: () => {
|
|
40
|
+
setExpanded(!expanded);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
};
|
|
45
|
+
}, [value, expanded]);
|
|
46
|
+
|
|
23
47
|
const collapsedPreview = useMemo(() => {
|
|
24
48
|
const children: React.ReactNode[] = [];
|
|
25
49
|
const keyList = keys(value);
|
|
@@ -48,6 +72,8 @@ export default function ValueObject({
|
|
|
48
72
|
setExpanded(state);
|
|
49
73
|
}
|
|
50
74
|
|
|
75
|
+
const ctxmListener = useCTXMListener();
|
|
76
|
+
|
|
51
77
|
return (
|
|
52
78
|
<>
|
|
53
79
|
<Chevron_Toggle expanded={expanded} onToggle={handleExpand} />
|
|
@@ -65,16 +91,13 @@ export default function ValueObject({
|
|
|
65
91
|
{
|
|
66
92
|
objectEntries.map(([key, value], ix) => {
|
|
67
93
|
return (
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<Char_Comma />
|
|
76
|
-
)}
|
|
77
|
-
</li>
|
|
94
|
+
<ValueObjectEntry
|
|
95
|
+
key={ix}
|
|
96
|
+
entryKey={key}
|
|
97
|
+
value={value}
|
|
98
|
+
path={path}
|
|
99
|
+
isLast={ix === objectEntries.length - 1}
|
|
100
|
+
/>
|
|
78
101
|
);
|
|
79
102
|
})
|
|
80
103
|
}
|
|
@@ -97,7 +120,7 @@ export default function ValueObject({
|
|
|
97
120
|
}
|
|
98
121
|
</>
|
|
99
122
|
);
|
|
100
|
-
}
|
|
123
|
+
});
|
|
101
124
|
|
|
102
125
|
interface ValueObjectKeyProps {
|
|
103
126
|
text: string,
|
|
@@ -110,4 +133,33 @@ function ValueObjectKey({
|
|
|
110
133
|
{text}
|
|
111
134
|
</span>
|
|
112
135
|
)
|
|
113
|
-
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface ValueObjectEntryProps {
|
|
139
|
+
entryKey: string,
|
|
140
|
+
value: unknown,
|
|
141
|
+
path: string[],
|
|
142
|
+
isLast: boolean
|
|
143
|
+
}
|
|
144
|
+
function ValueObjectEntry({
|
|
145
|
+
entryKey: key,
|
|
146
|
+
value,
|
|
147
|
+
path,
|
|
148
|
+
isLast
|
|
149
|
+
}: ValueObjectEntryProps)
|
|
150
|
+
{
|
|
151
|
+
return (
|
|
152
|
+
<li>
|
|
153
|
+
<ParseValue value={value} path={[...path, `${key}`]}>
|
|
154
|
+
<ValueObjectKey text={key} />
|
|
155
|
+
<Char_Colon />
|
|
156
|
+
</ParseValue>
|
|
157
|
+
|
|
158
|
+
{!isLast && (
|
|
159
|
+
<Char_Comma />
|
|
160
|
+
)}
|
|
161
|
+
</li>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default ValueObject;
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { padString } from "@hrnec06/util"
|
|
2
|
+
import { forwardRef, useImperativeHandle } from "react"
|
|
3
|
+
import { ParseValueRef } from "./DebugParser";
|
|
2
4
|
|
|
3
5
|
interface ValueStringProps {
|
|
4
6
|
value: string,
|
|
5
7
|
noEncase?: boolean
|
|
6
8
|
}
|
|
7
|
-
|
|
9
|
+
const ValueString = forwardRef<ParseValueRef, ValueStringProps>(({
|
|
8
10
|
value,
|
|
9
11
|
noEncase = false
|
|
10
|
-
}
|
|
12
|
+
}, ref) => {
|
|
13
|
+
useImperativeHandle(ref, () => ({
|
|
14
|
+
name: "String",
|
|
15
|
+
copyValue: value
|
|
16
|
+
}), [value]);
|
|
17
|
+
|
|
11
18
|
return (
|
|
12
19
|
<span className="text-[#ce9178]">
|
|
13
20
|
{padString(value, noEncase ? 0 : 1, '"')}
|
|
14
21
|
</span>
|
|
15
|
-
)
|
|
16
|
-
}
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default ValueString;
|
|
@@ -3,7 +3,7 @@ import clsx from "clsx";
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
4
|
import DragAreas from "./DragAreas";
|
|
5
5
|
import useSignal from "../../hooks/useSignal";
|
|
6
|
-
import useUpdatedRef from "../../hooks/
|
|
6
|
+
import useUpdatedRef from "../../hooks/useLatestRef";
|
|
7
7
|
import useListener from "../../hooks/useListener";
|
|
8
8
|
import { ResizeableBoxContext } from "./ResizeableBoxContext";
|
|
9
9
|
import RB from "./ResizeableBox.types";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export default function useDebounce<T>(value: T, delay: number, updateOnUnmount: boolean = false): T
|
|
4
|
+
{
|
|
5
|
+
const [debounced, setValue] = useState(value);
|
|
6
|
+
|
|
7
|
+
const update = useCallback((value: T) => setValue(value), []);
|
|
8
|
+
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
let updated = false;
|
|
11
|
+
|
|
12
|
+
const t = setTimeout(() => {
|
|
13
|
+
update(value);
|
|
14
|
+
updated = true;
|
|
15
|
+
}, delay);
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
clearTimeout(t);
|
|
19
|
+
|
|
20
|
+
if (!updated && updateOnUnmount)
|
|
21
|
+
update(value);
|
|
22
|
+
}
|
|
23
|
+
}, [value]);
|
|
24
|
+
|
|
25
|
+
return debounced;
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import useLatestRef from "./useLatestRef";
|
|
3
|
+
|
|
4
|
+
export default function useEvent<
|
|
5
|
+
F extends (...args: any[]) => any,
|
|
6
|
+
P extends any[] = Parameters<F>,
|
|
7
|
+
R = ReturnType<F>,
|
|
8
|
+
> (
|
|
9
|
+
cb: (...args: P) => R
|
|
10
|
+
)
|
|
11
|
+
{
|
|
12
|
+
const cache = useLatestRef(cb);
|
|
13
|
+
|
|
14
|
+
return useCallback((...args: P) => cache.current(...args), [cache]);
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react";
|
|
2
|
+
import useUpdateEffect from "./useUpdateEffect";
|
|
3
|
+
|
|
4
|
+
export default function useLatestRef<V>(value: V): React.RefObject<V> {
|
|
5
|
+
const ref = useRef(value);
|
|
6
|
+
|
|
7
|
+
useLayoutEffect(() => {
|
|
8
|
+
ref.current = value;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return ref;
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Nullable } from '@hrnec06/util'
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
3
|
+
import useEvent from './useEvent';
|
|
4
|
+
|
|
5
|
+
export default function useSyncRef<T>(
|
|
6
|
+
ref: React.RefObject<T>,
|
|
7
|
+
state: (value: T) => void
|
|
8
|
+
)
|
|
9
|
+
{
|
|
10
|
+
const syncRefs = useEvent((value: T) => {
|
|
11
|
+
ref.current = value;
|
|
12
|
+
|
|
13
|
+
state(value);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return syncRefs;
|
|
17
|
+
}
|