summaryrefslogtreecommitdiff
path: root/web-timeplot/src/core/store.js
blob: 9989e5fe781e4106fc994f1a117a36f9e71b09f6 (plain)
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
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,
        }));
    }
}