Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import { useRef } from 'react' import type { MutableRefObject } from 'react' import type { CompassPhase, StraightedgePhase, ExtendPhase, MacroPhase, ActiveTool } from '../types' import type { MacroInput } from '../engine/macros' export interface ToolPhaseManager { // Phase refs (direct access for hot path — RAF loop, useToolInteraction, downstream hooks) readonly compassPhaseRef: MutableRefObject<CompassPhase> readonly straightedgePhaseRef: MutableRefObject<StraightedgePhase> readonly extendPhaseRef: MutableRefObject<ExtendPhase> readonly macroPhaseRef: MutableRefObject<MacroPhase> readonly snappedPointIdRef: MutableRefObject<string | null> readonly activeToolRef: MutableRefObject<ActiveTool> readonly pointerCapturedRef: MutableRefObject<boolean> readonly needsDrawRef: MutableRefObject<boolean> // Lifecycle methods /** Reset ALL four phases to idle, clear pointerCaptured, set needsDraw, sync macroPhase. * Silent — does NOT call notifyPhaseChange(). Caller adds that if needed. */ resetAll(): void /** Enter macro selecting mode for a specific proposition. Calls notifyPhaseChange(). */ enterMacroSelecting(propId: number, inputs: MacroInput[]): void /** Enter macro choosing mode (picker open). Syncs macroPhase React state. */ enterMacroChoosing(): void /** Set active tool ref + sync React state. */ selectTool(tool: ActiveTool): void /** Set macroPhaseRef + sync React state. For useToolInteraction to call. */ setMacroPhase(phase: MacroPhase): void // Notifications /** Trigger voice + preview updates (replaces onToolStateChange). */ notifyPhaseChange(): void /** Set needsDrawRef = true. */ requestDraw(): void // Writable callback slots (set by EuclidCanvas at render time) onPhaseChange: (() => void) | null onMacroPhaseSync: ((phase: MacroPhase) => void) | null onActiveToolSync: ((tool: ActiveTool) => void) | null } /** * Creates and owns all 8 tool-phase refs. Provides lifecycle methods that * handle all side effects (needsDraw, React state sync, notifications) in one * place, fixing bugs where individual mutation sites missed extendPhase resets, * needsDraw, or eventBus notifications. * * The refs are exposed directly so the RAF draw loop and useToolInteraction can * read/write them at 60fps without method-call overhead. */ export function useToolPhaseManager(initialTool: ActiveTool): ToolPhaseManager { // Create all refs once const compassPhaseRef = useRef<CompassPhase>({ tag: 'idle' }) const straightedgePhaseRef = useRef<StraightedgePhase>({ tag: 'idle' }) const extendPhaseRef = useRef<ExtendPhase>({ tag: 'idle' }) const macroPhaseRef = useRef<MacroPhase>({ tag: 'idle' }) const snappedPointIdRef = useRef<string | null>(null) const activeToolRef = useRef<ActiveTool>(initialTool) const pointerCapturedRef = useRef(false) const needsDrawRef = useRef(true) // Stable manager object — created once via useRef init, never recreated const managerRef = useRef<ToolPhaseManager | null>(null) if (managerRef.current === null) { const manager: ToolPhaseManager = { // Refs compassPhaseRef, straightedgePhaseRef, extendPhaseRef, macroPhaseRef, snappedPointIdRef, activeToolRef, pointerCapturedRef, needsDrawRef, // Callback slots (written by EuclidCanvas at render time) onPhaseChange: null, onMacroPhaseSync: null, onActiveToolSync: null, resetAll() { compassPhaseRef.current = { tag: 'idle' } straightedgePhaseRef.current = { tag: 'idle' } extendPhaseRef.current = { tag: 'idle' } macroPhaseRef.current = { tag: 'idle' } pointerCapturedRef.current = false needsDrawRef.current = true manager.onMacroPhaseSync?.({ tag: 'idle' }) }, enterMacroSelecting(propId: number, inputs: MacroInput[]) { const phase: MacroPhase = { tag: 'selecting', propId, inputs, selectedPointIds: [], } macroPhaseRef.current = phase needsDrawRef.current = true manager.onMacroPhaseSync?.(phase) manager.onPhaseChange?.() }, enterMacroChoosing() { const phase: MacroPhase = { tag: 'choosing' } macroPhaseRef.current = phase manager.onMacroPhaseSync?.(phase) }, selectTool(tool: ActiveTool) { activeToolRef.current = tool manager.onActiveToolSync?.(tool) }, setMacroPhase(phase: MacroPhase) { macroPhaseRef.current = phase manager.onMacroPhaseSync?.(phase) }, notifyPhaseChange() { manager.onPhaseChange?.() }, requestDraw() { needsDrawRef.current = true }, } managerRef.current = manager } return managerRef.current } |