Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164// src/layout/layout-persistence.tsimport { createInitialLayoutState, type LayoutState, type EditorMode, type SidebarMode, MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH, MIN_RIGHT_PANEL_WIDTH, MAX_RIGHT_PANEL_WIDTH,} from './layout-state'
export const LAYOUT_STORAGE_KEY = 'reams:layout'export const WORKSPACE_TABS_STORAGE_KEY = 'reams:workspace-tabs'export const LAYOUT_VERSION = 5
interface PersistedLayoutState { version: number sidebarWidth?: number sidebarMode?: string rightPanelWidth?: number rightPanelCollapsed?: boolean editorMode?: string}
/** * Save layout state to localStorage. * Silently fails if localStorage is unavailable. * * v5+ does not persist tab state — tabs are owned by the workspace and * persisted under WORKSPACE_TABS_STORAGE_KEY. */export function saveLayoutState(state: LayoutState, options?: { persistEditorMode?: boolean }): void { try { const persisted: PersistedLayoutState = { version: LAYOUT_VERSION, sidebarWidth: state.sidebarWidth, sidebarMode: state.sidebarMode, rightPanelWidth: state.rightPanelWidth, rightPanelCollapsed: state.rightPanelCollapsed, ...(options?.persistEditorMode === false ? {} : { editorMode: state.editorMode }), } localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(persisted)) } catch { // localStorage may be unavailable (private browsing, storage full, etc.) // Silently fail — layout state is non-critical }}
/** * Load layout state from localStorage. * Returns default state if localStorage is unavailable, empty, or invalid. * * Older layout schemas are ignored; the workspace owns tab state. */export function loadLayoutState(): LayoutState { const defaults = createInitialLayoutState()
try { const stored = localStorage.getItem(LAYOUT_STORAGE_KEY) if (!stored) { return defaults }
const parsed: unknown = JSON.parse(stored) if (!isObject(parsed)) { return defaults }
const persisted = parsed as unknown as PersistedLayoutState
// Reject obsolete and unknown versions. if (persisted.version !== LAYOUT_VERSION) { return defaults }
// Load v5 state. return { sidebarWidth: clampNumber(persisted.sidebarWidth, MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH, defaults.sidebarWidth), sidebarMode: validateSidebarMode(persisted.sidebarMode, defaults.sidebarMode), rightPanelWidth: clampNumber(persisted.rightPanelWidth, MIN_RIGHT_PANEL_WIDTH, MAX_RIGHT_PANEL_WIDTH, defaults.rightPanelWidth), rightPanelCollapsed: typeof persisted.rightPanelCollapsed === 'boolean' ? persisted.rightPanelCollapsed : defaults.rightPanelCollapsed, editorMode: validateEditorMode(persisted.editorMode), } } catch { // JSON parse error or localStorage access error return defaults }}
function isObject(value: unknown): value is Record<string, unknown> { return typeof value === 'object' && value !== null && !Array.isArray(value)}
function clampNumber( value: unknown, min: number, max: number, defaultValue: number): number { if (typeof value !== 'number' || !Number.isFinite(value)) { return defaultValue } return Math.max(min, Math.min(max, value))}
function validateEditorMode(mode: unknown): EditorMode { if (mode === 'source' || mode === 'live-preview') { return mode } return 'live-preview'}
function validateSidebarMode(mode: unknown, fallback: SidebarMode): SidebarMode { if (mode === 'hidden' || mode === 'ribbon' || mode === 'full') { return mode } return fallback}
// ============================================================================// Workspace tab persistence (v5+)// ============================================================================
export interface WorkspaceTabsState { tabs: Array<{ docUrl: string }> activeIndex: number}
/** * Save workspace tab state to localStorage. * Silently fails if localStorage is unavailable. */export function saveWorkspaceTabs(state: WorkspaceTabsState, storageKey = WORKSPACE_TABS_STORAGE_KEY): void { try { localStorage.setItem(storageKey, JSON.stringify(state)) } catch { // Silent fail }}
/** * Load workspace tab state from localStorage. * Returns null if unavailable, empty, or invalid. */export function loadWorkspaceTabs(storageKey = WORKSPACE_TABS_STORAGE_KEY): WorkspaceTabsState | null { try { const stored = localStorage.getItem(storageKey) if (!stored) return null const parsed = JSON.parse(stored) if (!Array.isArray(parsed.tabs)) return null // Validate shape — filter out invalid entries const validTabs = parsed.tabs.filter( (t: unknown) => typeof t === 'object' && t !== null && typeof (t as { docUrl?: unknown }).docUrl === 'string' ) return { tabs: validTabs, activeIndex: typeof parsed.activeIndex === 'number' ? parsed.activeIndex : 0, } } catch { return null }}