summaryrefslogtreecommitdiff
path: root/web-timeplot/src/core/store.js
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2026-05-29 21:49:20 -0400
committergrothedev <grothedev@gmail.com>2026-05-29 21:49:20 -0400
commit6196004b51a6850909c154f5402ff4858eab479a (patch)
tree126b8bb1600d0a656e0df016e25d08c390f3540e /web-timeplot/src/core/store.js
parent27dc5849c3eaf4824d79938e7077abdbe2c82e24 (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.js291
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,
- }));
- }
-}