@arronqzy/react-view 1.0.3 → 1.0.7

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.
Files changed (43) hide show
  1. package/dist/styles.css +555 -15
  2. package/package.json +14 -7
  3. package/readme.md +74 -5
  4. package/src/core/template.tsx +12 -2
  5. package/src/panel/ReactViewOnlinePreview.tsx +18 -8
  6. package/src/panel/ReactViewPanel.tsx +403 -177
  7. package/src/panel/ai/AssistantChatPanel.tsx +895 -0
  8. package/src/panel/ai/AssistantMarkdown.tsx +256 -0
  9. package/src/panel/ai/applyAssistantAction.ts +652 -0
  10. package/src/panel/ai/revealConfigField.ts +137 -0
  11. package/src/panel/components/ConfigHintIcon.tsx +50 -0
  12. package/src/panel/components/ElementsLayer.tsx +39 -22
  13. package/src/panel/components/MaterialSidebar.tsx +157 -109
  14. package/src/panel/components/MoveableLayer.tsx +75 -61
  15. package/src/panel/components/PanelCanvas.tsx +81 -64
  16. package/src/panel/components/PanelConfigSidebar.tsx +471 -448
  17. package/src/panel/components/SelectLayer.tsx +1 -0
  18. package/src/panel/components/ViewElementScopePanel.tsx +13 -12
  19. package/src/panel/components/WorkspaceConfigSidebar.tsx +5 -2
  20. package/src/panel/components/WorkspaceProjectNav.tsx +25 -14
  21. package/src/panel/components/WorkspaceStageSplit.tsx +3 -1
  22. package/src/panel/components/scope-config/ScopeConfigContext.tsx +9 -0
  23. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +242 -0
  24. package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +12 -0
  25. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +6 -3
  26. package/src/panel/components/table/ConditionEditor.tsx +205 -0
  27. package/src/panel/components/table/JsonCodeEditor.tsx +115 -0
  28. package/src/panel/components/table/PanelConfigTableSection.tsx +2192 -0
  29. package/src/panel/components/table/TableNodeContent.tsx +544 -0
  30. package/src/panel/constants/messages.ts +76 -34
  31. package/src/panel/hooks/usePanelElements.ts +64 -52
  32. package/src/panel/hooks/useRafThrottledScroll.ts +30 -0
  33. package/src/panel/hooks/useWorkspaceProjects.ts +11 -8
  34. package/src/panel/types.ts +6 -0
  35. package/src/panel/utils/chartOptionBuilder.ts +12 -1
  36. package/src/panel/utils/panelElementDefaults.ts +69 -39
  37. package/src/panel/utils/scope-field-labels.ts +51 -37
  38. package/src/panel/utils/scope-template-chart.ts +92 -0
  39. package/src/panel/utils/scope-template-preview.ts +128 -0
  40. package/src/panel/utils/scope-template-spread.ts +252 -0
  41. package/src/panel/utils/scope-template-warnings.ts +131 -11
  42. package/src/panel/utils/scope-template.ts +105 -3
  43. package/src/tailwind.css +36 -15
@@ -0,0 +1,544 @@
1
+ import React, { useMemo, useRef } from "react";
2
+ import { useVirtualizer } from "@tanstack/react-virtual";
3
+ import {
4
+ transformToTableCached,
5
+ resolveCellDisplay,
6
+ resolveRowDisplay,
7
+ stylePropsToCss,
8
+ resolveRawTableInput,
9
+ resolveProgressDisplay,
10
+ type PanelTableConfig,
11
+ type NormalizedColumn,
12
+ type NormalizedRow,
13
+ type CellDisplay,
14
+ type TableCellActionPayload,
15
+ } from "@arronqzy/view-table";
16
+ import type { PanelElement } from "../../types";
17
+ import { useI18n } from "@arronqzy/i18n/react";
18
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@arronqzy/ui";
19
+
20
+ export type TableCellActionHandler = (payload: TableCellActionPayload) => void;
21
+
22
+ function wrapCellTooltip(cell: CellDisplay, node: React.ReactElement) {
23
+ if (!cell.tooltip?.enabled || !cell.tooltip.text) return node;
24
+ return (
25
+ <Tooltip delayDuration={250}>
26
+ <TooltipTrigger asChild>{node}</TooltipTrigger>
27
+ <TooltipContent
28
+ side={cell.tooltip.placement}
29
+ className="max-w-xs whitespace-pre-wrap break-words"
30
+ >
31
+ {cell.tooltip.text}
32
+ </TooltipContent>
33
+ </Tooltip>
34
+ );
35
+ }
36
+
37
+ function TableCellView({
38
+ cell,
39
+ row,
40
+ column,
41
+ elementId,
42
+ interactive,
43
+ onCellAction,
44
+ }: {
45
+ cell: CellDisplay;
46
+ row: NormalizedRow;
47
+ column: NormalizedColumn;
48
+ elementId: string;
49
+ interactive?: boolean;
50
+ onCellAction?: TableCellActionHandler;
51
+ }) {
52
+ const emit = (kind: TableCellActionPayload["kind"], blueprintNodeId?: string) => {
53
+ const id = blueprintNodeId?.trim();
54
+ if (!id || !onCellAction) return;
55
+ onCellAction({
56
+ kind,
57
+ blueprintNodeId: id,
58
+ elementId,
59
+ columnField: column.field,
60
+ rowId: row.id,
61
+ row: row.values,
62
+ value: cell.raw,
63
+ });
64
+ };
65
+
66
+ const style = stylePropsToCss(cell.style);
67
+ const textOverflow = cell.widgetProps?.textStyle?.overflow ?? "ellipsis";
68
+ const isWrap = cell.widget === "text" && textOverflow === "wrap";
69
+ const common: React.CSSProperties = {
70
+ ...style,
71
+ overflow: "hidden",
72
+ textOverflow: isWrap ? undefined : "ellipsis",
73
+ whiteSpace: isWrap ? "normal" : "nowrap",
74
+ wordBreak: isWrap ? "break-word" : undefined,
75
+ width: "100%",
76
+ height: "100%",
77
+ display: "flex",
78
+ alignItems: isWrap ? "flex-start" : "center",
79
+ boxSizing: "border-box",
80
+ padding: style.padding ?? "0 8px",
81
+ };
82
+
83
+ let content: React.ReactElement;
84
+
85
+ if (cell.widget === "tag" || cell.widget === "badge") {
86
+ const bg = cell.color ?? "#3b82f6";
87
+ content = (
88
+ <div style={common}>
89
+ <span
90
+ style={{
91
+ display: "inline-flex",
92
+ alignItems: "center",
93
+ gap: 6,
94
+ maxWidth: "100%",
95
+ borderRadius: cell.widget === "badge" ? 999 : 4,
96
+ padding: cell.widget === "badge" ? "0 8px" : "1px 8px",
97
+ fontSize: 12,
98
+ lineHeight: "20px",
99
+ color: "#fff",
100
+ background: bg,
101
+ }}
102
+ >
103
+ {cell.widget === "badge" ? (
104
+ <span
105
+ style={{
106
+ width: 6,
107
+ height: 6,
108
+ borderRadius: 99,
109
+ background: "rgba(255,255,255,0.9)",
110
+ flex: "0 0 auto",
111
+ }}
112
+ />
113
+ ) : null}
114
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{cell.text}</span>
115
+ </span>
116
+ </div>
117
+ );
118
+ } else if (cell.widget === "link") {
119
+ content = (
120
+ <div style={common}>
121
+ <a
122
+ href={cell.href || "#"}
123
+ target={cell.widgetProps?.openInNewTab === false ? undefined : "_blank"}
124
+ rel="noreferrer"
125
+ onClick={(e) => {
126
+ e.stopPropagation();
127
+ emit("click", cell.widgetProps?.actions?.onClickBlueprintNodeId);
128
+ }}
129
+ style={{ color: "#2563eb", textDecoration: "underline", overflow: "hidden", textOverflow: "ellipsis" }}
130
+ >
131
+ {cell.text || cell.href}
132
+ </a>
133
+ </div>
134
+ );
135
+ } else if (cell.widget === "progress") {
136
+ const pct = cell.progress ?? 0;
137
+ const display = resolveProgressDisplay(cell.widgetProps);
138
+ const trackColor = cell.color ?? "#3b82f6";
139
+ const label = (
140
+ <span style={{ fontSize: 11, color: "rgba(0,0,0,0.55)", flex: "0 0 auto" }}>
141
+ {cell.text}
142
+ </span>
143
+ );
144
+
145
+ if (display === "label") {
146
+ content = <div style={common}>{label}</div>;
147
+ } else if (display === "circle") {
148
+ const size = Math.max(16, cell.widgetProps?.progressSize ?? 28);
149
+ const stroke = Math.max(2, cell.widgetProps?.progressStrokeWidth ?? 3);
150
+ const r = (size - stroke) / 2;
151
+ const c = 2 * Math.PI * r;
152
+ const offset = c * (1 - Math.max(0, Math.min(100, pct)) / 100);
153
+ content = (
154
+ <div style={{ ...common, gap: 6, justifyContent: "center" }}>
155
+ <svg width={size} height={size} style={{ flex: "0 0 auto" }}>
156
+ <circle
157
+ cx={size / 2}
158
+ cy={size / 2}
159
+ r={r}
160
+ fill="none"
161
+ stroke="rgba(0,0,0,0.08)"
162
+ strokeWidth={stroke}
163
+ />
164
+ <circle
165
+ cx={size / 2}
166
+ cy={size / 2}
167
+ r={r}
168
+ fill="none"
169
+ stroke={trackColor}
170
+ strokeWidth={stroke}
171
+ strokeLinecap="round"
172
+ strokeDasharray={c}
173
+ strokeDashoffset={offset}
174
+ transform={`rotate(-90 ${size / 2} ${size / 2})`}
175
+ />
176
+ <text
177
+ x="50%"
178
+ y="50%"
179
+ dominantBaseline="central"
180
+ textAnchor="middle"
181
+ fontSize={Math.max(8, Math.round(size * 0.32))}
182
+ fill="rgba(0,0,0,0.65)"
183
+ >
184
+ {Math.round(pct)}
185
+ </text>
186
+ </svg>
187
+ </div>
188
+ );
189
+ } else {
190
+ const bar = (
191
+ <div
192
+ style={{
193
+ flex: 1,
194
+ height: 8,
195
+ borderRadius: 99,
196
+ background: "rgba(0,0,0,0.08)",
197
+ overflow: "hidden",
198
+ minWidth: 24,
199
+ }}
200
+ >
201
+ <div style={{ width: `${pct}%`, height: "100%", background: trackColor }} />
202
+ </div>
203
+ );
204
+
205
+ content = (
206
+ <div style={{ ...common, gap: 8 }}>
207
+ {bar}
208
+ {display === "barLabel" ? label : null}
209
+ </div>
210
+ );
211
+ }
212
+ } else if (cell.widget === "image") {
213
+ const w = cell.widgetProps?.imageWidth ?? 28;
214
+ const h = cell.widgetProps?.imageHeight ?? 28;
215
+ content = (
216
+ <div style={common}>
217
+ {cell.imageUrl ? (
218
+ <img
219
+ src={cell.imageUrl}
220
+ alt=""
221
+ style={{
222
+ width: w,
223
+ height: h,
224
+ objectFit: cell.widgetProps?.imageObjectFit ?? "cover",
225
+ borderRadius: 4,
226
+ }}
227
+ draggable={false}
228
+ />
229
+ ) : (
230
+ <span style={{ fontSize: 11, opacity: 0.5 }}>—</span>
231
+ )}
232
+ </div>
233
+ );
234
+ } else if (cell.widget === "boolean") {
235
+ const on = Boolean(cell.booleanValue);
236
+ const canToggle = Boolean(interactive && onCellAction);
237
+ content = (
238
+ <div style={common}>
239
+ <button
240
+ type="button"
241
+ disabled={!canToggle}
242
+ onMouseDown={(e) => e.stopPropagation()}
243
+ onClick={(e) => {
244
+ e.stopPropagation();
245
+ if (!canToggle) return;
246
+ if (on) {
247
+ emit("toggleOff", cell.widgetProps?.actions?.onFalseBlueprintNodeId);
248
+ } else {
249
+ emit("toggleOn", cell.widgetProps?.actions?.onTrueBlueprintNodeId);
250
+ }
251
+ }}
252
+ style={{
253
+ width: 28,
254
+ height: 16,
255
+ borderRadius: 99,
256
+ background: on ? "#16a34a" : "rgba(0,0,0,0.2)",
257
+ position: "relative",
258
+ display: "inline-block",
259
+ flex: "0 0 auto",
260
+ border: "none",
261
+ padding: 0,
262
+ cursor: canToggle ? "pointer" : "default",
263
+ }}
264
+ aria-pressed={on}
265
+ >
266
+ <span
267
+ style={{
268
+ position: "absolute",
269
+ top: 2,
270
+ left: on ? 14 : 2,
271
+ width: 12,
272
+ height: 12,
273
+ borderRadius: 99,
274
+ background: "#fff",
275
+ }}
276
+ />
277
+ </button>
278
+ <span style={{ marginLeft: 6, fontSize: 12 }}>{cell.text}</span>
279
+ </div>
280
+ );
281
+ } else {
282
+ const textStyle = cell.widgetProps?.textStyle;
283
+ const canClickText = Boolean(cell.widgetProps?.actions?.onClickBlueprintNodeId);
284
+ content = (
285
+ <div
286
+ style={{
287
+ ...common,
288
+ fontFamily: textStyle?.fontFamily,
289
+ fontStyle: textStyle?.fontStyle,
290
+ textDecoration: textStyle?.textDecoration === "none" ? undefined : textStyle?.textDecoration,
291
+ cursor: canClickText ? "pointer" : undefined,
292
+ color: canClickText && !common.color ? "#2563eb" : common.color,
293
+ }}
294
+ onClick={(e) => {
295
+ if (!canClickText) return;
296
+ e.stopPropagation();
297
+ emit("click", cell.widgetProps?.actions?.onClickBlueprintNodeId);
298
+ }}
299
+ >
300
+ {cell.text}
301
+ </div>
302
+ );
303
+ }
304
+
305
+ return wrapCellTooltip(cell, content);
306
+ }
307
+
308
+ const MemoCell = React.memo(TableCellView);
309
+
310
+ function TableRow({
311
+ row,
312
+ columns,
313
+ config,
314
+ rowHeight,
315
+ gridTemplate,
316
+ elementId,
317
+ interactive,
318
+ onCellAction,
319
+ }: {
320
+ row: NormalizedRow;
321
+ columns: NormalizedColumn[];
322
+ config: PanelTableConfig;
323
+ rowHeight: number;
324
+ gridTemplate: string;
325
+ elementId: string;
326
+ interactive?: boolean;
327
+ onCellAction?: TableCellActionHandler;
328
+ }) {
329
+ const rowDisplay = resolveRowDisplay(row, config);
330
+ return (
331
+ <div
332
+ role="row"
333
+ style={{
334
+ display: "grid",
335
+ gridTemplateColumns: gridTemplate,
336
+ height: rowHeight,
337
+ boxSizing: "border-box",
338
+ borderBottom: "1px solid rgba(0,0,0,0.06)",
339
+ ...stylePropsToCss(rowDisplay.style),
340
+ }}
341
+ >
342
+ {columns.map((col) => {
343
+ const cell = resolveCellDisplay(row, col, config);
344
+ return (
345
+ <div
346
+ key={col.id}
347
+ role="cell"
348
+ style={{
349
+ minWidth: 0,
350
+ borderRight: "1px solid rgba(0,0,0,0.04)",
351
+ }}
352
+ >
353
+ <MemoCell
354
+ cell={cell}
355
+ row={row}
356
+ column={col}
357
+ elementId={elementId}
358
+ interactive={interactive}
359
+ onCellAction={onCellAction}
360
+ />
361
+ </div>
362
+ );
363
+ })}
364
+ </div>
365
+ );
366
+ }
367
+
368
+ const MemoRow = React.memo(TableRow);
369
+
370
+ export function TableNodeContent({
371
+ element,
372
+ interactive = true,
373
+ onCellAction,
374
+ }: {
375
+ element: PanelElement;
376
+ interactive?: boolean;
377
+ onCellAction?: TableCellActionHandler;
378
+ }) {
379
+ const { t } = useI18n();
380
+ const parentRef = useRef<HTMLDivElement | null>(null);
381
+ const config = element.table;
382
+
383
+ const model = useMemo(() => {
384
+ const cfg = config ?? {};
385
+ try {
386
+ const raw = resolveRawTableInput(cfg);
387
+ return transformToTableCached(raw, cfg);
388
+ } catch {
389
+ return { columns: [], rows: [] };
390
+ }
391
+ }, [config]);
392
+
393
+ const columns = model.columns;
394
+ const tableConfig = config ?? {};
395
+ const rowHeight = Math.max(24, tableConfig.rowHeight ?? 36);
396
+ const showHeader = tableConfig.showHeader !== false;
397
+ const emptyText = tableConfig.emptyText || t("panel.config.tableEmpty");
398
+
399
+ const gridTemplate = useMemo(
400
+ () =>
401
+ columns
402
+ .map((c) => {
403
+ if (c.width) return `${c.width}px`;
404
+ if (c.minWidth) return `minmax(${c.minWidth}px, 1fr)`;
405
+ return "minmax(96px, 1fr)";
406
+ })
407
+ .join(" "),
408
+ [columns]
409
+ );
410
+
411
+ const virtualizer = useVirtualizer({
412
+ count: model.rows.length,
413
+ getScrollElement: () => parentRef.current,
414
+ estimateSize: () => rowHeight,
415
+ overscan: 8,
416
+ });
417
+
418
+ const tableStyle = stylePropsToCss(tableConfig.tableStyle ?? {});
419
+ const headerStyle = stylePropsToCss(tableConfig.headerStyle ?? {});
420
+ const tableTextColor =
421
+ (typeof tableStyle.color === "string" && tableStyle.color.trim()) ||
422
+ (typeof tableConfig.tableStyle?.color === "string" && tableConfig.tableStyle.color.trim()) ||
423
+ "#111827";
424
+ const headerTextColor =
425
+ (typeof headerStyle.color === "string" && headerStyle.color.trim()) ||
426
+ (typeof tableConfig.headerStyle?.color === "string" && tableConfig.headerStyle.color.trim()) ||
427
+ tableTextColor;
428
+
429
+ return (
430
+ <TooltipProvider delayDuration={250}>
431
+ <div
432
+ className="rv-table-node h-full w-full overflow-hidden"
433
+ style={{
434
+ background: (tableConfig.tableStyle?.backgroundColor as string) || "#fff",
435
+ border:
436
+ tableConfig.tableStyle?.borderWidth != null
437
+ ? `${tableConfig.tableStyle.borderWidth}px solid ${tableConfig.tableStyle.borderColor ?? "rgba(0,0,0,0.12)"}`
438
+ : "1px solid rgba(0,0,0,0.08)",
439
+ borderRadius: tableConfig.tableStyle?.borderRadius ?? 6,
440
+ fontSize: tableConfig.tableStyle?.fontSize ?? 12,
441
+ ...tableStyle,
442
+ color: tableTextColor,
443
+ display: "flex",
444
+ flexDirection: "column",
445
+ }}
446
+ >
447
+ {showHeader ? (
448
+ <div
449
+ role="row"
450
+ style={{
451
+ display: "grid",
452
+ gridTemplateColumns: gridTemplate,
453
+ height: rowHeight,
454
+ flex: "0 0 auto",
455
+ position: tableConfig.tableStyle?.stickyHeader === false ? "relative" : "sticky",
456
+ top: 0,
457
+ zIndex: 2,
458
+ background: (tableConfig.headerStyle?.backgroundColor as string) || "rgba(0,0,0,0.04)",
459
+ borderBottom: "1px solid rgba(0,0,0,0.1)",
460
+ ...headerStyle,
461
+ color: headerTextColor,
462
+ }}
463
+ >
464
+ {columns.map((col) => {
465
+ const headerLabel = (col.title ?? "").trim() || col.field || col.id;
466
+ return (
467
+ <div
468
+ key={col.id}
469
+ role="columnheader"
470
+ style={{
471
+ padding: "0 8px",
472
+ display: "flex",
473
+ alignItems: "center",
474
+ fontWeight: tableConfig.headerStyle?.fontWeight ?? 600,
475
+ overflow: "hidden",
476
+ textOverflow: "ellipsis",
477
+ whiteSpace: "nowrap",
478
+ borderRight: "1px solid rgba(0,0,0,0.04)",
479
+ textAlign: col.align,
480
+ justifyContent:
481
+ col.align === "center" ? "center" : col.align === "right" ? "flex-end" : "flex-start",
482
+ }}
483
+ title={headerLabel}
484
+ >
485
+ {headerLabel}
486
+ </div>
487
+ );
488
+ })}
489
+ </div>
490
+ ) : null}
491
+
492
+ <div ref={parentRef} className="min-h-0 flex-1 overflow-auto">
493
+ {model.rows.length === 0 ? (
494
+ <div className="flex h-full flex-col items-center justify-center gap-2 px-3 text-center text-xs text-muted-foreground">
495
+ <svg
496
+ viewBox="0 0 48 48"
497
+ className="h-10 w-10 opacity-45"
498
+ fill="none"
499
+ stroke="currentColor"
500
+ strokeWidth="1.6"
501
+ aria-hidden
502
+ >
503
+ <rect x="8" y="12" width="32" height="24" rx="3" />
504
+ <path d="M8 20h32M20 12v24M28 12v24" strokeOpacity="0.55" />
505
+ <circle cx="24" cy="30" r="1.2" fill="currentColor" stroke="none" />
506
+ </svg>
507
+ <div>{emptyText}</div>
508
+ </div>
509
+ ) : (
510
+ <div style={{ height: virtualizer.getTotalSize(), position: "relative", width: "100%" }}>
511
+ {virtualizer.getVirtualItems().map((item) => {
512
+ const row = model.rows[item.index];
513
+ return (
514
+ <div
515
+ key={row.id}
516
+ style={{
517
+ position: "absolute",
518
+ top: 0,
519
+ left: 0,
520
+ width: "100%",
521
+ height: item.size,
522
+ transform: `translateY(${item.start}px)`,
523
+ }}
524
+ >
525
+ <MemoRow
526
+ row={row}
527
+ columns={columns}
528
+ config={tableConfig}
529
+ rowHeight={rowHeight}
530
+ gridTemplate={gridTemplate}
531
+ elementId={element.id}
532
+ interactive={interactive}
533
+ onCellAction={onCellAction}
534
+ />
535
+ </div>
536
+ );
537
+ })}
538
+ </div>
539
+ )}
540
+ </div>
541
+ </div>
542
+ </TooltipProvider>
543
+ );
544
+ }
@@ -1,34 +1,76 @@
1
- export const PANEL_MESSAGES = {
2
- nodeNotFound: "未找到节点,可能已被删除",
3
- sourceNodeNotFound: "未找到源节点,可能已被删除",
4
- targetLayerNotFound: "目标图层不存在",
5
- nodeMoveSameLayer: "已在当前图层",
6
- nodeDeleteLocked: "锁定节点不可删除,请在右侧【节点信息】中取消锁定后重试",
7
- nodeDeleteLayerLocked: "当前节点所在图层已锁定,无法删除。请先在图层栏取消锁定",
8
- nodeBatchDeleteContainsLocked:
9
- "选中节点包含锁定节点,无法批量删除。请先在右侧【节点信息】中取消锁定",
10
- nodeMoveLocked: "锁定节点不允许更换图层,请先在右侧【节点信息】中取消锁定",
11
- nodeMoveSourceLayerLocked: "节点所在图层已锁定,无法更换图层。请先在图层栏取消锁定",
12
- nodeMoveTargetLayerLocked: "目标图层已锁定,无法移入。请先在图层栏取消锁定",
13
- activeLayerLockedCannotAdd: "当前图层已锁定,无法新增节点",
14
- nodeLayerLockedCannotCopy: "当前节点所在图层已锁定,无法复制",
15
- selectionContainsLockedLayerNode: "选中节点包含锁定图层内容,无法批量删除",
16
- sourceLayerLockedCannotPaste: "复制源节点所在图层已锁定,无法粘贴",
17
- nodeConfigLocked: "当前节点已锁定,配置项不可编辑。",
18
- nodeConfigLayerLocked: "当前节点所在图层已锁定,配置项不可编辑。",
19
- readImageFailed: "读取图片失败",
20
- readAudioFailed: "读取音频失败",
21
- readVideoFailed: "读取视频失败",
22
- readRecordAudioFailed: "录音读取失败",
23
- audioLocalSaved: "已写入本地音频(base64)",
24
- audioRemoteUploaded: "已上传服务器并更新远程链接",
25
- audioServerUploadFailed: "服务器上传失败,仅保留本地音频",
26
- audioPosterSet: "已设置音频占位图",
27
- audioRecordUnsupported: "当前环境不支持录音",
28
- audioRecordSaved: "录音已保存,可直接播放",
29
- audioRecording: "录音中...",
30
- audioRecordStartFailed: "无法开始录音,请检查麦克风权限",
31
- videoLocalSaved: "已写入本地视频(base64)",
32
- videoRemoteUploaded: "已上传服务器并更新远程链接",
33
- videoServerUploadFailed: "服务器上传失败,仅保留本地视频",
34
- } as const;
1
+ import type { TranslateFn } from "@arronqzy/i18n";
2
+
3
+ export function getPanelMessages(t: TranslateFn) {
4
+ return {
5
+ nodeNotFound: t("panel.messages.nodeNotFound"),
6
+ sourceNodeNotFound: t("panel.messages.sourceNodeNotFound"),
7
+ targetLayerNotFound: t("panel.messages.targetLayerNotFound"),
8
+ nodeMoveSameLayer: t("panel.messages.nodeMoveSameLayer"),
9
+ nodeDeleteLocked: t("panel.messages.nodeDeleteLocked"),
10
+ nodeDeleteLayerLocked: t("panel.messages.nodeDeleteLayerLocked"),
11
+ nodeBatchDeleteContainsLocked: t("panel.messages.nodeBatchDeleteContainsLocked"),
12
+ nodeMoveLocked: t("panel.messages.nodeMoveLocked"),
13
+ nodeMoveSourceLayerLocked: t("panel.messages.nodeMoveSourceLayerLocked"),
14
+ nodeMoveTargetLayerLocked: t("panel.messages.nodeMoveTargetLayerLocked"),
15
+ activeLayerLockedCannotAdd: t("panel.messages.activeLayerLockedCannotAdd"),
16
+ nodeLayerLockedCannotCopy: t("panel.messages.nodeLayerLockedCannotCopy"),
17
+ selectionContainsLockedLayerNode: t("panel.messages.selectionContainsLockedLayerNode"),
18
+ sourceLayerLockedCannotPaste: t("panel.messages.sourceLayerLockedCannotPaste"),
19
+ nodeConfigLocked: t("panel.messages.nodeConfigLocked"),
20
+ nodeConfigLayerLocked: t("panel.messages.nodeConfigLayerLocked"),
21
+ readImageFailed: t("panel.messages.readImageFailed"),
22
+ readAudioFailed: t("panel.messages.readAudioFailed"),
23
+ readVideoFailed: t("panel.messages.readVideoFailed"),
24
+ readRecordAudioFailed: t("panel.messages.readRecordAudioFailed"),
25
+ audioLocalSaved: t("panel.messages.audioLocalSaved"),
26
+ audioRemoteUploaded: t("panel.messages.audioRemoteUploaded"),
27
+ audioServerUploadFailed: t("panel.messages.audioServerUploadFailed"),
28
+ audioPosterSet: t("panel.messages.audioPosterSet"),
29
+ audioRecordUnsupported: t("panel.messages.audioRecordUnsupported"),
30
+ audioRecordSaved: t("panel.messages.audioRecordSaved"),
31
+ audioRecording: t("panel.messages.audioRecording"),
32
+ audioRecordStartFailed: t("panel.messages.audioRecordStartFailed"),
33
+ videoLocalSaved: t("panel.messages.videoLocalSaved"),
34
+ videoRemoteUploaded: t("panel.messages.videoRemoteUploaded"),
35
+ videoServerUploadFailed: t("panel.messages.videoServerUploadFailed"),
36
+ operationRestricted: t("panel.messages.operationRestricted"),
37
+ importInvalidFormat: t("panel.messages.importInvalidFormat"),
38
+ importJsonParseError: t("panel.messages.importJsonParseError"),
39
+ blueprintImportInvalid: t("panel.messages.blueprintImportInvalid"),
40
+ iconReadFailed: t("panel.messages.iconReadFailed"),
41
+ blueprintNotFound: t("panel.messages.blueprintNotFound"),
42
+ blueprintSynced: (name: string) => t("panel.messages.blueprintSynced", { name }),
43
+ blueprintSavedLocal: t("panel.messages.blueprintSavedLocal"),
44
+ blueprintSavedToLibrary: t("panel.messages.blueprintSavedToLibrary"),
45
+ blueprintSyncedToLibrary: t("panel.messages.blueprintSyncedToLibrary"),
46
+ blueprintExported: t("panel.messages.blueprintExported"),
47
+ blueprintRenamed: t("panel.messages.blueprintRenamed"),
48
+ renameFailed: t("panel.messages.renameFailed"),
49
+ blueprintDeletedFromLibrary: t("panel.messages.blueprintDeletedFromLibrary"),
50
+ blueprintImported: t("panel.messages.blueprintImported"),
51
+ workspaceCreated: (name: string) => t("panel.messages.workspaceCreated", { name }),
52
+ workspaceLoaded: t("panel.messages.workspaceLoaded"),
53
+ workspaceLoadedDesc: t("panel.messages.workspaceLoadedDesc"),
54
+ openWorkspaceFailed: t("panel.messages.openWorkspaceFailed"),
55
+ syncFailed: t("panel.messages.syncFailed"),
56
+ workspaceDeleted: t("panel.messages.workspaceDeleted"),
57
+ openPreviewFailed: t("panel.messages.openPreviewFailed"),
58
+ clearedBlueprintLogs: (removed: number) =>
59
+ t("panel.messages.clearedBlueprintLogs", { removed }),
60
+ noSavedBlueprintLogs: t("panel.messages.noSavedBlueprintLogs"),
61
+ mergeNeedTwoLayers: t("panel.messages.mergeNeedTwoLayers"),
62
+ layerNotFound: t("panel.messages.layerNotFound"),
63
+ defaultLayerCannotDelete: t("panel.messages.defaultLayerCannotDelete"),
64
+ lockedLayerCannotDelete: t("panel.messages.lockedLayerCannotDelete"),
65
+ selectTargetLayer: t("panel.messages.selectTargetLayer"),
66
+ shallowRefBlockingDelete: t("panel.messages.shallowRefBlockingDelete"),
67
+ noNodesSelected: t("panel.messages.noNodesSelected"),
68
+ noMigratableNodes: t("panel.messages.noMigratableNodes"),
69
+ lockedCannotOpenMapping: t("panel.messages.lockedCannotOpenMapping"),
70
+ saveWorkspaceFirst: t("panel.messages.saveWorkspaceFirst"),
71
+ workspaceNotFound: t("panel.messages.workspaceNotFound"),
72
+ workspaceDataInvalid: t("panel.messages.workspaceDataInvalid"),
73
+ };
74
+ }
75
+
76
+ export type PanelMessages = ReturnType<typeof getPanelMessages>;