@arronqzy/react-view 1.0.22 → 1.0.23
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 +11 -11
- package/src/panel/ReactViewPanel.tsx +109 -965
- package/src/panel/components/PanelConfigSidebar.tsx +297 -3712
- package/src/panel/components/PanelHistoryPopover.tsx +163 -0
- package/src/panel/components/PanelLayerDock.tsx +412 -0
- package/src/panel/components/PanelMenubar.tsx +149 -0
- package/src/panel/components/PanelWorkspaceDialogs.tsx +271 -0
- package/src/panel/components/config/PanelConfigAudioSection.tsx +334 -0
- package/src/panel/components/config/PanelConfigChartSection.tsx +799 -0
- package/src/panel/components/config/PanelConfigGeometrySection.tsx +233 -0
- package/src/panel/components/config/PanelConfigGridChildSpan.tsx +70 -0
- package/src/panel/components/config/PanelConfigGridSection.tsx +118 -0
- package/src/panel/components/config/PanelConfigMultiSelect.tsx +1291 -0
- package/src/panel/components/config/PanelConfigNodeInfo.tsx +169 -0
- package/src/panel/components/config/PanelConfigReferenceSection.tsx +99 -0
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +6 -2
- package/src/panel/components/config/PanelConfigStyleSections.tsx +249 -0
- package/src/panel/components/config/PanelConfigTextSection.tsx +268 -0
- package/src/panel/components/config/PanelConfigVideoSection.tsx +133 -0
- package/src/panel/components/config/PanelConfigViewportSection.tsx +139 -0
- package/src/panel/components/config/helpers.ts +85 -0
- package/src/panel/icons.tsx +160 -0
- package/src/panel/utils/panelSelection.ts +57 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
} from "react";
|
|
7
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
8
|
+
import {
|
|
9
|
+
Input,
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
Textarea,
|
|
16
|
+
} from "@arronqzy/ui";
|
|
17
|
+
import type { PanelElement } from "../../types";
|
|
18
|
+
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
19
|
+
|
|
20
|
+
export function PanelConfigGeometrySection({
|
|
21
|
+
element,
|
|
22
|
+
helpers,
|
|
23
|
+
updateElement,
|
|
24
|
+
}: {
|
|
25
|
+
element: PanelElement;
|
|
26
|
+
helpers: ConfigSectionHelpers;
|
|
27
|
+
updateElement: UpdateElement;
|
|
28
|
+
}) {
|
|
29
|
+
const { t } = useI18n();
|
|
30
|
+
const { renderSection, renderFieldGroup, renderColorField } = helpers;
|
|
31
|
+
const selectedElement = element;
|
|
32
|
+
const [geometryDrawPenColor, setGeometryDrawPenColor] = useState("#111827");
|
|
33
|
+
const [geometryDrawPenWidth, setGeometryDrawPenWidth] = useState(3);
|
|
34
|
+
const [isGeometryDrawing, setIsGeometryDrawing] = useState(false);
|
|
35
|
+
const geometryDrawCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
36
|
+
const geometryLastPointRef = useRef<{ x: number; y: number } | null>(null);
|
|
37
|
+
|
|
38
|
+
const updateSelectedGeometry = useCallback(
|
|
39
|
+
(patch: Partial<PanelElement>) => {
|
|
40
|
+
if (selectedElement.materialType !== "geometry") return;
|
|
41
|
+
updateElement(selectedElement.id, patch);
|
|
42
|
+
},
|
|
43
|
+
[selectedElement, updateElement]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const redrawGeometryPadFromElement = useCallback(() => {
|
|
47
|
+
const canvas = geometryDrawCanvasRef.current;
|
|
48
|
+
if (!canvas) return;
|
|
49
|
+
const ctx = canvas.getContext("2d");
|
|
50
|
+
if (!ctx) return;
|
|
51
|
+
const width = canvas.width;
|
|
52
|
+
const height = canvas.height;
|
|
53
|
+
ctx.clearRect(0, 0, width, height);
|
|
54
|
+
ctx.fillStyle = "#ffffff";
|
|
55
|
+
ctx.fillRect(0, 0, width, height);
|
|
56
|
+
const sketch = selectedElement.geometrySketchDataUrl;
|
|
57
|
+
if (!sketch) return;
|
|
58
|
+
const img = new Image();
|
|
59
|
+
img.onload = () => {
|
|
60
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
61
|
+
};
|
|
62
|
+
img.src = sketch;
|
|
63
|
+
}, [selectedElement.geometrySketchDataUrl]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (selectedElement.materialType !== "geometry") return;
|
|
67
|
+
redrawGeometryPadFromElement();
|
|
68
|
+
}, [redrawGeometryPadFromElement, selectedElement.id, selectedElement.materialType]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
{
|
|
73
|
+
renderSection(
|
|
74
|
+
"geometryConfig",
|
|
75
|
+
t("panel.config.sectionGeometry"),
|
|
76
|
+
<>
|
|
77
|
+
{renderFieldGroup(
|
|
78
|
+
t("panel.config.groupBasicShape"),
|
|
79
|
+
<>
|
|
80
|
+
<label className="block space-y-1">
|
|
81
|
+
<div>{t("panel.config.shape")}</div>
|
|
82
|
+
<Select
|
|
83
|
+
value={selectedElement.geometryShape ?? "rect"}
|
|
84
|
+
onValueChange={(value) =>
|
|
85
|
+
updateSelectedGeometry({
|
|
86
|
+
geometryShape: value as PanelElement["geometryShape"],
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<SelectTrigger className="h-7">
|
|
91
|
+
<SelectValue placeholder={t("panel.config.selectShape")} />
|
|
92
|
+
</SelectTrigger>
|
|
93
|
+
<SelectContent>
|
|
94
|
+
<SelectItem value="rect">{t("panel.config.shapeRect")}</SelectItem>
|
|
95
|
+
<SelectItem value="circle">{t("panel.config.shapeCircle")}</SelectItem>
|
|
96
|
+
<SelectItem value="triangle">{t("panel.config.shapeTriangle")}</SelectItem>
|
|
97
|
+
<SelectItem value="diamond">{t("panel.config.shapeDiamond")}</SelectItem>
|
|
98
|
+
<SelectItem value="hexagon">{t("panel.config.shapeHexagon")}</SelectItem>
|
|
99
|
+
<SelectItem value="star">{t("panel.config.shapeStar")}</SelectItem>
|
|
100
|
+
<SelectItem value="heart">{t("panel.config.shapeHeart")}</SelectItem>
|
|
101
|
+
</SelectContent>
|
|
102
|
+
</Select>
|
|
103
|
+
</label>
|
|
104
|
+
{renderColorField(
|
|
105
|
+
t("panel.config.geometryColor"),
|
|
106
|
+
selectedElement.geometryColor ?? "#3b82f6",
|
|
107
|
+
(next) => updateSelectedGeometry({ geometryColor: next || "#3b82f6" })
|
|
108
|
+
)}
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
{renderFieldGroup(
|
|
112
|
+
t("panel.config.groupCanvasScript"),
|
|
113
|
+
<>
|
|
114
|
+
<Textarea
|
|
115
|
+
value={selectedElement.geometryScript ?? ""}
|
|
116
|
+
onChange={(e) => updateSelectedGeometry({ geometryScript: e.target.value || undefined })}
|
|
117
|
+
spellCheck={false}
|
|
118
|
+
className="h-36 font-mono text-[11px]"
|
|
119
|
+
placeholder={t("panel.config.geometryScriptPlaceholder")}
|
|
120
|
+
/>
|
|
121
|
+
</>,
|
|
122
|
+
<>
|
|
123
|
+
{t("panel.config.canvasScriptHint")}
|
|
124
|
+
</>,
|
|
125
|
+
{ groupKey: "geometryCanvasScript", defaultOpen: false }
|
|
126
|
+
)}
|
|
127
|
+
{renderFieldGroup(
|
|
128
|
+
t("panel.config.groupSketchOverlay"),
|
|
129
|
+
<>
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<label className="block space-y-1">
|
|
132
|
+
<div className="text-[11px]">{t("panel.config.penColor")}</div>
|
|
133
|
+
<Input
|
|
134
|
+
type="color"
|
|
135
|
+
value={geometryDrawPenColor}
|
|
136
|
+
onChange={(e) => setGeometryDrawPenColor(e.target.value)}
|
|
137
|
+
className="h-7 w-10 p-1"
|
|
138
|
+
/>
|
|
139
|
+
</label>
|
|
140
|
+
<label className="block space-y-1">
|
|
141
|
+
<div className="text-[11px]">{t("panel.config.penWidth")}</div>
|
|
142
|
+
<Input
|
|
143
|
+
type="number"
|
|
144
|
+
min={1}
|
|
145
|
+
max={24}
|
|
146
|
+
value={geometryDrawPenWidth}
|
|
147
|
+
onChange={(e) => setGeometryDrawPenWidth(Math.max(1, Math.min(24, Number(e.target.value) || 1)))}
|
|
148
|
+
className="h-7 w-20"
|
|
149
|
+
/>
|
|
150
|
+
</label>
|
|
151
|
+
</div>
|
|
152
|
+
<div className="rounded border border-border/60 bg-white p-2">
|
|
153
|
+
<canvas
|
|
154
|
+
ref={geometryDrawCanvasRef}
|
|
155
|
+
width={320}
|
|
156
|
+
height={180}
|
|
157
|
+
className="h-[180px] w-full cursor-crosshair rounded border border-border/60"
|
|
158
|
+
onPointerDown={(e) => {
|
|
159
|
+
const canvas = geometryDrawCanvasRef.current;
|
|
160
|
+
if (!canvas) return;
|
|
161
|
+
const ctx = canvas.getContext("2d");
|
|
162
|
+
if (!ctx) return;
|
|
163
|
+
const rect = canvas.getBoundingClientRect();
|
|
164
|
+
const x = ((e.clientX - rect.left) / rect.width) * canvas.width;
|
|
165
|
+
const y = ((e.clientY - rect.top) / rect.height) * canvas.height;
|
|
166
|
+
geometryLastPointRef.current = { x, y };
|
|
167
|
+
setIsGeometryDrawing(true);
|
|
168
|
+
ctx.lineCap = "round";
|
|
169
|
+
ctx.lineJoin = "round";
|
|
170
|
+
ctx.strokeStyle = geometryDrawPenColor;
|
|
171
|
+
ctx.lineWidth = geometryDrawPenWidth;
|
|
172
|
+
ctx.beginPath();
|
|
173
|
+
ctx.moveTo(x, y);
|
|
174
|
+
}}
|
|
175
|
+
onPointerMove={(e) => {
|
|
176
|
+
if (!isGeometryDrawing) return;
|
|
177
|
+
const canvas = geometryDrawCanvasRef.current;
|
|
178
|
+
const last = geometryLastPointRef.current;
|
|
179
|
+
if (!canvas || !last) return;
|
|
180
|
+
const ctx = canvas.getContext("2d");
|
|
181
|
+
if (!ctx) return;
|
|
182
|
+
const rect = canvas.getBoundingClientRect();
|
|
183
|
+
const x = ((e.clientX - rect.left) / rect.width) * canvas.width;
|
|
184
|
+
const y = ((e.clientY - rect.top) / rect.height) * canvas.height;
|
|
185
|
+
ctx.lineTo(x, y);
|
|
186
|
+
ctx.stroke();
|
|
187
|
+
geometryLastPointRef.current = { x, y };
|
|
188
|
+
}}
|
|
189
|
+
onPointerUp={() => {
|
|
190
|
+
setIsGeometryDrawing(false);
|
|
191
|
+
geometryLastPointRef.current = null;
|
|
192
|
+
}}
|
|
193
|
+
onPointerLeave={() => {
|
|
194
|
+
setIsGeometryDrawing(false);
|
|
195
|
+
geometryLastPointRef.current = null;
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
<div className="flex items-center gap-2">
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
className="rounded border border-border px-2 py-1 text-[11px] hover:bg-accent"
|
|
203
|
+
onClick={() => {
|
|
204
|
+
const canvas = geometryDrawCanvasRef.current;
|
|
205
|
+
if (!canvas) return;
|
|
206
|
+
updateSelectedGeometry({ geometrySketchDataUrl: canvas.toDataURL("image/png") });
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
{t("panel.config.applySketch")}
|
|
210
|
+
</button>
|
|
211
|
+
<button
|
|
212
|
+
type="button"
|
|
213
|
+
className="rounded border border-border px-2 py-1 text-[11px] hover:bg-accent"
|
|
214
|
+
onClick={() => {
|
|
215
|
+
updateSelectedGeometry({ geometrySketchDataUrl: undefined });
|
|
216
|
+
redrawGeometryPadFromElement();
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
{t("panel.config.clearSketch")}
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
</>,
|
|
223
|
+
undefined,
|
|
224
|
+
{ groupKey: "geometrySketch", defaultOpen: false }
|
|
225
|
+
)}
|
|
226
|
+
</>,
|
|
227
|
+
true,
|
|
228
|
+
[t("panel.material.geometry"), "geometry", t("panel.config.shape"), "canvas", t("panel.config.searchKwScript"), t("panel.config.searchKwSketch")]
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
|
+
import { Input } from "@arronqzy/ui";
|
|
3
|
+
import type { PanelElement } from "../../types";
|
|
4
|
+
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
5
|
+
|
|
6
|
+
export function PanelConfigGridChildSpan({
|
|
7
|
+
element,
|
|
8
|
+
helpers,
|
|
9
|
+
updateElement,
|
|
10
|
+
}: {
|
|
11
|
+
element: PanelElement;
|
|
12
|
+
helpers: ConfigSectionHelpers;
|
|
13
|
+
updateElement: UpdateElement;
|
|
14
|
+
}) {
|
|
15
|
+
const { t } = useI18n();
|
|
16
|
+
const { renderSection, renderFieldGroup } = helpers;
|
|
17
|
+
const selectedElement = element;
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{selectedElement
|
|
21
|
+
? selectedElement.parentGridId
|
|
22
|
+
? renderSection(
|
|
23
|
+
"gridChildSpan",
|
|
24
|
+
t("panel.config.sectionGridChildSpan"),
|
|
25
|
+
<>
|
|
26
|
+
{renderFieldGroup(
|
|
27
|
+
t("panel.config.groupCrossSlots"),
|
|
28
|
+
<div className="grid grid-cols-2 gap-2">
|
|
29
|
+
<label className="block space-y-1">
|
|
30
|
+
<div>{t("panel.config.colSpan")}</div>
|
|
31
|
+
<Input
|
|
32
|
+
type="number"
|
|
33
|
+
min={1}
|
|
34
|
+
max={12}
|
|
35
|
+
value={selectedElement.gridColSpan ?? 1}
|
|
36
|
+
onChange={(e) =>
|
|
37
|
+
updateElement(selectedElement.id, {
|
|
38
|
+
gridColSpan: Math.max(1, Math.min(12, Number(e.target.value) || 1)),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
className="h-7"
|
|
42
|
+
/>
|
|
43
|
+
</label>
|
|
44
|
+
<label className="block space-y-1">
|
|
45
|
+
<div>{t("panel.config.rowSpan")}</div>
|
|
46
|
+
<Input
|
|
47
|
+
type="number"
|
|
48
|
+
min={1}
|
|
49
|
+
max={12}
|
|
50
|
+
value={selectedElement.gridRowSpan ?? 1}
|
|
51
|
+
onChange={(e) =>
|
|
52
|
+
updateElement(selectedElement.id, {
|
|
53
|
+
gridRowSpan: Math.max(1, Math.min(12, Number(e.target.value) || 1)),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
className="h-7"
|
|
57
|
+
/>
|
|
58
|
+
</label>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</>,
|
|
62
|
+
true,
|
|
63
|
+
[t("panel.config.searchKwGrid"), t("panel.config.searchKwCrossCol"), t("panel.config.searchKwCrossRow"), "span", "slot"],
|
|
64
|
+
<>{t("panel.config.gridChildHint")}</>
|
|
65
|
+
)
|
|
66
|
+
: null
|
|
67
|
+
: null}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
|
+
import { Input } from "@arronqzy/ui";
|
|
3
|
+
import type { PanelElement } from "../../types";
|
|
4
|
+
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
5
|
+
|
|
6
|
+
export function PanelConfigGridSection({
|
|
7
|
+
element,
|
|
8
|
+
helpers,
|
|
9
|
+
updateElement,
|
|
10
|
+
}: {
|
|
11
|
+
element: PanelElement;
|
|
12
|
+
helpers: ConfigSectionHelpers;
|
|
13
|
+
updateElement: UpdateElement;
|
|
14
|
+
}) {
|
|
15
|
+
const { t } = useI18n();
|
|
16
|
+
const { renderSection, renderFieldGroup } = helpers;
|
|
17
|
+
const selectedElement = element;
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{
|
|
21
|
+
renderSection(
|
|
22
|
+
"gridConfig",
|
|
23
|
+
t("panel.config.sectionGrid"),
|
|
24
|
+
<>
|
|
25
|
+
{renderFieldGroup(
|
|
26
|
+
t("panel.config.groupGridParams"),
|
|
27
|
+
<div className="grid grid-cols-2 gap-2">
|
|
28
|
+
<label className="block space-y-1">
|
|
29
|
+
<div>{t("panel.config.rowCount")}</div>
|
|
30
|
+
<Input
|
|
31
|
+
type="number"
|
|
32
|
+
min={1}
|
|
33
|
+
max={12}
|
|
34
|
+
value={selectedElement.gridRows ?? 2}
|
|
35
|
+
onChange={(e) =>
|
|
36
|
+
updateElement(selectedElement.id, {
|
|
37
|
+
gridRows: Math.max(1, Math.min(12, Number(e.target.value) || 2)),
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
className="h-7"
|
|
41
|
+
/>
|
|
42
|
+
</label>
|
|
43
|
+
<label className="block space-y-1">
|
|
44
|
+
<div>{t("panel.config.colCount")}</div>
|
|
45
|
+
<Input
|
|
46
|
+
type="number"
|
|
47
|
+
min={1}
|
|
48
|
+
max={12}
|
|
49
|
+
value={selectedElement.gridCols ?? 3}
|
|
50
|
+
onChange={(e) =>
|
|
51
|
+
updateElement(selectedElement.id, {
|
|
52
|
+
gridCols: Math.max(1, Math.min(12, Number(e.target.value) || 3)),
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
className="h-7"
|
|
56
|
+
/>
|
|
57
|
+
</label>
|
|
58
|
+
<label className="block space-y-1">
|
|
59
|
+
<div>{t("panel.config.gapPx")}</div>
|
|
60
|
+
<Input
|
|
61
|
+
type="number"
|
|
62
|
+
min={0}
|
|
63
|
+
max={80}
|
|
64
|
+
value={selectedElement.gridGap ?? 8}
|
|
65
|
+
onChange={(e) =>
|
|
66
|
+
updateElement(selectedElement.id, {
|
|
67
|
+
gridGap: Math.max(0, Math.min(80, Number(e.target.value) || 0)),
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
className="h-7"
|
|
71
|
+
/>
|
|
72
|
+
</label>
|
|
73
|
+
<label className="block space-y-1">
|
|
74
|
+
<div>{t("panel.config.paddingPx")}</div>
|
|
75
|
+
<Input
|
|
76
|
+
type="number"
|
|
77
|
+
min={0}
|
|
78
|
+
max={100}
|
|
79
|
+
value={selectedElement.gridPadding ?? 10}
|
|
80
|
+
onChange={(e) =>
|
|
81
|
+
updateElement(selectedElement.id, {
|
|
82
|
+
gridPadding: Math.max(0, Math.min(100, Number(e.target.value) || 0)),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
className="h-7"
|
|
86
|
+
/>
|
|
87
|
+
</label>
|
|
88
|
+
<label className="block space-y-1">
|
|
89
|
+
<div>{t("panel.config.snapThresholdPx")}</div>
|
|
90
|
+
<Input
|
|
91
|
+
type="number"
|
|
92
|
+
min={8}
|
|
93
|
+
max={120}
|
|
94
|
+
value={selectedElement.gridSnapThreshold ?? 36}
|
|
95
|
+
onChange={(e) =>
|
|
96
|
+
updateElement(selectedElement.id, {
|
|
97
|
+
gridSnapThreshold: Math.max(
|
|
98
|
+
8,
|
|
99
|
+
Math.min(120, Number(e.target.value) || 36)
|
|
100
|
+
),
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
className="h-7"
|
|
104
|
+
/>
|
|
105
|
+
</label>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</>,
|
|
109
|
+
true,
|
|
110
|
+
[t("panel.config.searchKwGrid"), "grid", t("panel.config.rows"), t("panel.config.cols"), t("panel.config.searchKwGap"), t("panel.config.searchKwPadding"), t("panel.config.searchKwSnap"), t("panel.config.searchKwThreshold")],
|
|
111
|
+
<>
|
|
112
|
+
{t("panel.config.gridHint")}
|
|
113
|
+
</>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
</>
|
|
117
|
+
);
|
|
118
|
+
}
|