* feat(components): match Figma asset browsing - Add thumbnail grid and list views grouped by source page - Support component drag insertion and main-component navigation - Keep asset previews working across document pages - Cover views, context actions, and drag-to-canvas behavior * fix(components): remove redundant Assets heading * fix(components): render crisp asset thumbnails - Render preview images at twice their display dimensions - Preserve the existing grid and list thumbnail sizes * fix(render): exclude ancestors from selection exports - Reparent selected export roots directly under the extracted page - Preserve absolute positions without drawing ancestor backgrounds - Cover transparent selection export around nested components * fix(components): address Assets panel review - Handle thumbnail export failures without stale previews - Share page lookup and thumbnail sizing across asset views - Make list actions keyboard accessible and honor requested export pages
86 lines
2.6 KiB
Vue
86 lines
2.6 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from 'reka-ui'
|
|
|
|
import { useI18n } from '@open-pencil/vue'
|
|
|
|
import AppMenu from '@/components/Shell/AppMenu.vue'
|
|
import SegmentedControl from '@/components/ui/SegmentedControl.vue'
|
|
import AssetsPanel from './assets-panel/AssetsPanel.vue'
|
|
import LayerTree from './LayerTree/LayerTree.vue'
|
|
import PagesPanel from './PagesPanel.vue'
|
|
|
|
const { menu, panels } = useI18n()
|
|
const activePanel = ref<'file' | 'assets'>('file')
|
|
const panelModel = computed({
|
|
get: () => activePanel.value,
|
|
set: (value: string) => {
|
|
if (value === 'file' || value === 'assets') activePanel.value = value
|
|
}
|
|
})
|
|
const panelOptions = computed(() => [
|
|
{ value: 'file', label: menu.value.file },
|
|
{ value: 'assets', label: panels.value.assets }
|
|
])
|
|
const panelTabsUI = { root: 'w-full' }
|
|
</script>
|
|
|
|
<template>
|
|
<aside
|
|
data-test-id="layers-panel"
|
|
class="flex min-w-0 flex-1 flex-col overflow-hidden border-r border-border bg-panel"
|
|
style="contain: paint layout style"
|
|
>
|
|
<AppMenu />
|
|
<div class="shrink-0 border-b border-border px-2 py-1.5">
|
|
<SegmentedControl
|
|
v-model="panelModel"
|
|
:options="panelOptions"
|
|
:label="panels.layers"
|
|
:ui="panelTabsUI"
|
|
>
|
|
<template #option="{ option }">
|
|
<span
|
|
:data-test-id="
|
|
option.value === 'file' ? 'left-panel-layers-tab' : 'left-panel-assets-tab'
|
|
"
|
|
class="truncate"
|
|
>
|
|
{{ option.label }}
|
|
</span>
|
|
</template>
|
|
</SegmentedControl>
|
|
</div>
|
|
<AssetsPanel v-if="activePanel === 'assets'" />
|
|
<SplitterGroup
|
|
v-else
|
|
direction="vertical"
|
|
auto-save-id="layers-layout"
|
|
class="flex-1 overflow-hidden"
|
|
>
|
|
<SplitterPanel
|
|
:default-size="30"
|
|
:min-size="10"
|
|
:max-size="60"
|
|
class="flex flex-col overflow-hidden"
|
|
>
|
|
<PagesPanel />
|
|
</SplitterPanel>
|
|
<SplitterResizeHandle class="group relative z-10 -my-1 h-2 cursor-row-resize">
|
|
<div
|
|
class="pointer-events-none absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-border"
|
|
/>
|
|
</SplitterResizeHandle>
|
|
<SplitterPanel :default-size="70" :min-size="20" class="flex flex-col overflow-hidden">
|
|
<header
|
|
data-test-id="layers-header"
|
|
class="shrink-0 px-3 py-2 text-[11px] font-semibold text-surface"
|
|
>
|
|
{{ panels.layers }}
|
|
</header>
|
|
<LayerTree data-test-id="layers-tree" />
|
|
</SplitterPanel>
|
|
</SplitterGroup>
|
|
</aside>
|
|
</template>
|