Compare commits

..

1 Commits

Author SHA1 Message Date
Paulus Schoutsen
3272c32f87 Move compatibility to own entrypoint again 2020-06-03 16:24:23 -07:00
277 changed files with 6545 additions and 8798 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
hass_frontend
hass_frontend_es5
.git

View File

@@ -66,7 +66,11 @@
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"import/extensions": 0,
"import/extensions": [
2,
"ignorePackages",
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": 0,
"default-case": 0,

31
Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
FROM node:8.11.1-alpine
# install yarn
ENV PATH /root/.yarn/bin:$PATH
## Install/force base tools
RUN apk update \
&& apk add make g++ curl bash binutils tar git python2 python3 \
&& rm -rf /var/cache/apk/* \
&& /bin/bash \
&& touch ~/.bashrc
## Install yarn
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
## Setup the project
RUN mkdir -p /frontend
WORKDIR /frontend
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
RUN chmod +x /usr/bin/docker_entrypoint.sh
CMD [ "docker_entrypoint.sh" ]

View File

@@ -22,6 +22,15 @@ This is the repository for the official [Home Assistant](https://home-assistant.
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
### Docker environment
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
- `sh ./script/docker_run.sh build` Build all the project with one command
- `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the _classic environment_) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.

View File

@@ -20,13 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
// 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.
// Polyfill only needed for ES5 workers so filter out in latestBuild
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
].filter(Boolean);
@@ -85,8 +79,8 @@ module.exports.babelExclude = () => [
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
const publicPath = (latestBuild, root = "") =>
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
const publicPath = (latestBuild) =>
latestBuild ? "/frontend_latest/" : "/frontend_es5/";
/*
BundleConfig {
@@ -118,6 +112,7 @@ module.exports.config = {
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",
},
outputPath: outputPath(paths.app_output_root, latestBuild),
@@ -132,6 +127,10 @@ module.exports.config = {
return {
entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
compatibility: path.resolve(
paths.polymer_dir,
"src/entrypoints/compatibility.ts"
),
},
outputPath: outputPath(paths.demo_output_root, latestBuild),
publicPath: publicPath(latestBuild),
@@ -170,12 +169,15 @@ module.exports.config = {
},
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: outputPath(paths.hassio_output_root, latestBuild),
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
outputPath: paths.hassio_output_root,
publicPath: paths.hassio_publicPath,
isProdBuild,
latestBuild,
dontHash: new Set(["entrypoint"]),

View File

@@ -20,7 +20,6 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast",
"gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
)
);

View File

@@ -6,32 +6,30 @@ const merge = require("merge-stream");
const path = require("path");
const paths = require("../paths");
const zopfliOptions = { threshold: 150 };
gulp.task("compress-app", function compressApp() {
const jsLatest = gulp
.src(path.resolve(paths.app_output_latest, "**/*.js"))
.pipe(zopfli(zopfliOptions))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.app_output_latest));
const jsEs5 = gulp
.src(path.resolve(paths.app_output_es5, "**/*.js"))
.pipe(zopfli(zopfliOptions))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.app_output_es5));
const polyfills = gulp
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
.pipe(zopfli(zopfliOptions))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
const translations = gulp
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
.pipe(zopfli(zopfliOptions))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
const icons = gulp
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
.pipe(zopfli(zopfliOptions))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
return merge(jsLatest, jsEs5, polyfills, translations, icons);
@@ -40,6 +38,6 @@ gulp.task("compress-app", function compressApp() {
gulp.task("compress-hassio", function compressApp() {
return gulp
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
.pipe(zopfli(zopfliOptions))
.pipe(zopfli())
.pipe(gulp.dest(paths.hassio_output_root));
});

View File

@@ -53,6 +53,7 @@ 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`,
});
@@ -78,6 +79,7 @@ gulp.task("gen-pages-prod", (done) => {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
es5Compatibility: es5Manifest["compatibility.js"],
es5PageJS: es5Manifest[`${page}.js`],
});
@@ -97,6 +99,7 @@ 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",
@@ -120,6 +123,7 @@ gulp.task("gen-index-app-prod", (done) => {
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"],
@@ -206,6 +210,7 @@ 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",
});
@@ -228,6 +233,7 @@ gulp.task("gen-index-demo-prod", (done) => {
const content = renderDemoTemplate("index", {
latestDemoJS: latestManifest["main.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5DemoJS: es5Manifest["main.js"],
});
const minified = minifyHtml(content);

View File

@@ -36,13 +36,11 @@ function copyMdiIcons(staticDir) {
function copyPolyfills(staticDir) {
const staticPath = genStaticPath(staticDir);
// For custom panels using ES5 builds that don't use Babel 7+
// Web Component polyfills and adapters
copyFileDir(
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
staticPath("polyfills/")
);
// Web Component polyfills and adapters
copyFileDir(
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
staticPath("polyfills/")

View File

@@ -1,9 +1,6 @@
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env");
const paths = require("../paths");
require("./clean.js");
require("./gen-icons-json.js");
@@ -11,24 +8,6 @@ require("./webpack.js");
require("./compress.js");
require("./rollup.js");
async function writeEntrypointJS() {
// We ship two builds and we need to do feature detection on what version to load.
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
try {
new Function("import('${paths.hassio_publicPath}/frontend_latest/entrypoint.js')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${paths.hassio_publicPath}/frontend_es5/entrypoint.js';
document.body.appendChild(el);
}
`,
{ encoding: "utf-8" }
);
}
gulp.task(
"develop-hassio",
gulp.series(
@@ -37,7 +16,6 @@ gulp.task(
},
"clean-hassio",
"gen-icons-json",
writeEntrypointJS,
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
)
);
@@ -51,7 +29,6 @@ gulp.task(
"clean-hassio",
"gen-icons-json",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
writeEntrypointJS,
...// Don't compress running tests
(env.isTest() ? [] : ["compress-hassio"])
)

View File

@@ -92,7 +92,11 @@ gulp.task("rollup-watch-app", () => {
});
gulp.task("rollup-watch-hassio", () => {
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
watchRollup(
// Force latestBuild = false for hassio config.
(conf) => rollupConfig.createHassioConfig({ ...conf, latestBuild: false }),
["hassio/src/**"]
);
});
gulp.task("rollup-dev-server-demo", () => {
@@ -133,7 +137,12 @@ gulp.task(
);
gulp.task("rollup-prod-hassio", () =>
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true })
buildRollup(
rollupConfig.createHassioConfig({
isProdBuild: true,
latestBuild: false,
})
)
);
gulp.task("rollup-prod-gallery", () =>

View File

@@ -129,7 +129,7 @@ gulp.task("webpack-watch-hassio", () => {
webpack(
createHassioConfig({
isProdBuild: false,
latestBuild: true,
latestBuild: false,
})
).watch({}, handler());
});
@@ -139,8 +139,9 @@ gulp.task(
() =>
new Promise((resolve) =>
webpack(
bothBuilds(createHassioConfig, {
createHassioConfig({
isProdBuild: true,
latestBuild: false,
}),
handler(resolve)
)

View File

@@ -34,7 +34,7 @@ module.exports = {
hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
hassio_publicPath: "/api/hassio/app",
hassio_publicPath: "/api/hassio/app/",
translations_src: path.resolve(__dirname, "../src/translations"),
};

View File

@@ -14,6 +14,32 @@ module.exports = function (userOptions = {}) {
return {
name: "ignore",
resolveId(importee, importer) {
// Only use ignore to intercept imports that we don't control
// inside node_module dependencies.
if (
importee.endsWith("commonjsHelpers.js") ||
importee.endsWith("rollupPluginBabelHelpers.js") ||
importee.endsWith("?commonjs-proxy") ||
!importer ||
!importer.includes("/node_modules/")
) {
return null;
}
let fullPath;
try {
fullPath = importee.startsWith(".")
? path.resolve(importee, importer)
: require.resolve(importee);
} catch (err) {
console.error("Error in ignore plugin", { importee, importer }, err);
throw err;
}
return files.some((toIgnorePath) => fullPath.startsWith(toIgnorePath))
? fullPath
: null;
},
load(id) {
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))

View File

@@ -70,9 +70,7 @@ const createWebpackConfig = ({
if (
!context.includes("/node_modules/") ||
// calling define.amd will call require("!!webpack amd options")
resource.startsWith("!!webpack") ||
// loaded by webpack dev server but doesn't exist.
resource === "webpack/hot"
resource.startsWith("!!webpack")
) {
return false;
}
@@ -82,11 +80,7 @@ const createWebpackConfig = ({
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
console.error(
"Error in Home Assistant ignore plugin",
resource,
context
);
console.error("Error in ignore plugin", resource, context);
throw err;
}

View File

@@ -45,6 +45,7 @@
(function() {
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");

View File

@@ -36,6 +36,7 @@
(function() {
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");

View File

@@ -1,3 +1,3 @@
import "~app/resources/ha-style";
import "~app/resources/roboto";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-connect";

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-undef */
import { CAST_NS } from "~app/cast/const";
import { HassMessage } from "~app/cast/receiver_messages";
import "~app/resources/custom-card-support";
import { CAST_NS } from "../../../src/cast/const";
import { HassMessage } from "../../../src/cast/receiver_messages";
import "../../../src/resources/custom-card-support";
import { castContext } from "./cast_context";
import { HcMain } from "./layout/hc-main";
import { ReceivedMessage } from "./types";

View File

@@ -192,8 +192,6 @@ export class HcMain extends HassElement {
this._handleNewLovelaceConfig(lovelaceConfig)
);
} catch (err) {
// eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg);
// Generate a Lovelace config.
this._unsubLovelace = () => undefined;
await this._generateLovelaceConfig();

View File

@@ -1,8 +1,11 @@
const { createCastConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
const { isProdBuild } = require("../build-scripts/env.js");
// File just used for stats builds
const latestBuild = true;
module.exports = createCastConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
latestBuild: true,
latestBuild,
});

View File

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

View File

@@ -92,11 +92,15 @@
(function() {
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5DemoJS %>");
});
};
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5DemoJS %>");
<% } %>
}

View File

@@ -2,8 +2,7 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "~app/components/ha-switch";
import "~app/components/ha-formfield";
import "../../../src/components/ha-switch";
import "./demo-card";
class DemoCards extends PolymerElement {
@@ -27,10 +26,9 @@ class DemoCards extends PolymerElement {
</style>
<app-toolbar>
<div class="filters">
<ha-formfield label="Show config">
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
Show config
</ha-switch>
</ha-formfield>
</div>
</app-toolbar>
<div class="cards">

View File

@@ -1,8 +1,5 @@
const { createGalleryConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
module.exports = createGalleryConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,6 +1,9 @@
import "~app/resources/compatibility";
import "~app/resources/roboto";
import "./hassio-main";
window.loadES5Adapter().then(() => {
// eslint-disable-next-line
import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
});
const styleEl = document.createElement("style");
styleEl.innerHTML = `

View File

@@ -14,9 +14,7 @@ import {
createHassioSession,
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
fetchHassioInfo,
HassioHomeAssistantInfo,
HassioInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
@@ -77,8 +75,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property() private _hostInfo: HassioHostInfo;
@property() private _hassioInfo?: HassioInfo;
@property() private _hassOsInfo?: HassioHassOSInfo;
@property() private _hassInfo: HassioHomeAssistantInfo;
@@ -151,7 +147,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
hass: this.hass,
narrow: this.narrow,
supervisorInfo: this._supervisorInfo,
hassioInfo: this._hassioInfo,
hostInfo: this._hostInfo,
hassInfo: this._hassInfo,
hassOsInfo: this._hassOsInfo,
@@ -161,7 +156,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
el.hass = this.hass;
el.narrow = this.narrow;
el.supervisorInfo = this._supervisorInfo;
el.hassioInfo = this._hassioInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
@@ -175,14 +169,12 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
return;
}
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hassioInfo = hassioInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;

View File

@@ -3,7 +3,6 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor";
import {
HassRouterPage,
@@ -27,8 +26,6 @@ class HassioPanelRouter extends HassRouterPage {
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo: HassioHostInfo;
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
@@ -57,7 +54,6 @@ class HassioPanelRouter extends HassRouterPage {
el.route = this.route;
el.narrow = this.narrow;
el.supervisorInfo = this.supervisorInfo;
el.hassioInfo = this.hassioInfo;
el.hostInfo = this.hostInfo;
el.hassInfo = this.hassInfo;
el.hassOsInfo = this.hassOsInfo;

View File

@@ -10,7 +10,6 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../src/types";
@@ -49,8 +48,6 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
@@ -64,7 +61,6 @@ class HassioPanel extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
.supervisorInfo=${this.supervisorInfo}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo}
.hassInfo=${this.hassInfo}
.hassOsInfo=${this.hassOsInfo}

View File

@@ -19,7 +19,6 @@ import {
shutdownHost,
updateOS,
} from "../../../src/data/hassio/host";
import { HassioInfo } from "../../../src/data/hassio/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
@@ -36,8 +35,6 @@ class HassioHostInfo extends LitElement {
@property() public hostInfo!: HassioHostInfoType;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
@property() private _errors?: string;
@@ -57,12 +54,6 @@ class HassioHostInfo extends LitElement {
<td>System</td>
<td>${this.hostInfo.operating_system}</td>
</tr>
${!this.hostInfo.features.includes("hassos")
? html`<tr>
<td>Docker version</td>
<td>${this.hassioInfo.docker}</td>
</tr>`
: ""}
${this.hostInfo.deployment
? html`
<tr>

View File

@@ -1,3 +1,4 @@
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -11,10 +12,7 @@ import {
HassioHassOSInfo,
HassioHostInfo,
} from "../../../src/data/hassio/host";
import {
HassioSupervisorInfo,
HassioInfo,
} from "../../../src/data/hassio/supervisor";
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
@@ -34,11 +32,9 @@ class HassioSystem extends LitElement {
@property() public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
public render(): TemplateResult | void {
return html`
@@ -60,7 +56,6 @@ class HassioSystem extends LitElement {
></hassio-supervisor-info>
<hassio-host-info
.hass=${this.hass}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-host-info>

View File

@@ -1,8 +1,11 @@
const { createHassioConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
const { isProdBuild } = require("../build-scripts/env.js");
// File just used for stats builds
const latestBuild = false;
module.exports = createHassioConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
latestBuild: true,
latestBuild,
});

View File

@@ -17,12 +17,13 @@
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
"format": "npm run format:eslint && npm run format:prettier",
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
"test": "npm run lint && npm run mocha"
"test": "npm run lint && npm run mocha",
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
"bash": "sh ./script/docker_run.sh bash $npm_package_version"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"~app": "file:./src",
"@formatjs/intl-pluralrules": "^1.5.8",
"@fullcalendar/core": "^5.0.0-beta.2",
"@fullcalendar/daygrid": "^5.0.0-beta.2",
@@ -75,7 +76,6 @@
"@thomasloven/round-slider": "0.5.0",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0",
"chartjs-chart-timeline": "^0.3.0",
@@ -89,7 +89,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4",
"home-assistant-js-websocket": "^5.3.0",
"home-assistant-js-websocket": "^5.2.1",
"idb-keyval": "^3.2.0",
"intl-messageformat": "^8.3.9",
"js-yaml": "^3.13.1",
@@ -108,8 +108,6 @@
"roboto-fontface": "^0.10.0",
"superstruct": "^0.6.1",
"unfetch": "^4.1.0",
"vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2",
"workbox-core": "^5.1.3",
"workbox-precaching": "^5.1.3",
@@ -189,7 +187,7 @@
"sinon": "^7.3.1",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^3.0.6",
"terser-webpack-plugin": "^1.2.3",
"ts-lit-plugin": "^1.1.10",
"ts-mocha": "^6.0.0",
"typescript": "^3.8.3",

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# Docker entry point inspired by travis build and script/build_frontend
# Stop on errors
set -e
# Build the frontend but not used the npm run build
/bin/bash script/build_frontend
# TEST
npm run test
#
#xvfb-run wct

103
script/docker_run.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
# Basic Docker Management scripts
# With this script you can build software, or enter an agnostic development environment and run commands interactively.
check_mandatory_tools(){
if [ "x$(which docker)" == "x" ]; then
echo "UNKNOWN - Missing docker binary! Are you sure it is installed and reachable?"
exit 3
fi
docker info > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "UNKNOWN - Unable to talk to the docker daemon! Maybe the docker daemon is not running"
exit 3
fi
}
check_dev_image(){
if [[ "$(docker images -q ${IMAGE_NAME}:$IMAGE_TAG 2> /dev/null)" == "" ]]; then
echo "UNKNOWN - Can't find the development docker image ${IMAGE_NAME}:$IMAGE_TAG"
while true; do
read -p "Do you want to create it now?" yn
case $yn in
[Yy]* ) create_image; break;;
[Nn]* ) exit 3;;
* ) echo "Please answer y or n";;
esac
done
fi
}
# Building the basic image for compiling the production frontend
create_image(){
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
}
#
# Execute interactive bash on basic image
#
run_bash_on_docker(){
check_dev_image
docker run -it \
-v $PWD/:/frontend/ \
-v /frontend/node_modules \
-v /frontend/bower_components \
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash
}
#
# Execute the basic image for compiling the production frontend
#
build_all(){
check_dev_image
docker run -it \
-v $PWD/:/frontend/ \
-v /frontend/node_modules \
-v /frontend/bower_components \
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash script/build_frontend
}
# Init Global Variable
IMAGE_NAME=home_assistant_fe_image
IMAGE_TAG=${2:-latest}
check_mandatory_tools
case "$1" in
setup_env)
create_image
;;
bash)
run_bash_on_docker
;;
build)
build_all
;;
*)
echo "NAME"
echo " Docker Management."
echo ""
echo "SYNOPSIS"
echo " ${0} command [version]"
echo ""
echo "DESCRIPTION"
echo " With this script you can build software, or enter an agnostic development environment and run commands interactively."
echo ""
echo " The command are:"
echo " setup_env Create develop images"
echo " bash Run bash on develop enviroments"
echo " build Run silent build"
echo ""
echo " The version is optional, if not inserted it assumes \"latest\". "
exit 1
;;
esac
exit 0

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20200620.0",
version="20200603.1",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@@ -6,18 +6,19 @@ import {
property,
PropertyValues,
} from "lit-element";
import {
AuthProvider,
fetchAuthProviders,
AuthUrlSearchParams,
} from "../data/auth";
import { AuthProvider, fetchAuthProviders } from "../data/auth";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow";
import { extractSearchParamsObject } from "../common/url/search-params";
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
interface QueryParams {
client_id?: string;
redirect_uri?: string;
state?: string;
}
class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@property() public clientId?: string;
@@ -32,7 +33,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
constructor() {
super();
this.translationFragment = "page-authorize";
const query = extractSearchParamsObject() as AuthUrlSearchParams;
const query: QueryParams = {};
const values = location.search.substr(1).split("&");
for (const item of values) {
const value = item.split("=");
if (value.length > 1) {
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
}
}
if (query.client_id) {
this.clientId = query.client_id;
}
@@ -137,7 +145,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
response.status === 400 &&
authProviders.code === "onboarding_required"
) {
location.href = `/onboarding.html${location.search}`;
location.href = "/?";
return;
}

View File

@@ -6,7 +6,7 @@ export const isValidEntityId = (entityId: string) =>
export const createValidEntityId = (input: string) =>
input
.toLowerCase()
.replace(/\s|'|\./g, "_") // replace spaces, points and quotes with underscore
.replace(/\s|'/g, "_") // replace spaces and quotes with underscore
.replace(/\W/g, "") // remove not allowed chars
.replace(/_{2,}/g, "_") // replace multiple underscores with 1
.replace(/_$/, ""); // remove underscores at the end

View File

@@ -57,11 +57,8 @@ export const iconColorCSS = css`
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
opacity: 0;
}
}

View File

@@ -1,8 +0,0 @@
export const extractSearchParamsObject = (): { [key: string]: string } => {
const query = {};
const searchParams = new URLSearchParams(location.search);
for (const [key, value] of searchParams.entries()) {
query[key] = value;
}
return query;
};

View File

@@ -8,9 +8,6 @@ class HaProgressButton extends PolymerElement {
static get template() {
return html`
<style>
:host {
outline: none;
}
.container {
position: relative;
display: inline-block;

View File

@@ -619,11 +619,6 @@ export class HaDataTable extends LitElement {
text-transform: inherit;
}
.mdc-data-table__cell a {
color: inherit;
text-decoration: none;
}
.mdc-data-table__cell--numeric {
text-align: right;
}

View File

@@ -1,228 +0,0 @@
import Vue from "vue";
import wrap from "@vue/web-component-wrapper";
import DateRangePicker from "vue2-daterange-picker";
// @ts-ignore
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
import { fireEvent } from "../common/dom/fire_event";
import { Constructor } from "../types";
import { customElement } from "lit-element/lib/decorators";
const Component = Vue.extend({
props: {
twentyfourHours: {
type: Boolean,
default: true,
},
disabled: {
type: Boolean,
default: false,
},
ranges: {
type: Boolean,
default: true,
},
startDate: {
type: [String, Date],
default() {
return new Date();
},
},
endDate: {
type: [String, Date],
default() {
return new Date();
},
},
},
render(createElement) {
// @ts-ignore
return createElement(DateRangePicker, {
props: {
"time-picker": true,
"auto-apply": false,
opens: "right",
"show-dropdowns": false,
"time-picker24-hour": this.twentyfourHours,
disabled: this.disabled,
ranges: this.ranges ? {} : false,
},
model: {
value: {
startDate: this.startDate,
endDate: this.endDate,
},
callback: (value) => {
// @ts-ignore
fireEvent(this.$el as HTMLElement, "change", value);
},
expression: "dateRange",
},
scopedSlots: {
input() {
return createElement("slot", {
domProps: { name: "input" },
});
},
header() {
return createElement("slot", {
domProps: { name: "header" },
});
},
ranges() {
return createElement("slot", {
domProps: { name: "ranges" },
});
},
footer() {
return createElement("slot", {
domProps: { name: "footer" },
});
},
},
});
},
});
const WrappedElement: Constructor<HTMLElement> = wrap(Vue, Component);
@customElement("date-range-picker")
class DateRangePickerElement extends WrappedElement {
constructor() {
super();
const style = document.createElement("style");
style.innerHTML = `
${dateRangePickerStyles}
.calendars {
display: flex;
}
.daterangepicker {
left: 0px !important;
top: auto;
background-color: var(--card-background-color);
border: none;
border-radius: var(--ha-card-border-radius, 4px);
box-shadow: var(
--ha-card-box-shadow,
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
);
color: var(--primary-text-color);
min-width: initial !important;
}
.daterangepicker:after {
border-bottom: 6px solid var(--card-background-color);
}
.daterangepicker .calendar-table {
background-color: var(--card-background-color);
border: none;
}
.daterangepicker .calendar-table td,
.daterangepicker .calendar-table th {
background-color: transparent;
color: var(--secondary-text-color);
border-radius: 0;
outline: none;
width: 32px;
height: 32px;
}
.daterangepicker td.off,
.daterangepicker td.off.end-date,
.daterangepicker td.off.in-range,
.daterangepicker td.off.start-date {
background-color: var(--secondary-background-color);
color: var(--disabled-text-color);
}
.daterangepicker td.in-range {
background-color: var(--light-primary-color);
color: var(--primary-text-color);
}
.daterangepicker td.active,
.daterangepicker td.active:hover {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker td.start-date.end-date {
border-radius: 50%;
}
.daterangepicker td.start-date {
border-radius: 50% 0 0 50%;
}
.daterangepicker td.end-date {
border-radius: 0 50% 50% 0;
}
.reportrange-text {
background: none !important;
padding: 0 !important;
border: none !important;
}
.daterangepicker .calendar-table .next span,
.daterangepicker .calendar-table .prev span {
border: solid var(--primary-text-color);
border-width: 0 2px 2px 0;
}
.daterangepicker .ranges li {
outline: none;
}
.daterangepicker .ranges li:hover {
background-color: var(--secondary-background-color);
}
.daterangepicker .ranges li.active {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker select.ampmselect,
.daterangepicker select.hourselect,
.daterangepicker select.minuteselect,
.daterangepicker select.secondselect {
background: transparent;
border: 1px solid var(--divider-color);
color: var(--primary-color);
}
.daterangepicker .drp-buttons .btn {
border: 1px solid var(--primary-color);
background-color: transparent;
color: var(--primary-color);
border-radius: 4px;
padding: 8px;
cursor: pointer;
}
.calendars-container {
flex-direction: column;
align-items: center;
}
.drp-calendar.col.right .calendar-table {
display: none;
}
.daterangepicker.show-ranges .drp-calendar.left {
border-left: 0px;
}
.daterangepicker .drp-calendar.left {
padding: 8px;
}
.daterangepicker.show-calendar .ranges {
margin-top: 0;
padding-top: 8px;
border-right: 1px solid var(--divider-color);
}
@media only screen and (max-width: 800px) {
.calendars {
flex-direction: column;
}
}
.calendar-table {
padding: 0 !important;
}
`;
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
// Stop click events from reaching the document, otherwise it will close the picker immediately.
shadowRoot.addEventListener("click", (ev) => ev.stopPropagation());
}
}
declare global {
interface HTMLElementTagNameMap {
"date-range-picker": DateRangePickerElement;
}
}

View File

@@ -40,7 +40,7 @@ export class HaButtonMenu extends LitElement {
static get styles(): CSSResult {
return css`
:host {
display: inline-block;
display: block;
position: relative;
}
`;

View File

@@ -176,9 +176,7 @@ class HaCameraStream extends LitElement {
Hls: HLSModule,
url: string
) {
const hls = new Hls({
liveBackBufferLength: 60,
});
const hls = new Hls();
this._hlsPolyfillInstance = hls;
hls.attachMedia(videoEl);
hls.on(Hls.Events.MEDIA_ATTACHED, () => {

View File

@@ -12,8 +12,6 @@ import {
class HaCard extends LitElement {
@property() public header?: string;
@property({ type: Boolean, reflect: true }) public outlined = false;
static get styles(): CSSResult {
return css`
:host {
@@ -21,12 +19,12 @@ class HaCard extends LitElement {
--ha-card-background,
var(--paper-card-background-color, white)
);
border-radius: var(--ha-card-border-radius, 4px);
border-radius: var(--ha-card-border-radius, 2px);
box-shadow: var(
--ha-card-box-shadow,
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2)
);
color: var(--primary-text-color);
display: block;
@@ -34,16 +32,6 @@ class HaCard extends LitElement {
position: relative;
}
:host([outlined]) {
box-shadow: none;
border-width: 1px;
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
}
.card-header,
:host ::slotted(.card-header) {
color: var(--ha-card-header-color, --primary-text-color);

View File

@@ -1,195 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../types";
import { mdiCalendar } from "@mdi/js";
import { formatDateTime } from "../common/datetime/format_date_time";
import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item";
import "./ha-svg-icon";
import "@polymer/paper-input/paper-input";
import "@material/mwc-list/mwc-list";
import "./date-range-picker";
export interface DateRangePickerRanges {
[key: string]: [Date, Date];
}
@customElement("ha-date-range-picker")
export class HaDateRangePicker extends LitElement {
@property() public hass!: HomeAssistant;
@property() public startDate!: Date;
@property() public endDate!: Date;
@property() public ranges?: DateRangePickerRanges;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) private _hour24format = false;
protected updated(changedProps: PropertyValues) {
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
this._hour24format = this._compute24hourFormat();
}
}
}
protected render(): TemplateResult {
return html`
<date-range-picker
?disabled=${this.disabled}
twentyfour-hours=${this._hour24format}
start-date=${this.startDate}
end-date=${this.endDate}
?ranges=${this.ranges !== undefined}
>
<div slot="input" class="date-range-inputs">
<ha-svg-icon path=${mdiCalendar}></ha-svg-icon>
<paper-input
.value=${formatDateTime(this.startDate, this.hass.language)}
.label=${this.hass.localize(
"ui.components.date-range-picker.start_date"
)}
.disabled=${this.disabled}
@click=${this._handleInputClick}
readonly
></paper-input>
<paper-input
.value=${formatDateTime(this.endDate, this.hass.language)}
label=${this.hass.localize(
"ui.components.date-range-picker.end_date"
)}
.disabled=${this.disabled}
@click=${this._handleInputClick}
readonly
></paper-input>
</div>
${this.ranges
? html`<div slot="ranges" class="date-range-ranges">
<mwc-list @click=${this._setDateRange}>
${Object.entries(this.ranges).map(
([name, dates]) => html`<mwc-list-item
.activated=${this.startDate.getTime() ===
dates[0].getTime() &&
this.endDate.getTime() === dates[1].getTime()}
.startDate=${dates[0]}
.endDate=${dates[1]}
>
${name}
</mwc-list-item>`
)}
</mwc-list>
</div>`
: ""}
<div slot="footer" class="date-range-footer">
<mwc-button @click=${this._cancelDateRange}
>${this.hass.localize("ui.common.cancel")}</mwc-button
>
<mwc-button @click=${this._applyDateRange}
>${this.hass.localize(
"ui.components.date-range-picker.select"
)}</mwc-button
>
</div>
</date-range-picker>
`;
}
private _compute24hourFormat() {
return (
new Intl.DateTimeFormat(this.hass.language, {
hour: "numeric",
})
.formatToParts(new Date(2020, 0, 1, 13))
.find((part) => part.type === "hour")!.value.length === 2
);
}
private _setDateRange(ev: Event) {
const target = ev.target as any;
const startDate = target.startDate;
const endDate = target.endDate;
const dateRangePicker = this._dateRangePicker;
dateRangePicker.clickRange([startDate, endDate]);
dateRangePicker.clickedApply();
}
private _cancelDateRange() {
this._dateRangePicker.clickCancel();
}
private _applyDateRange() {
this._dateRangePicker.clickedApply();
}
private get _dateRangePicker() {
const dateRangePicker = this.shadowRoot!.querySelector(
"date-range-picker"
) as any;
return dateRangePicker.vueComponent.$children[0];
}
private _handleInputClick() {
// close the date picker, so it will open again on the click event
if (this._dateRangePicker.open) {
this._dateRangePicker.open = false;
}
}
static get styles(): CSSResult {
return css`
ha-svg-icon {
margin-right: 8px;
}
.date-range-inputs {
display: flex;
align-items: center;
}
.date-range-ranges {
border-right: 1px solid var(--divider-color);
}
@media only screen and (max-width: 800px) {
.date-range-ranges {
border-right: none;
border-bottom: 1px solid var(--divider-color);
}
}
.date-range-footer {
display: flex;
justify-content: flex-end;
padding: 8px;
border-top: 1px solid var(--divider-color);
}
paper-input {
display: inline-block;
max-width: 200px;
}
paper-input:last-child {
margin-left: 8px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-date-range-picker": HaDateRangePicker;
}
}

View File

@@ -13,7 +13,7 @@ export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
<mwc-icon-button
aria-label=${hass.localize("ui.dialogs.generic.close")}
dialogAction="close"
class="header_button"
class="close_button"
>
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
@@ -25,9 +25,6 @@ export class HaDialog extends MwcDialog {
return [
style,
css`
.mdc-dialog {
z-index: var(--dialog-z-index, 7);
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
}
@@ -38,15 +35,10 @@ export class HaDialog extends MwcDialog {
display: block;
height: 20px;
}
.mdc-dialog__content {
padding: var(--dialog-content-padding, 20px 24px);
}
.header_button {
.close_button {
position: absolute;
right: 16px;
top: 12px;
text-decoration: none;
color: inherit;
}
`,
];

View File

@@ -1,33 +0,0 @@
import "@material/mwc-formfield";
import type { Formfield } from "@material/mwc-formfield";
import { style } from "@material/mwc-formfield/mwc-formfield-css";
import { css, CSSResult, customElement } from "lit-element";
import { Constructor } from "../types";
const MwcFormfield = customElements.get("mwc-formfield") as Constructor<
Formfield
>;
@customElement("ha-formfield")
export class HaFormfield extends MwcFormfield {
protected static get styles(): CSSResult[] {
return [
style,
css`
::slotted(ha-switch) {
margin-right: 10px;
}
[dir="rtl"] ::slotted(ha-switch),
::slotted(ha-switch)[dir="rtl"] {
margin-left: 10px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-formfield": HaFormfield;
}
}

View File

@@ -1,37 +1,17 @@
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-arrow-next")
export class HaIconButtonArrowNext extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiArrowRight;
import { HaIconButton } from "./ha-icon-button";
export class HaIconButtonArrowNext extends HaIconButton {
public connectedCallback() {
super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => {
this._icon =
this.icon =
window.getComputedStyle(this).direction === "ltr"
? mdiArrowRight
: mdiArrowLeft;
? "hass:arrow-right"
: "hass:arrow-left";
}, 100);
}
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
declare global {
@@ -39,3 +19,5 @@ declare global {
"ha-icon-button-arrow-next": HaIconButtonArrowNext;
}
}
customElements.define("ha-icon-button-arrow-next", HaIconButtonArrowNext);

View File

@@ -1,15 +1,8 @@
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { LitElement, property, TemplateResult, html } from "lit-element";
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-arrow-prev")
export class HaIconButtonArrowPrev extends LitElement {
@property({ type: Boolean }) public disabled = false;
@@ -39,3 +32,5 @@ declare global {
"ha-icon-button-arrow-prev": HaIconButtonArrowPrev;
}
}
customElements.define("ha-icon-button-arrow-prev", HaIconButtonArrowPrev);

View File

@@ -1,37 +1,17 @@
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
import "@material/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-next")
export class HaIconButtonNext extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiChevronRight;
import { HaIconButton } from "./ha-icon-button";
export class HaIconButtonNext extends HaIconButton {
public connectedCallback() {
super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => {
this._icon =
this.icon =
window.getComputedStyle(this).direction === "ltr"
? mdiChevronRight
: mdiChevronLeft;
? "hass:chevron-right"
: "hass:chevron-left";
}, 100);
}
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
declare global {
@@ -39,3 +19,5 @@ declare global {
"ha-icon-button-next": HaIconButtonNext;
}
}
customElements.define("ha-icon-button-next", HaIconButtonNext);

View File

@@ -1,37 +1,17 @@
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-prev")
export class HaIconButtonPrev extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiChevronLeft;
import { HaIconButton } from "./ha-icon-button";
export class HaIconButtonPrev extends HaIconButton {
public connectedCallback() {
super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => {
this._icon =
this.icon =
window.getComputedStyle(this).direction === "ltr"
? mdiChevronLeft
: mdiChevronRight;
? "hass:chevron-left"
: "hass:chevron-right";
}, 100);
}
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
declare global {
@@ -39,3 +19,5 @@ declare global {
"ha-icon-button-prev": HaIconButtonPrev;
}
}
customElements.define("ha-icon-button-prev", HaIconButtonPrev);

View File

@@ -24,21 +24,28 @@ export class HaIconButton extends LitElement {
protected render(): TemplateResult {
return html`
<mwc-icon-button .label=${this.label} .disabled=${this.disabled}>
<mwc-icon-button
.label=${this.label}
?disabled=${this.disabled}
@click=${this._handleClick}
>
<ha-icon .icon=${this.icon}></ha-icon>
</mwc-icon-button>
`;
}
private _handleClick(ev) {
if (this.disabled) {
ev.stopPropagation();
}
}
static get styles(): CSSResult {
return css`
:host {
display: inline-block;
outline: none;
}
:host([disabled]) {
pointer-events: none;
}
mwc-icon-button {
--mdc-theme-on-primary: currentColor;
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);

View File

@@ -54,7 +54,7 @@ class HaMarkdown extends LitElement {
}
ha-markdown-element code,
pre {
background-color: var(--markdown-code-background-color, none);
background-color: var(--markdown-code-background-color, #f6f8fa);
border-radius: 3px;
}
ha-markdown-element code {

View File

@@ -1,5 +1,3 @@
import "@material/mwc-icon-button";
import { mdiMenu } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
@@ -14,7 +12,8 @@ import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain";
import { subscribeNotifications } from "../data/persistent_notification";
import { HomeAssistant } from "../types";
import "./ha-svg-icon";
import "./ha-icon-button";
import { mdiMenu } from "@mdi/js";
@customElement("ha-menu-button")
class HaMenuButton extends LitElement {

View File

@@ -1,12 +1,6 @@
import "@material/mwc-icon-button";
import {
mdiBell,
mdiCellphoneSettingsVariant,
mdiMenuOpen,
mdiMenu,
mdiViewDashboard,
} from "@mdi/js";
import { mdiBell, mdiCellphoneSettingsVariant } from "@mdi/js";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "./ha-icon-button";
import "@polymer/paper-item/paper-icon-item";
import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
@@ -35,9 +29,9 @@ import {
getExternalConfig,
} from "../external_app/external_config";
import type { HomeAssistant, PanelInfo } from "../types";
import "./ha-svg-icon";
import "./ha-icon";
import "./ha-menu-button";
import "./ha-svg-icon";
import "./user/ha-user-badge";
const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
@@ -159,16 +153,13 @@ class HaSidebar extends LitElement {
<div class="menu">
${!this.narrow
? html`
<mwc-icon-button
.label=${hass.localize("ui.sidebar.sidebar_toggle")}
<ha-icon-button
aria-label=${hass.localize("ui.sidebar.sidebar_toggle")}
.icon=${hass.dockedSidebar === "docked"
? "hass:menu-open"
: "hass:menu"}
@click=${this._toggleSidebar}
>
<ha-svg-icon
.path=${hass.dockedSidebar === "docked"
? mdiMenuOpen
: mdiMenu}
></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
`
: ""}
<span class="title">Home Assistant</span>
@@ -183,16 +174,14 @@ class HaSidebar extends LitElement {
>
${this._renderPanel(
defaultPanel.url_path,
defaultPanel.title || hass.localize("panel.states"),
defaultPanel.icon,
!defaultPanel.icon ? mdiViewDashboard : undefined
defaultPanel.icon || "hass:view-dashboard",
defaultPanel.title || hass.localize("panel.states")
)}
${beforeSpacer.map((panel) =>
this._renderPanel(
panel.url_path,
hass.localize(`panel.${panel.title}`) || panel.title,
panel.icon,
undefined
hass.localize(`panel.${panel.title}`) || panel.title
)
)}
<div class="spacer" disabled></div>
@@ -200,9 +189,8 @@ class HaSidebar extends LitElement {
${afterSpacer.map((panel) =>
this._renderPanel(
panel.url_path,
hass.localize(`panel.${panel.title}`) || panel.title,
panel.icon,
undefined
hass.localize(`panel.${panel.title}`) || panel.title
)
)}
${this._externalConfig && this._externalConfig.hasSettingsScreen
@@ -455,12 +443,7 @@ class HaSidebar extends LitElement {
fireEvent(this, "hass-toggle-menu");
}
private _renderPanel(
urlPath: string,
title: string | null,
icon?: string | null,
iconPath?: string | null
) {
private _renderPanel(urlPath, icon, title) {
return html`
<a
aria-role="option"
@@ -471,12 +454,7 @@ class HaSidebar extends LitElement {
@mouseleave=${this._itemMouseLeave}
>
<paper-icon-item>
${iconPath
? html`<ha-svg-icon
slot="item-icon"
.path=${iconPath}
></ha-svg-icon>`
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
<ha-icon slot="item-icon" .icon="${icon}"></ha-icon>
<span class="item-text">${title}</span>
</paper-icon-item>
</a>
@@ -518,13 +496,13 @@ class HaSidebar extends LitElement {
width: 256px;
}
.menu mwc-icon-button {
.menu ha-icon-button {
color: var(--sidebar-icon-color);
}
:host([expanded]) .menu mwc-icon-button {
:host([expanded]) .menu ha-icon-button {
margin-right: 23px;
}
:host([expanded][_rtl]) .menu mwc-icon-button {
:host([expanded][_rtl]) .menu ha-icon-button {
margin-right: 0px;
margin-left: 23px;
}
@@ -736,7 +714,7 @@ class HaSidebar extends LitElement {
font-weight: 500;
}
:host([_rtl]) .menu mwc-icon-button {
:host([_rtl]) .menu ha-icon-button {
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}

View File

@@ -1,7 +1,15 @@
import { ripple } from "@material/mwc-ripple/ripple-directive";
import "@material/mwc-switch";
import type { Switch } from "@material/mwc-switch";
import { style } from "@material/mwc-switch/mwc-switch-css";
import { css, CSSResult, customElement, property } from "lit-element";
import {
css,
CSSResult,
customElement,
html,
property,
query,
} from "lit-element";
import { forwardHaptic } from "../data/haptics";
import { Constructor } from "../types";
@@ -14,12 +22,18 @@ export class HaSwitch extends MwcSwitch {
// Do not add haptic when a user is required to press save.
@property({ type: Boolean }) public haptic = false;
@query("slot") private _slot!: HTMLSlotElement;
protected firstUpdated() {
super.firstUpdated();
this.style.setProperty(
"--mdc-theme-secondary",
"var(--switch-checked-color)"
);
this.classList.toggle(
"slotted",
Boolean(this._slot.assignedNodes().length)
);
this.addEventListener("change", () => {
if (this.haptic) {
forwardHaptic("light");
@@ -27,10 +41,40 @@ export class HaSwitch extends MwcSwitch {
});
}
protected render() {
return html`
<div class="mdc-switch">
<div class="mdc-switch__track"></div>
<div
class="mdc-switch__thumb-underlay"
.ripple="${ripple({
interactionNode: this,
})}"
>
<div class="mdc-switch__thumb">
<input
type="checkbox"
id="basic-switch"
class="mdc-switch__native-control"
role="switch"
@change="${this._haChangeHandler}"
/>
</div>
</div>
</div>
<label for="basic-switch"><slot></slot></label>
`;
}
protected static get styles(): CSSResult[] {
return [
style,
css`
:host {
display: flex;
flex-direction: row;
align-items: center;
}
.mdc-switch.mdc-switch--checked .mdc-switch__thumb {
background-color: var(--switch-checked-button-color);
border-color: var(--switch-checked-button-color);
@@ -47,9 +91,18 @@ export class HaSwitch extends MwcSwitch {
background-color: var(--switch-unchecked-track-color);
border-color: var(--switch-unchecked-track-color);
}
:host(.slotted) .mdc-switch {
margin-right: 24px;
}
`,
];
}
private _haChangeHandler(e: Event) {
this.mdcFoundation.handleChange(e);
// catch "click" event and sync properties
this.checked = this.formElement.checked;
}
}
declare global {

View File

@@ -0,0 +1,46 @@
/*
Wrapper for paper-textarea.
paper-textarea crashes on iOS when created programmatically. This only impacts
our automation and script editors as they are using Preact. Polymer is using
template elements and does not have this issue.
paper-textarea issue: https://github.com/PolymerElements/paper-input/issues/556
WebKit issue: https://bugs.webkit.org/show_bug.cgi?id=174629
*/
import "@polymer/paper-input/paper-textarea";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HaTextarea extends PolymerElement {
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<paper-textarea
label="[[label]]"
placeholder="[[placeholder]]"
value="{{value}}"
></paper-textarea>
`;
}
static get properties() {
return {
name: String,
label: String,
placeholder: String,
value: {
type: String,
notify: true,
},
};
}
}
customElements.define("ha-textarea", HaTextarea);

View File

@@ -1,11 +1,5 @@
import { HomeAssistant } from "../types";
export interface AuthUrlSearchParams {
client_id?: string;
redirect_uri?: string;
state?: string;
}
export interface AuthProvider {
name: string;
id: string;

View File

@@ -5,7 +5,7 @@ import { HomeAssistant } from "../types";
import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
import { domainToName } from "./integration";
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf", "discovery"];
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {

View File

@@ -12,11 +12,6 @@ export interface ConfigUpdateValues {
internal_url?: string | null;
}
export interface CheckConfigResult {
result: "valid" | "invalid";
errors: string | null;
}
export const saveCoreConfig = (
hass: HomeAssistant,
values: Partial<ConfigUpdateValues>
@@ -30,6 +25,3 @@ export const detectCoreConfig = (hass: HomeAssistant) =>
hass.callWS<Partial<ConfigUpdateValues>>({
type: "config/core/detect",
});
export const checkCoreConfig = (hass: HomeAssistant) =>
hass.callApi<CheckConfigResult>("POST", "config/core/check_config");

View File

@@ -4,20 +4,6 @@ import { hassioApiResultExtractor, HassioResponse } from "./common";
export type HassioHomeAssistantInfo = any;
export type HassioSupervisorInfo = any;
export type HassioInfo = {
arch: string;
channel: string;
docker: string;
hassos?: string;
homeassistant: string;
hostname: string;
logging: string;
maching: string;
supervisor: string;
supported_arch: string[];
timezone: string;
};
export type HassioPanelInfo = PanelInfo<
| undefined
| {
@@ -52,12 +38,6 @@ export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
);
};
export const fetchHassioInfo = async (hass: HomeAssistant) => {
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<HassioInfo>>("GET", "hassio/info")
);
};
export const fetchHassioLogs = async (
hass: HomeAssistant,
provider: string

View File

@@ -1,67 +1,7 @@
import { HomeAssistant } from "../types";
export interface LogbookEntry {
when: string;
name: string;
message: string;
entity_id?: string;
domain: string;
context_user_id?: string;
}
const DATA_CACHE: {
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
} = {};
export const getLogbookData = (
hass: HomeAssistant,
startDate: string,
endDate: string,
entityId?: string
) => {
const ALL_ENTITIES = "*";
if (!entityId) {
entityId = ALL_ENTITIES;
}
const cacheKey = `${startDate}${endDate}`;
if (!DATA_CACHE[cacheKey]) {
DATA_CACHE[cacheKey] = {};
}
if (DATA_CACHE[cacheKey][entityId]) {
return DATA_CACHE[cacheKey][entityId];
}
if (entityId !== ALL_ENTITIES && DATA_CACHE[cacheKey][ALL_ENTITIES]) {
return DATA_CACHE[cacheKey][ALL_ENTITIES].then((entities) =>
entities.filter((entity) => entity.entity_id === entityId)
);
}
DATA_CACHE[cacheKey][entityId] = getLogbookDataFromServer(
hass,
startDate,
endDate,
entityId !== ALL_ENTITIES ? entityId : undefined
).then((entries) => entries.reverse());
return DATA_CACHE[cacheKey][entityId];
};
const getLogbookDataFromServer = async (
hass: HomeAssistant,
startDate: string,
endDate: string,
entityId?: string
) => {
const url = `logbook/${startDate}?end_time=${endDate}${
entityId ? `&entity=${entityId}` : ""
}`;
return hass.callApi<LogbookEntry[]>("GET", url);
};
export const clearLogbookCache = (startDate, endDate) => {
DATA_CACHE[`${startDate}${endDate}`] = {};
};

View File

@@ -51,7 +51,7 @@ export const onboardCoreConfigStep = (hass: HomeAssistant) =>
export const onboardIntegrationStep = (
hass: HomeAssistant,
params: { client_id: string; redirect_uri: string }
params: { client_id: string }
) =>
hass.callApi<OnboardingIntegrationStepResponse>(
"POST",

View File

@@ -11,7 +11,6 @@ import {
} from "lit-element";
import "../../components/dialog/ha-paper-dialog";
import "../../components/ha-switch";
import "../../components/ha-formfield";
import type { HaSwitch } from "../../components/ha-switch";
import {
getConfigEntrySystemOptions,
@@ -83,8 +82,13 @@ class DialogConfigEntrySystemOptions extends LitElement {
? html` <div class="error">${this._error}</div> `
: ""}
<div class="form">
<ha-formfield
.label=${html`<p>
<ha-switch
.checked=${!this._disableNewEntities}
@change=${this._disableNewEntitiesChanged}
.disabled=${this._submitting}
>
<div>
<p>
${this.hass.localize(
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
)}
@@ -97,15 +101,9 @@ class DialogConfigEntrySystemOptions extends LitElement {
`component.${this._params.entry.domain}.title`
) || this._params.entry.domain
)}
</p>`}
>
<ha-switch
.checked=${!this._disableNewEntities}
@change=${this._disableNewEntitiesChanged}
.disabled=${this._submitting}
>
</p>
</div>
</ha-switch>
</ha-formfield>
</div>
`}
</paper-dialog-scrollable>
@@ -174,6 +172,9 @@ class DialogConfigEntrySystemOptions extends LitElement {
padding-bottom: 24px;
color: var(--primary-text-color);
}
p {
margin: 0;
}
.secondary {
color: var(--secondary-text-color);
}

View File

@@ -14,7 +14,8 @@ import {
PropertyValues,
TemplateResult,
} from "lit-element";
import "../../components/ha-dialog";
import "../../components/dialog/ha-paper-dialog";
import type { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
import "../../components/ha-form/ha-form";
import "../../components/ha-markdown";
import {
@@ -26,6 +27,7 @@ import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../data/device_registry";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
@@ -88,6 +90,7 @@ class DataEntryFlowDialog extends LitElement {
// We only load the handlers once
if (this._handlers === undefined) {
this._loading = true;
this.updateComplete.then(() => this._scheduleCenterDialog());
try {
this._handlers = await params.flowConfig.getFlowHandlers(this.hass);
} finally {
@@ -95,6 +98,7 @@ class DataEntryFlowDialog extends LitElement {
}
}
await this.updateComplete;
this._scheduleCenterDialog();
return;
}
@@ -111,6 +115,9 @@ class DataEntryFlowDialog extends LitElement {
this._processStep(step);
this._loading = false;
// When the flow changes, center the dialog.
// Don't do it on each step or else the dialog keeps bouncing.
this._scheduleCenterDialog();
}
protected render(): TemplateResult {
@@ -119,16 +126,13 @@ class DataEntryFlowDialog extends LitElement {
}
return html`
<ha-dialog
open
@closing=${this._close}
scrimClickAction
escapeKeyAction
hideActions
<ha-paper-dialog
with-backdrop
opened
modal
@opened-changed=${this._openedChanged}
>
<div>
${this._loading ||
(this._step === null && this._handlers === undefined)
${this._loading || (this._step === null && this._handlers === undefined)
? html`
<step-flow-loading
.label=${this.hass.localize(
@@ -146,7 +150,7 @@ class DataEntryFlowDialog extends LitElement {
"ui.panel.config.integrations.config_flow.dismiss"
)}
icon="hass:close"
dialogAction="close"
dialog-dismiss
></ha-icon-button>
${this._step === null
? // Show handler picker
@@ -195,8 +199,7 @@ class DataEntryFlowDialog extends LitElement {
></step-flow-create-entry>
`}
`}
</div>
</ha-dialog>
</ha-paper-dialog>
`;
}
@@ -222,6 +225,18 @@ class DataEntryFlowDialog extends LitElement {
this._areas = [];
}
}
if (changedProps.has("_devices") && this._dialog) {
this._scheduleCenterDialog();
}
}
private _scheduleCenterDialog() {
setTimeout(() => this._dialog.center(), 0);
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private async _fetchDevices(configEntryId) {
@@ -295,7 +310,9 @@ class DataEntryFlowDialog extends LitElement {
}
}
private _close(): void {
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
// Closed dialog by clicking on the overlay
if (!ev.detail.value) {
if (this._step) {
this._flowDone();
} else if (this._step === null) {
@@ -304,19 +321,24 @@ class DataEntryFlowDialog extends LitElement {
this._params = undefined;
}
}
}
static get styles(): CSSResultArray {
return [
haStyleDialog,
css`
ha-dialog {
--dialog-content-padding: 0;
ha-paper-dialog {
max-width: 600px;
}
ha-paper-dialog > * {
margin: 0;
display: block;
padding: 0;
}
ha-icon-button {
padding: 16px;
position: absolute;
top: 0;
right: 0;
display: inline-block;
padding: 8px;
float: right;
}
`,
];

View File

@@ -42,8 +42,6 @@ class StepFlowPickHandler extends LitElement {
private _width?: number;
private _height?: number;
private _getHandlers = memoizeOne(
(h: string[], filter?: string, _localize?: LocalizeFunc) => {
const handlers: HandlerObj[] = h.map((handler) => {
@@ -84,10 +82,7 @@ class StepFlowPickHandler extends LitElement {
@value-changed=${this._filterChanged}
></search-input>
<div
style=${styleMap({
width: `${this._width}px`,
height: `${this._height}px`,
})}
style=${styleMap({ width: `${this._width}px` })}
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
>
${handlers.map(
@@ -144,20 +139,13 @@ class StepFlowPickHandler extends LitElement {
protected updated(changedProps) {
super.updated(changedProps);
// Store the width and height so that when we search, box doesn't jump
const div = this.shadowRoot!.querySelector("div")!;
// Store the width so that when we search, box doesn't jump
if (!this._width) {
const width = div.clientWidth;
const width = this.shadowRoot!.querySelector("div")!.clientWidth;
if (width) {
this._width = width;
}
}
if (!this._height) {
const height = div.clientHeight;
if (height) {
this._height = height;
}
}
}
private async _filterChanged(e) {
@@ -178,8 +166,8 @@ class StepFlowPickHandler extends LitElement {
configFlowContentStyles,
css`
img {
width: 40px;
height: 40px;
max-width: 40px;
max-height: 40px;
}
search-input {
display: block;
@@ -192,12 +180,12 @@ class StepFlowPickHandler extends LitElement {
overflow: auto;
max-height: 600px;
}
@media all and (max-height: 900px) {
@media all and (max-height: 1px) {
div {
max-height: calc(100vh - 134px);
max-height: calc(100vh - 205px);
}
div.advanced {
max-height: calc(100vh - 250px);
max-height: calc(100vh - 300px);
}
}
paper-icon-item {

View File

@@ -2,21 +2,8 @@ import { css } from "lit-element";
export const configFlowContentStyles = css`
h2 {
margin: 24px 0 0;
margin-top: 24px;
padding: 0 24px;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: var(
--mdc-typography-headline6-font-family,
var(--mdc-typography-font-family, Roboto, sans-serif)
);
font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
line-height: var(--mdc-typography-headline6-line-height, 2rem);
font-weight: var(--mdc-typography-headline6-font-weight, 500);
letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
text-transform: var(--mdc-typography-headline6-text-transform, inherit);
box-sizing: border-box;
}
.content {

View File

@@ -66,7 +66,7 @@ class DialogDeviceRegistryDetail extends LitElement {
<paper-input
.value=${this._nameByUser}
@value-changed=${this._nameChanged}
.label=${this.hass.localize("ui.panel.config.devices.name")}
.label=${this.hass.localize("ui.dialogs.devices.name")}
.placeholder=${device.name || ""}
.disabled=${this._submitting}
></paper-input>

View File

@@ -1,4 +1,5 @@
import "@material/mwc-button/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-input/paper-input";
import {
css,
@@ -10,7 +11,7 @@ import {
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../components/ha-dialog";
import "../../components/dialog/ha-paper-dialog";
import "../../components/ha-switch";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
@@ -40,17 +41,21 @@ class DialogBox extends LitElement {
const confirmPrompt = this._params.confirmation || this._params.prompt;
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@close=${this._close}
.heading=${this._params.title
<ha-paper-dialog
with-backdrop
opened
modal
@opened-changed="${this._openedChanged}"
>
<h2>
${this._params.title
? this._params.title
: this._params.confirmation &&
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
>
<div>
this.hass.localize(
"ui.dialogs.generic.default_confirmation_title"
)}
</h2>
<paper-dialog-scrollable>
${this._params.text
? html`
<p
@@ -78,21 +83,23 @@ class DialogBox extends LitElement {
></paper-input>
`
: ""}
</div>
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
${confirmPrompt &&
html`
<mwc-button @click=${this._dismiss} slot="secondaryAction">
<mwc-button @click="${this._dismiss}">
${this._params.dismissText
? this._params.dismissText
: this.hass.localize("ui.dialogs.generic.cancel")}
</mwc-button>
`}
<mwc-button @click=${this._confirm} slot="primaryAction">
<mwc-button @click="${this._confirm}">
${this._params.confirmText
? this._params.confirmText
: this.hass.localize("ui.dialogs.generic.ok")}
</mwc-button>
</ha-dialog>
</div>
</ha-paper-dialog>
`;
}
@@ -120,9 +127,11 @@ class DialogBox extends LitElement {
this._dismiss();
}
private _close(): void {
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
}
}
static get styles(): CSSResult[] {
return [
@@ -132,6 +141,15 @@ class DialogBox extends LitElement {
pointer-events: initial !important;
cursor: initial !important;
}
ha-paper-dialog {
min-width: 400px;
max-width: 500px;
}
@media (max-width: 400px) {
ha-paper-dialog {
min-width: initial;
}
}
a {
color: var(--primary-color);
}
@@ -147,10 +165,6 @@ class DialogBox extends LitElement {
.secondary {
color: var(--secondary-text-color);
}
ha-dialog {
/* Place above other dialogs */
--dialog-z-index: 104;
}
`,
];
}

View File

@@ -86,7 +86,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
<div class="main-title" main-title="" on-click="enlarge">
[[_computeStateName(stateObj)]]
</div>
<template is="dom-if" if="[[hass.user.is_admin]]">
<template is="dom-if" if="[[_computeConfig(hass)]]">
<ha-icon-button
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
icon="hass:settings"
@@ -219,6 +219,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
return stateObj ? computeStateName(stateObj) : "";
}
_computeConfig(hass) {
return hass.user.is_admin && isComponentLoaded(hass, "config");
}
_computeEdit(hass, stateObj) {
const domain = this._computeDomain(stateObj);
return (

View File

@@ -8,18 +8,14 @@ import {
property,
TemplateResult,
} from "lit-element";
import { computeStateName } from "../../../../../../common/entity/compute_state_name";
import "../../../../../../components/ha-dialog";
import "../../../../../../components/ha-switch";
import "../../../../../../components/ha-formfield";
import type { HaSwitch } from "../../../../../../components/ha-switch";
import { computeDeviceName } from "../../../../../../data/device_registry";
import {
fetchMQTTDebugInfo,
MQTTDeviceDebugInfo,
} from "../../../../../../data/mqtt";
import { haStyleDialog } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
import { computeStateName } from "../../common/entity/compute_state_name";
import "../../components/ha-dialog";
import "../../components/ha-switch";
import type { HaSwitch } from "../../components/ha-switch";
import { computeDeviceName } from "../../data/device_registry";
import { fetchMQTTDebugInfo, MQTTDeviceDebugInfo } from "../../data/mqtt";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import "./mqtt-discovery-payload";
import "./mqtt-messages";
import { MQTTDeviceDebugInfoDialogParams } from "./show-dialog-mqtt-device-debug-info";
@@ -65,28 +61,22 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
"ui.dialogs.mqtt_device_debug_info.payload_display"
)}
</h4>
<ha-formfield
.label=${this.hass!.localize(
"ui.dialogs.mqtt_device_debug_info.deserialize"
)}
>
<ha-switch
.checked=${this._showDeserialized}
@change=${this._showDeserializedChanged}
>
</ha-switch>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.dialogs.mqtt_device_debug_info.show_as_yaml"
${this.hass!.localize(
"ui.dialogs.mqtt_device_debug_info.deserialize"
)}
>
</ha-switch>
<ha-switch
.checked=${this._showAsYaml}
@change=${this._showAsYamlChanged}
>
${this.hass!.localize(
"ui.dialogs.mqtt_device_debug_info.show_as_yaml"
)}
</ha-switch>
</ha-formfield>
<h4>
${this.hass!.localize("ui.dialogs.mqtt_device_debug_info.entities")}
</h4>
@@ -201,8 +191,8 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
.showAsYaml=${this._showAsYaml}
.summary=${"Payload"}
>
</mqtt-discovery-payload>
</li>
</mqtt-discovery-payload>
</ul>
</li>
`

View File

@@ -9,9 +9,9 @@ import {
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { formatTimeWithSeconds } from "../../../../../../common/datetime/format_time";
import { HomeAssistant } from "../../../../../../types";
import { MQTTMessage } from "../../../../../../data/mqtt";
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
import { HomeAssistant } from "../../types";
import { MQTTMessage } from "../../data/mqtt";
@customElement("mqtt-messages")
class MQTTMessages extends LitElement {

View File

@@ -1,5 +1,5 @@
import { fireEvent } from "../../../../../../common/dom/fire_event";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import { fireEvent } from "../../common/dom/fire_event";
import { DeviceRegistryEntry } from "../../data/device_registry";
export interface MQTTDeviceDebugInfoDialogParams {
device: DeviceRegistryEntry;

View File

@@ -0,0 +1,113 @@
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../components/dialog/ha-paper-dialog";
import type { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
import { fetchZHADevice, ZHADevice } from "../../data/zha";
import "../../panels/config/zha/zha-device-card";
import type { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import type { ZHADeviceInfoDialogParams } from "./show-dialog-zha-device-info";
@customElement("dialog-zha-device-info")
class DialogZHADeviceInfo extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _params?: ZHADeviceInfoDialogParams;
@property() private _error?: string;
@property() private _device?: ZHADevice;
public async showDialog(params: ZHADeviceInfoDialogParams): Promise<void> {
this._params = params;
this._device = await fetchZHADevice(this.hass, params.ieee);
await this.updateComplete;
this._dialog.open();
}
protected render(): TemplateResult {
if (!this._params || !this._device) {
return html``;
}
return html`
<ha-paper-dialog
with-backdrop
opened
@opened-changed=${this._openedChanged}
>
${this._error
? html` <div class="error">${this._error}</div> `
: html`
<zha-device-card
class="card"
.hass=${this.hass}
.device=${this._device}
@zha-device-removed=${this._onDeviceRemoved}
.showEntityDetail=${false}
.showActions="${this._device.device_type !== "Coordinator"}"
></zha-device-card>
`}
</ha-paper-dialog>
`;
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!ev.detail.value) {
this._params = undefined;
this._error = undefined;
this._device = undefined;
}
}
private _onDeviceRemoved(): void {
this._closeDialog();
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private _closeDialog() {
this._dialog.close();
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-paper-dialog > * {
margin: 0;
display: block;
padding: 0;
}
.card {
box-sizing: border-box;
display: flex;
flex: 1 0 300px;
min-width: 0;
max-width: 600px;
word-wrap: break-word;
}
.error {
color: var(--google-red-500);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-zha-device-info": DialogZHADeviceInfo;
}
}

View File

@@ -0,0 +1,21 @@
import { fireEvent } from "../../common/dom/fire_event";
export interface ZHADeviceInfoDialogParams {
ieee: string;
}
export const loadZHADeviceInfoDialog = () =>
import(
/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info"
);
export const showZHADeviceInfoDialog = (
element: HTMLElement,
zhaDeviceInfoParams: ZHADeviceInfoDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zha-device-info",
dialogImport: loadZHADeviceInfoDialog,
dialogParams: zhaDeviceInfoParams,
});
};

View File

@@ -6,10 +6,10 @@ import {
property,
TemplateResult,
} from "lit-element";
import "../../../../../components/ha-code-editor";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import "../../components/ha-code-editor";
import { createCloseHeading } from "../../components/ha-dialog";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info";
@customElement("dialog-zha-device-zigbee-info")

View File

@@ -1,5 +1,5 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { ZHADevice } from "../../../../../data/zha";
import { fireEvent } from "../../common/dom/fire_event";
import { ZHADevice } from "../../data/zha";
export interface ZHADeviceZigbeeInfoDialogParams {
device: ZHADevice;

View File

@@ -1,5 +1,3 @@
// 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";

View File

@@ -1,5 +1,3 @@
// Compat needs to be first import
import "../resources/compatibility";
import {
Auth,
Connection,

View File

@@ -1,4 +1,3 @@
import "../resources/compatibility";
import { PolymerElement } from "@polymer/polymer";
import { fireEvent } from "../common/dom/fire_event";
import { loadJS } from "../common/dom/load_resource";
@@ -18,9 +17,12 @@ let es5Loaded: Promise<unknown> | undefined;
window.loadES5Adapter = () => {
if (!es5Loaded) {
es5Loaded = loadJS(
es5Loaded = Promise.all([
loadJS(
`${__STATIC_PATH__}polyfills/custom-elements-es5-adapter.js`
).catch(); // Swallow errors as it raises errors on old browsers.
).catch(),
import(/* webpackChunkName: "compat" */ "./compatibility"),
]);
}
return es5Loaded;
};

View File

@@ -1,5 +1,3 @@
// Compat needs to be first import
import "../resources/compatibility";
import "../onboarding/ha-onboarding";
import "../resources/ha-style";
import "../resources/roboto";

View File

@@ -2,7 +2,7 @@
if (navigator.userAgent.indexOf("Android") === -1 &&
navigator.userAgent.indexOf("CrOS") === -1) {
function _pf(src, type) {
var el = document.createElement("link");
const el = document.createElement("link");
el.rel = "preload";
el.as = "font";
el.type = "font/woff2";

View File

@@ -47,11 +47,15 @@
(function() {
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5PageJS %>");
});
}
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5PageJS %>");
<% } %>
}

View File

@@ -58,36 +58,34 @@
window.customPanelJS = "<%= latestCustomPanelJS %>";
window.latestJS = true;
</script>
<script>
{% for extra_module in extra_modules -%}
import("{{ extra_module }}");
<script type="module" crossorigin="use-credentials" src="{{ extra_module }}"></script>
{% endfor -%}
</script>
<script>
(function() {
if (!window.latestJS) {
window.customPanelJS = "<%= es5CustomPanelJS %>";
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% 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("<%= es5Compatibility %>").then(function() {
return System.import("<%= es5CoreJS %>");
}).then(function() {
System.import("<%= es5AppJS %>");
});
}
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5CoreJS %>");
_ls("<%= es5AppJS %>");
<% } %>
}
</script>
<script>
if (!window.latestJS) {
{% for extra_script in extra_js_es5 -%}
_ls("{{ extra_script }}");
{% endfor -%}
}
})();
</script>
{% for extra_url in extra_urls -%}

View File

@@ -49,11 +49,15 @@
(function() {
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5PageJS %>");
});
}
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5PageJS %>");
<% } %>
}

View File

@@ -91,20 +91,6 @@ class PartialPanelResolver extends HassRouterPage {
private _waitForStart = false;
private _disconnectedPanel?: ChildNode;
private _hiddenTimeout?: number;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
document.addEventListener(
"visibilitychange",
() => this._handleVisibilityChange(),
false
);
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
@@ -155,27 +141,6 @@ class PartialPanelResolver extends HassRouterPage {
}
}
private _handleVisibilityChange() {
if (document.hidden) {
this._hiddenTimeout = window.setTimeout(() => {
this._hiddenTimeout = undefined;
if (this.lastChild) {
this._disconnectedPanel = this.lastChild;
this.removeChild(this.lastChild);
}
}, 300000);
} else {
if (this._hiddenTimeout) {
clearTimeout(this._hiddenTimeout);
this._hiddenTimeout = undefined;
}
if (this._disconnectedPanel) {
this.appendChild(this._disconnectedPanel);
this._disconnectedPanel = undefined;
}
}
}
private async _updateRoutes(oldPanels?: HomeAssistant["panels"]) {
this.routerOptions = getRoutes(this.hass.panels);

View File

@@ -1,9 +1,9 @@
import {
Auth,
createConnection,
genClientId,
getAuth,
subscribeConfig,
genClientId,
} from "home-assistant-js-websocket";
import {
customElement,
@@ -14,12 +14,12 @@ import {
} from "lit-element";
import { HASSDomEvent } from "../common/dom/fire_event";
import { subscribeOne } from "../common/util/subscribe-one";
import { hassUrl, AuthUrlSearchParams } from "../data/auth";
import { hassUrl } from "../data/auth";
import {
fetchOnboardingOverview,
OnboardingResponses,
OnboardingStep,
onboardIntegrationStep,
ValidOnboardingStep,
} from "../data/onboarding";
import { subscribeUser } from "../data/ws-user";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
@@ -28,28 +28,19 @@ import { HomeAssistant } from "../types";
import { registerServiceWorker } from "../util/register-service-worker";
import "./onboarding-create-user";
import "./onboarding-loading";
import { extractSearchParamsObject } from "../common/url/search-params";
type OnboardingEvent =
| {
type: "user";
result: OnboardingResponses["user"];
}
| {
type: "core_config";
result: OnboardingResponses["core_config"];
}
| {
type: "integration";
};
interface OnboardingEvent<T extends ValidOnboardingStep> {
type: T;
result: OnboardingResponses[T];
}
declare global {
interface HASSDomEvents {
"onboarding-step": OnboardingEvent;
"onboarding-step": OnboardingEvent<ValidOnboardingStep>;
}
interface GlobalEventHandlersEventMap {
"onboarding-step": HASSDomEvent<OnboardingEvent>;
"onboarding-step": HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>;
}
}
@@ -159,7 +150,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
}
}
private async _handleStepDone(ev: HASSDomEvent<OnboardingEvent>) {
private async _handleStepDone(
ev: HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>
) {
const stepResult = ev.detail;
this._steps = this._steps!.map((step) =>
step.step === stepResult.type ? { ...step, done: true } : step
@@ -183,41 +176,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
} else if (stepResult.type === "core_config") {
// We do nothing
} else if (stepResult.type === "integration") {
const result = stepResult.result as OnboardingResponses["integration"];
this._loading = true;
// Determine if oauth redirect has been provided
const externalAuthParams = extractSearchParamsObject() as AuthUrlSearchParams;
const authParams =
externalAuthParams.client_id && externalAuthParams.redirect_uri
? externalAuthParams
: {
client_id: genClientId(),
redirect_uri: `${location.protocol}//${location.host}/?auth_callback=1`,
state: btoa(
JSON.stringify({
hassUrl: `${location.protocol}//${location.host}`,
clientId: genClientId(),
})
),
};
let result: OnboardingResponses["integration"];
try {
result = await onboardIntegrationStep(this.hass!, {
client_id: authParams.client_id!,
redirect_uri: authParams.redirect_uri!,
});
} catch (err) {
this.hass!.connection.close();
await this.hass!.auth.revoke();
alert(`Unable to finish onboarding: ${err.message}`);
document.location.assign("/?");
return;
}
// If we don't close the connection manually, the connection will be
// closed when we navigate away from the page. Firefox allows JS to
// continue to execute, and so HAWS will automatically reconnect once
@@ -230,17 +191,17 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
// Revoke current auth token.
await this.hass!.auth.revoke();
// Build up the url to redirect to
let redirectUrl = authParams.redirect_uri!;
redirectUrl +=
(redirectUrl.includes("?") ? "&" : "?") +
`code=${encodeURIComponent(result.auth_code)}`;
if (authParams.state) {
redirectUrl += `&state=${encodeURIComponent(authParams.state)}`;
}
document.location.assign(redirectUrl);
const state = btoa(
JSON.stringify({
hassUrl: `${location.protocol}//${location.host}`,
clientId: genClientId(),
})
);
document.location.assign(
`/?auth_callback=1&code=${encodeURIComponent(
result.auth_code
)}&state=${state}`
);
}
}

View File

@@ -1,4 +1,5 @@
import "@material/mwc-button/mwc-button";
import { genClientId } from "home-assistant-js-websocket";
import {
css,
CSSResult,
@@ -20,6 +21,7 @@ import {
} from "../data/config_flow";
import { DataEntryFlowProgress } from "../data/data_entry_flow";
import { domainToName } from "../data/integration";
import { onboardIntegrationStep } from "../data/onboarding";
import {
loadConfigFlowDialog,
showConfigFlowDialog,
@@ -167,8 +169,12 @@ class OnboardingIntegrations extends LitElement {
}
private async _finish() {
const result = await onboardIntegrationStep(this.hass, {
client_id: genClientId(),
});
fireEvent(this, "onboarding-step", {
type: "integration",
result,
});
}

View File

@@ -1,12 +1,9 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-icon-button";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-svg-icon";
import { mdiDotsVertical, mdiArrowUp, mdiArrowDown } from "@mdi/js";
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -99,42 +96,33 @@ export default class HaAutomationActionRow extends LitElement {
<div class="card-menu">
${this.index !== 0
? html`
<mwc-icon-button
.title=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-icon-button
icon="hass:arrow-up"
@click=${this._moveUp}
>
<ha-svg-icon path=${mdiArrowUp}></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
`
: ""}
${this.index !== this.totalActions - 1
? html`
<mwc-icon-button
.title=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-icon-button
icon="hass:arrow-down"
@click=${this._moveDown}
>
<ha-svg-icon path=${mdiArrowDown}></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
`
: ""}
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button
slot="trigger"
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<ha-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></ha-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item
@tap=${this._switchYamlMode}
.disabled=${selected === -1}
>
@@ -145,18 +133,19 @@ export default class HaAutomationActionRow extends LitElement {
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item @tap=${this._onDelete}>
</paper-item>
<paper-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
${yamlMode
? html`
@@ -270,17 +259,14 @@ export default class HaAutomationActionRow extends LitElement {
top: 0;
right: 0;
z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
color: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
ha-button-menu {
margin: 8px;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
.card-menu paper-item {
cursor: pointer;
}
`;
}

View File

@@ -1,5 +1,4 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { customElement, LitElement, property } from "lit-element";
import { html } from "lit-html";
import { WaitAction } from "../../../../../data/script";
@@ -20,7 +19,7 @@ export class HaWaitAction extends LitElement implements ActionElement {
const { wait_template, timeout } = this.action;
return html`
<paper-textarea
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
)}
@@ -28,7 +27,7 @@ export class HaWaitAction extends LitElement implements ActionElement {
.value=${wait_template}
@value-changed=${this._valueChanged}
dir="ltr"
></paper-textarea>
></ha-textarea>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_template.timeout"

View File

@@ -1,8 +1,6 @@
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -63,14 +61,19 @@ export default class HaAutomationConditionRow extends LitElement {
<ha-card>
<div class="card-content">
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
slot="trigger"
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
></mwc-icon-button>
<mwc-list-item @tap=${this._switchYamlMode}>
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<ha-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></ha-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item @tap=${this._switchYamlMode}>
${this._yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
@@ -78,18 +81,19 @@ export default class HaAutomationConditionRow extends LitElement {
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
"ui.panel.config.automation.editor.conditions.duplicate"
)}
</mwc-list-item>
<mwc-list-item @tap=${this._onDelete}>
</paper-item>
<paper-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
"ui.panel.config.automation.editor.conditions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
<ha-automation-condition-editor
.yamlMode=${this._yamlMode}
@@ -125,17 +129,14 @@ export default class HaAutomationConditionRow extends LitElement {
top: 0;
right: 0;
z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
color: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
ha-button-menu {
margin: 8px;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
.card-menu paper-item {
cursor: pointer;
}
`;
}

View File

@@ -2,7 +2,7 @@ import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-picker";
import "@polymer/paper-input/paper-textarea";
import "../../../../../components/ha-textarea";
import { NumericStateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-condition-row";
@@ -45,7 +45,7 @@ export default class HaNumericStateCondition extends LitElement {
.value=${below}
@value-changed=${this._valueChanged}
></paper-input>
<paper-textarea
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.numeric_state.value_template"
)}
@@ -53,7 +53,7 @@ export default class HaNumericStateCondition extends LitElement {
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></paper-textarea>
></ha-textarea>
`;
}

View File

@@ -1,5 +1,5 @@
import { customElement, html, LitElement, property } from "lit-element";
import "@polymer/paper-input/paper-textarea";
import "../../../../../components/ha-textarea";
import { TemplateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-condition-row";
@@ -17,7 +17,7 @@ export class HaTemplateCondition extends LitElement {
protected render() {
const { value_template } = this.condition;
return html`
<paper-textarea
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.template.value_template"
)}
@@ -25,7 +25,7 @@ export class HaTemplateCondition extends LitElement {
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></paper-textarea>
></ha-textarea>
`;
}

View File

@@ -1,6 +1,5 @@
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-input/paper-textarea";
import "../../../components/ha-icon-button";
import {
css,
@@ -118,7 +117,7 @@ export class HaAutomationEditor extends LitElement {
@value-changed=${this._valueChanged}
>
</paper-input>
<paper-textarea
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.description.label"
)}
@@ -128,7 +127,7 @@ export class HaAutomationEditor extends LitElement {
name="description"
.value=${this._config.description}
@value-changed=${this._valueChanged}
></paper-textarea>
></ha-textarea>
</div>
${stateObj
? html`

View File

@@ -54,7 +54,9 @@ class HaConfigAutomation extends HassRouterPage {
private _getAutomations = memoizeOne(
(states: HassEntities): AutomationEntity[] => {
return Object.values(states).filter(
(entity) => computeStateDomain(entity) === "automation"
(entity) =>
computeStateDomain(entity) === "automation" &&
!entity.attributes.hidden
) as AutomationEntity[];
}
);

View File

@@ -2,10 +2,8 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -92,14 +90,19 @@ export default class HaAutomationTriggerRow extends LitElement {
<ha-card>
<div class="card-content">
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button
slot="trigger"
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
></mwc-icon-button>
<mwc-list-item
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<ha-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></ha-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item
@tap=${this._switchYamlMode}
.disabled=${selected === -1}
>
@@ -110,18 +113,19 @@ export default class HaAutomationTriggerRow extends LitElement {
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
"ui.panel.config.automation.editor.triggers.duplicate"
)}
</mwc-list-item>
<mwc-list-item @tap=${this._onDelete}>
</paper-item>
<paper-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
"ui.panel.config.automation.editor.triggers.delete"
)}
</mwc-list-item>
</ha-button-menu>
</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
${yamlMode
? html`
@@ -228,17 +232,14 @@ export default class HaAutomationTriggerRow extends LitElement {
top: 0;
right: 0;
z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
color: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
ha-button-menu {
margin: 8px;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
.card-menu paper-item {
cursor: pointer;
}
`;
}

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