moves formatting of metrics to the displaying components
This commit is contained in:
parent
424ba431c7
commit
f5ff252f95
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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: {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue