export type WorkspaceId = "Layout" | "Modeling" | "Animation"; export type EditorType = "3D Viewport" | "Outliner" | "Properties" | "Timeline"; export type BlenderMode = "Object" | "Edit" | "Pose"; export interface RegionIR { id: string; kind: "header" | "main" | "toolbar" | "sidebar" | "footer"; visible: boolean; } export interface AreaIR { id: string; editor: EditorType; regions: RegionIR[]; rect: { x: number; y: number; width: number; height: number }; maximized: boolean; } export interface WorkspaceIR { id: WorkspaceId; name: string; areas: AreaIR[]; activeAreaId: string; revision: number; } export interface UIContextIR { workspaceId: WorkspaceId; activeAreaId: string; activeEditor: EditorType; mode: BlenderMode; activeObjectId: string | null; selection: string[]; viewLayer: string; pinnedData: string | null; revision: number; } export interface WebWorkspaceState { workspaces: Record; context: UIContextIR; operatorSearchOpen: boolean; sidebarVisible: boolean; } export type UICommand = | { type: "switchWorkspace"; workspaceId: WorkspaceId } | { type: "setMode"; mode: BlenderMode } | { type: "setActiveArea"; areaId: string } | { type: "toggleOperatorSearch"; open?: boolean } | { type: "toggleSidebar"; visible?: boolean }; function regions(): RegionIR[] { return [ { id: "header", kind: "header", visible: true }, { id: "main", kind: "main", visible: true }, { id: "toolbar", kind: "toolbar", visible: true }, { id: "sidebar", kind: "sidebar", visible: true }, ]; } function layoutAreas(): AreaIR[] { return [ { id: "viewport", editor: "3D Viewport", regions: regions(), rect: { x: 0, y: 0, width: 0.8, height: 0.78 }, maximized: false }, { id: "outliner", editor: "Outliner", regions: regions(), rect: { x: 0.8, y: 0, width: 0.2, height: 0.42 }, maximized: false }, { id: "properties", editor: "Properties", regions: regions(), rect: { x: 0.8, y: 0.42, width: 0.2, height: 0.36 }, maximized: false }, { id: "timeline", editor: "Timeline", regions: regions(), rect: { x: 0, y: 0.78, width: 1, height: 0.22 }, maximized: false }, ]; } function workspace(id: WorkspaceId): WorkspaceIR { return { id, name: id, areas: layoutAreas(), activeAreaId: "viewport", revision: 0 }; } export function createDefaultWebWorkspaceState(): WebWorkspaceState { const workspaces = { Layout: workspace("Layout"), Modeling: workspace("Modeling"), Animation: workspace("Animation"), } satisfies Record; return { workspaces, context: { workspaceId: "Layout", activeAreaId: "viewport", activeEditor: "3D Viewport", mode: "Object", activeObjectId: "BasicCube", selection: ["BasicCube"], viewLayer: "ViewLayer", pinnedData: null, revision: 0, }, operatorSearchOpen: false, sidebarVisible: true, }; } export function reduceUICommand(state: WebWorkspaceState, command: UICommand): WebWorkspaceState { switch (command.type) { case "switchWorkspace": { const next = state.workspaces[command.workspaceId]; return { ...state, context: { ...state.context, workspaceId: command.workspaceId, activeAreaId: next.activeAreaId, activeEditor: next.areas.find((area) => area.id === next.activeAreaId)?.editor ?? "3D Viewport", revision: state.context.revision + 1, }, }; } case "setMode": return { ...state, context: { ...state.context, mode: command.mode, revision: state.context.revision + 1 } }; case "setActiveArea": { const active = state.workspaces[state.context.workspaceId].areas.find((area) => area.id === command.areaId); if (!active) return state; return { ...state, context: { ...state.context, activeAreaId: active.id, activeEditor: active.editor, revision: state.context.revision + 1 }, }; } case "toggleOperatorSearch": return { ...state, operatorSearchOpen: command.open ?? !state.operatorSearchOpen }; case "toggleSidebar": return { ...state, sidebarVisible: command.visible ?? !state.sidebarVisible }; } }