mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Merge pull request #6093 from home-assistant/dev
This commit is contained in:
commit
5d5d6b247f
14
.github/workflows/release-drafter.yaml
vendored
Normal file
14
.github/workflows/release-drafter.yaml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@ -2,79 +2,139 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [safety@home-assistant.io][email]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[safety@home-assistant.io][email] or by using the report/flag feature of
|
||||
the medium used. All complaints will be reviewed and investigated promptly and
|
||||
fairly.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available [here][version].
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available [here][version].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder][mozilla].
|
||||
|
||||
## Adoption
|
||||
|
||||
This Code of Conduct was first adopted January 21st, 2017 and announced in [this][coc-blog] blog post.
|
||||
This Code of Conduct was first adopted January 21st, 2017 and announced in
|
||||
[this][coc-blog] blog post and has been updated on May 25th, 2020 to version
|
||||
2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog]
|
||||
blog post.
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations>.
|
||||
|
||||
[coc-blog]: /blog/2017/01/21/home-assistant-governance/
|
||||
[coc2-blog]: /blog/2020/05/25/code-of-conduct-updated/
|
||||
[email]: mailto:safety@home-assistant.io
|
||||
[coc-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[mozilla]: https://github.com/mozilla/diversity
|
||||
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
|
@ -1,39 +0,0 @@
|
||||
const options = ({ latestBuild }) => ({
|
||||
presets: [
|
||||
!latestBuild && [require("@babel/preset-env").default, { modules: false }],
|
||||
require("@babel/preset-typescript").default,
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
[
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/syntax-dynamic-import",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
[
|
||||
require("@babel/plugin-proposal-decorators").default,
|
||||
{ decoratorsBeforeExport: true },
|
||||
],
|
||||
[
|
||||
require("@babel/plugin-proposal-class-properties").default,
|
||||
{ loose: true },
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
module.exports.babelLoaderConfig = ({ latestBuild }) => {
|
||||
if (latestBuild === undefined) {
|
||||
throw Error("latestBuild not defined for babel loader config");
|
||||
}
|
||||
return {
|
||||
test: /\.m?js$|\.tsx?$/,
|
||||
exclude: [require.resolve("@mdi/js/mdi.js"), require.resolve("hls.js")],
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: options({ latestBuild }),
|
||||
},
|
||||
};
|
||||
};
|
199
build-scripts/bundle.js
Normal file
199
build-scripts/bundle.js
Normal file
@ -0,0 +1,199 @@
|
||||
const path = require("path");
|
||||
const env = require("./env.js");
|
||||
const paths = require("./paths.js");
|
||||
|
||||
// Files from NPM Packages that should not be imported
|
||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||
// Bloats bundle and it's not used.
|
||||
path.resolve(require.resolve("moment"), "../locale"),
|
||||
// Part of yaml.js and only used for !!js functions that we don't use
|
||||
require.resolve("esprima"),
|
||||
];
|
||||
|
||||
// Files from NPM packages that we should replace with empty file
|
||||
module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
[
|
||||
// Contains all color definitions for all material color sets.
|
||||
// We don't use it
|
||||
require.resolve("@polymer/paper-styles/color.js"),
|
||||
require.resolve("@polymer/paper-styles/default-theme.js"),
|
||||
// Loads stuff from a CDN
|
||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
||||
),
|
||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||
].filter(Boolean);
|
||||
|
||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
__DEV__: !isProdBuild,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(env.version()),
|
||||
__DEMO__: false,
|
||||
__BACKWARDS_COMPAT__: false,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
),
|
||||
...defineOverlay,
|
||||
});
|
||||
|
||||
module.exports.terserOptions = (latestBuild) => ({
|
||||
safari10: true,
|
||||
ecma: latestBuild ? undefined : 5,
|
||||
output: { comments: false },
|
||||
});
|
||||
|
||||
module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
babelrc: false,
|
||||
presets: [
|
||||
!latestBuild && [require("@babel/preset-env").default, { modules: false }],
|
||||
require("@babel/preset-typescript").default,
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
[
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/syntax-dynamic-import",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
[
|
||||
require("@babel/plugin-proposal-decorators").default,
|
||||
{ decoratorsBeforeExport: true },
|
||||
],
|
||||
[
|
||||
require("@babel/plugin-proposal-class-properties").default,
|
||||
{ loose: true },
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
// Are already ES5, cause warnings when babelified.
|
||||
module.exports.babelExclude = () => [
|
||||
require.resolve("@mdi/js/mdi.js"),
|
||||
require.resolve("hls.js"),
|
||||
];
|
||||
|
||||
const outputPath = (outputRoot, latestBuild) =>
|
||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||
|
||||
const publicPath = (latestBuild) =>
|
||||
latestBuild ? "/frontend_latest/" : "/frontend_es5/";
|
||||
|
||||
/*
|
||||
BundleConfig {
|
||||
// Object with entrypoints that need to be bundled
|
||||
entry: { [name: string]: pathToFile },
|
||||
// Folder where bundled files need to be written
|
||||
outputPath: string,
|
||||
// absolute url-path where bundled files can be found
|
||||
publicPath: string,
|
||||
// extra definitions that we need to replace in source
|
||||
defineOverlay: {[name: string]: value },
|
||||
// if this is a production build
|
||||
isProdBuild: boolean,
|
||||
// If we're targeting latest browsers
|
||||
latestBuild: boolean,
|
||||
// If we're doing a stats build (create nice chunk names)
|
||||
isStatsBuild: boolean,
|
||||
// Names of entrypoints that should not be hashed
|
||||
dontHash: Set<string>
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports.config = {
|
||||
app({ isProdBuild, latestBuild, isStatsBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
service_worker: "./src/entrypoints/service_worker.ts",
|
||||
app: "./src/entrypoints/app.ts",
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
},
|
||||
outputPath: outputPath(paths.app_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
};
|
||||
},
|
||||
|
||||
demo({ isProdBuild, latestBuild, isStatsBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputPath: outputPath(paths.demo_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
defineOverlay: {
|
||||
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
|
||||
__DEMO__: true,
|
||||
},
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
};
|
||||
},
|
||||
|
||||
cast({ isProdBuild, latestBuild }) {
|
||||
const entry = {
|
||||
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
|
||||
};
|
||||
|
||||
if (latestBuild) {
|
||||
entry.receiver = path.resolve(
|
||||
paths.cast_dir,
|
||||
"src/receiver/entrypoint.ts"
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
entry,
|
||||
outputPath: outputPath(paths.cast_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
defineOverlay: {
|
||||
__BACKWARDS_COMPAT__: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
hassio({ isProdBuild, latestBuild }) {
|
||||
if (latestBuild) {
|
||||
throw new Error("Hass.io does not support latest build!");
|
||||
}
|
||||
return {
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputPath: paths.hassio_output_root,
|
||||
publicPath: paths.hassio_publicPath,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
dontHash: new Set(["entrypoint"]),
|
||||
};
|
||||
},
|
||||
|
||||
gallery({ isProdBuild, latestBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
|
||||
},
|
||||
outputPath: outputPath(paths.gallery_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
};
|
||||
},
|
||||
};
|
@ -3,8 +3,13 @@ const path = require("path");
|
||||
const paths = require("./paths.js");
|
||||
|
||||
module.exports = {
|
||||
useRollup() {
|
||||
return process.env.ROLLUP === "1";
|
||||
},
|
||||
isProdBuild() {
|
||||
return process.env.NODE_ENV === "production";
|
||||
return (
|
||||
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
|
||||
);
|
||||
},
|
||||
isStatsBuild() {
|
||||
return process.env.STATS === "1";
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Run HA develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
const envVars = require("../env");
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
@ -11,6 +11,7 @@ require("./compress.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-app",
|
||||
@ -26,8 +27,8 @@ gulp.task(
|
||||
"gen-index-app-dev",
|
||||
"build-translations"
|
||||
),
|
||||
"copy-static",
|
||||
"webpack-watch-app"
|
||||
"copy-static-app",
|
||||
env.useRollup() ? "rollup-watch-app" : "webpack-watch-app"
|
||||
)
|
||||
);
|
||||
|
||||
@ -39,10 +40,10 @@ gulp.task(
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static",
|
||||
"webpack-prod-app",
|
||||
"copy-static-app",
|
||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||
...// Don't compress running tests
|
||||
(envVars.isTest() ? [] : ["compress-app"]),
|
||||
(env.isTest() ? [] : ["compress-app"]),
|
||||
gulp.parallel(
|
||||
"gen-pages-prod",
|
||||
"gen-index-app-prod",
|
||||
|
@ -1,11 +1,14 @@
|
||||
const gulp = require("gulp");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-cast",
|
||||
@ -17,7 +20,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"webpack-dev-server-cast"
|
||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
||||
)
|
||||
);
|
||||
|
||||
@ -31,7 +34,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"webpack-prod-cast",
|
||||
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
||||
"gen-index-cast-prod"
|
||||
)
|
||||
);
|
||||
|
@ -1,39 +1,36 @@
|
||||
const del = require("del");
|
||||
const gulp = require("gulp");
|
||||
const config = require("../paths");
|
||||
const paths = require("../paths");
|
||||
require("./translations");
|
||||
|
||||
gulp.task(
|
||||
"clean",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.root, config.build_dir]);
|
||||
return del([paths.app_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-demo",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.demo_root, config.build_dir]);
|
||||
return del([paths.demo_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-cast",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.cast_root, config.build_dir]);
|
||||
return del([paths.cast_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-hassio",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.hassio_root, config.build_dir]);
|
||||
})
|
||||
);
|
||||
gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
|
||||
return del([paths.hassio_output_root, paths.build_dir]);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"clean-gallery",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.gallery_root, config.build_dir]);
|
||||
return del([paths.gallery_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
@ -8,36 +8,36 @@ const paths = require("../paths");
|
||||
|
||||
gulp.task("compress-app", function compressApp() {
|
||||
const jsLatest = gulp
|
||||
.src(path.resolve(paths.output, "**/*.js"))
|
||||
.src(path.resolve(paths.app_output_latest, "**/*.js"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(paths.output));
|
||||
.pipe(gulp.dest(paths.app_output_latest));
|
||||
|
||||
const jsEs5 = gulp
|
||||
.src(path.resolve(paths.output_es5, "**/*.js"))
|
||||
.src(path.resolve(paths.app_output_es5, "**/*.js"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(paths.output_es5));
|
||||
.pipe(gulp.dest(paths.app_output_es5));
|
||||
|
||||
const polyfills = gulp
|
||||
.src(path.resolve(paths.static, "polyfills/*.js"))
|
||||
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
|
||||
|
||||
const translations = gulp
|
||||
.src(path.resolve(paths.static, "translations/**/*.json"))
|
||||
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.static, "translations")));
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
|
||||
|
||||
const icons = gulp
|
||||
.src(path.resolve(paths.static, "mdi/*.json"))
|
||||
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.static, "mdi")));
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
|
||||
|
||||
return merge(jsLatest, jsEs5, polyfills, translations, icons);
|
||||
});
|
||||
|
||||
gulp.task("compress-hassio", function compressApp() {
|
||||
return gulp
|
||||
.src(path.resolve(paths.hassio_root, "**/*.js"))
|
||||
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(paths.hassio_root));
|
||||
.pipe(gulp.dest(paths.hassio_output_root));
|
||||
});
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons-json.js");
|
||||
@ -8,6 +10,7 @@ require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-demo",
|
||||
@ -19,7 +22,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-dev-server-demo"
|
||||
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
|
||||
)
|
||||
);
|
||||
|
||||
@ -34,7 +37,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-prod-demo",
|
||||
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
||||
"gen-index-demo-prod"
|
||||
)
|
||||
);
|
||||
|
@ -6,31 +6,36 @@ const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const template = require("lodash.template");
|
||||
const minify = require("html-minifier").minify;
|
||||
const config = require("../paths.js");
|
||||
const paths = require("../paths.js");
|
||||
const env = require("../env.js");
|
||||
|
||||
const templatePath = (tpl) =>
|
||||
path.resolve(config.polymer_dir, "src/html/", `${tpl}.html.template`);
|
||||
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
|
||||
|
||||
const readFile = (pth) => fs.readFileSync(pth).toString();
|
||||
|
||||
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
|
||||
const compiled = template(readFile(pathFunc(pth)));
|
||||
return compiled({ ...data, renderTemplate });
|
||||
return compiled({
|
||||
...data,
|
||||
useRollup: env.useRollup(),
|
||||
renderTemplate,
|
||||
});
|
||||
};
|
||||
|
||||
const renderDemoTemplate = (pth, data = {}) =>
|
||||
renderTemplate(pth, data, (tpl) =>
|
||||
path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`)
|
||||
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
|
||||
);
|
||||
|
||||
const renderCastTemplate = (pth, data = {}) =>
|
||||
renderTemplate(pth, data, (tpl) =>
|
||||
path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`)
|
||||
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
|
||||
);
|
||||
|
||||
const renderGalleryTemplate = (pth, data = {}) =>
|
||||
renderTemplate(pth, data, (tpl) =>
|
||||
path.resolve(config.gallery_dir, "src/html/", `${tpl}.html.template`)
|
||||
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
|
||||
);
|
||||
|
||||
const minifyHtml = (content) =>
|
||||
@ -48,29 +53,36 @@ gulp.task("gen-pages-dev", (done) => {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: `/frontend_latest/${page}.js`,
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5PageJS: `/frontend_es5/${page}.js`,
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, `${page}.html`), content);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.app_output_root, `${page}.html`),
|
||||
content
|
||||
);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-pages-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(config.output, "manifest.json"));
|
||||
const es5Manifest = require(path.resolve(config.output_es5, "manifest.json"));
|
||||
const latestManifest = require(path.resolve(
|
||||
paths.app_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
paths.app_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
|
||||
for (const page of PAGES) {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: latestManifest[`${page}.js`],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5PageJS: es5Manifest[`${page}.js`],
|
||||
});
|
||||
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.root, `${page}.html`),
|
||||
path.resolve(paths.app_output_root, `${page}.html`),
|
||||
minifyHtml(content)
|
||||
);
|
||||
}
|
||||
@ -85,32 +97,39 @@ gulp.task("gen-index-app-dev", (done) => {
|
||||
latestCoreJS: "/frontend_latest/core.js",
|
||||
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5AppJS: "/frontend_es5/app.js",
|
||||
es5CoreJS: "/frontend_es5/core.js",
|
||||
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
|
||||
}).replace(/#THEMEC/g, "{{ theme_color }}");
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, "index.html"), content);
|
||||
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-app-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(config.output, "manifest.json"));
|
||||
const es5Manifest = require(path.resolve(config.output_es5, "manifest.json"));
|
||||
const latestManifest = require(path.resolve(
|
||||
paths.app_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
paths.app_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
const content = renderTemplate("index", {
|
||||
latestAppJS: latestManifest["app.js"],
|
||||
latestCoreJS: latestManifest["core.js"],
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5AppJS: es5Manifest["app.js"],
|
||||
es5CoreJS: es5Manifest["core.js"],
|
||||
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
||||
});
|
||||
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, "index.html"), minified);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.app_output_root, "index.html"),
|
||||
minified
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -119,7 +138,7 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
latestReceiverJS: "/frontend_latest/receiver.js",
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "receiver.html"),
|
||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
||||
contentReceiver
|
||||
);
|
||||
|
||||
@ -127,14 +146,17 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
||||
});
|
||||
fs.outputFileSync(path.resolve(config.cast_root, "faq.html"), contentFAQ);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.cast_output_root, "faq.html"),
|
||||
contentFAQ
|
||||
);
|
||||
|
||||
const contentLauncher = renderCastTemplate("launcher", {
|
||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "index.html"),
|
||||
path.resolve(paths.cast_output_root, "index.html"),
|
||||
contentLauncher
|
||||
);
|
||||
done();
|
||||
@ -142,11 +164,11 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
|
||||
gulp.task("gen-index-cast-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(
|
||||
config.cast_output,
|
||||
paths.cast_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
config.cast_output_es5,
|
||||
paths.cast_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
|
||||
@ -154,7 +176,7 @@ gulp.task("gen-index-cast-prod", (done) => {
|
||||
latestReceiverJS: latestManifest["receiver.js"],
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "receiver.html"),
|
||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
||||
contentReceiver
|
||||
);
|
||||
|
||||
@ -162,14 +184,17 @@ gulp.task("gen-index-cast-prod", (done) => {
|
||||
latestLauncherJS: latestManifest["launcher.js"],
|
||||
es5LauncherJS: es5Manifest["launcher.js"],
|
||||
});
|
||||
fs.outputFileSync(path.resolve(config.cast_root, "faq.html"), contentFAQ);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.cast_output_root, "faq.html"),
|
||||
contentFAQ
|
||||
);
|
||||
|
||||
const contentLauncher = renderCastTemplate("launcher", {
|
||||
latestLauncherJS: latestManifest["launcher.js"],
|
||||
es5LauncherJS: es5Manifest["launcher.js"],
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "index.html"),
|
||||
path.resolve(paths.cast_output_root, "index.html"),
|
||||
contentLauncher
|
||||
);
|
||||
done();
|
||||
@ -181,32 +206,36 @@ gulp.task("gen-index-demo-dev", (done) => {
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: "/frontend_latest/main.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5DemoJS: "/frontend_es5/main.js",
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), content);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.demo_output_root, "index.html"),
|
||||
content
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-demo-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(
|
||||
config.demo_output,
|
||||
paths.demo_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
config.demo_output_es5,
|
||||
paths.demo_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: latestManifest["main.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5DemoJS: es5Manifest["main.js"],
|
||||
});
|
||||
const minified = minifyHtml(content);
|
||||
|
||||
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), minified);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.demo_output_root, "index.html"),
|
||||
minified
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -217,13 +246,16 @@ gulp.task("gen-index-gallery-dev", (done) => {
|
||||
latestGalleryJS: "./frontend_latest/entrypoint.js",
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.gallery_output_root, "index.html"),
|
||||
content
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-gallery-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(
|
||||
config.gallery_output,
|
||||
paths.gallery_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const content = renderGalleryTemplate("index", {
|
||||
@ -231,6 +263,9 @@ gulp.task("gen-index-gallery-prod", (done) => {
|
||||
});
|
||||
const minified = minifyHtml(content);
|
||||
|
||||
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), minified);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.gallery_output_root, "index.html"),
|
||||
minified
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons-json.js");
|
||||
@ -8,6 +10,7 @@ require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-gallery",
|
||||
@ -20,7 +23,7 @@ gulp.task(
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-gallery",
|
||||
"gen-index-gallery-dev",
|
||||
"webpack-dev-server-gallery"
|
||||
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
|
||||
)
|
||||
);
|
||||
|
||||
@ -34,7 +37,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-gallery",
|
||||
"webpack-prod-gallery",
|
||||
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
||||
"gen-index-gallery-prod"
|
||||
)
|
||||
);
|
||||
|
@ -51,6 +51,12 @@ function copyPolyfills(staticDir) {
|
||||
);
|
||||
}
|
||||
|
||||
function copyLoaderJS(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
|
||||
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
|
||||
}
|
||||
|
||||
function copyFonts(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
// Local fonts
|
||||
@ -72,17 +78,17 @@ function copyMapPanel(staticDir) {
|
||||
);
|
||||
}
|
||||
|
||||
gulp.task("copy-translations", (done) => {
|
||||
const staticDir = paths.static;
|
||||
gulp.task("copy-translations-app", async () => {
|
||||
const staticDir = paths.app_output_static;
|
||||
copyTranslations(staticDir);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("copy-static", (done) => {
|
||||
const staticDir = paths.static;
|
||||
gulp.task("copy-static-app", async () => {
|
||||
const staticDir = paths.app_output_static;
|
||||
// Basic static files
|
||||
fs.copySync(polyPath("public"), paths.root);
|
||||
fs.copySync(polyPath("public"), paths.app_output_root);
|
||||
|
||||
copyLoaderJS(staticDir);
|
||||
copyPolyfills(staticDir);
|
||||
copyFonts(staticDir);
|
||||
copyTranslations(staticDir);
|
||||
@ -90,48 +96,50 @@ gulp.task("copy-static", (done) => {
|
||||
|
||||
// Panel assets
|
||||
copyMapPanel(staticDir);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("copy-static-demo", (done) => {
|
||||
gulp.task("copy-static-demo", async () => {
|
||||
// Copy app static files
|
||||
fs.copySync(
|
||||
polyPath("public/static"),
|
||||
path.resolve(paths.demo_root, "static")
|
||||
path.resolve(paths.demo_output_root, "static")
|
||||
);
|
||||
// Copy demo static files
|
||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_root);
|
||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root);
|
||||
|
||||
copyPolyfills(paths.demo_static);
|
||||
copyMapPanel(paths.demo_static);
|
||||
copyFonts(paths.demo_static);
|
||||
copyTranslations(paths.demo_static);
|
||||
copyMdiIcons(paths.demo_static);
|
||||
done();
|
||||
copyLoaderJS(paths.demo_output_static);
|
||||
copyPolyfills(paths.demo_output_static);
|
||||
copyMapPanel(paths.demo_output_static);
|
||||
copyFonts(paths.demo_output_static);
|
||||
copyTranslations(paths.demo_output_static);
|
||||
copyMdiIcons(paths.demo_output_static);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-cast", (done) => {
|
||||
gulp.task("copy-static-cast", async () => {
|
||||
// Copy app static files
|
||||
fs.copySync(polyPath("public/static"), paths.cast_static);
|
||||
fs.copySync(polyPath("public/static"), paths.cast_output_static);
|
||||
// Copy cast static files
|
||||
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_root);
|
||||
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root);
|
||||
|
||||
copyMapPanel(paths.cast_static);
|
||||
copyFonts(paths.cast_static);
|
||||
copyTranslations(paths.cast_static);
|
||||
copyMdiIcons(paths.cast_static);
|
||||
done();
|
||||
copyLoaderJS(paths.cast_output_static);
|
||||
copyPolyfills(paths.cast_output_static);
|
||||
copyMapPanel(paths.cast_output_static);
|
||||
copyFonts(paths.cast_output_static);
|
||||
copyTranslations(paths.cast_output_static);
|
||||
copyMdiIcons(paths.cast_output_static);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-gallery", (done) => {
|
||||
gulp.task("copy-static-gallery", async () => {
|
||||
// Copy app static files
|
||||
fs.copySync(polyPath("public/static"), paths.gallery_static);
|
||||
fs.copySync(polyPath("public/static"), paths.gallery_output_static);
|
||||
// Copy gallery static files
|
||||
fs.copySync(path.resolve(paths.gallery_dir, "public"), paths.gallery_root);
|
||||
fs.copySync(
|
||||
path.resolve(paths.gallery_dir, "public"),
|
||||
paths.gallery_output_root
|
||||
);
|
||||
|
||||
copyMapPanel(paths.gallery_static);
|
||||
copyFonts(paths.gallery_static);
|
||||
copyTranslations(paths.gallery_static);
|
||||
copyMdiIcons(paths.gallery_static);
|
||||
done();
|
||||
copyMapPanel(paths.gallery_output_static);
|
||||
copyFonts(paths.gallery_output_static);
|
||||
copyTranslations(paths.gallery_output_static);
|
||||
copyMdiIcons(paths.gallery_output_static);
|
||||
});
|
||||
|
@ -1,11 +1,12 @@
|
||||
const gulp = require("gulp");
|
||||
|
||||
const envVars = require("../env");
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./webpack.js");
|
||||
require("./compress.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-hassio",
|
||||
@ -15,7 +16,7 @@ gulp.task(
|
||||
},
|
||||
"clean-hassio",
|
||||
"gen-icons-json",
|
||||
"webpack-watch-hassio"
|
||||
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
|
||||
)
|
||||
);
|
||||
|
||||
@ -27,8 +28,8 @@ gulp.task(
|
||||
},
|
||||
"clean-hassio",
|
||||
"gen-icons-json",
|
||||
"webpack-prod-hassio",
|
||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
||||
...// Don't compress running tests
|
||||
(envVars.isTest() ? [] : ["compress-hassio"])
|
||||
(env.isTest() ? [] : ["compress-hassio"])
|
||||
)
|
||||
);
|
||||
|
155
build-scripts/gulp/rollup.js
Normal file
155
build-scripts/gulp/rollup.js
Normal file
@ -0,0 +1,155 @@
|
||||
// Tasks to run Rollup
|
||||
const path = require("path");
|
||||
const gulp = require("gulp");
|
||||
const rollup = require("rollup");
|
||||
const handler = require("serve-handler");
|
||||
const http = require("http");
|
||||
const log = require("fancy-log");
|
||||
const rollupConfig = require("../rollup");
|
||||
const paths = require("../paths");
|
||||
const open = require("open");
|
||||
|
||||
const bothBuilds = (createConfigFunc, params) =>
|
||||
gulp.series(
|
||||
async function buildLatest() {
|
||||
await buildRollup(
|
||||
createConfigFunc({
|
||||
...params,
|
||||
latestBuild: true,
|
||||
})
|
||||
);
|
||||
},
|
||||
async function buildES5() {
|
||||
await buildRollup(
|
||||
createConfigFunc({
|
||||
...params,
|
||||
latestBuild: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function createServer(serveOptions) {
|
||||
const server = http.createServer((request, response) => {
|
||||
return handler(request, response, {
|
||||
public: serveOptions.root,
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(
|
||||
serveOptions.port,
|
||||
serveOptions.networkAccess ? "0.0.0.0" : undefined,
|
||||
() => {
|
||||
log.info(`Available at http://localhost:${serveOptions.port}`);
|
||||
open(`http://localhost:${serveOptions.port}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
|
||||
const { inputOptions, outputOptions } = createConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
||||
});
|
||||
|
||||
const watcher = rollup.watch({
|
||||
...inputOptions,
|
||||
output: [outputOptions],
|
||||
watch: {
|
||||
include: ["src/**"] + extraWatchSrc,
|
||||
},
|
||||
});
|
||||
|
||||
let startedHttp = false;
|
||||
|
||||
watcher.on("event", (event) => {
|
||||
if (event.code === "BUNDLE_END") {
|
||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||
} else if (event.code === "ERROR") {
|
||||
log.error(event.error);
|
||||
} else if (event.code === "END") {
|
||||
if (startedHttp || !serveOptions) {
|
||||
return;
|
||||
}
|
||||
startedHttp = true;
|
||||
createServer(serveOptions);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series("build-translations", "copy-translations-app")
|
||||
);
|
||||
}
|
||||
|
||||
async function buildRollup(config) {
|
||||
const bundle = await rollup.rollup(config.inputOptions);
|
||||
await bundle.write(config.outputOptions);
|
||||
}
|
||||
|
||||
gulp.task("rollup-watch-app", () => {
|
||||
watchRollup(rollupConfig.createAppConfig);
|
||||
});
|
||||
|
||||
gulp.task("rollup-watch-hassio", () => {
|
||||
watchRollup(
|
||||
// Force latestBuild = false for hassio config.
|
||||
(conf) => rollupConfig.createHassioConfig({ ...conf, latestBuild: false }),
|
||||
["hassio/src/**"]
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-demo", () => {
|
||||
watchRollup(rollupConfig.createDemoConfig, ["demo/src/**"], {
|
||||
root: paths.demo_output_root,
|
||||
port: 8090,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-cast", () => {
|
||||
watchRollup(rollupConfig.createCastConfig, ["cast/src/**"], {
|
||||
root: paths.cast_output_root,
|
||||
port: 8080,
|
||||
networkAccess: true,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-gallery", () => {
|
||||
watchRollup(rollupConfig.createGalleryConfig, ["gallery/src/**"], {
|
||||
root: paths.gallery_output_root,
|
||||
port: 8100,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"rollup-prod-app",
|
||||
bothBuilds(rollupConfig.createAppConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"rollup-prod-demo",
|
||||
bothBuilds(rollupConfig.createDemoConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"rollup-prod-cast",
|
||||
bothBuilds(rollupConfig.createCastConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-hassio", () =>
|
||||
buildRollup(
|
||||
rollupConfig.createHassioConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-gallery", () =>
|
||||
buildRollup(
|
||||
rollupConfig.createGalleryConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: true,
|
||||
})
|
||||
)
|
||||
);
|
@ -9,7 +9,7 @@ const workboxBuild = require("workbox-build");
|
||||
const sourceMapUrl = require("source-map-url");
|
||||
const paths = require("../paths.js");
|
||||
|
||||
const swDest = path.resolve(paths.root, "service_worker.js");
|
||||
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
||||
|
||||
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
|
||||
|
||||
@ -31,32 +31,38 @@ self.addEventListener('install', (event) => {
|
||||
gulp.task("gen-service-worker-app-prod", async () => {
|
||||
// Read bundled source file
|
||||
const bundleManifestLatest = require(path.resolve(
|
||||
paths.output,
|
||||
paths.app_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
let serviceWorkerContent = fs.readFileSync(
|
||||
paths.root + bundleManifestLatest["service_worker.js"],
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js"],
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
// Delete old file from frontend_latest so manifest won't pick it up
|
||||
fs.removeSync(paths.root + bundleManifestLatest["service_worker.js"]);
|
||||
fs.removeSync(paths.root + bundleManifestLatest["service_worker.js.map"]);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js"]
|
||||
);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
|
||||
);
|
||||
|
||||
// Remove ES5
|
||||
const bundleManifestES5 = require(path.resolve(
|
||||
paths.output_es5,
|
||||
paths.app_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
fs.removeSync(paths.root + bundleManifestES5["service_worker.js"]);
|
||||
fs.removeSync(paths.root + bundleManifestES5["service_worker.js.map"]);
|
||||
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
|
||||
);
|
||||
|
||||
const workboxManifest = await workboxBuild.getManifest({
|
||||
// Files that mach this pattern will be considered unique and skip revision check
|
||||
// ignore JS files + translation files
|
||||
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
|
||||
|
||||
globDirectory: paths.root,
|
||||
globDirectory: paths.app_output_root,
|
||||
globPatterns: [
|
||||
"frontend_latest/*.js",
|
||||
// Cache all English translations because we catch them as fallback
|
||||
|
@ -38,9 +38,9 @@ const runDevServer = ({
|
||||
|
||||
const handler = (done) => (err, stats) => {
|
||||
if (err) {
|
||||
console.log(err.stack || err);
|
||||
log.error(err.stack || err);
|
||||
if (err.details) {
|
||||
console.log(err.details);
|
||||
log.error(err.details);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -48,7 +48,7 @@ const handler = (done) => (err, stats) => {
|
||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
console.log(stats.toString("minimal"));
|
||||
log.warn(stats.toString("minimal"));
|
||||
}
|
||||
|
||||
if (done) {
|
||||
@ -64,7 +64,7 @@ gulp.task("webpack-watch-app", () => {
|
||||
);
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series("build-translations", "copy-translations")
|
||||
gulp.series("build-translations", "copy-translations-app")
|
||||
);
|
||||
});
|
||||
|
||||
@ -82,7 +82,7 @@ gulp.task(
|
||||
gulp.task("webpack-dev-server-demo", () => {
|
||||
runDevServer({
|
||||
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
|
||||
contentBase: paths.demo_root,
|
||||
contentBase: paths.demo_output_root,
|
||||
port: 8090,
|
||||
});
|
||||
});
|
||||
@ -103,7 +103,7 @@ gulp.task(
|
||||
gulp.task("webpack-dev-server-cast", () => {
|
||||
runDevServer({
|
||||
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
|
||||
contentBase: paths.cast_root,
|
||||
contentBase: paths.cast_output_root,
|
||||
port: 8080,
|
||||
// Accessible from the network, because that's how Cast hits it.
|
||||
listenHost: "0.0.0.0",
|
||||
@ -152,7 +152,7 @@ gulp.task("webpack-dev-server-gallery", () => {
|
||||
runDevServer({
|
||||
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
|
||||
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
|
||||
contentBase: paths.gallery_root,
|
||||
contentBase: paths.gallery_output_root,
|
||||
port: 8100,
|
||||
});
|
||||
});
|
||||
|
@ -4,30 +4,36 @@ module.exports = {
|
||||
polymer_dir: path.resolve(__dirname, ".."),
|
||||
|
||||
build_dir: path.resolve(__dirname, "../build"),
|
||||
root: path.resolve(__dirname, "../hass_frontend"),
|
||||
static: path.resolve(__dirname, "../hass_frontend/static"),
|
||||
output: path.resolve(__dirname, "../hass_frontend/frontend_latest"),
|
||||
output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
|
||||
app_output_root: path.resolve(__dirname, "../hass_frontend"),
|
||||
app_output_static: path.resolve(__dirname, "../hass_frontend/static"),
|
||||
app_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../hass_frontend/frontend_latest"
|
||||
),
|
||||
app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
|
||||
|
||||
demo_dir: path.resolve(__dirname, "../demo"),
|
||||
demo_root: path.resolve(__dirname, "../demo/dist"),
|
||||
demo_static: path.resolve(__dirname, "../demo/dist/static"),
|
||||
demo_output: path.resolve(__dirname, "../demo/dist/frontend_latest"),
|
||||
demo_output_root: path.resolve(__dirname, "../demo/dist"),
|
||||
demo_output_static: path.resolve(__dirname, "../demo/dist/static"),
|
||||
demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"),
|
||||
demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"),
|
||||
|
||||
cast_dir: path.resolve(__dirname, "../cast"),
|
||||
cast_root: path.resolve(__dirname, "../cast/dist"),
|
||||
cast_static: path.resolve(__dirname, "../cast/dist/static"),
|
||||
cast_output: path.resolve(__dirname, "../cast/dist/frontend_latest"),
|
||||
cast_output_root: path.resolve(__dirname, "../cast/dist"),
|
||||
cast_output_static: path.resolve(__dirname, "../cast/dist/static"),
|
||||
cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"),
|
||||
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
|
||||
|
||||
gallery_dir: path.resolve(__dirname, "../gallery"),
|
||||
gallery_root: path.resolve(__dirname, "../gallery/dist"),
|
||||
gallery_output: path.resolve(__dirname, "../gallery/dist/frontend_latest"),
|
||||
gallery_static: path.resolve(__dirname, "../gallery/dist/static"),
|
||||
gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
|
||||
gallery_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../gallery/dist/frontend_latest"
|
||||
),
|
||||
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
|
||||
|
||||
hassio_dir: path.resolve(__dirname, "../hassio"),
|
||||
hassio_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_publicPath: "/api/hassio/app/",
|
||||
|
||||
translations_src: path.resolve(__dirname, "../src/translations"),
|
||||
|
14
build-scripts/rollup-plugins/dont-hash-plugin.js
Normal file
14
build-scripts/rollup-plugins/dont-hash-plugin.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = function (opts = {}) {
|
||||
const dontHash = opts.dontHash || new Set();
|
||||
|
||||
return {
|
||||
name: "dont-hash",
|
||||
renderChunk(_code, chunk, _options) {
|
||||
if (!chunk.isEntry || !dontHash.has(chunk.name)) {
|
||||
return null;
|
||||
}
|
||||
chunk.fileName = `${chunk.name}.js`;
|
||||
return null;
|
||||
},
|
||||
};
|
||||
};
|
26
build-scripts/rollup-plugins/ignore-plugin.js
Normal file
26
build-scripts/rollup-plugins/ignore-plugin.js
Normal file
@ -0,0 +1,26 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = function (userOptions = {}) {
|
||||
// Files need to be absolute paths.
|
||||
// This only works if the file has no exports
|
||||
// and only is imported for its side effects
|
||||
const files = userOptions.files || [];
|
||||
|
||||
if (files.length === 0) {
|
||||
return {
|
||||
name: "ignore",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: "ignore",
|
||||
|
||||
load(id) {
|
||||
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))
|
||||
? {
|
||||
code: "",
|
||||
}
|
||||
: null;
|
||||
},
|
||||
};
|
||||
};
|
34
build-scripts/rollup-plugins/manifest-plugin.js
Normal file
34
build-scripts/rollup-plugins/manifest-plugin.js
Normal file
@ -0,0 +1,34 @@
|
||||
const url = require("url");
|
||||
|
||||
const defaultOptions = {
|
||||
publicPath: "",
|
||||
};
|
||||
|
||||
module.exports = function (userOptions = {}) {
|
||||
const options = { ...defaultOptions, ...userOptions };
|
||||
|
||||
return {
|
||||
name: "manifest",
|
||||
generateBundle(outputOptions, bundle) {
|
||||
const manifest = {};
|
||||
|
||||
for (const chunk of Object.values(bundle)) {
|
||||
if (!chunk.isEntry) {
|
||||
continue;
|
||||
}
|
||||
// Add js extension to mimic Webpack manifest.
|
||||
manifest[`${chunk.name}.js`] = url.resolve(
|
||||
options.publicPath,
|
||||
chunk.fileName
|
||||
);
|
||||
}
|
||||
|
||||
this.emitFile({
|
||||
type: "asset",
|
||||
source: JSON.stringify(manifest, undefined, 2),
|
||||
name: "manifest.json",
|
||||
fileName: "manifest.json",
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
149
build-scripts/rollup-plugins/worker-plugin.js
Normal file
149
build-scripts/rollup-plugins/worker-plugin.js
Normal file
@ -0,0 +1,149 @@
|
||||
// Worker plugin
|
||||
// Each worker will include all of its dependencies
|
||||
// instead of relying on an importer.
|
||||
|
||||
// Forked from v.1.4.1
|
||||
// https://github.com/surma/rollup-plugin-off-main-thread
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const rollup = require("rollup");
|
||||
const path = require("path");
|
||||
const MagicString = require("magic-string");
|
||||
|
||||
const defaultOpts = {
|
||||
// A RegExp to find `new Workers()` calls. The second capture group _must_
|
||||
// capture the provided file name without the quotes.
|
||||
workerRegexp: /new Worker\((["'])(.+?)\1(,[^)]+)?\)/g,
|
||||
plugins: ["node-resolve", "commonjs", "babel", "terser", "ignore"],
|
||||
};
|
||||
|
||||
async function getBundledWorker(workerPath, rollupOptions) {
|
||||
const bundle = await rollup.rollup({
|
||||
...rollupOptions,
|
||||
input: {
|
||||
worker: workerPath,
|
||||
},
|
||||
});
|
||||
const { output } = await bundle.generate({
|
||||
// Generates cleanest output, we shouldn't have any imports/exports
|
||||
// that would be incompatible with ES5.
|
||||
format: "es",
|
||||
// We should not export anything. This will fail build if we are.
|
||||
exports: "none",
|
||||
});
|
||||
|
||||
let code;
|
||||
|
||||
for (const chunkOrAsset of output) {
|
||||
if (chunkOrAsset.name === "worker") {
|
||||
code = chunkOrAsset.code;
|
||||
} else if (chunkOrAsset.type !== "asset") {
|
||||
throw new Error("Unexpected extra output");
|
||||
}
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
module.exports = function (opts = {}) {
|
||||
opts = { ...defaultOpts, ...opts };
|
||||
|
||||
let rollupOptions;
|
||||
let refIds;
|
||||
|
||||
return {
|
||||
name: "hass-worker",
|
||||
|
||||
async buildStart(options) {
|
||||
refIds = {};
|
||||
rollupOptions = {
|
||||
plugins: options.plugins.filter((plugin) =>
|
||||
opts.plugins.includes(plugin.name)
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
async transform(code, id) {
|
||||
// Copy the regexp as they are stateful and this hook is async.
|
||||
const workerRegexp = new RegExp(
|
||||
opts.workerRegexp.source,
|
||||
opts.workerRegexp.flags
|
||||
);
|
||||
if (!workerRegexp.test(code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ms = new MagicString(code);
|
||||
// Reset the regexp
|
||||
workerRegexp.lastIndex = 0;
|
||||
while (true) {
|
||||
const match = workerRegexp.exec(code);
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
|
||||
const workerFile = match[2];
|
||||
let optionsObject = {};
|
||||
// Parse the optional options object
|
||||
if (match[3] && match[3].length > 0) {
|
||||
// FIXME: ooooof!
|
||||
optionsObject = new Function(`return ${match[3].slice(1)};`)();
|
||||
}
|
||||
delete optionsObject.type;
|
||||
|
||||
if (!new RegExp("^.*/").test(workerFile)) {
|
||||
this.warn(
|
||||
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find worker file and store it as a chunk with ID prefixed for our loader
|
||||
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
|
||||
let chunkRefId;
|
||||
if (resolvedWorkerFile in refIds) {
|
||||
chunkRefId = refIds[resolvedWorkerFile];
|
||||
} else {
|
||||
this.addWatchFile(resolvedWorkerFile);
|
||||
const source = await getBundledWorker(
|
||||
resolvedWorkerFile,
|
||||
rollupOptions
|
||||
);
|
||||
chunkRefId = refIds[resolvedWorkerFile] = this.emitFile({
|
||||
name: path.basename(resolvedWorkerFile),
|
||||
source,
|
||||
type: "asset",
|
||||
});
|
||||
}
|
||||
|
||||
const workerParametersStartIndex = match.index + "new Worker(".length;
|
||||
const workerParametersEndIndex =
|
||||
match.index + match[0].length - ")".length;
|
||||
|
||||
ms.overwrite(
|
||||
workerParametersStartIndex,
|
||||
workerParametersEndIndex,
|
||||
`import.meta.ROLLUP_FILE_URL_${chunkRefId}, ${JSON.stringify(
|
||||
optionsObject
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
code: ms.toString(),
|
||||
map: ms.generateMap({ hires: true }),
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
151
build-scripts/rollup.js
Normal file
151
build-scripts/rollup.js
Normal file
@ -0,0 +1,151 @@
|
||||
const path = require("path");
|
||||
|
||||
const commonjs = require("@rollup/plugin-commonjs");
|
||||
const resolve = require("@rollup/plugin-node-resolve");
|
||||
const json = require("@rollup/plugin-json");
|
||||
const babel = require("rollup-plugin-babel");
|
||||
const replace = require("@rollup/plugin-replace");
|
||||
const visualizer = require("rollup-plugin-visualizer");
|
||||
const { string } = require("rollup-plugin-string");
|
||||
const { terser } = require("rollup-plugin-terser");
|
||||
const manifest = require("./rollup-plugins/manifest-plugin");
|
||||
const worker = require("./rollup-plugins/worker-plugin");
|
||||
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
|
||||
const ignore = require("./rollup-plugins/ignore-plugin");
|
||||
|
||||
const bundle = require("./bundle");
|
||||
const paths = require("./paths");
|
||||
|
||||
const extensions = [".js", ".ts"];
|
||||
|
||||
/**
|
||||
* @param {Object} arg
|
||||
* @param { import("rollup").InputOption } arg.input
|
||||
*/
|
||||
const createRollupConfig = ({
|
||||
entry,
|
||||
outputPath,
|
||||
defineOverlay,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
publicPath,
|
||||
dontHash,
|
||||
}) => {
|
||||
return {
|
||||
/**
|
||||
* @type { import("rollup").InputOptions }
|
||||
*/
|
||||
inputOptions: {
|
||||
input: entry,
|
||||
// Some entry points contain no JavaScript. This setting silences a warning about that.
|
||||
// https://rollupjs.org/guide/en/#preserveentrysignatures
|
||||
preserveEntrySignatures: false,
|
||||
plugins: [
|
||||
ignore({
|
||||
files: bundle.emptyPackages({ latestBuild }),
|
||||
}),
|
||||
resolve({
|
||||
extensions,
|
||||
preferBuiltins: false,
|
||||
browser: true,
|
||||
rootDir: paths.polymer_dir,
|
||||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
"js-yaml": ["safeDump", "safeLoad"],
|
||||
},
|
||||
}),
|
||||
json(),
|
||||
babel({
|
||||
...bundle.babelOptions({ latestBuild }),
|
||||
extensions,
|
||||
exclude: bundle.babelExclude(),
|
||||
}),
|
||||
string({
|
||||
// Import certain extensions as strings
|
||||
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
|
||||
}),
|
||||
replace(
|
||||
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
|
||||
),
|
||||
manifest({
|
||||
publicPath,
|
||||
}),
|
||||
worker(),
|
||||
dontHashPlugin({ dontHash }),
|
||||
isProdBuild && terser(bundle.terserOptions(latestBuild)),
|
||||
isStatsBuild &&
|
||||
visualizer({
|
||||
// https://github.com/btd/rollup-plugin-visualizer#options
|
||||
open: true,
|
||||
sourcemap: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
/**
|
||||
* @type { import("rollup").OutputOptions }
|
||||
*/
|
||||
outputOptions: {
|
||||
// https://rollupjs.org/guide/en/#outputdir
|
||||
dir: outputPath,
|
||||
// https://rollupjs.org/guide/en/#outputformat
|
||||
format: latestBuild ? "es" : "systemjs",
|
||||
// https://rollupjs.org/guide/en/#outputexternallivebindings
|
||||
externalLiveBindings: false,
|
||||
// https://rollupjs.org/guide/en/#outputentryfilenames
|
||||
// https://rollupjs.org/guide/en/#outputchunkfilenames
|
||||
// https://rollupjs.org/guide/en/#outputassetfilenames
|
||||
entryFileNames:
|
||||
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
|
||||
chunkFileNames:
|
||||
isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
|
||||
assetFileNames:
|
||||
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
|
||||
// https://rollupjs.org/guide/en/#outputsourcemap
|
||||
sourcemap: isProdBuild ? true : "inline",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createRollupConfig(
|
||||
bundle.config.app({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createRollupConfig(
|
||||
bundle.config.demo({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createCastConfig = ({ isProdBuild, latestBuild }) => {
|
||||
return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
||||
};
|
||||
|
||||
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
|
||||
return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
|
||||
};
|
||||
|
||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
|
||||
return createRollupConfig(
|
||||
bundle.config.gallery({ isProdBuild, latestBuild })
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createAppConfig,
|
||||
createDemoConfig,
|
||||
createCastConfig,
|
||||
createHassioConfig,
|
||||
createGalleryConfig,
|
||||
};
|
@ -4,12 +4,12 @@ const TerserPlugin = require("terser-webpack-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const WorkerPlugin = require("worker-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const env = require("./env.js");
|
||||
const { babelLoaderConfig } = require("./babel.js");
|
||||
const bundle = require("./bundle");
|
||||
|
||||
const createWebpackConfig = ({
|
||||
entry,
|
||||
outputRoot,
|
||||
outputPath,
|
||||
publicPath,
|
||||
defineOverlay,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
@ -19,24 +19,30 @@ const createWebpackConfig = ({
|
||||
if (!dontHash) {
|
||||
dontHash = new Set();
|
||||
}
|
||||
const ignorePackages = bundle.ignorePackages({ latestBuild });
|
||||
return {
|
||||
mode: isProdBuild ? "production" : "development",
|
||||
devtool: isProdBuild
|
||||
? "cheap-module-source-map"
|
||||
: "eval-cheap-module-source-map",
|
||||
entry,
|
||||
node: false,
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.js$|\.ts$/,
|
||||
exclude: bundle.babelExclude(),
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: bundle.babelOptions({ latestBuild }),
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
esprima: "esprima",
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
@ -44,50 +50,50 @@ const createWebpackConfig = ({
|
||||
parallel: true,
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
terserOptions: {
|
||||
safari10: true,
|
||||
ecma: latestBuild ? undefined : 5,
|
||||
},
|
||||
terserOptions: bundle.terserOptions(latestBuild),
|
||||
}),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new WorkerPlugin(),
|
||||
new ManifestPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProdBuild,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(env.version()),
|
||||
__DEMO__: false,
|
||||
__BACKWARDS_COMPAT__: false,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
),
|
||||
...defineOverlay,
|
||||
new ManifestPlugin({
|
||||
// Only include the JS of entrypoints
|
||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||
}),
|
||||
new webpack.DefinePlugin(
|
||||
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
|
||||
),
|
||||
new webpack.IgnorePlugin({
|
||||
checkResource(resource, context) {
|
||||
// Only use ignore to intercept imports that we don't control
|
||||
// inside node_module dependencies.
|
||||
if (
|
||||
!context.includes("/node_modules/") ||
|
||||
// calling define.amd will call require("!!webpack amd options")
|
||||
resource.startsWith("!!webpack")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let fullPath;
|
||||
try {
|
||||
fullPath = resource.startsWith(".")
|
||||
? path.resolve(context, resource)
|
||||
: require.resolve(resource);
|
||||
} catch (err) {
|
||||
console.error("Error in ignore plugin", resource, context);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return ignorePackages.some((toIgnorePath) =>
|
||||
fullPath.startsWith(toIgnorePath)
|
||||
);
|
||||
},
|
||||
}),
|
||||
// Ignore moment.js locales
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
// Color.js is bloated, it contains all color definitions for all material color sets.
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@polymer\/paper-styles\/color\.js$/,
|
||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
// Ignore roboto pointing at CDN. We use local font-roboto-local.
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@polymer\/font-roboto\/roboto\.js$/,
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@vaadin\/vaadin-material-styles\/font-roboto\.js$/,
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
// Ignore mwc icons pointing at CDN.
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@material\/mwc-icon\/mwc-icon-font\.js$/,
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
].filter(Boolean),
|
||||
],
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
},
|
||||
@ -102,11 +108,8 @@ const createWebpackConfig = ({
|
||||
isProdBuild && !isStatsBuild
|
||||
? "chunk.[chunkhash].js"
|
||||
: "[name].chunk.js",
|
||||
path: path.resolve(
|
||||
outputRoot,
|
||||
latestBuild ? "frontend_latest" : "frontend_es5"
|
||||
),
|
||||
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
|
||||
path: outputPath,
|
||||
publicPath,
|
||||
// To silence warning in worker plugin
|
||||
globalObject: "self",
|
||||
},
|
||||
@ -114,94 +117,31 @@ const createWebpackConfig = ({
|
||||
};
|
||||
|
||||
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createWebpackConfig({
|
||||
entry: {
|
||||
service_worker: "./src/entrypoints/service_worker.ts",
|
||||
app: "./src/entrypoints/app.ts",
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.ts",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
},
|
||||
outputRoot: paths.root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
});
|
||||
return createWebpackConfig(
|
||||
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
|
||||
);
|
||||
};
|
||||
|
||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createWebpackConfig({
|
||||
entry: {
|
||||
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
|
||||
compatibility: path.resolve(
|
||||
paths.polymer_dir,
|
||||
"src/entrypoints/compatibility.ts"
|
||||
),
|
||||
},
|
||||
outputRoot: paths.demo_root,
|
||||
defineOverlay: {
|
||||
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
|
||||
__DEMO__: true,
|
||||
},
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
});
|
||||
return createWebpackConfig(
|
||||
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
|
||||
);
|
||||
};
|
||||
|
||||
const createCastConfig = ({ isProdBuild, latestBuild }) => {
|
||||
const entry = {
|
||||
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
|
||||
};
|
||||
|
||||
if (latestBuild) {
|
||||
entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts");
|
||||
}
|
||||
|
||||
return createWebpackConfig({
|
||||
entry,
|
||||
outputRoot: paths.cast_root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
defineOverlay: {
|
||||
__BACKWARDS_COMPAT__: true,
|
||||
},
|
||||
});
|
||||
return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
||||
};
|
||||
|
||||
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
|
||||
if (latestBuild) {
|
||||
throw new Error("Hass.io does not support latest build!");
|
||||
}
|
||||
const config = createWebpackConfig({
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputRoot: "",
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
dontHash: new Set(["entrypoint"]),
|
||||
});
|
||||
|
||||
config.output.path = paths.hassio_root;
|
||||
config.output.publicPath = paths.hassio_publicPath;
|
||||
|
||||
return config;
|
||||
return createWebpackConfig(
|
||||
bundle.config.hassio({ isProdBuild, latestBuild })
|
||||
);
|
||||
};
|
||||
|
||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
|
||||
const config = createWebpackConfig({
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
|
||||
},
|
||||
outputRoot: paths.gallery_root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
});
|
||||
|
||||
return config;
|
||||
return createWebpackConfig(
|
||||
bundle.config.gallery({ isProdBuild, latestBuild })
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
10
cast/rollup.config.js
Normal file
10
cast/rollup.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
|
||||
const config = rollup.createCastConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
latestBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
@ -46,7 +46,13 @@
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
@ -37,7 +37,13 @@
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
@ -82,6 +82,7 @@ export class HcMain extends HassElement {
|
||||
.hass=${this.hass}
|
||||
.lovelaceConfig=${this._lovelaceConfig}
|
||||
.viewPath=${this._lovelacePath}
|
||||
@config-refresh=${this._generateLovelaceConfig}
|
||||
></hc-lovelace>
|
||||
`;
|
||||
}
|
||||
@ -193,12 +194,7 @@ export class HcMain extends HassElement {
|
||||
} catch (err) {
|
||||
// Generate a Lovelace config.
|
||||
this._unsubLovelace = () => undefined;
|
||||
const { generateLovelaceConfigFromHass } = await import(
|
||||
"../../../../src/panels/lovelace/common/generate-lovelace-config"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceConfigFromHass(this.hass!)
|
||||
);
|
||||
await this._generateLovelaceConfig();
|
||||
}
|
||||
}
|
||||
if (!resourcesLoaded) {
|
||||
@ -218,6 +214,15 @@ export class HcMain extends HassElement {
|
||||
this._sendStatus();
|
||||
}
|
||||
|
||||
private async _generateLovelaceConfig() {
|
||||
const { generateLovelaceConfigFromHass } = await import(
|
||||
"../../../../src/panels/lovelace/common/generate-lovelace-config"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceConfigFromHass(this.hass!)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
||||
castContext.setApplicationState(lovelaceConfig.title!);
|
||||
this._lovelaceConfig = lovelaceConfig;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../src/resources/roboto";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "./layout/hc-lovelace";
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"background_color": "#FFFFFF",
|
||||
"description": "Open-source home automation platform running on Python 3.",
|
||||
"description": "Home automation platform that puts local control and privacy first.",
|
||||
"dir": "ltr",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
@ -31,7 +31,7 @@
|
||||
],
|
||||
"lang": "en-US",
|
||||
"name": "Home Assistant Demo",
|
||||
"short_name": "Demo",
|
||||
"short_name": "HA Demo",
|
||||
"start_url": "/?homescreen=1",
|
||||
"theme_color": "#03A9F4"
|
||||
}
|
||||
|
10
demo/rollup.config.js
Normal file
10
demo/rollup.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
|
||||
const config = rollup.createDemoConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
latestBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-styles/typography";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../../src/resources/ha-style";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "../../src/resources/compatibility";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import {
|
||||
|
@ -5,18 +5,6 @@
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" href="/static/icons/favicon.ico" />
|
||||
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Regular.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Medium.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
@ -96,6 +84,7 @@
|
||||
<div id="ha-init-skeleton"></div>
|
||||
<ha-demo></ha-demo>
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" src="<%= latestDemoJS %>"></script>
|
||||
|
||||
@ -104,8 +93,13 @@
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5DemoJS %>");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5DemoJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5DemoJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
10
gallery/rollup.config.js
Normal file
10
gallery/rollup.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
|
||||
const config = rollup.createGalleryConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
latestBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-styles/typography";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../../src/resources/ha-style";
|
||||
|
@ -10,6 +10,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../src/components/ha-card";
|
||||
import "../../src/managers/notification-manager";
|
||||
import "../../src/styles/polymer-ha-style";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/im);
|
||||
|
10
hassio/rollup.config.js
Normal file
10
hassio/rollup.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
|
||||
const config = rollup.createHassioConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
latestBuild: false,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
@ -64,6 +64,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
padding: 8px;
|
||||
max-width: 1024px;
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -630,14 +630,10 @@ class HassioAddonInfo extends LitElement {
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
ha-markdown img {
|
||||
max-width: 100%;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
.description a,
|
||||
ha-markdown a {
|
||||
.description a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.red {
|
||||
@ -675,6 +671,9 @@ class HassioAddonInfo extends LitElement {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as Fuse from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
|
||||
|
||||
export function filterAndSort(addons: HassioAddonInfo[], filter: string) {
|
||||
const options: Fuse.FuseOptions<HassioAddonInfo> = {
|
||||
const options: Fuse.IFuseOptions<HassioAddonInfo> = {
|
||||
keys: ["name", "description", "slug"],
|
||||
caseSensitive: false,
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(addons, options);
|
||||
return fuse.search(filter);
|
||||
return fuse.search(filter).map((result) => result.item);
|
||||
}
|
||||
|
@ -90,6 +90,9 @@ class HassioMarkdownDialog extends LitElement {
|
||||
color: var(--text-primary-color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import "../../src/resources/ha-style";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
|
||||
|
18
package.json
18
package.json
@ -86,10 +86,10 @@
|
||||
"deep-freeze": "^0.0.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^3.4.4",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^5.1.2",
|
||||
"home-assistant-js-websocket": "^5.2.1",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@ -126,6 +126,10 @@
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@rollup/plugin-commonjs": "^11.1.0",
|
||||
"@rollup/plugin-json": "^4.0.3",
|
||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||
"@rollup/plugin-replace": "^2.3.2",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||
"@types/codemirror": "^0.0.78",
|
||||
@ -165,22 +169,30 @@
|
||||
"lint-staged": "^8.1.5",
|
||||
"lit-analyzer": "^1.1.10",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.25.7",
|
||||
"map-stream": "^0.0.7",
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^6.0.2",
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"prettier": "^2.0.4",
|
||||
"raw-loader": "^2.0.0",
|
||||
"require-dir": "^1.2.0",
|
||||
"rollup": "^2.8.2",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"rollup-plugin-terser": "^5.3.0",
|
||||
"rollup-plugin-visualizer": "^4.0.4",
|
||||
"serve": "^11.3.0",
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-lit-plugin": "^1.1.10",
|
||||
"ts-mocha": "^6.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"web-component-tester": "^6.9.2",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
|
10
rollup.config.js
Normal file
10
rollup.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const rollup = require("./build-scripts/rollup.js");
|
||||
const env = require("./build-scripts/env.js");
|
||||
|
||||
const config = rollup.createAppConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
latestBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200519.5",
|
||||
version="20200603.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -83,12 +83,24 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
${this._renderStep(this._step)}
|
||||
<div class="action">
|
||||
<mwc-button raised @click=${this._handleSubmit}
|
||||
>${this._step.type === "form" ? "Next" : "Start over"}</mwc-button
|
||||
>${this._step.type === "form"
|
||||
? this.localize("ui.panel.page-authorize.form.next")
|
||||
: this.localize(
|
||||
"ui.panel.page-authorize.form.start_over"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
`;
|
||||
case "error":
|
||||
return html` <div class="error">Error: ${this._errorMessage}</div> `;
|
||||
return html`
|
||||
<div class="error">
|
||||
${this.localize(
|
||||
"ui.panel.page-authorize.form.error",
|
||||
"error",
|
||||
this._errorMessage
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
case "loading":
|
||||
return html` ${this.localize("ui.panel.page-authorize.form.working")} `;
|
||||
default:
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { wrap } from "comlink";
|
||||
|
||||
type FilterDataType = typeof import("./sort_filter_worker").api["filterData"];
|
||||
import type { api } from "./sort_filter_worker";
|
||||
|
||||
type FilterDataType = api["filterData"];
|
||||
type FilterDataParamTypes = Parameters<FilterDataType>;
|
||||
|
||||
type SortDataType = typeof import("./sort_filter_worker").api["sortData"];
|
||||
type SortDataType = api["sortData"];
|
||||
type SortDataParamTypes = Parameters<SortDataType>;
|
||||
|
||||
let worker: any | undefined;
|
||||
|
@ -67,10 +67,11 @@ const sortData = (
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Export for types
|
||||
export const api = {
|
||||
const api = {
|
||||
filterData,
|
||||
sortData,
|
||||
};
|
||||
|
||||
export type api = typeof api;
|
||||
|
||||
expose(api);
|
||||
|
@ -52,7 +52,7 @@ const rowRenderer = (root: HTMLElement, _owner, model: { item: Device }) => {
|
||||
}
|
||||
</style>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
<paper-item-body two-line="">
|
||||
<div class='name'>[[item.name]]</div>
|
||||
<div secondary>[[item.area]]</div>
|
||||
</paper-item-body>
|
||||
@ -188,7 +188,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
),
|
||||
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||
area: device.area_id
|
||||
? areaLookup[device.area_id].name
|
||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||
};
|
||||
});
|
||||
if (outputDevices.length === 1) {
|
||||
|
71
src/components/ha-markdown-element.ts
Normal file
71
src/components/ha-markdown-element.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { customElement, property, UpdatingElement } from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { renderMarkdown } from "../resources/render-markdown";
|
||||
|
||||
@customElement("ha-markdown-element")
|
||||
class HaMarkdownElement extends UpdatingElement {
|
||||
@property() public content?;
|
||||
|
||||
@property({ type: Boolean }) public allowSvg = false;
|
||||
|
||||
@property({ type: Boolean }) public breaks = false;
|
||||
|
||||
protected update(changedProps) {
|
||||
super.update(changedProps);
|
||||
if (this.content !== undefined) {
|
||||
this._render();
|
||||
}
|
||||
}
|
||||
|
||||
private async _render() {
|
||||
this.innerHTML = await renderMarkdown(
|
||||
this.content,
|
||||
{
|
||||
breaks: this.breaks,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
},
|
||||
{
|
||||
allowSvg: this.allowSvg,
|
||||
}
|
||||
);
|
||||
|
||||
this._resize();
|
||||
|
||||
const walker = document.createTreeWalker(
|
||||
this,
|
||||
1 /* SHOW_ELEMENT */,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
while (walker.nextNode()) {
|
||||
const node = walker.currentNode;
|
||||
|
||||
// Open external links in a new window
|
||||
if (
|
||||
node instanceof HTMLAnchorElement &&
|
||||
node.host !== document.location.host
|
||||
) {
|
||||
node.target = "_blank";
|
||||
node.rel = "noreferrer";
|
||||
|
||||
// protect referrer on external links and deny window.opener access for security reasons
|
||||
// (see https://mathiasbynens.github.io/rel-noopener/)
|
||||
node.rel = "noreferrer noopener";
|
||||
|
||||
// Fire a resize event when images loaded to notify content resized
|
||||
} else if (node instanceof HTMLImageElement) {
|
||||
node.addEventListener("load", this._resize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _resize = () => fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-markdown-element": HaMarkdownElement;
|
||||
}
|
||||
}
|
@ -1,65 +1,80 @@
|
||||
import { customElement, property, UpdatingElement } from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { renderMarkdown } from "../resources/render-markdown";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import "./ha-markdown-element";
|
||||
|
||||
@customElement("ha-markdown")
|
||||
class HaMarkdown extends UpdatingElement {
|
||||
@property() public content = "";
|
||||
class HaMarkdown extends LitElement {
|
||||
@property() public content?;
|
||||
|
||||
@property({ type: Boolean }) public allowSvg = false;
|
||||
|
||||
@property({ type: Boolean }) public breaks = false;
|
||||
|
||||
protected update(changedProps) {
|
||||
super.update(changedProps);
|
||||
this._render();
|
||||
}
|
||||
|
||||
private async _render() {
|
||||
this.innerHTML = await renderMarkdown(
|
||||
this.content,
|
||||
{
|
||||
breaks: this.breaks,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
},
|
||||
{
|
||||
allowSvg: this.allowSvg,
|
||||
}
|
||||
);
|
||||
|
||||
this._resize();
|
||||
|
||||
const walker = document.createTreeWalker(
|
||||
this,
|
||||
1 /* SHOW_ELEMENT */,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
while (walker.nextNode()) {
|
||||
const node = walker.currentNode;
|
||||
|
||||
// Open external links in a new window
|
||||
if (
|
||||
node instanceof HTMLAnchorElement &&
|
||||
node.host !== document.location.host
|
||||
) {
|
||||
node.target = "_blank";
|
||||
node.rel = "noreferrer";
|
||||
|
||||
// protect referrer on external links and deny window.opener access for security reasons
|
||||
// (see https://mathiasbynens.github.io/rel-noopener/)
|
||||
node.rel = "noreferrer noopener";
|
||||
|
||||
// Fire a resize event when images loaded to notify content resized
|
||||
} else if (node instanceof HTMLImageElement) {
|
||||
node.addEventListener("load", this._resize);
|
||||
}
|
||||
protected render(): TemplateResult {
|
||||
if (!this.content) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`<ha-markdown-element
|
||||
.content=${this.content}
|
||||
.allowSvg=${this.allowSvg}
|
||||
.breaks=${this.breaks}
|
||||
></ha-markdown-element>`;
|
||||
}
|
||||
|
||||
private _resize = () => fireEvent(this, "iron-resize");
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-markdown-element {
|
||||
-ms-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
}
|
||||
ha-markdown-element > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-markdown-element > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ha-markdown-element a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-markdown-element img {
|
||||
max-width: 100%;
|
||||
}
|
||||
ha-markdown-element code,
|
||||
pre {
|
||||
background-color: var(--markdown-code-background-color, #f6f8fa);
|
||||
border-radius: 3px;
|
||||
}
|
||||
ha-markdown-element code {
|
||||
font-size: 85%;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
ha-markdown-element pre code {
|
||||
padding: 0;
|
||||
}
|
||||
ha-markdown-element pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
line-height: 1.45;
|
||||
}
|
||||
ha-markdown-element h2 {
|
||||
font-size: 1.5em !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -65,14 +65,15 @@ export const fetchDeviceTriggerCapabilities = (
|
||||
trigger,
|
||||
});
|
||||
|
||||
const whitelist = [
|
||||
"above",
|
||||
"below",
|
||||
"brightness_pct",
|
||||
"code",
|
||||
"for",
|
||||
"position",
|
||||
"set_brightness",
|
||||
const deviceAutomationIdentifiers = [
|
||||
"device_id",
|
||||
"domain",
|
||||
"entity_id",
|
||||
"type",
|
||||
"subtype",
|
||||
"event",
|
||||
"condition",
|
||||
"platform",
|
||||
];
|
||||
|
||||
export const deviceAutomationsEqual = (
|
||||
@ -84,7 +85,7 @@ export const deviceAutomationsEqual = (
|
||||
}
|
||||
|
||||
for (const property in a) {
|
||||
if (whitelist.includes(property)) {
|
||||
if (!deviceAutomationIdentifiers.includes(property)) {
|
||||
continue;
|
||||
}
|
||||
if (!Object.is(a[property], b[property])) {
|
||||
@ -92,7 +93,7 @@ export const deviceAutomationsEqual = (
|
||||
}
|
||||
}
|
||||
for (const property in b) {
|
||||
if (whitelist.includes(property)) {
|
||||
if (!deviceAutomationIdentifiers.includes(property)) {
|
||||
continue;
|
||||
}
|
||||
if (!Object.is(a[property], b[property])) {
|
||||
|
@ -56,7 +56,8 @@ export const fetchRecent = (
|
||||
startTime,
|
||||
endTime,
|
||||
skipInitialState = false,
|
||||
significantChangesOnly?: boolean
|
||||
significantChangesOnly?: boolean,
|
||||
minimalResponse = true
|
||||
): Promise<HassEntity[][]> => {
|
||||
let url = "history/period";
|
||||
if (startTime) {
|
||||
@ -72,6 +73,9 @@ export const fetchRecent = (
|
||||
if (significantChangesOnly !== undefined) {
|
||||
url += `&significant_changes_only=${Number(significantChangesOnly)}`;
|
||||
}
|
||||
if (minimalResponse) {
|
||||
url += "&minimal_response";
|
||||
}
|
||||
|
||||
return hass.callApi("GET", url);
|
||||
};
|
||||
@ -83,14 +87,17 @@ export const fetchDate = (
|
||||
): Promise<HassEntity[][]> => {
|
||||
return hass.callApi(
|
||||
"GET",
|
||||
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}`
|
||||
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}&minimal_response`
|
||||
);
|
||||
};
|
||||
|
||||
const equalState = (obj1: LineChartState, obj2: LineChartState) =>
|
||||
obj1.state === obj2.state &&
|
||||
// They either both have an attributes object or not
|
||||
// Only compare attributes if both states have an attributes object.
|
||||
// When `minimal_response` is sent, only the first and last state
|
||||
// will have attributes except for domains in DOMAINS_USE_LAST_UPDATED.
|
||||
(!obj1.attributes ||
|
||||
!obj2.attributes ||
|
||||
LINE_ATTRIBUTES_TO_KEEP.every(
|
||||
(attr) => obj1.attributes![attr] === obj2.attributes![attr]
|
||||
));
|
||||
@ -101,12 +108,20 @@ const processTimelineEntity = (
|
||||
states: HassEntity[]
|
||||
): TimelineEntity => {
|
||||
const data: TimelineState[] = [];
|
||||
const last_element = states.length - 1;
|
||||
|
||||
for (const state of states) {
|
||||
if (data.length > 0 && state.state === data[data.length - 1].state) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy the data from the last element as its the newest
|
||||
// and is only needed to localize the data
|
||||
if (!state.entity_id) {
|
||||
state.attributes = states[last_element].attributes;
|
||||
state.entity_id = states[last_element].entity_id;
|
||||
}
|
||||
|
||||
data.push({
|
||||
state_localize: computeStateDisplay(localize, state, language),
|
||||
state: state.state,
|
||||
@ -198,7 +213,7 @@ export const computeHistory = (
|
||||
}
|
||||
|
||||
const stateWithUnit = stateInfo.find(
|
||||
(state) => "unit_of_measurement" in state.attributes
|
||||
(state) => state.attributes && "unit_of_measurement" in state.attributes
|
||||
);
|
||||
|
||||
let unit: string | undefined;
|
||||
|
@ -28,7 +28,6 @@ import {
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import "../../resources/ha-style";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
||||
|
@ -16,7 +16,6 @@ import "../../components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
import type { DataEntryFlowStepForm } from "../../data/data_entry_flow";
|
||||
import "../../resources/ha-style";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import * as Fuse from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -52,14 +52,14 @@ class StepFlowPickHandler extends LitElement {
|
||||
});
|
||||
|
||||
if (filter) {
|
||||
const options: Fuse.FuseOptions<HandlerObj> = {
|
||||
const options: Fuse.IFuseOptions<HandlerObj> = {
|
||||
keys: ["name", "slug"],
|
||||
caseSensitive: false,
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(handlers, options);
|
||||
return fuse.search(filter);
|
||||
return fuse.search(filter).map((result) => result.item);
|
||||
}
|
||||
return handlers.sort((a, b) =>
|
||||
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
|
||||
|
@ -5,7 +5,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import DialogMixin from "../mixins/dialog-mixin";
|
||||
import "../resources/ha-style";
|
||||
import "../styles/polymer-ha-style-dialog";
|
||||
import "./more-info/more-info-controls";
|
||||
|
||||
/*
|
||||
|
@ -4,7 +4,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { enableWrite } from "../common/auth/token_storage";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import "../resources/ha-style";
|
||||
import "../styles/polymer-ha-style";
|
||||
|
||||
class HaStoreAuth extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -17,7 +17,7 @@ import "../../data/ha-state-history-data";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
|
||||
import "../../resources/ha-style";
|
||||
import "../../styles/polymer-ha-style-dialog";
|
||||
import "../../state-summary/state-card-content";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import "./controls/more-info-content";
|
||||
|
@ -1,10 +1,7 @@
|
||||
// Load polyfill first so HTML imports start resolving
|
||||
/* eslint-disable import/first */
|
||||
import "@polymer/paper-styles/typography";
|
||||
import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../layouts/home-assistant";
|
||||
import "../resources/html-import/polyfill";
|
||||
import "../resources/roboto";
|
||||
import "../resources/ha-style";
|
||||
import "../layouts/home-assistant";
|
||||
import "../util/legacy-support";
|
||||
|
||||
setPassiveTouchGestures(true);
|
||||
@ -12,3 +9,5 @@ setPassiveTouchGestures(true);
|
||||
document.createElement = Document.prototype.createElement;
|
||||
|
||||
(window as any).frontendVersion = __VERSION__;
|
||||
|
||||
import("../resources/html-import/polyfill");
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Compat needs to be first import
|
||||
import "../resources/compatibility";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../auth/ha-authorize";
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Compat needs to be first import
|
||||
import "../resources/compatibility";
|
||||
import {
|
||||
Auth,
|
||||
Connection,
|
||||
@ -26,6 +28,7 @@ import { HomeAssistant } from "../types";
|
||||
declare global {
|
||||
interface Window {
|
||||
hassConnection: Promise<{ auth: Auth; conn: Connection }>;
|
||||
hassConnectionReady?: (hassConnection: Window["hassConnection"]) => void;
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +83,11 @@ window.hassConnection = (authProm() as Promise<Auth | ExternalAuth>).then(
|
||||
connProm
|
||||
);
|
||||
|
||||
// This is set if app was somehow loaded before core.
|
||||
if (window.hassConnectionReady) {
|
||||
window.hassConnectionReady(window.hassConnection);
|
||||
}
|
||||
|
||||
// Start fetching some of the data that we will need.
|
||||
window.hassConnection.then(({ conn }) => {
|
||||
const noop = () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "../resources/compatibility";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { loadJS } from "../common/dom/load_resource";
|
||||
@ -17,12 +18,9 @@ let es5Loaded: Promise<unknown> | undefined;
|
||||
|
||||
window.loadES5Adapter = () => {
|
||||
if (!es5Loaded) {
|
||||
es5Loaded = Promise.all([
|
||||
loadJS(
|
||||
`${__STATIC_PATH__}polyfills/custom-elements-es5-adapter.js`
|
||||
).catch(),
|
||||
import(/* webpackChunkName: "compat" */ "./compatibility"),
|
||||
]);
|
||||
es5Loaded = loadJS(
|
||||
`${__STATIC_PATH__}polyfills/custom-elements-es5-adapter.js`
|
||||
).catch(); // Swallow errors as it raises errors on old browsers.
|
||||
}
|
||||
return es5Loaded;
|
||||
};
|
||||
@ -51,7 +49,6 @@ function initialize(panel: CustomPanelInfo, properties: {}) {
|
||||
}
|
||||
|
||||
if (__BUILD__ === "es5") {
|
||||
// Load ES5 adapter. Swallow errors as it raises errors on old browsers.
|
||||
start = start.then(() => window.loadES5Adapter());
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Compat needs to be first import
|
||||
import "../resources/compatibility";
|
||||
import "../onboarding/ha-onboarding";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HassConfig } from "home-assistant-js-websocket";
|
||||
import { HassConfig, STATE_RUNNING } from "home-assistant-js-websocket";
|
||||
|
||||
export const demoConfig: HassConfig = {
|
||||
location_name: "Home",
|
||||
@ -18,6 +18,7 @@ export const demoConfig: HassConfig = {
|
||||
whitelist_external_dirs: [],
|
||||
config_source: "storage",
|
||||
safe_mode: false,
|
||||
state: STATE_RUNNING,
|
||||
internal_url: "http://homeassistant.local:8123",
|
||||
external_url: null,
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
);
|
||||
script.defer = true;
|
||||
script.src = src;
|
||||
return script;
|
||||
}
|
||||
window.Polymer = {
|
||||
lazyRegister: true,
|
||||
|
16
src/html/_preload_roboto.html.template
Normal file
16
src/html/_preload_roboto.html.template
Normal file
@ -0,0 +1,16 @@
|
||||
<script>
|
||||
if (navigator.userAgent.indexOf("Android") === -1 &&
|
||||
navigator.userAgent.indexOf("CrOS") === -1) {
|
||||
function _pf(src, type) {
|
||||
const el = document.createElement("link");
|
||||
el.rel = "preload";
|
||||
el.as = "font";
|
||||
el.type = "font/woff2";
|
||||
el.href = src;
|
||||
el.crossOrigin = "anonymous";
|
||||
document.head.append(el);
|
||||
}
|
||||
_pf("/static/fonts/roboto/Roboto-Regular.woff2");
|
||||
_pf("/static/fonts/roboto/Roboto-Medium.woff2");
|
||||
}
|
||||
</script>
|
@ -3,18 +3,6 @@
|
||||
<head>
|
||||
<title>Home Assistant</title>
|
||||
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Light.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Regular.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<%= renderTemplate('_header') %>
|
||||
<style>
|
||||
.content {
|
||||
@ -46,6 +34,7 @@
|
||||
</div>
|
||||
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestPageJS %>";
|
||||
@ -59,8 +48,13 @@
|
||||
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5PageJS %>");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5PageJS %>");
|
||||
}
|
||||
<% } else { %>
|
||||
_ls("<%= es5PageJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
@ -2,18 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preload" href="<%= latestCoreJS %>" as="script" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Regular.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Medium.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<link rel="preload" href="<%= latestAppJS %>" as="script" crossorigin="use-credentials" />
|
||||
<%= renderTemplate('_header') %>
|
||||
<title>Home Assistant</title>
|
||||
<link
|
||||
@ -61,26 +50,36 @@
|
||||
<home-assistant> </home-assistant>
|
||||
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestCoreJS %>";
|
||||
import "<%= latestAppJS %>";
|
||||
<script>
|
||||
import("<%= latestCoreJS %>");
|
||||
import("<%= latestAppJS %>");
|
||||
window.customPanelJS = "<%= latestCustomPanelJS %>";
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
{% for extra_module in extra_modules -%}
|
||||
<script type="module" crossorigin="use-credentials" src="{{ extra_module }}"></script>
|
||||
{% endfor -%}
|
||||
|
||||
|
||||
<script nomodule>
|
||||
<script>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
if (!window.latestJS) {
|
||||
window.customPanelJS = "<%= es5CustomPanelJS %>";
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5CoreJS %>");
|
||||
_ls("<%= es5AppJS %>");
|
||||
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
// Although core and app can load in any order, we need to
|
||||
// force loading core first because it contains polyfills
|
||||
return System.import("<%= es5CoreJS %>").then(function() {
|
||||
System.import("<%= es5AppJS %>");
|
||||
});
|
||||
}
|
||||
<% } else { %>
|
||||
_ls("<%= es5CoreJS %>");
|
||||
_ls("<%= es5AppJS %>");
|
||||
<% } %>
|
||||
{% for extra_script in extra_js_es5 -%}
|
||||
_ls("{{ extra_script }}");
|
||||
{% endfor -%}
|
||||
|
@ -3,18 +3,6 @@
|
||||
<head>
|
||||
<title>Home Assistant</title>
|
||||
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Light.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/static/fonts/roboto/Roboto-Regular.woff2"
|
||||
as="font"
|
||||
crossorigin
|
||||
/>
|
||||
<%= renderTemplate('_header') %>
|
||||
<style>
|
||||
.content {
|
||||
@ -48,6 +36,7 @@
|
||||
</div>
|
||||
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestPageJS %>";
|
||||
@ -61,8 +50,13 @@
|
||||
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5PageJS %>");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5PageJS %>");
|
||||
}
|
||||
<% } else { %>
|
||||
_ls("<%= es5PageJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
@ -3,7 +3,6 @@ import { html, property, PropertyValues } from "lit-element";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { getStorageDefaultPanelUrlPath } from "../data/panel";
|
||||
import "../resources/custom-card-support";
|
||||
import "../resources/ha-style";
|
||||
import { HassElement } from "../state/hass-element";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
import {
|
||||
@ -94,7 +93,18 @@ export class HomeAssistantAppEl extends HassElement {
|
||||
|
||||
protected async _initialize() {
|
||||
try {
|
||||
const { auth, conn } = await window.hassConnection;
|
||||
let result;
|
||||
|
||||
if (window.hassConnection) {
|
||||
result = await window.hassConnection;
|
||||
} else {
|
||||
// In the edge case that
|
||||
result = await new Promise((resolve) => {
|
||||
window.hassConnectionReady = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
const { auth, conn } = result;
|
||||
this._haVersion = conn.haVersion;
|
||||
this.initializeHass(auth, conn);
|
||||
} catch (err) {
|
||||
|
@ -8,6 +8,11 @@ import {
|
||||
RouteOptions,
|
||||
RouterOptions,
|
||||
} from "./hass-router-page";
|
||||
import {
|
||||
STATE_STARTING,
|
||||
STATE_NOT_RUNNING,
|
||||
STATE_RUNNING,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
const CACHE_URL_PATHS = ["lovelace", "developer-tools"];
|
||||
const COMPONENTS = {
|
||||
@ -84,6 +89,8 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
|
||||
@property() public narrow?: boolean;
|
||||
|
||||
private _waitForStart = false;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
@ -93,6 +100,15 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
|
||||
if (
|
||||
this._waitForStart &&
|
||||
(this.hass.config.state === STATE_STARTING ||
|
||||
this.hass.config.state === STATE_RUNNING)
|
||||
) {
|
||||
this._waitForStart = false;
|
||||
this.rebuild();
|
||||
}
|
||||
|
||||
if (this.hass.panels && (!oldHass || oldHass.panels !== this.hass.panels)) {
|
||||
this._updateRoutes(oldHass?.panels);
|
||||
}
|
||||
@ -128,6 +144,21 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
private async _updateRoutes(oldPanels?: HomeAssistant["panels"]) {
|
||||
this.routerOptions = getRoutes(this.hass.panels);
|
||||
|
||||
if (
|
||||
!this._waitForStart &&
|
||||
this._currentPage &&
|
||||
!this.hass.panels[this._currentPage]
|
||||
) {
|
||||
if (this.hass.config.state !== STATE_NOT_RUNNING) {
|
||||
this._waitForStart = true;
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
this.appendChild(this.createLoadingScreen());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!oldPanels ||
|
||||
!deepEqual(
|
||||
|
@ -24,7 +24,11 @@ export const SubscribeMixin = <T extends Constructor<UpdatingElement>>(
|
||||
if (this.__unsubs) {
|
||||
while (this.__unsubs.length) {
|
||||
const unsub = this.__unsubs.pop()!;
|
||||
Promise.resolve(unsub).then((unsubFunc) => unsubFunc());
|
||||
if (unsub instanceof Promise) {
|
||||
unsub.then((unsubFunc) => unsubFunc());
|
||||
} else {
|
||||
unsub();
|
||||
}
|
||||
}
|
||||
this.__unsubs = undefined;
|
||||
}
|
||||
|
@ -56,13 +56,24 @@ class DialogAreaDetail extends LitElement {
|
||||
<paper-dialog-scrollable>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="form">
|
||||
${entry ? html` <div>Area ID: ${entry.area_id}</div> ` : ""}
|
||||
${entry
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.area_id"
|
||||
)}:
|
||||
${entry.area_id}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
@value-changed=${this._nameChanged}
|
||||
label="Name"
|
||||
error-message="Name is required"
|
||||
.label=${this.hass.localize("ui.panel.config.areas.editor.name")}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.name_required"
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
</div>
|
||||
@ -110,7 +121,9 @@ class DialogAreaDetail extends LitElement {
|
||||
}
|
||||
this._params = undefined;
|
||||
} catch (err) {
|
||||
this._error = err.message || "Unknown error";
|
||||
this._error =
|
||||
err.message ||
|
||||
this.hass.localize("ui.panel.config.areas.editor.unknown_error");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
@ -51,14 +51,18 @@ export class HaDeviceAction extends LitElement {
|
||||
.value=${deviceId}
|
||||
@value-changed=${this._devicePicked}
|
||||
.hass=${this.hass}
|
||||
label="Device"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.device_id.label"
|
||||
)}
|
||||
></ha-device-picker>
|
||||
<ha-device-action-picker
|
||||
.value=${this.action}
|
||||
.deviceId=${deviceId}
|
||||
@value-changed=${this._deviceActionPicked}
|
||||
.hass=${this.hass}
|
||||
label="Action"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.device_id.action"
|
||||
)}
|
||||
></ha-device-action-picker>
|
||||
${extraFieldsData
|
||||
? html`
|
||||
@ -125,7 +129,7 @@ export class HaDeviceAction extends LitElement {
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.actions.type.device.extra_fields.${schema.name}`
|
||||
`ui.panel.config.automation.editor.actions.type.device_id.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
}
|
||||
|
@ -45,14 +45,18 @@ export class HaDeviceCondition extends LitElement {
|
||||
.value=${deviceId}
|
||||
@value-changed=${this._devicePicked}
|
||||
.hass=${this.hass}
|
||||
label="Device"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.device.label"
|
||||
)}
|
||||
></ha-device-picker>
|
||||
<ha-device-condition-picker
|
||||
.value=${this.condition}
|
||||
.deviceId=${deviceId}
|
||||
@value-changed=${this._deviceConditionPicked}
|
||||
.hass=${this.hass}
|
||||
label="Condition"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.device.condition"
|
||||
)}
|
||||
></ha-device-condition-picker>
|
||||
${extraFieldsData
|
||||
? html`
|
||||
|
@ -45,14 +45,18 @@ export class HaDeviceTrigger extends LitElement {
|
||||
.value=${deviceId}
|
||||
@value-changed=${this._devicePicked}
|
||||
.hass=${this.hass}
|
||||
label="Device"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.device.label"
|
||||
)}
|
||||
></ha-device-picker>
|
||||
<ha-device-trigger-picker
|
||||
.value=${this.trigger}
|
||||
.deviceId=${deviceId}
|
||||
@value-changed=${this._deviceTriggerPicked}
|
||||
.hass=${this.hass}
|
||||
label="Trigger"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.device.trigger"
|
||||
)}
|
||||
></ha-device-trigger-picker>
|
||||
${extraFieldsData
|
||||
? html`
|
||||
|
@ -10,7 +10,7 @@ import { fetchCloudSubscriptionInfo } from "../../../../data/cloud";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
import "../../../../resources/ha-style";
|
||||
import "../../../../styles/polymer-ha-style";
|
||||
import "../../ha-config-section";
|
||||
import "./cloud-alexa-pref";
|
||||
import "./cloud-google-pref";
|
||||
|
@ -7,7 +7,7 @@ import "../../../../components/ha-card";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
import "../../../../resources/ha-style";
|
||||
import "../../../../styles/polymer-ha-style";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -14,7 +14,7 @@ import "../../../../layouts/hass-subpage";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
import NavigateMixin from "../../../../mixins/navigate-mixin";
|
||||
import "../../../../resources/ha-style";
|
||||
import "../../../../styles/polymer-ha-style";
|
||||
import "../../ha-config-section";
|
||||
|
||||
/*
|
||||
|
@ -7,7 +7,7 @@ import "../../../../components/ha-card";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
import "../../../../resources/ha-style";
|
||||
import "../../../../styles/polymer-ha-style";
|
||||
import "../../ha-config-section";
|
||||
|
||||
/*
|
||||
|
@ -6,7 +6,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./ha-config-section-core";
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../ha-config-section";
|
||||
import "./ha-config-core-form";
|
||||
import "./ha-config-name-form";
|
||||
|
@ -6,7 +6,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { sortStatesByName } from "../../../common/entity/states_sort_by_name";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../ha-config-section";
|
||||
import "../ha-entity-config";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
@ -37,7 +37,7 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
|
||||
</span>
|
||||
<ha-entity-config
|
||||
hass="[[hass]]"
|
||||
label="Entity"
|
||||
label="[[localize('ui.panel.config.customize.picker.entity')]]"
|
||||
entities="[[entities]]"
|
||||
config="[[entityConfig]]"
|
||||
>
|
||||
|
@ -8,6 +8,8 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import hassAttributeUtil from "../../../util/hass-attributes-util";
|
||||
import "./ha-form-customize-attributes";
|
||||
import "../ha-form-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
class HaFormCustomize extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -154,7 +154,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
),
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||
area: device.area_id
|
||||
? areaLookup[device.area_id].name
|
||||
: this.hass.localize("ui.panel.config.devices.data_table.no_area"),
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
|
@ -8,6 +8,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import "../../components/ha-card";
|
||||
import "../../styles/polymer-ha-style";
|
||||
|
||||
class HaEntityConfig extends PolymerElement {
|
||||
static get template() {
|
||||
|
@ -108,7 +108,7 @@ export class DialogHelperDetail extends LitElement {
|
||||
@click="${this._goBack}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
Back
|
||||
${this.hass!.localize("ui.common.back")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import * as Fuse from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
@ -149,14 +149,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
if (!filter) {
|
||||
return [...configEntries];
|
||||
}
|
||||
const options: Fuse.FuseOptions<ConfigEntryExtended> = {
|
||||
const options: Fuse.IFuseOptions<ConfigEntryExtended> = {
|
||||
keys: ["domain", "localized_domain_name", "title"],
|
||||
caseSensitive: false,
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(configEntries, options);
|
||||
return fuse.search(filter);
|
||||
return fuse.search(filter).map((result) => result.item);
|
||||
}
|
||||
);
|
||||
|
||||
@ -193,14 +193,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
if (!filter) {
|
||||
return configEntriesInProgress;
|
||||
}
|
||||
const options: Fuse.FuseOptions<DataEntryFlowProgressExtended> = {
|
||||
const options: Fuse.IFuseOptions<DataEntryFlowProgressExtended> = {
|
||||
keys: ["handler", "localized_title"],
|
||||
caseSensitive: false,
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(configEntriesInProgress, options);
|
||||
return fuse.search(filter);
|
||||
return fuse.search(filter).map((result) => result.item);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -152,7 +152,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
columns.url_path = {
|
||||
title: "",
|
||||
filterable: true,
|
||||
width: "75px",
|
||||
width: "100px",
|
||||
template: (urlPath) =>
|
||||
narrow
|
||||
? html`
|
||||
|
@ -7,7 +7,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../ha-config-section";
|
||||
|
||||
/*
|
||||
|
@ -5,7 +5,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./ha-config-section-server-control";
|
||||
|
||||
|
@ -85,7 +85,7 @@ export class DialogAddUser extends LitElement {
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize="on"
|
||||
error-message="Required"
|
||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||
@value-changed=${this._nameChanged}
|
||||
@blur=${this._maybePopulateUsername}
|
||||
></paper-input>
|
||||
@ -99,7 +99,7 @@ export class DialogAddUser extends LitElement {
|
||||
auto-validate
|
||||
autocapitalize="none"
|
||||
@value-changed=${this._usernameChanged}
|
||||
error-message="Required"
|
||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
@ -110,7 +110,7 @@ export class DialogAddUser extends LitElement {
|
||||
required
|
||||
auto-validate
|
||||
@value-changed=${this._passwordChanged}
|
||||
error-message="Required"
|
||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||
></paper-input>
|
||||
<ha-switch .checked=${this._isAdmin} @change=${this._adminChanged}>
|
||||
${this.hass.localize("ui.panel.config.users.editor.admin")}
|
||||
@ -118,10 +118,9 @@ export class DialogAddUser extends LitElement {
|
||||
${!this._isAdmin
|
||||
? html`
|
||||
<br />
|
||||
The users group is a work in progress. The user will be unable
|
||||
to administer the instance via the UI. We're still auditing all
|
||||
management API endpoints to ensure that they correctly limit
|
||||
access to administrators.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.users_privileges_note"
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
@ -109,10 +109,9 @@ class DialogUserDetail extends LitElement {
|
||||
${!this._isAdmin
|
||||
? html`
|
||||
<br />
|
||||
The users group is a work in progress. The user will be unable
|
||||
to administer the instance via the UI. We're still auditing
|
||||
all management API endpoints to ensure that they correctly
|
||||
limit access to administrators.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.users_privileges_note"
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ import "../../../components/ha-service-description";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../ha-config-section";
|
||||
import "../ha-form-style";
|
||||
import "./zwave-groups";
|
||||
@ -106,7 +106,9 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<!-- Node card -->
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span>Z-Wave Node Management</span>
|
||||
<span
|
||||
>[[localize('ui.panel.config.zwave.node_management.header')]]</span
|
||||
>
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
on-click="toggleHelp"
|
||||
@ -114,13 +116,16 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
Run Z-Wave commands that affect a single node. Pick a node to see a
|
||||
list of available commands.
|
||||
[[localize('ui.panel.config.zwave.node_management.introduction')]]
|
||||
</span>
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu dynamic-align="" label="Nodes" class="flex">
|
||||
<paper-dropdown-menu
|
||||
dynamic-align=""
|
||||
label="[[localize('ui.panel.config.zwave.node_management.nodes')]]"
|
||||
class="flex"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="{{selectedNode}}"
|
||||
@ -134,7 +139,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<template is="dom-if" if="[[!computeIsNodeSelected(selectedNode)]]">
|
||||
<template is="dom-if" if="[[showHelp]]">
|
||||
<div style="color: grey; padding: 12px">
|
||||
Select node to view per-node options
|
||||
[[localize('ui.panel.config.zwave.node_management.introduction')]]
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@ -147,7 +152,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="refresh_node"
|
||||
service-data="[[computeNodeServiceData(selectedNode)]]"
|
||||
>
|
||||
Refresh Node
|
||||
[[localize('ui.panel.config.zwave.services.refresh_node')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -164,7 +169,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="remove_failed_node"
|
||||
service-data="[[computeNodeServiceData(selectedNode)]]"
|
||||
>
|
||||
Remove Failed Node
|
||||
[[localize('ui.panel.config.zwave.services.remove_failed_node')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -180,7 +185,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="replace_failed_node"
|
||||
service-data="[[computeNodeServiceData(selectedNode)]]"
|
||||
>
|
||||
Replace Failed Node
|
||||
[[localize('ui.panel.config.zwave.services.replace_failed_node')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -197,7 +202,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="print_node"
|
||||
service-data="[[computeNodeServiceData(selectedNode)]]"
|
||||
>
|
||||
Print Node
|
||||
[[localize('ui.panel.config.zwave.services.print_node')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -213,7 +218,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="heal_node"
|
||||
service-data="[[computeHealNodeServiceData(selectedNode)]]"
|
||||
>
|
||||
Heal Node
|
||||
[[localize('ui.panel.config.zwave.services.heal_node')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -229,7 +234,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="test_node"
|
||||
service-data="[[computeNodeServiceData(selectedNode)]]"
|
||||
>
|
||||
Test Node
|
||||
[[localize('ui.panel.config.zwave.services.test_node')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -239,13 +244,13 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
>
|
||||
</ha-service-description>
|
||||
<mwc-button on-click="_nodeMoreInfo"
|
||||
>Node Information</mwc-button
|
||||
>[[localize('ui.panel.config.zwave.services.node_info')]]</mwc-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu
|
||||
label="Entities of this node"
|
||||
label="[[localize('ui.panel.config.zwave.node_management.entities')]]"
|
||||
dynamic-align=""
|
||||
class="flex"
|
||||
>
|
||||
@ -270,7 +275,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="refresh_entity"
|
||||
service-data="[[computeRefreshEntityServiceData(selectedEntity)]]"
|
||||
>
|
||||
Refresh Entity
|
||||
[[localize('ui.panel.config.zwave.services.refresh_entity')]]
|
||||
</ha-call-service-button>
|
||||
<ha-service-description
|
||||
hass="[[hass]]"
|
||||
@ -280,7 +285,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
>
|
||||
</ha-service-description>
|
||||
<mwc-button on-click="_entityMoreInfo"
|
||||
>Entity Information</mwc-button
|
||||
>[[localize('ui.panel.config.zwave.node_management.entity_info')]]</mwc-button
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -288,11 +293,11 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
checked="{{entityIgnored}}"
|
||||
class="form-control"
|
||||
>
|
||||
Exclude this entity from Home Assistant
|
||||
[[localize('ui.panel.config.zwave.node_management.exclude_entity')]]
|
||||
</paper-checkbox>
|
||||
<paper-input
|
||||
disabled="{{entityIgnored}}"
|
||||
label="Polling intensity"
|
||||
label="[[localize('ui.panel.config.zwave.node_management.pooling_intensity')]]"
|
||||
type="number"
|
||||
min="0"
|
||||
value="{{entityPollingIntensity}}"
|
||||
@ -306,7 +311,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
service="set_poll_intensity"
|
||||
service-data="[[computePollIntensityServiceData(entityPollingIntensity)]]"
|
||||
>
|
||||
Save
|
||||
[[localize('ui.common.save')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -7,6 +7,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
class ZwaveGroups extends PolymerElement {
|
||||
static get template() {
|
||||
@ -35,10 +36,17 @@ class ZwaveGroups extends PolymerElement {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
<ha-card class="content" header="Node group associations">
|
||||
<ha-card
|
||||
class="content"
|
||||
header="[[localize('ui.panel.config.zwave.node_management.node_group_associations')]]"
|
||||
>
|
||||
<!-- TODO make api for getting groups and members -->
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu label="Group" dynamic-align="" class="flex">
|
||||
<paper-dropdown-menu
|
||||
label="[[localize('ui.panel.config.zwave.node_management.group')]]"
|
||||
dynamic-align=""
|
||||
class="flex"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="{{_selectedGroup}}"
|
||||
@ -52,7 +60,7 @@ class ZwaveGroups extends PolymerElement {
|
||||
<template is="dom-if" if="[[_computeIsGroupSelected(_selectedGroup)]]">
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu
|
||||
label="Node to control"
|
||||
label="[[localize('ui.panel.config.zwave.node_management.node_to_control')]]"
|
||||
dynamic-align=""
|
||||
class="flex"
|
||||
>
|
||||
@ -68,13 +76,18 @@ class ZwaveGroups extends PolymerElement {
|
||||
</div>
|
||||
|
||||
<div class="help-text">
|
||||
<span>Other Nodes in this group:</span>
|
||||
<span
|
||||
>[[localize('ui.panel.config.zwave.node_management.nodes_in_group')]]</span
|
||||
>
|
||||
<template is="dom-repeat" items="[[_otherGroupNodes]]" as="state">
|
||||
<div>[[state]]</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="help-text">
|
||||
<span>Max Associations:</span> <span>[[_maxAssociations]]</span>
|
||||
<span
|
||||
>[[localize('ui.panel.config.zwave.node_management.max_associations')]]</span
|
||||
>
|
||||
<span>[[_maxAssociations]]</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -90,7 +103,7 @@ class ZwaveGroups extends PolymerElement {
|
||||
service="change_association"
|
||||
service-data="[[_addAssocServiceData]]"
|
||||
>
|
||||
Add To Group
|
||||
[[localize('ui.panel.config.zwave.node_management.add_to_group')]]
|
||||
</ha-call-service-button>
|
||||
</template>
|
||||
<template
|
||||
@ -103,7 +116,7 @@ class ZwaveGroups extends PolymerElement {
|
||||
service="change_association"
|
||||
service-data="[[_removeAssocServiceData]]"
|
||||
>
|
||||
Remove From Group
|
||||
[[localize('ui.panel.config.zwave.node_management.remove_from_group')]]
|
||||
</ha-call-service-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_isBroadcastNodeInGroup]]">
|
||||
@ -113,7 +126,7 @@ class ZwaveGroups extends PolymerElement {
|
||||
service="change_association"
|
||||
service-data="[[_removeBroadcastNodeServiceData]]"
|
||||
>
|
||||
Remove Broadcast
|
||||
[[localize('ui.panel.config.zwave.node_management.remove_broadcast')]]
|
||||
</ha-call-service-button>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../components/dialog/ha-paper-dialog";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../styles/polymer-ha-style-dialog";
|
||||
|
||||
class ZwaveLogDialog extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -9,6 +9,7 @@ import "../../../components/ha-card";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../ha-config-section";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
@ -41,12 +42,12 @@ class OzwLog extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
</span>
|
||||
<ha-card class="content">
|
||||
<div class="device-picker">
|
||||
<paper-input label="Number of last log lines." type="number" min="0" max="1000" step="10" value="{{numLogLines}}">
|
||||
<paper-input label="[[localize('ui.panel.config.zwave.ozw_log.last_log_lines')]]" type="number" min="0" max="1000" step="10" value="{{numLogLines}}">
|
||||
</paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button raised="true" on-click="_openLogWindow">Load</mwc-button>
|
||||
<mwc-button raised="true" on-click="_tailLog" disabled="{{_completeLog}}">Tail</mwc-button>
|
||||
<mwc-button raised="true" on-click="_openLogWindow">[[localize('ui.panel.config.zwave.ozw_log.load')]]</mwc-button>
|
||||
<mwc-button raised="true" on-click="_tailLog" disabled="{{_completeLog}}">[[localize('ui.panel.config.zwave.ozw_log.tail')]]</mwc-button>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
`;
|
||||
|
@ -7,6 +7,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../components/buttons/ha-call-api-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
class ZwaveNodeProtection extends PolymerElement {
|
||||
static get template() {
|
||||
@ -32,9 +33,9 @@ class ZwaveNodeProtection extends PolymerElement {
|
||||
|
||||
</style>
|
||||
<div class="content">
|
||||
<ha-card header="Node protection">
|
||||
<ha-card header="[[localize('ui.panel.config.zwave.node_management.node_protection')]]">
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu label="Protection" dynamic-align class="flex" placeholder="{{_loadedProtectionValue}}">
|
||||
<paper-dropdown-menu label="[[localize('ui.panel.config.zwave.node_management.protection')]]" dynamic-align class="flex" placeholder="{{_loadedProtectionValue}}">
|
||||
<paper-listbox slot="dropdown-content" selected="{{_selectedProtectionParameter}}">
|
||||
<template is="dom-repeat" items="[[_protectionOptions]]" as="state">
|
||||
<paper-item>[[state]]</paper-item>
|
||||
@ -47,7 +48,7 @@ class ZwaveNodeProtection extends PolymerElement {
|
||||
hass="[[hass]]"
|
||||
path="[[_nodePath]]"
|
||||
data="[[_protectionData]]">
|
||||
Set Protection
|
||||
[[localize('ui.panel.config.zwave.node_management.set_protection')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user