@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,1291 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
Checkbox,
|
|
9
|
+
Input,
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
Textarea,
|
|
16
|
+
} from "@arronqzy/ui";
|
|
17
|
+
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../../types";
|
|
18
|
+
import { CHART_TYPES, getChartLabelsDisplayText, getChartValuesDisplayText } from "../../utils/chartOptionBuilder";
|
|
19
|
+
import { VIEWPORT_OVERFLOW_MODES, normalizeViewportOverflow, type ViewportOverflowMode } from "../../utils/viewportPlacement";
|
|
20
|
+
import { type ConfigSectionHelpers, type UpdateElement, mergeOptionPatch } from "./helpers";
|
|
21
|
+
|
|
22
|
+
export function PanelConfigMultiSelect({
|
|
23
|
+
elements,
|
|
24
|
+
helpers,
|
|
25
|
+
updateElement,
|
|
26
|
+
layers,
|
|
27
|
+
setReferenceCopyMode,
|
|
28
|
+
onExcludeSelectedNode,
|
|
29
|
+
onAdjustNodeZOrder,
|
|
30
|
+
}: {
|
|
31
|
+
elements: PanelElement[];
|
|
32
|
+
helpers: ConfigSectionHelpers;
|
|
33
|
+
updateElement: UpdateElement;
|
|
34
|
+
layers: PanelLayer[];
|
|
35
|
+
setReferenceCopyMode?: (id: string, mode: ReferenceCopyMode) => void;
|
|
36
|
+
onExcludeSelectedNode?: (nodeId: string) => void;
|
|
37
|
+
onAdjustNodeZOrder?: (
|
|
38
|
+
nodeId: string,
|
|
39
|
+
action: "bringForward" | "sendBackward" | "bringToFront" | "sendToBack"
|
|
40
|
+
) => void;
|
|
41
|
+
}) {
|
|
42
|
+
const { t } = useI18n();
|
|
43
|
+
const { hasSearch, normalizedSearch, renderFormatterLabel, renderOptionLabel } = helpers;
|
|
44
|
+
const effectiveSelectedElements = elements;
|
|
45
|
+
const [expandedNodeCards, setExpandedNodeCards] = useState<Record<string, boolean>>({});
|
|
46
|
+
const isNodeCardExpanded = useCallback(
|
|
47
|
+
(id: string) => expandedNodeCards[id] ?? true,
|
|
48
|
+
[expandedNodeCards]
|
|
49
|
+
);
|
|
50
|
+
const setNodeCardExpanded = useCallback((id: string, open: boolean) => {
|
|
51
|
+
setExpandedNodeCards((prev) => ({ ...prev, [id]: open }));
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="space-y-3">
|
|
56
|
+
<Card>
|
|
57
|
+
<CardHeader className="pb-2">
|
|
58
|
+
<CardTitle className="text-xs">{t("panel.config.batchTitleWithCount", { count: effectiveSelectedElements.length })}</CardTitle>
|
|
59
|
+
</CardHeader>
|
|
60
|
+
<CardContent className="space-y-2 text-xs">
|
|
61
|
+
<div className="grid grid-cols-2 gap-2">
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
className="rounded border border-border bg-background px-2 py-1 hover:bg-accent"
|
|
65
|
+
onClick={() => effectiveSelectedElements.forEach((el) => updateElement(el.id, { locked: true }))}
|
|
66
|
+
>
|
|
67
|
+
{t("panel.config.lockAll")}
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
className="rounded border border-border bg-background px-2 py-1 hover:bg-accent"
|
|
72
|
+
onClick={() => effectiveSelectedElements.forEach((el) => updateElement(el.id, { locked: false }))}
|
|
73
|
+
>
|
|
74
|
+
{t("panel.config.unlockAll")}
|
|
75
|
+
</button>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className="rounded border border-border bg-background px-2 py-1 hover:bg-accent"
|
|
79
|
+
onClick={() =>
|
|
80
|
+
effectiveSelectedElements.forEach((el) =>
|
|
81
|
+
onAdjustNodeZOrder?.(el.id, "bringForward")
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
>
|
|
85
|
+
{t("panel.config.bringAllForward")}
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
className="rounded border border-border bg-background px-2 py-1 hover:bg-accent"
|
|
90
|
+
onClick={() =>
|
|
91
|
+
effectiveSelectedElements.forEach((el) =>
|
|
92
|
+
onAdjustNodeZOrder?.(el.id, "sendBackward")
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
>
|
|
96
|
+
{t("panel.config.sendAllBackward")}
|
|
97
|
+
</button>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
className="rounded border border-border bg-background px-2 py-1 hover:bg-accent"
|
|
101
|
+
onClick={() =>
|
|
102
|
+
effectiveSelectedElements.forEach((el) =>
|
|
103
|
+
updateElement(el.id, { zIndex: 1 })
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
>
|
|
107
|
+
{t("panel.config.setAllZIndex1")}
|
|
108
|
+
</button>
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
className="rounded border border-border bg-background px-2 py-1 hover:bg-accent"
|
|
112
|
+
onClick={() =>
|
|
113
|
+
effectiveSelectedElements.forEach((el) =>
|
|
114
|
+
updateElement(el.id, {
|
|
115
|
+
style: {
|
|
116
|
+
...(el.style ?? {}),
|
|
117
|
+
backgroundColor: "#3b82f6",
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
{t("panel.config.setAllBgBlue")}
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</CardContent>
|
|
127
|
+
</Card>
|
|
128
|
+
{effectiveSelectedElements
|
|
129
|
+
.filter((el) => {
|
|
130
|
+
if (!hasSearch) return true;
|
|
131
|
+
const text = `${el.name ?? ""} ${el.id} ${el.materialType ?? ""} zIndex style layer`
|
|
132
|
+
.toLowerCase();
|
|
133
|
+
return text.includes(normalizedSearch);
|
|
134
|
+
})
|
|
135
|
+
.map((el) => (
|
|
136
|
+
<Card
|
|
137
|
+
key={el.id}
|
|
138
|
+
className={el.locked ? "border-amber-500/40 bg-amber-500/5" : ""}
|
|
139
|
+
>
|
|
140
|
+
<CardHeader className="pb-2">
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
className="inline-flex h-6 w-6 items-center justify-center rounded border border-border text-[11px] hover:bg-accent"
|
|
145
|
+
onClick={() => setNodeCardExpanded(el.id, !isNodeCardExpanded(el.id))}
|
|
146
|
+
aria-label={isNodeCardExpanded(el.id) ? t("panel.config.collapseNodeConfig") : t("panel.config.expandNodeConfig")}
|
|
147
|
+
>
|
|
148
|
+
{isNodeCardExpanded(el.id) ? "▾" : "▸"}
|
|
149
|
+
</button>
|
|
150
|
+
<CardTitle className="min-w-0 flex-1 text-xs truncate">
|
|
151
|
+
{el.name?.trim() || el.materialType || t("common.node")} · {el.id}
|
|
152
|
+
</CardTitle>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className="inline-flex h-6 items-center justify-center rounded border border-border px-2 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
156
|
+
onClick={() => onExcludeSelectedNode?.(el.id)}
|
|
157
|
+
title={t("panel.config.removeFromSelectionTitle")}
|
|
158
|
+
>
|
|
159
|
+
{t("panel.config.removeFromSelection")}
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
</CardHeader>
|
|
163
|
+
{isNodeCardExpanded(el.id) ? (
|
|
164
|
+
<CardContent className="space-y-2 text-xs">
|
|
165
|
+
<label className="flex items-center gap-2">
|
|
166
|
+
<Checkbox
|
|
167
|
+
checked={el.locked === true}
|
|
168
|
+
onCheckedChange={(checked) => updateElement(el.id, { locked: checked === true })}
|
|
169
|
+
/>
|
|
170
|
+
<span>{t("panel.config.lockedNode")}</span>
|
|
171
|
+
</label>
|
|
172
|
+
{el.locked ? (
|
|
173
|
+
<div className="rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[11px] text-amber-700 dark:text-amber-300">
|
|
174
|
+
{t("panel.config.lockedNodeHint")}
|
|
175
|
+
</div>
|
|
176
|
+
) : null}
|
|
177
|
+
<fieldset disabled={el.locked} className={el.locked ? "opacity-60" : ""}>
|
|
178
|
+
<div className="space-y-2">
|
|
179
|
+
<label className="block space-y-1">
|
|
180
|
+
<div>{t("panel.config.name")}</div>
|
|
181
|
+
<Input
|
|
182
|
+
className="h-7"
|
|
183
|
+
value={el.name ?? ""}
|
|
184
|
+
onChange={(e) => updateElement(el.id, { name: e.target.value || undefined })}
|
|
185
|
+
/>
|
|
186
|
+
</label>
|
|
187
|
+
<div className="grid grid-cols-2 gap-2">
|
|
188
|
+
<label className="block space-y-1">
|
|
189
|
+
<div>zIndex</div>
|
|
190
|
+
<Input
|
|
191
|
+
className="h-7"
|
|
192
|
+
type="number"
|
|
193
|
+
value={el.zIndex ?? 1}
|
|
194
|
+
onChange={(e) =>
|
|
195
|
+
updateElement(el.id, { zIndex: Number(e.target.value) || 1 })
|
|
196
|
+
}
|
|
197
|
+
/>
|
|
198
|
+
</label>
|
|
199
|
+
<label className="block space-y-1">
|
|
200
|
+
<div>{t("panel.config.layer")}</div>
|
|
201
|
+
<Select
|
|
202
|
+
value={el.layerId}
|
|
203
|
+
onValueChange={(value) => updateElement(el.id, { layerId: value })}
|
|
204
|
+
>
|
|
205
|
+
<SelectTrigger className="h-7">
|
|
206
|
+
<SelectValue placeholder={t("panel.config.selectLayer")} />
|
|
207
|
+
</SelectTrigger>
|
|
208
|
+
<SelectContent>
|
|
209
|
+
{layers.map((layer) => (
|
|
210
|
+
<SelectItem key={layer.id} value={layer.id}>
|
|
211
|
+
{layer.name}
|
|
212
|
+
</SelectItem>
|
|
213
|
+
))}
|
|
214
|
+
</SelectContent>
|
|
215
|
+
</Select>
|
|
216
|
+
</label>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="grid grid-cols-2 gap-2">
|
|
219
|
+
<label className="block space-y-1">
|
|
220
|
+
<div>X</div>
|
|
221
|
+
<Input
|
|
222
|
+
className="h-7"
|
|
223
|
+
type="number"
|
|
224
|
+
value={el.x}
|
|
225
|
+
onChange={(e) => updateElement(el.id, { x: Number(e.target.value) || 0 })}
|
|
226
|
+
/>
|
|
227
|
+
</label>
|
|
228
|
+
<label className="block space-y-1">
|
|
229
|
+
<div>Y</div>
|
|
230
|
+
<Input
|
|
231
|
+
className="h-7"
|
|
232
|
+
type="number"
|
|
233
|
+
value={el.y}
|
|
234
|
+
onChange={(e) => updateElement(el.id, { y: Number(e.target.value) || 0 })}
|
|
235
|
+
/>
|
|
236
|
+
</label>
|
|
237
|
+
<label className="block space-y-1">
|
|
238
|
+
<div>{t("panel.config.rotate")}</div>
|
|
239
|
+
<Input
|
|
240
|
+
className="h-7"
|
|
241
|
+
type="number"
|
|
242
|
+
value={el.rotate ?? 0}
|
|
243
|
+
onChange={(e) =>
|
|
244
|
+
updateElement(el.id, { rotate: Number(e.target.value) || 0 })
|
|
245
|
+
}
|
|
246
|
+
/>
|
|
247
|
+
</label>
|
|
248
|
+
<label className="block space-y-1">
|
|
249
|
+
<div>{t("panel.config.width")}</div>
|
|
250
|
+
<Input
|
|
251
|
+
className="h-7"
|
|
252
|
+
type="number"
|
|
253
|
+
min={1}
|
|
254
|
+
value={el.width}
|
|
255
|
+
onChange={(e) =>
|
|
256
|
+
updateElement(el.id, { width: Math.max(1, Number(e.target.value) || 1) })
|
|
257
|
+
}
|
|
258
|
+
/>
|
|
259
|
+
</label>
|
|
260
|
+
<label className="block space-y-1">
|
|
261
|
+
<div>{t("panel.config.height")}</div>
|
|
262
|
+
<Input
|
|
263
|
+
className="h-7"
|
|
264
|
+
type="number"
|
|
265
|
+
min={1}
|
|
266
|
+
value={el.height}
|
|
267
|
+
onChange={(e) =>
|
|
268
|
+
updateElement(el.id, { height: Math.max(1, Number(e.target.value) || 1) })
|
|
269
|
+
}
|
|
270
|
+
/>
|
|
271
|
+
</label>
|
|
272
|
+
</div>
|
|
273
|
+
<div className="grid grid-cols-2 gap-2">
|
|
274
|
+
<label className="block space-y-1">
|
|
275
|
+
<div>{t("panel.config.backgroundColor")}</div>
|
|
276
|
+
<Input
|
|
277
|
+
className="h-7"
|
|
278
|
+
value={el.style?.backgroundColor ?? ""}
|
|
279
|
+
placeholder="#000000"
|
|
280
|
+
onChange={(e) =>
|
|
281
|
+
updateElement(el.id, {
|
|
282
|
+
style: {
|
|
283
|
+
...(el.style ?? {}),
|
|
284
|
+
backgroundColor: e.target.value || undefined,
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
/>
|
|
289
|
+
</label>
|
|
290
|
+
<label className="block space-y-1">
|
|
291
|
+
<div>{t("panel.config.borderColorShort")}</div>
|
|
292
|
+
<Input
|
|
293
|
+
className="h-7"
|
|
294
|
+
value={el.style?.borderColor ?? ""}
|
|
295
|
+
placeholder="#000000"
|
|
296
|
+
onChange={(e) =>
|
|
297
|
+
updateElement(el.id, {
|
|
298
|
+
style: {
|
|
299
|
+
...(el.style ?? {}),
|
|
300
|
+
borderColor: e.target.value || undefined,
|
|
301
|
+
},
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
/>
|
|
305
|
+
</label>
|
|
306
|
+
</div>
|
|
307
|
+
{CHART_TYPES.has(el.materialType ?? "") ? (
|
|
308
|
+
<div className="grid grid-cols-2 gap-2">
|
|
309
|
+
<label className="block space-y-1 col-span-2">
|
|
310
|
+
<div>{t("panel.config.chartTitle")}</div>
|
|
311
|
+
<Input
|
|
312
|
+
className="h-7"
|
|
313
|
+
value={el.chart?.title ?? ""}
|
|
314
|
+
onChange={(e) =>
|
|
315
|
+
updateElement(el.id, {
|
|
316
|
+
chart: { ...(el.chart ?? {}), title: e.target.value },
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
/>
|
|
320
|
+
</label>
|
|
321
|
+
<label className="block space-y-1">
|
|
322
|
+
<div>{t("panel.config.primaryColor")}</div>
|
|
323
|
+
<Input
|
|
324
|
+
className="h-7"
|
|
325
|
+
value={el.chart?.color ?? ""}
|
|
326
|
+
onChange={(e) =>
|
|
327
|
+
updateElement(el.id, {
|
|
328
|
+
chart: { ...(el.chart ?? {}), color: e.target.value || undefined },
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
/>
|
|
332
|
+
</label>
|
|
333
|
+
<label className="flex items-center gap-2">
|
|
334
|
+
<Checkbox
|
|
335
|
+
checked={el.chart?.colorMode === "gradient"}
|
|
336
|
+
onCheckedChange={(checked) =>
|
|
337
|
+
updateElement(el.id, {
|
|
338
|
+
chart: { ...(el.chart ?? {}), colorMode: checked ? "gradient" : "solid" },
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
/>
|
|
342
|
+
<span>{t("panel.config.primaryGradient")}</span>
|
|
343
|
+
</label>
|
|
344
|
+
{el.chart?.colorMode === "gradient" ? (
|
|
345
|
+
<>
|
|
346
|
+
<label className="block space-y-1">
|
|
347
|
+
<div>{t("panel.config.gradientFrom")}</div>
|
|
348
|
+
<Input
|
|
349
|
+
className="h-7"
|
|
350
|
+
value={el.chart?.gradientFrom ?? el.chart?.color ?? "#3b82f6"}
|
|
351
|
+
onChange={(e) =>
|
|
352
|
+
updateElement(el.id, {
|
|
353
|
+
chart: { ...(el.chart ?? {}), gradientFrom: e.target.value || "#3b82f6" },
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
/>
|
|
357
|
+
</label>
|
|
358
|
+
<label className="block space-y-1">
|
|
359
|
+
<div>{t("panel.config.gradientTo")}</div>
|
|
360
|
+
<Input
|
|
361
|
+
className="h-7"
|
|
362
|
+
value={el.chart?.gradientTo ?? "#22d3ee"}
|
|
363
|
+
onChange={(e) =>
|
|
364
|
+
updateElement(el.id, {
|
|
365
|
+
chart: { ...(el.chart ?? {}), gradientTo: e.target.value || "#22d3ee" },
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
/>
|
|
369
|
+
</label>
|
|
370
|
+
<label className="block space-y-1 col-span-2">
|
|
371
|
+
<div>{t("panel.config.gradientDirection")}</div>
|
|
372
|
+
<Select
|
|
373
|
+
value={el.chart?.gradientDirection ?? "to-right"}
|
|
374
|
+
onValueChange={(value) =>
|
|
375
|
+
updateElement(el.id, {
|
|
376
|
+
chart: {
|
|
377
|
+
...(el.chart ?? {}),
|
|
378
|
+
gradientDirection: value as
|
|
379
|
+
| "to-right"
|
|
380
|
+
| "to-bottom"
|
|
381
|
+
| "to-bottom-right"
|
|
382
|
+
| "to-top-right",
|
|
383
|
+
},
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
>
|
|
387
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
388
|
+
<SelectContent>
|
|
389
|
+
<SelectItem value="to-right">{t("panel.config.dirToRight")}</SelectItem>
|
|
390
|
+
<SelectItem value="to-bottom">{t("panel.config.dirToBottom")}</SelectItem>
|
|
391
|
+
<SelectItem value="to-bottom-right">{t("panel.config.dirToBottomRight")}</SelectItem>
|
|
392
|
+
<SelectItem value="to-top-right">{t("panel.config.dirToTopRight")}</SelectItem>
|
|
393
|
+
</SelectContent>
|
|
394
|
+
</Select>
|
|
395
|
+
</label>
|
|
396
|
+
<div className="col-span-2 space-y-1">
|
|
397
|
+
<div className="text-[11px] text-muted-foreground">{t("panel.config.gradientPreview")}</div>
|
|
398
|
+
<div
|
|
399
|
+
className="h-6 rounded border border-border/60"
|
|
400
|
+
style={{
|
|
401
|
+
backgroundImage: `linear-gradient(${
|
|
402
|
+
(el.chart?.gradientDirection ?? "to-right") === "to-bottom"
|
|
403
|
+
? "to bottom"
|
|
404
|
+
: (el.chart?.gradientDirection ?? "to-right") === "to-bottom-right"
|
|
405
|
+
? "to bottom right"
|
|
406
|
+
: (el.chart?.gradientDirection ?? "to-right") === "to-top-right"
|
|
407
|
+
? "to top right"
|
|
408
|
+
: "to right"
|
|
409
|
+
}, ${el.chart?.gradientFrom ?? el.chart?.color ?? "#3b82f6"}, ${el.chart?.gradientTo ?? "#22d3ee"})`,
|
|
410
|
+
}}
|
|
411
|
+
/>
|
|
412
|
+
</div>
|
|
413
|
+
</>
|
|
414
|
+
) : null}
|
|
415
|
+
<label className="block space-y-1">
|
|
416
|
+
<div>{t("panel.config.render")}</div>
|
|
417
|
+
<Select
|
|
418
|
+
value={el.chart?.renderMode ?? "canvas"}
|
|
419
|
+
onValueChange={(value) =>
|
|
420
|
+
updateElement(el.id, {
|
|
421
|
+
chart: { ...(el.chart ?? {}), renderMode: value as "canvas" | "svg" },
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
>
|
|
425
|
+
<SelectTrigger className="h-7">
|
|
426
|
+
<SelectValue />
|
|
427
|
+
</SelectTrigger>
|
|
428
|
+
<SelectContent>
|
|
429
|
+
<SelectItem value="canvas">canvas</SelectItem>
|
|
430
|
+
<SelectItem value="svg">svg</SelectItem>
|
|
431
|
+
</SelectContent>
|
|
432
|
+
</Select>
|
|
433
|
+
</label>
|
|
434
|
+
<label className="block space-y-1">
|
|
435
|
+
<div>{t("panel.config.tooltipBg")}</div>
|
|
436
|
+
<Input
|
|
437
|
+
className="h-7"
|
|
438
|
+
value={el.chart?.tooltipBackgroundColor ?? ""}
|
|
439
|
+
placeholder="#0f172a"
|
|
440
|
+
onChange={(e) =>
|
|
441
|
+
updateElement(el.id, {
|
|
442
|
+
chart: {
|
|
443
|
+
...(el.chart ?? {}),
|
|
444
|
+
tooltipBackgroundColor: e.target.value || undefined,
|
|
445
|
+
},
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
/>
|
|
449
|
+
</label>
|
|
450
|
+
<label className="flex items-center gap-2">
|
|
451
|
+
<Checkbox
|
|
452
|
+
checked={el.chart?.tooltipShow ?? true}
|
|
453
|
+
onCheckedChange={(checked) =>
|
|
454
|
+
updateElement(el.id, {
|
|
455
|
+
chart: { ...(el.chart ?? {}), tooltipShow: checked === true },
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
/>
|
|
459
|
+
<span>{t("panel.config.showTooltip")}</span>
|
|
460
|
+
</label>
|
|
461
|
+
<label className="block space-y-1">
|
|
462
|
+
<div>{t("panel.config.tooltipTrigger")}</div>
|
|
463
|
+
<Select
|
|
464
|
+
value={el.chart?.tooltipTrigger ?? "axis"}
|
|
465
|
+
onValueChange={(value) =>
|
|
466
|
+
updateElement(el.id, {
|
|
467
|
+
chart: { ...(el.chart ?? {}), tooltipTrigger: value as "axis" | "item" },
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
>
|
|
471
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
472
|
+
<SelectContent>
|
|
473
|
+
<SelectItem value="axis">axis</SelectItem>
|
|
474
|
+
<SelectItem value="item">item</SelectItem>
|
|
475
|
+
</SelectContent>
|
|
476
|
+
</Select>
|
|
477
|
+
</label>
|
|
478
|
+
<label className="block space-y-1">
|
|
479
|
+
<div>{t("panel.config.tooltipTextColor")}</div>
|
|
480
|
+
<Input
|
|
481
|
+
className="h-7"
|
|
482
|
+
value={el.chart?.tooltipTextColor ?? ""}
|
|
483
|
+
placeholder="#f8fafc"
|
|
484
|
+
onChange={(e) =>
|
|
485
|
+
updateElement(el.id, {
|
|
486
|
+
chart: {
|
|
487
|
+
...(el.chart ?? {}),
|
|
488
|
+
tooltipTextColor: e.target.value || undefined,
|
|
489
|
+
},
|
|
490
|
+
})
|
|
491
|
+
}
|
|
492
|
+
/>
|
|
493
|
+
</label>
|
|
494
|
+
<label className="block space-y-1 col-span-2">
|
|
495
|
+
{renderFormatterLabel("Tooltip Formatter")}
|
|
496
|
+
<Input
|
|
497
|
+
className="h-7"
|
|
498
|
+
value={el.chart?.tooltipFormatter ?? ""}
|
|
499
|
+
placeholder="{b}: {c}"
|
|
500
|
+
onChange={(e) =>
|
|
501
|
+
updateElement(el.id, {
|
|
502
|
+
chart: {
|
|
503
|
+
...(el.chart ?? {}),
|
|
504
|
+
tooltipFormatter: e.target.value || undefined,
|
|
505
|
+
},
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
/>
|
|
509
|
+
</label>
|
|
510
|
+
<label className="block space-y-1 col-span-2">
|
|
511
|
+
<div>{t("panel.config.labelsCsv")}</div>
|
|
512
|
+
<Input
|
|
513
|
+
className="h-7"
|
|
514
|
+
value={getChartLabelsDisplayText(el.chart)}
|
|
515
|
+
onChange={(e) =>
|
|
516
|
+
updateElement(el.id, {
|
|
517
|
+
chart: {
|
|
518
|
+
...(el.chart ?? {}),
|
|
519
|
+
labelsText: e.target.value,
|
|
520
|
+
},
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
/>
|
|
524
|
+
</label>
|
|
525
|
+
<label className="block space-y-1 col-span-2">
|
|
526
|
+
<div>{t("panel.config.valuesCsv")}</div>
|
|
527
|
+
<Input
|
|
528
|
+
className="h-7"
|
|
529
|
+
value={getChartValuesDisplayText(el.chart)}
|
|
530
|
+
onChange={(e) =>
|
|
531
|
+
updateElement(el.id, {
|
|
532
|
+
chart: {
|
|
533
|
+
...(el.chart ?? {}),
|
|
534
|
+
valuesText: e.target.value,
|
|
535
|
+
},
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
/>
|
|
539
|
+
</label>
|
|
540
|
+
{["bar", "line", "area", "scatter"].includes(el.materialType ?? "") ? (
|
|
541
|
+
<>
|
|
542
|
+
<label className="block space-y-1">
|
|
543
|
+
<div>{t("panel.config.xAxisName")}</div>
|
|
544
|
+
<Input
|
|
545
|
+
className="h-7"
|
|
546
|
+
value={el.chart?.xAxisName ?? ""}
|
|
547
|
+
onChange={(e) =>
|
|
548
|
+
updateElement(el.id, {
|
|
549
|
+
chart: { ...(el.chart ?? {}), xAxisName: e.target.value },
|
|
550
|
+
})
|
|
551
|
+
}
|
|
552
|
+
/>
|
|
553
|
+
</label>
|
|
554
|
+
<label className="block space-y-1">
|
|
555
|
+
<div>{t("panel.config.yAxisName")}</div>
|
|
556
|
+
<Input
|
|
557
|
+
className="h-7"
|
|
558
|
+
value={el.chart?.yAxisName ?? ""}
|
|
559
|
+
onChange={(e) =>
|
|
560
|
+
updateElement(el.id, {
|
|
561
|
+
chart: { ...(el.chart ?? {}), yAxisName: e.target.value },
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
/>
|
|
565
|
+
</label>
|
|
566
|
+
<label className="block space-y-1">
|
|
567
|
+
<div>{t("panel.config.xAxisLabelColor")}</div>
|
|
568
|
+
<Input
|
|
569
|
+
className="h-7"
|
|
570
|
+
value={el.chart?.xAxisLabelColor ?? ""}
|
|
571
|
+
placeholder="#64748b"
|
|
572
|
+
onChange={(e) =>
|
|
573
|
+
updateElement(el.id, {
|
|
574
|
+
chart: { ...(el.chart ?? {}), xAxisLabelColor: e.target.value || undefined },
|
|
575
|
+
})
|
|
576
|
+
}
|
|
577
|
+
/>
|
|
578
|
+
</label>
|
|
579
|
+
<label className="block space-y-1">
|
|
580
|
+
<div>{t("panel.config.yAxisLabelColor")}</div>
|
|
581
|
+
<Input
|
|
582
|
+
className="h-7"
|
|
583
|
+
value={el.chart?.yAxisLabelColor ?? ""}
|
|
584
|
+
placeholder="#64748b"
|
|
585
|
+
onChange={(e) =>
|
|
586
|
+
updateElement(el.id, {
|
|
587
|
+
chart: { ...(el.chart ?? {}), yAxisLabelColor: e.target.value || undefined },
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
/>
|
|
591
|
+
</label>
|
|
592
|
+
<label className="block space-y-1">
|
|
593
|
+
<div>{t("panel.config.xAxisLabelFontSize")}</div>
|
|
594
|
+
<Input
|
|
595
|
+
className="h-7"
|
|
596
|
+
type="number"
|
|
597
|
+
min={8}
|
|
598
|
+
max={48}
|
|
599
|
+
value={el.chart?.xAxisLabelFontSize ?? 10}
|
|
600
|
+
onChange={(e) =>
|
|
601
|
+
updateElement(el.id, {
|
|
602
|
+
chart: {
|
|
603
|
+
...(el.chart ?? {}),
|
|
604
|
+
xAxisLabelFontSize: Math.max(8, Math.min(48, Number(e.target.value) || 10)),
|
|
605
|
+
},
|
|
606
|
+
})
|
|
607
|
+
}
|
|
608
|
+
/>
|
|
609
|
+
</label>
|
|
610
|
+
<label className="block space-y-1">
|
|
611
|
+
<div>{t("panel.config.yAxisLabelFontSize")}</div>
|
|
612
|
+
<Input
|
|
613
|
+
className="h-7"
|
|
614
|
+
type="number"
|
|
615
|
+
min={8}
|
|
616
|
+
max={48}
|
|
617
|
+
value={el.chart?.yAxisLabelFontSize ?? 10}
|
|
618
|
+
onChange={(e) =>
|
|
619
|
+
updateElement(el.id, {
|
|
620
|
+
chart: {
|
|
621
|
+
...(el.chart ?? {}),
|
|
622
|
+
yAxisLabelFontSize: Math.max(8, Math.min(48, Number(e.target.value) || 10)),
|
|
623
|
+
},
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
/>
|
|
627
|
+
</label>
|
|
628
|
+
<label className="flex items-center gap-2">
|
|
629
|
+
<Checkbox
|
|
630
|
+
checked={el.chart?.xAxisLabelAutoEllipsis ?? false}
|
|
631
|
+
onCheckedChange={(checked) =>
|
|
632
|
+
updateElement(el.id, {
|
|
633
|
+
chart: { ...(el.chart ?? {}), xAxisLabelAutoEllipsis: checked === true },
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
/>
|
|
637
|
+
<span>{t("panel.config.xAxisLabelAutoEllipsis")}</span>
|
|
638
|
+
</label>
|
|
639
|
+
<label className="flex items-center gap-2">
|
|
640
|
+
<Checkbox
|
|
641
|
+
checked={el.chart?.yAxisLabelAutoEllipsis ?? false}
|
|
642
|
+
onCheckedChange={(checked) =>
|
|
643
|
+
updateElement(el.id, {
|
|
644
|
+
chart: { ...(el.chart ?? {}), yAxisLabelAutoEllipsis: checked === true },
|
|
645
|
+
})
|
|
646
|
+
}
|
|
647
|
+
/>
|
|
648
|
+
<span>{t("panel.config.yAxisLabelAutoEllipsis")}</span>
|
|
649
|
+
</label>
|
|
650
|
+
<label className="flex items-center gap-2">
|
|
651
|
+
<Checkbox
|
|
652
|
+
checked={el.chart?.xAxisTickShow ?? true}
|
|
653
|
+
onCheckedChange={(checked) =>
|
|
654
|
+
updateElement(el.id, {
|
|
655
|
+
chart: { ...(el.chart ?? {}), xAxisTickShow: checked === true },
|
|
656
|
+
})
|
|
657
|
+
}
|
|
658
|
+
/>
|
|
659
|
+
<span>{t("panel.config.xAxisTick")}</span>
|
|
660
|
+
</label>
|
|
661
|
+
<label className="flex items-center gap-2">
|
|
662
|
+
<Checkbox
|
|
663
|
+
checked={el.chart?.yAxisTickShow ?? true}
|
|
664
|
+
onCheckedChange={(checked) =>
|
|
665
|
+
updateElement(el.id, {
|
|
666
|
+
chart: { ...(el.chart ?? {}), yAxisTickShow: checked === true },
|
|
667
|
+
})
|
|
668
|
+
}
|
|
669
|
+
/>
|
|
670
|
+
<span>{t("panel.config.yAxisTick")}</span>
|
|
671
|
+
</label>
|
|
672
|
+
<label className="block space-y-1">
|
|
673
|
+
<div>{t("panel.config.xAxisTickColor")}</div>
|
|
674
|
+
<Input
|
|
675
|
+
className="h-7"
|
|
676
|
+
value={el.chart?.xAxisTickColor ?? ""}
|
|
677
|
+
placeholder="#94a3b8"
|
|
678
|
+
onChange={(e) =>
|
|
679
|
+
updateElement(el.id, {
|
|
680
|
+
chart: { ...(el.chart ?? {}), xAxisTickColor: e.target.value || undefined },
|
|
681
|
+
})
|
|
682
|
+
}
|
|
683
|
+
/>
|
|
684
|
+
</label>
|
|
685
|
+
<label className="block space-y-1">
|
|
686
|
+
<div>{t("panel.config.yAxisTickColor")}</div>
|
|
687
|
+
<Input
|
|
688
|
+
className="h-7"
|
|
689
|
+
value={el.chart?.yAxisTickColor ?? ""}
|
|
690
|
+
placeholder="#94a3b8"
|
|
691
|
+
onChange={(e) =>
|
|
692
|
+
updateElement(el.id, {
|
|
693
|
+
chart: { ...(el.chart ?? {}), yAxisTickColor: e.target.value || undefined },
|
|
694
|
+
})
|
|
695
|
+
}
|
|
696
|
+
/>
|
|
697
|
+
</label>
|
|
698
|
+
<label className="flex items-center gap-2">
|
|
699
|
+
<Checkbox
|
|
700
|
+
checked={el.chart?.xAxisSplitLineShow ?? false}
|
|
701
|
+
onCheckedChange={(checked) =>
|
|
702
|
+
updateElement(el.id, {
|
|
703
|
+
chart: { ...(el.chart ?? {}), xAxisSplitLineShow: checked === true },
|
|
704
|
+
})
|
|
705
|
+
}
|
|
706
|
+
/>
|
|
707
|
+
<span>{t("panel.config.xAxisSplitLine")}</span>
|
|
708
|
+
</label>
|
|
709
|
+
<label className="flex items-center gap-2">
|
|
710
|
+
<Checkbox
|
|
711
|
+
checked={el.chart?.yAxisSplitLineShow ?? true}
|
|
712
|
+
onCheckedChange={(checked) =>
|
|
713
|
+
updateElement(el.id, {
|
|
714
|
+
chart: { ...(el.chart ?? {}), yAxisSplitLineShow: checked === true },
|
|
715
|
+
})
|
|
716
|
+
}
|
|
717
|
+
/>
|
|
718
|
+
<span>{t("panel.config.yAxisSplitLine")}</span>
|
|
719
|
+
</label>
|
|
720
|
+
<label className="block space-y-1">
|
|
721
|
+
<div>{t("panel.config.xAxisSplitLineColor")}</div>
|
|
722
|
+
<Input
|
|
723
|
+
className="h-7"
|
|
724
|
+
value={el.chart?.xAxisSplitLineColor ?? ""}
|
|
725
|
+
placeholder="#e2e8f0"
|
|
726
|
+
onChange={(e) =>
|
|
727
|
+
updateElement(el.id, {
|
|
728
|
+
chart: { ...(el.chart ?? {}), xAxisSplitLineColor: e.target.value || undefined },
|
|
729
|
+
})
|
|
730
|
+
}
|
|
731
|
+
/>
|
|
732
|
+
</label>
|
|
733
|
+
<label className="block space-y-1">
|
|
734
|
+
<div>{t("panel.config.yAxisSplitLineColor")}</div>
|
|
735
|
+
<Input
|
|
736
|
+
className="h-7"
|
|
737
|
+
value={el.chart?.yAxisSplitLineColor ?? ""}
|
|
738
|
+
placeholder="#e2e8f0"
|
|
739
|
+
onChange={(e) =>
|
|
740
|
+
updateElement(el.id, {
|
|
741
|
+
chart: { ...(el.chart ?? {}), yAxisSplitLineColor: e.target.value || undefined },
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
/>
|
|
745
|
+
</label>
|
|
746
|
+
</>
|
|
747
|
+
) : null}
|
|
748
|
+
{el.materialType === "gauge" ? (
|
|
749
|
+
<label className="block space-y-1">
|
|
750
|
+
<div>{t("panel.config.gaugeValue")}</div>
|
|
751
|
+
<Input
|
|
752
|
+
className="h-7"
|
|
753
|
+
type="number"
|
|
754
|
+
value={el.chart?.values?.[0] ?? 0}
|
|
755
|
+
onChange={(e) =>
|
|
756
|
+
updateElement(el.id, {
|
|
757
|
+
chart: {
|
|
758
|
+
...(el.chart ?? {}),
|
|
759
|
+
values: [Number(e.target.value) || 0],
|
|
760
|
+
},
|
|
761
|
+
})
|
|
762
|
+
}
|
|
763
|
+
/>
|
|
764
|
+
</label>
|
|
765
|
+
) : null}
|
|
766
|
+
{el.materialType === "bar" ? (
|
|
767
|
+
<label className="block space-y-1">
|
|
768
|
+
<div>{t("panel.config.barWidth")}</div>
|
|
769
|
+
<Input
|
|
770
|
+
className="h-7"
|
|
771
|
+
type="number"
|
|
772
|
+
min={1}
|
|
773
|
+
placeholder={t("panel.config.barWidthAuto")}
|
|
774
|
+
value={el.chart?.barWidth ?? ""}
|
|
775
|
+
onChange={(e) => {
|
|
776
|
+
const raw = e.target.value.trim();
|
|
777
|
+
updateElement(el.id, {
|
|
778
|
+
chart: {
|
|
779
|
+
...(el.chart ?? {}),
|
|
780
|
+
barWidth: raw
|
|
781
|
+
? Math.max(1, Number(raw) || 1)
|
|
782
|
+
: undefined,
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
}}
|
|
786
|
+
/>
|
|
787
|
+
</label>
|
|
788
|
+
) : null}
|
|
789
|
+
{(el.materialType === "line" || el.materialType === "area") ? (
|
|
790
|
+
<label className="flex items-center gap-2">
|
|
791
|
+
<Checkbox
|
|
792
|
+
checked={el.chart?.smooth ?? true}
|
|
793
|
+
onCheckedChange={(checked) =>
|
|
794
|
+
updateElement(el.id, {
|
|
795
|
+
chart: { ...(el.chart ?? {}), smooth: checked === true },
|
|
796
|
+
})
|
|
797
|
+
}
|
|
798
|
+
/>
|
|
799
|
+
<span>{t("panel.config.smooth")}</span>
|
|
800
|
+
</label>
|
|
801
|
+
) : null}
|
|
802
|
+
{el.materialType === "pie" ? (
|
|
803
|
+
<>
|
|
804
|
+
<label className="block space-y-1">
|
|
805
|
+
<div>{t("panel.config.pieInnerRadius")}</div>
|
|
806
|
+
<Input
|
|
807
|
+
className="h-7"
|
|
808
|
+
type="number"
|
|
809
|
+
min={0}
|
|
810
|
+
max={99}
|
|
811
|
+
value={el.chart?.pieInnerRadius ?? 30}
|
|
812
|
+
onChange={(e) =>
|
|
813
|
+
updateElement(el.id, {
|
|
814
|
+
chart: {
|
|
815
|
+
...(el.chart ?? {}),
|
|
816
|
+
pieInnerRadius: Math.max(0, Math.min(99, Number(e.target.value) || 0)),
|
|
817
|
+
},
|
|
818
|
+
})
|
|
819
|
+
}
|
|
820
|
+
/>
|
|
821
|
+
</label>
|
|
822
|
+
<label className="block space-y-1">
|
|
823
|
+
<div>{t("panel.config.pieOuterRadius")}</div>
|
|
824
|
+
<Input
|
|
825
|
+
className="h-7"
|
|
826
|
+
type="number"
|
|
827
|
+
min={1}
|
|
828
|
+
max={100}
|
|
829
|
+
value={el.chart?.pieOuterRadius ?? 65}
|
|
830
|
+
onChange={(e) =>
|
|
831
|
+
updateElement(el.id, {
|
|
832
|
+
chart: {
|
|
833
|
+
...(el.chart ?? {}),
|
|
834
|
+
pieOuterRadius: Math.max(1, Math.min(100, Number(e.target.value) || 1)),
|
|
835
|
+
},
|
|
836
|
+
})
|
|
837
|
+
}
|
|
838
|
+
/>
|
|
839
|
+
</label>
|
|
840
|
+
</>
|
|
841
|
+
) : null}
|
|
842
|
+
<label className="block space-y-1">
|
|
843
|
+
{renderOptionLabel(t("panel.config.gridLeft"), "grid.left", t("panel.config.gridLeftHint"))}
|
|
844
|
+
<Input
|
|
845
|
+
className="h-7"
|
|
846
|
+
type="number"
|
|
847
|
+
value={Number((el.chart?.option as any)?.grid?.left ?? 28)}
|
|
848
|
+
onChange={(e) =>
|
|
849
|
+
updateElement(el.id, {
|
|
850
|
+
chart: {
|
|
851
|
+
...(el.chart ?? {}),
|
|
852
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
853
|
+
grid: { left: Number(e.target.value) || 0 },
|
|
854
|
+
}),
|
|
855
|
+
},
|
|
856
|
+
})
|
|
857
|
+
}
|
|
858
|
+
/>
|
|
859
|
+
</label>
|
|
860
|
+
<label className="block space-y-1">
|
|
861
|
+
{renderOptionLabel(t("panel.config.gridRight"), "grid.right", t("panel.config.gridRightHint"))}
|
|
862
|
+
<Input
|
|
863
|
+
className="h-7"
|
|
864
|
+
type="number"
|
|
865
|
+
value={Number((el.chart?.option as any)?.grid?.right ?? 10)}
|
|
866
|
+
onChange={(e) =>
|
|
867
|
+
updateElement(el.id, {
|
|
868
|
+
chart: {
|
|
869
|
+
...(el.chart ?? {}),
|
|
870
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
871
|
+
grid: { right: Number(e.target.value) || 0 },
|
|
872
|
+
}),
|
|
873
|
+
},
|
|
874
|
+
})
|
|
875
|
+
}
|
|
876
|
+
/>
|
|
877
|
+
</label>
|
|
878
|
+
<label className="block space-y-1">
|
|
879
|
+
{renderOptionLabel(t("panel.config.gridTop"), "grid.top", t("panel.config.gridTopHint"))}
|
|
880
|
+
<Input
|
|
881
|
+
className="h-7"
|
|
882
|
+
type="number"
|
|
883
|
+
value={Number((el.chart?.option as any)?.grid?.top ?? 30)}
|
|
884
|
+
onChange={(e) =>
|
|
885
|
+
updateElement(el.id, {
|
|
886
|
+
chart: {
|
|
887
|
+
...(el.chart ?? {}),
|
|
888
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
889
|
+
grid: { top: Number(e.target.value) || 0 },
|
|
890
|
+
}),
|
|
891
|
+
},
|
|
892
|
+
})
|
|
893
|
+
}
|
|
894
|
+
/>
|
|
895
|
+
</label>
|
|
896
|
+
<label className="block space-y-1">
|
|
897
|
+
{renderOptionLabel(t("panel.config.gridBottom"), "grid.bottom", t("panel.config.gridBottomHint"))}
|
|
898
|
+
<Input
|
|
899
|
+
className="h-7"
|
|
900
|
+
type="number"
|
|
901
|
+
value={Number((el.chart?.option as any)?.grid?.bottom ?? 20)}
|
|
902
|
+
onChange={(e) =>
|
|
903
|
+
updateElement(el.id, {
|
|
904
|
+
chart: {
|
|
905
|
+
...(el.chart ?? {}),
|
|
906
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
907
|
+
grid: { bottom: Number(e.target.value) || 0 },
|
|
908
|
+
}),
|
|
909
|
+
},
|
|
910
|
+
})
|
|
911
|
+
}
|
|
912
|
+
/>
|
|
913
|
+
</label>
|
|
914
|
+
<label className="block space-y-1">
|
|
915
|
+
{renderOptionLabel(t("panel.config.legendPosition"), "legend.top", t("panel.config.legendPositionHint"))}
|
|
916
|
+
<Select
|
|
917
|
+
value={String((el.chart?.option as any)?.legend?.top ?? "top")}
|
|
918
|
+
onValueChange={(value) =>
|
|
919
|
+
updateElement(el.id, {
|
|
920
|
+
chart: {
|
|
921
|
+
...(el.chart ?? {}),
|
|
922
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
923
|
+
legend: { top: value },
|
|
924
|
+
}),
|
|
925
|
+
},
|
|
926
|
+
})
|
|
927
|
+
}
|
|
928
|
+
>
|
|
929
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
930
|
+
<SelectContent>
|
|
931
|
+
<SelectItem value="top">top</SelectItem>
|
|
932
|
+
<SelectItem value="bottom">bottom</SelectItem>
|
|
933
|
+
<SelectItem value="left">left</SelectItem>
|
|
934
|
+
<SelectItem value="right">right</SelectItem>
|
|
935
|
+
</SelectContent>
|
|
936
|
+
</Select>
|
|
937
|
+
</label>
|
|
938
|
+
<label className="block space-y-1">
|
|
939
|
+
{renderOptionLabel(t("panel.config.legendOrient"), "legend.orient", t("panel.config.legendOrientHint"))}
|
|
940
|
+
<Select
|
|
941
|
+
value={String((el.chart?.option as any)?.legend?.orient ?? "horizontal")}
|
|
942
|
+
onValueChange={(value) =>
|
|
943
|
+
updateElement(el.id, {
|
|
944
|
+
chart: {
|
|
945
|
+
...(el.chart ?? {}),
|
|
946
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
947
|
+
legend: { orient: value },
|
|
948
|
+
}),
|
|
949
|
+
},
|
|
950
|
+
})
|
|
951
|
+
}
|
|
952
|
+
>
|
|
953
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
954
|
+
<SelectContent>
|
|
955
|
+
<SelectItem value="horizontal">horizontal</SelectItem>
|
|
956
|
+
<SelectItem value="vertical">vertical</SelectItem>
|
|
957
|
+
</SelectContent>
|
|
958
|
+
</Select>
|
|
959
|
+
</label>
|
|
960
|
+
<label className="flex items-center gap-2">
|
|
961
|
+
<Checkbox
|
|
962
|
+
checked={Boolean((el.chart?.option as any)?.legend?.show ?? true)}
|
|
963
|
+
onCheckedChange={(checked) =>
|
|
964
|
+
updateElement(el.id, {
|
|
965
|
+
chart: {
|
|
966
|
+
...(el.chart ?? {}),
|
|
967
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
968
|
+
legend: { show: checked === true },
|
|
969
|
+
}),
|
|
970
|
+
},
|
|
971
|
+
})
|
|
972
|
+
}
|
|
973
|
+
/>
|
|
974
|
+
<span>{t("panel.config.showLegendWithKey")}</span>
|
|
975
|
+
</label>
|
|
976
|
+
<label className="flex items-center gap-2">
|
|
977
|
+
<Checkbox
|
|
978
|
+
checked={Boolean(
|
|
979
|
+
Array.isArray((el.chart?.option as any)?.dataZoom) &&
|
|
980
|
+
(el.chart?.option as any)?.dataZoom.some((z: any) => z?.type === "inside")
|
|
981
|
+
)}
|
|
982
|
+
onCheckedChange={(checked) => {
|
|
983
|
+
const prev = Array.isArray((el.chart?.option as any)?.dataZoom)
|
|
984
|
+
? [...(el.chart?.option as any).dataZoom]
|
|
985
|
+
: [];
|
|
986
|
+
const next = checked
|
|
987
|
+
? [...prev.filter((z: any) => z?.type !== "inside"), { type: "inside" }]
|
|
988
|
+
: prev.filter((z: any) => z?.type !== "inside");
|
|
989
|
+
updateElement(el.id, {
|
|
990
|
+
chart: {
|
|
991
|
+
...(el.chart ?? {}),
|
|
992
|
+
option: mergeOptionPatch(el.chart?.option as any, { dataZoom: next }),
|
|
993
|
+
},
|
|
994
|
+
});
|
|
995
|
+
}}
|
|
996
|
+
/>
|
|
997
|
+
<span>{t("panel.config.zoomInsideWithKey")}</span>
|
|
998
|
+
</label>
|
|
999
|
+
<label className="flex items-center gap-2">
|
|
1000
|
+
<Checkbox
|
|
1001
|
+
checked={Boolean(
|
|
1002
|
+
Array.isArray((el.chart?.option as any)?.dataZoom) &&
|
|
1003
|
+
(el.chart?.option as any)?.dataZoom.some((z: any) => z?.type === "slider")
|
|
1004
|
+
)}
|
|
1005
|
+
onCheckedChange={(checked) => {
|
|
1006
|
+
const prev = Array.isArray((el.chart?.option as any)?.dataZoom)
|
|
1007
|
+
? [...(el.chart?.option as any).dataZoom]
|
|
1008
|
+
: [];
|
|
1009
|
+
const next = checked
|
|
1010
|
+
? [...prev.filter((z: any) => z?.type !== "slider"), { type: "slider" }]
|
|
1011
|
+
: prev.filter((z: any) => z?.type !== "slider");
|
|
1012
|
+
updateElement(el.id, {
|
|
1013
|
+
chart: {
|
|
1014
|
+
...(el.chart ?? {}),
|
|
1015
|
+
option: mergeOptionPatch(el.chart?.option as any, { dataZoom: next }),
|
|
1016
|
+
},
|
|
1017
|
+
});
|
|
1018
|
+
}}
|
|
1019
|
+
/>
|
|
1020
|
+
<span>{t("panel.config.zoomSliderWithKey")}</span>
|
|
1021
|
+
</label>
|
|
1022
|
+
<label className="flex items-center gap-2">
|
|
1023
|
+
<Checkbox
|
|
1024
|
+
checked={Boolean((el.chart?.option as any)?.axisPointer?.show ?? false)}
|
|
1025
|
+
onCheckedChange={(checked) =>
|
|
1026
|
+
updateElement(el.id, {
|
|
1027
|
+
chart: {
|
|
1028
|
+
...(el.chart ?? {}),
|
|
1029
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
1030
|
+
axisPointer: { show: checked === true },
|
|
1031
|
+
}),
|
|
1032
|
+
},
|
|
1033
|
+
})
|
|
1034
|
+
}
|
|
1035
|
+
/>
|
|
1036
|
+
<span>{t("panel.config.showAxisPointerWithKey")}</span>
|
|
1037
|
+
</label>
|
|
1038
|
+
<label className="block space-y-1">
|
|
1039
|
+
<div>{t("panel.config.axisPointerTypeWithKey")}</div>
|
|
1040
|
+
<Select
|
|
1041
|
+
value={String((el.chart?.option as any)?.axisPointer?.type ?? "line")}
|
|
1042
|
+
onValueChange={(value) =>
|
|
1043
|
+
updateElement(el.id, {
|
|
1044
|
+
chart: {
|
|
1045
|
+
...(el.chart ?? {}),
|
|
1046
|
+
option: mergeOptionPatch(el.chart?.option as any, {
|
|
1047
|
+
axisPointer: { type: value },
|
|
1048
|
+
}),
|
|
1049
|
+
},
|
|
1050
|
+
})
|
|
1051
|
+
}
|
|
1052
|
+
>
|
|
1053
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1054
|
+
<SelectContent>
|
|
1055
|
+
<SelectItem value="line">line</SelectItem>
|
|
1056
|
+
<SelectItem value="shadow">shadow</SelectItem>
|
|
1057
|
+
<SelectItem value="cross">cross</SelectItem>
|
|
1058
|
+
</SelectContent>
|
|
1059
|
+
</Select>
|
|
1060
|
+
</label>
|
|
1061
|
+
<label className="block space-y-1 col-span-2">
|
|
1062
|
+
<div>{t("panel.config.advancedOptionJson")}</div>
|
|
1063
|
+
<Textarea
|
|
1064
|
+
className="h-28 font-mono text-[11px]"
|
|
1065
|
+
defaultValue={JSON.stringify(el.chart?.option ?? {}, null, 2)}
|
|
1066
|
+
placeholder={t("panel.config.advancedJsonPlaceholder")}
|
|
1067
|
+
onBlur={(e) => {
|
|
1068
|
+
const nextText = e.target.value.trim();
|
|
1069
|
+
if (!nextText) {
|
|
1070
|
+
updateElement(el.id, { chart: { ...(el.chart ?? {}), option: undefined } });
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
const parsed = JSON.parse(nextText) as Record<string, unknown>;
|
|
1075
|
+
updateElement(el.id, { chart: { ...(el.chart ?? {}), option: parsed } });
|
|
1076
|
+
} catch {
|
|
1077
|
+
// ignore invalid json input on blur
|
|
1078
|
+
}
|
|
1079
|
+
}}
|
|
1080
|
+
/>
|
|
1081
|
+
</label>
|
|
1082
|
+
</div>
|
|
1083
|
+
) : null}
|
|
1084
|
+
{el.materialType === "text" ? (
|
|
1085
|
+
<div className="grid grid-cols-2 gap-2">
|
|
1086
|
+
<label className="block space-y-1 col-span-2">
|
|
1087
|
+
<div>{t("panel.config.textHtml")}</div>
|
|
1088
|
+
<Textarea
|
|
1089
|
+
className="h-24"
|
|
1090
|
+
value={el.textHtml ?? ""}
|
|
1091
|
+
onChange={(e) => updateElement(el.id, { textHtml: e.target.value || "<p><br/></p>" })}
|
|
1092
|
+
/>
|
|
1093
|
+
</label>
|
|
1094
|
+
<label className="block space-y-1">
|
|
1095
|
+
<div>{t("panel.config.fontSize")}</div>
|
|
1096
|
+
<Input
|
|
1097
|
+
className="h-7"
|
|
1098
|
+
type="number"
|
|
1099
|
+
min={8}
|
|
1100
|
+
value={el.textFontSize ?? 14}
|
|
1101
|
+
onChange={(e) => updateElement(el.id, { textFontSize: Math.max(8, Number(e.target.value) || 14) })}
|
|
1102
|
+
/>
|
|
1103
|
+
</label>
|
|
1104
|
+
<label className="block space-y-1">
|
|
1105
|
+
<div>{t("panel.config.textColor")}</div>
|
|
1106
|
+
<Input
|
|
1107
|
+
className="h-7"
|
|
1108
|
+
value={el.textColor ?? ""}
|
|
1109
|
+
onChange={(e) => updateElement(el.id, { textColor: e.target.value || undefined })}
|
|
1110
|
+
/>
|
|
1111
|
+
</label>
|
|
1112
|
+
</div>
|
|
1113
|
+
) : null}
|
|
1114
|
+
{el.materialType === "audio" ? (
|
|
1115
|
+
<div className="grid grid-cols-2 gap-2">
|
|
1116
|
+
<label className="block space-y-1 col-span-2">
|
|
1117
|
+
<div>{t("panel.config.audioUrl")}</div>
|
|
1118
|
+
<Input
|
|
1119
|
+
className="h-7"
|
|
1120
|
+
value={el.audioRemoteUrl ?? ""}
|
|
1121
|
+
onChange={(e) => updateElement(el.id, { audioRemoteUrl: e.target.value || undefined })}
|
|
1122
|
+
/>
|
|
1123
|
+
</label>
|
|
1124
|
+
<label className="block space-y-1">
|
|
1125
|
+
<div>{t("panel.config.effect")}</div>
|
|
1126
|
+
<Select
|
|
1127
|
+
value={el.audioVisualEffect ?? "pulse"}
|
|
1128
|
+
onValueChange={(value) =>
|
|
1129
|
+
updateElement(el.id, { audioVisualEffect: value as "none" | "pulse" | "ripple" })
|
|
1130
|
+
}
|
|
1131
|
+
>
|
|
1132
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1133
|
+
<SelectContent>
|
|
1134
|
+
<SelectItem value="none">none</SelectItem>
|
|
1135
|
+
<SelectItem value="pulse">pulse</SelectItem>
|
|
1136
|
+
<SelectItem value="ripple">ripple</SelectItem>
|
|
1137
|
+
</SelectContent>
|
|
1138
|
+
</Select>
|
|
1139
|
+
</label>
|
|
1140
|
+
<label className="block space-y-1">
|
|
1141
|
+
<div>{t("panel.config.speed")}</div>
|
|
1142
|
+
<Select
|
|
1143
|
+
value={el.audioVisualSpeed ?? "normal"}
|
|
1144
|
+
onValueChange={(value) =>
|
|
1145
|
+
updateElement(el.id, { audioVisualSpeed: value as "slow" | "normal" | "fast" })
|
|
1146
|
+
}
|
|
1147
|
+
>
|
|
1148
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1149
|
+
<SelectContent>
|
|
1150
|
+
<SelectItem value="slow">slow</SelectItem>
|
|
1151
|
+
<SelectItem value="normal">normal</SelectItem>
|
|
1152
|
+
<SelectItem value="fast">fast</SelectItem>
|
|
1153
|
+
</SelectContent>
|
|
1154
|
+
</Select>
|
|
1155
|
+
</label>
|
|
1156
|
+
</div>
|
|
1157
|
+
) : null}
|
|
1158
|
+
{el.materialType === "video" ? (
|
|
1159
|
+
<label className="block space-y-1">
|
|
1160
|
+
<div>{t("panel.config.videoUrl")}</div>
|
|
1161
|
+
<Input
|
|
1162
|
+
className="h-7"
|
|
1163
|
+
value={el.videoRemoteUrl ?? ""}
|
|
1164
|
+
onChange={(e) => updateElement(el.id, { videoRemoteUrl: e.target.value || undefined })}
|
|
1165
|
+
/>
|
|
1166
|
+
</label>
|
|
1167
|
+
) : null}
|
|
1168
|
+
{el.materialType === "grid" ? (
|
|
1169
|
+
<div className="grid grid-cols-2 gap-2 rounded-lg border border-border/60 bg-muted/20 p-3">
|
|
1170
|
+
<label className="block space-y-1.5">
|
|
1171
|
+
<div>{t("panel.config.rows")}</div>
|
|
1172
|
+
<Input className="h-7" type="number" min={1} value={el.gridRows ?? 2} onChange={(e) => updateElement(el.id, { gridRows: Math.max(1, Number(e.target.value) || 2) })} />
|
|
1173
|
+
</label>
|
|
1174
|
+
<label className="block space-y-1.5">
|
|
1175
|
+
<div>{t("panel.config.cols")}</div>
|
|
1176
|
+
<Input className="h-7" type="number" min={1} value={el.gridCols ?? 3} onChange={(e) => updateElement(el.id, { gridCols: Math.max(1, Number(e.target.value) || 3) })} />
|
|
1177
|
+
</label>
|
|
1178
|
+
</div>
|
|
1179
|
+
) : null}
|
|
1180
|
+
{el.materialType === "geometry" ? (
|
|
1181
|
+
<div className="grid grid-cols-2 gap-2 rounded-lg border border-border/60 bg-muted/20 p-3">
|
|
1182
|
+
<label className="block space-y-1.5">
|
|
1183
|
+
<div>{t("panel.config.shape")}</div>
|
|
1184
|
+
<Select
|
|
1185
|
+
value={el.geometryShape ?? "rect"}
|
|
1186
|
+
onValueChange={(value) =>
|
|
1187
|
+
updateElement(el.id, { geometryShape: value as PanelElement["geometryShape"] })
|
|
1188
|
+
}
|
|
1189
|
+
>
|
|
1190
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1191
|
+
<SelectContent>
|
|
1192
|
+
<SelectItem value="rect">{t("panel.config.shapeRect")}</SelectItem>
|
|
1193
|
+
<SelectItem value="circle">{t("panel.config.shapeCircle")}</SelectItem>
|
|
1194
|
+
<SelectItem value="triangle">{t("panel.config.shapeTriangle")}</SelectItem>
|
|
1195
|
+
<SelectItem value="diamond">{t("panel.config.shapeDiamond")}</SelectItem>
|
|
1196
|
+
<SelectItem value="hexagon">{t("panel.config.shapeHexagon")}</SelectItem>
|
|
1197
|
+
<SelectItem value="star">{t("panel.config.shapeStar")}</SelectItem>
|
|
1198
|
+
<SelectItem value="heart">{t("panel.config.shapeHeart")}</SelectItem>
|
|
1199
|
+
</SelectContent>
|
|
1200
|
+
</Select>
|
|
1201
|
+
</label>
|
|
1202
|
+
<label className="block space-y-1.5">
|
|
1203
|
+
<div>{t("panel.config.color")}</div>
|
|
1204
|
+
<Input
|
|
1205
|
+
className="h-7"
|
|
1206
|
+
value={el.geometryColor ?? "#3b82f6"}
|
|
1207
|
+
onChange={(e) => updateElement(el.id, { geometryColor: e.target.value || "#3b82f6" })}
|
|
1208
|
+
/>
|
|
1209
|
+
</label>
|
|
1210
|
+
</div>
|
|
1211
|
+
) : null}
|
|
1212
|
+
{el.materialType === "reference" || el.materialType === "viewport" ? (
|
|
1213
|
+
<div className="grid grid-cols-2 gap-2">
|
|
1214
|
+
<label className="block space-y-1.5">
|
|
1215
|
+
<div>{t("panel.config.refLayer")}</div>
|
|
1216
|
+
<Select
|
|
1217
|
+
value={el.refLayerId ?? "__none__"}
|
|
1218
|
+
onValueChange={(value) => updateElement(el.id, { refLayerId: value === "__none__" ? undefined : value })}
|
|
1219
|
+
>
|
|
1220
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1221
|
+
<SelectContent>
|
|
1222
|
+
<SelectItem value="__none__">{t("common.none")}</SelectItem>
|
|
1223
|
+
{layers.filter((l) => l.id !== el.layerId).map((l) => (
|
|
1224
|
+
<SelectItem key={l.id} value={l.id}>{l.name}</SelectItem>
|
|
1225
|
+
))}
|
|
1226
|
+
</SelectContent>
|
|
1227
|
+
</Select>
|
|
1228
|
+
</label>
|
|
1229
|
+
<label className="block space-y-1.5">
|
|
1230
|
+
<div>{t("panel.config.copy")}</div>
|
|
1231
|
+
<Select
|
|
1232
|
+
value={el.refCopyMode ?? "shallow"}
|
|
1233
|
+
onValueChange={(value) => setReferenceCopyMode?.(el.id, value as ReferenceCopyMode)}
|
|
1234
|
+
>
|
|
1235
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1236
|
+
<SelectContent>
|
|
1237
|
+
<SelectItem value="shallow">shallow</SelectItem>
|
|
1238
|
+
<SelectItem value="deep">deep</SelectItem>
|
|
1239
|
+
</SelectContent>
|
|
1240
|
+
</Select>
|
|
1241
|
+
</label>
|
|
1242
|
+
</div>
|
|
1243
|
+
) : null}
|
|
1244
|
+
{el.materialType === "viewport" ? (
|
|
1245
|
+
<label className="block space-y-1.5">
|
|
1246
|
+
<div>{t("panel.config.viewportOverflow")}</div>
|
|
1247
|
+
<Select
|
|
1248
|
+
value={normalizeViewportOverflow(el.viewportOverflow)}
|
|
1249
|
+
onValueChange={(value) =>
|
|
1250
|
+
updateElement(el.id, {
|
|
1251
|
+
viewportOverflow: value as ViewportOverflowMode,
|
|
1252
|
+
})
|
|
1253
|
+
}
|
|
1254
|
+
>
|
|
1255
|
+
<SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
|
|
1256
|
+
<SelectContent>
|
|
1257
|
+
{VIEWPORT_OVERFLOW_MODES.map((mode) => (
|
|
1258
|
+
<SelectItem key={mode} value={mode}>
|
|
1259
|
+
{mode === "scroll-x"
|
|
1260
|
+
? t("panel.config.viewportOverflowScrollX")
|
|
1261
|
+
: mode === "scroll-y"
|
|
1262
|
+
? t("panel.config.viewportOverflowScrollY")
|
|
1263
|
+
: mode === "scroll"
|
|
1264
|
+
? t("panel.config.viewportOverflowScroll")
|
|
1265
|
+
: t("panel.config.viewportOverflowClip")}
|
|
1266
|
+
</SelectItem>
|
|
1267
|
+
))}
|
|
1268
|
+
</SelectContent>
|
|
1269
|
+
</Select>
|
|
1270
|
+
</label>
|
|
1271
|
+
) : null}
|
|
1272
|
+
</div>
|
|
1273
|
+
</fieldset>
|
|
1274
|
+
</CardContent>
|
|
1275
|
+
) : null}
|
|
1276
|
+
</Card>
|
|
1277
|
+
))}
|
|
1278
|
+
{hasSearch &&
|
|
1279
|
+
effectiveSelectedElements.every((el) => {
|
|
1280
|
+
const text = `${el.name ?? ""} ${el.id} ${el.materialType ?? ""} zIndex style layer`
|
|
1281
|
+
.toLowerCase();
|
|
1282
|
+
return !text.includes(normalizedSearch);
|
|
1283
|
+
}) ? (
|
|
1284
|
+
<div className="rounded border border-border/60 bg-background px-2 py-1.5 text-[11px] text-muted-foreground">
|
|
1285
|
+
{t("panel.config.noEditableMatch")}
|
|
1286
|
+
</div>
|
|
1287
|
+
) : null}
|
|
1288
|
+
</div>
|
|
1289
|
+
|
|
1290
|
+
);
|
|
1291
|
+
}
|