@hrnec06/react_utils 1.1.0 → 1.2.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 +3 -11
- package/src/debugger/DebugParser.tsx +401 -0
- package/src/debugger/Debugger.tsx +297 -0
- package/src/debugger/DebuggerContext.ts +41 -0
- package/src/debugger/DebuggerLogic.ts +30 -0
- package/src/debugger/DebuggerScrollBar.tsx +108 -0
- package/src/debugger/DebuggerSymbols.tsx +63 -0
- package/src/debugger/DebuggerWindowResize.tsx +136 -0
- package/src/hooks/useKeyListener.ts +47 -0
- package/src/hooks/useListener.ts +11 -0
- package/src/hooks/useSignal.ts +31 -0
- package/src/hooks/useUpdateEffect.ts +14 -0
- package/src/hooks/useUpdatedRef.ts +12 -0
- package/src/hooks/useWindowSize.ts +22 -0
- package/src/index.tsx +17 -0
- package/dist/index.d.mts +0 -40
- package/dist/index.d.ts +0 -40
- package/dist/index.js +0 -934
- package/dist/index.mjs +0 -891
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { minmax, Nullable, Vector2 } from "@hrnec06/util";
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import {DebuggerContext} from "./DebuggerContext";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import ResizeBorder from "./DebuggerWindowResize";
|
|
6
|
+
import ParseValue from "./DebugParser";
|
|
7
|
+
import ScrollBar from "./DebuggerScrollBar";
|
|
8
|
+
import useKeyListener from "../hooks/useKeyListener";
|
|
9
|
+
import useUpdateEffect from "../hooks/useUpdateEffect";
|
|
10
|
+
import useUpdatedRef from "../hooks/useUpdatedRef";
|
|
11
|
+
|
|
12
|
+
interface DebugProps {
|
|
13
|
+
value: unknown,
|
|
14
|
+
openPaths?: string[],
|
|
15
|
+
excludePaths?: string[],
|
|
16
|
+
size?: 'normal' | 'big' | 'tiny',
|
|
17
|
+
compactArrays?: number,
|
|
18
|
+
autoHeight?: boolean,
|
|
19
|
+
openRoot?: boolean
|
|
20
|
+
}
|
|
21
|
+
export default function Debug({
|
|
22
|
+
value,
|
|
23
|
+
openPaths,
|
|
24
|
+
excludePaths,
|
|
25
|
+
size = 'normal',
|
|
26
|
+
compactArrays = 1,
|
|
27
|
+
autoHeight = false,
|
|
28
|
+
openRoot = true
|
|
29
|
+
}: DebugProps) {
|
|
30
|
+
// Config
|
|
31
|
+
const LS_POS_KEY = 'debugger_position';
|
|
32
|
+
const LS_SIZE_KEY = 'debugger_size';
|
|
33
|
+
const LS_EXPAND_KEY = 'debugger_expanded';
|
|
34
|
+
|
|
35
|
+
// Keybinds
|
|
36
|
+
const f9_pressed = useKeyListener('F9');
|
|
37
|
+
|
|
38
|
+
// Position
|
|
39
|
+
const [position, setPosition] = useState<Vector2>([0, 0]);
|
|
40
|
+
const [grab, setGrab] = useState<Nullable<{
|
|
41
|
+
windowOrigin: Vector2,
|
|
42
|
+
positionOrigin: Vector2
|
|
43
|
+
}>>(null);
|
|
44
|
+
const [grabOffset, setGrabOffset] = useState<Nullable<Vector2>>(null);
|
|
45
|
+
|
|
46
|
+
// Resize
|
|
47
|
+
const [windowSize, setWindowSize] = useState<Vector2>([500, 500]);
|
|
48
|
+
|
|
49
|
+
// Scroll
|
|
50
|
+
const [scrollHeight, setScrollHeight] = useState(0);
|
|
51
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
52
|
+
const [scrollProgress, setScrollProgress] = useState(0);
|
|
53
|
+
|
|
54
|
+
const font = useMemo(() => {
|
|
55
|
+
switch (size) {
|
|
56
|
+
case 'tiny': return { fontSize: 12, lineHeight: 16 };
|
|
57
|
+
case 'normal': return { fontSize: 14, lineHeight: 20 };
|
|
58
|
+
case 'big': return { fontSize: 16, lineHeight: 24 };
|
|
59
|
+
}
|
|
60
|
+
}, [size]);
|
|
61
|
+
|
|
62
|
+
// HTML Refs
|
|
63
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
64
|
+
|
|
65
|
+
// Drag handler
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const mouseUp = (e: MouseEvent) => {
|
|
68
|
+
if (!grab) return;
|
|
69
|
+
|
|
70
|
+
setPosition(finalPositionRef.current);
|
|
71
|
+
setGrab(null);
|
|
72
|
+
setGrabOffset(null);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const mouseMove = (e: MouseEvent) => {
|
|
76
|
+
if (!grab) return;
|
|
77
|
+
|
|
78
|
+
setGrabOffset([
|
|
79
|
+
e.clientX - grab.windowOrigin[0],
|
|
80
|
+
e.clientY - grab.windowOrigin[1]
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
document.addEventListener('mouseup', mouseUp);
|
|
85
|
+
document.addEventListener('mousemove', mouseMove);
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
document.removeEventListener('mouseup', mouseUp);
|
|
89
|
+
document.removeEventListener('mousemove', mouseMove);
|
|
90
|
+
}
|
|
91
|
+
}, [grab]);
|
|
92
|
+
|
|
93
|
+
// Scrolling
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
updateScrollHeight();
|
|
96
|
+
}, [windowSize, value, size, containerRef]);
|
|
97
|
+
|
|
98
|
+
const isScrollable = useMemo(() => scrollHeight > 0, [scrollHeight]);
|
|
99
|
+
|
|
100
|
+
// Load saved position
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
try {
|
|
103
|
+
const saved_position = localStorage.getItem(LS_POS_KEY);
|
|
104
|
+
if (saved_position) {
|
|
105
|
+
const [v1, v2] = saved_position.split(',');
|
|
106
|
+
|
|
107
|
+
if (v1 === undefined || v2 === undefined)
|
|
108
|
+
throw new Error('Invalid vector: ' + saved_position);
|
|
109
|
+
|
|
110
|
+
const [v1_p, v2_p] = [parseInt(v1), parseInt(v2)];
|
|
111
|
+
|
|
112
|
+
if (isNaN(v1_p) || isNaN(v2_p))
|
|
113
|
+
throw new Error('Invalid vector values: ' + saved_position);
|
|
114
|
+
|
|
115
|
+
setPosition([v1_p, v2_p]);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(`Error loading saved position: `, error);
|
|
119
|
+
|
|
120
|
+
localStorage.removeItem(LS_POS_KEY);
|
|
121
|
+
}
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
// Load saved size
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
try {
|
|
127
|
+
const saved_size = localStorage.getItem(LS_SIZE_KEY);
|
|
128
|
+
if (saved_size) {
|
|
129
|
+
const [v1, v2] = saved_size.split(',');
|
|
130
|
+
|
|
131
|
+
if (v1 === undefined || v2 === undefined)
|
|
132
|
+
throw new Error('Invalid vector: ' + saved_size);
|
|
133
|
+
|
|
134
|
+
const [v1_p, v2_p] = [parseInt(v1), parseInt(v2)];
|
|
135
|
+
|
|
136
|
+
if (isNaN(v1_p) || isNaN(v2_p))
|
|
137
|
+
throw new Error('Invalid vector values: ' + saved_size);
|
|
138
|
+
|
|
139
|
+
setWindowSize([v1_p, v2_p]);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(`Error loading saved size: `, error);
|
|
143
|
+
|
|
144
|
+
localStorage.removeItem(LS_POS_KEY);
|
|
145
|
+
}
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
// Save saved position
|
|
149
|
+
useUpdateEffect(() => {
|
|
150
|
+
localStorage.setItem(LS_POS_KEY, `${position[0]},${position[1]}`);
|
|
151
|
+
}, [position]);
|
|
152
|
+
|
|
153
|
+
// Save saved size
|
|
154
|
+
useUpdateEffect(() => {
|
|
155
|
+
localStorage.setItem(LS_SIZE_KEY, `${windowSize[0]},${windowSize[1]}`);
|
|
156
|
+
}, [windowSize]);
|
|
157
|
+
|
|
158
|
+
// Reset position
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!f9_pressed)
|
|
161
|
+
return;
|
|
162
|
+
|
|
163
|
+
setPosition([0, 0]);
|
|
164
|
+
}, [f9_pressed]);
|
|
165
|
+
|
|
166
|
+
// Compute window position
|
|
167
|
+
const finalPosition: Vector2 = useMemo(() => {
|
|
168
|
+
return [
|
|
169
|
+
position[0] + (grabOffset?.[0] ?? 0),
|
|
170
|
+
position[1] + (grabOffset?.[1] ?? 0),
|
|
171
|
+
]
|
|
172
|
+
}, [position, grabOffset]);
|
|
173
|
+
const finalPositionRef = useUpdatedRef(finalPosition);
|
|
174
|
+
|
|
175
|
+
// Logic
|
|
176
|
+
|
|
177
|
+
const calculateContainerHeight = () => {
|
|
178
|
+
if (!containerRef.current) return 0;
|
|
179
|
+
|
|
180
|
+
return containerRef.current.clientHeight;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const calculateScrollHeight = () => {
|
|
184
|
+
if (!containerRef.current) return 0;
|
|
185
|
+
|
|
186
|
+
return containerRef.current.scrollHeight - calculateContainerHeight();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const updateScrollHeight = () => {
|
|
190
|
+
if (!containerRef.current) return;
|
|
191
|
+
|
|
192
|
+
setScrollHeight(calculateScrollHeight());
|
|
193
|
+
setContainerHeight(calculateContainerHeight());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handlers
|
|
197
|
+
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
|
|
200
|
+
setGrab({
|
|
201
|
+
positionOrigin: position,
|
|
202
|
+
windowOrigin: [e.clientX, e.clientY]
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const handleExpandChange = (path: string[], state: boolean) => {
|
|
207
|
+
if (!containerRef.current) return;
|
|
208
|
+
|
|
209
|
+
setScrollProgress(minmax((scrollProgress * scrollHeight) / calculateScrollHeight(), 0, 1));
|
|
210
|
+
|
|
211
|
+
updateScrollHeight();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const handleWheel = (e: React.WheelEvent<HTMLDivElement>) => {
|
|
215
|
+
const moveByPX = font.lineHeight * 3;
|
|
216
|
+
|
|
217
|
+
let addProgress = moveByPX / scrollHeight;
|
|
218
|
+
|
|
219
|
+
if (e.deltaY < 0)
|
|
220
|
+
addProgress *= -1;
|
|
221
|
+
|
|
222
|
+
setScrollProgress(p => minmax(p + addProgress, 0, 1));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<DebuggerContext.Provider
|
|
227
|
+
value={{
|
|
228
|
+
paths: {
|
|
229
|
+
exclude: excludePaths ?? [],
|
|
230
|
+
open: openPaths ?? []
|
|
231
|
+
},
|
|
232
|
+
options: {
|
|
233
|
+
compactArrays: compactArrays,
|
|
234
|
+
autoHeight: autoHeight,
|
|
235
|
+
openRoot: openRoot
|
|
236
|
+
},
|
|
237
|
+
window: {
|
|
238
|
+
resize: setWindowSize,
|
|
239
|
+
size: windowSize
|
|
240
|
+
},
|
|
241
|
+
placement: {
|
|
242
|
+
reposition: setPosition,
|
|
243
|
+
position: position
|
|
244
|
+
},
|
|
245
|
+
event: {
|
|
246
|
+
expand: handleExpandChange
|
|
247
|
+
},
|
|
248
|
+
scroll: {
|
|
249
|
+
setProgress: setScrollProgress,
|
|
250
|
+
progress: scrollProgress,
|
|
251
|
+
isScrollable: isScrollable
|
|
252
|
+
}
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
<div className="fixed pointer-events-none w-full h-full left-0 top-0 overflow-hidden">
|
|
256
|
+
<div
|
|
257
|
+
className={clsx(
|
|
258
|
+
"absolute font-jetbrains pointer-events-auto",
|
|
259
|
+
size === 'tiny' && 'text-xs',
|
|
260
|
+
size === 'normal' && 'text-sm',
|
|
261
|
+
size === 'big' && 'text-base'
|
|
262
|
+
)}
|
|
263
|
+
style={{
|
|
264
|
+
left: finalPosition[0],
|
|
265
|
+
top: finalPosition[1],
|
|
266
|
+
width: windowSize[0],
|
|
267
|
+
height: windowSize[1],
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
<ResizeBorder />
|
|
271
|
+
|
|
272
|
+
<div
|
|
273
|
+
onWheel={handleWheel}
|
|
274
|
+
className="bg-[#1f1f1f] shadow-lg rounded-md border border-primary-400 w-full h-full overflow-hidden flex"
|
|
275
|
+
>
|
|
276
|
+
<div
|
|
277
|
+
ref={containerRef}
|
|
278
|
+
onMouseDown={handleMouseDown}
|
|
279
|
+
className=" cursor-grab w-full"
|
|
280
|
+
style={{
|
|
281
|
+
transform: `translateY(${-(scrollProgress * scrollHeight)}px)`
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
284
|
+
<div className="p-3 pl-5">
|
|
285
|
+
<ParseValue value={value} />
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
<ScrollBar
|
|
289
|
+
containerHeight={containerHeight}
|
|
290
|
+
scrollHeight={scrollHeight}
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</DebuggerContext.Provider>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Optional, ReactUtils, Vector2 } from "@hrnec06/util";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
interface DebuggerContext {
|
|
5
|
+
paths: {
|
|
6
|
+
open: string[],
|
|
7
|
+
exclude: string[]
|
|
8
|
+
},
|
|
9
|
+
options: {
|
|
10
|
+
compactArrays: number,
|
|
11
|
+
autoHeight: boolean,
|
|
12
|
+
openRoot: boolean
|
|
13
|
+
},
|
|
14
|
+
window: {
|
|
15
|
+
resize: ReactUtils.SetState<Vector2>,
|
|
16
|
+
size: Vector2
|
|
17
|
+
},
|
|
18
|
+
placement: {
|
|
19
|
+
reposition: ReactUtils.SetState<Vector2>,
|
|
20
|
+
position: Vector2
|
|
21
|
+
},
|
|
22
|
+
event: {
|
|
23
|
+
expand: (path: string[], state: boolean) => void
|
|
24
|
+
},
|
|
25
|
+
scroll: {
|
|
26
|
+
setProgress: ReactUtils.SetState<number>,
|
|
27
|
+
progress: number,
|
|
28
|
+
isScrollable: boolean
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const DebuggerContext = createContext<Optional<DebuggerContext>>(undefined);
|
|
33
|
+
|
|
34
|
+
export default function useDebugger() {
|
|
35
|
+
const ctx = useContext(DebuggerContext);
|
|
36
|
+
|
|
37
|
+
if (!ctx)
|
|
38
|
+
throw new Error(`useDebugger may be used only within DebuggerContext.Provider!`);
|
|
39
|
+
|
|
40
|
+
return ctx;
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function matchPath(_path: string[], openPaths: string[]) {
|
|
2
|
+
for (const openPath of openPaths) {
|
|
3
|
+
const matchers = openPath.split('.');
|
|
4
|
+
const path = [..._path];
|
|
5
|
+
|
|
6
|
+
let passed = true;
|
|
7
|
+
|
|
8
|
+
while (path.length && passed) {
|
|
9
|
+
const pathItem = path.shift();
|
|
10
|
+
const matcher = matchers.shift();
|
|
11
|
+
|
|
12
|
+
if (matcher === '**')
|
|
13
|
+
return true;
|
|
14
|
+
|
|
15
|
+
if (matcher === '*') {
|
|
16
|
+
if (path.length > 1)
|
|
17
|
+
passed = false;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (pathItem !== matcher)
|
|
22
|
+
passed = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (path.length === 0 && passed)
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { minmax, Nullable, Vector2 } from "@hrnec06/util";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import useDebugger from "./DebuggerContext";
|
|
4
|
+
import useListener from "../hooks/useListener";
|
|
5
|
+
|
|
6
|
+
interface ScrollBarProps {
|
|
7
|
+
containerHeight: number,
|
|
8
|
+
scrollHeight: number
|
|
9
|
+
}
|
|
10
|
+
export default function ScrollBar({
|
|
11
|
+
containerHeight,
|
|
12
|
+
scrollHeight
|
|
13
|
+
}: ScrollBarProps) {
|
|
14
|
+
const debug = useDebugger();
|
|
15
|
+
|
|
16
|
+
const [wrapperHeight, setWrapperHeight] = useState(0);
|
|
17
|
+
|
|
18
|
+
const barHeight = (containerHeight / (scrollHeight + containerHeight)) * wrapperHeight;
|
|
19
|
+
const barTop = ((wrapperHeight - barHeight) * debug.scroll.progress);
|
|
20
|
+
|
|
21
|
+
const [grab, setGrab] = useState<Nullable<{
|
|
22
|
+
windowOrigin: number,
|
|
23
|
+
positionOrigin: number
|
|
24
|
+
}>>(null);
|
|
25
|
+
const [grabOffset, setGrabOffset] = useState<Nullable<number>>(null);
|
|
26
|
+
|
|
27
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!wrapperRef.current) return;
|
|
31
|
+
|
|
32
|
+
const observer = new ResizeObserver(() => updateWrapperHeight());
|
|
33
|
+
|
|
34
|
+
observer.observe(wrapperRef.current);
|
|
35
|
+
|
|
36
|
+
updateWrapperHeight();
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
observer.disconnect();
|
|
40
|
+
}
|
|
41
|
+
}, [wrapperRef]);
|
|
42
|
+
|
|
43
|
+
useListener(document, 'mouseup', (event) => {
|
|
44
|
+
if (!grab) return;
|
|
45
|
+
|
|
46
|
+
setGrab(null);
|
|
47
|
+
setGrabOffset(null);
|
|
48
|
+
}, [grab]);
|
|
49
|
+
|
|
50
|
+
useListener(document, 'mousemove', (event) => {
|
|
51
|
+
if (!grab) return;
|
|
52
|
+
|
|
53
|
+
setGrabOffset(event.clientY - (grab.windowOrigin - grab.positionOrigin * (wrapperHeight - barHeight)));
|
|
54
|
+
}, [grab]);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (grabOffset === null) return;
|
|
58
|
+
|
|
59
|
+
const fixedOffset = minmax(grabOffset, 0, wrapperHeight - barHeight) / (wrapperHeight - barHeight);
|
|
60
|
+
|
|
61
|
+
debug.scroll.setProgress(fixedOffset);
|
|
62
|
+
}, [grabOffset]);
|
|
63
|
+
|
|
64
|
+
const updateWrapperHeight = () => {
|
|
65
|
+
if (!wrapperRef.current) return;
|
|
66
|
+
|
|
67
|
+
setWrapperHeight(wrapperRef.current.clientHeight);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleWraperMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
|
|
73
|
+
if (grab) return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
|
|
79
|
+
if (grab) return;
|
|
80
|
+
|
|
81
|
+
setGrab({
|
|
82
|
+
positionOrigin: debug.scroll.progress,
|
|
83
|
+
windowOrigin: e.clientY
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="p-1 border-l border-l-primary-600">
|
|
89
|
+
<div
|
|
90
|
+
onMouseDown={handleWraperMouseDown}
|
|
91
|
+
ref={wrapperRef}
|
|
92
|
+
className="h-full flex-shrink-0"
|
|
93
|
+
>
|
|
94
|
+
{debug.scroll.isScrollable && wrapperHeight > 0 && (
|
|
95
|
+
<div
|
|
96
|
+
onMouseDown={handleMouseDown}
|
|
97
|
+
|
|
98
|
+
className="bg-primary-500 hover:bg-primary-400 w-2 rounded-lg"
|
|
99
|
+
style={{
|
|
100
|
+
transform: `translateY(${barTop}px)`,
|
|
101
|
+
height: `${barHeight}px`
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export function Char_Colon() {
|
|
5
|
+
return (
|
|
6
|
+
<span className="text-[#ccccab]">
|
|
7
|
+
{': '}
|
|
8
|
+
</span>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Char_Comma() {
|
|
13
|
+
return (
|
|
14
|
+
<span className="text-[#ccccab]">
|
|
15
|
+
{', '}
|
|
16
|
+
</span>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Char_Bracket_Props {
|
|
21
|
+
text: string
|
|
22
|
+
}
|
|
23
|
+
export function Char_Bracket({
|
|
24
|
+
text
|
|
25
|
+
}: Char_Bracket_Props) {
|
|
26
|
+
return (
|
|
27
|
+
<span className="text-[#da70d6]">
|
|
28
|
+
{text}
|
|
29
|
+
</span>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Chevron_Toggle_Props {
|
|
34
|
+
expanded: boolean,
|
|
35
|
+
onToggle?: (expanded: boolean) => void,
|
|
36
|
+
}
|
|
37
|
+
export function Chevron_Toggle({
|
|
38
|
+
expanded,
|
|
39
|
+
onToggle
|
|
40
|
+
}: Chevron_Toggle_Props) {
|
|
41
|
+
const handleClick = () => {
|
|
42
|
+
onToggle?.(!expanded);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<button
|
|
47
|
+
onClick={handleClick}
|
|
48
|
+
className={clsx(
|
|
49
|
+
"absolute",
|
|
50
|
+
'left-1 translate-y-0.5'
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{
|
|
54
|
+
<ChevronDown
|
|
55
|
+
className={clsx(
|
|
56
|
+
"size-4 text-primary-400 hover:text-zinc-500",
|
|
57
|
+
!expanded && '-rotate-90'
|
|
58
|
+
)}
|
|
59
|
+
/>
|
|
60
|
+
}
|
|
61
|
+
</button>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { cloneVector2, Nullable, removeVector2, Vector2 } from "@hrnec06/util";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import useDebugger from "./DebuggerContext";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
|
|
6
|
+
export default function ResizeBorder() {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<ResizeBar
|
|
10
|
+
className="-top-1.5 h-3 w-full cursor-ns-resize"
|
|
11
|
+
applyReposition
|
|
12
|
+
onMove={([, y]) => [0, y]}
|
|
13
|
+
/>
|
|
14
|
+
<ResizeBar
|
|
15
|
+
className="-right-1.5 h-full w-3 cursor-ew-resize"
|
|
16
|
+
onMove={([x]) => [-x, 0]}
|
|
17
|
+
/>
|
|
18
|
+
<ResizeBar
|
|
19
|
+
className="-bottom-1.5 h-3 w-full cursor-ns-resize"
|
|
20
|
+
onMove={([, y]) => [0, -y]}
|
|
21
|
+
/>
|
|
22
|
+
<ResizeBar
|
|
23
|
+
className="-left-1.5 h-full w-3 cursor-ew-resize"
|
|
24
|
+
applyReposition
|
|
25
|
+
onMove={([x,]) => [x, 0]}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<ResizeBar
|
|
29
|
+
className="-top-1.5 -left-1.5 size-3 cursor-nwse-resize"
|
|
30
|
+
applyReposition
|
|
31
|
+
onMove={([x, y]) => [x, y]}
|
|
32
|
+
/>
|
|
33
|
+
<ResizeBar
|
|
34
|
+
className="-top-1.5 -right-1.5 size-3 cursor-nesw-resize"
|
|
35
|
+
applyReposition={'y'}
|
|
36
|
+
onMove={([x, y]) => [-x, y]}
|
|
37
|
+
/>
|
|
38
|
+
<ResizeBar
|
|
39
|
+
className="-bottom-1.5 -right-1.5 size-3 cursor-nwse-resize"
|
|
40
|
+
onMove={([x, y]) => [-x, -y]}
|
|
41
|
+
/>
|
|
42
|
+
<ResizeBar
|
|
43
|
+
className="-bottom-1.5 -left-1.5 size-3 cursor-nesw-resize"
|
|
44
|
+
applyReposition={'x'}
|
|
45
|
+
onMove={([x, y]) => [x, -y]}
|
|
46
|
+
/>
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ResizeBarProps {
|
|
52
|
+
className: string,
|
|
53
|
+
applyReposition?: boolean | 'x' | 'y',
|
|
54
|
+
|
|
55
|
+
onMove: (offset: Vector2, originalSize: Vector2, originalPosition: Vector2) => Vector2
|
|
56
|
+
}
|
|
57
|
+
function ResizeBar({
|
|
58
|
+
className,
|
|
59
|
+
applyReposition = false,
|
|
60
|
+
|
|
61
|
+
onMove
|
|
62
|
+
}: ResizeBarProps) {
|
|
63
|
+
const debug = useDebugger();
|
|
64
|
+
|
|
65
|
+
const [drag, setDrag] = useState<Nullable<{
|
|
66
|
+
dragOrigin: Vector2,
|
|
67
|
+
originalSize: Vector2,
|
|
68
|
+
originalPosition: Vector2
|
|
69
|
+
}>>(null);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const mouseUp = () => {
|
|
73
|
+
if (!drag) return;
|
|
74
|
+
|
|
75
|
+
setDrag(null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const mouseMove = (e: MouseEvent) => {
|
|
79
|
+
if (!drag) return;
|
|
80
|
+
|
|
81
|
+
const offset: Vector2 = [
|
|
82
|
+
e.clientX - drag.dragOrigin[0],
|
|
83
|
+
e.clientY - drag.dragOrigin[1],
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const newSize = onMove(
|
|
87
|
+
offset,
|
|
88
|
+
drag.originalSize,
|
|
89
|
+
drag.originalPosition
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
newSize[0] = drag.originalSize[0] - newSize[0];
|
|
93
|
+
newSize[1] = drag.originalSize[1] - newSize[1];
|
|
94
|
+
|
|
95
|
+
if (applyReposition !== false) {
|
|
96
|
+
const repositionOffset = cloneVector2(newSize);
|
|
97
|
+
removeVector2(repositionOffset, drag.originalSize);
|
|
98
|
+
|
|
99
|
+
debug.placement.reposition([
|
|
100
|
+
drag.originalPosition[0] - ((applyReposition === true || applyReposition === 'x') ? repositionOffset[0] : 0),
|
|
101
|
+
drag.originalPosition[1] - ((applyReposition === true || applyReposition === 'y') ? repositionOffset[1] : 0)
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
debug.window.resize(newSize);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
document.addEventListener('mouseup', mouseUp);
|
|
109
|
+
document.addEventListener('mousemove', mouseMove);
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
document.removeEventListener('mouseup', mouseUp);
|
|
113
|
+
document.removeEventListener('mousemove', mouseMove);
|
|
114
|
+
}
|
|
115
|
+
}, [drag]);
|
|
116
|
+
|
|
117
|
+
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
|
|
120
|
+
setDrag({
|
|
121
|
+
dragOrigin: [e.clientX, e.clientY],
|
|
122
|
+
originalSize: debug.window.size,
|
|
123
|
+
originalPosition: debug.placement.position
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
onMouseDown={handleMouseDown}
|
|
130
|
+
className={clsx(
|
|
131
|
+
'absolute',
|
|
132
|
+
className,
|
|
133
|
+
)}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
}
|