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>
|
<button @click=${this.reset}>${icon_undo}</button>
|
||||||
${this.renderOptionalButtons()}
|
${this.renderOptionalButtons()}
|
||||||
<button @click=${this.switchPeripheralMode}>${icon_bluetooth}</button>
|
<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 = []
|
const buttons = []
|
||||||
// changing to fullscreen mode only makes sence when the app is openend in a regular
|
// 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)
|
// webbrowser (kiosk and standalone mode are always in fullscreen view)
|
||||||
if (this.appState.appMode === '') {
|
if (this.appState.appMode === 'BROWSER') {
|
||||||
buttons.push(html`
|
buttons.push(html`
|
||||||
<button @click=${this.toggleFullscreen}>
|
<button @click=${this.toggleFullscreen}>
|
||||||
<div id="fullscreen-icon">${icon_expand}</div>
|
<div id="fullscreen-icon">${icon_expand}</div>
|
||||||
|
|
@ -48,6 +48,17 @@ export class DashboardActions extends AppElement {
|
||||||
return buttons
|
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 () {
|
toggleFullscreen () {
|
||||||
const fullscreenElement = document.getElementsByTagName('web-app')[0]
|
const fullscreenElement = document.getElementsByTagName('web-app')[0]
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export class DashboardMetric extends AppElement {
|
||||||
return html`
|
return html`
|
||||||
<div class="label">${this.icon}</div>
|
<div class="label">${this.icon}</div>
|
||||||
<div class="content">
|
<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>
|
<span class="metric-unit">${this.unit}</span>
|
||||||
</div>
|
</div>
|
||||||
${this.batteryLevel &&
|
${this.batteryLevel &&
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export class PerformanceDashboard extends AppElement {
|
||||||
appState = APP_STATE
|
appState = APP_STATE
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const metrics = this.appState.metrics
|
const metrics = this.calculateFormattedMetrics(this.appState.metrics)
|
||||||
return html`
|
return html`
|
||||||
<dashboard-metric .icon=${icon_route} .unit=${metrics?.distanceTotal?.unit} .value=${metrics?.distanceTotal?.value}></dashboard-metric>
|
<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>
|
<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>
|
<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 }
|
// i.e. do something like this.appState = { ..this.appState, ...newState }
|
||||||
updateState = (newState) => {
|
updateState = (newState) => {
|
||||||
this.appState = { ...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 = () => {
|
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
|
// once we have multiple views, then we would rather reference some kind of router here
|
||||||
|
|
|
||||||
|
|
@ -7,32 +7,13 @@
|
||||||
|
|
||||||
import NoSleep from 'nosleep.js'
|
import NoSleep from 'nosleep.js'
|
||||||
|
|
||||||
export function createApp (app) {
|
const rowingMetricsFields = ['strokesTotal', 'distanceTotal', 'caloriesTotal', 'power', 'heartrate',
|
||||||
const fields = ['strokesTotal', 'distanceTotal', 'caloriesTotal', 'power', 'heartrate',
|
'heartrateBatteryLevel', 'splitFormatted', 'strokesPerMinute', 'durationTotalFormatted']
|
||||||
'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 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
|
let socket
|
||||||
|
|
||||||
|
|
@ -61,34 +42,29 @@ export function createApp (app) {
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// todo: we have to use different types of messages to make processing easier
|
||||||
socket.addEventListener('message', (event) => {
|
socket.addEventListener('message', (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
let activeFields = fields
|
let activeFields = rowingMetricsFields
|
||||||
// if we are in reset state only update heart rate and peripheral mode
|
// if we are in reset state only update heart rate
|
||||||
if (data.strokesTotal === 0) {
|
if (data.strokesTotal === 0) {
|
||||||
activeFields = ['heartrate', 'peripheralMode']
|
activeFields = ['heartrate']
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: formatting should happen in the related components (i.e.) PerformanceDashboard
|
const filteredData = Object.keys(data)
|
||||||
for (const [key, value] of Object.entries(data)) {
|
.filter(key => activeFields.includes(key))
|
||||||
if (activeFields.includes(key)) {
|
.reduce((obj, key) => {
|
||||||
const valueFormatted = fieldFormatter[key] ? fieldFormatter[key](value) : value
|
obj[key] = data[key]
|
||||||
if (valueFormatted.value !== undefined && valueFormatted.unit !== undefined) {
|
return obj
|
||||||
metrics[key] = {
|
}, {})
|
||||||
value: valueFormatted.value,
|
|
||||||
unit: valueFormatted.unit
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metrics[key] = {
|
|
||||||
value: valueFormatted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
|
|
@ -111,10 +87,15 @@ export function createApp (app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFields () {
|
function resetFields () {
|
||||||
for (const key of fields.filter((elem) => elem !== 'peripheralMode' && elem !== 'heartrate')) {
|
const appState = app.getState()
|
||||||
metrics[key] = { value: '--' }
|
// drop all metrics except heartrate
|
||||||
}
|
appState.metrics = Object.keys(appState.metrics)
|
||||||
app.updateState({ ...app.getState(), metrics })
|
.filter(key => key === 'heartrate' || key === 'heartrateBatteryLevel')
|
||||||
|
.reduce((obj, key) => {
|
||||||
|
obj[key] = appState.metrics[key]
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
app.updateState(appState)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAction (action) {
|
function handleAction (action) {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
export const APP_STATE = {
|
export const APP_STATE = {
|
||||||
// currently can be STANDALONE (Mobile Home Screen App), KIOSK (Raspberry Pi deployment) or '' (default)
|
// currently can be STANDALONE (Mobile Home Screen App), KIOSK (Raspberry Pi deployment) or '' (default)
|
||||||
appMode: '',
|
appMode: '',
|
||||||
// todo: this is currently embedded into the metrics object, but should probably be extracted
|
// currently can be FTMS, FTMSBIKE or PM5
|
||||||
peripheralMode: 'FTMSROWER',
|
peripheralMode: 'FTMS',
|
||||||
// contains all the rowing metrics that are delivered from the backend
|
// contains all the rowing metrics that are delivered from the backend
|
||||||
metrics: {}
|
metrics: {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue