| 1 | import type { TreeViewNode } from 'reactive-vscode' |
| 2 | import type { TreeItem } from 'vscode' |
| 3 | import type { SlidevProject } from '../projects' |
| 4 | import { isDeepEqual } from '@antfu/utils' |
| 5 | import { computed, defineService, shallowRef, useTreeView, watch } from 'reactive-vscode' |
| 6 | import { ThemeIcon, TreeItemCollapsibleState, Uri } from 'vscode' |
| 7 | import { activeEntry, projects } from '../projects' |
| 8 | import { getSlidesTitle } from '../utils/getSlidesTitle' |
| 9 | import { toRelativePath } from '../utils/toRelativePath' |
| 10 | |
| 11 | /** |
| 12 | * No projects has dependency files. Then it would be weired to show a collapse button. |
| 13 | */ |
| 14 | const nothingToCollapse = computed(() => [...projects.values()] |
| 15 | .every(project => Object.keys(project.data.markdownFiles).length <= 1)) |
| 16 | |
| 17 | function getProjectTreeItem(project: SlidevProject): TreeItem { |
| 18 | const active = activeEntry.value === project.entry |
| 19 | return { |
| 20 | label: toRelativePath(project.entry), |
| 21 | description: `${getSlidesTitle(project.data)}${project.detected.value?.ready ? ` (port: ${project.detected.value.port})` : ''}`, |
| 22 | resourceUri: Uri.file(project.entry), |
| 23 | iconPath: new ThemeIcon(active ? 'eye' : 'eye-closed'), |
| 24 | collapsibleState: nothingToCollapse.value ? TreeItemCollapsibleState.None : active ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed, |
| 25 | contextValue: `<project><${active ? 'active' : 'inactive'}>`, |
| 26 | command: { |
| 27 | title: 'Open', |
| 28 | command: 'vscode.open', |
| 29 | arguments: [Uri.file(project.entry)], |
| 30 | }, |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | function getFileTreeItem(file: string): TreeItem { |
| 35 | return { |
| 36 | resourceUri: Uri.file(file), |
| 37 | iconPath: new ThemeIcon('file'), |
| 38 | collapsibleState: TreeItemCollapsibleState.None, |
| 39 | command: { |
| 40 | title: 'Open', |
| 41 | command: 'vscode.open', |
| 42 | arguments: [Uri.file(file)], |
| 43 | }, |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | export const useProjectsTree = defineService(() => { |
| 48 | const treeData = computed(() => { |
| 49 | return [...projects.values()] |
| 50 | .sort((a, b) => a.entry.localeCompare(b.entry, undefined, { numeric: true })) |
| 51 | .map(project => ({ |
| 52 | treeItem: getProjectTreeItem(project), |
| 53 | children: Object.keys(project.data.markdownFiles) |
| 54 | .filter(file => file.toLowerCase() !== project.entry.toLowerCase()) |
| 55 | .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) |
| 56 | .map(file => ({ treeItem: getFileTreeItem(file) })), |
| 57 | })) |
| 58 | }) |
| 59 | |
| 60 | const treeItems = shallowRef<TreeViewNode[]>([]) |
| 61 | watch(treeData, (treeData) => { |
| 62 | if (!isDeepEqual(treeItems.value, treeData)) { |
| 63 | treeItems.value = treeData |
| 64 | } |
| 65 | }, { immediate: true }) |
| 66 | |
| 67 | const treeView = useTreeView('slidev-projects-tree', treeItems, { |
| 68 | showCollapseAll: true, |
| 69 | }) |
| 70 | |
| 71 | return treeView |
| 72 | }) |
| 73 |