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)
This commit is contained in:
parent
00d9c824d0
commit
cac178f06d
|
|
@ -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`<dashboard-metric .icon=${icon_route} .unit=${formattedMetrics?.totalLinearDistanceFormatted?.unit || 'm'} .value=${formattedMetrics?.totalLinearDistanceFormatted?.value}></dashboard-metric>`,
|
||||
|
||||
pace: html`<dashboard-metric .icon=${icon_stopwatch} unit="/500m" .value=${formattedMetrics?.cyclePaceFormatted?.value}></dashboard-metric>`,
|
||||
|
||||
power: html`<dashboard-metric .icon=${icon_bolt} unit="watt" .value=${formattedMetrics?.cyclePower?.value}></dashboard-metric>`,
|
||||
|
||||
stkRate: html`<dashboard-metric .icon=${icon_paddle} unit="/min" .value=${formattedMetrics?.cycleStrokeRate?.value}></dashboard-metric>`,
|
||||
|
||||
heartRate: html`<dashboard-metric .icon=${icon_heartbeat} unit="bpm" .value=${formattedMetrics?.heartrate?.value}>
|
||||
${formattedMetrics?.heartrateBatteryLevel?.value
|
||||
? html`<battery-icon .batteryLevel=${formattedMetrics?.heartrateBatteryLevel?.value}></battery-icon>`
|
||||
: ''}
|
||||
</dashboard-metric>`,
|
||||
|
||||
totalStk: html`<dashboard-metric .icon=${icon_paddle} unit="total" .value=${formattedMetrics?.totalNumberOfStrokes?.value}></dashboard-metric>`,
|
||||
|
||||
calories: html`<dashboard-metric .icon=${icon_fire} unit="kcal" .value=${formattedMetrics?.totalCalories?.value}></dashboard-metric>`,
|
||||
|
||||
timer: html`<dashboard-metric .icon=${icon_clock} .value=${formattedMetrics?.totalMovingTimeFormatted?.value}></dashboard-metric>`,
|
||||
|
||||
forceCurve: html`<dashboard-force-curve .value=${appState?.metrics.driveHandleForceCurve} style="grid-column: span 2"></dashboard-force-curve>`,
|
||||
|
||||
actions: html`<dashboard-actions .appState=${appState}></dashboard-actions>`
|
||||
})
|
||||
|
||||
@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`
|
||||
<div class="settings" @click=${this.openSettings}>
|
||||
${icon_settings}
|
||||
${this.dialog ? this.dialog : ''}
|
||||
</div>
|
||||
<dashboard-metric .icon=${icon_route} .unit=${metrics?.totalLinearDistanceFormatted?.unit || 'm'} .value=${metrics?.totalLinearDistanceFormatted?.value}></dashboard-metric>
|
||||
<dashboard-metric .icon=${icon_stopwatch} unit="/500m" .value=${metrics?.cyclePaceFormatted?.value}></dashboard-metric>
|
||||
<dashboard-metric .icon=${icon_bolt} unit="watt" .value=${metrics?.cyclePower?.value}></dashboard-metric>
|
||||
<dashboard-metric .icon=${icon_paddle} unit="/min" .value=${metrics?.cycleStrokeRate?.value}></dashboard-metric>
|
||||
${metrics?.heartrate?.value
|
||||
? html`
|
||||
<dashboard-metric .icon=${icon_heartbeat} unit="bpm" .value=${metrics?.heartrate?.value}>
|
||||
${metrics?.heartrateBatteryLevel?.value
|
||||
? html`
|
||||
<battery-icon .batteryLevel=${metrics?.heartrateBatteryLevel?.value}></battery-icon>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</dashboard-metric>`
|
||||
: html`<dashboard-metric .icon=${icon_paddle} unit="total" .value=${metrics?.totalNumberOfStrokes?.value}></dashboard-metric>`}
|
||||
<dashboard-force-curve .value=${this.appState?.metrics?.driveHandleForceCurve} style="grid-column: span 2"></dashboard-force-curve>
|
||||
<dashboard-metric .icon=${icon_fire} unit="kcal" .value=${metrics?.totalCalories?.value}></dashboard-metric>
|
||||
<dashboard-metric .icon=${icon_clock} .value=${metrics?.totalMovingTimeFormatted?.value}></dashboard-metric>
|
||||
<dashboard-actions .appState=${this.appState}></dashboard-actions>
|
||||
|
||||
${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] = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue