Fix workspace tab presentation hook ordering
This commit is contained in:
@@ -41,7 +41,7 @@ import {
|
|||||||
type WorkspaceDesktopTabRowItem,
|
type WorkspaceDesktopTabRowItem,
|
||||||
} from "@/screens/workspace/workspace-desktop-tabs-row";
|
} from "@/screens/workspace/workspace-desktop-tabs-row";
|
||||||
import {
|
import {
|
||||||
useWorkspaceTabPresentation,
|
WorkspaceTabPresentationResolver,
|
||||||
WorkspaceTabIcon,
|
WorkspaceTabIcon,
|
||||||
} from "@/screens/workspace/workspace-tab-presentation";
|
} from "@/screens/workspace/workspace-tab-presentation";
|
||||||
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
|
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
|
||||||
@@ -489,35 +489,41 @@ function DragOverlayTabChipInner({
|
|||||||
normalizedWorkspaceId: string;
|
normalizedWorkspaceId: string;
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const presentation = useWorkspaceTabPresentation({
|
|
||||||
tab,
|
|
||||||
serverId: normalizedServerId,
|
|
||||||
workspaceId: normalizedWorkspaceId,
|
|
||||||
});
|
|
||||||
const label =
|
|
||||||
presentation.titleState === "loading" ? "Loading..." : presentation.label;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<WorkspaceTabPresentationResolver
|
||||||
style={[
|
tab={tab}
|
||||||
styles.dragOverlayChip,
|
serverId={normalizedServerId}
|
||||||
{
|
workspaceId={normalizedWorkspaceId}
|
||||||
backgroundColor: theme.colors.surface1,
|
|
||||||
borderColor: theme.colors.borderAccent,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<WorkspaceTabIcon presentation={presentation} active size={14} />
|
{(presentation) => {
|
||||||
<Text
|
const label =
|
||||||
numberOfLines={1}
|
presentation.titleState === "loading" ? "Loading..." : presentation.label;
|
||||||
style={[
|
|
||||||
styles.dragOverlayLabel,
|
return (
|
||||||
{ color: theme.colors.foreground },
|
<View
|
||||||
]}
|
style={[
|
||||||
>
|
styles.dragOverlayChip,
|
||||||
{label}
|
{
|
||||||
</Text>
|
backgroundColor: theme.colors.surface1,
|
||||||
</View>
|
borderColor: theme.colors.borderAccent,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<WorkspaceTabIcon presentation={presentation} active size={14} />
|
||||||
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={[
|
||||||
|
styles.dragOverlayLabel,
|
||||||
|
{ color: theme.colors.foreground },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</WorkspaceTabPresentationResolver>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
|||||||
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
|
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
|
||||||
import { useWorkspaceTabLayout } from "@/screens/workspace/use-workspace-tab-layout";
|
import { useWorkspaceTabLayout } from "@/screens/workspace/use-workspace-tab-layout";
|
||||||
import {
|
import {
|
||||||
useWorkspaceTabPresentation,
|
WorkspaceTabPresentationResolver,
|
||||||
WorkspaceTabIcon,
|
WorkspaceTabIcon,
|
||||||
type WorkspaceTabPresentation,
|
type WorkspaceTabPresentation,
|
||||||
} from "@/screens/workspace/workspace-tab-presentation";
|
} from "@/screens/workspace/workspace-tab-presentation";
|
||||||
@@ -546,13 +546,6 @@ function ResolvedDesktopTabChip({
|
|||||||
showDropIndicatorBefore: boolean;
|
showDropIndicatorBefore: boolean;
|
||||||
showDropIndicatorAfter: boolean;
|
showDropIndicatorAfter: boolean;
|
||||||
}) {
|
}) {
|
||||||
const presentation = useWorkspaceTabPresentation({
|
|
||||||
tab: item.tab,
|
|
||||||
serverId: normalizedServerId,
|
|
||||||
workspaceId: normalizedWorkspaceId,
|
|
||||||
});
|
|
||||||
const tooltipLabel =
|
|
||||||
presentation.titleState === "loading" ? "Loading agent title" : presentation.label;
|
|
||||||
const resolvedTab = useMemo(
|
const resolvedTab = useMemo(
|
||||||
() =>
|
() =>
|
||||||
buildWorkspaceDesktopTabActions({
|
buildWorkspaceDesktopTabActions({
|
||||||
@@ -580,33 +573,46 @@ function ResolvedDesktopTabChip({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.tabSlot}>
|
<WorkspaceTabPresentationResolver
|
||||||
{showDropIndicatorBefore ? (
|
tab={item.tab}
|
||||||
<View style={[styles.tabDropIndicator, styles.tabDropIndicatorBefore]} />
|
serverId={normalizedServerId}
|
||||||
) : null}
|
workspaceId={normalizedWorkspaceId}
|
||||||
<TabChip
|
>
|
||||||
tab={item.tab}
|
{(presentation) => {
|
||||||
isActive={item.isActive}
|
const tooltipLabel =
|
||||||
isDragging={isDragging}
|
presentation.titleState === "loading" ? "Loading agent title" : presentation.label;
|
||||||
isFocused={isFocused}
|
|
||||||
resolvedTabWidth={resolvedTabWidth}
|
return (
|
||||||
showLabel={showLabel}
|
<View style={styles.tabSlot}>
|
||||||
showCloseButton={showCloseButton}
|
{showDropIndicatorBefore ? (
|
||||||
isCloseHovered={item.isCloseHovered}
|
<View style={[styles.tabDropIndicator, styles.tabDropIndicatorBefore]} />
|
||||||
isClosingTab={item.isClosingTab}
|
) : null}
|
||||||
presentation={presentation}
|
<TabChip
|
||||||
tooltipLabel={tooltipLabel}
|
tab={item.tab}
|
||||||
resolvedTab={resolvedTab}
|
isActive={item.isActive}
|
||||||
setHoveredTabKey={setHoveredTabKey}
|
isDragging={isDragging}
|
||||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
isFocused={isFocused}
|
||||||
onNavigateTab={onNavigateTab}
|
resolvedTabWidth={resolvedTabWidth}
|
||||||
onCloseTab={onCloseTab}
|
showLabel={showLabel}
|
||||||
dragHandleProps={dragHandleProps}
|
showCloseButton={showCloseButton}
|
||||||
/>
|
isCloseHovered={item.isCloseHovered}
|
||||||
{showDropIndicatorAfter ? (
|
isClosingTab={item.isClosingTab}
|
||||||
<View style={[styles.tabDropIndicator, styles.tabDropIndicatorAfter]} />
|
presentation={presentation}
|
||||||
) : null}
|
tooltipLabel={tooltipLabel}
|
||||||
</View>
|
resolvedTab={resolvedTab}
|
||||||
|
setHoveredTabKey={setHoveredTabKey}
|
||||||
|
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||||
|
onNavigateTab={onNavigateTab}
|
||||||
|
onCloseTab={onCloseTab}
|
||||||
|
dragHandleProps={dragHandleProps}
|
||||||
|
/>
|
||||||
|
{showDropIndicatorAfter ? (
|
||||||
|
<View style={[styles.tabDropIndicator, styles.tabDropIndicatorAfter]} />
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</WorkspaceTabPresentationResolver>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
|||||||
import { buildProviderCommand } from "@/utils/provider-command-templates";
|
import { buildProviderCommand } from "@/utils/provider-command-templates";
|
||||||
import { generateDraftId } from "@/stores/draft-keys";
|
import { generateDraftId } from "@/stores/draft-keys";
|
||||||
import {
|
import {
|
||||||
useWorkspaceTabPresentation,
|
WorkspaceTabPresentationResolver,
|
||||||
WorkspaceTabIcon,
|
WorkspaceTabIcon,
|
||||||
WorkspaceTabOptionRow,
|
WorkspaceTabOptionRow,
|
||||||
} from "@/screens/workspace/workspace-tab-presentation";
|
} from "@/screens/workspace/workspace-tab-presentation";
|
||||||
@@ -226,22 +226,24 @@ function ResolvedMobileActiveTabTrigger({
|
|||||||
normalizedServerId: string;
|
normalizedServerId: string;
|
||||||
normalizedWorkspaceId: string;
|
normalizedWorkspaceId: string;
|
||||||
}) {
|
}) {
|
||||||
const presentation = useWorkspaceTabPresentation({
|
|
||||||
tab: activeTab,
|
|
||||||
serverId: normalizedServerId,
|
|
||||||
workspaceId: normalizedWorkspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<WorkspaceTabPresentationResolver
|
||||||
<View style={styles.switcherTriggerIcon} testID="workspace-active-tab-icon">
|
tab={activeTab}
|
||||||
<WorkspaceTabIcon presentation={presentation} active />
|
serverId={normalizedServerId}
|
||||||
</View>
|
workspaceId={normalizedWorkspaceId}
|
||||||
|
>
|
||||||
|
{(presentation) => (
|
||||||
|
<>
|
||||||
|
<View style={styles.switcherTriggerIcon} testID="workspace-active-tab-icon">
|
||||||
|
<WorkspaceTabIcon presentation={presentation} active />
|
||||||
|
</View>
|
||||||
|
|
||||||
<Text style={styles.switcherTriggerText} numberOfLines={1}>
|
<Text style={styles.switcherTriggerText} numberOfLines={1}>
|
||||||
{presentation.titleState === "loading" ? "Loading..." : presentation.label}
|
{presentation.titleState === "loading" ? "Loading..." : presentation.label}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</WorkspaceTabPresentationResolver>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,11 +279,6 @@ function MobileWorkspaceTabOption({
|
|||||||
onCloseOtherTabs: (tabId: string) => Promise<void> | void;
|
onCloseOtherTabs: (tabId: string) => Promise<void> | void;
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const presentation = useWorkspaceTabPresentation({
|
|
||||||
tab,
|
|
||||||
serverId: normalizedServerId,
|
|
||||||
workspaceId: normalizedWorkspaceId,
|
|
||||||
});
|
|
||||||
const menuTestIDBase = `workspace-tab-menu-${tab.key}`;
|
const menuTestIDBase = `workspace-tab-menu-${tab.key}`;
|
||||||
const menuEntries = buildWorkspaceTabMenuEntries({
|
const menuEntries = buildWorkspaceTabMenuEntries({
|
||||||
surface: "mobile",
|
surface: "mobile",
|
||||||
@@ -298,53 +295,61 @@ function MobileWorkspaceTabOption({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceTabOptionRow
|
<WorkspaceTabPresentationResolver
|
||||||
presentation={presentation}
|
tab={tab}
|
||||||
selected={selected}
|
serverId={normalizedServerId}
|
||||||
active={active}
|
workspaceId={normalizedWorkspaceId}
|
||||||
onPress={onPress}
|
>
|
||||||
trailingAccessory={
|
{(presentation) => (
|
||||||
<DropdownMenu>
|
<WorkspaceTabOptionRow
|
||||||
<DropdownMenuTrigger
|
presentation={presentation}
|
||||||
testID={`${menuTestIDBase}-trigger`}
|
selected={selected}
|
||||||
accessibilityRole="button"
|
active={active}
|
||||||
accessibilityLabel={`Open menu for ${presentation.label}`}
|
onPress={onPress}
|
||||||
hitSlop={8}
|
trailingAccessory={
|
||||||
style={({ open, pressed }) => [
|
<DropdownMenu>
|
||||||
styles.mobileTabMenuTrigger,
|
<DropdownMenuTrigger
|
||||||
(open || pressed) && styles.mobileTabMenuTriggerActive,
|
testID={`${menuTestIDBase}-trigger`}
|
||||||
]}
|
accessibilityRole="button"
|
||||||
>
|
accessibilityLabel={`Open menu for ${presentation.label}`}
|
||||||
<Ellipsis
|
hitSlop={8}
|
||||||
size={theme.iconSize.sm}
|
style={({ open, pressed }) => [
|
||||||
color={theme.colors.foregroundMuted}
|
styles.mobileTabMenuTrigger,
|
||||||
/>
|
(open || pressed) && styles.mobileTabMenuTriggerActive,
|
||||||
</DropdownMenuTrigger>
|
]}
|
||||||
<DropdownMenuContent
|
>
|
||||||
side="bottom"
|
<Ellipsis
|
||||||
align="end"
|
size={theme.iconSize.sm}
|
||||||
width={220}
|
color={theme.colors.foregroundMuted}
|
||||||
testID={menuTestIDBase}
|
/>
|
||||||
>
|
</DropdownMenuTrigger>
|
||||||
{menuEntries.map((entry) =>
|
<DropdownMenuContent
|
||||||
entry.kind === "separator" ? (
|
side="bottom"
|
||||||
<DropdownMenuSeparator key={entry.key} />
|
align="end"
|
||||||
) : (
|
width={220}
|
||||||
<DropdownMenuItem
|
testID={menuTestIDBase}
|
||||||
key={entry.key}
|
>
|
||||||
testID={entry.testID}
|
{menuEntries.map((entry) =>
|
||||||
disabled={entry.disabled}
|
entry.kind === "separator" ? (
|
||||||
destructive={entry.destructive}
|
<DropdownMenuSeparator key={entry.key} />
|
||||||
onSelect={entry.onSelect}
|
) : (
|
||||||
>
|
<DropdownMenuItem
|
||||||
{entry.label}
|
key={entry.key}
|
||||||
</DropdownMenuItem>
|
testID={entry.testID}
|
||||||
)
|
disabled={entry.disabled}
|
||||||
)}
|
destructive={entry.destructive}
|
||||||
</DropdownMenuContent>
|
onSelect={entry.onSelect}
|
||||||
</DropdownMenu>
|
>
|
||||||
}
|
{entry.label}
|
||||||
/>
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</WorkspaceTabPresentationResolver>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,23 +21,56 @@ export interface WorkspaceTabPresentation {
|
|||||||
statusBucket: SidebarStateBucket | null;
|
statusBucket: SidebarStateBucket | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWorkspaceTabPresentation(input: {
|
type WorkspaceTabPresentationResolverProps = {
|
||||||
tab: WorkspaceTabDescriptor;
|
tab: WorkspaceTabDescriptor;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): WorkspaceTabPresentation {
|
children: (presentation: WorkspaceTabPresentation) => ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WorkspaceTabPresentationResolverInnerProps = WorkspaceTabPresentationResolverProps & {
|
||||||
|
registration: NonNullable<ReturnType<typeof getPanelRegistration>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function WorkspaceTabPresentationResolver({
|
||||||
|
tab,
|
||||||
|
serverId,
|
||||||
|
workspaceId,
|
||||||
|
children,
|
||||||
|
}: WorkspaceTabPresentationResolverProps): ReactElement {
|
||||||
ensurePanelsRegistered();
|
ensurePanelsRegistered();
|
||||||
const registration = getPanelRegistration(input.tab.kind);
|
const registration = getPanelRegistration(tab.kind);
|
||||||
invariant(registration, `No panel registration for kind: ${input.tab.kind}`);
|
invariant(registration, `No panel registration for kind: ${tab.kind}`);
|
||||||
const descriptor = registration.useDescriptor(input.tab.target, {
|
|
||||||
serverId: input.serverId,
|
return (
|
||||||
workspaceId: input.workspaceId,
|
<WorkspaceTabPresentationResolverInner
|
||||||
|
key={tab.kind}
|
||||||
|
registration={registration}
|
||||||
|
tab={tab}
|
||||||
|
serverId={serverId}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</WorkspaceTabPresentationResolverInner>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function WorkspaceTabPresentationResolverInner({
|
||||||
|
registration,
|
||||||
|
tab,
|
||||||
|
serverId,
|
||||||
|
workspaceId,
|
||||||
|
children,
|
||||||
|
}: WorkspaceTabPresentationResolverInnerProps): ReactElement {
|
||||||
|
const descriptor = registration.useDescriptor(tab.target as never, {
|
||||||
|
serverId,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return useMemo(
|
const presentation = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
key: input.tab.key,
|
key: tab.key,
|
||||||
kind: input.tab.kind,
|
kind: tab.kind,
|
||||||
label: descriptor.label,
|
label: descriptor.label,
|
||||||
subtitle: descriptor.subtitle,
|
subtitle: descriptor.subtitle,
|
||||||
titleState: descriptor.titleState,
|
titleState: descriptor.titleState,
|
||||||
@@ -50,10 +83,12 @@ export function useWorkspaceTabPresentation(input: {
|
|||||||
descriptor.statusBucket,
|
descriptor.statusBucket,
|
||||||
descriptor.subtitle,
|
descriptor.subtitle,
|
||||||
descriptor.titleState,
|
descriptor.titleState,
|
||||||
input.tab.key,
|
tab.key,
|
||||||
input.tab.kind,
|
tab.kind,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return <>{children(presentation)}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkspaceTabIconProps = {
|
type WorkspaceTabIconProps = {
|
||||||
|
|||||||
Reference in New Issue
Block a user