mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 07:33:18 +00:00
fix(app): choose workspace chrome within each desktop pane
The outer width wrapper added a compact header while the desktop split container kept its full header and pane rails. Each pane now owns the complete choice, replacing its header and rail together. Each split pane uses its own measured width: below 500 points it shows its own tab dropdown, even if the neighboring pane is wide. Pane width determines usable header space. Keep new-tab keyboard ownership outside the switched chrome and retain positive measurements while hidden. Co-authored-by: Elsa Granger <zeyugao@outlook.com>
This commit is contained in:
co-authored by
Elsa Granger
parent
bfe11d31a6
commit
d48f1cc64e
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
AdaptiveWorkspaceChrome,
|
||||
type WorkspaceCompactHeaderActions,
|
||||
} from "@/screens/workspace/adaptive-workspace-chrome";
|
||||
import { useWorkspaceTabDrag } from "./workspace-tab-drag-context";
|
||||
import {
|
||||
Fragment,
|
||||
@@ -42,6 +46,7 @@ import type { WorkspacePaneContentModel } from "@/screens/workspace/workspace-pa
|
||||
import { WorkspacePanelHost } from "@/screens/workspace/workspace-panel-host";
|
||||
import {
|
||||
WorkspaceDesktopTabsRow,
|
||||
useWorkspaceNewTabKeyboardAction,
|
||||
type WorkspaceDesktopTabRowItem,
|
||||
} from "@/screens/workspace/workspace-desktop-tabs-row";
|
||||
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
|
||||
@@ -61,6 +66,11 @@ import { isNative } from "@/constants/platform";
|
||||
interface SplitContainerProps {
|
||||
layout: WorkspaceLayout;
|
||||
renderMainHeader?: () => ReactNode;
|
||||
renderCompactHeader: (
|
||||
tabs: WorkspaceTabDescriptor[],
|
||||
activeTabKey: string,
|
||||
actions: WorkspaceCompactHeaderActions,
|
||||
) => ReactNode;
|
||||
workspaceKey: string;
|
||||
normalizedServerId: string;
|
||||
normalizedWorkspaceId: string;
|
||||
@@ -145,7 +155,6 @@ const EMPTY_SPLIT_SIZES: number[] = [];
|
||||
|
||||
export function SplitContainer({
|
||||
layout,
|
||||
renderMainHeader,
|
||||
workspaceKey,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
@@ -174,6 +183,8 @@ export function SplitContainer({
|
||||
onReorderTabsInPane,
|
||||
focusModeEnabled,
|
||||
onExitFocusMode,
|
||||
renderMainHeader,
|
||||
renderCompactHeader,
|
||||
}: SplitContainerProps) {
|
||||
const inheritedWindowChromeCorners = useWindowChromeCorners();
|
||||
const windowChromeCorners = focusModeEnabled ? inheritedWindowChromeCorners : "none";
|
||||
@@ -237,7 +248,6 @@ export function SplitContainer({
|
||||
<View style={styles.workspaceShell}>
|
||||
<WindowChromeRegion corners={mainColumnWindowChromeCorners}>
|
||||
<View style={mainColumnStyle}>
|
||||
{renderMainHeader?.()}
|
||||
{splitRoot.usesFallbackStrip && <WindowChromeSafeArea placement="below" />}
|
||||
{renderRoot ? (
|
||||
<SplitNodeView
|
||||
@@ -279,6 +289,8 @@ export function SplitContainer({
|
||||
onTogglePaneMaximized={handleTogglePaneMaximized}
|
||||
focusModeEnabled={focusModeEnabled}
|
||||
onExitFocusMode={onExitFocusMode}
|
||||
renderMainHeader={renderMainHeader}
|
||||
renderCompactHeader={renderCompactHeader}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
@@ -410,6 +422,8 @@ function SplitNodeView({
|
||||
onTogglePaneMaximized,
|
||||
focusModeEnabled,
|
||||
onExitFocusMode,
|
||||
renderMainHeader,
|
||||
renderCompactHeader,
|
||||
}: SplitNodeViewProps) {
|
||||
const [groupContainerSize, setGroupContainerSize] = useState(0);
|
||||
const groupId = node.kind === "group" ? node.group.id : null;
|
||||
@@ -496,6 +510,8 @@ function SplitNodeView({
|
||||
onTogglePaneMaximized={onTogglePaneMaximized}
|
||||
focusModeEnabled={focusModeEnabled}
|
||||
onExitFocusMode={onExitFocusMode}
|
||||
renderMainHeader={renderMainHeader}
|
||||
renderCompactHeader={renderCompactHeader}
|
||||
/>
|
||||
</WindowChromeRegion>
|
||||
</RetainedPanel>
|
||||
@@ -550,6 +566,8 @@ function SplitNodeView({
|
||||
onTogglePaneMaximized={onTogglePaneMaximized}
|
||||
focusModeEnabled={focusModeEnabled}
|
||||
onExitFocusMode={onExitFocusMode}
|
||||
renderMainHeader={renderMainHeader}
|
||||
renderCompactHeader={renderCompactHeader}
|
||||
/>
|
||||
</SplitGroupChild>
|
||||
{index < node.group.children.length - 1 &&
|
||||
@@ -608,6 +626,8 @@ const SplitPaneView = memo(function SplitPaneView({
|
||||
onTogglePaneMaximized,
|
||||
focusModeEnabled,
|
||||
onExitFocusMode,
|
||||
renderMainHeader,
|
||||
renderCompactHeader,
|
||||
}: SplitPaneViewProps) {
|
||||
recordRenderProfileReasons(`SplitPaneView:${pane.id}`, ["render"]);
|
||||
const paneRef = useRef<View | null>(null);
|
||||
@@ -701,6 +721,23 @@ const SplitPaneView = memo(function SplitPaneView({
|
||||
() => onTogglePaneMaximized(paneId),
|
||||
[onTogglePaneMaximized, paneId],
|
||||
);
|
||||
const compactHeaderActions = useMemo(
|
||||
() => ({
|
||||
closeAbove: handleCloseTabsToLeft,
|
||||
closeBelow: handleCloseTabsToRight,
|
||||
closeOthers: handleCloseOtherTabs,
|
||||
}),
|
||||
[handleCloseTabsToLeft, handleCloseTabsToRight, handleCloseOtherTabs],
|
||||
);
|
||||
const createNewTab = useCallback(() => onCreateNewTab({ paneId }), [onCreateNewTab, paneId]);
|
||||
useWorkspaceNewTabKeyboardAction({
|
||||
handlerName: "workspace-chrome-new-tab",
|
||||
ownsKeyboardShortcuts: isFocused && isWorkspaceFocused,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
paneId,
|
||||
createNewTab,
|
||||
});
|
||||
return (
|
||||
<RenderProfile id={`SplitPaneView:${pane.id}`}>
|
||||
<View
|
||||
@@ -709,43 +746,53 @@ const SplitPaneView = memo(function SplitPaneView({
|
||||
style={styles.pane}
|
||||
testID={`workspace-pane-${pane.id}`}
|
||||
>
|
||||
<WindowChromeSafeArea placement="inline" style={styles.paneTabs}>
|
||||
<TitlebarDragRegion />
|
||||
<WorkspaceDesktopTabsRow
|
||||
paneId={pane.id}
|
||||
isFocused={isFocused && isWorkspaceFocused}
|
||||
tabs={desktopTabRowItems}
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||
onNavigateTab={onNavigateTab}
|
||||
onCloseTab={onCloseTab}
|
||||
onCopyResumeCommand={onCopyResumeCommand}
|
||||
onCopyAgentId={onCopyAgentId}
|
||||
onCopyTerminalId={onCopyTerminalId}
|
||||
onCopyFilePath={onCopyFilePath}
|
||||
onReloadAgent={onReloadAgent}
|
||||
onRenameTab={onRenameTab}
|
||||
onCloseTabsToLeft={handleCloseTabsToLeft}
|
||||
onCloseTabsToRight={handleCloseTabsToRight}
|
||||
onCloseOtherTabs={handleCloseOtherTabs}
|
||||
onCreateNewTab={onCreateNewTab}
|
||||
onReorderTabs={handleReorderTabs}
|
||||
externalDndContext
|
||||
activeDragTabId={activeDragTabId}
|
||||
tabDropPreviewIndex={
|
||||
tabDropPreview?.paneId === pane.id ? tabDropPreview.indicatorIndex : null
|
||||
}
|
||||
showPaneSplitActions={!focusModeEnabled}
|
||||
showPaneMaximizeAction={workspaceHasMultiplePanes && !focusModeEnabled}
|
||||
paneMaximized={paneId === maximizedPaneId}
|
||||
onTogglePaneMaximized={handleTogglePaneMaximized}
|
||||
onSplitRight={handleSplitRight}
|
||||
onSplitDown={handleSplitDown}
|
||||
focusModeEnabled={Boolean(focusModeEnabled)}
|
||||
onExitFocusMode={onExitFocusMode}
|
||||
/>
|
||||
</WindowChromeSafeArea>
|
||||
<AdaptiveWorkspaceChrome
|
||||
compactHeader={renderCompactHeader(
|
||||
paneTabs,
|
||||
activeTabDescriptor?.key ?? "",
|
||||
compactHeaderActions,
|
||||
)}
|
||||
>
|
||||
{renderMainHeader?.()}
|
||||
<WindowChromeSafeArea placement="inline" style={styles.paneTabs}>
|
||||
<TitlebarDragRegion />
|
||||
<WorkspaceDesktopTabsRow
|
||||
paneId={pane.id}
|
||||
isFocused={isFocused && isWorkspaceFocused}
|
||||
tabs={desktopTabRowItems}
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||
onNavigateTab={onNavigateTab}
|
||||
onCloseTab={onCloseTab}
|
||||
onCopyResumeCommand={onCopyResumeCommand}
|
||||
onCopyAgentId={onCopyAgentId}
|
||||
onCopyTerminalId={onCopyTerminalId}
|
||||
onCopyFilePath={onCopyFilePath}
|
||||
onReloadAgent={onReloadAgent}
|
||||
onRenameTab={onRenameTab}
|
||||
onCloseTabsToLeft={handleCloseTabsToLeft}
|
||||
onCloseTabsToRight={handleCloseTabsToRight}
|
||||
onCloseOtherTabs={handleCloseOtherTabs}
|
||||
onCreateNewTab={onCreateNewTab}
|
||||
onReorderTabs={handleReorderTabs}
|
||||
ownsKeyboardShortcuts={false}
|
||||
externalDndContext
|
||||
activeDragTabId={activeDragTabId}
|
||||
tabDropPreviewIndex={
|
||||
tabDropPreview?.paneId === pane.id ? tabDropPreview.indicatorIndex : null
|
||||
}
|
||||
showPaneSplitActions={!focusModeEnabled}
|
||||
showPaneMaximizeAction={workspaceHasMultiplePanes && !focusModeEnabled}
|
||||
paneMaximized={paneId === maximizedPaneId}
|
||||
onTogglePaneMaximized={handleTogglePaneMaximized}
|
||||
onSplitRight={handleSplitRight}
|
||||
onSplitDown={handleSplitDown}
|
||||
focusModeEnabled={Boolean(focusModeEnabled)}
|
||||
onExitFocusMode={onExitFocusMode}
|
||||
/>
|
||||
</WindowChromeSafeArea>
|
||||
</AdaptiveWorkspaceChrome>
|
||||
|
||||
<View style={styles.paneContent}>
|
||||
<WorkspacePanelHost
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useCallback, useState, type ReactNode } from "react";
|
||||
import { View, type LayoutChangeEvent } from "react-native";
|
||||
import { selectWorkspaceChrome } from "./workspace-tab-layout";
|
||||
import { WorkspaceTabMeasurementScope } from "./workspace-desktop-tabs-row";
|
||||
|
||||
export function AdaptiveWorkspaceChrome({
|
||||
compactHeader,
|
||||
children,
|
||||
}: {
|
||||
compactHeader: ReactNode;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [width, setWidth] = useState<number | null>(null);
|
||||
const measure = useCallback((event: LayoutChangeEvent) => {
|
||||
const measured = event.nativeEvent.layout.width;
|
||||
if (measured > 0) setWidth(measured);
|
||||
}, []);
|
||||
return (
|
||||
<WorkspaceTabMeasurementScope>
|
||||
<View onLayout={measure}>
|
||||
{selectWorkspaceChrome({ width, compact: compactHeader, full: children })}
|
||||
</View>
|
||||
</WorkspaceTabMeasurementScope>
|
||||
);
|
||||
}
|
||||
|
||||
export interface WorkspaceCompactHeaderActions {
|
||||
closeAbove: (key: string) => Promise<void> | void;
|
||||
closeBelow: (key: string) => Promise<void> | void;
|
||||
closeOthers: (key: string) => Promise<void> | void;
|
||||
}
|
||||
@@ -967,6 +967,47 @@ function TabChip({
|
||||
);
|
||||
}
|
||||
|
||||
export function useWorkspaceNewTabKeyboardAction({
|
||||
handlerName = "workspace-new-tab",
|
||||
ownsKeyboardShortcuts,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
paneId,
|
||||
createNewTab,
|
||||
}: {
|
||||
handlerName?: string;
|
||||
ownsKeyboardShortcuts: boolean;
|
||||
normalizedServerId: string;
|
||||
normalizedWorkspaceId: string;
|
||||
paneId?: string;
|
||||
createNewTab: () => void;
|
||||
}) {
|
||||
const handleNewTabKeyboardAction = useCallback(
|
||||
(action: KeyboardActionDefinition): boolean => {
|
||||
if (!ownsKeyboardShortcuts) return false;
|
||||
if (action.id === "workspace.tab.menu.open") {
|
||||
createNewTab();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[createNewTab, ownsKeyboardShortcuts],
|
||||
);
|
||||
|
||||
useKeyboardActionHandler({
|
||||
handlerId: buildWorkspaceKeyboardHandlerId({
|
||||
name: handlerName,
|
||||
serverId: normalizedServerId,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
paneId,
|
||||
}),
|
||||
actions: ["workspace.tab.menu.open"],
|
||||
enabled: ownsKeyboardShortcuts,
|
||||
priority: 200,
|
||||
handle: handleNewTabKeyboardAction,
|
||||
});
|
||||
}
|
||||
|
||||
export function WorkspaceDesktopTabsRow(props: WorkspaceDesktopTabsRowProps) {
|
||||
const [presentations, setPresentations] = useState(
|
||||
() => new Map<string, WorkspaceTabPresentation>(),
|
||||
@@ -1259,29 +1300,12 @@ function ResolvedWorkspaceDesktopTabsRow({
|
||||
|
||||
const createNewTab = useCallback(() => onCreateNewTab({ paneId }), [onCreateNewTab, paneId]);
|
||||
|
||||
const handleNewTabKeyboardAction = useCallback(
|
||||
(action: KeyboardActionDefinition): boolean => {
|
||||
if (!ownsKeyboardShortcuts) return false;
|
||||
if (action.id === "workspace.tab.menu.open") {
|
||||
createNewTab();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[createNewTab, ownsKeyboardShortcuts],
|
||||
);
|
||||
|
||||
useKeyboardActionHandler({
|
||||
handlerId: buildWorkspaceKeyboardHandlerId({
|
||||
name: "workspace-new-tab",
|
||||
serverId: normalizedServerId,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
paneId,
|
||||
}),
|
||||
actions: ["workspace.tab.menu.open"],
|
||||
enabled: ownsKeyboardShortcuts,
|
||||
priority: 200,
|
||||
handle: handleNewTabKeyboardAction,
|
||||
useWorkspaceNewTabKeyboardAction({
|
||||
ownsKeyboardShortcuts,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
paneId,
|
||||
createNewTab,
|
||||
});
|
||||
|
||||
const rail: WorkspaceTabRailSurface = host === "explorer" ? "sidebar" : "surface";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { shouldUseCompactWorkspaceHeader } from "./workspace-tab-layout";
|
||||
import {
|
||||
AdaptiveWorkspaceChrome,
|
||||
type WorkspaceCompactHeaderActions,
|
||||
} from "./adaptive-workspace-chrome";
|
||||
import { planTimelineTailFetch } from "@/timeline/timeline-sync-plan";
|
||||
import { useHostFeature } from "@/runtime/host-features";
|
||||
import {
|
||||
@@ -148,7 +151,7 @@ import {
|
||||
} from "@/screens/workspace/use-workspace-tab-rename";
|
||||
import {
|
||||
WorkspaceDesktopTabsRow,
|
||||
WorkspaceTabMeasurementScope,
|
||||
useWorkspaceNewTabKeyboardAction,
|
||||
type WorkspaceDesktopTabRowItem,
|
||||
} from "@/screens/workspace/workspace-desktop-tabs-row";
|
||||
import { useDesktopBrowserNewTabRequests } from "@/desktop/browser/new-tab-requests";
|
||||
@@ -788,27 +791,6 @@ function WorkspaceContentProviders({
|
||||
);
|
||||
}
|
||||
|
||||
function AdaptiveWorkspaceChrome({
|
||||
compactHeader,
|
||||
children,
|
||||
}: {
|
||||
compactHeader: ReactNode;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [width, setWidth] = useState<number | null>(null);
|
||||
const measure = useCallback(
|
||||
(event: LayoutChangeEvent) => setWidth(event.nativeEvent.layout.width),
|
||||
[],
|
||||
);
|
||||
return (
|
||||
<WorkspaceTabMeasurementScope>
|
||||
<View onLayout={measure}>
|
||||
{width !== null && shouldUseCompactWorkspaceHeader(width) ? compactHeader : children}
|
||||
</View>
|
||||
</WorkspaceTabMeasurementScope>
|
||||
);
|
||||
}
|
||||
|
||||
function WorkspacePanelSurface({
|
||||
launcher,
|
||||
children,
|
||||
@@ -3384,69 +3366,71 @@ function WorkspaceScreenContent({
|
||||
handleOpenUrlInBrowserTab,
|
||||
],
|
||||
);
|
||||
const compactWorkspaceHeader = useMemo(
|
||||
() => (
|
||||
<CompactWorkspaceHeader
|
||||
isLoading={isWorkspaceHeaderLoading}
|
||||
title={workspaceHeaderTitle}
|
||||
subtitle={workspaceHeaderSubtitle}
|
||||
isSubtitleDistinct={isWorkspaceHeaderSubtitleDistinct}
|
||||
serverId={normalizedServerId}
|
||||
onToggleExplorer={handleToggleExplorerSidebar}
|
||||
explorerToggleLabel={explorerSidebarToggleLabel}
|
||||
explorerToggleAccessibilityState={explorerSidebarToggleAccessibilityState}
|
||||
>
|
||||
<MobileWorkspaceTabSwitcher
|
||||
tabs={tabs}
|
||||
activeTabKey={activeTabKey}
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
onSelectSwitcherTab={handleSelectSwitcherTab}
|
||||
createTerminalDisabled={createTerminalDisabled}
|
||||
onCreateDraftTab={handleCreateDraftTab}
|
||||
onCreateTerminal={handleCreateTerminal}
|
||||
onCopyResumeCommand={handleCopyResumeCommand}
|
||||
onCopyAgentId={handleCopyAgentId}
|
||||
onCopyTerminalId={handleCopyTerminalId}
|
||||
onCopyFilePath={handleCopyFilePath}
|
||||
onReloadAgent={handleReloadAgent}
|
||||
onRenameTab={handleRenameTab}
|
||||
onCloseTab={handleCloseTabById}
|
||||
onCloseTabsAbove={handleCloseTabsToLeft}
|
||||
onCloseTabsBelow={handleCloseTabsToRight}
|
||||
onCloseOtherTabs={handleCloseOtherTabs}
|
||||
/>
|
||||
<WorkspaceHeaderMenuMobile
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
scripts={compactHeaderScripts}
|
||||
currentBranchName={currentBranchName}
|
||||
showWorkspaceSetup={showWorkspaceSetup}
|
||||
showCreateBrowserTab={showCreateBrowserTab}
|
||||
createTerminalDisabled={createTerminalDisabled}
|
||||
importAgentDisabled={!canOpenImportSheet}
|
||||
copyPathDisabled={!workspaceDirectory}
|
||||
onCreateTerminalWithProfile={handleCreateTerminalWithProfile}
|
||||
onCreateBrowser={handleCreateBrowserTab}
|
||||
onOpenImportSheet={openImportSheet}
|
||||
onCopyWorkspacePath={handleCopyWorkspacePath}
|
||||
onCopyBranchName={handleCopyBranchName}
|
||||
onOpenSetupTab={handleOpenSetupTab}
|
||||
/>
|
||||
</CompactWorkspaceHeader>
|
||||
),
|
||||
const renderCompactWorkspaceHeader = useCallback(
|
||||
(
|
||||
headerTabs: WorkspaceTabDescriptor[],
|
||||
headerActiveTabKey: string,
|
||||
actions: WorkspaceCompactHeaderActions,
|
||||
) => {
|
||||
return (
|
||||
<CompactWorkspaceHeader
|
||||
isLoading={isWorkspaceHeaderLoading}
|
||||
title={workspaceHeaderTitle}
|
||||
subtitle={workspaceHeaderSubtitle}
|
||||
isSubtitleDistinct={isWorkspaceHeaderSubtitleDistinct}
|
||||
serverId={normalizedServerId}
|
||||
onToggleExplorer={handleToggleExplorerSidebar}
|
||||
explorerToggleLabel={explorerSidebarToggleLabel}
|
||||
explorerToggleAccessibilityState={explorerSidebarToggleAccessibilityState}
|
||||
>
|
||||
<MobileWorkspaceTabSwitcher
|
||||
tabs={headerTabs}
|
||||
activeTabKey={headerActiveTabKey}
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
onSelectSwitcherTab={handleSelectSwitcherTab}
|
||||
createTerminalDisabled={createTerminalDisabled}
|
||||
onCreateDraftTab={handleCreateDraftTab}
|
||||
onCreateTerminal={handleCreateTerminal}
|
||||
onCopyResumeCommand={handleCopyResumeCommand}
|
||||
onCopyAgentId={handleCopyAgentId}
|
||||
onCopyTerminalId={handleCopyTerminalId}
|
||||
onCopyFilePath={handleCopyFilePath}
|
||||
onReloadAgent={handleReloadAgent}
|
||||
onRenameTab={handleRenameTab}
|
||||
onCloseTab={handleCloseTabById}
|
||||
onCloseTabsAbove={actions.closeAbove}
|
||||
onCloseTabsBelow={actions.closeBelow}
|
||||
onCloseOtherTabs={actions.closeOthers}
|
||||
/>
|
||||
<WorkspaceHeaderMenuMobile
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
scripts={compactHeaderScripts}
|
||||
currentBranchName={currentBranchName}
|
||||
showWorkspaceSetup={showWorkspaceSetup}
|
||||
showCreateBrowserTab={showCreateBrowserTab}
|
||||
createTerminalDisabled={createTerminalDisabled}
|
||||
importAgentDisabled={!canOpenImportSheet}
|
||||
copyPathDisabled={!workspaceDirectory}
|
||||
onCreateTerminalWithProfile={handleCreateTerminalWithProfile}
|
||||
onCreateBrowser={handleCreateBrowserTab}
|
||||
onOpenImportSheet={openImportSheet}
|
||||
onCopyWorkspacePath={handleCopyWorkspacePath}
|
||||
onCopyBranchName={handleCopyBranchName}
|
||||
onOpenSetupTab={handleOpenSetupTab}
|
||||
/>
|
||||
</CompactWorkspaceHeader>
|
||||
);
|
||||
},
|
||||
[
|
||||
activeTabKey,
|
||||
canOpenImportSheet,
|
||||
compactHeaderScripts,
|
||||
createTerminalDisabled,
|
||||
currentBranchName,
|
||||
explorerSidebarToggleAccessibilityState,
|
||||
explorerSidebarToggleLabel,
|
||||
handleCloseOtherTabs,
|
||||
handleCloseTabById,
|
||||
handleCloseTabsToLeft,
|
||||
handleCloseTabsToRight,
|
||||
handleCopyAgentId,
|
||||
handleCopyBranchName,
|
||||
handleCopyFilePath,
|
||||
@@ -3469,12 +3453,16 @@ function WorkspaceScreenContent({
|
||||
openImportSheet,
|
||||
showCreateBrowserTab,
|
||||
showWorkspaceSetup,
|
||||
tabs,
|
||||
workspaceDirectory,
|
||||
workspaceHeaderSubtitle,
|
||||
workspaceHeaderTitle,
|
||||
],
|
||||
);
|
||||
const compactWorkspaceHeader = renderCompactWorkspaceHeader(tabs, activeTabKey, {
|
||||
closeAbove: handleCloseTabsToLeft,
|
||||
closeBelow: handleCloseTabsToRight,
|
||||
closeOthers: handleCloseOtherTabs,
|
||||
});
|
||||
const desktopSplitContent = useMemo(() => {
|
||||
if (!canRenderDesktopPaneSplits || !workspaceLayout || !persistenceKey) {
|
||||
return null;
|
||||
@@ -3483,6 +3471,7 @@ function WorkspaceScreenContent({
|
||||
<SplitContainer
|
||||
layout={workspaceLayout}
|
||||
renderMainHeader={renderWorkspaceScreenHeader}
|
||||
renderCompactHeader={renderCompactWorkspaceHeader}
|
||||
focusModeEnabled={desktopFocusModeEnabled}
|
||||
onExitFocusMode={toggleFocusMode}
|
||||
workspaceKey={persistenceKey}
|
||||
@@ -3519,6 +3508,7 @@ function WorkspaceScreenContent({
|
||||
canRenderDesktopPaneSplits,
|
||||
workspaceLayout,
|
||||
renderWorkspaceScreenHeader,
|
||||
renderCompactWorkspaceHeader,
|
||||
persistenceKey,
|
||||
desktopFocusModeEnabled,
|
||||
toggleFocusMode,
|
||||
@@ -3557,6 +3547,22 @@ function WorkspaceScreenContent({
|
||||
[uiTabs, sheets],
|
||||
);
|
||||
const workspacePanelKey = `${normalizedServerId}:${normalizedWorkspaceId}`;
|
||||
const createFallbackTab = useCallback(
|
||||
() => handleCreateNewTab({ paneId: focusedPaneIdOrUndefined }),
|
||||
[handleCreateNewTab, focusedPaneIdOrUndefined],
|
||||
);
|
||||
const ownsFallbackKeyboardShortcuts = useMemo(
|
||||
() => shouldRenderDesktopPaneFallback && isRouteFocused,
|
||||
[shouldRenderDesktopPaneFallback, isRouteFocused],
|
||||
);
|
||||
useWorkspaceNewTabKeyboardAction({
|
||||
handlerName: "workspace-chrome-new-tab",
|
||||
ownsKeyboardShortcuts: ownsFallbackKeyboardShortcuts,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
paneId: focusedPaneIdOrUndefined,
|
||||
createNewTab: createFallbackTab,
|
||||
});
|
||||
const compactHeaderLayout = resolveCompactHeaderLayout({
|
||||
header: compactWorkspaceHeader,
|
||||
activeTab: activeTabDescriptor,
|
||||
@@ -3580,37 +3586,40 @@ function WorkspaceScreenContent({
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.centerColumn}>
|
||||
<AdaptiveWorkspaceChrome compactHeader={compactWorkspaceHeader}>
|
||||
{rendersDesktopSplitContent ? null : renderWorkspaceScreenHeader()}
|
||||
{rendersDesktopSplitContent ? null : (
|
||||
<AdaptiveWorkspaceChrome compactHeader={compactWorkspaceHeader}>
|
||||
{renderWorkspaceScreenHeader()}
|
||||
|
||||
{shouldRenderDesktopPaneFallback ? (
|
||||
<NewTabLauncherProvider value={newTabLauncher}>
|
||||
<WorkspaceDesktopTabsRow
|
||||
paneId={focusedPaneIdOrUndefined}
|
||||
isFocused={isRouteFocused}
|
||||
tabs={desktopTabRowItems}
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||
onNavigateTab={navigateToTabId}
|
||||
onCloseTab={handleCloseTabById}
|
||||
onCopyResumeCommand={handleCopyResumeCommand}
|
||||
onCopyAgentId={handleCopyAgentId}
|
||||
onCopyTerminalId={handleCopyTerminalId}
|
||||
onCopyFilePath={handleCopyFilePath}
|
||||
onReloadAgent={handleReloadAgent}
|
||||
onRenameTab={handleRenameTab}
|
||||
onCloseTabsToLeft={handleCloseTabsToLeft}
|
||||
onCloseTabsToRight={handleCloseTabsToRight}
|
||||
onCloseOtherTabs={handleCloseOtherTabs}
|
||||
onCreateNewTab={handleCreateNewTab}
|
||||
onReorderTabs={handleReorderTabsInFocusedPane}
|
||||
focusModeEnabled={desktopFocusModeEnabled}
|
||||
onExitFocusMode={toggleFocusMode}
|
||||
/>
|
||||
</NewTabLauncherProvider>
|
||||
) : null}
|
||||
</AdaptiveWorkspaceChrome>
|
||||
{shouldRenderDesktopPaneFallback ? (
|
||||
<NewTabLauncherProvider value={newTabLauncher}>
|
||||
<WorkspaceDesktopTabsRow
|
||||
paneId={focusedPaneIdOrUndefined}
|
||||
ownsKeyboardShortcuts={false}
|
||||
isFocused={isRouteFocused}
|
||||
tabs={desktopTabRowItems}
|
||||
normalizedServerId={normalizedServerId}
|
||||
normalizedWorkspaceId={normalizedWorkspaceId}
|
||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||
onNavigateTab={navigateToTabId}
|
||||
onCloseTab={handleCloseTabById}
|
||||
onCopyResumeCommand={handleCopyResumeCommand}
|
||||
onCopyAgentId={handleCopyAgentId}
|
||||
onCopyTerminalId={handleCopyTerminalId}
|
||||
onCopyFilePath={handleCopyFilePath}
|
||||
onReloadAgent={handleReloadAgent}
|
||||
onRenameTab={handleRenameTab}
|
||||
onCloseTabsToLeft={handleCloseTabsToLeft}
|
||||
onCloseTabsToRight={handleCloseTabsToRight}
|
||||
onCloseOtherTabs={handleCloseOtherTabs}
|
||||
onCreateNewTab={handleCreateNewTab}
|
||||
onReorderTabs={handleReorderTabsInFocusedPane}
|
||||
focusModeEnabled={desktopFocusModeEnabled}
|
||||
onExitFocusMode={toggleFocusMode}
|
||||
/>
|
||||
</NewTabLauncherProvider>
|
||||
) : null}
|
||||
</AdaptiveWorkspaceChrome>
|
||||
)}
|
||||
<View style={styles.centerContent}>
|
||||
<View style={styles.content}>{desktopContent}</View>
|
||||
</View>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SidebarCloseButton } from "@/components/headers/sidebar-close-button";
|
||||
import { resolveWorkspaceTabChipBackdrop } from "@/screens/workspace/workspace-tab-chip-backdrop";
|
||||
import { darkPureBlackTheme, darkTheme, lightTheme } from "@/styles/theme";
|
||||
import {
|
||||
selectWorkspaceChrome,
|
||||
computeWorkspaceTabLayout,
|
||||
retainWorkspaceTabMeasuredWidth,
|
||||
} from "@/screens/workspace/workspace-tab-layout";
|
||||
@@ -266,3 +267,14 @@ it.each([
|
||||
])("workspace width %s selects compact=%s", (width, compact) => {
|
||||
expect(shouldUseCompactWorkspaceHeader(width)).toBe(compact);
|
||||
});
|
||||
|
||||
describe("desktop pane chrome", () => {
|
||||
it("replaces the pane header and rail together at the pane width boundary", () => {
|
||||
const full = ["workspace header", "pane tab rail"];
|
||||
expect(selectWorkspaceChrome({ width: 440, compact: "pane dropdown", full })).toBe(
|
||||
"pane dropdown",
|
||||
);
|
||||
expect(selectWorkspaceChrome({ width: 500, compact: "pane dropdown", full })).toBe(full);
|
||||
expect(selectWorkspaceChrome({ width: null, compact: "pane dropdown", full })).toBe(full);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -126,3 +126,12 @@ export function computeWorkspaceTabLayout(
|
||||
requiresHorizontalScrollFallback,
|
||||
};
|
||||
}
|
||||
|
||||
export function selectWorkspaceChrome<Compact, Full>(input: {
|
||||
width: number | null;
|
||||
compact: Compact;
|
||||
full: Full;
|
||||
}): Compact | Full {
|
||||
const compact = input.width !== null && shouldUseCompactWorkspaceHeader(input.width);
|
||||
return compact ? input.compact : input.full;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user