From 2d74b5407c8764cd4cbbd80237e568071a9befa0 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 6 Feb 2026 22:21:37 +0700 Subject: [PATCH] Update files --- .../app/src/components/file-explorer-pane.tsx | 224 ++++++------------ packages/app/src/components/git-diff-pane.tsx | 76 ++++-- 2 files changed, 123 insertions(+), 177 deletions(-) diff --git a/packages/app/src/components/file-explorer-pane.tsx b/packages/app/src/components/file-explorer-pane.tsx index 18834118a..ec9957841 100644 --- a/packages/app/src/components/file-explorer-pane.tsx +++ b/packages/app/src/components/file-explorer-pane.tsx @@ -3,15 +3,12 @@ import { ActivityIndicator, FlatList, Image as RNImage, - LayoutChangeEvent, ListRenderItemInfo, - Modal, Pressable, ScrollView as RNScrollView, Text, View, Platform, - useWindowDimensions, } from "react-native"; import { ScrollView, Gesture, GestureDetector } from "react-native-gesture-handler"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; @@ -28,6 +25,8 @@ import { BottomSheetBackdrop, } from "@gorhom/bottom-sheet"; import { + Copy, + Download, File, FileText, Folder, @@ -44,6 +43,13 @@ import type { import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { useSessionStore } from "@/stores/session-store"; import { useDownloadStore } from "@/stores/download-store"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { useFileExplorerActions } from "@/hooks/use-file-explorer-actions"; import { usePanelStore, @@ -74,7 +80,6 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { const { theme } = useUnistyles(); const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; - const { width: windowWidth, height: windowHeight } = useWindowDimensions(); const { connectionStates } = useDaemonConnections(); const daemonProfile = connectionStates.get(serverId)?.daemon; @@ -123,9 +128,6 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { ); const [expandedPaths, setExpandedPaths] = useState>(() => new Set(["."])); - const [menuEntry, setMenuEntry] = useState(null); - const [menuAnchor, setMenuAnchor] = useState({ top: 0, left: 0 }); - const [menuHeight, setMenuHeight] = useState(0); const [containerWidth, setContainerWidth] = useState(0); const wasInlinePreviewVisibleRef = useRef(false); @@ -236,26 +238,6 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { await Clipboard.setStringAsync(path); }, []); - const handleOpenMenu = useCallback((entry: ExplorerEntry, event: any) => { - event.stopPropagation(); - const { pageX, pageY } = event.nativeEvent ?? {}; - setMenuAnchor({ - left: typeof pageX === "number" ? pageX : 0, - top: typeof pageY === "number" ? pageY : 0, - }); - setMenuEntry(entry); - }, []); - - const handleCloseMenu = useCallback(() => { - setMenuEntry(null); - setMenuHeight(0); - }, []); - - const handleMenuLayout = useCallback((event: LayoutChangeEvent) => { - const { height } = event.nativeEvent.layout; - setMenuHeight((current) => (current === height ? current : height)); - }, []); - const startDownload = useDownloadStore((state) => state.startDownload); const handleDownloadEntry = useCallback( (entry: ExplorerEntry) => { @@ -275,23 +257,6 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { [agentId, serverId, daemonProfile, requestFileDownloadToken, startDownload] ); - const menuPosition = useMemo(() => { - if (!menuEntry) { - return null; - } - const menuWidth = 240; - const horizontalPadding = theme.spacing[2]; - const verticalPadding = theme.spacing[2]; - const maxLeft = Math.max(horizontalPadding, windowWidth - menuWidth - horizontalPadding); - const maxTop = Math.max(verticalPadding, windowHeight - menuHeight - verticalPadding); - const left = Math.min( - Math.max(menuAnchor.left - menuWidth + horizontalPadding, horizontalPadding), - maxLeft - ); - const top = Math.min(Math.max(menuAnchor.top + verticalPadding, verticalPadding), maxTop); - return { top, left, width: menuWidth }; - }, [menuAnchor.left, menuAnchor.top, menuEntry, menuHeight, theme.spacing, windowHeight, windowWidth]); - const handleSortCycle = useCallback(() => { const currentIndex = SORT_OPTIONS.findIndex((opt) => opt.value === sortOption); const nextIndex = (currentIndex + 1) % SORT_OPTIONS.length; @@ -445,23 +410,55 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { {entry.name} - handleOpenMenu(entry, event)} - hitSlop={8} - style={({ hovered, pressed }) => [ - styles.menuButton, - (hovered || pressed) && styles.menuButtonActive, - ]} - > - - + + event.stopPropagation?.()} + style={({ hovered, pressed, open }) => [ + styles.menuButton, + (hovered || pressed || open) && styles.menuButtonActive, + ]} + > + + + + + + Size + {formatFileSize({ size: entry.size })} + + + Modified + {formatTimeAgo(new Date(entry.modifiedAt))} + + + + } + onSelect={() => { + void handleCopyPath(entry.path); + }} + > + Copy path + + {entry.kind === "file" ? ( + } + onSelect={() => handleDownloadEntry(entry)} + > + Download + + ) : null} + + ); }, [ expandedPaths, handleEntryPress, - handleOpenMenu, + handleCopyPath, + handleDownloadEntry, isDirectoryLoading, selectedEntryPath, theme.colors, @@ -636,67 +633,6 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { )} - - - - {menuEntry && menuPosition ? ( - - - - {formatFileSize({ size: menuEntry.size })} - - - {formatTimeAgo(new Date(menuEntry.modifiedAt))} - - - - [ - styles.entryMenuItem, - (hovered || pressed) && styles.entryMenuItemHovered, - ]} - onPress={() => { - handleCopyPath(menuEntry.path); - handleCloseMenu(); - }} - > - Copy path - - {menuEntry.kind === "file" ? ( - [ - styles.entryMenuItem, - (hovered || pressed) && styles.entryMenuItemHovered, - ]} - onPress={async () => { - handleCloseMenu(); - handleDownloadEntry(menuEntry); - }} - > - Download - - ) : null} - - ) : null} - - - {isMobile ? ( Binary preview unavailable - {formatFileSize({ size: preview.size })} + {formatFileSize({ size: preview.size })} ); } @@ -1144,6 +1080,10 @@ const styles = StyleSheet.create((theme) => ({ fontSize: theme.fontSize.base, textAlign: "center", }, + binaryMetaText: { + color: theme.colors.foregroundMuted, + fontSize: theme.fontSize.sm, + }, entryRow: { flexDirection: "row", alignItems: "center", @@ -1179,52 +1119,24 @@ const styles = StyleSheet.create((theme) => ({ menuButtonActive: { backgroundColor: theme.colors.surface2, }, - menuOverlay: { - flex: 1, + contextMetaBlock: { + paddingVertical: theme.spacing[1], }, - menuBackdrop: { - ...StyleSheet.absoluteFillObject, - backgroundColor: "rgba(0, 0, 0, 0.2)", - }, - entryMenu: { - borderRadius: theme.borderRadius.lg, - backgroundColor: theme.colors.surface1, - overflow: "hidden", - ...(Platform.OS === "web" - ? ({ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.35)" } as any) - : { - shadowColor: "#000", - shadowOpacity: 0.35, - shadowRadius: 16, - shadowOffset: { width: 0, height: 10 }, - elevation: 14, - }), - }, - entryMenuHeader: { + contextMetaRow: { flexDirection: "row", + alignItems: "center", justifyContent: "space-between", - paddingVertical: theme.spacing[3], - paddingHorizontal: theme.spacing[4], + minHeight: 32, + paddingHorizontal: theme.spacing[3], }, - entryMenuMeta: { - color: theme.colors.foregroundMuted, + contextMetaLabel: { fontSize: theme.fontSize.sm, + color: theme.colors.foregroundMuted, }, - entryMenuDivider: { - height: 1, - backgroundColor: theme.colors.border, - }, - entryMenuItem: { - paddingVertical: theme.spacing[4], - paddingHorizontal: theme.spacing[4], - }, - entryMenuItemHovered: { - backgroundColor: theme.colors.surface2, - }, - entryMenuText: { + contextMetaValue: { + fontSize: theme.fontSize.sm, color: theme.colors.foreground, - fontSize: theme.fontSize.lg, - fontWeight: theme.fontWeight.semibold, + fontWeight: theme.fontWeight.medium, }, previewHeaderText: { flex: 1, diff --git a/packages/app/src/components/git-diff-pane.tsx b/packages/app/src/components/git-diff-pane.tsx index 1a5669ae3..40069995d 100644 --- a/packages/app/src/components/git-diff-pane.tsx +++ b/packages/app/src/components/git-diff-pane.tsx @@ -15,7 +15,7 @@ import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gest import { StyleSheet, useUnistyles } from "react-native-unistyles"; import AsyncStorage from "@react-native-async-storage/async-storage"; import * as Linking from "expo-linking"; -import { Archive, ChevronDown, ChevronRight, GitBranch, MoreVertical, ArrowLeftRight, ListChevronsDownUp, ListChevronsUpDown } from "lucide-react-native"; +import { Archive, ChevronDown, ChevronRight, GitBranch, MoreVertical, ListChevronsDownUp, ListChevronsUpDown } from "lucide-react-native"; import { useCheckoutGitActionsStore } from "@/stores/checkout-git-actions-store"; import { useCheckoutDiffQuery, @@ -741,6 +741,14 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) { const trimmed = baseRef.replace(/^refs\/(heads|remotes)\//, "").trim(); return trimmed.startsWith("origin/") ? trimmed.slice("origin/".length) : trimmed; }, [baseRef]); + const committedDiffDescription = useMemo(() => { + if (!branchLabel || !baseRefLabel) { + return undefined; + } + return branchLabel === baseRefLabel + ? undefined + : `${branchLabel} -> ${baseRefLabel}`; + }, [baseRefLabel, branchLabel]); const commitDisabled = actionsDisabled || commitStatus === "pending"; const prDisabled = actionsDisabled || prCreateStatus === "pending"; const mergeDisabled = @@ -1101,25 +1109,45 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) { {isGit ? ( - [ - styles.diffStatusRow, - (hovered || pressed) && styles.diffStatusRowHovered, - ]} - testID="changes-diff-status" - onPress={() => setDiffModeOverride(diffMode === "uncommitted" ? "base" : "uncommitted")} - > - {({ hovered, pressed }) => ( - <> - - {diffMode === "uncommitted" ? "Uncommitted" : "Committed"} - - {(hovered || pressed) ? ( - - ) : null} - - )} - + + [ + styles.diffModeTrigger, + hovered && styles.diffModeTriggerHovered, + (pressed || open) && styles.diffModeTriggerPressed, + ]} + testID="changes-diff-status" + accessibilityRole="button" + accessibilityLabel="Diff mode" + > + + {diffMode === "uncommitted" ? "Uncommitted" : "Committed"} + + + + + setDiffModeOverride("uncommitted")} + > + Uncommitted + + + setDiffModeOverride("base")} + > + Committed + + + {files.length > 0 ? ( [ @@ -1193,7 +1221,7 @@ const styles = StyleSheet.create((theme) => ({ justifyContent: "space-between", paddingRight: theme.spacing[3], }, - diffStatusRow: { + diffModeTrigger: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1], @@ -1204,6 +1232,12 @@ const styles = StyleSheet.create((theme) => ({ paddingVertical: theme.spacing[1], borderRadius: theme.borderRadius.base, }, + diffModeTriggerHovered: { + backgroundColor: theme.colors.surface2, + }, + diffModeTriggerPressed: { + backgroundColor: theme.colors.surface2, + }, diffStatusRowHovered: { backgroundColor: theme.colors.surface2, },