diff options
| author | Adam <[email protected]> | 2025-10-03 09:04:28 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-10-03 09:04:28 -0500 |
| commit | 3fa280d21878ae391674a21758199df3d2d8c3b5 (patch) | |
| tree | f70c6ecafffeecc8e7a59dc9acef66c59a9ea54a /packages/desktop/src/components/file-tree.tsx | |
| parent | 1d58b5548287a3e32ffce3abdcf0f2db08fdb155 (diff) | |
| download | opencode-3fa280d21878ae391674a21758199df3d2d8c3b5.tar.gz opencode-3fa280d21878ae391674a21758199df3d2d8c3b5.zip | |
chore: app -> desktop
Diffstat (limited to 'packages/desktop/src/components/file-tree.tsx')
| -rw-r--r-- | packages/desktop/src/components/file-tree.tsx | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/packages/desktop/src/components/file-tree.tsx b/packages/desktop/src/components/file-tree.tsx new file mode 100644 index 000000000..d31255ced --- /dev/null +++ b/packages/desktop/src/components/file-tree.tsx @@ -0,0 +1,110 @@ +import { useLocal } from "@/context" +import type { LocalFile } from "@/context/local" +import { Collapsible, FileIcon, Tooltip } from "@/ui" +import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js" +import { Dynamic } from "solid-js/web" + +export default function FileTree(props: { + path: string + class?: string + nodeClass?: string + level?: number + onFileClick?: (file: LocalFile) => void +}) { + const local = useLocal() + const level = props.level ?? 0 + + const Node = (p: ParentProps & ComponentProps<"div"> & { node: LocalFile; as?: "div" | "button" }) => ( + <Dynamic + component={p.as ?? "div"} + classList={{ + "p-0.5 w-full flex items-center gap-x-2 hover:bg-background-element cursor-pointer": true, + "bg-background-element": local.file.active()?.path === p.node.path, + [props.nodeClass ?? ""]: !!props.nodeClass, + }} + style={`padding-left: ${level * 10}px`} + draggable={true} + onDragStart={(e: any) => { + const evt = e as globalThis.DragEvent + evt.dataTransfer!.effectAllowed = "copy" + evt.dataTransfer!.setData("text/plain", `file:${p.node.path}`) + + // Create custom drag image without margins + const dragImage = document.createElement("div") + dragImage.className = + "flex items-center gap-x-2 px-2 py-1 bg-background-element rounded-md border border-border-1" + dragImage.style.position = "absolute" + dragImage.style.top = "-1000px" + + // Copy only the icon and text content without padding + const icon = e.currentTarget.querySelector("svg") + const text = e.currentTarget.querySelector("span") + if (icon && text) { + dragImage.innerHTML = icon.outerHTML + text.outerHTML + } + + document.body.appendChild(dragImage) + evt.dataTransfer!.setDragImage(dragImage, 0, 12) + setTimeout(() => document.body.removeChild(dragImage), 0) + }} + {...p} + > + {p.children} + <span + classList={{ + "text-xs whitespace-nowrap truncate": true, + "text-text-muted/40": p.node.ignored, + "text-text-muted/80": !p.node.ignored, + "!text-text": local.file.active()?.path === p.node.path, + "!text-primary": local.file.changed(p.node.path), + }} + > + {p.node.name} + </span> + <Show when={local.file.changed(p.node.path)}> + <span class="ml-auto mr-1 w-1.5 h-1.5 rounded-full bg-primary/50 shrink-0" /> + </Show> + </Dynamic> + ) + + return ( + <div class={`flex flex-col ${props.class}`}> + <For each={local.file.children(props.path)}> + {(node) => ( + <Tooltip forceMount={false} openDelay={2000} value={node.path} placement="right"> + <Switch> + <Match when={node.type === "directory"}> + <Collapsible + class="w-full" + forceMount={false} + open={local.file.node(node.path)?.expanded} + onOpenChange={(open) => (open ? local.file.expand(node.path) : local.file.collapse(node.path))} + > + <Collapsible.Trigger> + <Node node={node}> + <Collapsible.Arrow size={16} class="text-text-muted/60 ml-1" /> + <FileIcon + node={node} + expanded={local.file.node(node.path).expanded} + class="text-text-muted/60 -ml-1" + /> + </Node> + </Collapsible.Trigger> + <Collapsible.Content> + <FileTree path={node.path} level={level + 1} onFileClick={props.onFileClick} /> + </Collapsible.Content> + </Collapsible> + </Match> + <Match when={node.type === "file"}> + <Node node={node} as="button" onClick={() => props.onFileClick?.(node)}> + <div class="w-4 shrink-0" /> + <FileIcon node={node} class="text-primary" /> + </Node> + </Match> + </Switch> + </Tooltip> + )} + </For> + </div> + ) +} |
