summaryrefslogtreecommitdiff
path: root/web-timeplot/src/core/store.js
diff options
context:
space:
mode:
authorThomas Grothe <grothe.tr@gmail.com>2026-04-30 00:53:13 -0400
committerThomas Grothe <grothe.tr@gmail.com>2026-04-30 00:53:13 -0400
commit73d75835e18a33c7f6c1b09bbcef93b16a7a9bfa (patch)
treee079c6c45416333e29cf900831c07619a87d5c39 /web-timeplot/src/core/store.js
parenta1c95e72bea26f554eb05916d6fc584927367159 (diff)
redo timeplot web
Diffstat (limited to 'web-timeplot/src/core/store.js')
-rw-r--r--web-timeplot/src/core/store.js95
1 files changed, 95 insertions, 0 deletions
diff --git a/web-timeplot/src/core/store.js b/web-timeplot/src/core/store.js
new file mode 100644
index 0000000..9989e5f
--- /dev/null
+++ b/web-timeplot/src/core/store.js
@@ -0,0 +1,95 @@
+function clonePanelState(panels) {
+ return Object.fromEntries(Object.entries(panels).map(([key, value]) => [key, { ...value }]));
+}
+
+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,
+ },
+ },
+ source: {
+ activeId: 'synthetic-wave',
+ preset: 'telemetry',
+ sampleRateHz: 60,
+ amplitude: 1,
+ noise: 0.08,
+ },
+ 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 = initialState;
+ 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;
+ 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,
+ source: partial.source ? { ...state.source, ...partial.source } : state.source,
+ panels: partial.panels ? clonePanelState({ ...state.panels, ...partial.panels }) : state.panels,
+ }));
+ }
+}