Why: make material, background work, confirmations and files accessible to nontechnical users across desktop and mobile. Evidence: Dev SELF receipts for Frontend tsc, 190 tests, knip baseline, build, nine fixture browser cases and two real HTTP/browser smoke cases using MockRunner. Impact: existing API and permissions remain authoritative; stale events/queries and duplicate effects are guarded, while one file preview and reading state are preserved. Refs: work-companion-ui design r0.2 and plan r1 U2-U5; UI01-UI13.
70 lines
3.1 KiB
TypeScript
70 lines
3.1 KiB
TypeScript
export interface WorkbenchLayoutOptions {
|
|
width: number
|
|
contextOpen: boolean
|
|
leftCollapsed: boolean
|
|
rightCollapsed: boolean
|
|
leftWidth: number
|
|
rightWidth: number
|
|
previewWidth: number
|
|
rightTab: 'work' | 'files'
|
|
filesView: 'list' | 'preview'
|
|
previewOpen: boolean
|
|
previewExpanded: boolean
|
|
}
|
|
|
|
export type WorkbenchPane = 'context' | 'conversation' | 'work' | 'files'
|
|
const COMPACT_WORKBENCH_WIDTH = 1024
|
|
|
|
function boundedWidth(value: number, fallback: number, min: number, max: number) {
|
|
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : fallback))
|
|
}
|
|
|
|
// Only the rendered widths are bounded. Saved desktop preferences stay in the store.
|
|
export function selectWorkbenchLayout(options: WorkbenchLayoutOptions) {
|
|
const width = Number.isFinite(options.width) ? Math.max(0, options.width) : 1440
|
|
const available = Math.max(0, width - 8)
|
|
const compact = width < COMPACT_WORKBENCH_WIDTH
|
|
const filesWide = options.rightTab === 'files' && options.filesView === 'preview' && options.previewOpen
|
|
const previewExpanded = filesWide && options.previewExpanded && !options.rightCollapsed
|
|
if (compact) {
|
|
const pane: WorkbenchPane = options.contextOpen ? 'context' : options.rightCollapsed ? 'conversation' : options.rightTab
|
|
return {
|
|
compact, pane, showContext: pane === 'context', showConversation: pane === 'conversation',
|
|
showRight: pane === 'work' || pane === 'files', contextCollapsed: false, previewExpanded,
|
|
leftWidth: pane === 'context' ? available : 0,
|
|
rightWidth: pane === 'work' || pane === 'files' ? available : 0,
|
|
leftDivider: false, rightDivider: false,
|
|
}
|
|
}
|
|
|
|
const showRight = !options.rightCollapsed
|
|
const leftDivider = !options.leftCollapsed
|
|
const rightDivider = showRight && !previewExpanded
|
|
const panelCount = 1 + Number(!previewExpanded) + Number(showRight)
|
|
const gutters = (panelCount - 1) * 4 + (Number(leftDivider) + Number(rightDivider)) * 8
|
|
const leftMin = options.leftCollapsed ? 52 : 220
|
|
const rightMin = showRight ? filesWide ? 420 : 280 : 0
|
|
let leftWidth = options.leftCollapsed ? 52 : boundedWidth(options.leftWidth, 260, leftMin, 420)
|
|
let rightWidth = showRight
|
|
? filesWide ? boundedWidth(options.previewWidth, 700, rightMin, 1100) : boundedWidth(options.rightWidth, 360, rightMin, 560)
|
|
: 0
|
|
if (previewExpanded) rightWidth = available - gutters - leftWidth
|
|
else {
|
|
const conversationMin = filesWide && showRight ? 320 : 360
|
|
const budget = available - gutters - conversationMin
|
|
if (leftWidth + rightWidth > budget) {
|
|
const extraLeft = leftWidth - leftMin
|
|
const extraRight = rightWidth - rightMin
|
|
const ratio = Math.max(0, budget - leftMin - rightMin) / (extraLeft + extraRight || 1)
|
|
leftWidth = leftMin + Math.floor(extraLeft * ratio)
|
|
rightWidth = rightMin + Math.floor(extraRight * ratio)
|
|
}
|
|
}
|
|
const pane: WorkbenchPane = previewExpanded ? 'files' : 'conversation'
|
|
return {
|
|
compact, pane,
|
|
showContext: true, showConversation: !previewExpanded, showRight, contextCollapsed: options.leftCollapsed,
|
|
previewExpanded, leftWidth, rightWidth, leftDivider, rightDivider,
|
|
}
|
|
}
|