diff options
| author | grothedev <grothedev@gmail.com> | 2026-05-29 21:49:20 -0400 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2026-05-29 21:49:20 -0400 |
| commit | 6196004b51a6850909c154f5402ff4858eab479a (patch) | |
| tree | 126b8bb1600d0a656e0df016e25d08c390f3540e /web-timeplot/src/core/store.js | |
| parent | 27dc5849c3eaf4824d79938e7077abdbe2c82e24 (diff) | |
mv web stuff to root project dirHEADprototypeframeworkmain
Diffstat (limited to 'web-timeplot/src/core/store.js')
| -rw-r--r-- | web-timeplot/src/core/store.js | 291 |
1 files changed, 0 insertions, 291 deletions
diff --git a/web-timeplot/src/core/store.js b/web-timeplot/src/core/store.js deleted file mode 100644 index 38052eb..0000000 --- a/web-timeplot/src/core/store.js +++ /dev/null @@ -1,291 +0,0 @@ -const STORAGE_KEY = 'timeplot.app-state.v1'; - -function clonePanelState(panels) { - return Object.fromEntries(Object.entries(panels).map(([key, value]) => [key, { ...value }])); -} - -function cloneNamedState(items) { - return Object.fromEntries(Object.entries(items).map(([key, value]) => [key, { ...value }])); -} - -function sanitizePersistedSource(source) { - return { - type: source.type, - preset: source.preset, - sampleRateHz: source.sampleRateHz, - amplitude: source.amplitude, - noise: source.noise, - replayRate: source.replayRate, - dataFileName: source.dataFileName, - wsUrl: source.wsUrl, - wsReconnectMs: source.wsReconnectMs, - }; -} - -function createPersistableState(state) { - return { - plot: { - showGrid: state.plot.showGrid, - showPoints: state.plot.showPoints, - windowDurationMs: state.plot.windowDurationMs, - maxPoints: state.plot.maxPoints, - }, - time: { - speed: state.time.speed, - }, - panels: clonePanelState(state.panels), - graphs: cloneNamedState(state.graphs), - sources: Object.fromEntries(Object.entries(state.sources).map(([key, value]) => [ - key, - sanitizePersistedSource(value), - ])), - }; -} - -function mergePersistedState(baseState, persistedState) { - if (!persistedState || typeof persistedState !== 'object') { - return baseState; - } - - const mergedState = { - ...baseState, - time: persistedState.time - ? { - ...baseState.time, - speed: persistedState.time.speed ?? baseState.time.speed, - paused: false, - } - : baseState.time, - plot: persistedState.plot - ? { - ...baseState.plot, - ...persistedState.plot, - valueRange: baseState.plot.valueRange, - hoveredPoint: null, - tooltip: { ...baseState.plot.tooltip }, - } - : baseState.plot, - panels: persistedState.panels - ? clonePanelState(Object.fromEntries(Object.entries(baseState.panels).map(([key, value]) => [ - key, - { - ...value, - ...(persistedState.panels[key] ?? {}), - }, - ]))) - : baseState.panels, - graphs: persistedState.graphs - ? cloneNamedState(Object.fromEntries(Object.entries(baseState.graphs).map(([key, value]) => [ - key, - { - ...value, - ...(persistedState.graphs[key] ?? {}), - }, - ]))) - : baseState.graphs, - sources: persistedState.sources - ? Object.fromEntries(Object.entries(baseState.sources).map(([key, value]) => { - const persistedSource = persistedState.sources[key] ?? {}; - const nextType = persistedSource.type ?? value.type; - - return [ - key, - { - ...value, - ...persistedSource, - type: nextType, - dataset: [], - datasetPointCount: 0, - datasetDurationMs: 0, - loadError: nextType === 'csv-replay' && persistedSource.dataFileName - ? `Reload ${persistedSource.dataFileName} to restore replay data` - : '', - wsStatus: 'idle', - wsStatusDetail: '', - }, - ]; - })) - : baseState.sources, - }; - - return mergedState; -} - -function loadPersistedState() { - if (typeof localStorage === 'undefined') { - return null; - } - - try { - const raw = localStorage.getItem(STORAGE_KEY); - if (!raw) { - return null; - } - - return JSON.parse(raw); - } catch (error) { - console.warn('[timeplot] failed to load persisted state', error); - return null; - } -} - -function savePersistedState(state) { - if (typeof localStorage === 'undefined') { - return; - } - - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(createPersistableState(state))); - } catch (error) { - console.warn('[timeplot] failed to persist state', error); - } -} - -export function createInitialState() { - return { - app: { - title: 'TimePlot', - renderer: 'pending', - }, - time: { - realNowMs: Date.now(), - realElapsedMs: 0, - plotTimeMs: 0, - speed: 1, - paused: false, - }, - plot: { - showGrid: true, - showPoints: true, - windowDurationMs: 20000, - maxPoints: 1600, - valueRange: { - min: -1.6, - max: 1.6, - }, - hoveredPoint: null, - tooltip: { - visible: false, - x: 0, - y: 0, - point: null, - }, - }, - sources: { - signalA: { - id: 'signal-a', - label: 'Signal A', - type: 'synthetic-wave', - preset: 'telemetry', - sampleRateHz: 60, - amplitude: 1, - noise: 0.08, - replayRate: 1, - dataset: [], - dataFileName: '', - datasetPointCount: 0, - datasetDurationMs: 0, - loadError: '', - wsUrl: 'ws://localhost:8080', - wsReconnectMs: 2000, - wsStatus: 'idle', - wsStatusDetail: '', - }, - signalB: { - id: 'signal-b', - label: 'Signal B', - type: 'synthetic-wave', - preset: 'chirp', - sampleRateHz: 48, - amplitude: 0.8, - noise: 0.04, - replayRate: 1, - dataset: [], - dataFileName: '', - datasetPointCount: 0, - datasetDurationMs: 0, - loadError: '', - wsUrl: 'ws://localhost:8080', - wsReconnectMs: 2000, - wsStatus: 'idle', - wsStatusDetail: '', - }, - }, - graphs: { - primary: { - sourceKey: 'signalA', - transform: 'raw', - title: 'Primary signal', - }, - secondary: { - sourceKey: 'signalB', - transform: 'delta', - title: 'Secondary signal', - }, - }, - panels: { - status: { title: 'Status', visible: true }, - source: { title: 'Data Source', visible: true }, - config: { title: 'Config', visible: true }, - help: { title: 'Help', visible: false }, - }, - }; -} - -export class Store { - constructor(initialState = createInitialState()) { - this.state = mergePersistedState(initialState, loadPersistedState()); - this.listeners = new Set(); - } - - getState() { - return this.state; - } - - subscribe(listener) { - this.listeners.add(listener); - return () => this.listeners.delete(listener); - } - - setState(updater) { - const nextState = typeof updater === 'function' ? updater(this.state) : updater; - this.state = nextState; - savePersistedState(this.state); - for (const listener of this.listeners) { - listener(this.state); - } - } - - patch(partial) { - this.setState((state) => ({ - ...state, - ...partial, - time: partial.time ? { ...state.time, ...partial.time } : state.time, - plot: partial.plot - ? { - ...state.plot, - ...partial.plot, - valueRange: partial.plot.valueRange - ? { ...state.plot.valueRange, ...partial.plot.valueRange } - : state.plot.valueRange, - tooltip: partial.plot.tooltip - ? { ...state.plot.tooltip, ...partial.plot.tooltip } - : state.plot.tooltip, - } - : state.plot, - sources: partial.sources - ? Object.fromEntries(Object.entries({ ...state.sources, ...partial.sources }).map(([key, value]) => [ - key, - { ...state.sources[key], ...value }, - ])) - : state.sources, - graphs: partial.graphs - ? cloneNamedState(Object.fromEntries(Object.entries({ ...state.graphs, ...partial.graphs }).map(([key, value]) => [ - key, - { ...state.graphs[key], ...value }, - ]))) - : state.graphs, - panels: partial.panels ? clonePanelState({ ...state.panels, ...partial.panels }) : state.panels, - })); - } -} |
