@arronqzy/react-view 1.0.22 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/src/panel/ReactViewPanel.tsx +109 -965
- package/src/panel/components/PanelConfigSidebar.tsx +297 -3712
- package/src/panel/components/PanelHistoryPopover.tsx +163 -0
- package/src/panel/components/PanelLayerDock.tsx +412 -0
- package/src/panel/components/PanelMenubar.tsx +149 -0
- package/src/panel/components/PanelWorkspaceDialogs.tsx +271 -0
- package/src/panel/components/config/PanelConfigAudioSection.tsx +334 -0
- package/src/panel/components/config/PanelConfigChartSection.tsx +799 -0
- package/src/panel/components/config/PanelConfigGeometrySection.tsx +233 -0
- package/src/panel/components/config/PanelConfigGridChildSpan.tsx +70 -0
- package/src/panel/components/config/PanelConfigGridSection.tsx +118 -0
- package/src/panel/components/config/PanelConfigMultiSelect.tsx +1291 -0
- package/src/panel/components/config/PanelConfigNodeInfo.tsx +169 -0
- package/src/panel/components/config/PanelConfigReferenceSection.tsx +99 -0
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +6 -2
- package/src/panel/components/config/PanelConfigStyleSections.tsx +249 -0
- package/src/panel/components/config/PanelConfigTextSection.tsx +268 -0
- package/src/panel/components/config/PanelConfigVideoSection.tsx +133 -0
- package/src/panel/components/config/PanelConfigViewportSection.tsx +139 -0
- package/src/panel/components/config/helpers.ts +85 -0
- package/src/panel/icons.tsx +160 -0
- package/src/panel/utils/panelSelection.ts +57 -0
|
@@ -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,223 +122,6 @@ 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;
|
|
@@ -2072,84 +1835,32 @@ function ReactViewPanelInner({
|
|
|
2072
1835
|
}
|
|
2073
1836
|
`}</style>
|
|
2074
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">
|
|
2075
|
-
<
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
<MenubarSeparator />
|
|
2102
|
-
<MenubarItem
|
|
2103
|
-
onClick={() => {
|
|
2104
|
-
applyTheme(!isDark);
|
|
2105
|
-
}}
|
|
2106
|
-
>
|
|
2107
|
-
{isDark ? t("panel.menubar.switchToLight") : t("panel.menubar.switchToDark")}
|
|
2108
|
-
</MenubarItem>
|
|
2109
|
-
</MenubarContent>
|
|
2110
|
-
</MenubarMenu>
|
|
2111
|
-
<MenubarMenu>
|
|
2112
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.blueprint")}</MenubarTrigger>
|
|
2113
|
-
<MenubarContent className="z-[10100]">
|
|
2114
|
-
<MenubarItem onClick={openBlueprintExportDialog}>{t("panel.menubar.export")}</MenubarItem>
|
|
2115
|
-
<MenubarItem onClick={() => blueprintImportInputRef.current?.click()}>
|
|
2116
|
-
{t("panel.menubar.import")}
|
|
2117
|
-
</MenubarItem>
|
|
2118
|
-
</MenubarContent>
|
|
2119
|
-
</MenubarMenu>
|
|
2120
|
-
<MenubarMenu>
|
|
2121
|
-
<MenubarTrigger className="px-2 py-1 text-xs font-normal">{t("panel.menubar.settings")}</MenubarTrigger>
|
|
2122
|
-
<MenubarContent className="z-[10100]">
|
|
2123
|
-
<MenubarRadioGroup
|
|
2124
|
-
value={panelFontSize}
|
|
2125
|
-
onValueChange={(value) => setPanelFontSize(value as "sm" | "md" | "lg")}
|
|
2126
|
-
>
|
|
2127
|
-
<MenubarRadioItem value="sm">{t("panel.menubar.fontSmall")}</MenubarRadioItem>
|
|
2128
|
-
<MenubarRadioItem value="md">{t("panel.menubar.fontMedium")}</MenubarRadioItem>
|
|
2129
|
-
<MenubarRadioItem value="lg">{t("panel.menubar.fontLarge")}</MenubarRadioItem>
|
|
2130
|
-
</MenubarRadioGroup>
|
|
2131
|
-
<MenubarSeparator />
|
|
2132
|
-
<div className="px-2 py-1.5 text-[11px] text-muted-foreground">
|
|
2133
|
-
{t("panel.theme.language")}
|
|
2134
|
-
</div>
|
|
2135
|
-
<MenubarRadioGroup
|
|
2136
|
-
value={locale}
|
|
2137
|
-
onValueChange={(value) => setLocale(value as "zh-CN" | "en-US")}
|
|
2138
|
-
>
|
|
2139
|
-
<MenubarRadioItem value="zh-CN">{t("panel.theme.zhCN")}</MenubarRadioItem>
|
|
2140
|
-
<MenubarRadioItem value="en-US">{t("panel.theme.enUS")}</MenubarRadioItem>
|
|
2141
|
-
</MenubarRadioGroup>
|
|
2142
|
-
<MenubarSeparator />
|
|
2143
|
-
<MenubarCheckboxItem
|
|
2144
|
-
checked={outputScale}
|
|
2145
|
-
onCheckedChange={(checked) => setOutputScale(checked === true)}
|
|
2146
|
-
title={t("panel.menubar.outputScaleHint")}
|
|
2147
|
-
>
|
|
2148
|
-
{t("panel.menubar.outputScale")}
|
|
2149
|
-
</MenubarCheckboxItem>
|
|
2150
|
-
</MenubarContent>
|
|
2151
|
-
</MenubarMenu>
|
|
2152
|
-
</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
|
+
/>
|
|
2153
1864
|
<WorkspaceProjectNav
|
|
2154
1865
|
projects={workspaceProjects.projects}
|
|
2155
1866
|
activeProjectId={workspaceProjects.activeProjectId}
|
|
@@ -2354,122 +2065,19 @@ function ReactViewPanelInner({
|
|
|
2354
2065
|
</TooltipTrigger>
|
|
2355
2066
|
<TooltipContent className="z-[10000]">{t("panel.menubar.redoShortcut")}</TooltipContent>
|
|
2356
2067
|
</Tooltip>
|
|
2357
|
-
<
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
</button>
|
|
2371
|
-
{isHistoryPanelExpanded ? (
|
|
2372
|
-
<div
|
|
2373
|
-
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"
|
|
2374
|
-
style={{ zIndex: PANEL_Z_INDEX.historyPopover }}
|
|
2375
|
-
>
|
|
2376
|
-
<div className="mb-1 flex items-center justify-between">
|
|
2377
|
-
<span className="text-[11px] font-semibold text-muted-foreground">{t("panel.history.title")}</span>
|
|
2378
|
-
<span className="text-[11px] text-muted-foreground">
|
|
2379
|
-
{history.length > 0 ? `${historyCursor + 1}/${history.length}` : "0/0"}
|
|
2380
|
-
</span>
|
|
2381
|
-
</div>
|
|
2382
|
-
<Input
|
|
2383
|
-
value={historyKeyword}
|
|
2384
|
-
onChange={(e) => setHistoryKeyword(e.target.value)}
|
|
2385
|
-
placeholder={t("panel.history.searchPlaceholder")}
|
|
2386
|
-
className="mb-2 h-7 text-xs"
|
|
2387
|
-
/>
|
|
2388
|
-
<div
|
|
2389
|
-
className={`max-h-52 space-y-1 overflow-auto pr-1 text-[11px] ${themedScrollbarClass}`}
|
|
2390
|
-
>
|
|
2391
|
-
{history.length === 0 ? (
|
|
2392
|
-
<Empty className="py-4">
|
|
2393
|
-
<EmptyIcon className="h-8 w-8">
|
|
2394
|
-
<IconHistory />
|
|
2395
|
-
</EmptyIcon>
|
|
2396
|
-
<EmptyTitle className="text-xs">{t("panel.history.emptyTitle")}</EmptyTitle>
|
|
2397
|
-
<EmptyDescription className="text-[11px]">
|
|
2398
|
-
{t("panel.history.emptyDesc")}
|
|
2399
|
-
</EmptyDescription>
|
|
2400
|
-
</Empty>
|
|
2401
|
-
) : (
|
|
2402
|
-
history
|
|
2403
|
-
.slice(Math.max(0, history.length - 20))
|
|
2404
|
-
.reverse()
|
|
2405
|
-
.filter((item) =>
|
|
2406
|
-
!normalizedHistoryKeyword
|
|
2407
|
-
? true
|
|
2408
|
-
: item.label.toLowerCase().includes(normalizedHistoryKeyword)
|
|
2409
|
-
)
|
|
2410
|
-
.map((item) => (
|
|
2411
|
-
<button
|
|
2412
|
-
key={`${item.index}-${item.timestamp}`}
|
|
2413
|
-
type="button"
|
|
2414
|
-
className={[
|
|
2415
|
-
"w-full rounded border px-2 py-1 text-left transition-colors",
|
|
2416
|
-
item.active
|
|
2417
|
-
? "border-primary/50 bg-primary/10 text-foreground"
|
|
2418
|
-
: "border-border/60 bg-background/70 text-muted-foreground hover:bg-accent/50",
|
|
2419
|
-
].join(" ")}
|
|
2420
|
-
title={new Date(item.timestamp).toLocaleString()}
|
|
2421
|
-
onClick={() => {
|
|
2422
|
-
if (item.active) return;
|
|
2423
|
-
goToHistory(item.index);
|
|
2424
|
-
}}
|
|
2425
|
-
>
|
|
2426
|
-
<div className="flex items-center justify-between gap-2">
|
|
2427
|
-
<span className="truncate">{item.label}</span>
|
|
2428
|
-
<span className="shrink-0 text-[10px] text-muted-foreground">
|
|
2429
|
-
{formatRelativeTime(item.timestamp, historyNow, t)}
|
|
2430
|
-
</span>
|
|
2431
|
-
</div>
|
|
2432
|
-
</button>
|
|
2433
|
-
))
|
|
2434
|
-
)}
|
|
2435
|
-
{history.length > 0 &&
|
|
2436
|
-
history
|
|
2437
|
-
.slice(Math.max(0, history.length - 20))
|
|
2438
|
-
.reverse()
|
|
2439
|
-
.filter((item) =>
|
|
2440
|
-
!normalizedHistoryKeyword
|
|
2441
|
-
? true
|
|
2442
|
-
: item.label.toLowerCase().includes(normalizedHistoryKeyword)
|
|
2443
|
-
).length === 0 ? (
|
|
2444
|
-
<Empty className="py-4">
|
|
2445
|
-
<EmptyIcon className="h-8 w-8">
|
|
2446
|
-
<svg
|
|
2447
|
-
viewBox="0 0 24 24"
|
|
2448
|
-
className="h-4 w-4"
|
|
2449
|
-
fill="none"
|
|
2450
|
-
stroke="currentColor"
|
|
2451
|
-
strokeWidth="1.8"
|
|
2452
|
-
aria-hidden="true"
|
|
2453
|
-
>
|
|
2454
|
-
<circle cx="11" cy="11" r="7" />
|
|
2455
|
-
<path d="m20 20-3.5-3.5" />
|
|
2456
|
-
</svg>
|
|
2457
|
-
</EmptyIcon>
|
|
2458
|
-
<EmptyTitle className="text-xs">{t("panel.history.noMatchTitle")}</EmptyTitle>
|
|
2459
|
-
<EmptyDescription className="text-[11px]">
|
|
2460
|
-
{t("panel.history.noMatchDesc")}
|
|
2461
|
-
</EmptyDescription>
|
|
2462
|
-
</Empty>
|
|
2463
|
-
) : null}
|
|
2464
|
-
</div>
|
|
2465
|
-
</div>
|
|
2466
|
-
) : null}
|
|
2467
|
-
</div>
|
|
2468
|
-
</TooltipTrigger>
|
|
2469
|
-
<TooltipContent className="z-[10000]">
|
|
2470
|
-
{isHistoryPanelExpanded ? t("panel.history.collapse") : t("panel.history.expand")}
|
|
2471
|
-
</TooltipContent>
|
|
2472
|
-
</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
|
+
/>
|
|
2473
2081
|
</TooltipProvider>
|
|
2474
2082
|
<DropdownMenu>
|
|
2475
2083
|
<DropdownMenuTrigger asChild>
|
|
@@ -2985,328 +2593,36 @@ function ReactViewPanelInner({
|
|
|
2985
2593
|
}
|
|
2986
2594
|
/>
|
|
2987
2595
|
|
|
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
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
</TooltipTrigger>
|
|
3019
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.add")}</TooltipContent>
|
|
3020
|
-
</Tooltip>
|
|
3021
|
-
<Tooltip>
|
|
3022
|
-
<TooltipTrigger asChild>
|
|
3023
|
-
<button
|
|
3024
|
-
type="button"
|
|
3025
|
-
onClick={() => {
|
|
3026
|
-
if (!canMergeLayers) return;
|
|
3027
|
-
setIsMergingLayers(true);
|
|
3028
|
-
}}
|
|
3029
|
-
aria-label={t("panel.layers.merge")}
|
|
3030
|
-
disabled={!canMergeLayers}
|
|
3031
|
-
className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
|
|
3032
|
-
>
|
|
3033
|
-
<IconMerge />
|
|
3034
|
-
</button>
|
|
3035
|
-
</TooltipTrigger>
|
|
3036
|
-
<TooltipContent className="z-[10000]">
|
|
3037
|
-
{canMergeLayers ? t("panel.layers.merge") : t("panel.layers.mergeNeedTwo")}
|
|
3038
|
-
</TooltipContent>
|
|
3039
|
-
</Tooltip>
|
|
3040
|
-
</div>
|
|
3041
|
-
|
|
3042
|
-
<TabsList
|
|
3043
|
-
className={`h-auto w-full justify-start gap-1 overflow-x-auto bg-muted/40 p-1 ${themedScrollbarClass}`}
|
|
3044
|
-
>
|
|
3045
|
-
{layers.map((layer) => (
|
|
3046
|
-
<TabsTrigger
|
|
3047
|
-
key={layer.id}
|
|
3048
|
-
value={layer.id}
|
|
3049
|
-
className="flex min-w-[120px] items-center gap-1 px-2 py-1 text-xs"
|
|
3050
|
-
>
|
|
3051
|
-
<span className="truncate">{layer.name}</span>
|
|
3052
|
-
{layer.isMapping ? (
|
|
3053
|
-
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
3054
|
-
{t("panel.layers.mapping")}
|
|
3055
|
-
</span>
|
|
3056
|
-
) : null}
|
|
3057
|
-
{layer.isPrimary ? (
|
|
3058
|
-
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
3059
|
-
{t("panel.layers.primary")}
|
|
3060
|
-
</span>
|
|
3061
|
-
) : null}
|
|
3062
|
-
{layer.locked ? <IconLock locked /> : null}
|
|
3063
|
-
</TabsTrigger>
|
|
3064
|
-
))}
|
|
3065
|
-
</TabsList>
|
|
3066
|
-
|
|
3067
|
-
<TabsContent value={activeLayerId} className="mt-2 space-y-2">
|
|
3068
|
-
{activeLayer ? (
|
|
3069
|
-
<div className="flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
3070
|
-
{editingLayerId === activeLayer.id ? (
|
|
3071
|
-
<Input
|
|
3072
|
-
value={editingLayerName}
|
|
3073
|
-
onChange={(e) => setEditingLayerName(e.target.value)}
|
|
3074
|
-
onKeyDown={(e) => {
|
|
3075
|
-
if (e.key === "Enter") {
|
|
3076
|
-
e.preventDefault();
|
|
3077
|
-
renameLayer(editingLayerId, editingLayerName);
|
|
3078
|
-
setEditingLayerId(null);
|
|
3079
|
-
setEditingLayerName("");
|
|
3080
|
-
return;
|
|
3081
|
-
}
|
|
3082
|
-
if (e.key === "Escape") {
|
|
3083
|
-
e.preventDefault();
|
|
3084
|
-
setEditingLayerId(null);
|
|
3085
|
-
setEditingLayerName("");
|
|
3086
|
-
}
|
|
3087
|
-
}}
|
|
3088
|
-
className="h-7 min-w-0 flex-1"
|
|
3089
|
-
placeholder={t("panel.layers.namePlaceholder")}
|
|
3090
|
-
autoFocus
|
|
3091
|
-
/>
|
|
3092
|
-
) : (
|
|
3093
|
-
<span className="truncate font-medium">{activeLayer.name}</span>
|
|
3094
|
-
)}
|
|
3095
|
-
{activeLayer.isMapping ? (
|
|
3096
|
-
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
3097
|
-
{t("panel.layers.mappingLayer")}
|
|
3098
|
-
</span>
|
|
3099
|
-
) : null}
|
|
3100
|
-
{activeLayer.isPrimary ? (
|
|
3101
|
-
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
3102
|
-
{t("panel.layers.primaryLayer")}
|
|
3103
|
-
</span>
|
|
3104
|
-
) : null}
|
|
3105
|
-
<div className="flex-1" />
|
|
3106
|
-
<Tooltip>
|
|
3107
|
-
<TooltipTrigger asChild>
|
|
3108
|
-
<button
|
|
3109
|
-
type="button"
|
|
3110
|
-
onClick={() => setPrimaryLayer(activeLayer.id)}
|
|
3111
|
-
disabled={activeLayer.isPrimary}
|
|
3112
|
-
aria-label={activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
3113
|
-
className={[
|
|
3114
|
-
"rounded border px-2 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-60",
|
|
3115
|
-
activeLayer.isPrimary
|
|
3116
|
-
? "border-emerald-500/40 bg-emerald-500/10 text-emerald-600"
|
|
3117
|
-
: "border-border hover:bg-accent",
|
|
3118
|
-
].join(" ")}
|
|
3119
|
-
>
|
|
3120
|
-
{activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
3121
|
-
</button>
|
|
3122
|
-
</TooltipTrigger>
|
|
3123
|
-
<TooltipContent className="z-[10000]">
|
|
3124
|
-
{activeLayer.isPrimary ? t("panel.layers.isPrimaryHint") : t("panel.layers.setPrimaryHint")}
|
|
3125
|
-
</TooltipContent>
|
|
3126
|
-
</Tooltip>
|
|
3127
|
-
<Tooltip>
|
|
3128
|
-
<TooltipTrigger asChild>
|
|
3129
|
-
<button
|
|
3130
|
-
type="button"
|
|
3131
|
-
onClick={() => toggleLayerLock(activeLayer.id)}
|
|
3132
|
-
disabled={!activeLayer.editable}
|
|
3133
|
-
aria-label={activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
3134
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
3135
|
-
>
|
|
3136
|
-
<IconLock locked={activeLayer.locked} />
|
|
3137
|
-
</button>
|
|
3138
|
-
</TooltipTrigger>
|
|
3139
|
-
<TooltipContent className="z-[10000]">
|
|
3140
|
-
{activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
3141
|
-
</TooltipContent>
|
|
3142
|
-
</Tooltip>
|
|
3143
|
-
<Tooltip>
|
|
3144
|
-
<TooltipTrigger asChild>
|
|
3145
|
-
{editingLayerId === activeLayer.id ? (
|
|
3146
|
-
<button
|
|
3147
|
-
type="button"
|
|
3148
|
-
onClick={() => {
|
|
3149
|
-
renameLayer(editingLayerId, editingLayerName);
|
|
3150
|
-
setEditingLayerId(null);
|
|
3151
|
-
setEditingLayerName("");
|
|
3152
|
-
}}
|
|
3153
|
-
aria-label={t("panel.layers.saveName")}
|
|
3154
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3155
|
-
>
|
|
3156
|
-
<IconCheck />
|
|
3157
|
-
</button>
|
|
3158
|
-
) : (
|
|
3159
|
-
<button
|
|
3160
|
-
type="button"
|
|
3161
|
-
onClick={() => {
|
|
3162
|
-
setEditingLayerId(activeLayer.id);
|
|
3163
|
-
setEditingLayerName(activeLayer.name);
|
|
3164
|
-
}}
|
|
3165
|
-
disabled={!activeLayer.editable}
|
|
3166
|
-
aria-label={t("panel.layers.rename")}
|
|
3167
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
3168
|
-
>
|
|
3169
|
-
<IconEdit />
|
|
3170
|
-
</button>
|
|
3171
|
-
)}
|
|
3172
|
-
</TooltipTrigger>
|
|
3173
|
-
<TooltipContent className="z-[10000]">
|
|
3174
|
-
{editingLayerId === activeLayer.id ? t("panel.layers.saveName") : t("panel.layers.rename")}
|
|
3175
|
-
</TooltipContent>
|
|
3176
|
-
</Tooltip>
|
|
3177
|
-
{editingLayerId === activeLayer.id ? (
|
|
3178
|
-
<Tooltip>
|
|
3179
|
-
<TooltipTrigger asChild>
|
|
3180
|
-
<button
|
|
3181
|
-
type="button"
|
|
3182
|
-
onClick={() => {
|
|
3183
|
-
setEditingLayerId(null);
|
|
3184
|
-
setEditingLayerName("");
|
|
3185
|
-
}}
|
|
3186
|
-
aria-label={t("panel.layers.cancelEditName")}
|
|
3187
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3188
|
-
>
|
|
3189
|
-
<IconClose />
|
|
3190
|
-
</button>
|
|
3191
|
-
</TooltipTrigger>
|
|
3192
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.cancelEdit")}</TooltipContent>
|
|
3193
|
-
</Tooltip>
|
|
3194
|
-
) : null}
|
|
3195
|
-
<Tooltip>
|
|
3196
|
-
<TooltipTrigger asChild>
|
|
3197
|
-
<button
|
|
3198
|
-
type="button"
|
|
3199
|
-
onClick={() => {
|
|
3200
|
-
const blockReason = getLayerDeleteBlockReason(activeLayer.id);
|
|
3201
|
-
if (blockReason) {
|
|
3202
|
-
showActionHint(blockReason);
|
|
3203
|
-
return;
|
|
3204
|
-
}
|
|
3205
|
-
setConfirmDeleteLayerId(activeLayer.id);
|
|
3206
|
-
const firstTarget = layers.find((l) => l.id !== activeLayer.id);
|
|
3207
|
-
setDeleteTargetLayerId(firstTarget?.id ?? "");
|
|
3208
|
-
setDeleteMode(activeLayer.isMapping ? "remove" : "move");
|
|
3209
|
-
}}
|
|
3210
|
-
disabled={!activeLayer.editable || activeLayer.locked}
|
|
3211
|
-
aria-label={
|
|
3212
|
-
!activeLayer.editable
|
|
3213
|
-
? t("panel.layers.cannotDeleteDefault")
|
|
3214
|
-
: activeLayer.locked
|
|
3215
|
-
? t("panel.layers.cannotDeleteLocked")
|
|
3216
|
-
: t("panel.layers.delete")
|
|
3217
|
-
}
|
|
3218
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
3219
|
-
>
|
|
3220
|
-
<IconTrash />
|
|
3221
|
-
</button>
|
|
3222
|
-
</TooltipTrigger>
|
|
3223
|
-
<TooltipContent className="z-[10000]">
|
|
3224
|
-
{!activeLayer.editable
|
|
3225
|
-
? t("panel.layers.cannotDeleteDefault")
|
|
3226
|
-
: activeLayer.locked
|
|
3227
|
-
? t("panel.layers.cannotDeleteLocked")
|
|
3228
|
-
: t("panel.layers.delete")}
|
|
3229
|
-
</TooltipContent>
|
|
3230
|
-
</Tooltip>
|
|
3231
|
-
</div>
|
|
3232
|
-
) : null}
|
|
3233
|
-
|
|
3234
|
-
{isLayerPanelExpanded ? (
|
|
3235
|
-
<div className="rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
3236
|
-
<div className="mb-1 text-muted-foreground">{t("panel.layers.selectMergeLayers")}</div>
|
|
3237
|
-
<div className="flex flex-wrap gap-2">
|
|
3238
|
-
{layers.map((layer) => (
|
|
3239
|
-
<label key={layer.id} className="flex items-center gap-1.5">
|
|
3240
|
-
<Checkbox
|
|
3241
|
-
checked={Boolean(layer.mergeSelected)}
|
|
3242
|
-
disabled={layer.isMapping}
|
|
3243
|
-
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"
|
|
3244
|
-
onCheckedChange={() => toggleLayerMergeSelected(layer.id)}
|
|
3245
|
-
/>
|
|
3246
|
-
<span
|
|
3247
|
-
className="max-w-[140px] truncate"
|
|
3248
|
-
title={layer.isMapping ? t("panel.layers.mappingCannotMerge") : layer.name}
|
|
3249
|
-
>
|
|
3250
|
-
{layer.name}
|
|
3251
|
-
{layer.isMapping ? t("panel.layers.cannotMergeSuffix") : ""}
|
|
3252
|
-
</span>
|
|
3253
|
-
</label>
|
|
3254
|
-
))}
|
|
3255
|
-
</div>
|
|
3256
|
-
</div>
|
|
3257
|
-
) : null}
|
|
3258
|
-
</TabsContent>
|
|
3259
|
-
</Tabs>
|
|
3260
|
-
|
|
3261
|
-
{isLayerPanelExpanded && isMergingLayers ? (
|
|
3262
|
-
<div className="mt-2 flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
|
|
3263
|
-
<span className="text-muted-foreground">{t("panel.layers.mergedName")}</span>
|
|
3264
|
-
<Input
|
|
3265
|
-
value={mergeLayerName}
|
|
3266
|
-
onChange={(e) => setMergeLayerName(e.target.value)}
|
|
3267
|
-
className="h-7 min-w-0 flex-1"
|
|
3268
|
-
placeholder={t("panel.layers.mergedNamePlaceholder")}
|
|
3269
|
-
autoFocus
|
|
3270
|
-
/>
|
|
3271
|
-
<Tooltip>
|
|
3272
|
-
<TooltipTrigger asChild>
|
|
3273
|
-
<button
|
|
3274
|
-
type="button"
|
|
3275
|
-
onClick={() => {
|
|
3276
|
-
if (!canMergeLayers) return;
|
|
3277
|
-
mergeSelectedLayers(mergeLayerName || undefined);
|
|
3278
|
-
setIsMergingLayers(false);
|
|
3279
|
-
setMergeLayerName("");
|
|
3280
|
-
}}
|
|
3281
|
-
aria-label={t("panel.layers.confirmMergeAria")}
|
|
3282
|
-
disabled={!canMergeLayers}
|
|
3283
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3284
|
-
>
|
|
3285
|
-
<IconCheck />
|
|
3286
|
-
</button>
|
|
3287
|
-
</TooltipTrigger>
|
|
3288
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.confirmMerge")}</TooltipContent>
|
|
3289
|
-
</Tooltip>
|
|
3290
|
-
<Tooltip>
|
|
3291
|
-
<TooltipTrigger asChild>
|
|
3292
|
-
<button
|
|
3293
|
-
type="button"
|
|
3294
|
-
onClick={() => {
|
|
3295
|
-
setIsMergingLayers(false);
|
|
3296
|
-
setMergeLayerName("");
|
|
3297
|
-
}}
|
|
3298
|
-
aria-label={t("panel.layers.cancelMergeAria")}
|
|
3299
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
3300
|
-
>
|
|
3301
|
-
<IconClose />
|
|
3302
|
-
</button>
|
|
3303
|
-
</TooltipTrigger>
|
|
3304
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.cancelMerge")}</TooltipContent>
|
|
3305
|
-
</Tooltip>
|
|
3306
|
-
</div>
|
|
3307
|
-
) : null}
|
|
3308
|
-
</TooltipProvider>
|
|
3309
|
-
</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
|
+
/>
|
|
3310
2626
|
</div>
|
|
3311
2627
|
</div>
|
|
3312
2628
|
</ResizablePanel>
|
|
@@ -3370,199 +2686,27 @@ function ReactViewPanelInner({
|
|
|
3370
2686
|
open={assistantOpen}
|
|
3371
2687
|
onOpenChange={setAssistantOpen}
|
|
3372
2688
|
/>
|
|
3373
|
-
<
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
}
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
mappingDeleteProceedRef.current = null;
|
|
3395
|
-
setMappingDeleteConfirmOpen(false);
|
|
3396
|
-
fn?.();
|
|
3397
|
-
}}
|
|
3398
|
-
>
|
|
3399
|
-
{t("panel.layers.continueDelete")}
|
|
3400
|
-
</AlertDialogAction>
|
|
3401
|
-
</AlertDialogFooter>
|
|
3402
|
-
</AlertDialogContent>
|
|
3403
|
-
</AlertDialog>
|
|
3404
|
-
<Dialog
|
|
3405
|
-
open={Boolean(deletingLayer)}
|
|
3406
|
-
onOpenChange={(open) => {
|
|
3407
|
-
if (open) return;
|
|
3408
|
-
setConfirmDeleteLayerId(null);
|
|
3409
|
-
setDeleteMode("move");
|
|
3410
|
-
setDeleteTargetLayerId("");
|
|
3411
|
-
}}
|
|
3412
|
-
>
|
|
3413
|
-
<DialogContent
|
|
3414
|
-
className="sm:max-w-[520px]"
|
|
3415
|
-
overlayClassName="bg-transparent pointer-events-none"
|
|
3416
|
-
>
|
|
3417
|
-
<DialogHeader>
|
|
3418
|
-
<DialogTitle>{t("panel.layers.confirmDeleteLayerTitle")}</DialogTitle>
|
|
3419
|
-
<DialogDescription>
|
|
3420
|
-
{t("panel.layers.aboutToDeleteLayer", {
|
|
3421
|
-
name: deletingLayer?.name ?? "-",
|
|
3422
|
-
})}
|
|
3423
|
-
</DialogDescription>
|
|
3424
|
-
</DialogHeader>
|
|
3425
|
-
{deletingLayer ? (
|
|
3426
|
-
<RadioGroup
|
|
3427
|
-
value={deletingLayerMode}
|
|
3428
|
-
onValueChange={(value) => setDeleteMode(value as "move" | "remove")}
|
|
3429
|
-
className="space-y-2 text-sm"
|
|
3430
|
-
>
|
|
3431
|
-
{!deletingLayer.isMapping ? (
|
|
3432
|
-
<div className="flex items-center gap-3">
|
|
3433
|
-
<label className="flex items-center gap-1.5">
|
|
3434
|
-
<RadioGroupItem value="move" />
|
|
3435
|
-
{t("panel.layers.migrateNodesTo")}
|
|
3436
|
-
</label>
|
|
3437
|
-
<Select
|
|
3438
|
-
value={deleteTargetLayerId || "__none__"}
|
|
3439
|
-
onValueChange={(value) =>
|
|
3440
|
-
setDeleteTargetLayerId(value === "__none__" ? "" : value)
|
|
3441
|
-
}
|
|
3442
|
-
disabled={deletingLayerMode !== "move" || deleteTargetCandidates.length === 0}
|
|
3443
|
-
>
|
|
3444
|
-
<SelectTrigger className="h-8 w-[220px]">
|
|
3445
|
-
<SelectValue placeholder={t("panel.layers.selectTargetLayer")} />
|
|
3446
|
-
</SelectTrigger>
|
|
3447
|
-
<SelectContent>
|
|
3448
|
-
<SelectItem value="__none__">{t("panel.layers.pleaseSelectLayer")}</SelectItem>
|
|
3449
|
-
{deleteTargetCandidates.map((l) => (
|
|
3450
|
-
<SelectItem key={l.id} value={l.id}>
|
|
3451
|
-
{l.name}
|
|
3452
|
-
</SelectItem>
|
|
3453
|
-
))}
|
|
3454
|
-
</SelectContent>
|
|
3455
|
-
</Select>
|
|
3456
|
-
</div>
|
|
3457
|
-
) : null}
|
|
3458
|
-
<label className="flex items-center gap-1.5">
|
|
3459
|
-
<RadioGroupItem value="remove" />
|
|
3460
|
-
{deletingLayer.isMapping ? t("panel.layers.deleteMappingWithNodes") : t("panel.layers.deleteAllNodesToo")}
|
|
3461
|
-
</label>
|
|
3462
|
-
</RadioGroup>
|
|
3463
|
-
) : null}
|
|
3464
|
-
<DialogFooter>
|
|
3465
|
-
<button
|
|
3466
|
-
type="button"
|
|
3467
|
-
onClick={() => {
|
|
3468
|
-
setConfirmDeleteLayerId(null);
|
|
3469
|
-
setDeleteMode("move");
|
|
3470
|
-
setDeleteTargetLayerId("");
|
|
3471
|
-
}}
|
|
3472
|
-
className="rounded border border-border px-3 py-1.5 text-sm hover:bg-accent"
|
|
3473
|
-
>
|
|
3474
|
-
{t("common.cancel")}
|
|
3475
|
-
</button>
|
|
3476
|
-
<button
|
|
3477
|
-
type="button"
|
|
3478
|
-
onClick={() => {
|
|
3479
|
-
if (!deletingLayer) return;
|
|
3480
|
-
const result = deleteLayer(deletingLayer.id, {
|
|
3481
|
-
mode: deletingLayerMode,
|
|
3482
|
-
targetLayerId: deleteTargetLayerId || undefined,
|
|
3483
|
-
});
|
|
3484
|
-
if (!result.ok) {
|
|
3485
|
-
showActionHint(result.reason);
|
|
3486
|
-
return;
|
|
3487
|
-
}
|
|
3488
|
-
setConfirmDeleteLayerId(null);
|
|
3489
|
-
setDeleteMode("move");
|
|
3490
|
-
setDeleteTargetLayerId("");
|
|
3491
|
-
}}
|
|
3492
|
-
className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90"
|
|
3493
|
-
>
|
|
3494
|
-
{t("panel.layers.confirmDeleteTitle")}
|
|
3495
|
-
</button>
|
|
3496
|
-
</DialogFooter>
|
|
3497
|
-
</DialogContent>
|
|
3498
|
-
</Dialog>
|
|
3499
|
-
<Dialog
|
|
3500
|
-
open={titleIconPreviewOpen}
|
|
3501
|
-
onOpenChange={setTitleIconPreviewOpen}
|
|
3502
|
-
>
|
|
3503
|
-
<DialogContent
|
|
3504
|
-
className="sm:max-w-[860px]"
|
|
3505
|
-
overlayClassName="bg-transparent pointer-events-none"
|
|
3506
|
-
>
|
|
3507
|
-
<DialogHeader>
|
|
3508
|
-
<DialogTitle>{t("panel.menubar.titleIconPreviewTitle")}</DialogTitle>
|
|
3509
|
-
<DialogDescription>{t("panel.menubar.titleIconPreviewDesc")}</DialogDescription>
|
|
3510
|
-
</DialogHeader>
|
|
3511
|
-
<div className="flex items-center justify-end gap-2">
|
|
3512
|
-
<button
|
|
3513
|
-
type="button"
|
|
3514
|
-
onClick={() => setTitleIconZoom((z) => Math.max(0.5, Number((z - 0.1).toFixed(2))))}
|
|
3515
|
-
className="rounded border border-border px-2 py-1 text-xs hover:bg-accent"
|
|
3516
|
-
>
|
|
3517
|
-
-
|
|
3518
|
-
</button>
|
|
3519
|
-
<span className="w-14 text-center text-xs text-muted-foreground">
|
|
3520
|
-
{(titleIconZoom * 100).toFixed(0)}%
|
|
3521
|
-
</span>
|
|
3522
|
-
<button
|
|
3523
|
-
type="button"
|
|
3524
|
-
onClick={() => setTitleIconZoom((z) => Math.min(8, Number((z + 0.1).toFixed(2))))}
|
|
3525
|
-
className="rounded border border-border px-2 py-1 text-xs hover:bg-accent"
|
|
3526
|
-
>
|
|
3527
|
-
+
|
|
3528
|
-
</button>
|
|
3529
|
-
<button
|
|
3530
|
-
type="button"
|
|
3531
|
-
onClick={() => setTitleIconZoom(1.6)}
|
|
3532
|
-
className="rounded border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
|
|
3533
|
-
>
|
|
3534
|
-
{t("common.reset")}
|
|
3535
|
-
</button>
|
|
3536
|
-
</div>
|
|
3537
|
-
{titleIconDataUrl ? (
|
|
3538
|
-
<div
|
|
3539
|
-
className="max-h-[78vh] overflow-auto rounded border border-border bg-muted/20 p-4"
|
|
3540
|
-
onWheel={(e) => {
|
|
3541
|
-
e.preventDefault();
|
|
3542
|
-
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
3543
|
-
setTitleIconZoom((z) => {
|
|
3544
|
-
const next = Number((z + delta).toFixed(2));
|
|
3545
|
-
return Math.min(8, Math.max(0.5, next));
|
|
3546
|
-
});
|
|
3547
|
-
}}
|
|
3548
|
-
>
|
|
3549
|
-
<img
|
|
3550
|
-
src={titleIconDataUrl}
|
|
3551
|
-
alt={t("panel.menubar.titleIconPreviewAlt")}
|
|
3552
|
-
className="mx-auto rounded border border-border/70 object-contain"
|
|
3553
|
-
style={{
|
|
3554
|
-
width: 200,
|
|
3555
|
-
height: 200,
|
|
3556
|
-
maxWidth: "none",
|
|
3557
|
-
maxHeight: "none",
|
|
3558
|
-
transform: `scale(${titleIconZoom})`,
|
|
3559
|
-
transformOrigin: "center center",
|
|
3560
|
-
}}
|
|
3561
|
-
/>
|
|
3562
|
-
</div>
|
|
3563
|
-
) : null}
|
|
3564
|
-
</DialogContent>
|
|
3565
|
-
</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
|
+
/>
|
|
3566
2710
|
<BlueprintMetaDialog
|
|
3567
2711
|
open={blueprintMetaDialogOpen}
|
|
3568
2712
|
mode={blueprintMetaDialogMode}
|