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.renderOptionalButtons()} - - -
-
- -
${this.blePeripheralMode()}
-
- ${this._dialog ? this._dialog : ''} - ` +
+ + + ${this.renderOptionalButtons()} +
+ +
+ + +
+ +
${this.blePeripheralMode()}
+
+
+ + ${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' }) + } + }}> ${iconUpload}
Upload training?

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' }) + } + }}> ${iconPoweroff}
Shutdown Open Rowing Monitor?

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} + +
${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`` } } /**