Merge pull request #3688 from home-assistant/dev

20190911.0
This commit is contained in:
Bram Kragten 2019-09-11 13:43:09 +02:00 committed by GitHub
commit e78fb35593
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
135 changed files with 4134 additions and 2803 deletions

View File

@ -1,6 +1,7 @@
{ {
"extends": ["airbnb-base", "prettier"], "extends": ["airbnb-base", "prettier"],
"parserOptions": { "parserOptions": {
"ecmaVersion": "2020",
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true, "jsx": true,
"modules": true "modules": true

View File

@ -1,12 +1,9 @@
{ {
"extends": "./.eslintrc-hound.json", "extends": "./.eslintrc-hound.json",
"plugins": [ "plugins": ["react"],
"react"
],
"env": { "env": {
"browser": true "browser": true
}, },
"parser": "babel-eslint",
"rules": { "rules": {
"import/no-unresolved": 2, "import/no-unresolved": 2,
"linebreak-style": 0, "linebreak-style": 0,

View File

@ -1,48 +0,0 @@
module.exports.babelLoaderConfig = ({ latestBuild }) => {
if (latestBuild === undefined) {
throw Error("latestBuild not defined for babel loader config");
}
return {
test: /\.m?js$|\.tsx?$/,
use: {
loader: "babel-loader",
options: {
presets: [
!latestBuild && [
require("@babel/preset-env").default,
{ modules: false },
],
[
require("@babel/preset-typescript").default,
{
jsxPragma: "h",
},
],
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
[
"@babel/transform-react-jsx",
{
pragma: "h",
},
],
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
],
},
},
};
};

View File

@ -0,0 +1,73 @@
const del = require("del");
const gulp = require("gulp");
const mapStream = require("map-stream");
const inDir = "translations";
const downloadDir = inDir + "/downloads";
const tasks = [];
function hasHtml(data) {
return /<[a-z][\s\S]*>/i.test(data);
}
function recursiveCheckHasHtml(file, data, errors, recKey) {
Object.keys(data).forEach(function(key) {
if (typeof data[key] === "object") {
nextRecKey = recKey ? `${recKey}.${key}` : key;
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
} else if (hasHtml(data[key])) {
errors.push(`HTML found in ${file.path} at key ${recKey}.${key}`);
}
});
}
function checkHtml() {
let errors = [];
return mapStream(function(file, cb) {
const content = file.contents;
let error;
if (content) {
if (hasHtml(String(content))) {
const data = JSON.parse(String(content));
recursiveCheckHasHtml(file, data, errors);
if (errors.length > 0) {
error = errors.join("\r\n");
}
}
}
cb(error, file);
});
}
let taskName = "clean-downloaded-translations";
gulp.task(taskName, function() {
return del([`${downloadDir}/**`]);
});
tasks.push(taskName);
taskName = "check-translations-html";
gulp.task(taskName, function() {
return gulp.src(`${downloadDir}/*.json`).pipe(checkHtml());
});
tasks.push(taskName);
taskName = "move-downloaded-translations";
gulp.task(taskName, function() {
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDir));
});
tasks.push(taskName);
taskName = "check-downloaded-translations";
gulp.task(
taskName,
gulp.series(
"check-translations-html",
"move-downloaded-translations",
"clean-downloaded-translations"
)
);
tasks.push(taskName);
module.exports = tasks;

View File

@ -7,7 +7,6 @@ const CompressionPlugin = require("compression-webpack-plugin");
const zopfli = require("@gfx/zopfli"); const zopfli = require("@gfx/zopfli");
const ManifestPlugin = require("webpack-manifest-plugin"); const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js"); const paths = require("./paths.js");
const { babelLoaderConfig } = require("./babel.js");
let version = fs let version = fs
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8") .readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
@ -41,6 +40,20 @@ const resolve = {
}, },
}; };
const tsLoader = (latestBuild) => ({
test: /\.ts|tsx$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
options: {
compilerOptions: latestBuild
? { noEmit: false }
: { target: "es5", noEmit: false },
},
},
],
});
const cssLoader = { const cssLoader = {
test: /\.css$/, test: /\.css$/,
use: "raw-loader", use: "raw-loader",
@ -118,7 +131,7 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
devtool: genDevTool(isProdBuild), devtool: genDevTool(isProdBuild),
entry, entry,
module: { module: {
rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader], rules: [tsLoader(latestBuild), cssLoader, htmlLoader],
}, },
optimization: optimization(latestBuild), optimization: optimization(latestBuild),
plugins: [ plugins: [
@ -186,7 +199,7 @@ const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
compatibility: "./src/entrypoints/compatibility.ts", compatibility: "./src/entrypoints/compatibility.ts",
}, },
module: { module: {
rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader], rules: [tsLoader(latestBuild), cssLoader, htmlLoader],
}, },
optimization: optimization(latestBuild), optimization: optimization(latestBuild),
plugins: [ plugins: [
@ -233,7 +246,7 @@ const createCastConfig = ({ isProdBuild, latestBuild }) => {
devtool: genDevTool(isProdBuild), devtool: genDevTool(isProdBuild),
entry, entry,
module: { module: {
rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader], rules: [tsLoader(latestBuild), cssLoader, htmlLoader],
}, },
optimization: optimization(latestBuild), optimization: optimization(latestBuild),
plugins: [ plugins: [

View File

@ -134,7 +134,13 @@ export class HcMain extends HassElement {
this._error = err; this._error = err;
return; return;
} }
const connection = await createConnection({ auth }); let connection;
try {
connection = await createConnection({ auth });
} catch (err) {
this._error = err;
return;
}
if (this.hass) { if (this.hass) {
this.hass.connection.close(); this.hass.connection.close();
} }

View File

@ -1,6 +1,5 @@
const path = require("path"); const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin");
const { babelLoaderConfig } = require("../build-scripts/babel.js");
const webpackBase = require("../build-scripts/webpack.js"); const webpackBase = require("../build-scripts/webpack.js");
const isProd = process.env.NODE_ENV === "production"; const isProd = process.env.NODE_ENV === "production";
@ -17,7 +16,20 @@ module.exports = {
entry: "./src/entrypoint.js", entry: "./src/entrypoint.js",
module: { module: {
rules: [ rules: [
babelLoaderConfig({ latestBuild }), {
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
options: {
compilerOptions: latestBuild
? { noEmit: false }
: { target: "es5", noEmit: false },
},
},
],
},
{ {
test: /\.css$/, test: /\.css$/,
use: "raw-loader", use: "raw-loader",

View File

@ -102,7 +102,7 @@ export function parseTextToColoredPre(text) {
if (match[1] === undefined) continue; if (match[1] === undefined) continue;
for (const colorCode of match[1].split(";")) { match[1].split(";").forEach((colorCode) => {
switch (parseInt(colorCode)) { switch (parseInt(colorCode)) {
case 0: case 0:
// reset // reset
@ -195,7 +195,7 @@ export function parseTextToColoredPre(text) {
state.backgroundColor = null; state.backgroundColor = null;
break; break;
} }
} });
} }
addSpan(text.substring(i)); addSpan(text.substring(i));

View File

@ -1,5 +1,7 @@
window.loadES5Adapter().then(() => { window.loadES5Adapter().then(() => {
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons"); import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons");
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-main" */ "./hassio-main"); import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
}); });
const styleEl = document.createElement("style"); const styleEl = document.createElement("style");

View File

@ -3,7 +3,6 @@ const CompressionPlugin = require("compression-webpack-plugin");
const zopfli = require("@gfx/zopfli"); const zopfli = require("@gfx/zopfli");
const config = require("./config.js"); const config = require("./config.js");
const { babelLoaderConfig } = require("../build-scripts/babel.js");
const webpackBase = require("../build-scripts/webpack.js"); const webpackBase = require("../build-scripts/webpack.js");
const isProdBuild = process.env.NODE_ENV === "production"; const isProdBuild = process.env.NODE_ENV === "production";
@ -19,7 +18,20 @@ module.exports = {
}, },
module: { module: {
rules: [ rules: [
babelLoaderConfig({ latestBuild }), {
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
options: {
compilerOptions: latestBuild
? { noEmit: false }
: { target: "es5", noEmit: false },
},
},
],
},
{ {
test: /\.(html)$/, test: /\.(html)$/,
use: { use: {

View File

@ -8,7 +8,7 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"build": "script/build_frontend", "build": "script/build_frontend",
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc", "lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'src/**/*.tsx' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts' && tsc",
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts", "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", "docker_build": "sh ./script/docker_run.sh build $npm_package_version",
@ -17,9 +17,11 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@material/mwc-base": "^0.6.0", "@material/mwc-base": "^0.8.0",
"@material/mwc-button": "^0.6.0", "@material/mwc-button": "^0.8.0",
"@material/mwc-ripple": "^0.6.0", "@material/mwc-checkbox": "^0.8.0",
"@material/mwc-fab": "^0.8.0",
"@material/mwc-ripple": "0.8.0",
"@mdi/svg": "4.3.95", "@mdi/svg": "4.3.95",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.0.2",
"@polymer/app-localize-behavior": "^3.0.1", "@polymer/app-localize-behavior": "^3.0.1",
@ -45,7 +47,6 @@
"@polymer/paper-dialog-scrollable": "^3.0.1", "@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-drawer-panel": "^3.0.1", "@polymer/paper-drawer-panel": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.0.1", "@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-fab": "^3.0.1",
"@polymer/paper-icon-button": "^3.0.2", "@polymer/paper-icon-button": "^3.0.2",
"@polymer/paper-input": "^3.0.1", "@polymer/paper-input": "^3.0.1",
"@polymer/paper-item": "^3.0.1", "@polymer/paper-item": "^3.0.1",
@ -79,12 +80,12 @@
"fuse.js": "^3.4.4", "fuse.js": "^3.4.4",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4", "hls.js": "^0.12.4",
"home-assistant-js-websocket": "4.3.1", "home-assistant-js-websocket": "^4.4.0",
"intl-messageformat": "^2.2.0", "intl-messageformat": "^2.2.0",
"jquery": "^3.3.1", "jquery": "^3.4.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"leaflet": "^1.4.0", "leaflet": "^1.4.0",
"lit-element": "^2.2.0", "lit-element": "^2.2.1",
"lit-html": "^1.1.0", "lit-html": "^1.1.0",
"marked": "^0.6.1", "marked": "^0.6.1",
"mdn-polyfills": "^5.16.0", "mdn-polyfills": "^5.16.0",
@ -103,15 +104,6 @@
"xss": "^1.0.6" "xss": "^1.0.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.0",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@babel/plugin-proposal-object-rest-spread": "^7.4.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/preset-env": "^7.4.2",
"@babel/preset-typescript": "^7.3.3",
"@gfx/zopfli": "^1.0.11", "@gfx/zopfli": "^1.0.11",
"@types/chai": "^4.1.7", "@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12", "@types/chromecast-caf-receiver": "^3.0.12",
@ -120,19 +112,17 @@
"@types/leaflet": "^1.4.3", "@types/leaflet": "^1.4.3",
"@types/memoize-one": "4.1.0", "@types/memoize-one": "4.1.0",
"@types/mocha": "^5.2.6", "@types/mocha": "^5.2.6",
"babel-eslint": "^10",
"babel-loader": "^8.0.5",
"chai": "^4.2.0", "chai": "^4.2.0",
"compression-webpack-plugin": "^2.0.0", "compression-webpack-plugin": "^2.0.0",
"copy-webpack-plugin": "^5.0.2", "copy-webpack-plugin": "^5.0.2",
"del": "^4.0.0", "del": "^4.0.0",
"eslint": "^5.15.3", "eslint": "^6.3.0",
"eslint-config-airbnb-base": "^13.1.0", "eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^4.1.0", "eslint-config-prettier": "^6.2.0",
"eslint-import-resolver-webpack": "^0.11.0", "eslint-import-resolver-webpack": "^0.11.1",
"eslint-plugin-import": "^2.16.0", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^3.0.1", "eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-react": "^7.12.4", "eslint-plugin-react": "^7.14.3",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"gulp-foreach": "^0.1.0", "gulp-foreach": "^0.1.0",
@ -148,17 +138,18 @@
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^1.3.1", "husky": "^1.3.1",
"lint-staged": "^8.1.5", "lint-staged": "^8.1.5",
"lodash.template": "^4.4.0", "lodash.template": "^4.5.0",
"map-stream": "^0.0.7",
"merge-stream": "^1.0.1", "merge-stream": "^1.0.1",
"mocha": "^6.0.2", "mocha": "^6.0.2",
"parse5": "^5.1.0", "parse5": "^5.1.0",
"polymer-cli": "^1.9.7",
"prettier": "^1.16.4", "prettier": "^1.16.4",
"raw-loader": "^2.0.0", "raw-loader": "^2.0.0",
"reify": "^0.18.1", "reify": "^0.18.1",
"require-dir": "^1.2.0", "require-dir": "^1.2.0",
"sinon": "^7.3.1", "sinon": "^7.3.1",
"terser-webpack-plugin": "^1.2.3", "terser-webpack-plugin": "^1.2.3",
"ts-loader": "^6.0.4",
"ts-mocha": "^6.0.0", "ts-mocha": "^6.0.0",
"tslint": "^5.14.0", "tslint": "^5.14.0",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",

View File

@ -21,7 +21,7 @@ fi
[ -z "${LOKALISE_TOKEN-}" ] && LOKALISE_TOKEN="$(<.lokalise_token)" [ -z "${LOKALISE_TOKEN-}" ] && LOKALISE_TOKEN="$(<.lokalise_token)"
PROJECT_ID="3420425759f6d6d241f598.13594006" PROJECT_ID="3420425759f6d6d241f598.13594006"
LOCAL_DIR="$(pwd)/translations" LOCAL_DIR="$(pwd)/translations/downloads"
FILE_FORMAT=json FILE_FORMAT=json
mkdir -p ${LOCAL_DIR} mkdir -p ${LOCAL_DIR}
@ -35,3 +35,5 @@ docker run \
--export_empty skip \ --export_empty skip \
--type json \ --type json \
--unzip_to /opt/dest --unzip_to /opt/dest
./node_modules/.bin/gulp check-downloaded-translations

View File

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

View File

@ -97,14 +97,11 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) || this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) ||
this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24) this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)
) { ) {
this.cacheConfig = Object.assign( this.cacheConfig = {
{},
{
refresh: stateObj.attributes.refresh || 0, refresh: stateObj.attributes.refresh || 0,
cacheKey: stateObj.entity_id, cacheKey: stateObj.entity_id,
hoursToShow: stateObj.attributes.hours_to_show || 24, hoursToShow: stateObj.attributes.hours_to_show || 24,
} };
);
} }
} }

View File

@ -14,7 +14,7 @@ export interface GetStatusMessage extends BaseCastMessage {
export interface ConnectMessage extends BaseCastMessage { export interface ConnectMessage extends BaseCastMessage {
type: "connect"; type: "connect";
refreshToken: string; refreshToken: string;
clientId: string; clientId: string | null;
hassUrl: string; hassUrl: string;
} }

View File

@ -1,11 +1,12 @@
interface OnChangeComponent { // interface OnChangeComponent {
props: { // props: {
index: number; // index: number;
onChange(index: number, data: object); // onChange(index: number, data: object);
}; // };
} // }
export function onChangeEvent(this: OnChangeComponent, prop, ev) { // export function onChangeEvent(this: OnChangeComponent, prop, ev) {
export function onChangeEvent(this: any, prop, ev) {
const origData = this.props[prop]; const origData = this.props[prop];
if (ev.target.value === origData[ev.target.name]) { if (ev.target.value === origData[ev.target.name]) {

View File

@ -0,0 +1,32 @@
import { customElement } from "lit-element";
import {
DeviceAction,
fetchDeviceActions,
localizeDeviceAutomationAction,
} from "../../data/device_automation";
import "../../components/ha-paper-dropdown-menu";
import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
@customElement("ha-device-action-picker")
class HaDeviceActionPicker extends HaDeviceAutomationPicker<DeviceAction> {
protected NO_AUTOMATION_TEXT = "No actions";
protected UNKNOWN_AUTOMATION_TEXT = "Unknown action";
constructor() {
super(
localizeDeviceAutomationAction,
fetchDeviceActions,
(deviceId?: string) => ({
device_id: deviceId || "",
domain: "",
entity_id: "",
})
);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-action-picker": HaDeviceActionPicker;
}
}

View File

@ -287,11 +287,12 @@ class HaChartBase extends mixinBehaviors(
} }
positionX += this._chart.canvas.offsetLeft; positionX += this._chart.canvas.offsetLeft;
// Display, position, and set styles for font // Display, position, and set styles for font
this.tooltip = Object.assign({}, this.tooltip, { this.tooltip = {
...this.tooltip,
opacity: 1, opacity: 1,
left: `${positionX}px`, left: `${positionX}px`,
top: `${positionY}px`, top: `${positionY}px`,
}); };
} }
_legendClick(event) { _legendClick(event) {

View File

@ -21,6 +21,7 @@ class StateBadge extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
@property() public stateObj?: HassEntity; @property() public stateObj?: HassEntity;
@property() public overrideIcon?: string; @property() public overrideIcon?: string;
@property() public overrideImage?: string;
@query("ha-icon") private _icon!: HaIcon; @query("ha-icon") private _icon!: HaIcon;
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
@ -55,8 +56,11 @@ class StateBadge extends LitElement {
}; };
if (stateObj) { if (stateObj) {
// hide icon if we have entity picture // hide icon if we have entity picture
if (stateObj.attributes.entity_picture && !this.overrideIcon) { if (
let imageUrl = stateObj.attributes.entity_picture; (stateObj.attributes.entity_picture && !this.overrideIcon) ||
this.overrideImage
) {
let imageUrl = this.overrideImage || stateObj.attributes.entity_picture;
if (this.hass) { if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl); imageUrl = this.hass.hassUrl(imageUrl);
} }

View File

@ -0,0 +1,20 @@
import { Constructor, customElement } from "lit-element";
import "@material/mwc-checkbox";
// tslint:disable-next-line
import { Checkbox } from "@material/mwc-checkbox";
// tslint:disable-next-line
const MwcCheckbox = customElements.get("mwc-checkbox") as Constructor<Checkbox>;
@customElement("ha-checkbox")
export class HaCheckbox extends MwcCheckbox {
protected firstUpdated() {
super.firstUpdated();
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-checkbox": HaCheckbox;
}
}

49
src/components/ha-fab.ts Normal file
View File

@ -0,0 +1,49 @@
import {
classMap,
html,
customElement,
Constructor,
} from "@material/mwc-base/base-element";
import { ripple } from "@material/mwc-ripple/ripple-directive.js";
import "@material/mwc-fab";
// tslint:disable-next-line
import { Fab } from "@material/mwc-fab";
// tslint:disable-next-line
const MwcFab = customElements.get("mwc-fab") as Constructor<Fab>;
@customElement("ha-fab")
export class HaFab extends MwcFab {
// We override the render method because we don't have an icon font and mwc-fab doesn't support our svg-icon sets.
// Based on version mwc-fab 0.8
protected render() {
const classes = {
"mdc-fab--mini": this.mini,
"mdc-fab--exited": this.exited,
"mdc-fab--extended": this.extended,
};
const showLabel = this.label !== "" && this.extended;
return html`
<button
.ripple="${ripple()}"
class="mdc-fab ${classMap(classes)}"
?disabled="${this.disabled}"
aria-label="${this.label || this.icon}"
>
${showLabel && this.showIconAtEnd ? this.label : ""}
${this.icon
? html`
<ha-icon .icon=${this.icon}></ha-icon>
`
: ""}
${showLabel && !this.showIconAtEnd ? this.label : ""}
</button>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-fab": HaFab;
}
}

View File

@ -49,13 +49,17 @@ class HaMarkdown extends UpdatingElement {
// Open external links in a new window // Open external links in a new window
if ( if (
node.nodeName === "A" && node instanceof HTMLAnchorElement &&
(node as HTMLAnchorElement).host !== document.location.host node.host !== document.location.host
) { ) {
(node as HTMLAnchorElement).target = "_blank"; node.target = "_blank";
// protect referrer on external links and deny window.opener access for security reasons
// (see https://mathiasbynens.github.io/rel-noopener/)
node.rel = "noreferrer noopener";
// Fire a resize event when images loaded to notify content resized // Fire a resize event when images loaded to notify content resized
} else if (node.nodeName === "IMG") { } else if (node) {
node.addEventListener("load", this._resize); node.addEventListener("load", this._resize);
} }
} }

View File

@ -10,6 +10,9 @@ export interface DeviceAutomation {
event?: string; event?: string;
} }
// tslint:disable-next-line: no-empty-interface
export interface DeviceAction extends DeviceAutomation {}
export interface DeviceCondition extends DeviceAutomation { export interface DeviceCondition extends DeviceAutomation {
condition: string; condition: string;
} }
@ -18,6 +21,12 @@ export interface DeviceTrigger extends DeviceAutomation {
platform: string; platform: string;
} }
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
hass.callWS<DeviceAction[]>({
type: "device_automation/action/list",
device_id: deviceId,
});
export const fetchDeviceConditions = (hass: HomeAssistant, deviceId: string) => export const fetchDeviceConditions = (hass: HomeAssistant, deviceId: string) =>
hass.callWS<DeviceCondition[]>({ hass.callWS<DeviceCondition[]>({
type: "device_automation/condition/list", type: "device_automation/condition/list",
@ -52,6 +61,24 @@ export const deviceAutomationsEqual = (
return true; return true;
}; };
export const localizeDeviceAutomationAction = (
hass: HomeAssistant,
action: DeviceAction
) => {
const state = action.entity_id ? hass.states[action.entity_id] : undefined;
return hass.localize(
`component.${action.domain}.device_automation.action_type.${action.type}`,
"entity_name",
state ? compute_state_name(state) : "<unknown>",
"subtype",
hass.localize(
`component.${action.domain}.device_automation.action_subtype.${
action.subtype
}`
)
);
};
export const localizeDeviceAutomationCondition = ( export const localizeDeviceAutomationCondition = (
hass: HomeAssistant, hass: HomeAssistant,
condition: DeviceCondition condition: DeviceCondition

View File

@ -163,7 +163,7 @@ class HaStateHistoryData extends LocalizeMixin(PolymerElement) {
localize, localize,
language language
).then((stateHistory) => { ).then((stateHistory) => {
this._setData(Object.assign({}, stateHistory)); this._setData({ ...stateHistory });
}); });
}, cacheConfig.refresh * 1000); }, cacheConfig.refresh * 1000);
} }

View File

@ -7,6 +7,12 @@ export interface EventAction {
event_data_template?: { [key: string]: any }; event_data_template?: { [key: string]: any };
} }
export interface DeviceAction {
device_id: string;
domain: string;
entity_id: string;
}
export const triggerScript = ( export const triggerScript = (
hass: HomeAssistant, hass: HomeAssistant,
entityId: string, entityId: string,

View File

@ -79,10 +79,11 @@ class MoreInfoGroup extends PolymerElement {
// Groups need to be filtered out or we'll show content of // Groups need to be filtered out or we'll show content of
// first child above the children of the current group // first child above the children of the current group
if (groupDomain !== "group") { if (groupDomain !== "group") {
groupDomainStateObj = Object.assign({}, baseStateObj, { groupDomainStateObj = {
...baseStateObj,
entity_id: stateObj.entity_id, entity_id: stateObj.entity_id,
attributes: Object.assign({}, baseStateObj.attributes), attributes: { ...baseStateObj.attributes },
}); };
for (let i = 0; i < states.length; i++) { for (let i = 0; i < states.length; i++) {
if (groupDomain !== computeStateDomain(states[i])) { if (groupDomain !== computeStateDomain(states[i])) {

View File

@ -1,6 +1,7 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";

View File

@ -193,9 +193,10 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
} }
if (this._cacheConfig.cacheKey !== `more_info.${newVal.entity_id}`) { if (this._cacheConfig.cacheKey !== `more_info.${newVal.entity_id}`) {
this._cacheConfig = Object.assign({}, this._cacheConfig, { this._cacheConfig = {
...this._cacheConfig,
cacheKey: `more_info.${newVal.entity_id}`, cacheKey: `more_info.${newVal.entity_id}`,
}); };
} }
} }

View File

@ -80,11 +80,7 @@ function initPushNotifications() {
event.waitUntil( event.waitUntil(
self.registration self.registration
.getNotifications({ tag: data.tag }) .getNotifications({ tag: data.tag })
.then(function(notifications) { .then((notifications) => notifications.forEach((n) => n.close()))
for (const n of notifications) {
n.close();
}
})
); );
return; return;
} }

View File

@ -14,7 +14,7 @@ import { HassElement } from "../state/hass-element";
export class HomeAssistantAppEl extends HassElement { export class HomeAssistantAppEl extends HassElement {
@property() private _route?: Route; @property() private _route?: Route;
@property() private _error?: boolean; @property() private _error = false;
@property() private _panelUrl?: string; @property() private _panelUrl?: string;
protected render() { protected render() {
@ -49,6 +49,7 @@ export class HomeAssistantAppEl extends HassElement {
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("_panelUrl")) { if (changedProps.has("_panelUrl")) {
this.panelUrlChanged(this._panelUrl!); this.panelUrlChanged(this._panelUrl!);
this._updateHass({ panelUrl: this._panelUrl }); this._updateHass({ panelUrl: this._panelUrl });
@ -70,7 +71,11 @@ export class HomeAssistantAppEl extends HassElement {
} }
} }
private _routeChanged(ev) { private async _routeChanged(ev) {
// routeChangged event listener is called while we're doing the fist render,
// causing the update to be ignored. So delay it to next task (Lit render is sync).
await new Promise((resolve) => setTimeout(resolve, 0));
const route = ev.detail.value as Route; const route = ev.detail.value as Route;
// If it's the first route that we process, // If it's the first route that we process,
// check if we should navigate away from / // check if we should navigate away from /

View File

@ -8,7 +8,6 @@ import {
} from "lit-element"; } from "lit-element";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-fab/paper-fab";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { import {
@ -19,6 +18,7 @@ import {
subscribeAreaRegistry, subscribeAreaRegistry,
} from "../../../data/area_registry"; } from "../../../data/area_registry";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../ha-config-section"; import "../ha-config-section";
@ -100,7 +100,7 @@ class HaConfigAreaRegistry extends LitElement {
</ha-config-section> </ha-config-section>
</hass-subpage> </hass-subpage>
<paper-fab <ha-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
icon="hass:plus" icon="hass:plus"
title="${this.hass.localize( title="${this.hass.localize(
@ -110,7 +110,7 @@ class HaConfigAreaRegistry extends LitElement {
class="${classMap({ class="${classMap({
rtl: computeRTL(this.hass), rtl: computeRTL(this.hass),
})}" })}"
></paper-fab> ></ha-fab>
`; `;
} }
@ -183,24 +183,24 @@ All devices in this area will become unassigned.`)
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab.rtl { ha-fab.rtl {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[is-wide].rtl { ha-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@ -10,11 +10,11 @@ import {
import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-fab/paper-fab";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { h, render } from "preact"; import { h, render } from "preact";
import "../../../components/ha-fab";
import "../../../components/ha-paper-icon-button-arrow-prev"; import "../../../components/ha-paper-icon-button-arrow-prev";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
@ -113,7 +113,7 @@ class HaAutomationEditor extends LitElement {
})}" })}"
></div> ></div>
</div> </div>
<paper-fab <ha-fab
slot="fab" slot="fab"
?is-wide="${this.isWide}" ?is-wide="${this.isWide}"
?dirty="${this._dirty}" ?dirty="${this._dirty}"
@ -125,7 +125,7 @@ class HaAutomationEditor extends LitElement {
class="${classMap({ class="${classMap({
rtl: computeRTL(this.hass), rtl: computeRTL(this.hass),
})}" })}"
></paper-fab> ></ha-fab>
</ha-app-layout> </ha-app-layout>
`; `;
} }
@ -301,7 +301,7 @@ class HaAutomationEditor extends LitElement {
span[slot="introduction"] a { span[slot="introduction"] a {
color: var(--primary-color); color: var(--primary-color);
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
@ -310,21 +310,21 @@ class HaAutomationEditor extends LitElement {
transition: margin-bottom 0.3s; transition: margin-bottom 0.3s;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab[dirty] { ha-fab[dirty] {
margin-bottom: 0; margin-bottom: 0;
} }
paper-fab.rtl { ha-fab.rtl {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[is-wide].rtl { ha-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@ -8,13 +8,13 @@ import {
customElement, customElement,
} from "lit-element"; } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "@polymer/paper-fab/paper-fab";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../components/entity/ha-entity-toggle"; import "../../../components/entity/ha-entity-toggle";
import "../ha-config-section"; import "../ha-config-section";
@ -136,7 +136,7 @@ class HaAutomationPicker extends LitElement {
</ha-config-section> </ha-config-section>
<a href="/config/automation/new"> <a href="/config/automation/new">
<paper-fab <ha-fab
slot="fab" slot="fab"
?is-wide=${this.isWide} ?is-wide=${this.isWide}
icon="hass:plus" icon="hass:plus"
@ -144,7 +144,7 @@ class HaAutomationPicker extends LitElement {
"ui.panel.config.automation.picker.add_automation" "ui.panel.config.automation.picker.add_automation"
)} )}
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
></paper-fab ></ha-fab
></a> ></a>
</hass-subpage> </hass-subpage>
`; `;
@ -186,24 +186,24 @@ class HaAutomationPicker extends LitElement {
display: flex; display: flex;
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab[rtl] { ha-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[rtl][is-wide] { ha-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@ -48,9 +48,9 @@ class HaCustomizeAttribute extends PolymerElement {
tapButton() { tapButton() {
if (this.item.secondary) { if (this.item.secondary) {
this.item = Object.assign({}, this.item, { secondary: false }); this.item = { ...this.item, secondary: false };
} else { } else {
this.item = Object.assign({}, this.item, { closed: true }); this.item = { ...this.item, closed: true };
} }
} }
@ -73,7 +73,7 @@ class HaCustomizeAttribute extends PolymerElement {
this.$.child = child = document.createElement(tag.toLowerCase()); this.$.child = child = document.createElement(tag.toLowerCase());
child.className = "form-control"; child.className = "form-control";
child.addEventListener("item-changed", () => { child.addEventListener("item-changed", () => {
this.item = Object.assign({}, child.item); this.item = { ...child.item };
}); });
} }
child.setProperties({ item: this.item }); child.setProperties({ item: this.item });

View File

@ -141,17 +141,15 @@ class HaFormCustomize extends PolymerElement {
} }
_initOpenObject(key, value, secondary, config) { _initOpenObject(key, value, secondary, config) {
return Object.assign( return {
{
attribute: key, attribute: key,
value: value, value: value,
closed: false, closed: false,
domain: computeStateDomain(this.entity), domain: computeStateDomain(this.entity),
secondary: secondary, secondary: secondary,
description: key, description: key,
}, ...config,
config };
);
} }
loadEntity(entity) { loadEntity(entity) {

View File

@ -1,7 +1,6 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-fab/paper-fab";
import "@polymer/iron-icon/iron-icon"; import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
@ -9,6 +8,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../components/entity/ha-state-icon"; import "../../../components/entity/ha-state-icon";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import "../../../resources/ha-style"; import "../../../resources/ha-style";
@ -53,24 +53,24 @@ class HaConfigManagerDashboard extends LocalizeMixin(
color: var(--primary-text-color); color: var(--primary-text-color);
text-decoration: none; text-decoration: none;
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab[rtl] { ha-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[rtl][is-wide] { ha-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;
@ -141,13 +141,13 @@ class HaConfigManagerDashboard extends LocalizeMixin(
</ha-card> </ha-card>
</ha-config-section> </ha-config-section>
<paper-fab <ha-fab
icon="hass:plus" icon="hass:plus"
title="[[localize('ui.panel.config.integrations.new')]]" title="[[localize('ui.panel.config.integrations.new')]]"
on-click="_createFlow" on-click="_createFlow"
is-wide$="[[isWide]]" is-wide$="[[isWide]]"
rtl$="[[rtl]]" rtl$="[[rtl]]"
></paper-fab> ></ha-fab>
</hass-subpage> </hass-subpage>
`; `;
} }

View File

@ -8,7 +8,7 @@ import Trigger from "./trigger/index";
import Condition from "./condition/index"; import Condition from "./condition/index";
import Script from "./script/index"; import Script from "./script/index";
export default class Automation extends Component { export default class Automation extends Component<any> {
constructor() { constructor() {
super(); super();
@ -18,29 +18,26 @@ export default class Automation extends Component {
this.actionChanged = this.actionChanged.bind(this); this.actionChanged = this.actionChanged.bind(this);
} }
onChange(ev) { public onChange(ev) {
this.props.onChange( this.props.onChange({
Object.assign({}, this.props.automation, { ...this.props.automation,
[ev.target.name]: ev.target.value, [ev.target.name]: ev.target.value,
}) });
);
} }
triggerChanged(trigger) { public triggerChanged(trigger) {
this.props.onChange(Object.assign({}, this.props.automation, { trigger })); this.props.onChange({ ...this.props.automation, trigger });
} }
conditionChanged(condition) { public conditionChanged(condition) {
this.props.onChange( this.props.onChange({ ...this.props.automation, condition });
Object.assign({}, this.props.automation, { condition })
);
} }
actionChanged(action) { public actionChanged(action) {
this.props.onChange(Object.assign({}, this.props.automation, { action })); this.props.onChange({ ...this.props.automation, action });
} }
render({ automation, isWide, hass, localize }) { public render({ automation, isWide, hass, localize }) {
const { alias, trigger, condition, action } = automation; const { alias, trigger, condition, action } = automation;
return ( return (

View File

@ -23,25 +23,26 @@ const TYPES = {
const OPTIONS = Object.keys(TYPES).sort(); const OPTIONS = Object.keys(TYPES).sort();
export default class ConditionRow extends Component { export default class ConditionRow extends Component<any> {
constructor() { constructor() {
super(); super();
this.typeChanged = this.typeChanged.bind(this); this.typeChanged = this.typeChanged.bind(this);
} }
typeChanged(ev) { public typeChanged(ev) {
const type = ev.target.selectedItem.attributes.condition.value; const type = ev.target.selectedItem.attributes.condition.value;
if (type !== this.props.condition.condition) { if (type !== this.props.condition.condition) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, condition: type,
Object.assign({ condition: type }, TYPES[type].defaultConfig) ...TYPES[type].defaultConfig,
); });
} }
} }
render({ index, condition, onChange, hass, localize }) { public render({ index, condition, onChange, hass, localize }) {
// tslint:disable-next-line: variable-name
const Comp = TYPES[condition.condition]; const Comp = TYPES[condition.condition];
const selected = OPTIONS.indexOf(condition.condition); const selected = OPTIONS.indexOf(condition.condition);

View File

@ -7,14 +7,14 @@ import "../../../../components/ha-card";
import ConditionEdit from "./condition_edit"; import ConditionEdit from "./condition_edit";
export default class ConditionRow extends Component { export default class ConditionRow extends Component<any> {
constructor() { constructor() {
super(); super();
this.onDelete = this.onDelete.bind(this); this.onDelete = this.onDelete.bind(this);
} }
onDelete() { public onDelete() {
// eslint-disable-next-line // eslint-disable-next-line
if ( if (
confirm( confirm(
@ -27,7 +27,7 @@ export default class ConditionRow extends Component {
} }
} }
render(props) { public render(props) {
return ( return (
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">

View File

@ -3,32 +3,28 @@ import { h, Component } from "preact";
import "../../../../components/device/ha-device-picker"; import "../../../../components/device/ha-device-picker";
import "../../../../components/device/ha-device-condition-picker"; import "../../../../components/device/ha-device-condition-picker";
import { onChangeEvent } from "../../../../common/preact/event"; export default class DeviceCondition extends Component<any, any> {
export default class DeviceCondition extends Component {
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "condition");
this.devicePicked = this.devicePicked.bind(this); this.devicePicked = this.devicePicked.bind(this);
this.deviceConditionPicked = this.deviceConditionPicked.bind(this); this.deviceConditionPicked = this.deviceConditionPicked.bind(this);
this.state.device_id = undefined; this.state = { device_id: undefined };
} }
devicePicked(ev) { public devicePicked(ev) {
this.setState({ device_id: ev.target.value }); this.setState({ device_id: ev.target.value });
} }
deviceConditionPicked(ev) { public deviceConditionPicked(ev) {
const deviceCondition = ev.target.value; const deviceCondition = ev.target.value;
this.props.onChange( this.props.onChange(this.props.index, deviceCondition);
this.props.index,
(this.props.condition = deviceCondition)
);
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ condition, hass }, { device_id }) { public render({ condition, hass }, { device_id }) {
if (device_id === undefined) device_id = condition.device_id; if (device_id === undefined) {
device_id = condition.device_id;
}
return ( return (
<div> <div>
@ -50,7 +46,7 @@ export default class DeviceCondition extends Component {
} }
} }
DeviceCondition.defaultConfig = { (DeviceCondition as any).defaultConfig = {
device_id: "", device_id: "",
domain: "", domain: "",
entity_id: "", entity_id: "",

View File

@ -4,7 +4,7 @@ import "../../../../components/ha-card";
import ConditionRow from "./condition_row"; import ConditionRow from "./condition_row";
export default class Condition extends Component { export default class Condition extends Component<any> {
constructor() { constructor() {
super(); super();
@ -12,7 +12,7 @@ export default class Condition extends Component {
this.conditionChanged = this.conditionChanged.bind(this); this.conditionChanged = this.conditionChanged.bind(this);
} }
addCondition() { public addCondition() {
const condition = this.props.condition.concat({ const condition = this.props.condition.concat({
condition: "state", condition: "state",
}); });
@ -20,7 +20,7 @@ export default class Condition extends Component {
this.props.onChange(condition); this.props.onChange(condition);
} }
conditionChanged(index, newValue) { public conditionChanged(index, newValue) {
const condition = this.props.condition.concat(); const condition = this.props.condition.concat();
if (newValue === null) { if (newValue === null) {
@ -32,7 +32,7 @@ export default class Condition extends Component {
this.props.onChange(condition); this.props.onChange(condition);
} }
render({ condition, hass, localize }) { public render({ condition, hass, localize }) {
return ( return (
<div class="triggers"> <div class="triggers">
{condition.map((cnd, idx) => ( {condition.map((cnd, idx) => (

View File

@ -5,7 +5,8 @@ import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class NumericStateCondition extends Component { export default class NumericStateCondition extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -13,15 +14,15 @@ export default class NumericStateCondition extends Component {
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
} }
entityPicked(ev) { public entityPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.condition,
Object.assign({}, this.props.condition, { entity_id: ev.target.value }) entity_id: ev.target.value,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ condition, hass, localize }) { public render({ condition, hass, localize }) {
const { value_template, entity_id, below, above } = condition; const { value_template, entity_id, below, above } = condition;
return ( return (
<div> <div>
@ -61,6 +62,6 @@ export default class NumericStateCondition extends Component {
} }
} }
NumericStateCondition.defaultConfig = { (NumericStateCondition as any).defaultConfig = {
entity_id: "", entity_id: "",
}; };

View File

@ -4,7 +4,8 @@ import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class StateCondition extends Component { export default class StateCondition extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -12,15 +13,15 @@ export default class StateCondition extends Component {
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
} }
entityPicked(ev) { public entityPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.condition,
Object.assign({}, this.props.condition, { entity_id: ev.target.value }) entity_id: ev.target.value,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ condition, hass, localize }) { public render({ condition, hass, localize }) {
const { entity_id, state } = condition; const { entity_id, state } = condition;
const cndFor = condition.for; const cndFor = condition.for;
return ( return (
@ -45,7 +46,7 @@ export default class StateCondition extends Component {
} }
} }
StateCondition.defaultConfig = { (StateCondition as any).defaultConfig = {
entity_id: "", entity_id: "",
state: "", state: "",
}; };

View File

@ -5,7 +5,11 @@ import "@polymer/paper-radio-group/paper-radio-group";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class SunCondition extends Component { export default class SunCondition extends Component<any> {
private onChange: (obj: any) => void;
private afterPicked: (obj: any) => void;
private beforePicked: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -14,8 +18,8 @@ export default class SunCondition extends Component {
this.beforePicked = this.radioGroupPicked.bind(this, "before"); this.beforePicked = this.radioGroupPicked.bind(this, "before");
} }
radioGroupPicked(key, ev) { public radioGroupPicked(key, ev) {
const condition = Object.assign({}, this.props.condition); const condition = { ...this.props.condition };
if (ev.target.selected) { if (ev.target.selected) {
condition[key] = ev.target.selected; condition[key] = ev.target.selected;
@ -26,7 +30,7 @@ export default class SunCondition extends Component {
this.props.onChange(this.props.index, condition); this.props.onChange(this.props.index, condition);
} }
render({ condition, localize }) { public render({ condition, localize }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
const { after, after_offset, before, before_offset } = condition; const { after, after_offset, before, before_offset } = condition;
return ( return (
@ -101,4 +105,4 @@ export default class SunCondition extends Component {
} }
} }
SunCondition.defaultConfig = {}; (SunCondition as any).defaultConfig = {};

View File

@ -3,14 +3,15 @@ import "../../../../components/ha-textarea";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class TemplateCondition extends Component { export default class TemplateCondition extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "condition"); this.onChange = onChangeEvent.bind(this, "condition");
} }
render({ condition, localize }) { public render({ condition, localize }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
const { value_template } = condition; const { value_template } = condition;
return ( return (
@ -29,6 +30,6 @@ export default class TemplateCondition extends Component {
} }
} }
TemplateCondition.defaultConfig = { (TemplateCondition as any).defaultConfig = {
value_template: "", value_template: "",
}; };

View File

@ -3,7 +3,8 @@ import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class TimeCondition extends Component { export default class TimeCondition extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -11,7 +12,7 @@ export default class TimeCondition extends Component {
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ condition, localize }) { public render({ condition, localize }) {
const { after, before } = condition; const { after, before } = condition;
return ( return (
<div> <div>
@ -36,4 +37,4 @@ export default class TimeCondition extends Component {
} }
} }
TimeCondition.defaultConfig = {}; (TimeCondition as any).defaultConfig = {};

View File

@ -1,6 +1,5 @@
import { h, Component } from "preact"; import { h, Component } from "preact";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event";
import hasLocation from "../../../../common/entity/has_location"; import hasLocation from "../../../../common/entity/has_location";
import computeStateDomain from "../../../../common/entity/compute_state_domain"; import computeStateDomain from "../../../../common/entity/compute_state_domain";
@ -8,31 +7,30 @@ function zoneAndLocationFilter(stateObj) {
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone"; return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
} }
export default class ZoneCondition extends Component { export default class ZoneCondition extends Component<any> {
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "condition");
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
this.zonePicked = this.zonePicked.bind(this); this.zonePicked = this.zonePicked.bind(this);
} }
entityPicked(ev) { public entityPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.condition,
Object.assign({}, this.props.condition, { entity_id: ev.target.value }) entity_id: ev.target.value,
); });
} }
zonePicked(ev) { public zonePicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.condition,
Object.assign({}, this.props.condition, { zone: ev.target.value }) zone: ev.target.value,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ condition, hass, localize }) { public render({ condition, hass, localize }) {
const { entity_id, zone } = condition; const { entity_id, zone } = condition;
return ( return (
<div> <div>
@ -61,7 +59,7 @@ export default class ZoneCondition extends Component {
} }
} }
ZoneCondition.defaultConfig = { (ZoneCondition as any).defaultConfig = {
entity_id: "", entity_id: "",
zone: "", zone: "",
}; };

View File

@ -1,15 +1,18 @@
import { h, Component } from "preact"; import { h, Component } from "preact";
import "../../../components/ha-textarea"; import "../../../components/ha-textarea";
export default class JSONTextArea extends Component { export default class JSONTextArea extends Component<any, any> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state.isValid = true; this.state = {
this.state.value = JSON.stringify(props.value || {}, null, 2); isvalid: true,
value: JSON.stringify(props.value || {}, null, 2),
};
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
} }
onChange(ev) { public onChange(ev) {
const value = ev.target.value; const value = ev.target.value;
let parsed; let parsed;
let isValid; let isValid;
@ -31,16 +34,18 @@ export default class JSONTextArea extends Component {
} }
} }
componentWillReceiveProps({ value }) { public componentWillReceiveProps({ value }) {
if (value === this.props.value) return; if (value === this.props.value) {
return;
}
this.setState({ this.setState({
value: JSON.stringify(value, null, 2), value: JSON.stringify(value, null, 2),
isValid: true, isValid: true,
}); });
} }
render({ label }, { value, isValid }) { public render({ label }, { value, isValid }) {
const style = { const style: any = {
minWidth: 300, minWidth: 300,
width: "100%", width: "100%",
}; };

View File

@ -7,6 +7,23 @@ declare global {
namespace JSX { namespace JSX {
interface IntrinsicElements { interface IntrinsicElements {
"paper-input": Partial<PaperInputElement>; "paper-input": Partial<PaperInputElement>;
"ha-config-section": any;
"ha-card": any;
"paper-radio-button": any;
"paper-radio-group": any;
"ha-entity-picker": any;
"paper-listbox": any;
"paper-item": any;
"paper-menu-button": any;
"paper-dropdown-menu-light": any;
"paper-icon-button": any;
"ha-device-picker": any;
"ha-device-condition-picker": any;
"ha-textarea": any;
"ha-service-picker": any;
"mwc-button": any;
"ha-device-trigger-picker": any;
"ha-device-action-picker": any;
} }
} }
} }

View File

@ -6,7 +6,13 @@ import "../../../components/ha-card";
import Script from "./script/index"; import Script from "./script/index";
export default class ScriptEditor extends Component { export default class ScriptEditor extends Component<{
onChange: (...args: any[]) => any;
script: any;
isWide: any;
hass: any;
localize: any;
}> {
constructor() { constructor() {
super(); super();
@ -14,19 +20,19 @@ export default class ScriptEditor extends Component {
this.sequenceChanged = this.sequenceChanged.bind(this); this.sequenceChanged = this.sequenceChanged.bind(this);
} }
onChange(ev) { public onChange(ev) {
this.props.onChange( this.props.onChange({
Object.assign({}, this.props.script, { ...this.props.script,
[ev.target.name]: ev.target.value, [ev.target.name]: ev.target.value,
}) });
);
} }
sequenceChanged(sequence) { public sequenceChanged(sequence) {
this.props.onChange(Object.assign({}, this.props.script, { sequence })); this.props.onChange({ ...this.props.script, sequence });
} }
render({ script, isWide, hass, localize }) { // @ts-ignore
public render({ script, isWide, hass, localize }) {
const { alias, sequence } = script; const { alias, sequence } = script;
return ( return (

View File

@ -6,6 +6,7 @@ import "@polymer/paper-item/paper-item";
import CallServiceAction from "./call_service"; import CallServiceAction from "./call_service";
import ConditionAction from "./condition"; import ConditionAction from "./condition";
import DelayAction from "./delay"; import DelayAction from "./delay";
import DeviceAction from "./device";
import EventAction from "./event"; import EventAction from "./event";
import WaitAction from "./wait"; import WaitAction from "./wait";
@ -15,12 +16,14 @@ const TYPES = {
wait_template: WaitAction, wait_template: WaitAction,
condition: ConditionAction, condition: ConditionAction,
event: EventAction, event: EventAction,
device_id: DeviceAction,
}; };
const OPTIONS = Object.keys(TYPES).sort(); const OPTIONS = Object.keys(TYPES).sort();
function getType(action) { function getType(action) {
const keys = Object.keys(TYPES); const keys = Object.keys(TYPES);
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
if (keys[i] in action) { if (keys[i] in action) {
return keys[i]; return keys[i];
@ -29,14 +32,14 @@ function getType(action) {
return null; return null;
} }
export default class Action extends Component { export default class Action extends Component<any> {
constructor() { constructor() {
super(); super();
this.typeChanged = this.typeChanged.bind(this); this.typeChanged = this.typeChanged.bind(this);
} }
typeChanged(ev) { public typeChanged(ev) {
const newType = ev.target.selectedItem.attributes.action.value; const newType = ev.target.selectedItem.attributes.action.value;
const oldType = getType(this.props.action); const oldType = getType(this.props.action);
@ -45,9 +48,11 @@ export default class Action extends Component {
} }
} }
render({ index, action, onChange, hass, localize }) { public render({ index, action, onChange, hass, localize }) {
const type = getType(action); const type = getType(action);
// tslint:disable-next-line: variable-name
const Comp = type && TYPES[type]; const Comp = type && TYPES[type];
// @ts-ignore
const selected = OPTIONS.indexOf(type); const selected = OPTIONS.indexOf(type);
if (!Comp) { if (!Comp) {

View File

@ -7,14 +7,14 @@ import "../../../../components/ha-card";
import ActionEdit from "./action_edit"; import ActionEdit from "./action_edit";
export default class Action extends Component { export default class Action extends Component<any> {
constructor() { constructor() {
super(); super();
this.onDelete = this.onDelete.bind(this); this.onDelete = this.onDelete.bind(this);
} }
onDelete() { public onDelete() {
// eslint-disable-next-line // eslint-disable-next-line
if ( if (
confirm( confirm(
@ -27,7 +27,7 @@ export default class Action extends Component {
} }
} }
render(props) { public render(props) {
return ( return (
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">

View File

@ -3,7 +3,7 @@ import "../../../../components/ha-service-picker";
import JSONTextArea from "../json_textarea"; import JSONTextArea from "../json_textarea";
export default class CallServiceAction extends Component { export default class CallServiceAction extends Component<any> {
constructor() { constructor() {
super(); super();
@ -11,21 +11,18 @@ export default class CallServiceAction extends Component {
this.serviceDataChanged = this.serviceDataChanged.bind(this); this.serviceDataChanged = this.serviceDataChanged.bind(this);
} }
serviceChanged(ev) { public serviceChanged(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.action,
Object.assign({}, this.props.action, { service: ev.target.value }) service: ev.target.value,
); });
} }
serviceDataChanged(data) { public serviceDataChanged(data) {
this.props.onChange( this.props.onChange(this.props.index, { ...this.props.action, data });
this.props.index,
Object.assign({}, this.props.action, { data })
);
} }
render({ action, hass, localize }) { public render({ action, hass, localize }) {
const { service, data } = action; const { service, data } = action;
return ( return (
@ -47,7 +44,7 @@ export default class CallServiceAction extends Component {
} }
} }
CallServiceAction.defaultConfig = { (CallServiceAction as any).defaultConfig = {
alias: "", alias: "",
service: "", service: "",
data: {}, data: {},

View File

@ -3,9 +3,9 @@ import { h, Component } from "preact";
import StateCondition from "../condition/state"; import StateCondition from "../condition/state";
import ConditionEdit from "../condition/condition_edit"; import ConditionEdit from "../condition/condition_edit";
export default class ConditionAction extends Component { export default class ConditionAction extends Component<any> {
// eslint-disable-next-line // eslint-disable-next-line
render({ action, index, onChange, hass, localize }) { public render({ action, index, onChange, hass, localize }) {
return ( return (
<ConditionEdit <ConditionEdit
condition={action} condition={action}
@ -18,7 +18,7 @@ export default class ConditionAction extends Component {
} }
} }
ConditionAction.defaultConfig = Object.assign( (ConditionAction as any).defaultConfig = {
{ condition: "state" }, condition: "state",
StateCondition.defaultConfig ...(StateCondition as any).defaultConfig,
); };

View File

@ -2,14 +2,15 @@ import { h, Component } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class DelayAction extends Component { export default class DelayAction extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "action"); this.onChange = onChangeEvent.bind(this, "action");
} }
render({ action, localize }) { public render({ action, localize }) {
const { delay } = action; const { delay } = action;
return ( return (
<div> <div>
@ -26,6 +27,6 @@ export default class DelayAction extends Component {
} }
} }
DelayAction.defaultConfig = { (DelayAction as any).defaultConfig = {
delay: "", delay: "",
}; };

View File

@ -0,0 +1,63 @@
import { h, Component } from "preact";
import "../../../../components/device/ha-device-picker";
import "../../../../components/device/ha-device-action-picker";
import { HomeAssistant } from "../../../../types";
import { DeviceAction } from "../../../../data/script";
export default class DeviceActionEditor extends Component<
{
index: number;
action: DeviceAction;
hass: HomeAssistant;
onChange(index: number, action: DeviceAction);
},
{
device_id: string | undefined;
}
> {
public static defaultConfig: DeviceAction = {
device_id: "",
domain: "",
entity_id: "",
};
constructor() {
super();
this.devicePicked = this.devicePicked.bind(this);
this.deviceActionPicked = this.deviceActionPicked.bind(this);
this.state = { device_id: undefined };
}
public render() {
const { action, hass } = this.props;
const deviceId = this.state.device_id || action.device_id;
return (
<div>
<ha-device-picker
value={deviceId}
onChange={this.devicePicked}
hass={hass}
label="Device"
/>
<ha-device-action-picker
value={action}
deviceId={deviceId}
onChange={this.deviceActionPicked}
hass={hass}
label="Action"
/>
</div>
);
}
private devicePicked(ev) {
this.setState({ device_id: ev.target.value });
}
private deviceActionPicked(ev) {
const deviceAction = { ...ev.target.value };
this.props.onChange(this.props.index, deviceAction);
}
}

View File

@ -4,7 +4,7 @@ import "../../../../components/ha-card";
import ActionRow from "./action_row"; import ActionRow from "./action_row";
export default class Script extends Component { export default class Script extends Component<any> {
constructor() { constructor() {
super(); super();
@ -12,7 +12,7 @@ export default class Script extends Component {
this.actionChanged = this.actionChanged.bind(this); this.actionChanged = this.actionChanged.bind(this);
} }
addAction() { public addAction() {
const script = this.props.script.concat({ const script = this.props.script.concat({
service: "", service: "",
}); });
@ -20,7 +20,7 @@ export default class Script extends Component {
this.props.onChange(script); this.props.onChange(script);
} }
actionChanged(index, newValue) { public actionChanged(index, newValue) {
const script = this.props.script.concat(); const script = this.props.script.concat();
if (newValue === null) { if (newValue === null) {
@ -32,7 +32,7 @@ export default class Script extends Component {
this.props.onChange(script); this.props.onChange(script);
} }
render({ script, hass, localize }) { public render({ script, hass, localize }) {
return ( return (
<div class="script"> <div class="script">
{script.map((act, idx) => ( {script.map((act, idx) => (

View File

@ -5,7 +5,8 @@ import "../../../../components/ha-textarea";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class WaitAction extends Component { export default class WaitAction extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -15,16 +16,14 @@ export default class WaitAction extends Component {
// Gets fired on mount. If empty, onChangeEvent removes attribute. // Gets fired on mount. If empty, onChangeEvent removes attribute.
// Without the attribute this action is no longer matched to this component. // Without the attribute this action is no longer matched to this component.
onTemplateChange(ev) { public onTemplateChange(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.action,
Object.assign({}, this.props.action, {
[ev.target.getAttribute("name")]: ev.target.value, [ev.target.getAttribute("name")]: ev.target.value,
}) });
);
} }
render({ action, localize }) { public render({ action, localize }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
const { wait_template, timeout } = action; const { wait_template, timeout } = action;
return ( return (
@ -51,7 +50,7 @@ export default class WaitAction extends Component {
} }
} }
WaitAction.defaultConfig = { (WaitAction as any).defaultConfig = {
wait_template: "", wait_template: "",
timeout: "", timeout: "",
}; };

View File

@ -2,31 +2,29 @@ import { h, Component } from "preact";
import "../../../../components/device/ha-device-picker"; import "../../../../components/device/ha-device-picker";
import "../../../../components/device/ha-device-trigger-picker"; import "../../../../components/device/ha-device-trigger-picker";
import "../../../../components/device/ha-device-automation-picker";
import { onChangeEvent } from "../../../../common/preact/event"; export default class DeviceTrigger extends Component<any, any> {
export default class DeviceTrigger extends Component {
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "trigger");
this.devicePicked = this.devicePicked.bind(this); this.devicePicked = this.devicePicked.bind(this);
this.deviceTriggerPicked = this.deviceTriggerPicked.bind(this); this.deviceTriggerPicked = this.deviceTriggerPicked.bind(this);
this.state.device_id = undefined; this.state = { device_id: undefined };
} }
devicePicked(ev) { public devicePicked(ev) {
this.setState({ device_id: ev.target.value }); this.setState({ device_id: ev.target.value });
} }
deviceTriggerPicked(ev) { public deviceTriggerPicked(ev) {
const deviceTrigger = ev.target.value; const deviceTrigger = ev.target.value;
this.props.onChange(this.props.index, (this.props.trigger = deviceTrigger)); this.props.onChange(this.props.index, deviceTrigger);
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, hass }, { device_id }) { public render({ trigger, hass }, { device_id }) {
if (device_id === undefined) device_id = trigger.device_id; if (device_id === undefined) {
device_id = trigger.device_id;
}
return ( return (
<div> <div>
@ -48,7 +46,7 @@ export default class DeviceTrigger extends Component {
} }
} }
DeviceTrigger.defaultConfig = { (DeviceTrigger as any).defaultConfig = {
device_id: "", device_id: "",
domain: "", domain: "",
entity_id: "", entity_id: "",

View File

@ -4,7 +4,8 @@ import "@polymer/paper-input/paper-input";
import JSONTextArea from "../json_textarea"; import JSONTextArea from "../json_textarea";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class EventTrigger extends Component { export default class EventTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -13,14 +14,15 @@ export default class EventTrigger extends Component {
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
eventDataChanged(event_data) { // tslint:disable-next-line: variable-name
this.props.onChange( public eventDataChanged(event_data) {
this.props.index, this.props.onChange(this.props.index, {
Object.assign({}, this.props.trigger, { event_data }) ...this.props.trigger,
); event_data,
});
} }
render({ trigger, localize }) { public render({ trigger, localize }) {
const { event_type, event_data } = trigger; const { event_type, event_data } = trigger;
return ( return (
<div> <div>
@ -44,7 +46,7 @@ export default class EventTrigger extends Component {
} }
} }
EventTrigger.defaultConfig = { (EventTrigger as any).defaultConfig = {
event_type: "", event_type: "",
event_data: {}, event_data: {},
}; };

View File

@ -5,7 +5,8 @@ import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class GeolocationTrigger extends Component { export default class GeolocationTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -14,22 +15,22 @@ export default class GeolocationTrigger extends Component {
this.radioGroupPicked = this.radioGroupPicked.bind(this); this.radioGroupPicked = this.radioGroupPicked.bind(this);
} }
zonePicked(ev) { public zonePicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.trigger,
Object.assign({}, this.props.trigger, { zone: ev.target.value }) zone: ev.target.value,
); });
} }
radioGroupPicked(ev) { public radioGroupPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.trigger,
Object.assign({}, this.props.trigger, { event: ev.target.selected }) event: ev.target.selected,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, hass, localize }) { public render({ trigger, hass, localize }) {
const { source, zone, event } = trigger; const { source, zone, event } = trigger;
return ( return (
@ -78,7 +79,7 @@ export default class GeolocationTrigger extends Component {
} }
} }
GeolocationTrigger.defaultConfig = { (GeolocationTrigger as any).defaultConfig = {
source: "", source: "",
zone: "", zone: "",
event: "enter", event: "enter",

View File

@ -2,22 +2,22 @@ import { h, Component } from "preact";
import "@polymer/paper-radio-button/paper-radio-button"; import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
export default class HassTrigger extends Component { export default class HassTrigger extends Component<any> {
constructor() { constructor() {
super(); super();
this.radioGroupPicked = this.radioGroupPicked.bind(this); this.radioGroupPicked = this.radioGroupPicked.bind(this);
} }
radioGroupPicked(ev) { public radioGroupPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.trigger,
Object.assign({}, this.props.trigger, { event: ev.target.selected }) event: ev.target.selected,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, localize }) { public render({ trigger, localize }) {
const { event } = trigger; const { event } = trigger;
return ( return (
<div> <div>
@ -47,6 +47,6 @@ export default class HassTrigger extends Component {
} }
} }
HassTrigger.defaultConfig = { (HassTrigger as any).defaultConfig = {
event: "start", event: "start",
}; };

View File

@ -5,7 +5,7 @@ import "../../../../components/ha-card";
import TriggerRow from "./trigger_row"; import TriggerRow from "./trigger_row";
import StateTrigger from "./state"; import StateTrigger from "./state";
export default class Trigger extends Component { export default class Trigger extends Component<any> {
constructor() { constructor() {
super(); super();
@ -13,15 +13,16 @@ export default class Trigger extends Component {
this.triggerChanged = this.triggerChanged.bind(this); this.triggerChanged = this.triggerChanged.bind(this);
} }
addTrigger() { public addTrigger() {
const trigger = this.props.trigger.concat( const trigger = this.props.trigger.concat({
Object.assign({ platform: "state" }, StateTrigger.defaultConfig) platform: "state",
); ...(StateTrigger as any).defaultConfig,
});
this.props.onChange(trigger); this.props.onChange(trigger);
} }
triggerChanged(index, newValue) { public triggerChanged(index, newValue) {
const trigger = this.props.trigger.concat(); const trigger = this.props.trigger.concat();
if (newValue === null) { if (newValue === null) {
@ -33,7 +34,7 @@ export default class Trigger extends Component {
this.props.onChange(trigger); this.props.onChange(trigger);
} }
render({ trigger, hass, localize }) { public render({ trigger, hass, localize }) {
return ( return (
<div class="triggers"> <div class="triggers">
{trigger.map((trg, idx) => ( {trigger.map((trg, idx) => (

View File

@ -3,7 +3,8 @@ import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class MQTTTrigger extends Component { export default class MQTTTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -11,7 +12,7 @@ export default class MQTTTrigger extends Component {
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, localize }) { public render({ trigger, localize }) {
const { topic, payload } = trigger; const { topic, payload } = trigger;
return ( return (
<div> <div>
@ -36,6 +37,6 @@ export default class MQTTTrigger extends Component {
} }
} }
MQTTTrigger.defaultConfig = { (MQTTTrigger as any).defaultConfig = {
topic: "", topic: "",
}; };

View File

@ -6,7 +6,8 @@ import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class NumericStateTrigger extends Component { export default class NumericStateTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -14,15 +15,15 @@ export default class NumericStateTrigger extends Component {
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
} }
entityPicked(ev) { public entityPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.trigger,
Object.assign({}, this.props.trigger, { entity_id: ev.target.value }) entity_id: ev.target.value,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, hass, localize }) { public render({ trigger, hass, localize }) {
const { value_template, entity_id, below, above } = trigger; const { value_template, entity_id, below, above } = trigger;
let trgFor = trigger.for; let trgFor = trigger.for;
@ -82,6 +83,6 @@ export default class NumericStateTrigger extends Component {
} }
} }
NumericStateTrigger.defaultConfig = { (NumericStateTrigger as any).defaultConfig = {
entity_id: "", entity_id: "",
}; };

View File

@ -5,7 +5,8 @@ import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class StateTrigger extends Component { export default class StateTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -13,15 +14,15 @@ export default class StateTrigger extends Component {
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
} }
entityPicked(ev) { public entityPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.trigger,
Object.assign({}, this.props.trigger, { entity_id: ev.target.value }) entity_id: ev.target.value,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, hass, localize }) { public render({ trigger, hass, localize }) {
const { entity_id, to } = trigger; const { entity_id, to } = trigger;
const trgFrom = trigger.from; const trgFrom = trigger.from;
let trgFor = trigger.for; let trgFor = trigger.for;
@ -73,6 +74,6 @@ export default class StateTrigger extends Component {
} }
} }
StateTrigger.defaultConfig = { (StateTrigger as any).defaultConfig = {
entity_id: "", entity_id: "",
}; };

View File

@ -6,7 +6,8 @@ import "@polymer/paper-radio-group/paper-radio-group";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class SunTrigger extends Component { export default class SunTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -14,15 +15,15 @@ export default class SunTrigger extends Component {
this.radioGroupPicked = this.radioGroupPicked.bind(this); this.radioGroupPicked = this.radioGroupPicked.bind(this);
} }
radioGroupPicked(ev) { public radioGroupPicked(ev) {
this.props.onChange( this.props.onChange(this.props.index, {
this.props.index, ...this.props.trigger,
Object.assign({}, this.props.trigger, { event: ev.target.selected }) event: ev.target.selected,
); });
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, localize }) { public render({ trigger, localize }) {
const { offset, event } = trigger; const { offset, event } = trigger;
return ( return (
<div> <div>
@ -61,6 +62,6 @@ export default class SunTrigger extends Component {
} }
} }
SunTrigger.defaultConfig = { (SunTrigger as any).defaultConfig = {
event: "sunrise", event: "sunrise",
}; };

View File

@ -4,14 +4,15 @@ import "../../../../components/ha-textarea";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class TemplateTrigger extends Component { export default class TemplateTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "trigger"); this.onChange = onChangeEvent.bind(this, "trigger");
} }
render({ trigger, localize }) { public render({ trigger, localize }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
const { value_template } = trigger; const { value_template } = trigger;
return ( return (
@ -30,6 +31,6 @@ export default class TemplateTrigger extends Component {
} }
} }
TemplateTrigger.defaultConfig = { (TemplateTrigger as any).defaultConfig = {
value_template: "", value_template: "",
}; };

View File

@ -4,7 +4,8 @@ import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class TimeTrigger extends Component { export default class TimeTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -12,7 +13,7 @@ export default class TimeTrigger extends Component {
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, localize }) { public render({ trigger, localize }) {
const { at } = trigger; const { at } = trigger;
return ( return (
<div> <div>
@ -29,6 +30,6 @@ export default class TimeTrigger extends Component {
} }
} }
TimeTrigger.defaultConfig = { (TimeTrigger as any).defaultConfig = {
at: "", at: "",
}; };

View File

@ -4,7 +4,8 @@ import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class TimePatternTrigger extends Component { export default class TimePatternTrigger extends Component<any> {
private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -12,7 +13,7 @@ export default class TimePatternTrigger extends Component {
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, localize }) { public render({ trigger, localize }) {
const { hours, minutes, seconds } = trigger; const { hours, minutes, seconds } = trigger;
return ( return (
<div> <div>
@ -45,7 +46,7 @@ export default class TimePatternTrigger extends Component {
} }
} }
TimePatternTrigger.defaultConfig = { (TimePatternTrigger as any).defaultConfig = {
hours: "", hours: "",
minutes: "", minutes: "",
seconds: "", seconds: "",

View File

@ -36,25 +36,15 @@ const TYPES = {
const OPTIONS = Object.keys(TYPES).sort(); const OPTIONS = Object.keys(TYPES).sort();
export default class TriggerEdit extends Component { export default class TriggerEdit extends Component<any> {
constructor() { constructor() {
super(); super();
this.typeChanged = this.typeChanged.bind(this); this.typeChanged = this.typeChanged.bind(this);
} }
typeChanged(ev) { public render({ index, trigger, onChange, hass, localize }) {
const type = ev.target.selectedItem.attributes.platform.value; // tslint:disable-next-line: variable-name
if (type !== this.props.trigger.platform) {
this.props.onChange(
this.props.index,
Object.assign({ platform: type }, TYPES[type].defaultConfig)
);
}
}
render({ index, trigger, onChange, hass, localize }) {
const Comp = TYPES[trigger.platform]; const Comp = TYPES[trigger.platform];
const selected = OPTIONS.indexOf(trigger.platform); const selected = OPTIONS.indexOf(trigger.platform);
@ -102,4 +92,15 @@ export default class TriggerEdit extends Component {
</div> </div>
); );
} }
private typeChanged(ev) {
const type = ev.target.selectedItem.attributes.platform.value;
if (type !== this.props.trigger.platform) {
this.props.onChange(this.props.index, {
platform: type,
...TYPES[type].defaultConfig,
});
}
}
} }

View File

@ -7,27 +7,14 @@ import "../../../../components/ha-card";
import TriggerEdit from "./trigger_edit"; import TriggerEdit from "./trigger_edit";
export default class TriggerRow extends Component { export default class TriggerRow extends Component<any> {
constructor() { constructor() {
super(); super();
this.onDelete = this.onDelete.bind(this); this.onDelete = this.onDelete.bind(this);
} }
onDelete() { public render(props) {
// eslint-disable-next-line
if (
confirm(
this.props.localize(
"ui.panel.config.automation.editor.triggers.delete_confirm"
)
)
) {
this.props.onChange(this.props.index, null);
}
}
render(props) {
return ( return (
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
@ -61,4 +48,17 @@ export default class TriggerRow extends Component {
</ha-card> </ha-card>
); );
} }
private onDelete() {
// eslint-disable-next-line
if (
confirm(
this.props.localize(
"ui.panel.config.automation.editor.triggers.delete_confirm"
)
)
) {
this.props.onChange(this.props.index, null);
}
}
} }

View File

@ -2,15 +2,15 @@ import { h, Component } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
export default class WebhookTrigger extends Component<any> {
export default class WebhookTrigger extends Component { private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "trigger"); this.onChange = onChangeEvent.bind(this, "trigger");
} }
render({ trigger, localize }) { public render({ trigger, localize }) {
const { webhook_id: webhookId } = trigger; const { webhook_id: webhookId } = trigger;
return ( return (
<div> <div>
@ -27,6 +27,6 @@ export default class WebhookTrigger extends Component {
} }
} }
WebhookTrigger.defaultConfig = { (WebhookTrigger as any).defaultConfig = {
webhook_id: "", webhook_id: "",
}; };

View File

@ -3,7 +3,6 @@ import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event";
import hasLocation from "../../../../common/entity/has_location"; import hasLocation from "../../../../common/entity/has_location";
import computeStateDomain from "../../../../common/entity/compute_state_domain"; import computeStateDomain from "../../../../common/entity/compute_state_domain";
@ -11,39 +10,17 @@ function zoneAndLocationFilter(stateObj) {
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone"; return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
} }
export default class ZoneTrigger extends Component { export default class ZoneTrigger extends Component<any> {
constructor() { constructor() {
super(); super();
this.onChange = onChangeEvent.bind(this, "trigger");
this.radioGroupPicked = this.radioGroupPicked.bind(this); this.radioGroupPicked = this.radioGroupPicked.bind(this);
this.entityPicked = this.entityPicked.bind(this); this.entityPicked = this.entityPicked.bind(this);
this.zonePicked = this.zonePicked.bind(this); this.zonePicked = this.zonePicked.bind(this);
} }
entityPicked(ev) {
this.props.onChange(
this.props.index,
Object.assign({}, this.props.trigger, { entity_id: ev.target.value })
);
}
zonePicked(ev) {
this.props.onChange(
this.props.index,
Object.assign({}, this.props.trigger, { zone: ev.target.value })
);
}
radioGroupPicked(ev) {
this.props.onChange(
this.props.index,
Object.assign({}, this.props.trigger, { event: ev.target.selected })
);
}
/* eslint-disable camelcase */ /* eslint-disable camelcase */
render({ trigger, hass, localize }) { public render({ trigger, hass, localize }) {
const { entity_id, zone, event } = trigger; const { entity_id, zone, event } = trigger;
return ( return (
<div> <div>
@ -91,9 +68,30 @@ export default class ZoneTrigger extends Component {
</div> </div>
); );
} }
private entityPicked(ev) {
this.props.onChange(this.props.index, {
...this.props.trigger,
entity_id: ev.target.value,
});
} }
ZoneTrigger.defaultConfig = { private zonePicked(ev) {
this.props.onChange(this.props.index, {
...this.props.trigger,
zone: ev.target.value,
});
}
private radioGroupPicked(ev) {
this.props.onChange(this.props.index, {
...this.props.trigger,
event: ev.target.selected,
});
}
}
(ZoneTrigger as any).defaultConfig = {
entity_id: "", entity_id: "",
zone: "", zone: "",
event: "enter", event: "enter",

View File

@ -8,7 +8,6 @@ import {
} from "lit-element"; } from "lit-element";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-fab/paper-fab";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { import {
@ -19,6 +18,7 @@ import {
createPerson, createPerson,
} from "../../../data/person"; } from "../../../data/person";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import { compare } from "../../../common/string/compare"; import { compare } from "../../../common/string/compare";
@ -109,12 +109,12 @@ class HaConfigPerson extends LitElement {
</ha-config-section> </ha-config-section>
</hass-subpage> </hass-subpage>
<paper-fab <ha-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
icon="hass:plus" icon="hass:plus"
title="Add Person" title="Add Person"
@click=${this._createPerson} @click=${this._createPerson}
></paper-fab> ></ha-fab>
`; `;
} }
@ -221,14 +221,14 @@ All devices belonging to this person will become unassigned.`)
ha-card.storage paper-item { ha-card.storage paper-item {
cursor: pointer; cursor: pointer;
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }

View File

@ -1,13 +1,13 @@
import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-fab/paper-fab";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { h, render } from "preact"; import { h, render } from "preact";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
import "../../../components/ha-paper-icon-button-arrow-prev"; import "../../../components/ha-paper-icon-button-arrow-prev";
import "../../../components/ha-fab";
import Script from "../js/script"; import Script from "../js/script";
import unmountPreact from "../../../common/preact/unmount"; import unmountPreact from "../../../common/preact/unmount";
@ -65,7 +65,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
span[slot="introduction"] a { span[slot="introduction"] a {
color: var(--primary-color); color: var(--primary-color);
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
@ -74,21 +74,21 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
transition: margin-bottom 0.3s; transition: margin-bottom 0.3s;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab[dirty] { ha-fab[dirty] {
margin-bottom: 0; margin-bottom: 0;
} }
paper-fab[rtl] { ha-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[rtl][is-wide] { ha-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;
@ -115,7 +115,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
</template> </template>
<div id="root"></div> <div id="root"></div>
</div> </div>
<paper-fab <ha-fab
slot="fab" slot="fab"
is-wide$="[[isWide]]" is-wide$="[[isWide]]"
dirty$="[[dirty]]" dirty$="[[dirty]]"
@ -123,7 +123,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
title="Save" title="Save"
on-click="saveScript" on-click="saveScript"
rtl$="[[rtl]]" rtl$="[[rtl]]"
></paper-fab> ></ha-fab>
</ha-app-layout> </ha-app-layout>
`; `;
} }

View File

@ -7,7 +7,6 @@ import {
property, property,
customElement, customElement,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-fab/paper-fab";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
@ -17,6 +16,7 @@ import "../../../layouts/hass-subpage";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../ha-config-section"; import "../ha-config-section";
@ -81,13 +81,13 @@ class HaScriptPicker extends LitElement {
</ha-config-section> </ha-config-section>
<a href="/config/script/new"> <a href="/config/script/new">
<paper-fab <ha-fab
slot="fab" slot="fab"
?is-wide=${this.isWide} ?is-wide=${this.isWide}
icon="hass:plus" icon="hass:plus"
title="Add Script" title="Add Script"
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
></paper-fab> ></ha-fab>
</a> </a>
</hass-subpage> </hass-subpage>
`; `;
@ -135,24 +135,24 @@ class HaScriptPicker extends LitElement {
display: flex; display: flex;
} }
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab[rtl] { ha-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[rtl][is-wide] { ha-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@ -1,4 +1,3 @@
import "@polymer/paper-fab/paper-fab";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
@ -7,6 +6,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab";
import LocalizeMixin from "../../../mixins/localize-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin";
import NavigateMixin from "../../../mixins/navigate-mixin"; import NavigateMixin from "../../../mixins/navigate-mixin";
@ -27,21 +27,21 @@ class HaUserPicker extends EventsMixin(
static get template() { static get template() {
return html` return html`
<style> <style>
paper-fab { ha-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
paper-fab[is-wide] { ha-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
paper-fab[rtl] { ha-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
paper-fab[rtl][is-wide] { ha-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;
@ -78,13 +78,13 @@ class HaUserPicker extends EventsMixin(
</template> </template>
</ha-card> </ha-card>
<paper-fab <ha-fab
is-wide$="[[isWide]]" is-wide$="[[isWide]]"
icon="hass:plus" icon="hass:plus"
title="[[localize('ui.panel.config.users.picker.add_user')]]" title="[[localize('ui.panel.config.users.picker.add_user')]]"
on-click="_addUser" on-click="_addUser"
rtl$="[[rtl]]" rtl$="[[rtl]]"
></paper-fab> ></ha-fab>
</hass-subpage> </hass-subpage>
`; `;
} }

View File

@ -52,6 +52,7 @@ export class HaPanelCustom extends UpdatingElement {
return; return;
} }
const props = {}; const props = {};
// @ts-ignore
for (const key of changedProps.keys()) { for (const key of changedProps.keys()) {
props[key] = this[key]; props[key] = this[key];
} }

View File

@ -224,7 +224,7 @@ class HaPanelDevService extends PolymerElement {
const fields = serviceDomains[domain][service].fields; const fields = serviceDomains[domain][service].fields;
return Object.keys(fields).map(function(field) { return Object.keys(fields).map(function(field) {
return Object.assign({ key: field }, fields[field]); return { key: field, ...fields[field] };
}); });
} }
@ -282,9 +282,9 @@ class HaPanelDevService extends PolymerElement {
_fillExampleData() { _fillExampleData() {
const example = {}; const example = {};
for (const attribute of this._attributes) { this._attributes.forEach((attribute) => {
example[attribute.key] = attribute.example; example[attribute.key] = attribute.example;
} });
this.serviceData = JSON.stringify(example, null, 2); this.serviceData = JSON.stringify(example, null, 2);
} }

View File

@ -43,7 +43,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
} }
public static getStubConfig() { public static getStubConfig() {
return { states: ["arm_home", "arm_away"] }; return { states: ["arm_home", "arm_away"], entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -37,7 +37,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return document.createElement("hui-gauge-card-editor"); return document.createElement("hui-gauge-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(): object {
return {}; return { entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -202,6 +202,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
.hass=${this.hass} .hass=${this.hass}
.stateObj=${stateObj} .stateObj=${stateObj}
.overrideIcon=${entityConf.icon} .overrideIcon=${entityConf.icon}
.overrideImage=${entityConf.image}
></state-badge> ></state-badge>
` `
: ""} : ""}

View File

@ -8,6 +8,10 @@ import "../../../data/ha-state-history-data";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
class HuiHistoryGraphCard extends PolymerElement { class HuiHistoryGraphCard extends PolymerElement {
static getStubConfig() {
return { entities: [] };
}
static get template() { static get template() {
return html` return html`
<style> <style>
@ -66,12 +70,12 @@ class HuiHistoryGraphCard extends PolymerElement {
const _entities = []; const _entities = [];
const _names = {}; const _names = {};
for (const entity of entities) { entities.forEach((entity) => {
_entities.push(entity.entity); _entities.push(entity.entity);
if (entity.name) { if (entity.name) {
_names[entity.entity] = entity.name; _names[entity.entity] = entity.name;
} }
} });
this.setProperties({ this.setProperties({
_cacheConfig: { _cacheConfig: {

View File

@ -33,7 +33,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
return document.createElement("hui-light-card-editor"); return document.createElement("hui-light-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(): object {
return {}; return { entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@ -96,9 +96,6 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
@value-changing=${this._dragEvent} @value-changing=${this._dragEvent}
@value-changed=${this._setBrightness} @value-changed=${this._setBrightness}
></round-slider> ></round-slider>
</div>
<div id="tooltip">
<div class="icon-state">
<ha-icon <ha-icon
class="light-icon" class="light-icon"
data-state="${stateObj.state}" data-state="${stateObj.state}"
@ -109,6 +106,8 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
})}" })}"
@click="${this._handleTap}" @click="${this._handleTap}"
></ha-icon> ></ha-icon>
</div>
<div id="tooltip">
<div class="brightness" @ha-click="${this._handleTap}"> <div class="brightness" @ha-click="${this._handleTap}">
${brightness} % ${brightness} %
</div> </div>
@ -116,7 +115,6 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
${this._config.name || computeStateName(stateObj)} ${this._config.name || computeStateName(stateObj)}
</div> </div>
</div> </div>
</div>
</ha-card> </ha-card>
`; `;
} }
@ -165,19 +163,10 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
right: 0; right: 0;
height: 100%; height: 100%;
text-align: center; text-align: center;
z-index: 15;
}
.icon-state {
display: block;
margin: auto;
width: 100%;
height: 100%;
transform: translate(0, 25%);
} }
#light { #light {
margin: 0 auto; margin: auto;
padding-top: 0; padding-top: 0;
padding-bottom: 32px; padding-bottom: 32px;
display: flex; display: flex;
@ -187,19 +176,21 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
width: 160px; width: 160px;
} }
#light round-slider { #light round-slider {
z-index: 20 !important;
margin: 0 auto; margin: 0 auto;
display: inline-block; display: inline-block;
--round-slider-path-color: var(--disabled-text-color); --round-slider-path-color: var(--disabled-text-color);
--round-slider-bar-color: var(--primary-color); --round-slider-bar-color: var(--primary-color);
z-index: 20;
} }
.light-icon { .light-icon {
margin: auto; position: absolute;
margin: 0 auto;
width: 76px; width: 76px;
height: 76px; height: 76px;
color: var(--paper-item-icon-color, #44739e); color: var(--paper-item-icon-color, #44739e);
cursor: pointer; cursor: pointer;
z-index: 20;
} }
.light-icon[data-state="on"] { .light-icon[data-state="on"] {
@ -211,7 +202,10 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
} }
.name { .name {
padding-top: 32px; position: absolute;
top: 160px;
left: 50%;
transform: translate(-50%);
font-size: var(--name-font-size); font-size: var(--name-font-size);
} }
@ -219,8 +213,8 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
font-size: var(--brightness-font-size); font-size: var(--brightness-font-size);
position: absolute; position: absolute;
margin: 0 auto; margin: 0 auto;
top: 135px;
left: 50%; left: 50%;
top: 45%;
transform: translate(-50%); transform: translate(-50%);
opacity: 0; opacity: 0;
transition: opacity 0.5s ease-in-out; transition: opacity 0.5s ease-in-out;

View File

@ -9,7 +9,7 @@ class HuiMediaControlCard extends LegacyWrapperCard {
} }
static getStubConfig() { static getStubConfig() {
return {}; return { entity: "" };
} }
constructor() { constructor() {

View File

@ -22,7 +22,9 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
set hass(hass: HomeAssistant) { set hass(hass: HomeAssistant) {
this._hass = hass; this._hass = hass;
for (const el of this.shadowRoot!.querySelectorAll("#root > *")) { for (const el of Array.from(
this.shadowRoot!.querySelectorAll("#root > *")
)) {
const element = el as LovelaceElement; const element = el as LovelaceElement;
element.hass = this._hass; element.hass = this._hass;
} }

View File

@ -146,7 +146,7 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
} }
public static getStubConfig(): object { public static getStubConfig(): object {
return {}; return { entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -84,6 +84,7 @@ export interface ConfigEntity extends EntityConfig {
export interface GlanceConfigEntity extends ConfigEntity { export interface GlanceConfigEntity extends ConfigEntity {
show_last_changed?: boolean; show_last_changed?: boolean;
image?: string;
} }
export interface GlanceCardConfig extends LovelaceCardConfig { export interface GlanceCardConfig extends LovelaceCardConfig {

View File

@ -36,6 +36,7 @@ const SPECIAL_TYPES = new Set([
"section", "section",
"weblink", "weblink",
"cast", "cast",
"select",
]); ]);
const DOMAIN_TO_ELEMENT_TYPE = { const DOMAIN_TO_ELEMENT_TYPE = {
alert: "toggle", alert: "toggle",

View File

@ -1,8 +1,5 @@
import { directive, PropertyPart } from "lit-html"; import { directive, PropertyPart } from "lit-html";
// See https://github.com/home-assistant/home-assistant-polymer/pull/2457 import "@material/mwc-ripple";
// on how to undo mwc -> paper migration
// import "@material/mwc-ripple";
import "@polymer/paper-ripple";
const isTouch = const isTouch =
"ontouchstart" in window || "ontouchstart" in window ||
@ -19,7 +16,7 @@ interface LongPressElement extends Element {
class LongPress extends HTMLElement implements LongPress { class LongPress extends HTMLElement implements LongPress {
public holdTime: number; public holdTime: number;
protected ripple: any; public ripple: any;
protected timer: number | undefined; protected timer: number | undefined;
protected held: boolean; protected held: boolean;
protected cooldownStart: boolean; protected cooldownStart: boolean;
@ -28,7 +25,7 @@ class LongPress extends HTMLElement implements LongPress {
constructor() { constructor() {
super(); super();
this.holdTime = 500; this.holdTime = 500;
this.ripple = document.createElement("paper-ripple"); this.ripple = document.createElement("mwc-ripple");
this.timer = undefined; this.timer = undefined;
this.held = false; this.held = false;
this.cooldownStart = false; this.cooldownStart = false;
@ -37,7 +34,6 @@ class LongPress extends HTMLElement implements LongPress {
public connectedCallback() { public connectedCallback() {
Object.assign(this.style, { Object.assign(this.style, {
borderRadius: "50%", // paper-ripple
position: "absolute", position: "absolute",
width: isTouch ? "100px" : "50px", width: isTouch ? "100px" : "50px",
height: isTouch ? "100px" : "50px", height: isTouch ? "100px" : "50px",
@ -46,9 +42,7 @@ class LongPress extends HTMLElement implements LongPress {
}); });
this.appendChild(this.ripple); this.appendChild(this.ripple);
this.ripple.style.color = "#03a9f4"; // paper-ripple this.ripple.primary = true;
this.ripple.style.color = "var(--primary-color)"; // paper-ripple
// this.ripple.primary = true;
[ [
"touchcancel", "touchcancel",
@ -154,17 +148,14 @@ class LongPress extends HTMLElement implements LongPress {
top: `${y}px`, top: `${y}px`,
display: null, display: null,
}); });
this.ripple.holdDown = true; // paper-ripple this.ripple.disabled = false;
this.ripple.simulatedRipple(); // paper-ripple this.ripple.active = true;
// this.ripple.disabled = false; this.ripple.unbounded = true;
// this.ripple.active = true;
// this.ripple.unbounded = true;
} }
private stopAnimation() { private stopAnimation() {
this.ripple.holdDown = false; // paper-ripple this.ripple.active = false;
// this.ripple.active = false; this.ripple.disabled = true;
// this.ripple.disabled = true;
this.style.display = "none"; this.style.display = "none";
} }
} }

View File

@ -72,7 +72,7 @@ export class HuiDialogEditCard extends LitElement {
? html` ? html`
<hui-card-picker <hui-card-picker
.hass="${this.hass}" .hass="${this.hass}"
@config-changed="${this._handleConfigChanged}" @config-changed="${this._handleCardPicked}"
></hui-card-picker> ></hui-card-picker>
` `
: html` : html`
@ -220,6 +220,18 @@ export class HuiDialogEditCard extends LitElement {
]; ];
} }
private _handleCardPicked(ev) {
this._cardConfig = ev.detail.config;
if (this._params!.entities && this._params!.entities.length > 0) {
if (Object.keys(this._cardConfig!).includes("entities")) {
this._cardConfig!.entities = this._params!.entities;
} else if (Object.keys(this._cardConfig!).includes("entity")) {
this._cardConfig!.entity = this._params!.entities[0];
}
}
this._error = ev.detail.error;
}
private _handleConfigChanged(ev) { private _handleConfigChanged(ev) {
this._cardConfig = ev.detail.config; this._cardConfig = ev.detail.config;
this._error = ev.detail.error; this._error = ev.detail.error;

View File

@ -15,6 +15,7 @@ const dialogTag = "hui-dialog-edit-card";
export interface EditCardDialogParams { export interface EditCardDialogParams {
lovelace: Lovelace; lovelace: Lovelace;
path: [number] | [number, number]; path: [number] | [number, number];
entities?: string[]; // We can pass entity id's that will be added to the config when a card is picked
} }
const registerEditCardDialog = (element: HTMLElement): Event => const registerEditCardDialog = (element: HTMLElement): Event =>

View File

@ -0,0 +1,79 @@
import {
html,
LitElement,
TemplateResult,
customElement,
property,
} from "lit-element";
import "../../../../components/dialog/ha-paper-dialog";
import { toggleAttribute } from "../../../../common/dom/toggle_attribute";
import "../../components/hui-views-list";
// tslint:disable-next-line:no-duplicate-imports
import { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
import { SelectViewDialogParams } from "./show-select-view-dialog";
import { PolymerChangedEvent } from "../../../../polymer-types";
@customElement("hui-dialog-select-view")
export class HuiDialogSelectView extends LitElement {
@property() private _params?: SelectViewDialogParams;
public async showDialog(params: SelectViewDialogParams): Promise<void> {
this._params = params;
await this.updateComplete;
}
protected updated(changedProps) {
super.updated(changedProps);
toggleAttribute(
this,
"hide-icons",
this._params!.lovelace!.config
? !this._params!.lovelace!.config.views.some((view) => view.icon)
: true
);
}
protected render(): TemplateResult | void {
if (!this._params) {
return html``;
}
return html`
<ha-paper-dialog
with-backdrop
opened
@opened-changed="${this._openedChanged}"
>
<h2>Choose a view</h2>
<hui-views-list
.lovelaceConfig=${this._params!.lovelace.config}
@view-selected=${this._selectView}>
</hui-view-list>
</ha-paper-dialog>
`;
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private _selectView(e: CustomEvent): void {
const view: number = e.detail.view;
this._params!.viewSelectedCallback(view);
this._dialog.close();
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-dialog-select-view": HuiDialogSelectView;
}
}

View File

@ -0,0 +1,19 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import { Lovelace } from "../../types";
export interface SelectViewDialogParams {
lovelace: Lovelace;
viewSelectedCallback: (view: number) => void;
}
export const showSelectViewDialog = (
element: HTMLElement,
selectViewDialogParams: SelectViewDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "hui-dialog-select-view",
dialogImport: () =>
import(/* webpackChunkName: "hui-dialog-select-view" */ "./hui-dialog-select-view"),
dialogParams: selectViewDialogParams,
});
};

View File

@ -0,0 +1,151 @@
import {
html,
LitElement,
TemplateResult,
customElement,
property,
css,
CSSResult,
} from "lit-element";
import { Checkbox } from "@material/mwc-checkbox";
import { HomeAssistant } from "../../../../types";
import computeStateName from "../../../../common/entity/compute_state_name";
import computeDomain from "../../../../common/entity/compute_domain";
import "../../../../components/ha-checkbox";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-relative-time";
import "../../../../components/ha-icon";
import { fireEvent } from "../../../../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"entity-selection-changed": EntitySelectionChangedEvent;
}
}
export interface EntitySelectionChangedEvent {
entity: string;
selected: boolean;
}
@customElement("hui-select-row")
class HuiSelectRow extends LitElement {
@property() public hass!: HomeAssistant;
@property() public entity?: string;
@property() public selectable = true;
protected render(): TemplateResult | void {
if (!this.entity) {
return html``;
}
const stateObj = this.entity ? this.hass.states[this.entity] : undefined;
if (!stateObj) {
return html``;
}
return html`
<div class="flex-row" role="rowgroup">
<div class="flex-cell" role="cell">
${this.selectable
? html`
<ha-checkbox @change=${this._handleSelect}></ha-checkbox>
`
: ""}
<state-badge .hass=${this.hass} .stateObj=${stateObj}></state-badge>
${computeStateName(stateObj)}
</div>
<div class="flex-cell" role="cell">${stateObj.entity_id}</div>
<div class="flex-cell" role="cell">
${computeDomain(stateObj.entity_id)}
</div>
<div class="flex-cell" role="cell">
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_changed}
></ha-relative-time>
</div>
</div>
`;
}
private _handleSelect(ev: Event): void {
const checkbox = ev.currentTarget as Checkbox;
fireEvent(this, "entity-selection-changed", {
entity: this.entity!,
selected: checkbox.checked as boolean,
});
}
static get styles(): CSSResult {
return css`
div {
box-sizing: border-box;
}
.flex-row {
display: flex;
flex-flow: row wrap;
}
.flex-row:hover {
background: var(--table-row-alternative-background-color, #eee);
}
.flex-cell {
width: calc(100% / 4);
padding: 12px 24px;
border-bottom: 1px solid #e0e0e0;
overflow: hidden;
text-overflow: ellipsis;
line-height: 40px;
}
@media all and (max-width: 767px) {
.flex-cell {
width: calc(100% / 3);
padding-top: 0;
}
.flex-cell:first-child {
width: 100%;
padding-top: 12px;
padding-bottom: 0;
border-bottom: 0;
}
}
@media all and (max-width: 430px) {
.flex-cell {
border-bottom: 0;
padding: 0 24px;
}
.flex-cell:first-child {
padding-top: 12px;
}
.flex-cell:last-child {
padding-bottom: 12px;
border-bottom: 1px solid #e0e0e0;
}
.flex-cell {
width: 100%;
}
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-select-row": HuiSelectRow;
}
}

View File

@ -0,0 +1,213 @@
import {
html,
LitElement,
TemplateResult,
PropertyValues,
property,
customElement,
css,
CSSResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../../components/ha-fab";
import "./hui-select-row";
import { computeRTL } from "../../../../common/util/compute_rtl";
import { computeUnusedEntities } from "../../common/compute-unused-entities";
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
import { showEditCardDialog } from "../card-editor/show-edit-card-dialog";
import { HomeAssistant } from "../../../../types";
import { Lovelace } from "../../types";
import { LovelaceConfig } from "../../../../data/lovelace";
@customElement("hui-unused-entities")
export class HuiUnusedEntities extends LitElement {
@property() public lovelace?: Lovelace;
@property() public hass?: HomeAssistant;
@property() private _unusedEntities: string[] = [];
private _selectedEntities: string[] = [];
private get _config(): LovelaceConfig {
return this.lovelace!.config;
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("lovelace")) {
this._getUnusedEntities();
}
}
protected render(): TemplateResult | void {
if (!this.hass || !this.lovelace) {
return html``;
}
if (this.lovelace.mode === "storage" && this.lovelace.editMode === false) {
return html``;
}
return html`
<ha-card header="Unused entities">
<div class="card-content">
These are the entities that you have available, but are not in your
Lovelace UI yet.
${this.lovelace.mode === "storage"
? html`
<br />Select the entities you want to add to a card and then
click the add card button.
`
: ""}
</div>
<div
class="table-container"
role="table"
aria-label="Unused Entities"
@entity-selection-changed=${this._handleSelectionChanged}
>
<div class="flex-row header" role="rowgroup">
<div class="flex-cell" role="columnheader">Entity</div>
<div class="flex-cell" role="columnheader">Entity id</div>
<div class="flex-cell" role="columnheader">Domain</div>
<div class="flex-cell" role="columnheader">Last Changed</div>
</div>
${this._unusedEntities.map((entity) => {
return html`
<hui-select-row
.selectable=${this.lovelace!.mode === "storage"}
.hass=${this.hass}
.entity=${entity}
></hui-select-row>
`;
})}
</div>
</ha-card>
${this.lovelace.mode === "storage"
? html`
<ha-fab
class="${classMap({
rtl: computeRTL(this.hass),
})}"
icon="hass:plus"
label="${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.add"
)}"
@click="${this._selectView}"
></ha-fab>
`
: ""}
`;
}
private _getUnusedEntities(): void {
if (!this.hass || !this.lovelace) {
return;
}
this._selectedEntities = [];
this._unusedEntities = computeUnusedEntities(this.hass, this._config!);
}
private _handleSelectionChanged(ev: any): void {
if (ev.detail.selected) {
this._selectedEntities.push(ev.detail.entity);
} else {
const index = this._selectedEntities.indexOf(ev.detail.entity);
if (index !== -1) {
this._selectedEntities.splice(index, 1);
}
}
}
private _selectView(): void {
showSelectViewDialog(this, {
lovelace: this.lovelace!,
viewSelectedCallback: (view) => this._addCard(view),
});
}
private _addCard(view: number): void {
showEditCardDialog(this, {
lovelace: this.lovelace!,
path: [view],
entities: this._selectedEntities,
});
}
static get styles(): CSSResult {
return css`
:host {
background: var(--lovelace-background);
padding: 16px;
}
ha-fab {
position: sticky;
float: right;
bottom: 16px;
z-index: 1;
}
ha-fab.rtl {
float: left;
}
div {
box-sizing: border-box;
}
.table-container {
display: block;
margin: auto;
}
.flex-row {
display: flex;
flex-flow: row wrap;
}
.flex-row .flex-cell {
font-weight: bold;
}
.flex-cell {
width: calc(100% / 4);
padding: 12px 24px;
border-bottom: 1px solid #e0e0e0;
vertical-align: middle;
}
@media all and (max-width: 767px) {
.flex-cell {
width: calc(100% / 3);
}
.flex-cell:first-child {
width: 100%;
border-bottom: 0;
}
}
@media all and (max-width: 430px) {
.flex-cell {
border-bottom: 0;
}
.flex-cell:last-child {
border-bottom: 1px solid #e0e0e0;
}
.flex-cell {
width: 100%;
}
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-unused-entities": HuiUnusedEntities;
}
}

View File

@ -116,6 +116,20 @@ class HUIRoot extends LitElement {
@iron-select="${this._deselect}" @iron-select="${this._deselect}"
slot="dropdown-content" slot="dropdown-content"
> >
${__DEMO__ /* No unused entities available in the demo */
? ""
: html`
<paper-item
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.unused_entities"
)}
@tap="${this._handleUnusedEntities}"
>
${this.hass!.localize(
"ui.panel.lovelace.menu.unused_entities"
)}
</paper-item>
`}
<paper-item @tap="${this.lovelace!.enableFullEditMode}"> <paper-item @tap="${this.lovelace!.enableFullEditMode}">
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.lovelace.editor.menu.raw_editor" "ui.panel.lovelace.editor.menu.raw_editor"
@ -160,11 +174,6 @@ class HUIRoot extends LitElement {
"ui.panel.lovelace.menu.refresh" "ui.panel.lovelace.menu.refresh"
)} )}
</paper-item> </paper-item>
`
: ""}
${__DEMO__ /* No unused entities available in the demo */
? ""
: html`
<paper-item <paper-item
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.unused_entities" "ui.panel.lovelace.menu.unused_entities"
@ -175,7 +184,10 @@ class HUIRoot extends LitElement {
"ui.panel.lovelace.menu.unused_entities" "ui.panel.lovelace.menu.unused_entities"
)} )}
</paper-item> </paper-item>
`} `
: ""}
${this.hass!.user!.is_admin
? html`
<paper-item <paper-item
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.configure_ui" "ui.panel.lovelace.menu.configure_ui"
@ -186,6 +198,8 @@ class HUIRoot extends LitElement {
"ui.panel.lovelace.menu.configure_ui" "ui.panel.lovelace.menu.configure_ui"
)} )}
</paper-item> </paper-item>
`
: ""}
<paper-item <paper-item
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.help" "ui.panel.lovelace.menu.help"
@ -422,6 +436,15 @@ class HUIRoot extends LitElement {
} }
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) { if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
// Leave unused entities when leaving edit mode
if (
this.lovelace!.mode === "storage" &&
this._routeData!.view === "hass-unused-entities"
) {
const views = this.config && this.config.views;
navigate(this, `/lovelace/${views[0].path || 0}`);
newSelectView = 0;
}
// On edit mode change, recreate the current view from scratch // On edit mode change, recreate the current view from scratch
force = true; force = true;
// Recalculate to see if we need to adjust content area for tab bar // Recalculate to see if we need to adjust content area for tab bar
@ -562,10 +585,10 @@ class HUIRoot extends LitElement {
if (viewIndex === "hass-unused-entities") { if (viewIndex === "hass-unused-entities") {
const unusedEntities = document.createElement("hui-unused-entities"); const unusedEntities = document.createElement("hui-unused-entities");
// Wait for promise to resolve so that the element has been upgraded. // Wait for promise to resolve so that the element has been upgraded.
import(/* webpackChunkName: "hui-unused-entities" */ "./hui-unused-entities").then( import(/* webpackChunkName: "hui-unused-entities" */ "./editor/unused-entities/hui-unused-entities").then(
() => { () => {
unusedEntities.setConfig(this.config);
unusedEntities.hass = this.hass!; unusedEntities.hass = this.hass!;
unusedEntities.lovelace = this.lovelace!;
} }
); );
if (this.config.background) { if (this.config.background) {

View File

@ -1,112 +0,0 @@
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "./cards/hui-entities-card";
import { computeUnusedEntities } from "./common/compute-unused-entities";
import { createCardElement } from "./common/create-card-element";
import { HomeAssistant } from "../../types";
import { LovelaceCard } from "./types";
import { LovelaceConfig } from "../../data/lovelace";
import computeDomain from "../../common/entity/compute_domain";
export class HuiUnusedEntities extends LitElement {
private _hass?: HomeAssistant;
private _config?: LovelaceConfig;
private _elements?: LovelaceCard[];
static get properties(): PropertyDeclarations {
return {
_hass: {},
_config: {},
};
}
set hass(hass: HomeAssistant) {
this._hass = hass;
if (!this._elements) {
this._createElements();
return;
}
for (const element of this._elements) {
element.hass = this._hass;
}
}
public setConfig(config: LovelaceConfig): void {
this._config = config;
this._createElements();
}
protected render(): TemplateResult | void {
if (!this._config || !this._hass) {
return html``;
}
return html`
${this.renderStyle()}
<div id="root">${this._elements}</div>
`;
}
private renderStyle(): TemplateResult {
return html`
<style>
:host {
background: var(--lovelace-background);
}
#root {
padding: 4px;
display: flex;
flex-wrap: wrap;
}
hui-entities-card {
max-width: 400px;
padding: 4px;
flex: 1 auto;
}
</style>
`;
}
private _createElements(): void {
if (!this._hass) {
return;
}
const domains: { [domain: string]: string[] } = {};
computeUnusedEntities(this._hass, this._config!).forEach((entity) => {
const domain = computeDomain(entity);
if (!(domain in domains)) {
domains[domain] = [];
}
domains[domain].push(entity);
});
this._elements = Object.keys(domains)
.sort()
.map((domain) => {
const el = createCardElement({
type: "entities",
title: this._hass!.localize(`domain.${domain}`) || domain,
entities: domains[domain].map((entity) => ({
entity,
secondary_info: "entity-id",
})),
show_header_toggle: false,
});
el.hass = this._hass;
return el;
});
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-unused-entities": HuiUnusedEntities;
}
}
customElements.define("hui-unused-entities", HuiUnusedEntities);

View File

@ -1,3 +1,3 @@
// hui-view dependencies for when in edit mode. // hui-view dependencies for when in edit mode.
import "@polymer/paper-fab/paper-fab";
import "./components/hui-card-options"; import "./components/hui-card-options";
import "../../components/ha-fab";

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