Fix file explorer split behavior and compact tree rows

This commit is contained in:
Mohamed Boudra
2026-02-06 22:01:57 +07:00
parent d236c18ae5
commit 38a96484c9
@@ -127,6 +127,7 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
const [menuAnchor, setMenuAnchor] = useState({ top: 0, left: 0 }); const [menuAnchor, setMenuAnchor] = useState({ top: 0, left: 0 });
const [menuHeight, setMenuHeight] = useState(0); const [menuHeight, setMenuHeight] = useState(0);
const [containerWidth, setContainerWidth] = useState(0); const [containerWidth, setContainerWidth] = useState(0);
const wasInlinePreviewVisibleRef = useRef(false);
// Bottom sheet for file preview (mobile) // Bottom sheet for file preview (mobile)
const previewSheetRef = useRef<BottomSheetModal>(null); const previewSheetRef = useRef<BottomSheetModal>(null);
@@ -337,6 +338,7 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
useEffect(() => { useEffect(() => {
if (!shouldShowInlinePreview) { if (!shouldShowInlinePreview) {
wasInlinePreviewVisibleRef.current = false;
return; return;
} }
if (containerWidth <= 0) { if (containerWidth <= 0) {
@@ -345,12 +347,16 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
const available = Math.max(0, containerWidth); const available = Math.max(0, containerWidth);
const maxTree = Math.max(minTreeWidth, available - minPreviewWidth); const maxTree = Math.max(minTreeWidth, available - minPreviewWidth);
const desired = Math.round(available * safeSplitRatio); const isOpeningInlinePreview = !wasInlinePreviewVisibleRef.current;
const desired = isOpeningInlinePreview
? Math.round(available * safeSplitRatio)
: splitTreeWidth.value;
const clamped = Math.max(minTreeWidth, Math.min(maxTree, desired)); const clamped = Math.max(minTreeWidth, Math.min(maxTree, desired));
splitAvailableWidth.value = available; splitAvailableWidth.value = available;
splitMaxTreeWidth.value = maxTree; splitMaxTreeWidth.value = maxTree;
splitTreeWidth.value = clamped; splitTreeWidth.value = clamped;
wasInlinePreviewVisibleRef.current = true;
}, [ }, [
containerWidth, containerWidth,
minPreviewWidth, minPreviewWidth,
@@ -380,7 +386,7 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
splitStartTreeWidth.value = splitTreeWidth.value; splitStartTreeWidth.value = splitTreeWidth.value;
}) })
.onUpdate((event) => { .onUpdate((event) => {
const nextWidth = splitStartTreeWidth.value + event.translationX; const nextWidth = splitStartTreeWidth.value - event.translationX;
const clamped = Math.max( const clamped = Math.max(
minTreeWidth, minTreeWidth,
Math.min(splitMaxTreeWidth.value, nextWidth) Math.min(splitMaxTreeWidth.value, nextWidth)
@@ -543,43 +549,8 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
</View> </View>
) : ( ) : (
<View style={styles.desktopSplit}> <View style={styles.desktopSplit}>
<Animated.View
style={[
styles.treePane,
shouldShowInlinePreview
? [styles.treePaneWithPreview, { minWidth: minTreeWidth }, treePaneAnimatedStyle]
: styles.treePaneFill,
]}
>
<View style={styles.paneHeader} testID="files-pane-header">
<View style={styles.paneHeaderLeft} />
<Pressable style={styles.sortButton} onPress={handleSortCycle}>
<Text style={styles.sortButtonText}>{currentSortLabel}</Text>
</Pressable>
</View>
<FlatList
style={styles.treeList}
data={treeRows}
renderItem={renderTreeRow}
keyExtractor={(row) => row.entry.path}
contentContainerStyle={styles.entriesContent}
initialNumToRender={24}
maxToRenderPerBatch={40}
windowSize={12}
/>
</Animated.View>
{shouldShowInlinePreview ? ( {shouldShowInlinePreview ? (
<View style={styles.previewPane}> <View style={styles.previewPane}>
<GestureDetector gesture={splitResizeGesture}>
<View
style={[
styles.splitResizeHandle,
Platform.OS === "web" && ({ cursor: "col-resize" } as any),
Platform.OS === "web" && ({ touchAction: "none", userSelect: "none" } as any),
]}
/>
</GestureDetector>
<View style={styles.paneHeader} testID="preview-pane-header"> <View style={styles.paneHeader} testID="preview-pane-header">
<Text style={styles.previewHeaderText} numberOfLines={1}> <Text style={styles.previewHeaderText} numberOfLines={1}>
{selectedEntryPath?.split("/").pop() ?? "Preview"} {selectedEntryPath?.split("/").pop() ?? "Preview"}
@@ -606,6 +577,62 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
<FilePreviewBody preview={preview} isLoading={isPreviewLoading} variant="inline" /> <FilePreviewBody preview={preview} isLoading={isPreviewLoading} variant="inline" />
</View> </View>
) : null} ) : null}
{shouldShowInlinePreview ? (
<Animated.View
style={[
styles.treePane,
styles.treePaneWithPreview,
{ minWidth: minTreeWidth },
treePaneAnimatedStyle,
]}
>
<GestureDetector gesture={splitResizeGesture}>
<View
style={[
styles.splitResizeHandle,
Platform.OS === "web" && ({ cursor: "col-resize" } as any),
Platform.OS === "web" && ({ touchAction: "none", userSelect: "none" } as any),
]}
/>
</GestureDetector>
<View style={styles.paneHeader} testID="files-pane-header">
<View style={styles.paneHeaderLeft} />
<Pressable style={styles.sortButton} onPress={handleSortCycle}>
<Text style={styles.sortButtonText}>{currentSortLabel}</Text>
</Pressable>
</View>
<FlatList
style={styles.treeList}
data={treeRows}
renderItem={renderTreeRow}
keyExtractor={(row) => row.entry.path}
contentContainerStyle={styles.entriesContent}
initialNumToRender={24}
maxToRenderPerBatch={40}
windowSize={12}
/>
</Animated.View>
) : (
<View style={[styles.treePane, styles.treePaneFill]}>
<View style={styles.paneHeader} testID="files-pane-header">
<View style={styles.paneHeaderLeft} />
<Pressable style={styles.sortButton} onPress={handleSortCycle}>
<Text style={styles.sortButtonText}>{currentSortLabel}</Text>
</Pressable>
</View>
<FlatList
style={styles.treeList}
data={treeRows}
renderItem={renderTreeRow}
keyExtractor={(row) => row.entry.path}
contentContainerStyle={styles.entriesContent}
initialNumToRender={24}
maxToRenderPerBatch={40}
windowSize={12}
/>
</View>
)}
</View> </View>
)} )}
@@ -1014,6 +1041,7 @@ const styles = StyleSheet.create((theme) => ({
}, },
treePane: { treePane: {
minWidth: 0, minWidth: 0,
position: "relative",
}, },
treePaneFill: { treePaneFill: {
flex: 1, flex: 1,
@@ -1022,6 +1050,8 @@ const styles = StyleSheet.create((theme) => ({
flex: 0, flex: 0,
flexGrow: 0, flexGrow: 0,
flexShrink: 0, flexShrink: 0,
borderLeftWidth: 1,
borderLeftColor: theme.colors.border,
}, },
splitResizeHandle: { splitResizeHandle: {
position: "absolute", position: "absolute",
@@ -1034,9 +1064,6 @@ const styles = StyleSheet.create((theme) => ({
previewPane: { previewPane: {
flex: 1, flex: 1,
minWidth: 0, minWidth: 0,
position: "relative",
borderLeftWidth: 1,
borderLeftColor: theme.colors.border,
}, },
paneHeader: { paneHeader: {
flexDirection: "row", flexDirection: "row",
@@ -1121,7 +1148,7 @@ const styles = StyleSheet.create((theme) => ({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
paddingVertical: theme.spacing[1], paddingVertical: 2,
paddingRight: theme.spacing[2], paddingRight: theme.spacing[2],
}, },
entryRowActive: { entryRowActive: {
@@ -1143,8 +1170,8 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
}, },
menuButton: { menuButton: {
width: 36, width: 30,
height: 36, height: 30,
borderRadius: theme.borderRadius.full, borderRadius: theme.borderRadius.full,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",