mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
132 lines
3.2 KiB
TypeScript
132 lines
3.2 KiB
TypeScript
|
|
import { createContext, useContext, useEffect, useMemo, useState } from "react"
|
||
|
|
import {
|
||
|
|
FileTree,
|
||
|
|
useFileTree,
|
||
|
|
useFileTreeSelection,
|
||
|
|
} from "@pierre/trees/react"
|
||
|
|
|
||
|
|
import type { GitStatusEntry } from "@pierre/trees"
|
||
|
|
import type { ReviewDiffFile } from "@/lib/api"
|
||
|
|
import { Skeleton } from "@/components/ui/skeleton"
|
||
|
|
import { treeThemeStyle } from "@/components/agents/AgentGitPanel"
|
||
|
|
|
||
|
|
export interface ReviewSidebarData {
|
||
|
|
title: string
|
||
|
|
files: Array<ReviewDiffFile> | null
|
||
|
|
selected: string | null
|
||
|
|
viewed: Set<string>
|
||
|
|
onSelect: (path: string) => void
|
||
|
|
}
|
||
|
|
|
||
|
|
const ReviewSidebarContext = createContext<{
|
||
|
|
data: ReviewSidebarData | null
|
||
|
|
setData: (data: ReviewSidebarData | null) => void
|
||
|
|
} | null>(null)
|
||
|
|
|
||
|
|
export function ReviewSidebarProvider({
|
||
|
|
children,
|
||
|
|
}: {
|
||
|
|
children: React.ReactNode
|
||
|
|
}) {
|
||
|
|
const [data, setData] = useState<ReviewSidebarData | null>(null)
|
||
|
|
const value = useMemo(() => ({ data, setData }), [data])
|
||
|
|
return (
|
||
|
|
<ReviewSidebarContext.Provider value={value}>
|
||
|
|
{children}
|
||
|
|
</ReviewSidebarContext.Provider>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useReviewSidebarData(): ReviewSidebarData | null {
|
||
|
|
return useContext(ReviewSidebarContext)?.data ?? null
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useRegisterReviewSidebar(data: ReviewSidebarData) {
|
||
|
|
const setData = useContext(ReviewSidebarContext)?.setData
|
||
|
|
useEffect(() => {
|
||
|
|
if (!setData) return
|
||
|
|
setData(data)
|
||
|
|
return () => setData(null)
|
||
|
|
}, [setData, data])
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ReviewFileTree({ data }: { data: ReviewSidebarData }) {
|
||
|
|
return (
|
||
|
|
<div className="flex min-h-0 flex-1 flex-col pb-2">
|
||
|
|
<div className="px-4 py-1 text-[10px] font-semibold tracking-wide text-[var(--ui-text-dim)] uppercase">
|
||
|
|
{data.title}
|
||
|
|
</div>
|
||
|
|
{!data.files ? (
|
||
|
|
<div className="px-4 pt-1">
|
||
|
|
<Skeleton className="h-40 w-full" />
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<ReviewFileTreeExplorer
|
||
|
|
files={data.files}
|
||
|
|
selected={data.selected}
|
||
|
|
onSelect={data.onSelect}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function ReviewFileTreeExplorer({
|
||
|
|
files,
|
||
|
|
selected,
|
||
|
|
onSelect,
|
||
|
|
}: {
|
||
|
|
files: Array<ReviewDiffFile>
|
||
|
|
selected: string | null
|
||
|
|
onSelect: (path: string) => void
|
||
|
|
}) {
|
||
|
|
const paths = useMemo(() => files.map((file) => file.path), [files])
|
||
|
|
const gitStatus = useMemo<Array<GitStatusEntry>>(
|
||
|
|
() => files.map((file) => ({ path: file.path, status: file.status })),
|
||
|
|
[files]
|
||
|
|
)
|
||
|
|
|
||
|
|
const { model } = useFileTree({
|
||
|
|
paths,
|
||
|
|
gitStatus,
|
||
|
|
initialExpansion: "open",
|
||
|
|
flattenEmptyDirectories: true,
|
||
|
|
icons: "standard",
|
||
|
|
})
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
model.resetPaths(paths)
|
||
|
|
}, [model, paths])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
model.setGitStatus(gitStatus)
|
||
|
|
}, [model, gitStatus])
|
||
|
|
|
||
|
|
const selection = useFileTreeSelection(model)
|
||
|
|
useEffect(() => {
|
||
|
|
const path = selection[0]
|
||
|
|
if (path) onSelect(path)
|
||
|
|
}, [selection, onSelect])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (selected) {
|
||
|
|
model.scrollToPath(selected, { focus: false })
|
||
|
|
}
|
||
|
|
}, [model, selected])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-0 flex-1">
|
||
|
|
<FileTree
|
||
|
|
model={model}
|
||
|
|
style={
|
||
|
|
{
|
||
|
|
height: "100%",
|
||
|
|
...treeThemeStyle(),
|
||
|
|
"--trees-theme-sidebar-bg": "transparent",
|
||
|
|
} as React.CSSProperties
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|