mirror of
https://github.com/crabwise-ai/crabwalk.git
synced 2026-08-14 00:57:52 +00:00
fix(workspace): improve file tree refresh behavior and state management
Fix file tree not properly resetting when refreshing workspace directory. Add useEffect hook to reset children state when entry path changes, preventing stale data display. Update handleRefresh to properly clear cache before reload and handle cases where selected files are deleted during refresh. Remove children.length check from loadChildren to allow directory re-expansion after refresh.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { ChevronRight, ChevronDown, Folder, FolderOpen, FileText } from 'lucide-react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import type { DirectoryEntry } from '~/lib/workspace-fs'
|
||||
@@ -33,12 +33,18 @@ function FileTreeItem({ entry, selectedPath, onSelect, onLoadDirectory, level }:
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const [children, setChildren] = useState<DirectoryEntry[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// Reset children when entry path changes (e.g., on refresh)
|
||||
useEffect(() => {
|
||||
setChildren([])
|
||||
setExpanded(false)
|
||||
}, [entry.path])
|
||||
const isSelected = selectedPath === entry.path
|
||||
const isDirectory = entry.type === 'directory'
|
||||
const paddingLeft = level * 16 + 8
|
||||
|
||||
const loadChildren = useCallback(async () => {
|
||||
if (!isDirectory || children.length > 0 || !onLoadDirectory) return
|
||||
if (!isDirectory || !onLoadDirectory) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const entries = await onLoadDirectory(entry.path)
|
||||
@@ -48,7 +54,7 @@ function FileTreeItem({ entry, selectedPath, onSelect, onLoadDirectory, level }:
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [entry.path, isDirectory, children.length, onLoadDirectory])
|
||||
}, [entry.path, isDirectory, onLoadDirectory])
|
||||
|
||||
const handleToggle = useCallback(
|
||||
async (e: React.MouseEvent) => {
|
||||
|
||||
@@ -235,15 +235,74 @@ function WorkspacePage() {
|
||||
const handleRefresh = useCallback(async () => {
|
||||
if (!workspacePath || !pathValid) return
|
||||
|
||||
// Clear cache and reload
|
||||
setPathCache(new Map())
|
||||
await loadDirectory(workspacePath)
|
||||
// Store current selection before clearing cache
|
||||
const currentSelectedPath = selectedPath
|
||||
|
||||
// Reload selected file if any
|
||||
if (selectedPath) {
|
||||
await loadFile(selectedPath)
|
||||
// Clear cache first, then reload
|
||||
// Use a callback to ensure cache is cleared before loading
|
||||
setPathCache(new Map())
|
||||
|
||||
// Small delay to ensure React has processed the state update
|
||||
// before we try to load the directory
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
// Reload root directory - this will repopulate the file tree
|
||||
// Force reload by bypassing cache check
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await trpc.workspace.listDirectory.query({
|
||||
workspaceRoot: workspacePath,
|
||||
path: workspacePath,
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error)
|
||||
}
|
||||
|
||||
// Update cache with fresh data
|
||||
setPathCache(new Map([[workspacePath, result.entries]]))
|
||||
} catch (error) {
|
||||
console.error('Failed to load directory:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [workspacePath, pathValid, selectedPath])
|
||||
|
||||
// Reload selected file if any (with error handling for deleted files)
|
||||
if (currentSelectedPath) {
|
||||
try {
|
||||
const result = await trpc.workspace.readFile.query({
|
||||
workspaceRoot: workspacePath,
|
||||
path: currentSelectedPath,
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
// File no longer exists - clear selection gracefully
|
||||
setSelectedPath(null)
|
||||
setSelectedFileContent('')
|
||||
setSelectedFileName('')
|
||||
setSelectedFileSize(undefined)
|
||||
setSelectedFileModified(undefined)
|
||||
setFileError(result.error)
|
||||
} else {
|
||||
setSelectedFileContent(result.content)
|
||||
setSelectedFileName(result.name)
|
||||
// Get file metadata from the parent directory entry if available
|
||||
const parentDir = pathCache.get(getParentDirPath(currentSelectedPath) || workspacePath)
|
||||
const fileEntry = parentDir?.find(e => e.path === currentSelectedPath)
|
||||
setSelectedFileSize(fileEntry?.size)
|
||||
setSelectedFileModified(fileEntry?.modifiedAt)
|
||||
}
|
||||
} catch (error) {
|
||||
// File no longer exists - clear selection gracefully
|
||||
setSelectedPath(null)
|
||||
setSelectedFileContent('')
|
||||
setSelectedFileName('')
|
||||
setSelectedFileSize(undefined)
|
||||
setSelectedFileModified(undefined)
|
||||
setFileError(error instanceof Error ? error.message : 'Failed to read file')
|
||||
}
|
||||
}
|
||||
}, [workspacePath, pathValid, selectedPath, loadFile])
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
||||
Reference in New Issue
Block a user