| 1 | import { defaultSidebarWidth, SIDEBAR_MAX_WIDTH } from "../store/layout"; |
| 2 | import type { Translator } from "../lib/i18n"; |
| 3 | import type { Meta, RemoteTabRefView, TabMeta } from "../lib/types"; |
| 4 | import type { SidebarImTopicSource } from "../app-runtime/sidebarImProjection"; |
| 5 | import type { useSessionBannerCommands } from "../app-runtime/useSessionBannerCommands"; |
| 6 | import type { useProjectTopicCommands } from "../app-runtime/useProjectTopicCommands"; |
| 7 | import type { useOnboardingCommands } from "../app-runtime/useOnboardingCommands"; |
| 8 | import type { useShellGeometry } from "../app-runtime/useShellGeometry"; |
| 9 | import type { useAppShellStores } from "../app-runtime/useAppShellStores"; |
| 10 | import type { SessionStatusBannersProps } from "./SessionStatusBanners"; |
| 11 | import type { SidebarRegionProps } from "./SidebarRegion"; |
| 12 | import { sessionIdentityRoute } from "../lib/sessionIdentity"; |
| 13 | |
| 14 | type BannerCommands = ReturnType<typeof useSessionBannerCommands>; |
| 15 | type ShellStores = ReturnType<typeof useAppShellStores>; |
| 16 | type ProjectTopicCommands = ReturnType<typeof useProjectTopicCommands>; |
| 17 | type OnboardingCommands = ReturnType<typeof useOnboardingCommands>; |
| 18 | |
| 19 | /** Pure prop assembly for the chrome regions (sidebar, app chrome, status |
| 20 | * banners); store and hook ownership stays with the caller. */ |
| 21 | |
| 22 | export function buildSidebarRegionProps(input: { |
| 23 | className: string; |
| 24 | shell: ShellStores; |
| 25 | t: Translator; |
| 26 | geometry: ReturnType<typeof useShellGeometry>; |
| 27 | projectTree: { |
| 28 | activeTab: TabMeta | undefined; |
| 29 | /** Owned by useActiveRemoteRef: this assembly must not rebuild it, because |
| 30 | * the project tree keys its tree walks on the object's identity. */ |
| 31 | activeRemote: RemoteTabRefView | undefined; |
| 32 | activeScope?: string; |
| 33 | activeWorkspaceRoot?: string; |
| 34 | imTopicSources: Record<string, SidebarImTopicSource>; |
| 35 | refreshSignal: number; |
| 36 | searchExpanded: boolean; |
| 37 | searchFocusSignal: number; |
| 38 | showShortcutBadges: boolean; |
| 39 | shortcutPlatform: SidebarRegionProps["projectTree"]["shortcutPlatform"]; |
| 40 | onVisibleTopicsChange: SidebarRegionProps["projectTree"]["onVisibleTopicsChange"]; |
| 41 | draftSummaries: SidebarRegionProps["projectTree"]["draftSummaries"]; |
| 42 | onOpenDraft: SidebarRegionProps["projectTree"]["onOpenDraft"]; |
| 43 | }; |
| 44 | topics: ProjectTopicCommands; |
| 45 | commands: { |
| 46 | onNewSession: () => void; |
| 47 | onOpenPalette: () => void; |
| 48 | onOpenTrash: () => void; |
| 49 | onOpenAutomation: () => void; |
| 50 | onOpenSettings: SidebarRegionProps["onOpenSettings"]; |
| 51 | onOpenTopic: SidebarRegionProps["projectTree"]["onOpenTopic"]; |
| 52 | }; |
| 53 | paletteShortcut: string; |
| 54 | }): SidebarRegionProps { |
| 55 | const { geometry, topics, commands } = input; |
| 56 | const shell = input.shell; |
| 57 | return { |
| 58 | className: input.className, |
| 59 | collapsed: shell.sidebarCollapsed, |
| 60 | t: input.t, |
| 61 | onNewSession: commands.onNewSession, |
| 62 | onOpenPalette: commands.onOpenPalette, |
| 63 | paletteShortcut: input.paletteShortcut, |
| 64 | onOpenTrash: commands.onOpenTrash, |
| 65 | onOpenAutomation: commands.onOpenAutomation, |
| 66 | onOpenSettings: commands.onOpenSettings, |
| 67 | resize: { |
| 68 | min: geometry.sidebarResizeMinWidth, max: SIDEBAR_MAX_WIDTH, value: geometry.sidebarRenderWidth, |
| 69 | onPointerDown: geometry.startSidebarResize, onKeyDown: geometry.resizeSidebarWithKeyboard, |
| 70 | onReset: () => geometry.setExpandedSidebarWidth(defaultSidebarWidth()), |
| 71 | }, |
| 72 | projectTree: { |
| 73 | activeScope: input.projectTree.activeScope ?? input.projectTree.activeTab?.scope, |
| 74 | activeWorkspaceRoot: input.projectTree.activeWorkspaceRoot ?? input.projectTree.activeTab?.workspaceRoot, |
| 75 | activeTopicId: input.projectTree.activeTab?.topicId, activeSessionPath: sessionIdentityRoute(input.projectTree.activeTab), |
| 76 | activeRemote: input.projectTree.activeRemote, |
| 77 | imTopicSources: input.projectTree.imTopicSources, onOpenTopic: commands.onOpenTopic, |
| 78 | onCreateTopic: topics.onCreateTopic, onCreateIsolatedWorktree: topics.onCreateIsolatedWorktree, |
| 79 | onTopicsChanged: topics.refreshProjectsAndTabs, onRenameTopic: topics.renameTopic, refreshSignal: input.projectTree.refreshSignal, |
| 80 | onAddProject: topics.onAddProject, |
| 81 | searchExpanded: input.projectTree.searchExpanded, searchFocusSignal: input.projectTree.searchFocusSignal, |
| 82 | showShortcutBadges: input.projectTree.showShortcutBadges, shortcutPlatform: input.projectTree.shortcutPlatform, |
| 83 | onVisibleTopicsChange: input.projectTree.onVisibleTopicsChange, |
| 84 | draftSummaries: input.projectTree.draftSummaries, |
| 85 | onOpenDraft: input.projectTree.onOpenDraft, |
| 86 | }, |
| 87 | }; |
| 88 | } |
| 89 | |
| 90 | export function buildSessionStatusBannerProps(input: { |
| 91 | t: Translator; |
| 92 | activeTab: TabMeta | undefined; |
| 93 | leaseBlocked: SessionStatusBannersProps["leaseBlocked"]; |
| 94 | meta: Meta | null | undefined; |
| 95 | configWarnings: SessionStatusBannersProps["configWarnings"]; |
| 96 | dismissConfigWarnings: () => void; |
| 97 | updateChecksEnabled: boolean; |
| 98 | shell: ShellStores; |
| 99 | banners: BannerCommands; |
| 100 | onboarding: OnboardingCommands; |
| 101 | }): SessionStatusBannersProps { |
| 102 | const { banners, shell, onboarding } = input; |
| 103 | return { |
| 104 | t: input.t, |
| 105 | takenOver: Boolean(input.activeTab?.takenOver), |
| 106 | reclaimTabId: input.activeTab?.id ?? "", |
| 107 | reclaimBusyTabId: shell.reclaimBusyTab, |
| 108 | onReclaim: banners.reclaimSession, |
| 109 | leaseBlocked: input.leaseBlocked, |
| 110 | startupError: input.meta?.startupErr, |
| 111 | takeoverDialogTabId: shell.takeoverDialogTab, |
| 112 | onOpenTakeover: banners.openTakeoverDialog, |
| 113 | onCloseTakeover: banners.closeTakeoverDialog, |
| 114 | configWarnings: input.configWarnings, |
| 115 | onOpenConfigFile: banners.openConfigFile, |
| 116 | onReloadConfigFile: banners.reloadConfigFile, |
| 117 | onDismissConfigWarnings: input.dismissConfigWarnings, |
| 118 | providerSetupNeeded: shell.providerSetupNeeded, |
| 119 | needsOnboarding: shell.needsOnboarding, |
| 120 | onConfigureProvider: () => { |
| 121 | onboarding.chooseOnboardingProvider(); |
| 122 | }, |
| 123 | updateChecksEnabled: input.updateChecksEnabled, |
| 124 | onShowReleaseNotes: banners.showReleaseNotes, |
| 125 | }; |
| 126 | } |
| 127 | |
| 128 | /** The app/layout frame class lists; flags arrive from the caller's stores. */ |
| 129 | export function buildAppShellClassNames(input: { |
| 130 | platform: string; |
| 131 | windowsFrameless: boolean; |
| 132 | browserPreview: boolean; |
| 133 | imDetailActive: boolean; |
| 134 | sidebarCollapsed: boolean; |
| 135 | sidebarResizing: boolean; |
| 136 | dockGridOpen: boolean; |
| 137 | dockOverlay: boolean; |
| 138 | terminalOpen: boolean; |
| 139 | terminalResizing: boolean; |
| 140 | dockOpen: boolean; |
| 141 | dockMaximized: boolean; |
| 142 | dockResizing: boolean; |
| 143 | }): { app: string; layout: string } { |
| 144 | return { |
| 145 | app: [ |
| 146 | "app", |
| 147 | `app--${input.platform}`, |
| 148 | input.windowsFrameless ? "app--windows-frameless" : "", |
| 149 | input.browserPreview ? "app--browser-preview" : "", |
| 150 | "app--workbench", |
| 151 | ].filter(Boolean).join(" "), |
| 152 | layout: [ |
| 153 | "layout", |
| 154 | "layout--workbench", |
| 155 | input.imDetailActive ? "layout--statusbar-hidden" : "", |
| 156 | input.sidebarCollapsed ? "layout--sidebar-collapsed" : "", |
| 157 | input.sidebarResizing ? "layout--resizing layout--sidebar-resizing" : "", |
| 158 | input.dockGridOpen ? "layout--workspace-open" : "", |
| 159 | input.dockOverlay ? "layout--workspace-overlay" : "", |
| 160 | "layout--terminal-drawer-open", |
| 161 | input.terminalOpen ? "layout--terminal-drawer-expanded" : "", |
| 162 | input.terminalResizing ? "layout--terminal-resizing" : "", |
| 163 | input.dockOpen && input.dockMaximized ? "layout--workspace-maximized" : "", |
| 164 | input.dockResizing ? "layout--resizing layout--workspace-resizing" : "", |
| 165 | ] |
| 166 | .filter(Boolean) |
| 167 | .join(" "), |
| 168 | }; |
| 169 | } |
| 170 |