@arronqzy/react-view 1.0.5 → 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.
- package/dist/styles.css +519 -9
- package/package.json +12 -4
- package/src/core/template.tsx +12 -2
- package/src/panel/ReactViewOnlinePreview.tsx +18 -8
- package/src/panel/ReactViewPanel.tsx +386 -170
- package/src/panel/ai/AssistantChatPanel.tsx +895 -0
- package/src/panel/ai/AssistantMarkdown.tsx +256 -0
- package/src/panel/ai/applyAssistantAction.ts +652 -0
- package/src/panel/ai/revealConfigField.ts +137 -0
- package/src/panel/components/ConfigHintIcon.tsx +6 -2
- package/src/panel/components/ElementsLayer.tsx +37 -18
- package/src/panel/components/MaterialSidebar.tsx +157 -109
- package/src/panel/components/MoveableLayer.tsx +7 -6
- package/src/panel/components/PanelConfigSidebar.tsx +434 -389
- package/src/panel/components/ViewElementScopePanel.tsx +9 -5
- package/src/panel/components/WorkspaceConfigSidebar.tsx +5 -2
- package/src/panel/components/WorkspaceProjectNav.tsx +25 -14
- package/src/panel/components/WorkspaceStageSplit.tsx +3 -1
- package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +11 -7
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +6 -18
- package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +5 -2
- package/src/panel/components/table/ConditionEditor.tsx +205 -0
- package/src/panel/components/table/JsonCodeEditor.tsx +115 -0
- package/src/panel/components/table/PanelConfigTableSection.tsx +2192 -0
- package/src/panel/components/table/TableNodeContent.tsx +544 -0
- package/src/panel/constants/messages.ts +76 -34
- package/src/panel/hooks/usePanelElements.ts +64 -52
- package/src/panel/hooks/useWorkspaceProjects.ts +11 -8
- package/src/panel/types.ts +4 -0
- package/src/panel/utils/panelElementDefaults.ts +69 -39
- package/src/panel/utils/scope-field-labels.ts +51 -38
- package/src/panel/utils/scope-template-preview.ts +10 -6
- package/src/panel/utils/scope-template-spread.ts +44 -21
- package/src/panel/utils/scope-template-warnings.ts +28 -16
- package/src/panel/utils/scope-template.ts +82 -2
- package/src/tailwind.css +17 -9
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
3
|
import type { State } from "@arronqzy/rx-store";
|
|
3
4
|
import type { PanelElement } from "./types";
|
|
4
5
|
|
|
@@ -14,6 +15,14 @@ import { SelectLayer } from "./components/SelectLayer";
|
|
|
14
15
|
import { MoveableLayer } from "./components/MoveableLayer";
|
|
15
16
|
import { buildChartOption, CHART_TYPES } from "./utils/chartOptionBuilder";
|
|
16
17
|
import { MaterialSidebar } from "./components/MaterialSidebar";
|
|
18
|
+
import { AssistantChatPanel } from "./ai/AssistantChatPanel";
|
|
19
|
+
import {
|
|
20
|
+
listLayersFromStore,
|
|
21
|
+
listPanelElementsFromStore,
|
|
22
|
+
getActiveLayerIdFromStore,
|
|
23
|
+
type ApplyAssistantDeps,
|
|
24
|
+
} from "./ai/applyAssistantAction";
|
|
25
|
+
import { revealPanelConfigFromPatch } from "./ai/revealConfigField";
|
|
17
26
|
import {
|
|
18
27
|
BlueprintGraph,
|
|
19
28
|
BlueprintMetaDialog,
|
|
@@ -55,7 +64,7 @@ import {
|
|
|
55
64
|
useViewScopeStoreVersion,
|
|
56
65
|
} from "./scope/view-scope-store";
|
|
57
66
|
import { resolvePanelElementScope } from "./utils/scope-template";
|
|
58
|
-
import {
|
|
67
|
+
import { getPanelMessages } from "./constants/messages";
|
|
59
68
|
import { PANEL_Z_INDEX } from "./constants/zIndex";
|
|
60
69
|
import { useRafThrottledScroll } from "./hooks/useRafThrottledScroll";
|
|
61
70
|
import {
|
|
@@ -67,6 +76,7 @@ import {
|
|
|
67
76
|
AlertDialogFooter,
|
|
68
77
|
AlertDialogHeader,
|
|
69
78
|
AlertDialogTitle,
|
|
79
|
+
Button,
|
|
70
80
|
Checkbox,
|
|
71
81
|
Dialog,
|
|
72
82
|
DialogContent,
|
|
@@ -274,16 +284,20 @@ function IconImage() {
|
|
|
274
284
|
);
|
|
275
285
|
}
|
|
276
286
|
|
|
277
|
-
function formatRelativeTime(
|
|
287
|
+
function formatRelativeTime(
|
|
288
|
+
timestamp: number,
|
|
289
|
+
now: number,
|
|
290
|
+
t: (key: string, params?: Record<string, string | number>) => string
|
|
291
|
+
): string {
|
|
278
292
|
const diff = Math.max(0, now - timestamp);
|
|
279
293
|
const sec = Math.floor(diff / 1000);
|
|
280
|
-
if (sec < 60) return
|
|
294
|
+
if (sec < 60) return t("common.secondsAgo", { sec });
|
|
281
295
|
const min = Math.floor(sec / 60);
|
|
282
|
-
if (min < 60) return
|
|
296
|
+
if (min < 60) return t("common.minutesAgo", { min });
|
|
283
297
|
const hour = Math.floor(min / 60);
|
|
284
|
-
if (hour < 24) return
|
|
298
|
+
if (hour < 24) return t("common.hoursAgo", { hour });
|
|
285
299
|
const day = Math.floor(hour / 24);
|
|
286
|
-
return
|
|
300
|
+
return t("common.daysAgo", { day });
|
|
287
301
|
}
|
|
288
302
|
|
|
289
303
|
function getSelectedTargetsFromIds(
|
|
@@ -333,7 +347,12 @@ export type ReactViewPanelProps = {
|
|
|
333
347
|
className?: string;
|
|
334
348
|
};
|
|
335
349
|
|
|
336
|
-
export function ReactViewPanel({
|
|
350
|
+
export function ReactViewPanel({
|
|
351
|
+
initialZoom = 1,
|
|
352
|
+
className,
|
|
353
|
+
}: ReactViewPanelProps) {
|
|
354
|
+
const { t, locale, setLocale } = useI18n();
|
|
355
|
+
const messages = useMemo(() => getPanelMessages(t), [t]);
|
|
337
356
|
const THEME_STORAGE_KEY = "panel:theme";
|
|
338
357
|
const TITLE_ICON_STORAGE_KEY = "panel:titleIconDataUrl";
|
|
339
358
|
const { setTheme } = useTheme();
|
|
@@ -435,16 +454,19 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
435
454
|
const [isHistoryPanelExpanded, setIsHistoryPanelExpanded] = useState(false);
|
|
436
455
|
const [historyNow, setHistoryNow] = useState(Date.now());
|
|
437
456
|
const [historyKeyword, setHistoryKeyword] = useState("");
|
|
438
|
-
const [productName, setProductName] = useState("
|
|
457
|
+
const [productName, setProductName] = useState(t("panel.defaults.unnamedProduct"));
|
|
439
458
|
const [titleIconDataUrl, setTitleIconDataUrl] = useState<string>("");
|
|
440
459
|
const [titleIconPreviewOpen, setTitleIconPreviewOpen] = useState(false);
|
|
441
460
|
const [titleIconZoom, setTitleIconZoom] = useState(1.6);
|
|
442
461
|
const [pendingPreviewLayerId, setPendingPreviewLayerId] = useState<string | null>(null);
|
|
443
462
|
const [panelFontSize, setPanelFontSize] = useState<"sm" | "md" | "lg">("md");
|
|
444
463
|
const [blueprintOpen, setBlueprintOpen] = useState(false);
|
|
464
|
+
const [assistantOpen, setAssistantOpen] = useState(false);
|
|
445
465
|
const [blueprintGraph, setBlueprintGraph] = useState(() => BlueprintGraph.empty());
|
|
466
|
+
const blueprintGraphRef = useRef(blueprintGraph);
|
|
467
|
+
blueprintGraphRef.current = blueprintGraph;
|
|
446
468
|
const [blueprintMeta, setBlueprintMeta] = useState<BlueprintMetaDraft>({
|
|
447
|
-
name: "
|
|
469
|
+
name: t("panel.defaults.unnamedBlueprint"),
|
|
448
470
|
remark: "",
|
|
449
471
|
});
|
|
450
472
|
const [blueprintLibraryItems, setBlueprintLibraryItems] = useState<
|
|
@@ -836,7 +858,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
836
858
|
const blueprintDebugSession = useBlueprintDebugSession({
|
|
837
859
|
graph: blueprintGraph,
|
|
838
860
|
blueprintId: activeBlueprintLibraryId,
|
|
839
|
-
blueprintName: blueprintMeta.name || "
|
|
861
|
+
blueprintName: blueprintMeta.name || t("panel.defaults.unnamedBlueprint"),
|
|
840
862
|
resolveLibraryBlueprint,
|
|
841
863
|
libraryNameById: blueprintLibraryNameById,
|
|
842
864
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
@@ -902,8 +924,8 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
902
924
|
toast({
|
|
903
925
|
title:
|
|
904
926
|
removed > 0
|
|
905
|
-
?
|
|
906
|
-
:
|
|
927
|
+
? messages.clearedBlueprintLogs(removed)
|
|
928
|
+
: messages.noSavedBlueprintLogs,
|
|
907
929
|
});
|
|
908
930
|
},
|
|
909
931
|
hasSavedRuns: blueprintDebugSession.totalSavedRunCount > 0,
|
|
@@ -954,7 +976,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
954
976
|
async (id: string) => {
|
|
955
977
|
const record = await getBlueprintLibraryRecord(id);
|
|
956
978
|
if (!record) {
|
|
957
|
-
toast({ title: "
|
|
979
|
+
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
958
980
|
void refreshBlueprintLibrary();
|
|
959
981
|
return;
|
|
960
982
|
}
|
|
@@ -1026,7 +1048,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1026
1048
|
|
|
1027
1049
|
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
1028
1050
|
if (!existing) {
|
|
1029
|
-
toast({ title: "
|
|
1051
|
+
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
1030
1052
|
void refreshBlueprintLibrary();
|
|
1031
1053
|
setActiveBlueprintLibraryId(null);
|
|
1032
1054
|
setBlueprintSyncedDocument(null);
|
|
@@ -1043,7 +1065,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1043
1065
|
await putBlueprintLibraryRecord(record);
|
|
1044
1066
|
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1045
1067
|
await refreshBlueprintLibrary();
|
|
1046
|
-
toast({ title:
|
|
1068
|
+
toast({ title: messages.blueprintSynced(record.name) });
|
|
1047
1069
|
}, [
|
|
1048
1070
|
activeBlueprintLibraryId,
|
|
1049
1071
|
blueprintGraph.document,
|
|
@@ -1080,7 +1102,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1080
1102
|
setActiveBlueprintLibraryId(record.id);
|
|
1081
1103
|
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1082
1104
|
await refreshBlueprintLibrary();
|
|
1083
|
-
toast({ title: "
|
|
1105
|
+
toast({ title: t("panel.messages.blueprintSavedLocal") });
|
|
1084
1106
|
},
|
|
1085
1107
|
[activeBlueprintLibraryId, blueprintGraph.document, refreshBlueprintLibrary]
|
|
1086
1108
|
);
|
|
@@ -1097,7 +1119,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1097
1119
|
downloadBlueprintExport(
|
|
1098
1120
|
buildBlueprintExportPayload(blueprintGraph.document, meta)
|
|
1099
1121
|
);
|
|
1100
|
-
toast({ title: "
|
|
1122
|
+
toast({ title: t("panel.messages.blueprintExported") });
|
|
1101
1123
|
return;
|
|
1102
1124
|
}
|
|
1103
1125
|
await saveBlueprintToLibrary(meta);
|
|
@@ -1109,7 +1131,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1109
1131
|
async (id: string, name: string) => {
|
|
1110
1132
|
const updated = await updateBlueprintLibraryMeta(id, { name });
|
|
1111
1133
|
if (!updated) {
|
|
1112
|
-
toast({ title: "
|
|
1134
|
+
toast({ title: t("panel.messages.renameFailed") });
|
|
1113
1135
|
void refreshBlueprintLibrary();
|
|
1114
1136
|
return;
|
|
1115
1137
|
}
|
|
@@ -1117,7 +1139,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1117
1139
|
if (activeBlueprintLibraryId === id) {
|
|
1118
1140
|
setBlueprintMeta((prev) => ({ ...prev, name }));
|
|
1119
1141
|
}
|
|
1120
|
-
toast({ title: "
|
|
1142
|
+
toast({ title: t("panel.messages.blueprintRenamed") });
|
|
1121
1143
|
},
|
|
1122
1144
|
[activeBlueprintLibraryId, refreshBlueprintLibrary]
|
|
1123
1145
|
);
|
|
@@ -1130,7 +1152,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1130
1152
|
setActiveBlueprintLibraryId(null);
|
|
1131
1153
|
setBlueprintSyncedDocument(null);
|
|
1132
1154
|
}
|
|
1133
|
-
toast({ title: "
|
|
1155
|
+
toast({ title: t("panel.messages.blueprintDeletedFromLibrary") });
|
|
1134
1156
|
},
|
|
1135
1157
|
[activeBlueprintLibraryId, refreshBlueprintLibrary]
|
|
1136
1158
|
);
|
|
@@ -1150,7 +1172,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1150
1172
|
snapshotWorkspaceBlueprint();
|
|
1151
1173
|
}
|
|
1152
1174
|
await loadBlueprintFromLibrary(record.id);
|
|
1153
|
-
toast({ title: "
|
|
1175
|
+
toast({ title: t("panel.messages.blueprintImported") });
|
|
1154
1176
|
},
|
|
1155
1177
|
[
|
|
1156
1178
|
activeBlueprintLibraryId,
|
|
@@ -1170,7 +1192,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1170
1192
|
workspaceBlueprintRef.current = {
|
|
1171
1193
|
document: record.blueprintDocument,
|
|
1172
1194
|
meta: {
|
|
1173
|
-
name: record.blueprintMeta?.name ?? "
|
|
1195
|
+
name: record.blueprintMeta?.name ?? t("panel.defaults.unnamedBlueprint"),
|
|
1174
1196
|
remark: record.blueprintMeta?.remark ?? "",
|
|
1175
1197
|
},
|
|
1176
1198
|
};
|
|
@@ -1198,7 +1220,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1198
1220
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
1199
1221
|
});
|
|
1200
1222
|
|
|
1201
|
-
useBlueprintPageLifecycle({
|
|
1223
|
+
const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
1202
1224
|
graph: blueprintGraph,
|
|
1203
1225
|
active: blueprintOpen,
|
|
1204
1226
|
bootKey: workspaceProjects.activeProjectId ?? undefined,
|
|
@@ -1213,7 +1235,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1213
1235
|
const handleWorkspaceCreateProject = useCallback(async () => {
|
|
1214
1236
|
const result = await workspaceProjects.handleCreateProject();
|
|
1215
1237
|
if (result?.name) {
|
|
1216
|
-
toast({ title:
|
|
1238
|
+
toast({ title: messages.workspaceCreated(result.name) });
|
|
1217
1239
|
}
|
|
1218
1240
|
return result;
|
|
1219
1241
|
}, [workspaceProjects]);
|
|
@@ -1225,12 +1247,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1225
1247
|
await workspaceProjects.handleOpenProject(id);
|
|
1226
1248
|
setSelectedIds([]);
|
|
1227
1249
|
toast({
|
|
1228
|
-
title: "
|
|
1229
|
-
description: "
|
|
1250
|
+
title: t("panel.messages.workspaceLoaded"),
|
|
1251
|
+
description: t("panel.messages.workspaceLoadedDesc"),
|
|
1230
1252
|
});
|
|
1231
1253
|
} catch (error) {
|
|
1232
1254
|
toast({
|
|
1233
|
-
title: error instanceof Error ? error.message : "
|
|
1255
|
+
title: error instanceof Error ? error.message : t("panel.messages.openWorkspaceFailed"),
|
|
1234
1256
|
});
|
|
1235
1257
|
}
|
|
1236
1258
|
},
|
|
@@ -1241,11 +1263,11 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1241
1263
|
try {
|
|
1242
1264
|
const name = await workspaceProjects.handleSyncProject();
|
|
1243
1265
|
if (name) {
|
|
1244
|
-
toast({ title:
|
|
1266
|
+
toast({ title: messages.blueprintSynced(name) });
|
|
1245
1267
|
}
|
|
1246
1268
|
} catch (error) {
|
|
1247
1269
|
toast({
|
|
1248
|
-
title: error instanceof Error ? error.message : "
|
|
1270
|
+
title: error instanceof Error ? error.message : t("panel.messages.syncFailed"),
|
|
1249
1271
|
});
|
|
1250
1272
|
}
|
|
1251
1273
|
}, [workspaceProjects]);
|
|
@@ -1253,7 +1275,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1253
1275
|
const handleWorkspaceDeleteProject = useCallback(
|
|
1254
1276
|
async (id: string) => {
|
|
1255
1277
|
await workspaceProjects.handleDeleteProject(id);
|
|
1256
|
-
toast({ title: "
|
|
1278
|
+
toast({ title: t("panel.messages.workspaceDeleted") });
|
|
1257
1279
|
},
|
|
1258
1280
|
[workspaceProjects]
|
|
1259
1281
|
);
|
|
@@ -1264,7 +1286,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1264
1286
|
await workspaceProjects.handlePreviewProject(projectId, options);
|
|
1265
1287
|
} catch (error) {
|
|
1266
1288
|
toast({
|
|
1267
|
-
title: error instanceof Error ? error.message : "
|
|
1289
|
+
title: error instanceof Error ? error.message : t("panel.messages.openPreviewFailed"),
|
|
1268
1290
|
});
|
|
1269
1291
|
}
|
|
1270
1292
|
},
|
|
@@ -1303,42 +1325,200 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1303
1325
|
const layer = layerById.get(el.layerId);
|
|
1304
1326
|
return !layer?.locked;
|
|
1305
1327
|
});
|
|
1328
|
+
|
|
1329
|
+
const selectedIdsRef = useRef(selectedIds);
|
|
1330
|
+
selectedIdsRef.current = selectedIds;
|
|
1331
|
+
const zoomRef = useRef(zoom);
|
|
1332
|
+
zoomRef.current = zoom;
|
|
1333
|
+
const blueprintOpenRef = useRef(blueprintOpen);
|
|
1334
|
+
blueprintOpenRef.current = blueprintOpen;
|
|
1335
|
+
const workspaceLiveRef = useRef({
|
|
1336
|
+
activeProjectId: workspaceProjects.activeProjectId,
|
|
1337
|
+
activeProjectName: workspaceProjects.activeProjectName,
|
|
1338
|
+
dirty: workspaceProjects.dirty,
|
|
1339
|
+
projectCount: workspaceProjects.projects.length,
|
|
1340
|
+
});
|
|
1341
|
+
workspaceLiveRef.current = {
|
|
1342
|
+
activeProjectId: workspaceProjects.activeProjectId,
|
|
1343
|
+
activeProjectName: workspaceProjects.activeProjectName,
|
|
1344
|
+
dirty: workspaceProjects.dirty,
|
|
1345
|
+
projectCount: workspaceProjects.projects.length,
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
const assistantDeps = useMemo<ApplyAssistantDeps>(
|
|
1349
|
+
() => ({
|
|
1350
|
+
addElementFromMaterial,
|
|
1351
|
+
updateElement,
|
|
1352
|
+
deleteElement,
|
|
1353
|
+
duplicateElement,
|
|
1354
|
+
setSelectedIds: (ids) => {
|
|
1355
|
+
selectedIdsRef.current = ids;
|
|
1356
|
+
selectViewElements(ids);
|
|
1357
|
+
// Newly added nodes may not be in DOM until next paint — refresh Moveable targets.
|
|
1358
|
+
requestAnimationFrame(() => {
|
|
1359
|
+
setSelectedTargets(
|
|
1360
|
+
getSelectedTargetsFromIds(canvasRef.current, selectedIdsRef.current)
|
|
1361
|
+
);
|
|
1362
|
+
});
|
|
1363
|
+
},
|
|
1364
|
+
getSelectedIds: () => selectedIdsRef.current,
|
|
1365
|
+
revealConfigFromPatch: (patch) => {
|
|
1366
|
+
window.setTimeout(() => {
|
|
1367
|
+
revealPanelConfigFromPatch(patch);
|
|
1368
|
+
}, 60);
|
|
1369
|
+
},
|
|
1370
|
+
focusWorkspace: (area) => {
|
|
1371
|
+
if (area === "blueprint") {
|
|
1372
|
+
setConfigFocus("blueprint");
|
|
1373
|
+
setBlueprintOpen(true);
|
|
1374
|
+
} else {
|
|
1375
|
+
focusViewConfig();
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
getElementIds: () =>
|
|
1379
|
+
new Set(listPanelElementsFromStore().map((el) => el.id)),
|
|
1380
|
+
getElement: (id) => listPanelElementsFromStore().find((el) => el.id === id),
|
|
1381
|
+
listElements: () => listPanelElementsFromStore(),
|
|
1382
|
+
getLayers: () => listLayersFromStore(),
|
|
1383
|
+
getActiveLayerId: () => getActiveLayerIdFromStore(),
|
|
1384
|
+
setActiveLayer,
|
|
1385
|
+
addLayer,
|
|
1386
|
+
renameLayer,
|
|
1387
|
+
setLayerLocked: (id, locked) => {
|
|
1388
|
+
const layer = listLayersFromStore().find((l) => l.id === id);
|
|
1389
|
+
if (!layer) return;
|
|
1390
|
+
if (!!layer.locked !== locked) toggleLayerLock(id);
|
|
1391
|
+
},
|
|
1392
|
+
deleteLayer: (id) => {
|
|
1393
|
+
const result = deleteLayer(id, { mode: "remove" });
|
|
1394
|
+
return result.ok
|
|
1395
|
+
? { ok: true }
|
|
1396
|
+
: { ok: false, reason: result.reason };
|
|
1397
|
+
},
|
|
1398
|
+
bringElementsToFront,
|
|
1399
|
+
sendElementsToBack,
|
|
1400
|
+
bringElementsForward,
|
|
1401
|
+
sendElementsBackward,
|
|
1402
|
+
getZoom: () => zoomRef.current.x,
|
|
1403
|
+
setZoomAbsolute: (value) => {
|
|
1404
|
+
const next = Math.min(4, Math.max(0.25, value));
|
|
1405
|
+
const rounded = Number(next.toFixed(4));
|
|
1406
|
+
zoomRef.current = { x: rounded, y: rounded };
|
|
1407
|
+
setZoom({ x: rounded, y: rounded });
|
|
1408
|
+
},
|
|
1409
|
+
adjustZoomDelta: (delta) => {
|
|
1410
|
+
const next = Math.min(4, Math.max(0.25, zoomRef.current.x + delta));
|
|
1411
|
+
const rounded = Number(next.toFixed(4));
|
|
1412
|
+
zoomRef.current = { x: rounded, y: rounded };
|
|
1413
|
+
setZoom({ x: rounded, y: rounded });
|
|
1414
|
+
},
|
|
1415
|
+
fitViewport: () => {
|
|
1416
|
+
zoomRef.current = { x: 1, y: 1 };
|
|
1417
|
+
setZoom({ x: 1, y: 1 });
|
|
1418
|
+
},
|
|
1419
|
+
undo,
|
|
1420
|
+
redo,
|
|
1421
|
+
setBlueprintGraph,
|
|
1422
|
+
getBlueprintGraph: () => blueprintGraphRef.current,
|
|
1423
|
+
setBlueprintOpen,
|
|
1424
|
+
getBlueprintOpen: () => blueprintOpenRef.current,
|
|
1425
|
+
runBlueprintAll: () => blueprintDebugSession.runAll(),
|
|
1426
|
+
getWorkspace: () => ({ ...workspaceLiveRef.current }),
|
|
1427
|
+
workspaceSave: () => workspaceProjects.handleSaveProject(),
|
|
1428
|
+
workspaceSync: () => workspaceProjects.handleSyncProject(),
|
|
1429
|
+
workspaceCreate: async (name) => {
|
|
1430
|
+
if (name?.trim()) setProductName(name.trim());
|
|
1431
|
+
return workspaceProjects.handleCreateProject();
|
|
1432
|
+
},
|
|
1433
|
+
workspaceOpen: (id) => workspaceProjects.handleOpenProject(id),
|
|
1434
|
+
workspacePreview: async (id) => {
|
|
1435
|
+
const target =
|
|
1436
|
+
id ?? workspaceLiveRef.current.activeProjectId ?? undefined;
|
|
1437
|
+
if (!target) {
|
|
1438
|
+
const created = await workspaceProjects.handleCreateProject();
|
|
1439
|
+
await openOnlinePreviewForProject(created.id, { syncFirst: false });
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
await openOnlinePreviewForProject(target, { syncFirst: true });
|
|
1443
|
+
},
|
|
1444
|
+
exportPanelJson: () => JSON.stringify(exportPanelData(), null, 2),
|
|
1445
|
+
importPanelJson: (json) => {
|
|
1446
|
+
const parsed = JSON.parse(json) as State;
|
|
1447
|
+
const ok = importPanelData(parsed);
|
|
1448
|
+
if (ok) {
|
|
1449
|
+
selectedIdsRef.current = [];
|
|
1450
|
+
setSelectedIds([]);
|
|
1451
|
+
}
|
|
1452
|
+
return ok;
|
|
1453
|
+
},
|
|
1454
|
+
setTheme: (theme) => applyTheme(theme === "dark"),
|
|
1455
|
+
setLocale,
|
|
1456
|
+
setPanelFontSize,
|
|
1457
|
+
}),
|
|
1458
|
+
[
|
|
1459
|
+
addElementFromMaterial,
|
|
1460
|
+
updateElement,
|
|
1461
|
+
deleteElement,
|
|
1462
|
+
duplicateElement,
|
|
1463
|
+
selectViewElements,
|
|
1464
|
+
focusViewConfig,
|
|
1465
|
+
setActiveLayer,
|
|
1466
|
+
addLayer,
|
|
1467
|
+
renameLayer,
|
|
1468
|
+
toggleLayerLock,
|
|
1469
|
+
deleteLayer,
|
|
1470
|
+
bringElementsToFront,
|
|
1471
|
+
sendElementsToBack,
|
|
1472
|
+
bringElementsForward,
|
|
1473
|
+
sendElementsBackward,
|
|
1474
|
+
undo,
|
|
1475
|
+
redo,
|
|
1476
|
+
setBlueprintGraph,
|
|
1477
|
+
blueprintDebugSession,
|
|
1478
|
+
workspaceProjects,
|
|
1479
|
+
openOnlinePreviewForProject,
|
|
1480
|
+
exportPanelData,
|
|
1481
|
+
importPanelData,
|
|
1482
|
+
applyTheme,
|
|
1483
|
+
setLocale,
|
|
1484
|
+
]
|
|
1485
|
+
);
|
|
1306
1486
|
const showActionHint = useCallback((message: string) => {
|
|
1307
1487
|
toast({
|
|
1308
|
-
title: "
|
|
1488
|
+
title: t("panel.messages.operationRestricted"),
|
|
1309
1489
|
description: message,
|
|
1310
1490
|
});
|
|
1311
1491
|
}, []);
|
|
1312
1492
|
const hintUnlockNodeForDelete = useCallback(() => {
|
|
1313
|
-
showActionHint(
|
|
1493
|
+
showActionHint(messages.nodeDeleteLocked);
|
|
1314
1494
|
}, [showActionHint]);
|
|
1315
1495
|
const hintUnlockLayerForDelete = useCallback(() => {
|
|
1316
|
-
showActionHint(
|
|
1496
|
+
showActionHint(messages.nodeDeleteLayerLocked);
|
|
1317
1497
|
}, [showActionHint]);
|
|
1318
1498
|
const hintLockedNodesInBatchDelete = useCallback(() => {
|
|
1319
|
-
showActionHint(
|
|
1499
|
+
showActionHint(messages.nodeBatchDeleteContainsLocked);
|
|
1320
1500
|
}, [showActionHint]);
|
|
1321
1501
|
const hintNodeNotFound = useCallback(() => {
|
|
1322
|
-
showActionHint(
|
|
1502
|
+
showActionHint(messages.nodeNotFound);
|
|
1323
1503
|
}, [showActionHint]);
|
|
1324
1504
|
const hintUnlockNodeForMove = useCallback(() => {
|
|
1325
|
-
showActionHint(
|
|
1505
|
+
showActionHint(messages.nodeMoveLocked);
|
|
1326
1506
|
}, [showActionHint]);
|
|
1327
1507
|
const hintUnlockSourceLayerForMove = useCallback(() => {
|
|
1328
|
-
showActionHint(
|
|
1508
|
+
showActionHint(messages.nodeMoveSourceLayerLocked);
|
|
1329
1509
|
}, [showActionHint]);
|
|
1330
1510
|
const hintTargetLayerNotFound = useCallback(() => {
|
|
1331
|
-
showActionHint(
|
|
1511
|
+
showActionHint(messages.targetLayerNotFound);
|
|
1332
1512
|
}, [showActionHint]);
|
|
1333
1513
|
const hintUnlockTargetLayerForMove = useCallback(() => {
|
|
1334
|
-
showActionHint(
|
|
1514
|
+
showActionHint(messages.nodeMoveTargetLayerLocked);
|
|
1335
1515
|
}, [showActionHint]);
|
|
1336
1516
|
const getLayerDeleteBlockReason = useCallback(
|
|
1337
1517
|
(layerId: string) => {
|
|
1338
1518
|
const targetLayer = layerById.get(layerId);
|
|
1339
|
-
if (!targetLayer) return "
|
|
1340
|
-
if (!targetLayer.editable) return "
|
|
1341
|
-
if (targetLayer.locked) return "
|
|
1519
|
+
if (!targetLayer) return t("panel.messages.layerNotFound");
|
|
1520
|
+
if (!targetLayer.editable) return t("panel.layers.cannotDeleteDefault");
|
|
1521
|
+
if (targetLayer.locked) return t("panel.layers.cannotDeleteLocked");
|
|
1342
1522
|
if (targetLayer.isMapping) return null;
|
|
1343
1523
|
const hasBlockingRef = allElements.some((el) => {
|
|
1344
1524
|
if (el.layerId === layerId) return false;
|
|
@@ -1346,7 +1526,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1346
1526
|
if (el.refLayerId !== layerId) return false;
|
|
1347
1527
|
return (el.refCopyMode ?? "shallow") !== "deep";
|
|
1348
1528
|
});
|
|
1349
|
-
if (hasBlockingRef) return "
|
|
1529
|
+
if (hasBlockingRef) return t("panel.messages.shallowRefBlockingDelete");
|
|
1350
1530
|
return null;
|
|
1351
1531
|
},
|
|
1352
1532
|
[allElements, layerById]
|
|
@@ -1397,7 +1577,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1397
1577
|
const parsed = JSON.parse(text) as State;
|
|
1398
1578
|
const ok = importPanelData(parsed);
|
|
1399
1579
|
if (!ok) {
|
|
1400
|
-
window.alert(
|
|
1580
|
+
window.alert(messages.importInvalidFormat);
|
|
1401
1581
|
} else {
|
|
1402
1582
|
setSelectedIds([]);
|
|
1403
1583
|
}
|
|
@@ -1556,7 +1736,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1556
1736
|
const previewFaviconLink = faviconHrefWithVersion
|
|
1557
1737
|
? `<link rel="icon" type="image/png" href="${faviconHrefWithVersion}" /><link rel="shortcut icon" type="image/png" href="${faviconHrefWithVersion}" />`
|
|
1558
1738
|
: "";
|
|
1559
|
-
const previewTitle = productName.trim() || "
|
|
1739
|
+
const previewTitle = productName.trim() || t("panel.defaults.unnamedProduct");
|
|
1560
1740
|
const escapedPreviewTitle = previewTitle
|
|
1561
1741
|
.replace(/&/g, "&")
|
|
1562
1742
|
.replace(/</g, "<")
|
|
@@ -1678,7 +1858,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1678
1858
|
}
|
|
1679
1859
|
} catch (error) {
|
|
1680
1860
|
toast({
|
|
1681
|
-
title: error instanceof Error ? error.message : "
|
|
1861
|
+
title: error instanceof Error ? error.message : t("panel.messages.openPreviewFailed"),
|
|
1682
1862
|
});
|
|
1683
1863
|
}
|
|
1684
1864
|
})();
|
|
@@ -1768,59 +1948,70 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1768
1948
|
<nav className="panel-font-root relative z-30 flex items-center gap-2 border-b border-border bg-background/95 px-2 text-foreground">
|
|
1769
1949
|
<Menubar className="h-8 border-0 bg-transparent p-0 shadow-none">
|
|
1770
1950
|
<MenubarMenu>
|
|
1771
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal"
|
|
1951
|
+
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.file")}</MenubarTrigger>
|
|
1772
1952
|
<MenubarContent className="z-[10100]">
|
|
1773
|
-
{/* <MenubarItem onClick={handlePreviewLayer}
|
|
1774
|
-
<MenubarItem onClick={handleExport}
|
|
1775
|
-
<MenubarItem onClick={() => importInputRef.current?.click()}
|
|
1953
|
+
{/* <MenubarItem onClick={handlePreviewLayer}>{t("panel.workspace.previewDocTitle")}</MenubarItem> */}
|
|
1954
|
+
<MenubarItem onClick={handleExport}>{t("panel.menubar.export")}</MenubarItem>
|
|
1955
|
+
<MenubarItem onClick={() => importInputRef.current?.click()}>{t("panel.menubar.import")}</MenubarItem>
|
|
1776
1956
|
</MenubarContent>
|
|
1777
1957
|
</MenubarMenu>
|
|
1778
1958
|
<MenubarMenu>
|
|
1779
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal"
|
|
1959
|
+
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.edit")}</MenubarTrigger>
|
|
1780
1960
|
<MenubarContent className="z-[10100]">
|
|
1781
|
-
<MenubarItem disabled={!canUndo} onClick={undo}
|
|
1782
|
-
<MenubarItem disabled={!canRedo} onClick={redo}
|
|
1961
|
+
<MenubarItem disabled={!canUndo} onClick={undo}>{t("panel.menubar.undo")}</MenubarItem>
|
|
1962
|
+
<MenubarItem disabled={!canRedo} onClick={redo}>{t("panel.menubar.redo")}</MenubarItem>
|
|
1783
1963
|
<MenubarSeparator />
|
|
1784
|
-
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => bringElementsForward(selectedIds)}
|
|
1785
|
-
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => sendElementsBackward(selectedIds)}
|
|
1786
|
-
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => bringElementsToFront(selectedIds)}
|
|
1787
|
-
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => sendElementsToBack(selectedIds)}
|
|
1964
|
+
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => bringElementsForward(selectedIds)}>{t("panel.menubar.bringForward")}</MenubarItem>
|
|
1965
|
+
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => sendElementsBackward(selectedIds)}>{t("panel.menubar.sendBackward")}</MenubarItem>
|
|
1966
|
+
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => bringElementsToFront(selectedIds)}>{t("panel.menubar.bringToFront")}</MenubarItem>
|
|
1967
|
+
<MenubarItem disabled={!hasUnlockedSelection} onClick={() => sendElementsToBack(selectedIds)}>{t("panel.menubar.sendToBack")}</MenubarItem>
|
|
1788
1968
|
</MenubarContent>
|
|
1789
1969
|
</MenubarMenu>
|
|
1790
1970
|
<MenubarMenu>
|
|
1791
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal"
|
|
1971
|
+
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.view")}</MenubarTrigger>
|
|
1792
1972
|
<MenubarContent className="z-[10100]">
|
|
1793
|
-
<MenubarItem onClick={() => adjustUniformZoom((z) => z - 0.1)}
|
|
1794
|
-
<MenubarItem onClick={() => adjustUniformZoom((z) => z + 0.1)}
|
|
1973
|
+
<MenubarItem onClick={() => adjustUniformZoom((z) => z - 0.1)}>{t("panel.menubar.zoomOut")}</MenubarItem>
|
|
1974
|
+
<MenubarItem onClick={() => adjustUniformZoom((z) => z + 0.1)}>{t("panel.menubar.zoomIn")}</MenubarItem>
|
|
1795
1975
|
<MenubarSeparator />
|
|
1796
1976
|
<MenubarItem
|
|
1797
1977
|
onClick={() => {
|
|
1798
1978
|
applyTheme(!isDark);
|
|
1799
1979
|
}}
|
|
1800
1980
|
>
|
|
1801
|
-
{isDark ? "
|
|
1981
|
+
{isDark ? t("panel.menubar.switchToLight") : t("panel.menubar.switchToDark")}
|
|
1802
1982
|
</MenubarItem>
|
|
1803
1983
|
</MenubarContent>
|
|
1804
1984
|
</MenubarMenu>
|
|
1805
1985
|
<MenubarMenu>
|
|
1806
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal"
|
|
1986
|
+
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.blueprint")}</MenubarTrigger>
|
|
1807
1987
|
<MenubarContent className="z-[10100]">
|
|
1808
|
-
<MenubarItem onClick={openBlueprintExportDialog}
|
|
1988
|
+
<MenubarItem onClick={openBlueprintExportDialog}>{t("panel.menubar.export")}</MenubarItem>
|
|
1809
1989
|
<MenubarItem onClick={() => blueprintImportInputRef.current?.click()}>
|
|
1810
|
-
|
|
1990
|
+
{t("panel.menubar.import")}
|
|
1811
1991
|
</MenubarItem>
|
|
1812
1992
|
</MenubarContent>
|
|
1813
1993
|
</MenubarMenu>
|
|
1814
1994
|
<MenubarMenu>
|
|
1815
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal"
|
|
1995
|
+
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.settings")}</MenubarTrigger>
|
|
1816
1996
|
<MenubarContent className="z-[10100]">
|
|
1817
1997
|
<MenubarRadioGroup
|
|
1818
1998
|
value={panelFontSize}
|
|
1819
1999
|
onValueChange={(value) => setPanelFontSize(value as "sm" | "md" | "lg")}
|
|
1820
2000
|
>
|
|
1821
|
-
<MenubarRadioItem value="sm"
|
|
1822
|
-
<MenubarRadioItem value="md"
|
|
1823
|
-
<MenubarRadioItem value="lg"
|
|
2001
|
+
<MenubarRadioItem value="sm">{t("panel.menubar.fontSmall")}</MenubarRadioItem>
|
|
2002
|
+
<MenubarRadioItem value="md">{t("panel.menubar.fontMedium")}</MenubarRadioItem>
|
|
2003
|
+
<MenubarRadioItem value="lg">{t("panel.menubar.fontLarge")}</MenubarRadioItem>
|
|
2004
|
+
</MenubarRadioGroup>
|
|
2005
|
+
<MenubarSeparator />
|
|
2006
|
+
<div className="px-2 py-1.5 text-[11px] text-muted-foreground">
|
|
2007
|
+
{t("panel.theme.language")}
|
|
2008
|
+
</div>
|
|
2009
|
+
<MenubarRadioGroup
|
|
2010
|
+
value={locale}
|
|
2011
|
+
onValueChange={(value) => setLocale(value as "zh-CN" | "en-US")}
|
|
2012
|
+
>
|
|
2013
|
+
<MenubarRadioItem value="zh-CN">{t("panel.theme.zhCN")}</MenubarRadioItem>
|
|
2014
|
+
<MenubarRadioItem value="en-US">{t("panel.theme.enUS")}</MenubarRadioItem>
|
|
1824
2015
|
</MenubarRadioGroup>
|
|
1825
2016
|
</MenubarContent>
|
|
1826
2017
|
</MenubarMenu>
|
|
@@ -1837,6 +2028,15 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1837
2028
|
onPreviewProject={openOnlinePreviewForProject}
|
|
1838
2029
|
/>
|
|
1839
2030
|
<div className="flex-1" />
|
|
2031
|
+
<Button
|
|
2032
|
+
type="button"
|
|
2033
|
+
variant={assistantOpen ? "secondary" : "outline"}
|
|
2034
|
+
size="sm"
|
|
2035
|
+
className="h-7 shrink-0 px-2 text-xs"
|
|
2036
|
+
onClick={() => setAssistantOpen((v) => !v)}
|
|
2037
|
+
>
|
|
2038
|
+
{assistantOpen ? t("panel.ai.hide") : t("panel.ai.open")}
|
|
2039
|
+
</Button>
|
|
1840
2040
|
</nav>
|
|
1841
2041
|
<ResizablePanelGroup direction="horizontal" className="min-h-0 w-full flex-1">
|
|
1842
2042
|
<ResizablePanel defaultSize={15} minSize={15}>
|
|
@@ -1938,10 +2138,10 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1938
2138
|
<Input
|
|
1939
2139
|
value={productName}
|
|
1940
2140
|
onChange={(e) => setProductName(e.target.value)}
|
|
1941
|
-
placeholder="
|
|
2141
|
+
placeholder={t("panel.menubar.productNamePlaceholder")}
|
|
1942
2142
|
className="h-7 w-[220px] text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none"
|
|
1943
2143
|
/>
|
|
1944
|
-
<span className="text-[11px] text-muted-foreground"
|
|
2144
|
+
<span className="text-[11px] text-muted-foreground">{t("panel.menubar.productName")}</span>
|
|
1945
2145
|
<TooltipProvider delayDuration={150}>
|
|
1946
2146
|
<Tooltip>
|
|
1947
2147
|
<TooltipTrigger asChild>
|
|
@@ -1949,12 +2149,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1949
2149
|
type="button"
|
|
1950
2150
|
onClick={() => titleIconInputRef.current?.click()}
|
|
1951
2151
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
1952
|
-
aria-label="
|
|
2152
|
+
aria-label={t("panel.menubar.uploadTitleIcon")}
|
|
1953
2153
|
>
|
|
1954
2154
|
<IconImage />
|
|
1955
2155
|
</button>
|
|
1956
2156
|
</TooltipTrigger>
|
|
1957
|
-
<TooltipContent className="z-[10000]"
|
|
2157
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.uploadTitleIcon")}</TooltipContent>
|
|
1958
2158
|
</Tooltip>
|
|
1959
2159
|
{titleIconDataUrl ? (
|
|
1960
2160
|
<Tooltip>
|
|
@@ -1963,12 +2163,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1963
2163
|
type="button"
|
|
1964
2164
|
onClick={() => setTitleIconDataUrl("")}
|
|
1965
2165
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
1966
|
-
aria-label="
|
|
2166
|
+
aria-label={t("panel.menubar.clearTitleIcon")}
|
|
1967
2167
|
>
|
|
1968
2168
|
<IconClose />
|
|
1969
2169
|
</button>
|
|
1970
2170
|
</TooltipTrigger>
|
|
1971
|
-
<TooltipContent className="z-[10000]"
|
|
2171
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.clearTitleIcon")}</TooltipContent>
|
|
1972
2172
|
</Tooltip>
|
|
1973
2173
|
) : null}
|
|
1974
2174
|
{titleIconDataUrl ? (
|
|
@@ -1978,17 +2178,17 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1978
2178
|
type="button"
|
|
1979
2179
|
onClick={() => setTitleIconPreviewOpen(true)}
|
|
1980
2180
|
className="h-7 shrink-0 flex min-w-[92px] items-center gap-1.5 rounded border border-border bg-muted/30 px-2 py-1 hover:bg-accent/40"
|
|
1981
|
-
aria-label="
|
|
2181
|
+
aria-label={t("panel.menubar.viewTitleIcon")}
|
|
1982
2182
|
>
|
|
1983
2183
|
<img
|
|
1984
2184
|
src={titleIconDataUrl}
|
|
1985
|
-
alt="
|
|
2185
|
+
alt={t("panel.menubar.titleIconThumbAlt")}
|
|
1986
2186
|
className="h-5 w-5 shrink-0 rounded border border-border/60 object-cover"
|
|
1987
2187
|
/>
|
|
1988
2188
|
<span className="text-[10px] text-muted-foreground">Title Icon</span>
|
|
1989
2189
|
</button>
|
|
1990
2190
|
</TooltipTrigger>
|
|
1991
|
-
<TooltipContent className="z-[10000]"
|
|
2191
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.viewTitleIconHint")}</TooltipContent>
|
|
1992
2192
|
</Tooltip>
|
|
1993
2193
|
) : null}
|
|
1994
2194
|
<Tooltip>
|
|
@@ -1998,12 +2198,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
1998
2198
|
onClick={undo}
|
|
1999
2199
|
disabled={!canUndo}
|
|
2000
2200
|
className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
|
|
2001
|
-
aria-label="
|
|
2201
|
+
aria-label={t("panel.menubar.undo")}
|
|
2002
2202
|
>
|
|
2003
2203
|
<IconUndo />
|
|
2004
2204
|
</button>
|
|
2005
2205
|
</TooltipTrigger>
|
|
2006
|
-
<TooltipContent className="z-[10000]"
|
|
2206
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.undoShortcut")}</TooltipContent>
|
|
2007
2207
|
</Tooltip>
|
|
2008
2208
|
<Tooltip>
|
|
2009
2209
|
<TooltipTrigger asChild>
|
|
@@ -2012,12 +2212,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2012
2212
|
onClick={redo}
|
|
2013
2213
|
disabled={!canRedo}
|
|
2014
2214
|
className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
|
|
2015
|
-
aria-label="
|
|
2215
|
+
aria-label={t("panel.menubar.redo")}
|
|
2016
2216
|
>
|
|
2017
2217
|
<IconRedo />
|
|
2018
2218
|
</button>
|
|
2019
2219
|
</TooltipTrigger>
|
|
2020
|
-
<TooltipContent className="z-[10000]"
|
|
2220
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.redoShortcut")}</TooltipContent>
|
|
2021
2221
|
</Tooltip>
|
|
2022
2222
|
<Tooltip>
|
|
2023
2223
|
<TooltipTrigger asChild>
|
|
@@ -2029,7 +2229,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2029
2229
|
"rounded border border-border p-1 hover:bg-accent",
|
|
2030
2230
|
isHistoryPanelExpanded ? "bg-accent/60" : "",
|
|
2031
2231
|
].join(" ")}
|
|
2032
|
-
aria-label={isHistoryPanelExpanded ? "
|
|
2232
|
+
aria-label={isHistoryPanelExpanded ? t("panel.history.collapse") : t("panel.history.expand")}
|
|
2033
2233
|
>
|
|
2034
2234
|
<IconHistory />
|
|
2035
2235
|
</button>
|
|
@@ -2039,7 +2239,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2039
2239
|
style={{ zIndex: PANEL_Z_INDEX.historyPopover }}
|
|
2040
2240
|
>
|
|
2041
2241
|
<div className="mb-1 flex items-center justify-between">
|
|
2042
|
-
<span className="text-[11px] font-semibold text-muted-foreground"
|
|
2242
|
+
<span className="text-[11px] font-semibold text-muted-foreground">{t("panel.history.title")}</span>
|
|
2043
2243
|
<span className="text-[11px] text-muted-foreground">
|
|
2044
2244
|
{history.length > 0 ? `${historyCursor + 1}/${history.length}` : "0/0"}
|
|
2045
2245
|
</span>
|
|
@@ -2047,7 +2247,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2047
2247
|
<Input
|
|
2048
2248
|
value={historyKeyword}
|
|
2049
2249
|
onChange={(e) => setHistoryKeyword(e.target.value)}
|
|
2050
|
-
placeholder="
|
|
2250
|
+
placeholder={t("panel.history.searchPlaceholder")}
|
|
2051
2251
|
className="mb-2 h-7 text-xs"
|
|
2052
2252
|
/>
|
|
2053
2253
|
<div
|
|
@@ -2058,9 +2258,9 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2058
2258
|
<EmptyIcon className="h-8 w-8">
|
|
2059
2259
|
<IconHistory />
|
|
2060
2260
|
</EmptyIcon>
|
|
2061
|
-
<EmptyTitle className="text-xs"
|
|
2261
|
+
<EmptyTitle className="text-xs">{t("panel.history.emptyTitle")}</EmptyTitle>
|
|
2062
2262
|
<EmptyDescription className="text-[11px]">
|
|
2063
|
-
|
|
2263
|
+
{t("panel.history.emptyDesc")}
|
|
2064
2264
|
</EmptyDescription>
|
|
2065
2265
|
</Empty>
|
|
2066
2266
|
) : (
|
|
@@ -2091,7 +2291,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2091
2291
|
<div className="flex items-center justify-between gap-2">
|
|
2092
2292
|
<span className="truncate">{item.label}</span>
|
|
2093
2293
|
<span className="shrink-0 text-[10px] text-muted-foreground">
|
|
2094
|
-
{formatRelativeTime(item.timestamp, historyNow)}
|
|
2294
|
+
{formatRelativeTime(item.timestamp, historyNow, t)}
|
|
2095
2295
|
</span>
|
|
2096
2296
|
</div>
|
|
2097
2297
|
</button>
|
|
@@ -2120,9 +2320,9 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2120
2320
|
<path d="m20 20-3.5-3.5" />
|
|
2121
2321
|
</svg>
|
|
2122
2322
|
</EmptyIcon>
|
|
2123
|
-
<EmptyTitle className="text-xs"
|
|
2323
|
+
<EmptyTitle className="text-xs">{t("panel.history.noMatchTitle")}</EmptyTitle>
|
|
2124
2324
|
<EmptyDescription className="text-[11px]">
|
|
2125
|
-
|
|
2325
|
+
{t("panel.history.noMatchDesc")}
|
|
2126
2326
|
</EmptyDescription>
|
|
2127
2327
|
</Empty>
|
|
2128
2328
|
) : null}
|
|
@@ -2132,7 +2332,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2132
2332
|
</div>
|
|
2133
2333
|
</TooltipTrigger>
|
|
2134
2334
|
<TooltipContent className="z-[10000]">
|
|
2135
|
-
{isHistoryPanelExpanded ? "
|
|
2335
|
+
{isHistoryPanelExpanded ? t("panel.history.collapse") : t("panel.history.expand")}
|
|
2136
2336
|
</TooltipContent>
|
|
2137
2337
|
</Tooltip>
|
|
2138
2338
|
</TooltipProvider>
|
|
@@ -2142,7 +2342,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2142
2342
|
type="button"
|
|
2143
2343
|
disabled={!hasUnlockedSelection}
|
|
2144
2344
|
className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
|
|
2145
|
-
aria-label="
|
|
2345
|
+
aria-label={t("panel.menubar.nodeZOrderAria")}
|
|
2146
2346
|
>
|
|
2147
2347
|
<IconLayers />
|
|
2148
2348
|
</button>
|
|
@@ -2153,28 +2353,28 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2153
2353
|
onClick={() => bringElementsForward(selectedIds)}
|
|
2154
2354
|
>
|
|
2155
2355
|
<span className="mr-2 inline-flex"><IconForward /></span>
|
|
2156
|
-
|
|
2356
|
+
{t("panel.menubar.bringForward")}
|
|
2157
2357
|
</DropdownMenuItem>
|
|
2158
2358
|
<DropdownMenuItem
|
|
2159
2359
|
disabled={!hasUnlockedSelection}
|
|
2160
2360
|
onClick={() => sendElementsBackward(selectedIds)}
|
|
2161
2361
|
>
|
|
2162
2362
|
<span className="mr-2 inline-flex"><IconBackward /></span>
|
|
2163
|
-
|
|
2363
|
+
{t("panel.menubar.sendBackward")}
|
|
2164
2364
|
</DropdownMenuItem>
|
|
2165
2365
|
<DropdownMenuItem
|
|
2166
2366
|
disabled={!hasUnlockedSelection}
|
|
2167
2367
|
onClick={() => bringElementsToFront(selectedIds)}
|
|
2168
2368
|
>
|
|
2169
2369
|
<span className="mr-2 inline-flex"><IconBringFront /></span>
|
|
2170
|
-
|
|
2370
|
+
{t("panel.menubar.bringToFront")}
|
|
2171
2371
|
</DropdownMenuItem>
|
|
2172
2372
|
<DropdownMenuItem
|
|
2173
2373
|
disabled={!hasUnlockedSelection}
|
|
2174
2374
|
onClick={() => sendElementsToBack(selectedIds)}
|
|
2175
2375
|
>
|
|
2176
2376
|
<span className="mr-2 inline-flex"><IconSendBack /></span>
|
|
2177
|
-
|
|
2377
|
+
{t("panel.menubar.sendToBack")}
|
|
2178
2378
|
</DropdownMenuItem>
|
|
2179
2379
|
</DropdownMenuContent>
|
|
2180
2380
|
</DropdownMenu>
|
|
@@ -2183,11 +2383,11 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2183
2383
|
data-blueprint-toggle
|
|
2184
2384
|
className="flex shrink-0 items-center gap-2 rounded-md border border-border bg-muted/30 px-2 py-1"
|
|
2185
2385
|
>
|
|
2186
|
-
<span className="text-[11px] text-muted-foreground"
|
|
2386
|
+
<span className="text-[11px] text-muted-foreground">{t("panel.menubar.blueprint")}</span>
|
|
2187
2387
|
<Switch
|
|
2188
2388
|
checked={blueprintOpen}
|
|
2189
2389
|
onCheckedChange={setBlueprintOpen}
|
|
2190
|
-
aria-label="
|
|
2390
|
+
aria-label={t("panel.menubar.showBlueprintPanel")}
|
|
2191
2391
|
/>
|
|
2192
2392
|
</div>
|
|
2193
2393
|
<TooltipProvider delayDuration={150}>
|
|
@@ -2197,7 +2397,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2197
2397
|
<Switch
|
|
2198
2398
|
checked={isDark}
|
|
2199
2399
|
onCheckedChange={applyTheme}
|
|
2200
|
-
aria-label="
|
|
2400
|
+
aria-label={t("panel.menubar.toggleTheme")}
|
|
2201
2401
|
className="data-[state=checked]:bg-primary/80 data-[state=unchecked]:bg-secondary"
|
|
2202
2402
|
/>
|
|
2203
2403
|
<div
|
|
@@ -2231,7 +2431,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2231
2431
|
</div>
|
|
2232
2432
|
</div>
|
|
2233
2433
|
</TooltipTrigger>
|
|
2234
|
-
<TooltipContent className="z-[10000]"
|
|
2434
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.toggleThemeHint")}</TooltipContent>
|
|
2235
2435
|
</Tooltip>
|
|
2236
2436
|
<Tooltip>
|
|
2237
2437
|
<TooltipTrigger asChild>
|
|
@@ -2243,7 +2443,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2243
2443
|
-
|
|
2244
2444
|
</button>
|
|
2245
2445
|
</TooltipTrigger>
|
|
2246
|
-
<TooltipContent className="z-[10000]"
|
|
2446
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.zoomOutCanvas")}</TooltipContent>
|
|
2247
2447
|
</Tooltip>
|
|
2248
2448
|
</TooltipProvider>
|
|
2249
2449
|
<span className="w-20 text-center text-xs">
|
|
@@ -2262,7 +2462,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2262
2462
|
+
|
|
2263
2463
|
</button>
|
|
2264
2464
|
</TooltipTrigger>
|
|
2265
|
-
<TooltipContent className="z-[10000]"
|
|
2465
|
+
<TooltipContent className="z-[10000]">{t("panel.menubar.zoomInCanvas")}</TooltipContent>
|
|
2266
2466
|
</Tooltip>
|
|
2267
2467
|
</TooltipProvider>
|
|
2268
2468
|
</div>
|
|
@@ -2278,7 +2478,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2278
2478
|
try {
|
|
2279
2479
|
await handleImportFile(file);
|
|
2280
2480
|
} catch {
|
|
2281
|
-
window.alert(
|
|
2481
|
+
window.alert(messages.importJsonParseError);
|
|
2282
2482
|
}
|
|
2283
2483
|
}}
|
|
2284
2484
|
/>
|
|
@@ -2294,7 +2494,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2294
2494
|
try {
|
|
2295
2495
|
await handleBlueprintImportFile(file);
|
|
2296
2496
|
} catch {
|
|
2297
|
-
window.alert(
|
|
2497
|
+
window.alert(messages.blueprintImportInvalid);
|
|
2298
2498
|
}
|
|
2299
2499
|
}}
|
|
2300
2500
|
/>
|
|
@@ -2311,7 +2511,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2311
2511
|
const normalizedDataUrl = await normalizeTitleIconFile(file);
|
|
2312
2512
|
setTitleIconDataUrl(normalizedDataUrl);
|
|
2313
2513
|
} catch {
|
|
2314
|
-
window.alert(
|
|
2514
|
+
window.alert(messages.iconReadFailed);
|
|
2315
2515
|
}
|
|
2316
2516
|
}}
|
|
2317
2517
|
/>
|
|
@@ -2427,7 +2627,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2427
2627
|
}}
|
|
2428
2628
|
onDropMaterial={({ materialId, x, y }) => {
|
|
2429
2629
|
if (activeLayer?.locked) {
|
|
2430
|
-
showActionHint(
|
|
2630
|
+
showActionHint(messages.activeLayerLockedCannotAdd);
|
|
2431
2631
|
return;
|
|
2432
2632
|
}
|
|
2433
2633
|
addElementFromMaterial(materialId, x, y);
|
|
@@ -2441,6 +2641,9 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2441
2641
|
onSelectIds={selectViewElements}
|
|
2442
2642
|
updateElement={updateElement}
|
|
2443
2643
|
layerLocked={Boolean(activeLayer?.locked)}
|
|
2644
|
+
onTableCellAction={(payload) => {
|
|
2645
|
+
void triggerBlueprintNode(payload.blueprintNodeId, payload);
|
|
2646
|
+
}}
|
|
2444
2647
|
/>
|
|
2445
2648
|
|
|
2446
2649
|
<SelectLayer
|
|
@@ -2477,13 +2680,13 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2477
2680
|
if (!node) return;
|
|
2478
2681
|
const layer = layerById.get(node.layerId);
|
|
2479
2682
|
if (layer?.locked) {
|
|
2480
|
-
showActionHint(
|
|
2683
|
+
showActionHint(messages.nodeLayerLockedCannotCopy);
|
|
2481
2684
|
return;
|
|
2482
2685
|
}
|
|
2483
2686
|
setCopiedNodeId(contextMenuNodeId);
|
|
2484
2687
|
}}
|
|
2485
2688
|
>
|
|
2486
|
-
|
|
2689
|
+
{t("panel.menubar.duplicateNode")}
|
|
2487
2690
|
</DropdownMenuItem>
|
|
2488
2691
|
{contextMenuNodeId &&
|
|
2489
2692
|
byId.get(contextMenuNodeId)?.mappingSourceNodeId ? (
|
|
@@ -2495,7 +2698,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2495
2698
|
if (!sourceNodeId) return;
|
|
2496
2699
|
const sourceNode = byId.get(sourceNodeId);
|
|
2497
2700
|
if (!sourceNode) {
|
|
2498
|
-
showActionHint(
|
|
2701
|
+
showActionHint(messages.sourceNodeNotFound);
|
|
2499
2702
|
return;
|
|
2500
2703
|
}
|
|
2501
2704
|
if (activeLayerId !== sourceNode.layerId) {
|
|
@@ -2505,7 +2708,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2505
2708
|
setContextMenuNodeId(null);
|
|
2506
2709
|
}}
|
|
2507
2710
|
>
|
|
2508
|
-
|
|
2711
|
+
{t("panel.menubar.locateSourceNode")}
|
|
2509
2712
|
</DropdownMenuItem>
|
|
2510
2713
|
) : null}
|
|
2511
2714
|
<DropdownMenuItem
|
|
@@ -2522,7 +2725,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2522
2725
|
setContextMenuNodeId(null);
|
|
2523
2726
|
}}
|
|
2524
2727
|
>
|
|
2525
|
-
|
|
2728
|
+
{t("panel.menubar.openInMappingLayer")}
|
|
2526
2729
|
</DropdownMenuItem>
|
|
2527
2730
|
<DropdownMenuItem
|
|
2528
2731
|
onClick={() => {
|
|
@@ -2553,7 +2756,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2553
2756
|
return !!layerById.get(el.layerId)?.locked;
|
|
2554
2757
|
});
|
|
2555
2758
|
if (hasLockedLayerNode) {
|
|
2556
|
-
showActionHint(
|
|
2759
|
+
showActionHint(messages.selectionContainsLockedLayerNode);
|
|
2557
2760
|
return;
|
|
2558
2761
|
}
|
|
2559
2762
|
deleteElements(selectedIds);
|
|
@@ -2577,8 +2780,8 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2577
2780
|
}}
|
|
2578
2781
|
>
|
|
2579
2782
|
{contextMenuNodeId && selectedIds.includes(contextMenuNodeId) && selectedIds.length > 1
|
|
2580
|
-
?
|
|
2581
|
-
: "
|
|
2783
|
+
? t("panel.menubar.deleteSelectedNodes", { count: selectedIds.length })
|
|
2784
|
+
: t("panel.menubar.deleteNode")}
|
|
2582
2785
|
</DropdownMenuItem>
|
|
2583
2786
|
</>
|
|
2584
2787
|
) : (
|
|
@@ -2589,7 +2792,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2589
2792
|
if (!copiedNodeId) return;
|
|
2590
2793
|
const sourceNode = byId.get(copiedNodeId);
|
|
2591
2794
|
if (sourceNode && layerById.get(sourceNode.layerId)?.locked) {
|
|
2592
|
-
showActionHint(
|
|
2795
|
+
showActionHint(messages.sourceLayerLockedCannotPaste);
|
|
2593
2796
|
return;
|
|
2594
2797
|
}
|
|
2595
2798
|
const canvasEl = canvasRef.current;
|
|
@@ -2623,7 +2826,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2623
2826
|
);
|
|
2624
2827
|
}}
|
|
2625
2828
|
>
|
|
2626
|
-
|
|
2829
|
+
{t("panel.menubar.pasteNode")}
|
|
2627
2830
|
</DropdownMenuItem>
|
|
2628
2831
|
<DropdownMenuItem
|
|
2629
2832
|
disabled={elements.length === 0}
|
|
@@ -2631,7 +2834,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2631
2834
|
selectViewElements(elements.map((el) => el.id));
|
|
2632
2835
|
}}
|
|
2633
2836
|
>
|
|
2634
|
-
|
|
2837
|
+
{t("panel.menubar.selectAllInLayer")}
|
|
2635
2838
|
</DropdownMenuItem>
|
|
2636
2839
|
</>
|
|
2637
2840
|
)}
|
|
@@ -2645,20 +2848,20 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2645
2848
|
<TooltipProvider delayDuration={120}>
|
|
2646
2849
|
<Tabs value={activeLayerId} onValueChange={setActiveLayer}>
|
|
2647
2850
|
<div className="mb-1 flex items-center gap-2">
|
|
2648
|
-
<div className="text-[11px] font-semibold text-muted-foreground"
|
|
2851
|
+
<div className="text-[11px] font-semibold text-muted-foreground">{t("panel.layers.title")}</div>
|
|
2649
2852
|
<Tooltip>
|
|
2650
2853
|
<TooltipTrigger asChild>
|
|
2651
2854
|
<button
|
|
2652
2855
|
type="button"
|
|
2653
2856
|
onClick={() => setIsLayerPanelExpanded((v) => !v)}
|
|
2654
|
-
aria-label={isLayerPanelExpanded ? "
|
|
2857
|
+
aria-label={isLayerPanelExpanded ? t("panel.layers.collapseInputAria") : t("panel.layers.expandInputAria")}
|
|
2655
2858
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
2656
2859
|
>
|
|
2657
2860
|
<IconChevron expanded={isLayerPanelExpanded} />
|
|
2658
2861
|
</button>
|
|
2659
2862
|
</TooltipTrigger>
|
|
2660
2863
|
<TooltipContent className="z-[10000]">
|
|
2661
|
-
{isLayerPanelExpanded ? "
|
|
2864
|
+
{isLayerPanelExpanded ? t("panel.layers.collapseInput") : t("panel.layers.expandInput")}
|
|
2662
2865
|
</TooltipContent>
|
|
2663
2866
|
</Tooltip>
|
|
2664
2867
|
<Tooltip>
|
|
@@ -2666,13 +2869,13 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2666
2869
|
<button
|
|
2667
2870
|
type="button"
|
|
2668
2871
|
onClick={addLayer}
|
|
2669
|
-
aria-label="
|
|
2872
|
+
aria-label={t("panel.layers.add")}
|
|
2670
2873
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
2671
2874
|
>
|
|
2672
2875
|
<IconPlus />
|
|
2673
2876
|
</button>
|
|
2674
2877
|
</TooltipTrigger>
|
|
2675
|
-
<TooltipContent className="z-[10000]"
|
|
2878
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.add")}</TooltipContent>
|
|
2676
2879
|
</Tooltip>
|
|
2677
2880
|
<Tooltip>
|
|
2678
2881
|
<TooltipTrigger asChild>
|
|
@@ -2682,7 +2885,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2682
2885
|
if (!canMergeLayers) return;
|
|
2683
2886
|
setIsMergingLayers(true);
|
|
2684
2887
|
}}
|
|
2685
|
-
aria-label="
|
|
2888
|
+
aria-label={t("panel.layers.merge")}
|
|
2686
2889
|
disabled={!canMergeLayers}
|
|
2687
2890
|
className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
|
|
2688
2891
|
>
|
|
@@ -2690,7 +2893,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2690
2893
|
</button>
|
|
2691
2894
|
</TooltipTrigger>
|
|
2692
2895
|
<TooltipContent className="z-[10000]">
|
|
2693
|
-
{canMergeLayers ? "
|
|
2896
|
+
{canMergeLayers ? t("panel.layers.merge") : t("panel.layers.mergeNeedTwo")}
|
|
2694
2897
|
</TooltipContent>
|
|
2695
2898
|
</Tooltip>
|
|
2696
2899
|
</div>
|
|
@@ -2707,12 +2910,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2707
2910
|
<span className="truncate">{layer.name}</span>
|
|
2708
2911
|
{layer.isMapping ? (
|
|
2709
2912
|
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
2710
|
-
|
|
2913
|
+
{t("panel.layers.mapping")}
|
|
2711
2914
|
</span>
|
|
2712
2915
|
) : null}
|
|
2713
2916
|
{layer.isPrimary ? (
|
|
2714
2917
|
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
2715
|
-
|
|
2918
|
+
{t("panel.layers.primary")}
|
|
2716
2919
|
</span>
|
|
2717
2920
|
) : null}
|
|
2718
2921
|
{layer.locked ? <IconLock locked /> : null}
|
|
@@ -2742,7 +2945,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2742
2945
|
}
|
|
2743
2946
|
}}
|
|
2744
2947
|
className="h-7 min-w-0 flex-1"
|
|
2745
|
-
placeholder="
|
|
2948
|
+
placeholder={t("panel.layers.namePlaceholder")}
|
|
2746
2949
|
autoFocus
|
|
2747
2950
|
/>
|
|
2748
2951
|
) : (
|
|
@@ -2750,12 +2953,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2750
2953
|
)}
|
|
2751
2954
|
{activeLayer.isMapping ? (
|
|
2752
2955
|
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
2753
|
-
|
|
2956
|
+
{t("panel.layers.mappingLayer")}
|
|
2754
2957
|
</span>
|
|
2755
2958
|
) : null}
|
|
2756
2959
|
{activeLayer.isPrimary ? (
|
|
2757
2960
|
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
2758
|
-
|
|
2961
|
+
{t("panel.layers.primaryLayer")}
|
|
2759
2962
|
</span>
|
|
2760
2963
|
) : null}
|
|
2761
2964
|
<div className="flex-1" />
|
|
@@ -2765,7 +2968,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2765
2968
|
type="button"
|
|
2766
2969
|
onClick={() => setPrimaryLayer(activeLayer.id)}
|
|
2767
2970
|
disabled={activeLayer.isPrimary}
|
|
2768
|
-
aria-label={activeLayer.isPrimary ? "
|
|
2971
|
+
aria-label={activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
2769
2972
|
className={[
|
|
2770
2973
|
"rounded border px-2 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-60",
|
|
2771
2974
|
activeLayer.isPrimary
|
|
@@ -2773,11 +2976,11 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2773
2976
|
: "border-border hover:bg-accent",
|
|
2774
2977
|
].join(" ")}
|
|
2775
2978
|
>
|
|
2776
|
-
{activeLayer.isPrimary ? "
|
|
2979
|
+
{activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
2777
2980
|
</button>
|
|
2778
2981
|
</TooltipTrigger>
|
|
2779
2982
|
<TooltipContent className="z-[10000]">
|
|
2780
|
-
{activeLayer.isPrimary ? "
|
|
2983
|
+
{activeLayer.isPrimary ? t("panel.layers.isPrimaryHint") : t("panel.layers.setPrimaryHint")}
|
|
2781
2984
|
</TooltipContent>
|
|
2782
2985
|
</Tooltip>
|
|
2783
2986
|
<Tooltip>
|
|
@@ -2786,14 +2989,14 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2786
2989
|
type="button"
|
|
2787
2990
|
onClick={() => toggleLayerLock(activeLayer.id)}
|
|
2788
2991
|
disabled={!activeLayer.editable}
|
|
2789
|
-
aria-label={activeLayer.locked ? "
|
|
2992
|
+
aria-label={activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
2790
2993
|
className="rounded border border-border p-1 disabled:opacity-40"
|
|
2791
2994
|
>
|
|
2792
2995
|
<IconLock locked={activeLayer.locked} />
|
|
2793
2996
|
</button>
|
|
2794
2997
|
</TooltipTrigger>
|
|
2795
2998
|
<TooltipContent className="z-[10000]">
|
|
2796
|
-
{activeLayer.locked ? "
|
|
2999
|
+
{activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
2797
3000
|
</TooltipContent>
|
|
2798
3001
|
</Tooltip>
|
|
2799
3002
|
<Tooltip>
|
|
@@ -2806,7 +3009,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2806
3009
|
setEditingLayerId(null);
|
|
2807
3010
|
setEditingLayerName("");
|
|
2808
3011
|
}}
|
|
2809
|
-
aria-label="
|
|
3012
|
+
aria-label={t("panel.layers.saveName")}
|
|
2810
3013
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
2811
3014
|
>
|
|
2812
3015
|
<IconCheck />
|
|
@@ -2819,7 +3022,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2819
3022
|
setEditingLayerName(activeLayer.name);
|
|
2820
3023
|
}}
|
|
2821
3024
|
disabled={!activeLayer.editable}
|
|
2822
|
-
aria-label="
|
|
3025
|
+
aria-label={t("panel.layers.rename")}
|
|
2823
3026
|
className="rounded border border-border p-1 disabled:opacity-40"
|
|
2824
3027
|
>
|
|
2825
3028
|
<IconEdit />
|
|
@@ -2827,7 +3030,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2827
3030
|
)}
|
|
2828
3031
|
</TooltipTrigger>
|
|
2829
3032
|
<TooltipContent className="z-[10000]">
|
|
2830
|
-
{editingLayerId === activeLayer.id ? "
|
|
3033
|
+
{editingLayerId === activeLayer.id ? t("panel.layers.saveName") : t("panel.layers.rename")}
|
|
2831
3034
|
</TooltipContent>
|
|
2832
3035
|
</Tooltip>
|
|
2833
3036
|
{editingLayerId === activeLayer.id ? (
|
|
@@ -2839,13 +3042,13 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2839
3042
|
setEditingLayerId(null);
|
|
2840
3043
|
setEditingLayerName("");
|
|
2841
3044
|
}}
|
|
2842
|
-
aria-label="
|
|
3045
|
+
aria-label={t("panel.layers.cancelEditName")}
|
|
2843
3046
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
2844
3047
|
>
|
|
2845
3048
|
<IconClose />
|
|
2846
3049
|
</button>
|
|
2847
3050
|
</TooltipTrigger>
|
|
2848
|
-
<TooltipContent className="z-[10000]"
|
|
3051
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.cancelEdit")}</TooltipContent>
|
|
2849
3052
|
</Tooltip>
|
|
2850
3053
|
) : null}
|
|
2851
3054
|
<Tooltip>
|
|
@@ -2866,10 +3069,10 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2866
3069
|
disabled={!activeLayer.editable || activeLayer.locked}
|
|
2867
3070
|
aria-label={
|
|
2868
3071
|
!activeLayer.editable
|
|
2869
|
-
? "
|
|
3072
|
+
? t("panel.layers.cannotDeleteDefault")
|
|
2870
3073
|
: activeLayer.locked
|
|
2871
|
-
? "
|
|
2872
|
-
: "
|
|
3074
|
+
? t("panel.layers.cannotDeleteLocked")
|
|
3075
|
+
: t("panel.layers.delete")
|
|
2873
3076
|
}
|
|
2874
3077
|
className="rounded border border-border p-1 disabled:opacity-40"
|
|
2875
3078
|
>
|
|
@@ -2878,10 +3081,10 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2878
3081
|
</TooltipTrigger>
|
|
2879
3082
|
<TooltipContent className="z-[10000]">
|
|
2880
3083
|
{!activeLayer.editable
|
|
2881
|
-
? "
|
|
3084
|
+
? t("panel.layers.cannotDeleteDefault")
|
|
2882
3085
|
: activeLayer.locked
|
|
2883
|
-
? "
|
|
2884
|
-
: "
|
|
3086
|
+
? t("panel.layers.cannotDeleteLocked")
|
|
3087
|
+
: t("panel.layers.delete")}
|
|
2885
3088
|
</TooltipContent>
|
|
2886
3089
|
</Tooltip>
|
|
2887
3090
|
</div>
|
|
@@ -2889,7 +3092,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2889
3092
|
|
|
2890
3093
|
{isLayerPanelExpanded ? (
|
|
2891
3094
|
<div className="rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
2892
|
-
<div className="mb-1 text-muted-foreground"
|
|
3095
|
+
<div className="mb-1 text-muted-foreground">{t("panel.layers.selectMergeLayers")}</div>
|
|
2893
3096
|
<div className="flex flex-wrap gap-2">
|
|
2894
3097
|
{layers.map((layer) => (
|
|
2895
3098
|
<label key={layer.id} className="flex items-center gap-1.5">
|
|
@@ -2901,10 +3104,10 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2901
3104
|
/>
|
|
2902
3105
|
<span
|
|
2903
3106
|
className="max-w-[140px] truncate"
|
|
2904
|
-
title={layer.isMapping ? "
|
|
3107
|
+
title={layer.isMapping ? t("panel.layers.mappingCannotMerge") : layer.name}
|
|
2905
3108
|
>
|
|
2906
3109
|
{layer.name}
|
|
2907
|
-
{layer.isMapping ? "
|
|
3110
|
+
{layer.isMapping ? t("panel.layers.cannotMergeSuffix") : ""}
|
|
2908
3111
|
</span>
|
|
2909
3112
|
</label>
|
|
2910
3113
|
))}
|
|
@@ -2916,12 +3119,12 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2916
3119
|
|
|
2917
3120
|
{isLayerPanelExpanded && isMergingLayers ? (
|
|
2918
3121
|
<div className="mt-2 flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
2919
|
-
<span className="text-muted-foreground"
|
|
3122
|
+
<span className="text-muted-foreground">{t("panel.layers.mergedName")}</span>
|
|
2920
3123
|
<Input
|
|
2921
3124
|
value={mergeLayerName}
|
|
2922
3125
|
onChange={(e) => setMergeLayerName(e.target.value)}
|
|
2923
3126
|
className="h-7 min-w-0 flex-1"
|
|
2924
|
-
placeholder="
|
|
3127
|
+
placeholder={t("panel.layers.mergedNamePlaceholder")}
|
|
2925
3128
|
autoFocus
|
|
2926
3129
|
/>
|
|
2927
3130
|
<Tooltip>
|
|
@@ -2934,14 +3137,14 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2934
3137
|
setIsMergingLayers(false);
|
|
2935
3138
|
setMergeLayerName("");
|
|
2936
3139
|
}}
|
|
2937
|
-
aria-label="
|
|
3140
|
+
aria-label={t("panel.layers.confirmMergeAria")}
|
|
2938
3141
|
disabled={!canMergeLayers}
|
|
2939
3142
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
2940
3143
|
>
|
|
2941
3144
|
<IconCheck />
|
|
2942
3145
|
</button>
|
|
2943
3146
|
</TooltipTrigger>
|
|
2944
|
-
<TooltipContent className="z-[10000]"
|
|
3147
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.confirmMerge")}</TooltipContent>
|
|
2945
3148
|
</Tooltip>
|
|
2946
3149
|
<Tooltip>
|
|
2947
3150
|
<TooltipTrigger asChild>
|
|
@@ -2951,13 +3154,13 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2951
3154
|
setIsMergingLayers(false);
|
|
2952
3155
|
setMergeLayerName("");
|
|
2953
3156
|
}}
|
|
2954
|
-
aria-label="
|
|
3157
|
+
aria-label={t("panel.layers.cancelMergeAria")}
|
|
2955
3158
|
className="rounded border border-border p-1 hover:bg-accent"
|
|
2956
3159
|
>
|
|
2957
3160
|
<IconClose />
|
|
2958
3161
|
</button>
|
|
2959
3162
|
</TooltipTrigger>
|
|
2960
|
-
<TooltipContent className="z-[10000]"
|
|
3163
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.cancelMerge")}</TooltipContent>
|
|
2961
3164
|
</Tooltip>
|
|
2962
3165
|
</div>
|
|
2963
3166
|
) : null}
|
|
@@ -2981,6 +3184,10 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
2981
3184
|
}
|
|
2982
3185
|
onUpdateBlueprintNode={handleUpdateBlueprintNode}
|
|
2983
3186
|
blueprintLibraryOptions={blueprintLibraryOptions}
|
|
3187
|
+
blueprintNodeOptions={blueprintGraph.document.nodes.map((node) => ({
|
|
3188
|
+
id: node.id,
|
|
3189
|
+
label: `${node.label || node.id}${node.nodeType ? ` (${node.nodeType})` : ""}`,
|
|
3190
|
+
}))}
|
|
2984
3191
|
selectedElement={selectedElement}
|
|
2985
3192
|
selectedElements={selectedElements}
|
|
2986
3193
|
viewElementScope={selectedElementScope}
|
|
@@ -3015,6 +3222,11 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3015
3222
|
</div>
|
|
3016
3223
|
</ResizablePanel>
|
|
3017
3224
|
</ResizablePanelGroup>
|
|
3225
|
+
<AssistantChatPanel
|
|
3226
|
+
deps={assistantDeps}
|
|
3227
|
+
open={assistantOpen}
|
|
3228
|
+
onOpenChange={setAssistantOpen}
|
|
3229
|
+
/>
|
|
3018
3230
|
<AlertDialog
|
|
3019
3231
|
open={mappingDeleteConfirmOpen}
|
|
3020
3232
|
onOpenChange={(open) => {
|
|
@@ -3024,13 +3236,15 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3024
3236
|
>
|
|
3025
3237
|
<AlertDialogContent overlayClassName="bg-transparent pointer-events-none">
|
|
3026
3238
|
<AlertDialogHeader>
|
|
3027
|
-
<AlertDialogTitle
|
|
3239
|
+
<AlertDialogTitle>{t("panel.layers.confirmDeleteTitle")}</AlertDialogTitle>
|
|
3028
3240
|
<AlertDialogDescription>
|
|
3029
|
-
|
|
3241
|
+
{t("panel.layers.confirmDeleteMappingBody", {
|
|
3242
|
+
count: mappingDeleteImpactCount,
|
|
3243
|
+
})}
|
|
3030
3244
|
</AlertDialogDescription>
|
|
3031
3245
|
</AlertDialogHeader>
|
|
3032
3246
|
<AlertDialogFooter>
|
|
3033
|
-
<AlertDialogCancel
|
|
3247
|
+
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
|
3034
3248
|
<AlertDialogAction
|
|
3035
3249
|
onClick={() => {
|
|
3036
3250
|
const fn = mappingDeleteProceedRef.current;
|
|
@@ -3039,7 +3253,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3039
3253
|
fn?.();
|
|
3040
3254
|
}}
|
|
3041
3255
|
>
|
|
3042
|
-
|
|
3256
|
+
{t("panel.layers.continueDelete")}
|
|
3043
3257
|
</AlertDialogAction>
|
|
3044
3258
|
</AlertDialogFooter>
|
|
3045
3259
|
</AlertDialogContent>
|
|
@@ -3058,9 +3272,11 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3058
3272
|
overlayClassName="bg-transparent pointer-events-none"
|
|
3059
3273
|
>
|
|
3060
3274
|
<DialogHeader>
|
|
3061
|
-
<DialogTitle
|
|
3275
|
+
<DialogTitle>{t("panel.layers.confirmDeleteLayerTitle")}</DialogTitle>
|
|
3062
3276
|
<DialogDescription>
|
|
3063
|
-
|
|
3277
|
+
{t("panel.layers.aboutToDeleteLayer", {
|
|
3278
|
+
name: deletingLayer?.name ?? "-",
|
|
3279
|
+
})}
|
|
3064
3280
|
</DialogDescription>
|
|
3065
3281
|
</DialogHeader>
|
|
3066
3282
|
{deletingLayer ? (
|
|
@@ -3073,7 +3289,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3073
3289
|
<div className="flex items-center gap-3">
|
|
3074
3290
|
<label className="flex items-center gap-1.5">
|
|
3075
3291
|
<RadioGroupItem value="move" />
|
|
3076
|
-
|
|
3292
|
+
{t("panel.layers.migrateNodesTo")}
|
|
3077
3293
|
</label>
|
|
3078
3294
|
<Select
|
|
3079
3295
|
value={deleteTargetLayerId || "__none__"}
|
|
@@ -3083,10 +3299,10 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3083
3299
|
disabled={deletingLayerMode !== "move" || deleteTargetCandidates.length === 0}
|
|
3084
3300
|
>
|
|
3085
3301
|
<SelectTrigger className="h-8 w-[220px]">
|
|
3086
|
-
<SelectValue placeholder="
|
|
3302
|
+
<SelectValue placeholder={t("panel.layers.selectTargetLayer")} />
|
|
3087
3303
|
</SelectTrigger>
|
|
3088
3304
|
<SelectContent>
|
|
3089
|
-
<SelectItem value="__none__"
|
|
3305
|
+
<SelectItem value="__none__">{t("panel.layers.pleaseSelectLayer")}</SelectItem>
|
|
3090
3306
|
{deleteTargetCandidates.map((l) => (
|
|
3091
3307
|
<SelectItem key={l.id} value={l.id}>
|
|
3092
3308
|
{l.name}
|
|
@@ -3098,7 +3314,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3098
3314
|
) : null}
|
|
3099
3315
|
<label className="flex items-center gap-1.5">
|
|
3100
3316
|
<RadioGroupItem value="remove" />
|
|
3101
|
-
{deletingLayer.isMapping ? "
|
|
3317
|
+
{deletingLayer.isMapping ? t("panel.layers.deleteMappingWithNodes") : t("panel.layers.deleteAllNodesToo")}
|
|
3102
3318
|
</label>
|
|
3103
3319
|
</RadioGroup>
|
|
3104
3320
|
) : null}
|
|
@@ -3112,7 +3328,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3112
3328
|
}}
|
|
3113
3329
|
className="rounded border border-border px-3 py-1.5 text-sm hover:bg-accent"
|
|
3114
3330
|
>
|
|
3115
|
-
|
|
3331
|
+
{t("common.cancel")}
|
|
3116
3332
|
</button>
|
|
3117
3333
|
<button
|
|
3118
3334
|
type="button"
|
|
@@ -3132,7 +3348,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3132
3348
|
}}
|
|
3133
3349
|
className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90"
|
|
3134
3350
|
>
|
|
3135
|
-
|
|
3351
|
+
{t("panel.layers.confirmDeleteTitle")}
|
|
3136
3352
|
</button>
|
|
3137
3353
|
</DialogFooter>
|
|
3138
3354
|
</DialogContent>
|
|
@@ -3146,8 +3362,8 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3146
3362
|
overlayClassName="bg-transparent pointer-events-none"
|
|
3147
3363
|
>
|
|
3148
3364
|
<DialogHeader>
|
|
3149
|
-
<DialogTitle>
|
|
3150
|
-
<DialogDescription
|
|
3365
|
+
<DialogTitle>{t("panel.menubar.titleIconPreviewTitle")}</DialogTitle>
|
|
3366
|
+
<DialogDescription>{t("panel.menubar.titleIconPreviewDesc")}</DialogDescription>
|
|
3151
3367
|
</DialogHeader>
|
|
3152
3368
|
<div className="flex items-center justify-end gap-2">
|
|
3153
3369
|
<button
|
|
@@ -3172,7 +3388,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3172
3388
|
onClick={() => setTitleIconZoom(1.6)}
|
|
3173
3389
|
className="rounded border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
|
|
3174
3390
|
>
|
|
3175
|
-
|
|
3391
|
+
{t("common.reset")}
|
|
3176
3392
|
</button>
|
|
3177
3393
|
</div>
|
|
3178
3394
|
{titleIconDataUrl ? (
|
|
@@ -3189,7 +3405,7 @@ export function ReactViewPanel({ initialZoom = 1, className }: ReactViewPanelPro
|
|
|
3189
3405
|
>
|
|
3190
3406
|
<img
|
|
3191
3407
|
src={titleIconDataUrl}
|
|
3192
|
-
alt="
|
|
3408
|
+
alt={t("panel.menubar.titleIconPreviewAlt")}
|
|
3193
3409
|
className="mx-auto rounded border border-border/70 object-contain"
|
|
3194
3410
|
style={{
|
|
3195
3411
|
width: 200,
|