You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

148 lines
3.9 KiB
JavaScript

import { useSignal } from '@preact/signals'
import clsx from 'clsx'
import { useEffect, useState } from 'preact/hooks'
export const EXAMPLE_TREE = {
type: 'root',
children: [
{
type: 'folder',
name: 'bin/',
children: [
{
type: 'file',
name: 'a.out',
},
],
},
{
type: 'file',
name: 'main.c',
},
{
type: 'file',
name: 'data.csv',
},
],
}
const flattenTree = (node, depth = 0, path = []) => {
if (node.type === 'root') {
return node.children.flatMap(entry => flattenTree(entry, depth, []))
}
if (node.type === 'folder')
return [
{
depth,
type: 'folder',
name: node.name,
path: path.join('/') + '/',
},
...node.children.flatMap(entry => flattenTree(entry, depth + 1, [...path, node.name])),
]
if (node.type === 'file') {
return [
{
depth,
type: 'file',
name: node.name,
path: path.join('/'),
},
]
}
throw new Error(`invalid node type "${node?.type ?? '<unknown>'}"`)
}
const TreeViewNode = ({ listDir, actionOpenFile, node, depth, path }) => {
if (node.type === 'root') {
const [children, setChildren] = useState([])
useEffect(async () => {
setChildren(await listDir(path))
}, [])
return children.flatMap(entry => (
<TreeViewNode
listDir={listDir}
actionOpenFile={actionOpenFile}
node={entry}
depth={0}
path={'/' + entry.name}
/>
))
}
if (node.type === 'folder') {
const [collapsed, setCollapsed] = useState(true)
const [children, setChildren] = useState([])
useEffect(async () => {
setChildren(collapsed ? [] : await listDir(path))
}, [collapsed])
return (
<>
<div
class="entry folder"
style={{ '--depth': depth }}
title={path}
onClick={() => setCollapsed(c => !c)}
>
<div class="material-symbols-outlined">folder</div>
<div class="name">{node.name}/</div>
<div class="actions"></div>
</div>
{!collapsed &&
children.flatMap(entry => (
<TreeViewNode
listDir={listDir}
actionOpenFile={actionOpenFile}
node={entry}
depth={depth + 1}
path={path + '/' + entry.name}
/>
))}
</>
)
}
if (node.type === 'file') {
return (
<div
class="entry file"
style={{ '--depth': depth }}
title={path}
onClick={() => {
console.log('open', path)
actionOpenFile?.(path)
}}
>
<div class="material-symbols-outlined">description</div>
<div class="name">{node.name}</div>
<div class="actions"></div>
</div>
)
}
}
const ICONS = {
['folder']: 'folder',
['file']: 'description',
}
export const TreeView = ({ listDir, actionOpenFile, rootPath }) => {
return (
<div class="tree-view">
<TreeViewNode
listDir={listDir}
actionOpenFile={actionOpenFile}
node={{ type: 'root' }}
depth={0}
path={rootPath}
/>
</div>
)
}