moves formatting of metrics to the displaying components

This commit is contained in:
Lars Berning 2022-01-15 12:29:48 +01:00
parent 424ba431c7
commit f5ff252f95
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
6 changed files with 78 additions and 58 deletions

View File

@ -21,7 +21,7 @@ export class DashboardActions extends AppElement {
<button @click=${this.reset}>${icon_undo}</button>
${this.renderOptionalButtons()}
<button @click=${this.switchPeripheralMode}>${icon_bluetooth}</button>
<div class="metric-unit">${this.appState?.metrics?.peripheralMode?.value}</div>
<div class="metric-unit">${this.peripheralMode()}</div>
`
}
@ -29,7 +29,7 @@ export class DashboardActions extends AppElement {
const buttons = []
// changing to fullscreen mode only makes sence when the app is openend in a regular
// webbrowser (kiosk and standalone mode are always in fullscreen view)
if (this.appState.appMode === '') {
if (this.appState.appMode === 'BROWSER') {
buttons.push(html`
<button @click=${this.toggleFullscreen}>
<div id="fullscreen-icon">${icon_expand}</div>
@ -48,6 +48,17 @@ export class DashboardActions extends AppElement {
return buttons
}
peripheralMode () {
const value = this.appState?.peripheralMode
if (value === 'PM5') {
return 'C2 PM5'
} else if (value === 'FTMSBIKE') {
return 'FTMS Bike'
} else {
return 'FTMS Rower'
}
}
toggleFullscreen () {
const fullscreenElement = document.getElementsByTagName('web-app')[0]
if (!document.fullscreenElement) {

View File

@ -31,7 +31,7 @@ export class DashboardMetric extends AppElement {
return html`
<div class="label">${this.icon}</div>
<div class="content">
<span class="metric-value">${this.value}</span>
<span class="metric-value">${this.value !== undefined ? this.value : '--'}</span>
<span class="metric-unit">${this.unit}</span>
</div>
${this.batteryLevel &&

View File

@ -26,7 +26,7 @@ export class PerformanceDashboard extends AppElement {
appState = APP_STATE
render () {
const metrics = this.appState.metrics
const metrics = this.calculateFormattedMetrics(this.appState.metrics)
return html`
<dashboard-metric .icon=${icon_route} .unit=${metrics?.distanceTotal?.unit} .value=${metrics?.distanceTotal?.value}></dashboard-metric>
<dashboard-metric .icon=${icon_stopwatch} unit="/500m" .value=${metrics?.splitFormatted?.value}></dashboard-metric>
@ -42,4 +42,31 @@ export class PerformanceDashboard extends AppElement {
<dashboard-actions .appState=${this.appState}></dashboard-actions>
`
}
calculateFormattedMetrics (metrics) {
const fieldFormatter = {
distanceTotal: (value) => value >= 10000
? { value: (value / 1000).toFixed(1), unit: 'km' }
: { value: Math.round(value), unit: 'm' },
caloriesTotal: (value) => Math.round(value),
power: (value) => Math.round(value),
strokesPerMinute: (value) => Math.round(value)
}
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) {
formattedMetrics[key] = {
value: valueFormatted.value,
unit: valueFormatted.unit
}
} else {
formattedMetrics[key] = {
value: valueFormatted
}
}
}
return formattedMetrics
}
}

View File

@ -47,12 +47,13 @@ export class App extends LitElement {
// i.e. do something like this.appState = { ..this.appState, ...newState }
updateState = (newState) => {
this.appState = { ...newState }
// console.table(this.appState.metrics)
}
// return a copy of the state to other components to minimize risk of side effects
// return a deep copy of the state to other components to minimize risk of side effects
getState = () => {
return { ...this.appState }
// todo: could use structuredClone once the browser support is wider
// https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
return JSON.parse(JSON.stringify(this.appState))
}
// once we have multiple views, then we would rather reference some kind of router here

View File

@ -7,32 +7,13 @@
import NoSleep from 'nosleep.js'
export function createApp (app) {
const fields = ['strokesTotal', 'distanceTotal', 'caloriesTotal', 'power', 'heartrate',
'splitFormatted', 'strokesPerMinute', 'durationTotalFormatted', 'peripheralMode']
// todo: formatting should happen in the related components (i.e.) PerformanceDashboard
const fieldFormatter = {
peripheralMode: (value) => {
if (value === 'PM5') {
return 'C2 PM5'
} else if (value === 'FTMSBIKE') {
return 'FTMS Bike'
} else {
return 'FTMS Rower'
}
},
distanceTotal: (value) => value >= 10000
? { value: (value / 1000).toFixed(1), unit: 'km' }
: { value: Math.round(value), unit: 'm' },
caloriesTotal: (value) => Math.round(value),
power: (value) => Math.round(value),
strokesPerMinute: (value) => Math.round(value)
}
const mode = window.location.hash
const appMode = mode === '#:standalone:' ? 'STANDALONE' : mode === '#:kiosk:' ? 'KIOSK' : ''
app.updateState({ ...app.getState(), appMode })
const rowingMetricsFields = ['strokesTotal', 'distanceTotal', 'caloriesTotal', 'power', 'heartrate',
'heartrateBatteryLevel', 'splitFormatted', 'strokesPerMinute', 'durationTotalFormatted']
const metrics = {}
export function createApp (app) {
const mode = window.location.hash
const appMode = mode === '#:standalone:' ? 'STANDALONE' : mode === '#:kiosk:' ? 'KIOSK' : 'BROWSER'
app.updateState({ ...app.getState(), appMode })
let socket
@ -61,34 +42,29 @@ export function createApp (app) {
}, 1000)
})
// todo: we have to use different types of messages to make processing easier
socket.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
let activeFields = fields
// if we are in reset state only update heart rate and peripheral mode
let activeFields = rowingMetricsFields
// if we are in reset state only update heart rate
if (data.strokesTotal === 0) {
activeFields = ['heartrate', 'peripheralMode']
activeFields = ['heartrate']
}
// todo: formatting should happen in the related components (i.e.) PerformanceDashboard
for (const [key, value] of Object.entries(data)) {
if (activeFields.includes(key)) {
const valueFormatted = fieldFormatter[key] ? fieldFormatter[key](value) : value
if (valueFormatted.value !== undefined && valueFormatted.unit !== undefined) {
metrics[key] = {
value: valueFormatted.value,
unit: valueFormatted.unit
}
} else {
metrics[key] = {
value: valueFormatted
}
}
}
}
const filteredData = Object.keys(data)
.filter(key => activeFields.includes(key))
.reduce((obj, key) => {
obj[key] = data[key]
return obj
}, {})
app.updateState({ ...app.getState(), metrics })
let updatedState = { ...app.getState(), metrics: filteredData }
if (data.peripheralMode) {
updatedState = { ...app.getState(), peripheralMode: data.peripheralMode }
}
app.updateState(updatedState)
} catch (err) {
console.log(err)
}
@ -111,10 +87,15 @@ export function createApp (app) {
}
function resetFields () {
for (const key of fields.filter((elem) => elem !== 'peripheralMode' && elem !== 'heartrate')) {
metrics[key] = { value: '--' }
}
app.updateState({ ...app.getState(), metrics })
const appState = app.getState()
// drop all metrics except heartrate
appState.metrics = Object.keys(appState.metrics)
.filter(key => key === 'heartrate' || key === 'heartrateBatteryLevel')
.reduce((obj, key) => {
obj[key] = appState.metrics[key]
return obj
}, {})
app.updateState(appState)
}
function handleAction (action) {

View File

@ -8,8 +8,8 @@
export const APP_STATE = {
// currently can be STANDALONE (Mobile Home Screen App), KIOSK (Raspberry Pi deployment) or '' (default)
appMode: '',
// todo: this is currently embedded into the metrics object, but should probably be extracted
peripheralMode: 'FTMSROWER',
// currently can be FTMS, FTMSBIKE or PM5
peripheralMode: 'FTMS',
// contains all the rowing metrics that are delivered from the backend
metrics: {}
}