From 70a86d091497df79aa2e39db57fb56f2d7f99790 Mon Sep 17 00:00:00 2001 From: Lars Berning <151194+laberning@users.noreply.github.com> Date: Sat, 27 Nov 2021 17:17:58 +0100 Subject: [PATCH] initial attempt to refactor frontend with lit --- app/client/.eslintrc.json | 21 ++ app/client/components/App.js | 44 ++++ app/client/components/AppElement.js | 16 ++ app/client/components/DashboardActions.js | 59 +++++ app/client/components/DashboardMetric.js | 55 +++++ app/client/components/PerformanceDashboard.js | 40 +++ app/client/index.html | 130 +--------- app/client/index.js | 5 +- app/client/lib/icons.js | 26 ++ app/client/{app.js => lib/network.js} | 71 ++---- app/client/style.css | 20 +- package-lock.json | 232 ++++++++++++++++++ package.json | 6 +- snowpack.config.js | 15 +- 14 files changed, 544 insertions(+), 196 deletions(-) create mode 100644 app/client/.eslintrc.json create mode 100644 app/client/components/App.js create mode 100644 app/client/components/AppElement.js create mode 100644 app/client/components/DashboardActions.js create mode 100644 app/client/components/DashboardMetric.js create mode 100644 app/client/components/PerformanceDashboard.js create mode 100644 app/client/lib/icons.js rename app/client/{app.js => lib/network.js} (65%) diff --git a/app/client/.eslintrc.json b/app/client/.eslintrc.json new file mode 100644 index 0000000..7235261 --- /dev/null +++ b/app/client/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "env": { + "browser": true, + "node": false, + "es2021": true + }, + "extends": [ + "standard", + "plugin:wc/recommended", + "plugin:lit/recommended" + ], + "parser": "@babel/eslint-parser", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "ignorePatterns": ["**/*.min.js"], + "rules": { + "camelcase": 0 + } +} diff --git a/app/client/components/App.js b/app/client/components/App.js new file mode 100644 index 0000000..1d68a21 --- /dev/null +++ b/app/client/components/App.js @@ -0,0 +1,44 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Main Component of the Open Rowing Monitor App +*/ + +import { AppElement, html, css } from './AppElement' +import { customElement } from 'lit/decorators.js' +import { createApp } from '../lib/network.js' +import './PerformanceDashboard' + +@customElement('web-app') +export class App extends AppElement { + static get styles () { + return css` + ` + } + + constructor () { + super() + this.app = createApp() + window.app = this.app + this.app.setMetricsCallback(metrics => this.metricsUpdated(metrics)) + } + + static properties = { + metrics: { state: true } + }; + + render () { + return html` + + ` + } + + metricsUpdated (metrics) { + this.metrics = Object.assign({}, metrics) + } + + createRenderRoot () { + return this + } +} diff --git a/app/client/components/AppElement.js b/app/client/components/AppElement.js new file mode 100644 index 0000000..c09c638 --- /dev/null +++ b/app/client/components/AppElement.js @@ -0,0 +1,16 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Base Component for all other App Components +*/ + +import { LitElement } from 'lit' +export * from 'lit' + +export class AppElement extends LitElement { + // todo: should use shadow root once the global style file is dissolved into the components + createRenderRoot () { + return this + } +} diff --git a/app/client/components/DashboardActions.js b/app/client/components/DashboardActions.js new file mode 100644 index 0000000..be1cfc3 --- /dev/null +++ b/app/client/components/DashboardActions.js @@ -0,0 +1,59 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Component that renders the action buttons of the dashboard +*/ + +import { AppElement, html, css } from './AppElement' +import { customElement, property } from 'lit/decorators.js' +import { icon_undo, icon_expand, icon_compress, icon_poweroff, icon_bluetooth } from '../lib/icons' + +@customElement('dashboard-actions') +export class DashboardActions extends AppElement { + static get styles () { + return css` + ` + } + + @property({ type: String }) + peripheralMode = '' + + render () { + return html` + + + + + +
${this.peripheralMode}
+ ` + } + + toggleFullscreen () { + const fullscreenElement = document.getElementsByTagName('web-app')[0] + if (!document.fullscreenElement) { + fullscreenElement.requestFullscreen({ navigationUI: 'hide' }) + } else { + if (document.exitFullscreen) { + document.exitFullscreen() + } + } + } + + close () { + window.close() + } + + // todo: should use events instead of communicating via a global app object + reset () { + window.app.reset() + } + + switchPeripheralMode () { + window.app.switchPeripheralMode() + } +} diff --git a/app/client/components/DashboardMetric.js b/app/client/components/DashboardMetric.js new file mode 100644 index 0000000..b735b82 --- /dev/null +++ b/app/client/components/DashboardMetric.js @@ -0,0 +1,55 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Component that renders a metric of the dashboard +*/ + +import { AppElement, html, svg, css } from './AppElement' +import { customElement, property } from 'lit/decorators.js' + +@customElement('dashboard-metric') +export class DashboardMetric extends AppElement { + static get styles () { + return css` + ` + } + + @property({ type: Object }) + icon + + @property({ type: String }) + unit = '' + + @property({ type: String }) + value = '' + + @property({ type: String }) + batteryLevel = '' + + render () { + return html` +
${this.icon}
+
+ ${this.value} + ${this.unit} +
+ ${this.batteryLevel + ? html`
${this.batteryIcon}
` + : ''} + ` + } + + get batteryIcon () { + // 416 is the max width value of the battery bar in the SVG graphic + const batteryWidth = this.batteryLevel * 416 / 100 + + return svg` + + + ` + } +} diff --git a/app/client/components/PerformanceDashboard.js b/app/client/components/PerformanceDashboard.js new file mode 100644 index 0000000..0e82bae --- /dev/null +++ b/app/client/components/PerformanceDashboard.js @@ -0,0 +1,40 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Component that renders the dashboard +*/ + +import { AppElement, html, css } from './AppElement' +import { customElement, property } from 'lit/decorators.js' +import './DashboardMetric' +import './DashboardActions' +import { icon_route, icon_stopwatch, icon_bolt, icon_paddle, icon_heartbeat, icon_fire, icon_clock } from '../lib/icons' + +@customElement('performance-dashboard') +export class PerformanceDashboard extends AppElement { + static get styles () { + return css` + ` + } + + @property({ type: Object }) + metrics + + render () { + return html` + + + + + ${this.metrics?.heartrate?.value + ? html`` + : html``} + + + + ` + } +} diff --git a/app/client/index.html b/app/client/index.html index 3700845..be8347d 100644 --- a/app/client/index.html +++ b/app/client/index.html @@ -15,132 +15,8 @@ Open Rowing Monitor - -
-
-
- -
-
- - m -
-
-
-
- -
-
- - /500m -
-
-
-
- -
-
- - watt -
-
-
-
- -
-
- - /min -
-
- -
-
-
- -
-
- - total -
-
- -
-
- -
-
- - bpm -
-
- -
-
-
- -
-
- -
-
- - kcal -
-
-
-
- - -
-
- - -
-
-
-
- - - - -
-
-
-
+ + + diff --git a/app/client/index.js b/app/client/index.js index e741970..60cb120 100644 --- a/app/client/index.js +++ b/app/client/index.js @@ -2,8 +2,7 @@ /* Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - Init file for the web frontend + Initialization file for the web application */ -import { createApp } from './app.js' -window.app = createApp() +import './components/App' diff --git a/app/client/lib/icons.js b/app/client/lib/icons.js new file mode 100644 index 0000000..a5a22c8 --- /dev/null +++ b/app/client/lib/icons.js @@ -0,0 +1,26 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + SVG Icons that are used by the Application +*/ + +import { svg } from 'lit' + +export const icon_route = svg`` +export const icon_stopwatch = svg`` +export const icon_bolt = svg`` +export const icon_paddle = svg` + + ` +export const icon_heartbeat = svg`` +export const icon_fire = svg`` +export const icon_clock = svg`` +export const icon_undo = svg`` +export const icon_poweroff = svg`` +export const icon_expand = svg`` +export const icon_compress = svg`` +export const icon_bluetooth = svg`` diff --git a/app/client/app.js b/app/client/lib/network.js similarity index 65% rename from app/client/app.js rename to app/client/lib/network.js index bdbc33a..a13471e 100644 --- a/app/client/app.js +++ b/app/client/lib/network.js @@ -26,15 +26,18 @@ export function createApp () { power: (value) => Math.round(value), strokesPerMinute: (value) => Math.round(value) } - const standalone = (window.location.hash === '#:standalone:') - + // const standalone = (window.location.hash === '#:standalone:') + let metricsCallback + const metrics = { + } + /* if (standalone) { document.getElementById('close-button').style.display = 'inline-block' document.getElementById('fullscreen-button').style.display = 'none' } else { document.getElementById('fullscreen-button').style.display = 'inline-block' document.getElementById('close-button').style.display = 'none' - } + } */ let socket @@ -67,23 +70,6 @@ export function createApp () { try { const data = JSON.parse(event.data) - // show heart rate, if present - if (data.heartrate !== undefined) { - if (data.heartrate !== 0) { - document.getElementById('heartrate-container').style.display = 'inline-block' - document.getElementById('strokes-total-container').style.display = 'none' - if (data.heartrateBatteryLevel !== undefined) { - document.getElementById('heartrate-battery-container').style.display = 'inline-block' - setHeartrateMonitorBatteryLevel(data.heartrateBatteryLevel) - } else { - document.getElementById('heartrate-battery-container').style.display = 'none' - } - } else { - document.getElementById('strokes-total-container').style.display = 'inline-block' - document.getElementById('heartrate-container').style.display = 'none' - } - } - let activeFields = fields // if we are in reset state only update heart rate and peripheral mode if (data.strokesTotal === 0) { @@ -94,13 +80,21 @@ export function createApp () { if (activeFields.includes(key)) { const valueFormatted = fieldFormatter[key] ? fieldFormatter[key](value) : value if (valueFormatted.value !== undefined && valueFormatted.unit !== undefined) { - if (document.getElementById(key)) document.getElementById(key).innerHTML = valueFormatted.value - if (document.getElementById(`${key}Unit`)) document.getElementById(`${key}Unit`).innerHTML = valueFormatted.unit + metrics[key] = { + value: valueFormatted.value, + unit: valueFormatted.unit + } } else { - if (document.getElementById(key)) document.getElementById(key).innerHTML = valueFormatted + metrics[key] = { + value: valueFormatted + } } } } + + if (metricsCallback) { + metricsCallback(metrics) + } } catch (err) { console.log(err) } @@ -125,25 +119,13 @@ export function createApp () { function resetFields () { for (const key of fields.filter((elem) => elem !== 'peripheralMode')) { - if (document.getElementById(key)) document.getElementById(key).innerHTML = '--' - } - } - - function toggleFullscreen () { - const fullscreenElement = document.getElementById('dashboard') - if (!document.fullscreenElement) { - fullscreenElement.requestFullscreen({ navigationUI: 'hide' }) - } else { - if (document.exitFullscreen) { - document.exitFullscreen() + if (metrics[key])metrics[key].value = '--' + if (metricsCallback) { + metricsCallback(metrics) } } } - function close () { - window.close() - } - function reset () { resetFields() if (socket)socket.send(JSON.stringify({ command: 'reset' })) @@ -153,17 +135,14 @@ export function createApp () { if (socket)socket.send(JSON.stringify({ command: 'switchPeripheralMode' })) } - function setHeartrateMonitorBatteryLevel (batteryLevel) { - if (document.getElementById('battery-level') !== null) { - // 416 is the max width value of the battery bar in the SVG graphic - document.getElementById('battery-level').setAttribute('width', `${batteryLevel * 416 / 100}px`) - } + function setMetricsCallback (callback) { + metricsCallback = callback } return { - toggleFullscreen, reset, - close, - switchPeripheralMode + switchPeripheralMode, + metrics, + setMetricsCallback } } diff --git a/app/client/style.css b/app/client/style.css index fd3a623..836a266 100644 --- a/app/client/style.css +++ b/app/client/style.css @@ -11,7 +11,7 @@ body { overscroll-behavior: contain; } -.grid { +performance-dashboard { display: grid; height: calc(100vh - 2vw); padding: 1vw; @@ -20,18 +20,19 @@ body { grid-template-rows: repeat(2, minmax(0, 1fr)); } -.col { +dashboard-metric, dashboard-actions { background: #002b57; text-align: center; + position: relative; padding: 0.5em 0.2em 0 0.2em; } -.col.actions { +dashboard-actions { padding: 0.5em 0 0 0; } @media (orientation: portrait) { - .grid { + performance-dashboard { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(4, minmax(0, 1fr)); } @@ -70,7 +71,6 @@ div.label, div.content { right: 0.2em; bottom: 0; position: absolute; - display: none; } .metric-value { font-size: 150%; @@ -93,13 +93,3 @@ button { display: inline-block; width: 3.5em; } - -#close-button, #heartrate-container { - display: none; -} - -#heartrate-container, #strokes-total-container { - width: 100%; - height: 100%; - position: relative; -} diff --git a/package-lock.json b/package-lock.json index c2f234d..367e62e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@abandonware/noble": "^1.9.2-14", "ant-plus": "^0.1.24", "finalhandler": "^1.1.2", + "lit": "^2.0.2", "loglevel": "^1.8.0", "nosleep.js": "^0.12.0", "onoff": "^6.0.3", @@ -21,14 +22,17 @@ "xml2js": "^0.4.23" }, "devDependencies": { + "@babel/eslint-parser": "^7.16.3", "@babel/plugin-proposal-class-properties": "^7.16.0", "@babel/plugin-proposal-decorators": "^7.16.4", "@snowpack/plugin-babel": "^2.1.7", "eslint": "^7.32.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.25.3", + "eslint-plugin-lit": "^1.6.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-wc": "^1.3.2", "http2-proxy": "^5.0.53", "markdownlint-cli": "^0.30.0", "npm-run-all": "^4.1.5", @@ -202,6 +206,33 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/eslint-parser": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.16.3.tgz", + "integrity": "sha512-iB4ElZT0jAt7PKVaeVulOECdGe6UnmA/O0P9jlF5g5GBOwDVbna8AXhHRu4s27xQf6OkveyA8iTDv1jHdDejgQ==", + "dev": true, + "dependencies": { + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", @@ -734,6 +765,11 @@ "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "dev": true }, + "node_modules/@lit/reactive-element": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.2.tgz", + "integrity": "sha512-oz3d3MKjQ2tXynQgyaQaMpGTDNyNDeBdo6dXf1AbjTwhA1IRINHmA7kSaVYv9ttKweNkEoNqp9DqteDdgWzPEg==" + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.6.tgz", @@ -1138,6 +1174,11 @@ "@types/node": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2791,6 +2832,23 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/eslint-plugin-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.6.1.tgz", + "integrity": "sha512-BpPoWVhf8dQ/Sz5Pi9NlqbGoH5BcMcVyXhi2XTx2XGMAO9U2lS+GTSsqJjI5hL3OuxCicNiUEWXazAwi9cAGxQ==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "^1.2.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, "node_modules/eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -2860,6 +2918,19 @@ "eslint": "^7.0.0" } }, + "node_modules/eslint-plugin-wc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-1.3.2.tgz", + "integrity": "sha512-/Tt3kIXBp1jh06xYtRqPwAvpNxVVk9YtbcFCKEgLa5l3GY+urZyn376pISaaZxkm9HVD3AIPOF5i9/uFwyF0Zw==", + "dev": true, + "dependencies": { + "is-valid-element-name": "^1.0.0", + "js-levenshtein-esm": "^1.2.0" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -4127,6 +4198,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -4209,6 +4286,15 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "devOptional": true }, + "node_modules/is-valid-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", + "integrity": "sha1-Ju8/12zfHxItEFQG4y01sN4AWYE=", + "dev": true, + "dependencies": { + "is-potential-custom-element-name": "^1.0.0" + } + }, "node_modules/is-valid-identifier": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-valid-identifier/-/is-valid-identifier-2.0.2.tgz", @@ -4272,6 +4358,12 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "devOptional": true }, + "node_modules/js-levenshtein-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", + "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4469,6 +4561,33 @@ "uc.micro": "^1.0.1" } }, + "node_modules/lit": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.0.2.tgz", + "integrity": "sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==", + "dependencies": { + "@lit/reactive-element": "^1.0.0", + "lit-element": "^3.0.0", + "lit-html": "^2.0.0" + } + }, + "node_modules/lit-element": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.2.tgz", + "integrity": "sha512-9vTJ47D2DSE4Jwhle7aMzEwO2ZcOPRikqfT3CVG7Qol2c9/I4KZwinZNW5Xv8hNm+G/enSSfIwqQhIXi6ioAUg==", + "dependencies": { + "@lit/reactive-element": "^1.0.0", + "lit-html": "^2.0.0" + } + }, + "node_modules/lit-html": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.2.tgz", + "integrity": "sha512-dON7Zg8btb14/fWohQLQBdSgkoiQA4mIUy87evmyJHtxRq7zS6LlC32bT5EPWiof5PUQaDpF45v2OlrxHA5Clg==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -6401,6 +6520,15 @@ "node": ">=0.10.0" } }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -8222,6 +8350,25 @@ } } }, + "@babel/eslint-parser": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.16.3.tgz", + "integrity": "sha512-iB4ElZT0jAt7PKVaeVulOECdGe6UnmA/O0P9jlF5g5GBOwDVbna8AXhHRu4s27xQf6OkveyA8iTDv1jHdDejgQ==", + "dev": true, + "requires": { + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "@babel/generator": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", @@ -8632,6 +8779,11 @@ "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "dev": true }, + "@lit/reactive-element": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.2.tgz", + "integrity": "sha512-oz3d3MKjQ2tXynQgyaQaMpGTDNyNDeBdo6dXf1AbjTwhA1IRINHmA7kSaVYv9ttKweNkEoNqp9DqteDdgWzPEg==" + }, "@mapbox/node-pre-gyp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.6.tgz", @@ -8978,6 +9130,11 @@ "@types/node": "*" } }, + "@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -10256,6 +10413,17 @@ } } }, + "eslint-plugin-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.6.1.tgz", + "integrity": "sha512-BpPoWVhf8dQ/Sz5Pi9NlqbGoH5BcMcVyXhi2XTx2XGMAO9U2lS+GTSsqJjI5hL3OuxCicNiUEWXazAwi9cAGxQ==", + "dev": true, + "requires": { + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "^1.2.0" + } + }, "eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -10301,6 +10469,16 @@ "dev": true, "requires": {} }, + "eslint-plugin-wc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-1.3.2.tgz", + "integrity": "sha512-/Tt3kIXBp1jh06xYtRqPwAvpNxVVk9YtbcFCKEgLa5l3GY+urZyn376pISaaZxkm9HVD3AIPOF5i9/uFwyF0Zw==", + "dev": true, + "requires": { + "is-valid-element-name": "^1.0.0", + "js-levenshtein-esm": "^1.2.0" + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -11250,6 +11428,12 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -11305,6 +11489,15 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "devOptional": true }, + "is-valid-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", + "integrity": "sha1-Ju8/12zfHxItEFQG4y01sN4AWYE=", + "dev": true, + "requires": { + "is-potential-custom-element-name": "^1.0.0" + } + }, "is-valid-identifier": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-valid-identifier/-/is-valid-identifier-2.0.2.tgz", @@ -11356,6 +11549,12 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "devOptional": true }, + "js-levenshtein-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", + "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11523,6 +11722,33 @@ "uc.micro": "^1.0.1" } }, + "lit": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.0.2.tgz", + "integrity": "sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==", + "requires": { + "@lit/reactive-element": "^1.0.0", + "lit-element": "^3.0.0", + "lit-html": "^2.0.0" + } + }, + "lit-element": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.2.tgz", + "integrity": "sha512-9vTJ47D2DSE4Jwhle7aMzEwO2ZcOPRikqfT3CVG7Qol2c9/I4KZwinZNW5Xv8hNm+G/enSSfIwqQhIXi6ioAUg==", + "requires": { + "@lit/reactive-element": "^1.0.0", + "lit-html": "^2.0.0" + } + }, + "lit-html": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.2.tgz", + "integrity": "sha512-dON7Zg8btb14/fWohQLQBdSgkoiQA4mIUy87evmyJHtxRq7zS6LlC32bT5EPWiof5PUQaDpF45v2OlrxHA5Clg==", + "requires": { + "@types/trusted-types": "^2.0.2" + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -13017,6 +13243,12 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", diff --git a/package.json b/package.json index 88eb42c..2ebd740 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "scripts": { "lint": "eslint ./app ./config && markdownlint '**/*.md' --ignore node_modules", "start": "node app/server.js", - "dev": "npm-run-all --parallel start build:watch", + "dev": "npm-run-all --parallel start dev:frontend", "dev:frontend": "snowpack dev", "build": "snowpack build", "build:watch": "snowpack build --watch", @@ -30,6 +30,7 @@ "@abandonware/noble": "^1.9.2-14", "ant-plus": "^0.1.24", "finalhandler": "^1.1.2", + "lit": "^2.0.2", "loglevel": "^1.8.0", "nosleep.js": "^0.12.0", "onoff": "^6.0.3", @@ -38,14 +39,17 @@ "xml2js": "^0.4.23" }, "devDependencies": { + "@babel/eslint-parser": "^7.16.3", "@babel/plugin-proposal-class-properties": "^7.16.0", "@babel/plugin-proposal-decorators": "^7.16.4", "@snowpack/plugin-babel": "^2.1.7", "eslint": "^7.32.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.25.3", + "eslint-plugin-lit": "^1.6.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-wc": "^1.3.2", "http2-proxy": "^5.0.53", "markdownlint-cli": "^0.30.0", "npm-run-all": "^4.1.5", diff --git a/snowpack.config.js b/snowpack.config.js index caaa340..6b334cc 100644 --- a/snowpack.config.js +++ b/snowpack.config.js @@ -1,18 +1,25 @@ // Snowpack Configuration File // See all supported options: https://www.snowpack.dev/reference/configuration import proxy from 'http2-proxy' +import { nodeResolve } from '@rollup/plugin-node-resolve' -// todo: might add a proxy for websockets here so we can use snowpack dev server with HMR export default { mount: { // the web frontend is located in this directory './app/client': { url: '/' } - // mount "public" to the root URL path ("/*") and serve files with zero transformations: - // './public': { url: '/', static: true, resolve: false } }, plugins: ['@snowpack/plugin-babel'], packageOptions: { - /* ... */ + rollup: { + plugins: [ + // todo: related to the lit documentation this should enable development mode + // unfortunately this currently does not seem to work + nodeResolve({ + exportConditions: ['development'], + dedupe: true + }) + ] + } }, devOptions: { open: 'none',