11"use client" ;
22
3- import { useCallback , useEffect , useMemo , useRef , useState , useSyncExternalStore } from "react" ;
3+ import { useMemo } from "react" ;
44import { ChevronRight , ChevronDown , Folder } from "lucide-react" ;
55import { SolidStorage } from "../lib/hooks/useSolidStorages" ;
6- import { FolderTreeChild , folderUrlsEqual , ensureTrailingSlash , getAuthenticatedSession , fetchContainerListing , foldersFromListing } from "../lib/helpers" ;
7- import { getContainerListing , loadContainerListing , subscribeContainerCache , getContainerCacheVersion } from "../lib/cache" ;
6+ import { FolderTreeChild , folderUrlsEqual , ensureTrailingSlash } from "../lib/helpers" ;
87import {
98 SidebarMenu ,
109 SidebarMenuButton ,
@@ -13,6 +12,7 @@ import {
1312 SidebarMenuSubItem ,
1413 SidebarMenuSkeleton ,
1514} from "@/components/ui/sidebar" ;
15+ import { useFolderTree } from "../lib/hooks/useFolderTree" ;
1616
1717interface FolderTreeProps {
1818 storages : SolidStorage [ ] ;
@@ -25,116 +25,16 @@ export default function FolderTree({
2525 currentFolderUrl,
2626 onNavigate
2727} : FolderTreeProps ) {
28- const [ expandedUrls , setExpandedUrls ] = useState < Set < string > > ( new Set ( ) ) ;
29- const [ loadingUrls , setLoadingUrls ] = useState < Set < string > > ( new Set ( ) ) ;
30- const [ errorByUrl , setErrorByUrl ] = useState < Record < string , string > > ( { } ) ;
31-
32- const cacheVersion = useSyncExternalStore (
33- subscribeContainerCache ,
34- getContainerCacheVersion ,
35- getContainerCacheVersion ,
36- )
37-
38- // Children come from the shared cache, not a local copy.
39- // cacheVersion makes this recompute when listings are written or invalidated.
40- const childrenByUrl = useMemo ( ( ) => {
41- // cacheVersion is read so listings recompute after cache writes/invalidation.
42- void cacheVersion ;
43- const next : Record < string , FolderTreeChild [ ] > = { } ;
44- for ( const url of expandedUrls ) {
45- const cached = getContainerListing ( url ) ;
46- if ( cached ) {
47- next [ url ] = foldersFromListing ( cached ) ;
48- }
49- }
50- return next ;
51- } , [ cacheVersion , expandedUrls ] )
52-
53- const expandedUrlsRef = useRef ( expandedUrls ) ;
54- expandedUrlsRef . current = expandedUrls ;
28+ const { expandedUrls, loadingUrls, errorByUrl, childrenByUrl, toggleExpand } =
29+ useFolderTree ( ) ;
5530
5631 // Keep the current folder URL in one shape so highlight checks stay reliable.
5732 const normalizedCurrentFolderUrl = useMemo ( ( ) => (
5833 currentFolderUrl ? ensureTrailingSlash ( currentFolderUrl ) : null
5934 ) , [ currentFolderUrl ] ) ;
6035
61- // Load child folders via the shared container cache (same data as main browse).
62- const loadChildren = useCallback ( async ( folderUrl : string ) => {
63- const normalizedUrl = ensureTrailingSlash ( folderUrl ) ;
64-
65- if ( getContainerListing ( normalizedUrl ) ) {
66- return ;
67- }
68-
69- setLoadingUrls ( ( prev ) => {
70- const next = new Set ( prev ) ;
71- next . add ( normalizedUrl ) ;
72- return next ;
73- } ) ;
74-
75- setErrorByUrl ( ( prev ) => {
76- if ( ! ( normalizedUrl in prev ) ) return prev ;
77- const next = { ...prev } ;
78- delete next [ normalizedUrl ] ;
79- return next ;
80- } ) ;
81-
82- try {
83- const { fetch } = getAuthenticatedSession ( ) ;
84- await loadContainerListing (
85- normalizedUrl ,
86- ( ) => fetchContainerListing ( normalizedUrl , fetch ) ,
87- ) ;
88- } catch ( error ) {
89- const message =
90- error instanceof Error ? error . message : "Failed to load folders" ;
91- setErrorByUrl ( ( prev ) => ( { ...prev , [ normalizedUrl ] : message } ) ) ;
92- } finally {
93- setLoadingUrls ( ( prev ) => {
94- const next = new Set ( prev ) ;
95- next . delete ( normalizedUrl ) ;
96- return next ;
97- } ) ;
98- }
99- } , [ ] ) ;
100-
101-
102- // After cache invalidation, refetch any expanded folder that no longer has a listing.
103- useEffect ( ( ) => {
104- return subscribeContainerCache ( ( ) => {
105- for ( const url of expandedUrlsRef . current ) {
106- if ( ! getContainerListing ( url ) ) {
107- void loadChildren ( url )
108- }
109- }
110- } )
111- } , [ loadChildren ] )
112-
113- // Open or close a folder branch. Fetch children only when opening.
114- const toggleExpand = useCallback ( async ( folderUrl : string ) => {
115- const normalizedUrl = ensureTrailingSlash ( folderUrl ) ;
116-
117- const isExpanded = expandedUrls . has ( normalizedUrl ) ;
118- if ( isExpanded ) {
119- setExpandedUrls ( ( prev ) => {
120- const next = new Set ( prev ) ;
121- next . delete ( normalizedUrl ) ;
122- return next ;
123- } ) ;
124- return ;
125- }
126-
127- setExpandedUrls ( ( prev ) => {
128- const next = new Set ( prev ) ;
129- next . add ( normalizedUrl ) ;
130- return next ;
131- } ) ;
132-
133- await loadChildren ( normalizedUrl ) ;
134- } , [ expandedUrls , loadChildren ] ) ;
135-
13636 // Render one folder row and its nested children when expanded.
137- const renderNode = useCallback ( ( node : FolderTreeChild ) => {
37+ function renderNode ( node : FolderTreeChild ) {
13838 const nodeUrl = ensureTrailingSlash ( node . url ) ;
13939 const isExpanded = expandedUrls . has ( nodeUrl ) ;
14040 const isLoading = loadingUrls . has ( nodeUrl ) ;
@@ -194,16 +94,7 @@ export default function FolderTree({
19494 ) }
19595 </ SidebarMenuItem >
19696 ) ;
197- } , [
198- childrenByUrl ,
199- errorByUrl ,
200- expandedUrls ,
201- loadingUrls ,
202- normalizedCurrentFolderUrl ,
203- onNavigate ,
204- toggleExpand ,
205- ] ,
206- ) ;
97+ }
20798
20899 // Turn storage roots into the same shape used by child folder nodes.
209100 const rootNodes : FolderTreeChild [ ] = useMemo (
0 commit comments