34 lines
1.5 KiB
Vue
34 lines
1.5 KiB
Vue
<script setup lang="ts">
|
|
import type { FileNode } from '@/contracts'
|
|
import { Document, Folder, FolderOpened } from '@element-plus/icons-vue'
|
|
import AppIcon from '@/components/common/AppIcon.vue'
|
|
|
|
defineProps<{ node: FileNode; activePath: string | null }>()
|
|
const emit = defineEmits<{
|
|
open: [node: FileNode]
|
|
contextMenu: [event: MouseEvent, node: FileNode]
|
|
}>()
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="tree-node" :class="{ active: node.path === activePath }"
|
|
@click="emit('open', node)" @contextmenu="emit('contextMenu', $event, node)">
|
|
<AppIcon :icon="node.type === 'folder' ? (node.is_open ? FolderOpened : Folder) : Document" :size="16" />
|
|
<span class="name">{{ node.name }}</span>
|
|
<span v-if="node.is_dirty">●</span>
|
|
</div>
|
|
<div v-if="node.type === 'folder' && node.is_open" class="children">
|
|
<FileTreeNode v-for="child in node.children ?? []" :key="child.id" :node="child" :active-path="activePath"
|
|
@open="emit('open', $event)" @context-menu="(event, target) => emit('contextMenu', event, target)" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.tree-node { display: flex; align-items: center; gap: var(--space-xs); min-height: 28px; padding: 0 var(--space-sm); border-radius: var(--radius-sm); cursor: pointer; }
|
|
.tree-node:hover, .tree-node.active { background: var(--color-background-secondary); }
|
|
.name { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.children { padding-left: var(--space-md); }
|
|
</style>
|