@arronqzy/react-view 1.0.22 → 1.0.24

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.
@@ -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
+ }