Skip to content

Commit 37a0e4a

Browse files
committed
cleanup
1 parent 02b4f4c commit 37a0e4a

File tree

2 files changed

+52
-181
lines changed

2 files changed

+52
-181
lines changed

apps/array/src/main/preload.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,18 +220,15 @@ contextBridge.exposeInMainWorld("electronAPI", {
220220
ipcRenderer.invoke("get-changed-files-head", repoPath),
221221
getFileAtHead: (repoPath: string, filePath: string): Promise<string | null> =>
222222
ipcRenderer.invoke("get-file-at-head", repoPath, filePath),
223-
// Lazy directory listing
224223
listDirectory: (
225224
dirPath: string,
226225
): Promise<
227226
Array<{ name: string; path: string; type: "file" | "directory" }>
228227
> => ipcRenderer.invoke("fs:list-directory", dirPath),
229-
// Watcher control
230228
watcherStart: (repoPath: string): Promise<void> =>
231229
ipcRenderer.invoke("watcher:start", repoPath),
232230
watcherStop: (repoPath: string): Promise<void> =>
233231
ipcRenderer.invoke("watcher:stop", repoPath),
234-
// Events
235232
onDirectoryChanged: (
236233
listener: (data: { repoPath: string; dirPath: string }) => void,
237234
): (() => void) => {
Lines changed: 52 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,11 @@
11
import { PanelMessage } from "@components/ui/PanelMessage";
22
import { usePanelLayoutStore } from "@features/panels";
33
import { useTaskData } from "@features/task-detail/hooks/useTaskData";
4-
import {
5-
FileIcon,
6-
FolderIcon,
7-
FolderOpenIcon,
8-
SpinnerGap,
9-
} from "@phosphor-icons/react";
4+
import { FileIcon, FolderIcon, FolderOpenIcon } from "@phosphor-icons/react";
105
import { Box, Flex, Text } from "@radix-ui/themes";
116
import type { Task } from "@shared/types";
12-
import {
13-
createContext,
14-
useCallback,
15-
useContext,
16-
useEffect,
17-
useMemo,
18-
useRef,
19-
useState,
20-
} from "react";
7+
import { useQuery, useQueryClient } from "@tanstack/react-query";
8+
import { useEffect, useState } from "react";
219

2210
interface FileTreePanelProps {
2311
taskId: string;
@@ -30,29 +18,6 @@ interface DirectoryEntry {
3018
type: "file" | "directory";
3119
}
3220

33-
type DirectoryChangeCallback = (dirPath: string) => void;
34-
35-
interface DirectoryChangeContextValue {
36-
subscribe: (dirPath: string, callback: DirectoryChangeCallback) => () => void;
37-
}
38-
39-
const DirectoryChangeContext =
40-
createContext<DirectoryChangeContextValue | null>(null);
41-
42-
function useDirectoryChange(
43-
dirPath: string | null,
44-
callback: DirectoryChangeCallback,
45-
) {
46-
const ctx = useContext(DirectoryChangeContext);
47-
const callbackRef = useRef(callback);
48-
callbackRef.current = callback;
49-
50-
useEffect(() => {
51-
if (!ctx || !dirPath) return;
52-
return ctx.subscribe(dirPath, (path) => callbackRef.current(path));
53-
}, [ctx, dirPath]);
54-
}
55-
5621
interface LazyTreeItemProps {
5722
entry: DirectoryEntry;
5823
depth: number;
@@ -62,62 +27,31 @@ interface LazyTreeItemProps {
6227

6328
function LazyTreeItem({ entry, depth, taskId, repoPath }: LazyTreeItemProps) {
6429
const [isExpanded, setIsExpanded] = useState(false);
65-
const [children, setChildren] = useState<DirectoryEntry[] | null>(null);
66-
const [isLoading, setIsLoading] = useState(false);
6730
const openFile = usePanelLayoutStore((state) => state.openFile);
6831

69-
const loadChildren = useCallback(async () => {
70-
if (entry.type !== "directory") return;
32+
const { data: children } = useQuery({
33+
queryKey: ["directory", entry.path],
34+
queryFn: () => window.electronAPI.listDirectory(entry.path),
35+
enabled: entry.type === "directory" && isExpanded,
36+
staleTime: Infinity,
37+
});
7138

72-
setIsLoading(true);
73-
try {
74-
const entries = await window.electronAPI.listDirectory(entry.path);
75-
setChildren(entries);
76-
} catch (error) {
77-
console.error("Failed to load directory:", error);
78-
setChildren([]);
79-
} finally {
80-
setIsLoading(false);
81-
}
82-
}, [entry.path, entry.type]);
83-
84-
const handleClick = async () => {
39+
const handleClick = () => {
8540
if (entry.type === "directory") {
86-
if (!isExpanded && children === null) {
87-
await loadChildren();
88-
}
8941
setIsExpanded(!isExpanded);
9042
} else {
91-
const relativePath = entry.path.replace(`${repoPath}/`, "");
92-
openFile(taskId, relativePath);
43+
openFile(taskId, entry.path.replace(`${repoPath}/`, ""));
9344
}
9445
};
9546

96-
useDirectoryChange(
97-
entry.type === "directory" && isExpanded ? entry.path : null,
98-
useCallback(() => {
99-
window.electronAPI
100-
.listDirectory(entry.path)
101-
.then((entries) => {
102-
setChildren(entries);
103-
})
104-
.catch((error) => {
105-
console.error("Failed to refresh directory:", error);
106-
});
107-
}, [entry.path]),
108-
);
109-
11047
return (
11148
<Box>
11249
<Flex
11350
align="center"
11451
gap="2"
11552
py="1"
11653
px="2"
117-
style={{
118-
paddingLeft: `${depth * 16 + 8}px`,
119-
cursor: "pointer",
120-
}}
54+
style={{ paddingLeft: `${depth * 16 + 8}px`, cursor: "pointer" }}
12155
className="rounded hover:bg-gray-2"
12256
onClick={handleClick}
12357
>
@@ -133,101 +67,43 @@ function LazyTreeItem({ entry, depth, taskId, repoPath }: LazyTreeItemProps) {
13367
<Text size="2" style={{ userSelect: "none" }}>
13468
{entry.name}
13569
</Text>
136-
{isLoading && (
137-
<SpinnerGap
138-
size={12}
139-
className="animate-spin"
140-
color="var(--gray-9)"
141-
/>
142-
)}
14370
</Flex>
144-
{entry.type === "directory" && isExpanded && children && (
145-
<Box>
146-
{children.map((child) => (
147-
<LazyTreeItem
148-
key={child.path}
149-
entry={child}
150-
depth={depth + 1}
151-
taskId={taskId}
152-
repoPath={repoPath}
153-
/>
154-
))}
155-
</Box>
156-
)}
71+
{isExpanded &&
72+
children?.map((child) => (
73+
<LazyTreeItem
74+
key={child.path}
75+
entry={child}
76+
depth={depth + 1}
77+
taskId={taskId}
78+
repoPath={repoPath}
79+
/>
80+
))}
15781
</Box>
15882
);
15983
}
16084

16185
export function FileTreePanel({ taskId, task }: FileTreePanelProps) {
16286
const taskData = useTaskData({ taskId, initialTask: task });
16387
const repoPath = taskData.repoPath;
164-
165-
const [rootEntries, setRootEntries] = useState<DirectoryEntry[] | null>(null);
166-
const [isLoading, setIsLoading] = useState(true);
167-
const [error, setError] = useState<string | null>(null);
168-
169-
const subscribersRef = useRef<Map<string, Set<DirectoryChangeCallback>>>(
170-
new Map(),
171-
);
172-
173-
const contextValue = useMemo<DirectoryChangeContextValue>(
174-
() => ({
175-
subscribe: (dirPath, callback) => {
176-
if (!subscribersRef.current.has(dirPath)) {
177-
subscribersRef.current.set(dirPath, new Set());
178-
}
179-
subscribersRef.current.get(dirPath)?.add(callback);
180-
return () => {
181-
subscribersRef.current.get(dirPath)?.delete(callback);
182-
if (subscribersRef.current.get(dirPath)?.size === 0) {
183-
subscribersRef.current.delete(dirPath);
184-
}
185-
};
186-
},
187-
}),
188-
[],
189-
);
88+
const queryClient = useQueryClient();
89+
90+
const {
91+
data: rootEntries,
92+
isLoading,
93+
error,
94+
} = useQuery({
95+
queryKey: ["directory", repoPath],
96+
queryFn: () => window.electronAPI.listDirectory(repoPath!),
97+
enabled: !!repoPath,
98+
staleTime: Infinity,
99+
});
190100

191101
useEffect(() => {
192102
if (!repoPath) return;
193-
194-
setIsLoading(true);
195-
setError(null);
196-
197-
window.electronAPI
198-
.listDirectory(repoPath)
199-
.then((entries) => {
200-
setRootEntries(entries);
201-
})
202-
.catch((err) => {
203-
console.error("Failed to load root directory:", err);
204-
setError("Failed to load files");
205-
})
206-
.finally(() => {
207-
setIsLoading(false);
208-
});
209-
}, [repoPath]);
210-
211-
useEffect(() => {
212-
if (!repoPath) return;
213-
214-
const unsub = window.electronAPI.onDirectoryChanged(({ dirPath }) => {
215-
if (dirPath === repoPath) {
216-
window.electronAPI.listDirectory(repoPath).then((entries) => {
217-
setRootEntries(entries);
218-
});
219-
}
220-
221-
const callbacks = subscribersRef.current.get(dirPath);
222-
if (callbacks) {
223-
for (const cb of callbacks) {
224-
cb(dirPath);
225-
}
226-
}
103+
return window.electronAPI.onDirectoryChanged(({ dirPath }) => {
104+
queryClient.invalidateQueries({ queryKey: ["directory", dirPath] });
227105
});
228-
229-
return unsub;
230-
}, [repoPath]);
106+
}, [repoPath, queryClient]);
231107

232108
if (!repoPath) {
233109
return <PanelMessage>No repository path available</PanelMessage>;
@@ -238,28 +114,26 @@ export function FileTreePanel({ taskId, task }: FileTreePanelProps) {
238114
}
239115

240116
if (error) {
241-
return <PanelMessage color="red">{error}</PanelMessage>;
117+
return <PanelMessage color="red">Failed to load files</PanelMessage>;
242118
}
243119

244-
if (!rootEntries || rootEntries.length === 0) {
120+
if (!rootEntries?.length) {
245121
return <PanelMessage>No files found</PanelMessage>;
246122
}
247123

248124
return (
249-
<DirectoryChangeContext.Provider value={contextValue}>
250-
<Box height="100%" overflowY="auto" p="4">
251-
<Flex direction="column" gap="1">
252-
{rootEntries.map((entry) => (
253-
<LazyTreeItem
254-
key={entry.path}
255-
entry={entry}
256-
depth={0}
257-
taskId={taskId}
258-
repoPath={repoPath}
259-
/>
260-
))}
261-
</Flex>
262-
</Box>
263-
</DirectoryChangeContext.Provider>
125+
<Box height="100%" overflowY="auto" p="4">
126+
<Flex direction="column" gap="1">
127+
{rootEntries.map((entry) => (
128+
<LazyTreeItem
129+
key={entry.path}
130+
entry={entry}
131+
depth={0}
132+
taskId={taskId}
133+
repoPath={repoPath}
134+
/>
135+
))}
136+
</Flex>
137+
</Box>
264138
);
265139
}

0 commit comments

Comments
 (0)