New React UI around the unchanged engine (#1). GNB_ONLY page: - AppHeader: GNB + GNBBrand; sample drawings in GNBNav (NavigationMenu) and GNBMobileMenu (same data, real ?model= links); file open and about/notice in GNBUtilities. - DwgViewerCanvas: useRef/useEffect wrapper. new Viewer2D(host) on mount, option props diffed and pushed through the engine setters, onSelect/onPointerWorld/onViewChange forwarded to React. Viewer2D has no dispose(), so teardown stops the rAF loop / WebGL context from outside (also needed for StrictMode double mount). - ViewerToolbar: Model/Layout ToggleGroup (sample's visibility rule), Fit, 2.5D and canvas-theme Toggles, window Toggles. - FloatingPanel: HM Card in a fixed frame; drag/collapse via the ported makePanelDraggableAndCollapsible, CSS resize. - LayersPanel (FilterBar search, Switch per layer, show/hide all via LayerPanelManager), PropertiesPanel (formatEntityProperties → Badge + Table, zoom to selection), OptionsPanel (Switch/Slider/Input, IndexedDB through optionsStore, reset). - Load feedback per HMDesign: Progress for the measurable part (download bytes, stage steps) and LoadingScreen for the blocking WASM parse / Viewer2D.load(), painted before each blocking stage. - StatusBar with status line, zoom %, live CAD coordinates in the bottom-right corner; AboutDialog keeps the acadrust MPL-2.0 notice, THIRD-PARTY-NOTICES and the local patch reachable. - Drag & drop, ?model= auto-load, F shortcut, ResizeObserver resync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
35 lines
948 B
TypeScript
35 lines
948 B
TypeScript
import { useSyncExternalStore } from "react"
|
|
|
|
/**
|
|
* Minimal external store for values the viewer emits at pointer-move / camera-change rate (CAD coordinates,
|
|
* zoom %). Keeping them out of App state means only the small readout re-renders, not the whole screen.
|
|
*/
|
|
export type ValueStore<T> = {
|
|
get: () => T
|
|
set: (value: T) => void
|
|
subscribe: (listener: () => void) => () => void
|
|
}
|
|
|
|
export function createValueStore<T>(initial: T): ValueStore<T> {
|
|
let value = initial
|
|
const listeners = new Set<() => void>()
|
|
return {
|
|
get: () => value,
|
|
set: (next) => {
|
|
if (Object.is(next, value)) return
|
|
value = next
|
|
listeners.forEach((l) => l())
|
|
},
|
|
subscribe: (listener) => {
|
|
listeners.add(listener)
|
|
return () => {
|
|
listeners.delete(listener)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
export function useValueStore<T>(store: ValueStore<T>): T {
|
|
return useSyncExternalStore(store.subscribe, store.get, store.get)
|
|
}
|