Skip to content

Commit 891c4cf

Browse files
committed
refactor: prepare state layer for react
1 parent 29e8729 commit 891c4cf

File tree

4 files changed

+103
-63
lines changed

4 files changed

+103
-63
lines changed

src/features/dashboard/sandbox/inspect/context.tsx

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,59 @@ export function SandboxInspectProvider({
3737
const { sandbox } = useSandboxContext()
3838
const storeRef = useRef<FilesystemStore>(null)
3939
const eventManagerRef = useRef<FilesystemEventManager>(null)
40+
const operationsRef = useRef<FilesystemOperations>(null)
4041

41-
if (!storeRef.current && sandbox) {
42-
storeRef.current = createFilesystemStore(rootPath)
43-
eventManagerRef.current = new FilesystemEventManager(
44-
storeRef.current,
45-
sandbox,
46-
rootPath
47-
)
48-
}
42+
useLayoutEffect(() => {
43+
if (!storeRef.current && sandbox) {
44+
storeRef.current = createFilesystemStore(rootPath)
45+
eventManagerRef.current = new FilesystemEventManager(
46+
storeRef.current,
47+
sandbox,
48+
rootPath
49+
)
50+
51+
const eventManager = eventManagerRef.current
52+
const store = storeRef.current
53+
54+
operationsRef.current = {
55+
loadDirectory: async (path: string) => {
56+
await eventManager.loadDirectory(path)
57+
},
58+
selectNode: (path: string) => {
59+
store.getState().setSelected(path)
60+
},
61+
toggleDirectory: async (path: string) => {
62+
const normalizedPath = normalizePath(path)
63+
const state = store.getState()
64+
const node = state.getNode(normalizedPath)
65+
66+
if (!node || node.type !== FileType.DIR) {
67+
console.log(`Cannot toggle non-directory node at path: ${path}`)
68+
return
69+
}
70+
71+
const newExpandedState = !node.isExpanded
72+
console.log(
73+
`Toggling directory ${path} to ${newExpandedState ? 'expanded' : 'collapsed'}`
74+
)
75+
76+
state.setExpanded(normalizedPath, newExpandedState)
77+
78+
if (newExpandedState) {
79+
if (!node.isLoaded) {
80+
console.log(`Loading unloaded directory: ${path}`)
81+
await eventManager.loadDirectory(normalizedPath)
82+
} else {
83+
console.log(`Directory already loaded: ${path}`)
84+
}
85+
}
86+
},
87+
refreshDirectory: async (path: string) => {
88+
await eventManager.refreshDirectory(path)
89+
},
90+
}
91+
}
92+
}, [sandbox, rootPath])
4993

5094
useLayoutEffect(() => {
5195
const initializeRoot = async () => {
@@ -90,49 +134,18 @@ export function SandboxInspectProvider({
90134
}
91135
}, [rootPath, sandbox])
92136

93-
const operations = useMemo<FilesystemOperations>(() => {
94-
if (!storeRef.current || !eventManagerRef.current) {
95-
throw new Error('Filesystem store or event manager not initialized')
96-
}
97-
const eventManager = eventManagerRef.current
98-
const store = storeRef.current
99-
100-
return {
101-
loadDirectory: async (path: string) => {
102-
await eventManager.loadDirectory(path)
103-
},
104-
selectNode: (path: string) => {
105-
store.getState().setSelected(path)
106-
},
107-
toggleDirectory: async (path: string) => {
108-
const normalizedPath = normalizePath(path)
109-
const state = store.getState()
110-
const node = state.getNode(normalizedPath)
111-
112-
if (!node || node.type !== FileType.DIR) return
113-
114-
const newExpandedState = !node.isExpanded
115-
state.setExpanded(normalizedPath, newExpandedState)
116-
117-
if (newExpandedState) {
118-
if (!node.isLoaded) {
119-
await eventManager.loadDirectory(normalizedPath)
120-
}
121-
}
122-
},
123-
refreshDirectory: async (path: string) => {
124-
await eventManager.refreshDirectory(path)
125-
},
126-
}
127-
}, [])
128-
129-
if (!storeRef.current || !eventManagerRef.current || !sandbox) {
137+
if (
138+
!storeRef.current ||
139+
!eventManagerRef.current ||
140+
!sandbox ||
141+
!operationsRef.current
142+
) {
130143
return null
131144
}
132145

133146
const contextValue: SandboxInspectContextValue = {
134147
store: storeRef.current,
135-
operations: operations,
148+
operations: operationsRef.current,
136149
eventManager: eventManagerRef.current,
137150
}
138151

src/features/dashboard/sandbox/inspect/filesystem/store.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { create } from 'zustand'
44
import { immer } from 'zustand/middleware/immer'
5+
import { enableMapSet } from 'immer'
56
import {
67
normalizePath,
78
getParentPath,
@@ -10,6 +11,8 @@ import {
1011
import { FileType } from 'e2b'
1112
import { FilesystemNode } from './types'
1213

14+
enableMapSet()
15+
1316
interface FilesystemStatics {
1417
rootPath: string
1518
}
@@ -49,6 +52,10 @@ export type FilesystemStoreData = FilesystemStatics &
4952
FilesystemMutations &
5053
FilesystemComputed
5154

55+
// to retain reference-stable arrays of children per directory path
56+
const childrenCache: Map<string, { ref: string[]; result: FilesystemNode[] }> =
57+
new Map()
58+
5259
export const createFilesystemStore = (rootPath: string) =>
5360
create<FilesystemStoreData>()(
5461
immer((set, get) => ({
@@ -255,9 +262,17 @@ export const createFilesystemStore = (rootPath: string) =>
255262

256263
if (!node || node.type === FileType.FILE) return []
257264

258-
return node.children
265+
const cached = childrenCache.get(normalizedPath)
266+
if (cached && cached.ref === node.children) {
267+
return cached.result
268+
}
269+
270+
const result = node.children
259271
.map((childPath) => state.nodes.get(childPath))
260272
.filter((child): child is FilesystemNode => child !== undefined)
273+
274+
childrenCache.set(normalizedPath, { ref: node.children, result })
275+
return result
261276
},
262277

263278
getNode: (path: string) => {

src/features/dashboard/sandbox/inspect/hooks/use-directory.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { useMemo } from 'react'
44
import { useSandboxInspectContext } from '../context'
55
import { FileType } from 'e2b'
66
import { FilesystemNode } from '../filesystem/types'
7+
import { useStore } from 'zustand'
78

89
/**
910
* Hook for accessing directory children with automatic updates
1011
*/
1112
export function useDirectoryChildren(path: string): FilesystemNode[] {
1213
const { store } = useSandboxInspectContext()
1314

14-
return store((state) => state.getChildren(path))
15+
return useStore(store, (state) => state.getChildren(path))
1516
}
1617

1718
/**
@@ -20,17 +21,27 @@ export function useDirectoryChildren(path: string): FilesystemNode[] {
2021
export function useDirectoryState(path: string) {
2122
const { store } = useSandboxInspectContext()
2223

23-
return store((state) => {
24+
const isExpanded = useStore(store, (state) => state.isExpanded(path))
25+
const isLoading = useStore(store, (state) => state.loadingPaths.has(path))
26+
const hasError = useStore(store, (state) => state.errorPaths.has(path))
27+
const error = useStore(store, (state) => state.errorPaths.get(path))
28+
const isLoaded = useStore(store, (state) => {
2429
const node = state.getNode(path)
25-
return {
26-
isExpanded: state.isExpanded(path),
27-
isLoading: state.loadingPaths.has(path),
28-
hasError: state.errorPaths.has(path),
29-
error: state.errorPaths.get(path),
30-
isLoaded: node?.type === FileType.DIR ? !!node?.isLoaded : undefined,
31-
hasChildren: state.hasChildren(path),
32-
}
30+
return node?.type === FileType.DIR ? !!node?.isLoaded : undefined
3331
})
32+
const hasChildren = useStore(store, (state) => state.hasChildren(path))
33+
34+
return useMemo(
35+
() => ({
36+
isExpanded,
37+
isLoading,
38+
hasError,
39+
error,
40+
isLoaded,
41+
hasChildren,
42+
}),
43+
[isExpanded, isLoading, hasError, error, isLoaded, hasChildren]
44+
)
3445
}
3546

3647
/**

src/features/dashboard/sandbox/inspect/hooks/use-node.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import { useMemo } from 'react'
44
import { useSandboxInspectContext } from '../context'
55
import type { FilesystemNode } from '../filesystem/types'
6+
import { useStore } from 'zustand'
67

78
/**
89
* Hook for accessing a specific filesystem node
910
*/
1011
export function useFilesystemNode(path: string): FilesystemNode | undefined {
1112
const { store } = useSandboxInspectContext()
1213

13-
return store((state) => state.getNode(path))
14+
return useStore(store, (state) => state.getNode(path))
1415
}
1516

1617
/**
@@ -19,7 +20,7 @@ export function useFilesystemNode(path: string): FilesystemNode | undefined {
1920
export function useNodeSelection(path: string) {
2021
const { store, operations } = useSandboxInspectContext()
2122

22-
const isSelected = store((state) => state.isSelected(path))
23+
const isSelected = useStore(store, (state) => state.isSelected(path))
2324

2425
const select = useMemo(
2526
() => () => operations.selectNode(path),
@@ -51,7 +52,7 @@ export function useNode(path: string) {
5152
export function useRootChildren() {
5253
const { store } = useSandboxInspectContext()
5354

54-
return store((state) => state.getChildren(state.rootPath))
55+
return useStore(store, (state) => state.getChildren(state.rootPath))
5556
}
5657

5758
/**
@@ -60,7 +61,7 @@ export function useRootChildren() {
6061
export function useSelectedPath() {
6162
const { store } = useSandboxInspectContext()
6263

63-
return store((state) => state.selectedPath)
64+
return useStore(store, (state) => state.selectedPath)
6465
}
6566

6667
/**
@@ -69,7 +70,7 @@ export function useSelectedPath() {
6970
export function useLoadingPaths() {
7071
const { store } = useSandboxInspectContext()
7172

72-
return store((state) => Array.from(state.loadingPaths))
73+
return useStore(store, (state) => state.loadingPaths)
7374
}
7475

7576
/**
@@ -78,5 +79,5 @@ export function useLoadingPaths() {
7879
export function useErrorPaths() {
7980
const { store } = useSandboxInspectContext()
8081

81-
return store((state) => Object.fromEntries(state.errorPaths))
82+
return useStore(store, (state) => state.errorPaths)
8283
}

0 commit comments

Comments
 (0)