summaryrefslogtreecommitdiff
path: root/web-timeplot/src/app/create-app.js
diff options
context:
space:
mode:
Diffstat (limited to 'web-timeplot/src/app/create-app.js')
-rw-r--r--web-timeplot/src/app/create-app.js152
1 files changed, 152 insertions, 0 deletions
diff --git a/web-timeplot/src/app/create-app.js b/web-timeplot/src/app/create-app.js
new file mode 100644
index 0000000..daf3559
--- /dev/null
+++ b/web-timeplot/src/app/create-app.js
@@ -0,0 +1,152 @@
+import { EventBus } from '../core/event-bus.js';
+import { Store, createInitialState } from '../core/store.js';
+import { TimeController } from '../core/time-controller.js';
+import { PlotBuffer } from '../plot/plot-buffer.js';
+import { TimeplotView } from '../plot/timeplot-view.js';
+import { SourceRegistry } from '../data/source-registry.js';
+import { PanelManager } from '../ui/panel-manager.js';
+
+function clamp(value, min, max) {
+ return Math.min(max, Math.max(min, value));
+}
+
+export async function createApp(root) {
+ const bus = new EventBus();
+ const store = new Store(createInitialState());
+ const timeController = new TimeController(store);
+ const buffer = new PlotBuffer(store.getState().plot.maxPoints);
+ let sourceRegistry;
+
+ const actions = {
+ togglePause: () => timeController.togglePause(),
+ setSpeed: (speed) => timeController.setSpeed(speed),
+ resetScene: () => {
+ timeController.reset();
+ buffer.clear();
+ sourceRegistry.reset();
+ },
+ togglePanel: (panelId) => {
+ store.setState((state) => ({
+ ...state,
+ panels: {
+ ...state.panels,
+ [panelId]: {
+ ...state.panels[panelId],
+ visible: !state.panels[panelId].visible,
+ },
+ },
+ }));
+ },
+ updateSource: (field, value) => {
+ store.setState((state) => ({
+ ...state,
+ source: {
+ ...state.source,
+ [field]: value,
+ },
+ }));
+ sourceRegistry.syncFromState();
+ },
+ updatePlot: (field, value) => {
+ store.setState((state) => ({
+ ...state,
+ plot: {
+ ...state.plot,
+ [field]: value,
+ },
+ }));
+
+ if (field === 'maxPoints') {
+ buffer.maxPoints = clamp(value, 200, 4000);
+ }
+ },
+ };
+
+ const panelManager = new PanelManager({ root, store, actions });
+ const elements = panelManager.mount();
+
+ const plotView = new TimeplotView({
+ host: elements.canvasHost,
+ onHover: (hoverState) => {
+ store.setState((state) => ({
+ ...state,
+ plot: {
+ ...state.plot,
+ hoveredPoint: hoverState?.point ?? null,
+ tooltip: hoverState
+ ? {
+ visible: true,
+ x: hoverState.x,
+ y: hoverState.y,
+ point: hoverState.point,
+ }
+ : {
+ ...state.plot.tooltip,
+ visible: false,
+ point: null,
+ },
+ },
+ }));
+ },
+ });
+
+ const renderer = await plotView.init();
+ store.patch({
+ app: {
+ ...store.getState().app,
+ renderer,
+ },
+ });
+
+ sourceRegistry = new SourceRegistry(store, bus);
+
+ bus.on('data:point', (point) => {
+ buffer.addPoint(point);
+ });
+
+ const keyHandler = (event) => {
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement) {
+ return;
+ }
+
+ if (event.code === 'Space') {
+ event.preventDefault();
+ actions.togglePause();
+ return;
+ }
+
+ if (event.key === '[') {
+ actions.setSpeed(store.getState().time.speed - 0.1);
+ return;
+ }
+
+ if (event.key === ']') {
+ actions.setSpeed(store.getState().time.speed + 0.1);
+ return;
+ }
+
+ if (event.key.toLowerCase() === 'g') {
+ actions.updatePlot('showGrid', !store.getState().plot.showGrid);
+ }
+ };
+
+ window.addEventListener('keydown', keyHandler);
+
+ plotView.app.ticker.add(() => {
+ timeController.tick();
+ sourceRegistry.syncFromState();
+ sourceRegistry.update(store.getState().time.plotTimeMs);
+
+ const state = store.getState();
+ const visiblePoints = buffer.getVisiblePoints(state.time.plotTimeMs, state.plot.windowDurationMs);
+ plotView.render(state, visiblePoints);
+ panelManager.sync(state, visiblePoints.length);
+ });
+
+ return {
+ destroy() {
+ window.removeEventListener('keydown', keyHandler);
+ plotView.destroy();
+ },
+ };
+}