diff --git a/app/client/components/DashboardForceCurve.js b/app/client/components/DashboardForceCurve.js
index 2e57958c7c..59521cf6d6 100644
--- a/app/client/components/DashboardForceCurve.js
+++ b/app/client/components/DashboardForceCurve.js
@@ -13,8 +13,20 @@ import { Chart, Filler, Legend, LinearScale, LineController, LineElement, PointE
@customElement('dashboard-force-curve')
export class DashboardForceCurve extends AppElement {
static styles = css`
+ :host {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .title {
+ font-size: 80%;
+ text-align: center;
+ padding: 0.2em 0;
+ }
+
canvas {
- margin-top: 24px;
+ flex: 1;
+ min-height: 0;
}
`
@@ -31,6 +43,7 @@ export class DashboardForceCurve extends AppElement {
firstUpdated () {
const ctx = this.renderRoot.querySelector('#chart').getContext('2d')
+ const baseFontSize = parseFloat(getComputedStyle(this).fontSize)
this._chart = new Chart(
ctx,
{
@@ -58,27 +71,12 @@ export class DashboardForceCurve extends AppElement {
...ctx.dataset.data.map((point) => point.y)
) === ctx.dataset.data[ctx.dataIndex].y,
font: {
- size: 16
+ size: baseFontSize * 0.4
},
color: 'rgb(255,255,255)'
},
legend: {
- title: {
- display: true,
- text: 'Force Curve',
- color: 'rgb(255,255,255)',
- font: {
- size: 32
- },
- padding: {
- }
- },
- labels: {
- boxWidth: 0,
- font: {
- size: 0
- }
- }
+ display: false,
}
},
scales: {
@@ -119,7 +117,8 @@ export class DashboardForceCurve extends AppElement {
}
return html`
-
+
Force Curve
+
`
}
}
diff --git a/app/client/components/DashboardActions.js b/app/client/components/DashboardToolbar.js
similarity index 50%
rename from app/client/components/DashboardActions.js
rename to app/client/components/DashboardToolbar.js
index a24ca248fd..9b7285bbb5 100644
--- a/app/client/components/DashboardActions.js
+++ b/app/client/components/DashboardToolbar.js
@@ -2,75 +2,83 @@
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor
- Component that renders the action buttons of the dashboard
+ Toolbar component combining settings and action buttons
*/
import { AppElement, html, css } from './AppElement.js'
import { customElement, property, state } from 'lit/decorators.js'
-import { iconUndo, iconExpand, iconCompress, iconPoweroff, iconBluetooth, iconUpload, iconHeartbeat, iconAntplus } from '../lib/icons.js'
+import { iconSettings, iconUndo, iconExpand, iconCompress, iconPoweroff, iconBluetooth, iconUpload, iconHeartbeat, iconAntplus } from '../lib/icons.js'
+import './SettingsDialog.js'
import './AppDialog.js'
-@customElement('dashboard-actions')
-export class DashboardActions extends AppElement {
+@customElement('dashboard-toolbar')
+export class DashboardToolbar extends AppElement {
static styles = css`
+ :host {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.5em;
+ padding: 0.2em 0.3em;
+ background: var(--theme-widget-color);
+ border-radius: var(--theme-border-radius);
+ }
+
+ .button-group {
+ display: flex;
+ align-items: center;
+ gap: 0.3em;
+ flex-wrap: wrap;
+ }
+
button {
position: relative;
- outline:none;
- background-color: var(--theme-button-color);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 2.5em;
+ height: 2.5em;
+ padding: 0;
border: 0;
border-radius: var(--theme-border-radius);
+ background: var(--theme-button-color);
color: var(--theme-font-color);
- margin: 0.2em 4px;
- font-size: 60%;
- text-decoration: none;
- display: inline-flex;
- width: 3.2em;
- min-width: 3.2em;
- height: 2.2em;
- justify-content: center;
- align-items: center;
+ font-size: 0.4em;
+ cursor: pointer;
}
button:hover {
filter: brightness(150%);
}
- button > div.text {
+ button .text {
position: absolute;
left: 2px;
bottom: 2px;
font-size: 40%;
}
- #fullscreen-icon {
- display: inline-flex;
+ .icon {
+ height: 1.2em;
}
- .top-button-group {
+ .peripheral-mode-container {
display: flex;
- flex-wrap: wrap;
- justify-content: center;
- }
-
- #windowed-icon {
- display: none;
- }
-
- .icon {
- height: 1.7em;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.1em;
}
.peripheral-mode {
- font-size: 50%;
+ font-size: 0.3em;
}
+ .fullscreen-icon { display: flex; }
+ .windowed-icon { display: none; }
+
@media (display-mode: fullscreen) {
- #fullscreen-icon {
- display: none;
- }
- #windowed-icon {
- display: inline-flex;
- }
+ .fullscreen-icon { display: none; }
+ .windowed-icon { display: flex; }
}
`
@@ -85,24 +93,35 @@ export class DashboardActions extends AppElement {
render () {
return html`
-
-
-
-
${this.blePeripheralMode()}
-
- ${this._dialog ? this._dialog : ''}
- `
+
+
+
+ ${this.renderOptionalButtons()}
+
+
+
+
+ ${this._dialog ? this._dialog : ''}
+ `
}
firstUpdated () {
@@ -120,30 +139,23 @@ export class DashboardActions extends AppElement {
renderOptionalButtons () {
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) and if the
- // browser supports this feature
if (this._appMode === 'BROWSER' && document.documentElement.requestFullscreen) {
buttons.push(html`
-
- `)
+
+ `)
}
- // add a button to power down the device, if browser is running on the device in kiosk mode
- // and the shutdown feature is enabled
- // (might also make sence to enable this for all clients but then we would need visual feedback)
if (this._appMode === 'KIOSK' && this.config?.shutdownEnabled) {
buttons.push(html`
-
- `)
+
+ `)
}
-
if (this.config?.uploadEnabled) {
buttons.push(html`
-
- `)
+
+ `)
}
return buttons
}
@@ -166,6 +178,12 @@ export class DashboardActions extends AppElement {
}
}
+ openSettings () {
+ this._dialog = html` {
+ this._dialog = undefined
+ }}>`
+ }
+
toggleFullscreen () {
const fullscreenElement = document.getElementsByTagName('web-app')[0]
if (!document.fullscreenElement) {
@@ -195,31 +213,29 @@ export class DashboardActions extends AppElement {
uploadTraining () {
this._dialog = html`
-
+ {
+ this._dialog = undefined
+ if (event.detail === 'confirm') {
+ this.sendEvent('triggerAction', { command: 'upload' })
+ }
+ }}>
Do you want to finish your workout and upload it to webservices (Strava, Intervals.icu and RowsAndAll)?
`
- function dialogClosed (event) {
- this._dialog = undefined
- if (event.detail === 'confirm') {
- this.sendEvent('triggerAction', { command: 'upload' })
- }
- }
}
shutdown () {
this._dialog = html`
-
+ {
+ this._dialog = undefined
+ if (event.detail === 'confirm') {
+ this.sendEvent('triggerAction', { command: 'shutdown' })
+ }
+ }}>
Do you want to shutdown the device?
`
- function dialogClosed (event) {
- this._dialog = undefined
- if (event.detail === 'confirm') {
- this.sendEvent('triggerAction', { command: 'shutdown' })
- }
- }
}
}
diff --git a/app/client/components/PerformanceDashboard.js b/app/client/components/PerformanceDashboard.js
index 7d0e932eb3..3424025eaf 100644
--- a/app/client/components/PerformanceDashboard.js
+++ b/app/client/components/PerformanceDashboard.js
@@ -6,9 +6,8 @@
*/
import { AppElement, html, css } from './AppElement.js'
-import { customElement, property, state } from 'lit/decorators.js'
-import './SettingsDialog.js'
-import { iconSettings } from '../lib/icons.js'
+import { customElement, property } from 'lit/decorators.js'
+import './DashboardToolbar.js'
import { DASHBOARD_METRICS } from '../store/dashboardMetrics.js'
@customElement('performance-dashboard')
@@ -16,54 +15,55 @@ export class PerformanceDashboard extends AppElement {
static styles = css`
:host {
display: grid;
- height: calc(100vh - 2vw);
- padding: 1vw;
- grid-gap: 1vw;
- grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template:
+ "toolbar" auto
+ "metrics" 1fr
+ / 1fr;
+ height: 100vh;
+ gap: 1vw;
+ box-sizing: border-box;
}
- @media (orientation: portrait) {
- :host {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- grid-template-rows: repeat(4, minmax(0, 1fr));
- }
+ dashboard-toolbar {
+ grid-area: toolbar;
}
- dashboard-metric, dashboard-actions, dashboard-force-curve {
- background: var(--theme-widget-color);
- text-align: center;
- position: relative;
- padding: 0.5em 0.2em 0 0.2em;
- border-radius: var(--theme-border-radius);
+ .metrics-grid {
+ grid-area: metrics;
+ display: grid;
+ gap: 1vw;
+ grid-template-columns: repeat(4, 1fr);
+ grid-template-rows: repeat(2, 1fr);
+ min-height: 0; /* prevent grid blowout */
}
- dashboard-actions {
- padding: 0.5em 0 0 0;
+ .metrics-grid.rows-3 {
+ grid-template-rows: repeat(3, 1fr);
}
- .settings {
- padding: 0.1em 0;
- position: absolute;
- bottom: 0;
- right: 0;
- z-index: 20;
- }
+ @media (orientation: portrait) {
+ .metrics-grid {
+ grid-template-columns: repeat(2, 1fr);
+ grid-template-rows: repeat(4, 1fr);
+ }
- .settings .icon {
- cursor: pointer;
- height: 1em;
+ .metrics-grid.rows-3 {
+ grid-template-rows: repeat(6, 1fr);
+ }
}
- .settings:hover .icon {
- filter: brightness(150%);
+ dashboard-metric,
+ dashboard-force-curve {
+ background: var(--theme-widget-color);
+ text-align: center;
+ padding: 0.5em 0.2em 0;
+ border-radius: var(--theme-border-radius);
+ min-height: 0; /* prevent grid blowout */
}
`
@property()
appState = {}
- @state()
- _dialog
-
dashboardMetricComponentsFactory = (appState) => {
const metrics = appState.metrics
const configs = appState.config
@@ -83,27 +83,11 @@ export class PerformanceDashboard extends AppElement {
return prev
}, [])
+ const gridClass = this.appState.config.guiConfigs.maxNumberOfTiles === 12 ? 'rows-3' : ''
+
return html`
-
-
- ${iconSettings}
- ${this._dialog ? this._dialog : ''}
-
-
- ${metricConfig}
+
+
`
}
-
- openSettings () {
- this._dialog = html``
-
- /* eslint-disable-next-line no-unused-vars -- Standard construct?? */
- function dialogClosed (event) {
- this._dialog = undefined
- }
- }
}
diff --git a/app/client/index.js b/app/client/index.js
index 4bfc50271d..26a9fa2c64 100644
--- a/app/client/index.js
+++ b/app/client/index.js
@@ -30,6 +30,12 @@ export class App extends LitElement {
config[key] = JSON.parse(localStorage.getItem(key)) ?? config[key]
})
+ // migrate: remove deprecated 'actions' metric from saved dashboardMetrics
+ if (config.dashboardMetrics.includes('actions')) {
+ config.dashboardMetrics = config.dashboardMetrics.filter(m => m !== 'actions')
+ localStorage.setItem('dashboardMetrics', JSON.stringify(config.dashboardMetrics))
+ }
+
// 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
diff --git a/app/client/store/appState.js b/app/client/store/appState.js
index 39ed76d3a0..1d06fa67d3 100644
--- a/app/client/store/appState.js
+++ b/app/client/store/appState.js
@@ -47,7 +47,7 @@ export const APP_STATE = {
// true if remote device shutdown is enabled
shutdownEnabled: false,
guiConfigs: {
- dashboardMetrics: ['distance', 'timer', 'pace', 'power', 'stkRate', 'totalStk', 'calories', 'actions'],
+ dashboardMetrics: ['distance', 'timer', 'pace', 'power', 'stkRate', 'totalStk', 'calories'],
showIcons: true,
maxNumberOfTiles: 8
}
diff --git a/app/client/store/dashboardMetrics.js b/app/client/store/dashboardMetrics.js
index de019120ec..bfae322fa7 100644
--- a/app/client/store/dashboardMetrics.js
+++ b/app/client/store/dashboardMetrics.js
@@ -7,7 +7,6 @@ import { html } from 'lit'
import { formatDistance, formatNumber, secondsToTimeString } from '../lib/helper'
import { iconBolt, iconClock, iconAlarmclock, iconFire, iconHeartbeat, iconPaddle, iconRoute, iconStopwatch, rowerIcon } from '../lib/icons'
import '../components/DashboardForceCurve.js'
-import '../components/DashboardActions.js'
import '../components/DashboardMetric.js'
import '../components/BatteryIcon.js'
@@ -94,9 +93,7 @@ export const DASHBOARD_METRICS = {
recoveryDuration: { displayName: 'Recovery duration', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.recoveryDuration, 2), 'sec', config?.guiConfigs?.showIcons ? 'Recovery' : '') },
- forceCurve: { displayName: 'Force curve', size: 2, template: (metrics) => html`` },
-
- actions: { displayName: 'Actions', size: 1, template: (_, config) => html`` }
+ forceCurve: { displayName: 'Force curve', size: 2, template: (metrics) => html`` }
}
/**