diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..94218355ff --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile +FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9 + +ENV \ + DEBIAN_FRONTEND=noninteractive \ + DEVCONTAINER=true \ + PATH=$PATH:./node_modules/.bin + +# Install nvm +COPY .nvmrc /tmp/.nvmrc +RUN \ + su vscode -c \ + "source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1" \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..78d39cd415 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +{ + "name": "Home Assistant Frontend", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "appPort": 8123, + "context": "..", + "postCreateCommand": "script/bootstrap", + "extensions": [ + "github.vscode-pull-request-github", + "dbaeumer.vscode-eslint", + "ms-vscode.vscode-typescript-tslint-plugin", + "esbenp.prettier-vscode", + "bierner.lit-html", + "runem.lit-plugin", + "ms-python.vscode-pylance" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "files.eol": "\n", + "editor.tabSize": 2, + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "files.trimTrailingWhitespace": true + } +} diff --git a/.gitignore b/.gitignore index 43b965f476..317cc85ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ dist # vscode .vscode/* !.vscode/extensions.json +!.vscode/launch.json +!.vscode/tasks.json # Cast dev settings src/cast/dev_const.ts diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index 64d3d21d2c..0000000000 --- a/.hound.yml +++ /dev/null @@ -1,6 +0,0 @@ -jshint: - enabled: false - -eslint: - enabled: true - config_file: .eslintrc-hound.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..e3be0de9c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,44 @@ +{ + // https://github.com/microsoft/vscode-js-debug/blob/master/OPTIONS.md + "configurations": [ + { + "name": "Debug Frontend", + "request": "launch", + "type": "pwa-chrome", + "url": "http://localhost:8123/", + "webRoot": "${workspaceFolder}/hass_frontend", + "disableNetworkCache": true, + "preLaunchTask": "Develop Frontend", + "outFiles": [ + "${workspaceFolder}/hass_frontend/frontend_latest/*.js" + ] + }, + { + "name": "Debug Gallery", + "request": "launch", + "type": "pwa-chrome", + "url": "http://localhost:8100/", + "webRoot": "${workspaceFolder}/gallery/dist", + "disableNetworkCache": true, + "preLaunchTask": "Develop Gallery" + }, + { + "name": "Debug Demo", + "request": "launch", + "type": "pwa-chrome", + "url": "http://localhost:8090/", + "webRoot": "${workspaceFolder}/demo/dist", + "disableNetworkCache": true, + "preLaunchTask": "Develop Demo" + }, + { + "name": "Debug Cast", + "request": "launch", + "type": "pwa-chrome", + "url": "http://localhost:8080/", + "webRoot": "${workspaceFolder}/cast/dist", + "disableNetworkCache": true, + "preLaunchTask": "Develop Cast" + }, + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..c6868baeb0 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,208 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Develop Frontend", + "type": "gulp", + "task": "develop-app", + // Sync changes here to other tasks until issue resolved + // https://github.com/Microsoft/vscode/issues/61497 + "problemMatcher": { + "owner": "ha-build", + "source": "ha-build", + "fileLocation": "absolute", + "severity": "error", + "pattern": [ + { + "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", + "severity": 1, + "file": 2, + "message": 3, + "line": 4, + "column": 5 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "Changes detected. Starting compilation", + "endsPattern": "Build done @" + } + }, + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + }, + "runOptions": { + "instanceLimit": 1 + } + }, + { + "label": "Develop Supervisor panel", + "type": "gulp", + "task": "develop-hassio", + "problemMatcher": { + "owner": "ha-build", + "source": "ha-build", + "fileLocation": "absolute", + "severity": "error", + "pattern": [ + { + "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", + "severity": 1, + "file": 2, + "message": 3, + "line": 4, + "column": 5 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "Changes detected. Starting compilation", + "endsPattern": "Build done @" + } + }, + "isBackground": true, + "group": "build", + "runOptions": { + "instanceLimit": 1 + } + }, + { + "label": "Develop Gallery", + "type": "gulp", + "task": "develop-gallery", + "problemMatcher": { + "owner": "ha-build", + "source": "ha-build", + "fileLocation": "absolute", + "severity": "error", + "pattern": [ + { + "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", + "severity": 1, + "file": 2, + "message": 3, + "line": 4, + "column": 5 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "Changes detected. Starting compilation", + "endsPattern": "Build done @" + } + }, + + "isBackground": true, + "group": "build", + "runOptions": { + "instanceLimit": 1 + } + }, + { + "label": "Develop Demo", + "type": "gulp", + "task": "develop-demo", + "problemMatcher": { + "owner": "ha-build", + "source": "ha-build", + "fileLocation": "absolute", + "severity": "error", + "pattern": [ + { + "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", + "severity": 1, + "file": 2, + "message": 3, + "line": 4, + "column": 5 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "Changes detected. Starting compilation", + "endsPattern": "Build done @" + } + }, + + "isBackground": true, + "group": "build", + "runOptions": { + "instanceLimit": 1 + } + }, + { + "label": "Develop Cast", + "type": "gulp", + "task": "develop-cast", + "problemMatcher": { + "owner": "ha-build", + "source": "ha-build", + "fileLocation": "absolute", + "severity": "error", + "pattern": [ + { + "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", + "severity": 1, + "file": 2, + "message": 3, + "line": 4, + "column": 5 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "Changes detected. Starting compilation", + "endsPattern": "Build done @" + } + }, + + "isBackground": true, + "group": "build", + "runOptions": { + "instanceLimit": 1 + } + }, + { + "label": "Run HA Core in devcontainer", + "type": "shell", + "command": "script/core", + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "runOptions": { + "instanceLimit": 1 + } + }, + { + "label": "Run HA Core for Supervisor in devcontainer", + "type": "shell", + "command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core", + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "runOptions": { + "instanceLimit": 1 + } + } + ], + "inputs": [ + { + "id": "supervisorHost", + "type": "promptString", + "description": "The IP of the Supervisor host running the Remote API proxy add-on" + }, + { + "id": "supervisorToken", + "type": "promptString", + "description": "The token for the Remote API proxy add-on" + } + ] +} diff --git a/build-scripts/README.md b/build-scripts/README.md new file mode 100644 index 0000000000..0a6963f7e9 --- /dev/null +++ b/build-scripts/README.md @@ -0,0 +1,39 @@ +# Bundling Home Assistant Frontend + +The Home Assistant build pipeline contains various steps to prepare a build. + +- Generating icon files to be included +- Generating translation files to be included +- Converting TypeScript, CSS and JSON files to JavaScript +- Bundling +- Minifying the files +- Generating the HTML entrypoint files +- Generating the service worker +- Compressing the files + +## Converting files + +Currently in Home Assistant we use a bundler to convert TypeScript, CSS and JSON files to JavaScript files that the browser understands. + +We currently rely on Webpack but also have experimental Rollup support. Both of these programs bundle the converted files in both production and development. + +For development, bundling is optional. We just want to get the right files in the browser. + +Responsibilities of the converter during development: + +- Convert TypeScript to JavaScript +- Convert CSS to JavaScript that sets the content as the default export +- Convert JSON to JavaScript that sets the content as the default export +- Make sure import, dynamic import and web worker references work + - Add extensions where missing + - Resolve absolute package imports +- Filter out specific imports/packages +- Replace constants with values + +In production, the following responsibilities are added: + +- Minify HTML +- Bundle multiple imports so that the browser can fetch less files +- Generate a second version that is ES5 compatible + +Configuration for all these steps are specified in [bundle.js](bundle.js). diff --git a/build-scripts/bundle.js b/build-scripts/bundle.js index 41f60aa66d..d21b2aea1a 100644 --- a/build-scripts/bundle.js +++ b/build-scripts/bundle.js @@ -44,7 +44,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ }); module.exports.terserOptions = (latestBuild) => ({ - safari10: true, + safari10: !latestBuild, ecma: latestBuild ? undefined : 5, output: { comments: false }, }); @@ -117,7 +117,7 @@ BundleConfig { */ module.exports.config = { - app({ isProdBuild, latestBuild, isStatsBuild }) { + app({ isProdBuild, latestBuild, isStatsBuild, isWDS }) { return { entry: { service_worker: "./src/entrypoints/service_worker.ts", @@ -132,6 +132,7 @@ module.exports.config = { isProdBuild, latestBuild, isStatsBuild, + isWDS, }; }, diff --git a/build-scripts/env.js b/build-scripts/env.js index 8980d3bf92..8b2dea2eb4 100644 --- a/build-scripts/env.js +++ b/build-scripts/env.js @@ -6,6 +6,9 @@ module.exports = { useRollup() { return process.env.ROLLUP === "1"; }, + useWDS() { + return process.env.WDS === "1"; + }, isProdBuild() { return ( process.env.NODE_ENV === "production" || module.exports.isStatsBuild() diff --git a/build-scripts/gulp/app.js b/build-scripts/gulp/app.js index 9429260617..39d98b1b75 100644 --- a/build-scripts/gulp/app.js +++ b/build-scripts/gulp/app.js @@ -12,6 +12,7 @@ require("./webpack.js"); require("./service-worker.js"); require("./entry-html.js"); require("./rollup.js"); +require("./wds.js"); gulp.task( "develop-app", @@ -28,7 +29,11 @@ gulp.task( "build-translations" ), "copy-static-app", - env.useRollup() ? "rollup-watch-app" : "webpack-watch-app" + env.useWDS() + ? "wds-watch-app" + : env.useRollup() + ? "rollup-watch-app" + : "webpack-watch-app" ) ); diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index e95f2f6091..eeb1b57ecc 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -19,6 +19,7 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { return compiled({ ...data, useRollup: env.useRollup(), + useWDS: env.useWDS(), renderTemplate, }); }; @@ -90,10 +91,23 @@ gulp.task("gen-pages-prod", (done) => { }); gulp.task("gen-index-app-dev", (done) => { + let latestAppJS, latestCoreJS, latestCustomPanelJS; + + if (env.useWDS()) { + latestAppJS = "http://localhost:8000/src/entrypoints/app.ts"; + latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts"; + latestCustomPanelJS = + "http://localhost:8000/src/entrypoints/custom-panel.ts"; + } else { + latestAppJS = "/frontend_latest/app.js"; + latestCoreJS = "/frontend_latest/core.js"; + latestCustomPanelJS = "/frontend_latest/custom-panel.js"; + } + const content = renderTemplate("index", { - latestAppJS: "/frontend_latest/app.js", - latestCoreJS: "/frontend_latest/core.js", - latestCustomPanelJS: "/frontend_latest/custom-panel.js", + latestAppJS, + latestCoreJS, + latestCustomPanelJS, es5AppJS: "/frontend_es5/app.js", es5CoreJS: "/frontend_es5/core.js", diff --git a/build-scripts/gulp/translations.js b/build-scripts/gulp/translations.js index e357d38edf..049f1c3dab 100755 --- a/build-scripts/gulp/translations.js +++ b/build-scripts/gulp/translations.js @@ -33,21 +33,10 @@ String.prototype.rsplit = function (sep, maxsplit) { : split; }; -// Panel translations which should be split from the core translations. These -// should mirror the fragment definitions in polymer.json, so that we load -// additional resources at equivalent points. -const TRANSLATION_FRAGMENTS = [ - "config", - "history", - "logbook", - "mailbox", - "profile", - "shopping-list", - "page-authorize", - "page-demo", - "page-onboarding", - "developer-tools", -]; +// Panel translations which should be split from the core translations. +const TRANSLATION_FRAGMENTS = Object.keys( + require("../../src/translations/en.json").ui.panel +); function recursiveFlatten(prefix, data) { let output = {}; diff --git a/build-scripts/gulp/wds.js b/build-scripts/gulp/wds.js new file mode 100644 index 0000000000..d70afd1a9a --- /dev/null +++ b/build-scripts/gulp/wds.js @@ -0,0 +1,11 @@ +// Tasks to run Rollup +const gulp = require("gulp"); +const { startDevServer } = require("@web/dev-server"); + +gulp.task("wds-watch-app", () => { + startDevServer({ + config: { + watch: true, + }, + }); +}); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index 38fba59640..c5790f0b69 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -18,6 +18,14 @@ const bothBuilds = (createConfigFunc, params) => [ createConfigFunc({ ...params, latestBuild: false }), ]; +/** + * @param {{ + * compiler: import("webpack").Compiler, + * contentBase: string, + * port: number, + * listenHost?: string + * }} + */ const runDevServer = ({ compiler, contentBase, @@ -33,10 +41,13 @@ const runDevServer = ({ throw err; } // Server listening - log("[webpack-dev-server]", `http://localhost:${port}`); + log( + "[webpack-dev-server]", + `Project is running at http://localhost:${port}` + ); }); -const handler = (done) => (err, stats) => { +const doneHandler = (done) => (err, stats) => { if (err) { log.error(err.stack || err); if (err.details) { @@ -45,22 +56,31 @@ const handler = (done) => (err, stats) => { return; } - log(`Build done @ ${new Date().toLocaleTimeString()}`); - if (stats.hasErrors() || stats.hasWarnings()) { - log.warn(stats.toString("minimal")); + console.log(stats.toString("minimal")); } + log(`Build done @ ${new Date().toLocaleTimeString()}`); + if (done) { done(); } }; +const prodBuild = (conf) => + new Promise((resolve) => { + webpack( + conf, + // Resolve promise when done. Because we pass a callback, webpack closes itself + doneHandler(resolve) + ); + }); + gulp.task("webpack-watch-app", () => { - // we are not calling done, so this command will run forever + // This command will run forever because we don't close compiler webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch( { ignored: /build-translations/ }, - handler() + doneHandler() ); gulp.watch( path.join(paths.translations_src, "en.json"), @@ -68,15 +88,12 @@ gulp.task("webpack-watch-app", () => { ); }); -gulp.task( - "webpack-prod-app", - () => - new Promise((resolve) => - webpack( - bothBuilds(createAppConfig, { isProdBuild: true }), - handler(resolve) - ) - ) +gulp.task("webpack-prod-app", () => + prodBuild( + bothBuilds(createAppConfig, { + isProdBuild: true, + }) + ) ); gulp.task("webpack-dev-server-demo", () => { @@ -87,17 +104,12 @@ gulp.task("webpack-dev-server-demo", () => { }); }); -gulp.task( - "webpack-prod-demo", - () => - new Promise((resolve) => - webpack( - bothBuilds(createDemoConfig, { - isProdBuild: true, - }), - handler(resolve) - ) - ) +gulp.task("webpack-prod-demo", () => + prodBuild( + bothBuilds(createDemoConfig, { + isProdBuild: true, + }) + ) ); gulp.task("webpack-dev-server-cast", () => { @@ -110,41 +122,30 @@ gulp.task("webpack-dev-server-cast", () => { }); }); -gulp.task( - "webpack-prod-cast", - () => - new Promise((resolve) => - webpack( - bothBuilds(createCastConfig, { - isProdBuild: true, - }), - - handler(resolve) - ) - ) +gulp.task("webpack-prod-cast", () => + prodBuild( + bothBuilds(createCastConfig, { + isProdBuild: true, + }) + ) ); gulp.task("webpack-watch-hassio", () => { - // we are not calling done, so this command will run forever + // This command will run forever because we don't close compiler webpack( createHassioConfig({ isProdBuild: false, latestBuild: true, }) - ).watch({}, handler()); + ).watch({}, doneHandler()); }); -gulp.task( - "webpack-prod-hassio", - () => - new Promise((resolve) => - webpack( - bothBuilds(createHassioConfig, { - isProdBuild: true, - }), - handler(resolve) - ) - ) +gulp.task("webpack-prod-hassio", () => + prodBuild( + bothBuilds(createHassioConfig, { + isProdBuild: true, + }) + ) ); gulp.task("webpack-dev-server-gallery", () => { @@ -156,17 +157,11 @@ gulp.task("webpack-dev-server-gallery", () => { }); }); -gulp.task( - "webpack-prod-gallery", - () => - new Promise((resolve) => - webpack( - createGalleryConfig({ - isProdBuild: true, - latestBuild: true, - }), - - handler(resolve) - ) - ) +gulp.task("webpack-prod-gallery", () => + prodBuild( + createGalleryConfig({ + isProdBuild: true, + latestBuild: true, + }) + ) ); diff --git a/build-scripts/paths.js b/build-scripts/paths.js index ab1b6d3853..ae613b9b10 100644 --- a/build-scripts/paths.js +++ b/build-scripts/paths.js @@ -1,4 +1,4 @@ -var path = require("path"); +const path = require("path"); module.exports = { polymer_dir: path.resolve(__dirname, ".."), diff --git a/build-scripts/rollup-plugins/ignore-plugin.js b/build-scripts/rollup-plugins/ignore-plugin.js index 4bebb69095..5819958092 100644 --- a/build-scripts/rollup-plugins/ignore-plugin.js +++ b/build-scripts/rollup-plugins/ignore-plugin.js @@ -1,5 +1,3 @@ -const path = require("path"); - module.exports = function (userOptions = {}) { // Files need to be absolute paths. // This only works if the file has no exports diff --git a/build-scripts/rollup.js b/build-scripts/rollup.js index 3757f25866..c5f8972854 100644 --- a/build-scripts/rollup.js +++ b/build-scripts/rollup.js @@ -3,7 +3,7 @@ const path = require("path"); const commonjs = require("@rollup/plugin-commonjs"); const resolve = require("@rollup/plugin-node-resolve"); const json = require("@rollup/plugin-json"); -const babel = require("rollup-plugin-babel"); +const babel = require("@rollup/plugin-babel").babel; const replace = require("@rollup/plugin-replace"); const visualizer = require("rollup-plugin-visualizer"); const { string } = require("rollup-plugin-string"); @@ -31,6 +31,7 @@ const createRollupConfig = ({ isStatsBuild, publicPath, dontHash, + isWDS, }) => { return { /** @@ -61,6 +62,7 @@ const createRollupConfig = ({ ...bundle.babelOptions({ latestBuild }), extensions, exclude: bundle.babelExclude(), + babelHelpers: isWDS ? "inline" : "bundled", }), string({ // Import certain extensions as strings @@ -69,19 +71,21 @@ const createRollupConfig = ({ replace( bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) ), - manifest({ - publicPath, - }), - worker(), - dontHashPlugin({ dontHash }), - isProdBuild && terser(bundle.terserOptions(latestBuild)), - isStatsBuild && + !isWDS && + manifest({ + publicPath, + }), + !isWDS && worker(), + !isWDS && dontHashPlugin({ dontHash }), + !isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)), + !isWDS && + isStatsBuild && visualizer({ // https://github.com/btd/rollup-plugin-visualizer#options open: true, sourcemap: true, }), - ], + ].filter(Boolean), }, /** * @type { import("rollup").OutputOptions } @@ -108,12 +112,13 @@ const createRollupConfig = ({ }; }; -const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { +const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => { return createRollupConfig( bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, + isWDS, }) ); }; diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index 65d4287a34..d29521ce35 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -4,6 +4,21 @@ const TerserPlugin = require("terser-webpack-plugin"); const ManifestPlugin = require("webpack-manifest-plugin"); const paths = require("./paths.js"); const bundle = require("./bundle"); +const log = require("fancy-log"); + +class LogStartCompilePlugin { + ignoredFirst = false; + + apply(compiler) { + compiler.hooks.beforeCompile.tap("LogStartCompilePlugin", () => { + if (!this.ignoredFirst) { + this.ignoredFirst = true; + return; + } + log("Changes detected. Starting compilation"); + }); + } +} const createWebpackConfig = ({ entry, @@ -104,7 +119,8 @@ const createWebpackConfig = ({ ), path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js") ), - ], + !isProdBuild && new LogStartCompilePlugin(), + ].filter(Boolean), resolve: { extensions: [".ts", ".js", ".json"], }, diff --git a/demo/src/configs/demo-configs.ts b/demo/src/configs/demo-configs.ts index 7e586a7c71..1c9897c9ad 100644 --- a/demo/src/configs/demo-configs.ts +++ b/demo/src/configs/demo-configs.ts @@ -3,22 +3,10 @@ import { Lovelace } from "../../../src/panels/lovelace/types"; import { DemoConfig } from "./types"; export const demoConfigs: Array<() => Promise> = [ - () => - import(/* webpackChunkName: "arsaboo" */ "./arsaboo").then( - (mod) => mod.demoArsaboo - ), - () => - import(/* webpackChunkName: "teachingbirds" */ "./teachingbirds").then( - (mod) => mod.demoTeachingbirds - ), - () => - import(/* webpackChunkName: "kernehed" */ "./kernehed").then( - (mod) => mod.demoKernehed - ), - () => - import(/* webpackChunkName: "jimpower" */ "./jimpower").then( - (mod) => mod.demoJimpower - ), + () => import("./arsaboo").then((mod) => mod.demoArsaboo), + () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), + () => import("./kernehed").then((mod) => mod.demoKernehed), + () => import("./jimpower").then((mod) => mod.demoJimpower), ]; // eslint-disable-next-line import/no-mutable-exports diff --git a/demo/src/configs/types.ts b/demo/src/configs/types.ts index c0643d65ac..c8b096cd6e 100644 --- a/demo/src/configs/types.ts +++ b/demo/src/configs/types.ts @@ -9,5 +9,5 @@ export interface DemoConfig { authorUrl: string; lovelace: (localize: LocalizeFunc) => LovelaceConfig; entities: (localize: LocalizeFunc) => Entity[]; - theme: () => { [key: string]: string } | null; + theme: () => Record | null; } diff --git a/demo/src/entrypoint.ts b/demo/src/entrypoint.ts index ebaa3172a8..a42ae3ccb1 100644 --- a/demo/src/entrypoint.ts +++ b/demo/src/entrypoint.ts @@ -7,7 +7,5 @@ import "./ha-demo"; /* polyfill for paper-dropdown */ setTimeout(() => { - import( - /* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min" - ); + import("web-animations-js/web-animations-next-lite.min"); }, 1000); diff --git a/gallery/src/components/demo-card.js b/gallery/src/components/demo-card.js index 6a88200a6c..04be1de04c 100644 --- a/gallery/src/components/demo-card.js +++ b/gallery/src/components/demo-card.js @@ -21,15 +21,16 @@ class DemoCard extends PolymerElement { } pre { width: 400px; - margin: 16px; + margin: 0 16px; overflow: auto; + color: var(--primary-text-color); } @media only screen and (max-width: 800px) { .root { flex-direction: column; } pre { - margin-left: 0; + margin: 16px 0; } } diff --git a/gallery/src/components/demo-more-info.js b/gallery/src/components/demo-more-info.js index 0805e77c37..77e8161f6c 100644 --- a/gallery/src/components/demo-more-info.js +++ b/gallery/src/components/demo-more-info.js @@ -26,8 +26,9 @@ class DemoMoreInfo extends PolymerElement { pre { width: 400px; - margin: 16px; + margin: 0 16px; overflow: auto; + color: var(--primary-text-color); } @media only screen and (max-width: 800px) { @@ -35,7 +36,7 @@ class DemoMoreInfo extends PolymerElement { flex-direction: column; } pre { - margin-left: 0; + margin: 16px 0; } } diff --git a/gallery/src/demos/demo-hui-alarm-panel-card.ts b/gallery/src/demos/demo-hui-alarm-panel-card.ts index 73d46b53fd..efeacf5f19 100644 --- a/gallery/src/demos/demo-hui-alarm-panel-card.ts +++ b/gallery/src/demos/demo-hui-alarm-panel-card.ts @@ -73,13 +73,7 @@ const CONFIGS = [ class DemoAlarmPanelEntity extends PolymerElement { static get template() { - return html` - - `; + return html` `; } static get properties() { @@ -88,7 +82,6 @@ class DemoAlarmPanelEntity extends PolymerElement { type: Object, value: CONFIGS, }, - hass: Object, }; } diff --git a/gallery/src/demos/demo-hui-conditional-card.ts b/gallery/src/demos/demo-hui-conditional-card.ts index e5ce595f91..561ad45376 100644 --- a/gallery/src/demos/demo-hui-conditional-card.ts +++ b/gallery/src/demos/demo-hui-conditional-card.ts @@ -55,13 +55,7 @@ const CONFIGS = [ class DemoConditional extends PolymerElement { static get template() { - return html` - - `; + return html` `; } static get properties() { @@ -70,7 +64,6 @@ class DemoConditional extends PolymerElement { type: Object, value: CONFIGS, }, - hass: Object, }; } diff --git a/gallery/src/demos/demo-hui-entity-button-card.ts b/gallery/src/demos/demo-hui-entity-button-card.ts index 5b89327432..5c9430bf64 100644 --- a/gallery/src/demos/demo-hui-entity-button-card.ts +++ b/gallery/src/demos/demo-hui-entity-button-card.ts @@ -20,10 +20,10 @@ const CONFIGS = [ `, }, { - heading: "With Name", + heading: "With Name (defined in card)", config: ` - type: button - name: Bedroom + name: Custom Name entity: light.bed_light `, }, @@ -32,7 +32,7 @@ const CONFIGS = [ config: ` - type: button entity: light.bed_light - icon: mdi:hotel + icon: mdi:tools `, }, { @@ -71,13 +71,7 @@ const CONFIGS = [ class DemoButtonEntity extends PolymerElement { static get template() { - return html` - - `; + return html` `; } static get properties() { @@ -86,7 +80,6 @@ class DemoButtonEntity extends PolymerElement { type: Object, value: CONFIGS, }, - hass: Object, }; } diff --git a/gallery/src/demos/demo-hui-gauge-card.ts b/gallery/src/demos/demo-hui-gauge-card.ts index 5dd0cf582b..1704d31f6e 100644 --- a/gallery/src/demos/demo-hui-gauge-card.ts +++ b/gallery/src/demos/demo-hui-gauge-card.ts @@ -7,6 +7,8 @@ import "../components/demo-cards"; const ENTITIES = [ getEntity("sensor", "brightness", "12", {}), + getEntity("sensor", "brightness_medium", "53", {}), + getEntity("sensor", "brightness_high", "87", {}), getEntity("plant", "bonsai", "ok", {}), getEntity("sensor", "not_working", "unavailable", {}), getEntity("sensor", "outside_humidity", "54", { @@ -21,16 +23,10 @@ const CONFIGS = [ { heading: "Basic example", config: ` -- type: gauge - entity: sensor.brightness - `, - }, - { - heading: "With title", - config: ` - type: gauge title: Humidity entity: sensor.outside_humidity + name: Outside Humidity `, }, { @@ -39,6 +35,7 @@ const CONFIGS = [ - type: gauge entity: sensor.outside_temperature unit_of_measurement: C + name: Outside Temperature `, }, { @@ -46,19 +43,45 @@ const CONFIGS = [ config: ` - type: gauge entity: sensor.brightness + name: Brightness Low severity: - red: 32 + red: 75 green: 0 - yellow: 23 + yellow: 50 `, }, { - heading: "Setting Min and Max Values", + heading: "Setting Severity Levels", + config: ` +- type: gauge + entity: sensor.brightness_medium + name: Brightness Medium + severity: + red: 75 + green: 0 + yellow: 50 + `, + }, + { + heading: "Setting Severity Levels", + config: ` +- type: gauge + entity: sensor.brightness_high + name: Brightness High + severity: + red: 75 + green: 0 + yellow: 50 + `, + }, + { + heading: "Setting Min (0) and Max (15) Values", config: ` - type: gauge entity: sensor.brightness + name: Brightness min: 0 - max: 38 + max: 15 `, }, { diff --git a/gallery/src/demos/demo-hui-light-card.ts b/gallery/src/demos/demo-hui-light-card.ts index 552bb2b0df..97808f930a 100644 --- a/gallery/src/demos/demo-hui-light-card.ts +++ b/gallery/src/demos/demo-hui-light-card.ts @@ -8,29 +8,43 @@ import "../components/demo-cards"; const ENTITIES = [ getEntity("light", "bed_light", "on", { friendly_name: "Bed Light", - brightness: 130, + brightness: 255, }), - getEntity("light", "dim", "off", { + getEntity("light", "dim_on", "on", { + friendly_name: "Dining Room", + supported_features: 1, + brightness: 100, + }), + getEntity("light", "dim_off", "off", { + friendly_name: "Dining Room", supported_features: 1, }), getEntity("light", "unavailable", "unavailable", { + friendly_name: "Lost Light", supported_features: 1, }), ]; const CONFIGS = [ { - heading: "Basic example", + heading: "Switchable Light", config: ` - type: light entity: light.bed_light `, }, { - heading: "Dim", + heading: "Dimmable Light On", config: ` - type: light - entity: light.dim + entity: light.dim_on + `, + }, + { + heading: "Dimmable Light Off", + config: ` +- type: light + entity: light.dim_off `, }, { diff --git a/gallery/src/demos/demo-hui-map-card.ts b/gallery/src/demos/demo-hui-map-card.ts index df4c8844db..392826fb93 100644 --- a/gallery/src/demos/demo-hui-map-card.ts +++ b/gallery/src/demos/demo-hui-map-card.ts @@ -163,13 +163,7 @@ const CONFIGS = [ class DemoMap extends PolymerElement { static get template() { - return html` - - `; + return html` `; } static get properties() { @@ -178,7 +172,6 @@ class DemoMap extends PolymerElement { type: Object, value: CONFIGS, }, - hass: Object, }; } diff --git a/gallery/src/demos/demo-hui-media-control-card.ts b/gallery/src/demos/demo-hui-media-control-card.ts index 6298163a25..2847ee3e61 100644 --- a/gallery/src/demos/demo-hui-media-control-card.ts +++ b/gallery/src/demos/demo-hui-media-control-card.ts @@ -150,13 +150,7 @@ const CONFIGS = [ class DemoHuiMediControlCard extends PolymerElement { static get template() { - return html` - - `; + return html` `; } static get properties() { @@ -165,7 +159,6 @@ class DemoHuiMediControlCard extends PolymerElement { type: Object, value: CONFIGS, }, - hass: Object, }; } diff --git a/gallery/src/demos/demo-hui-media-player-rows.ts b/gallery/src/demos/demo-hui-media-player-rows.ts index 8287f5b773..147f36985e 100644 --- a/gallery/src/demos/demo-hui-media-player-rows.ts +++ b/gallery/src/demos/demo-hui-media-player-rows.ts @@ -57,13 +57,7 @@ const CONFIGS = [ class DemoHuiMediaPlayerRows extends PolymerElement { static get template() { - return html` - - `; + return html` `; } static get properties() { @@ -72,7 +66,6 @@ class DemoHuiMediaPlayerRows extends PolymerElement { type: Object, value: CONFIGS, }, - hass: Object, }; } diff --git a/gallery/src/ha-gallery.js b/gallery/src/ha-gallery.js index a464deb51f..779790b632 100644 --- a/gallery/src/ha-gallery.js +++ b/gallery/src/ha-gallery.js @@ -20,48 +20,47 @@ class HaGallery extends PolymerElement { static get template() { return html` @@ -70,32 +69,42 @@ class HaGallery extends PolymerElement { -
[[_withDefault(_demo, "Home Assistant Gallery")]]
+
+ [[_withDefault(_demo, "Home Assistant Gallery")]] +
-
-
-