@arronqzy/react-view 1.0.38 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/styles.css +316 -241
- package/package.json +5 -5
- package/readme.md +15 -12
- package/src/index.ts +23 -1
- package/src/panel/ReactViewPanel.tsx +140 -90
- package/src/panel/components/ElementsLayer.tsx +1 -1
- package/src/panel/components/MaterialSidebar.tsx +304 -264
- package/src/panel/components/MoveableLayer.tsx +11 -2
- package/src/panel/components/PanelCanvas.tsx +1 -0
- package/src/panel/components/PanelConfigSidebar.tsx +49 -65
- package/src/panel/components/PanelLayerDock.tsx +397 -316
- package/src/panel/components/PanelRulers.tsx +169 -42
- package/src/panel/components/SelectLayer.tsx +2 -2
- package/src/panel/components/WorkspaceProjectNav.tsx +1 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +228 -52
- package/src/panel/icons.tsx +117 -0
- package/src/panel/library/abuilder-storage.ts +101 -3
- package/src/panel/library/abuilder-view-handle.ts +100 -0
- package/src/panel/library/event-subscription.ts +70 -5
- package/src/panel/library/workspace-project-sync.ts +7 -1
- package/src/panel/library/workspace-storage.ts +58 -4
- package/src/tailwind.css +66 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Checkbox,
|
|
4
4
|
Input,
|
|
@@ -16,9 +16,12 @@ import {
|
|
|
16
16
|
IconChevron,
|
|
17
17
|
IconClose,
|
|
18
18
|
IconEdit,
|
|
19
|
+
IconEyeOff,
|
|
20
|
+
IconLayers,
|
|
19
21
|
IconLock,
|
|
20
22
|
IconMerge,
|
|
21
23
|
IconPlus,
|
|
24
|
+
IconStar,
|
|
22
25
|
IconTrash,
|
|
23
26
|
} from "../icons";
|
|
24
27
|
import type { PanelLayer } from "../types";
|
|
@@ -54,6 +57,9 @@ export type PanelLayerDockProps = {
|
|
|
54
57
|
mergeSelectedLayers: (name?: string) => void;
|
|
55
58
|
};
|
|
56
59
|
|
|
60
|
+
const iconBtn =
|
|
61
|
+
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md border border-border/60 bg-background/80 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-40";
|
|
62
|
+
|
|
57
63
|
export function PanelLayerDock({
|
|
58
64
|
t,
|
|
59
65
|
isLayerPanelExpanded,
|
|
@@ -84,329 +90,404 @@ export function PanelLayerDock({
|
|
|
84
90
|
setMergeLayerName,
|
|
85
91
|
mergeSelectedLayers,
|
|
86
92
|
}: PanelLayerDockProps) {
|
|
93
|
+
const [dockVisible, setDockVisible] = useState(true);
|
|
94
|
+
|
|
95
|
+
if (!dockVisible) {
|
|
96
|
+
return (
|
|
97
|
+
<div className="flex justify-center border-t border-border/50 bg-muted/5 px-2 py-0.5">
|
|
98
|
+
<TooltipProvider delayDuration={120}>
|
|
99
|
+
<Tooltip>
|
|
100
|
+
<TooltipTrigger asChild>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={() => setDockVisible(true)}
|
|
104
|
+
aria-label={t("panel.layers.title")}
|
|
105
|
+
className="inline-flex h-6 items-center gap-1 rounded-md border border-border/60 bg-background/90 px-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
106
|
+
>
|
|
107
|
+
<IconLayers />
|
|
108
|
+
<span data-typo="caption" className="hidden text-[11px] font-medium sm:inline">
|
|
109
|
+
{t("panel.layers.title")}
|
|
110
|
+
</span>
|
|
111
|
+
</button>
|
|
112
|
+
</TooltipTrigger>
|
|
113
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.title")}</TooltipContent>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
</TooltipProvider>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
87
120
|
return (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
121
|
+
<div className="border-t border-border/50 bg-muted/5 px-2.5 py-1.5">
|
|
122
|
+
<TooltipProvider delayDuration={120}>
|
|
123
|
+
<Tabs value={activeLayerId} onValueChange={setActiveLayer}>
|
|
124
|
+
<div className="flex min-w-0 items-center gap-1">
|
|
125
|
+
<Tooltip>
|
|
126
|
+
<TooltipTrigger asChild>
|
|
127
|
+
<span className="inline-flex h-6 w-6 shrink-0 items-center justify-center text-muted-foreground">
|
|
128
|
+
<IconLayers />
|
|
129
|
+
</span>
|
|
130
|
+
</TooltipTrigger>
|
|
131
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.title")}</TooltipContent>
|
|
132
|
+
</Tooltip>
|
|
133
|
+
|
|
134
|
+
<Tooltip>
|
|
135
|
+
<TooltipTrigger asChild>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => setIsLayerPanelExpanded((v) => !v)}
|
|
139
|
+
aria-label={
|
|
140
|
+
isLayerPanelExpanded
|
|
141
|
+
? t("panel.layers.collapseInputAria")
|
|
142
|
+
: t("panel.layers.expandInputAria")
|
|
143
|
+
}
|
|
144
|
+
className={iconBtn}
|
|
145
|
+
>
|
|
146
|
+
<IconChevron expanded={isLayerPanelExpanded} />
|
|
147
|
+
</button>
|
|
148
|
+
</TooltipTrigger>
|
|
149
|
+
<TooltipContent className="z-[10000]">
|
|
150
|
+
{isLayerPanelExpanded
|
|
151
|
+
? t("panel.layers.collapseInput")
|
|
152
|
+
: t("panel.layers.expandInput")}
|
|
153
|
+
</TooltipContent>
|
|
154
|
+
</Tooltip>
|
|
155
|
+
|
|
156
|
+
<Tooltip>
|
|
157
|
+
<TooltipTrigger asChild>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={addLayer}
|
|
161
|
+
aria-label={t("panel.layers.add")}
|
|
162
|
+
className={iconBtn}
|
|
163
|
+
>
|
|
164
|
+
<IconPlus />
|
|
165
|
+
</button>
|
|
166
|
+
</TooltipTrigger>
|
|
167
|
+
<TooltipContent className="z-[10000]">{t("panel.layers.add")}</TooltipContent>
|
|
168
|
+
</Tooltip>
|
|
169
|
+
|
|
170
|
+
<Tooltip>
|
|
171
|
+
<TooltipTrigger asChild>
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
onClick={() => {
|
|
175
|
+
if (!canMergeLayers) return;
|
|
176
|
+
setIsMergingLayers(true);
|
|
177
|
+
setIsLayerPanelExpanded(true);
|
|
178
|
+
}}
|
|
179
|
+
aria-label={t("panel.layers.merge")}
|
|
180
|
+
disabled={!canMergeLayers}
|
|
181
|
+
className={iconBtn}
|
|
182
|
+
>
|
|
183
|
+
<IconMerge />
|
|
184
|
+
</button>
|
|
185
|
+
</TooltipTrigger>
|
|
186
|
+
<TooltipContent className="z-[10000]">
|
|
187
|
+
{canMergeLayers ? t("panel.layers.merge") : t("panel.layers.mergeNeedTwo")}
|
|
188
|
+
</TooltipContent>
|
|
189
|
+
</Tooltip>
|
|
190
|
+
|
|
191
|
+
<TabsList
|
|
192
|
+
className={`ml-0.5 h-6 min-w-0 flex-1 justify-start gap-0.5 overflow-x-auto rounded-md bg-muted/30 p-0.5 ${themedScrollbarClass}`}
|
|
193
|
+
>
|
|
194
|
+
{layers.map((layer) => (
|
|
195
|
+
<TabsTrigger
|
|
196
|
+
key={layer.id}
|
|
197
|
+
value={layer.id}
|
|
198
|
+
className="flex h-5 max-w-[120px] min-w-[64px] items-center gap-1 rounded px-1.5 py-0 text-[11px] font-medium data-[state=active]:font-semibold"
|
|
199
|
+
>
|
|
200
|
+
<span className="truncate">{layer.name}</span>
|
|
201
|
+
{layer.isPrimary ? (
|
|
202
|
+
<span className="text-emerald-500" title={t("panel.layers.primary")}>
|
|
203
|
+
<IconStar filled />
|
|
204
|
+
</span>
|
|
205
|
+
) : null}
|
|
206
|
+
{layer.locked ? <IconLock locked /> : null}
|
|
207
|
+
</TabsTrigger>
|
|
208
|
+
))}
|
|
209
|
+
</TabsList>
|
|
210
|
+
|
|
211
|
+
{activeLayer ? (
|
|
212
|
+
<div className="flex shrink-0 items-center gap-0.5 border-l border-border/50 pl-1">
|
|
213
|
+
{editingLayerId === activeLayer.id ? (
|
|
214
|
+
<Input
|
|
215
|
+
value={editingLayerName}
|
|
216
|
+
onChange={(e) => setEditingLayerName(e.target.value)}
|
|
217
|
+
onKeyDown={(e) => {
|
|
218
|
+
if (e.key === "Enter") {
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
renameLayer(editingLayerId, editingLayerName);
|
|
221
|
+
setEditingLayerId(null);
|
|
222
|
+
setEditingLayerName("");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (e.key === "Escape") {
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
setEditingLayerId(null);
|
|
228
|
+
setEditingLayerName("");
|
|
229
|
+
}
|
|
230
|
+
}}
|
|
231
|
+
className="h-6 w-24 min-w-0 text-[11px]"
|
|
232
|
+
placeholder={t("panel.layers.namePlaceholder")}
|
|
233
|
+
autoFocus
|
|
234
|
+
/>
|
|
235
|
+
) : null}
|
|
141
236
|
|
|
142
|
-
|
|
143
|
-
|
|
237
|
+
<Tooltip>
|
|
238
|
+
<TooltipTrigger asChild>
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
onClick={() => setPrimaryLayer(activeLayer.id)}
|
|
242
|
+
disabled={activeLayer.isPrimary}
|
|
243
|
+
aria-label={
|
|
244
|
+
activeLayer.isPrimary
|
|
245
|
+
? t("panel.layers.isPrimary")
|
|
246
|
+
: t("panel.layers.setPrimary")
|
|
247
|
+
}
|
|
248
|
+
className={[
|
|
249
|
+
iconBtn,
|
|
250
|
+
activeLayer.isPrimary ? "text-emerald-500" : "",
|
|
251
|
+
].join(" ")}
|
|
144
252
|
>
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
{t("panel.layers.mapping")}
|
|
155
|
-
</span>
|
|
156
|
-
) : null}
|
|
157
|
-
{layer.isPrimary ? (
|
|
158
|
-
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
159
|
-
{t("panel.layers.primary")}
|
|
160
|
-
</span>
|
|
161
|
-
) : null}
|
|
162
|
-
{layer.locked ? <IconLock locked /> : null}
|
|
163
|
-
</TabsTrigger>
|
|
164
|
-
))}
|
|
165
|
-
</TabsList>
|
|
253
|
+
<IconStar filled={activeLayer.isPrimary} />
|
|
254
|
+
</button>
|
|
255
|
+
</TooltipTrigger>
|
|
256
|
+
<TooltipContent className="z-[10000]">
|
|
257
|
+
{activeLayer.isPrimary
|
|
258
|
+
? t("panel.layers.isPrimaryHint")
|
|
259
|
+
: t("panel.layers.setPrimaryHint")}
|
|
260
|
+
</TooltipContent>
|
|
261
|
+
</Tooltip>
|
|
166
262
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
setEditingLayerName("");
|
|
186
|
-
}
|
|
187
|
-
}}
|
|
188
|
-
className="h-7 min-w-0 flex-1"
|
|
189
|
-
placeholder={t("panel.layers.namePlaceholder")}
|
|
190
|
-
autoFocus
|
|
191
|
-
/>
|
|
192
|
-
) : (
|
|
193
|
-
<span className="truncate font-medium">{activeLayer.name}</span>
|
|
194
|
-
)}
|
|
195
|
-
{activeLayer.isMapping ? (
|
|
196
|
-
<span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
|
|
197
|
-
{t("panel.layers.mappingLayer")}
|
|
198
|
-
</span>
|
|
199
|
-
) : null}
|
|
200
|
-
{activeLayer.isPrimary ? (
|
|
201
|
-
<span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
|
|
202
|
-
{t("panel.layers.primaryLayer")}
|
|
203
|
-
</span>
|
|
204
|
-
) : null}
|
|
205
|
-
<div className="flex-1" />
|
|
206
|
-
<Tooltip>
|
|
207
|
-
<TooltipTrigger asChild>
|
|
208
|
-
<button
|
|
209
|
-
type="button"
|
|
210
|
-
onClick={() => setPrimaryLayer(activeLayer.id)}
|
|
211
|
-
disabled={activeLayer.isPrimary}
|
|
212
|
-
aria-label={activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
213
|
-
className={[
|
|
214
|
-
"rounded border px-2 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-60",
|
|
215
|
-
activeLayer.isPrimary
|
|
216
|
-
? "border-emerald-500/40 bg-emerald-500/10 text-emerald-600"
|
|
217
|
-
: "border-border hover:bg-accent",
|
|
218
|
-
].join(" ")}
|
|
219
|
-
>
|
|
220
|
-
{activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
|
|
221
|
-
</button>
|
|
222
|
-
</TooltipTrigger>
|
|
223
|
-
<TooltipContent className="z-[10000]">
|
|
224
|
-
{activeLayer.isPrimary ? t("panel.layers.isPrimaryHint") : t("panel.layers.setPrimaryHint")}
|
|
225
|
-
</TooltipContent>
|
|
226
|
-
</Tooltip>
|
|
227
|
-
<Tooltip>
|
|
228
|
-
<TooltipTrigger asChild>
|
|
229
|
-
<button
|
|
230
|
-
type="button"
|
|
231
|
-
onClick={() => toggleLayerLock(activeLayer.id)}
|
|
232
|
-
disabled={!activeLayer.editable}
|
|
233
|
-
aria-label={activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
234
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
235
|
-
>
|
|
236
|
-
<IconLock locked={activeLayer.locked} />
|
|
237
|
-
</button>
|
|
238
|
-
</TooltipTrigger>
|
|
239
|
-
<TooltipContent className="z-[10000]">
|
|
240
|
-
{activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
241
|
-
</TooltipContent>
|
|
242
|
-
</Tooltip>
|
|
243
|
-
<Tooltip>
|
|
244
|
-
<TooltipTrigger asChild>
|
|
245
|
-
{editingLayerId === activeLayer.id ? (
|
|
246
|
-
<button
|
|
247
|
-
type="button"
|
|
248
|
-
onClick={() => {
|
|
249
|
-
renameLayer(editingLayerId, editingLayerName);
|
|
250
|
-
setEditingLayerId(null);
|
|
251
|
-
setEditingLayerName("");
|
|
252
|
-
}}
|
|
253
|
-
aria-label={t("panel.layers.saveName")}
|
|
254
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
255
|
-
>
|
|
256
|
-
<IconCheck />
|
|
257
|
-
</button>
|
|
258
|
-
) : (
|
|
259
|
-
<button
|
|
260
|
-
type="button"
|
|
261
|
-
onClick={() => {
|
|
262
|
-
setEditingLayerId(activeLayer.id);
|
|
263
|
-
setEditingLayerName(activeLayer.name);
|
|
264
|
-
}}
|
|
265
|
-
disabled={!activeLayer.editable}
|
|
266
|
-
aria-label={t("panel.layers.rename")}
|
|
267
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
268
|
-
>
|
|
269
|
-
<IconEdit />
|
|
270
|
-
</button>
|
|
271
|
-
)}
|
|
272
|
-
</TooltipTrigger>
|
|
273
|
-
<TooltipContent className="z-[10000]">
|
|
274
|
-
{editingLayerId === activeLayer.id ? t("panel.layers.saveName") : t("panel.layers.rename")}
|
|
275
|
-
</TooltipContent>
|
|
276
|
-
</Tooltip>
|
|
277
|
-
{editingLayerId === activeLayer.id ? (
|
|
278
|
-
<Tooltip>
|
|
279
|
-
<TooltipTrigger asChild>
|
|
280
|
-
<button
|
|
281
|
-
type="button"
|
|
282
|
-
onClick={() => {
|
|
283
|
-
setEditingLayerId(null);
|
|
284
|
-
setEditingLayerName("");
|
|
285
|
-
}}
|
|
286
|
-
aria-label={t("panel.layers.cancelEditName")}
|
|
287
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
288
|
-
>
|
|
289
|
-
<IconClose />
|
|
290
|
-
</button>
|
|
291
|
-
</TooltipTrigger>
|
|
292
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.cancelEdit")}</TooltipContent>
|
|
293
|
-
</Tooltip>
|
|
294
|
-
) : null}
|
|
295
|
-
<Tooltip>
|
|
296
|
-
<TooltipTrigger asChild>
|
|
297
|
-
<button
|
|
298
|
-
type="button"
|
|
299
|
-
onClick={() => {
|
|
300
|
-
const blockReason = getLayerDeleteBlockReason(activeLayer.id);
|
|
301
|
-
if (blockReason) {
|
|
302
|
-
showActionHint(blockReason);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
setConfirmDeleteLayerId(activeLayer.id);
|
|
306
|
-
const firstTarget = layers.find((l) => l.id !== activeLayer.id);
|
|
307
|
-
setDeleteTargetLayerId(firstTarget?.id ?? "");
|
|
308
|
-
setDeleteMode(activeLayer.isMapping ? "remove" : "move");
|
|
309
|
-
}}
|
|
310
|
-
disabled={!activeLayer.editable || activeLayer.locked}
|
|
311
|
-
aria-label={
|
|
312
|
-
!activeLayer.editable
|
|
313
|
-
? t("panel.layers.cannotDeleteDefault")
|
|
314
|
-
: activeLayer.locked
|
|
315
|
-
? t("panel.layers.cannotDeleteLocked")
|
|
316
|
-
: t("panel.layers.delete")
|
|
317
|
-
}
|
|
318
|
-
className="rounded border border-border p-1 disabled:opacity-40"
|
|
319
|
-
>
|
|
320
|
-
<IconTrash />
|
|
321
|
-
</button>
|
|
322
|
-
</TooltipTrigger>
|
|
323
|
-
<TooltipContent className="z-[10000]">
|
|
324
|
-
{!activeLayer.editable
|
|
325
|
-
? t("panel.layers.cannotDeleteDefault")
|
|
326
|
-
: activeLayer.locked
|
|
327
|
-
? t("panel.layers.cannotDeleteLocked")
|
|
328
|
-
: t("panel.layers.delete")}
|
|
329
|
-
</TooltipContent>
|
|
330
|
-
</Tooltip>
|
|
331
|
-
</div>
|
|
332
|
-
) : null}
|
|
263
|
+
<Tooltip>
|
|
264
|
+
<TooltipTrigger asChild>
|
|
265
|
+
<button
|
|
266
|
+
type="button"
|
|
267
|
+
onClick={() => toggleLayerLock(activeLayer.id)}
|
|
268
|
+
disabled={!activeLayer.editable}
|
|
269
|
+
aria-label={
|
|
270
|
+
activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")
|
|
271
|
+
}
|
|
272
|
+
className={iconBtn}
|
|
273
|
+
>
|
|
274
|
+
<IconLock locked={activeLayer.locked} />
|
|
275
|
+
</button>
|
|
276
|
+
</TooltipTrigger>
|
|
277
|
+
<TooltipContent className="z-[10000]">
|
|
278
|
+
{activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
|
|
279
|
+
</TooltipContent>
|
|
280
|
+
</Tooltip>
|
|
333
281
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
282
|
+
<Tooltip>
|
|
283
|
+
<TooltipTrigger asChild>
|
|
284
|
+
{editingLayerId === activeLayer.id ? (
|
|
285
|
+
<button
|
|
286
|
+
type="button"
|
|
287
|
+
onClick={() => {
|
|
288
|
+
renameLayer(editingLayerId, editingLayerName);
|
|
289
|
+
setEditingLayerId(null);
|
|
290
|
+
setEditingLayerName("");
|
|
291
|
+
}}
|
|
292
|
+
aria-label={t("panel.layers.saveName")}
|
|
293
|
+
className={iconBtn}
|
|
294
|
+
>
|
|
295
|
+
<IconCheck />
|
|
296
|
+
</button>
|
|
297
|
+
) : (
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
onClick={() => {
|
|
301
|
+
setEditingLayerId(activeLayer.id);
|
|
302
|
+
setEditingLayerName(activeLayer.name);
|
|
303
|
+
}}
|
|
304
|
+
disabled={!activeLayer.editable}
|
|
305
|
+
aria-label={t("panel.layers.rename")}
|
|
306
|
+
className={iconBtn}
|
|
307
|
+
>
|
|
308
|
+
<IconEdit />
|
|
309
|
+
</button>
|
|
310
|
+
)}
|
|
311
|
+
</TooltipTrigger>
|
|
312
|
+
<TooltipContent className="z-[10000]">
|
|
313
|
+
{editingLayerId === activeLayer.id
|
|
314
|
+
? t("panel.layers.saveName")
|
|
315
|
+
: t("panel.layers.rename")}
|
|
316
|
+
</TooltipContent>
|
|
317
|
+
</Tooltip>
|
|
360
318
|
|
|
361
|
-
{
|
|
362
|
-
<
|
|
363
|
-
<
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
<
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}}
|
|
381
|
-
aria-label={t("panel.layers.confirmMergeAria")}
|
|
382
|
-
disabled={!canMergeLayers}
|
|
383
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
384
|
-
>
|
|
385
|
-
<IconCheck />
|
|
386
|
-
</button>
|
|
387
|
-
</TooltipTrigger>
|
|
388
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.confirmMerge")}</TooltipContent>
|
|
389
|
-
</Tooltip>
|
|
390
|
-
<Tooltip>
|
|
391
|
-
<TooltipTrigger asChild>
|
|
392
|
-
<button
|
|
393
|
-
type="button"
|
|
394
|
-
onClick={() => {
|
|
395
|
-
setIsMergingLayers(false);
|
|
396
|
-
setMergeLayerName("");
|
|
397
|
-
}}
|
|
398
|
-
aria-label={t("panel.layers.cancelMergeAria")}
|
|
399
|
-
className="rounded border border-border p-1 hover:bg-accent"
|
|
400
|
-
>
|
|
401
|
-
<IconClose />
|
|
402
|
-
</button>
|
|
403
|
-
</TooltipTrigger>
|
|
404
|
-
<TooltipContent className="z-[10000]">{t("panel.layers.cancelMerge")}</TooltipContent>
|
|
405
|
-
</Tooltip>
|
|
406
|
-
</div>
|
|
319
|
+
{editingLayerId === activeLayer.id ? (
|
|
320
|
+
<Tooltip>
|
|
321
|
+
<TooltipTrigger asChild>
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
onClick={() => {
|
|
325
|
+
setEditingLayerId(null);
|
|
326
|
+
setEditingLayerName("");
|
|
327
|
+
}}
|
|
328
|
+
aria-label={t("panel.layers.cancelEditName")}
|
|
329
|
+
className={iconBtn}
|
|
330
|
+
>
|
|
331
|
+
<IconClose />
|
|
332
|
+
</button>
|
|
333
|
+
</TooltipTrigger>
|
|
334
|
+
<TooltipContent className="z-[10000]">
|
|
335
|
+
{t("panel.layers.cancelEdit")}
|
|
336
|
+
</TooltipContent>
|
|
337
|
+
</Tooltip>
|
|
407
338
|
) : null}
|
|
408
|
-
|
|
339
|
+
|
|
340
|
+
<Tooltip>
|
|
341
|
+
<TooltipTrigger asChild>
|
|
342
|
+
<button
|
|
343
|
+
type="button"
|
|
344
|
+
onClick={() => {
|
|
345
|
+
const blockReason = getLayerDeleteBlockReason(activeLayer.id);
|
|
346
|
+
if (blockReason) {
|
|
347
|
+
showActionHint(blockReason);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
setConfirmDeleteLayerId(activeLayer.id);
|
|
351
|
+
const firstTarget = layers.find((l) => l.id !== activeLayer.id);
|
|
352
|
+
setDeleteTargetLayerId(firstTarget?.id ?? "");
|
|
353
|
+
setDeleteMode(activeLayer.isMapping ? "remove" : "move");
|
|
354
|
+
}}
|
|
355
|
+
disabled={!activeLayer.editable || activeLayer.locked}
|
|
356
|
+
aria-label={
|
|
357
|
+
!activeLayer.editable
|
|
358
|
+
? t("panel.layers.cannotDeleteDefault")
|
|
359
|
+
: activeLayer.locked
|
|
360
|
+
? t("panel.layers.cannotDeleteLocked")
|
|
361
|
+
: t("panel.layers.delete")
|
|
362
|
+
}
|
|
363
|
+
className={iconBtn}
|
|
364
|
+
>
|
|
365
|
+
<IconTrash />
|
|
366
|
+
</button>
|
|
367
|
+
</TooltipTrigger>
|
|
368
|
+
<TooltipContent className="z-[10000]">
|
|
369
|
+
{!activeLayer.editable
|
|
370
|
+
? t("panel.layers.cannotDeleteDefault")
|
|
371
|
+
: activeLayer.locked
|
|
372
|
+
? t("panel.layers.cannotDeleteLocked")
|
|
373
|
+
: t("panel.layers.delete")}
|
|
374
|
+
</TooltipContent>
|
|
375
|
+
</Tooltip>
|
|
376
|
+
</div>
|
|
377
|
+
) : null}
|
|
378
|
+
|
|
379
|
+
<Tooltip>
|
|
380
|
+
<TooltipTrigger asChild>
|
|
381
|
+
<button
|
|
382
|
+
type="button"
|
|
383
|
+
onClick={() => setDockVisible(false)}
|
|
384
|
+
aria-label={t("panel.layers.collapseInputAria")}
|
|
385
|
+
className={iconBtn}
|
|
386
|
+
>
|
|
387
|
+
<IconEyeOff />
|
|
388
|
+
</button>
|
|
389
|
+
</TooltipTrigger>
|
|
390
|
+
<TooltipContent className="z-[10000]">
|
|
391
|
+
{t("panel.layers.collapseInput")}
|
|
392
|
+
</TooltipContent>
|
|
393
|
+
</Tooltip>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
{isLayerPanelExpanded ? (
|
|
397
|
+
<TabsContent value={activeLayerId} className="mt-1 space-y-1">
|
|
398
|
+
<div className="rounded-md border border-border/50 bg-card/80 px-2 py-1.5">
|
|
399
|
+
<div
|
|
400
|
+
data-typo="caption"
|
|
401
|
+
className="mb-1 text-[11px] font-medium text-muted-foreground"
|
|
402
|
+
>
|
|
403
|
+
{t("panel.layers.selectMergeLayers")}
|
|
404
|
+
</div>
|
|
405
|
+
<div className="flex flex-wrap gap-x-3 gap-y-1">
|
|
406
|
+
{layers.map((layer) => (
|
|
407
|
+
<label
|
|
408
|
+
key={layer.id}
|
|
409
|
+
className="flex min-w-0 items-center gap-1.5 text-[11px]"
|
|
410
|
+
>
|
|
411
|
+
<Checkbox
|
|
412
|
+
checked={Boolean(layer.mergeSelected)}
|
|
413
|
+
disabled={layer.isMapping}
|
|
414
|
+
className="h-3.5 w-3.5 border border-foreground/70 bg-background data-[state=checked]:border-primary data-[state=checked]:bg-primary"
|
|
415
|
+
onCheckedChange={() => toggleLayerMergeSelected(layer.id)}
|
|
416
|
+
/>
|
|
417
|
+
<span
|
|
418
|
+
className="max-w-[120px] truncate font-medium text-foreground/90"
|
|
419
|
+
title={
|
|
420
|
+
layer.isMapping ? t("panel.layers.mappingCannotMerge") : layer.name
|
|
421
|
+
}
|
|
422
|
+
>
|
|
423
|
+
{layer.name}
|
|
424
|
+
{layer.isMapping ? t("panel.layers.cannotMergeSuffix") : ""}
|
|
425
|
+
</span>
|
|
426
|
+
</label>
|
|
427
|
+
))}
|
|
428
|
+
</div>
|
|
409
429
|
</div>
|
|
410
430
|
|
|
431
|
+
{isMergingLayers ? (
|
|
432
|
+
<div className="flex items-center gap-1.5 rounded-md border border-border/50 bg-card/80 px-2 py-1">
|
|
433
|
+
<span
|
|
434
|
+
data-typo="caption"
|
|
435
|
+
className="shrink-0 text-[11px] text-muted-foreground"
|
|
436
|
+
>
|
|
437
|
+
{t("panel.layers.mergedName")}
|
|
438
|
+
</span>
|
|
439
|
+
<Input
|
|
440
|
+
value={mergeLayerName}
|
|
441
|
+
onChange={(e) => setMergeLayerName(e.target.value)}
|
|
442
|
+
className="h-6 min-w-0 flex-1 text-[11px]"
|
|
443
|
+
placeholder={t("panel.layers.mergedNamePlaceholder")}
|
|
444
|
+
autoFocus
|
|
445
|
+
/>
|
|
446
|
+
<Tooltip>
|
|
447
|
+
<TooltipTrigger asChild>
|
|
448
|
+
<button
|
|
449
|
+
type="button"
|
|
450
|
+
onClick={() => {
|
|
451
|
+
if (!canMergeLayers) return;
|
|
452
|
+
mergeSelectedLayers(mergeLayerName || undefined);
|
|
453
|
+
setIsMergingLayers(false);
|
|
454
|
+
setMergeLayerName("");
|
|
455
|
+
}}
|
|
456
|
+
aria-label={t("panel.layers.confirmMergeAria")}
|
|
457
|
+
disabled={!canMergeLayers}
|
|
458
|
+
className={iconBtn}
|
|
459
|
+
>
|
|
460
|
+
<IconCheck />
|
|
461
|
+
</button>
|
|
462
|
+
</TooltipTrigger>
|
|
463
|
+
<TooltipContent className="z-[10000]">
|
|
464
|
+
{t("panel.layers.confirmMerge")}
|
|
465
|
+
</TooltipContent>
|
|
466
|
+
</Tooltip>
|
|
467
|
+
<Tooltip>
|
|
468
|
+
<TooltipTrigger asChild>
|
|
469
|
+
<button
|
|
470
|
+
type="button"
|
|
471
|
+
onClick={() => {
|
|
472
|
+
setIsMergingLayers(false);
|
|
473
|
+
setMergeLayerName("");
|
|
474
|
+
}}
|
|
475
|
+
aria-label={t("panel.layers.cancelMergeAria")}
|
|
476
|
+
className={iconBtn}
|
|
477
|
+
>
|
|
478
|
+
<IconClose />
|
|
479
|
+
</button>
|
|
480
|
+
</TooltipTrigger>
|
|
481
|
+
<TooltipContent className="z-[10000]">
|
|
482
|
+
{t("panel.layers.cancelMerge")}
|
|
483
|
+
</TooltipContent>
|
|
484
|
+
</Tooltip>
|
|
485
|
+
</div>
|
|
486
|
+
) : null}
|
|
487
|
+
</TabsContent>
|
|
488
|
+
) : null}
|
|
489
|
+
</Tabs>
|
|
490
|
+
</TooltipProvider>
|
|
491
|
+
</div>
|
|
411
492
|
);
|
|
412
493
|
}
|