mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-29 12:16:39 +00:00
commit
cd94442455
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@ -13,6 +13,10 @@
|
|||||||
|
|
||||||
**Last working Home Assistant release (if known):**
|
**Last working Home Assistant release (if known):**
|
||||||
|
|
||||||
|
**UI (States or Lovelace UI?):**
|
||||||
|
<!--
|
||||||
|
- Frontend -> Developer tools -> Info
|
||||||
|
-->
|
||||||
|
|
||||||
**Browser and Operating System:**
|
**Browser and Operating System:**
|
||||||
<!--
|
<!--
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const BabelMinifyPlugin = require("babel-minify-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
|
|
||||||
module.exports.resolve = {
|
module.exports.resolve = {
|
||||||
extensions: [".ts", ".js", ".json", ".tsx"],
|
extensions: [".ts", ".js", ".json", ".tsx"],
|
||||||
@ -29,32 +29,15 @@ module.exports.plugins = [
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports.optimization = {
|
module.exports.optimization = (latestBuild) => ({
|
||||||
minimizer: [
|
minimizer: [
|
||||||
// Took options from Polymer build tool
|
new TerserPlugin({
|
||||||
// https://github.com/Polymer/tools/blob/master/packages/build/src/js-transform.ts
|
cache: true,
|
||||||
new BabelMinifyPlugin(
|
parallel: true,
|
||||||
{
|
extractComments: true,
|
||||||
// Disable the minify-constant-folding plugin because it has a bug relating
|
terserOptions: {
|
||||||
// to invalid substitution of constant values into export specifiers:
|
ecma: latestBuild ? undefined : 5,
|
||||||
// https://github.com/babel/minify/issues/820
|
|
||||||
evaluate: false,
|
|
||||||
|
|
||||||
// TODO(aomarks) Find out why we disabled this plugin.
|
|
||||||
simplifyComparisons: false,
|
|
||||||
|
|
||||||
// Prevent removal of things that babel thinks are unreachable, but sometimes
|
|
||||||
// gets wrong: https://github.com/Polymer/tools/issues/724
|
|
||||||
deadcode: false,
|
|
||||||
|
|
||||||
// Disable the simplify plugin because it can eat some statements preceeding
|
|
||||||
// loops. https://github.com/babel/minify/issues/824
|
|
||||||
simplify: false,
|
|
||||||
|
|
||||||
// This is breaking ES6 output. https://github.com/Polymer/tools/issues/261
|
|
||||||
mangle: false,
|
|
||||||
},
|
},
|
||||||
{}
|
}),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
};
|
});
|
||||||
|
@ -39,7 +39,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
optimization: webpackBase.optimization,
|
optimization: webpackBase.optimization(latestBuild),
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__DEV__: false,
|
__DEV__: false,
|
||||||
@ -74,6 +74,7 @@ module.exports = {
|
|||||||
new WorkboxPlugin.GenerateSW({
|
new WorkboxPlugin.GenerateSW({
|
||||||
swDest: "service_worker_es5.js",
|
swDest: "service_worker_es5.js",
|
||||||
importWorkboxFrom: "local",
|
importWorkboxFrom: "local",
|
||||||
|
include: [],
|
||||||
}),
|
}),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: webpackBase.resolve,
|
resolve: webpackBase.resolve,
|
||||||
|
@ -7,6 +7,7 @@ const isProd = process.env.NODE_ENV === "production";
|
|||||||
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||||
const buildPath = path.resolve(__dirname, "dist");
|
const buildPath = path.resolve(__dirname, "dist");
|
||||||
const publicPath = isProd ? "./" : "http://localhost:8080/";
|
const publicPath = isProd ? "./" : "http://localhost:8080/";
|
||||||
|
const latestBuild = true;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: isProd ? "production" : "development",
|
mode: isProd ? "production" : "development",
|
||||||
@ -16,7 +17,7 @@ module.exports = {
|
|||||||
entry: "./src/entrypoint.js",
|
entry: "./src/entrypoint.js",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
babelLoaderConfig({ latestBuild: true }),
|
babelLoaderConfig({ latestBuild }),
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: "raw-loader",
|
use: "raw-loader",
|
||||||
@ -32,7 +33,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
optimization: webpackBase.optimization,
|
optimization: webpackBase.optimization(latestBuild),
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
"public",
|
"public",
|
||||||
@ -51,15 +52,6 @@ module.exports = {
|
|||||||
to: "static/images/leaflet/",
|
to: "static/images/leaflet/",
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
isProd &&
|
|
||||||
new UglifyJsPlugin({
|
|
||||||
extractComments: true,
|
|
||||||
sourceMap: true,
|
|
||||||
uglifyOptions: {
|
|
||||||
// Disabling because it broke output
|
|
||||||
mangle: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: webpackBase.resolve,
|
resolve: webpackBase.resolve,
|
||||||
output: {
|
output: {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
const CompressionPlugin = require("compression-webpack-plugin");
|
const CompressionPlugin = require("compression-webpack-plugin");
|
||||||
const config = require("./config.js");
|
const config = require("./config.js");
|
||||||
const { babelLoaderConfig } = require("../config/babel.js");
|
const { babelLoaderConfig } = require("../config/babel.js");
|
||||||
const { minimizer } = require("../config/babel.js");
|
const webpackBase = require("../config/webpack.js");
|
||||||
|
|
||||||
const isProdBuild = process.env.NODE_ENV === "production";
|
const isProdBuild = process.env.NODE_ENV === "production";
|
||||||
const isCI = process.env.CI === "true";
|
const isCI = process.env.CI === "true";
|
||||||
const chunkFilename = isProdBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
const chunkFilename = isProdBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||||
|
const latestBuild = false;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: isProdBuild ? "production" : "development",
|
mode: isProdBuild ? "production" : "development",
|
||||||
@ -15,7 +16,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
babelLoaderConfig({ latestBuild: false }),
|
babelLoaderConfig({ latestBuild }),
|
||||||
{
|
{
|
||||||
test: /\.(html)$/,
|
test: /\.(html)$/,
|
||||||
use: {
|
use: {
|
||||||
@ -27,9 +28,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: webpackBase.optimization(latestBuild),
|
||||||
minimizer,
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
isProdBuild &&
|
isProdBuild &&
|
||||||
!isCI &&
|
!isCI &&
|
||||||
|
@ -73,7 +73,8 @@
|
|||||||
"deep-clone-simple": "^1.1.1",
|
"deep-clone-simple": "^1.1.1",
|
||||||
"es6-object-assign": "^1.1.0",
|
"es6-object-assign": "^1.1.0",
|
||||||
"fecha": "^3.0.0",
|
"fecha": "^3.0.0",
|
||||||
"home-assistant-js-websocket": "^3.2.4",
|
"hls.js": "^0.12.3",
|
||||||
|
"home-assistant-js-websocket": "^3.3.0",
|
||||||
"intl-messageformat": "^2.2.0",
|
"intl-messageformat": "^2.2.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"js-yaml": "^3.12.0",
|
"js-yaml": "^3.12.0",
|
||||||
@ -107,12 +108,12 @@
|
|||||||
"@gfx/zopfli": "^1.0.9",
|
"@gfx/zopfli": "^1.0.9",
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/codemirror": "^0.0.71",
|
"@types/codemirror": "^0.0.71",
|
||||||
|
"@types/hls.js": "^0.12.2",
|
||||||
"@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.5",
|
"@types/mocha": "^5.2.5",
|
||||||
"babel-eslint": "^10",
|
"babel-eslint": "^10",
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^8.0.4",
|
||||||
"babel-minify-webpack-plugin": "^0.3.1",
|
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"compression-webpack-plugin": "^2.0.0",
|
"compression-webpack-plugin": "^2.0.0",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
@ -146,6 +147,7 @@
|
|||||||
"reify": "^0.18.1",
|
"reify": "^0.18.1",
|
||||||
"require-dir": "^1.0.0",
|
"require-dir": "^1.0.0",
|
||||||
"sinon": "^7.1.1",
|
"sinon": "^7.1.1",
|
||||||
|
"terser-webpack-plugin": "^1.2.3",
|
||||||
"ts-mocha": "^2.0.0",
|
"ts-mocha": "^2.0.0",
|
||||||
"tslint": "^5.11.0",
|
"tslint": "^5.11.0",
|
||||||
"tslint-config-prettier": "^1.15.0",
|
"tslint-config-prettier": "^1.15.0",
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20190305.1",
|
version="20190312.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",
|
||||||
|
@ -78,6 +78,7 @@ export const DOMAINS_TOGGLE = new Set([
|
|||||||
"light",
|
"light",
|
||||||
"switch",
|
"switch",
|
||||||
"group",
|
"group",
|
||||||
|
"automation",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** Temperature units. */
|
/** Temperature units. */
|
||||||
|
@ -53,7 +53,7 @@ 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) {
|
if (stateObj.attributes.entity_picture && !this.overrideIcon) {
|
||||||
hostStyle.backgroundImage =
|
hostStyle.backgroundImage =
|
||||||
"url(" + stateObj.attributes.entity_picture + ")";
|
"url(" + stateObj.attributes.entity_picture + ")";
|
||||||
iconStyle.display = "none";
|
iconStyle.display = "none";
|
||||||
|
@ -22,6 +22,7 @@ import { DEFAULT_PANEL } from "../common/const";
|
|||||||
const computeUrl = (urlPath) => `/${urlPath}`;
|
const computeUrl = (urlPath) => `/${urlPath}`;
|
||||||
|
|
||||||
const computePanels = (hass: HomeAssistant) => {
|
const computePanels = (hass: HomeAssistant) => {
|
||||||
|
const isAdmin = hass.user.is_admin;
|
||||||
const panels = hass.panels;
|
const panels = hass.panels;
|
||||||
const sortValue = {
|
const sortValue = {
|
||||||
map: 1,
|
map: 1,
|
||||||
@ -30,9 +31,9 @@ const computePanels = (hass: HomeAssistant) => {
|
|||||||
};
|
};
|
||||||
const result: Panel[] = [];
|
const result: Panel[] = [];
|
||||||
|
|
||||||
Object.keys(panels).forEach((key) => {
|
Object.values(panels).forEach((panel) => {
|
||||||
if (panels[key].title) {
|
if (panel.title && (panel.component_name !== "config" || isAdmin)) {
|
||||||
result.push(panels[key]);
|
result.push(panel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,62 +130,66 @@ class HaSidebar extends LitElement {
|
|||||||
: html``}
|
: html``}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
|
|
||||||
<div>
|
${!hass.user.is_admin
|
||||||
<div class="divider"></div>
|
? ""
|
||||||
|
: html`
|
||||||
|
<div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
<div class="subheader">
|
<div class="subheader">
|
||||||
${hass.localize("ui.sidebar.developer_tools")}
|
${hass.localize("ui.sidebar.developer_tools")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dev-tools">
|
<div class="dev-tools">
|
||||||
<a href="/dev-service" tabindex="-1">
|
<a href="/dev-service" tabindex="-1">
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:remote"
|
|
||||||
alt="${hass.localize("panel.dev-services")}"
|
|
||||||
title="${hass.localize("panel.dev-services")}"
|
|
||||||
></paper-icon-button>
|
|
||||||
</a>
|
|
||||||
<a href="/dev-state" tabindex="-1">
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:code-tags"
|
|
||||||
alt="${hass.localize("panel.dev-states")}"
|
|
||||||
title="${hass.localize("panel.dev-states")}"
|
|
||||||
></paper-icon-button>
|
|
||||||
</a>
|
|
||||||
<a href="/dev-event" tabindex="-1">
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:radio-tower"
|
|
||||||
alt="${hass.localize("panel.dev-events")}"
|
|
||||||
title="${hass.localize("panel.dev-events")}"
|
|
||||||
></paper-icon-button>
|
|
||||||
</a>
|
|
||||||
<a href="/dev-template" tabindex="-1">
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:file-xml"
|
|
||||||
alt="${hass.localize("panel.dev-templates")}"
|
|
||||||
title="${hass.localize("panel.dev-templates")}"
|
|
||||||
></paper-icon-button>
|
|
||||||
</a>
|
|
||||||
${isComponentLoaded(hass, "mqtt")
|
|
||||||
? html`
|
|
||||||
<a href="/dev-mqtt" tabindex="-1">
|
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:altimeter"
|
icon="hass:remote"
|
||||||
alt="${hass.localize("panel.dev-mqtt")}"
|
alt="${hass.localize("panel.dev-services")}"
|
||||||
title="${hass.localize("panel.dev-mqtt")}"
|
title="${hass.localize("panel.dev-services")}"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
</a>
|
</a>
|
||||||
`
|
<a href="/dev-state" tabindex="-1">
|
||||||
: html``}
|
<paper-icon-button
|
||||||
<a href="/dev-info" tabindex="-1">
|
icon="hass:code-tags"
|
||||||
<paper-icon-button
|
alt="${hass.localize("panel.dev-states")}"
|
||||||
icon="hass:information-outline"
|
title="${hass.localize("panel.dev-states")}"
|
||||||
alt="${hass.localize("panel.dev-info")}"
|
></paper-icon-button>
|
||||||
title="${hass.localize("panel.dev-info")}"
|
</a>
|
||||||
></paper-icon-button>
|
<a href="/dev-event" tabindex="-1">
|
||||||
</a>
|
<paper-icon-button
|
||||||
</div>
|
icon="hass:radio-tower"
|
||||||
</div>
|
alt="${hass.localize("panel.dev-events")}"
|
||||||
|
title="${hass.localize("panel.dev-events")}"
|
||||||
|
></paper-icon-button>
|
||||||
|
</a>
|
||||||
|
<a href="/dev-template" tabindex="-1">
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:file-xml"
|
||||||
|
alt="${hass.localize("panel.dev-templates")}"
|
||||||
|
title="${hass.localize("panel.dev-templates")}"
|
||||||
|
></paper-icon-button>
|
||||||
|
</a>
|
||||||
|
${isComponentLoaded(hass, "mqtt")
|
||||||
|
? html`
|
||||||
|
<a href="/dev-mqtt" tabindex="-1">
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:altimeter"
|
||||||
|
alt="${hass.localize("panel.dev-mqtt")}"
|
||||||
|
title="${hass.localize("panel.dev-mqtt")}"
|
||||||
|
></paper-icon-button>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
<a href="/dev-info" tabindex="-1">
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:information-outline"
|
||||||
|
alt="${hass.localize("panel.dev-info")}"
|
||||||
|
title="${hass.localize("panel.dev-info")}"
|
||||||
|
></paper-icon-button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import { User } from "../../data/auth";
|
import { User } from "../../data/user";
|
||||||
import { CurrentUser } from "../../types";
|
import { CurrentUser } from "../../types";
|
||||||
|
|
||||||
const computeInitials = (name: string) => {
|
const computeInitials = (name: string) => {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { User, fetchUsers } from "../../data/auth";
|
import { User, fetchUsers } from "../../data/user";
|
||||||
import compare from "../../common/string/compare";
|
import compare from "../../common/string/compare";
|
||||||
|
|
||||||
class HaEntityPicker extends LitElement {
|
class HaEntityPicker extends LitElement {
|
||||||
|
@ -1,26 +1,9 @@
|
|||||||
import { HomeAssistant } from "../types";
|
|
||||||
|
|
||||||
export interface AuthProvider {
|
export interface AuthProvider {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Credential {
|
export interface Credential {
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
is_owner: boolean;
|
|
||||||
is_active: boolean;
|
|
||||||
system_generated: boolean;
|
|
||||||
group_ids: string[];
|
|
||||||
credentials: Credential[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchUsers = async (hass: HomeAssistant) =>
|
|
||||||
hass.callWS<User[]>({
|
|
||||||
type: "config/auth/list",
|
|
||||||
});
|
|
||||||
|
@ -1,12 +1,37 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant, CameraEntity } from "../types";
|
||||||
|
|
||||||
export interface CameraThumbnail {
|
export interface CameraThumbnail {
|
||||||
content_type: string;
|
content_type: string;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Stream {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
|
||||||
|
`/api/camera_proxy_stream/${entity.entity_id}?token=${
|
||||||
|
entity.attributes.access_token
|
||||||
|
}`;
|
||||||
|
|
||||||
export const fetchThumbnail = (hass: HomeAssistant, entityId: string) =>
|
export const fetchThumbnail = (hass: HomeAssistant, entityId: string) =>
|
||||||
hass.callWS<CameraThumbnail>({
|
hass.callWS<CameraThumbnail>({
|
||||||
type: "camera_thumbnail",
|
type: "camera_thumbnail",
|
||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchStreamUrl = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityId: string,
|
||||||
|
format?: "hls"
|
||||||
|
) => {
|
||||||
|
const data = {
|
||||||
|
type: "camera/stream",
|
||||||
|
entity_id: entityId,
|
||||||
|
};
|
||||||
|
if (format) {
|
||||||
|
// @ts-ignore
|
||||||
|
data.format = format;
|
||||||
|
}
|
||||||
|
return hass.callWS<Stream>(data);
|
||||||
|
};
|
||||||
|
@ -1,5 +1,45 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface EntityFilter {
|
||||||
|
include_domains: string[];
|
||||||
|
include_entities: string[];
|
||||||
|
exclude_domains: string[];
|
||||||
|
exclude_entities: string[];
|
||||||
|
}
|
||||||
|
interface CloudStatusBase {
|
||||||
|
logged_in: boolean;
|
||||||
|
cloud: "disconnected" | "connecting" | "connected";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CertificateInformation {
|
||||||
|
common_name: string;
|
||||||
|
expire_date: string;
|
||||||
|
fingerprint: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CloudStatusLoggedIn = CloudStatusBase & {
|
||||||
|
email: string;
|
||||||
|
google_entities: EntityFilter;
|
||||||
|
google_domains: string[];
|
||||||
|
alexa_entities: EntityFilter;
|
||||||
|
alexa_domains: string[];
|
||||||
|
prefs: {
|
||||||
|
google_enabled: boolean;
|
||||||
|
alexa_enabled: boolean;
|
||||||
|
google_allow_unlock: boolean;
|
||||||
|
cloudhooks: { [webhookId: string]: CloudWebhook };
|
||||||
|
};
|
||||||
|
remote_domain: string | undefined;
|
||||||
|
remote_connected: boolean;
|
||||||
|
remote_certificate: undefined | CertificateInformation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CloudStatus = CloudStatusBase | CloudStatusLoggedIn;
|
||||||
|
|
||||||
|
export interface SubscriptionInfo {
|
||||||
|
human_description: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CloudWebhook {
|
export interface CloudWebhook {
|
||||||
webhook_id: string;
|
webhook_id: string;
|
||||||
cloudhook_id: string;
|
cloudhook_id: string;
|
||||||
@ -18,3 +58,29 @@ export const deleteCloudhook = (hass: HomeAssistant, webhookId: string) =>
|
|||||||
type: "cloud/cloudhook/delete",
|
type: "cloud/cloudhook/delete",
|
||||||
webhook_id: webhookId,
|
webhook_id: webhookId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const connectCloudRemote = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "cloud/remote/connect",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const disconnectCloudRemote = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "cloud/remote/disconnect",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchCloudSubscriptionInfo = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<SubscriptionInfo>({ type: "cloud/subscription" });
|
||||||
|
|
||||||
|
export const updateCloudPref = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
prefs: {
|
||||||
|
google_enabled?: boolean;
|
||||||
|
alexa_enabled?: boolean;
|
||||||
|
google_allow_unlock?: boolean;
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "cloud/update_prefs",
|
||||||
|
...prefs,
|
||||||
|
});
|
||||||
|
@ -10,10 +10,12 @@ export interface DeviceRegistryEntry {
|
|||||||
sw_version?: string;
|
sw_version?: string;
|
||||||
hub_device_id?: string;
|
hub_device_id?: string;
|
||||||
area_id?: string;
|
area_id?: string;
|
||||||
|
name_by_user?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceRegistryEntryMutableParams {
|
export interface DeviceRegistryEntryMutableParams {
|
||||||
area_id: string;
|
area_id?: string;
|
||||||
|
name_by_user?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchDeviceRegistry = (hass: HomeAssistant) =>
|
export const fetchDeviceRegistry = (hass: HomeAssistant) =>
|
||||||
|
36
src/data/frontend.ts
Normal file
36
src/data/frontend.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
interface FrontendUserData {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValidUserDataKey = keyof FrontendUserData;
|
||||||
|
|
||||||
|
export const fetchFrontendUserData = async <
|
||||||
|
UserDataKey extends ValidUserDataKey
|
||||||
|
>(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
key: UserDataKey
|
||||||
|
): Promise<FrontendUserData[UserDataKey] | null> => {
|
||||||
|
const result = await hass.callWS<{
|
||||||
|
value: FrontendUserData[UserDataKey] | null;
|
||||||
|
}>({
|
||||||
|
type: "frontend/get_user_data",
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
return result.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveFrontendUserData = async <
|
||||||
|
UserDataKey extends ValidUserDataKey
|
||||||
|
>(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
key: UserDataKey,
|
||||||
|
value: FrontendUserData[UserDataKey]
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS<void>({
|
||||||
|
type: "frontend/set_user_data",
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
});
|
24
src/data/onboarding.ts
Normal file
24
src/data/onboarding.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { handleFetchPromise } from "../util/hass-call-api";
|
||||||
|
|
||||||
|
export interface OnboardingStep {
|
||||||
|
step: string;
|
||||||
|
done: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserStepResponse {
|
||||||
|
auth_code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onboardUserStep = async (params: {
|
||||||
|
client_id: string;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}) =>
|
||||||
|
handleFetchPromise<UserStepResponse>(
|
||||||
|
fetch("/api/onboarding/users", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "same-origin",
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
})
|
||||||
|
);
|
31
src/data/translation.ts
Normal file
31
src/data/translation.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import { fetchFrontendUserData, saveFrontendUserData } from "./frontend";
|
||||||
|
|
||||||
|
export interface FrontendTranslationData {
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface FrontendUserData {
|
||||||
|
language: FrontendTranslationData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||||
|
fetchFrontendUserData(hass, "language");
|
||||||
|
|
||||||
|
export const saveTranslationPreferences = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
data: FrontendTranslationData
|
||||||
|
) => saveFrontendUserData(hass, "language", data);
|
||||||
|
|
||||||
|
export const getHassTranslations = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
language: string
|
||||||
|
): Promise<{}> => {
|
||||||
|
const result = await hass.callWS<{ resources: {} }>({
|
||||||
|
type: "frontend/get_translations",
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
return result.resources;
|
||||||
|
};
|
49
src/data/user.ts
Normal file
49
src/data/user.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import { Credential } from "./auth";
|
||||||
|
|
||||||
|
export const SYSTEM_GROUP_ID_ADMIN = "system-admin";
|
||||||
|
export const SYSTEM_GROUP_ID_USER = "system-users";
|
||||||
|
export const SYSTEM_GROUP_ID_READ_ONLY = "system-read-only";
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
is_owner: boolean;
|
||||||
|
is_active: boolean;
|
||||||
|
system_generated: boolean;
|
||||||
|
group_ids: string[];
|
||||||
|
credentials: Credential[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateUserParams {
|
||||||
|
name?: User["name"];
|
||||||
|
group_ids?: User["group_ids"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchUsers = async (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<User[]>({
|
||||||
|
type: "config/auth/list",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createUser = async (hass: HomeAssistant, name: string) =>
|
||||||
|
hass.callWS<{ user: User }>({
|
||||||
|
type: "config/auth/create",
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateUser = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
userId: string,
|
||||||
|
params: UpdateUserParams
|
||||||
|
) =>
|
||||||
|
hass.callWS<{ user: User }>({
|
||||||
|
...params,
|
||||||
|
type: "config/auth/update",
|
||||||
|
user_id: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteUser = async (hass: HomeAssistant, userId: string) =>
|
||||||
|
hass.callWS<void>({
|
||||||
|
type: "config/auth/delete",
|
||||||
|
user_id: userId,
|
||||||
|
});
|
@ -1,12 +1,6 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export interface ZHADeviceEntity extends HassEntity {
|
|
||||||
device_info?: {
|
|
||||||
identifiers: any[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ZHAEntityReference extends HassEntity {
|
export interface ZHAEntityReference extends HassEntity {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
@ -20,6 +14,8 @@ export interface ZHADevice {
|
|||||||
quirk_class: string;
|
quirk_class: string;
|
||||||
entities: ZHAEntityReference[];
|
entities: ZHAEntityReference[];
|
||||||
manufacturer_code: number;
|
manufacturer_code: number;
|
||||||
|
device_reg_id: string;
|
||||||
|
user_given_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Attribute {
|
export interface Attribute {
|
||||||
@ -78,6 +74,37 @@ export const fetchDevices = (hass: HomeAssistant): Promise<ZHADevice[]> =>
|
|||||||
type: "zha/devices",
|
type: "zha/devices",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchBindableDevices = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
ieeeAddress: string
|
||||||
|
): Promise<ZHADevice[]> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/devices/bindable",
|
||||||
|
ieee: ieeeAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const bindDevices = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
sourceIEEE: string,
|
||||||
|
targetIEEE: string
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/devices/bind",
|
||||||
|
source_ieee: sourceIEEE,
|
||||||
|
target_ieee: targetIEEE,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const unbindDevices = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
sourceIEEE: string,
|
||||||
|
targetIEEE: string
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/devices/unbind",
|
||||||
|
source_ieee: sourceIEEE,
|
||||||
|
target_ieee: targetIEEE,
|
||||||
|
});
|
||||||
|
|
||||||
export const readAttributeValue = (
|
export const readAttributeValue = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
data: ReadAttributeServiceData
|
data: ReadAttributeServiceData
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import computeStateName from "../../../common/entity/compute_state_name";
|
|
||||||
import emptyImageBase64 from "../../../common/empty_image_base64";
|
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class MoreInfoCamera extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
max-width: 640px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="camera-image"
|
|
||||||
src="[[computeCameraImageUrl(hass, stateObj, isVisible)]]"
|
|
||||||
on-load="imageLoaded"
|
|
||||||
alt="[[_computeStateName(stateObj)]]"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
isVisible: {
|
|
||||||
type: Boolean,
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.isVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
this.isVisible = false;
|
|
||||||
super.disconnectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
imageLoaded() {
|
|
||||||
this.fire("iron-resize");
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStateName(stateObj) {
|
|
||||||
return computeStateName(stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeCameraImageUrl(hass, stateObj, isVisible) {
|
|
||||||
if (hass.demo) {
|
|
||||||
return "/demo/webcam.jpg";
|
|
||||||
}
|
|
||||||
if (stateObj && isVisible) {
|
|
||||||
return (
|
|
||||||
"/api/camera_proxy_stream/" +
|
|
||||||
stateObj.entity_id +
|
|
||||||
"?token=" +
|
|
||||||
stateObj.attributes.access_token
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Return an empty image if no stateObj (= dialog not open) or in cleanup mode.
|
|
||||||
return emptyImageBase64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-camera", MoreInfoCamera);
|
|
132
src/dialogs/more-info/controls/more-info-camera.ts
Normal file
132
src/dialogs/more-info/controls/more-info-camera.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { property, UpdatingElement, PropertyValues } from "lit-element";
|
||||||
|
|
||||||
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
|
import { HomeAssistant, CameraEntity } from "../../../types";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { fetchStreamUrl, computeMJPEGStreamUrl } from "../../../data/camera";
|
||||||
|
|
||||||
|
type HLSModule = typeof import("hls.js");
|
||||||
|
|
||||||
|
class MoreInfoCamera extends UpdatingElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
@property() public stateObj?: CameraEntity;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._teardownPlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
if (!changedProps.has("stateObj")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldState = changedProps.get("stateObj") as this["stateObj"];
|
||||||
|
const oldEntityId = oldState ? oldState.entity_id : undefined;
|
||||||
|
const curEntityId = this.stateObj ? this.stateObj.entity_id : undefined;
|
||||||
|
|
||||||
|
// Same entity, ignore.
|
||||||
|
if (curEntityId === oldEntityId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tear down if we have something and we need to build it up
|
||||||
|
if (oldEntityId) {
|
||||||
|
this._teardownPlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntityId) {
|
||||||
|
this._startPlayback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _startPlayback(): Promise<void> {
|
||||||
|
if (!this.stateObj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoEl = document.createElement("video");
|
||||||
|
videoEl.style.width = "100%";
|
||||||
|
videoEl.autoplay = true;
|
||||||
|
videoEl.controls = true;
|
||||||
|
videoEl.muted = true;
|
||||||
|
|
||||||
|
// tslint:disable-next-line
|
||||||
|
let Hls: HLSModule | undefined;
|
||||||
|
|
||||||
|
let hlsSupported =
|
||||||
|
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
|
||||||
|
|
||||||
|
if (!hlsSupported) {
|
||||||
|
Hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
|
||||||
|
.default as HLSModule;
|
||||||
|
hlsSupported = Hls.isSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hlsSupported) {
|
||||||
|
try {
|
||||||
|
const { url } = await fetchStreamUrl(
|
||||||
|
this.hass!,
|
||||||
|
this.stateObj.entity_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Hls) {
|
||||||
|
this._renderHLSPolyfill(videoEl, Hls, url);
|
||||||
|
} else {
|
||||||
|
this._renderHLSNative(videoEl, url);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
// Fails if entity doesn't support it. In that case we go
|
||||||
|
// for mjpeg.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._renderMJPEG();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
|
||||||
|
videoEl.src = url;
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
videoEl.addEventListener("loadedmetadata", resolve)
|
||||||
|
);
|
||||||
|
videoEl.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _renderHLSPolyfill(
|
||||||
|
videoEl: HTMLVideoElement,
|
||||||
|
// tslint:disable-next-line
|
||||||
|
Hls: HLSModule,
|
||||||
|
url: string
|
||||||
|
) {
|
||||||
|
const hls = new Hls();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
hls.on(Hls.Events.MEDIA_ATTACHED, resolve);
|
||||||
|
hls.attachMedia(videoEl);
|
||||||
|
});
|
||||||
|
hls.loadSource(url);
|
||||||
|
this.appendChild(videoEl);
|
||||||
|
videoEl.addEventListener("loadeddata", () =>
|
||||||
|
fireEvent(this, "iron-resize")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderMJPEG() {
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.style.width = "100%";
|
||||||
|
img.addEventListener("load", () => fireEvent(this, "iron-resize"));
|
||||||
|
img.src = __DEMO__
|
||||||
|
? "/demo/webcamp.jpg"
|
||||||
|
: computeMJPEGStreamUrl(this.stateObj!);
|
||||||
|
img.alt = computeStateName(this.stateObj!);
|
||||||
|
this.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _teardownPlayback(): any {
|
||||||
|
while (this.lastChild) {
|
||||||
|
this.removeChild(this.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("more-info-camera", MoreInfoCamera);
|
@ -1,3 +0,0 @@
|
|||||||
import "../components/ha-iconset-svg";
|
|
||||||
import "../resources/roboto";
|
|
||||||
import "../onboarding/ha-onboarding";
|
|
10
src/entrypoints/onboarding.ts
Normal file
10
src/entrypoints/onboarding.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import "../components/ha-iconset-svg";
|
||||||
|
import "../resources/ha-style";
|
||||||
|
import "../resources/roboto";
|
||||||
|
import "../onboarding/ha-onboarding";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
stepsPromise: Promise<Response>;
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { demoPanels } from "./demo_panels";
|
|||||||
import { getEntity, Entity } from "./entity";
|
import { getEntity, Entity } from "./entity";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { HassEntities } from "home-assistant-js-websocket";
|
import { HassEntities } from "home-assistant-js-websocket";
|
||||||
import { getActiveTranslation } from "../util/hass-translation";
|
import { getLocalLanguage } from "../util/hass-translation";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
|
|
||||||
const ensureArray = <T>(val: T | T[]): T[] =>
|
const ensureArray = <T>(val: T | T[]): T[] =>
|
||||||
@ -87,6 +87,8 @@ export const provideHass = (
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const localLanguage = getLocalLanguage();
|
||||||
|
|
||||||
const hassObj: MockHomeAssistant = {
|
const hassObj: MockHomeAssistant = {
|
||||||
// Home Assistant properties
|
// Home Assistant properties
|
||||||
auth: {} as any,
|
auth: {} as any,
|
||||||
@ -128,13 +130,15 @@ export const provideHass = (
|
|||||||
user: {
|
user: {
|
||||||
credentials: [],
|
credentials: [],
|
||||||
id: "abcd",
|
id: "abcd",
|
||||||
|
is_admin: true,
|
||||||
is_owner: true,
|
is_owner: true,
|
||||||
mfa_modules: [],
|
mfa_modules: [],
|
||||||
name: "Demo User",
|
name: "Demo User",
|
||||||
},
|
},
|
||||||
panelUrl: "lovelace",
|
panelUrl: "lovelace",
|
||||||
|
|
||||||
language: getActiveTranslation(),
|
language: localLanguage,
|
||||||
|
selectedLanguage: localLanguage,
|
||||||
resources: null as any,
|
resources: null as any,
|
||||||
localize: () => "",
|
localize: () => "",
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import LocalizeMixin from "../../mixins/localize-mixin";
|
|||||||
import EventsMixin from "../../mixins/events-mixin";
|
import EventsMixin from "../../mixins/events-mixin";
|
||||||
|
|
||||||
import { getState } from "../../util/ha-pref-storage";
|
import { getState } from "../../util/ha-pref-storage";
|
||||||
import { getActiveTranslation } from "../../util/hass-translation";
|
import { getLocalLanguage } from "../../util/hass-translation";
|
||||||
import { fetchWithAuth } from "../../util/fetch-with-auth";
|
import { fetchWithAuth } from "../../util/fetch-with-auth";
|
||||||
import hassCallApi from "../../util/hass-call-api";
|
import hassCallApi from "../../util/hass-call-api";
|
||||||
import { subscribePanels } from "../../data/ws-panels";
|
import { subscribePanels } from "../../data/ws-panels";
|
||||||
@ -49,7 +49,7 @@ export default (superClass) =>
|
|||||||
user: null,
|
user: null,
|
||||||
panelUrl: this._panelUrl,
|
panelUrl: this._panelUrl,
|
||||||
|
|
||||||
language: getActiveTranslation(),
|
language: getLocalLanguage(),
|
||||||
// If resources are already loaded, don't discard them
|
// If resources are already loaded, don't discard them
|
||||||
resources: (this.hass && this.hass.resources) || null,
|
resources: (this.hass && this.hass.resources) || null,
|
||||||
localize: () => "",
|
localize: () => "",
|
||||||
|
@ -24,7 +24,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this.addEventListener("hass-dock-sidebar", (ev) => {
|
this.addEventListener("hass-dock-sidebar", (ev) => {
|
||||||
this._updateHass({ dockedSidebar: ev.detail.dock });
|
this._updateHass({ dockedSidebar: ev.detail.dock });
|
||||||
storeState(this.hass);
|
storeState(this.hass!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,32 +1,42 @@
|
|||||||
import applyThemesOnElement from "../../common/dom/apply_themes_on_element";
|
import applyThemesOnElement from "../../common/dom/apply_themes_on_element";
|
||||||
import { storeState } from "../../util/ha-pref-storage";
|
import { storeState } from "../../util/ha-pref-storage";
|
||||||
import { subscribeThemes } from "../../data/ws-themes";
|
import { subscribeThemes } from "../../data/ws-themes";
|
||||||
|
import { Constructor, LitElement } from "lit-element";
|
||||||
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
import { HASSDomEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
export default (superClass) =>
|
declare global {
|
||||||
|
// for add event listener
|
||||||
|
interface HTMLElementEventMap {
|
||||||
|
settheme: HASSDomEvent<string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
||||||
class extends superClass {
|
class extends superClass {
|
||||||
firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this.addEventListener("settheme", (ev) => {
|
this.addEventListener("settheme", (ev) => {
|
||||||
this._updateHass({ selectedTheme: ev.detail });
|
this._updateHass({ selectedTheme: ev.detail });
|
||||||
this._applyTheme();
|
this._applyTheme();
|
||||||
storeState(this.hass);
|
storeState(this.hass!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hassConnected() {
|
protected hassConnected() {
|
||||||
super.hassConnected();
|
super.hassConnected();
|
||||||
|
|
||||||
subscribeThemes(this.hass.connection, (themes) => {
|
subscribeThemes(this.hass!.connection, (themes) => {
|
||||||
this._updateHass({ themes });
|
this._updateHass({ themes });
|
||||||
this._applyTheme();
|
this._applyTheme();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyTheme() {
|
private _applyTheme() {
|
||||||
applyThemesOnElement(
|
applyThemesOnElement(
|
||||||
document.documentElement,
|
document.documentElement,
|
||||||
this.hass.themes,
|
this.hass!.themes,
|
||||||
this.hass.selectedTheme,
|
this.hass!.selectedTheme,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,11 +1,17 @@
|
|||||||
import { translationMetadata } from "../../resources/translations-metadata";
|
import { translationMetadata } from "../../resources/translations-metadata";
|
||||||
import { getTranslation } from "../../util/hass-translation";
|
import {
|
||||||
import { storeState } from "../../util/ha-pref-storage";
|
getTranslation,
|
||||||
|
getLocalLanguage,
|
||||||
|
getUserLanguage,
|
||||||
|
} from "../../util/hass-translation";
|
||||||
import { Constructor, LitElement } from "lit-element";
|
import { Constructor, LitElement } from "lit-element";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
import { computeLocalize } from "../../common/translations/localize";
|
import { computeLocalize } from "../../common/translations/localize";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { saveFrontendUserData } from "../../data/frontend";
|
||||||
|
import { storeState } from "../../util/ha-pref-storage";
|
||||||
|
import { getHassTranslations } from "../../data/translation";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* superClass needs to contain `this.hass` and `this._updateHass`.
|
* superClass needs to contain `this.hass` and `this._updateHass`.
|
||||||
@ -16,60 +22,86 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this.addEventListener("hass-language-select", (e) =>
|
this.addEventListener("hass-language-select", (e) =>
|
||||||
this._selectLanguage(e)
|
this._selectLanguage((e as CustomEvent).detail.language, true)
|
||||||
);
|
);
|
||||||
this._loadResources();
|
this._loadCoreTranslations(getLocalLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassConnected() {
|
protected hassConnected() {
|
||||||
super.hassConnected();
|
super.hassConnected();
|
||||||
this._loadBackendTranslations();
|
getUserLanguage(this.hass!).then((language) => {
|
||||||
this.style.direction = computeRTL(this.hass!) ? "rtl" : "ltr";
|
if (language && this.hass!.language !== language) {
|
||||||
|
// We just get language from backend, no need to save back
|
||||||
|
this._selectLanguage(language, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._applyTranslations(this.hass!);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassReconnected() {
|
protected hassReconnected() {
|
||||||
super.hassReconnected();
|
super.hassReconnected();
|
||||||
this._loadBackendTranslations();
|
this._applyTranslations(this.hass!);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected panelUrlChanged(newPanelUrl) {
|
protected panelUrlChanged(newPanelUrl) {
|
||||||
super.panelUrlChanged(newPanelUrl);
|
super.panelUrlChanged(newPanelUrl);
|
||||||
this._loadTranslationFragment(newPanelUrl);
|
// this may be triggered before hassConnected
|
||||||
|
this._loadFragmentTranslations(
|
||||||
|
this.hass ? this.hass.language : getLocalLanguage(),
|
||||||
|
newPanelUrl
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadBackendTranslations() {
|
private _selectLanguage(language: string, saveToBackend: boolean) {
|
||||||
const hass = this.hass;
|
if (!this.hass) {
|
||||||
if (!hass || !hass.language) {
|
// should not happen, do it to avoid use this.hass!
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const language = hass.selectedLanguage || hass.language;
|
// update selectedLanguage so that it can be saved to local storage
|
||||||
|
this._updateHass({ language, selectedLanguage: language });
|
||||||
|
storeState(this.hass);
|
||||||
|
if (saveToBackend) {
|
||||||
|
saveFrontendUserData(this.hass, "language", { language });
|
||||||
|
}
|
||||||
|
|
||||||
const { resources } = await hass.callWS({
|
this._applyTranslations(this.hass);
|
||||||
type: "frontend/get_translations",
|
}
|
||||||
language,
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we've switched selected languages just ignore this response
|
private _applyTranslations(hass: HomeAssistant) {
|
||||||
if ((hass.selectedLanguage || hass.language) !== language) {
|
this.style.direction = computeRTL(hass) ? "rtl" : "ltr";
|
||||||
|
this._loadCoreTranslations(hass.language);
|
||||||
|
this._loadHassTranslations(hass.language);
|
||||||
|
this._loadFragmentTranslations(hass.language, hass.panelUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadHassTranslations(language: string) {
|
||||||
|
const resources = await getHassTranslations(this.hass!, language);
|
||||||
|
|
||||||
|
// Ignore the repsonse if user switched languages before we got response
|
||||||
|
if (this.hass!.language !== language) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateResources(language, resources);
|
this._updateResources(language, resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _loadTranslationFragment(panelUrl) {
|
private async _loadFragmentTranslations(
|
||||||
|
language: string,
|
||||||
|
panelUrl: string
|
||||||
|
) {
|
||||||
if (translationMetadata.fragments.includes(panelUrl)) {
|
if (translationMetadata.fragments.includes(panelUrl)) {
|
||||||
this._loadResources(panelUrl);
|
const result = await getTranslation(panelUrl, language);
|
||||||
|
this._updateResources(result.language, result.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadResources(fragment?) {
|
private async _loadCoreTranslations(language: string) {
|
||||||
const result = await getTranslation(fragment);
|
const result = await getTranslation(null, language);
|
||||||
this._updateResources(result.language, result.data);
|
this._updateResources(result.language, result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateResources(language, data) {
|
private _updateResources(language: string, data: any) {
|
||||||
// Update the language in hass, and update the resources with the newly
|
// Update the language in hass, and update the resources with the newly
|
||||||
// loaded resources. This merges the new data on top of the old data for
|
// loaded resources. This merges the new data on top of the old data for
|
||||||
// this language, so that the full translation set can be loaded across
|
// this language, so that the full translation set can be loaded across
|
||||||
@ -88,14 +120,4 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
}
|
}
|
||||||
this._updateHass(changes);
|
this._updateHass(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _selectLanguage(event) {
|
|
||||||
const language: string = event.detail.language;
|
|
||||||
this._updateHass({ language, selectedLanguage: language });
|
|
||||||
this.style.direction = computeRTL(this.hass!) ? "rtl" : "ltr";
|
|
||||||
storeState(this.hass);
|
|
||||||
this._loadResources();
|
|
||||||
this._loadBackendTranslations();
|
|
||||||
this._loadTranslationFragment(this.hass!.panelUrl);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
PropertyDeclarations,
|
PropertyDeclarations,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { getActiveTranslation } from "../util/hass-translation";
|
import { getLocalLanguage } from "../util/hass-translation";
|
||||||
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
|
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
|
||||||
import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
|
import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
|
||||||
|
|
||||||
@ -35,7 +35,8 @@ export const litLocalizeLiteMixin = <T extends LitElement>(
|
|||||||
super();
|
super();
|
||||||
// This will prevent undefined errors if called before connected to DOM.
|
// This will prevent undefined errors if called before connected to DOM.
|
||||||
this.localize = empty;
|
this.localize = empty;
|
||||||
this.language = getActiveTranslation();
|
// Use browser language setup before login.
|
||||||
|
this.language = getLocalLanguage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
|
@ -35,7 +35,10 @@ export const localizeLiteBaseMixin = (superClass) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _updateResources() {
|
private async _updateResources() {
|
||||||
const { language, data } = await getTranslation(this.translationFragment);
|
const { language, data } = await getTranslation(
|
||||||
|
this.translationFragment,
|
||||||
|
this.language
|
||||||
|
);
|
||||||
this.resources = {
|
this.resources = {
|
||||||
[language]: data,
|
[language]: data,
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* Lite mixin to add localization without depending on the Hass object.
|
* Lite mixin to add localization without depending on the Hass object.
|
||||||
*/
|
*/
|
||||||
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
|
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
|
||||||
import { getActiveTranslation } from "../util/hass-translation";
|
import { getLocalLanguage } from "../util/hass-translation";
|
||||||
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
|
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
|
||||||
import { computeLocalize } from "../common/translations/localize";
|
import { computeLocalize } from "../common/translations/localize";
|
||||||
|
|
||||||
@ -16,7 +16,8 @@ export const localizeLiteMixin = dedupingMixin(
|
|||||||
return {
|
return {
|
||||||
language: {
|
language: {
|
||||||
type: String,
|
type: String,
|
||||||
value: getActiveTranslation(),
|
// Use browser language setup before login.
|
||||||
|
value: getLocalLanguage(),
|
||||||
},
|
},
|
||||||
resources: Object,
|
resources: Object,
|
||||||
// The fragment to load.
|
// The fragment to load.
|
||||||
|
@ -1,175 +0,0 @@
|
|||||||
import "@polymer/polymer/lib/elements/dom-if";
|
|
||||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
|
|
||||||
|
|
||||||
class HaOnboarding extends localizeLiteMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
margin: 32px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.page-onboarding.intro')]]
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.page-onboarding.user.intro')]]
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<template is='dom-if' if='[[_errorMsg]]'>
|
|
||||||
<p class='error'>[[_computeErrorMsg(localize, _errorMsg)]]</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
<paper-input
|
|
||||||
autofocus
|
|
||||||
label="[[localize('ui.panel.page-onboarding.user.data.name')]]"
|
|
||||||
value='{{_name}}'
|
|
||||||
required
|
|
||||||
auto-validate
|
|
||||||
autocapitalize='on'
|
|
||||||
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
|
|
||||||
on-blur='_maybePopulateUsername'
|
|
||||||
></paper-input>
|
|
||||||
|
|
||||||
<paper-input
|
|
||||||
label="[[localize('ui.panel.page-onboarding.user.data.username')]]"
|
|
||||||
value='{{_username}}'
|
|
||||||
required
|
|
||||||
auto-validate
|
|
||||||
autocapitalize='none'
|
|
||||||
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
|
|
||||||
></paper-input>
|
|
||||||
|
|
||||||
<paper-input
|
|
||||||
label="[[localize('ui.panel.page-onboarding.user.data.password')]]"
|
|
||||||
value='{{_password}}'
|
|
||||||
required
|
|
||||||
type='password'
|
|
||||||
auto-validate
|
|
||||||
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
|
|
||||||
></paper-input>
|
|
||||||
|
|
||||||
<template is='dom-if' if='[[!_loading]]'>
|
|
||||||
<p class='action'>
|
|
||||||
<mwc-button raised on-click='_submitForm'>
|
|
||||||
[[localize('ui.panel.page-onboarding.user.create_account')]]
|
|
||||||
</mwc-button>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
_name: String,
|
|
||||||
_username: String,
|
|
||||||
_password: String,
|
|
||||||
_loading: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
translationFragment: {
|
|
||||||
type: String,
|
|
||||||
value: "page-onboarding",
|
|
||||||
},
|
|
||||||
_errorMsg: String,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async ready() {
|
|
||||||
super.ready();
|
|
||||||
this.addEventListener("keypress", (ev) => {
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
this._submitForm();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await window.stepsPromise;
|
|
||||||
|
|
||||||
if (response.status === 404) {
|
|
||||||
// We don't load the component when onboarding is done
|
|
||||||
document.location = "/";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const steps = await response.json();
|
|
||||||
|
|
||||||
if (steps.every((step) => step.done)) {
|
|
||||||
// Onboarding is done!
|
|
||||||
document.location = "/";
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
alert("Something went wrong loading loading onboarding, try refreshing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_maybePopulateUsername() {
|
|
||||||
if (this._username) return;
|
|
||||||
|
|
||||||
const parts = this._name.split(" ");
|
|
||||||
|
|
||||||
if (parts.length) {
|
|
||||||
this._username = parts[0].toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _submitForm() {
|
|
||||||
if (!this._name || !this._username || !this._password) {
|
|
||||||
this._errorMsg = "required_fields";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._errorMsg = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/onboarding/users", {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "same-origin",
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: this._name,
|
|
||||||
username: this._username,
|
|
||||||
password: this._password,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
throw {
|
|
||||||
message: `Bad response from server: ${response.status}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
document.location = "/";
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.error(err);
|
|
||||||
this.setProperties({
|
|
||||||
_loading: false,
|
|
||||||
_errorMsg: err.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeErrorMsg(localize, errorMsg) {
|
|
||||||
return (
|
|
||||||
localize(`ui.panel.page-onboarding.user.error.${errorMsg}`) || errorMsg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("ha-onboarding", HaOnboarding);
|
|
230
src/onboarding/ha-onboarding.ts
Normal file
230
src/onboarding/ha-onboarding.ts
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
PropertyValues,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { genClientId } from "home-assistant-js-websocket";
|
||||||
|
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||||
|
import { OnboardingStep, onboardUserStep } from "../data/onboarding";
|
||||||
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
|
|
||||||
|
@customElement("ha-onboarding")
|
||||||
|
class HaOnboarding extends litLocalizeLiteMixin(LitElement) {
|
||||||
|
public translationFragment = "page-onboarding";
|
||||||
|
|
||||||
|
@property() private _name = "";
|
||||||
|
@property() private _username = "";
|
||||||
|
@property() private _password = "";
|
||||||
|
@property() private _passwordConfirm = "";
|
||||||
|
@property() private _loading = false;
|
||||||
|
@property() private _errorMsg?: string = undefined;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<p>
|
||||||
|
${this.localize("ui.panel.page-onboarding.intro")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
${this.localize("ui.panel.page-onboarding.user.intro")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
${
|
||||||
|
this._errorMsg
|
||||||
|
? html`
|
||||||
|
<p class="error">
|
||||||
|
${this.localize(
|
||||||
|
`ui.panel.page-onboarding.user.error.${this._errorMsg}`
|
||||||
|
) || this._errorMsg}
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<paper-input
|
||||||
|
autofocus
|
||||||
|
name="name"
|
||||||
|
label="${this.localize("ui.panel.page-onboarding.user.data.name")}"
|
||||||
|
.value=${this._name}
|
||||||
|
@value-changed=${this._handleValueChanged}
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
autocapitalize='on'
|
||||||
|
.errorMessage="${this.localize(
|
||||||
|
"ui.panel.page-onboarding.user.required_field"
|
||||||
|
)}"
|
||||||
|
@blur=${this._maybePopulateUsername}
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
name="username"
|
||||||
|
label="${this.localize("ui.panel.page-onboarding.user.data.username")}"
|
||||||
|
value=${this._username}
|
||||||
|
@value-changed=${this._handleValueChanged}
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
autocapitalize='none'
|
||||||
|
.errorMessage="${this.localize(
|
||||||
|
"ui.panel.page-onboarding.user.required_field"
|
||||||
|
)}"
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
name="password"
|
||||||
|
label="${this.localize("ui.panel.page-onboarding.user.data.password")}"
|
||||||
|
value=${this._password}
|
||||||
|
@value-changed=${this._handleValueChanged}
|
||||||
|
required
|
||||||
|
type='password'
|
||||||
|
auto-validate
|
||||||
|
.errorMessage="${this.localize(
|
||||||
|
"ui.panel.page-onboarding.user.required_field"
|
||||||
|
)}"
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
name="passwordConfirm"
|
||||||
|
label="${this.localize(
|
||||||
|
"ui.panel.page-onboarding.user.data.password_confirm"
|
||||||
|
)}"
|
||||||
|
value=${this._passwordConfirm}
|
||||||
|
@value-changed=${this._handleValueChanged}
|
||||||
|
required
|
||||||
|
type='password'
|
||||||
|
.invalid=${this._password !== "" &&
|
||||||
|
this._passwordConfirm !== "" &&
|
||||||
|
this._passwordConfirm !== this._password}
|
||||||
|
.errorMessage="${this.localize(
|
||||||
|
"ui.panel.page-onboarding.user.error.password_not_match"
|
||||||
|
)}"
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<p class="action">
|
||||||
|
<mwc-button
|
||||||
|
raised
|
||||||
|
@click=${this._submitForm}
|
||||||
|
.disabled=${this._loading}
|
||||||
|
>
|
||||||
|
${this.localize("ui.panel.page-onboarding.user.create_account")}
|
||||||
|
</mwc-button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.addEventListener("keypress", (ev) => {
|
||||||
|
if (ev.keyCode === 13) {
|
||||||
|
this._submitForm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await window.stepsPromise;
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
// We don't load the component when onboarding is done
|
||||||
|
document.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps: OnboardingStep[] = await response.json();
|
||||||
|
|
||||||
|
if (steps.every((step) => step.done)) {
|
||||||
|
// Onboarding is done!
|
||||||
|
document.location.href = "/";
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert("Something went wrong loading loading onboarding, try refreshing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleValueChanged(ev: PolymerChangedEvent<string>): void {
|
||||||
|
const name = (ev.target as any).name;
|
||||||
|
this[`_${name}`] = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _maybePopulateUsername(): void {
|
||||||
|
if (this._username) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = this._name.split(" ");
|
||||||
|
|
||||||
|
if (parts.length) {
|
||||||
|
this._username = parts[0].toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _submitForm(): Promise<void> {
|
||||||
|
if (!this._name || !this._username || !this._password) {
|
||||||
|
this._errorMsg = "required_fields";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._password !== this._passwordConfirm) {
|
||||||
|
this._errorMsg = "password_not_match";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loading = true;
|
||||||
|
this._errorMsg = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clientId = genClientId();
|
||||||
|
|
||||||
|
const { auth_code } = await onboardUserStep({
|
||||||
|
client_id: clientId,
|
||||||
|
name: this._name,
|
||||||
|
username: this._username,
|
||||||
|
password: this._password,
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = btoa(
|
||||||
|
JSON.stringify({
|
||||||
|
hassUrl: `${location.protocol}//${location.host}`,
|
||||||
|
clientId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
document.location.href = `/?auth_callback=1&code=${encodeURIComponent(
|
||||||
|
auth_code
|
||||||
|
)}&state=${state}`;
|
||||||
|
} catch (err) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error(err);
|
||||||
|
this._loading = false;
|
||||||
|
this._errorMsg = err.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
margin: 32px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-onboarding": HaOnboarding;
|
||||||
|
}
|
||||||
|
}
|
@ -123,7 +123,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._error = err;
|
this._error = err.message || "Unknown error";
|
||||||
} finally {
|
} finally {
|
||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,11 @@ class HaConfigAreaRegistry extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.area_registry.picker.no_areas"
|
"ui.panel.config.area_registry.no_areas"
|
||||||
)}
|
)}
|
||||||
<mwc-button @click=${this._createArea}>
|
<mwc-button @click=${this._createArea}>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.area_registry.picker.create_area"
|
"ui.panel.config.area_registry.create_area"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
@ -104,7 +104,7 @@ class HaConfigAreaRegistry extends LitElement {
|
|||||||
?is-wide=${this.isWide}
|
?is-wide=${this.isWide}
|
||||||
icon="hass:plus"
|
icon="hass:plus"
|
||||||
title="${this.hass.localize(
|
title="${this.hass.localize(
|
||||||
"ui.panel.config.area_registry.picker.create_area"
|
"ui.panel.config.area_registry.create_area"
|
||||||
)}"
|
)}"
|
||||||
@click=${this._createArea}
|
@click=${this._createArea}
|
||||||
class="${classMap({
|
class="${classMap({
|
||||||
|
@ -3,6 +3,8 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
PropertyDeclarations,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
@ -12,9 +14,8 @@ import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-tog
|
|||||||
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { updatePref } from "./data";
|
|
||||||
import { CloudStatusLoggedIn } from "./types";
|
|
||||||
import "./cloud-exposed-entities";
|
import "./cloud-exposed-entities";
|
||||||
|
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
|
||||||
|
|
||||||
export class CloudAlexaPref extends LitElement {
|
export class CloudAlexaPref extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
@ -35,7 +36,6 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
const enabled = this.cloudStatus!.prefs.alexa_enabled;
|
const enabled = this.cloudStatus!.prefs.alexa_enabled;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<paper-card heading="Alexa">
|
<paper-card heading="Alexa">
|
||||||
<paper-toggle-button
|
<paper-toggle-button
|
||||||
.checked="${enabled}"
|
.checked="${enabled}"
|
||||||
@ -51,7 +51,7 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.home-assistant.io/cloud/alexa/"
|
href="https://www.nabucasa.com/config/amazon_alexa/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Config documentation
|
Config documentation
|
||||||
@ -80,25 +80,23 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
private async _toggleChanged(ev) {
|
private async _toggleChanged(ev) {
|
||||||
const toggle = ev.target as PaperToggleButtonElement;
|
const toggle = ev.target as PaperToggleButtonElement;
|
||||||
try {
|
try {
|
||||||
await updatePref(this.hass!, { alexa_enabled: toggle.checked! });
|
await updateCloudPref(this.hass!, { alexa_enabled: toggle.checked! });
|
||||||
fireEvent(this, "ha-refresh-cloud-status");
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toggle.checked = !toggle.checked;
|
toggle.checked = !toggle.checked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
a {
|
||||||
a {
|
color: var(--primary-color);
|
||||||
color: var(--primary-color);
|
}
|
||||||
}
|
paper-card > paper-toggle-button {
|
||||||
paper-card > paper-toggle-button {
|
position: absolute;
|
||||||
position: absolute;
|
right: 8px;
|
||||||
right: 8px;
|
top: 16px;
|
||||||
top: 16px;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,12 @@ import "../../../components/entity/ha-state-icon";
|
|||||||
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { EntityFilter } from "./types";
|
|
||||||
import computeStateName from "../../../common/entity/compute_state_name";
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
import {
|
import {
|
||||||
FilterFunc,
|
FilterFunc,
|
||||||
generateFilter,
|
generateFilter,
|
||||||
} from "../../../common/entity/entity_filter";
|
} from "../../../common/entity/entity_filter";
|
||||||
|
import { EntityFilter } from "../../../data/cloud";
|
||||||
|
|
||||||
export class CloudExposedEntities extends LitElement {
|
export class CloudExposedEntities extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
@ -3,6 +3,8 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
PropertyDeclarations,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
@ -13,9 +15,8 @@ import "../../../components/buttons/ha-call-api-button";
|
|||||||
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { updatePref } from "./data";
|
|
||||||
import { CloudStatusLoggedIn } from "./types";
|
|
||||||
import "./cloud-exposed-entities";
|
import "./cloud-exposed-entities";
|
||||||
|
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
|
||||||
|
|
||||||
export class CloudGooglePref extends LitElement {
|
export class CloudGooglePref extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
@ -36,7 +37,6 @@ export class CloudGooglePref extends LitElement {
|
|||||||
const { google_enabled, google_allow_unlock } = this.cloudStatus.prefs;
|
const { google_enabled, google_allow_unlock } = this.cloudStatus.prefs;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<paper-card heading="Google Assistant">
|
<paper-card heading="Google Assistant">
|
||||||
<paper-toggle-button
|
<paper-toggle-button
|
||||||
id="google_enabled"
|
id="google_enabled"
|
||||||
@ -58,7 +58,7 @@ export class CloudGooglePref extends LitElement {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.home-assistant.io/cloud/google_assistant/"
|
href="https://www.nabucasa.com/config/google_assistant/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Config documentation
|
Config documentation
|
||||||
@ -103,37 +103,35 @@ export class CloudGooglePref extends LitElement {
|
|||||||
private async _toggleChanged(ev) {
|
private async _toggleChanged(ev) {
|
||||||
const toggle = ev.target as PaperToggleButtonElement;
|
const toggle = ev.target as PaperToggleButtonElement;
|
||||||
try {
|
try {
|
||||||
await updatePref(this.hass!, { [toggle.id]: toggle.checked! });
|
await updateCloudPref(this.hass!, { [toggle.id]: toggle.checked! });
|
||||||
fireEvent(this, "ha-refresh-cloud-status");
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toggle.checked = !toggle.checked;
|
toggle.checked = !toggle.checked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
a {
|
||||||
a {
|
color: var(--primary-color);
|
||||||
color: var(--primary-color);
|
}
|
||||||
}
|
paper-card > paper-toggle-button {
|
||||||
paper-card > paper-toggle-button {
|
position: absolute;
|
||||||
position: absolute;
|
right: 8px;
|
||||||
right: 8px;
|
top: 16px;
|
||||||
top: 16px;
|
}
|
||||||
}
|
ha-call-api-button {
|
||||||
ha-call-api-button {
|
color: var(--primary-color);
|
||||||
color: var(--primary-color);
|
font-weight: 500;
|
||||||
font-weight: 500;
|
}
|
||||||
}
|
.unlock {
|
||||||
.unlock {
|
display: flex;
|
||||||
display: flex;
|
flex-direction: row;
|
||||||
flex-direction: row;
|
padding-top: 16px;
|
||||||
padding-top: 16px;
|
}
|
||||||
}
|
.unlock > div {
|
||||||
.unlock > div {
|
flex: 1;
|
||||||
flex: 1;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
148
src/panels/config/cloud/cloud-remote-pref.ts
Normal file
148
src/panels/config/cloud/cloud-remote-pref.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyDeclarations,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-card/paper-card";
|
||||||
|
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
|
||||||
|
import "../../../components/buttons/ha-call-api-button";
|
||||||
|
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import {
|
||||||
|
connectCloudRemote,
|
||||||
|
disconnectCloudRemote,
|
||||||
|
CloudStatusLoggedIn,
|
||||||
|
} from "../../../data/cloud";
|
||||||
|
import format_date_time from "../../../common/datetime/format_date_time";
|
||||||
|
|
||||||
|
@customElement("cloud-remote-pref")
|
||||||
|
export class CloudRemotePref extends LitElement {
|
||||||
|
public hass?: HomeAssistant;
|
||||||
|
public cloudStatus?: CloudStatusLoggedIn;
|
||||||
|
|
||||||
|
static get properties(): PropertyDeclarations {
|
||||||
|
return {
|
||||||
|
hass: {},
|
||||||
|
cloudStatus: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this.cloudStatus) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
remote_connected,
|
||||||
|
remote_domain,
|
||||||
|
remote_certificate,
|
||||||
|
} = this.cloudStatus;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<paper-card heading="Remote Control">
|
||||||
|
<paper-toggle-button
|
||||||
|
.checked="${remote_connected}"
|
||||||
|
@change="${this._toggleChanged}"
|
||||||
|
></paper-toggle-button>
|
||||||
|
<div class="card-content">
|
||||||
|
Home Assistant Cloud provides you with a secure remote connection to
|
||||||
|
your instance while away from home. Your instance
|
||||||
|
${remote_connected ? "is" : "will be"} available at
|
||||||
|
<a href="https://${remote_domain}" target="_blank">
|
||||||
|
https://${remote_domain}</a
|
||||||
|
>.
|
||||||
|
${!remote_certificate
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<div class="data-row">
|
||||||
|
<paper-item-body two-line>
|
||||||
|
Certificate expiration date
|
||||||
|
<div secondary>Will be automatically renewed</div>
|
||||||
|
</paper-item-body>
|
||||||
|
<div class="data-value">
|
||||||
|
${format_date_time(
|
||||||
|
new Date(remote_certificate.expire_date),
|
||||||
|
this.hass!.language
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<paper-item-body>
|
||||||
|
Certificate fingerprint
|
||||||
|
</paper-item-body>
|
||||||
|
<div class="data-value">
|
||||||
|
${remote_certificate.fingerprint}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<a href="https://www.nabucasa.com/config/remote/" target="_blank">
|
||||||
|
<mwc-button>Learn how it works</mwc-button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _toggleChanged(ev) {
|
||||||
|
const toggle = ev.target as PaperToggleButtonElement;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (toggle.checked) {
|
||||||
|
await connectCloudRemote(this.hass!);
|
||||||
|
} else {
|
||||||
|
await disconnectCloudRemote(this.hass!);
|
||||||
|
}
|
||||||
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
} catch (err) {
|
||||||
|
toggle.checked = !toggle.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.data-row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.data-value {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
paper-card > paper-toggle-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
ha-call-api-button {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.unlock {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
.unlock > div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"cloud-remote-pref": CloudRemotePref;
|
||||||
|
}
|
||||||
|
}
|
@ -17,8 +17,8 @@ import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
|||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { WebhookDialogParams } from "./types";
|
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { WebhookDialogParams } from "./show-cloud-webhook-manage-dialog";
|
||||||
|
|
||||||
const inputLabel = "Public URL – Click to copy to clipboard";
|
const inputLabel = "Public URL – Click to copy to clipboard";
|
||||||
|
|
||||||
@ -60,10 +60,17 @@ export class CloudWebhookManageDialog extends LitElement {
|
|||||||
@blur="${this._restoreLabel}"
|
@blur="${this._restoreLabel}"
|
||||||
></paper-input>
|
></paper-input>
|
||||||
<p>
|
<p>
|
||||||
If you no longer want to use this webhook, you can
|
${cloudhook.managed
|
||||||
<button class="link" @click="${this._disableWebhook}">
|
? html`
|
||||||
disable it</button
|
This webhook is managed by an integration and cannot be
|
||||||
>.
|
disabled.
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
If you no longer want to use this webhook, you can
|
||||||
|
<button class="link" @click="${this._disableWebhook}">
|
||||||
|
disable it</button
|
||||||
|
>.
|
||||||
|
`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -10,23 +10,15 @@ import "@polymer/paper-item/paper-item-body";
|
|||||||
import "@polymer/paper-spinner/paper-spinner";
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
import { HomeAssistant, WebhookError } from "../../../types";
|
import { HomeAssistant, WebhookError } from "../../../types";
|
||||||
import { WebhookDialogParams, CloudStatusLoggedIn } from "./types";
|
|
||||||
import { Webhook, fetchWebhooks } from "../../../data/webhook";
|
import { Webhook, fetchWebhooks } from "../../../data/webhook";
|
||||||
import {
|
import {
|
||||||
createCloudhook,
|
createCloudhook,
|
||||||
deleteCloudhook,
|
deleteCloudhook,
|
||||||
CloudWebhook,
|
CloudWebhook,
|
||||||
|
CloudStatusLoggedIn,
|
||||||
} from "../../../data/cloud";
|
} from "../../../data/cloud";
|
||||||
|
import { showManageCloudhookDialog } from "./show-cloud-webhook-manage-dialog";
|
||||||
declare global {
|
|
||||||
// for fire event
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"manage-cloud-webhook": WebhookDialogParams;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CloudWebhooks extends LitElement {
|
export class CloudWebhooks extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
@ -138,14 +130,13 @@ export class CloudWebhooks extends LitElement {
|
|||||||
private _showDialog(webhookId: string) {
|
private _showDialog(webhookId: string) {
|
||||||
const webhook = this._localHooks!.find(
|
const webhook = this._localHooks!.find(
|
||||||
(ent) => ent.webhook_id === webhookId
|
(ent) => ent.webhook_id === webhookId
|
||||||
);
|
)!;
|
||||||
const cloudhook = this._cloudHooks![webhookId];
|
const cloudhook = this._cloudHooks![webhookId];
|
||||||
const params: WebhookDialogParams = {
|
showManageCloudhookDialog(this, {
|
||||||
webhook: webhook!,
|
webhook,
|
||||||
cloudhook,
|
cloudhook,
|
||||||
disableHook: () => this._disableWebhook(webhookId),
|
disableHook: () => this._disableWebhook(webhookId),
|
||||||
};
|
});
|
||||||
fireEvent(this, "manage-cloud-webhook", params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleManageButton(ev: MouseEvent) {
|
private _handleManageButton(ev: MouseEvent) {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { HomeAssistant } from "../../../types";
|
|
||||||
import { SubscriptionInfo } from "./types";
|
|
||||||
|
|
||||||
export const fetchSubscriptionInfo = (hass: HomeAssistant) =>
|
|
||||||
hass.callWS<SubscriptionInfo>({ type: "cloud/subscription" });
|
|
||||||
|
|
||||||
export const updatePref = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
prefs: {
|
|
||||||
google_enabled?: boolean;
|
|
||||||
alexa_enabled?: boolean;
|
|
||||||
google_allow_unlock?: boolean;
|
|
||||||
}
|
|
||||||
) =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "cloud/update_prefs",
|
|
||||||
...prefs,
|
|
||||||
});
|
|
@ -16,10 +16,10 @@ import formatDateTime from "../../../common/datetime/format_date_time";
|
|||||||
import EventsMixin from "../../../mixins/events-mixin";
|
import EventsMixin from "../../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { fetchCloudSubscriptionInfo } from "../../../data/cloud";
|
||||||
import { fetchSubscriptionInfo } from "./data";
|
|
||||||
import "./cloud-alexa-pref";
|
import "./cloud-alexa-pref";
|
||||||
import "./cloud-google-pref";
|
import "./cloud-google-pref";
|
||||||
|
import "./cloud-remote-pref";
|
||||||
|
|
||||||
let registeredWebhookDialog = false;
|
let registeredWebhookDialog = false;
|
||||||
|
|
||||||
@ -66,6 +66,9 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<hass-subpage header="Home Assistant Cloud">
|
<hass-subpage header="Home Assistant Cloud">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -83,7 +86,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
<div class="account-row">
|
<div class="account-row">
|
||||||
<paper-item-body two-line="">
|
<paper-item-body two-line="">
|
||||||
[[cloudStatus.email]]
|
[[cloudStatus.email]]
|
||||||
<div secondary="" class="wrap">
|
<div secondary class="wrap">
|
||||||
[[_formatSubscription(_subscription)]]
|
[[_formatSubscription(_subscription)]]
|
||||||
</div>
|
</div>
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
@ -121,6 +124,11 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<cloud-remote-pref
|
||||||
|
hass="[[hass]]"
|
||||||
|
cloud-status="[[cloudStatus]]"
|
||||||
|
></cloud-remote-pref>
|
||||||
|
|
||||||
<cloud-alexa-pref
|
<cloud-alexa-pref
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
cloud-status="[[cloudStatus]]"
|
cloud-status="[[cloudStatus]]"
|
||||||
@ -172,8 +180,12 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeRemoteConnected(connected) {
|
||||||
|
return connected ? "Connected" : "Not Connected";
|
||||||
|
}
|
||||||
|
|
||||||
async _fetchSubscriptionInfo() {
|
async _fetchSubscriptionInfo() {
|
||||||
this._subscription = await fetchSubscriptionInfo(this.hass);
|
this._subscription = await fetchCloudSubscriptionInfo(this.hass);
|
||||||
if (
|
if (
|
||||||
this._subscription.provider &&
|
this._subscription.provider &&
|
||||||
this.cloudStatus &&
|
this.cloudStatus &&
|
||||||
|
@ -74,8 +74,10 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
|||||||
<span slot="header">Home Assistant Cloud</span>
|
<span slot="header">Home Assistant Cloud</span>
|
||||||
<div slot="introduction">
|
<div slot="introduction">
|
||||||
<p>
|
<p>
|
||||||
Home Assistant Cloud connects your local instance securely to
|
Home Assistant Cloud provides you with a secure remote
|
||||||
cloud-only services Amazon Alexa and Google Assistant.
|
connection to your instance while away from home. It also allows
|
||||||
|
you to connect with cloud-only services: Amazon Alexa and Google
|
||||||
|
Assistant.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This service is run by our partner
|
This service is run by our partner
|
||||||
|
@ -66,8 +66,10 @@ class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
|
|||||||
The trial will give you access to all the benefits of Home Assistant Cloud, including:
|
The trial will give you access to all the benefits of Home Assistant Cloud, including:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Control of Home Assistant away from home</li>
|
||||||
<li>Integration with Google Assistant</li>
|
<li>Integration with Google Assistant</li>
|
||||||
<li>Integration with Amazon Alexa</li>
|
<li>Integration with Amazon Alexa</li>
|
||||||
|
<li>Easy integration with webhook-based apps like OwnTracks</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
This service is run by our partner <a href='https://www.nabucasa.com' target='_blank'>Nabu Casa, Inc</a>, a company founded by the founders of Home Assistant and Hass.io.
|
This service is run by our partner <a href='https://www.nabucasa.com' target='_blank'>Nabu Casa, Inc</a>, a company founded by the founders of Home Assistant and Hass.io.
|
||||||
|
21
src/panels/config/cloud/show-cloud-webhook-manage-dialog.ts
Normal file
21
src/panels/config/cloud/show-cloud-webhook-manage-dialog.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { Webhook } from "../../../data/webhook";
|
||||||
|
import { CloudWebhook } from "../../../data/cloud";
|
||||||
|
|
||||||
|
export interface WebhookDialogParams {
|
||||||
|
webhook: Webhook;
|
||||||
|
cloudhook: CloudWebhook;
|
||||||
|
disableHook: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showManageCloudhookDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
webhookDialogParams: WebhookDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "cloud-webhook-manage-dialog",
|
||||||
|
dialogImport: () =>
|
||||||
|
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./cloud-webhook-manage-dialog"),
|
||||||
|
dialogParams: webhookDialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -1,39 +0,0 @@
|
|||||||
import { CloudWebhook } from "../../../data/cloud";
|
|
||||||
import { Webhook } from "../../../data/webhook";
|
|
||||||
|
|
||||||
export interface EntityFilter {
|
|
||||||
include_domains: string[];
|
|
||||||
include_entities: string[];
|
|
||||||
exclude_domains: string[];
|
|
||||||
exclude_entities: string[];
|
|
||||||
}
|
|
||||||
interface CloudStatusBase {
|
|
||||||
logged_in: boolean;
|
|
||||||
cloud: "disconnected" | "connecting" | "connected";
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CloudStatusLoggedIn = CloudStatusBase & {
|
|
||||||
email: string;
|
|
||||||
google_entities: EntityFilter;
|
|
||||||
google_domains: string[];
|
|
||||||
alexa_entities: EntityFilter;
|
|
||||||
alexa_domains: string[];
|
|
||||||
prefs: {
|
|
||||||
google_enabled: boolean;
|
|
||||||
alexa_enabled: boolean;
|
|
||||||
google_allow_unlock: boolean;
|
|
||||||
cloudhooks: { [webhookId: string]: CloudWebhook };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CloudStatus = CloudStatusBase | CloudStatusLoggedIn;
|
|
||||||
|
|
||||||
export interface SubscriptionInfo {
|
|
||||||
human_description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebhookDialogParams {
|
|
||||||
webhook: Webhook;
|
|
||||||
cloudhook: CloudWebhook;
|
|
||||||
disableHook: () => void;
|
|
||||||
}
|
|
@ -139,7 +139,7 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
});
|
});
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._error = err;
|
this._error = err.message || "Unknown error";
|
||||||
} finally {
|
} finally {
|
||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
<ha-entities-picker
|
<ha-entities-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._deviceTrackers}
|
.value=${this._deviceTrackers}
|
||||||
domainFilter="device_tracker"
|
domain-filter="device_tracker"
|
||||||
.pickedEntityLabel=${this.hass.localize(
|
.pickedEntityLabel=${this.hass.localize(
|
||||||
"ui.panel.config.person.detail.device_tracker_picked"
|
"ui.panel.config.person.detail.device_tracker_picked"
|
||||||
)}
|
)}
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
showPersonDetailDialog,
|
showPersonDetailDialog,
|
||||||
loadPersonDetailDialog,
|
loadPersonDetailDialog,
|
||||||
} from "./show-dialog-person-detail";
|
} from "./show-dialog-person-detail";
|
||||||
import { User, fetchUsers } from "../../../data/auth";
|
import { User, fetchUsers } from "../../../data/user";
|
||||||
|
|
||||||
class HaConfigPerson extends LitElement {
|
class HaConfigPerson extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { Person, PersonMutableParams } from "../../../data/person";
|
import { Person, PersonMutableParams } from "../../../data/person";
|
||||||
import { User } from "../../../data/auth";
|
import { User } from "../../../data/user";
|
||||||
|
|
||||||
export interface PersonDetailDialogParams {
|
export interface PersonDetailDialogParams {
|
||||||
entry?: Person;
|
entry?: Person;
|
||||||
|
@ -66,7 +66,7 @@ class HaUserPicker extends EventsMixin(
|
|||||||
<paper-item-body two-line>
|
<paper-item-body two-line>
|
||||||
<div>[[_withDefault(user.name, 'Unnamed User')]]</div>
|
<div>[[_withDefault(user.name, 'Unnamed User')]]</div>
|
||||||
<div secondary="">
|
<div secondary="">
|
||||||
[[user.id]]
|
[[_computeGroup(localize, user)]]
|
||||||
<template is="dom-if" if="[[user.system_generated]]">
|
<template is="dom-if" if="[[user.system_generated]]">
|
||||||
- System Generated
|
- System Generated
|
||||||
</template>
|
</template>
|
||||||
@ -124,6 +124,10 @@ class HaUserPicker extends EventsMixin(
|
|||||||
return `/config/users/${user.id}`;
|
return `/config/users/${user.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeGroup(localize, user) {
|
||||||
|
return localize(`groups.${user.group_ids[0]}`);
|
||||||
|
}
|
||||||
|
|
||||||
_computeRTL(hass) {
|
_computeRTL(hass) {
|
||||||
return computeRTL(hass);
|
return computeRTL(hass);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import NavigateMixin from "../../../mixins/navigate-mixin";
|
|||||||
import "./ha-config-user-picker";
|
import "./ha-config-user-picker";
|
||||||
import "./ha-user-editor";
|
import "./ha-user-editor";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { fetchUsers } from "../../../data/auth";
|
import { fetchUsers } from "../../../data/user";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @appliesMixin NavigateMixin
|
* @appliesMixin NavigateMixin
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-card/paper-card";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../../layouts/hass-subpage";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
import NavigateMixin from "../../../mixins/navigate-mixin";
|
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin NavigateMixin
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HaUserEditor extends EventsMixin(
|
|
||||||
NavigateMixin(LocalizeMixin(PolymerElement))
|
|
||||||
) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style">
|
|
||||||
paper-card {
|
|
||||||
display: block;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto 16px;
|
|
||||||
}
|
|
||||||
paper-card:first-child {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
paper-card:last-child {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
hass-subpage paper-card:first-of-type {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<hass-subpage
|
|
||||||
header="[[localize('ui.panel.config.users.editor.caption')]]"
|
|
||||||
>
|
|
||||||
<paper-card heading="[[_computeName(user)]]">
|
|
||||||
<table class="card-content">
|
|
||||||
<tr>
|
|
||||||
<td>ID</td>
|
|
||||||
<td>[[user.id]]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Owner</td>
|
|
||||||
<td>[[user.is_owner]]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Active</td>
|
|
||||||
<td>[[user.is_active]]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>System generated</td>
|
|
||||||
<td>[[user.system_generated]]</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</paper-card>
|
|
||||||
<paper-card>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button
|
|
||||||
class="warning"
|
|
||||||
on-click="_deleteUser"
|
|
||||||
disabled="[[user.system_generated]]"
|
|
||||||
>
|
|
||||||
[[localize('ui.panel.config.users.editor.delete_user')]]
|
|
||||||
</mwc-button>
|
|
||||||
<template is="dom-if" if="[[user.system_generated]]">
|
|
||||||
Unable to remove system generated users.
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</hass-subpage>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
user: Object,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeName(user) {
|
|
||||||
return user && (user.name || "Unnamed user");
|
|
||||||
}
|
|
||||||
|
|
||||||
async _deleteUser(ev) {
|
|
||||||
if (
|
|
||||||
!confirm(
|
|
||||||
`Are you sure you want to delete ${this._computeName(this.user)}`
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ev.target.blur();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.hass.callWS({
|
|
||||||
type: "config/auth/delete",
|
|
||||||
user_id: this.user.id,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
alert(err.code);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.fire("reload-users");
|
|
||||||
this.navigate("/config/users");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-user-editor", HaUserEditor);
|
|
217
src/panels/config/users/ha-user-editor.ts
Normal file
217
src/panels/config/users/ha-user-editor.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
customElement,
|
||||||
|
CSSResultArray,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { until } from "lit-html/directives/until";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
|
||||||
|
import "../../../layouts/hass-subpage";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
deleteUser,
|
||||||
|
updateUser,
|
||||||
|
SYSTEM_GROUP_ID_USER,
|
||||||
|
SYSTEM_GROUP_ID_ADMIN,
|
||||||
|
} from "../../../data/user";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"reload-users": undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN];
|
||||||
|
|
||||||
|
@customElement("ha-user-editor")
|
||||||
|
class HaUserEditor extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
@property() public user?: User;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
const hass = this.hass;
|
||||||
|
const user = this.user;
|
||||||
|
if (!hass || !user) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.header=${hass.localize("ui.panel.config.users.editor.caption")}
|
||||||
|
>
|
||||||
|
<ha-card .header=${this._name}>
|
||||||
|
<table class="card-content">
|
||||||
|
<tr>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>${user.id}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Owner</td>
|
||||||
|
<td>${user.is_owner}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Group</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
@change=${this._handleGroupChange}
|
||||||
|
.value=${until(
|
||||||
|
this.updateComplete.then(() => user.group_ids[0])
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
${GROUPS.map(
|
||||||
|
(groupId) => html`
|
||||||
|
<option value=${groupId}>
|
||||||
|
${hass.localize(`groups.${groupId}`)}
|
||||||
|
</option>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
${user.group_ids[0] === SYSTEM_GROUP_ID_USER
|
||||||
|
? html`
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="user-experiment">
|
||||||
|
The users group is a work in progress. The user will be
|
||||||
|
unable to administer the instance via the UI. We're still
|
||||||
|
auditing all management API endpoints to ensure that they
|
||||||
|
correctly limit access to administrators.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Active</td>
|
||||||
|
<td>${user.is_active}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>System generated</td>
|
||||||
|
<td>${user.system_generated}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button @click=${this._handleRenameUser}>
|
||||||
|
${hass.localize("ui.panel.config.users.editor.rename_user")}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
class="warning"
|
||||||
|
@click=${this._deleteUser}
|
||||||
|
.disabled=${user.system_generated}
|
||||||
|
>
|
||||||
|
${hass.localize("ui.panel.config.users.editor.delete_user")}
|
||||||
|
</mwc-button>
|
||||||
|
${user.system_generated
|
||||||
|
? html`
|
||||||
|
Unable to remove system generated users.
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _name() {
|
||||||
|
return this.user && (this.user.name || "Unnamed user");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleRenameUser(ev): Promise<void> {
|
||||||
|
ev.currentTarget.blur();
|
||||||
|
const newName = prompt("New name?", this.user!.name);
|
||||||
|
if (newName === null || newName === this.user!.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateUser(this.hass!, this.user!.id, {
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
fireEvent(this, "reload-users");
|
||||||
|
} catch (err) {
|
||||||
|
alert(`User rename failed: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleGroupChange(ev): Promise<void> {
|
||||||
|
const selectEl = ev.currentTarget as HTMLSelectElement;
|
||||||
|
const newGroup = selectEl.value;
|
||||||
|
try {
|
||||||
|
await updateUser(this.hass!, this.user!.id, {
|
||||||
|
group_ids: [newGroup],
|
||||||
|
});
|
||||||
|
fireEvent(this, "reload-users");
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Group update failed: ${err.message}`);
|
||||||
|
selectEl.value = this.user!.group_ids[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteUser(ev): Promise<void> {
|
||||||
|
if (!confirm(`Are you sure you want to delete ${this._name}`)) {
|
||||||
|
ev.target.blur();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await deleteUser(this.hass!, this.user!.id);
|
||||||
|
} catch (err) {
|
||||||
|
alert(err.code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "reload-users");
|
||||||
|
navigate(this, "/config/users");
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
ha-card {
|
||||||
|
display: block;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
ha-card:first-child {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
ha-card:last-child {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
hass-subpage ha-card:first-of-type {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.user-experiment {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-user-editor": HaUserEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -3,37 +3,37 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
property,
|
||||||
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { Cluster } from "../../../data/zha";
|
import { Cluster, ZHADevice, fetchBindableDevices } from "../../../data/zha";
|
||||||
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 { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ZHAClusterSelectedParams, ZHANodeSelectedParams } from "./types";
|
import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
|
||||||
import "./zha-cluster-attributes";
|
import "./zha-cluster-attributes";
|
||||||
import "./zha-cluster-commands";
|
import "./zha-cluster-commands";
|
||||||
import "./zha-network";
|
import "./zha-network";
|
||||||
import "./zha-node";
|
import "./zha-node";
|
||||||
|
import "./zha-binding";
|
||||||
|
|
||||||
export class HaConfigZha extends LitElement {
|
export class HaConfigZha extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
public isWide?: boolean;
|
@property() public isWide?: boolean;
|
||||||
private _selectedNode?: HassEntity;
|
@property() private _selectedDevice?: ZHADevice;
|
||||||
private _selectedCluster?: Cluster;
|
@property() private _selectedCluster?: Cluster;
|
||||||
|
@property() private _bindableDevices: ZHADevice[] = [];
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
return {
|
if (changedProperties.has("_selectedDevice")) {
|
||||||
hass: {},
|
this._fetchBindableDevices();
|
||||||
isWide: {},
|
}
|
||||||
_selectedCluster: {},
|
super.update(changedProperties);
|
||||||
_selectedNode: {},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
@ -57,25 +57,35 @@ export class HaConfigZha extends LitElement {
|
|||||||
.isWide="${this.isWide}"
|
.isWide="${this.isWide}"
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
@zha-cluster-selected="${this._onClusterSelected}"
|
@zha-cluster-selected="${this._onClusterSelected}"
|
||||||
@zha-node-selected="${this._onNodeSelected}"
|
@zha-node-selected="${this._onDeviceSelected}"
|
||||||
></zha-node>
|
></zha-node>
|
||||||
${this._selectedCluster
|
${this._selectedCluster
|
||||||
? html`
|
? html`
|
||||||
<zha-cluster-attributes
|
<zha-cluster-attributes
|
||||||
.isWide="${this.isWide}"
|
.isWide="${this.isWide}"
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
.selectedNode="${this._selectedNode}"
|
.selectedNode="${this._selectedDevice}"
|
||||||
.selectedCluster="${this._selectedCluster}"
|
.selectedCluster="${this._selectedCluster}"
|
||||||
></zha-cluster-attributes>
|
></zha-cluster-attributes>
|
||||||
|
|
||||||
<zha-cluster-commands
|
<zha-cluster-commands
|
||||||
.isWide="${this.isWide}"
|
.isWide="${this.isWide}"
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
.selectedNode="${this._selectedNode}"
|
.selectedNode="${this._selectedDevice}"
|
||||||
.selectedCluster="${this._selectedCluster}"
|
.selectedCluster="${this._selectedCluster}"
|
||||||
></zha-cluster-commands>
|
></zha-cluster-commands>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._selectedDevice && this._bindableDevices.length > 0
|
||||||
|
? html`
|
||||||
|
<zha-binding-control
|
||||||
|
.isWide="${this.isWide}"
|
||||||
|
.hass="${this.hass}"
|
||||||
|
.selectedDevice="${this._selectedDevice}"
|
||||||
|
.bindableDevices="${this._bindableDevices}"
|
||||||
|
></zha-binding-control>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</ha-app-layout>
|
</ha-app-layout>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -86,13 +96,24 @@ export class HaConfigZha extends LitElement {
|
|||||||
this._selectedCluster = selectedClusterEvent.detail.cluster;
|
this._selectedCluster = selectedClusterEvent.detail.cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onNodeSelected(
|
private _onDeviceSelected(
|
||||||
selectedNodeEvent: HASSDomEvent<ZHANodeSelectedParams>
|
selectedNodeEvent: HASSDomEvent<ZHADeviceSelectedParams>
|
||||||
): void {
|
): void {
|
||||||
this._selectedNode = selectedNodeEvent.detail.node;
|
this._selectedDevice = selectedNodeEvent.detail.node;
|
||||||
this._selectedCluster = undefined;
|
this._selectedCluster = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _fetchBindableDevices(): Promise<void> {
|
||||||
|
if (this._selectedDevice && this.hass) {
|
||||||
|
this._bindableDevices = (await fetchBindableDevices(
|
||||||
|
this.hass,
|
||||||
|
this._selectedDevice!.ieee
|
||||||
|
)).sort((a, b) => {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [haStyle];
|
return [haStyle];
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ZHADeviceEntity, Cluster } from "../../../data/zha";
|
import { ZHADevice, Cluster } from "../../../data/zha";
|
||||||
|
|
||||||
export interface PickerTarget extends EventTarget {
|
export interface PickerTarget extends EventTarget {
|
||||||
selected: number;
|
selected: number;
|
||||||
@ -34,8 +34,8 @@ export interface IssueCommandServiceData {
|
|||||||
command_type: string;
|
command_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZHANodeSelectedParams {
|
export interface ZHADeviceSelectedParams {
|
||||||
node: ZHADeviceEntity;
|
node: ZHADevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZHAClusterSelectedParams {
|
export interface ZHAClusterSelectedParams {
|
||||||
|
211
src/panels/config/zha/zha-binding.ts
Normal file
211
src/panels/config/zha/zha-binding.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-card/paper-card";
|
||||||
|
import "../../../components/buttons/ha-call-service-button";
|
||||||
|
import "../../../components/ha-service-description";
|
||||||
|
import { ZHADevice, bindDevices, unbindDevices } from "../../../data/zha";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import "../ha-config-section";
|
||||||
|
import { ItemSelectedEvent } from "./types";
|
||||||
|
|
||||||
|
@customElement("zha-binding-control")
|
||||||
|
export class ZHABindingControl extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
@property() public isWide?: boolean;
|
||||||
|
@property() public selectedDevice?: ZHADevice;
|
||||||
|
@property() private _showHelp: boolean = false;
|
||||||
|
@property() private _bindTargetIndex: number = -1;
|
||||||
|
@property() private bindableDevices: ZHADevice[] = [];
|
||||||
|
private _deviceToBind?: ZHADevice;
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
if (changedProperties.has("selectedDevice")) {
|
||||||
|
this._bindTargetIndex = -1;
|
||||||
|
}
|
||||||
|
super.update(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<ha-config-section .isWide="${this.isWide}">
|
||||||
|
<div class="sectionHeader" slot="header">
|
||||||
|
<span>Device Binding</span>
|
||||||
|
<paper-icon-button
|
||||||
|
class="toggle-help-icon"
|
||||||
|
@click="${this._onHelpTap}"
|
||||||
|
icon="hass:help-circle"
|
||||||
|
>
|
||||||
|
</paper-icon-button>
|
||||||
|
</div>
|
||||||
|
<span slot="introduction">Bind and unbind devices.</span>
|
||||||
|
|
||||||
|
<paper-card class="content">
|
||||||
|
<div class="command-picker">
|
||||||
|
<paper-dropdown-menu label="Bindable Devices" class="flex">
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected="${this._bindTargetIndex}"
|
||||||
|
@iron-select="${this._bindTargetIndexChanged}"
|
||||||
|
>
|
||||||
|
${this.bindableDevices.map(
|
||||||
|
(device) => html`
|
||||||
|
<paper-item>${device.name}</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
${this._showHelp
|
||||||
|
? html`
|
||||||
|
<div class="helpText">
|
||||||
|
Select a device to issue a bind command.
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button @click="${this._onBindDevicesClick}">Bind</mwc-button>
|
||||||
|
${this._showHelp
|
||||||
|
? html`
|
||||||
|
<div class="helpText">
|
||||||
|
Bind devices.
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<mwc-button @click="${this._onUnbindDevicesClick}"
|
||||||
|
>Unbind</mwc-button
|
||||||
|
>
|
||||||
|
${this._showHelp
|
||||||
|
? html`
|
||||||
|
<div class="helpText">
|
||||||
|
Unbind devices.
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
</ha-config-section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
|
||||||
|
this._bindTargetIndex = event.target!.selected;
|
||||||
|
this._deviceToBind =
|
||||||
|
this._bindTargetIndex === -1
|
||||||
|
? undefined
|
||||||
|
: this.bindableDevices[this._bindTargetIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onHelpTap(): void {
|
||||||
|
this._showHelp = !this._showHelp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onBindDevicesClick(): Promise<void> {
|
||||||
|
if (this.hass && this._deviceToBind && this.selectedDevice) {
|
||||||
|
await bindDevices(
|
||||||
|
this.hass,
|
||||||
|
this.selectedDevice.ieee,
|
||||||
|
this._deviceToBind.ieee
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onUnbindDevicesClick(): Promise<void> {
|
||||||
|
if (this.hass && this._deviceToBind && this.selectedDevice) {
|
||||||
|
await unbindDevices(
|
||||||
|
this.hass,
|
||||||
|
this.selectedDevice.ieee,
|
||||||
|
this._deviceToBind.ieee
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.flex {
|
||||||
|
-ms-flex: 1 1 0.000000001px;
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
-webkit-flex-basis: 0.000000001px;
|
||||||
|
flex-basis: 0.000000001px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-card {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions.warning ha-call-service-button {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-picker {
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-ms-flex-direction: row;
|
||||||
|
-webkit-flex-direction: row;
|
||||||
|
flex-direction: row;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 28px;
|
||||||
|
padding-right: 28px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-text {
|
||||||
|
padding-left: 28px;
|
||||||
|
padding-right: 28px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionHeader {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
color: grey;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-help-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
right: 0;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-service-description {
|
||||||
|
display: block;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"zha-binding-control": ZHABindingControl;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import {
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
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 { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
@ -18,9 +19,13 @@ import "../../../components/ha-service-description";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { ItemSelectedEvent, NodeServiceData } from "./types";
|
import { ItemSelectedEvent, NodeServiceData, ChangeEvent } from "./types";
|
||||||
import "./zha-clusters";
|
import "./zha-clusters";
|
||||||
import "./zha-device-card";
|
import "./zha-device-card";
|
||||||
|
import {
|
||||||
|
updateDeviceRegistryEntry,
|
||||||
|
DeviceRegistryEntryMutableParams,
|
||||||
|
} from "../../../data/device_registry";
|
||||||
import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha";
|
import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -40,6 +45,7 @@ export class ZHANode extends LitElement {
|
|||||||
private _selectedNode?: ZHADevice;
|
private _selectedNode?: ZHADevice;
|
||||||
private _serviceData?: {};
|
private _serviceData?: {};
|
||||||
private _nodes: ZHADevice[];
|
private _nodes: ZHADevice[];
|
||||||
|
private _userSelectedName?: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -58,6 +64,7 @@ export class ZHANode extends LitElement {
|
|||||||
_entities: {},
|
_entities: {},
|
||||||
_serviceData: {},
|
_serviceData: {},
|
||||||
_nodes: {},
|
_nodes: {},
|
||||||
|
_userSelectedName: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +116,11 @@ export class ZHANode extends LitElement {
|
|||||||
>
|
>
|
||||||
${this._nodes.map(
|
${this._nodes.map(
|
||||||
(entry) => html`
|
(entry) => html`
|
||||||
<paper-item>${entry.name}</paper-item>
|
<paper-item
|
||||||
|
>${entry.user_given_name
|
||||||
|
? entry.user_given_name
|
||||||
|
: entry.name}</paper-item
|
||||||
|
>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
@ -132,6 +143,18 @@ export class ZHANode extends LitElement {
|
|||||||
></zha-device-card>
|
></zha-device-card>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._selectedNodeIndex !== -1
|
||||||
|
? html`
|
||||||
|
<div class="input-text">
|
||||||
|
<paper-input
|
||||||
|
type="string"
|
||||||
|
.value="${this._userSelectedName}"
|
||||||
|
@value-changed="${this._onUserSelectedNameChanged}"
|
||||||
|
placeholder="User given name"
|
||||||
|
></paper-input>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""}
|
${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""}
|
||||||
${this._selectedNode ? this._renderClusters() : ""}
|
${this._selectedNode ? this._renderClusters() : ""}
|
||||||
</paper-card>
|
</paper-card>
|
||||||
@ -170,6 +193,21 @@ export class ZHANode extends LitElement {
|
|||||||
/>
|
/>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
<mwc-button
|
||||||
|
@click="${this._onUpdateDeviceNameClick}"
|
||||||
|
.disabled="${!this._userSelectedName ||
|
||||||
|
this._userSelectedName === ""}"
|
||||||
|
>Update Name</mwc-button
|
||||||
|
>
|
||||||
|
${this._showHelp
|
||||||
|
? html`
|
||||||
|
<div class="helpText">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.services.updateDeviceName"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -191,6 +229,7 @@ export class ZHANode extends LitElement {
|
|||||||
private _selectedNodeChanged(event: ItemSelectedEvent): void {
|
private _selectedNodeChanged(event: ItemSelectedEvent): void {
|
||||||
this._selectedNodeIndex = event!.target!.selected;
|
this._selectedNodeIndex = event!.target!.selected;
|
||||||
this._selectedNode = this._nodes[this._selectedNodeIndex];
|
this._selectedNode = this._nodes[this._selectedNodeIndex];
|
||||||
|
this._userSelectedName = "";
|
||||||
fireEvent(this, "zha-node-selected", { node: this._selectedNode });
|
fireEvent(this, "zha-node-selected", { node: this._selectedNode });
|
||||||
this._serviceData = this._computeNodeServiceData();
|
this._serviceData = this._computeNodeServiceData();
|
||||||
}
|
}
|
||||||
@ -201,6 +240,27 @@ export class ZHANode extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onUserSelectedNameChanged(value: ChangeEvent): void {
|
||||||
|
this._userSelectedName = value.detail!.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onUpdateDeviceNameClick(): Promise<void> {
|
||||||
|
if (this.hass) {
|
||||||
|
const values: DeviceRegistryEntryMutableParams = {
|
||||||
|
name_by_user: this._userSelectedName,
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateDeviceRegistryEntry(
|
||||||
|
this.hass,
|
||||||
|
this._selectedNode!.device_reg_id,
|
||||||
|
values
|
||||||
|
);
|
||||||
|
|
||||||
|
this._selectedNode!.user_given_name = this._userSelectedName!;
|
||||||
|
this._userSelectedName = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _computeNodeServiceData(): NodeServiceData {
|
private _computeNodeServiceData(): NodeServiceData {
|
||||||
return {
|
return {
|
||||||
ieee_address: this._selectedNode!.ieee,
|
ieee_address: this._selectedNode!.ieee,
|
||||||
@ -277,6 +337,7 @@ export class ZHANode extends LitElement {
|
|||||||
padding-left: 28px;
|
padding-left: 28px;
|
||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-service-description {
|
ha-service-description {
|
||||||
@ -294,6 +355,12 @@ export class ZHANode extends LitElement {
|
|||||||
right: 0;
|
right: 0;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-text {
|
||||||
|
padding-left: 28px;
|
||||||
|
padding-right: 28px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
css,
|
css,
|
||||||
property,
|
property,
|
||||||
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
states?: string[];
|
states?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-alarm-panel-card")
|
||||||
class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement() {
|
public static async getConfigElement() {
|
||||||
await import(/* webpackChunkName: "hui-alarm-panel-card-editor" */ "../editor/config-elements/hui-alarm-panel-card-editor");
|
await import(/* webpackChunkName: "hui-alarm-panel-card-editor" */ "../editor/config-elements/hui-alarm-panel-card-editor");
|
||||||
@ -50,7 +52,9 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _config?: Config;
|
@property() private _config?: Config;
|
||||||
|
|
||||||
@property() private _code?: string;
|
@property() private _code?: string;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
@ -205,98 +209,109 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
this._code = "";
|
this._code = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult {
|
||||||
return [
|
return css`
|
||||||
css`
|
ha-card {
|
||||||
ha-card {
|
padding-bottom: 16px;
|
||||||
padding-bottom: 16px;
|
position: relative;
|
||||||
position: relative;
|
--alarm-color-disarmed: var(--label-badge-green);
|
||||||
--alarm-color-disarmed: var(--label-badge-green);
|
--alarm-color-pending: var(--label-badge-yellow);
|
||||||
--alarm-color-pending: var(--label-badge-yellow);
|
--alarm-color-triggered: var(--label-badge-red);
|
||||||
--alarm-color-triggered: var(--label-badge-red);
|
--alarm-color-armed: var(--label-badge-red);
|
||||||
--alarm-color-armed: var(--label-badge-red);
|
--alarm-color-autoarm: rgba(0, 153, 255, 0.1);
|
||||||
--alarm-color-autoarm: rgba(0, 153, 255, 0.1);
|
--alarm-state-color: var(--alarm-color-armed);
|
||||||
--alarm-state-color: var(--alarm-color-armed);
|
--base-unit: 15px;
|
||||||
--base-unit: 15px;
|
font-size: calc(var(--base-unit));
|
||||||
font-size: calc(var(--base-unit));
|
}
|
||||||
}
|
|
||||||
ha-label-badge {
|
ha-label-badge {
|
||||||
|
--ha-label-badge-color: var(--alarm-state-color);
|
||||||
|
--label-badge-text-color: var(--alarm-state-color);
|
||||||
|
--label-badge-background-color: var(--paper-card-background-color);
|
||||||
|
color: var(--alarm-state-color);
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disarmed {
|
||||||
|
--alarm-state-color: var(--alarm-color-disarmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.triggered {
|
||||||
|
--alarm-state-color: var(--alarm-color-triggered);
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arming {
|
||||||
|
--alarm-state-color: var(--alarm-color-pending);
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending {
|
||||||
|
--alarm-state-color: var(--alarm-color-pending);
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
--ha-label-badge-color: var(--alarm-state-color);
|
--ha-label-badge-color: var(--alarm-state-color);
|
||||||
--label-badge-text-color: var(--alarm-state-color);
|
|
||||||
--label-badge-background-color: var(--paper-card-background-color);
|
|
||||||
color: var(--alarm-state-color);
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
top: 12px;
|
|
||||||
}
|
}
|
||||||
.disarmed {
|
100% {
|
||||||
--alarm-state-color: var(--alarm-color-disarmed);
|
--ha-label-badge-color: rgba(255, 153, 0, 0.3);
|
||||||
}
|
}
|
||||||
.triggered {
|
}
|
||||||
--alarm-state-color: var(--alarm-color-triggered);
|
|
||||||
animation: pulse 1s infinite;
|
paper-input {
|
||||||
}
|
margin: 0 auto 8px;
|
||||||
.arming {
|
max-width: 150px;
|
||||||
--alarm-state-color: var(--alarm-color-pending);
|
font-size: calc(var(--base-unit));
|
||||||
animation: pulse 1s infinite;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.pending {
|
|
||||||
--alarm-state-color: var(--alarm-color-pending);
|
.state {
|
||||||
animation: pulse 1s infinite;
|
margin-left: 16px;
|
||||||
}
|
font-size: calc(var(--base-unit) * 0.9);
|
||||||
@keyframes pulse {
|
position: relative;
|
||||||
0% {
|
bottom: 16px;
|
||||||
--ha-label-badge-color: var(--alarm-state-color);
|
color: var(--alarm-state-color);
|
||||||
}
|
animation: none;
|
||||||
100% {
|
}
|
||||||
--ha-label-badge-color: rgba(255, 153, 0, 0.3);
|
|
||||||
}
|
#keypad {
|
||||||
}
|
display: flex;
|
||||||
paper-input {
|
justify-content: center;
|
||||||
margin: 0 auto 8px;
|
flex-wrap: wrap;
|
||||||
max-width: 150px;
|
margin: auto;
|
||||||
font-size: calc(var(--base-unit));
|
width: 300px;
|
||||||
text-align: center;
|
}
|
||||||
}
|
|
||||||
.state {
|
#keypad mwc-button {
|
||||||
margin-left: 16px;
|
margin-bottom: 5%;
|
||||||
font-size: calc(var(--base-unit) * 0.9);
|
width: 30%;
|
||||||
position: relative;
|
padding: calc(var(--base-unit));
|
||||||
bottom: 16px;
|
font-size: calc(var(--base-unit) * 1.1);
|
||||||
color: var(--alarm-state-color);
|
box-sizing: border-box;
|
||||||
animation: none;
|
}
|
||||||
}
|
|
||||||
#keypad {
|
.actions {
|
||||||
display: flex;
|
margin: 0 8px;
|
||||||
justify-content: center;
|
padding-top: 20px;
|
||||||
flex-wrap: wrap;
|
display: flex;
|
||||||
margin: auto;
|
flex-wrap: wrap;
|
||||||
width: 300px;
|
justify-content: center;
|
||||||
}
|
font-size: calc(var(--base-unit) * 1);
|
||||||
#keypad mwc-button {
|
}
|
||||||
margin-bottom: 5%;
|
|
||||||
width: 30%;
|
.actions mwc-button {
|
||||||
padding: calc(var(--base-unit));
|
min-width: calc(var(--base-unit) * 9);
|
||||||
font-size: calc(var(--base-unit) * 1.1);
|
margin: 0 4px;
|
||||||
box-sizing: border-box;
|
}
|
||||||
}
|
|
||||||
.actions {
|
mwc-button#disarm {
|
||||||
margin: 0 8px;
|
color: var(--google-red-500);
|
||||||
padding-top: 20px;
|
}
|
||||||
display: flex;
|
`;
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(var(--base-unit) * 1);
|
|
||||||
}
|
|
||||||
.actions mwc-button {
|
|
||||||
min-width: calc(var(--base-unit) * 9);
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
mwc-button#disarm {
|
|
||||||
color: var(--google-red-500);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,5 +320,3 @@ declare global {
|
|||||||
"hui-alarm-panel-card": HuiAlarmPanelCard;
|
"hui-alarm-panel-card": HuiAlarmPanelCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-alarm-panel-card", HuiAlarmPanelCard);
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
css,
|
css,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
@ -18,8 +19,9 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-empty-state-card")
|
||||||
export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
|
export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 2;
|
return 2;
|
||||||
@ -29,12 +31,6 @@ export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
|
|||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return {
|
|
||||||
hass: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -83,5 +79,3 @@ declare global {
|
|||||||
"hui-empty-state-card": HuiEmptyStateCard;
|
"hui-empty-state-card": HuiEmptyStateCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-empty-state-card", HuiEmptyStateCard);
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
@ -36,6 +39,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
|
|||||||
theme?: string;
|
theme?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-entities-card")
|
||||||
class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-entities-card-editor" */ "../editor/config-elements/hui-entities-card-editor");
|
await import(/* webpackChunkName: "hui-entities-card-editor" */ "../editor/config-elements/hui-entities-card-editor");
|
||||||
@ -46,8 +50,10 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
|||||||
return { entities: [] };
|
return { entities: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property() protected _config?: EntitiesCardConfig;
|
||||||
|
|
||||||
protected _hass?: HomeAssistant;
|
protected _hass?: HomeAssistant;
|
||||||
protected _config?: EntitiesCardConfig;
|
|
||||||
protected _configEntities?: EntitiesCardEntityConfig[];
|
protected _configEntities?: EntitiesCardEntityConfig[];
|
||||||
|
|
||||||
set hass(hass: HomeAssistant) {
|
set hass(hass: HomeAssistant) {
|
||||||
@ -65,12 +71,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return {
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -100,7 +100,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
|||||||
const { show_header_toggle, title } = this._config;
|
const { show_header_toggle, title } = this._config;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card>
|
<ha-card>
|
||||||
${!title && !show_header_toggle
|
${!title && !show_header_toggle
|
||||||
? html``
|
? html``
|
||||||
@ -128,38 +127,52 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
ha-card {
|
||||||
ha-card {
|
padding: 16px;
|
||||||
padding: 16px;
|
}
|
||||||
}
|
|
||||||
#states {
|
#states {
|
||||||
margin: -4px 0;
|
margin: -4px 0;
|
||||||
}
|
}
|
||||||
#states > * {
|
|
||||||
margin: 8px 0;
|
#states > * {
|
||||||
}
|
margin: 8px 0;
|
||||||
#states > div > * {
|
}
|
||||||
overflow: hidden;
|
|
||||||
}
|
#states > div > * {
|
||||||
.header {
|
overflow: hidden;
|
||||||
@apply --paper-font-headline;
|
}
|
||||||
/* overwriting line-height +8 because entity-toggle can be 40px height,
|
|
||||||
compensating this with reduced padding */
|
.header {
|
||||||
line-height: 40px;
|
/* start paper-font-headline style */
|
||||||
color: var(--primary-text-color);
|
font-family: "Roboto", "Noto", sans-serif;
|
||||||
padding: 4px 0 12px;
|
-webkit-font-smoothing: antialiased; /* OS X subpixel AA bleed bug */
|
||||||
display: flex;
|
text-rendering: optimizeLegibility;
|
||||||
justify-content: space-between;
|
font-size: 24px;
|
||||||
}
|
font-weight: 400;
|
||||||
.header .name {
|
letter-spacing: -0.012em;
|
||||||
@apply --paper-font-common-nowrap;
|
/* end paper-font-headline style */
|
||||||
}
|
|
||||||
.state-card-dialog {
|
line-height: 40px;
|
||||||
cursor: pointer;
|
color: var(--primary-text-color);
|
||||||
}
|
padding: 4px 0 12px;
|
||||||
</style>
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .name {
|
||||||
|
/* start paper-font-common-nowrap style */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
/* end paper-font-common-nowrap */
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-card-dialog {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,5 +205,3 @@ declare global {
|
|||||||
"hui-entities-card": HuiEntitiesCard;
|
"hui-entities-card": HuiEntitiesCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-entities-card", HuiEntitiesCard);
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
css,
|
css,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
@ -34,6 +35,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-entity-button-card")
|
||||||
class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-entity-button-card-editor" */ "../editor/config-elements/hui-entity-button-card-editor");
|
await import(/* webpackChunkName: "hui-entity-button-card-editor" */ "../editor/config-elements/hui-entity-button-card-editor");
|
||||||
@ -47,15 +49,9 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _config?: Config;
|
||||||
return {
|
|
||||||
hass: {},
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 2;
|
return 2;
|
||||||
@ -147,11 +143,13 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
|||||||
padding: 4% 0;
|
padding: 4% 0;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon {
|
ha-icon {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
height: auto;
|
height: auto;
|
||||||
color: var(--paper-item-icon-color, #44739e);
|
color: var(--paper-item-icon-color, #44739e);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="light"][data-state="on"],
|
ha-icon[data-domain="light"][data-state="on"],
|
||||||
ha-icon[data-domain="switch"][data-state="on"],
|
ha-icon[data-domain="switch"][data-state="on"],
|
||||||
ha-icon[data-domain="binary_sensor"][data-state="on"],
|
ha-icon[data-domain="binary_sensor"][data-state="on"],
|
||||||
@ -159,6 +157,7 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
|||||||
ha-icon[data-domain="sun"][data-state="above_horizon"] {
|
ha-icon[data-domain="sun"][data-state="above_horizon"] {
|
||||||
color: var(--paper-item-icon-active-color, #fdd835);
|
color: var(--paper-item-icon-active-color, #fdd835);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-state="unavailable"] {
|
ha-icon[data-state="unavailable"] {
|
||||||
color: var(--state-icon-unavailable-color);
|
color: var(--state-icon-unavailable-color);
|
||||||
}
|
}
|
||||||
@ -198,5 +197,3 @@ declare global {
|
|||||||
"hui-entity-button-card": HuiEntityButtonCard;
|
"hui-entity-button-card": HuiEntityButtonCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-entity-button-card", HuiEntityButtonCard);
|
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit-element";
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
import { LovelaceCard } from "../types";
|
import { LovelaceCard } from "../types";
|
||||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||||
@ -21,15 +29,11 @@ export const createErrorCardConfig = (error, origConfig) => ({
|
|||||||
origConfig,
|
origConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@customElement("hui-error-card")
|
||||||
export class HuiErrorCard extends LitElement implements LovelaceCard {
|
export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
|
|
||||||
static get properties() {
|
@property() private _config?: Config;
|
||||||
return {
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 4;
|
return 4;
|
||||||
@ -45,22 +49,20 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()} ${this._config.error}
|
${this._config.error}
|
||||||
<pre>${this._toStr(this._config.origConfig)}</pre>
|
<pre>${this._toStr(this._config.origConfig)}</pre>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
:host {
|
||||||
:host {
|
display: block;
|
||||||
display: block;
|
background-color: #ef5350;
|
||||||
background-color: #ef5350;
|
color: white;
|
||||||
color: white;
|
padding: 8px;
|
||||||
padding: 8px;
|
font-weight: 500;
|
||||||
font-weight: 500;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,5 +76,3 @@ declare global {
|
|||||||
"hui-error-card": HuiErrorCard;
|
"hui-error-card": HuiErrorCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-error-card", HuiErrorCard);
|
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
property,
|
property,
|
||||||
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ export const severityMap = {
|
|||||||
normal: "var(--label-badge-blue)",
|
normal: "var(--label-badge-blue)",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@customElement("hui-gauge-card")
|
||||||
class HuiGaugeCard extends LitElement implements LovelaceCard {
|
class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-gauge-card-editor" */ "../editor/config-elements/hui-gauge-card-editor");
|
await import(/* webpackChunkName: "hui-gauge-card-editor" */ "../editor/config-elements/hui-gauge-card-editor");
|
||||||
@ -55,7 +57,9 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _config?: Config;
|
@property() private _config?: Config;
|
||||||
|
|
||||||
private _updated?: boolean;
|
private _updated?: boolean;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
@ -306,5 +310,3 @@ declare global {
|
|||||||
"hui-gauge-card": HuiGaugeCard;
|
"hui-gauge-card": HuiGaugeCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-gauge-card", HuiGaugeCard);
|
|
||||||
|
@ -2,8 +2,11 @@ import {
|
|||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@ -29,34 +32,32 @@ export interface ConfigEntity extends EntityConfig {
|
|||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config extends LovelaceCardConfig {
|
export interface GlanceCardConfig extends LovelaceCardConfig {
|
||||||
show_name?: boolean;
|
show_name?: boolean;
|
||||||
show_state?: boolean;
|
show_state?: boolean;
|
||||||
|
show_icon?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
entities: ConfigEntity[];
|
entities: ConfigEntity[];
|
||||||
columns?: number;
|
columns?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-glance-card")
|
||||||
export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-glance-card-editor" */ "../editor/config-elements/hui-glance-card-editor");
|
await import(/* webpackChunkName: "hui-glance-card-editor" */ "../editor/config-elements/hui-glance-card-editor");
|
||||||
return document.createElement("hui-glance-card-editor");
|
return document.createElement("hui-glance-card-editor");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getStubConfig(): object {
|
public static getStubConfig(): object {
|
||||||
return { entities: [] };
|
return { entities: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
private _configEntities?: ConfigEntity[];
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _config?: GlanceCardConfig;
|
||||||
return {
|
|
||||||
hass: {},
|
private _configEntities?: ConfigEntity[];
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return (
|
return (
|
||||||
@ -65,7 +66,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(config: Config): void {
|
public setConfig(config: GlanceCardConfig): void {
|
||||||
this._config = { theme: "default", ...config };
|
this._config = { theme: "default", ...config };
|
||||||
const entities = processConfigEntities<ConfigEntity>(config.entities);
|
const entities = processConfigEntities<ConfigEntity>(config.entities);
|
||||||
|
|
||||||
@ -120,7 +121,6 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
const { title } = this._config;
|
const { title } = this._config;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card .header="${title}">
|
<ha-card .header="${title}">
|
||||||
<div class="entities ${classMap({ "no-header": !title })}">
|
<div class="entities ${classMap({ "no-header": !title })}">
|
||||||
${this._configEntities!.map((entityConf) =>
|
${this._configEntities!.map((entityConf) =>
|
||||||
@ -143,41 +143,39 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
.entities {
|
||||||
.entities {
|
display: flex;
|
||||||
display: flex;
|
padding: 0 16px 4px;
|
||||||
padding: 0 16px 4px;
|
flex-wrap: wrap;
|
||||||
flex-wrap: wrap;
|
}
|
||||||
}
|
.entities.no-header {
|
||||||
.entities.no-header {
|
padding-top: 16px;
|
||||||
padding-top: 16px;
|
}
|
||||||
}
|
.entity {
|
||||||
.entity {
|
box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
padding: 0 4px;
|
||||||
padding: 0 4px;
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
align-items: center;
|
cursor: pointer;
|
||||||
cursor: pointer;
|
margin-bottom: 12px;
|
||||||
margin-bottom: 12px;
|
width: var(--glance-column-width, 20%);
|
||||||
width: var(--glance-column-width, 20%);
|
}
|
||||||
}
|
.entity div {
|
||||||
.entity div {
|
width: 100%;
|
||||||
width: 100%;
|
text-align: center;
|
||||||
text-align: center;
|
white-space: nowrap;
|
||||||
white-space: nowrap;
|
overflow: hidden;
|
||||||
overflow: hidden;
|
text-overflow: ellipsis;
|
||||||
text-overflow: ellipsis;
|
}
|
||||||
}
|
.name {
|
||||||
.name {
|
min-height: var(--paper-font-body1_-_line-height, 20px);
|
||||||
min-height: var(--paper-font-body1_-_line-height, 20px);
|
}
|
||||||
}
|
state-badge {
|
||||||
state-badge {
|
margin: 8px 0;
|
||||||
margin: 8px 0;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,10 +211,14 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<state-badge
|
${this._config!.show_icon !== false
|
||||||
.stateObj="${stateObj}"
|
? html`
|
||||||
.overrideIcon="${entityConf.icon}"
|
<state-badge
|
||||||
></state-badge>
|
.stateObj="${stateObj}"
|
||||||
|
.overrideIcon="${entityConf.icon}"
|
||||||
|
></state-badge>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
${this._config!.show_state !== false
|
${this._config!.show_state !== false
|
||||||
? html`
|
? html`
|
||||||
<div>
|
<div>
|
||||||
@ -232,12 +234,12 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleTap(ev: MouseEvent) {
|
private _handleTap(ev: MouseEvent): void {
|
||||||
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
||||||
handleClick(this, this.hass!, config, false);
|
handleClick(this, this.hass!, config, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleHold(ev: MouseEvent) {
|
private _handleHold(ev: MouseEvent): void {
|
||||||
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
||||||
handleClick(this, this.hass!, config, true);
|
handleClick(this, this.hass!, config, true);
|
||||||
}
|
}
|
||||||
@ -248,5 +250,3 @@ declare global {
|
|||||||
"hui-glance-card": HuiGlanceCard;
|
"hui-glance-card": HuiGlanceCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-glance-card", HuiGlanceCard);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
@ -17,6 +20,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-iframe-card")
|
||||||
export class HuiIframeCard extends LitElement implements LovelaceCard {
|
export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-iframe-card-editor" */ "../editor/config-elements/hui-iframe-card-editor");
|
await import(/* webpackChunkName: "hui-iframe-card-editor" */ "../editor/config-elements/hui-iframe-card-editor");
|
||||||
@ -26,13 +30,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||||||
return { url: "https://www.home-assistant.io", aspect_ratio: "50%" };
|
return { url: "https://www.home-assistant.io", aspect_ratio: "50%" };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _config?: Config;
|
@property() protected _config?: Config;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return {
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
@ -60,7 +58,6 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||||||
const aspectRatio = this._config.aspect_ratio || "50%";
|
const aspectRatio = this._config.aspect_ratio || "50%";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card .header="${this._config.title}">
|
<ha-card .header="${this._config.title}">
|
||||||
<div
|
<div
|
||||||
id="root"
|
id="root"
|
||||||
@ -74,25 +71,25 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
ha-card {
|
||||||
ha-card {
|
overflow: hidden;
|
||||||
overflow: hidden;
|
}
|
||||||
}
|
|
||||||
#root {
|
#root {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
iframe {
|
|
||||||
position: absolute;
|
iframe {
|
||||||
border: none;
|
position: absolute;
|
||||||
width: 100%;
|
border: none;
|
||||||
height: 100%;
|
width: 100%;
|
||||||
top: 0;
|
height: 100%;
|
||||||
left: 0;
|
top: 0;
|
||||||
}
|
left: 0;
|
||||||
</style>
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,5 +99,3 @@ declare global {
|
|||||||
"hui-iframe-card": HuiIframeCard;
|
"hui-iframe-card": HuiIframeCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-iframe-card", HuiIframeCard);
|
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
property,
|
property,
|
||||||
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
theme?: string;
|
theme?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-light-card")
|
||||||
export class HuiLightCard extends LitElement implements LovelaceCard {
|
export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-light-card-editor" */ "../editor/config-elements/hui-light-card-editor");
|
await import(/* webpackChunkName: "hui-light-card-editor" */ "../editor/config-elements/hui-light-card-editor");
|
||||||
@ -55,9 +57,13 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _config?: Config;
|
@property() private _config?: Config;
|
||||||
|
|
||||||
@property() private _roundSliderStyle?: TemplateResult;
|
@property() private _roundSliderStyle?: TemplateResult;
|
||||||
|
|
||||||
@property() private _jQuery?: any;
|
@property() private _jQuery?: any;
|
||||||
|
|
||||||
private _brightnessTimout?: number;
|
private _brightnessTimout?: number;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
@ -183,6 +189,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-card {
|
ha-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -193,6 +200,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
--brightness-font-size: 1.2rem;
|
--brightness-font-size: 1.2rem;
|
||||||
--rail-border-color: transparent;
|
--rail-border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tooltip {
|
#tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -202,6 +210,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 15;
|
z-index: 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-state {
|
.icon-state {
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@ -209,40 +218,50 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
transform: translate(0, 25%);
|
transform: translate(0, 25%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light {
|
#light {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-bar.rs-transition.rs-first,
|
#light .rs-bar.rs-transition.rs-first,
|
||||||
.rs-bar.rs-transition.rs-second {
|
.rs-bar.rs-transition.rs-second {
|
||||||
z-index: 20 !important;
|
z-index: 20 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-range-color {
|
#light .rs-range-color {
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-path-color {
|
#light .rs-path-color {
|
||||||
background-color: var(--disabled-text-color);
|
background-color: var(--disabled-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-handle {
|
#light .rs-handle {
|
||||||
background-color: var(--paper-card-background-color, white);
|
background-color: var(--paper-card-background-color, white);
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
border: 2px solid var(--disabled-text-color);
|
border: 2px solid var(--disabled-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-handle.rs-focus {
|
#light .rs-handle.rs-focus {
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-handle:after {
|
#light .rs-handle:after {
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-border {
|
#light .rs-border {
|
||||||
border-color: var(--rail-border-color);
|
border-color: var(--rail-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#light .rs-inner.rs-bg-color.rs-border,
|
#light .rs-inner.rs-bg-color.rs-border,
|
||||||
#light .rs-overlay.rs-transition.rs-bg-color {
|
#light .rs-overlay.rs-transition.rs-bg-color {
|
||||||
background-color: var(--paper-card-background-color, white);
|
background-color: var(--paper-card-background-color, white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.light-icon {
|
.light-icon {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: 76px;
|
width: 76px;
|
||||||
@ -250,16 +269,20 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
color: var(--paper-item-icon-color, #44739e);
|
color: var(--paper-item-icon-color, #44739e);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light-icon[data-state="on"] {
|
.light-icon[data-state="on"] {
|
||||||
color: var(--paper-item-icon-active-color, #fdd835);
|
color: var(--paper-item-icon-active-color, #fdd835);
|
||||||
}
|
}
|
||||||
|
|
||||||
.light-icon[data-state="unavailable"] {
|
.light-icon[data-state="unavailable"] {
|
||||||
color: var(--state-icon-unavailable-color);
|
color: var(--state-icon-unavailable-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
font-size: var(--name-font-size);
|
font-size: var(--name-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.brightness {
|
.brightness {
|
||||||
font-size: var(--brightness-font-size);
|
font-size: var(--brightness-font-size);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -276,9 +299,11 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
text-shadow: var(--brightness-font-text-shadow);
|
text-shadow: var(--brightness-font-text-shadow);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show_brightness {
|
.show_brightness {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-info {
|
.more-info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -352,5 +377,3 @@ declare global {
|
|||||||
"hui-light-card": HuiLightCard;
|
"hui-light-card": HuiLightCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-light-card", HuiLightCard);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@ -17,22 +20,18 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-markdown-card")
|
||||||
export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-markdown-card-editor" */ "../editor/config-elements/hui-markdown-card-editor");
|
await import(/* webpackChunkName: "hui-markdown-card-editor" */ "../editor/config-elements/hui-markdown-card-editor");
|
||||||
return document.createElement("hui-markdown-card-editor");
|
return document.createElement("hui-markdown-card-editor");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getStubConfig(): object {
|
public static getStubConfig(): object {
|
||||||
return { content: " " };
|
return { content: " " };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _config?: Config;
|
@property() private _config?: Config;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return {
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return this._config!.content.split("\n").length;
|
return this._config!.content.split("\n").length;
|
||||||
@ -52,7 +51,6 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card .header="${this._config.title}">
|
<ha-card .header="${this._config.title}">
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
class="markdown ${classMap({
|
class="markdown ${classMap({
|
||||||
@ -64,35 +62,40 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
:host {
|
||||||
:host {
|
/* start paper-font-headline style */
|
||||||
@apply --paper-font-body1;
|
font-family: "Roboto", "Noto", sans-serif;
|
||||||
}
|
-webkit-font-smoothing: antialiased; /* OS X subpixel AA bleed bug */
|
||||||
ha-markdown {
|
text-rendering: optimizeLegibility;
|
||||||
display: block;
|
font-size: 24px;
|
||||||
padding: 0 16px 16px;
|
font-weight: 400;
|
||||||
-ms-user-select: initial;
|
letter-spacing: -0.012em;
|
||||||
-webkit-user-select: initial;
|
/* end paper-font-headline style */
|
||||||
-moz-user-select: initial;
|
}
|
||||||
}
|
ha-markdown {
|
||||||
.markdown.no-header {
|
display: block;
|
||||||
padding-top: 16px;
|
padding: 0 16px 16px;
|
||||||
}
|
-ms-user-select: initial;
|
||||||
ha-markdown > *:first-child {
|
-webkit-user-select: initial;
|
||||||
margin-top: 0;
|
-moz-user-select: initial;
|
||||||
}
|
}
|
||||||
ha-markdown > *:last-child {
|
.markdown.no-header {
|
||||||
margin-bottom: 0;
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
ha-markdown a {
|
ha-markdown > *:first-child {
|
||||||
color: var(--primary-color);
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
ha-markdown img {
|
ha-markdown > *:last-child {
|
||||||
max-width: 100%;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
ha-markdown a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
ha-markdown img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,5 +105,3 @@ declare global {
|
|||||||
"hui-markdown-card": HuiMarkdownCard;
|
"hui-markdown-card": HuiMarkdownCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-markdown-card", HuiMarkdownCard);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
@ -20,6 +23,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-picture-card")
|
||||||
export class HuiPictureCard extends LitElement implements LovelaceCard {
|
export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-picture-card-editor" */ "../editor/config-elements/hui-picture-card-editor");
|
await import(/* webpackChunkName: "hui-picture-card-editor" */ "../editor/config-elements/hui-picture-card-editor");
|
||||||
@ -35,11 +39,8 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
protected _config?: Config;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() protected _config?: Config;
|
||||||
return { _config: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 3;
|
return 3;
|
||||||
@ -59,7 +60,6 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card
|
<ha-card
|
||||||
@ha-click="${this._handleTap}"
|
@ha-click="${this._handleTap}"
|
||||||
@ha-hold="${this._handleHold}"
|
@ha-hold="${this._handleHold}"
|
||||||
@ -75,20 +75,20 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
ha-card {
|
||||||
ha-card {
|
overflow: hidden;
|
||||||
overflow: hidden;
|
}
|
||||||
}
|
|
||||||
ha-card.clickable {
|
ha-card.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
img {
|
|
||||||
display: block;
|
img {
|
||||||
width: 100%;
|
display: block;
|
||||||
}
|
width: 100%;
|
||||||
</style>
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,5 +106,3 @@ declare global {
|
|||||||
"hui-picture-card": HuiPictureCard;
|
"hui-picture-card": HuiPictureCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-picture-card", HuiPictureCard);
|
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit-element";
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element";
|
import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element";
|
||||||
|
|
||||||
@ -17,15 +25,11 @@ interface Config extends LovelaceCardConfig {
|
|||||||
elements: LovelaceElementConfig[];
|
elements: LovelaceElementConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-picture-elements-card")
|
||||||
class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||||
private _config?: Config;
|
@property() private _config?: Config;
|
||||||
private _hass?: HomeAssistant;
|
|
||||||
|
|
||||||
static get properties() {
|
private _hass?: HomeAssistant;
|
||||||
return {
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
set hass(hass: HomeAssistant) {
|
set hass(hass: HomeAssistant) {
|
||||||
this._hass = hass;
|
this._hass = hass;
|
||||||
@ -60,7 +64,6 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card .header="${this._config.title}">
|
<ha-card .header="${this._config.title}">
|
||||||
<div id="root">
|
<div id="root">
|
||||||
<hui-image
|
<hui-image
|
||||||
@ -84,20 +87,20 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
#root {
|
||||||
#root {
|
position: relative;
|
||||||
position: relative;
|
}
|
||||||
}
|
|
||||||
.element {
|
.element {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
ha-card {
|
|
||||||
overflow: hidden;
|
ha-card {
|
||||||
}
|
overflow: hidden;
|
||||||
</style>
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,5 +110,3 @@ declare global {
|
|||||||
"hui-picture-elements-card": HuiPictureElementsCard;
|
"hui-picture-elements-card": HuiPictureElementsCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-picture-elements-card", HuiPictureElementsCard);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@ -34,16 +37,11 @@ interface Config extends LovelaceCardConfig {
|
|||||||
show_state?: boolean;
|
show_state?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-picture-entity-card")
|
||||||
class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _config?: Config;
|
||||||
return {
|
|
||||||
hass: {},
|
|
||||||
_config: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 3;
|
return 3;
|
||||||
@ -109,7 +107,6 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<hui-image
|
<hui-image
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
@ -132,37 +129,44 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
ha-card {
|
||||||
ha-card {
|
min-height: 75px;
|
||||||
min-height: 75px;
|
overflow: hidden;
|
||||||
overflow: hidden;
|
position: relative;
|
||||||
position: relative;
|
}
|
||||||
}
|
|
||||||
hui-image.clickable {
|
hui-image.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.footer {
|
|
||||||
@apply --paper-font-common-nowrap;
|
.footer {
|
||||||
position: absolute;
|
/* start paper-font-common-nowrap style */
|
||||||
left: 0;
|
white-space: nowrap;
|
||||||
right: 0;
|
overflow: hidden;
|
||||||
bottom: 0;
|
text-overflow: ellipsis;
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
/* end paper-font-common-nowrap style */
|
||||||
padding: 16px;
|
|
||||||
font-size: 16px;
|
position: absolute;
|
||||||
line-height: 16px;
|
left: 0;
|
||||||
color: white;
|
right: 0;
|
||||||
}
|
bottom: 0;
|
||||||
.both {
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
display: flex;
|
padding: 16px;
|
||||||
justify-content: space-between;
|
font-size: 16px;
|
||||||
}
|
line-height: 16px;
|
||||||
.state {
|
color: white;
|
||||||
text-align: right;
|
}
|
||||||
}
|
|
||||||
</style>
|
.both {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,5 +184,3 @@ declare global {
|
|||||||
"hui-picture-entity-card": HuiPictureEntityCard;
|
"hui-picture-entity-card": HuiPictureEntityCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-picture-entity-card", HuiPictureEntityCard);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@ -39,18 +42,15 @@ interface Config extends LovelaceCardConfig {
|
|||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-picture-glance-card")
|
||||||
class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
private _entitiesDialog?: EntityConfig[];
|
|
||||||
private _entitiesToggle?: EntityConfig[];
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _config?: Config;
|
||||||
return {
|
|
||||||
hass: {},
|
private _entitiesDialog?: EntityConfig[];
|
||||||
_config: {},
|
|
||||||
};
|
private _entitiesToggle?: EntityConfig[];
|
||||||
}
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 3;
|
return 3;
|
||||||
@ -91,7 +91,6 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<hui-image
|
<hui-image
|
||||||
class="${classMap({
|
class="${classMap({
|
||||||
@ -177,44 +176,52 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
toggleEntity(this.hass!, (ev.target as any).entity);
|
toggleEntity(this.hass!, (ev.target as any).entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
ha-card {
|
||||||
ha-card {
|
position: relative;
|
||||||
position: relative;
|
min-height: 48px;
|
||||||
min-height: 48px;
|
overflow: hidden;
|
||||||
overflow: hidden;
|
}
|
||||||
}
|
|
||||||
hui-image.clickable {
|
hui-image.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.box {
|
|
||||||
@apply --paper-font-common-nowrap;
|
.box {
|
||||||
position: absolute;
|
/* start paper-font-common-nowrap style */
|
||||||
left: 0;
|
white-space: nowrap;
|
||||||
right: 0;
|
overflow: hidden;
|
||||||
bottom: 0;
|
text-overflow: ellipsis;
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
/* end paper-font-common-nowrap style */
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 16px;
|
position: absolute;
|
||||||
line-height: 40px;
|
left: 0;
|
||||||
color: white;
|
right: 0;
|
||||||
display: flex;
|
bottom: 0;
|
||||||
justify-content: space-between;
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
}
|
padding: 4px 8px;
|
||||||
.box .title {
|
font-size: 16px;
|
||||||
font-weight: 500;
|
line-height: 40px;
|
||||||
margin-left: 8px;
|
color: white;
|
||||||
}
|
display: flex;
|
||||||
ha-icon {
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
}
|
||||||
padding: 8px;
|
|
||||||
color: #a9a9a9;
|
.box .title {
|
||||||
}
|
font-weight: 500;
|
||||||
ha-icon.state-on {
|
margin-left: 8px;
|
||||||
color: white;
|
}
|
||||||
}
|
|
||||||
</style>
|
ha-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
color: #a9a9a9;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-icon.state-on {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,5 +231,3 @@ declare global {
|
|||||||
"hui-picture-glance-card": HuiPictureGlanceCard;
|
"hui-picture-glance-card": HuiPictureGlanceCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-picture-glance-card", HuiPictureGlanceCard);
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import "../../../cards/ha-plant-card";
|
|
||||||
|
|
||||||
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
|
|
||||||
|
|
||||||
// should be interface when converted to TS
|
|
||||||
export const Config = {
|
|
||||||
name: "",
|
|
||||||
entity: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
class HuiPlantStatusCard extends LegacyWrapperCard {
|
|
||||||
static async getConfigElement() {
|
|
||||||
await import(/* webpackChunkName: "hui-plant-status-card-editor" */ "../editor/config-elements/hui-plant-status-card-editor");
|
|
||||||
return document.createElement("hui-plant-status-card-editor");
|
|
||||||
}
|
|
||||||
|
|
||||||
static getStubConfig() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super("ha-plant-card", "plant");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hui-plant-status-card", HuiPlantStatusCard);
|
|
237
src/panels/lovelace/cards/hui-plant-status-card.ts
Normal file
237
src/panels/lovelace/cards/hui-plant-status-card.ts
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import "../../../components/ha-icon";
|
||||||
|
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
|
|
||||||
|
import { LovelaceCardEditor, LovelaceCard } from "../types";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
|
const SENSORS = {
|
||||||
|
moisture: "hass:water",
|
||||||
|
temperature: "hass:thermometer",
|
||||||
|
brightness: "hass:white-balance-sunny",
|
||||||
|
conductivity: "hass:emoticon-poop",
|
||||||
|
battery: "hass:battery",
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PlantAttributeTarget extends EventTarget {
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlantStatusConfig extends LovelaceCardConfig {
|
||||||
|
name?: string;
|
||||||
|
entity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("hui-plant-status-card")
|
||||||
|
class HuiPlantStatusCard extends LitElement implements LovelaceCard {
|
||||||
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
|
await import(/* webpackChunkName: "hui-plant-status-card-editor" */ "../editor/config-elements/hui-plant-status-card-editor");
|
||||||
|
return document.createElement("hui-plant-status-card-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getStubConfig(): object {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private _config?: PlantStatusConfig;
|
||||||
|
|
||||||
|
public getCardSize(): number {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: PlantStatusConfig): void {
|
||||||
|
if (!config.entity || config.entity.split(".")[0] !== "plant") {
|
||||||
|
throw new Error("Specify an entity from within the plant domain.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this.hass || !this._config) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._config!.entity];
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return html`
|
||||||
|
<hui-warning
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.warning.entity_not_found",
|
||||||
|
"entity",
|
||||||
|
this._config.entity
|
||||||
|
)}</hui-warning
|
||||||
|
>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card
|
||||||
|
class="${stateObj.attributes.entity_picture ? "has-plant-image" : ""}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="banner"
|
||||||
|
style="background-image:url(${stateObj.attributes.entity_picture})"
|
||||||
|
>
|
||||||
|
<div class="header">
|
||||||
|
${this._config.title || computeStateName(stateObj)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
${this.computeAttributes(stateObj).map(
|
||||||
|
(item) => html`
|
||||||
|
<div
|
||||||
|
class="attributes"
|
||||||
|
@click="${this._handleMoreInfo}"
|
||||||
|
.value="${item}"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ha-icon
|
||||||
|
icon="${this.computeIcon(
|
||||||
|
item,
|
||||||
|
stateObj.attributes.battery
|
||||||
|
)}"
|
||||||
|
></ha-icon>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="${stateObj.attributes.problem.indexOf(item) === -1
|
||||||
|
? ""
|
||||||
|
: "problem"}"
|
||||||
|
>
|
||||||
|
${stateObj.attributes[item]}
|
||||||
|
</div>
|
||||||
|
<div class="uom">
|
||||||
|
${stateObj.attributes.unit_of_measurement_dict[item] || ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-plant-image .banner {
|
||||||
|
padding-top: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
/* start paper-font-headline style */
|
||||||
|
font-family: "Roboto", "Noto", sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased; /* OS X subpixel AA bleed bug */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: -0.012em;
|
||||||
|
/* end paper-font-headline style */
|
||||||
|
|
||||||
|
line-height: 40px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-plant-image .header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
color: white;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(0, 0, 0, var(--dark-secondary-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 32px 24px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-plant-image .content {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-icon {
|
||||||
|
color: var(--paper-item-icon-color);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes div {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uom {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeAttributes(stateObj: HassEntity): string[] {
|
||||||
|
return Object.keys(SENSORS).filter((key) => key in stateObj.attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeIcon(attr: string, batLvl: number): string {
|
||||||
|
const icon = SENSORS[attr];
|
||||||
|
if (attr === "battery") {
|
||||||
|
if (batLvl <= 5) {
|
||||||
|
return `${icon}-alert`;
|
||||||
|
}
|
||||||
|
if (batLvl < 95) {
|
||||||
|
return `${icon}-${Math.round(batLvl / 10 - 0.01) * 10}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleMoreInfo(ev: Event): void {
|
||||||
|
const target = ev.currentTarget! as PlantAttributeTarget;
|
||||||
|
const stateObj = this.hass!.states[this._config!.entity];
|
||||||
|
|
||||||
|
if (target.value) {
|
||||||
|
fireEvent(this, "hass-more-info", {
|
||||||
|
entityId: stateObj.attributes.sensors[target.value],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-plant-status-card": HuiPlantStatusCard;
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,12 @@ import {
|
|||||||
html,
|
html,
|
||||||
svg,
|
svg,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-spinner/paper-spinner";
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
|
|
||||||
@ -145,6 +148,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
hours_to_show?: number;
|
hours_to_show?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-sensor-card")
|
||||||
class HuiSensorCard extends LitElement implements LovelaceCard {
|
class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-sensor-card-editor" */ "../editor/config-elements/hui-sensor-card-editor");
|
await import(/* webpackChunkName: "hui-sensor-card-editor" */ "../editor/config-elements/hui-sensor-card-editor");
|
||||||
@ -155,18 +159,13 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
private _history?: any;
|
|
||||||
private _date?: Date;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _config?: Config;
|
||||||
return {
|
|
||||||
hass: {},
|
@property() private _history?: any;
|
||||||
_config: {},
|
|
||||||
_history: {},
|
private _date?: Date;
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public setConfig(config: Config): void {
|
public setConfig(config: Config): void {
|
||||||
if (!config.entity || config.entity.split(".")[0] !== "sensor") {
|
if (!config.entity || config.entity.split(".")[0] !== "sensor") {
|
||||||
@ -244,7 +243,6 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
|||||||
graph = "";
|
graph = "";
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<ha-card @click="${this._handleClick}">
|
<ha-card @click="${this._handleClick}">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
@ -324,87 +322,95 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
|||||||
this._date = new Date();
|
this._date = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
:host {
|
||||||
:host {
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
}
|
||||||
}
|
|
||||||
ha-card {
|
ha-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.flex {
|
|
||||||
display: flex;
|
.flex {
|
||||||
}
|
display: flex;
|
||||||
.header {
|
}
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
.header {
|
||||||
min-width: 0;
|
align-items: center;
|
||||||
opacity: 0.8;
|
display: flex;
|
||||||
position: relative;
|
min-width: 0;
|
||||||
}
|
opacity: 0.8;
|
||||||
.name {
|
position: relative;
|
||||||
display: block;
|
}
|
||||||
display: -webkit-box;
|
|
||||||
font-size: 1.2rem;
|
.name {
|
||||||
font-weight: 500;
|
display: block;
|
||||||
max-height: 1.4rem;
|
display: -webkit-box;
|
||||||
margin-top: 2px;
|
font-size: 1.2rem;
|
||||||
opacity: 0.8;
|
font-weight: 500;
|
||||||
overflow: hidden;
|
max-height: 1.4rem;
|
||||||
text-overflow: ellipsis;
|
margin-top: 2px;
|
||||||
-webkit-line-clamp: 1;
|
opacity: 0.8;
|
||||||
-webkit-box-orient: vertical;
|
overflow: hidden;
|
||||||
word-wrap: break-word;
|
text-overflow: ellipsis;
|
||||||
word-break: break-all;
|
-webkit-line-clamp: 1;
|
||||||
}
|
-webkit-box-orient: vertical;
|
||||||
.icon {
|
word-wrap: break-word;
|
||||||
color: var(--paper-item-icon-color, #44739e);
|
word-break: break-all;
|
||||||
display: inline-block;
|
}
|
||||||
flex: 0 0 40px;
|
|
||||||
line-height: 40px;
|
.icon {
|
||||||
position: relative;
|
color: var(--paper-item-icon-color, #44739e);
|
||||||
text-align: center;
|
display: inline-block;
|
||||||
width: 40px;
|
flex: 0 0 40px;
|
||||||
}
|
line-height: 40px;
|
||||||
.info {
|
position: relative;
|
||||||
flex-wrap: wrap;
|
text-align: center;
|
||||||
margin: 16px 0 16px 8px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
#value {
|
|
||||||
display: inline-block;
|
.info {
|
||||||
font-size: 2rem;
|
flex-wrap: wrap;
|
||||||
font-weight: 400;
|
margin: 16px 0 16px 8px;
|
||||||
line-height: 1em;
|
}
|
||||||
margin-right: 4px;
|
|
||||||
}
|
#value {
|
||||||
#measurement {
|
display: inline-block;
|
||||||
align-self: flex-end;
|
font-size: 2rem;
|
||||||
display: inline-block;
|
font-weight: 400;
|
||||||
font-size: 1.3rem;
|
line-height: 1em;
|
||||||
line-height: 1.2em;
|
margin-right: 4px;
|
||||||
margin-top: 0.1em;
|
}
|
||||||
opacity: 0.6;
|
|
||||||
vertical-align: bottom;
|
#measurement {
|
||||||
}
|
align-self: flex-end;
|
||||||
.graph {
|
display: inline-block;
|
||||||
align-self: flex-end;
|
font-size: 1.3rem;
|
||||||
margin: auto;
|
line-height: 1.2em;
|
||||||
margin-bottom: 0px;
|
margin-top: 0.1em;
|
||||||
position: relative;
|
opacity: 0.6;
|
||||||
width: 100%;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
.graph > div {
|
|
||||||
align-self: flex-end;
|
.graph {
|
||||||
margin: auto 8px;
|
align-self: flex-end;
|
||||||
}
|
margin: auto;
|
||||||
</style>
|
margin-bottom: 0px;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph > div {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin: auto 8px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,5 +420,3 @@ declare global {
|
|||||||
"hui-sensor-card": HuiSensorCard;
|
"hui-sensor-card": HuiSensorCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-sensor-card", HuiSensorCard);
|
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
property,
|
property,
|
||||||
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { repeat } from "lit-html/directives/repeat";
|
import { repeat } from "lit-html/directives/repeat";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
@ -28,6 +29,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-shopping-list-card")
|
||||||
class HuiShoppingListCard extends LitElement implements LovelaceCard {
|
class HuiShoppingListCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-shopping-list-editor" */ "../editor/config-elements/hui-shopping-list-editor");
|
await import(/* webpackChunkName: "hui-shopping-list-editor" */ "../editor/config-elements/hui-shopping-list-editor");
|
||||||
@ -39,9 +41,13 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _config?: Config;
|
@property() private _config?: Config;
|
||||||
|
|
||||||
@property() private _uncheckedItems?: ShoppingListItem[];
|
@property() private _uncheckedItems?: ShoppingListItem[];
|
||||||
|
|
||||||
@property() private _checkedItems?: ShoppingListItem[];
|
@property() private _checkedItems?: ShoppingListItem[];
|
||||||
|
|
||||||
private _unsubEvents?: Promise<() => Promise<void>>;
|
private _unsubEvents?: Promise<() => Promise<void>>;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
@ -178,61 +184,68 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult {
|
||||||
return [
|
return css`
|
||||||
css`
|
.editRow,
|
||||||
.editRow,
|
.addRow {
|
||||||
.addRow {
|
display: flex;
|
||||||
display: flex;
|
flex-direction: row;
|
||||||
flex-direction: row;
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
padding: 9px 15px 11px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-item-body {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-checkbox {
|
||||||
|
padding: 11px 11px 11px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-input {
|
||||||
|
--paper-input-container-underline: {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
.addButton {
|
--paper-input-container-underline-focus: {
|
||||||
padding: 9px 15px 11px 15px;
|
display: none;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
paper-item-body {
|
--paper-input-container-underline-disabled: {
|
||||||
width: 75%;
|
display: none;
|
||||||
}
|
}
|
||||||
paper-checkbox {
|
position: relative;
|
||||||
padding: 11px 11px 11px 18px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
paper-input {
|
|
||||||
--paper-input-container-underline: {
|
.checked {
|
||||||
display: none;
|
margin-left: 17px;
|
||||||
}
|
margin-bottom: 11px;
|
||||||
--paper-input-container-underline-focus: {
|
margin-top: 11px;
|
||||||
display: none;
|
}
|
||||||
}
|
|
||||||
--paper-input-container-underline-disabled: {
|
.label {
|
||||||
display: none;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
.divider {
|
||||||
}
|
height: 1px;
|
||||||
.checked {
|
background-color: var(--divider-color);
|
||||||
margin-left: 17px;
|
margin: 10px;
|
||||||
margin-bottom: 11px;
|
}
|
||||||
margin-top: 11px;
|
|
||||||
}
|
.clearall {
|
||||||
.label {
|
cursor: pointer;
|
||||||
color: var(--primary-color);
|
margin-bottom: 3px;
|
||||||
}
|
float: right;
|
||||||
.divider {
|
padding-right: 10px;
|
||||||
height: 1px;
|
}
|
||||||
background-color: var(--divider-color);
|
|
||||||
margin: 10px;
|
.addRow > ha-icon {
|
||||||
}
|
color: var(--secondary-text-color);
|
||||||
.clearall {
|
}
|
||||||
cursor: pointer;
|
`;
|
||||||
margin-bottom: 3px;
|
|
||||||
float: right;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
.addRow > ha-icon {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchData(): Promise<void> {
|
private async _fetchData(): Promise<void> {
|
||||||
@ -301,5 +314,3 @@ declare global {
|
|||||||
"hui-shopping-list-card": HuiShoppingListCard;
|
"hui-shopping-list-card": HuiShoppingListCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-shopping-list-card", HuiShoppingListCard);
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
@ -52,6 +53,7 @@ export interface Config extends LovelaceCardConfig {
|
|||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-thermostat-card")
|
||||||
export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import(/* webpackChunkName: "hui-thermostat-card-editor" */ "../editor/config-elements/hui-thermostat-card-editor");
|
await import(/* webpackChunkName: "hui-thermostat-card-editor" */ "../editor/config-elements/hui-thermostat-card-editor");
|
||||||
@ -62,22 +64,19 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
return { entity: "" };
|
return { entity: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
private _roundSliderStyle?: TemplateResult;
|
|
||||||
private _jQuery?: any;
|
|
||||||
private _broadCard?: boolean;
|
|
||||||
private _loaded?: boolean;
|
|
||||||
private _updated?: boolean;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _config?: Config;
|
||||||
return {
|
|
||||||
hass: {},
|
@property() private _roundSliderStyle?: TemplateResult;
|
||||||
_config: {},
|
|
||||||
roundSliderStyle: {},
|
@property() private _jQuery?: any;
|
||||||
_jQuery: {},
|
|
||||||
};
|
private _broadCard?: boolean;
|
||||||
}
|
|
||||||
|
private _loaded?: boolean;
|
||||||
|
|
||||||
|
private _updated?: boolean;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 4;
|
return 4;
|
||||||
@ -574,5 +573,3 @@ declare global {
|
|||||||
"hui-thermostat-card": HuiThermostatCard;
|
"hui-thermostat-card": HuiThermostatCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-thermostat-card", HuiThermostatCard);
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-input/paper-textarea";
|
import "@polymer/paper-input/paper-textarea";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
@ -31,15 +32,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-action-editor")
|
||||||
export class HuiActionEditor extends LitElement {
|
export class HuiActionEditor extends LitElement {
|
||||||
public config?: ActionConfig;
|
@property() public config?: ActionConfig;
|
||||||
public label?: string;
|
|
||||||
public actions?: string[];
|
|
||||||
protected hass?: HomeAssistant;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() public label?: string;
|
||||||
return { hass: {}, config: {}, label: {}, actions: {} };
|
|
||||||
}
|
@property() public actions?: string[];
|
||||||
|
|
||||||
|
@property() protected hass?: HomeAssistant;
|
||||||
|
|
||||||
get _action(): string {
|
get _action(): string {
|
||||||
return this.config!.action || "";
|
return this.config!.action || "";
|
||||||
@ -126,5 +127,3 @@ declare global {
|
|||||||
"hui-action-editor": HuiActionEditor;
|
"hui-action-editor": HuiActionEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-action-editor", HuiActionEditor);
|
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { html, LitElement, PropertyDeclarations } from "lit-element";
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-menu-button/paper-menu-button";
|
import "@polymer/paper-menu-button/paper-menu-button";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
@ -12,63 +20,18 @@ import { Lovelace } from "../types";
|
|||||||
import { swapCard } from "../editor/config-util";
|
import { swapCard } from "../editor/config-util";
|
||||||
import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog";
|
import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog";
|
||||||
|
|
||||||
|
@customElement("hui-card-options")
|
||||||
export class HuiCardOptions extends LitElement {
|
export class HuiCardOptions extends LitElement {
|
||||||
public cardConfig?: LovelaceCardConfig;
|
public cardConfig?: LovelaceCardConfig;
|
||||||
public hass?: HomeAssistant;
|
|
||||||
public lovelace?: Lovelace;
|
|
||||||
public path?: [number, number];
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() public hass?: HomeAssistant;
|
||||||
return { hass: {}, lovelace: {}, path: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
@property() public lovelace?: Lovelace;
|
||||||
|
|
||||||
|
@property() public path?: [number, number];
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
|
||||||
div.options {
|
|
||||||
border-top: 1px solid #e8e8e8;
|
|
||||||
padding: 5px 8px;
|
|
||||||
background: var(--paper-card-background-color, white);
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px,
|
|
||||||
rgba(0, 0, 0, 0.12) 0px 1px 5px -4px,
|
|
||||||
rgba(0, 0, 0, 0.2) 0px 3px 1px -2px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.options .primary-actions {
|
|
||||||
flex: 1;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.options .secondary-actions {
|
|
||||||
flex: 4;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-button {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-button.move-arrow[disabled] {
|
|
||||||
color: var(--disabled-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-menu-button {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-item.header {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<div class="primary-actions">
|
<div class="primary-actions">
|
||||||
@ -122,6 +85,54 @@ export class HuiCardOptions extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
div.options {
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
padding: 5px 8px;
|
||||||
|
background: var(--paper-card-background-color, white);
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px,
|
||||||
|
rgba(0, 0, 0, 0.12) 0px 1px 5px -4px,
|
||||||
|
rgba(0, 0, 0, 0.2) 0px 3px 1px -2px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.options .primary-actions {
|
||||||
|
flex: 1;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.options .secondary-actions {
|
||||||
|
flex: 4;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-icon-button {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-icon-button.move-arrow[disabled] {
|
||||||
|
color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-menu-button {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-item.header {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private _editCard(): void {
|
private _editCard(): void {
|
||||||
showEditCardDialog(this, {
|
showEditCardDialog(this, {
|
||||||
lovelace: this.lovelace!,
|
lovelace: this.lovelace!,
|
||||||
@ -162,5 +173,3 @@ declare global {
|
|||||||
"hui-card-options": HuiCardOptions;
|
"hui-card-options": HuiCardOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-card-options", HuiCardOptions);
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
|
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
|
||||||
|
|
||||||
@ -11,20 +14,15 @@ import { DOMAINS_TOGGLE } from "../../../common/const";
|
|||||||
import { turnOnOffEntities } from "../common/entity/turn-on-off-entities";
|
import { turnOnOffEntities } from "../common/entity/turn-on-off-entities";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
@customElement("hui-entities-toggle")
|
||||||
class HuiEntitiesToggle extends LitElement {
|
class HuiEntitiesToggle extends LitElement {
|
||||||
public entities?: string[];
|
@property() public entities?: string[];
|
||||||
protected hass?: HomeAssistant;
|
|
||||||
private _toggleEntities?: string[];
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() protected hass?: HomeAssistant;
|
||||||
return {
|
|
||||||
hass: {},
|
|
||||||
entities: {},
|
|
||||||
_toggleEntities: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public updated(changedProperties: PropertyValues) {
|
@property() private _toggleEntities?: string[];
|
||||||
|
|
||||||
|
public updated(changedProperties: PropertyValues): void {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has("entities")) {
|
if (changedProperties.has("entities")) {
|
||||||
this._toggleEntities = this.entities!.filter(
|
this._toggleEntities = this.entities!.filter(
|
||||||
@ -41,7 +39,6 @@ class HuiEntitiesToggle extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<paper-toggle-button
|
<paper-toggle-button
|
||||||
?checked="${this._toggleEntities!.some((entityId) => {
|
?checked="${this._toggleEntities!.some((entityId) => {
|
||||||
const stateObj = this.hass!.states[entityId];
|
const stateObj = this.hass!.states[entityId];
|
||||||
@ -52,20 +49,18 @@ class HuiEntitiesToggle extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
:host {
|
||||||
:host {
|
width: 38px;
|
||||||
width: 38px;
|
display: block;
|
||||||
display: block;
|
}
|
||||||
}
|
paper-toggle-button {
|
||||||
paper-toggle-button {
|
cursor: pointer;
|
||||||
cursor: pointer;
|
--paper-toggle-button-label-spacing: 0;
|
||||||
--paper-toggle-button-label-spacing: 0;
|
padding: 13px 5px;
|
||||||
padding: 13px 5px;
|
margin: -4px -5px;
|
||||||
margin: -4px -5px;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,5 +75,3 @@ declare global {
|
|||||||
"hui-entities-toggle": HuiEntitiesToggle;
|
"hui-entities-toggle": HuiEntitiesToggle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-entities-toggle", HuiEntitiesToggle);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -12,16 +15,11 @@ import { EntityConfig } from "../entity-rows/types";
|
|||||||
import "../../../components/entity/ha-entity-picker";
|
import "../../../components/entity/ha-entity-picker";
|
||||||
import { EditorTarget } from "../editor/types";
|
import { EditorTarget } from "../editor/types";
|
||||||
|
|
||||||
|
@customElement("hui-entity-editor")
|
||||||
export class HuiEntityEditor extends LitElement {
|
export class HuiEntityEditor extends LitElement {
|
||||||
protected hass?: HomeAssistant;
|
@property() protected hass?: HomeAssistant;
|
||||||
protected entities?: EntityConfig[];
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() protected entities?: EntityConfig[];
|
||||||
return {
|
|
||||||
hass: {},
|
|
||||||
entities: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.entities) {
|
if (!this.entities) {
|
||||||
@ -29,7 +27,6 @@ export class HuiEntityEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<h3>Entities</h3>
|
<h3>Entities</h3>
|
||||||
<div class="entities">
|
<div class="entities">
|
||||||
${this.entities.map((entityConf, index) => {
|
${this.entities.map((entityConf, index) => {
|
||||||
@ -79,13 +76,11 @@ export class HuiEntityEditor extends LitElement {
|
|||||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
.entities {
|
||||||
.entities {
|
padding-left: 20px;
|
||||||
padding-left: 20px;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,5 +90,3 @@ declare global {
|
|||||||
"hui-entity-editor": HuiEntityEditor;
|
"hui-entity-editor": HuiEntityEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-entity-editor", HuiEntityEditor);
|
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
property,
|
property,
|
||||||
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -19,10 +20,12 @@ import "../components/hui-warning";
|
|||||||
|
|
||||||
class HuiGenericEntityRow extends LitElement {
|
class HuiGenericEntityRow extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public config?: EntitiesCardEntityConfig;
|
@property() public config?: EntitiesCardEntityConfig;
|
||||||
|
|
||||||
@property() public showSecondary: boolean = true;
|
@property() public showSecondary: boolean = true;
|
||||||
|
|
||||||
protected render() {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this.config) {
|
if (!this.hass || !this.config) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
@ -73,7 +76,7 @@ class HuiGenericEntityRow extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (changedProps.has("hass")) {
|
if (changedProps.has("hass")) {
|
||||||
this.toggleAttribute("rtl", computeRTL(this.hass!));
|
this.toggleAttribute("rtl", computeRTL(this.hass!));
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
query,
|
query,
|
||||||
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
@ -26,33 +27,43 @@ export interface StateSpecificConfig {
|
|||||||
[state: string]: string;
|
[state: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
@customElement("hui-image")
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class HuiImage extends LitElement {
|
class HuiImage extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public entity?: string;
|
@property() public entity?: string;
|
||||||
|
|
||||||
@property() public image?: string;
|
@property() public image?: string;
|
||||||
|
|
||||||
@property() public stateImage?: StateSpecificConfig;
|
@property() public stateImage?: StateSpecificConfig;
|
||||||
|
|
||||||
@property() public cameraImage?: string;
|
@property() public cameraImage?: string;
|
||||||
|
|
||||||
@property() public aspectRatio?: string;
|
@property() public aspectRatio?: string;
|
||||||
|
|
||||||
@property() public filter?: string;
|
@property() public filter?: string;
|
||||||
|
|
||||||
@property() public stateFilter?: StateSpecificConfig;
|
@property() public stateFilter?: StateSpecificConfig;
|
||||||
|
|
||||||
@property() private _loadError?: boolean;
|
@property() private _loadError?: boolean;
|
||||||
|
|
||||||
@property() private _cameraImageSrc?: string;
|
@property() private _cameraImageSrc?: string;
|
||||||
|
|
||||||
@query("img") private _image!: HTMLImageElement;
|
@query("img") private _image!: HTMLImageElement;
|
||||||
|
|
||||||
private _lastImageHeight?: number;
|
private _lastImageHeight?: number;
|
||||||
|
|
||||||
private _cameraUpdater?: number;
|
private _cameraUpdater?: number;
|
||||||
|
|
||||||
private _attached?: boolean;
|
private _attached?: boolean;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._attached = true;
|
this._attached = true;
|
||||||
this._startUpdateCameraInterval();
|
this._startUpdateCameraInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._attached = false;
|
this._attached = false;
|
||||||
this._stopUpdateCameraInterval();
|
this._stopUpdateCameraInterval();
|
||||||
@ -137,7 +148,7 @@ class HuiImage extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startUpdateCameraInterval() {
|
private _startUpdateCameraInterval(): void {
|
||||||
this._stopUpdateCameraInterval();
|
this._stopUpdateCameraInterval();
|
||||||
if (this.cameraImage && this._attached) {
|
if (this.cameraImage && this._attached) {
|
||||||
this._cameraUpdater = window.setInterval(
|
this._cameraUpdater = window.setInterval(
|
||||||
@ -147,23 +158,23 @@ class HuiImage extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _stopUpdateCameraInterval() {
|
private _stopUpdateCameraInterval(): void {
|
||||||
if (this._cameraUpdater) {
|
if (this._cameraUpdater) {
|
||||||
clearInterval(this._cameraUpdater);
|
clearInterval(this._cameraUpdater);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onImageError() {
|
private _onImageError(): void {
|
||||||
this._loadError = true;
|
this._loadError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onImageLoad() {
|
private async _onImageLoad(): Promise<void> {
|
||||||
this._loadError = false;
|
this._loadError = false;
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
this._lastImageHeight = this._image.offsetHeight;
|
this._lastImageHeight = this._image.offsetHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateCameraImageSrc() {
|
private async _updateCameraImageSrc(): Promise<void> {
|
||||||
if (!this.hass || !this.cameraImage) {
|
if (!this.hass || !this.cameraImage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -221,5 +232,3 @@ declare global {
|
|||||||
"hui-image": HuiImage;
|
"hui-image": HuiImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-image", HuiImage);
|
|
||||||
|
115
src/panels/lovelace/components/hui-input-list-editor.ts
Normal file
115
src/panels/lovelace/components/hui-input-list-editor.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
|
import { EditorTarget } from "../editor/types";
|
||||||
|
|
||||||
|
@customElement("hui-input-list-editor")
|
||||||
|
export class HuiInputListEditor extends LitElement {
|
||||||
|
@property() protected value?: string[];
|
||||||
|
@property() protected hass?: HomeAssistant;
|
||||||
|
@property() protected inputLabel?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this.value) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${this.value.map((listEntry, index) => {
|
||||||
|
return html`
|
||||||
|
<paper-input
|
||||||
|
label="${this.inputLabel}"
|
||||||
|
.value="${listEntry}"
|
||||||
|
.configValue="${"entry"}"
|
||||||
|
.index="${index}"
|
||||||
|
@value-changed="${this._valueChanged}"
|
||||||
|
@blur="${this._consolidateEntries}"
|
||||||
|
><paper-icon-button
|
||||||
|
slot="suffix"
|
||||||
|
class="clear-button"
|
||||||
|
icon="hass:close"
|
||||||
|
no-ripple
|
||||||
|
@click="${this._removeEntry}"
|
||||||
|
>Clear</paper-icon-button
|
||||||
|
></paper-input
|
||||||
|
>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<paper-input
|
||||||
|
label="${this.inputLabel}"
|
||||||
|
@change="${this._addEntry}"
|
||||||
|
></paper-input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addEntry(ev: Event): void {
|
||||||
|
const target = ev.target! as EditorTarget;
|
||||||
|
if (target.value === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newEntries = this.value!.concat(target.value as string);
|
||||||
|
target.value = "";
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newEntries,
|
||||||
|
});
|
||||||
|
(ev.target! as LitElement).blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: Event): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const target = ev.target! as EditorTarget;
|
||||||
|
const newEntries = this.value!.concat();
|
||||||
|
newEntries[target.index!] = target.value!;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newEntries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _consolidateEntries(ev: Event): void {
|
||||||
|
const target = ev.target! as EditorTarget;
|
||||||
|
if (target.value === "") {
|
||||||
|
const newEntries = this.value!.concat();
|
||||||
|
newEntries.splice(target.index!, 1);
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newEntries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeEntry(ev: Event): void {
|
||||||
|
const parent = (ev.currentTarget as any).parentElement;
|
||||||
|
const newEntries = this.value!.concat();
|
||||||
|
newEntries.splice(parent.index!, 1);
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newEntries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
paper-input > paper-icon-button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-input-list-editor": HuiInputListEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
|
||||||
@ -20,16 +23,11 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HuiThemeSelectionEditor extends LitElement {
|
@customElement("hui-theme-select-editor")
|
||||||
public value?: string;
|
export class HuiThemeSelectEditor extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
@property() public value?: string;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() public hass?: HomeAssistant;
|
||||||
return {
|
|
||||||
hass: {},
|
|
||||||
value: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
const themes = ["Backend-selected", "default"].concat(
|
const themes = ["Backend-selected", "default"].concat(
|
||||||
@ -37,7 +35,6 @@ export class HuiThemeSelectionEditor extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
|
||||||
<paper-dropdown-menu
|
<paper-dropdown-menu
|
||||||
label="Theme"
|
label="Theme"
|
||||||
dynamic-align
|
dynamic-align
|
||||||
@ -58,13 +55,11 @@ export class HuiThemeSelectionEditor extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
paper-dropdown-menu {
|
||||||
paper-dropdown-menu {
|
width: 100%;
|
||||||
width: 100%;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +74,6 @@ export class HuiThemeSelectionEditor extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"hui-theme-select-editor": HuiThemeSelectionEditor;
|
"hui-theme-select-editor": HuiThemeSelectEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-theme-select-editor", HuiThemeSelectionEditor);
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -19,30 +20,32 @@ const FORMATS: { [key: string]: (ts: Date, lang: string) => string } = {
|
|||||||
};
|
};
|
||||||
const INTERVAL_FORMAT = ["relative", "total"];
|
const INTERVAL_FORMAT = ["relative", "total"];
|
||||||
|
|
||||||
|
@customElement("hui-timestamp-display")
|
||||||
class HuiTimestampDisplay extends LitElement {
|
class HuiTimestampDisplay extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
public ts?: Date;
|
|
||||||
public format?: "relative" | "total" | "date" | "datetime" | "time";
|
@property() public ts?: Date;
|
||||||
private _relative?: string;
|
|
||||||
|
@property() public format?:
|
||||||
|
| "relative"
|
||||||
|
| "total"
|
||||||
|
| "date"
|
||||||
|
| "datetime"
|
||||||
|
| "time";
|
||||||
|
|
||||||
|
@property() private _relative?: string;
|
||||||
|
|
||||||
private _connected?: boolean;
|
private _connected?: boolean;
|
||||||
|
|
||||||
private _interval?: number;
|
private _interval?: number;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
public connectedCallback(): void {
|
||||||
return {
|
|
||||||
ts: {},
|
|
||||||
hass: {},
|
|
||||||
format: {},
|
|
||||||
_relative: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._connected = true;
|
this._connected = true;
|
||||||
this._startInterval();
|
this._startInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
this._clearInterval();
|
this._clearInterval();
|
||||||
@ -65,18 +68,18 @@ class HuiTimestampDisplay extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
${this._relative}
|
${this._relative}
|
||||||
`;
|
`;
|
||||||
} else if (format in FORMATS) {
|
}
|
||||||
|
if (format in FORMATS) {
|
||||||
return html`
|
return html`
|
||||||
${FORMATS[format](this.ts, this.hass.language)}
|
${FORMATS[format](this.ts, this.hass.language)}
|
||||||
`;
|
`;
|
||||||
} else {
|
|
||||||
return html`
|
|
||||||
Invalid format
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
return html`
|
||||||
|
Invalid format
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (!changedProperties.has("format") || !this._connected) {
|
if (!changedProperties.has("format") || !this._connected) {
|
||||||
return;
|
return;
|
||||||
@ -89,11 +92,11 @@ class HuiTimestampDisplay extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _format() {
|
private get _format(): string {
|
||||||
return this.format || "relative";
|
return this.format || "relative";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startInterval() {
|
private _startInterval(): void {
|
||||||
this._clearInterval();
|
this._clearInterval();
|
||||||
if (this._connected && INTERVAL_FORMAT.includes(this._format)) {
|
if (this._connected && INTERVAL_FORMAT.includes(this._format)) {
|
||||||
this._updateRelative();
|
this._updateRelative();
|
||||||
@ -101,14 +104,14 @@ class HuiTimestampDisplay extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearInterval() {
|
private _clearInterval(): void {
|
||||||
if (this._interval) {
|
if (this._interval) {
|
||||||
clearInterval(this._interval);
|
clearInterval(this._interval);
|
||||||
this._interval = undefined;
|
this._interval = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateRelative() {
|
private _updateRelative(): void {
|
||||||
if (this.ts && this.hass!.localize) {
|
if (this.ts && this.hass!.localize) {
|
||||||
this._relative =
|
this._relative =
|
||||||
this._format === "relative"
|
this._format === "relative"
|
||||||
@ -126,5 +129,3 @@ declare global {
|
|||||||
"hui-timestamp-display": HuiTimestampDisplay;
|
"hui-timestamp-display": HuiTimestampDisplay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-timestamp-display", HuiTimestampDisplay);
|
|
||||||
|
@ -6,6 +6,7 @@ import codeMirrorCSS from "codemirror/lib/codemirror.css";
|
|||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -16,9 +17,12 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-yaml-editor")
|
||||||
export class HuiYamlEditor extends HTMLElement {
|
export class HuiYamlEditor extends HTMLElement {
|
||||||
public _hass?: HomeAssistant;
|
public _hass?: HomeAssistant;
|
||||||
|
|
||||||
public codemirror: CodeMirror;
|
public codemirror: CodeMirror;
|
||||||
|
|
||||||
private _value: string;
|
private _value: string;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
@ -108,7 +112,7 @@ export class HuiYamlEditor extends HTMLElement {
|
|||||||
fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
|
fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
|
||||||
}
|
}
|
||||||
|
|
||||||
private setScrollBarDirection() {
|
private setScrollBarDirection(): void {
|
||||||
if (!this.codemirror) {
|
if (!this.codemirror) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -124,5 +128,3 @@ declare global {
|
|||||||
"hui-yaml-editor": HuiYamlEditor;
|
"hui-yaml-editor": HuiYamlEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.customElements.define("hui-yaml-editor", HuiYamlEditor);
|
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { html, css, LitElement, TemplateResult, CSSResult } from "lit-element";
|
import {
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
@ -33,8 +40,10 @@ const cards = [
|
|||||||
{ name: "Weather Forecast", type: "weather-forecast" },
|
{ name: "Weather Forecast", type: "weather-forecast" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@customElement("hui-card-picker")
|
||||||
export class HuiCardPicker extends LitElement {
|
export class HuiCardPicker extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
@ -97,5 +106,3 @@ declare global {
|
|||||||
"hui-card-picker": HuiCardPicker;
|
"hui-card-picker": HuiCardPicker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-card-picker", HuiCardPicker);
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
@ -23,18 +24,13 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-dialog-edit-card")
|
||||||
export class HuiDialogEditCard extends LitElement {
|
export class HuiDialogEditCard extends LitElement {
|
||||||
protected hass?: HomeAssistant;
|
@property() protected hass?: HomeAssistant;
|
||||||
private _params?: EditCardDialogParams;
|
|
||||||
private _cardConfig?: LovelaceCardConfig;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() private _params?: EditCardDialogParams;
|
||||||
return {
|
|
||||||
hass: {},
|
@property() private _cardConfig?: LovelaceCardConfig;
|
||||||
_params: {},
|
|
||||||
_cardConfig: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -78,11 +74,11 @@ export class HuiDialogEditCard extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _cardPicked(cardConf: LovelaceCardConfig) {
|
private _cardPicked(cardConf: LovelaceCardConfig): void {
|
||||||
this._cardConfig = cardConf;
|
this._cardConfig = cardConf;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _cancel() {
|
private _cancel(): void {
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._cardConfig = undefined;
|
this._cardConfig = undefined;
|
||||||
}
|
}
|
||||||
@ -93,5 +89,3 @@ declare global {
|
|||||||
"hui-dialog-edit-card": HuiDialogEditCard;
|
"hui-dialog-edit-card": HuiDialogEditCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-dialog-edit-card", HuiDialogEditCard);
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
import "@polymer/paper-dialog/paper-dialog";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -13,14 +16,9 @@ import { moveCard } from "../config-util";
|
|||||||
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
||||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||||
|
|
||||||
|
@customElement("hui-dialog-move-card-view")
|
||||||
export class HuiDialogMoveCardView extends LitElement {
|
export class HuiDialogMoveCardView extends LitElement {
|
||||||
private _params?: MoveCardViewDialogParams;
|
@property() private _params?: MoveCardViewDialogParams;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return {
|
|
||||||
_params: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async showDialog(params: MoveCardViewDialogParams): Promise<void> {
|
public async showDialog(params: MoveCardViewDialogParams): Promise<void> {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
@ -32,29 +30,6 @@ export class HuiDialogMoveCardView extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<style>
|
|
||||||
paper-item {
|
|
||||||
margin: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
paper-item[active] {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
paper-item[active]:before {
|
|
||||||
border-radius: 4px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
content: "";
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
opacity: 0.12;
|
|
||||||
transition: opacity 15ms linear;
|
|
||||||
will-change: opacity;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<paper-dialog
|
<paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@ -75,6 +50,32 @@ export class HuiDialogMoveCardView extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
paper-item {
|
||||||
|
margin: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
paper-item[active] {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
paper-item[active]:before {
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
opacity: 0.12;
|
||||||
|
transition: opacity 15ms linear;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private get _dialog(): PaperDialogElement {
|
private get _dialog(): PaperDialogElement {
|
||||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||||
}
|
}
|
||||||
@ -104,5 +105,3 @@ declare global {
|
|||||||
"hui-dialog-move-card-view": HuiDialogMoveCardView;
|
"hui-dialog-move-card-view": HuiDialogMoveCardView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-dialog-move-card-view", HuiDialogMoveCardView);
|
|
||||||
|
@ -2,9 +2,9 @@ import {
|
|||||||
html,
|
html,
|
||||||
css,
|
css,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
import "@polymer/paper-dialog/paper-dialog";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
@ -15,15 +15,12 @@ import "./hui-card-picker";
|
|||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||||
|
|
||||||
|
@customElement("hui-dialog-pick-card")
|
||||||
export class HuiDialogPickCard extends LitElement {
|
export class HuiDialogPickCard extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||||
public closeDialog?: () => void;
|
public closeDialog?: () => void;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<paper-dialog
|
||||||
@ -47,7 +44,7 @@ export class HuiDialogPickCard extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev) {
|
private _openedChanged(ev): void {
|
||||||
if (!ev.detail.value) {
|
if (!ev.detail.value) {
|
||||||
this.closeDialog!();
|
this.closeDialog!();
|
||||||
}
|
}
|
||||||
@ -88,5 +85,3 @@ declare global {
|
|||||||
"hui-dialog-pick-card": HuiDialogPickCard;
|
"hui-dialog-pick-card": HuiDialogPickCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-dialog-pick-card", HuiDialogPickCard);
|
|
||||||
|
@ -2,10 +2,11 @@ import {
|
|||||||
html,
|
html,
|
||||||
css,
|
css,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
@ -49,36 +50,33 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("hui-edit-card")
|
||||||
export class HuiEditCard extends LitElement {
|
export class HuiEditCard extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
public lovelace?: Lovelace;
|
|
||||||
public path?: [number] | [number, number];
|
|
||||||
public cardConfig?: LovelaceCardConfig;
|
|
||||||
public closeDialog?: () => void;
|
|
||||||
private _configElement?: LovelaceCardEditor | null;
|
|
||||||
private _uiEditor?: boolean;
|
|
||||||
private _configValue?: ConfigValue;
|
|
||||||
private _configState?: string;
|
|
||||||
private _loading?: boolean;
|
|
||||||
private _saving: boolean;
|
|
||||||
private _errorMsg?: TemplateResult;
|
|
||||||
private _cardType?: string;
|
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
@property() public cardConfig?: LovelaceCardConfig;
|
||||||
return {
|
|
||||||
hass: {},
|
public lovelace?: Lovelace;
|
||||||
cardConfig: {},
|
|
||||||
viewIndex: {},
|
public path?: [number] | [number, number];
|
||||||
_cardIndex: {},
|
|
||||||
_configElement: {},
|
public closeDialog?: () => void;
|
||||||
_configValue: {},
|
|
||||||
_configState: {},
|
@property() private _configElement?: LovelaceCardEditor | null;
|
||||||
_errorMsg: {},
|
|
||||||
_uiEditor: {},
|
@property() private _uiEditor?: boolean;
|
||||||
_saving: {},
|
|
||||||
_loading: {},
|
@property() private _configValue?: ConfigValue;
|
||||||
};
|
|
||||||
}
|
@property() private _configState?: string;
|
||||||
|
|
||||||
|
@property() private _loading?: boolean;
|
||||||
|
|
||||||
|
@property() private _saving: boolean;
|
||||||
|
|
||||||
|
@property() private _errorMsg?: TemplateResult;
|
||||||
|
|
||||||
|
private _cardType?: string;
|
||||||
|
|
||||||
private get _dialog(): PaperDialogElement {
|
private get _dialog(): PaperDialogElement {
|
||||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||||
@ -88,7 +86,7 @@ export class HuiEditCard extends LitElement {
|
|||||||
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
this._saving = false;
|
this._saving = false;
|
||||||
}
|
}
|
||||||
@ -270,7 +268,7 @@ export class HuiEditCard extends LitElement {
|
|||||||
this._updatePreview(value);
|
this._updatePreview(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updatePreview(config: LovelaceCardConfig) {
|
private async _updatePreview(config: LovelaceCardConfig): Promise<void> {
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
|
||||||
if (!this._previewEl) {
|
if (!this._previewEl) {
|
||||||
@ -286,7 +284,7 @@ export class HuiEditCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setPreviewError(error: ConfigError) {
|
private _setPreviewError(error: ConfigError): void {
|
||||||
if (!this._previewEl) {
|
if (!this._previewEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -323,7 +321,7 @@ export class HuiEditCard extends LitElement {
|
|||||||
this._resizeDialog();
|
this._resizeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isConfigValid() {
|
private _isConfigValid(): boolean {
|
||||||
if (!this._configValue || !this._configValue.value) {
|
if (!this._configValue || !this._configValue.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -401,7 +399,7 @@ export class HuiEditCard extends LitElement {
|
|||||||
return this.path!.length === 1;
|
return this.path!.length === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev) {
|
private _openedChanged(ev): void {
|
||||||
if (!ev.detail.value) {
|
if (!ev.detail.value) {
|
||||||
this.closeDialog!();
|
this.closeDialog!();
|
||||||
}
|
}
|
||||||
@ -518,5 +516,3 @@ declare global {
|
|||||||
"hui-edit-card": HuiEditCard;
|
"hui-edit-card": HuiEditCard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-edit-card", HuiEditCard);
|
|
||||||
|
@ -17,7 +17,7 @@ export interface EditCardDialogParams {
|
|||||||
path: [number] | [number, number];
|
path: [number] | [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerEditCardDialog = (element: HTMLElement) =>
|
const registerEditCardDialog = (element: HTMLElement): Event =>
|
||||||
fireEvent(element, "register-dialog", {
|
fireEvent(element, "register-dialog", {
|
||||||
dialogShowEvent,
|
dialogShowEvent,
|
||||||
dialogTag,
|
dialogTag,
|
||||||
@ -28,7 +28,7 @@ const registerEditCardDialog = (element: HTMLElement) =>
|
|||||||
export const showEditCardDialog = (
|
export const showEditCardDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
editCardDialogParams: EditCardDialogParams
|
editCardDialogParams: EditCardDialogParams
|
||||||
) => {
|
): void => {
|
||||||
if (!registeredDialog) {
|
if (!registeredDialog) {
|
||||||
registeredDialog = true;
|
registeredDialog = true;
|
||||||
registerEditCardDialog(element);
|
registerEditCardDialog(element);
|
||||||
|
@ -15,7 +15,7 @@ export interface MoveCardViewDialogParams {
|
|||||||
lovelace: Lovelace;
|
lovelace: Lovelace;
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerEditCardDialog = (element: HTMLElement) =>
|
const registerEditCardDialog = (element: HTMLElement): Event =>
|
||||||
fireEvent(element, "register-dialog", {
|
fireEvent(element, "register-dialog", {
|
||||||
dialogShowEvent: "show-move-card-view",
|
dialogShowEvent: "show-move-card-view",
|
||||||
dialogTag: "hui-dialog-move-card-view",
|
dialogTag: "hui-dialog-move-card-view",
|
||||||
@ -26,7 +26,7 @@ const registerEditCardDialog = (element: HTMLElement) =>
|
|||||||
export const showMoveCardViewDialog = (
|
export const showMoveCardViewDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
moveCardViewDialogParams: MoveCardViewDialogParams
|
moveCardViewDialogParams: MoveCardViewDialogParams
|
||||||
) => {
|
): void => {
|
||||||
if (!registeredDialog) {
|
if (!registeredDialog) {
|
||||||
registeredDialog = true;
|
registeredDialog = true;
|
||||||
registerEditCardDialog(element);
|
registerEditCardDialog(element);
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -26,20 +29,18 @@ const cardConfigStruct = struct({
|
|||||||
states: "array?",
|
states: "array?",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@customElement("hui-alarm-panel-card-editor")
|
||||||
export class HuiAlarmPanelCardEditor extends LitElement
|
export class HuiAlarmPanelCardEditor extends LitElement
|
||||||
implements LovelaceCardEditor {
|
implements LovelaceCardEditor {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
|
@property() private _config?: Config;
|
||||||
|
|
||||||
public setConfig(config: Config): void {
|
public setConfig(config: Config): void {
|
||||||
config = cardConfigStruct(config);
|
config = cardConfigStruct(config);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return { hass: {}, _config: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
get _entity(): string {
|
get _entity(): string {
|
||||||
return this._config!.entity || "";
|
return this._config!.entity || "";
|
||||||
}
|
}
|
||||||
@ -60,7 +61,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
|
|||||||
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
|
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${configElementStyle} ${this.renderStyle()}
|
${configElementStyle}
|
||||||
<div class="card-config">
|
<div class="card-config">
|
||||||
<div class="side-by-side">
|
<div class="side-by-side">
|
||||||
<paper-input
|
<paper-input
|
||||||
@ -107,23 +108,21 @@ export class HuiAlarmPanelCardEditor extends LitElement
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
static get styles(): CSSResult {
|
||||||
return html`
|
return css`
|
||||||
<style>
|
.states {
|
||||||
.states {
|
display: flex;
|
||||||
display: flex;
|
flex-direction: row;
|
||||||
flex-direction: row;
|
}
|
||||||
}
|
.deleteState {
|
||||||
.deleteState {
|
visibility: hidden;
|
||||||
visibility: hidden;
|
}
|
||||||
}
|
.states:hover > .deleteState {
|
||||||
.states:hover > .deleteState {
|
visibility: visible;
|
||||||
visibility: visible;
|
}
|
||||||
}
|
ha-icon {
|
||||||
ha-icon {
|
padding-top: 12px;
|
||||||
padding-top: 12px;
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,5 +189,3 @@ declare global {
|
|||||||
"hui-alarm-panel-card-editor": HuiAlarmPanelCardEditor;
|
"hui-alarm-panel-card-editor": HuiAlarmPanelCardEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-alarm-panel-card-editor", HuiAlarmPanelCardEditor);
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -44,10 +45,19 @@ const cardConfigStruct = struct({
|
|||||||
entities: [entitiesConfigStruct],
|
entities: [entitiesConfigStruct],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@customElement("hui-entities-card-editor")
|
||||||
export class HuiEntitiesCardEditor extends LitElement
|
export class HuiEntitiesCardEditor extends LitElement
|
||||||
implements LovelaceCardEditor {
|
implements LovelaceCardEditor {
|
||||||
static get properties(): PropertyDeclarations {
|
@property() public hass?: HomeAssistant;
|
||||||
return { hass: {}, _config: {}, _configEntities: {} };
|
|
||||||
|
@property() private _config?: EntitiesCardConfig;
|
||||||
|
|
||||||
|
@property() private _configEntities?: EntitiesCardEntityConfig[];
|
||||||
|
|
||||||
|
public setConfig(config: EntitiesCardConfig): void {
|
||||||
|
config = cardConfigStruct(config);
|
||||||
|
this._config = config;
|
||||||
|
this._configEntities = processEditorEntities(config.entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
get _title(): string {
|
get _title(): string {
|
||||||
@ -58,16 +68,6 @@ export class HuiEntitiesCardEditor extends LitElement
|
|||||||
return this._config!.theme || "Backend-selected";
|
return this._config!.theme || "Backend-selected";
|
||||||
}
|
}
|
||||||
|
|
||||||
public hass?: HomeAssistant;
|
|
||||||
private _config?: EntitiesCardConfig;
|
|
||||||
private _configEntities?: EntitiesCardEntityConfig[];
|
|
||||||
|
|
||||||
public setConfig(config: EntitiesCardConfig): void {
|
|
||||||
config = cardConfigStruct(config);
|
|
||||||
this._config = config;
|
|
||||||
this._configEntities = processEditorEntities(config.entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -141,5 +141,3 @@ declare global {
|
|||||||
"hui-entities-card-editor": HuiEntitiesCardEditor;
|
"hui-entities-card-editor": HuiEntitiesCardEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("hui-entities-card-editor", HuiEntitiesCardEditor);
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyDeclarations,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
@ -33,20 +34,18 @@ const cardConfigStruct = struct({
|
|||||||
theme: "string?",
|
theme: "string?",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@customElement("hui-entity-button-card-editor")
|
||||||
export class HuiEntityButtonCardEditor extends LitElement
|
export class HuiEntityButtonCardEditor extends LitElement
|
||||||
implements LovelaceCardEditor {
|
implements LovelaceCardEditor {
|
||||||
public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
private _config?: Config;
|
|
||||||
|
@property() private _config?: Config;
|
||||||
|
|
||||||
public setConfig(config: Config): void {
|
public setConfig(config: Config): void {
|
||||||
config = cardConfigStruct(config);
|
config = cardConfigStruct(config);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
|
||||||
return { hass: {}, _config: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
get _entity(): string {
|
get _entity(): string {
|
||||||
return this._config!.entity || "";
|
return this._config!.entity || "";
|
||||||
}
|
}
|
||||||
@ -161,8 +160,3 @@ declare global {
|
|||||||
"hui-entity-button-card-editor": HuiEntityButtonCardEditor;
|
"hui-entity-button-card-editor": HuiEntityButtonCardEditor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(
|
|
||||||
"hui-entity-button-card-editor",
|
|
||||||
HuiEntityButtonCardEditor
|
|
||||||
);
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user