From cac178f06d1975ea460b5acbf7ef7b43191fe09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=A1sz?= <> Date: Wed, 22 Mar 2023 13:44:34 +0100 Subject: [PATCH] Add settable metric tiles and settings persistence Make the metric tiles settable via the settings dialog and implement persistence of these settings to the browser localStorage (partially fix #131) --- app/client/components/PerformanceDashboard.js | 62 ++++++++++++------- app/client/index.js | 16 ++++- app/client/lib/app.js | 4 +- app/client/store/appState.js | 5 +- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/app/client/components/PerformanceDashboard.js b/app/client/components/PerformanceDashboard.js index 4e5c913..91492b6 100644 --- a/app/client/components/PerformanceDashboard.js +++ b/app/client/components/PerformanceDashboard.js @@ -34,7 +34,7 @@ export class PerformanceDashboard extends AppElement { } } - dashboard-metric, dashboard-actions,dashboard-force-curve { + dashboard-metric, dashboard-actions, dashboard-force-curve { background: var(--theme-widget-color); text-align: center; position: relative; @@ -63,6 +63,32 @@ export class PerformanceDashboard extends AppElement { filter: brightness(150%); } ` + dashboardMetricComponents = (formattedMetrics, appState) => ({ + distance: html``, + + pace: html``, + + power: html``, + + stkRate: html``, + + heartRate: html` + ${formattedMetrics?.heartrateBatteryLevel?.value + ? html`` + : ''} + `, + + totalStk: html``, + + calories: html``, + + timer: html``, + + forceCurve: html``, + + actions: html`` + }) + @state({ type: Object }) dialog @@ -73,31 +99,19 @@ export class PerformanceDashboard extends AppElement { appState = APP_STATE render () { - const metrics = this.calculateFormattedMetrics(this.appState.metrics) + const metricConfig = [...new Set(this.appState.config.guiConfigs.dashboardMetrics)].reduce((prev, metricName) => { + prev.push(this.dashboardMetricComponents(this.metrics, this.appState)[metricName]) + return prev + }, []) + + this.metrics = this.calculateFormattedMetrics(this.appState.metrics) return html`
${icon_settings} ${this.dialog ? this.dialog : ''}
- - - - - ${metrics?.heartrate?.value - ? html` - - ${metrics?.heartrateBatteryLevel?.value - ? html` - - ` - : '' - } - ` - : html``} - - - - + + ${metricConfig} ` } @@ -124,10 +138,10 @@ export class PerformanceDashboard extends AppElement { const formattedMetrics = {} for (const [key, value] of Object.entries(metrics)) { const valueFormatted = fieldFormatter[key] ? fieldFormatter[key](value) : value - if (valueFormatted.value !== undefined && valueFormatted.unit !== undefined) { + if (valueFormatted?.value !== undefined && valueFormatted?.unit !== undefined) { formattedMetrics[key] = { - value: valueFormatted.value, - unit: valueFormatted.unit + value: valueFormatted?.value, + unit: valueFormatted?.unit } } else { formattedMetrics[key] = { diff --git a/app/client/index.js b/app/client/index.js index b26dfcd..d81f2cd 100644 --- a/app/client/index.js +++ b/app/client/index.js @@ -28,6 +28,11 @@ export class App extends LitElement { // todo: we also want a mechanism here to get notified of state changes }) + const config = this.appState.config.guiConfigs + Object.keys(config).forEach(key => { + config[key] = JSON.parse(localStorage.getItem(key)) ?? config[key] + }) + // this is how we implement changes to the global state: // once any child component sends this CustomEvent we update the global state according // to the changes that were passed to us @@ -39,13 +44,22 @@ export class App extends LitElement { this.addEventListener('triggerAction', (event) => { this.app.handleAction(event.detail) }) + + // notify the app about the triggered action + this.addEventListener('changeGuiSetting', (event) => { + Object.keys(event.detail.config.guiConfigs).forEach(key => { + localStorage.setItem(key, JSON.stringify(event.detail.config.guiConfigs[key])) + }) + + this.updateState(event.detail) + }) } // the global state is updated by replacing the appState with a copy of the new state // todo: maybe it is more convenient to just pass the state elements that should be changed? // i.e. do something like this.appState = { ..this.appState, ...newState } updateState = (newState) => { - this.appState = { ...newState } + this.appState = { ...this.appState, ...newState } } // return a deep copy of the state to other components to minimize risk of side effects diff --git a/app/client/lib/app.js b/app/client/lib/app.js index fff8a1b..7404998 100644 --- a/app/client/lib/app.js +++ b/app/client/lib/app.js @@ -38,7 +38,7 @@ export function createApp (app) { let initialWebsocketOpenend = true function initWebsocket () { // use the native websocket implementation of browser to communicate with backend - socket = new WebSocket(`ws://${location.host}/websocket`) + socket = new WebSocket('ws://localhost:100/websocket') socket.addEventListener('open', (event) => { console.log('websocket opened') @@ -71,7 +71,7 @@ export function createApp (app) { const data = message.data switch (message.type) { case 'config': { - app.updateState({ ...app.getState(), config: data }) + app.updateState({ ...app.getState(), config: { ...app.getState().config, ...data } }) break } case 'metrics': { diff --git a/app/client/store/appState.js b/app/client/store/appState.js index 77c78e2..454a0cd 100644 --- a/app/client/store/appState.js +++ b/app/client/store/appState.js @@ -20,6 +20,9 @@ export const APP_STATE = { // true if upload to strava is enabled stravaUploadEnabled: false, // true if remote device shutdown is enabled - shutdownEnabled: false + shutdownEnabled: false, + guiConfigs: { + dashboardMetrics: ['distance', 'timer', 'pace', 'power', 'stkRate', 'totalStk', 'calories', 'actions'] + } } }