mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +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):**
|
||||
|
||||
**UI (States or Lovelace UI?):**
|
||||
<!--
|
||||
- Frontend -> Developer tools -> Info
|
||||
-->
|
||||
|
||||
**Browser and Operating System:**
|
||||
<!--
|
||||
|
@ -1,6 +1,6 @@
|
||||
const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const BabelMinifyPlugin = require("babel-minify-webpack-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports.resolve = {
|
||||
extensions: [".ts", ".js", ".json", ".tsx"],
|
||||
@ -29,32 +29,15 @@ module.exports.plugins = [
|
||||
),
|
||||
];
|
||||
|
||||
module.exports.optimization = {
|
||||
module.exports.optimization = (latestBuild) => ({
|
||||
minimizer: [
|
||||
// Took options from Polymer build tool
|
||||
// https://github.com/Polymer/tools/blob/master/packages/build/src/js-transform.ts
|
||||
new BabelMinifyPlugin(
|
||||
{
|
||||
// Disable the minify-constant-folding plugin because it has a bug relating
|
||||
// to invalid substitution of constant values into export specifiers:
|
||||
// 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,
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
extractComments: true,
|
||||
terserOptions: {
|
||||
ecma: latestBuild ? undefined : 5,
|
||||
},
|
||||
{}
|
||||
),
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: webpackBase.optimization,
|
||||
optimization: webpackBase.optimization(latestBuild),
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: false,
|
||||
@ -74,6 +74,7 @@ module.exports = {
|
||||
new WorkboxPlugin.GenerateSW({
|
||||
swDest: "service_worker_es5.js",
|
||||
importWorkboxFrom: "local",
|
||||
include: [],
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: webpackBase.resolve,
|
||||
|
@ -7,6 +7,7 @@ const isProd = process.env.NODE_ENV === "production";
|
||||
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
const buildPath = path.resolve(__dirname, "dist");
|
||||
const publicPath = isProd ? "./" : "http://localhost:8080/";
|
||||
const latestBuild = true;
|
||||
|
||||
module.exports = {
|
||||
mode: isProd ? "production" : "development",
|
||||
@ -16,7 +17,7 @@ module.exports = {
|
||||
entry: "./src/entrypoint.js",
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild: true }),
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
@ -32,7 +33,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: webpackBase.optimization,
|
||||
optimization: webpackBase.optimization(latestBuild),
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
"public",
|
||||
@ -51,15 +52,6 @@ module.exports = {
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
]),
|
||||
isProd &&
|
||||
new UglifyJsPlugin({
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
// Disabling because it broke output
|
||||
mangle: false,
|
||||
},
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: webpackBase.resolve,
|
||||
output: {
|
||||
|
@ -1,11 +1,12 @@
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
const config = require("./config.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 isCI = process.env.CI === "true";
|
||||
const chunkFilename = isProdBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
const latestBuild = false;
|
||||
|
||||
module.exports = {
|
||||
mode: isProdBuild ? "production" : "development",
|
||||
@ -15,7 +16,7 @@ module.exports = {
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild: false }),
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
@ -27,9 +28,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimizer,
|
||||
},
|
||||
optimization: webpackBase.optimization(latestBuild),
|
||||
plugins: [
|
||||
isProdBuild &&
|
||||
!isCI &&
|
||||
|
@ -73,7 +73,8 @@
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"es6-object-assign": "^1.1.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",
|
||||
"jquery": "^3.3.1",
|
||||
"js-yaml": "^3.12.0",
|
||||
@ -107,12 +108,12 @@
|
||||
"@gfx/zopfli": "^1.0.9",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/codemirror": "^0.0.71",
|
||||
"@types/hls.js": "^0.12.2",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/memoize-one": "^4.1.0",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"babel-eslint": "^10",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"chai": "^4.2.0",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
@ -146,6 +147,7 @@
|
||||
"reify": "^0.18.1",
|
||||
"require-dir": "^1.0.0",
|
||||
"sinon": "^7.1.1",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-mocha": "^2.0.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-prettier": "^1.15.0",
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20190305.1",
|
||||
version="20190312.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -78,6 +78,7 @@ export const DOMAINS_TOGGLE = new Set([
|
||||
"light",
|
||||
"switch",
|
||||
"group",
|
||||
"automation",
|
||||
]);
|
||||
|
||||
/** Temperature units. */
|
||||
|
@ -53,7 +53,7 @@ class StateBadge extends LitElement {
|
||||
};
|
||||
if (stateObj) {
|
||||
// hide icon if we have entity picture
|
||||
if (stateObj.attributes.entity_picture) {
|
||||
if (stateObj.attributes.entity_picture && !this.overrideIcon) {
|
||||
hostStyle.backgroundImage =
|
||||
"url(" + stateObj.attributes.entity_picture + ")";
|
||||
iconStyle.display = "none";
|
||||
|
@ -22,6 +22,7 @@ import { DEFAULT_PANEL } from "../common/const";
|
||||
const computeUrl = (urlPath) => `/${urlPath}`;
|
||||
|
||||
const computePanels = (hass: HomeAssistant) => {
|
||||
const isAdmin = hass.user.is_admin;
|
||||
const panels = hass.panels;
|
||||
const sortValue = {
|
||||
map: 1,
|
||||
@ -30,9 +31,9 @@ const computePanels = (hass: HomeAssistant) => {
|
||||
};
|
||||
const result: Panel[] = [];
|
||||
|
||||
Object.keys(panels).forEach((key) => {
|
||||
if (panels[key].title) {
|
||||
result.push(panels[key]);
|
||||
Object.values(panels).forEach((panel) => {
|
||||
if (panel.title && (panel.component_name !== "config" || isAdmin)) {
|
||||
result.push(panel);
|
||||
}
|
||||
});
|
||||
|
||||
@ -129,62 +130,66 @@ class HaSidebar extends LitElement {
|
||||
: html``}
|
||||
</paper-listbox>
|
||||
|
||||
<div>
|
||||
<div class="divider"></div>
|
||||
${!hass.user.is_admin
|
||||
? ""
|
||||
: html`
|
||||
<div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="subheader">
|
||||
${hass.localize("ui.sidebar.developer_tools")}
|
||||
</div>
|
||||
<div class="subheader">
|
||||
${hass.localize("ui.sidebar.developer_tools")}
|
||||
</div>
|
||||
|
||||
<div class="dev-tools">
|
||||
<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">
|
||||
<div class="dev-tools">
|
||||
<a href="/dev-service" tabindex="-1">
|
||||
<paper-icon-button
|
||||
icon="hass:altimeter"
|
||||
alt="${hass.localize("panel.dev-mqtt")}"
|
||||
title="${hass.localize("panel.dev-mqtt")}"
|
||||
icon="hass:remote"
|
||||
alt="${hass.localize("panel.dev-services")}"
|
||||
title="${hass.localize("panel.dev-services")}"
|
||||
></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>
|
||||
<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
|
||||
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,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { User } from "../../data/auth";
|
||||
import { User } from "../../data/user";
|
||||
import { CurrentUser } from "../../types";
|
||||
|
||||
const computeInitials = (name: string) => {
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
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";
|
||||
|
||||
class HaEntityPicker extends LitElement {
|
||||
|
@ -1,26 +1,9 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface AuthProvider {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface Credential {
|
||||
export interface Credential {
|
||||
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 {
|
||||
content_type: 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) =>
|
||||
hass.callWS<CameraThumbnail>({
|
||||
type: "camera_thumbnail",
|
||||
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";
|
||||
|
||||
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 {
|
||||
webhook_id: string;
|
||||
cloudhook_id: string;
|
||||
@ -18,3 +58,29 @@ export const deleteCloudhook = (hass: HomeAssistant, webhookId: string) =>
|
||||
type: "cloud/cloudhook/delete",
|
||||
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;
|
||||
hub_device_id?: string;
|
||||
area_id?: string;
|
||||
name_by_user?: string;
|
||||
}
|
||||
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id: string;
|
||||
area_id?: string;
|
||||
name_by_user?: string;
|
||||
}
|
||||
|
||||
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 { HomeAssistant } from "../types";
|
||||
|
||||
export interface ZHADeviceEntity extends HassEntity {
|
||||
device_info?: {
|
||||
identifiers: any[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ZHAEntityReference extends HassEntity {
|
||||
name: string;
|
||||
}
|
||||
@ -20,6 +14,8 @@ export interface ZHADevice {
|
||||
quirk_class: string;
|
||||
entities: ZHAEntityReference[];
|
||||
manufacturer_code: number;
|
||||
device_reg_id: string;
|
||||
user_given_name: string;
|
||||
}
|
||||
|
||||
export interface Attribute {
|
||||
@ -78,6 +74,37 @@ export const fetchDevices = (hass: HomeAssistant): Promise<ZHADevice[]> =>
|
||||
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 = (
|
||||
hass: HomeAssistant,
|
||||
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 { HomeAssistant } from "../types";
|
||||
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";
|
||||
|
||||
const ensureArray = <T>(val: T | T[]): T[] =>
|
||||
@ -87,6 +87,8 @@ export const provideHass = (
|
||||
);
|
||||
});
|
||||
|
||||
const localLanguage = getLocalLanguage();
|
||||
|
||||
const hassObj: MockHomeAssistant = {
|
||||
// Home Assistant properties
|
||||
auth: {} as any,
|
||||
@ -128,13 +130,15 @@ export const provideHass = (
|
||||
user: {
|
||||
credentials: [],
|
||||
id: "abcd",
|
||||
is_admin: true,
|
||||
is_owner: true,
|
||||
mfa_modules: [],
|
||||
name: "Demo User",
|
||||
},
|
||||
panelUrl: "lovelace",
|
||||
|
||||
language: getActiveTranslation(),
|
||||
language: localLanguage,
|
||||
selectedLanguage: localLanguage,
|
||||
resources: null as any,
|
||||
localize: () => "",
|
||||
|
||||
|
@ -12,7 +12,7 @@ import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
|
||||
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 hassCallApi from "../../util/hass-call-api";
|
||||
import { subscribePanels } from "../../data/ws-panels";
|
||||
@ -49,7 +49,7 @@ export default (superClass) =>
|
||||
user: null,
|
||||
panelUrl: this._panelUrl,
|
||||
|
||||
language: getActiveTranslation(),
|
||||
language: getLocalLanguage(),
|
||||
// If resources are already loaded, don't discard them
|
||||
resources: (this.hass && this.hass.resources) || null,
|
||||
localize: () => "",
|
||||
|
@ -24,7 +24,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("hass-dock-sidebar", (ev) => {
|
||||
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 { storeState } from "../../util/ha-pref-storage";
|
||||
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 {
|
||||
firstUpdated(changedProps) {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("settheme", (ev) => {
|
||||
this._updateHass({ selectedTheme: ev.detail });
|
||||
this._applyTheme();
|
||||
storeState(this.hass);
|
||||
storeState(this.hass!);
|
||||
});
|
||||
}
|
||||
|
||||
hassConnected() {
|
||||
protected hassConnected() {
|
||||
super.hassConnected();
|
||||
|
||||
subscribeThemes(this.hass.connection, (themes) => {
|
||||
subscribeThemes(this.hass!.connection, (themes) => {
|
||||
this._updateHass({ themes });
|
||||
this._applyTheme();
|
||||
});
|
||||
}
|
||||
|
||||
_applyTheme() {
|
||||
private _applyTheme() {
|
||||
applyThemesOnElement(
|
||||
document.documentElement,
|
||||
this.hass.themes,
|
||||
this.hass.selectedTheme,
|
||||
this.hass!.themes,
|
||||
this.hass!.selectedTheme,
|
||||
true
|
||||
);
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
import { translationMetadata } from "../../resources/translations-metadata";
|
||||
import { getTranslation } from "../../util/hass-translation";
|
||||
import { storeState } from "../../util/ha-pref-storage";
|
||||
import {
|
||||
getTranslation,
|
||||
getLocalLanguage,
|
||||
getUserLanguage,
|
||||
} from "../../util/hass-translation";
|
||||
import { Constructor, LitElement } from "lit-element";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
import { computeLocalize } from "../../common/translations/localize";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
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`.
|
||||
@ -16,60 +22,86 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("hass-language-select", (e) =>
|
||||
this._selectLanguage(e)
|
||||
this._selectLanguage((e as CustomEvent).detail.language, true)
|
||||
);
|
||||
this._loadResources();
|
||||
this._loadCoreTranslations(getLocalLanguage());
|
||||
}
|
||||
|
||||
protected hassConnected() {
|
||||
super.hassConnected();
|
||||
this._loadBackendTranslations();
|
||||
this.style.direction = computeRTL(this.hass!) ? "rtl" : "ltr";
|
||||
getUserLanguage(this.hass!).then((language) => {
|
||||
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() {
|
||||
super.hassReconnected();
|
||||
this._loadBackendTranslations();
|
||||
this._applyTranslations(this.hass!);
|
||||
}
|
||||
|
||||
protected 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() {
|
||||
const hass = this.hass;
|
||||
if (!hass || !hass.language) {
|
||||
private _selectLanguage(language: string, saveToBackend: boolean) {
|
||||
if (!this.hass) {
|
||||
// should not happen, do it to avoid use this.hass!
|
||||
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({
|
||||
type: "frontend/get_translations",
|
||||
language,
|
||||
});
|
||||
this._applyTranslations(this.hass);
|
||||
}
|
||||
|
||||
// If we've switched selected languages just ignore this response
|
||||
if ((hass.selectedLanguage || hass.language) !== language) {
|
||||
private _applyTranslations(hass: HomeAssistant) {
|
||||
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;
|
||||
}
|
||||
|
||||
this._updateResources(language, resources);
|
||||
}
|
||||
|
||||
private _loadTranslationFragment(panelUrl) {
|
||||
private async _loadFragmentTranslations(
|
||||
language: string,
|
||||
panelUrl: string
|
||||
) {
|
||||
if (translationMetadata.fragments.includes(panelUrl)) {
|
||||
this._loadResources(panelUrl);
|
||||
const result = await getTranslation(panelUrl, language);
|
||||
this._updateResources(result.language, result.data);
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadResources(fragment?) {
|
||||
const result = await getTranslation(fragment);
|
||||
private async _loadCoreTranslations(language: string) {
|
||||
const result = await getTranslation(null, language);
|
||||
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
|
||||
// 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
|
||||
@ -88,14 +120,4 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
||||
}
|
||||
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,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { getActiveTranslation } from "../util/hass-translation";
|
||||
import { getLocalLanguage } from "../util/hass-translation";
|
||||
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
|
||||
import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
@ -35,7 +35,8 @@ export const litLocalizeLiteMixin = <T extends LitElement>(
|
||||
super();
|
||||
// This will prevent undefined errors if called before connected to DOM.
|
||||
this.localize = empty;
|
||||
this.language = getActiveTranslation();
|
||||
// Use browser language setup before login.
|
||||
this.language = getLocalLanguage();
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
|
@ -35,7 +35,10 @@ export const localizeLiteBaseMixin = (superClass) =>
|
||||
}
|
||||
|
||||
private async _updateResources() {
|
||||
const { language, data } = await getTranslation(this.translationFragment);
|
||||
const { language, data } = await getTranslation(
|
||||
this.translationFragment,
|
||||
this.language
|
||||
);
|
||||
this.resources = {
|
||||
[language]: data,
|
||||
};
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Lite mixin to add localization without depending on the Hass object.
|
||||
*/
|
||||
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 { computeLocalize } from "../common/translations/localize";
|
||||
|
||||
@ -16,7 +16,8 @@ export const localizeLiteMixin = dedupingMixin(
|
||||
return {
|
||||
language: {
|
||||
type: String,
|
||||
value: getActiveTranslation(),
|
||||
// Use browser language setup before login.
|
||||
value: getLocalLanguage(),
|
||||
},
|
||||
resources: Object,
|
||||
// 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;
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
@ -86,11 +86,11 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
? html`
|
||||
<div class="empty">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.area_registry.picker.no_areas"
|
||||
"ui.panel.config.area_registry.no_areas"
|
||||
)}
|
||||
<mwc-button @click=${this._createArea}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.area_registry.picker.create_area"
|
||||
"ui.panel.config.area_registry.create_area"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
@ -104,7 +104,7 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
?is-wide=${this.isWide}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.area_registry.picker.create_area"
|
||||
"ui.panel.config.area_registry.create_area"
|
||||
)}"
|
||||
@click=${this._createArea}
|
||||
class="${classMap({
|
||||
|
@ -3,6 +3,8 @@ import {
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
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 { HomeAssistant } from "../../../types";
|
||||
import { updatePref } from "./data";
|
||||
import { CloudStatusLoggedIn } from "./types";
|
||||
import "./cloud-exposed-entities";
|
||||
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
|
||||
|
||||
export class CloudAlexaPref extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
@ -35,7 +36,6 @@ export class CloudAlexaPref extends LitElement {
|
||||
const enabled = this.cloudStatus!.prefs.alexa_enabled;
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-card heading="Alexa">
|
||||
<paper-toggle-button
|
||||
.checked="${enabled}"
|
||||
@ -51,7 +51,7 @@ export class CloudAlexaPref extends LitElement {
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.home-assistant.io/cloud/alexa/"
|
||||
href="https://www.nabucasa.com/config/amazon_alexa/"
|
||||
target="_blank"
|
||||
>
|
||||
Config documentation
|
||||
@ -80,25 +80,23 @@ export class CloudAlexaPref extends LitElement {
|
||||
private async _toggleChanged(ev) {
|
||||
const toggle = ev.target as PaperToggleButtonElement;
|
||||
try {
|
||||
await updatePref(this.hass!, { alexa_enabled: toggle.checked! });
|
||||
await updateCloudPref(this.hass!, { alexa_enabled: toggle.checked! });
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
} catch (err) {
|
||||
toggle.checked = !toggle.checked;
|
||||
}
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card > paper-toggle-button {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 16px;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card > paper-toggle-button {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ import "../../../components/entity/ha-state-icon";
|
||||
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EntityFilter } from "./types";
|
||||
import computeStateName from "../../../common/entity/compute_state_name";
|
||||
import {
|
||||
FilterFunc,
|
||||
generateFilter,
|
||||
} from "../../../common/entity/entity_filter";
|
||||
import { EntityFilter } from "../../../data/cloud";
|
||||
|
||||
export class CloudExposedEntities extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
@ -3,6 +3,8 @@ import {
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
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 { HomeAssistant } from "../../../types";
|
||||
import { updatePref } from "./data";
|
||||
import { CloudStatusLoggedIn } from "./types";
|
||||
import "./cloud-exposed-entities";
|
||||
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
|
||||
|
||||
export class CloudGooglePref extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
@ -36,7 +37,6 @@ export class CloudGooglePref extends LitElement {
|
||||
const { google_enabled, google_allow_unlock } = this.cloudStatus.prefs;
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-card heading="Google Assistant">
|
||||
<paper-toggle-button
|
||||
id="google_enabled"
|
||||
@ -58,7 +58,7 @@ export class CloudGooglePref extends LitElement {
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.home-assistant.io/cloud/google_assistant/"
|
||||
href="https://www.nabucasa.com/config/google_assistant/"
|
||||
target="_blank"
|
||||
>
|
||||
Config documentation
|
||||
@ -103,37 +103,35 @@ export class CloudGooglePref extends LitElement {
|
||||
private async _toggleChanged(ev) {
|
||||
const toggle = ev.target as PaperToggleButtonElement;
|
||||
try {
|
||||
await updatePref(this.hass!, { [toggle.id]: toggle.checked! });
|
||||
await updateCloudPref(this.hass!, { [toggle.id]: toggle.checked! });
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
} catch (err) {
|
||||
toggle.checked = !toggle.checked;
|
||||
}
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
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 { HomeAssistant } from "../../../types";
|
||||
import { WebhookDialogParams } from "./types";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { WebhookDialogParams } from "./show-cloud-webhook-manage-dialog";
|
||||
|
||||
const inputLabel = "Public URL – Click to copy to clipboard";
|
||||
|
||||
@ -60,10 +60,17 @@ export class CloudWebhookManageDialog extends LitElement {
|
||||
@blur="${this._restoreLabel}"
|
||||
></paper-input>
|
||||
<p>
|
||||
If you no longer want to use this webhook, you can
|
||||
<button class="link" @click="${this._disableWebhook}">
|
||||
disable it</button
|
||||
>.
|
||||
${cloudhook.managed
|
||||
? html`
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
@ -10,23 +10,15 @@ import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
import { HomeAssistant, WebhookError } from "../../../types";
|
||||
import { WebhookDialogParams, CloudStatusLoggedIn } from "./types";
|
||||
import { Webhook, fetchWebhooks } from "../../../data/webhook";
|
||||
import {
|
||||
createCloudhook,
|
||||
deleteCloudhook,
|
||||
CloudWebhook,
|
||||
CloudStatusLoggedIn,
|
||||
} from "../../../data/cloud";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"manage-cloud-webhook": WebhookDialogParams;
|
||||
}
|
||||
}
|
||||
import { showManageCloudhookDialog } from "./show-cloud-webhook-manage-dialog";
|
||||
|
||||
export class CloudWebhooks extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
@ -138,14 +130,13 @@ export class CloudWebhooks extends LitElement {
|
||||
private _showDialog(webhookId: string) {
|
||||
const webhook = this._localHooks!.find(
|
||||
(ent) => ent.webhook_id === webhookId
|
||||
);
|
||||
)!;
|
||||
const cloudhook = this._cloudHooks![webhookId];
|
||||
const params: WebhookDialogParams = {
|
||||
webhook: webhook!,
|
||||
showManageCloudhookDialog(this, {
|
||||
webhook,
|
||||
cloudhook,
|
||||
disableHook: () => this._disableWebhook(webhookId),
|
||||
};
|
||||
fireEvent(this, "manage-cloud-webhook", params);
|
||||
});
|
||||
}
|
||||
|
||||
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 LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
import { fetchSubscriptionInfo } from "./data";
|
||||
import { fetchCloudSubscriptionInfo } from "../../../data/cloud";
|
||||
import "./cloud-alexa-pref";
|
||||
import "./cloud-google-pref";
|
||||
import "./cloud-remote-pref";
|
||||
|
||||
let registeredWebhookDialog = false;
|
||||
|
||||
@ -66,6 +66,9 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
text-transform: capitalize;
|
||||
padding: 16px;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
<hass-subpage header="Home Assistant Cloud">
|
||||
<div class="content">
|
||||
@ -83,7 +86,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<div class="account-row">
|
||||
<paper-item-body two-line="">
|
||||
[[cloudStatus.email]]
|
||||
<div secondary="" class="wrap">
|
||||
<div secondary class="wrap">
|
||||
[[_formatSubscription(_subscription)]]
|
||||
</div>
|
||||
</paper-item-body>
|
||||
@ -121,6 +124,11 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<cloud-remote-pref
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
></cloud-remote-pref>
|
||||
|
||||
<cloud-alexa-pref
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
@ -172,8 +180,12 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
}
|
||||
}
|
||||
|
||||
_computeRemoteConnected(connected) {
|
||||
return connected ? "Connected" : "Not Connected";
|
||||
}
|
||||
|
||||
async _fetchSubscriptionInfo() {
|
||||
this._subscription = await fetchSubscriptionInfo(this.hass);
|
||||
this._subscription = await fetchCloudSubscriptionInfo(this.hass);
|
||||
if (
|
||||
this._subscription.provider &&
|
||||
this.cloudStatus &&
|
||||
|
@ -74,8 +74,10 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
Home Assistant Cloud connects your local instance securely to
|
||||
cloud-only services Amazon Alexa and Google Assistant.
|
||||
Home Assistant Cloud provides you with a secure remote
|
||||
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>
|
||||
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:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Control of Home Assistant away from home</li>
|
||||
<li>Integration with Google Assistant</li>
|
||||
<li>Integration with Amazon Alexa</li>
|
||||
<li>Easy integration with webhook-based apps like OwnTracks</li>
|
||||
</ul>
|
||||
<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.
|
||||
|
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;
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class DialogPersonDetail extends LitElement {
|
||||
<ha-entities-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._deviceTrackers}
|
||||
domainFilter="device_tracker"
|
||||
domain-filter="device_tracker"
|
||||
.pickedEntityLabel=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_picked"
|
||||
)}
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
showPersonDetailDialog,
|
||||
loadPersonDetailDialog,
|
||||
} from "./show-dialog-person-detail";
|
||||
import { User, fetchUsers } from "../../../data/auth";
|
||||
import { User, fetchUsers } from "../../../data/user";
|
||||
|
||||
class HaConfigPerson extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { Person, PersonMutableParams } from "../../../data/person";
|
||||
import { User } from "../../../data/auth";
|
||||
import { User } from "../../../data/user";
|
||||
|
||||
export interface PersonDetailDialogParams {
|
||||
entry?: Person;
|
||||
|
@ -66,7 +66,7 @@ class HaUserPicker extends EventsMixin(
|
||||
<paper-item-body two-line>
|
||||
<div>[[_withDefault(user.name, 'Unnamed User')]]</div>
|
||||
<div secondary="">
|
||||
[[user.id]]
|
||||
[[_computeGroup(localize, user)]]
|
||||
<template is="dom-if" if="[[user.system_generated]]">
|
||||
- System Generated
|
||||
</template>
|
||||
@ -124,6 +124,10 @@ class HaUserPicker extends EventsMixin(
|
||||
return `/config/users/${user.id}`;
|
||||
}
|
||||
|
||||
_computeGroup(localize, user) {
|
||||
return localize(`groups.${user.group_ids[0]}`);
|
||||
}
|
||||
|
||||
_computeRTL(hass) {
|
||||
return computeRTL(hass);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import NavigateMixin from "../../../mixins/navigate-mixin";
|
||||
import "./ha-config-user-picker";
|
||||
import "./ha-user-editor";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { fetchUsers } from "../../../data/auth";
|
||||
import { fetchUsers } from "../../../data/user";
|
||||
|
||||
/*
|
||||
* @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 {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
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 "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ZHAClusterSelectedParams, ZHANodeSelectedParams } from "./types";
|
||||
import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
|
||||
import "./zha-cluster-attributes";
|
||||
import "./zha-cluster-commands";
|
||||
import "./zha-network";
|
||||
import "./zha-node";
|
||||
import "./zha-binding";
|
||||
|
||||
export class HaConfigZha extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public isWide?: boolean;
|
||||
private _selectedNode?: HassEntity;
|
||||
private _selectedCluster?: Cluster;
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
@property() private _selectedDevice?: ZHADevice;
|
||||
@property() private _selectedCluster?: Cluster;
|
||||
@property() private _bindableDevices: ZHADevice[] = [];
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
isWide: {},
|
||||
_selectedCluster: {},
|
||||
_selectedNode: {},
|
||||
};
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("_selectedDevice")) {
|
||||
this._fetchBindableDevices();
|
||||
}
|
||||
super.update(changedProperties);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@ -57,25 +57,35 @@ export class HaConfigZha extends LitElement {
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
@zha-cluster-selected="${this._onClusterSelected}"
|
||||
@zha-node-selected="${this._onNodeSelected}"
|
||||
@zha-node-selected="${this._onDeviceSelected}"
|
||||
></zha-node>
|
||||
${this._selectedCluster
|
||||
? html`
|
||||
<zha-cluster-attributes
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.selectedNode="${this._selectedNode}"
|
||||
.selectedNode="${this._selectedDevice}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-attributes>
|
||||
|
||||
<zha-cluster-commands
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.selectedNode="${this._selectedNode}"
|
||||
.selectedNode="${this._selectedDevice}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></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>
|
||||
`;
|
||||
}
|
||||
@ -86,13 +96,24 @@ export class HaConfigZha extends LitElement {
|
||||
this._selectedCluster = selectedClusterEvent.detail.cluster;
|
||||
}
|
||||
|
||||
private _onNodeSelected(
|
||||
selectedNodeEvent: HASSDomEvent<ZHANodeSelectedParams>
|
||||
private _onDeviceSelected(
|
||||
selectedNodeEvent: HASSDomEvent<ZHADeviceSelectedParams>
|
||||
): void {
|
||||
this._selectedNode = selectedNodeEvent.detail.node;
|
||||
this._selectedDevice = selectedNodeEvent.detail.node;
|
||||
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[] {
|
||||
return [haStyle];
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ZHADeviceEntity, Cluster } from "../../../data/zha";
|
||||
import { ZHADevice, Cluster } from "../../../data/zha";
|
||||
|
||||
export interface PickerTarget extends EventTarget {
|
||||
selected: number;
|
||||
@ -34,8 +34,8 @@ export interface IssueCommandServiceData {
|
||||
command_type: string;
|
||||
}
|
||||
|
||||
export interface ZHANodeSelectedParams {
|
||||
node: ZHADeviceEntity;
|
||||
export interface ZHADeviceSelectedParams {
|
||||
node: ZHADevice;
|
||||
}
|
||||
|
||||
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 "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@ -18,9 +19,13 @@ import "../../../components/ha-service-description";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { ItemSelectedEvent, NodeServiceData } from "./types";
|
||||
import { ItemSelectedEvent, NodeServiceData, ChangeEvent } from "./types";
|
||||
import "./zha-clusters";
|
||||
import "./zha-device-card";
|
||||
import {
|
||||
updateDeviceRegistryEntry,
|
||||
DeviceRegistryEntryMutableParams,
|
||||
} from "../../../data/device_registry";
|
||||
import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha";
|
||||
|
||||
declare global {
|
||||
@ -40,6 +45,7 @@ export class ZHANode extends LitElement {
|
||||
private _selectedNode?: ZHADevice;
|
||||
private _serviceData?: {};
|
||||
private _nodes: ZHADevice[];
|
||||
private _userSelectedName?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -58,6 +64,7 @@ export class ZHANode extends LitElement {
|
||||
_entities: {},
|
||||
_serviceData: {},
|
||||
_nodes: {},
|
||||
_userSelectedName: {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -109,7 +116,11 @@ export class ZHANode extends LitElement {
|
||||
>
|
||||
${this._nodes.map(
|
||||
(entry) => html`
|
||||
<paper-item>${entry.name}</paper-item>
|
||||
<paper-item
|
||||
>${entry.user_given_name
|
||||
? entry.user_given_name
|
||||
: entry.name}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
@ -132,6 +143,18 @@ export class ZHANode extends LitElement {
|
||||
></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._selectedNode ? this._renderClusters() : ""}
|
||||
</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>
|
||||
`;
|
||||
}
|
||||
@ -191,6 +229,7 @@ export class ZHANode extends LitElement {
|
||||
private _selectedNodeChanged(event: ItemSelectedEvent): void {
|
||||
this._selectedNodeIndex = event!.target!.selected;
|
||||
this._selectedNode = this._nodes[this._selectedNodeIndex];
|
||||
this._userSelectedName = "";
|
||||
fireEvent(this, "zha-node-selected", { node: this._selectedNode });
|
||||
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 {
|
||||
return {
|
||||
ieee_address: this._selectedNode!.ieee,
|
||||
@ -277,6 +337,7 @@ export class ZHANode extends LitElement {
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
ha-service-description {
|
||||
@ -294,6 +355,12 @@ export class ZHANode extends LitElement {
|
||||
right: 0;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.input-text {
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
CSSResult,
|
||||
css,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@ -39,6 +40,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
states?: string[];
|
||||
}
|
||||
|
||||
@customElement("hui-alarm-panel-card")
|
||||
class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement() {
|
||||
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() private _config?: Config;
|
||||
|
||||
@property() private _code?: string;
|
||||
|
||||
public getCardSize(): number {
|
||||
@ -205,98 +209,109 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
this._code = "";
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
ha-card {
|
||||
padding-bottom: 16px;
|
||||
position: relative;
|
||||
--alarm-color-disarmed: var(--label-badge-green);
|
||||
--alarm-color-pending: var(--label-badge-yellow);
|
||||
--alarm-color-triggered: var(--label-badge-red);
|
||||
--alarm-color-armed: var(--label-badge-red);
|
||||
--alarm-color-autoarm: rgba(0, 153, 255, 0.1);
|
||||
--alarm-state-color: var(--alarm-color-armed);
|
||||
--base-unit: 15px;
|
||||
font-size: calc(var(--base-unit));
|
||||
}
|
||||
ha-label-badge {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
padding-bottom: 16px;
|
||||
position: relative;
|
||||
--alarm-color-disarmed: var(--label-badge-green);
|
||||
--alarm-color-pending: var(--label-badge-yellow);
|
||||
--alarm-color-triggered: var(--label-badge-red);
|
||||
--alarm-color-armed: var(--label-badge-red);
|
||||
--alarm-color-autoarm: rgba(0, 153, 255, 0.1);
|
||||
--alarm-state-color: var(--alarm-color-armed);
|
||||
--base-unit: 15px;
|
||||
font-size: calc(var(--base-unit));
|
||||
}
|
||||
|
||||
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);
|
||||
--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);
|
||||
100% {
|
||||
--ha-label-badge-color: rgba(255, 153, 0, 0.3);
|
||||
}
|
||||
.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);
|
||||
}
|
||||
100% {
|
||||
--ha-label-badge-color: rgba(255, 153, 0, 0.3);
|
||||
}
|
||||
}
|
||||
paper-input {
|
||||
margin: 0 auto 8px;
|
||||
max-width: 150px;
|
||||
font-size: calc(var(--base-unit));
|
||||
text-align: center;
|
||||
}
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
font-size: calc(var(--base-unit) * 0.9);
|
||||
position: relative;
|
||||
bottom: 16px;
|
||||
color: var(--alarm-state-color);
|
||||
animation: none;
|
||||
}
|
||||
#keypad {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin: auto;
|
||||
width: 300px;
|
||||
}
|
||||
#keypad mwc-button {
|
||||
margin-bottom: 5%;
|
||||
width: 30%;
|
||||
padding: calc(var(--base-unit));
|
||||
font-size: calc(var(--base-unit) * 1.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.actions {
|
||||
margin: 0 8px;
|
||||
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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
paper-input {
|
||||
margin: 0 auto 8px;
|
||||
max-width: 150px;
|
||||
font-size: calc(var(--base-unit));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
font-size: calc(var(--base-unit) * 0.9);
|
||||
position: relative;
|
||||
bottom: 16px;
|
||||
color: var(--alarm-state-color);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
#keypad {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin: auto;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#keypad mwc-button {
|
||||
margin-bottom: 5%;
|
||||
width: 30%;
|
||||
padding: calc(var(--base-unit));
|
||||
font-size: calc(var(--base-unit) * 1.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 0 8px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-alarm-panel-card", HuiAlarmPanelCard);
|
||||
|
@ -1,10 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-card/paper-card";
|
||||
@ -18,8 +19,9 @@ export interface Config extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
@customElement("hui-empty-state-card")
|
||||
export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
|
||||
public hass?: HomeAssistant;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 2;
|
||||
@ -29,12 +31,6 @@ export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
|
||||
// tslint:disable-next-line
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@ -83,5 +79,3 @@ declare global {
|
||||
"hui-empty-state-card": HuiEmptyStateCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-empty-state-card", HuiEmptyStateCard);
|
||||
|
@ -1,9 +1,12 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
@ -36,6 +39,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
@customElement("hui-entities-card")
|
||||
class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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: [] };
|
||||
}
|
||||
|
||||
@property() protected _config?: EntitiesCardConfig;
|
||||
|
||||
protected _hass?: HomeAssistant;
|
||||
protected _config?: EntitiesCardConfig;
|
||||
|
||||
protected _configEntities?: EntitiesCardEntityConfig[];
|
||||
|
||||
set hass(hass: HomeAssistant) {
|
||||
@ -65,12 +71,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
if (!this._config) {
|
||||
return 0;
|
||||
@ -100,7 +100,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
const { show_header_toggle, title } = this._config;
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card>
|
||||
${!title && !show_header_toggle
|
||||
? html``
|
||||
@ -128,38 +127,52 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
padding: 16px;
|
||||
}
|
||||
#states {
|
||||
margin: -4px 0;
|
||||
}
|
||||
#states > * {
|
||||
margin: 8px 0;
|
||||
}
|
||||
#states > div > * {
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
@apply --paper-font-headline;
|
||||
/* overwriting line-height +8 because entity-toggle can be 40px height,
|
||||
compensating this with reduced padding */
|
||||
line-height: 40px;
|
||||
color: var(--primary-text-color);
|
||||
padding: 4px 0 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header .name {
|
||||
@apply --paper-font-common-nowrap;
|
||||
}
|
||||
.state-card-dialog {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#states {
|
||||
margin: -4px 0;
|
||||
}
|
||||
|
||||
#states > * {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
#states > div > * {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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;
|
||||
color: var(--primary-text-color);
|
||||
padding: 4px 0 12px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-entities-card", HuiEntitiesCard);
|
||||
|
@ -1,11 +1,12 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
@ -34,6 +35,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
hold_action?: ActionConfig;
|
||||
}
|
||||
|
||||
@customElement("hui-entity-button-card")
|
||||
class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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;
|
||||
private _config?: Config;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 2;
|
||||
@ -147,11 +143,13 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
||||
padding: 4% 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
width: 40%;
|
||||
height: auto;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
}
|
||||
|
||||
ha-icon[data-domain="light"][data-state="on"],
|
||||
ha-icon[data-domain="switch"][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"] {
|
||||
color: var(--paper-item-icon-active-color, #fdd835);
|
||||
}
|
||||
|
||||
ha-icon[data-state="unavailable"] {
|
||||
color: var(--state-icon-unavailable-color);
|
||||
}
|
||||
@ -198,5 +197,3 @@ declare global {
|
||||
"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 { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
@ -21,15 +29,11 @@ export const createErrorCardConfig = (error, origConfig) => ({
|
||||
origConfig,
|
||||
});
|
||||
|
||||
@customElement("hui-error-card")
|
||||
export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 4;
|
||||
@ -45,22 +49,20 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()} ${this._config.error}
|
||||
${this._config.error}
|
||||
<pre>${this._toStr(this._config.origConfig)}</pre>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
background-color: #ef5350;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
background-color: #ef5350;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -74,5 +76,3 @@ declare global {
|
||||
"hui-error-card": HuiErrorCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-error-card", HuiErrorCard);
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
|
||||
@ -45,6 +46,7 @@ export const severityMap = {
|
||||
normal: "var(--label-badge-blue)",
|
||||
};
|
||||
|
||||
@customElement("hui-gauge-card")
|
||||
class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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() private _config?: Config;
|
||||
|
||||
private _updated?: boolean;
|
||||
|
||||
public getCardSize(): number {
|
||||
@ -306,5 +310,3 @@ declare global {
|
||||
"hui-gauge-card": HuiGaugeCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-gauge-card", HuiGaugeCard);
|
||||
|
@ -2,8 +2,11 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@ -29,34 +32,32 @@ export interface ConfigEntity extends EntityConfig {
|
||||
hold_action?: ActionConfig;
|
||||
}
|
||||
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
export interface GlanceCardConfig extends LovelaceCardConfig {
|
||||
show_name?: boolean;
|
||||
show_state?: boolean;
|
||||
show_icon?: boolean;
|
||||
title?: string;
|
||||
theme?: string;
|
||||
entities: ConfigEntity[];
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
@customElement("hui-glance-card")
|
||||
export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-glance-card-editor" */ "../editor/config-elements/hui-glance-card-editor");
|
||||
return document.createElement("hui-glance-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return { entities: [] };
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _configEntities?: ConfigEntity[];
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: GlanceCardConfig;
|
||||
|
||||
private _configEntities?: ConfigEntity[];
|
||||
|
||||
public getCardSize(): number {
|
||||
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 };
|
||||
const entities = processConfigEntities<ConfigEntity>(config.entities);
|
||||
|
||||
@ -120,7 +121,6 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
const { title } = this._config;
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card .header="${title}">
|
||||
<div class="entities ${classMap({ "no-header": !title })}">
|
||||
${this._configEntities!.map((entityConf) =>
|
||||
@ -143,41 +143,39 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.entities {
|
||||
display: flex;
|
||||
padding: 0 16px 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.entities.no-header {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.entity {
|
||||
box-sizing: border-box;
|
||||
padding: 0 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
width: var(--glance-column-width, 20%);
|
||||
}
|
||||
.entity div {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.name {
|
||||
min-height: var(--paper-font-body1_-_line-height, 20px);
|
||||
}
|
||||
state-badge {
|
||||
margin: 8px 0;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.entities {
|
||||
display: flex;
|
||||
padding: 0 16px 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.entities.no-header {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.entity {
|
||||
box-sizing: border-box;
|
||||
padding: 0 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
width: var(--glance-column-width, 20%);
|
||||
}
|
||||
.entity div {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.name {
|
||||
min-height: var(--paper-font-body1_-_line-height, 20px);
|
||||
}
|
||||
state-badge {
|
||||
margin: 8px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -213,10 +211,14 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<state-badge
|
||||
.stateObj="${stateObj}"
|
||||
.overrideIcon="${entityConf.icon}"
|
||||
></state-badge>
|
||||
${this._config!.show_icon !== false
|
||||
? html`
|
||||
<state-badge
|
||||
.stateObj="${stateObj}"
|
||||
.overrideIcon="${entityConf.icon}"
|
||||
></state-badge>
|
||||
`
|
||||
: ""}
|
||||
${this._config!.show_state !== false
|
||||
? html`
|
||||
<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;
|
||||
handleClick(this, this.hass!, config, false);
|
||||
}
|
||||
|
||||
private _handleHold(ev: MouseEvent) {
|
||||
private _handleHold(ev: MouseEvent): void {
|
||||
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
||||
handleClick(this, this.hass!, config, true);
|
||||
}
|
||||
@ -248,5 +250,3 @@ declare global {
|
||||
"hui-glance-card": HuiGlanceCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-glance-card", HuiGlanceCard);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
@ -17,6 +20,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
url: string;
|
||||
}
|
||||
|
||||
@customElement("hui-iframe-card")
|
||||
export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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%" };
|
||||
}
|
||||
|
||||
protected _config?: Config;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() protected _config?: Config;
|
||||
|
||||
public getCardSize(): number {
|
||||
if (!this._config) {
|
||||
@ -60,7 +58,6 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
const aspectRatio = this._config.aspect_ratio || "50%";
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card .header="${this._config.title}">
|
||||
<div
|
||||
id="root"
|
||||
@ -74,25 +71,25 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
iframe {
|
||||
position: absolute;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -102,5 +99,3 @@ declare global {
|
||||
"hui-iframe-card": HuiIframeCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-iframe-card", HuiIframeCard);
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
|
||||
@ -45,6 +46,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
@customElement("hui-light-card")
|
||||
export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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() private _config?: Config;
|
||||
|
||||
@property() private _roundSliderStyle?: TemplateResult;
|
||||
|
||||
@property() private _jQuery?: any;
|
||||
|
||||
private _brightnessTimout?: number;
|
||||
|
||||
public getCardSize(): number {
|
||||
@ -183,6 +189,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@ -193,6 +200,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
--brightness-font-size: 1.2rem;
|
||||
--rail-border-color: transparent;
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -202,6 +210,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
text-align: center;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.icon-state {
|
||||
display: block;
|
||||
margin: auto;
|
||||
@ -209,40 +218,50 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
height: 100%;
|
||||
transform: translate(0, 25%);
|
||||
}
|
||||
|
||||
#light {
|
||||
margin: 0 auto;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
#light .rs-bar.rs-transition.rs-first,
|
||||
.rs-bar.rs-transition.rs-second {
|
||||
z-index: 20 !important;
|
||||
}
|
||||
|
||||
#light .rs-range-color {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
#light .rs-path-color {
|
||||
background-color: var(--disabled-text-color);
|
||||
}
|
||||
|
||||
#light .rs-handle {
|
||||
background-color: var(--paper-card-background-color, white);
|
||||
padding: 7px;
|
||||
border: 2px solid var(--disabled-text-color);
|
||||
}
|
||||
|
||||
#light .rs-handle.rs-focus {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
#light .rs-handle:after {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
#light .rs-border {
|
||||
border-color: var(--rail-border-color);
|
||||
}
|
||||
|
||||
#light .rs-inner.rs-bg-color.rs-border,
|
||||
#light .rs-overlay.rs-transition.rs-bg-color {
|
||||
background-color: var(--paper-card-background-color, white);
|
||||
}
|
||||
|
||||
.light-icon {
|
||||
margin: auto;
|
||||
width: 76px;
|
||||
@ -250,16 +269,20 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.light-icon[data-state="on"] {
|
||||
color: var(--paper-item-icon-active-color, #fdd835);
|
||||
}
|
||||
|
||||
.light-icon[data-state="unavailable"] {
|
||||
color: var(--state-icon-unavailable-color);
|
||||
}
|
||||
|
||||
.name {
|
||||
padding-top: 40px;
|
||||
font-size: var(--name-font-size);
|
||||
}
|
||||
|
||||
.brightness {
|
||||
font-size: var(--brightness-font-size);
|
||||
position: absolute;
|
||||
@ -276,9 +299,11 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
text-shadow: var(--brightness-font-text-shadow);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.show_brightness {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.more-info {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
@ -352,5 +377,3 @@ declare global {
|
||||
"hui-light-card": HuiLightCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-light-card", HuiLightCard);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@ -17,22 +20,18 @@ export interface Config extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
@customElement("hui-markdown-card")
|
||||
export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-markdown-card-editor" */ "../editor/config-elements/hui-markdown-card-editor");
|
||||
return document.createElement("hui-markdown-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return { content: " " };
|
||||
}
|
||||
|
||||
private _config?: Config;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
public getCardSize(): number {
|
||||
return this._config!.content.split("\n").length;
|
||||
@ -52,7 +51,6 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card .header="${this._config.title}">
|
||||
<ha-markdown
|
||||
class="markdown ${classMap({
|
||||
@ -64,35 +62,40 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
@apply --paper-font-body1;
|
||||
}
|
||||
ha-markdown {
|
||||
display: block;
|
||||
padding: 0 16px 16px;
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
.markdown.no-header {
|
||||
padding-top: 16px;
|
||||
}
|
||||
ha-markdown > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-markdown > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ha-markdown a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-markdown img {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
/* 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 */
|
||||
}
|
||||
ha-markdown {
|
||||
display: block;
|
||||
padding: 0 16px 16px;
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
.markdown.no-header {
|
||||
padding-top: 16px;
|
||||
}
|
||||
ha-markdown > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-markdown > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ha-markdown a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-markdown img {
|
||||
max-width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -102,5 +105,3 @@ declare global {
|
||||
"hui-markdown-card": HuiMarkdownCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-markdown-card", HuiMarkdownCard);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
@ -20,6 +23,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
hold_action?: ActionConfig;
|
||||
}
|
||||
|
||||
@customElement("hui-picture-card")
|
||||
export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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;
|
||||
protected _config?: Config;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { _config: {} };
|
||||
}
|
||||
@property() protected _config?: Config;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 3;
|
||||
@ -59,7 +60,6 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card
|
||||
@ha-click="${this._handleTap}"
|
||||
@ha-hold="${this._handleHold}"
|
||||
@ -75,20 +75,20 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ha-card.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -106,5 +106,3 @@ declare global {
|
||||
"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";
|
||||
|
||||
@ -17,15 +25,11 @@ interface Config extends LovelaceCardConfig {
|
||||
elements: LovelaceElementConfig[];
|
||||
}
|
||||
|
||||
@customElement("hui-picture-elements-card")
|
||||
class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
private _config?: Config;
|
||||
private _hass?: HomeAssistant;
|
||||
@property() private _config?: Config;
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
set hass(hass: HomeAssistant) {
|
||||
this._hass = hass;
|
||||
@ -60,7 +64,6 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card .header="${this._config.title}">
|
||||
<div id="root">
|
||||
<hui-image
|
||||
@ -84,20 +87,20 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
#root {
|
||||
position: relative;
|
||||
}
|
||||
.element {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
#root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.element {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -107,5 +110,3 @@ declare global {
|
||||
"hui-picture-elements-card": HuiPictureElementsCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-picture-elements-card", HuiPictureElementsCard);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@ -34,16 +37,11 @@ interface Config extends LovelaceCardConfig {
|
||||
show_state?: boolean;
|
||||
}
|
||||
|
||||
@customElement("hui-picture-entity-card")
|
||||
class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 3;
|
||||
@ -109,7 +107,6 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card>
|
||||
<hui-image
|
||||
.hass="${this.hass}"
|
||||
@ -132,37 +129,44 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
min-height: 75px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
hui-image.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.footer {
|
||||
@apply --paper-font-common-nowrap;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: white;
|
||||
}
|
||||
.both {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.state {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
min-height: 75px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
hui-image.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footer {
|
||||
/* start paper-font-common-nowrap style */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* end paper-font-common-nowrap style */
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.both {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.state {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -180,5 +184,3 @@ declare global {
|
||||
"hui-picture-entity-card": HuiPictureEntityCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-picture-entity-card", HuiPictureEntityCard);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@ -39,18 +42,15 @@ interface Config extends LovelaceCardConfig {
|
||||
hold_action?: ActionConfig;
|
||||
}
|
||||
|
||||
@customElement("hui-picture-glance-card")
|
||||
class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _entitiesDialog?: EntityConfig[];
|
||||
private _entitiesToggle?: EntityConfig[];
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
private _entitiesDialog?: EntityConfig[];
|
||||
|
||||
private _entitiesToggle?: EntityConfig[];
|
||||
|
||||
public getCardSize(): number {
|
||||
return 3;
|
||||
@ -91,7 +91,6 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card>
|
||||
<hui-image
|
||||
class="${classMap({
|
||||
@ -177,44 +176,52 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
toggleEntity(this.hass!, (ev.target as any).entity);
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
position: relative;
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
hui-image.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.box {
|
||||
@apply --paper-font-common-nowrap;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.box .title {
|
||||
font-weight: 500;
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-icon {
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
color: #a9a9a9;
|
||||
}
|
||||
ha-icon.state-on {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
position: relative;
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
hui-image.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.box {
|
||||
/* start paper-font-common-nowrap style */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* end paper-font-common-nowrap style */
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.box .title {
|
||||
font-weight: 500;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
svg,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
|
||||
@ -145,6 +148,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
hours_to_show?: number;
|
||||
}
|
||||
|
||||
@customElement("hui-sensor-card")
|
||||
class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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 {};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _history?: any;
|
||||
private _date?: Date;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
_history: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
@property() private _history?: any;
|
||||
|
||||
private _date?: Date;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
if (!config.entity || config.entity.split(".")[0] !== "sensor") {
|
||||
@ -244,7 +243,6 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||
graph = "";
|
||||
}
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card @click="${this._handleClick}">
|
||||
<div class="flex">
|
||||
<div class="icon">
|
||||
@ -324,87 +322,95 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||
this._date = new Date();
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
opacity: 0.8;
|
||||
position: relative;
|
||||
}
|
||||
.name {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
max-height: 1.4rem;
|
||||
margin-top: 2px;
|
||||
opacity: 0.8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
.icon {
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
display: inline-block;
|
||||
flex: 0 0 40px;
|
||||
line-height: 40px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
}
|
||||
.info {
|
||||
flex-wrap: wrap;
|
||||
margin: 16px 0 16px 8px;
|
||||
}
|
||||
#value {
|
||||
display: inline-block;
|
||||
font-size: 2rem;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
margin-right: 4px;
|
||||
}
|
||||
#measurement {
|
||||
align-self: flex-end;
|
||||
display: inline-block;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.2em;
|
||||
margin-top: 0.1em;
|
||||
opacity: 0.6;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.graph {
|
||||
align-self: flex-end;
|
||||
margin: auto;
|
||||
margin-bottom: 0px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.graph > div {
|
||||
align-self: flex-end;
|
||||
margin: auto 8px;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
opacity: 0.8;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
max-height: 1.4rem;
|
||||
margin-top: 2px;
|
||||
opacity: 0.8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
display: inline-block;
|
||||
flex: 0 0 40px;
|
||||
line-height: 40px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex-wrap: wrap;
|
||||
margin: 16px 0 16px 8px;
|
||||
}
|
||||
|
||||
#value {
|
||||
display: inline-block;
|
||||
font-size: 2rem;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#measurement {
|
||||
align-self: flex-end;
|
||||
display: inline-block;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.2em;
|
||||
margin-top: 0.1em;
|
||||
opacity: 0.6;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.graph {
|
||||
align-self: flex-end;
|
||||
margin: auto;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-sensor-card", HuiSensorCard);
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { repeat } from "lit-html/directives/repeat";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
@ -28,6 +29,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
@customElement("hui-shopping-list-card")
|
||||
class HuiShoppingListCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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() private _config?: Config;
|
||||
|
||||
@property() private _uncheckedItems?: ShoppingListItem[];
|
||||
|
||||
@property() private _checkedItems?: ShoppingListItem[];
|
||||
|
||||
private _unsubEvents?: Promise<() => Promise<void>>;
|
||||
|
||||
public getCardSize(): number {
|
||||
@ -178,61 +184,68 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
.editRow,
|
||||
.addRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.editRow,
|
||||
.addRow {
|
||||
display: flex;
|
||||
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 {
|
||||
padding: 9px 15px 11px 15px;
|
||||
cursor: pointer;
|
||||
--paper-input-container-underline-focus: {
|
||||
display: none;
|
||||
}
|
||||
paper-item-body {
|
||||
width: 75%;
|
||||
--paper-input-container-underline-disabled: {
|
||||
display: none;
|
||||
}
|
||||
paper-checkbox {
|
||||
padding: 11px 11px 11px 18px;
|
||||
}
|
||||
paper-input {
|
||||
--paper-input-container-underline: {
|
||||
display: none;
|
||||
}
|
||||
--paper-input-container-underline-focus: {
|
||||
display: none;
|
||||
}
|
||||
--paper-input-container-underline-disabled: {
|
||||
display: none;
|
||||
}
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.checked {
|
||||
margin-left: 17px;
|
||||
margin-bottom: 11px;
|
||||
margin-top: 11px;
|
||||
}
|
||||
.label {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: var(--divider-color);
|
||||
margin: 10px;
|
||||
}
|
||||
.clearall {
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
float: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.addRow > ha-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.checked {
|
||||
margin-left: 17px;
|
||||
margin-bottom: 11px;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: var(--divider-color);
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.clearall {
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
float: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.addRow > ha-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
@ -301,5 +314,3 @@ declare global {
|
||||
"hui-shopping-list-card": HuiShoppingListCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-shopping-list-card", HuiShoppingListCard);
|
||||
|
@ -1,9 +1,10 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
@ -52,6 +53,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@customElement("hui-thermostat-card")
|
||||
export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
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: "" };
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _roundSliderStyle?: TemplateResult;
|
||||
private _jQuery?: any;
|
||||
private _broadCard?: boolean;
|
||||
private _loaded?: boolean;
|
||||
private _updated?: boolean;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
roundSliderStyle: {},
|
||||
_jQuery: {},
|
||||
};
|
||||
}
|
||||
@property() private _config?: Config;
|
||||
|
||||
@property() private _roundSliderStyle?: TemplateResult;
|
||||
|
||||
@property() private _jQuery?: any;
|
||||
|
||||
private _broadCard?: boolean;
|
||||
|
||||
private _loaded?: boolean;
|
||||
|
||||
private _updated?: boolean;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 4;
|
||||
@ -574,5 +573,3 @@ declare global {
|
||||
"hui-thermostat-card": HuiThermostatCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-thermostat-card", HuiThermostatCard);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
@ -31,15 +32,15 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-action-editor")
|
||||
export class HuiActionEditor extends LitElement {
|
||||
public config?: ActionConfig;
|
||||
public label?: string;
|
||||
public actions?: string[];
|
||||
protected hass?: HomeAssistant;
|
||||
@property() public config?: ActionConfig;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, config: {}, label: {}, actions: {} };
|
||||
}
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public actions?: string[];
|
||||
|
||||
@property() protected hass?: HomeAssistant;
|
||||
|
||||
get _action(): string {
|
||||
return this.config!.action || "";
|
||||
@ -126,5 +127,3 @@ declare global {
|
||||
"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 "@polymer/paper-menu-button/paper-menu-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
@ -12,63 +20,18 @@ import { Lovelace } from "../types";
|
||||
import { swapCard } from "../editor/config-util";
|
||||
import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog";
|
||||
|
||||
@customElement("hui-card-options")
|
||||
export class HuiCardOptions extends LitElement {
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
public hass?: HomeAssistant;
|
||||
public lovelace?: Lovelace;
|
||||
public path?: [number, number];
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, lovelace: {}, path: {} };
|
||||
}
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
protected render() {
|
||||
@property() public lovelace?: Lovelace;
|
||||
|
||||
@property() public path?: [number, number];
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
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>
|
||||
<div class="options">
|
||||
<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 {
|
||||
showEditCardDialog(this, {
|
||||
lovelace: this.lovelace!,
|
||||
@ -162,5 +173,3 @@ declare global {
|
||||
"hui-card-options": HuiCardOptions;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-card-options", HuiCardOptions);
|
||||
|
@ -1,9 +1,12 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
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 { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("hui-entities-toggle")
|
||||
class HuiEntitiesToggle extends LitElement {
|
||||
public entities?: string[];
|
||||
protected hass?: HomeAssistant;
|
||||
private _toggleEntities?: string[];
|
||||
@property() public entities?: string[];
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
entities: {},
|
||||
_toggleEntities: {},
|
||||
};
|
||||
}
|
||||
@property() protected hass?: HomeAssistant;
|
||||
|
||||
public updated(changedProperties: PropertyValues) {
|
||||
@property() private _toggleEntities?: string[];
|
||||
|
||||
public updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("entities")) {
|
||||
this._toggleEntities = this.entities!.filter(
|
||||
@ -41,7 +39,6 @@ class HuiEntitiesToggle extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-toggle-button
|
||||
?checked="${this._toggleEntities!.some((entityId) => {
|
||||
const stateObj = this.hass!.states[entityId];
|
||||
@ -52,20 +49,18 @@ class HuiEntitiesToggle extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
width: 38px;
|
||||
display: block;
|
||||
}
|
||||
paper-toggle-button {
|
||||
cursor: pointer;
|
||||
--paper-toggle-button-label-spacing: 0;
|
||||
padding: 13px 5px;
|
||||
margin: -4px -5px;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
width: 38px;
|
||||
display: block;
|
||||
}
|
||||
paper-toggle-button {
|
||||
cursor: pointer;
|
||||
--paper-toggle-button-label-spacing: 0;
|
||||
padding: 13px 5px;
|
||||
margin: -4px -5px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -80,5 +75,3 @@ declare global {
|
||||
"hui-entities-toggle": HuiEntitiesToggle;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-entities-toggle", HuiEntitiesToggle);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -12,16 +15,11 @@ import { EntityConfig } from "../entity-rows/types";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
|
||||
@customElement("hui-entity-editor")
|
||||
export class HuiEntityEditor extends LitElement {
|
||||
protected hass?: HomeAssistant;
|
||||
protected entities?: EntityConfig[];
|
||||
@property() protected hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
entities: {},
|
||||
};
|
||||
}
|
||||
@property() protected entities?: EntityConfig[];
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.entities) {
|
||||
@ -29,7 +27,6 @@ export class HuiEntityEditor extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<h3>Entities</h3>
|
||||
<div class="entities">
|
||||
${this.entities.map((entityConf, index) => {
|
||||
@ -79,13 +76,11 @@ export class HuiEntityEditor extends LitElement {
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.entities {
|
||||
padding-left: 20px;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.entities {
|
||||
padding-left: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -95,5 +90,3 @@ declare global {
|
||||
"hui-entity-editor": HuiEntityEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-entity-editor", HuiEntityEditor);
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
CSSResult,
|
||||
PropertyValues,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -19,10 +20,12 @@ import "../components/hui-warning";
|
||||
|
||||
class HuiGenericEntityRow extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() public config?: EntitiesCardEntityConfig;
|
||||
|
||||
@property() public showSecondary: boolean = true;
|
||||
|
||||
protected render() {
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass || !this.config) {
|
||||
return html``;
|
||||
}
|
||||
@ -73,7 +76,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("hass")) {
|
||||
this.toggleAttribute("rtl", computeRTL(this.hass!));
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
css,
|
||||
PropertyValues,
|
||||
query,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
@ -26,33 +27,43 @@ export interface StateSpecificConfig {
|
||||
[state: string]: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
@customElement("hui-image")
|
||||
class HuiImage extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() public entity?: string;
|
||||
|
||||
@property() public image?: string;
|
||||
|
||||
@property() public stateImage?: StateSpecificConfig;
|
||||
|
||||
@property() public cameraImage?: string;
|
||||
|
||||
@property() public aspectRatio?: string;
|
||||
|
||||
@property() public filter?: string;
|
||||
|
||||
@property() public stateFilter?: StateSpecificConfig;
|
||||
|
||||
@property() private _loadError?: boolean;
|
||||
|
||||
@property() private _cameraImageSrc?: string;
|
||||
|
||||
@query("img") private _image!: HTMLImageElement;
|
||||
|
||||
private _lastImageHeight?: number;
|
||||
|
||||
private _cameraUpdater?: number;
|
||||
|
||||
private _attached?: boolean;
|
||||
|
||||
public connectedCallback() {
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._attached = true;
|
||||
this._startUpdateCameraInterval();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._attached = false;
|
||||
this._stopUpdateCameraInterval();
|
||||
@ -137,7 +148,7 @@ class HuiImage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _startUpdateCameraInterval() {
|
||||
private _startUpdateCameraInterval(): void {
|
||||
this._stopUpdateCameraInterval();
|
||||
if (this.cameraImage && this._attached) {
|
||||
this._cameraUpdater = window.setInterval(
|
||||
@ -147,23 +158,23 @@ class HuiImage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _stopUpdateCameraInterval() {
|
||||
private _stopUpdateCameraInterval(): void {
|
||||
if (this._cameraUpdater) {
|
||||
clearInterval(this._cameraUpdater);
|
||||
}
|
||||
}
|
||||
|
||||
private _onImageError() {
|
||||
private _onImageError(): void {
|
||||
this._loadError = true;
|
||||
}
|
||||
|
||||
private async _onImageLoad() {
|
||||
private async _onImageLoad(): Promise<void> {
|
||||
this._loadError = false;
|
||||
await this.updateComplete;
|
||||
this._lastImageHeight = this._image.offsetHeight;
|
||||
}
|
||||
|
||||
private async _updateCameraImageSrc() {
|
||||
private async _updateCameraImageSrc(): Promise<void> {
|
||||
if (!this.hass || !this.cameraImage) {
|
||||
return;
|
||||
}
|
||||
@ -221,5 +232,3 @@ declare global {
|
||||
"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 {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
|
||||
@ -20,16 +23,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export class HuiThemeSelectionEditor extends LitElement {
|
||||
public value?: string;
|
||||
public hass?: HomeAssistant;
|
||||
@customElement("hui-theme-select-editor")
|
||||
export class HuiThemeSelectEditor extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
value: {},
|
||||
};
|
||||
}
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const themes = ["Backend-selected", "default"].concat(
|
||||
@ -37,7 +35,6 @@ export class HuiThemeSelectionEditor extends LitElement {
|
||||
);
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-dropdown-menu
|
||||
label="Theme"
|
||||
dynamic-align
|
||||
@ -58,13 +55,11 @@ export class HuiThemeSelectionEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -79,8 +74,6 @@ export class HuiThemeSelectionEditor extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-theme-select-editor": HuiThemeSelectionEditor;
|
||||
"hui-theme-select-editor": HuiThemeSelectEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-theme-select-editor", HuiThemeSelectionEditor);
|
||||
|
@ -1,9 +1,10 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -19,30 +20,32 @@ const FORMATS: { [key: string]: (ts: Date, lang: string) => string } = {
|
||||
};
|
||||
const INTERVAL_FORMAT = ["relative", "total"];
|
||||
|
||||
@customElement("hui-timestamp-display")
|
||||
class HuiTimestampDisplay extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public ts?: Date;
|
||||
public format?: "relative" | "total" | "date" | "datetime" | "time";
|
||||
private _relative?: string;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() public ts?: Date;
|
||||
|
||||
@property() public format?:
|
||||
| "relative"
|
||||
| "total"
|
||||
| "date"
|
||||
| "datetime"
|
||||
| "time";
|
||||
|
||||
@property() private _relative?: string;
|
||||
|
||||
private _connected?: boolean;
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
ts: {},
|
||||
hass: {},
|
||||
format: {},
|
||||
_relative: {},
|
||||
};
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._connected = true;
|
||||
this._startInterval();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._connected = false;
|
||||
this._clearInterval();
|
||||
@ -65,18 +68,18 @@ class HuiTimestampDisplay extends LitElement {
|
||||
return html`
|
||||
${this._relative}
|
||||
`;
|
||||
} else if (format in FORMATS) {
|
||||
}
|
||||
if (format in FORMATS) {
|
||||
return html`
|
||||
${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);
|
||||
if (!changedProperties.has("format") || !this._connected) {
|
||||
return;
|
||||
@ -89,11 +92,11 @@ class HuiTimestampDisplay extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private get _format() {
|
||||
private get _format(): string {
|
||||
return this.format || "relative";
|
||||
}
|
||||
|
||||
private _startInterval() {
|
||||
private _startInterval(): void {
|
||||
this._clearInterval();
|
||||
if (this._connected && INTERVAL_FORMAT.includes(this._format)) {
|
||||
this._updateRelative();
|
||||
@ -101,14 +104,14 @@ class HuiTimestampDisplay extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _clearInterval() {
|
||||
private _clearInterval(): void {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateRelative() {
|
||||
private _updateRelative(): void {
|
||||
if (this.ts && this.hass!.localize) {
|
||||
this._relative =
|
||||
this._format === "relative"
|
||||
@ -126,5 +129,3 @@ declare global {
|
||||
"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 { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { customElement } from "lit-element";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -16,9 +17,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-yaml-editor")
|
||||
export class HuiYamlEditor extends HTMLElement {
|
||||
public _hass?: HomeAssistant;
|
||||
|
||||
public codemirror: CodeMirror;
|
||||
|
||||
private _value: string;
|
||||
|
||||
public constructor() {
|
||||
@ -108,7 +112,7 @@ export class HuiYamlEditor extends HTMLElement {
|
||||
fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
|
||||
}
|
||||
|
||||
private setScrollBarDirection() {
|
||||
private setScrollBarDirection(): void {
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
@ -124,5 +128,3 @@ declare global {
|
||||
"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 { HomeAssistant } from "../../../../types";
|
||||
@ -33,8 +40,10 @@ const cards = [
|
||||
{ name: "Weather Forecast", type: "weather-forecast" },
|
||||
];
|
||||
|
||||
@customElement("hui-card-picker")
|
||||
export class HuiCardPicker extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@ -97,5 +106,3 @@ declare global {
|
||||
"hui-card-picker": HuiCardPicker;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-card-picker", HuiCardPicker);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@ -23,18 +24,13 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-dialog-edit-card")
|
||||
export class HuiDialogEditCard extends LitElement {
|
||||
protected hass?: HomeAssistant;
|
||||
private _params?: EditCardDialogParams;
|
||||
private _cardConfig?: LovelaceCardConfig;
|
||||
@property() protected hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_params: {},
|
||||
_cardConfig: {},
|
||||
};
|
||||
}
|
||||
@property() private _params?: EditCardDialogParams;
|
||||
|
||||
@property() private _cardConfig?: LovelaceCardConfig;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -78,11 +74,11 @@ export class HuiDialogEditCard extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _cardPicked(cardConf: LovelaceCardConfig) {
|
||||
private _cardPicked(cardConf: LovelaceCardConfig): void {
|
||||
this._cardConfig = cardConf;
|
||||
}
|
||||
|
||||
private _cancel() {
|
||||
private _cancel(): void {
|
||||
this._params = undefined;
|
||||
this._cardConfig = undefined;
|
||||
}
|
||||
@ -93,5 +89,3 @@ declare global {
|
||||
"hui-dialog-edit-card": HuiDialogEditCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-edit-card", HuiDialogEditCard);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@ -13,14 +16,9 @@ import { moveCard } from "../config-util";
|
||||
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
|
||||
@customElement("hui-dialog-move-card-view")
|
||||
export class HuiDialogMoveCardView extends LitElement {
|
||||
private _params?: MoveCardViewDialogParams;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_params: {},
|
||||
};
|
||||
}
|
||||
@property() private _params?: MoveCardViewDialogParams;
|
||||
|
||||
public async showDialog(params: MoveCardViewDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
@ -32,29 +30,6 @@ export class HuiDialogMoveCardView extends LitElement {
|
||||
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
|
||||
with-backdrop
|
||||
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 {
|
||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||
}
|
||||
@ -104,5 +105,3 @@ declare global {
|
||||
"hui-dialog-move-card-view": HuiDialogMoveCardView;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-move-card-view", HuiDialogMoveCardView);
|
||||
|
@ -2,9 +2,9 @@ import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
@ -15,15 +15,12 @@ import "./hui-card-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
|
||||
@customElement("hui-dialog-pick-card")
|
||||
export class HuiDialogPickCard extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||
public closeDialog?: () => void;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<paper-dialog
|
||||
@ -47,7 +44,7 @@ export class HuiDialogPickCard extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _openedChanged(ev) {
|
||||
private _openedChanged(ev): void {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog!();
|
||||
}
|
||||
@ -88,5 +85,3 @@ declare global {
|
||||
"hui-dialog-pick-card": HuiDialogPickCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-pick-card", HuiDialogPickCard);
|
||||
|
@ -2,10 +2,11 @@ import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import yaml from "js-yaml";
|
||||
@ -49,36 +50,33 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-edit-card")
|
||||
export class HuiEditCard extends LitElement {
|
||||
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;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
cardConfig: {},
|
||||
viewIndex: {},
|
||||
_cardIndex: {},
|
||||
_configElement: {},
|
||||
_configValue: {},
|
||||
_configState: {},
|
||||
_errorMsg: {},
|
||||
_uiEditor: {},
|
||||
_saving: {},
|
||||
_loading: {},
|
||||
};
|
||||
}
|
||||
@property() public cardConfig?: LovelaceCardConfig;
|
||||
|
||||
public lovelace?: Lovelace;
|
||||
|
||||
public path?: [number] | [number, number];
|
||||
|
||||
public closeDialog?: () => void;
|
||||
|
||||
@property() private _configElement?: LovelaceCardEditor | null;
|
||||
|
||||
@property() private _uiEditor?: boolean;
|
||||
|
||||
@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 {
|
||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||
@ -88,7 +86,7 @@ export class HuiEditCard extends LitElement {
|
||||
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
||||
}
|
||||
|
||||
protected constructor() {
|
||||
public constructor() {
|
||||
super();
|
||||
this._saving = false;
|
||||
}
|
||||
@ -270,7 +268,7 @@ export class HuiEditCard extends LitElement {
|
||||
this._updatePreview(value);
|
||||
}
|
||||
|
||||
private async _updatePreview(config: LovelaceCardConfig) {
|
||||
private async _updatePreview(config: LovelaceCardConfig): Promise<void> {
|
||||
await this.updateComplete;
|
||||
|
||||
if (!this._previewEl) {
|
||||
@ -286,7 +284,7 @@ export class HuiEditCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _setPreviewError(error: ConfigError) {
|
||||
private _setPreviewError(error: ConfigError): void {
|
||||
if (!this._previewEl) {
|
||||
return;
|
||||
}
|
||||
@ -323,7 +321,7 @@ export class HuiEditCard extends LitElement {
|
||||
this._resizeDialog();
|
||||
}
|
||||
|
||||
private _isConfigValid() {
|
||||
private _isConfigValid(): boolean {
|
||||
if (!this._configValue || !this._configValue.value) {
|
||||
return false;
|
||||
}
|
||||
@ -401,7 +399,7 @@ export class HuiEditCard extends LitElement {
|
||||
return this.path!.length === 1;
|
||||
}
|
||||
|
||||
private _openedChanged(ev) {
|
||||
private _openedChanged(ev): void {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog!();
|
||||
}
|
||||
@ -518,5 +516,3 @@ declare global {
|
||||
"hui-edit-card": HuiEditCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-edit-card", HuiEditCard);
|
||||
|
@ -17,7 +17,7 @@ export interface EditCardDialogParams {
|
||||
path: [number] | [number, number];
|
||||
}
|
||||
|
||||
const registerEditCardDialog = (element: HTMLElement) =>
|
||||
const registerEditCardDialog = (element: HTMLElement): Event =>
|
||||
fireEvent(element, "register-dialog", {
|
||||
dialogShowEvent,
|
||||
dialogTag,
|
||||
@ -28,7 +28,7 @@ const registerEditCardDialog = (element: HTMLElement) =>
|
||||
export const showEditCardDialog = (
|
||||
element: HTMLElement,
|
||||
editCardDialogParams: EditCardDialogParams
|
||||
) => {
|
||||
): void => {
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
registerEditCardDialog(element);
|
||||
|
@ -15,7 +15,7 @@ export interface MoveCardViewDialogParams {
|
||||
lovelace: Lovelace;
|
||||
}
|
||||
|
||||
const registerEditCardDialog = (element: HTMLElement) =>
|
||||
const registerEditCardDialog = (element: HTMLElement): Event =>
|
||||
fireEvent(element, "register-dialog", {
|
||||
dialogShowEvent: "show-move-card-view",
|
||||
dialogTag: "hui-dialog-move-card-view",
|
||||
@ -26,7 +26,7 @@ const registerEditCardDialog = (element: HTMLElement) =>
|
||||
export const showMoveCardViewDialog = (
|
||||
element: HTMLElement,
|
||||
moveCardViewDialogParams: MoveCardViewDialogParams
|
||||
) => {
|
||||
): void => {
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
registerEditCardDialog(element);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@ -26,20 +29,18 @@ const cardConfigStruct = struct({
|
||||
states: "array?",
|
||||
});
|
||||
|
||||
@customElement("hui-alarm-panel-card-editor")
|
||||
export class HuiAlarmPanelCardEditor extends LitElement
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() private _config?: Config;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
@ -60,7 +61,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
|
||||
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
|
||||
|
||||
return html`
|
||||
${configElementStyle} ${this.renderStyle()}
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
@ -107,23 +108,21 @@ export class HuiAlarmPanelCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.states {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.deleteState {
|
||||
visibility: hidden;
|
||||
}
|
||||
.states:hover > .deleteState {
|
||||
visibility: visible;
|
||||
}
|
||||
ha-icon {
|
||||
padding-top: 12px;
|
||||
}
|
||||
</style>
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.states {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.deleteState {
|
||||
visibility: hidden;
|
||||
}
|
||||
.states:hover > .deleteState {
|
||||
visibility: visible;
|
||||
}
|
||||
ha-icon {
|
||||
padding-top: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -190,5 +189,3 @@ declare global {
|
||||
"hui-alarm-panel-card-editor": HuiAlarmPanelCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-alarm-panel-card-editor", HuiAlarmPanelCardEditor);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@ -44,10 +45,19 @@ const cardConfigStruct = struct({
|
||||
entities: [entitiesConfigStruct],
|
||||
});
|
||||
|
||||
@customElement("hui-entities-card-editor")
|
||||
export class HuiEntitiesCardEditor extends LitElement
|
||||
implements LovelaceCardEditor {
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {}, _configEntities: {} };
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@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 {
|
||||
@ -58,16 +68,6 @@ export class HuiEntitiesCardEditor extends LitElement
|
||||
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 {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@ -141,5 +141,3 @@ declare global {
|
||||
"hui-entities-card-editor": HuiEntitiesCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-entities-card-editor", HuiEntitiesCardEditor);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
@ -33,20 +34,18 @@ const cardConfigStruct = struct({
|
||||
theme: "string?",
|
||||
});
|
||||
|
||||
@customElement("hui-entity-button-card-editor")
|
||||
export class HuiEntityButtonCardEditor extends LitElement
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() private _config?: Config;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
@ -161,8 +160,3 @@ declare global {
|
||||
"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