Separate explorer checkout from panel state
This commit is contained in:
@@ -388,9 +388,11 @@ function AppContainer({
|
||||
const { theme } = useUnistyles();
|
||||
const daemons = useHosts();
|
||||
const { settings, updateSettings } = useAppSettings();
|
||||
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
|
||||
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
||||
const toggleBothSidebars = usePanelStore((state) => state.toggleBothSidebars);
|
||||
const toggleMobileAgentList = usePanelStore((state) => state.toggleMobileAgentList);
|
||||
const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer);
|
||||
const toggleDesktopAgentList = usePanelStore((state) => state.toggleDesktopAgentList);
|
||||
const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer);
|
||||
const toggleDesktopSidebars = usePanelStore((state) => state.toggleDesktopSidebars);
|
||||
const toggleFocusMode = usePanelStore((state) => state.toggleFocusMode);
|
||||
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
|
||||
const agentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
||||
@@ -405,6 +407,8 @@ function AppContainer({
|
||||
const isCompactLayout = useIsCompactFormFactor();
|
||||
const chromeEnabled = chromeEnabledOverride ?? daemons.length > 0;
|
||||
const pathname = usePathname();
|
||||
const toggleAgentList = isCompactLayout ? toggleMobileAgentList : toggleDesktopAgentList;
|
||||
const toggleFileExplorer = isCompactLayout ? toggleMobileFileExplorer : toggleDesktopFileExplorer;
|
||||
// TODO: stop matching pathname here as a branch. `chromeEnabled` should not
|
||||
// conflate workspace/project-specific chrome (sidebar, mobile gesture) with
|
||||
// global concerns like keyboard shortcuts. Split those out so settings (and
|
||||
@@ -445,7 +449,7 @@ function AppContainer({
|
||||
isMobile: isCompactLayout,
|
||||
toggleAgentList,
|
||||
toggleFileExplorer,
|
||||
toggleBothSidebars,
|
||||
toggleBothSidebars: toggleDesktopSidebars,
|
||||
toggleFocusMode,
|
||||
cycleTheme,
|
||||
});
|
||||
@@ -490,7 +494,7 @@ function MobileGestureWrapper({
|
||||
chromeEnabled: boolean;
|
||||
}) {
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const openAgentList = usePanelStore((state) => state.openAgentList);
|
||||
const showMobileAgentList = usePanelStore((state) => state.showMobileAgentList);
|
||||
const horizontalScroll = useHorizontalScrollOptional();
|
||||
const {
|
||||
translateX,
|
||||
@@ -507,8 +511,8 @@ function MobileGestureWrapper({
|
||||
|
||||
const handleGestureOpen = useCallback(() => {
|
||||
gestureAnimatingRef.current = true;
|
||||
openAgentList();
|
||||
}, [openAgentList, gestureAnimatingRef]);
|
||||
showMobileAgentList();
|
||||
}, [showMobileAgentList, gestureAnimatingRef]);
|
||||
|
||||
const openGesture = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -125,7 +125,8 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
||||
const [expandedInlineToolCallIds, setExpandedInlineToolCallIds] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
|
||||
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
|
||||
const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer);
|
||||
const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout);
|
||||
|
||||
// Get serverId (fallback to agent's serverId if not provided)
|
||||
@@ -205,15 +206,21 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
||||
isGit: agent.projectPlacement?.checkout?.isGit ?? true,
|
||||
tab: "files",
|
||||
});
|
||||
openFileExplorer();
|
||||
if (isMobile) {
|
||||
showMobileFileExplorer();
|
||||
return;
|
||||
}
|
||||
openDesktopFileExplorer();
|
||||
},
|
||||
[
|
||||
agent.cwd,
|
||||
openFileExplorer,
|
||||
isMobile,
|
||||
openDesktopFileExplorer,
|
||||
requestDirectoryListing,
|
||||
resolvedServerId,
|
||||
router,
|
||||
setExplorerTabForCheckout,
|
||||
showMobileFileExplorer,
|
||||
openWorkspaceFile,
|
||||
workspaceId,
|
||||
],
|
||||
|
||||
@@ -54,7 +54,8 @@ export function ExplorerSidebar({
|
||||
const isMobile = useIsCompactFormFactor();
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
||||
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
|
||||
const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer);
|
||||
const explorerTab = usePanelStore((state) => state.explorerTab);
|
||||
const explorerWidth = usePanelStore((state) => state.explorerWidth);
|
||||
const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout);
|
||||
@@ -107,15 +108,26 @@ export function ExplorerSidebar({
|
||||
mobileView,
|
||||
desktopFileExplorerOpen,
|
||||
});
|
||||
closeToAgent();
|
||||
if (isMobile) {
|
||||
showMobileAgent();
|
||||
return;
|
||||
}
|
||||
closeDesktopFileExplorer();
|
||||
},
|
||||
[closeToAgent, desktopFileExplorerOpen, isOpen, mobileView],
|
||||
[
|
||||
closeDesktopFileExplorer,
|
||||
desktopFileExplorerOpen,
|
||||
isMobile,
|
||||
isOpen,
|
||||
mobileView,
|
||||
showMobileAgent,
|
||||
],
|
||||
);
|
||||
|
||||
const handleCloseFromGesture = useCallback(() => {
|
||||
gestureAnimatingRef.current = true;
|
||||
closeToAgent();
|
||||
}, [closeToAgent, gestureAnimatingRef]);
|
||||
showMobileAgent();
|
||||
}, [gestureAnimatingRef, showMobileAgent]);
|
||||
|
||||
const enableSidebarCloseGesture = isMobile && isOpen;
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ export function SidebarMenuToggle({
|
||||
const isMobile = useIsCompactFormFactor();
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
||||
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
|
||||
const toggleMobileAgentList = usePanelStore((state) => state.toggleMobileAgentList);
|
||||
const toggleDesktopAgentList = usePanelStore((state) => state.toggleDesktopAgentList);
|
||||
const toggleShortcutKeys = getShortcutOs() === "mac" ? ["mod", "B"] : ["mod", "."];
|
||||
|
||||
const isOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen;
|
||||
@@ -57,7 +58,7 @@ export function SidebarMenuToggle({
|
||||
|
||||
return (
|
||||
<HeaderToggleButton
|
||||
onPress={toggleAgentList}
|
||||
onPress={isMobile ? toggleMobileAgentList : toggleDesktopAgentList}
|
||||
tooltipLabel="Toggle sidebar"
|
||||
tooltipKeys={toggleShortcutKeys}
|
||||
tooltipSide={tooltipSide}
|
||||
|
||||
@@ -118,7 +118,7 @@ export const LeftSidebar = memo(function LeftSidebar({
|
||||
const isCompactLayout = useIsCompactFormFactor();
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
||||
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
||||
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
|
||||
const pathname = usePathname();
|
||||
const daemons = useHosts();
|
||||
const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname]);
|
||||
@@ -208,18 +208,18 @@ export const LeftSidebar = memo(function LeftSidebar({
|
||||
const openProjectPicker = useOpenProjectPicker(activeServerId);
|
||||
|
||||
const handleOpenProjectMobile = useCallback(() => {
|
||||
closeToAgent();
|
||||
showMobileAgent();
|
||||
void openProjectPicker();
|
||||
}, [closeToAgent, openProjectPicker]);
|
||||
}, [showMobileAgent, openProjectPicker]);
|
||||
|
||||
const handleOpenProjectDesktop = useCallback(() => {
|
||||
void openProjectPicker();
|
||||
}, [openProjectPicker]);
|
||||
|
||||
const handleSettingsMobile = useCallback(() => {
|
||||
closeToAgent();
|
||||
showMobileAgent();
|
||||
router.push(buildSettingsRoute());
|
||||
}, [closeToAgent]);
|
||||
}, [showMobileAgent]);
|
||||
|
||||
const handleSettingsDesktop = useCallback(() => {
|
||||
router.push(buildSettingsRoute());
|
||||
@@ -272,7 +272,7 @@ export const LeftSidebar = memo(function LeftSidebar({
|
||||
insetsTop={insets.top}
|
||||
insetsBottom={insets.bottom}
|
||||
isOpen={isOpen}
|
||||
closeToAgent={closeToAgent}
|
||||
closeToAgent={showMobileAgent}
|
||||
handleOpenProject={handleOpenProjectMobile}
|
||||
handleSettings={handleSettingsMobile}
|
||||
handleViewMoreNavigate={handleViewMoreNavigate}
|
||||
|
||||
@@ -89,8 +89,8 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi
|
||||
const xtermTheme = useMemo(() => toXtermTheme(theme.colors.terminal), [theme]);
|
||||
const isMobile = useIsCompactFormFactor();
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const openAgentList = usePanelStore((state) => state.openAgentList);
|
||||
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
|
||||
const showMobileAgentList = usePanelStore((state) => state.showMobileAgentList);
|
||||
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
|
||||
const swipeGesturesEnabled = isMobile && mobileView === "agent";
|
||||
const { shift: keyboardShift, style: keyboardPaddingStyle } = useKeyboardShiftStyle({
|
||||
mode: "padding",
|
||||
@@ -573,13 +573,13 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi
|
||||
if (!swipeGesturesEnabled) {
|
||||
return;
|
||||
}
|
||||
openAgentList();
|
||||
showMobileAgentList();
|
||||
}}
|
||||
onSwipeLeft={() => {
|
||||
if (!swipeGesturesEnabled) {
|
||||
return;
|
||||
}
|
||||
openFileExplorer();
|
||||
showMobileFileExplorer();
|
||||
}}
|
||||
onInput={handleTerminalData}
|
||||
onResize={handleTerminalResize}
|
||||
|
||||
@@ -36,7 +36,11 @@ import {
|
||||
useHostRuntimeIsConnected,
|
||||
} from "@/runtime/host-runtime";
|
||||
import { ExplorerSidebarAnimationProvider } from "@/contexts/explorer-sidebar-animation-context";
|
||||
import { usePanelStore, type ExplorerCheckoutContext } from "@/stores/panel-store";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
import {
|
||||
setActiveExplorerCheckout,
|
||||
type ExplorerCheckoutContext,
|
||||
} from "@/stores/explorer-checkout-context";
|
||||
import { MAX_CONTENT_WIDTH, useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { WelcomeScreen } from "@/components/welcome-screen";
|
||||
import type { Agent } from "@/contexts/session-context";
|
||||
@@ -237,10 +241,12 @@ function DraftAgentScreenContent({
|
||||
const isMobile = useIsCompactFormFactor();
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
||||
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
|
||||
const closeFileExplorer = usePanelStore((state) => state.closeFileExplorer);
|
||||
const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout);
|
||||
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
|
||||
const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer);
|
||||
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
|
||||
const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer);
|
||||
const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer);
|
||||
const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer);
|
||||
const activateExplorerTabForCheckout = usePanelStore(
|
||||
(state) => state.activateExplorerTabForCheckout,
|
||||
);
|
||||
@@ -646,18 +652,39 @@ function DraftAgentScreenContent({
|
||||
return;
|
||||
}
|
||||
activateExplorerTabForCheckout(draftExplorerCheckout);
|
||||
openFileExplorer();
|
||||
}, [activateExplorerTabForCheckout, draftExplorerCheckout, openFileExplorer]);
|
||||
if (isMobile) {
|
||||
showMobileFileExplorer();
|
||||
return;
|
||||
}
|
||||
openDesktopFileExplorer();
|
||||
}, [
|
||||
activateExplorerTabForCheckout,
|
||||
draftExplorerCheckout,
|
||||
isMobile,
|
||||
openDesktopFileExplorer,
|
||||
showMobileFileExplorer,
|
||||
]);
|
||||
const handleToggleExplorer = useCallback(() => {
|
||||
if (!canOpenExplorer) {
|
||||
return;
|
||||
}
|
||||
if (isExplorerOpen) {
|
||||
toggleFileExplorer();
|
||||
if (isMobile) {
|
||||
toggleMobileFileExplorer();
|
||||
return;
|
||||
}
|
||||
toggleDesktopFileExplorer();
|
||||
return;
|
||||
}
|
||||
openExplorerForDraftCheckout();
|
||||
}, [canOpenExplorer, isExplorerOpen, openExplorerForDraftCheckout, toggleFileExplorer]);
|
||||
}, [
|
||||
canOpenExplorer,
|
||||
isExplorerOpen,
|
||||
isMobile,
|
||||
openExplorerForDraftCheckout,
|
||||
toggleDesktopFileExplorer,
|
||||
toggleMobileFileExplorer,
|
||||
]);
|
||||
const explorerOpenGesture = useExplorerOpenGesture({
|
||||
enabled: isMobile && mobileView === "agent" && canOpenExplorer,
|
||||
onOpen: openExplorerForDraftCheckout,
|
||||
@@ -956,8 +983,19 @@ function DraftAgentScreenContent({
|
||||
if (!isFocused || canOpenExplorer || !isExplorerOpen) {
|
||||
return;
|
||||
}
|
||||
closeFileExplorer();
|
||||
}, [canOpenExplorer, closeFileExplorer, isExplorerOpen, isFocused]);
|
||||
if (isMobile) {
|
||||
showMobileAgent();
|
||||
return;
|
||||
}
|
||||
closeDesktopFileExplorer();
|
||||
}, [
|
||||
canOpenExplorer,
|
||||
closeDesktopFileExplorer,
|
||||
isExplorerOpen,
|
||||
isFocused,
|
||||
isMobile,
|
||||
showMobileAgent,
|
||||
]);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setActiveExplorerCheckout(null);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
|
||||
import { PairDeviceModal } from "@/desktop/components/pair-device-modal";
|
||||
|
||||
export function OpenProjectScreen({ serverId }: { serverId: string }) {
|
||||
const openAgentList = usePanelStore((s) => s.openAgentList);
|
||||
const openDesktopAgentList = usePanelStore((s) => s.openDesktopAgentList);
|
||||
const openProjectPicker = useOpenProjectPicker(serverId);
|
||||
const hasHydrated = useSessionStore((s) => s.sessions[serverId]?.hasHydratedWorkspaces ?? false);
|
||||
const hasProjects = useHasWorkspaces(serverId);
|
||||
@@ -31,9 +31,9 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCompactLayout) {
|
||||
openAgentList();
|
||||
openDesktopAgentList();
|
||||
}
|
||||
}, [isCompactLayout, openAgentList]);
|
||||
}, [isCompactLayout, openDesktopAgentList]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
||||
@@ -48,7 +48,11 @@ import { WorkspaceScriptsButton } from "@/screens/workspace/workspace-scripts-bu
|
||||
import { ExplorerSidebarAnimationProvider } from "@/contexts/explorer-sidebar-animation-context";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
|
||||
import { usePanelStore, type ExplorerCheckoutContext } from "@/stores/panel-store";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
import {
|
||||
setActiveExplorerCheckout,
|
||||
type ExplorerCheckoutContext,
|
||||
} from "@/stores/explorer-checkout-context";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import {
|
||||
buildWorkspaceTabPersistenceKey,
|
||||
@@ -843,13 +847,14 @@ function WorkspaceScreenContent({
|
||||
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
||||
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
|
||||
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
|
||||
const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer);
|
||||
const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer);
|
||||
const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer);
|
||||
const activateExplorerTabForCheckout = usePanelStore(
|
||||
(state) => state.activateExplorerTabForCheckout,
|
||||
);
|
||||
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
||||
const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout);
|
||||
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
|
||||
|
||||
const isExplorerOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
|
||||
|
||||
@@ -876,16 +881,36 @@ function WorkspaceScreenContent({
|
||||
return;
|
||||
}
|
||||
activateExplorerTabForCheckout(activeExplorerCheckout);
|
||||
openFileExplorer();
|
||||
}, [activateExplorerTabForCheckout, activeExplorerCheckout, openFileExplorer]);
|
||||
if (isMobile) {
|
||||
showMobileFileExplorer();
|
||||
return;
|
||||
}
|
||||
openDesktopFileExplorer();
|
||||
}, [
|
||||
activateExplorerTabForCheckout,
|
||||
activeExplorerCheckout,
|
||||
isMobile,
|
||||
openDesktopFileExplorer,
|
||||
showMobileFileExplorer,
|
||||
]);
|
||||
|
||||
const handleToggleExplorer = useCallback(() => {
|
||||
if (isExplorerOpen) {
|
||||
toggleFileExplorer();
|
||||
if (isMobile) {
|
||||
toggleMobileFileExplorer();
|
||||
return;
|
||||
}
|
||||
toggleDesktopFileExplorer();
|
||||
return;
|
||||
}
|
||||
openExplorerForWorkspace();
|
||||
}, [isExplorerOpen, openExplorerForWorkspace, toggleFileExplorer]);
|
||||
}, [
|
||||
isExplorerOpen,
|
||||
isMobile,
|
||||
openExplorerForWorkspace,
|
||||
toggleDesktopFileExplorer,
|
||||
toggleMobileFileExplorer,
|
||||
]);
|
||||
|
||||
const explorerOpenGesture = useExplorerOpenGesture({
|
||||
enabled: isMobile && mobileView === "agent",
|
||||
@@ -899,14 +924,14 @@ function WorkspaceScreenContent({
|
||||
|
||||
const handler = BackHandler.addEventListener("hardwareBackPress", () => {
|
||||
if (isExplorerOpen) {
|
||||
closeToAgent();
|
||||
showMobileAgent();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return () => handler.remove();
|
||||
}, [closeToAgent, isExplorerOpen, isRouteFocused]);
|
||||
}, [isExplorerOpen, isRouteFocused, showMobileAgent]);
|
||||
|
||||
const persistenceKey = useMemo(
|
||||
() =>
|
||||
@@ -1190,7 +1215,7 @@ function WorkspaceScreenContent({
|
||||
const handleOpenFileFromExplorer = useCallback(
|
||||
function handleOpenFileFromExplorer(filePath: string) {
|
||||
if (isMobile) {
|
||||
closeToAgent();
|
||||
showMobileAgent();
|
||||
}
|
||||
if (!persistenceKey) {
|
||||
return;
|
||||
@@ -1200,7 +1225,7 @@ function WorkspaceScreenContent({
|
||||
navigateToTabId(tabId);
|
||||
}
|
||||
},
|
||||
[closeToAgent, isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey],
|
||||
[isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey, showMobileAgent],
|
||||
);
|
||||
|
||||
const handleOpenFileFromChat = useCallback(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface ExplorerCheckoutContext {
|
||||
serverId: string;
|
||||
cwd: string;
|
||||
isGit: boolean;
|
||||
}
|
||||
|
||||
let activeExplorerCheckout: ExplorerCheckoutContext | null = null;
|
||||
|
||||
export function getActiveExplorerCheckout(): ExplorerCheckoutContext | null {
|
||||
return activeExplorerCheckout;
|
||||
}
|
||||
|
||||
export function setActiveExplorerCheckout(checkout: ExplorerCheckoutContext | null) {
|
||||
if (
|
||||
activeExplorerCheckout?.serverId === checkout?.serverId &&
|
||||
activeExplorerCheckout?.cwd === checkout?.cwd &&
|
||||
activeExplorerCheckout?.isGit === checkout?.isGit
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeExplorerCheckout = checkout;
|
||||
}
|
||||
@@ -8,8 +8,13 @@ import {
|
||||
resolveExplorerTabForCheckout,
|
||||
type ExplorerTab,
|
||||
} from "./explorer-tab-memory";
|
||||
import {
|
||||
getActiveExplorerCheckout,
|
||||
type ExplorerCheckoutContext,
|
||||
} from "./explorer-checkout-context";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
export type { ExplorerTab } from "./explorer-tab-memory";
|
||||
export type { ExplorerCheckoutContext } from "./explorer-checkout-context";
|
||||
|
||||
/**
|
||||
* Mobile panel state machine.
|
||||
@@ -37,12 +42,6 @@ interface DesktopSidebarState {
|
||||
}
|
||||
|
||||
export type SortOption = "name" | "modified" | "size";
|
||||
export interface ExplorerCheckoutContext {
|
||||
serverId: string;
|
||||
cwd: string;
|
||||
isGit: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SIDEBAR_WIDTH = 320;
|
||||
export const MIN_SIDEBAR_WIDTH = 200;
|
||||
export const MAX_SIDEBAR_WIDTH = 600;
|
||||
@@ -68,21 +67,25 @@ interface PanelState {
|
||||
explorerTabByCheckout: Record<string, ExplorerTab>;
|
||||
expandedPathsByWorkspace: Record<string, string[]>;
|
||||
diffExpandedPathsByWorkspace: Record<string, string[]>;
|
||||
activeExplorerCheckout: ExplorerCheckoutContext | null;
|
||||
sidebarWidth: number;
|
||||
explorerWidth: number;
|
||||
explorerSortOption: SortOption;
|
||||
explorerFilesSplitRatio: number;
|
||||
|
||||
// Actions
|
||||
openAgentList: () => void;
|
||||
openFileExplorer: () => void;
|
||||
closeFileExplorer: () => void;
|
||||
closeToAgent: () => void;
|
||||
toggleAgentList: () => void;
|
||||
toggleFileExplorer: () => void;
|
||||
toggleBothSidebars: () => void;
|
||||
toggleFocusMode: () => void;
|
||||
showMobileAgent: () => void;
|
||||
showMobileAgentList: () => void;
|
||||
showMobileFileExplorer: () => void;
|
||||
toggleMobileAgentList: () => void;
|
||||
toggleMobileFileExplorer: () => void;
|
||||
openDesktopAgentList: () => void;
|
||||
closeDesktopAgentList: () => void;
|
||||
toggleDesktopAgentList: () => void;
|
||||
openDesktopFileExplorer: () => void;
|
||||
closeDesktopFileExplorer: () => void;
|
||||
toggleDesktopFileExplorer: () => void;
|
||||
toggleDesktopSidebars: () => void;
|
||||
|
||||
// File explorer settings actions
|
||||
setExplorerTab: (tab: ExplorerTab) => void;
|
||||
@@ -90,7 +93,6 @@ interface PanelState {
|
||||
setExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void;
|
||||
setDiffExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void;
|
||||
activateExplorerTabForCheckout: (checkout: ExplorerCheckoutContext) => void;
|
||||
setActiveExplorerCheckout: (checkout: ExplorerCheckoutContext | null) => void;
|
||||
setSidebarWidth: (width: number) => void;
|
||||
setExplorerWidth: (width: number) => void;
|
||||
setExplorerSortOption: (option: SortOption) => void;
|
||||
@@ -116,14 +118,17 @@ function clampExplorerFilesSplitRatio(ratio: number): number {
|
||||
return clampNumber(ratio, MIN_EXPLORER_FILES_SPLIT_RATIO, MAX_EXPLORER_FILES_SPLIT_RATIO);
|
||||
}
|
||||
|
||||
function resolveExplorerTabFromActiveCheckout(state: PanelState): ExplorerTab | null {
|
||||
if (!state.activeExplorerCheckout) {
|
||||
function resolveExplorerTabFromCheckout(
|
||||
state: PanelState,
|
||||
checkout: ExplorerCheckoutContext | null,
|
||||
): ExplorerTab | null {
|
||||
if (!checkout) {
|
||||
return null;
|
||||
}
|
||||
return resolveExplorerTabForCheckout({
|
||||
serverId: state.activeExplorerCheckout.serverId,
|
||||
cwd: state.activeExplorerCheckout.cwd,
|
||||
isGit: state.activeExplorerCheckout.isGit,
|
||||
serverId: checkout.serverId,
|
||||
cwd: checkout.cwd,
|
||||
isGit: checkout.isGit,
|
||||
explorerTabByCheckout: state.explorerTabByCheckout,
|
||||
});
|
||||
}
|
||||
@@ -148,103 +153,118 @@ export const usePanelStore = create<PanelState>()(
|
||||
explorerTabByCheckout: {},
|
||||
expandedPathsByWorkspace: {},
|
||||
diffExpandedPathsByWorkspace: {},
|
||||
activeExplorerCheckout: null,
|
||||
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
||||
explorerWidth: DEFAULT_EXPLORER_SIDEBAR_WIDTH,
|
||||
explorerSortOption: "name",
|
||||
explorerFilesSplitRatio: DEFAULT_EXPLORER_FILES_SPLIT_RATIO,
|
||||
|
||||
openAgentList: () =>
|
||||
set((state) => ({
|
||||
mobileView: "agent-list" as const,
|
||||
desktop: { ...state.desktop, agentListOpen: true },
|
||||
})),
|
||||
|
||||
openFileExplorer: () =>
|
||||
set((state) => {
|
||||
const resolvedTab = resolveExplorerTabFromActiveCheckout(state);
|
||||
return {
|
||||
mobileView: "file-explorer" as MobilePanelView,
|
||||
desktop: { ...state.desktop, fileExplorerOpen: true },
|
||||
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
|
||||
};
|
||||
}),
|
||||
closeFileExplorer: () =>
|
||||
set((state) => ({
|
||||
mobileView: state.mobileView === "file-explorer" ? ("agent" as const) : state.mobileView,
|
||||
desktop: {
|
||||
...state.desktop,
|
||||
fileExplorerOpen: false,
|
||||
},
|
||||
})),
|
||||
|
||||
closeToAgent: () =>
|
||||
set((state) => ({
|
||||
mobileView: "agent" as const,
|
||||
// On desktop, closing depends on which panel triggered it
|
||||
// This is called when closing via gesture/backdrop, so we close the currently active mobile panel
|
||||
desktop: {
|
||||
...state.desktop,
|
||||
agentListOpen: state.mobileView === "agent-list" ? false : state.desktop.agentListOpen,
|
||||
fileExplorerOpen:
|
||||
state.mobileView === "file-explorer" ? false : state.desktop.fileExplorerOpen,
|
||||
},
|
||||
})),
|
||||
|
||||
toggleAgentList: () =>
|
||||
set((state) => ({
|
||||
// Mobile: toggle between agent and agent-list
|
||||
mobileView: (state.mobileView === "agent-list"
|
||||
? "agent"
|
||||
: "agent-list") as MobilePanelView,
|
||||
desktop: {
|
||||
...state.desktop,
|
||||
agentListOpen: !state.desktop.agentListOpen,
|
||||
},
|
||||
})),
|
||||
|
||||
toggleFileExplorer: () =>
|
||||
set((state) => {
|
||||
// Mobile: toggle between agent and file-explorer
|
||||
const willOpenMobile = state.mobileView !== "file-explorer";
|
||||
const willOpenDesktop = !state.desktop.fileExplorerOpen;
|
||||
const nextMobileView: MobilePanelView = willOpenMobile ? "file-explorer" : "agent";
|
||||
const nextDesktop = {
|
||||
...state.desktop,
|
||||
fileExplorerOpen: willOpenDesktop,
|
||||
};
|
||||
const nextState: Pick<PanelState, "mobileView" | "desktop"> &
|
||||
Partial<Pick<PanelState, "explorerTab">> = {
|
||||
mobileView: nextMobileView,
|
||||
desktop: nextDesktop,
|
||||
};
|
||||
if (willOpenMobile || willOpenDesktop) {
|
||||
const resolvedTab = resolveExplorerTabFromActiveCheckout(state);
|
||||
if (resolvedTab) {
|
||||
nextState.explorerTab = resolvedTab;
|
||||
}
|
||||
}
|
||||
return nextState;
|
||||
}),
|
||||
|
||||
toggleFocusMode: () =>
|
||||
set((state) => ({
|
||||
desktop: { ...state.desktop, focusModeEnabled: !state.desktop.focusModeEnabled },
|
||||
})),
|
||||
|
||||
toggleBothSidebars: () =>
|
||||
showMobileAgent: () =>
|
||||
set((state) => {
|
||||
if (state.mobileView === "agent") {
|
||||
return state;
|
||||
}
|
||||
return { mobileView: "agent" as const };
|
||||
}),
|
||||
|
||||
showMobileAgentList: () =>
|
||||
set((state) => {
|
||||
if (state.mobileView === "agent-list") {
|
||||
return state;
|
||||
}
|
||||
return { mobileView: "agent-list" as const };
|
||||
}),
|
||||
|
||||
showMobileFileExplorer: () =>
|
||||
set((state) => {
|
||||
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
|
||||
return {
|
||||
mobileView: "file-explorer" as const,
|
||||
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
|
||||
};
|
||||
}),
|
||||
|
||||
toggleMobileAgentList: () =>
|
||||
set((state) => ({
|
||||
mobileView: state.mobileView === "agent-list" ? "agent" : "agent-list",
|
||||
})),
|
||||
|
||||
toggleMobileFileExplorer: () =>
|
||||
set((state) => {
|
||||
if (state.mobileView === "file-explorer") {
|
||||
return { mobileView: "agent" as const };
|
||||
}
|
||||
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
|
||||
return {
|
||||
mobileView: "file-explorer" as const,
|
||||
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
|
||||
};
|
||||
}),
|
||||
|
||||
openDesktopAgentList: () =>
|
||||
set((state) => {
|
||||
if (state.desktop.agentListOpen) {
|
||||
return state;
|
||||
}
|
||||
return { desktop: { ...state.desktop, agentListOpen: true } };
|
||||
}),
|
||||
|
||||
closeDesktopAgentList: () =>
|
||||
set((state) => {
|
||||
if (!state.desktop.agentListOpen) {
|
||||
return state;
|
||||
}
|
||||
return { desktop: { ...state.desktop, agentListOpen: false } };
|
||||
}),
|
||||
|
||||
toggleDesktopAgentList: () =>
|
||||
set((state) => ({
|
||||
desktop: { ...state.desktop, agentListOpen: !state.desktop.agentListOpen },
|
||||
})),
|
||||
|
||||
openDesktopFileExplorer: () =>
|
||||
set((state) => {
|
||||
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
|
||||
return {
|
||||
desktop: { ...state.desktop, fileExplorerOpen: true },
|
||||
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
|
||||
};
|
||||
}),
|
||||
|
||||
closeDesktopFileExplorer: () =>
|
||||
set((state) => {
|
||||
if (!state.desktop.fileExplorerOpen) {
|
||||
return state;
|
||||
}
|
||||
return { desktop: { ...state.desktop, fileExplorerOpen: false } };
|
||||
}),
|
||||
|
||||
toggleDesktopFileExplorer: () =>
|
||||
set((state) => {
|
||||
const willOpen = !state.desktop.fileExplorerOpen;
|
||||
const resolvedTab = willOpen
|
||||
? resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout())
|
||||
: null;
|
||||
return {
|
||||
desktop: { ...state.desktop, fileExplorerOpen: willOpen },
|
||||
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
|
||||
};
|
||||
}),
|
||||
|
||||
toggleDesktopSidebars: () =>
|
||||
set((state) => {
|
||||
// If either sidebar is open, close both. Otherwise, open both.
|
||||
const anyOpen = state.desktop.agentListOpen || state.desktop.fileExplorerOpen;
|
||||
if (anyOpen) {
|
||||
return {
|
||||
mobileView: "agent" as MobilePanelView,
|
||||
desktop: { ...state.desktop, agentListOpen: false, fileExplorerOpen: false },
|
||||
};
|
||||
}
|
||||
const resolvedTab = resolveExplorerTabFromActiveCheckout(state);
|
||||
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
|
||||
return {
|
||||
mobileView: "agent" as MobilePanelView,
|
||||
desktop: { ...state.desktop, agentListOpen: true, fileExplorerOpen: true },
|
||||
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
|
||||
};
|
||||
@@ -280,7 +300,6 @@ export const usePanelStore = create<PanelState>()(
|
||||
})),
|
||||
activateExplorerTabForCheckout: (checkout) =>
|
||||
set((state) => ({
|
||||
activeExplorerCheckout: checkout,
|
||||
explorerTab: resolveExplorerTabForCheckout({
|
||||
serverId: checkout.serverId,
|
||||
cwd: checkout.cwd,
|
||||
@@ -288,18 +307,6 @@ export const usePanelStore = create<PanelState>()(
|
||||
explorerTabByCheckout: state.explorerTabByCheckout,
|
||||
}),
|
||||
})),
|
||||
setActiveExplorerCheckout: (checkout) =>
|
||||
set((state) => {
|
||||
const current = state.activeExplorerCheckout;
|
||||
if (
|
||||
current?.serverId === checkout?.serverId &&
|
||||
current?.cwd === checkout?.cwd &&
|
||||
current?.isGit === checkout?.isGit
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
return { activeExplorerCheckout: checkout };
|
||||
}),
|
||||
setSidebarWidth: (width) => set({ sidebarWidth: clampSidebarWidth(width) }),
|
||||
setExplorerWidth: (width) => set({ explorerWidth: clampWidth(width) }),
|
||||
setExplorerSortOption: (option) => set({ explorerSortOption: option }),
|
||||
@@ -400,8 +407,6 @@ export const usePanelStore = create<PanelState>()(
|
||||
state.diffExpandedPathsByWorkspace = {};
|
||||
}
|
||||
|
||||
state.activeExplorerCheckout = null;
|
||||
|
||||
return state as PanelState;
|
||||
},
|
||||
partialize: (state) => ({
|
||||
@@ -435,12 +440,12 @@ export function usePanelState(isMobile: boolean) {
|
||||
return {
|
||||
isAgentListOpen: store.mobileView === "agent-list",
|
||||
isFileExplorerOpen: store.mobileView === "file-explorer",
|
||||
openAgentList: store.openAgentList,
|
||||
openFileExplorer: store.openFileExplorer,
|
||||
closeAgentList: store.closeToAgent,
|
||||
closeFileExplorer: store.closeToAgent,
|
||||
toggleAgentList: store.toggleAgentList,
|
||||
toggleFileExplorer: store.toggleFileExplorer,
|
||||
openAgentList: store.showMobileAgentList,
|
||||
openFileExplorer: store.showMobileFileExplorer,
|
||||
closeAgentList: store.showMobileAgent,
|
||||
closeFileExplorer: store.showMobileAgent,
|
||||
toggleAgentList: store.toggleMobileAgentList,
|
||||
toggleFileExplorer: store.toggleMobileFileExplorer,
|
||||
// Explorer settings
|
||||
explorerTab: store.explorerTab,
|
||||
explorerTabByCheckout: store.explorerTabByCheckout,
|
||||
@@ -450,7 +455,6 @@ export function usePanelState(isMobile: boolean) {
|
||||
setExplorerTab: store.setExplorerTab,
|
||||
setExplorerTabForCheckout: store.setExplorerTabForCheckout,
|
||||
activateExplorerTabForCheckout: store.activateExplorerTabForCheckout,
|
||||
setActiveExplorerCheckout: store.setActiveExplorerCheckout,
|
||||
setExplorerWidth: store.setExplorerWidth,
|
||||
setExplorerSortOption: store.setExplorerSortOption,
|
||||
setExplorerFilesSplitRatio: store.setExplorerFilesSplitRatio,
|
||||
@@ -461,18 +465,12 @@ export function usePanelState(isMobile: boolean) {
|
||||
return {
|
||||
isAgentListOpen: store.desktop.agentListOpen,
|
||||
isFileExplorerOpen: store.desktop.fileExplorerOpen,
|
||||
openAgentList: store.openAgentList,
|
||||
openFileExplorer: store.openFileExplorer,
|
||||
closeAgentList: () =>
|
||||
usePanelStore.setState((state) => ({
|
||||
desktop: { ...state.desktop, agentListOpen: false },
|
||||
})),
|
||||
closeFileExplorer: () =>
|
||||
usePanelStore.setState((state) => ({
|
||||
desktop: { ...state.desktop, fileExplorerOpen: false },
|
||||
})),
|
||||
toggleAgentList: store.toggleAgentList,
|
||||
toggleFileExplorer: store.toggleFileExplorer,
|
||||
openAgentList: store.openDesktopAgentList,
|
||||
openFileExplorer: store.openDesktopFileExplorer,
|
||||
closeAgentList: store.closeDesktopAgentList,
|
||||
closeFileExplorer: store.closeDesktopFileExplorer,
|
||||
toggleAgentList: store.toggleDesktopAgentList,
|
||||
toggleFileExplorer: store.toggleDesktopFileExplorer,
|
||||
// Explorer settings
|
||||
explorerTab: store.explorerTab,
|
||||
explorerTabByCheckout: store.explorerTabByCheckout,
|
||||
@@ -482,7 +480,6 @@ export function usePanelState(isMobile: boolean) {
|
||||
setExplorerTab: store.setExplorerTab,
|
||||
setExplorerTabForCheckout: store.setExplorerTabForCheckout,
|
||||
activateExplorerTabForCheckout: store.activateExplorerTabForCheckout,
|
||||
setActiveExplorerCheckout: store.setActiveExplorerCheckout,
|
||||
setExplorerWidth: store.setExplorerWidth,
|
||||
setExplorerSortOption: store.setExplorerSortOption,
|
||||
setExplorerFilesSplitRatio: store.setExplorerFilesSplitRatio,
|
||||
|
||||
Reference in New Issue
Block a user