Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135// src/layout/hooks/useWorkspaceViews.tsimport { useState, useEffect, useRef } from 'react'import type { WorkspaceLeaf } from '../../plugin/workspace'import type { View } from '../../plugin/views/view'import type { App } from '../../plugin/app'
interface WorkspaceViewsState { leftLeaves: readonly WorkspaceLeaf[] rightLeaves: readonly WorkspaceLeaf[] registeredTypes: string[]}
/** * Internal snapshot that captures both leaf identity AND view identity * at the time of setState. The leaf objects are mutable (their .view * property changes when a plugin calls openView/setViewState), so we * must snapshot the view references separately to detect changes. */interface ViewsSnapshot { state: WorkspaceViewsState leftViews: (View | null)[] rightViews: (View | null)[]}
function snapshotViews(leaves: readonly WorkspaceLeaf[]): (View | null)[] { return leaves.map(l => l.view)}
/** * Compare current workspace leaves against a snapshot. * Checks both leaf object identity and view identity (from the snapshot). */function leafArrayChanged( snapshotLeaves: readonly WorkspaceLeaf[], snapshotViews: (View | null)[], currentLeaves: readonly WorkspaceLeaf[],): boolean { if (snapshotLeaves.length !== currentLeaves.length) return true for (let i = 0; i < snapshotLeaves.length; i++) { if (snapshotLeaves[i] !== currentLeaves[i]) return true // Compare current view against the SNAPSHOTTED view reference, // not the leaf's current .view (which would be the same since // snapshotLeaves[i] IS the same mutable object as currentLeaves[i]) if (snapshotViews[i] !== currentLeaves[i].view) return true } return false}
function arraysEqual(a: string[], b: string[]): boolean { if (a.length !== b.length) return false for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false } return true}
/** * React hook that subscribes to Workspace view registry and panel leaf changes. * Re-renders when views are registered/unregistered or layout changes. * * Uses a snapshot-based comparison to detect changes: leaf identity is compared * by object reference, and view identity is compared against a captured snapshot * (not the live mutable .view property on the leaf object). This correctly * detects when a view is swapped on an existing leaf via openView/setViewState. */export function useWorkspaceViews(app: App): WorkspaceViewsState { const workspace = app.workspace const [state, setState] = useState<WorkspaceViewsState>(() => ({ leftLeaves: workspace.getLeftLeaves(), rightLeaves: workspace.getRightLeaves(), registeredTypes: workspace.getRegisteredViewTypes(), }))
// Keep a snapshot ref that includes view references at the time of last setState const snapshotRef = useRef<ViewsSnapshot>({ state, leftViews: snapshotViews(workspace.getLeftLeaves()), rightViews: snapshotViews(workspace.getRightLeaves()), }) snapshotRef.current.state = state
useEffect(() => { const refresh = () => { const currentLeft = workspace.getLeftLeaves() const currentRight = workspace.getRightLeaves() const currentTypes = workspace.getRegisteredViewTypes() const snap = snapshotRef.current
// Skip setState if nothing actually changed const leftChanged = leafArrayChanged(snap.state.leftLeaves, snap.leftViews, currentLeft) const rightChanged = leafArrayChanged(snap.state.rightLeaves, snap.rightViews, currentRight) const typesChanged = !arraysEqual(snap.state.registeredTypes, currentTypes)
if (!leftChanged && !rightChanged && !typesChanged) { return }
const newState: WorkspaceViewsState = { leftLeaves: currentLeft, rightLeaves: currentRight, registeredTypes: currentTypes, }
// Update snapshot with current view references snapshotRef.current = { state: newState, leftViews: snapshotViews(currentLeft), rightViews: snapshotViews(currentRight), }
setState(newState) }
// view-registered and view-unregistered are Reams-novel events (on reamsEvents) // layout-change is Obsidian-compatible (on workspace) const reamsRefs = [ app.reamsEvents.on('view-registered', refresh), app.reamsEvents.on('view-unregistered', refresh), ] const workspaceRef = workspace.on('layout-change', refresh)
// Sync on mount in case state changed between initial render and effect refresh()
return () => { for (const ref of reamsRefs) { app.reamsEvents.offref(ref) } workspace.offref(workspaceRef) } }, [app, workspace])
return state}