orca/mobile/src/worktree/workspace-view-settings.ts

96 lines
3.6 KiB
TypeScript

// Bi-directional mapping between the mobile workspaces screen's local view model
// and the desktop's shared PersistedUIState (read/written via the ui.get/ui.set
// RPCs). Keeping these settings in the same global store is what lets a grouping
// or filter change on the phone show up on desktop and vice-versa.
import type { WorkspaceStatusDefinition } from '../../../src/shared/types'
import { coerceMobileWorkspaceStatuses } from './mobile-workspace-statuses'
export type MobileGroupMode = 'none' | 'workspaceStatus' | 'repo' | 'prStatus'
// Desktop sort adds 'manual'; mobile renders it but sorts by server order.
export type MobileSortMode = 'smart' | 'name' | 'recent' | 'repo' | 'manual'
// Desktop PersistedUIState fields this screen syncs (a structural subset).
export type WorkspaceViewSettings = {
groupBy?: 'none' | 'workspace-status' | 'repo' | 'pr-status'
sortBy?: 'name' | 'smart' | 'recent' | 'repo' | 'manual'
hideSleepingWorkspaces?: boolean
hideDefaultBranchWorkspace?: boolean
alwaysShowDefaultBranchWorkspace?: boolean
filterRepoIds?: string[]
collapsedGroups?: string[]
workspaceStatuses?: WorkspaceStatusDefinition[]
}
const GROUP_TO_DESKTOP: Record<MobileGroupMode, NonNullable<WorkspaceViewSettings['groupBy']>> = {
none: 'none',
workspaceStatus: 'workspace-status',
repo: 'repo',
prStatus: 'pr-status'
}
const GROUP_FROM_DESKTOP: Record<NonNullable<WorkspaceViewSettings['groupBy']>, MobileGroupMode> = {
none: 'none',
'workspace-status': 'workspaceStatus',
repo: 'repo',
'pr-status': 'prStatus'
}
const SORT_VALUES: readonly MobileSortMode[] = ['smart', 'name', 'recent', 'repo', 'manual']
export function groupModeToDesktop(
mode: MobileGroupMode
): NonNullable<WorkspaceViewSettings['groupBy']> {
return GROUP_TO_DESKTOP[mode]
}
export function groupModeFromDesktop(
groupBy: WorkspaceViewSettings['groupBy']
): MobileGroupMode | null {
return groupBy ? (GROUP_FROM_DESKTOP[groupBy] ?? null) : null
}
export function sortModeFromDesktop(
sortBy: WorkspaceViewSettings['sortBy']
): MobileSortMode | null {
return sortBy && SORT_VALUES.includes(sortBy) ? sortBy : null
}
export type MobileViewState = {
groupMode: MobileGroupMode
sortMode: MobileSortMode
hideSleeping: boolean
hideDefaultBranch: boolean
alwaysShowDefaultBranch: boolean
filterRepoIds: string[]
collapsedGroups: string[]
workspaceStatuses: readonly WorkspaceStatusDefinition[]
}
// Apply a desktop PersistedUIState onto the local view state, leaving any field
// the desktop hasn't set untouched (so a partial ui.get doesn't clobber).
export function applyDesktopViewSettings(
current: MobileViewState,
settings: WorkspaceViewSettings
): MobileViewState {
const groupMode = groupModeFromDesktop(settings.groupBy)
const sortMode = sortModeFromDesktop(settings.sortBy)
// Why: a partially hydrated desktop settings payload may carry an empty
// status catalog; mobile must keep renderable groups instead of hiding rows.
const workspaceStatuses = settings.workspaceStatuses
? coerceMobileWorkspaceStatuses(settings.workspaceStatuses)
: current.workspaceStatuses
const next: MobileViewState = {
groupMode: groupMode ?? current.groupMode,
sortMode: sortMode ?? current.sortMode,
hideSleeping: settings.hideSleepingWorkspaces ?? current.hideSleeping,
hideDefaultBranch: settings.hideDefaultBranchWorkspace ?? current.hideDefaultBranch,
alwaysShowDefaultBranch:
settings.alwaysShowDefaultBranchWorkspace ?? current.alwaysShowDefaultBranch,
filterRepoIds: settings.filterRepoIds ?? current.filterRepoIds,
collapsedGroups: settings.collapsedGroups ?? current.collapsedGroups,
workspaceStatuses
}
return next
}