refactor(file-explorer): centralize navigation state in session context and optimize initialization
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Image,
|
Image,
|
||||||
@@ -20,11 +20,11 @@ export default function FileExplorerScreen() {
|
|||||||
requestDirectoryListing,
|
requestDirectoryListing,
|
||||||
requestFilePreview,
|
requestFilePreview,
|
||||||
} = useSession();
|
} = useSession();
|
||||||
const [currentPath, setCurrentPath] = useState(".");
|
|
||||||
const [selectedEntryPath, setSelectedEntryPath] = useState<string | null>(null);
|
const [selectedEntryPath, setSelectedEntryPath] = useState<string | null>(null);
|
||||||
|
|
||||||
const agent = agentId ? agents.get(agentId) : undefined;
|
const agent = agentId ? agents.get(agentId) : undefined;
|
||||||
const explorerState = agentId ? fileExplorer.get(agentId) : undefined;
|
const explorerState = agentId ? fileExplorer.get(agentId) : undefined;
|
||||||
|
const currentPath = explorerState?.currentPath ?? ".";
|
||||||
const directory = explorerState?.directories.get(currentPath);
|
const directory = explorerState?.directories.get(currentPath);
|
||||||
const entries = directory?.entries ?? [];
|
const entries = directory?.entries ?? [];
|
||||||
const isLoading = explorerState?.isLoading ?? false;
|
const isLoading = explorerState?.isLoading ?? false;
|
||||||
@@ -32,15 +32,32 @@ export default function FileExplorerScreen() {
|
|||||||
const preview = selectedEntryPath
|
const preview = selectedEntryPath
|
||||||
? explorerState?.files.get(selectedEntryPath)
|
? explorerState?.files.get(selectedEntryPath)
|
||||||
: null;
|
: null;
|
||||||
|
const shouldShowPreview = Boolean(selectedEntryPath);
|
||||||
|
|
||||||
|
const initializedAgentRef = useRef<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!agentId) {
|
if (!agentId) {
|
||||||
|
initializedAgentRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentPath(".");
|
|
||||||
|
if (initializedAgentRef.current === agentId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializedAgentRef.current = agentId;
|
||||||
setSelectedEntryPath(null);
|
setSelectedEntryPath(null);
|
||||||
requestDirectoryListing(agentId, ".");
|
|
||||||
}, [agentId, requestDirectoryListing]);
|
const hasDirectory = explorerState?.directories.has(currentPath) ?? false;
|
||||||
|
if (!hasDirectory) {
|
||||||
|
requestDirectoryListing(agentId, currentPath);
|
||||||
|
}
|
||||||
|
}, [agentId, currentPath, explorerState, requestDirectoryListing]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedEntryPath(null);
|
||||||
|
}, [currentPath]);
|
||||||
|
|
||||||
const parentPath = useMemo(() => {
|
const parentPath = useMemo(() => {
|
||||||
if (currentPath === ".") {
|
if (currentPath === ".") {
|
||||||
@@ -59,7 +76,6 @@ export default function FileExplorerScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entry.kind === "directory") {
|
if (entry.kind === "directory") {
|
||||||
setCurrentPath(entry.path);
|
|
||||||
setSelectedEntryPath(null);
|
setSelectedEntryPath(null);
|
||||||
requestDirectoryListing(agentId, entry.path);
|
requestDirectoryListing(agentId, entry.path);
|
||||||
return;
|
return;
|
||||||
@@ -75,7 +91,6 @@ export default function FileExplorerScreen() {
|
|||||||
if (!agentId || !parentPath) {
|
if (!agentId || !parentPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentPath(parentPath);
|
|
||||||
setSelectedEntryPath(null);
|
setSelectedEntryPath(null);
|
||||||
requestDirectoryListing(agentId, parentPath);
|
requestDirectoryListing(agentId, parentPath);
|
||||||
}, [agentId, parentPath, requestDirectoryListing]);
|
}, [agentId, parentPath, requestDirectoryListing]);
|
||||||
@@ -109,6 +124,50 @@ export default function FileExplorerScreen() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
|
{shouldShowPreview && (
|
||||||
|
<View style={styles.previewSection}>
|
||||||
|
{isLoading && !preview ? (
|
||||||
|
<View style={styles.centerState}>
|
||||||
|
<ActivityIndicator size="small" />
|
||||||
|
<Text style={styles.loadingText}>Loading file...</Text>
|
||||||
|
</View>
|
||||||
|
) : !preview ? (
|
||||||
|
<View style={styles.centerState}>
|
||||||
|
<Text style={styles.emptyText}>No preview available yet</Text>
|
||||||
|
</View>
|
||||||
|
) : preview.kind === "text" ? (
|
||||||
|
<ScrollView
|
||||||
|
style={styles.textPreview}
|
||||||
|
horizontal={false}
|
||||||
|
contentContainerStyle={styles.textPreviewContent}
|
||||||
|
>
|
||||||
|
<ScrollView horizontal>
|
||||||
|
<Text style={styles.codeText}>{preview.content}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</ScrollView>
|
||||||
|
) : preview.kind === "image" && preview.content ? (
|
||||||
|
<View style={styles.imagePreviewContainer}>
|
||||||
|
<Image
|
||||||
|
source={{
|
||||||
|
uri: `data:${preview.mimeType ?? "image/png"};base64,${
|
||||||
|
preview.content
|
||||||
|
}`,
|
||||||
|
}}
|
||||||
|
style={styles.image}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={styles.centerState}>
|
||||||
|
<Text style={styles.emptyText}>Binary preview unavailable</Text>
|
||||||
|
<Text style={styles.entryMeta}>
|
||||||
|
{formatFileSize({ size: preview.size })}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<View style={styles.listSection}>
|
<View style={styles.listSection}>
|
||||||
{error ? (
|
{error ? (
|
||||||
<View style={styles.centerState}>
|
<View style={styles.centerState}>
|
||||||
@@ -153,52 +212,6 @@ export default function FileExplorerScreen() {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.previewSection}>
|
|
||||||
{selectedEntryPath && isLoading && !preview ? (
|
|
||||||
<View style={styles.centerState}>
|
|
||||||
<ActivityIndicator size="small" />
|
|
||||||
<Text style={styles.loadingText}>Loading file...</Text>
|
|
||||||
</View>
|
|
||||||
) : !selectedEntryPath ? (
|
|
||||||
<View style={styles.centerState}>
|
|
||||||
<Text style={styles.emptyText}>Select a file to preview</Text>
|
|
||||||
</View>
|
|
||||||
) : !preview ? (
|
|
||||||
<View style={styles.centerState}>
|
|
||||||
<Text style={styles.emptyText}>No preview available yet</Text>
|
|
||||||
</View>
|
|
||||||
) : preview.kind === "text" ? (
|
|
||||||
<ScrollView
|
|
||||||
style={styles.textPreview}
|
|
||||||
horizontal={false}
|
|
||||||
contentContainerStyle={styles.textPreviewContent}
|
|
||||||
>
|
|
||||||
<ScrollView horizontal>
|
|
||||||
<Text style={styles.codeText}>{preview.content}</Text>
|
|
||||||
</ScrollView>
|
|
||||||
</ScrollView>
|
|
||||||
) : preview.kind === "image" && preview.content ? (
|
|
||||||
<View style={styles.imagePreviewContainer}>
|
|
||||||
<Image
|
|
||||||
source={{
|
|
||||||
uri: `data:${preview.mimeType ?? "image/png"};base64,${
|
|
||||||
preview.content
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
style={styles.image}
|
|
||||||
resizeMode="contain"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={styles.centerState}>
|
|
||||||
<Text style={styles.emptyText}>Binary preview unavailable</Text>
|
|
||||||
<Text style={styles.entryMeta}>
|
|
||||||
{formatFileSize({ size: preview.size })}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export interface AgentFileExplorerState {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
pendingRequest: ExplorerRequestState | null;
|
pendingRequest: ExplorerRequestState | null;
|
||||||
|
currentPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createExplorerState = (): AgentFileExplorerState => ({
|
const createExplorerState = (): AgentFileExplorerState => ({
|
||||||
@@ -127,6 +128,7 @@ const createExplorerState = (): AgentFileExplorerState => ({
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
pendingRequest: null,
|
pendingRequest: null,
|
||||||
|
currentPath: ".",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -923,6 +925,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
|
|||||||
isLoading: true,
|
isLoading: true,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
pendingRequest: { path: normalizedPath, mode: "list" },
|
pendingRequest: { path: normalizedPath, mode: "list" },
|
||||||
|
currentPath: normalizedPath,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const msg: WSInboundMessage = {
|
const msg: WSInboundMessage = {
|
||||||
|
|||||||
Reference in New Issue
Block a user