@arronqzy/vue-view 0.1.24 → 0.1.26
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/README.md +1 -11
- package/package.json +3 -3
- package/src/index.ts +25 -1
- package/src/panel/VueViewOnlinePreview.vue +25 -0
- package/src/panel/VueViewPanel.vue +188 -63
- package/src/panel/components/ElementsLayer.vue +1 -1
- package/src/panel/components/MaterialPreview.vue +28 -27
- package/src/panel/components/MaterialSidebar.vue +288 -225
- package/src/panel/components/MaterialSidebarTreeNode.vue +15 -10
- package/src/panel/components/MoveableLayer.vue +8 -0
- package/src/panel/components/PanelCanvas.vue +1 -0
- package/src/panel/components/PanelConfigSidebar.vue +23 -41
- package/src/panel/components/PanelRulers.vue +158 -40
- package/src/panel/components/SelectLayer.vue +2 -1
- package/src/panel/components/config/ConfigFieldGroup.vue +3 -3
- package/src/panel/components/config/ConfigSection.vue +5 -5
- package/src/panel/hooks/useWorkspaceProjects.ts +209 -67
- package/src/panel/lib/panel-ruler-canvas.ts +8 -4
- package/src/panel/library/abuilder-storage.ts +101 -3
- package/src/panel/library/abuilder-view-handle.ts +100 -0
- package/src/panel/library/capture-preview-snapshot.ts +800 -0
- package/src/panel/library/event-subscription.ts +70 -5
- package/src/panel/library/preview-snapshot.ts +252 -0
- 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,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
3
|
import { computed, ref } from "vue";
|
|
4
|
-
import {
|
|
4
|
+
import { Empty, Input, Select, Tabs, Tooltip } from "ant-design-vue";
|
|
5
5
|
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
|
|
6
6
|
import { getPanelMessages } from "../constants/messages";
|
|
7
7
|
import {
|
|
@@ -48,7 +48,6 @@ const props = withDefaults(defineProps<MaterialSidebarProps>(), {
|
|
|
48
48
|
const leftTab = ref<"materials" | "tree">("materials");
|
|
49
49
|
const activeCategoryId = ref<MaterialCategoryId>("charts");
|
|
50
50
|
const keyword = ref("");
|
|
51
|
-
const treeKeyword = ref("");
|
|
52
51
|
const referenceOnlyTree = ref(false);
|
|
53
52
|
const draggingTreeNodeId = ref<string | null>(null);
|
|
54
53
|
const dragOverLayerId = ref<string | null>(null);
|
|
@@ -56,14 +55,26 @@ const expandedKeys = ref<Record<string, boolean>>({ root: true });
|
|
|
56
55
|
|
|
57
56
|
const categories = computed(() => { void locale.value; return getDefaultCategories(t); });
|
|
58
57
|
const normalizedKeyword = computed(() => keyword.value.trim().toLowerCase());
|
|
59
|
-
const isSearching = computed(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
const isSearching = computed(
|
|
59
|
+
() => leftTab.value === "materials" && normalizedKeyword.value.length > 0
|
|
60
|
+
);
|
|
61
|
+
const isTreeSearching = computed(
|
|
62
|
+
() => leftTab.value === "tree" && normalizedKeyword.value.length > 0
|
|
63
|
+
);
|
|
64
|
+
const normalizedTreeKeyword = computed(() =>
|
|
65
|
+
isTreeSearching.value ? normalizedKeyword.value : ""
|
|
66
|
+
);
|
|
67
|
+
const searchPlaceholder = computed(() =>
|
|
68
|
+
leftTab.value === "tree"
|
|
69
|
+
? t("panel.material.treeSearchPlaceholder")
|
|
70
|
+
: t("panel.material.searchPlaceholder")
|
|
65
71
|
);
|
|
66
72
|
|
|
73
|
+
function onLeftTabChange(key: string | number) {
|
|
74
|
+
leftTab.value = key === "tree" ? "tree" : "materials";
|
|
75
|
+
keyword.value = "";
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
const matchedItems = computed(() => {
|
|
68
79
|
if (!isSearching.value) return [];
|
|
69
80
|
const result: Array<MaterialItem & { categoryTitle: string }> = [];
|
|
@@ -78,6 +89,15 @@ const matchedItems = computed(() => {
|
|
|
78
89
|
return result;
|
|
79
90
|
});
|
|
80
91
|
|
|
92
|
+
const activeCategory = computed(
|
|
93
|
+
() =>
|
|
94
|
+
categories.value.find((c) => c.id === activeCategoryId.value) ?? categories.value[0]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const displayItems = computed(() =>
|
|
98
|
+
isSearching.value ? matchedItems.value : (activeCategory.value?.items ?? [])
|
|
99
|
+
);
|
|
100
|
+
|
|
81
101
|
const elementsByLayer = computed(() => {
|
|
82
102
|
const map = new Map<string, PanelElement[]>();
|
|
83
103
|
for (const layer of props.layers) map.set(layer.id, []);
|
|
@@ -270,258 +290,301 @@ const treeSearchEmpty = computed(
|
|
|
270
290
|
<template>
|
|
271
291
|
<aside
|
|
272
292
|
:class="[
|
|
273
|
-
'grid h-full w-full border-r border-border bg-muted/
|
|
293
|
+
'rv-material-sidebar grid h-full min-h-0 w-full overflow-hidden border-r border-border/60 bg-muted/20 text-foreground',
|
|
274
294
|
props.class ?? '',
|
|
275
295
|
]"
|
|
276
296
|
style="grid-template-rows: auto 1fr"
|
|
277
297
|
>
|
|
278
|
-
<div class="border-b border-border bg-background/
|
|
298
|
+
<div class="border-b border-border/50 bg-background/70 px-2 py-1.5 backdrop-blur-sm">
|
|
279
299
|
<Input
|
|
280
300
|
v-model:value="keyword"
|
|
281
301
|
size="small"
|
|
282
|
-
:placeholder="
|
|
283
|
-
|
|
302
|
+
:placeholder="searchPlaceholder"
|
|
303
|
+
:aria-label="searchPlaceholder"
|
|
304
|
+
class="text-[12px]"
|
|
284
305
|
/>
|
|
285
306
|
</div>
|
|
286
307
|
|
|
287
|
-
<Tabs
|
|
308
|
+
<Tabs
|
|
309
|
+
:active-key="leftTab"
|
|
310
|
+
class="flex h-full min-h-0 flex-col overflow-hidden"
|
|
311
|
+
size="small"
|
|
312
|
+
@update:active-key="onLeftTabChange"
|
|
313
|
+
>
|
|
288
314
|
<Tabs.TabPane key="materials" :tab="t('panel.material.tabMaterials')" class="min-h-0 flex-1">
|
|
289
|
-
<div class="
|
|
290
|
-
<div
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
: 'text-muted-foreground hover:bg-accent/60 hover:text-accent-foreground',
|
|
300
|
-
]"
|
|
301
|
-
@click="activeCategoryId = c.id"
|
|
315
|
+
<div class="flex h-full min-h-0 flex-col overflow-hidden">
|
|
316
|
+
<div
|
|
317
|
+
v-if="!isSearching"
|
|
318
|
+
class="shrink-0 border-b border-border/40 bg-background/80 px-2 py-1.5"
|
|
319
|
+
>
|
|
320
|
+
<Select
|
|
321
|
+
v-model:value="activeCategoryId"
|
|
322
|
+
size="small"
|
|
323
|
+
class="w-full text-[12px] font-semibold"
|
|
324
|
+
:aria-label="activeCategory?.title"
|
|
302
325
|
>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
<div :class="`overflow-auto ${themedScrollbarClass}`">
|
|
308
|
-
<div class="px-2.5 py-2.5 text-xs font-semibold">
|
|
309
|
-
{{ isSearching ? t("panel.material.searchResults", { count: matchedItems.length }) : activeCategory.title }}
|
|
310
|
-
</div>
|
|
311
|
-
<div class="grid gap-2 px-2.5 pb-3">
|
|
312
|
-
<button
|
|
313
|
-
v-for="it in isSearching ? matchedItems : activeCategory.items"
|
|
314
|
-
:key="isSearching ? `${it.id}-${(it as any).categoryTitle}` : it.id"
|
|
315
|
-
type="button"
|
|
316
|
-
draggable="true"
|
|
317
|
-
class="cursor-pointer rounded-xl border border-border bg-card px-2 py-2 text-left text-xs text-card-foreground hover:bg-accent/60"
|
|
318
|
-
@dragstart="onMaterialDragStart($event, it)"
|
|
326
|
+
<Select.Option
|
|
327
|
+
v-for="category in categories"
|
|
328
|
+
:key="category.id"
|
|
329
|
+
:value="category.id"
|
|
319
330
|
>
|
|
320
|
-
<
|
|
321
|
-
<
|
|
322
|
-
<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
331
|
+
<span class="inline-flex w-full min-w-0 items-center justify-between gap-2">
|
|
332
|
+
<span class="truncate font-medium">{{ category.title }}</span>
|
|
333
|
+
<span class="tabular-nums text-[11px] font-normal text-muted-foreground">
|
|
334
|
+
{{ category.items.length }}
|
|
335
|
+
</span>
|
|
336
|
+
</span>
|
|
337
|
+
</Select.Option>
|
|
338
|
+
</Select>
|
|
339
|
+
</div>
|
|
340
|
+
<div :class="`min-h-0 flex-1 overflow-auto ${themedScrollbarClass}`">
|
|
341
|
+
<div class="px-2 pb-2.5 pt-1.5">
|
|
342
|
+
<div class="mb-1.5 flex items-baseline justify-between gap-2 px-0.5">
|
|
343
|
+
<div class="truncate text-[12px] font-semibold text-foreground/90">
|
|
344
|
+
<template v-if="isSearching">
|
|
345
|
+
{{ t("panel.material.searchResults", { count: matchedItems.length }) }}
|
|
346
|
+
</template>
|
|
347
|
+
<template v-else>{{ activeCategory?.title }}</template>
|
|
348
|
+
</div>
|
|
349
|
+
<div
|
|
350
|
+
v-if="!isSearching"
|
|
351
|
+
class="tabular-nums text-[11px] text-muted-foreground"
|
|
352
|
+
>
|
|
353
|
+
{{ activeCategory?.items.length ?? 0 }}
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
<div class="rv-material-grid">
|
|
357
|
+
<button
|
|
358
|
+
v-for="it in displayItems"
|
|
359
|
+
:key="isSearching ? `${it.id}-${(it as any).categoryTitle}` : it.id"
|
|
360
|
+
type="button"
|
|
361
|
+
draggable="true"
|
|
362
|
+
class="rv-material-card cursor-pointer rounded-lg border border-border/50 bg-card px-1.5 py-1.5 text-left text-card-foreground shadow-[0_1px_1px_hsl(var(--foreground)/0.03)] transition-colors hover:border-border hover:bg-accent/40"
|
|
363
|
+
@dragstart="onMaterialDragStart($event, it)"
|
|
364
|
+
>
|
|
365
|
+
<div class="flex flex-col gap-1">
|
|
366
|
+
<MaterialPreview :id="it.id" />
|
|
367
|
+
<div class="min-w-0 px-0.5">
|
|
368
|
+
<div class="truncate text-center text-[11px] font-normal text-muted-foreground">
|
|
369
|
+
{{ it.title }}
|
|
370
|
+
</div>
|
|
371
|
+
<div
|
|
372
|
+
v-if="isSearching"
|
|
373
|
+
class="mt-0.5 truncate text-center text-[10px] text-muted-foreground"
|
|
374
|
+
>
|
|
375
|
+
{{ (it as any).categoryTitle }}
|
|
376
|
+
</div>
|
|
329
377
|
</div>
|
|
330
378
|
</div>
|
|
379
|
+
</button>
|
|
380
|
+
<Empty
|
|
381
|
+
v-if="isSearching && matchedItems.length === 0"
|
|
382
|
+
class="py-5"
|
|
383
|
+
:description="t('panel.material.emptyMaterialsTitle')"
|
|
384
|
+
>
|
|
385
|
+
<template #description>
|
|
386
|
+
<span class="text-[12px] font-medium">{{ t("panel.material.emptyMaterialsTitle") }}</span>
|
|
387
|
+
<div class="text-[11px] text-muted-foreground">
|
|
388
|
+
{{ t("panel.material.emptyMaterialsDesc") }}
|
|
389
|
+
</div>
|
|
390
|
+
</template>
|
|
391
|
+
</Empty>
|
|
392
|
+
<div
|
|
393
|
+
v-else-if="!isSearching && (activeCategory?.items.length ?? 0) === 0"
|
|
394
|
+
class="px-1 py-3 text-[11px] text-muted-foreground"
|
|
395
|
+
>
|
|
396
|
+
{{ t("panel.material.emptyMaterialsTitle") }}
|
|
331
397
|
</div>
|
|
332
|
-
</
|
|
333
|
-
<Empty
|
|
334
|
-
v-if="isSearching && matchedItems.length === 0"
|
|
335
|
-
class="py-5"
|
|
336
|
-
:description="t('panel.material.emptyMaterialsTitle')"
|
|
337
|
-
>
|
|
338
|
-
<template #description>
|
|
339
|
-
<span class="text-xs">{{ t("panel.material.emptyMaterialsTitle") }}</span>
|
|
340
|
-
<div class="text-[11px] text-muted-foreground">
|
|
341
|
-
{{ t("panel.material.emptyMaterialsDesc") }}
|
|
342
|
-
</div>
|
|
343
|
-
</template>
|
|
344
|
-
</Empty>
|
|
398
|
+
</div>
|
|
345
399
|
</div>
|
|
346
400
|
</div>
|
|
347
401
|
</div>
|
|
348
402
|
</Tabs.TabPane>
|
|
349
403
|
|
|
350
404
|
<Tabs.TabPane key="tree" :tab="t('panel.material.tabTree')" class="min-h-0 flex-1">
|
|
351
|
-
<div :class="`h-full overflow-auto px-
|
|
352
|
-
<div class="
|
|
353
|
-
<
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
<
|
|
362
|
-
<Switch v-model:checked="referenceOnlyTree" size="small" :aria-label="t('panel.material.referenceOnlyTree')" />
|
|
363
|
-
</div>
|
|
364
|
-
|
|
365
|
-
<div class="rounded border border-border bg-card">
|
|
366
|
-
<div class="flex items-center gap-1 px-2 py-1.5 font-medium">
|
|
405
|
+
<div :class="`h-full overflow-auto px-1.5 py-1.5 text-xs ${themedScrollbarClass}`">
|
|
406
|
+
<div class="flex items-center gap-0.5 rounded-sm px-0.5 py-0.5 font-medium hover:bg-accent/40">
|
|
407
|
+
<button
|
|
408
|
+
type="button"
|
|
409
|
+
class="flex h-5 w-4 shrink-0 items-center justify-center rounded text-[11px] text-muted-foreground hover:bg-accent"
|
|
410
|
+
@click="setExpanded('root', !isExpanded('root', true))"
|
|
411
|
+
>
|
|
412
|
+
{{ isExpanded("root", true) ? "▾" : "▸" }}
|
|
413
|
+
</button>
|
|
414
|
+
<span class="min-w-0 flex-1 truncate text-[12px]">root</span>
|
|
415
|
+
<Tooltip :title="t('panel.material.referenceOnlyTree')">
|
|
367
416
|
<button
|
|
368
417
|
type="button"
|
|
369
|
-
class="flex h-
|
|
370
|
-
|
|
418
|
+
class="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md border transition-colors"
|
|
419
|
+
:class="
|
|
420
|
+
referenceOnlyTree
|
|
421
|
+
? 'border-primary/50 bg-primary/15 text-primary'
|
|
422
|
+
: 'border-border/70 bg-background/80 text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
423
|
+
"
|
|
424
|
+
:aria-label="t('panel.material.referenceOnlyTree')"
|
|
425
|
+
:aria-pressed="referenceOnlyTree"
|
|
426
|
+
@click="referenceOnlyTree = !referenceOnlyTree"
|
|
371
427
|
>
|
|
372
|
-
|
|
428
|
+
<svg
|
|
429
|
+
viewBox="0 0 24 24"
|
|
430
|
+
class="h-3.5 w-3.5"
|
|
431
|
+
fill="none"
|
|
432
|
+
stroke="currentColor"
|
|
433
|
+
:stroke-width="referenceOnlyTree ? 2.2 : 2"
|
|
434
|
+
>
|
|
435
|
+
<path
|
|
436
|
+
d="M4 5h16l-5.5 6.5V18l-5 2.5v-9L4 5Z"
|
|
437
|
+
:fill="referenceOnlyTree ? 'currentColor' : 'none'"
|
|
438
|
+
:fill-opacity="referenceOnlyTree ? 0.2 : 0"
|
|
439
|
+
/>
|
|
440
|
+
<circle cx="16.5" cy="16.5" r="2.2" />
|
|
441
|
+
<path d="M14.8 15.2 12 12.5" />
|
|
442
|
+
</svg>
|
|
373
443
|
</button>
|
|
374
|
-
|
|
375
|
-
|
|
444
|
+
</Tooltip>
|
|
445
|
+
</div>
|
|
376
446
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
447
|
+
<div v-show="isExpanded('root', true)" class="ml-[7px] border-l border-border/45 pl-1.5">
|
|
448
|
+
<template v-for="layer in layers" :key="layer.id">
|
|
449
|
+
<div v-if="!(isTreeSearching && getRootNodes(layer.id).length === 0)">
|
|
450
|
+
<div
|
|
451
|
+
:class="[
|
|
452
|
+
'flex min-w-0 items-center gap-0.5 rounded-sm py-0.5 pr-0.5 transition-colors',
|
|
453
|
+
layer.isMapping ? 'text-violet-800 dark:text-violet-200' : 'text-foreground',
|
|
454
|
+
dragOverLayerId === layer.id
|
|
455
|
+
? getLayerDropState(layer).canDropIntoLayer
|
|
456
|
+
? 'bg-primary/12 ring-1 ring-inset ring-primary/35'
|
|
457
|
+
: 'bg-destructive/10 ring-1 ring-inset ring-destructive/35'
|
|
458
|
+
: 'hover:bg-accent/50',
|
|
459
|
+
]"
|
|
460
|
+
:title="t('panel.material.dragToThisLayer')"
|
|
461
|
+
@dragover="onLayerDragOver($event, layer)"
|
|
462
|
+
@dragleave="dragOverLayerId === layer.id && (dragOverLayerId = null)"
|
|
463
|
+
@drop="onLayerDrop($event, layer)"
|
|
464
|
+
>
|
|
465
|
+
<button
|
|
466
|
+
type="button"
|
|
467
|
+
class="flex h-5 w-4 shrink-0 items-center justify-center rounded text-[11px] text-muted-foreground hover:bg-accent"
|
|
468
|
+
@click="setExpanded(`layer:${layer.id}`, !isExpanded(`layer:${layer.id}`, true))"
|
|
394
469
|
>
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
type="button"
|
|
414
|
-
class="flex h-7 w-7 shrink-0 items-center justify-center rounded text-sm text-muted-foreground hover:bg-accent"
|
|
415
|
-
@click="
|
|
416
|
-
setExpanded(`layer:${layer.id}`, !isExpanded(`layer:${layer.id}`, true))
|
|
417
|
-
"
|
|
418
|
-
>
|
|
419
|
-
{{
|
|
420
|
-
isExpanded(`layer:${layer.id}`, true) ? "▾" : "▸"
|
|
421
|
-
}}
|
|
422
|
-
</button>
|
|
423
|
-
<span class="min-w-0 flex-1 truncate text-xs font-medium text-foreground">
|
|
424
|
-
{{ layer.name }}
|
|
425
|
-
<span class="font-normal text-muted-foreground">
|
|
426
|
-
({{ getRootNodes(layer.id).length }})
|
|
427
|
-
</span>
|
|
428
|
-
</span>
|
|
429
|
-
<span
|
|
430
|
-
v-if="layer.isMapping"
|
|
431
|
-
class="shrink-0 rounded-md border-2 border-violet-600 bg-violet-500/22 px-1.5 py-0.5 text-[10px] font-semibold text-violet-950 dark:border-violet-400 dark:bg-violet-500/35 dark:text-violet-50"
|
|
432
|
-
>
|
|
433
|
-
{{ t("panel.material.mappingLayer") }}
|
|
434
|
-
</span>
|
|
435
|
-
<span
|
|
436
|
-
v-if="dragOverLayerId === layer.id"
|
|
437
|
-
:class="[
|
|
438
|
-
'ml-auto shrink-0 rounded px-1.5 py-0.5 text-[10px]',
|
|
439
|
-
getLayerDropState(layer).canDropIntoLayer
|
|
440
|
-
? 'bg-primary/15 text-primary'
|
|
441
|
-
: 'bg-destructive/15 text-destructive',
|
|
442
|
-
]"
|
|
443
|
-
>
|
|
444
|
-
{{
|
|
445
|
-
getLayerDropState(layer).canDropIntoLayer
|
|
446
|
-
? t("panel.material.willMoveToLayer")
|
|
447
|
-
: getLayerDropState(layer).dropBlockReason
|
|
448
|
-
}}
|
|
449
|
-
</span>
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
<div
|
|
453
|
-
:class="[
|
|
454
|
-
'mb-2 rounded-md border border-dashed px-2 py-1.5 text-[10px] transition-colors',
|
|
455
|
-
dragOverLayerId === layer.id
|
|
456
|
-
? getLayerDropState(layer).canDropIntoLayer
|
|
457
|
-
? 'border-primary/50 bg-primary/10 text-primary'
|
|
458
|
-
: 'border-destructive/50 bg-destructive/10 text-destructive'
|
|
459
|
-
: 'border-border/50 bg-muted/20 text-muted-foreground hover:border-border hover:bg-muted/35',
|
|
460
|
-
]"
|
|
461
|
-
:title="t('panel.material.dragToThisLayer')"
|
|
462
|
-
@dragover="onLayerDragOver($event, layer)"
|
|
463
|
-
@dragleave="dragOverLayerId === layer.id && (dragOverLayerId = null)"
|
|
464
|
-
@drop="onLayerDrop($event, layer)"
|
|
470
|
+
{{ isExpanded(`layer:${layer.id}`, true) ? "▾" : "▸" }}
|
|
471
|
+
</button>
|
|
472
|
+
<svg
|
|
473
|
+
viewBox="0 0 24 24"
|
|
474
|
+
class="h-3.5 w-3.5 shrink-0"
|
|
475
|
+
fill="none"
|
|
476
|
+
stroke="currentColor"
|
|
477
|
+
stroke-width="2"
|
|
478
|
+
>
|
|
479
|
+
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" />
|
|
480
|
+
</svg>
|
|
481
|
+
<div class="flex min-w-0 flex-1 items-center gap-0.5">
|
|
482
|
+
<span class="min-w-0 truncate text-[12px] font-medium">
|
|
483
|
+
{{ layer.name }}
|
|
484
|
+
</span>
|
|
485
|
+
<span
|
|
486
|
+
v-if="layer.isMapping"
|
|
487
|
+
class="shrink-0 rounded border border-violet-500/60 bg-violet-500/15 px-1 text-[10px] font-medium"
|
|
465
488
|
>
|
|
466
|
-
{{
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}}
|
|
473
|
-
</div>
|
|
474
|
-
|
|
475
|
-
<div v-show="isExpanded(`layer:${layer.id}`, true)">
|
|
476
|
-
<div
|
|
477
|
-
v-if="getRootNodes(layer.id).length === 0"
|
|
478
|
-
class="rounded border border-border/40 bg-muted/15 py-2 pl-3 text-[11px] text-muted-foreground"
|
|
479
|
-
>
|
|
480
|
-
{{ t("panel.material.emptyLayer") }}
|
|
481
|
-
</div>
|
|
482
|
-
<MaterialSidebarTreeNode
|
|
483
|
-
v-for="node in getRootNodes(layer.id)"
|
|
484
|
-
:key="node.id"
|
|
485
|
-
:node="node"
|
|
486
|
-
:level="2"
|
|
487
|
-
:path="`${layer.id}/${node.id}`"
|
|
488
|
-
:visited="new Set()"
|
|
489
|
-
:selected-ids="selectedIds"
|
|
490
|
-
:layer-by-id="layerById"
|
|
491
|
-
:elements-by-layer="elementsByLayer"
|
|
492
|
-
:children-by-grid-by-layer="childrenByGridByLayer"
|
|
493
|
-
:expanded-keys="expandedKeys"
|
|
494
|
-
:normalized-tree-keyword="normalizedTreeKeyword"
|
|
495
|
-
:is-tree-searching="isTreeSearching"
|
|
496
|
-
:dragging-tree-node-id="draggingTreeNodeId"
|
|
497
|
-
@toggle-expanded="setExpanded"
|
|
498
|
-
@select-node="(id, lid) => onSelectNode?.(id, lid)"
|
|
499
|
-
@node-context-menu="(p) => onNodeContextMenu?.(p)"
|
|
500
|
-
@delete-node="(id) => onDeleteNode?.(id)"
|
|
501
|
-
@copy-node="(id, mode) => onCopyNode?.(id, mode)"
|
|
502
|
-
@drag-start="(id) => (draggingTreeNodeId = id)"
|
|
503
|
-
@drag-end="
|
|
504
|
-
draggingTreeNodeId = null;
|
|
505
|
-
dragOverLayerId = null;
|
|
506
|
-
"
|
|
507
|
-
/>
|
|
508
|
-
</div>
|
|
509
|
-
</Card>
|
|
489
|
+
{{ t("panel.material.mappingLayer") }}
|
|
490
|
+
</span>
|
|
491
|
+
</div>
|
|
492
|
+
<span class="ml-auto shrink-0 pl-1 tabular-nums text-[11px] text-muted-foreground">
|
|
493
|
+
{{ getRootNodes(layer.id).length }}
|
|
494
|
+
</span>
|
|
510
495
|
</div>
|
|
511
|
-
</template>
|
|
512
496
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
497
|
+
<div
|
|
498
|
+
v-show="isExpanded(`layer:${layer.id}`, true)"
|
|
499
|
+
class="ml-[7px] border-l border-border/45 pl-1.5"
|
|
500
|
+
>
|
|
501
|
+
<div
|
|
502
|
+
v-if="getRootNodes(layer.id).length === 0"
|
|
503
|
+
class="py-1 pl-1 text-[11px] text-muted-foreground"
|
|
504
|
+
>
|
|
505
|
+
{{ t("panel.material.emptyLayer") }}
|
|
518
506
|
</div>
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
507
|
+
<MaterialSidebarTreeNode
|
|
508
|
+
v-for="node in getRootNodes(layer.id)"
|
|
509
|
+
:key="node.id"
|
|
510
|
+
:node="node"
|
|
511
|
+
:level="0"
|
|
512
|
+
:path="`${layer.id}/${node.id}`"
|
|
513
|
+
:visited="new Set()"
|
|
514
|
+
:selected-ids="selectedIds"
|
|
515
|
+
:layer-by-id="layerById"
|
|
516
|
+
:elements-by-layer="elementsByLayer"
|
|
517
|
+
:children-by-grid-by-layer="childrenByGridByLayer"
|
|
518
|
+
:expanded-keys="expandedKeys"
|
|
519
|
+
:normalized-tree-keyword="normalizedTreeKeyword"
|
|
520
|
+
:is-tree-searching="isTreeSearching"
|
|
521
|
+
:dragging-tree-node-id="draggingTreeNodeId"
|
|
522
|
+
@toggle-expanded="setExpanded"
|
|
523
|
+
@select-node="(id, lid) => onSelectNode?.(id, lid)"
|
|
524
|
+
@node-context-menu="(p) => onNodeContextMenu?.(p)"
|
|
525
|
+
@delete-node="(id) => onDeleteNode?.(id)"
|
|
526
|
+
@copy-node="(id, mode) => onCopyNode?.(id, mode)"
|
|
527
|
+
@drag-start="(id) => (draggingTreeNodeId = id)"
|
|
528
|
+
@drag-end="
|
|
529
|
+
draggingTreeNodeId = null;
|
|
530
|
+
dragOverLayerId = null;
|
|
531
|
+
"
|
|
532
|
+
/>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
</template>
|
|
536
|
+
|
|
537
|
+
<Empty v-if="treeSearchEmpty" class="my-2 py-5" :description="t('panel.material.emptyNodesTitle')">
|
|
538
|
+
<template #description>
|
|
539
|
+
<span class="text-xs">{{ t("panel.material.emptyNodesTitle") }}</span>
|
|
540
|
+
<div class="text-[11px] text-muted-foreground">
|
|
541
|
+
{{ t("panel.material.emptyNodesDesc") }}
|
|
542
|
+
</div>
|
|
543
|
+
</template>
|
|
544
|
+
</Empty>
|
|
522
545
|
</div>
|
|
523
546
|
</div>
|
|
524
547
|
</Tabs.TabPane>
|
|
525
548
|
</Tabs>
|
|
526
549
|
</aside>
|
|
527
550
|
</template>
|
|
551
|
+
|
|
552
|
+
<style scoped>
|
|
553
|
+
:deep(.ant-tabs) {
|
|
554
|
+
display: flex;
|
|
555
|
+
min-height: 0;
|
|
556
|
+
height: 100%;
|
|
557
|
+
flex-direction: column;
|
|
558
|
+
}
|
|
559
|
+
:deep(.ant-tabs-nav) {
|
|
560
|
+
margin: 0;
|
|
561
|
+
padding: 0.5rem 0.5rem 0;
|
|
562
|
+
flex-shrink: 0;
|
|
563
|
+
}
|
|
564
|
+
:deep(.ant-tabs-content-holder),
|
|
565
|
+
:deep(.ant-tabs-content) {
|
|
566
|
+
display: flex;
|
|
567
|
+
min-height: 0;
|
|
568
|
+
height: 100%;
|
|
569
|
+
flex: 1 1 auto;
|
|
570
|
+
flex-direction: column;
|
|
571
|
+
overflow: hidden;
|
|
572
|
+
}
|
|
573
|
+
/* 勿给未激活 pane 设 display:flex,否则会盖掉 antd 的 display:none 留下大块空白 */
|
|
574
|
+
:deep(.ant-tabs-tabpane-active) {
|
|
575
|
+
display: flex !important;
|
|
576
|
+
min-height: 0;
|
|
577
|
+
height: 100%;
|
|
578
|
+
flex: 1 1 auto;
|
|
579
|
+
flex-direction: column;
|
|
580
|
+
overflow: hidden;
|
|
581
|
+
}
|
|
582
|
+
:deep(.ant-select-selector) {
|
|
583
|
+
white-space: nowrap !important;
|
|
584
|
+
}
|
|
585
|
+
:deep(.ant-select-selection-item) {
|
|
586
|
+
overflow: hidden;
|
|
587
|
+
text-overflow: ellipsis;
|
|
588
|
+
white-space: nowrap !important;
|
|
589
|
+
}
|
|
590
|
+
</style>
|