@arronqzy/react-view 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/readme.md +6 -1
- package/src/panel/I18nRoot.tsx +14 -2
- package/src/panel/ReactViewOnlinePreview.tsx +29 -18
- package/src/panel/ReactViewPanel.tsx +154 -1006
- package/src/panel/components/PanelConfigSidebar.tsx +297 -3712
- package/src/panel/components/PanelHistoryPopover.tsx +163 -0
- package/src/panel/components/PanelLayerDock.tsx +412 -0
- package/src/panel/components/PanelMenubar.tsx +149 -0
- package/src/panel/components/PanelWorkspaceDialogs.tsx +271 -0
- package/src/panel/components/config/PanelConfigAudioSection.tsx +334 -0
- package/src/panel/components/config/PanelConfigChartSection.tsx +799 -0
- package/src/panel/components/config/PanelConfigGeometrySection.tsx +233 -0
- package/src/panel/components/config/PanelConfigGridChildSpan.tsx +70 -0
- package/src/panel/components/config/PanelConfigGridSection.tsx +118 -0
- package/src/panel/components/config/PanelConfigMultiSelect.tsx +1291 -0
- package/src/panel/components/config/PanelConfigNodeInfo.tsx +169 -0
- package/src/panel/components/config/PanelConfigReferenceSection.tsx +99 -0
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +6 -2
- package/src/panel/components/config/PanelConfigStyleSections.tsx +249 -0
- package/src/panel/components/config/PanelConfigTextSection.tsx +268 -0
- package/src/panel/components/config/PanelConfigVideoSection.tsx +133 -0
- package/src/panel/components/config/PanelConfigViewportSection.tsx +139 -0
- package/src/panel/components/config/helpers.ts +85 -0
- package/src/panel/hooks/useWorkspaceProjects.ts +112 -34
- package/src/panel/icons.tsx +160 -0
- package/src/panel/index.tsx +15 -1
- package/src/panel/library/parse-workspace-data.ts +142 -0
- package/src/panel/library/workspace-project-cache.ts +13 -4
- package/src/panel/library/workspace-project-db.ts +24 -13
- package/src/panel/library/workspace-project-sync.ts +17 -6
- package/src/panel/utils/outputScale.ts +9 -4
- package/src/panel/utils/panelSelection.ts +57 -0
- package/src/panel/utils/panelStateIO.ts +18 -0
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
type BlueprintMetaDraft,
|
|
66
66
|
} from "@arronqzy/react-blueprint";
|
|
67
67
|
import type { LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
68
|
-
import { abortClockNode, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
68
|
+
import { abortClockNode, appStorageKey, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
69
69
|
import { WorkspaceStageSplit } from "./components/WorkspaceStageSplit";
|
|
70
70
|
import {
|
|
71
71
|
WorkspaceConfigSidebar,
|
|
@@ -84,55 +84,35 @@ import { getPanelMessages } from "./constants/messages";
|
|
|
84
84
|
import { PANEL_Z_INDEX } from "./constants/zIndex";
|
|
85
85
|
import { useRafThrottledScroll } from "./hooks/useRafThrottledScroll";
|
|
86
86
|
import {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
IconBackward,
|
|
88
|
+
IconBringFront,
|
|
89
|
+
IconClose,
|
|
90
|
+
IconForward,
|
|
91
|
+
IconImage,
|
|
92
|
+
IconLayers,
|
|
93
|
+
IconRedo,
|
|
94
|
+
IconSendBack,
|
|
95
|
+
IconUndo,
|
|
96
|
+
} from "./icons";
|
|
97
|
+
import {
|
|
98
|
+
getSelectedTargetsFromIds,
|
|
99
|
+
shouldClearSelectionOnBlank,
|
|
100
|
+
} from "./utils/panelSelection";
|
|
101
|
+
import { PanelMenubar } from "./components/PanelMenubar";
|
|
102
|
+
import { PanelHistoryPopover } from "./components/PanelHistoryPopover";
|
|
103
|
+
import { PanelLayerDock } from "./components/PanelLayerDock";
|
|
104
|
+
import { PanelWorkspaceDialogs } from "./components/PanelWorkspaceDialogs";
|
|
105
|
+
import {
|
|
95
106
|
Button,
|
|
96
|
-
Checkbox,
|
|
97
|
-
Dialog,
|
|
98
|
-
DialogContent,
|
|
99
|
-
DialogDescription,
|
|
100
|
-
DialogFooter,
|
|
101
|
-
DialogHeader,
|
|
102
|
-
DialogTitle,
|
|
103
107
|
DropdownMenu,
|
|
104
108
|
DropdownMenuContent,
|
|
105
109
|
DropdownMenuItem,
|
|
106
110
|
DropdownMenuTrigger,
|
|
107
|
-
Empty,
|
|
108
|
-
EmptyDescription,
|
|
109
|
-
EmptyIcon,
|
|
110
|
-
EmptyTitle,
|
|
111
111
|
Input,
|
|
112
|
-
Menubar,
|
|
113
|
-
MenubarCheckboxItem,
|
|
114
|
-
MenubarContent,
|
|
115
|
-
MenubarItem,
|
|
116
|
-
MenubarMenu,
|
|
117
|
-
MenubarRadioGroup,
|
|
118
|
-
MenubarRadioItem,
|
|
119
|
-
MenubarSeparator,
|
|
120
|
-
MenubarTrigger,
|
|
121
|
-
RadioGroup,
|
|
122
|
-
RadioGroupItem,
|
|
123
112
|
ResizableHandle,
|
|
124
113
|
ResizablePanel,
|
|
125
114
|
ResizablePanelGroup,
|
|
126
|
-
Select,
|
|
127
|
-
SelectContent,
|
|
128
|
-
SelectItem,
|
|
129
|
-
SelectTrigger,
|
|
130
|
-
SelectValue,
|
|
131
115
|
Switch,
|
|
132
|
-
Tabs,
|
|
133
|
-
TabsContent,
|
|
134
|
-
TabsList,
|
|
135
|
-
TabsTrigger,
|
|
136
116
|
Toaster,
|
|
137
117
|
Tooltip,
|
|
138
118
|
TooltipContent,
|
|
@@ -142,242 +122,33 @@ import {
|
|
|
142
122
|
useTheme,
|
|
143
123
|
} from "@arronqzy/ui";
|
|
144
124
|
|
|
145
|
-
function IconPlus() {
|
|
146
|
-
return (
|
|
147
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
148
|
-
<path d="M12 5v14M5 12h14" />
|
|
149
|
-
</svg>
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function IconMerge() {
|
|
154
|
-
return (
|
|
155
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
156
|
-
<path d="M7 7h4a4 4 0 0 1 4 4v6" />
|
|
157
|
-
<path d="m12 14 3 3 3-3" />
|
|
158
|
-
<path d="M7 17h2" />
|
|
159
|
-
</svg>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function IconLock({ locked }: { locked: boolean }) {
|
|
164
|
-
return locked ? (
|
|
165
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
166
|
-
<rect x="4" y="11" width="16" height="9" rx="2" />
|
|
167
|
-
<path d="M8 11V8a4 4 0 0 1 8 0v3" />
|
|
168
|
-
</svg>
|
|
169
|
-
) : (
|
|
170
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
171
|
-
<rect x="4" y="11" width="16" height="9" rx="2" />
|
|
172
|
-
<path d="M16 11V8a4 4 0 0 0-7-2.5" />
|
|
173
|
-
</svg>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function IconEdit() {
|
|
178
|
-
return (
|
|
179
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
180
|
-
<path d="M12 20h9" />
|
|
181
|
-
<path d="m16.5 3.5 4 4L8 20l-5 1 1-5Z" />
|
|
182
|
-
</svg>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function IconTrash() {
|
|
187
|
-
return (
|
|
188
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
189
|
-
<path d="M3 6h18" />
|
|
190
|
-
<path d="M8 6V4h8v2" />
|
|
191
|
-
<path d="M19 6l-1 14H6L5 6" />
|
|
192
|
-
</svg>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function IconCheck() {
|
|
197
|
-
return (
|
|
198
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
199
|
-
<path d="m5 12 5 5L20 7" />
|
|
200
|
-
</svg>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function IconClose() {
|
|
205
|
-
return (
|
|
206
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
207
|
-
<path d="M6 6l12 12M18 6 6 18" />
|
|
208
|
-
</svg>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function IconChevron({ expanded }: { expanded: boolean }) {
|
|
213
|
-
return (
|
|
214
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
215
|
-
{expanded ? <path d="m6 14 6-6 6 6" /> : <path d="m6 10 6 6 6-6" />}
|
|
216
|
-
</svg>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function IconUndo() {
|
|
221
|
-
return (
|
|
222
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
223
|
-
<path d="M9 14 4 9l5-5" />
|
|
224
|
-
<path d="M20 20a9 9 0 0 0-9-9H4" />
|
|
225
|
-
</svg>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function IconRedo() {
|
|
230
|
-
return (
|
|
231
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
232
|
-
<path d="m15 14 5-5-5-5" />
|
|
233
|
-
<path d="M4 20a9 9 0 0 1 9-9h7" />
|
|
234
|
-
</svg>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function IconHistory() {
|
|
239
|
-
return (
|
|
240
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
241
|
-
<path d="M3 12a9 9 0 1 0 3-6.7" />
|
|
242
|
-
<path d="M3 4v4h4" />
|
|
243
|
-
<path d="M12 7v5l3 2" />
|
|
244
|
-
</svg>
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function IconBringFront() {
|
|
249
|
-
return (
|
|
250
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
251
|
-
<path d="M7 17h10M12 17V7" />
|
|
252
|
-
<path d="m8.5 9 3.5-3.5L15.5 9" />
|
|
253
|
-
</svg>
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function IconSendBack() {
|
|
258
|
-
return (
|
|
259
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
260
|
-
<path d="M7 7h10M12 17V7" />
|
|
261
|
-
<path d="m15.5 15-3.5 3.5L8.5 15" />
|
|
262
|
-
</svg>
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function IconForward() {
|
|
267
|
-
return (
|
|
268
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
269
|
-
<path d="M12 18V6" />
|
|
270
|
-
<path d="m8.5 9 3.5-3.5L15.5 9" />
|
|
271
|
-
</svg>
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function IconBackward() {
|
|
276
|
-
return (
|
|
277
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
278
|
-
<path d="M12 6v12" />
|
|
279
|
-
<path d="m15.5 15-3.5 3.5L8.5 15" />
|
|
280
|
-
</svg>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function IconLayers() {
|
|
285
|
-
return (
|
|
286
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
287
|
-
<path d="m12 4 8 4-8 4-8-4 8-4Z" />
|
|
288
|
-
<path d="m4 12 8 4 8-4" />
|
|
289
|
-
<path d="m4 16 8 4 8-4" />
|
|
290
|
-
</svg>
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function IconImage() {
|
|
295
|
-
return (
|
|
296
|
-
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
|
|
297
|
-
<rect x="3" y="5" width="18" height="14" rx="2" />
|
|
298
|
-
<circle cx="9" cy="10" r="1.5" />
|
|
299
|
-
<path d="m21 16-5.5-5.5L7 19" />
|
|
300
|
-
</svg>
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function formatRelativeTime(
|
|
305
|
-
timestamp: number,
|
|
306
|
-
now: number,
|
|
307
|
-
t: (key: string, params?: Record<string, string | number>) => string
|
|
308
|
-
): string {
|
|
309
|
-
const diff = Math.max(0, now - timestamp);
|
|
310
|
-
const sec = Math.floor(diff / 1000);
|
|
311
|
-
if (sec < 60) return t("common.secondsAgo", { sec });
|
|
312
|
-
const min = Math.floor(sec / 60);
|
|
313
|
-
if (min < 60) return t("common.minutesAgo", { min });
|
|
314
|
-
const hour = Math.floor(min / 60);
|
|
315
|
-
if (hour < 24) return t("common.hoursAgo", { hour });
|
|
316
|
-
const day = Math.floor(hour / 24);
|
|
317
|
-
return t("common.daysAgo", { day });
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function getSelectedTargetsFromIds(
|
|
321
|
-
container: HTMLElement | null,
|
|
322
|
-
ids: string[],
|
|
323
|
-
) {
|
|
324
|
-
if (!container) return [];
|
|
325
|
-
const targets: HTMLElement[] = [];
|
|
326
|
-
for (const id of ids) {
|
|
327
|
-
const el = container.querySelector<HTMLElement>(
|
|
328
|
-
`[data-element-id="${id}"]`,
|
|
329
|
-
);
|
|
330
|
-
if (el) targets.push(el);
|
|
331
|
-
}
|
|
332
|
-
return targets;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function shouldClearSelectionOnBlank(target: HTMLElement | null) {
|
|
336
|
-
if (!target) return false;
|
|
337
|
-
if (
|
|
338
|
-
target.closest(".rv-selectable") ||
|
|
339
|
-
target.closest(".moveable-control-box") ||
|
|
340
|
-
target.closest(".moveable-group") ||
|
|
341
|
-
target.closest(".moveable-line") ||
|
|
342
|
-
target.closest(".moveable-control") ||
|
|
343
|
-
target.closest(".moveable-direction")
|
|
344
|
-
) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
if (
|
|
348
|
-
target.closest("button") ||
|
|
349
|
-
target.closest("input") ||
|
|
350
|
-
target.closest("select") ||
|
|
351
|
-
target.closest("textarea") ||
|
|
352
|
-
target.closest("label") ||
|
|
353
|
-
target.closest("a") ||
|
|
354
|
-
target.closest("[role='menuitem']") ||
|
|
355
|
-
target.closest("[data-radix-popper-content-wrapper]")
|
|
356
|
-
) {
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
125
|
export type ReactViewPanelProps = {
|
|
363
126
|
initialZoom?: number;
|
|
364
127
|
className?: string;
|
|
365
|
-
/**
|
|
128
|
+
/** 外部传入的完整工作区数据,挂载后按这份数据完整渲染;空则显示空白,不自动打开 IndexedDB 记录 */
|
|
366
129
|
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
367
130
|
/** 界面语言;省略时按 localStorage / 浏览器语言解析 */
|
|
368
131
|
locale?: Locale | null;
|
|
369
132
|
onLocaleChange?: (locale: Locale) => void;
|
|
133
|
+
/**
|
|
134
|
+
* 隔离本实例的 IndexedDB / localStorage / BroadcastChannel。
|
|
135
|
+
* 同一页面挂多个 App 时传入不同值,避免工作区、蓝图库、预览缓存互相覆盖。
|
|
136
|
+
* 省略或空字符串保持历史全局库名。
|
|
137
|
+
*/
|
|
138
|
+
nameSpace?: string | null;
|
|
370
139
|
};
|
|
371
140
|
|
|
372
141
|
function ReactViewPanelInner({
|
|
373
142
|
initialZoom = 1,
|
|
374
143
|
className,
|
|
375
144
|
initialWorkspace = null,
|
|
145
|
+
nameSpace = null,
|
|
376
146
|
}: ReactViewPanelProps) {
|
|
377
147
|
const { t, locale, setLocale } = useI18n();
|
|
378
148
|
const messages = useMemo(() => getPanelMessages(t), [t]);
|
|
379
|
-
const THEME_STORAGE_KEY = "panel:theme";
|
|
380
|
-
const TITLE_ICON_STORAGE_KEY = "panel:titleIconDataUrl";
|
|
149
|
+
const THEME_STORAGE_KEY = appStorageKey("panel:theme", nameSpace);
|
|
150
|
+
const TITLE_ICON_STORAGE_KEY = appStorageKey("panel:titleIconDataUrl", nameSpace);
|
|
151
|
+
const FONT_SIZE_STORAGE_KEY = appStorageKey("panel:fontSize", nameSpace);
|
|
381
152
|
const { setTheme } = useTheme();
|
|
382
153
|
const themedScrollbarClass =
|
|
383
154
|
"scrollbar-thin [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-muted/40 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/80 [&::-webkit-scrollbar-thumb]:hover:bg-border";
|
|
@@ -483,7 +254,7 @@ function ReactViewPanelInner({
|
|
|
483
254
|
const [titleIconZoom, setTitleIconZoom] = useState(1.6);
|
|
484
255
|
const [pendingPreviewLayerId, setPendingPreviewLayerId] = useState<string | null>(null);
|
|
485
256
|
const [panelFontSize, setPanelFontSize] = useState<"sm" | "md" | "lg">("md");
|
|
486
|
-
const [outputScale, setOutputScale] = useState(() => readOutputScale());
|
|
257
|
+
const [outputScale, setOutputScale] = useState(() => readOutputScale(nameSpace));
|
|
487
258
|
const [blueprintOpen, setBlueprintOpen] = useState(false);
|
|
488
259
|
const [assistantOpen, setAssistantOpen] = useState(false);
|
|
489
260
|
const [blueprintGraph, setBlueprintGraph] = useState(() => BlueprintGraph.empty());
|
|
@@ -577,35 +348,26 @@ function ReactViewPanelInner({
|
|
|
577
348
|
|
|
578
349
|
useEffect(() => {
|
|
579
350
|
try {
|
|
580
|
-
const stored = localStorage.getItem(
|
|
351
|
+
const stored = localStorage.getItem(FONT_SIZE_STORAGE_KEY);
|
|
581
352
|
if (stored === "sm" || stored === "md" || stored === "lg") {
|
|
582
353
|
setPanelFontSize(stored);
|
|
583
354
|
}
|
|
584
355
|
} catch {
|
|
585
356
|
// ignore storage errors
|
|
586
357
|
}
|
|
587
|
-
}, []);
|
|
358
|
+
}, [FONT_SIZE_STORAGE_KEY]);
|
|
588
359
|
|
|
589
360
|
useEffect(() => {
|
|
590
361
|
try {
|
|
591
|
-
localStorage.setItem(
|
|
362
|
+
localStorage.setItem(FONT_SIZE_STORAGE_KEY, panelFontSize);
|
|
592
363
|
} catch {
|
|
593
364
|
// ignore storage errors
|
|
594
365
|
}
|
|
595
|
-
}, [panelFontSize]);
|
|
366
|
+
}, [FONT_SIZE_STORAGE_KEY, panelFontSize]);
|
|
596
367
|
|
|
597
368
|
useEffect(() => {
|
|
598
|
-
writeOutputScale(outputScale);
|
|
599
|
-
}, [outputScale]);
|
|
600
|
-
|
|
601
|
-
useEffect(() => {
|
|
602
|
-
try {
|
|
603
|
-
const stored = localStorage.getItem(TITLE_ICON_STORAGE_KEY) ?? "";
|
|
604
|
-
if (stored) setTitleIconDataUrl(stored);
|
|
605
|
-
} catch {
|
|
606
|
-
// ignore storage errors
|
|
607
|
-
}
|
|
608
|
-
}, [TITLE_ICON_STORAGE_KEY]);
|
|
369
|
+
writeOutputScale(outputScale, nameSpace);
|
|
370
|
+
}, [nameSpace, outputScale]);
|
|
609
371
|
|
|
610
372
|
useEffect(() => {
|
|
611
373
|
try {
|
|
@@ -865,13 +627,13 @@ function ReactViewPanelInner({
|
|
|
865
627
|
|
|
866
628
|
const resolveLibraryBlueprint = useCallback<LibraryBlueprintResolver>(
|
|
867
629
|
async (libraryBlueprintId) => {
|
|
868
|
-
const record = await getBlueprintLibraryRecord(libraryBlueprintId);
|
|
630
|
+
const record = await getBlueprintLibraryRecord(libraryBlueprintId, nameSpace);
|
|
869
631
|
if (!record) return null;
|
|
870
|
-
const items = await listBlueprintLibrary();
|
|
632
|
+
const items = await listBlueprintLibrary(nameSpace);
|
|
871
633
|
const nameById = new Map(items.map((item) => [item.id, item.name]));
|
|
872
634
|
return documentToRunnableGraph(record.document, { libraryNameById: nameById });
|
|
873
635
|
},
|
|
874
|
-
[]
|
|
636
|
+
[nameSpace]
|
|
875
637
|
);
|
|
876
638
|
|
|
877
639
|
const handleBlueprintExecutionBlocked = useCallback((message: string) => {
|
|
@@ -893,6 +655,7 @@ function ReactViewPanelInner({
|
|
|
893
655
|
libraryNameById: blueprintLibraryNameById,
|
|
894
656
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
895
657
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
658
|
+
nameSpace,
|
|
896
659
|
});
|
|
897
660
|
|
|
898
661
|
const { selectLifecycleNode, selectedLifecycleNodeId: debugLifecycleNodeId } =
|
|
@@ -994,9 +757,9 @@ function ReactViewPanelInner({
|
|
|
994
757
|
);
|
|
995
758
|
|
|
996
759
|
const refreshBlueprintLibrary = useCallback(async () => {
|
|
997
|
-
const items = await listBlueprintLibrary();
|
|
760
|
+
const items = await listBlueprintLibrary(nameSpace);
|
|
998
761
|
setBlueprintLibraryItems(items);
|
|
999
|
-
}, []);
|
|
762
|
+
}, [nameSpace]);
|
|
1000
763
|
|
|
1001
764
|
useEffect(() => {
|
|
1002
765
|
void refreshBlueprintLibrary();
|
|
@@ -1004,7 +767,7 @@ function ReactViewPanelInner({
|
|
|
1004
767
|
|
|
1005
768
|
const loadBlueprintFromLibrary = useCallback(
|
|
1006
769
|
async (id: string) => {
|
|
1007
|
-
const record = await getBlueprintLibraryRecord(id);
|
|
770
|
+
const record = await getBlueprintLibraryRecord(id, nameSpace);
|
|
1008
771
|
if (!record) {
|
|
1009
772
|
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
1010
773
|
void refreshBlueprintLibrary();
|
|
@@ -1023,7 +786,7 @@ function ReactViewPanelInner({
|
|
|
1023
786
|
setBlueprintOpen(true);
|
|
1024
787
|
});
|
|
1025
788
|
},
|
|
1026
|
-
[refreshBlueprintLibrary, t]
|
|
789
|
+
[nameSpace, refreshBlueprintLibrary, t]
|
|
1027
790
|
);
|
|
1028
791
|
|
|
1029
792
|
const snapshotWorkspaceBlueprint = useCallback(() => {
|
|
@@ -1078,7 +841,7 @@ function ReactViewPanelInner({
|
|
|
1078
841
|
const syncBlueprintToLibrary = useCallback(async () => {
|
|
1079
842
|
if (!activeBlueprintLibraryId) return;
|
|
1080
843
|
await runBusyTask(t("common.syncingBlueprint"), async () => {
|
|
1081
|
-
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
844
|
+
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId, nameSpace);
|
|
1082
845
|
if (!existing) {
|
|
1083
846
|
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
1084
847
|
void refreshBlueprintLibrary();
|
|
@@ -1094,7 +857,7 @@ function ReactViewPanelInner({
|
|
|
1094
857
|
meta: blueprintMeta,
|
|
1095
858
|
source: existing.source,
|
|
1096
859
|
});
|
|
1097
|
-
await putBlueprintLibraryRecord(record);
|
|
860
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
1098
861
|
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1099
862
|
await refreshBlueprintLibrary();
|
|
1100
863
|
toast({ title: messages.blueprintSynced(record.name) });
|
|
@@ -1105,6 +868,7 @@ function ReactViewPanelInner({
|
|
|
1105
868
|
blueprintMeta,
|
|
1106
869
|
refreshBlueprintLibrary,
|
|
1107
870
|
t,
|
|
871
|
+
nameSpace,
|
|
1108
872
|
]);
|
|
1109
873
|
|
|
1110
874
|
const saveBlueprintToLibrary = useCallback(
|
|
@@ -1115,7 +879,7 @@ function ReactViewPanelInner({
|
|
|
1115
879
|
let recordId = activeBlueprintLibraryId ?? undefined;
|
|
1116
880
|
let createdAt: number | undefined;
|
|
1117
881
|
if (activeBlueprintLibraryId) {
|
|
1118
|
-
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
882
|
+
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId, nameSpace);
|
|
1119
883
|
if (existing?.source === "saved") {
|
|
1120
884
|
recordId = existing.id;
|
|
1121
885
|
createdAt = existing.createdAt;
|
|
@@ -1133,14 +897,14 @@ function ReactViewPanelInner({
|
|
|
1133
897
|
meta,
|
|
1134
898
|
source: "saved",
|
|
1135
899
|
});
|
|
1136
|
-
await putBlueprintLibraryRecord(record);
|
|
900
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
1137
901
|
setActiveBlueprintLibraryId(record.id);
|
|
1138
902
|
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1139
903
|
await refreshBlueprintLibrary();
|
|
1140
904
|
toast({ title: t("panel.messages.blueprintSavedLocal") });
|
|
1141
905
|
});
|
|
1142
906
|
},
|
|
1143
|
-
[activeBlueprintLibraryId, blueprintGraph.document, refreshBlueprintLibrary, t]
|
|
907
|
+
[activeBlueprintLibraryId, blueprintGraph.document, nameSpace, refreshBlueprintLibrary, t]
|
|
1144
908
|
);
|
|
1145
909
|
|
|
1146
910
|
const openBlueprintMetaDialog = useCallback((mode: "export" | "save") => {
|
|
@@ -1167,7 +931,7 @@ function ReactViewPanelInner({
|
|
|
1167
931
|
|
|
1168
932
|
const handleRenameBlueprintLibraryItem = useCallback(
|
|
1169
933
|
async (id: string, name: string) => {
|
|
1170
|
-
const updated = await updateBlueprintLibraryMeta(id, { name });
|
|
934
|
+
const updated = await updateBlueprintLibraryMeta(id, { name }, nameSpace);
|
|
1171
935
|
if (!updated) {
|
|
1172
936
|
toast({ title: t("panel.messages.renameFailed") });
|
|
1173
937
|
void refreshBlueprintLibrary();
|
|
@@ -1179,12 +943,12 @@ function ReactViewPanelInner({
|
|
|
1179
943
|
}
|
|
1180
944
|
toast({ title: t("panel.messages.blueprintRenamed") });
|
|
1181
945
|
},
|
|
1182
|
-
[activeBlueprintLibraryId, refreshBlueprintLibrary]
|
|
946
|
+
[activeBlueprintLibraryId, nameSpace, refreshBlueprintLibrary]
|
|
1183
947
|
);
|
|
1184
948
|
|
|
1185
949
|
const handleDeleteBlueprintLibraryItem = useCallback(
|
|
1186
950
|
async (id: string) => {
|
|
1187
|
-
await deleteBlueprintLibraryRecord(id);
|
|
951
|
+
await deleteBlueprintLibraryRecord(id, nameSpace);
|
|
1188
952
|
await refreshBlueprintLibrary();
|
|
1189
953
|
if (activeBlueprintLibraryId === id) {
|
|
1190
954
|
setActiveBlueprintLibraryId(null);
|
|
@@ -1192,7 +956,7 @@ function ReactViewPanelInner({
|
|
|
1192
956
|
}
|
|
1193
957
|
toast({ title: t("panel.messages.blueprintDeletedFromLibrary") });
|
|
1194
958
|
},
|
|
1195
|
-
[activeBlueprintLibraryId, refreshBlueprintLibrary]
|
|
959
|
+
[activeBlueprintLibraryId, nameSpace, refreshBlueprintLibrary]
|
|
1196
960
|
);
|
|
1197
961
|
|
|
1198
962
|
const openBlueprintExportDialog = useCallback(() => {
|
|
@@ -1207,7 +971,7 @@ function ReactViewPanelInner({
|
|
|
1207
971
|
const text = await file.text();
|
|
1208
972
|
const payload = parseBlueprintImportFile(await parseJsonText(text));
|
|
1209
973
|
const record = libraryRecordFromImport(payload);
|
|
1210
|
-
await putBlueprintLibraryRecord(record);
|
|
974
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
1211
975
|
await refreshBlueprintLibrary();
|
|
1212
976
|
if (!activeBlueprintLibraryId) {
|
|
1213
977
|
snapshotWorkspaceBlueprint();
|
|
@@ -1224,6 +988,7 @@ function ReactViewPanelInner({
|
|
|
1224
988
|
[
|
|
1225
989
|
activeBlueprintLibraryId,
|
|
1226
990
|
loadBlueprintFromLibrary,
|
|
991
|
+
nameSpace,
|
|
1227
992
|
refreshBlueprintLibrary,
|
|
1228
993
|
snapshotWorkspaceBlueprint,
|
|
1229
994
|
t,
|
|
@@ -1267,6 +1032,7 @@ function ReactViewPanelInner({
|
|
|
1267
1032
|
panelRevision: `${historyCursor}|${allElements.length}`,
|
|
1268
1033
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
1269
1034
|
initialWorkspace,
|
|
1035
|
+
nameSpace,
|
|
1270
1036
|
});
|
|
1271
1037
|
|
|
1272
1038
|
useEffect(() => {
|
|
@@ -1276,11 +1042,11 @@ function ReactViewPanelInner({
|
|
|
1276
1042
|
allElements,
|
|
1277
1043
|
layers,
|
|
1278
1044
|
activeLayerId,
|
|
1279
|
-
outputScale: readOutputScale(),
|
|
1045
|
+
outputScale: readOutputScale(nameSpace),
|
|
1280
1046
|
});
|
|
1281
1047
|
});
|
|
1282
1048
|
return () => registerPreviewSnapshotProvider(null);
|
|
1283
|
-
}, [activeLayerId, allElements, canvasEl, layers]);
|
|
1049
|
+
}, [activeLayerId, allElements, canvasEl, layers, nameSpace]);
|
|
1284
1050
|
|
|
1285
1051
|
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
1286
1052
|
graph: blueprintGraph,
|
|
@@ -2069,84 +1835,32 @@ function ReactViewPanelInner({
|
|
|
2069
1835
|
}
|
|
2070
1836
|
`}</style>
|
|
2071
1837
|
<nav className="panel-font-root relative z-30 flex items-center gap-2 border-b border-border bg-background/95 px-2 text-foreground">
|
|
2072
|
-
<
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
<MenubarSeparator />
|
|
2099
|
-
<MenubarItem
|
|
2100
|
-
onClick={() => {
|
|
2101
|
-
applyTheme(!isDark);
|
|
2102
|
-
}}
|
|
2103
|
-
>
|
|
2104
|
-
{isDark ? t("panel.menubar.switchToLight") : t("panel.menubar.switchToDark")}
|
|
2105
|
-
</MenubarItem>
|
|
2106
|
-
</MenubarContent>
|
|
2107
|
-
</MenubarMenu>
|
|
2108
|
-
<MenubarMenu>
|
|
2109
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.blueprint")}</MenubarTrigger>
|
|
2110
|
-
<MenubarContent className="z-[10100]">
|
|
2111
|
-
<MenubarItem onClick={openBlueprintExportDialog}>{t("panel.menubar.export")}</MenubarItem>
|
|
2112
|
-
<MenubarItem onClick={() => blueprintImportInputRef.current?.click()}>
|
|
2113
|
-
{t("panel.menubar.import")}
|
|
2114
|
-
</MenubarItem>
|
|
2115
|
-
</MenubarContent>
|
|
2116
|
-
</MenubarMenu>
|
|
2117
|
-
<MenubarMenu>
|
|
2118
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.settings")}</MenubarTrigger>
|
|
2119
|
-
<MenubarContent className="z-[10100]">
|
|
2120
|
-
<MenubarRadioGroup
|
|
2121
|
-
value={panelFontSize}
|
|
2122
|
-
onValueChange={(value) => setPanelFontSize(value as "sm" | "md" | "lg")}
|
|
2123
|
-
>
|
|
2124
|
-
<MenubarRadioItem value="sm">{t("panel.menubar.fontSmall")}</MenubarRadioItem>
|
|
2125
|
-
<MenubarRadioItem value="md">{t("panel.menubar.fontMedium")}</MenubarRadioItem>
|
|
2126
|
-
<MenubarRadioItem value="lg">{t("panel.menubar.fontLarge")}</MenubarRadioItem>
|
|
2127
|
-
</MenubarRadioGroup>
|
|
2128
|
-
<MenubarSeparator />
|
|
2129
|
-
<div className="px-2 py-1.5 text-[11px] text-muted-foreground">
|
|
2130
|
-
{t("panel.theme.language")}
|
|
2131
|
-
</div>
|
|
2132
|
-
<MenubarRadioGroup
|
|
2133
|
-
value={locale}
|
|
2134
|
-
onValueChange={(value) => setLocale(value as "zh-CN" | "en-US")}
|
|
2135
|
-
>
|
|
2136
|
-
<MenubarRadioItem value="zh-CN">{t("panel.theme.zhCN")}</MenubarRadioItem>
|
|
2137
|
-
<MenubarRadioItem value="en-US">{t("panel.theme.enUS")}</MenubarRadioItem>
|
|
2138
|
-
</MenubarRadioGroup>
|
|
2139
|
-
<MenubarSeparator />
|
|
2140
|
-
<MenubarCheckboxItem
|
|
2141
|
-
checked={outputScale}
|
|
2142
|
-
onCheckedChange={(checked) => setOutputScale(checked === true)}
|
|
2143
|
-
title={t("panel.menubar.outputScaleHint")}
|
|
2144
|
-
>
|
|
2145
|
-
{t("panel.menubar.outputScale")}
|
|
2146
|
-
</MenubarCheckboxItem>
|
|
2147
|
-
</MenubarContent>
|
|
2148
|
-
</MenubarMenu>
|
|
2149
|
-
</Menubar>
|
|
1838
|
+
<PanelMenubar
|
|
1839
|
+
t={t}
|
|
1840
|
+
handleExport={handleExport}
|
|
1841
|
+
importInputRef={importInputRef}
|
|
1842
|
+
blueprintImportInputRef={blueprintImportInputRef}
|
|
1843
|
+
canUndo={canUndo}
|
|
1844
|
+
canRedo={canRedo}
|
|
1845
|
+
undo={undo}
|
|
1846
|
+
redo={redo}
|
|
1847
|
+
hasUnlockedSelection={hasUnlockedSelection}
|
|
1848
|
+
selectedIds={selectedIds}
|
|
1849
|
+
bringElementsForward={bringElementsForward}
|
|
1850
|
+
sendElementsBackward={sendElementsBackward}
|
|
1851
|
+
bringElementsToFront={bringElementsToFront}
|
|
1852
|
+
sendElementsToBack={sendElementsToBack}
|
|
1853
|
+
adjustUniformZoom={adjustUniformZoom}
|
|
1854
|
+
applyTheme={applyTheme}
|
|
1855
|
+
isDark={isDark}
|
|
1856
|
+
openBlueprintExportDialog={openBlueprintExportDialog}
|
|
1857
|
+
panelFontSize={panelFontSize}
|
|
1858
|
+
setPanelFontSize={setPanelFontSize}
|
|
1859
|
+
locale={locale}
|
|
1860
|
+
setLocale={setLocale}
|
|
1861
|
+
outputScale={outputScale}
|
|
1862
|
+
setOutputScale={setOutputScale}
|
|
1863
|
+
/>
|
|
2150
1864
|
<WorkspaceProjectNav
|
|
2151
1865
|
projects={workspaceProjects.projects}
|
|
2152
1866
|
activeProjectId={workspaceProjects.activeProjectId}
|
|
@@ -2351,122 +2065,19 @@ function ReactViewPanelInner({
|
|
|
2351
2065
|
</TooltipTrigger>
|
|
2352
2066
|
<TooltipContent className="z-[10000]">{t("panel.menubar.redoShortcut")}</TooltipContent>
|
|
2353
2067
|
</Tooltip>
|
|
2354
|
-
<
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
</button>
|
|
2368
|
-
{isHistoryPanelExpanded ? (
|
|
2369
|
-
<div
|
|
2370
|
-
className="absolute left-1/2 top-[calc(100%+6px)] w-[280px] -translate-x-1/2 rounded-lg border border-border bg-card/95 p-2 shadow-lg backdrop-blur"
|
|
2371
|
-
style={{ zIndex: PANEL_Z_INDEX.historyPopover }}
|
|
2372
|
-
>
|
|
2373
|
-
<div className="mb-1 flex items-center justify-between">
|
|
2374
|
-
<span className="text-[11px] font-semibold text-muted-foreground">{t("panel.history.title")}</span>
|
|
2375
|
-
<span className="text-[11px] text-muted-foreground">
|
|
2376
|
-
{history.length > 0 ? `${historyCursor + 1}/${history.length}` : "0/0"}
|
|
2377
|
-
</span>
|
|
2378
|
-
</div>
|
|
2379
|
-
<Input
|
|
2380
|
-
value={historyKeyword}
|
|
2381
|
-
onChange={(e) => setHistoryKeyword(e.target.value)}
|
|
2382
|
-
placeholder={t("panel.history.searchPlaceholder")}
|
|
2383
|
-
className="mb-2 h-7 text-xs"
|
|
2384
|
-
/>
|
|
2385
|
-
<div
|
|
2386
|
-
className={`max-h-52 space-y-1 overflow-auto pr-1 text-[11px] ${themedScrollbarClass}`}
|
|
2387
|
-
>
|
|
2388
|
-
{history.length === 0 ? (
|
|
2389
|
-
<Empty className="py-4">
|
|
2390
|
-
<EmptyIcon className="h-8 w-8">
|
|
2391
|
-
<IconHistory />
|
|
2392
|
-
</EmptyIcon>
|
|
2393
|
-
<EmptyTitle className="text-xs">{t("panel.history.emptyTitle")}</EmptyTitle>
|
|
2394
|
-
<EmptyDescription className="text-[11px]">
|
|
2395
|
-
{t("panel.history.emptyDesc")}
|
|
2396
|
-
</EmptyDescription>
|
|
2397
|
-
</Empty>
|
|
2398
|
-
) : (
|
|
2399
|
-
history
|
|
2400
|
-
.slice(Math.max(0, history.length - 20))
|
|
2401
|
-
.reverse()
|
|
2402
|
-
.filter((item) =>
|
|
2403
|
-
!normalizedHistoryKeyword
|
|
2404
|
-
? true
|
|
2405
|
-
: item.label.toLowerCase().includes(normalizedHistoryKeyword)
|
|
2406
|
-
)
|
|
2407
|
-
.map((item) => (
|
|
2408
|
-
<button
|
|
2409
|
-
key={`${item.index}-${item.timestamp}`}
|
|
2410
|
-
type="button"
|
|
2411
|
-
className={[
|
|
2412
|
-
"w-full rounded border px-2 py-1 text-left transition-colors",
|
|
2413
|
-
item.active
|
|
2414
|
-
? "border-primary/50 bg-primary/10 text-foreground"
|
|
2415
|
-
: "border-border/60 bg-background/70 text-muted-foreground hover:bg-accent/50",
|
|
2416
|
-
].join(" ")}
|
|
2417
|
-
title={new Date(item.timestamp).toLocaleString()}
|
|
2418
|
-
onClick={() => {
|
|
2419
|
-
if (item.active) return;
|
|
2420
|
-
goToHistory(item.index);
|
|
2421
|
-
}}
|
|
2422
|
-
>
|
|
2423
|
-
<div className="flex items-center justify-between gap-2">
|
|
2424
|
-
<span className="truncate">{item.label}</span>
|
|
2425
|
-
<span className="shrink-0 text-[10px] text-muted-foreground">
|
|
2426
|
-
{formatRelativeTime(item.timestamp, historyNow, t)}
|
|
2427
|
-
</span>
|
|
2428
|
-
</div>
|
|
2429
|
-
</button>
|
|
2430
|
-
))
|
|
2431
|
-
)}
|
|
2432
|
-
{history.length > 0 &&
|
|
2433
|
-
history
|
|
2434
|
-
.slice(Math.max(0, history.length - 20))
|
|
2435
|
-
.reverse()
|
|
2436
|
-
.filter((item) =>
|
|
2437
|
-
!normalizedHistoryKeyword
|
|
2438
|
-
? true
|
|
2439
|
-
: item.label.toLowerCase().includes(normalizedHistoryKeyword)
|
|
2440
|
-
).length === 0 ? (
|
|
2441
|
-
<Empty className="py-4">
|
|
2442
|
-
<EmptyIcon className="h-8 w-8">
|
|
2443
|
-
<svg
|
|
2444
|
-
viewBox="0 0 24 24"
|
|
2445
|
-
className="h-4 w-4"
|
|
2446
|
-
fill="none"
|
|
2447
|
-
stroke="currentColor"
|
|
2448
|
-
strokeWidth="1.8"
|
|
2449
|
-
aria-hidden="true"
|
|
2450
|
-
>
|
|
2451
|
-
<circle cx="11" cy="11" r="7" />
|
|
2452
|
-
<path d="m20 20-3.5-3.5" />
|
|
2453
|
-
</svg>
|
|
2454
|
-
</EmptyIcon>
|
|
2455
|
-
<EmptyTitle className="text-xs">{t("panel.history.noMatchTitle")}</EmptyTitle>
|
|
2456
|
-
<EmptyDescription className="text-[11px]">
|
|
2457
|
-
{t("panel.history.noMatchDesc")}
|
|
2458
|
-
</EmptyDescription>
|
|
2459
|
-
</Empty>
|
|
2460
|
-
) : null}
|
|
2461
|
-
</div>
|
|
2462
|
-
</div>
|
|
2463
|
-
) : null}
|
|
2464
|
-
</div>
|
|
2465
|
-
</TooltipTrigger>
|
|
2466
|
-
<TooltipContent className="z-[10000]">
|
|
2467
|
-
{isHistoryPanelExpanded ? t("panel.history.collapse") : t("panel.history.expand")}
|
|
2468
|
-
</TooltipContent>
|
|
2469
|
-
</Tooltip>
|
|
2068
|
+
<PanelHistoryPopover
|
|
2069
|
+
t={t}
|
|
2070
|
+
isHistoryPanelExpanded={isHistoryPanelExpanded}
|
|
2071
|
+
setIsHistoryPanelExpanded={setIsHistoryPanelExpanded}
|
|
2072
|
+
history={history}
|
|
2073
|
+
historyCursor={historyCursor}
|
|
2074
|
+
historyKeyword={historyKeyword}
|
|
2075
|
+
setHistoryKeyword={setHistoryKeyword}
|
|
2076
|
+
normalizedHistoryKeyword={normalizedHistoryKeyword}
|
|
2077
|
+
goToHistory={goToHistory}
|
|
2078
|
+
historyNow={historyNow}
|
|
2079
|
+
themedScrollbarClass={themedScrollbarClass}
|
|
2080
|
+
/>
|
|
2470
2081
|
</TooltipProvider>
|
|
2471
2082
|
<DropdownMenu>
|
|
2472
2083
|
<DropdownMenuTrigger asChild>
|
|
@@ -2982,328 +2593,36 @@ function ReactViewPanelInner({
|
|
|
2982
2593
|
}
|
|
2983
2594
|
/>
|
|
2984
2595
|
|
|
2985
|
-
<
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
</TooltipTrigger>
|
|
3016
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.add")}</TooltipContent>
|
|
3017
|
-
</Tooltip>
|
|
3018
|
-
<Tooltip>
|
|
3019
|
-
<TooltipTrigger asChild>
|
|
3020
|
-
<button
|
|
3021
|
-
type="button"
|
|
3022
|
-
onClick={() => {
|
|
3023
|
-
if (!canMergeLayers) return;
|
|
3024
|
-
setIsMergingLayers(true);
|
|
3025
|
-
}}
|
|
3026
|
-
aria-label={t("panel.layers.merge")}
|
|
3027
|
-
disabled={!canMergeLayers}
|
|
3028
|
-
className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
|
|
3029
|
-
>
|
|
3030
|
-
<IconMerge />
|
|
3031
|
-
</button>
|
|
3032
|
-
</TooltipTrigger>
|
|
3033
|
-
<TooltipContent className="z-[10000]">
|
|
3034
|
-
{canMergeLayers ? t("panel.layers.merge") : t("panel.layers.mergeNeedTwo")}
|
|
3035
|
-
</TooltipContent>
|
|
3036
|
-
</Tooltip>
|
|
3037
|
-
</div>
|
|
3038
|
-
|
|
3039
|
-
<TabsList
|
|
3040
|
-
className={`h-auto w-full justify-start gap-1 overflow-x-auto bg-muted/40 p-1 ${themedScrollbarClass}`}
|
|
3041
|
-
>
|
|
3042
|
-
{layers.map((layer) => (
|
|
3043
|
-
<TabsTrigger
|
|
3044
|
-
key={layer.id}
|
|
3045
|
-
value={layer.id}
|
|
3046
|
-
className="flex min-w-[120px] items-center gap-1 px-2 py-1 text-xs"
|
|
3047
|
-
>
|
|
3048
|
-
<span className="truncate">{layer.name}</span>
|
|
3049
|
-
{layer.isMapping ? (
|
|
3050
|
-
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
3051
|
-
{t("panel.layers.mapping")}
|
|
3052
|
-
</span>
|
|
3053
|
-
) : null}
|
|
3054
|
-
{layer.isPrimary ? (
|
|
3055
|
-
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
3056
|
-
{t("panel.layers.primary")}
|
|
3057
|
-
</span>
|
|
3058
|
-
) : null}
|
|
3059
|
-
{layer.locked ? <IconLock locked /> : null}
|
|
3060
|
-
</TabsTrigger>
|
|
3061
|
-
))}
|
|
3062
|
-
</TabsList>
|
|
3063
|
-
|
|
3064
|
-
<TabsContent value={activeLayerId} className="mt-2 space-y-2">
|
|
3065
|
-
{activeLayer ? (
|
|
3066
|
-
<div className="flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
3067
|
-
{editingLayerId === activeLayer.id ? (
|
|
3068
|
-
<Input
|
|
3069
|
-
value={editingLayerName}
|
|
3070
|
-
onChange={(e) => setEditingLayerName(e.target.value)}
|
|
3071
|
-
onKeyDown={(e) => {
|
|
3072
|
-
if (e.key === "Enter") {
|
|
3073
|
-
e.preventDefault();
|
|
3074
|
-
renameLayer(editingLayerId, editingLayerName);
|
|
3075
|
-
setEditingLayerId(null);
|
|
3076
|
-
setEditingLayerName("");
|
|
3077
|
-
return;
|
|
3078
|
-
}
|
|
3079
|
-
if (e.key === "Escape") {
|
|
3080
|
-
e.preventDefault();
|
|
3081
|
-
setEditingLayerId(null);
|
|
3082
|
-
setEditingLayerName("");
|
|
3083
|
-
}
|
|
3084
|
-
}}
|
|
3085
|
-
className="h-7 min-w-0 flex-1"
|
|
3086
|
-
placeholder={t("panel.layers.namePlaceholder")}
|
|
3087
|
-
autoFocus
|
|
3088
|
-
/>
|
|
3089
|
-
) : (
|
|
3090
|
-
<span className="truncate font-medium">{activeLayer.name}</span>
|
|
3091
|
-
)}
|
|
3092
|
-
{activeLayer.isMapping ? (
|
|
3093
|
-
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
3094
|
-
{t("panel.layers.mappingLayer")}
|
|
3095
|
-
</span>
|
|
3096
|
-
) : null}
|
|
3097
|
-
{activeLayer.isPrimary ? (
|
|
3098
|
-
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
3099
|
-
{t("panel.layers.primaryLayer")}
|
|
3100
|
-
</span>
|
|
3101
|
-
) : null}
|
|
3102
|
-
<div className="flex-1" />
|
|
3103
|
-
<Tooltip>
|
|
3104
|
-
<TooltipTrigger asChild>
|
|
3105
|
-
<button
|
|
3106
|
-
type="button"
|
|
3107
|
-
onClick={() => setPrimaryLayer(activeLayer.id)}
|
|
3108
|
-
disabled={activeLayer.isPrimary}
|
|
3109
|
-
aria-label={activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
3110
|
-
className={[
|
|
3111
|
-
"rounded border px-2 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-60",
|
|
3112
|
-
activeLayer.isPrimary
|
|
3113
|
-
? "border-emerald-500/40 bg-emerald-500/10 text-emerald-600"
|
|
3114
|
-
: "border-border hover:bg-accent",
|
|
3115
|
-
].join(" ")}
|
|
3116
|
-
>
|
|
3117
|
-
{activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
3118
|
-
</button>
|
|
3119
|
-
</TooltipTrigger>
|
|
3120
|
-
<TooltipContent className="z-[10000]">
|
|
3121
|
-
{activeLayer.isPrimary ? t("panel.layers.isPrimaryHint") : t("panel.layers.setPrimaryHint")}
|
|
3122
|
-
</TooltipContent>
|
|
3123
|
-
</Tooltip>
|
|
3124
|
-
<Tooltip>
|
|
3125
|
-
<TooltipTrigger asChild>
|
|
3126
|
-
<button
|
|
3127
|
-
type="button"
|
|
3128
|
-
onClick={() => toggleLayerLock(activeLayer.id)}
|
|
3129
|
-
disabled={!activeLayer.editable}
|
|
3130
|
-
aria-label={activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
3131
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
3132
|
-
>
|
|
3133
|
-
<IconLock locked={activeLayer.locked} />
|
|
3134
|
-
</button>
|
|
3135
|
-
</TooltipTrigger>
|
|
3136
|
-
<TooltipContent className="z-[10000]">
|
|
3137
|
-
{activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
3138
|
-
</TooltipContent>
|
|
3139
|
-
</Tooltip>
|
|
3140
|
-
<Tooltip>
|
|
3141
|
-
<TooltipTrigger asChild>
|
|
3142
|
-
{editingLayerId === activeLayer.id ? (
|
|
3143
|
-
<button
|
|
3144
|
-
type="button"
|
|
3145
|
-
onClick={() => {
|
|
3146
|
-
renameLayer(editingLayerId, editingLayerName);
|
|
3147
|
-
setEditingLayerId(null);
|
|
3148
|
-
setEditingLayerName("");
|
|
3149
|
-
}}
|
|
3150
|
-
aria-label={t("panel.layers.saveName")}
|
|
3151
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3152
|
-
>
|
|
3153
|
-
<IconCheck />
|
|
3154
|
-
</button>
|
|
3155
|
-
) : (
|
|
3156
|
-
<button
|
|
3157
|
-
type="button"
|
|
3158
|
-
onClick={() => {
|
|
3159
|
-
setEditingLayerId(activeLayer.id);
|
|
3160
|
-
setEditingLayerName(activeLayer.name);
|
|
3161
|
-
}}
|
|
3162
|
-
disabled={!activeLayer.editable}
|
|
3163
|
-
aria-label={t("panel.layers.rename")}
|
|
3164
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
3165
|
-
>
|
|
3166
|
-
<IconEdit />
|
|
3167
|
-
</button>
|
|
3168
|
-
)}
|
|
3169
|
-
</TooltipTrigger>
|
|
3170
|
-
<TooltipContent className="z-[10000]">
|
|
3171
|
-
{editingLayerId === activeLayer.id ? t("panel.layers.saveName") : t("panel.layers.rename")}
|
|
3172
|
-
</TooltipContent>
|
|
3173
|
-
</Tooltip>
|
|
3174
|
-
{editingLayerId === activeLayer.id ? (
|
|
3175
|
-
<Tooltip>
|
|
3176
|
-
<TooltipTrigger asChild>
|
|
3177
|
-
<button
|
|
3178
|
-
type="button"
|
|
3179
|
-
onClick={() => {
|
|
3180
|
-
setEditingLayerId(null);
|
|
3181
|
-
setEditingLayerName("");
|
|
3182
|
-
}}
|
|
3183
|
-
aria-label={t("panel.layers.cancelEditName")}
|
|
3184
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3185
|
-
>
|
|
3186
|
-
<IconClose />
|
|
3187
|
-
</button>
|
|
3188
|
-
</TooltipTrigger>
|
|
3189
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.cancelEdit")}</TooltipContent>
|
|
3190
|
-
</Tooltip>
|
|
3191
|
-
) : null}
|
|
3192
|
-
<Tooltip>
|
|
3193
|
-
<TooltipTrigger asChild>
|
|
3194
|
-
<button
|
|
3195
|
-
type="button"
|
|
3196
|
-
onClick={() => {
|
|
3197
|
-
const blockReason = getLayerDeleteBlockReason(activeLayer.id);
|
|
3198
|
-
if (blockReason) {
|
|
3199
|
-
showActionHint(blockReason);
|
|
3200
|
-
return;
|
|
3201
|
-
}
|
|
3202
|
-
setConfirmDeleteLayerId(activeLayer.id);
|
|
3203
|
-
const firstTarget = layers.find((l) => l.id !== activeLayer.id);
|
|
3204
|
-
setDeleteTargetLayerId(firstTarget?.id ?? "");
|
|
3205
|
-
setDeleteMode(activeLayer.isMapping ? "remove" : "move");
|
|
3206
|
-
}}
|
|
3207
|
-
disabled={!activeLayer.editable || activeLayer.locked}
|
|
3208
|
-
aria-label={
|
|
3209
|
-
!activeLayer.editable
|
|
3210
|
-
? t("panel.layers.cannotDeleteDefault")
|
|
3211
|
-
: activeLayer.locked
|
|
3212
|
-
? t("panel.layers.cannotDeleteLocked")
|
|
3213
|
-
: t("panel.layers.delete")
|
|
3214
|
-
}
|
|
3215
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
3216
|
-
>
|
|
3217
|
-
<IconTrash />
|
|
3218
|
-
</button>
|
|
3219
|
-
</TooltipTrigger>
|
|
3220
|
-
<TooltipContent className="z-[10000]">
|
|
3221
|
-
{!activeLayer.editable
|
|
3222
|
-
? t("panel.layers.cannotDeleteDefault")
|
|
3223
|
-
: activeLayer.locked
|
|
3224
|
-
? t("panel.layers.cannotDeleteLocked")
|
|
3225
|
-
: t("panel.layers.delete")}
|
|
3226
|
-
</TooltipContent>
|
|
3227
|
-
</Tooltip>
|
|
3228
|
-
</div>
|
|
3229
|
-
) : null}
|
|
3230
|
-
|
|
3231
|
-
{isLayerPanelExpanded ? (
|
|
3232
|
-
<div className="rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
3233
|
-
<div className="mb-1 text-muted-foreground">{t("panel.layers.selectMergeLayers")}</div>
|
|
3234
|
-
<div className="flex flex-wrap gap-2">
|
|
3235
|
-
{layers.map((layer) => (
|
|
3236
|
-
<label key={layer.id} className="flex items-center gap-1.5">
|
|
3237
|
-
<Checkbox
|
|
3238
|
-
checked={Boolean(layer.mergeSelected)}
|
|
3239
|
-
disabled={layer.isMapping}
|
|
3240
|
-
className="h-4 w-4 border-2 border-foreground/80 bg-background ring-1 ring-foreground/40 data-[state=checked]:border-primary data-[state=checked]:ring-primary/40"
|
|
3241
|
-
onCheckedChange={() => toggleLayerMergeSelected(layer.id)}
|
|
3242
|
-
/>
|
|
3243
|
-
<span
|
|
3244
|
-
className="max-w-[140px] truncate"
|
|
3245
|
-
title={layer.isMapping ? t("panel.layers.mappingCannotMerge") : layer.name}
|
|
3246
|
-
>
|
|
3247
|
-
{layer.name}
|
|
3248
|
-
{layer.isMapping ? t("panel.layers.cannotMergeSuffix") : ""}
|
|
3249
|
-
</span>
|
|
3250
|
-
</label>
|
|
3251
|
-
))}
|
|
3252
|
-
</div>
|
|
3253
|
-
</div>
|
|
3254
|
-
) : null}
|
|
3255
|
-
</TabsContent>
|
|
3256
|
-
</Tabs>
|
|
3257
|
-
|
|
3258
|
-
{isLayerPanelExpanded && isMergingLayers ? (
|
|
3259
|
-
<div className="mt-2 flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
3260
|
-
<span className="text-muted-foreground">{t("panel.layers.mergedName")}</span>
|
|
3261
|
-
<Input
|
|
3262
|
-
value={mergeLayerName}
|
|
3263
|
-
onChange={(e) => setMergeLayerName(e.target.value)}
|
|
3264
|
-
className="h-7 min-w-0 flex-1"
|
|
3265
|
-
placeholder={t("panel.layers.mergedNamePlaceholder")}
|
|
3266
|
-
autoFocus
|
|
3267
|
-
/>
|
|
3268
|
-
<Tooltip>
|
|
3269
|
-
<TooltipTrigger asChild>
|
|
3270
|
-
<button
|
|
3271
|
-
type="button"
|
|
3272
|
-
onClick={() => {
|
|
3273
|
-
if (!canMergeLayers) return;
|
|
3274
|
-
mergeSelectedLayers(mergeLayerName || undefined);
|
|
3275
|
-
setIsMergingLayers(false);
|
|
3276
|
-
setMergeLayerName("");
|
|
3277
|
-
}}
|
|
3278
|
-
aria-label={t("panel.layers.confirmMergeAria")}
|
|
3279
|
-
disabled={!canMergeLayers}
|
|
3280
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3281
|
-
>
|
|
3282
|
-
<IconCheck />
|
|
3283
|
-
</button>
|
|
3284
|
-
</TooltipTrigger>
|
|
3285
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.confirmMerge")}</TooltipContent>
|
|
3286
|
-
</Tooltip>
|
|
3287
|
-
<Tooltip>
|
|
3288
|
-
<TooltipTrigger asChild>
|
|
3289
|
-
<button
|
|
3290
|
-
type="button"
|
|
3291
|
-
onClick={() => {
|
|
3292
|
-
setIsMergingLayers(false);
|
|
3293
|
-
setMergeLayerName("");
|
|
3294
|
-
}}
|
|
3295
|
-
aria-label={t("panel.layers.cancelMergeAria")}
|
|
3296
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3297
|
-
>
|
|
3298
|
-
<IconClose />
|
|
3299
|
-
</button>
|
|
3300
|
-
</TooltipTrigger>
|
|
3301
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.cancelMerge")}</TooltipContent>
|
|
3302
|
-
</Tooltip>
|
|
3303
|
-
</div>
|
|
3304
|
-
) : null}
|
|
3305
|
-
</TooltipProvider>
|
|
3306
|
-
</div>
|
|
2596
|
+
<PanelLayerDock
|
|
2597
|
+
t={t}
|
|
2598
|
+
isLayerPanelExpanded={isLayerPanelExpanded}
|
|
2599
|
+
setIsLayerPanelExpanded={setIsLayerPanelExpanded}
|
|
2600
|
+
addLayer={addLayer}
|
|
2601
|
+
canMergeLayers={canMergeLayers}
|
|
2602
|
+
setIsMergingLayers={setIsMergingLayers}
|
|
2603
|
+
isMergingLayers={isMergingLayers}
|
|
2604
|
+
layers={layers}
|
|
2605
|
+
activeLayerId={activeLayerId}
|
|
2606
|
+
setActiveLayer={setActiveLayer}
|
|
2607
|
+
themedScrollbarClass={themedScrollbarClass}
|
|
2608
|
+
activeLayer={activeLayer}
|
|
2609
|
+
editingLayerId={editingLayerId}
|
|
2610
|
+
setEditingLayerId={setEditingLayerId}
|
|
2611
|
+
editingLayerName={editingLayerName}
|
|
2612
|
+
setEditingLayerName={setEditingLayerName}
|
|
2613
|
+
renameLayer={renameLayer}
|
|
2614
|
+
setPrimaryLayer={setPrimaryLayer}
|
|
2615
|
+
toggleLayerLock={toggleLayerLock}
|
|
2616
|
+
getLayerDeleteBlockReason={getLayerDeleteBlockReason}
|
|
2617
|
+
showActionHint={showActionHint}
|
|
2618
|
+
setConfirmDeleteLayerId={setConfirmDeleteLayerId}
|
|
2619
|
+
setDeleteTargetLayerId={setDeleteTargetLayerId}
|
|
2620
|
+
setDeleteMode={setDeleteMode}
|
|
2621
|
+
toggleLayerMergeSelected={toggleLayerMergeSelected}
|
|
2622
|
+
mergeLayerName={mergeLayerName}
|
|
2623
|
+
setMergeLayerName={setMergeLayerName}
|
|
2624
|
+
mergeSelectedLayers={mergeSelectedLayers}
|
|
2625
|
+
/>
|
|
3307
2626
|
</div>
|
|
3308
2627
|
</div>
|
|
3309
2628
|
</ResizablePanel>
|
|
@@ -3367,199 +2686,27 @@ function ReactViewPanelInner({
|
|
|
3367
2686
|
open={assistantOpen}
|
|
3368
2687
|
onOpenChange={setAssistantOpen}
|
|
3369
2688
|
/>
|
|
3370
|
-
<
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
}
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
mappingDeleteProceedRef.current = null;
|
|
3392
|
-
setMappingDeleteConfirmOpen(false);
|
|
3393
|
-
fn?.();
|
|
3394
|
-
}}
|
|
3395
|
-
>
|
|
3396
|
-
{t("panel.layers.continueDelete")}
|
|
3397
|
-
</AlertDialogAction>
|
|
3398
|
-
</AlertDialogFooter>
|
|
3399
|
-
</AlertDialogContent>
|
|
3400
|
-
</AlertDialog>
|
|
3401
|
-
<Dialog
|
|
3402
|
-
open={Boolean(deletingLayer)}
|
|
3403
|
-
onOpenChange={(open) => {
|
|
3404
|
-
if (open) return;
|
|
3405
|
-
setConfirmDeleteLayerId(null);
|
|
3406
|
-
setDeleteMode("move");
|
|
3407
|
-
setDeleteTargetLayerId("");
|
|
3408
|
-
}}
|
|
3409
|
-
>
|
|
3410
|
-
<DialogContent
|
|
3411
|
-
className="sm:max-w-[520px]"
|
|
3412
|
-
overlayClassName="bg-transparent pointer-events-none"
|
|
3413
|
-
>
|
|
3414
|
-
<DialogHeader>
|
|
3415
|
-
<DialogTitle>{t("panel.layers.confirmDeleteLayerTitle")}</DialogTitle>
|
|
3416
|
-
<DialogDescription>
|
|
3417
|
-
{t("panel.layers.aboutToDeleteLayer", {
|
|
3418
|
-
name: deletingLayer?.name ?? "-",
|
|
3419
|
-
})}
|
|
3420
|
-
</DialogDescription>
|
|
3421
|
-
</DialogHeader>
|
|
3422
|
-
{deletingLayer ? (
|
|
3423
|
-
<RadioGroup
|
|
3424
|
-
value={deletingLayerMode}
|
|
3425
|
-
onValueChange={(value) => setDeleteMode(value as "move" | "remove")}
|
|
3426
|
-
className="space-y-2 text-sm"
|
|
3427
|
-
>
|
|
3428
|
-
{!deletingLayer.isMapping ? (
|
|
3429
|
-
<div className="flex items-center gap-3">
|
|
3430
|
-
<label className="flex items-center gap-1.5">
|
|
3431
|
-
<RadioGroupItem value="move" />
|
|
3432
|
-
{t("panel.layers.migrateNodesTo")}
|
|
3433
|
-
</label>
|
|
3434
|
-
<Select
|
|
3435
|
-
value={deleteTargetLayerId || "__none__"}
|
|
3436
|
-
onValueChange={(value) =>
|
|
3437
|
-
setDeleteTargetLayerId(value === "__none__" ? "" : value)
|
|
3438
|
-
}
|
|
3439
|
-
disabled={deletingLayerMode !== "move" || deleteTargetCandidates.length === 0}
|
|
3440
|
-
>
|
|
3441
|
-
<SelectTrigger className="h-8 w-[220px]">
|
|
3442
|
-
<SelectValue placeholder={t("panel.layers.selectTargetLayer")} />
|
|
3443
|
-
</SelectTrigger>
|
|
3444
|
-
<SelectContent>
|
|
3445
|
-
<SelectItem value="__none__">{t("panel.layers.pleaseSelectLayer")}</SelectItem>
|
|
3446
|
-
{deleteTargetCandidates.map((l) => (
|
|
3447
|
-
<SelectItem key={l.id} value={l.id}>
|
|
3448
|
-
{l.name}
|
|
3449
|
-
</SelectItem>
|
|
3450
|
-
))}
|
|
3451
|
-
</SelectContent>
|
|
3452
|
-
</Select>
|
|
3453
|
-
</div>
|
|
3454
|
-
) : null}
|
|
3455
|
-
<label className="flex items-center gap-1.5">
|
|
3456
|
-
<RadioGroupItem value="remove" />
|
|
3457
|
-
{deletingLayer.isMapping ? t("panel.layers.deleteMappingWithNodes") : t("panel.layers.deleteAllNodesToo")}
|
|
3458
|
-
</label>
|
|
3459
|
-
</RadioGroup>
|
|
3460
|
-
) : null}
|
|
3461
|
-
<DialogFooter>
|
|
3462
|
-
<button
|
|
3463
|
-
type="button"
|
|
3464
|
-
onClick={() => {
|
|
3465
|
-
setConfirmDeleteLayerId(null);
|
|
3466
|
-
setDeleteMode("move");
|
|
3467
|
-
setDeleteTargetLayerId("");
|
|
3468
|
-
}}
|
|
3469
|
-
className="rounded border border-border px-3 py-1.5 text-sm hover:bg-accent"
|
|
3470
|
-
>
|
|
3471
|
-
{t("common.cancel")}
|
|
3472
|
-
</button>
|
|
3473
|
-
<button
|
|
3474
|
-
type="button"
|
|
3475
|
-
onClick={() => {
|
|
3476
|
-
if (!deletingLayer) return;
|
|
3477
|
-
const result = deleteLayer(deletingLayer.id, {
|
|
3478
|
-
mode: deletingLayerMode,
|
|
3479
|
-
targetLayerId: deleteTargetLayerId || undefined,
|
|
3480
|
-
});
|
|
3481
|
-
if (!result.ok) {
|
|
3482
|
-
showActionHint(result.reason);
|
|
3483
|
-
return;
|
|
3484
|
-
}
|
|
3485
|
-
setConfirmDeleteLayerId(null);
|
|
3486
|
-
setDeleteMode("move");
|
|
3487
|
-
setDeleteTargetLayerId("");
|
|
3488
|
-
}}
|
|
3489
|
-
className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90"
|
|
3490
|
-
>
|
|
3491
|
-
{t("panel.layers.confirmDeleteTitle")}
|
|
3492
|
-
</button>
|
|
3493
|
-
</DialogFooter>
|
|
3494
|
-
</DialogContent>
|
|
3495
|
-
</Dialog>
|
|
3496
|
-
<Dialog
|
|
3497
|
-
open={titleIconPreviewOpen}
|
|
3498
|
-
onOpenChange={setTitleIconPreviewOpen}
|
|
3499
|
-
>
|
|
3500
|
-
<DialogContent
|
|
3501
|
-
className="sm:max-w-[860px]"
|
|
3502
|
-
overlayClassName="bg-transparent pointer-events-none"
|
|
3503
|
-
>
|
|
3504
|
-
<DialogHeader>
|
|
3505
|
-
<DialogTitle>{t("panel.menubar.titleIconPreviewTitle")}</DialogTitle>
|
|
3506
|
-
<DialogDescription>{t("panel.menubar.titleIconPreviewDesc")}</DialogDescription>
|
|
3507
|
-
</DialogHeader>
|
|
3508
|
-
<div className="flex items-center justify-end gap-2">
|
|
3509
|
-
<button
|
|
3510
|
-
type="button"
|
|
3511
|
-
onClick={() => setTitleIconZoom((z) => Math.max(0.5, Number((z - 0.1).toFixed(2))))}
|
|
3512
|
-
className="rounded border border-border px-2 py-1 text-xs hover:bg-accent"
|
|
3513
|
-
>
|
|
3514
|
-
-
|
|
3515
|
-
</button>
|
|
3516
|
-
<span className="w-14 text-center text-xs text-muted-foreground">
|
|
3517
|
-
{(titleIconZoom * 100).toFixed(0)}%
|
|
3518
|
-
</span>
|
|
3519
|
-
<button
|
|
3520
|
-
type="button"
|
|
3521
|
-
onClick={() => setTitleIconZoom((z) => Math.min(8, Number((z + 0.1).toFixed(2))))}
|
|
3522
|
-
className="rounded border border-border px-2 py-1 text-xs hover:bg-accent"
|
|
3523
|
-
>
|
|
3524
|
-
+
|
|
3525
|
-
</button>
|
|
3526
|
-
<button
|
|
3527
|
-
type="button"
|
|
3528
|
-
onClick={() => setTitleIconZoom(1.6)}
|
|
3529
|
-
className="rounded border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
|
|
3530
|
-
>
|
|
3531
|
-
{t("common.reset")}
|
|
3532
|
-
</button>
|
|
3533
|
-
</div>
|
|
3534
|
-
{titleIconDataUrl ? (
|
|
3535
|
-
<div
|
|
3536
|
-
className="max-h-[78vh] overflow-auto rounded border border-border bg-muted/20 p-4"
|
|
3537
|
-
onWheel={(e) => {
|
|
3538
|
-
e.preventDefault();
|
|
3539
|
-
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
3540
|
-
setTitleIconZoom((z) => {
|
|
3541
|
-
const next = Number((z + delta).toFixed(2));
|
|
3542
|
-
return Math.min(8, Math.max(0.5, next));
|
|
3543
|
-
});
|
|
3544
|
-
}}
|
|
3545
|
-
>
|
|
3546
|
-
<img
|
|
3547
|
-
src={titleIconDataUrl}
|
|
3548
|
-
alt={t("panel.menubar.titleIconPreviewAlt")}
|
|
3549
|
-
className="mx-auto rounded border border-border/70 object-contain"
|
|
3550
|
-
style={{
|
|
3551
|
-
width: 200,
|
|
3552
|
-
height: 200,
|
|
3553
|
-
maxWidth: "none",
|
|
3554
|
-
maxHeight: "none",
|
|
3555
|
-
transform: `scale(${titleIconZoom})`,
|
|
3556
|
-
transformOrigin: "center center",
|
|
3557
|
-
}}
|
|
3558
|
-
/>
|
|
3559
|
-
</div>
|
|
3560
|
-
) : null}
|
|
3561
|
-
</DialogContent>
|
|
3562
|
-
</Dialog>
|
|
2689
|
+
<PanelWorkspaceDialogs
|
|
2690
|
+
t={t}
|
|
2691
|
+
mappingDeleteConfirmOpen={mappingDeleteConfirmOpen}
|
|
2692
|
+
setMappingDeleteConfirmOpen={setMappingDeleteConfirmOpen}
|
|
2693
|
+
mappingDeleteProceedRef={mappingDeleteProceedRef}
|
|
2694
|
+
mappingDeleteImpactCount={mappingDeleteImpactCount}
|
|
2695
|
+
deletingLayer={deletingLayer}
|
|
2696
|
+
deletingLayerMode={deletingLayerMode}
|
|
2697
|
+
setConfirmDeleteLayerId={setConfirmDeleteLayerId}
|
|
2698
|
+
setDeleteMode={setDeleteMode}
|
|
2699
|
+
setDeleteTargetLayerId={setDeleteTargetLayerId}
|
|
2700
|
+
deleteTargetLayerId={deleteTargetLayerId}
|
|
2701
|
+
deleteTargetCandidates={deleteTargetCandidates}
|
|
2702
|
+
deleteLayer={deleteLayer}
|
|
2703
|
+
showActionHint={showActionHint}
|
|
2704
|
+
titleIconPreviewOpen={titleIconPreviewOpen}
|
|
2705
|
+
setTitleIconPreviewOpen={setTitleIconPreviewOpen}
|
|
2706
|
+
titleIconZoom={titleIconZoom}
|
|
2707
|
+
setTitleIconZoom={setTitleIconZoom}
|
|
2708
|
+
titleIconDataUrl={titleIconDataUrl}
|
|
2709
|
+
/>
|
|
3563
2710
|
<BlueprintMetaDialog
|
|
3564
2711
|
open={blueprintMetaDialogOpen}
|
|
3565
2712
|
mode={blueprintMetaDialogMode}
|
|
@@ -3589,11 +2736,12 @@ function ReactViewPanelInner({
|
|
|
3589
2736
|
export function ReactViewPanel({
|
|
3590
2737
|
locale,
|
|
3591
2738
|
onLocaleChange,
|
|
2739
|
+
nameSpace,
|
|
3592
2740
|
...rest
|
|
3593
2741
|
}: ReactViewPanelProps) {
|
|
3594
2742
|
return (
|
|
3595
|
-
<I18nRoot locale={locale} onLocaleChange={onLocaleChange}>
|
|
3596
|
-
<ReactViewPanelInner {...rest} />
|
|
2743
|
+
<I18nRoot locale={locale} onLocaleChange={onLocaleChange} nameSpace={nameSpace}>
|
|
2744
|
+
<ReactViewPanelInner nameSpace={nameSpace} {...rest} />
|
|
3597
2745
|
</I18nRoot>
|
|
3598
2746
|
);
|
|
3599
2747
|
}
|