Compare commits

..

3 Commits

Author SHA1 Message Date
Donnie
72bf0c918a Update src/dialogs/template-editor/ha-template-editor.ts
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-17 16:07:54 -08:00
Donnie
419f5d13bf Update src/dialogs/template-editor/ha-template-editor.ts
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-17 16:07:48 -08:00
Donnie
16549b3404 Add ability to launch small version of template editor in a dialog using shortcut 2020-11-17 11:41:29 -08:00
748 changed files with 10541 additions and 31158 deletions

View File

@@ -26,9 +26,6 @@
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true
} }
} }

View File

@@ -74,12 +74,12 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
``` ```
## Problem-relevant frontend configuration ## Problem-relevant configuration
<!-- <!--
An example configuration that caused the problem for you, e.g. the YAML configuration An example configuration that caused the problem for you. Fill this out even
of the used cards. Fill this out even if it seems unimportant to you. Please be sure if it seems unimportant to you. Please be sure to remove personal information
to remove personal information like passwords, private URLs and other credentials. like passwords, private URLs and other credentials.
--> -->
```yaml ```yaml
@@ -89,7 +89,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
## Javascript errors shown in your browser console/inspector ## Javascript errors shown in your browser console/inspector
<!-- <!--
If you come across any Javascript or other error logs, e.g. in your browser If you come across any javascript or other error logs, e.g., in your browser
console/inspector please provide them. console/inspector please provide them.
--> -->

View File

@@ -1,19 +0,0 @@
name: Netlify
on:
schedule:
- cron: "0 0 * * *"
jobs:
trigger_builds:
name: Trigger netlify build preview
runs-on: "ubuntu-latest"
steps:
- name: Trigger Cast build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
- name: Trigger Demo build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
- name: Trigger Gallery build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}

View File

@@ -1,81 +0,0 @@
name: Release
on:
release:
types:
- published
env:
WHEELS_TAG: 3.7-alpine3.11
PYTHON_VERSION: 3.7
NODE_VERSION: 12.1
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Build and release package
run: |
python3 -m pip install twine
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
script/release
wheels-init:
name: Init wheels build
needs: release
runs-on: ubuntu-latest
steps:
- name: Generate requirements.txt
run: |
# Sleep to give pypi time to populate the new version across mirrors
sleep 240
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Upload requirements.txt
uses: actions/upload-artifact@v2
with:
name: requirements
path: ./requirements.txt
build-wheels:
name: Build wheels for ${{ matrix.arch }}
needs: wheels-init
runs-on: ubuntu-latest
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
with:
name: requirements
- name: Build wheels
uses: home-assistant/wheels@master
with:
tag: ${{ env.WHEELS_TAG }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt"

View File

@@ -1,65 +0,0 @@
name: Translations
on:
schedule:
- cron: "30 0 * * *"
push:
branches:
- dev
paths:
- translations/en.json
env:
NODE_VERSION: 12
jobs:
upload:
name: Upload
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Upload Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push

6
.hound.yml Normal file
View File

@@ -0,0 +1,6 @@
jshint:
enabled: false
eslint:
enabled: true
config_file: .eslintrc-hound.json

View File

@@ -14,7 +14,7 @@ This is the repository for the official [Home Assistant](https://home-assistant.
- Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/) - Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/)
- Production build: `script/build_frontend` - Production build: `script/build_frontend`
- Gallery: `cd gallery && script/develop_gallery` - Gallery: `cd gallery && script/develop_gallery`
- Supervisor: [Instructions](https://developers.home-assistant.io/docs/supervisor/developing) - Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
## Frontend development ## Frontend development

View File

@@ -0,0 +1,30 @@
# https://dev.azure.com/home-assistant
trigger: none
pr: none
schedules:
- cron: "0 0 * * *"
displayName: "build preview"
branches:
include:
- dev
always: true
variables:
- group: netlify
jobs:
- job: 'Netlify_preview'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
# Cast
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST}
# Demo
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO}
# Gallery
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY}
displayName: 'Trigger netlify build preview'

View File

@@ -0,0 +1,59 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
tags:
include:
- "*"
pr: none
variables:
- name: versionWheels
value: '1.10.1-3.7-alpine3.11'
- name: versionNode
value: '12.1'
- group: twine
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
stages:
- stage: "Validate"
jobs:
- template: templates/azp-job-version.yaml@azure
- stage: "Build"
jobs:
- job: "ReleasePython"
pool:
vmImage: "ubuntu-latest"
steps:
- task: UsePythonVersion@0
displayName: "Use Python 3.7"
inputs:
versionSpec: "3.7"
- task: NodeTool@0
displayName: "Use Node $(versionNode)"
inputs:
versionSpec: "$(versionNode)"
- script: pip install twine wheel
displayName: "Install tools"
- script: |
export TWINE_USERNAME="$(twineUser)"
export TWINE_PASSWORD="$(twinePassword)"
script/release
displayName: "Build and release package"
- stage: "Wheels"
jobs:
- template: templates/azp-job-wheels.yaml@azure
parameters:
builderVersion: '$(versionWheels)'
wheelsRequirement: 'requirement.txt'
preBuild:
- script: |
sleep 240
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt

View File

@@ -0,0 +1,70 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- dev
paths:
include:
- translations/en.json
pr: none
schedules:
- cron: "30 0 * * *"
displayName: "frontend translation update"
branches:
include:
- dev
always: true
variables:
- group: translation
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs:
- job: 'Upload'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
./script/translations_upload_base
displayName: 'Upload Translation'
- job: 'Download'
dependsOn:
- 'Upload'
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- template: templates/azp-step-git-init.yaml@azure
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
npm install
./script/translations_download
displayName: 'Download Translation'
- script: |
git checkout dev
git add translation
git commit -am "[ci skip] Translation update"
git push
displayName: 'Update translation'

View File

@@ -1,39 +0,0 @@
# 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).

View File

@@ -44,7 +44,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
}); });
module.exports.terserOptions = (latestBuild) => ({ module.exports.terserOptions = (latestBuild) => ({
safari10: !latestBuild, safari10: true,
ecma: latestBuild ? undefined : 5, ecma: latestBuild ? undefined : 5,
output: { comments: false }, output: { comments: false },
}); });
@@ -117,7 +117,7 @@ BundleConfig {
*/ */
module.exports.config = { module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild, isWDS }) { app({ isProdBuild, latestBuild, isStatsBuild }) {
return { return {
entry: { entry: {
service_worker: "./src/entrypoints/service_worker.ts", service_worker: "./src/entrypoints/service_worker.ts",
@@ -132,7 +132,6 @@ module.exports.config = {
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
isWDS,
}; };
}, },

View File

@@ -6,9 +6,6 @@ module.exports = {
useRollup() { useRollup() {
return process.env.ROLLUP === "1"; return process.env.ROLLUP === "1";
}, },
useWDS() {
return process.env.WDS === "1";
},
isProdBuild() { isProdBuild() {
return ( return (
process.env.NODE_ENV === "production" || module.exports.isStatsBuild() process.env.NODE_ENV === "production" || module.exports.isStatsBuild()

View File

@@ -12,7 +12,6 @@ require("./webpack.js");
require("./service-worker.js"); require("./service-worker.js");
require("./entry-html.js"); require("./entry-html.js");
require("./rollup.js"); require("./rollup.js");
require("./wds.js");
gulp.task( gulp.task(
"develop-app", "develop-app",
@@ -29,11 +28,7 @@ gulp.task(
"build-translations" "build-translations"
), ),
"copy-static-app", "copy-static-app",
env.useWDS() env.useRollup() ? "rollup-watch-app" : "webpack-watch-app"
? "wds-watch-app"
: env.useRollup()
? "rollup-watch-app"
: "webpack-watch-app"
) )
); );

View File

@@ -19,7 +19,6 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
return compiled({ return compiled({
...data, ...data,
useRollup: env.useRollup(), useRollup: env.useRollup(),
useWDS: env.useWDS(),
renderTemplate, renderTemplate,
}); });
}; };
@@ -91,23 +90,10 @@ gulp.task("gen-pages-prod", (done) => {
}); });
gulp.task("gen-index-app-dev", (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", { const content = renderTemplate("index", {
latestAppJS, latestAppJS: "/frontend_latest/app.js",
latestCoreJS, latestCoreJS: "/frontend_latest/core.js",
latestCustomPanelJS, latestCustomPanelJS: "/frontend_latest/custom-panel.js",
es5AppJS: "/frontend_es5/app.js", es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js", es5CoreJS: "/frontend_es5/core.js",

View File

@@ -33,10 +33,21 @@ String.prototype.rsplit = function (sep, maxsplit) {
: split; : split;
}; };
// Panel translations which should be split from the core translations. // Panel translations which should be split from the core translations. These
const TRANSLATION_FRAGMENTS = Object.keys( // should mirror the fragment definitions in polymer.json, so that we load
require("../../src/translations/en.json").ui.panel // additional resources at equivalent points.
); const TRANSLATION_FRAGMENTS = [
"config",
"history",
"logbook",
"mailbox",
"profile",
"shopping-list",
"page-authorize",
"page-demo",
"page-onboarding",
"developer-tools",
];
function recursiveFlatten(prefix, data) { function recursiveFlatten(prefix, data) {
let output = {}; let output = {};

View File

@@ -1,11 +0,0 @@
// Tasks to run Rollup
const gulp = require("gulp");
const { startDevServer } = require("@web/dev-server");
gulp.task("wds-watch-app", () => {
startDevServer({
config: {
watch: true,
},
});
});

View File

@@ -47,7 +47,7 @@ const runDevServer = ({
); );
}); });
const doneHandler = (done) => (err, stats) => { const handler = (done) => (err, stats) => {
if (err) { if (err) {
log.error(err.stack || err); log.error(err.stack || err);
if (err.details) { if (err.details) {
@@ -67,20 +67,11 @@ const doneHandler = (done) => (err, stats) => {
} }
}; };
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", () => { gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler // we are not calling done, so this command will run forever
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch( webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
{ ignored: /build-translations/ }, { ignored: /build-translations/ },
doneHandler() handler()
); );
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
@@ -88,12 +79,15 @@ gulp.task("webpack-watch-app", () => {
); );
}); });
gulp.task("webpack-prod-app", () => gulp.task(
prodBuild( "webpack-prod-app",
bothBuilds(createAppConfig, { () =>
isProdBuild: true, new Promise((resolve) =>
}) webpack(
) bothBuilds(createAppConfig, { isProdBuild: true }),
handler(resolve)
)
)
); );
gulp.task("webpack-dev-server-demo", () => { gulp.task("webpack-dev-server-demo", () => {
@@ -104,12 +98,17 @@ gulp.task("webpack-dev-server-demo", () => {
}); });
}); });
gulp.task("webpack-prod-demo", () => gulp.task(
prodBuild( "webpack-prod-demo",
bothBuilds(createDemoConfig, { () =>
isProdBuild: true, new Promise((resolve) =>
}) webpack(
) bothBuilds(createDemoConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
); );
gulp.task("webpack-dev-server-cast", () => { gulp.task("webpack-dev-server-cast", () => {
@@ -122,30 +121,41 @@ gulp.task("webpack-dev-server-cast", () => {
}); });
}); });
gulp.task("webpack-prod-cast", () => gulp.task(
prodBuild( "webpack-prod-cast",
bothBuilds(createCastConfig, { () =>
isProdBuild: true, new Promise((resolve) =>
}) webpack(
) bothBuilds(createCastConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
); );
gulp.task("webpack-watch-hassio", () => { gulp.task("webpack-watch-hassio", () => {
// This command will run forever because we don't close compiler // we are not calling done, so this command will run forever
webpack( webpack(
createHassioConfig({ createHassioConfig({
isProdBuild: false, isProdBuild: false,
latestBuild: true, latestBuild: true,
}) })
).watch({}, doneHandler()); ).watch({}, handler());
}); });
gulp.task("webpack-prod-hassio", () => gulp.task(
prodBuild( "webpack-prod-hassio",
bothBuilds(createHassioConfig, { () =>
isProdBuild: true, new Promise((resolve) =>
}) webpack(
) bothBuilds(createHassioConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
); );
gulp.task("webpack-dev-server-gallery", () => { gulp.task("webpack-dev-server-gallery", () => {
@@ -157,11 +167,17 @@ gulp.task("webpack-dev-server-gallery", () => {
}); });
}); });
gulp.task("webpack-prod-gallery", () => gulp.task(
prodBuild( "webpack-prod-gallery",
createGalleryConfig({ () =>
isProdBuild: true, new Promise((resolve) =>
latestBuild: true, webpack(
}) createGalleryConfig({
) isProdBuild: true,
latestBuild: true,
}),
handler(resolve)
)
)
); );

View File

@@ -1,3 +1,5 @@
const path = require("path");
module.exports = function (userOptions = {}) { module.exports = function (userOptions = {}) {
// Files need to be absolute paths. // Files need to be absolute paths.
// This only works if the file has no exports // This only works if the file has no exports

View File

@@ -3,7 +3,7 @@ const path = require("path");
const commonjs = require("@rollup/plugin-commonjs"); const commonjs = require("@rollup/plugin-commonjs");
const resolve = require("@rollup/plugin-node-resolve"); const resolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json"); const json = require("@rollup/plugin-json");
const babel = require("@rollup/plugin-babel").babel; const babel = require("rollup-plugin-babel");
const replace = require("@rollup/plugin-replace"); const replace = require("@rollup/plugin-replace");
const visualizer = require("rollup-plugin-visualizer"); const visualizer = require("rollup-plugin-visualizer");
const { string } = require("rollup-plugin-string"); const { string } = require("rollup-plugin-string");
@@ -31,7 +31,6 @@ const createRollupConfig = ({
isStatsBuild, isStatsBuild,
publicPath, publicPath,
dontHash, dontHash,
isWDS,
}) => { }) => {
return { return {
/** /**
@@ -62,7 +61,6 @@ const createRollupConfig = ({
...bundle.babelOptions({ latestBuild }), ...bundle.babelOptions({ latestBuild }),
extensions, extensions,
exclude: bundle.babelExclude(), exclude: bundle.babelExclude(),
babelHelpers: isWDS ? "inline" : "bundled",
}), }),
string({ string({
// Import certain extensions as strings // Import certain extensions as strings
@@ -71,21 +69,19 @@ const createRollupConfig = ({
replace( replace(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
), ),
!isWDS && manifest({
manifest({ publicPath,
publicPath, }),
}), worker(),
!isWDS && worker(), dontHashPlugin({ dontHash }),
!isWDS && dontHashPlugin({ dontHash }), isProdBuild && terser(bundle.terserOptions(latestBuild)),
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)), isStatsBuild &&
!isWDS &&
isStatsBuild &&
visualizer({ visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options // https://github.com/btd/rollup-plugin-visualizer#options
open: true, open: true,
sourcemap: true, sourcemap: true,
}), }),
].filter(Boolean), ],
}, },
/** /**
* @type { import("rollup").OutputOptions } * @type { import("rollup").OutputOptions }
@@ -112,13 +108,12 @@ const createRollupConfig = ({
}; };
}; };
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => { const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createRollupConfig( return createRollupConfig(
bundle.config.app({ bundle.config.app({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
isWDS,
}) })
); );
}; };

View File

@@ -36,7 +36,6 @@ const createWebpackConfig = ({
const ignorePackages = bundle.ignorePackages({ latestBuild }); const ignorePackages = bundle.ignorePackages({ latestBuild });
return { return {
mode: isProdBuild ? "production" : "development", mode: isProdBuild ? "production" : "development",
target: ["web", latestBuild ? "es2017" : "es5"],
devtool: isProdBuild devtool: isProdBuild
? "cheap-module-source-map" ? "cheap-module-source-map"
: "eval-cheap-module-source-map", : "eval-cheap-module-source-map",
@@ -132,6 +131,22 @@ const createWebpackConfig = ({
} }
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
}, },
environment: {
// The environment supports arrow functions ('() => { ... }').
arrowFunction: latestBuild,
// The environment supports BigInt as literal (123n).
bigIntLiteral: false,
// The environment supports const and let for variable declarations.
const: latestBuild,
// The environment supports destructuring ('{ a, b } = obj').
destructuring: latestBuild,
// The environment supports an async import() function to import EcmaScript modules.
dynamicImport: latestBuild,
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
forOf: latestBuild,
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
module: latestBuild,
},
chunkFilename: chunkFilename:
isProdBuild && !isStatsBuild isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js" ? "chunk.[chunkhash].js"

View File

@@ -1,8 +1,8 @@
import { import {
customElement, customElement,
html, html,
internalProperty,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history"; import { mockHistory } from "../../../../demo/src/stubs/history";

View File

@@ -1,4 +1,4 @@
import "web-animations-js/web-animations-next-lite.min"; import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto"; import "../../../src/resources/roboto";
import "../../../src/resources/ha-style";
import "./layout/hc-lovelace"; import "./layout/hc-lovelace";

View File

@@ -54,8 +54,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "21", state: "21",
attributes: { attributes: {
friendly_name: "Living room temperature", friendly_name: "Living room temperature",
device_class: "temperature",
unit_of_measurement: "°C",
}, },
}, },
"sensor.study_temp_rounded": { "sensor.study_temp_rounded": {
@@ -63,8 +61,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "23", state: "23",
attributes: { attributes: {
friendly_name: "Study temperature", friendly_name: "Study temperature",
device_class: "temperature",
unit_of_measurement: "°C",
}, },
}, },
"sensor.living_room": { "sensor.living_room": {
@@ -265,7 +261,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
entity_id: "light.kitchen_lights", entity_id: "light.kitchen_lights",
state: "off", state: "off",
attributes: { attributes: {
friendly_name: "Kitchen Lights", friendly_name: "Kitchen lights",
supported_features: 1, supported_features: 1,
}, },
}, },
@@ -488,7 +484,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
attributes: { attributes: {
min_mireds: 111, min_mireds: 111,
max_mireds: 400, max_mireds: 400,
friendly_name: "Garage Lights", friendly_name: "Garage lights",
supported_features: 55, supported_features: 55,
}, },
}, },

View File

@@ -12,7 +12,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
{ {
type: "entities", type: "entities",
title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"), title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"),
state_color: true,
entities: [ entities: [
{ {
entity: "light.kitchen_lights", entity: "light.kitchen_lights",

View File

@@ -3,10 +3,22 @@ import { Lovelace } from "../../../src/panels/lovelace/types";
import { DemoConfig } from "./types"; import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
() => import("./arsaboo").then((mod) => mod.demoArsaboo), () =>
() => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), import(/* webpackChunkName: "arsaboo" */ "./arsaboo").then(
() => import("./kernehed").then((mod) => mod.demoKernehed), (mod) => mod.demoArsaboo
() => import("./jimpower").then((mod) => mod.demoJimpower), ),
() =>
import(/* webpackChunkName: "teachingbirds" */ "./teachingbirds").then(
(mod) => mod.demoTeachingbirds
),
() =>
import(/* webpackChunkName: "kernehed" */ "./kernehed").then(
(mod) => mod.demoKernehed
),
() =>
import(/* webpackChunkName: "jimpower" */ "./jimpower").then(
(mod) => mod.demoJimpower
),
]; ];
// eslint-disable-next-line import/no-mutable-exports // eslint-disable-next-line import/no-mutable-exports

View File

@@ -653,7 +653,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7", entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7",
state: "off", state: "off",
attributes: { attributes: {
density: 0, Density: 0,
battery_level: 59, battery_level: 59,
friendly_name: "Downstairs Smoke Detector", friendly_name: "Downstairs Smoke Detector",
device_class: "smoke", device_class: "smoke",
@@ -663,7 +663,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.smoke_sensor_158d0001b8deba", entity_id: "binary_sensor.smoke_sensor_158d0001b8deba",
state: "off", state: "off",
attributes: { attributes: {
density: 0, Density: 0,
battery_level: 65, battery_level: 65,
friendly_name: "Upstairs Smoke Detector", friendly_name: "Upstairs Smoke Detector",
device_class: "smoke", device_class: "smoke",

View File

@@ -3,7 +3,49 @@ import { DemoConfig } from "../types";
export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
name: "Kingia Castle", name: "Kingia Castle",
resources: [], resources: [
// {
// url: "/local/custom_ui/dark-sky-weather-card.js?v=4",
// type: "js",
// },
// {
// url: "/local/custom_ui/mini-media-player-bundle.js?v=0.9.8",
// type: "module",
// },
// {
// url: "/local/custom_ui/tracker-card.js?v=0.1.5",
// type: "js",
// },
// {
// url: "/local/custom_ui/surveillance-card.js?v=0.0.1",
// type: "module",
// },
// {
// url: "/local/custom_ui/mini-graph-card-bundle.js?v=0.1.0",
// type: "module",
// },
// {
// url: "/local/custom_ui/slider-entity-row.js?v=d6da75",
// type: "js",
// },
// {
// url:
// "/local/custom_ui/compact-custom-header/compact-custom-header.js?v=0.2.7",
// type: "js",
// },
// {
// url: "/local/custom_ui/waze-card.js?v=1.1.1",
// type: "js",
// },
// {
// url: "/local/custom_ui/circle-sensor-card.js?v=1.2.0",
// type: "module",
// },
// {
// url: "/local/custom_ui/monster-card.js?v=0.2.3",
// type: "js",
// },
],
views: [ views: [
{ {
cards: [ cards: [
@@ -561,6 +603,89 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
}, },
{ {
cards: [ cards: [
// {
// style: {
// "background-image": 'url("/assets/jimpower/cardbackK.png")',
// "background-size": "100% 400px",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// "background-repeat": "no-repeat",
// color: "#999999",
// "border-radius": "20px",
// border: "solid 1px rgba(100,100,100,0.3)",
// "background-color": "rgba(50,50,50,0.3)",
// },
// type: "custom:card-modder",
// card: {
// entity_visibility: "sensor.dark_sky_visibility",
// entity_sun: "sun.sun",
// entity_daily_summary:
// "sensor.bom_gc_forecast_detailed_summary_0",
// entity_temperature: "sensor.bom_temp",
// entity_forecast_high_temp_3:
// "sensor.bom_gc_forecast_max_temp_c_3",
// entity_forecast_high_temp_2:
// "sensor.bom_gc_forecast_max_temp_c_2",
// entity_forecast_high_temp_5:
// "sensor.bom_gc_forecast_max_temp_c_5",
// entity_forecast_high_temp_4:
// "sensor.bom_gc_forecast_max_temp_c_4",
// entity_wind_speed: "sensor.bom_wind_sp",
// entity_forecast_icon_4: "sensor.dark_sky_icon_4",
// entity_forecast_icon_5: "sensor.dark_sky_icon_5",
// entity_forecast_icon_2: "sensor.dark_sky_icon_2",
// entity_forecast_icon_3: "sensor.dark_sky_icon_3",
// entity_forecast_icon_1: "sensor.dark_sky_icon_1",
// entity_forecast_high_temp_1:
// "sensor.bom_gc_forecast_max_temp_c_1",
// entity_wind_bearing: "sensor.bom_wind_bear",
// entity_forecast_low_temp_2:
// "sensor.bom_gc_forecast_min_temp_c_2",
// entity_forecast_low_temp_3:
// "sensor.bom_gc_forecast_min_temp_c_3",
// entity_pressure: "sensor.bom_pres",
// entity_forecast_low_temp_1:
// "sensor.bom_gc_forecast_min_temp_c_1",
// entity_forecast_low_temp_4:
// "sensor.bom_gc_forecast_min_temp_c_4",
// entity_forecast_low_temp_5:
// "sensor.bom_gc_forecast_min_temp_c_5",
// entity_humidity: "sensor.bom_humd",
// type: "custom:dark-sky-weather-card",
// entity_current_conditions: "sensor.dark_sky_icon",
// },
// },
// {
// style: {
// "background-image": 'url("/assets/jimpower/home/waze_5.png")',
// "background-size": "100% 400px",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// "background-repeat": "no-repeat",
// "border-radius": "20px",
// border: "solid 1px rgba(100,100,100,0.3)",
// "background-color": "rgba(50,50,50,0.3)",
// },
// type: "custom:card-modder",
// card: {
// entities: [
// {
// name: "James",
// zone: "zone.home",
// entity: "sensor.james_to_home",
// },
// {
// name: "Tina",
// zone: "zone.home",
// entity: "sensor.tina_to_home",
// },
// {
// name: "Work",
// zone: "zone.powertec",
// entity: "sensor.commute_to_work",
// },
// ],
// type: "custom:waze-card",
// },
// },
{ {
style: { style: {
"border-radius": "20px", "border-radius": "20px",
@@ -597,8 +722,46 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
], ],
type: "vertical-stack", type: "vertical-stack",
}, },
// {
// cards: [
// {
// style: {
// "border-radius": "20px",
// color: "#999999",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// border: "solid 1px rgba(100,100,100,0.3)",
// },
// type: "custom:card-modder",
// card: {
// type: "picture-entity",
// entity: "camera.bom_radar",
// },
// },
// // {
// // style: {
// // "background-image": 'url("/assets/jimpower/cardbackK.png")',
// // "background-size": "100% 525px",
// // "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// // "background-repeat": "no-repeat",
// // color: "#999999",
// // "border-radius": "20px",
// // border: "solid 1px rgba(100,100,100,0.3)",
// // "background-color": "rgba(50,50,50,0.3)",
// // },
// // type: "custom:card-modder",
// // card: {
// // title: null,
// // type: "custom:tracker-card",
// // trackers: [
// // "sensor.custom_card_tracker",
// // "sensor.custom_component_tracker",
// // ],
// // },
// // },
// ],
// type: "vertical-stack",
// },
], ],
path: "home",
icon: "mdi:castle", icon: "mdi:castle",
name: "Home", name: "Home",
background: background:
@@ -718,13 +881,26 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
card: { card: {
image: "/assets/jimpower/security/air_8.jpg", image: "/assets/jimpower/security/air_8.jpg",
elements: [ elements: [
{
image:
"https://www.airvisual.com/assets/aqi/ic-face-1-green.svg",
type: "image",
style: {
width: "80px",
top: "30%",
left: "12%",
transform: "none",
height: "80px",
},
entity: "sensor.us_air_pollution_level_2",
},
{ {
style: { style: {
color: "hsl(120, 41%, 39%)", color: "hsl(120, 41%, 39%)",
top: "50%", top: "50%",
"font-weight": 600, "font-weight": 600,
"font-size": "50px", "font-size": "20px",
left: "30%", left: "44%",
}, },
type: "state-label", type: "state-label",
entity: "sensor.us_air_pollution_level_2", entity: "sensor.us_air_pollution_level_2",
@@ -744,7 +920,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
style: { style: {
color: "white", color: "white",
top: "80%", top: "80%",
left: "48%", left: "52%",
}, },
type: "state-icon", type: "state-icon",
entity: "sensor.us_main_pollutant_2", entity: "sensor.us_main_pollutant_2",
@@ -1235,7 +1411,6 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "vertical-stack", type: "vertical-stack",
}, },
], ],
path: "security",
icon: "hass:shield-home", icon: "hass:shield-home",
name: "Security", name: "Security",
background: background:

View File

@@ -101,12 +101,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
"sensor.zwave_battery_front_door": { "sensor.zwave_battery_front_door": {
entity_id: "sensor.zwave_battery_front_door", entity_id: "sensor.zwave_battery_front_door",
state: "63", state: "63",
attributes: { attributes: { friendly_name: "Battery", icon: "mdi:battery-60" },
friendly_name: "Battery",
icon: "mdi:battery-60",
unit_of_measurement: "%",
device_class: "battery",
},
}, },
"sensor.oskar_devices": { "sensor.oskar_devices": {
entity_id: "sensor.oskar_devices", entity_id: "sensor.oskar_devices",
@@ -169,7 +164,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
}, },
"input_select.christmas_pattern": { "input_select.christmas_pattern": {
entity_id: "input_select.christmas_pattern", entity_id: "input_select.christmas_pattern",
state: "Rainbow", state: "None",
attributes: { attributes: {
options: [ options: [
"None", "None",
@@ -191,7 +186,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
}, },
"input_select.christmas_palette": { "input_select.christmas_palette": {
entity_id: "input_select.christmas_palette", entity_id: "input_select.christmas_palette",
state: "Party", state: "None",
attributes: { attributes: {
options: [ options: [
"None", "None",
@@ -462,7 +457,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "0.0", state: "0.0",
attributes: { attributes: {
unit_of_measurement: "kB/s", unit_of_measurement: "kB/s",
friendly_name: "Downloading", friendly_name: "Nedladdning",
icon: "mdi:file-download", icon: "mdi:file-download",
}, },
}, },
@@ -476,7 +471,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "0.0", state: "0.0",
attributes: { attributes: {
unit_of_measurement: "kB/s", unit_of_measurement: "kB/s",
friendly_name: "Uploading", friendly_name: "Uppladdning",
icon: "mdi:file-upload", icon: "mdi:file-upload",
}, },
}, },

View File

@@ -2,7 +2,44 @@ import { DemoConfig } from "../types";
export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
name: "Hem", name: "Hem",
resources: [], resources: [
// {
// url: "/local/custom-lovelace/monster-card.js",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8",
// type: "module",
// },
// {
// url: "/local/custom-lovelace/slideshow-card.js?=1.1.0",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0",
// type: "module",
// },
// {
// url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/tracker-card.js?v=0.1.5",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/card-tools.js?v=6ce5d0",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/krisinfo.js?=0.0.1",
// type: "js",
// },
],
views: [ views: [
{ {
cards: [ cards: [
@@ -27,7 +64,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
style: { style: {
color: "white", color: "white",
top: "93%", top: "93%",
left: "85%", left: "90%",
}, },
type: "state-label", type: "state-label",
entity: "sensor.battery_oskar", entity: "sensor.battery_oskar",
@@ -50,7 +87,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{ {
style: { style: {
color: "white", color: "white",
top: "93%", top: "92%",
left: "20%", left: "20%",
}, },
type: "state-label", type: "state-label",
@@ -59,8 +96,8 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{ {
style: { style: {
color: "white", color: "white",
top: "93%", top: "92%",
left: "85%", left: "90%",
}, },
type: "state-label", type: "state-label",
entity: "sensor.battery_bella", entity: "sensor.battery_bella",
@@ -68,7 +105,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{ {
style: { style: {
color: "white", color: "white",
top: "93%", top: "92%",
left: "55%", left: "55%",
}, },
type: "state-label", type: "state-label",
@@ -94,6 +131,78 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
type: "entities", type: "entities",
title: "Lock", title: "Lock",
}, },
// {
// filter: {
// exclude: [
// {
// state: "not_home",
// },
// ],
// include: [
// {
// entity_id: "device_tracker.annasiphone",
// },
// {
// entity_id: "device_tracker.iphone_2",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "G\u00e4ster",
// },
// show_empty: false,
// },
// {
// filter: {
// exclude: [
// {
// state: "Inget",
// },
// {
// state: "i.u.",
// },
// ],
// include: [
// {
// entity_id: "sensor.pollen_al",
// },
// {
// entity_id: "sensor.pollen_alm",
// },
// {
// entity_id: "sensor.pollen_salg_vide",
// },
// {
// entity_id: "sensor.pollen_bjork",
// },
// {
// entity_id: "sensor.pollen_bok",
// },
// {
// entity_id: "sensor.pollen_ek",
// },
// {
// entity_id: "sensor.pollen_grabo",
// },
// {
// entity_id: "sensor.pollen_gras",
// },
// {
// entity_id: "sensor.pollen_hassel",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "Pollenniv\u00e5er",
// },
// show_empty: false,
// },
{ {
cards: [ cards: [
{ {
@@ -117,6 +226,10 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
], ],
type: "vertical-stack", type: "vertical-stack",
}, },
// {
// url: "https://embed.windy.com/embed2.html",
// type: "iframe",
// },
{ {
entities: [ entities: [
{ {
@@ -150,7 +263,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
], ],
type: "glance", type: "glance",
show_state: false, show_state: false,
columns: 4,
}, },
{ {
entities: ["sensor.oskar_bluetooth"], entities: ["sensor.oskar_bluetooth"],
@@ -158,6 +270,32 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
type: "entities", type: "entities",
title: "Occupancy", title: "Occupancy",
}, },
// {
// filter: {
// exclude: [
// {
// state: false,
// },
// ],
// include: [
// {
// entity_id:
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2",
// },
// {
// entity_id:
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "Brandvarnare",
// },
// show_empty: false,
// },
{ {
type: "weather-forecast", type: "weather-forecast",
entity: "weather.smhi_vader", entity: "weather.smhi_vader",
@@ -240,9 +378,41 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
"binary_sensor.windows_server", "binary_sensor.windows_server",
"binary_sensor.teamspeak", "binary_sensor.teamspeak",
"binary_sensor.harmony_hub", "binary_sensor.harmony_hub",
// {
// style: {
// height: "1px",
// width: "85%",
// "margin-left": "auto",
// background: "#62717b",
// "margin-right": "auto",
// },
// type: "divider",
// },
// {
// items: ["sensor.uptime_router", "sensor.installerad_routeros"],
// head: {
// entity: "binary_sensor.router",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "mdi:router",
// },
// },
// {
// items: [
// "sensor.uptime_router_server",
// "sensor.installerad_routeros_server",
// ],
// head: {
// entity: "binary_sensor.router_server",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "mdi:router",
// },
// },
], ],
show_header_toggle: false, show_header_toggle: false,
state_color: true,
type: "entities", type: "entities",
title: "Network", title: "Network",
}, },
@@ -252,10 +422,29 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
"binary_sensor.ubiquiti_switch", "binary_sensor.ubiquiti_switch",
"binary_sensor.ubiquiti_nvr", "binary_sensor.ubiquiti_nvr",
"binary_sensor.entre_kamera", "binary_sensor.entre_kamera",
// {
// items: ["sensor.uptime_ap_1"],
// head: {
// entity: "binary_sensor.accesspunkt_1",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "router-wireless",
// },
// },
// {
// items: ["sensor.uptime_ap_2"],
// head: {
// entity: "binary_sensor.accesspunkt_2",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "router-wireless",
// },
// },
"sensor.total_clients_wireless", "sensor.total_clients_wireless",
], ],
show_header_toggle: false, show_header_toggle: false,
state_color: true,
type: "entities", type: "entities",
title: "Ubiquiti", title: "Ubiquiti",
}, },

View File

@@ -215,7 +215,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
card: { card: {
type: "glance", type: "glance",
show_state: false, show_state: false,
columns: 4,
}, },
state_filter: ["on"], state_filter: ["on"],
}, },
@@ -809,6 +808,67 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
], ],
type: "vertical-stack", type: "vertical-stack",
}, },
// {
// cards: [
// {
// entities: [
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Bedside",
// entity: "light.bedside_lamp",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Bedroom",
// entity: "light.bedroom_ceiling_light",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Isa",
// entity: "light.isa_ceiling_light",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Upstairs hallway",
// entity: "light.upstairs_hallway_ceiling_light_level",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Nightlight",
// entity: "light.gateway_light_34ce008bfc4b",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Walk in closet",
// entity: "light.walk_in_closet_lights",
// },
// {
// hide_when_off: true,
// toggle: false,
// type: "custom:slider-entity-row",
// name: "Stefan",
// entity: "light.stefan_lightstrip",
// },
// ],
// show_header_toggle: false,
// type: "entities",
// title: "Upstairs",
// },
// ],
// type: "vertical-stack",
// },
], ],
path: "lights", path: "lights",
title: "Lights", title: "Lights",
@@ -858,6 +918,10 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "Dafang", name: "Dafang",
icon: "mdi:webcam", icon: "mdi:webcam",
}, },
{
name: "IR Hallway",
entity: "sensor.system_ir_blaster",
},
{ {
name: "IR Bedroom", name: "IR Bedroom",
entity: "sensor.system_ir_blaster_bedroom", entity: "sensor.system_ir_blaster_bedroom",
@@ -876,7 +940,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
"sensor.system_ring_chime", "sensor.system_ring_chime",
], ],
type: "glance", type: "glance",
columns: 4, columns: 5,
show_state: false, show_state: false,
}, },
{ {

View File

@@ -9,5 +9,5 @@ export interface DemoConfig {
authorUrl: string; authorUrl: string;
lovelace: (localize: LocalizeFunc) => LovelaceConfig; lovelace: (localize: LocalizeFunc) => LovelaceConfig;
entities: (localize: LocalizeFunc) => Entity[]; entities: (localize: LocalizeFunc) => Entity[];
theme: () => Record<string, string> | null; theme: () => { [key: string]: string } | null;
} }

View File

@@ -3,8 +3,8 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager"; import { CastManager } from "../../../src/cast/cast_manager";

View File

@@ -3,9 +3,9 @@ import {
css, css,
CSSResult, CSSResult,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";

View File

@@ -1,11 +1,13 @@
import "../../src/resources/safari-14-attachshadow-patch";
import "@polymer/polymer/lib/elements/dom-if"; import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat"; import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style"; import "../../src/resources/ha-style";
import "../../src/resources/roboto"; import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";
import "./ha-demo"; import "./ha-demo";
/* polyfill for paper-dropdown */ /* polyfill for paper-dropdown */
setTimeout(() => { setTimeout(() => {
import("web-animations-js/web-animations-next-lite.min"); import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
}, 1000); }, 1000);

View File

@@ -1,4 +1,3 @@
// Compat needs to be first import
import "../../src/resources/compatibility"; import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate"; import { navigate } from "../../src/common/navigate";

View File

@@ -21,16 +21,15 @@ class DemoCard extends PolymerElement {
} }
pre { pre {
width: 400px; width: 400px;
margin: 0 16px; margin: 16px;
overflow: auto; overflow: auto;
color: var(--primary-text-color);
} }
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {
.root { .root {
flex-direction: column; flex-direction: column;
} }
pre { pre {
margin: 16px 0; margin-left: 0;
} }
} }
</style> </style>

View File

@@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
import "../../../src/components/ha-formfield";
import "./demo-card"; import "./demo-card";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
class DemoCards extends PolymerElement { class DemoCards extends PolymerElement {
static get template() { static get template() {

View File

@@ -2,62 +2,58 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/dialogs/more-info/more-info-content";
import "../../../src/state-summary/state-card-content"; import "../../../src/state-summary/state-card-content";
import "../../../src/dialogs/more-info/more-info-content";
class DemoMoreInfo extends PolymerElement { class DemoMoreInfo extends PolymerElement {
static get template() { static get template() {
return html` return html`
<style> <style>
.root { :host {
display: flex; display: flex;
align-items: start;
} }
#card {
max-width: 400px;
width: 100vw;
}
ha-card { ha-card {
width: 352px; width: 333px;
padding: 20px 24px; padding: 20px 24px;
} }
state-card-content { state-card-content {
display: block; display: block;
margin-bottom: 16px; margin-bottom: 16px;
} }
pre { pre {
width: 400px; width: 400px;
margin: 0 16px; margin: 16px;
overflow: auto; overflow: auto;
color: var(--primary-text-color);
} }
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {
.root { :host {
flex-direction: column; flex-direction: column;
} }
pre { pre {
margin: 16px 0; margin-left: 0;
} }
} }
</style> </style>
<div class="root"> <ha-card>
<div id="card"> <state-card-content
<ha-card> state-obj="[[_stateObj]]"
<state-card-content hass="[[hass]]"
state-obj="[[_stateObj]]" in-dialog
hass="[[hass]]" ></state-card-content>
in-dialog
></state-card-content>
<more-info-content <more-info-content
hass="[[hass]]" hass="[[hass]]"
state-obj="[[_stateObj]]" state-obj="[[_stateObj]]"
></more-info-content> ></more-info-content>
</ha-card> </ha-card>
</div> <template is="dom-if" if="[[showConfig]]">
<template is="dom-if" if="[[showConfig]]"> <pre>[[_jsonEntity(_stateObj)]]</pre>
<pre>[[_jsonEntity(_stateObj)]]</pre> </template>
</template>
</div>
`; `;
} }

View File

@@ -2,8 +2,6 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
import "./demo-more-info"; import "./demo-more-info";
@@ -11,10 +9,6 @@ class DemoMoreInfos extends PolymerElement {
static get template() { static get template() {
return html` return html`
<style> <style>
#container {
min-height: calc(100vh - 128px);
background: var(--primary-background-color);
}
.cards { .cards {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -29,31 +23,20 @@ class DemoMoreInfos extends PolymerElement {
.filters { .filters {
margin-left: 60px; margin-left: 60px;
} }
ha-formfield {
margin-right: 16px;
}
</style> </style>
<app-toolbar> <app-toolbar>
<div class="filters"> <div class="filters">
<ha-formfield label="Show entities"> <ha-switch checked="{{_showConfig}}">Show entity</ha-switch>
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
</ha-switch>
</ha-formfield>
<ha-formfield label="Dark theme">
<ha-switch on-change="_darkThemeToggled"> </ha-switch>
</ha-formfield>
</div> </div>
</app-toolbar> </app-toolbar>
<div id="container"> <div class="cards">
<div class="cards"> <template is="dom-repeat" items="[[entities]]">
<template is="dom-repeat" items="[[entities]]"> <demo-more-info
<demo-more-info entity-id="[[item]]"
entity-id="[[item]]" show-config="[[_showConfig]]"
show-config="[[_showConfig]]" hass="[[hass]]"
hass="[[hass]]" ></demo-more-info>
></demo-more-info> </template>
</template>
</div>
</div> </div>
`; `;
} }
@@ -68,16 +51,6 @@ class DemoMoreInfos extends PolymerElement {
}, },
}; };
} }
_showConfigToggled(ev) {
this._showConfig = ev.target.checked;
}
_darkThemeToggled(ev) {
applyThemesOnElement(this.$.container, { themes: {} }, "default", {
dark: ev.target.checked,
});
}
} }
customElements.define("demo-more-infos", DemoMoreInfos); customElements.define("demo-more-infos", DemoMoreInfos);

View File

@@ -7,8 +7,8 @@ export const createMediaPlayerEntities = () => [
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead", media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
supported_features: 64063, supported_features: 195135,
entity_picture: "/images/album_cover_2.jpg", entity_picture: "/images/album_cover_2.jpg",
media_duration: 300, media_duration: 300,
media_position: 50, media_position: 50,
@@ -24,8 +24,8 @@ export const createMediaPlayerEntities = () => [
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead", media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media // Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 195135, supported_features: 64063,
entity_picture: "/images/album_cover.jpg", entity_picture: "/images/album_cover.jpg",
media_duration: 300, media_duration: 300,
media_position: 0, media_position: 0,

View File

@@ -1,72 +0,0 @@
import { getEntity } from "../../../src/fake_data/entity";
export const createPlantEntities = () => [
getEntity("plant", "lemon_tree", "ok", {
problem: "none",
sensors: {
moisture: "sensor.lemon_tree_moisture",
battery: "sensor.lemon_tree_battery",
temperature: "sensor.lemon_tree_temperature",
conductivity: "sensor.lemon_tree_conductivity",
brightness: "sensor.lemon_tree_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 95,
temperature: 15.6,
conductivity: 1,
brightness: 12,
max_brightness: 20,
friendly_name: "Lemon Tree",
}),
getEntity("plant", "apple_tree", "ok", {
problem: "brightness",
sensors: {
moisture: "sensor.apple_tree_moisture",
battery: "sensor.apple_tree_battery",
temperature: "sensor.apple_tree_temperature",
conductivity: "sensor.apple_tree_conductivity",
brightness: "sensor.apple_tree_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 2,
temperature: 15.6,
conductivity: 1,
brightness: 25,
max_brightness: 20,
friendly_name: "Apple Tree",
}),
getEntity("plant", "sunflowers", "ok", {
problem: "moisture, temperature, conductivity",
sensors: {
moisture: "sensor.sunflowers_moisture",
temperature: "sensor.sunflowers_temperature",
conductivity: "sensor.sunflowers_conductivity",
brightness: "sensor.sunflowers_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
conductivity: "μS/cm",
},
moisture: 54,
temperature: 15.6,
conductivity: 1,
brightness: 25,
entity_picture: "/images/sunflowers.jpg",
}),
];

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -76,19 +71,35 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-alarm-panel-card") class DemoAlarmPanelEntity extends PolymerElement {
class DemoAlarmPanelEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html`
<demo-cards
protected render(): TemplateResult { id="demos"
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
hass.updateTranslations(null, "en"); type: Object,
hass.updateTranslations("lovelace", "en"); value: CONFIGS,
},
hass: Object,
};
}
public ready() {
super.ready();
this._setupDemo();
}
private async _setupDemo() {
const hass = provideHass(this.$.demos);
await hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -58,19 +53,31 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-conditional-card") class DemoConditional extends PolymerElement {
class DemoConditional extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html`
<demo-cards
protected render(): TemplateResult { id="demos"
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -222,19 +217,24 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-entities-card") class DemoEntities extends PolymerElement {
class DemoEntities extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -25,10 +20,10 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "With Name (defined in card)", heading: "With Name",
config: ` config: `
- type: button - type: button
name: Custom Name name: Bedroom
entity: light.bed_light entity: light.bed_light
`, `,
}, },
@@ -37,7 +32,7 @@ const CONFIGS = [
config: ` config: `
- type: button - type: button
entity: light.bed_light entity: light.bed_light
icon: mdi:tools icon: mdi:hotel
`, `,
}, },
{ {
@@ -53,7 +48,7 @@ const CONFIGS = [
config: ` config: `
- type: button - type: button
entity: light.bed_light entity: light.bed_light
tap_action: tap_action:
action: toggle action: toggle
`, `,
}, },
@@ -74,19 +69,31 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-entity-button-card") class DemoButtonEntity extends PolymerElement {
class DemoButtonEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html`
<demo-cards
protected render(): TemplateResult { id="demos"
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -48,7 +43,7 @@ const ENTITIES = [
const CONFIGS = [ const CONFIGS = [
{ {
heading: "Unfiltered controller", heading: "Controller",
config: ` config: `
- type: entities - type: entities
entities: entities:
@@ -58,7 +53,7 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "Filtered entities card", heading: "Basic",
config: ` config: `
- type: entity-filter - type: entity-filter
entities: entities:
@@ -74,27 +69,7 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: 'With "entities" card config', heading: "With card config",
config: `
- type: entity-filter
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
state_filter:
- "on"
- not_home
card:
type: entities
title: Custom Title
show_header_toggle: false
`,
},
{
heading: 'With "glance" card config',
config: ` config: `
- type: entity-filter - type: entity-filter
entities: entities:
@@ -109,27 +84,31 @@ const CONFIGS = [
- not_home - not_home
card: card:
type: glance type: glance
show_state: true show_state: false
title: Custom Title
`, `,
}, },
]; ];
@customElement("demo-hui-entity-filter-card") class DemoFilter extends PolymerElement {
class DemoEntityFilter extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }
customElements.define("demo-hui-entity-filter-card", DemoEntityFilter); customElements.define("demo-hui-entity-filter-card", DemoFilter);

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -112,19 +107,24 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-gauge-card") class DemoGaugeEntity extends PolymerElement {
class DemoGaugeEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -82,8 +77,7 @@ const CONFIGS = [
heading: "With title", heading: "With title",
config: ` config: `
- type: glance - type: glance
title: Custom title title: This is glance
columns: 4
entities: entities:
- device_tracker.demo_paulus - device_tracker.demo_paulus
- media_player.living_room - media_player.living_room
@@ -110,10 +104,9 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "No entity names", heading: "No name",
config: ` config: `
- type: glance - type: glance
columns: 4
show_name: false show_name: false
entities: entities:
- device_tracker.demo_paulus - device_tracker.demo_paulus
@@ -126,10 +119,9 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "No state labels", heading: "No state",
config: ` config: `
- type: glance - type: glance
columns: 4
show_state: false show_state: false
entities: entities:
- device_tracker.demo_paulus - device_tracker.demo_paulus
@@ -142,10 +134,9 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "No names and no state labels", heading: "No name and no state",
config: ` config: `
- type: glance - type: glance
columns: 4
show_name: false show_name: false
show_state: false show_state: false
entities: entities:
@@ -159,24 +150,47 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "Custom name + custom icon", heading: "Custom name, custom icon",
config: ` config: `
- type: glance - type: glance
columns: 4
entities: entities:
- entity: device_tracker.demo_paulus - entity: device_tracker.demo_paulus
name: ¯\\_(ツ)_/¯ name: ¯\\_(ツ)_/¯
icon: mdi:home-assistant icon: mdi:home-assistant
- entity: media_player.living_room - media_player.living_room
name: ¯\\_(ツ)_/¯ - sun.sun
icon: mdi:home-assistant - cover.kitchen_window
- entity: light.kitchen_lights
icon: mdi:alarm-light
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "Custom tap action",
config: `
- type: glance
entities:
- entity: lock.kitchen_door
tap_action:
type: toggle
- entity: light.ceiling_lights
tap_action:
action: call-service
service: light.turn_on
service_data:
entity_id: light.ceiling_lights
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
`, `,
}, },
{ {
heading: "Selectively hidden name", heading: "Selectively hidden name",
config: ` config: `
- type: glance - type: glance
columns: 4
entities: entities:
- device_tracker.demo_paulus - device_tracker.demo_paulus
- entity: media_player.living_room - entity: media_player.living_room
@@ -185,51 +199,45 @@ const CONFIGS = [
- entity: cover.kitchen_window - entity: cover.kitchen_window
name: name:
- light.kitchen_lights - light.kitchen_lights
- entity: lock.kitchen_door
name:
- light.ceiling_lights
`, `,
}, },
{ {
heading: "Custom tap action", heading: "Primary theme",
config: ` config: `
- type: glance - type: glance
columns: 4 theming: primary
entities: entities:
- entity: lock.kitchen_door - device_tracker.demo_paulus
name: Custom - media_player.living_room
tap_action: - sun.sun
type: toggle - cover.kitchen_window
- entity: light.ceiling_lights - light.kitchen_lights
name: Custom - lock.kitchen_door
tap_action: - light.ceiling_lights
action: call-service
service: light.turn_on
service_data:
entity_id: light.ceiling_lights
- entity: sun.sun
name: Regular
- entity: light.kitchen_lights
name: Regular
`, `,
}, },
]; ];
@customElement("demo-hui-glance-card") class DemoPicEntity extends PolymerElement {
class DemoGlanceEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }
customElements.define("demo-hui-glance-card", DemoGlanceEntity); customElements.define("demo-hui-glance-card", DemoPicEntity);

View File

@@ -1,4 +1,6 @@
import { customElement, html, LitElement, TemplateResult } from "lit-element"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards"; import "../components/demo-cards";
const CONFIGS = [ const CONFIGS = [
@@ -35,10 +37,18 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-iframe-card") class DemoIframe extends PolymerElement {
class DemoIframe extends LitElement { static get template() {
protected render(): TemplateResult { return html` <demo-cards configs="[[_configs]]"></demo-cards> `;
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; }
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -68,19 +63,24 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-light-card") class DemoLightEntity extends PolymerElement {
class DemoLightEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -166,19 +161,31 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-map-card") class DemoMap extends PolymerElement {
class DemoMap extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html`
<demo-cards
protected render(): TemplateResult { id="demos"
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { mockTemplate } from "../../../demo/src/stubs/template"; import { mockTemplate } from "../../../demo/src/stubs/template";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -259,19 +254,23 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-markdown-card") class DemoMarkdown extends PolymerElement {
class DemoMarkdown extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
hass.updateTranslations(null, "en"); type: Object,
hass.updateTranslations("lovelace", "en"); value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
mockTemplate(hass); mockTemplate(hass);
} }
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players"; import { createMediaPlayerEntities } from "../data/media_players";
@@ -151,33 +146,35 @@ const CONFIGS = [
entity: media_player.receiver_off entity: media_player.receiver_off
`, `,
}, },
{
heading: "Grid Full Size",
config: `
- type: grid
columns: 1
cards:
- type: media-control
entity: media_player.music_paused
`,
},
]; ];
@customElement("demo-hui-media-control-card") class DemoHuiMediControlCard extends PolymerElement {
class DemoHuiMediaControlCard extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html`
<demo-cards
protected render(): TemplateResult { id="demos"
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(createMediaPlayerEntities()); hass.addEntities(createMediaPlayerEntities());
} }
} }
customElements.define("demo-hui-media-control-card", DemoHuiMediaControlCard); customElements.define("demo-hui-media-control-card", DemoHuiMediControlCard);

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players"; import { createMediaPlayerEntities } from "../data/media_players";
@@ -31,9 +26,9 @@ const CONFIGS = [
- entity: media_player.android_cast - entity: media_player.android_cast
name: Screen casting name: Screen casting
- entity: media_player.image_display - entity: media_player.image_display
name: Digital Picture Frame name: Digital Picture Frame
- entity: media_player.sonos_idle - entity: media_player.sonos_idle
name: Sonos Idle name: Sonos Idle
- entity: media_player.idle_browse_media - entity: media_player.idle_browse_media
name: Idle waiting for Browse Media name: Idle waiting for Browse Media
- entity: media_player.theater_off - entity: media_player.theater_off
@@ -43,7 +38,7 @@ const CONFIGS = [
- entity: media_player.theater_off_static - entity: media_player.theater_off_static
name: Player Off (cannot be switched on) name: Player Off (cannot be switched on)
- entity: media_player.theater_on_static - entity: media_player.theater_on_static
name: Player On (cannot be switched off) name: Player On (cannot be switched off)
- entity: media_player.idle - entity: media_player.idle
name: Player Idle name: Player Idle
- entity: media_player.playing - entity: media_player.playing
@@ -60,21 +55,33 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-media-player-row") class DemoHuiMediaPlayerRows extends PolymerElement {
class DemoHuiMediaPlayerRow extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html`
<demo-cards
protected render(): TemplateResult { id="demos"
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(createMediaPlayerEntities()); hass.addEntities(createMediaPlayerEntities());
} }
} }
customElements.define("demo-hui-media-player-row", DemoHuiMediaPlayerRow); customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows);

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -130,21 +125,26 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-picture-elements-card") class DemoPicElements extends PolymerElement {
class DemoPictureElements extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }
customElements.define("demo-hui-picture-elements-card", DemoPictureElements); customElements.define("demo-hui-picture-elements-card", DemoPicElements);

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -85,21 +80,26 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-picture-entity-card") class DemoPicEntity extends PolymerElement {
class DemoPictureEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }
customElements.define("demo-hui-picture-entity-card", DemoPictureEntity); customElements.define("demo-hui-picture-entity-card", DemoPicEntity);

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -126,21 +121,26 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-picture-glance-card") class DemoPicGlance extends PolymerElement {
class DemoPictureGlance extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }
customElements.define("demo-hui-picture-glance-card", DemoPictureGlance); customElements.define("demo-hui-picture-glance-card", DemoPicGlance);

View File

@@ -1,55 +0,0 @@
import {
customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
import { createPlantEntities } from "../data/plants";
const CONFIGS = [
{
heading: "Basic example",
config: `
- type: plant-status
entity: plant.lemon_tree
`,
},
{
heading: "Problem (too bright) + low battery",
config: `
- type: plant-status
entity: plant.apple_tree
`,
},
{
heading: "With picture + multiple problems",
config: `
- type: plant-status
entity: plant.sunflowers
name: Sunflowers Name Overwrite
`,
},
];
@customElement("demo-hui-plant-card")
export class DemoPlantEntity extends LitElement {
@query("#demos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(createPlantEntities());
}
}
customElements.define("demo-hui-plant-card", DemoPlantEntity);

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -25,19 +20,24 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-shopping-list-card") class DemoShoppingListEntity extends PolymerElement {
class DemoShoppingListEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.mockAPI("shopping_list", () => [ hass.mockAPI("shopping_list", () => [
{ name: "list", id: 1, complete: false }, { name: "list", id: 1, complete: false },

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../demo/src/stubs/history"; import { mockHistory } from "../../../demo/src/stubs/history";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -137,19 +132,24 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-stack-card") class DemoStack extends PolymerElement {
class DemoStack extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
mockHistory(hass); mockHistory(hass);
} }

View File

@@ -1,11 +1,6 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -79,19 +74,24 @@ const CONFIGS = [
}, },
]; ];
@customElement("demo-hui-thermostat-card") class DemoThermostatEntity extends PolymerElement {
class DemoThermostatEntity extends LitElement { static get template() {
@query("#demos") private _demoRoot!: HTMLElement; return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,29 +1,12 @@
import { import { html } from "@polymer/polymer/lib/utils/html-tag";
customElement, /* eslint-plugin-disable lit */
html, import { PolymerElement } from "@polymer/polymer/polymer-element";
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
} from "../../../src/data/light";
import "../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { import { provideHass } from "../../../src/fake_data/provide_hass";
MockHomeAssistant,
provideHass,
} from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos"; import "../components/demo-more-infos";
import "../../../src/dialogs/more-info/more-info-content";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {
@@ -31,52 +14,38 @@ const ENTITIES = [
}), }),
getEntity("light", "kitchen_light", "on", { getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light", friendly_name: "Brightness Light",
brightness: 200, brightness: 80,
supported_features: SUPPORT_BRIGHTNESS, supported_features: SUPPORT_BRIGHTNESS,
}), }),
getEntity("light", "color_temperature_light", "on", {
friendly_name: "White Color Temperature Light",
brightness: 128,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_BRIGHTNESS + SUPPORT_COLOR_TEMP,
}),
getEntity("light", "color_effectslight", "on", {
friendly_name: "Color Effets Light",
brightness: 255,
hs_color: [30, 100],
white_value: 36,
supported_features:
SUPPORT_BRIGHTNESS +
SUPPORT_EFFECT +
SUPPORT_FLASH +
SUPPORT_COLOR +
SUPPORT_TRANSITION +
SUPPORT_WHITE_VALUE,
effect_list: ["random", "colorloop"],
}),
]; ];
@customElement("demo-more-info-light") class DemoMoreInfoLight extends PolymerElement {
class DemoMoreInfoLight extends LitElement { static get template() {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html` return html`
<demo-more-infos <demo-more-infos
.hass=${this.hass} hass="[[hass]]"
.entities=${ENTITIES.map((ent) => ent.entityId)} entities="[[_entities]]"
></demo-more-infos> ></demo-more-infos>
`; `;
} }
protected firstUpdated(changedProperties: PropertyValues) { static get properties() {
super.firstUpdated(changedProperties); return {
const hass = provideHass(this._demoRoot); _entities: {
hass.updateTranslations(null, "en"); type: Array,
value: ENTITIES.map((ent) => ent.entityId),
},
};
}
public ready() {
super.ready();
this._setupDemo();
}
private async _setupDemo() {
const hass = provideHass(this);
await hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
} }
} }

View File

@@ -1,10 +1,9 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { customElement, html, LitElement, TemplateResult } from "lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../src/data/lovelace"; import { ActionHandlerEvent } from "../../../src/data/lovelace";
import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive"; import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive";
@customElement("demo-util-long-press")
export class DemoUtilLongPress extends LitElement { export class DemoUtilLongPress extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
@@ -21,7 +20,7 @@ export class DemoUtilLongPress extends LitElement {
<textarea></textarea> <textarea></textarea>
<div>Try pressing and scrolling too!</div> <div>(try pressing and scrolling too!)</div>
</ha-card> </ha-card>
` `
)} )}
@@ -63,3 +62,5 @@ export class DemoUtilLongPress extends LitElement {
`; `;
} }
} }
customElements.define("demo-util-long-press", DemoUtilLongPress);

View File

@@ -14,51 +14,54 @@ import "../../src/styles/polymer-ha-style";
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
import { DEMOS } from "../build/import-demos"; import { DEMOS } from "../build/import-demos";
const fixPath = (path) => path.substr(2, path.length - 5);
class HaGallery extends PolymerElement { class HaGallery extends PolymerElement {
static get template() { static get template() {
return html` return html`
<style include="iron-positioning ha-style"> <style include="iron-positioning ha-style">
:host { :host {
-ms-user-select: initial; -ms-user-select: initial;
-webkit-user-select: initial; -webkit-user-select: initial;
-moz-user-select: initial; -moz-user-select: initial;
} }
app-header-layout { app-header-layout {
min-height: 100vh; min-height: 100vh;
} }
ha-icon-button.invisible { ha-icon-button.invisible {
visibility: hidden; visibility: hidden;
} }
.pickers { .pickers {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: start; align-items: start;
} }
.pickers ha-card { .pickers ha-card {
width: 400px; width: 400px;
display: block; display: block;
margin: 16px 8px; margin: 16px 8px;
} }
.pickers ha-card:last-child { .pickers ha-card:last-child {
margin-bottom: 16px; margin-bottom: 16px;
} }
.intro { .intro {
margin: -1em 0; margin: -1em 0;
} }
p a { p a {
color: var(--primary-color); color: var(--primary-color);
} }
a {
color: var(--primary-text-color);
text-decoration: none;
}
a {
color: var(--primary-text-color);
text-decoration: none;
}
</style> </style>
<app-header-layout> <app-header-layout>
@@ -67,42 +70,32 @@ class HaGallery extends PolymerElement {
<ha-icon-button <ha-icon-button
icon="hass:arrow-left" icon="hass:arrow-left"
on-click="_backTapped" on-click="_backTapped"
class$="[[_computeHeaderButtonClass(_demo)]]" class$='[[_computeHeaderButtonClass(_demo)]]'
></ha-icon-button> ></ha-icon-button>
<div main-title> <div main-title>[[_withDefault(_demo, "Home Assistant Gallery")]]</div>
[[_withDefault(_demo, "Home Assistant Gallery")]]
</div>
</app-toolbar> </app-toolbar>
</app-header> </app-header>
<div class="content"> <div class='content'>
<div id="demo"></div> <div id='demo'></div>
<template is="dom-if" if="[[!_demo]]"> <template is='dom-if' if='[[!_demo]]'>
<div class="pickers"> <div class='pickers'>
<ha-card header="Lovelace Card Demos"> <ha-card header="Lovelace card demos">
<div class="card-content intro"> <div class='card-content intro'>
<p> <p>
Lovelace has many different cards. Each card allows the user Lovelace has many different cards. Each card allows the user to tell a different story about what is going on in their house. These cards are very customizable, as no household is the same.
to tell a different story about what is going on in their
house. These cards are very customizable, as no household is
the same.
</p> </p>
<p> <p>
This gallery helps our developers and designers to see all This gallery helps our developers and designers to see all the different states that each card can be in.
the different states that each card can be in.
</p> </p>
<p> <p>
Check Check <a href='https://www.home-assistant.io/lovelace'>the official website</a> for instructions on how to get started with Lovelace.</a>.
<a href="https://www.home-assistant.io/lovelace"
>the official website</a
>
for instructions on how to get started with Lovelace.
</p> </p>
</div> </div>
<template is="dom-repeat" items="[[_lovelaceDemos]]"> <template is='dom-repeat' items='[[_lovelaceDemos]]'>
<a href="#[[item]]"> <a href='#[[item]]'>
<paper-item> <paper-item>
<paper-item-body>{{ item }}</paper-item-body> <paper-item-body>{{ item }}</paper-item-body>
<ha-icon icon="hass:chevron-right"></ha-icon> <ha-icon icon="hass:chevron-right"></ha-icon>
@@ -111,14 +104,14 @@ class HaGallery extends PolymerElement {
</template> </template>
</ha-card> </ha-card>
<ha-card header="More Info Demos"> <ha-card header="More Info demos">
<div class="card-content intro"> <div class='card-content intro'>
<p> <p>
More info screens show up when an entity is clicked. More info screens show up when an entity is clicked.
</p> </p>
</div> </div>
<template is="dom-repeat" items="[[_moreInfoDemos]]"> <template is='dom-repeat' items='[[_moreInfoDemos]]'>
<a href="#[[item]]"> <a href='#[[item]]'>
<paper-item> <paper-item>
<paper-item-body>{{ item }}</paper-item-body> <paper-item-body>{{ item }}</paper-item-body>
<ha-icon icon="hass:chevron-right"></ha-icon> <ha-icon icon="hass:chevron-right"></ha-icon>
@@ -127,14 +120,14 @@ class HaGallery extends PolymerElement {
</template> </template>
</ha-card> </ha-card>
<ha-card header="Util Demos"> <ha-card header="Util demos">
<div class="card-content intro"> <div class='card-content intro'>
<p> <p>
Test pages for our utility functions. Test pages for our utility functions.
</p> </p>
</div> </div>
<template is="dom-repeat" items="[[_utilDemos]]"> <template is='dom-repeat' items='[[_utilDemos]]'>
<a href="#[[item]]"> <a href='#[[item]]'>
<paper-item> <paper-item>
<paper-item-body>{{ item }}</paper-item-body> <paper-item-body>{{ item }}</paper-item-body>
<ha-icon icon="hass:chevron-right"></ha-icon> <ha-icon icon="hass:chevron-right"></ha-icon>
@@ -146,10 +139,7 @@ class HaGallery extends PolymerElement {
</template> </template>
</div> </div>
</app-header-layout> </app-header-layout>
<notification-manager <notification-manager hass=[[_fakeHass]] id='notifications'></notification-manager>
hass="[[_fakeHass]]"
id="notifications"
></notification-manager>
`; `;
} }

View File

@@ -12,7 +12,6 @@ import {
} from "lit-element"; } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/common/search/search-input"; import "../../../src/common/search/search-input";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
@@ -23,7 +22,6 @@ import {
reloadHassioAddons, reloadHassioAddons,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
@@ -192,11 +190,7 @@ class HassioAddonStore extends LitElement {
private async _loadData() { private async _loadData() {
try { try {
const [addonsInfo, supervisor] = await Promise.all([ const addonsInfo = await fetchHassioAddonsInfo(this.hass);
fetchHassioAddonsInfo(this.hass),
fetchHassioSupervisorInfo(this.hass),
]);
fireEvent(this, "supervisor-update", { supervisor });
this._repos = addonsInfo.repositories; this._repos = addonsInfo.repositories;
this._repos.sort(sortRepos); this._repos.sort(sortRepos);
this._addons = addonsInfo.addons; this._addons = addonsInfo.addons;

View File

@@ -7,14 +7,13 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "web-animations-js/web-animations-next-lite.min"; import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
HassioAddonDetails, HassioAddonDetails,
@@ -29,6 +28,7 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/buttons/ha-progress-button";
@customElement("hassio-addon-audio") @customElement("hassio-addon-audio")
class HassioAddonAudio extends LitElement { class HassioAddonAudio extends LitElement {

View File

@@ -7,11 +7,11 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/ha-circular-progress";
import "./hassio-addon-audio"; import "./hassio-addon-audio";
import "./hassio-addon-config"; import "./hassio-addon-config";
import "./hassio-addon-network"; import "./hassio-addon-network";
@@ -26,41 +26,28 @@ class HassioAddonConfigDashboard extends LitElement {
if (!this.addon) { if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`; return html`<ha-circular-progress active></ha-circular-progress>`;
} }
const hasOptions =
this.addon.options && Object.keys(this.addon.options).length;
const hasSchema =
this.addon.schema && Object.keys(this.addon.schema).length;
return html` return html`
<div class="content"> <div class="content">
${hasOptions || hasSchema || this.addon.network || this.addon.audio <hassio-addon-config
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-config>
${this.addon.network
? html` ? html`
${hasOptions || hasSchema <hassio-addon-network
? html` .hass=${this.hass}
<hassio-addon-config .addon=${this.addon}
.hass=${this.hass} ></hassio-addon-network>
.addon=${this.addon}
></hassio-addon-config>
`
: ""}
${this.addon.network
? html`
<hassio-addon-network
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-network>
`
: ""}
${this.addon.audio
? html`
<hassio-addon-audio
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-audio>
`
: ""}
` `
: "This add-on does not expose configuration for you to mess with.... 👋"} : ""}
${this.addon.audio
? html`
<hassio-addon-audio
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-audio>
`
: ""}
</div> </div>
`; `;
} }

View File

@@ -14,12 +14,12 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "../../../src/components/ha-circular-progress";
import { import {
fetchHassioAddonInfo, fetchHassioAddonInfo,
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/components/ha-circular-progress";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage"; import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";

View File

@@ -7,8 +7,8 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-circular-progress";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";

View File

@@ -69,7 +69,7 @@ const STAGE_ICON = {
const PERMIS_DESC = { const PERMIS_DESC = {
stage: { stage: {
title: "Add-on Stage", title: "Add-on Stage",
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`, description: `Add-ons can have one of three stages:\n\n<ha-svg-icon .path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon .path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon .path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
}, },
rating: { rating: {
title: "Add-on Security Rating", title: "Add-on Security Rating",

View File

@@ -7,8 +7,8 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-circular-progress";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";

View File

@@ -27,8 +27,6 @@ declare global {
} }
} }
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-snapshot") @customElement("hassio-upload-snapshot")
export class HassioUploadSnapshot extends LitElement { export class HassioUploadSnapshot extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@@ -53,20 +51,6 @@ export class HassioUploadSnapshot extends LitElement {
private async _uploadFile(ev) { private async _uploadFile(ev) {
const file = ev.detail.files[0]; const file = ev.detail.files[0];
if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, {
title: "Snapshot file is too big",
text: html`The maximum allowed filesize is 1GB.<br />
<a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install"
target="_blank"
>Have a look here on how to restore it.</a
>`,
confirmText: "ok",
});
return;
}
if (!["application/x-tar"].includes(file.type)) { if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Unsupported file format", title: "Unsupported file format",

View File

@@ -1,87 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../src/components/ha-bar";
import "../../../src/components/ha-settings-row";
import { roundWithOneDecimal } from "../../../src/util/calculate";
@customElement("supervisor-metric")
class SupervisorMetric extends LitElement {
@property({ type: Number }) public value!: number;
@property({ type: String }) public description!: string;
@property({ type: String }) public tooltip?: string;
protected render(): TemplateResult {
const roundedValue = roundWithOneDecimal(this.value);
return html`<ha-settings-row>
<span slot="heading">
${this.description}
</span>
<div slot="description" title="${this.tooltip ?? ""}">
<span class="value">
${roundedValue}%
</span>
<ha-bar
class="${classMap({
"target-warning": roundedValue > 50,
"target-critical": roundedValue > 85,
})}"
.value=${this.value}
></ha-bar>
</div>
</ha-settings-row>`;
}
static get styles(): CSSResult {
return css`
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row > div[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
}
ha-bar {
--ha-bar-primary-color: var(
--hassio-bar-ok-color,
var(--success-color)
);
}
.target-warning {
--ha-bar-primary-color: var(
--hassio-bar-warning-color,
var(--warning-color)
);
}
.target-critical {
--ha-bar-primary-color: var(
--hassio-bar-critical-color,
var(--error-color)
);
}
.value {
width: 42px;
padding-right: 4px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-metric": SupervisorMetric;
}
}

View File

@@ -12,7 +12,7 @@ import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { compare } from "../../../src/common/string/compare"; import { compare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { HassioAddonInfo } from "../../../src/data/hassio/addon";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
@@ -22,14 +22,14 @@ import { hassioStyle } from "../resources/hassio-style";
class HassioAddons extends LitElement { class HassioAddons extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public addons?: HassioAddonInfo[];
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="content"> <div class="content">
<h1>Add-ons</h1> <h1>Add-ons</h1>
<div class="card-group"> <div class="card-group">
${!this.supervisor.supervisor.addons?.length ${!this.addons?.length
? html` ? html`
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
@@ -41,7 +41,7 @@ class HassioAddons extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: this.supervisor.supervisor.addons : this.addons
.sort((a, b) => compare(a.name, b.name)) .sort((a, b) => compare(a.name, b.name))
.map( .map(
(addon) => html` (addon) => html`

View File

@@ -7,7 +7,11 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
@@ -19,12 +23,16 @@ import "./hassio-update";
class HassioDashboard extends LitElement { class HassioDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
@@ -39,11 +47,13 @@ class HassioDashboard extends LitElement {
<div class="content"> <div class="content">
<hassio-update <hassio-update
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this.supervisor} .hassInfo=${this.hassInfo}
.supervisorInfo=${this.supervisorInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-update> ></hassio-update>
<hassio-addons <hassio-addons
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this.supervisor} .addons=${this.supervisorInfo.addons}
></hassio-addons> ></hassio-addons>
</div> </div>
</hass-tabs-subpage> </hass-tabs-subpage>

View File

@@ -23,7 +23,6 @@ import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -36,20 +35,31 @@ import { hassioStyle } from "../resources/hassio-style";
export class HassioUpdate extends LitElement { export class HassioUpdate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public hassInfo?: HassioHomeAssistantInfo;
private _pendingUpdates = memoizeOne((supervisor: Supervisor): number => { @property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo;
return Object.keys(supervisor).filter(
(value) => supervisor[value].update_available @property({ attribute: false }) public supervisorInfo?: HassioSupervisorInfo;
).length;
}); private _pendingUpdates = memoizeOne(
(
core?: HassioHomeAssistantInfo,
supervisor?: HassioSupervisorInfo,
os?: HassioHassOSInfo
): number => {
return [core, supervisor, os].filter(
(value) => !!value && value?.update_available
).length;
}
);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisor) { const updatesAvailable = this._pendingUpdates(
return html``; this.hassInfo,
} this.supervisorInfo,
this.hassOsInfo
);
const updatesAvailable = this._pendingUpdates(this.supervisor);
if (!updatesAvailable) { if (!updatesAvailable) {
return html``; return html``;
} }
@@ -64,24 +74,26 @@ export class HassioUpdate extends LitElement {
<div class="card-group"> <div class="card-group">
${this._renderUpdateCard( ${this._renderUpdateCard(
"Home Assistant Core", "Home Assistant Core",
this.supervisor.core, this.hassInfo!,
"hassio/homeassistant/update", "hassio/homeassistant/update",
`https://${ `https://${
this.supervisor.core.version_latest.includes("b") ? "rc" : "www" this.hassInfo?.version_latest.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/` }.home-assistant.io/latest-release-notes/`
)} )}
${this._renderUpdateCard( ${this._renderUpdateCard(
"Supervisor", "Supervisor",
this.supervisor.supervisor, this.supervisorInfo!,
"hassio/supervisor/update", "hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}` `https://github.com//home-assistant/hassio/releases/tag/${
this.supervisorInfo!.version_latest
}`
)} )}
${this.supervisor.host.features.includes("hassos") ${this.hassOsInfo
? this._renderUpdateCard( ? this._renderUpdateCard(
"Operating System", "Operating System",
this.supervisor.os, this.hassOsInfo,
"hassio/os/update", "hassio/os/update",
`https://github.com//home-assistant/hassos/releases/tag/${this.supervisor.os.version_latest}` `https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
) )
: ""} : ""}
</div> </div>

View File

@@ -3,9 +3,9 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";

View File

@@ -11,7 +11,10 @@ export const showHassioMarkdownDialog = (
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-markdown", dialogTag: "dialog-hassio-markdown",
dialogImport: () => import("./dialog-hassio-markdown"), dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"
),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -1,7 +1,5 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button"; import "@material/mwc-icon-button";
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-tab"; import "@material/mwc-tab";
import "@material/mwc-tab-bar"; import "@material/mwc-tab-bar";
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
@@ -18,22 +16,18 @@ import {
} from "lit-element"; } from "lit-element";
import { cache } from "lit-html/directives/cache"; import { cache } from "lit-html/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-chips";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-formfield"; import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-radio"; import "../../../../src/components/ha-radio";
import type { HaRadio } from "../../../../src/components/ha-radio";
import "../../../../src/components/ha-related-items"; import "../../../../src/components/ha-related-items";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
AccessPoints,
accesspointScan,
NetworkInterface, NetworkInterface,
updateNetworkInterface, updateNetworkInterface,
WifiConfiguration,
} from "../../../../src/data/hassio/network"; } from "../../../../src/data/hassio/network";
import { import {
showAlertDialog, showAlertDialog,
@@ -44,51 +38,54 @@ import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { HassioNetworkDialogParams } from "./show-dialog-network"; import { HassioNetworkDialogParams } from "./show-dialog-network";
const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network") @customElement("dialog-hassio-network")
export class DialogHassioNetwork extends LitElement export class DialogHassioNetwork extends LitElement
implements HassDialog<HassioNetworkDialogParams> { implements HassDialog<HassioNetworkDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _accessPoints?: AccessPoints; @internalProperty() private _prosessing = false;
@internalProperty() private _curTabIndex = 0;
@internalProperty() private _dirty = false;
@internalProperty() private _interface?: NetworkInterface;
@internalProperty() private _interfaces!: NetworkInterface[];
@internalProperty() private _params?: HassioNetworkDialogParams; @internalProperty() private _params?: HassioNetworkDialogParams;
@internalProperty() private _processing = false; @internalProperty() private _network!: {
interface: string;
data: NetworkInterface;
}[];
@internalProperty() private _scanning = false; @internalProperty() private _curTabIndex = 0;
@internalProperty() private _wifiConfiguration?: WifiConfiguration; @internalProperty() private _device?: {
interface: string;
data: NetworkInterface;
};
@internalProperty() private _dirty = false;
public async showDialog(params: HassioNetworkDialogParams): Promise<void> { public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
this._params = params; this._params = params;
this._dirty = false; this._dirty = false;
this._curTabIndex = 0; this._curTabIndex = 0;
this._interfaces = params.network.interfaces.sort((a, b) => { this._network = Object.keys(params.network?.interfaces)
return a.primary > b.primary ? -1 : 1; .map((device) => ({
}); interface: device,
this._interface = { ...this._interfaces[this._curTabIndex] }; data: params.network.interfaces[device],
}))
.sort((a, b) => {
return a.data.primary > b.data.primary ? -1 : 1;
});
this._device = this._network[this._curTabIndex];
this._device.data.nameservers = String(this._device.data.nameservers);
await this.updateComplete; await this.updateComplete;
} }
public closeDialog(): void { public closeDialog(): void {
this._params = undefined; this._params = undefined;
this._processing = false; this._prosessing = false;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._params || !this._interface) { if (!this._params || !this._network) {
return html``; return html``;
} }
@@ -110,11 +107,11 @@ export class DialogHassioNetwork extends LitElement
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
${this._interfaces.length > 1 ${this._network.length > 1
? html` <mwc-tab-bar ? html` <mwc-tab-bar
.activeIndex=${this._curTabIndex} .activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated} @MDCTabBar:activated=${this._handleTabActivated}
>${this._interfaces.map( >${this._network.map(
(device) => (device) =>
html`<mwc-tab html`<mwc-tab
.id=${device.interface} .id=${device.interface}
@@ -132,302 +129,81 @@ export class DialogHassioNetwork extends LitElement
private _renderTab() { private _renderTab() {
return html` <div class="form container"> return html` <div class="form container">
${IP_VERSIONS.map((version) => <ha-formfield label="DHCP">
this._interface![version] ? this._renderIPConfiguration(version) : "" <ha-radio
)} @change=${this._handleRadioValueChanged}
${this._interface?.type === "wireless" value="dhcp"
? html` name="method"
<ha-expansion-panel header="Wi-Fi" outlined> ?checked=${this._device!.data.method === "dhcp"}
${this._interface?.wifi?.ssid >
? html`<p>Connected to: ${this._interface?.wifi?.ssid}</p>` </ha-radio>
: ""} </ha-formfield>
<mwc-button <ha-formfield label="Static">
class="scan" <ha-radio
@click=${this._scanForAP} @change=${this._handleRadioValueChanged}
.disabled=${this._scanning} value="static"
> name="method"
${this._scanning ?checked=${this._device!.data.method === "static"}
? html`<ha-circular-progress active size="small"> >
</ha-circular-progress>` </ha-radio>
: "Scan for accesspoints"} </ha-formfield>
</mwc-button> ${this._device!.data.method !== "dhcp"
${this._accessPoints && ? html` <paper-input
this._accessPoints.accesspoints &&
this._accessPoints.accesspoints.length !== 0
? html`
<mwc-list>
${this._accessPoints.accesspoints
.filter((ap) => ap.ssid)
.map(
(ap) =>
html`
<mwc-list-item
twoline
@click=${this._selectAP}
.activated=${ap.ssid ===
this._wifiConfiguration?.ssid}
.ap=${ap}
>
<span>${ap.ssid}</span>
<span slot="secondary">
${ap.mac} - Strength: ${ap.signal}
</span>
</mwc-list-item>
`
)}
</mwc-list>
`
: ""}
${this._wifiConfiguration
? html`
<div class="radio-row">
<ha-formfield label="open">
<ha-radio
@change=${this._handleRadioValueChangedAp}
.ap=${this._wifiConfiguration}
value="open"
name="auth"
.checked=${this._wifiConfiguration.auth ===
undefined ||
this._wifiConfiguration.auth === "open"}
>
</ha-radio>
</ha-formfield>
<ha-formfield label="wep">
<ha-radio
@change=${this._handleRadioValueChangedAp}
.ap=${this._wifiConfiguration}
value="wep"
name="auth"
.checked=${this._wifiConfiguration.auth === "wep"}
>
</ha-radio>
</ha-formfield>
<ha-formfield label="wpa-psk">
<ha-radio
@change=${this._handleRadioValueChangedAp}
.ap=${this._wifiConfiguration}
value="wpa-psk"
name="auth"
.checked=${this._wifiConfiguration.auth ===
"wpa-psk"}
>
</ha-radio>
</ha-formfield>
</div>
${this._wifiConfiguration.auth === "wpa-psk" ||
this._wifiConfiguration.auth === "wep"
? html`
<paper-input
class="flex-auto"
type="password"
id="psk"
label="Password"
version="wifi"
@value-changed=${this
._handleInputValueChangedWifi}
>
</paper-input>
`
: ""}
`
: ""}
</ha-expansion-panel>
`
: ""}
${this._dirty
? html`<div class="warning">
If you are changing the Wi-Fi, IP or gateway addresses, you might
lose the connection!
</div>`
: ""}
</div>
<div class="buttons">
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
${this._processing
? html`<ha-circular-progress active size="small">
</ha-circular-progress>`
: "Save"}
</mwc-button>
</div>`;
}
private _selectAP(event) {
this._wifiConfiguration = event.currentTarget.ap;
this._dirty = true;
}
private async _scanForAP() {
if (!this._interface) {
return;
}
this._scanning = true;
try {
this._accessPoints = await accesspointScan(
this.hass,
this._interface.interface
);
} catch (err) {
showAlertDialog(this, {
title: "Failed to scan for accesspoints",
text: extractApiErrorMessage(err),
});
} finally {
this._scanning = false;
}
}
private _renderIPConfiguration(version: string) {
return html`
<ha-expansion-panel
.header=${`IPv${version.charAt(version.length - 1)}`}
outlined
>
<div class="radio-row">
<ha-formfield label="DHCP">
<ha-radio
@change=${this._handleRadioValueChanged}
.version=${version}
value="auto"
name="${version}method"
.checked=${this._interface![version]?.method === "auto"}
>
</ha-radio>
</ha-formfield>
<ha-formfield label="Static">
<ha-radio
@change=${this._handleRadioValueChanged}
.version=${version}
value="static"
name="${version}method"
.checked=${this._interface![version]?.method === "static"}
>
</ha-radio>
</ha-formfield>
<ha-formfield label="Disabled" class="warning">
<ha-radio
@change=${this._handleRadioValueChanged}
.version=${version}
value="disabled"
name="${version}method"
.checked=${this._interface![version]?.method === "disabled"}
>
</ha-radio>
</ha-formfield>
</div>
${this._interface![version].method === "static"
? html`
<paper-input
class="flex-auto" class="flex-auto"
id="address" id="ip_address"
label="IP address/Netmask" label="IP address/Netmask"
.version=${version} .value="${this._device!.data.ip_address}"
.value=${this._toString(this._interface![version].address)}
@value-changed=${this._handleInputValueChanged} @value-changed=${this._handleInputValueChanged}
> ></paper-input>
</paper-input>
<paper-input <paper-input
class="flex-auto" class="flex-auto"
id="gateway" id="gateway"
label="Gateway address" label="Gateway address"
.version=${version} .value="${this._device!.data.gateway}"
.value=${this._interface![version].gateway}
@value-changed=${this._handleInputValueChanged} @value-changed=${this._handleInputValueChanged}
> ></paper-input>
</paper-input>
<paper-input <paper-input
class="flex-auto" class="flex-auto"
id="nameservers" id="nameservers"
label="DNS servers" label="DNS servers"
.version=${version} .value="${this._device!.data.nameservers as string}"
.value=${this._toString(this._interface![version].nameservers)}
@value-changed=${this._handleInputValueChanged} @value-changed=${this._handleInputValueChanged}
> ></paper-input>
</paper-input> NB!: If you are changing IP or gateway addresses, you might lose
` the connection.`
: ""} : ""}
</ha-expansion-panel> </div>
`; <div class="buttons">
} <mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
<mwc-button @click=${this._updateNetwork} ?disabled=${!this._dirty}>
_toArray(data: string | string[]): string[] { ${this._prosessing
if (Array.isArray(data)) { ? html`<ha-circular-progress active></ha-circular-progress>`
if (data && typeof data[0] === "string") { : "Update"}
data = data[0]; </mwc-button>
} </div>`;
}
if (!data) {
return [];
}
if (typeof data === "string") {
return data.replace(/ /g, "").split(",");
}
return data;
}
_toString(data: string | string[]): string {
if (!data) {
return "";
}
if (Array.isArray(data)) {
return data.join(", ");
}
return data;
} }
private async _updateNetwork() { private async _updateNetwork() {
this._processing = true; this._prosessing = true;
let interfaceOptions: Partial<NetworkInterface> = {}; let options: Partial<NetworkInterface> = {
method: this._device!.data.method,
IP_VERSIONS.forEach((version) => { };
interfaceOptions[version] = { if (options.method !== "dhcp") {
method: this._interface![version]?.method || "auto", options = {
...options,
address: this._device!.data.ip_address,
gateway: this._device!.data.gateway,
dns: String(this._device!.data.nameservers).split(","),
}; };
if (this._interface![version]?.method === "static") {
interfaceOptions[version] = {
...interfaceOptions[version],
address: this._toArray(this._interface![version]?.address),
gateway: this._interface![version]?.gateway,
nameservers: this._toArray(this._interface![version]?.nameservers),
};
}
});
if (this._wifiConfiguration) {
interfaceOptions = {
...interfaceOptions,
wifi: {
ssid: this._wifiConfiguration.ssid,
mode: this._wifiConfiguration.mode,
auth: this._wifiConfiguration.auth || "open",
},
};
if (interfaceOptions.wifi!.auth !== "open") {
interfaceOptions.wifi = {
...interfaceOptions.wifi,
psk: this._wifiConfiguration.psk,
};
}
} }
interfaceOptions.enabled =
this._wifiConfiguration !== undefined ||
interfaceOptions.ipv4?.method !== "disabled" ||
interfaceOptions.ipv6?.method !== "disabled";
try { try {
await updateNetworkInterface( await updateNetworkInterface(this.hass, this._device!.interface, options);
this.hass,
this._interface!.interface,
interfaceOptions
);
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to change network settings", title: "Failed to change network settings",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
this._processing = false; this._prosessing = false;
return; return;
} }
this._params?.loadData(); this._params?.loadData();
@@ -443,73 +219,40 @@ export class DialogHassioNetwork extends LitElement
dismissText: "no", dismissText: "no",
}); });
if (!confirm) { if (!confirm) {
this.requestUpdate("_interface"); this.requestUpdate("_device");
return; return;
} }
} }
this._curTabIndex = ev.detail.index; this._curTabIndex = ev.detail.index;
this._interface = { ...this._interfaces[ev.detail.index] }; this._device = this._network[ev.detail.index];
this._device.data.nameservers = String(this._device.data.nameservers);
} }
private _handleRadioValueChanged(ev: CustomEvent): void { private _handleRadioValueChanged(ev: CustomEvent): void {
const value = (ev.target as any).value as "disabled" | "auto" | "static"; const value = (ev.target as HaRadio).value as "dhcp" | "static";
const version = (ev.target as any).version as "ipv4" | "ipv6";
if ( if (!value || !this._device || this._device!.data.method === value) {
!value ||
!this._interface ||
this._interface[version]!.method === value
) {
return; return;
} }
this._dirty = true; this._dirty = true;
this._interface[version]!.method = value; this._device!.data.method = value;
this.requestUpdate("_interface"); this.requestUpdate("_device");
}
private _handleRadioValueChangedAp(ev: CustomEvent): void {
const value = ((ev.target as any).value as string) as
| "open"
| "wep"
| "wpa-psk";
this._wifiConfiguration!.auth = value;
this._dirty = true;
this.requestUpdate("_wifiConfiguration");
} }
private _handleInputValueChanged(ev: CustomEvent): void { private _handleInputValueChanged(ev: CustomEvent): void {
const value: string | null | undefined = (ev.target as PaperInputElement) const value: string | null | undefined = (ev.target as PaperInputElement)
.value; .value;
const version = (ev.target as any).version as "ipv4" | "ipv6";
const id = (ev.target as PaperInputElement).id; const id = (ev.target as PaperInputElement).id;
if ( if (!value || !this._device || this._device.data[id] === value) {
!value ||
!this._interface ||
this._toString(this._interface[version]![id]) === this._toString(value)
) {
return; return;
} }
this._dirty = true; this._dirty = true;
this._interface[version]![id] = value;
}
private _handleInputValueChangedWifi(ev: CustomEvent): void { this._device.data[id] = value;
const value: string | null | undefined = (ev.target as PaperInputElement)
.value;
const id = (ev.target as PaperInputElement).id;
if (
!value ||
!this._wifiConfiguration ||
this._wifiConfiguration![id] === value
) {
return;
}
this._dirty = true;
this._wifiConfiguration![id] = value;
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@@ -556,16 +299,12 @@ export class DialogHassioNetwork extends LitElement
--mdc-theme-primary: var(--error-color); --mdc-theme-primary: var(--error-color);
} }
mwc-button.scan {
margin-left: 8px;
}
:host([rtl]) app-toolbar { :host([rtl]) app-toolbar {
direction: rtl; direction: rtl;
text-align: right; text-align: right;
} }
.container { .container {
padding: 0 8px 4px; padding: 20px 24px;
} }
.form { .form {
margin-bottom: 53px; margin-bottom: 53px;
@@ -583,24 +322,6 @@ export class DialogHassioNetwork extends LitElement
padding-bottom: max(env(safe-area-inset-bottom), 8px); padding-bottom: max(env(safe-area-inset-bottom), 8px);
background-color: var(--mdc-theme-surface, #fff); background-color: var(--mdc-theme-surface, #fff);
} }
.warning {
color: var(--error-color);
--primary-color: var(--error-color);
}
div.warning {
margin: 12px 4px -12px;
}
ha-expansion-panel {
--expansion-panel-summary-padding: 0 16px;
margin: 4px 0;
}
paper-input {
padding: 0 14px;
}
mwc-list-item {
--mdc-list-side-padding: 10px;
}
`, `,
]; ];
} }

View File

@@ -13,7 +13,10 @@ export const showNetworkDialog = (
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-network", dialogTag: "dialog-hassio-network",
dialogImport: () => import("./dialog-hassio-network"), dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-network" */ "./dialog-hassio-network"
),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -4,7 +4,10 @@ import "./dialog-hassio-registries";
export const showRegistriesDialog = (element: HTMLElement): void => { export const showRegistriesDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-registries", dialogTag: "dialog-hassio-registries",
dialogImport: () => import("./dialog-hassio-registries"), dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-registries" */ "./dialog-hassio-registries"
),
dialogParams: {}, dialogParams: {},
}); });
}; };

View File

@@ -13,7 +13,10 @@ export const showRepositoriesDialog = (
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-repositories", dialogTag: "dialog-hassio-repositories",
dialogImport: () => import("./dialog-hassio-repositories"), dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-repositories" */ "./dialog-hassio-repositories"
),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -109,7 +109,7 @@ class HassioSnapshotDialog extends LitElement {
return html``; return html``;
} }
return html` return html`
<ha-dialog open @closing=${this._closeDialog} .heading=${true}> <ha-dialog open stacked @closing=${this._closeDialog} .heading=${true}>
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> <span slot="title">
@@ -191,37 +191,47 @@ class HassioSnapshotDialog extends LitElement {
: ""} : ""}
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""} ${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
<div class="button-row" slot="primaryAction"> <div>Actions:</div>
<mwc-button @click=${this._partialRestoreClicked}> ${!this._onboarding
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> ? html`<mwc-button
Restore Selected @click=${this._downloadClicked}
</mwc-button> slot="primaryAction"
${!this._onboarding >
? html` <ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon>
<mwc-button @click=${this._deleteClicked}> Download Snapshot
<ha-svg-icon .path=${mdiDelete} class="icon warning"> </mwc-button>`
</ha-svg-icon> : ""}
<span class="warning">Delete Snapshot</span>
</mwc-button> <mwc-button
` @click=${this._partialRestoreClicked}
: ""} slot="secondaryAction"
</div> >
<div class="button-row" slot="secondaryAction"> <ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
${this._snapshot.type === "full" Restore Selected
? html` </mwc-button>
<mwc-button @click=${this._fullRestoreClicked}> ${this._snapshot.type === "full"
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> ? html`
Restore Everything <mwc-button
</mwc-button> @click=${this._fullRestoreClicked}
` slot="secondaryAction"
: ""} >
${!this._onboarding <ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
? html`<mwc-button @click=${this._downloadClicked}> Wipe &amp; restore
<ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon> </mwc-button>
Download Snapshot `
</mwc-button>` : ""}
: ""} ${!this._onboarding
</div> ? html`<mwc-button
@click=${this._deleteClicked}
slot="secondaryAction"
>
<ha-svg-icon
.path=${mdiDelete}
class="icon warning"
></ha-svg-icon>
<span class="warning">Delete Snapshot</span>
</mwc-button>`
: ""}
</ha-dialog> </ha-dialog>
`; `;
} }
@@ -235,14 +245,6 @@ class HassioSnapshotDialog extends LitElement {
display: block; display: block;
margin: 4px; margin: 4px;
} }
mwc-button ha-svg-icon {
margin-right: 4px;
}
.button-row {
display: grid;
gap: 8px;
margin-right: 8px;
}
.details { .details {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
@@ -250,6 +252,10 @@ class HassioSnapshotDialog extends LitElement {
.error { .error {
color: var(--error-color); color: var(--error-color);
} }
.buttons {
display: flex;
flex-direction: column;
}
.buttons li { .buttons li {
list-style-type: none; list-style-type: none;
} }

View File

@@ -12,7 +12,10 @@ export const showHassioSnapshotDialog = (
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot", dialogTag: "dialog-hassio-snapshot",
dialogImport: () => import("./dialog-hassio-snapshot"), dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"
),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -13,7 +13,10 @@ export const showSnapshotUploadDialog = (
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot-upload", dialogTag: "dialog-hassio-snapshot-upload",
dialogImport: () => import("./dialog-hassio-snapshot-upload"), dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-snapshot-upload" */ "./dialog-hassio-snapshot-upload"
),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -1,7 +1,6 @@
// Compat needs to be first import
import "../../src/resources/compatibility"; import "../../src/resources/compatibility";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch"; import "../../src/resources/safari-14-attachshadow-patch";
import "../../src/resources/roboto";
import "./hassio-main"; import "./hassio-main";
const styleEl = document.createElement("style"); const styleEl = document.createElement("style");

View File

@@ -1,22 +1,29 @@
import { customElement, html, property, PropertyValues } from "lit-element"; import {
import { atLeastVersion } from "../../src/common/config/version"; html,
PropertyValues,
customElement,
LitElement,
property,
} from "lit-element";
import "./hassio-router";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { HomeAssistant, Route } from "../../src/types";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event"; import { fireEvent } from "../../src/common/dom/fire_event";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { HomeAssistant, Route } from "../../src/types"; import { atLeastVersion } from "../../src/common/config/version";
import "./hassio-router";
import { SupervisorBaseElement } from "./supervisor-base-element";
@customElement("hassio-main") @customElement("hassio-main")
export class HassioMain extends SupervisorBaseElement { export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public panel!: HassioPanelInfo; @property() public panel!: HassioPanelInfo;
@property({ type: Boolean }) public narrow!: boolean; @property() public narrow!: boolean;
@property({ attribute: false }) public route?: Route; @property() public route?: Route;
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
@@ -70,13 +77,9 @@ export class HassioMain extends SupervisorBaseElement {
} }
protected render() { protected render() {
if (!this.supervisor || !this.hass) {
return html``;
}
return html` return html`
<hassio-router <hassio-router
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this.supervisor}
.route=${this.route} .route=${this.route}
.panel=${this.panel} .panel=${this.panel}
.narrow=${this.narrow} .narrow=${this.narrow}

View File

@@ -1,5 +1,10 @@
import { customElement, property } from "lit-element"; import { customElement, property } from "lit-element";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
@@ -16,12 +21,20 @@ import "./system/hassio-system";
class HassioPanelRouter extends HassRouterPage { class HassioPanelRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public supervisorInfo?: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo?: HassioHostInfo;
@property({ attribute: false }) public hassInfo?: HassioHomeAssistantInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
routes: { routes: {
dashboard: { dashboard: {
@@ -41,9 +54,13 @@ class HassioPanelRouter extends HassRouterPage {
protected updatePageEl(el) { protected updatePageEl(el) {
el.hass = this.hass; el.hass = this.hass;
el.supervisor = this.supervisor;
el.route = this.route; el.route = this.route;
el.narrow = this.narrow; el.narrow = this.narrow;
el.supervisorInfo = this.supervisorInfo;
el.hassioInfo = this.hassioInfo;
el.hostInfo = this.hostInfo;
el.hassInfo = this.hassInfo;
el.hassOsInfo = this.hassOsInfo;
} }
} }

View File

@@ -1,13 +1,18 @@
import { import {
css,
CSSResult,
customElement, customElement,
html, html,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
css,
CSSResult,
} from "lit-element"; } from "lit-element";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router"; import "./hassio-panel-router";
@@ -15,19 +20,34 @@ import "./hassio-panel-router";
class HassioPanel extends LitElement { class HassioPanel extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisorInfo) {
return html``;
}
return html` return html`
<hassio-panel-router <hassio-panel-router
.hass=${this.hass}
.supervisor=${this.supervisor}
.route=${this.route} .route=${this.route}
.hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.supervisorInfo=${this.supervisorInfo}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo}
.hassInfo=${this.hassInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-panel-router> ></hassio-panel-router>
`; `;
} }

View File

@@ -1,6 +1,24 @@
import { customElement, property } from "lit-element"; import {
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; customElement,
import { Supervisor } from "../../src/data/supervisor/supervisor"; property,
internalProperty,
PropertyValues,
} from "lit-element";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
} from "../../src/data/hassio/host";
import {
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
fetchHassioInfo,
HassioHomeAssistantInfo,
HassioInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
@@ -14,11 +32,9 @@ import "./hassio-panel";
class HassioRouter extends HassRouterPage { class HassioRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property() public panel!: HassioPanelInfo;
@property({ attribute: false }) public panel!: HassioPanelInfo; @property() public narrow!: boolean;
@property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it. // Hass.io has a page with tabs, so we route all non-matching routes to it.
@@ -35,22 +51,47 @@ class HassioRouter extends HassRouterPage {
system: "dashboard", system: "dashboard",
addon: { addon: {
tag: "hassio-addon-dashboard", tag: "hassio-addon-dashboard",
load: () => import("./addon-view/hassio-addon-dashboard"), load: () =>
import(
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
),
}, },
ingress: { ingress: {
tag: "hassio-ingress-view", tag: "hassio-ingress-view",
load: () => import("./ingress-view/hassio-ingress-view"), load: () =>
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
}, },
}, },
}; };
@internalProperty() private _supervisorInfo?: HassioSupervisorInfo;
@internalProperty() private _hostInfo?: HassioHostInfo;
@internalProperty() private _hassioInfo?: HassioInfo;
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
@internalProperty() private _hassInfo?: HassioHomeAssistantInfo;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
protected updatePageEl(el) { protected updatePageEl(el) {
// the tabs page does its own routing so needs full route. // the tabs page does its own routing so needs full route.
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail; const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
el.hass = this.hass; el.hass = this.hass;
el.supervisor = this.supervisor;
el.narrow = this.narrow; el.narrow = this.narrow;
el.supervisorInfo = this._supervisorInfo;
el.hassioInfo = this._hassioInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
el.route = route; el.route = route;
if (el.localName === "hassio-ingress-view") { if (el.localName === "hassio-ingress-view") {
@@ -61,12 +102,45 @@ class HassioRouter extends HassRouterPage {
private async _fetchData() { private async _fetchData() {
if (this.panel.config && this.panel.config.ingress) { if (this.panel.config && this.panel.config.ingress) {
this._redirectIngress(this.panel.config.ingress); this._redirectIngress(this.panel.config.ingress);
return;
}
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hassioInfo = hassioInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
} }
} }
private _redirectIngress(addonSlug: string) { private _redirectIngress(addonSlug: string) {
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` }; this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
} }
private _apiCalled(ev) {
if (!ev.detail.success) {
return;
}
let tries = 1;
const tryUpdate = () => {
this._fetchData().catch(() => {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
}
} }
declare global { declare global {

View File

@@ -1,17 +1,14 @@
import { mdiMenu } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import { import {
fetchHassioAddonInfo, fetchHassioAddonInfo,
HassioAddonDetails, HassioAddonDetails,
@@ -20,10 +17,13 @@ import {
createHassioSession, createHassioSession,
validateHassioSession, validateHassioSession,
} from "../../../src/data/hassio/ingress"; } from "../../../src/data/hassio/ingress";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { navigate } from "../../../src/common/navigate";
import { mdiMenu } from "@mdi/js";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("hassio-ingress-view") @customElement("hassio-ingress-view")
class HassioIngressView extends LitElement { class HassioIngressView extends LitElement {

View File

@@ -26,6 +26,7 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
@@ -40,7 +41,7 @@ import {
HassioSnapshot, HassioSnapshot,
reloadHassioSnapshots, reloadHassioSnapshots,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { PolymerChangedEvent } from "../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../src/polymer-types";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
@@ -66,7 +67,7 @@ class HassioSnapshots extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@internalProperty() private _snapshotName = ""; @internalProperty() private _snapshotName = "";
@@ -264,8 +265,8 @@ class HassioSnapshots extends LitElement {
} }
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
if (changedProps.has("supervisor")) { if (changedProps.has("supervisorInfo")) {
this._addonList = this.supervisor.supervisor.addons this._addonList = this.supervisorInfo.addons
.map((addon) => ({ .map((addon) => ({
slug: addon.slug, slug: addon.slug,
name: addon.name, name: addon.name,
@@ -371,6 +372,7 @@ class HassioSnapshots extends LitElement {
await createHassioPartialSnapshot(this.hass, data); await createHassioPartialSnapshot(this.hass, data);
} }
this._updateSnapshots(); this._updateSnapshots();
fireEvent(this, "hass-api-called", { success: true, response: null });
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }

View File

@@ -1,69 +0,0 @@
import { LitElement, property, PropertyValues } from "lit-element";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
} from "../../src/data/hassio/host";
import { fetchNetworkInfo } from "../../src/data/hassio/network";
import { fetchHassioResolution } from "../../src/data/hassio/resolution";
import {
fetchHassioHomeAssistantInfo,
fetchHassioInfo,
fetchHassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
declare global {
interface HASSDomEvents {
"supervisor-update": Partial<Supervisor>;
}
}
export class SupervisorBaseElement extends urlSyncMixin(
ProvideHassLitMixin(LitElement)
) {
@property({ attribute: false }) public supervisor?: Supervisor;
protected _updateSupervisor(obj: Partial<Supervisor>): void {
this.supervisor = { ...this.supervisor!, ...obj };
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._initSupervisor();
this.addEventListener("supervisor-update", (ev) =>
this._updateSupervisor(ev.detail)
);
}
private async _initSupervisor(): Promise<void> {
const [
supervisor,
host,
core,
info,
os,
network,
resolution,
] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
fetchHassioHassOsInfo(this.hass),
fetchNetworkInfo(this.hass),
fetchHassioResolution(this.hass),
]);
this.supervisor = {
supervisor,
host,
core,
info,
os,
network,
resolution,
};
}
}

View File

@@ -1,246 +0,0 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import {
extractApiErrorMessage,
fetchHassioStats,
HassioStats,
} from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import "../components/supervisor-metric";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-core-info")
class HassioCoreInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _metrics?: HassioStats;
protected render(): TemplateResult | void {
const metrics = [
{
description: "Core CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: "Core RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
)}`,
},
];
return html`
<ha-card header="Core">
<div class="card-content">
<div>
<ha-settings-row>
<span slot="heading">
Version
</span>
<span slot="description">
core-${this.supervisor.core.version}
</span>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Newest Version
</span>
<span slot="description">
core-${this.supervisor.core.version_latest}
</span>
${this.supervisor.core.update_available
? html`
<ha-progress-button
title="Update the core"
@click=${this._coreUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
</div>
<div>
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
`
)}
</div>
</div>
<div class="card-actions">
<ha-progress-button
slot="primaryAction"
class="warning"
@click=${this._coreRestart}
title="Restart Home Assistant Core"
>
Restart Core
</ha-progress-button>
</div>
</ha-card>
`;
}
protected firstUpdated(): void {
this._loadData();
}
private async _loadData(): Promise<void> {
this._metrics = await fetchHassioStats(this.hass, "core");
}
private async _coreRestart(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Restart Home Assistant Core",
text: "Are you sure you want to restart Home Assistant Core",
confirmText: "restart",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await restartCore(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to restart Home Assistant Core",
text: extractApiErrorMessage(err),
});
} finally {
button.progress = false;
}
}
private async _coreUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Update Home Assistant Core",
text: `Are you sure you want to update Home Assistant Core to version ${this.supervisor.core.version_latest}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await updateCore(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to update Home Assistant Core",
text: extractApiErrorMessage(err),
});
} finally {
button.progress = false;
}
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
.card-actions {
height: 48px;
border-top: none;
display: flex;
justify-content: flex-end;
align-items: center;
}
.card-content {
display: flex;
flex-direction: column;
height: calc(100% - 124px);
justify-content: space-between;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row[three-line] {
height: 74px;
}
ha-settings-row > span[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
.warning {
--mdc-theme-primary: var(--error-color);
}
ha-button-menu {
color: var(--secondary-text-color);
--mdc-menu-min-width: 200px;
}
@media (min-width: 563px) {
paper-listbox {
max-height: 150px;
overflow: auto;
}
}
paper-item {
cursor: pointer;
min-height: 35px;
}
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-core-info": HassioCoreInfo;
}
}

View File

@@ -8,12 +8,12 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
@@ -27,6 +27,8 @@ import {
changeHostOptions, changeHostOptions,
configSyncOS, configSyncOS,
fetchHassioHostInfo, fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo as HassioHostInfoType,
rebootHost, rebootHost,
shutdownHost, shutdownHost,
updateOS, updateOS,
@@ -35,7 +37,7 @@ import {
fetchNetworkInfo, fetchNetworkInfo,
NetworkInfo, NetworkInfo,
} from "../../../src/data/hassio/network"; } from "../../../src/data/hassio/network";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { HassioInfo } from "../../../src/data/hassio/supervisor";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -43,11 +45,6 @@ import {
} from "../../../src/dialogs/generic/show-dialog-box"; } from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import {
getValueInPercentage,
roundWithOneDecimal,
} from "../../../src/util/calculate";
import "../components/supervisor-metric";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network"; import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@@ -56,132 +53,114 @@ import { hassioStyle } from "../resources/hassio-style";
class HassioHostInfo extends LitElement { class HassioHostInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property() public hostInfo!: HassioHostInfoType;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
@internalProperty() public _networkInfo?: NetworkInfo;
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const primaryIpAddress = this.supervisor.host.features.includes("network") const primaryIpAddress = this.hostInfo.features.includes("network")
? this._primaryIpAddress(this.supervisor.network!) ? this._primaryIpAddress(this._networkInfo!)
: ""; : "";
const metrics = [
{
description: "Used Space",
value: this._getUsedSpace(
this.supervisor.host.disk_used,
this.supervisor.host.disk_total
),
tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`,
},
];
return html` return html`
<ha-card header="Host"> <ha-card header="Host System">
<div class="card-content"> <div class="card-content">
<div> ${this.hostInfo.features.includes("hostname")
${this.supervisor.host.features.includes("hostname") ? html`<ha-settings-row>
? html`<ha-settings-row> <span slot="heading">
<span slot="heading"> Hostname
Hostname </span>
</span> <span slot="description">
<span slot="description"> ${this.hostInfo.hostname}
${this.supervisor.host.hostname} </span>
</span> <mwc-button
<mwc-button title="Change the hostname"
title="Change the hostname" label="Change"
label="Change" @click=${this._changeHostnameClicked}
@click=${this._changeHostnameClicked} >
> </mwc-button>
</mwc-button> </ha-settings-row>`
</ha-settings-row>` : ""}
: ""} ${this.hostInfo.features.includes("network")
${this.supervisor.host.features.includes("network") ? html` <ha-settings-row>
? html` <ha-settings-row> <span slot="heading">
<span slot="heading"> IP Address
IP Address </span>
</span> <span slot="description">
<span slot="description"> ${primaryIpAddress}
${primaryIpAddress} </span>
</span> <mwc-button
<mwc-button title="Change the network"
title="Change the network" label="Change"
label="Change" @click=${this._changeNetworkClicked}
@click=${this._changeNetworkClicked} >
> </mwc-button>
</mwc-button> </ha-settings-row>`
</ha-settings-row>` : ""}
: ""}
<ha-settings-row> <ha-settings-row>
<span slot="heading"> <span slot="heading">
Operating System Operating System
</span> </span>
<span slot="description"> <span slot="description">
${this.supervisor.host.operating_system} ${this.hostInfo.operating_system}
</span> </span>
${this.supervisor.os.update_available ${this.hostInfo.features.includes("hassos") &&
? html` this.hassOsInfo.update_available
<ha-progress-button ? html`
title="Update the host OS" <ha-progress-button
@click=${this._osUpdate} title="Update the host OS"
> @click=${this._osUpdate}
Update >
</ha-progress-button> Update
` </ha-progress-button>
: ""}
</ha-settings-row>
${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
Docker version
</span>
<span slot="description">
${this.supervisor.info.docker}
</span>
</ha-settings-row>`
: ""}
${this.supervisor.host.deployment
? html`<ha-settings-row>
<span slot="heading">
Deployment
</span>
<span slot="description">
${this.supervisor.host.deployment}
</span>
</ha-settings-row>`
: ""}
</div>
<div>
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
` `
)} : ""}
</div> </ha-settings-row>
${!this.hostInfo.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
Docker version
</span>
<span slot="description">
${this.hassioInfo.docker}
</span>
</ha-settings-row>`
: ""}
${this.hostInfo.deployment
? html`<ha-settings-row>
<span slot="heading">
Deployment
</span>
<span slot="description">
${this.hostInfo.deployment}
</span>
</ha-settings-row>`
: ""}
</div> </div>
<div class="card-actions"> <div class="card-actions">
${this.supervisor.host.features.includes("reboot") ${this.hostInfo.features.includes("reboot")
? html` ? html`
<ha-progress-button <ha-progress-button
title="Reboot the host OS" title="Reboot the host OS"
class="warning" class="warning"
@click=${this._hostReboot} @click=${this._hostReboot}
> >
Reboot Host Reboot
</ha-progress-button> </ha-progress-button>
` `
: ""} : ""}
${this.supervisor.host.features.includes("shutdown") ${this.hostInfo.features.includes("shutdown")
? html` ? html`
<ha-progress-button <ha-progress-button
title="Shutdown the host OS" title="Shutdown the host OS"
class="warning" class="warning"
@click=${this._hostShutdown} @click=${this._hostShutdown}
> >
Shutdown Host Shutdown
</ha-progress-button> </ha-progress-button>
` `
: ""} : ""}
@@ -196,7 +175,7 @@ class HassioHostInfo extends LitElement {
<mwc-list-item title="Show a list of hardware"> <mwc-list-item title="Show a list of hardware">
Hardware Hardware
</mwc-list-item> </mwc-list-item>
${this.supervisor.host.features.includes("hassos") ${this.hostInfo.features.includes("hassos")
? html`<mwc-list-item ? html`<mwc-list-item
title="Load HassOS configs or updates from USB" title="Load HassOS configs or updates from USB"
> >
@@ -213,15 +192,13 @@ class HassioHostInfo extends LitElement {
this._loadData(); this._loadData();
} }
private _getUsedSpace = memoizeOne((used: number, total: number) =>
roundWithOneDecimal(getValueInPercentage(used, 0, total))
);
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => { private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
if (!network_info || !network_info.interfaces) { if (!network_info) {
return ""; return "";
} }
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0]; return Object.keys(network_info?.interfaces)
.map((device) => network_info.interfaces[device])
.find((device) => device.primary)?.ip_address;
}); });
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) { private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
@@ -339,13 +316,13 @@ class HassioHostInfo extends LitElement {
private async _changeNetworkClicked(): Promise<void> { private async _changeNetworkClicked(): Promise<void> {
showNetworkDialog(this, { showNetworkDialog(this, {
network: this.supervisor.network!, network: this._networkInfo!,
loadData: () => this._loadData(), loadData: () => this._loadData(),
}); });
} }
private async _changeHostnameClicked(): Promise<void> { private async _changeHostnameClicked(): Promise<void> {
const curHostname: string = this.supervisor.host.hostname; const curHostname: string = this.hostInfo.hostname;
const hostname = await showPromptDialog(this, { const hostname = await showPromptDialog(this, {
title: "Change Hostname", title: "Change Hostname",
inputLabel: "Please enter a new hostname:", inputLabel: "Please enter a new hostname:",
@@ -356,8 +333,7 @@ class HassioHostInfo extends LitElement {
if (hostname && hostname !== curHostname) { if (hostname && hostname !== curHostname) {
try { try {
await changeHostOptions(this.hass, { hostname }); await changeHostOptions(this.hass, { hostname });
const host = await fetchHassioHostInfo(this.hass); this.hostInfo = await fetchHassioHostInfo(this.hass);
fireEvent(this, "supervisor-update", { host });
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Setting hostname failed", title: "Setting hostname failed",
@@ -370,8 +346,7 @@ class HassioHostInfo extends LitElement {
private async _importFromUSB(): Promise<void> { private async _importFromUSB(): Promise<void> {
try { try {
await configSyncOS(this.hass); await configSyncOS(this.hass);
const host = await fetchHassioHostInfo(this.hass); this.hostInfo = await fetchHassioHostInfo(this.hass);
fireEvent(this, "supervisor-update", { host });
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to import from USB", title: "Failed to import from USB",
@@ -381,8 +356,7 @@ class HassioHostInfo extends LitElement {
} }
private async _loadData(): Promise<void> { private async _loadData(): Promise<void> {
const network = await fetchNetworkInfo(this.hass); this._networkInfo = await fetchNetworkInfo(this.hass);
fireEvent(this, "supervisor-update", { network });
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@@ -403,12 +377,6 @@ class HassioHostInfo extends LitElement {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.card-content {
display: flex;
flex-direction: column;
height: calc(100% - 124px);
justify-content: space-between;
}
ha-settings-row { ha-settings-row {
padding: 0; padding: 0;
height: 54px; height: 54px;

View File

@@ -3,7 +3,6 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
@@ -13,32 +12,27 @@ import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
import { import { extractApiErrorMessage } from "../../../src/data/hassio/common";
extractApiErrorMessage, import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
fetchHassioStats, import { fetchHassioResolution } from "../../../src/data/hassio/resolution";
HassioStats,
} from "../../../src/data/hassio/common";
import { import {
fetchHassioSupervisorInfo, fetchHassioSupervisorInfo,
HassioSupervisorInfo as HassioSupervisorInfoType,
reloadSupervisor, reloadSupervisor,
restartSupervisor,
setSupervisorOption, setSupervisorOption,
SupervisorOptions, SupervisorOptions,
updateSupervisor, updateSupervisor,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box"; } from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import { documentationUrl } from "../../../src/util/documentation-url"; import { documentationUrl } from "../../../src/util/documentation-url";
import "../components/supervisor-metric";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON = { const ISSUES = {
container: { container: {
title: "Containers known to cause issues", title: "Containers known to cause issues",
url: "/more-info/unsupported/container", url: "/more-info/unsupported/container",
@@ -52,10 +46,6 @@ const UNSUPPORTED_REASON = {
title: "Docker Version", title: "Docker Version",
url: "/more-info/unsupported/docker_version", url: "/more-info/unsupported/docker_version",
}, },
job_conditions: {
title: "Ignored job conditions",
url: "/more-info/unsupported/job_conditions",
},
lxc: { title: "LXC", url: "/more-info/unsupported/lxc" }, lxc: { title: "LXC", url: "/more-info/unsupported/lxc" },
network_manager: { network_manager: {
title: "Network Manager", title: "Network Manager",
@@ -69,194 +59,122 @@ const UNSUPPORTED_REASON = {
systemd: { title: "Systemd", url: "/more-info/unsupported/systemd" }, systemd: { title: "Systemd", url: "/more-info/unsupported/systemd" },
}; };
const UNHEALTHY_REASON = {
privileged: {
title: "Supervisor is not privileged",
url: "/more-info/unsupported/privileged",
},
supervisor: {
title: "Supervisor was not able to update",
url: "/more-info/unhealthy/supervisor",
},
setup: {
title: "Setup of the Supervisor failed",
url: "/more-info/unhealthy/setup",
},
docker: {
title: "The Docker environment is not working properly",
url: "/more-info/unhealthy/docker",
},
};
@customElement("hassio-supervisor-info") @customElement("hassio-supervisor-info")
class HassioSupervisorInfo extends LitElement { class HassioSupervisorInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false })
public supervisorInfo!: HassioSupervisorInfoType;
@internalProperty() private _metrics?: HassioStats; @property() public hostInfo!: HassioHostInfoType;
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const metrics = [
{
description: "Supervisor CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: "Supervisor RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
)}`,
},
];
return html` return html`
<ha-card header="Supervisor"> <ha-card header="Supervisor">
<div class="card-content"> <div class="card-content">
<div> <ha-settings-row>
<ha-settings-row> <span slot="heading">
<span slot="heading"> Version
Version </span>
</span> <span slot="description">
<span slot="description"> ${this.supervisorInfo.version}
supervisor-${this.supervisor.supervisor.version} </span>
</span> </ha-settings-row>
</ha-settings-row> <ha-settings-row>
<ha-settings-row> <span slot="heading">
<span slot="heading"> Newest Version
Newest Version </span>
</span> <span slot="description">
<span slot="description"> ${this.supervisorInfo.version_latest}
supervisor-${this.supervisor.supervisor.version_latest} </span>
</span> ${this.supervisorInfo.update_available
${this.supervisor.supervisor.update_available ? html`
? html` <ha-progress-button
<ha-progress-button title="Update the supervisor"
title="Update the supervisor" @click=${this._supervisorUpdate}
@click=${this._supervisorUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Channel
</span>
<span slot="description">
${this.supervisor.supervisor.channel}
</span>
${this.supervisor.supervisor.channel === "beta"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get stable updates for Home Assistant, supervisor and host"
>
Leave beta channel
</ha-progress-button>
`
: this.supervisor.supervisor.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
Join beta channel
</ha-progress-button>
`
: ""}
</ha-settings-row>
${this.supervisor.supervisor.supported
? html` <ha-settings-row three-line>
<span slot="heading">
Share Diagnostics
</span>
<div slot="description" class="diagnostics-description">
Share crash reports and diagnostic information.
<button
class="link"
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
>
Learn more
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisor.supervisor.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
You are running an unsupported installation.
<button
class="link"
title="Learn more about how you can make your system compliant"
@click=${this._unsupportedDialog}
> >
Learn more Update
</button> </ha-progress-button>
</div>`}
${!this.supervisor.supervisor.healthy
? html`<div class="error">
Your installation is running in an unhealthy state.
<button
class="link"
title="Learn more about why your system is marked as unhealthy"
@click=${this._unhealthyDialog}
>
Learn more
</button>
</div>`
: ""}
</div>
<div class="metrics-block">
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
` `
)} : ""}
</div> </ha-settings-row>
<ha-settings-row>
<span slot="heading">
Channel
</span>
<span slot="description">
${this.supervisorInfo.channel}
</span>
${this.supervisorInfo.channel === "beta"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get stable updates for Home Assistant, supervisor and host"
>
Leave beta channel
</ha-progress-button>
`
: this.supervisorInfo.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
Join beta channel
</ha-progress-button>
`
: ""}
</ha-settings-row>
${this.supervisorInfo?.supported
? html` <ha-settings-row three-line>
<span slot="heading">
Share Diagnostics
</span>
<div slot="description" class="diagnostics-description">
Share crash reports and diagnostic information.
<button
class="link"
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
>
Learn more
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisorInfo.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
You are running an unsupported installation.
<button
class="link"
title="Learn more about how you can make your system compliant"
@click=${this._unsupportedDialog}
>
Learn more
</button>
</div>`}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button <ha-progress-button
@click=${this._supervisorReload} @click=${this._supervisorReload}
title="Reload parts of the Supervisor" title="Reload parts of the supervisor"
> >
Reload Supervisor Reload
</ha-progress-button>
<ha-progress-button
class="warning"
@click=${this._supervisorRestart}
title="Restart the Supervisor"
>
Restart Supervisor
</ha-progress-button> </ha-progress-button>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
protected firstUpdated(): void {
this._loadData();
}
private async _loadData(): Promise<void> {
this._metrics = await fetchHassioStats(this.hass, "supervisor");
}
private async _toggleBeta(ev: CustomEvent): Promise<void> { private async _toggleBeta(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any; const button = ev.currentTarget as any;
button.progress = true; button.progress = true;
if (this.supervisor.supervisor.channel === "stable") { if (this.supervisorInfo.channel === "stable") {
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: "WARNING", title: "WARNING",
text: html` Beta releases are for testers and early adopters and can text: html` Beta releases are for testers and early adopters and can
@@ -285,19 +203,18 @@ class HassioSupervisorInfo extends LitElement {
try { try {
const data: Partial<SupervisorOptions> = { const data: Partial<SupervisorOptions> = {
channel: channel: this.supervisorInfo.channel === "stable" ? "beta" : "stable",
this.supervisor.supervisor.channel === "stable" ? "beta" : "stable",
}; };
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
await this._reloadSupervisor(); await reloadSupervisor(this.hass);
fireEvent(this, "hass-api-called", { success: true, response: null });
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to set supervisor option", title: "Failed to set supervisor option",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
} finally {
button.progress = false;
} }
button.progress = false;
} }
private async _supervisorReload(ev: CustomEvent): Promise<void> { private async _supervisorReload(ev: CustomEvent): Promise<void> {
@@ -305,49 +222,15 @@ class HassioSupervisorInfo extends LitElement {
button.progress = true; button.progress = true;
try { try {
await this._reloadSupervisor(); await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to reload the supervisor", title: "Failed to reload the supervisor",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
} finally {
button.progress = false;
}
}
private async _reloadSupervisor(): Promise<void> {
await reloadSupervisor(this.hass);
const supervisor = await fetchHassioSupervisorInfo(this.hass);
fireEvent(this, "supervisor-update", { supervisor });
}
private async _supervisorRestart(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Restart the Supervisor",
text: "Are you sure you want to restart the Supervisor",
confirmText: "restart",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await restartSupervisor(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to restart the supervisor",
text: extractApiErrorMessage(err),
});
} finally {
button.progress = false;
} }
button.progress = false;
} }
private async _supervisorUpdate(ev: CustomEvent): Promise<void> { private async _supervisorUpdate(ev: CustomEvent): Promise<void> {
@@ -356,7 +239,7 @@ class HassioSupervisorInfo extends LitElement {
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: "Update Supervisor", title: "Update Supervisor",
text: `Are you sure you want to update supervisor to version ${this.supervisor.supervisor.version_latest}?`, text: `Are you sure you want to update supervisor to version ${this.supervisorInfo.version_latest}?`,
confirmText: "update", confirmText: "update",
dismissText: "cancel", dismissText: "cancel",
}); });
@@ -373,9 +256,8 @@ class HassioSupervisorInfo extends LitElement {
title: "Failed to update the supervisor", title: "Failed to update the supervisor",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
} finally {
button.progress = false;
} }
button.progress = false;
} }
private async _diagnosticsInformationDialog(): Promise<void> { private async _diagnosticsInformationDialog(): Promise<void> {
@@ -394,53 +276,22 @@ class HassioSupervisorInfo extends LitElement {
} }
private async _unsupportedDialog(): Promise<void> { private async _unsupportedDialog(): Promise<void> {
const resolution = await fetchHassioResolution(this.hass);
await showAlertDialog(this, { await showAlertDialog(this, {
title: "You are running an unsupported installation", title: "You are running an unsupported installation",
text: html`Below is a list of issues found with your installation, click text: html`Below is a list of issues found with your installation, click
on the links to learn how you can resolve the issues. <br /><br /> on the links to learn how you can resolve the issues. <br /><br />
<ul> <ul>
${this.supervisor.resolution.unsupported.map( ${resolution.unsupported.map(
(issue) => html` (issue) => html`
<li> <li>
${UNSUPPORTED_REASON[issue] ${ISSUES[issue]
? html`<a ? html`<a
href="${documentationUrl( href="${documentationUrl(this.hass, ISSUES[issue].url)}"
this.hass,
UNSUPPORTED_REASON[issue].url
)}"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
${UNSUPPORTED_REASON[issue].title} ${ISSUES[issue].title}
</a>`
: issue}
</li>
`
)}
</ul>`,
});
}
private async _unhealthyDialog(): Promise<void> {
await showAlertDialog(this, {
title: "Your installation is unhealthy",
text: html`Running an unhealthy installation will cause issues. Below is a
list of issues found with your installation, click on the links to learn
how you can resolve the issues. <br /><br />
<ul>
${this.supervisor.resolution.unhealthy.map(
(issue) => html`
<li>
${UNHEALTHY_REASON[issue]
? html`<a
href="${documentationUrl(
this.hass,
UNHEALTHY_REASON[issue].url
)}"
target="_blank"
rel="noreferrer"
>
${UNHEALTHY_REASON[issue].title}
</a>` </a>`
: issue} : issue}
</li> </li>
@@ -453,7 +304,7 @@ class HassioSupervisorInfo extends LitElement {
private async _toggleDiagnostics(): Promise<void> { private async _toggleDiagnostics(): Promise<void> {
try { try {
const data: SupervisorOptions = { const data: SupervisorOptions = {
diagnostics: !this.supervisor.supervisor?.diagnostics, diagnostics: !this.supervisorInfo?.diagnostics,
}; };
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
} catch (err) { } catch (err) {
@@ -482,15 +333,6 @@ class HassioSupervisorInfo extends LitElement {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.card-content {
display: flex;
flex-direction: column;
height: calc(100% - 124px);
justify-content: space-between;
}
.metrics-block {
margin-top: 16px;
}
button.link { button.link {
color: var(--primary-color); color: var(--primary-color);
} }

View File

@@ -0,0 +1,184 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-bar";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common";
import { HassioHostInfo } from "../../../src/data/hassio/host";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import {
getValueInPercentage,
roundWithOneDecimal,
} from "../../../src/util/calculate";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-system-metrics")
class HassioSystemMetrics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public hostInfo!: HassioHostInfo;
@internalProperty() private _supervisorMetrics?: HassioStats;
@internalProperty() private _coreMetrics?: HassioStats;
protected render(): TemplateResult | void {
const metrics = [
{
description: "Core CPU Usage",
value: this._coreMetrics?.cpu_percent,
},
{
description: "Core RAM Usage",
value: this._coreMetrics?.memory_percent,
tooltip: `${bytesToString(
this._coreMetrics?.memory_usage
)}/${bytesToString(this._coreMetrics?.memory_limit)}`,
},
{
description: "Supervisor CPU Usage",
value: this._supervisorMetrics?.cpu_percent,
},
{
description: "Supervisor RAM Usage",
value: this._supervisorMetrics?.memory_percent,
tooltip: `${bytesToString(
this._supervisorMetrics?.memory_usage
)}/${bytesToString(this._supervisorMetrics?.memory_limit)}`,
},
{
description: "Used Space",
value: this._getUsedSpace(this.hostInfo),
tooltip: `${this.hostInfo.disk_used} GB/${this.hostInfo.disk_total} GB`,
},
];
return html`
<ha-card header="System Metrics">
<div class="card-content">
${metrics.map((metric) =>
this._renderMetric(
metric.description,
metric.value ?? 0,
metric.tooltip
)
)}
</div>
</ha-card>
`;
}
protected firstUpdated(): void {
this._loadData();
}
private _renderMetric(
description: string,
value: number,
tooltip?: string
): TemplateResult {
const roundedValue = roundWithOneDecimal(value);
return html`<ha-settings-row>
<span slot="heading">
${description}
</span>
<div slot="description" title="${tooltip ?? ""}">
<span class="value">
${roundedValue}%
</span>
<ha-bar
class="${classMap({
"target-warning": roundedValue > 50,
"target-critical": roundedValue > 85,
})}"
.value=${value}
></ha-bar>
</div>
</ha-settings-row>`;
}
private _getUsedSpace = memoizeOne((hostInfo: HassioHostInfo) =>
roundWithOneDecimal(
getValueInPercentage(hostInfo.disk_used, 0, hostInfo.disk_total)
)
);
private async _loadData(): Promise<void> {
const [supervisor, core] = await Promise.all([
fetchHassioStats(this.hass, "supervisor"),
fetchHassioStats(this.hass, "core"),
]);
this._supervisorMetrics = supervisor;
this._coreMetrics = core;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row > div[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
}
ha-bar {
--ha-bar-primary-color: var(
--hassio-bar-ok-color,
var(--success-color)
);
}
.target-warning {
--ha-bar-primary-color: var(
--hassio-bar-warning-color,
var(--warning-color)
);
}
.target-critical {
--ha-bar-primary-color: var(
--hassio-bar-critical-color,
var(--error-color)
);
}
.value {
width: 42px;
padding-right: 4px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-system-metrics": HassioSystemMetrics;
}
}

View File

@@ -7,27 +7,40 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import {
HassioHassOSInfo,
HassioHostInfo,
} from "../../../src/data/hassio/host";
import {
HassioInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import "./hassio-core-info";
import "./hassio-host-info"; import "./hassio-host-info";
import "./hassio-supervisor-info"; import "./hassio-supervisor-info";
import "./hassio-supervisor-log"; import "./hassio-supervisor-log";
import "./hassio-system-metrics";
@customElement("hassio-system") @customElement("hassio-system")
class HassioSystem extends LitElement { class HassioSystem extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property() public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
@@ -41,18 +54,21 @@ class HassioSystem extends LitElement {
<span slot="header">System</span> <span slot="header">System</span>
<div class="content"> <div class="content">
<div class="card-group"> <div class="card-group">
<hassio-core-info
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-core-info>
<hassio-supervisor-info <hassio-supervisor-info
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this.supervisor} .hostInfo=${this.hostInfo}
.supervisorInfo=${this.supervisorInfo}
></hassio-supervisor-info> ></hassio-supervisor-info>
<hassio-host-info <hassio-host-info
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this.supervisor} .hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-host-info> ></hassio-host-info>
<hassio-system-metrics
.hass=${this.hass}
.hostInfo=${this.hostInfo}
></hassio-system-metrics>
</div> </div>
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log> <hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
</div> </div>

View File

@@ -29,22 +29,22 @@
"@fullcalendar/daygrid": "5.1.0", "@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0", "@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0", "@fullcalendar/list": "5.1.0",
"@material/chips": "=9.0.0-canary.1c156d69d.0", "@material/chips": "=8.0.0-canary.774dcfc8e.0",
"@material/mwc-button": "^0.20.0", "@material/mwc-button": "^0.19.0",
"@material/mwc-checkbox": "^0.20.0", "@material/mwc-checkbox": "^0.19.0",
"@material/mwc-circular-progress": "^0.20.0", "@material/mwc-circular-progress": "^0.19.0",
"@material/mwc-dialog": "^0.20.0", "@material/mwc-dialog": "^0.19.0",
"@material/mwc-fab": "^0.20.0", "@material/mwc-fab": "^0.19.0",
"@material/mwc-formfield": "^0.20.0", "@material/mwc-formfield": "^0.19.0",
"@material/mwc-icon-button": "^0.20.0", "@material/mwc-icon-button": "^0.19.0",
"@material/mwc-list": "^0.20.0", "@material/mwc-list": "^0.19.0",
"@material/mwc-menu": "^0.20.0", "@material/mwc-menu": "^0.19.0",
"@material/mwc-radio": "^0.20.0", "@material/mwc-radio": "^0.19.0",
"@material/mwc-ripple": "^0.20.0", "@material/mwc-ripple": "^0.19.0",
"@material/mwc-switch": "^0.20.0", "@material/mwc-switch": "^0.19.0",
"@material/mwc-tab": "^0.20.0", "@material/mwc-tab": "^0.19.0",
"@material/mwc-tab-bar": "^0.20.0", "@material/mwc-tab-bar": "^0.19.0",
"@material/top-app-bar": "=9.0.0-canary.1c156d69d.0", "@material/top-app-bar": "=8.0.0-canary.774dcfc8e.0",
"@mdi/js": "5.6.55", "@mdi/js": "5.6.55",
"@mdi/svg": "5.6.55", "@mdi/svg": "5.6.55",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.0.2",
@@ -83,9 +83,6 @@
"@types/sortablejs": "^1.10.6", "@types/sortablejs": "^1.10.6",
"@vaadin/vaadin-combo-box": "^5.0.10", "@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7", "@vaadin/vaadin-date-picker": "^4.0.7",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.2.0", "@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7", "@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0", "chart.js": "~2.8.0",
@@ -112,7 +109,7 @@
"marked": "^1.1.1", "marked": "^1.1.1",
"mdn-polyfills": "^5.16.0", "mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2", "memoize-one": "^5.0.2",
"node-vibrant": "3.2.1-alpha.1", "node-vibrant": "^3.1.6",
"proxy-polyfill": "^0.3.1", "proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
@@ -120,11 +117,9 @@
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2", "sortablejs": "^1.10.2",
"superstruct": "^0.10.13", "superstruct": "^0.10.12",
"tinykeys": "^1.1.1", "tinykeys": "^1.1.1",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"vis-data": "^7.1.1",
"vis-network": "^8.5.4",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1", "vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2", "web-animations-js": "^2.3.2",
@@ -146,9 +141,6 @@
"@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.11.5", "@babel/preset-env": "^7.11.5",
"@babel/preset-typescript": "^7.10.4", "@babel/preset-typescript": "^7.10.4",
"@koa/cors": "^3.1.0",
"@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1",
"@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-json": "^4.0.3", "@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-node-resolve": "^7.1.3",
@@ -167,8 +159,6 @@
"@types/webspeechapi": "^0.0.29", "@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.4.0", "@typescript-eslint/eslint-plugin": "^4.4.0",
"@typescript-eslint/parser": "^4.4.0", "@typescript-eslint/parser": "^4.4.0",
"@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"cpx": "^1.5.0", "cpx": "^1.5.0",
@@ -205,6 +195,7 @@
"raw-loader": "^2.0.0", "raw-loader": "^2.0.0",
"require-dir": "^1.2.0", "require-dir": "^1.2.0",
"rollup": "^2.8.2", "rollup": "^2.8.2",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
"rollup-plugin-terser": "^5.3.0", "rollup-plugin-terser": "^5.3.0",
"rollup-plugin-visualizer": "^4.0.4", "rollup-plugin-visualizer": "^4.0.4",

Some files were not shown because too many files have changed in this diff Show More