mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-08 20:56:34 +00:00
Compare commits
71 Commits
20230531.0
...
ha-form-co
Author | SHA1 | Date | |
---|---|---|---|
![]() |
261cc6598d | ||
![]() |
8580d3f9bf | ||
![]() |
6d29b764d3 | ||
![]() |
78cff3a921 | ||
![]() |
e3c312feaf | ||
![]() |
31e4166248 | ||
![]() |
0442e3e06e | ||
![]() |
0a8252c16a | ||
![]() |
0a62d711f2 | ||
![]() |
d7c3ff3e9d | ||
![]() |
2767f866f3 | ||
![]() |
040d5af0aa | ||
![]() |
06c6e312b0 | ||
![]() |
841dffe563 | ||
![]() |
a41e0d446f | ||
![]() |
9a0f24cd8b | ||
![]() |
2e531a9006 | ||
![]() |
76255f2efb | ||
![]() |
19fc92419a | ||
![]() |
e7c2625cf1 | ||
![]() |
c39fdcda6e | ||
![]() |
fd1381ab3b | ||
![]() |
7b8f4d1e72 | ||
![]() |
b0a278df97 | ||
![]() |
93e31df106 | ||
![]() |
47fdae764f | ||
![]() |
b8efc06caa | ||
![]() |
fcacdf6534 | ||
![]() |
45d260f0ce | ||
![]() |
d5f46a69b0 | ||
![]() |
fe8eb333b9 | ||
![]() |
677cd2de10 | ||
![]() |
1470eb484f | ||
![]() |
10ee8fda5b | ||
![]() |
e044ddcb57 | ||
![]() |
29c564bb69 | ||
![]() |
1bf03f020e | ||
![]() |
6c684fd8ee | ||
![]() |
ddaf403378 | ||
![]() |
b337074758 | ||
![]() |
a96eff4d25 | ||
![]() |
e6bdc3a15e | ||
![]() |
33e15eec22 | ||
![]() |
3c0afd6cde | ||
![]() |
31a3fa02d9 | ||
![]() |
71954f545c | ||
![]() |
9f3e8abe69 | ||
![]() |
21f983572c | ||
![]() |
5667d71b02 | ||
![]() |
0d0e5fdaaa | ||
![]() |
b1f5ff26d9 | ||
![]() |
27451ca30e | ||
![]() |
928b4e6f1e | ||
![]() |
b63a32109e | ||
![]() |
efb0098eac | ||
![]() |
a67b845812 | ||
![]() |
d29b7626f3 | ||
![]() |
47ac7062dc | ||
![]() |
e7f5d927b1 | ||
![]() |
6b06393559 | ||
![]() |
efa02c309b | ||
![]() |
9b2e77e781 | ||
![]() |
24b4060c97 | ||
![]() |
5e4c1ab4fc | ||
![]() |
287e6dbb60 | ||
![]() |
40c9292e16 | ||
![]() |
d51dd00ec7 | ||
![]() |
0db50d13d3 | ||
![]() |
9eb3618d97 | ||
![]() |
03eee9c7d5 | ||
![]() |
a49d59f4c6 |
File diff suppressed because one or more lines are too long
@@ -8,4 +8,4 @@ plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.5.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.6.0.cjs
|
||||
|
@@ -44,7 +44,10 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<ha-markdown .content=${this._content}></ha-markdown>`
|
||||
? html`<ha-markdown
|
||||
.content=${this._content}
|
||||
lazy-images
|
||||
></ha-markdown>`
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@@ -659,6 +659,7 @@ class HassioAddonInfo extends LitElement {
|
||||
<div class="card-content">
|
||||
<ha-markdown
|
||||
.content=${this.addon.long_description}
|
||||
lazy-images
|
||||
></ha-markdown>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
|
@@ -7,6 +7,7 @@ import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
||||
import { slugify } from "../../../../src/common/string/slugify";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
|
40
package.json
40
package.json
@@ -48,10 +48,10 @@
|
||||
"@fullcalendar/list": "6.1.8",
|
||||
"@fullcalendar/timegrid": "6.1.8",
|
||||
"@lezer/highlight": "1.1.6",
|
||||
"@lit-labs/context": "0.3.1",
|
||||
"@lit-labs/context": "0.3.2",
|
||||
"@lit-labs/motion": "1.0.3",
|
||||
"@lit-labs/virtualizer": "2.0.2",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.0",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-button": "0.27.0",
|
||||
@@ -92,8 +92,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.0.7",
|
||||
"@vaadin/vaadin-themable-mixin": "24.0.7",
|
||||
"@vaadin/combo-box": "24.0.8",
|
||||
"@vaadin/vaadin-themable-mixin": "24.0.8",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -118,7 +118,7 @@
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.7.4",
|
||||
"lit": "2.7.5",
|
||||
"marked": "4.3.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
@@ -140,29 +140,29 @@
|
||||
"vue": "2.7.14",
|
||||
"vue2-daterange-picker": "0.6.8",
|
||||
"weekstart": "2.0.0",
|
||||
"workbox-cacheable-response": "6.6.0",
|
||||
"workbox-core": "6.6.0",
|
||||
"workbox-expiration": "6.6.0",
|
||||
"workbox-precaching": "6.6.0",
|
||||
"workbox-routing": "6.6.0",
|
||||
"workbox-strategies": "6.6.0",
|
||||
"workbox-cacheable-response": "7.0.0",
|
||||
"workbox-core": "7.0.0",
|
||||
"workbox-expiration": "7.0.0",
|
||||
"workbox-precaching": "7.0.0",
|
||||
"workbox-routing": "7.0.0",
|
||||
"workbox-strategies": "7.0.0",
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.22.1",
|
||||
"@babel/plugin-proposal-decorators": "7.22.3",
|
||||
"@babel/plugin-transform-runtime": "7.22.2",
|
||||
"@babel/preset-env": "7.22.2",
|
||||
"@babel/plugin-transform-runtime": "7.22.4",
|
||||
"@babel/preset-env": "7.22.4",
|
||||
"@babel/preset-typescript": "7.21.5",
|
||||
"@koa/cors": "4.0.0",
|
||||
"@octokit/auth-oauth-device": "4.0.4",
|
||||
"@octokit/plugin-retry": "4.1.3",
|
||||
"@octokit/plugin-retry": "5.0.0",
|
||||
"@octokit/rest": "19.0.11",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-commonjs": "25.0.0",
|
||||
"@rollup/plugin-json": "6.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.0.2",
|
||||
"@rollup/plugin-node-resolve": "15.1.0",
|
||||
"@rollup/plugin-replace": "5.0.2",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.2",
|
||||
"@types/chromecast-caf-receiver": "6.0.9",
|
||||
@@ -180,15 +180,15 @@
|
||||
"@types/sortablejs": "1.15.1",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.7",
|
||||
"@typescript-eslint/parser": "5.59.7",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.8",
|
||||
"@typescript-eslint/parser": "5.59.8",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "4.3.7",
|
||||
"del": "7.0.0",
|
||||
"eslint": "8.41.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.0.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
@@ -243,7 +243,7 @@
|
||||
"webpack-dev-server": "4.15.0",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpackbar": "5.0.2",
|
||||
"workbox-build": "6.6.0"
|
||||
"workbox-build": "7.0.0"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
"resolutions": {
|
||||
@@ -255,5 +255,5 @@
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
},
|
||||
"packageManager": "yarn@3.5.1"
|
||||
"packageManager": "yarn@3.6.0"
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230531.0"
|
||||
version = "20230608.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -83,6 +83,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
configurator: mdiCog,
|
||||
conversation: mdiMicrophoneMessage,
|
||||
counter: mdiCounter,
|
||||
datetime: mdiCalendarClock,
|
||||
date: mdiCalendar,
|
||||
demo: mdiHomeAssistant,
|
||||
google_assistant: mdiGoogleAssistant,
|
||||
@@ -112,6 +113,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
siren: mdiBullhorn,
|
||||
stt: mdiMicrophoneMessage,
|
||||
text: mdiFormTextbox,
|
||||
time: mdiClock,
|
||||
timer: mdiTimerOutline,
|
||||
tts: mdiSpeakerMessage,
|
||||
updater: mdiCloudUpload,
|
||||
|
@@ -17,7 +17,7 @@ export const protocolIntegrationPicked = async (
|
||||
element: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
options?: { brand?: string; domain?: string }
|
||||
options?: { brand?: string; domain?: string; config_entry?: string }
|
||||
) => {
|
||||
if (options?.domain) {
|
||||
const localize = await hass.loadBackendTranslation("title", options.domain);
|
||||
@@ -32,11 +32,16 @@ export const protocolIntegrationPicked = async (
|
||||
}
|
||||
|
||||
if (domain === "zwave_js") {
|
||||
const entries = await getConfigEntries(hass, {
|
||||
domain,
|
||||
});
|
||||
const entries = options?.config_entry
|
||||
? undefined
|
||||
: await getConfigEntries(hass, {
|
||||
domain,
|
||||
});
|
||||
|
||||
if (!isComponentLoaded(hass, "zwave_js") || !entries.length) {
|
||||
if (
|
||||
!isComponentLoaded(hass, "zwave_js") ||
|
||||
(!options?.config_entry && !entries?.length)
|
||||
) {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
showConfirmationDialog(element, {
|
||||
title: hass.localize(
|
||||
@@ -71,14 +76,19 @@ export const protocolIntegrationPicked = async (
|
||||
}
|
||||
|
||||
showZWaveJSAddNodeDialog(element, {
|
||||
entry_id: entries[0].entry_id,
|
||||
entry_id: options?.config_entry || entries![0].entry_id,
|
||||
});
|
||||
} else if (domain === "zha") {
|
||||
const entries = await getConfigEntries(hass, {
|
||||
domain,
|
||||
});
|
||||
const entries = options?.config_entry
|
||||
? undefined
|
||||
: await getConfigEntries(hass, {
|
||||
domain,
|
||||
});
|
||||
|
||||
if (!isComponentLoaded(hass, "zha") || !entries.length) {
|
||||
if (
|
||||
!isComponentLoaded(hass, "zha") ||
|
||||
(!options?.config_entry && !entries?.length)
|
||||
) {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
showConfirmationDialog(element, {
|
||||
title: hass.localize(
|
||||
@@ -117,10 +127,15 @@ export const protocolIntegrationPicked = async (
|
||||
|
||||
navigate("/config/zha/add");
|
||||
} else if (domain === "matter") {
|
||||
const entries = await getConfigEntries(hass, {
|
||||
domain,
|
||||
});
|
||||
if (!isComponentLoaded(hass, domain) || !entries.length) {
|
||||
const entries = options?.config_entry
|
||||
? undefined
|
||||
: await getConfigEntries(hass, {
|
||||
domain,
|
||||
});
|
||||
if (
|
||||
!isComponentLoaded(hass, domain) ||
|
||||
(!options?.config_entry && !entries?.length)
|
||||
) {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
showConfirmationDialog(element, {
|
||||
title: hass.localize(
|
||||
|
@@ -111,10 +111,10 @@ class HaDevicesPicker extends LitElement {
|
||||
event.stopPropagation();
|
||||
const curValue = (event.currentTarget as any).curValue;
|
||||
const newValue = event.detail.value;
|
||||
if (newValue === curValue || newValue !== "") {
|
||||
if (newValue === curValue) {
|
||||
return;
|
||||
}
|
||||
if (newValue === "") {
|
||||
if (newValue === undefined) {
|
||||
this._updateDevices(
|
||||
this._currentDevices.filter((dev) => dev !== curValue)
|
||||
);
|
||||
|
@@ -38,6 +38,31 @@ export class StateBadge extends LitElement {
|
||||
|
||||
@state() private _iconStyle: { [name: string]: string | undefined } = {};
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (
|
||||
this.hasUpdated &&
|
||||
this.overrideImage === undefined &&
|
||||
(this.stateObj?.attributes.entity_picture ||
|
||||
this.stateObj?.attributes.entity_picture_local)
|
||||
) {
|
||||
// Update image on connect, so we get new auth token
|
||||
this.requestUpdate("stateObj");
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (
|
||||
this.overrideImage === undefined &&
|
||||
(this.stateObj?.attributes.entity_picture ||
|
||||
this.stateObj?.attributes.entity_picture_local)
|
||||
) {
|
||||
// Clear image on disconnect so we don't fetch with old auth when we reconnect
|
||||
this.style.backgroundImage = "";
|
||||
}
|
||||
}
|
||||
|
||||
private get _stateColor() {
|
||||
const domain = this.stateObj
|
||||
? computeStateDomain(this.stateObj)
|
||||
|
74
src/components/ha-form/ha-form-conditional.ts
Normal file
74
src/components/ha-form/ha-form-conditional.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-form";
|
||||
import type {
|
||||
HaFormDataContainer,
|
||||
HaFormElement,
|
||||
HaFormConditionalSchema,
|
||||
HaFormSchema,
|
||||
} from "./types";
|
||||
|
||||
@customElement("ha-form-conditional")
|
||||
export class HaFormConditional extends LitElement implements HaFormElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public data!: HaFormDataContainer;
|
||||
|
||||
@property({ attribute: false }) public schema!: HaFormConditionalSchema;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public computeLabel?: (
|
||||
schema: HaFormSchema,
|
||||
data?: HaFormDataContainer
|
||||
) => string;
|
||||
|
||||
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("schema") || changedProps.has("data")) {
|
||||
this.toggleAttribute("hidden", !this.schema.condition(this.data));
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.schema.condition(this.data)) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.data}
|
||||
.schema=${this.schema.schema}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.computeHelper=${this.computeHelper}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host([hidden]) {
|
||||
display: none !important;
|
||||
}
|
||||
:host ha-form {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-conditional": HaFormConditional;
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@ const LOAD_ELEMENTS = {
|
||||
float: () => import("./ha-form-float"),
|
||||
grid: () => import("./ha-form-grid"),
|
||||
expandable: () => import("./ha-form-expandable"),
|
||||
conditional: () => import("./ha-form-conditional"),
|
||||
integer: () => import("./ha-form-integer"),
|
||||
multi_select: () => import("./ha-form-multi_select"),
|
||||
positive_time_period_dict: () =>
|
||||
@@ -189,12 +190,13 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.root {
|
||||
display: grid;
|
||||
row-gap: 24px;
|
||||
}
|
||||
.root > * {
|
||||
display: block;
|
||||
}
|
||||
.root > *:not([own-margin]):not(:last-child) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-alert[own-margin] {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
@@ -13,7 +13,8 @@ export type HaFormSchema =
|
||||
| HaFormTimeSchema
|
||||
| HaFormSelector
|
||||
| HaFormGridSchema
|
||||
| HaFormExpandableSchema;
|
||||
| HaFormExpandableSchema
|
||||
| HaFormConditionalSchema;
|
||||
|
||||
export interface HaFormBaseSchema {
|
||||
name: string;
|
||||
@@ -47,6 +48,13 @@ export interface HaFormExpandableSchema extends HaFormBaseSchema {
|
||||
schema: readonly HaFormSchema[];
|
||||
}
|
||||
|
||||
export interface HaFormConditionalSchema extends HaFormBaseSchema {
|
||||
type: "conditional";
|
||||
name: "";
|
||||
condition: (data: HaFormDataContainer) => boolean;
|
||||
schema: readonly HaFormSchema[];
|
||||
}
|
||||
|
||||
export interface HaFormSelector extends HaFormBaseSchema {
|
||||
type?: never;
|
||||
selector: Selector;
|
||||
@@ -99,7 +107,10 @@ export interface HaFormTimeSchema extends HaFormBaseSchema {
|
||||
export type SchemaUnion<
|
||||
SchemaArray extends readonly HaFormSchema[],
|
||||
Schema = SchemaArray[number]
|
||||
> = Schema extends HaFormGridSchema | HaFormExpandableSchema
|
||||
> = Schema extends
|
||||
| HaFormGridSchema
|
||||
| HaFormExpandableSchema
|
||||
| HaFormConditionalSchema
|
||||
? SchemaUnion<Schema["schema"]>
|
||||
: Schema;
|
||||
|
||||
|
@@ -176,7 +176,7 @@ class HaHsColorPicker extends LitElement {
|
||||
super.updated(changedProps);
|
||||
if (
|
||||
changedProps.has("colorBrightness") ||
|
||||
changedProps.has("vw") ||
|
||||
changedProps.has("wv") ||
|
||||
changedProps.has("ww") ||
|
||||
changedProps.has("cw") ||
|
||||
changedProps.has("minKelvin") ||
|
||||
@@ -317,9 +317,13 @@ class HaHsColorPicker extends LitElement {
|
||||
const cy = ((y + 1) * size) / 2;
|
||||
|
||||
const markerPosition = `${cx}px, ${cy}px`;
|
||||
const markerScale = this._pressed ? "1.5" : "1";
|
||||
const markerScale = this._pressed
|
||||
? this._pressed === "touch"
|
||||
? "2.5"
|
||||
: "1.5"
|
||||
: "1";
|
||||
const markerOffset =
|
||||
this._pressed === "touch" ? `0px, -${size / 8}px` : "0px, 0px";
|
||||
this._pressed === "touch" ? `0px, -${size / 16}px` : "0px, 0px";
|
||||
|
||||
return html`
|
||||
<div class="container ${classMap({ pressed: Boolean(this._pressed) })}">
|
||||
|
@@ -17,8 +17,14 @@ export class HaListItem extends ListItemBase {
|
||||
styles,
|
||||
css`
|
||||
:host {
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
padding-left: var(
|
||||
--mdc-list-side-padding-left,
|
||||
var(--mdc-list-side-padding, 20px)
|
||||
);
|
||||
padding-right: var(
|
||||
--mdc-list-side-padding-right,
|
||||
var(--mdc-list-side-padding, 20px)
|
||||
);
|
||||
}
|
||||
:host([graphic="avatar"]:not([twoLine])),
|
||||
:host([graphic="icon"]:not([twoLine])) {
|
||||
|
@@ -11,6 +11,9 @@ class HaMarkdownElement extends ReactiveElement {
|
||||
|
||||
@property({ type: Boolean }) public breaks = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "lazy-images" }) public lazyImages =
|
||||
false;
|
||||
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@@ -58,6 +61,9 @@ class HaMarkdownElement extends ReactiveElement {
|
||||
|
||||
// Fire a resize event when images loaded to notify content resized
|
||||
} else if (node instanceof HTMLImageElement) {
|
||||
if (this.lazyImages) {
|
||||
node.loading = "lazy";
|
||||
}
|
||||
node.addEventListener("load", this._resize);
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,9 @@ export class HaMarkdown extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public breaks = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "lazy-images" }) public lazyImages =
|
||||
false;
|
||||
|
||||
protected render() {
|
||||
if (!this.content) {
|
||||
return nothing;
|
||||
@@ -24,6 +27,7 @@ export class HaMarkdown extends LitElement {
|
||||
.content=${this.content}
|
||||
.allowSvg=${this.allowSvg}
|
||||
.breaks=${this.breaks}
|
||||
.lazyImages=${this.lazyImages}
|
||||
></ha-markdown-element>`;
|
||||
}
|
||||
|
||||
|
@@ -29,9 +29,10 @@ export class HaOutlinedIconButton extends IconButton {
|
||||
--md-ripple-hover-opacity: 0;
|
||||
--md-ripple-pressed-opacity: 0;
|
||||
}
|
||||
button {
|
||||
/* Fix md-outlined-icon-button padding for iOS */
|
||||
.outlined {
|
||||
/* Fix md-outlined-icon-button padding and margin for iOS */
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,6 +1,10 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiDevices, mdiPaletteSwatch, mdiSofa } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiDevices,
|
||||
mdiPaletteSwatch,
|
||||
mdiSofa,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -11,10 +15,11 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import { SceneEntity } from "../data/scene";
|
||||
import { findRelated, ItemType, RelatedResult } from "../data/search";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
@@ -72,13 +77,55 @@ export class HaRelatedItems extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _relatedEntities = memoizeOne((entityIds: string[]) =>
|
||||
this._toEntities(entityIds)
|
||||
);
|
||||
|
||||
private _relatedAutomations = memoizeOne((automationEntityIds: string[]) =>
|
||||
this._toEntities(automationEntityIds)
|
||||
);
|
||||
|
||||
private _relatedScripts = memoizeOne((scriptEntityIds: string[]) =>
|
||||
this._toEntities(scriptEntityIds)
|
||||
);
|
||||
|
||||
private _relatedGroups = memoizeOne((groupEntityIds: string[]) =>
|
||||
this._toEntities(groupEntityIds)
|
||||
);
|
||||
|
||||
private _relatedScenes = memoizeOne((sceneEntityIds: string[]) =>
|
||||
this._toEntities(sceneEntityIds)
|
||||
);
|
||||
|
||||
private _toEntities = (entityIds: string[]) =>
|
||||
entityIds
|
||||
.map((entityId) => this.hass.states[entityId])
|
||||
.filter((entity) => entity)
|
||||
.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.attributes.friendly_name ?? a.entity_id,
|
||||
b.attributes.friendly_name ?? b.entity_id,
|
||||
this.hass.language
|
||||
)
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._related) {
|
||||
return nothing;
|
||||
}
|
||||
if (Object.keys(this._related).length === 0) {
|
||||
return html`
|
||||
${this.hass.localize("ui.components.related-items.no_related_found")}
|
||||
<mwc-list>
|
||||
<ha-list-item hasMeta graphic="icon" noninteractive>
|
||||
<ha-svg-icon
|
||||
.path=${mdiAlertCircleOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.components.related-items.no_related_found"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</mwc-list>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
@@ -92,7 +139,7 @@ export class HaRelatedItems extends LitElement {
|
||||
(configEntry) => configEntry.entry_id === relatedConfigEntryId
|
||||
);
|
||||
if (!entry) {
|
||||
return "";
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a
|
||||
@@ -117,7 +164,7 @@ export class HaRelatedItems extends LitElement {
|
||||
`;
|
||||
})}</mwc-list
|
||||
>`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.device
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.device")}
|
||||
@@ -125,7 +172,7 @@ export class HaRelatedItems extends LitElement {
|
||||
${this._related.device.map((relatedDeviceId) => {
|
||||
const device = this.hass.devices[relatedDeviceId];
|
||||
if (!device) {
|
||||
return "";
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a
|
||||
@@ -144,7 +191,7 @@ export class HaRelatedItems extends LitElement {
|
||||
`;
|
||||
})} </mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.area
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.area")}
|
||||
@@ -153,7 +200,7 @@ export class HaRelatedItems extends LitElement {
|
||||
>${this._related.area.map((relatedAreaId) => {
|
||||
const area = this.hass.areas[relatedAreaId];
|
||||
if (!area) {
|
||||
return "";
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a
|
||||
@@ -183,21 +230,16 @@ export class HaRelatedItems extends LitElement {
|
||||
`;
|
||||
})}</mwc-list
|
||||
>`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.entity
|
||||
? html`
|
||||
<h3>${this.hass.localize("ui.components.related-items.entity")}</h3>
|
||||
<mwc-list>
|
||||
${this._related.entity.map((entityId) => {
|
||||
const entity: HassEntity | undefined =
|
||||
this.hass.states[entityId];
|
||||
if (!entity) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
${this._relatedEntities(this._related.entity).map(
|
||||
(entity) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${entityId}
|
||||
.entityId=${entity.entity_id}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
@@ -208,24 +250,20 @@ export class HaRelatedItems extends LitElement {
|
||||
${entity.attributes.friendly_name || entity.entity_id}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.group
|
||||
? html`
|
||||
<h3>${this.hass.localize("ui.components.related-items.group")}</h3>
|
||||
<mwc-list>
|
||||
${this._related.group.map((groupId) => {
|
||||
const group: HassEntity | undefined = this.hass.states[groupId];
|
||||
if (!group) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
${this._relatedGroups(this._related.group).map(
|
||||
(group) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${groupId}
|
||||
.entityId=${group.entity_id}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
@@ -236,25 +274,20 @@ export class HaRelatedItems extends LitElement {
|
||||
${group.attributes.friendly_name || group.entity_id}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.scene
|
||||
? html`
|
||||
<h3>${this.hass.localize("ui.components.related-items.scene")}</h3>
|
||||
<mwc-list>
|
||||
${this._related.scene.map((sceneId) => {
|
||||
const scene: SceneEntity | undefined =
|
||||
this.hass.states[sceneId];
|
||||
if (!scene) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
${this._relatedScenes(this._related.scene).map(
|
||||
(scene) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${sceneId}
|
||||
.entityId=${scene.entity_id}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
@@ -265,11 +298,11 @@ export class HaRelatedItems extends LitElement {
|
||||
${scene.attributes.friendly_name || scene.entity_id}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.automation_blueprint
|
||||
? html`
|
||||
<h3>
|
||||
@@ -298,23 +331,18 @@ export class HaRelatedItems extends LitElement {
|
||||
})}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.automation
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.automation")}
|
||||
</h3>
|
||||
<mwc-list>
|
||||
${this._related.automation.map((automationId) => {
|
||||
const automation: HassEntity | undefined =
|
||||
this.hass.states[automationId];
|
||||
if (!automation) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
${this._relatedAutomations(this._related.automation).map(
|
||||
(automation) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${automationId}
|
||||
.entityId=${automation.entity_id}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
@@ -326,11 +354,11 @@ export class HaRelatedItems extends LitElement {
|
||||
automation.entity_id}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.script_blueprint
|
||||
? html`
|
||||
<h3>
|
||||
@@ -359,21 +387,16 @@ export class HaRelatedItems extends LitElement {
|
||||
})}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._related.script
|
||||
? html`
|
||||
<h3>${this.hass.localize("ui.components.related-items.script")}</h3>
|
||||
<mwc-list>
|
||||
${this._related.script.map((scriptId) => {
|
||||
const script: HassEntity | undefined =
|
||||
this.hass.states[scriptId];
|
||||
if (!script) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
${this._relatedScripts(this._related.script).map(
|
||||
(script) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${scriptId}
|
||||
.entityId=${script.entity_id}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
@@ -384,11 +407,11 @@ export class HaRelatedItems extends LitElement {
|
||||
${script.attributes.friendly_name || script.entity_id}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -77,7 +77,9 @@ export class HaDeviceSelector extends LitElement {
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.entityFilter=${this.selector.device?.entity
|
||||
? this._filterEntities
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
@@ -92,7 +94,9 @@ export class HaDeviceSelector extends LitElement {
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.entityFilter=${this.selector.device?.entity
|
||||
? this._filterEntities
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-devices-picker>
|
||||
@@ -115,14 +119,10 @@ export class HaDeviceSelector extends LitElement {
|
||||
);
|
||||
};
|
||||
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
if (!this.selector.device?.entity) {
|
||||
return true;
|
||||
}
|
||||
return ensureArray(this.selector.device.entity).some((filter) =>
|
||||
private _filterEntities = (entity: HassEntity): boolean =>
|
||||
ensureArray(this.selector.device!.entity).some((filter) =>
|
||||
filterSelectorEntities(filter, entity, this._entitySources)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -305,9 +305,13 @@ class HaTempColorPicker extends LitElement {
|
||||
const cy = ((y + 1) * size) / 2;
|
||||
|
||||
const markerPosition = `${cx}px, ${cy}px`;
|
||||
const markerScale = this._pressed ? "1.5" : "1";
|
||||
const markerScale = this._pressed
|
||||
? this._pressed === "touch"
|
||||
? "2.5"
|
||||
: "1.5"
|
||||
: "1";
|
||||
const markerOffset =
|
||||
this._pressed === "touch" ? `0px, -${size / 8}px` : "0px, 0px";
|
||||
this._pressed === "touch" ? `0px, -${size / 16}px` : "0px, 0px";
|
||||
|
||||
return html`
|
||||
<div class="container ${classMap({ pressed: Boolean(this._pressed) })}">
|
||||
|
@@ -471,6 +471,11 @@ export class HaMap extends ReactiveElement {
|
||||
background: #090909;
|
||||
--map-filter: invert(0.9) hue-rotate(170deg) grayscale(0.7);
|
||||
}
|
||||
#map:active {
|
||||
cursor: grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
.light {
|
||||
color: #000000;
|
||||
}
|
||||
|
@@ -58,6 +58,10 @@ export const lightSupportsBrightness = (entity: LightEntity) =>
|
||||
modesSupportingBrightness.includes(mode)
|
||||
) || false;
|
||||
|
||||
export const lightSupportsFavoriteColors = (entity: LightEntity) =>
|
||||
lightSupportsColor(entity) ||
|
||||
lightSupportsColorMode(entity, LightColorMode.COLOR_TEMP);
|
||||
|
||||
export const getLightCurrentModeRgbColor = (
|
||||
entity: LightEntity
|
||||
): number[] | undefined =>
|
||||
|
@@ -169,9 +169,16 @@ export interface ZHANetworkBackup {
|
||||
node_info: ZHANetworkBackupNodeInfo;
|
||||
}
|
||||
|
||||
export interface ZHADeviceSettings {
|
||||
path: string;
|
||||
baudrate?: number;
|
||||
flow_control?: string;
|
||||
}
|
||||
|
||||
export interface ZHANetworkSettings {
|
||||
settings: ZHANetworkBackup;
|
||||
radio_type: "ezsp" | "znp" | "deconz" | "zigate" | "xbee";
|
||||
device: ZHADeviceSettings;
|
||||
}
|
||||
|
||||
export interface ZHANetworkBackupAndMetadata {
|
||||
|
@@ -79,6 +79,7 @@ class DialogLightColorFavorite extends LitElement {
|
||||
<light-color-picker
|
||||
.hass=${this.hass}
|
||||
entityId=${this._entry.entity_id}
|
||||
.defaultMode=${this._dialogParams?.defaultMode}
|
||||
@color-changed=${this._colorChanged}
|
||||
>
|
||||
</light-color-picker>
|
||||
|
@@ -31,6 +31,7 @@ import {
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { showConfirmationDialog } from "../../../generic/show-dialog-box";
|
||||
import "./ha-favorite-color-button";
|
||||
import type { LightPickerMode } from "./light-color-picker";
|
||||
import { showLightColorFavoriteDialog } from "./show-dialog-light-color-favorite";
|
||||
|
||||
@customElement("ha-more-info-light-favorite-colors")
|
||||
@@ -147,8 +148,14 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
private _edit = async (index) => {
|
||||
// Make sure the current favorite color is set
|
||||
await this._apply(index);
|
||||
|
||||
const defaultMode: LightPickerMode =
|
||||
"color_temp_kelvin" in this._favoriteColors[index]
|
||||
? "color_temp"
|
||||
: "color";
|
||||
const color = await showLightColorFavoriteDialog(this, {
|
||||
entry: this.entry!,
|
||||
defaultMode,
|
||||
title: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.light.favorite_color.edit_title"
|
||||
),
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
||||
import "@material/mwc-tab/mwc-tab";
|
||||
import { mdiEyedropper } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -10,7 +11,14 @@ import {
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { hs2rgb, rgb2hs } from "../../../../common/color/convert-color";
|
||||
import {
|
||||
hex2rgb,
|
||||
hs2rgb,
|
||||
hsv2rgb,
|
||||
rgb2hex,
|
||||
rgb2hs,
|
||||
rgb2hsv,
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { throttle } from "../../../../common/util/throttle";
|
||||
import "../../../../components/ha-button-toggle-group";
|
||||
@@ -27,8 +35,9 @@ import {
|
||||
lightSupportsColorMode,
|
||||
} from "../../../../data/light";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "../../../../components/ha-icon";
|
||||
|
||||
type Mode = "color_temp" | "color";
|
||||
export type LightPickerMode = "color_temp" | "color";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -42,7 +51,7 @@ class LightColorPicker extends LitElement {
|
||||
|
||||
@property() public entityId!: string;
|
||||
|
||||
@property() public defaultMode!: Mode;
|
||||
@property() public defaultMode?: LightPickerMode;
|
||||
|
||||
@state() private _cwSliderValue?: number;
|
||||
|
||||
@@ -58,9 +67,9 @@ class LightColorPicker extends LitElement {
|
||||
|
||||
@state() private _ctPickerValue?: number;
|
||||
|
||||
@state() private _mode?: Mode;
|
||||
@state() private _mode?: LightPickerMode;
|
||||
|
||||
@state() private _modes: Mode[] = [];
|
||||
@state() private _modes: LightPickerMode[] = [];
|
||||
|
||||
get stateObj() {
|
||||
return this.hass.states[this.entityId] as LightEntity | undefined;
|
||||
@@ -80,6 +89,16 @@ class LightColorPicker extends LitElement {
|
||||
!supportsRgbww &&
|
||||
lightSupportsColorMode(this.stateObj, LightColorMode.RGBW);
|
||||
|
||||
const hexValue = this._hsPickerValue
|
||||
? rgb2hex(
|
||||
hsv2rgb([
|
||||
this._hsPickerValue[0],
|
||||
this._hsPickerValue[1],
|
||||
((this._colorBrightnessSliderValue ?? 100) / 100) * 255,
|
||||
])
|
||||
)
|
||||
: "";
|
||||
|
||||
return html`
|
||||
${this._modes.length > 1
|
||||
? html`
|
||||
@@ -116,27 +135,37 @@ class LightColorPicker extends LitElement {
|
||||
: nothing}
|
||||
${this._mode === "color"
|
||||
? html`
|
||||
<ha-hs-color-picker
|
||||
@value-changed=${this._hsColorChanged}
|
||||
@cursor-moved=${this._hsColorCursorMoved}
|
||||
.value=${this._hsPickerValue}
|
||||
.colorBrightness=${this._colorBrightnessSliderValue != null
|
||||
? (this._colorBrightnessSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.wv=${this._wvSliderValue != null
|
||||
? (this._wvSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.ww=${this._wwSliderValue != null
|
||||
? (this._wwSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.cw=${this._cwSliderValue != null
|
||||
? (this._cwSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.minKelvin=${this.stateObj.attributes.min_color_temp_kelvin}
|
||||
.maxKelvin=${this.stateObj.attributes.max_color_temp_kelvin}
|
||||
>
|
||||
</ha-hs-color-picker>
|
||||
<div class="color-container">
|
||||
<label class="native-color-picker">
|
||||
<input
|
||||
type="color"
|
||||
.value=${hexValue ?? ""}
|
||||
@input=${this._nativeColorChanged}
|
||||
/>
|
||||
<ha-svg-icon .path=${mdiEyedropper}></ha-svg-icon>
|
||||
</label>
|
||||
|
||||
<ha-hs-color-picker
|
||||
@value-changed=${this._hsColorChanged}
|
||||
@cursor-moved=${this._hsColorCursorMoved}
|
||||
.value=${this._hsPickerValue}
|
||||
.colorBrightness=${this._colorBrightnessSliderValue != null
|
||||
? (this._colorBrightnessSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.wv=${this._wvSliderValue != null
|
||||
? (this._wvSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.ww=${this._wwSliderValue != null
|
||||
? (this._wwSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.cw=${this._cwSliderValue != null
|
||||
? (this._cwSliderValue * 255) / 100
|
||||
: undefined}
|
||||
.minKelvin=${this.stateObj.attributes.min_color_temp_kelvin}
|
||||
.maxKelvin=${this.stateObj.attributes.max_color_temp_kelvin}
|
||||
>
|
||||
</ha-hs-color-picker>
|
||||
</div>
|
||||
${supportsRgbw || supportsRgbww
|
||||
? html`<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
@@ -267,7 +296,7 @@ class LightColorPicker extends LitElement {
|
||||
|
||||
const supportsColor = lightSupportsColor(this.stateObj!);
|
||||
|
||||
const modes: Mode[] = [];
|
||||
const modes: LightPickerMode[] = [];
|
||||
if (supportsColor) {
|
||||
modes.push("color");
|
||||
}
|
||||
@@ -349,6 +378,23 @@ class LightColorPicker extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _nativeColorChanged(ev) {
|
||||
const rgb = hex2rgb(ev.currentTarget.value);
|
||||
|
||||
const hsv = rgb2hsv(rgb);
|
||||
|
||||
this._hsPickerValue = [hsv[0], hsv[1]];
|
||||
|
||||
if (
|
||||
lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW) ||
|
||||
lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW)
|
||||
) {
|
||||
this._colorBrightnessSliderValue = hsv[2] / 2.55;
|
||||
}
|
||||
|
||||
this._throttleUpdateColor();
|
||||
}
|
||||
|
||||
private _hsColorChanged(ev: CustomEvent) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
@@ -537,10 +583,70 @@ class LightColorPicker extends LitElement {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ha-hs-color-picker {
|
||||
.native-color-picker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.native-color-picker ha-svg-icon {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
display: block;
|
||||
width: var(--mdc-icon-button-size, 48px);
|
||||
height: var(--mdc-icon-button-size, 48px);
|
||||
padding: calc(
|
||||
(var(--mdc-icon-button-size, 48px) - var(--mdc-icon-size, 24px)) / 2
|
||||
);
|
||||
background-color: transparent;
|
||||
border-radius: calc(var(--mdc-icon-button-size, 48px) / 2);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
}
|
||||
|
||||
input[type="color"]:focus-visible,
|
||||
input[type="color"]:hover {
|
||||
background-color: rgb(127, 127, 127, 0.15);
|
||||
}
|
||||
|
||||
input[type="color"]::-webkit-color-swatch-wrapper {
|
||||
display: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
input[type="color"]::-moz-color-swatch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="color"]::-webkit-color-swatch {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.color-container {
|
||||
position: relative;
|
||||
max-width: 300px;
|
||||
min-width: 200px;
|
||||
margin: 44px 0 44px 0;
|
||||
margin: 0 0 44px 0;
|
||||
padding-top: 44px;
|
||||
}
|
||||
|
||||
ha-hs-color-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-temp-color-picker {
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { ExtEntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
import { LightColor } from "../../../../data/light";
|
||||
import type { LightPickerMode } from "./light-color-picker";
|
||||
|
||||
export interface LightColorFavoriteDialogParams {
|
||||
entry: ExtEntityRegistryEntry;
|
||||
title: string;
|
||||
defaultMode?: LightPickerMode;
|
||||
submit?: (color?: LightColor) => void;
|
||||
cancel?: () => void;
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LightPickerMode } from "./light-color-picker";
|
||||
|
||||
export interface LightColorPickerViewParams {
|
||||
entityId: string;
|
||||
defaultMode: "color" | "color_temp";
|
||||
defaultMode: LightPickerMode;
|
||||
}
|
||||
|
||||
export const loadLightColorPickerView = () =>
|
||||
|
@@ -34,6 +34,8 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"configurator",
|
||||
"counter",
|
||||
"cover",
|
||||
"date",
|
||||
"datetime",
|
||||
"fan",
|
||||
"group",
|
||||
"humidifier",
|
||||
@@ -49,6 +51,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"siren",
|
||||
"sun",
|
||||
"switch",
|
||||
"time",
|
||||
"timer",
|
||||
"update",
|
||||
"vacuum",
|
||||
|
@@ -195,7 +195,7 @@ class MoreInfoCover extends LitElement {
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize(
|
||||
`ui.dialogs.more_info_control.cover.switch_mode.${
|
||||
this._mode || "position"
|
||||
this._mode === "position" ? "button" : "position"
|
||||
}`
|
||||
)}
|
||||
.path=${this._mode === "position"
|
||||
|
51
src/dialogs/more-info/controls/more-info-date.ts
Normal file
51
src/dialogs/more-info/controls/more-info-date.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { setDateValue } from "../../../data/date";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-date")
|
||||
class MoreInfoDate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${this.stateObj.state}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
setDateValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-date": MoreInfoDate;
|
||||
}
|
||||
}
|
80
src/dialogs/more-info/controls/more-info-datetime.ts
Normal file
80
src/dialogs/more-info/controls/more-info-datetime.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { format } from "date-fns";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { setDateTimeValue } from "../../../data/datetime";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-datetime")
|
||||
class MoreInfoDatetime extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dateObj = new Date(this.stateObj.state);
|
||||
const time = format(dateObj, "HH:mm:ss");
|
||||
const date = format(dateObj, "yyyy-MM-dd");
|
||||
|
||||
return html`<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${date}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
.value=${time}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>`;
|
||||
}
|
||||
|
||||
private _stopEventPropagation(ev: Event): void {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
const dateObj = new Date(this.stateObj!.state);
|
||||
const newTime = ev.detail.value.split(":").map(Number);
|
||||
dateObj.setHours(newTime[0], newTime[1], newTime[2]);
|
||||
|
||||
setDateTimeValue(this.hass!, this.stateObj!.entity_id, dateObj);
|
||||
}
|
||||
|
||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
const dateObj = new Date(this.stateObj!.state);
|
||||
const newDate = ev.detail.value.split("-").map(Number);
|
||||
dateObj.setFullYear(newDate[0], newDate[1] - 1, newDate[2]);
|
||||
|
||||
setDateTimeValue(this.hass!, this.stateObj!.entity_id, dateObj);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
ha-date-input + ha-time-input {
|
||||
margin-left: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-datetime": MoreInfoDatetime;
|
||||
}
|
||||
}
|
@@ -22,37 +22,32 @@ class MoreInfoInputDatetime extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${
|
||||
this.stateObj.attributes.has_date
|
||||
? html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${stateToIsoDateString(this.stateObj)}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
`
|
||||
: ``
|
||||
}
|
||||
${
|
||||
this.stateObj.attributes.has_time
|
||||
? html`
|
||||
<ha-time-input
|
||||
.value=${this.stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: this.stateObj.attributes.has_date
|
||||
? this.stateObj.state.split(" ")[1]
|
||||
: this.stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
`
|
||||
: ``
|
||||
}
|
||||
</hui-generic-entity-row>
|
||||
${this.stateObj.attributes.has_date
|
||||
? html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${stateToIsoDateString(this.stateObj)}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
`
|
||||
: ``}
|
||||
${this.stateObj.attributes.has_time
|
||||
? html`
|
||||
<ha-time-input
|
||||
.value=${this.stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: this.stateObj.attributes.has_date
|
||||
? this.stateObj.state.split(" ")[1]
|
||||
: this.stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
`
|
||||
: ``}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,7 @@ import {
|
||||
lightSupportsBrightness,
|
||||
lightSupportsColor,
|
||||
lightSupportsColorMode,
|
||||
lightSupportsFavoriteColors,
|
||||
} from "../../../data/light";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||
@@ -206,7 +207,9 @@ class MoreInfoLight extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.entry && (this.editMode || hasFavoriteColors)
|
||||
${this.entry &&
|
||||
lightSupportsFavoriteColors(this.stateObj) &&
|
||||
(this.editMode || hasFavoriteColors)
|
||||
? html`
|
||||
<ha-more-info-light-favorite-colors
|
||||
.hass=${this.hass}
|
||||
|
55
src/dialogs/more-info/controls/more-info-time.ts
Normal file
55
src/dialogs/more-info/controls/more-info-time.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { setTimeValue } from "../../../data/time";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-time")
|
||||
class MoreInfoTime extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-time-input
|
||||
.value=${this.stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _stopEventPropagation(ev: Event): void {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
setTimeValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-time": MoreInfoTime;
|
||||
}
|
||||
}
|
@@ -32,6 +32,7 @@ import {
|
||||
ExtEntityRegistryEntry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import { lightSupportsFavoriteColors } from "../../data/light";
|
||||
import { SearchableDomains } from "../../data/search";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import "../../state-summary/state-card-content";
|
||||
@@ -359,7 +360,10 @@ export class MoreInfoDialog extends LitElement {
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${this._entry && domain === "light"
|
||||
${this._entry &&
|
||||
stateObj &&
|
||||
domain === "light" &&
|
||||
lightSupportsFavoriteColors(stateObj)
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
|
@@ -192,7 +192,8 @@ export class MoreInfoHistory extends LitElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isComponentLoaded(this.hass, "history") || this._subscribed) {
|
||||
|
||||
if (!isComponentLoaded(this.hass, "history")) {
|
||||
return;
|
||||
}
|
||||
if (this._subscribed) {
|
||||
|
@@ -13,6 +13,8 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
|
||||
configurator: () => import("./controls/more-info-configurator"),
|
||||
counter: () => import("./controls/more-info-counter"),
|
||||
cover: () => import("./controls/more-info-cover"),
|
||||
date: () => import("./controls/more-info-date"),
|
||||
datetime: () => import("./controls/more-info-datetime"),
|
||||
fan: () => import("./controls/more-info-fan"),
|
||||
group: () => import("./controls/more-info-group"),
|
||||
humidifier: () => import("./controls/more-info-humidifier"),
|
||||
@@ -27,6 +29,7 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
|
||||
siren: () => import("./controls/more-info-siren"),
|
||||
sun: () => import("./controls/more-info-sun"),
|
||||
switch: () => import("./controls/more-info-switch"),
|
||||
time: () => import("./controls/more-info-time"),
|
||||
timer: () => import("./controls/more-info-timer"),
|
||||
update: () => import("./controls/more-info-update"),
|
||||
vacuum: () => import("./controls/more-info-vacuum"),
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import "../../components/ha-icon-button-prev";
|
||||
@@ -13,6 +13,7 @@ import { HomeAssistant } from "../../types";
|
||||
import "./notification-item";
|
||||
import "../../components/ha-header-bar";
|
||||
import "../../components/ha-drawer";
|
||||
import type { HaDrawer } from "../../components/ha-drawer";
|
||||
|
||||
@customElement("notification-drawer")
|
||||
export class HuiNotificationDrawer extends LitElement {
|
||||
@@ -22,6 +23,8 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@query("ha-drawer") private _drawer?: HaDrawer;
|
||||
|
||||
private _unsubNotifications?: UnsubscribeFunc;
|
||||
|
||||
connectedCallback() {
|
||||
@@ -53,12 +56,14 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
}
|
||||
|
||||
closeDialog = () => {
|
||||
if (this._drawer) {
|
||||
this._drawer.open = false;
|
||||
}
|
||||
if (this._unsubNotifications) {
|
||||
this._unsubNotifications();
|
||||
this._unsubNotifications = undefined;
|
||||
}
|
||||
this._notifications = [];
|
||||
this._open = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
};
|
||||
|
||||
@@ -87,11 +92,7 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
});
|
||||
|
||||
return html`
|
||||
<ha-drawer
|
||||
type="modal"
|
||||
.open=${this._open}
|
||||
@MDCDrawer:closed=${this._closeDrawer}
|
||||
>
|
||||
<ha-drawer type="modal" open @MDCDrawer:closed=${this._dialogClosed}>
|
||||
<ha-header-bar>
|
||||
<div slot="title">
|
||||
${this.hass.localize("ui.notification_drawer.title")}
|
||||
@@ -99,7 +100,7 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
<ha-icon-button-prev
|
||||
slot="actionItems"
|
||||
.hass=${this.hass}
|
||||
@click=${this._closeDrawer}
|
||||
@click=${this.closeDialog}
|
||||
.label=${this.hass.localize("ui.notification_drawer.close")}
|
||||
>
|
||||
</ha-icon-button-prev>
|
||||
@@ -132,9 +133,9 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDrawer(ev) {
|
||||
private _dialogClosed(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this.closeDialog();
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dismissAll() {
|
||||
|
@@ -128,9 +128,9 @@ class OnboardingCoreConfig extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<ha-country-picker
|
||||
<ha-country-picker
|
||||
class="flex"
|
||||
.language=${this.hass.locale.language}
|
||||
.language=${this.hass.locale.language}
|
||||
.label=${
|
||||
this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.country"
|
||||
@@ -335,12 +335,12 @@ class OnboardingCoreConfig extends LitElement {
|
||||
]
|
||||
);
|
||||
|
||||
private _handleValueChanged(ev) {
|
||||
const target = ev.currentTarget;
|
||||
private _handleValueChanged(ev: ValueChangedEvent<string>) {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
this[`_${target.getAttribute("name")}`] = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleChange(ev: ValueChangedEvent<string>) {
|
||||
private _handleChange(ev: Event) {
|
||||
const target = ev.currentTarget as HaTextField;
|
||||
this[`_${target.name}`] = target.value;
|
||||
}
|
||||
|
@@ -191,6 +191,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
slot="icons"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
fixed
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -523,17 +524,19 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
defaultValue: this.action.alias,
|
||||
confirmText: this.hass.localize("ui.common.submit"),
|
||||
});
|
||||
const value = { ...this.action };
|
||||
if (!alias) {
|
||||
delete value.alias;
|
||||
} else {
|
||||
value.alias = alias;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
if (this._yamlMode) {
|
||||
this._yamlEditor?.setValue(value);
|
||||
if (alias !== null) {
|
||||
const value = { ...this.action };
|
||||
if (alias === "") {
|
||||
delete value.alias;
|
||||
} else {
|
||||
value.alias = alias;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
if (this._yamlMode) {
|
||||
this._yamlEditor?.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -147,7 +147,11 @@ export default class HaAutomationAction extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu @action=${this._addAction} .disabled=${this.disabled}>
|
||||
<ha-button-menu
|
||||
@action=${this._addAction}
|
||||
.disabled=${this.disabled}
|
||||
fixed
|
||||
>
|
||||
<ha-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
@@ -262,9 +266,9 @@ export default class HaAutomationAction extends LitElement {
|
||||
`ha-automation-action-${action}`
|
||||
) as CustomElementConstructor & { defaultConfig: Action };
|
||||
|
||||
actions = this.actions.concat({
|
||||
...elClass.defaultConfig,
|
||||
});
|
||||
actions = this.actions.concat(
|
||||
elClass ? { ...elClass.defaultConfig } : { [action]: {} }
|
||||
);
|
||||
}
|
||||
this._focusLastActionOnChange = true;
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
|
@@ -130,6 +130,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
slot="icons"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
fixed
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -470,16 +471,17 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
defaultValue: this.condition.alias,
|
||||
confirmText: this.hass.localize("ui.common.submit"),
|
||||
});
|
||||
|
||||
const value = { ...this.condition };
|
||||
if (!alias) {
|
||||
delete value.alias;
|
||||
} else {
|
||||
value.alias = alias;
|
||||
if (alias !== null) {
|
||||
const value = { ...this.condition };
|
||||
if (alias === "") {
|
||||
delete value.alias;
|
||||
} else {
|
||||
value.alias = alias;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
public expand() {
|
||||
|
@@ -191,7 +191,11 @@ export default class HaAutomationCondition extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu @action=${this._addCondition} .disabled=${this.disabled}>
|
||||
<ha-button-menu
|
||||
@action=${this._addCondition}
|
||||
.disabled=${this.disabled}
|
||||
fixed
|
||||
>
|
||||
<ha-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
|
@@ -153,6 +153,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
slot="icons"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
fixed
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -580,17 +581,19 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
confirmText: this.hass.localize("ui.common.submit"),
|
||||
});
|
||||
|
||||
const value = { ...this.trigger };
|
||||
if (!alias) {
|
||||
delete value.alias;
|
||||
} else {
|
||||
value.alias = alias;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
if (this._yamlMode) {
|
||||
this._yamlEditor?.setValue(value);
|
||||
if (alias !== null) {
|
||||
const value = { ...this.trigger };
|
||||
if (alias === "") {
|
||||
delete value.alias;
|
||||
} else {
|
||||
value.alias = alias;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
if (this._yamlMode) {
|
||||
this._yamlEditor?.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -75,27 +75,25 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${
|
||||
this.reOrderMode && !this.nested
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.reOrderMode && !this.nested
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="info"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.title"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_triggers"
|
||||
)}
|
||||
<mwc-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_triggers"
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
<mwc-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null
|
||||
}
|
||||
</mwc-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
<div class="triggers">
|
||||
${repeat(
|
||||
this.triggers,
|
||||
@@ -141,8 +139,11 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
</ha-automation-trigger-row>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu @action=${this._addTrigger} .disabled=${this.disabled}>
|
||||
<ha-button-menu
|
||||
@action=${this._addTrigger}
|
||||
.disabled=${this.disabled}
|
||||
fixed
|
||||
>
|
||||
<ha-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
@@ -153,22 +154,20 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
${
|
||||
this.clipboard?.trigger
|
||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.paste"
|
||||
)}
|
||||
(${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.${this.clipboard.trigger.platform}.label`
|
||||
)})
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentPaste}
|
||||
></ha-svg-icon
|
||||
></mwc-list-item>`
|
||||
: nothing
|
||||
}
|
||||
${this.clipboard?.trigger
|
||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.paste"
|
||||
)}
|
||||
(${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.${this.clipboard.trigger.platform}.label`
|
||||
)})
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentPaste}
|
||||
></ha-svg-icon
|
||||
></mwc-list-item>`
|
||||
: nothing}
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label, icon]) => html`
|
||||
<mwc-list-item .value=${opt} graphic="icon">
|
||||
|
@@ -27,7 +27,8 @@ import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
|
||||
@customElement("ha-config-section-general")
|
||||
class HaConfigSectionGeneral extends LitElement {
|
||||
@@ -301,13 +302,13 @@ class HaConfigSectionGeneral extends LitElement {
|
||||
this._updateUnits = true;
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev) {
|
||||
const target = ev.currentTarget;
|
||||
this[`_${target.name}`] = ev.detail.value;
|
||||
private _handleValueChanged(ev: ValueChangedEvent<string>) {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
this[`_${target.getAttribute("name")}`] = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleChange(ev) {
|
||||
const target = ev.currentTarget;
|
||||
private _handleChange(ev: Event) {
|
||||
const target = ev.currentTarget as HaTextField;
|
||||
this[`_${target.name}`] = target.value;
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,15 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiCancel, mdiFilterVariant, mdiPlus } from "@mdi/js";
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import {
|
||||
protocolIntegrationPicked,
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
@@ -39,8 +43,6 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { showMatterAddDeviceDialog } from "../integrations/integration-panels/matter/show-dialog-add-matter-device";
|
||||
import { showZWaveJSAddNodeDialog } from "../integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
||||
|
||||
interface DeviceRowData extends DeviceRegistryEntry {
|
||||
@@ -186,6 +188,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
let filterConfigEntry: ConfigEntry | undefined;
|
||||
|
||||
const filteredDomains = new Set<string>();
|
||||
|
||||
filters.forEach((value, key) => {
|
||||
if (key === "config_entry") {
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
@@ -193,6 +197,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
);
|
||||
startLength = outputDevices.length;
|
||||
filterConfigEntry = entries.find((entry) => entry.entry_id === value);
|
||||
if (filterConfigEntry) {
|
||||
filteredDomains.add(filterConfigEntry.domain);
|
||||
}
|
||||
}
|
||||
if (key === "domain") {
|
||||
const entryIds = entries
|
||||
@@ -202,6 +209,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
device.config_entries.some((entryId) => entryIds.includes(entryId))
|
||||
);
|
||||
startLength = outputDevices.length;
|
||||
filteredDomains.add(value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -216,8 +224,12 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
),
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
model:
|
||||
device.model ||
|
||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||
manufacturer:
|
||||
device.manufacturer ||
|
||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||
area:
|
||||
device.area_id && areaLookup[device.area_id]
|
||||
? areaLookup[device.area_id].name
|
||||
@@ -251,6 +263,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
return {
|
||||
devicesOutput: outputDevices,
|
||||
filteredConfigEntry: filterConfigEntry,
|
||||
filteredDomains,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -546,25 +559,25 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
}
|
||||
|
||||
private _addDevice() {
|
||||
const { filteredConfigEntry } = this._devicesAndFilterDomains(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this.hass.localize
|
||||
);
|
||||
if (filteredConfigEntry?.domain === "zha") {
|
||||
navigate(`/config/zha/add`);
|
||||
return;
|
||||
}
|
||||
if (filteredConfigEntry?.domain === "zwave_js") {
|
||||
this._showZJSAddDeviceDialog(filteredConfigEntry);
|
||||
return;
|
||||
}
|
||||
if (filteredConfigEntry?.domain === "matter") {
|
||||
showMatterAddDeviceDialog(this);
|
||||
const { filteredConfigEntry, filteredDomains } =
|
||||
this._devicesAndFilterDomains(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this.hass.localize
|
||||
);
|
||||
if (
|
||||
filteredDomains.size === 1 &&
|
||||
(PROTOCOL_INTEGRATIONS as ReadonlyArray<string>).includes(
|
||||
[...filteredDomains][0]
|
||||
)
|
||||
) {
|
||||
protocolIntegrationPicked(this, this.hass, [...filteredDomains][0], {
|
||||
config_entry: filteredConfigEntry?.entry_id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
showAddIntegrationDialog(this, {
|
||||
@@ -572,12 +585,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _showZJSAddDeviceDialog(filteredConfigEntry: ConfigEntry) {
|
||||
showZWaveJSAddNodeDialog(this, {
|
||||
entry_id: filteredConfigEntry!.entry_id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
removeEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { fetchIntegrationManifest } from "../../../data/integration";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -46,15 +47,33 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
@query("entity-registry-settings-editor")
|
||||
private _registryEditor?: EntityRegistrySettingsEditor;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
if (this.entry.config_entry_id) {
|
||||
getConfigEntry(this.hass, this.entry.config_entry_id).then((entry) => {
|
||||
this._helperConfigEntry = entry.config_entry;
|
||||
});
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("entry")) {
|
||||
this._fetchHelperConfigEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchHelperConfigEntry() {
|
||||
this._helperConfigEntry = undefined;
|
||||
if (!this.entry?.config_entry_id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const configEntry = (
|
||||
await getConfigEntry(this.hass, this.entry.config_entry_id)
|
||||
).config_entry;
|
||||
const manifest = await fetchIntegrationManifest(
|
||||
this.hass,
|
||||
configEntry.domain
|
||||
);
|
||||
if (manifest.integration_type === "helper") {
|
||||
this._helperConfigEntry = configEntry;
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const stateObj: HassEntity | undefined =
|
||||
this.hass.states[this.entry.entity_id];
|
||||
|
@@ -67,6 +67,11 @@ import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import {
|
||||
protocolIntegrationPicked,
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
||||
|
||||
export interface StateEntity
|
||||
extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
|
||||
@@ -348,7 +353,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
? entities.concat(stateEntities)
|
||||
: entities;
|
||||
|
||||
const filteredDomains: string[] = [];
|
||||
let filteredConfigEntry: ConfigEntry | undefined;
|
||||
const filteredDomains = new Set<string>();
|
||||
|
||||
filters.forEach((value, key) => {
|
||||
if (key === "config_entry") {
|
||||
@@ -373,7 +379,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
const configEntry = entries.find((entry) => entry.entry_id === value);
|
||||
|
||||
if (configEntry) {
|
||||
filteredDomains.push(configEntry.domain);
|
||||
filteredDomains.add(configEntry.domain);
|
||||
filteredConfigEntry = configEntry;
|
||||
}
|
||||
}
|
||||
if (key === "domain") {
|
||||
@@ -389,7 +396,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
entity.config_entry_id &&
|
||||
entryIds.includes(entity.config_entry_id)
|
||||
);
|
||||
filteredDomains.push(value);
|
||||
filteredDomains.add(value);
|
||||
startLength = filteredEntities.length;
|
||||
}
|
||||
});
|
||||
@@ -444,7 +451,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
this._numHiddenEntities = startLength - result.length;
|
||||
return { filteredEntities: result, filteredDomains: filteredDomains };
|
||||
return { filteredEntities: result, filteredConfigEntry, filteredDomains };
|
||||
}
|
||||
);
|
||||
|
||||
@@ -509,7 +516,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
this._entries
|
||||
);
|
||||
|
||||
const includeZHAFab = filteredDomains.includes("zha");
|
||||
const includeAddDeviceFab =
|
||||
filteredDomains.size === 1 &&
|
||||
(PROTOCOL_INTEGRATIONS as ReadonlyArray<string>).includes(
|
||||
[...filteredDomains][0]
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
@@ -545,7 +556,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@search-changed=${this._handleSearchChange}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
.hasFab=${includeZHAFab}
|
||||
.hasFab=${includeAddDeviceFab}
|
||||
>
|
||||
<ha-integration-overflow-menu
|
||||
.hass=${this.hass}
|
||||
@@ -701,16 +712,16 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
</ha-check-list-item>
|
||||
</ha-button-menu>
|
||||
`}
|
||||
${includeZHAFab
|
||||
? html`<a href="/config/zha/add" slot="fab">
|
||||
<ha-fab
|
||||
.label=${this.hass.localize("ui.panel.config.zha.add_device")}
|
||||
extended
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</a>`
|
||||
${includeAddDeviceFab
|
||||
? html`<ha-fab
|
||||
.label=${this.hass.localize("ui.panel.config.devices.add_device")}
|
||||
extended
|
||||
@click=${this._addDevice}
|
||||
slot="fab"
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>`
|
||||
: nothing}
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
@@ -959,6 +970,36 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
this._showHidden = true;
|
||||
}
|
||||
|
||||
private _addDevice() {
|
||||
const { filteredConfigEntry, filteredDomains } =
|
||||
this._filteredEntitiesAndDomains(
|
||||
this._entities!,
|
||||
this._devices,
|
||||
this._areas,
|
||||
this._stateEntities,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this._showUnavailable,
|
||||
this._showReadOnly,
|
||||
this._showHidden,
|
||||
this._entries
|
||||
);
|
||||
if (
|
||||
filteredDomains.size === 1 &&
|
||||
(PROTOCOL_INTEGRATIONS as ReadonlyArray<string>).includes(
|
||||
[...filteredDomains][0]
|
||||
)
|
||||
) {
|
||||
protocolIntegrationPicked(this, this.hass, [...filteredDomains][0], {
|
||||
config_entry: filteredConfigEntry?.entry_id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
showAddIntegrationDialog(this, {
|
||||
domain: this._searchParms.get("domain") || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -10,16 +10,18 @@ import {
|
||||
mdiCloud,
|
||||
mdiCog,
|
||||
mdiDelete,
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
mdiHandExtendedOutline,
|
||||
mdiOpenInNew,
|
||||
mdiPackageVariant,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlus,
|
||||
mdiProgressHelper,
|
||||
mdiReload,
|
||||
mdiReloadAlert,
|
||||
mdiRenameBox,
|
||||
mdiShapeOutline,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
@@ -213,6 +215,27 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
ERROR_STATES.includes(entry.state)
|
||||
);
|
||||
|
||||
const normalEntries = configEntries
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.source !== "ignore" && !ERROR_STATES.includes(entry.state)
|
||||
)
|
||||
.sort((a, b) => {
|
||||
if (Boolean(a.disabled_by) !== Boolean(b.disabled_by)) {
|
||||
return a.disabled_by ? 1 : -1;
|
||||
}
|
||||
return caseInsensitiveStringCompare(
|
||||
a.title,
|
||||
b.title,
|
||||
this.hass.locale.language
|
||||
);
|
||||
});
|
||||
|
||||
const devices = this._getDevices(configEntries, this.hass.devices);
|
||||
const entities = this._getEntities(configEntries, this._entities);
|
||||
|
||||
const services = !devices.some((device) => device.entry_type !== "service");
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -221,7 +244,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
<div class="container">
|
||||
<div class="column small">
|
||||
<ha-card>
|
||||
<ha-card class="overview">
|
||||
<div class="card-content">
|
||||
<div class="logo-container">
|
||||
<img
|
||||
@@ -243,7 +266,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
path=${mdiPackageVariant}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.provided_by_custom_integration"
|
||||
"ui.panel.config.integrations.config_entry.custom_integration"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
@@ -258,28 +281,47 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
${this._logInfo
|
||||
? html`<ha-list-item
|
||||
@request-selected=${this._logInfo.level ===
|
||||
LogSeverity.DEBUG
|
||||
? this._handleDisableDebugLogging
|
||||
: this._handleEnableDebugLogging}
|
||||
graphic="icon"
|
||||
${devices.length > 0
|
||||
? html`<a
|
||||
href=${devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`}
|
||||
>
|
||||
${this._logInfo.level === LogSeverity.DEBUG
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_debug_logging"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.enable_debug_logging"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${this._logInfo.level === LogSeverity.DEBUG
|
||||
? mdiBugStop
|
||||
: mdiBugPlay}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${services
|
||||
? mdiHandExtendedOutline
|
||||
: mdiDevices}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${
|
||||
services ? "services" : "devices"
|
||||
}`,
|
||||
"count",
|
||||
devices.length
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${entities.length > 0
|
||||
? html`<a
|
||||
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiShapeOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entities`,
|
||||
"count",
|
||||
entities.length
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${this._manifest
|
||||
? html`<a
|
||||
@@ -329,6 +371,32 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${this._logInfo
|
||||
? html`<ha-list-item
|
||||
@request-selected=${this._logInfo.level ===
|
||||
LogSeverity.DEBUG
|
||||
? this._handleDisableDebugLogging
|
||||
: this._handleEnableDebugLogging}
|
||||
graphic="icon"
|
||||
>
|
||||
${this._logInfo.level === LogSeverity.DEBUG
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_debug_logging"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.enable_debug_logging"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
class=${this._logInfo.level === LogSeverity.DEBUG
|
||||
? "warning"
|
||||
: ""}
|
||||
.path=${this._logInfo.level === LogSeverity.DEBUG
|
||||
? mdiBugStop
|
||||
: mdiBugPlay}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -354,7 +422,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
.flow=${flow}
|
||||
@click=${this._continueFlow}
|
||||
.label=${this.hass.localize(
|
||||
"config_entry.disabled_by.config_entry"
|
||||
"ui.panel.config.integrations.configure"
|
||||
)}
|
||||
></ha-button>
|
||||
</ha-list-item>`
|
||||
@@ -410,43 +478,38 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
<ha-card>
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.integration_page.entries`
|
||||
)}
|
||||
${this._manifest?.integration_type
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.integrations.integration_page.entries_${this._manifest?.integration_type}`
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.integrations.integration_page.entries`
|
||||
)}
|
||||
</h1>
|
||||
${configEntries.length === 0
|
||||
? html`<div class="card-content">
|
||||
${normalEntries.length === 0
|
||||
? html`<div class="card-content no-entries">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.integration_page.no_entries`
|
||||
"ui.panel.config.integrations.integration_page.no_entries"
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
<mwc-list>
|
||||
${configEntries
|
||||
.filter((entry) => !ERROR_STATES.includes(entry.state))
|
||||
.sort((a, b) => {
|
||||
if (Boolean(a.disabled_by) !== Boolean(b.disabled_by)) {
|
||||
return a.disabled_by ? 1 : -1;
|
||||
}
|
||||
return caseInsensitiveStringCompare(
|
||||
a.title,
|
||||
b.title,
|
||||
this.hass.locale.language
|
||||
);
|
||||
})
|
||||
.map((item) => this._renderConfigEntry(item))}
|
||||
${normalEntries.map((item) => this._renderConfigEntry(item))}
|
||||
</mwc-list>
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._addIntegration}>
|
||||
${this._manifest?.integration_type
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.integrations.integration_page.add_${this._manifest?.integration_type}`
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.integrations.integration_page.add_entry`
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
@click=${this._addIntegration}
|
||||
.label=${`Add ${domainToName(this.hass.localize, this.domain)}`}
|
||||
extended
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -464,21 +527,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
let stateTextExtra: TemplateResult | string | undefined;
|
||||
let icon: string = mdiAlertCircle;
|
||||
|
||||
if (item.disabled_by) {
|
||||
stateText = [
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.disable.disabled_by.${item.disabled_by}`
|
||||
) || item.disabled_by,
|
||||
];
|
||||
if (item.state === "failed_unload") {
|
||||
stateTextExtra = html`.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
)}.`;
|
||||
}
|
||||
} else if (item.state === "not_loaded") {
|
||||
if (!item.disabled_by && item.state === "not_loaded") {
|
||||
stateText = ["ui.panel.config.integrations.config_entry.not_loaded"];
|
||||
} else if (item.state === "setup_in_progress") {
|
||||
icon = mdiProgressHelper;
|
||||
@@ -508,66 +557,87 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
const devices = this._getDevices(item, this.hass.devices);
|
||||
const services = this._getServices(item, this.hass.devices);
|
||||
const entities = this._getEntities(item, this._entities);
|
||||
const devices = this._getConfigEntryDevices(item);
|
||||
const services = this._getConfigEntryServices(item);
|
||||
const entities = this._getConfigEntryEntities(item);
|
||||
|
||||
let devicesLine: (TemplateResult | string)[] = [];
|
||||
|
||||
for (const [items, localizeKey] of [
|
||||
[devices, "devices"],
|
||||
[services, "services"],
|
||||
] as const) {
|
||||
if (items.length === 0) {
|
||||
continue;
|
||||
if (item.disabled_by) {
|
||||
devicesLine.push(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled_cause",
|
||||
{
|
||||
cause:
|
||||
this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.disable.disabled_by.${item.disabled_by}`
|
||||
) || item.disabled_by,
|
||||
}
|
||||
)
|
||||
);
|
||||
if (item.state === "failed_unload") {
|
||||
devicesLine.push(`.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
)}.`);
|
||||
}
|
||||
} else {
|
||||
for (const [items, localizeKey] of [
|
||||
[devices, "devices"],
|
||||
[services, "services"],
|
||||
] as const) {
|
||||
if (items.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const url =
|
||||
items.length === 1
|
||||
? `/config/devices/device/${items[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`;
|
||||
devicesLine.push(
|
||||
// no white space before/after template on purpose
|
||||
html`<a href=${url}
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${localizeKey}`,
|
||||
"count",
|
||||
items.length
|
||||
)}</a
|
||||
>`
|
||||
);
|
||||
}
|
||||
|
||||
if (entities.length) {
|
||||
devicesLine.push(
|
||||
// no white space before/after template on purpose
|
||||
html`<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
"count",
|
||||
entities.length
|
||||
)}</a
|
||||
>`
|
||||
);
|
||||
}
|
||||
|
||||
if (devicesLine.length === 0) {
|
||||
devicesLine = ["No devices or entities"];
|
||||
} else if (devicesLine.length === 2) {
|
||||
devicesLine = [
|
||||
devicesLine[0],
|
||||
` ${this.hass.localize("ui.common.and")} `,
|
||||
devicesLine[1],
|
||||
];
|
||||
} else if (devicesLine.length === 3) {
|
||||
devicesLine = [
|
||||
devicesLine[0],
|
||||
", ",
|
||||
devicesLine[1],
|
||||
` ${this.hass.localize("ui.common.and")} `,
|
||||
devicesLine[2],
|
||||
];
|
||||
}
|
||||
const url =
|
||||
items.length === 1
|
||||
? `/config/devices/device/${items[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`;
|
||||
devicesLine.push(
|
||||
// no white space before/after template on purpose
|
||||
html`<a href=${url}
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${localizeKey}`,
|
||||
"count",
|
||||
items.length
|
||||
)}</a
|
||||
>`
|
||||
);
|
||||
}
|
||||
|
||||
if (entities.length) {
|
||||
devicesLine.push(
|
||||
// no white space before/after template on purpose
|
||||
html`<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
"count",
|
||||
entities.length
|
||||
)}</a
|
||||
>`
|
||||
);
|
||||
}
|
||||
|
||||
if (devicesLine.length === 0) {
|
||||
devicesLine = ["No devices or entities"];
|
||||
} else if (devicesLine.length === 2) {
|
||||
devicesLine = [
|
||||
devicesLine[0],
|
||||
` ${this.hass.localize("ui.common.and")} `,
|
||||
devicesLine[1],
|
||||
];
|
||||
} else if (devicesLine.length === 3) {
|
||||
devicesLine = [
|
||||
devicesLine[0],
|
||||
", ",
|
||||
devicesLine[1],
|
||||
` ${this.hass.localize("ui.common.and")} `,
|
||||
devicesLine[2],
|
||||
];
|
||||
}
|
||||
return html`<ha-list-item
|
||||
hasMeta
|
||||
class="config_entry ${classMap({
|
||||
@@ -577,6 +647,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
"state-error": ERROR_STATES.includes(item!.state),
|
||||
})}"
|
||||
data-entry-id=${item.entry_id}
|
||||
.disabled=${item.disabled_by}
|
||||
.configEntry=${item}
|
||||
twoline
|
||||
noninteractive
|
||||
@@ -637,6 +708,61 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
${item.disabled_by && devices.length
|
||||
? html`<a
|
||||
href=${devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon .path=${mdiDevices} slot="graphic"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.devices`,
|
||||
"count",
|
||||
devices.length
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${item.disabled_by && services.length
|
||||
? html`<a
|
||||
href=${services.length === 1
|
||||
? `/config/devices/device/${services[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiHandExtendedOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.services`,
|
||||
"count",
|
||||
services.length
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${item.disabled_by && entities.length
|
||||
? html`<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiShapeOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entities`,
|
||||
"count",
|
||||
entities.length
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${!item.disabled_by &&
|
||||
RECOVERABLE_STATES.includes(item.state) &&
|
||||
item.supports_unload &&
|
||||
@@ -807,49 +933,77 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _getEntities = memoizeOne(
|
||||
(
|
||||
configEntry: ConfigEntry,
|
||||
configEntry: ConfigEntry[],
|
||||
entityRegistryEntries: EntityRegistryEntry[]
|
||||
): EntityRegistryEntry[] => {
|
||||
if (!entityRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
const entryIds = configEntry.map((entry) => entry.entry_id);
|
||||
return entityRegistryEntries.filter(
|
||||
(entity) => entity.config_entry_id === configEntry.entry_id
|
||||
(entity) =>
|
||||
entity.config_entry_id && entryIds.includes(entity.config_entry_id)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _getDevices = memoizeOne(
|
||||
(
|
||||
configEntry: ConfigEntry,
|
||||
configEntry: ConfigEntry[],
|
||||
deviceRegistryEntries: HomeAssistant["devices"]
|
||||
): DeviceRegistryEntry[] => {
|
||||
if (!deviceRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(deviceRegistryEntries).filter(
|
||||
(device) =>
|
||||
device.config_entries.includes(configEntry.entry_id) &&
|
||||
device.entry_type !== "service"
|
||||
const entryIds = configEntry.map((entry) => entry.entry_id);
|
||||
return Object.values(deviceRegistryEntries).filter((device) =>
|
||||
device.config_entries.some((entryId) => entryIds.includes(entryId))
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _getServices = memoizeOne(
|
||||
(
|
||||
configEntry: ConfigEntry,
|
||||
deviceRegistryEntries: HomeAssistant["devices"]
|
||||
): DeviceRegistryEntry[] => {
|
||||
if (!deviceRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(deviceRegistryEntries).filter(
|
||||
(device) =>
|
||||
device.config_entries.includes(configEntry.entry_id) &&
|
||||
device.entry_type === "service"
|
||||
);
|
||||
}
|
||||
);
|
||||
private _getConfigEntryEntities = (
|
||||
configEntry: ConfigEntry
|
||||
): EntityRegistryEntry[] => {
|
||||
const entries = this._domainConfigEntries(
|
||||
this.domain,
|
||||
this._extraConfigEntries || this.configEntries
|
||||
);
|
||||
const entityRegistryEntries = this._getEntities(entries, this._entities);
|
||||
return entityRegistryEntries.filter(
|
||||
(entity) => entity.config_entry_id === configEntry.entry_id
|
||||
);
|
||||
};
|
||||
|
||||
private _getConfigEntryDevices = (
|
||||
configEntry: ConfigEntry
|
||||
): DeviceRegistryEntry[] => {
|
||||
const entries = this._domainConfigEntries(
|
||||
this.domain,
|
||||
this._extraConfigEntries || this.configEntries
|
||||
);
|
||||
const deviceRegistryEntries = this._getDevices(entries, this.hass.devices);
|
||||
return Object.values(deviceRegistryEntries).filter(
|
||||
(device) =>
|
||||
device.config_entries.includes(configEntry.entry_id) &&
|
||||
device.entry_type !== "service"
|
||||
);
|
||||
};
|
||||
|
||||
private _getConfigEntryServices = (
|
||||
configEntry: ConfigEntry
|
||||
): DeviceRegistryEntry[] => {
|
||||
const entries = this._domainConfigEntries(
|
||||
this.domain,
|
||||
this._extraConfigEntries || this.configEntries
|
||||
);
|
||||
const deviceRegistryEntries = this._getDevices(entries, this.hass.devices);
|
||||
return Object.values(deviceRegistryEntries).filter(
|
||||
(device) =>
|
||||
device.config_entries.includes(configEntry.entry_id) &&
|
||||
device.entry_type === "service"
|
||||
);
|
||||
};
|
||||
|
||||
private _showOptions(ev) {
|
||||
showOptionsFlowDialog(
|
||||
@@ -1168,23 +1322,27 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
.card-header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.card-content {
|
||||
.no-entries {
|
||||
padding-top: 12px;
|
||||
}
|
||||
.logo-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.card-actions {
|
||||
.overview .card-actions {
|
||||
padding: 0;
|
||||
}
|
||||
img {
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
max-height: 100px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
ha-alert:first-of-type {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-list-item.discovered {
|
||||
--mdc-list-item-meta-size: auto;
|
||||
--mdc-list-item-meta-display: flex;
|
||||
@@ -1195,6 +1353,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
--mdc-list-item-meta-size: auto;
|
||||
--mdc-list-item-meta-display: flex;
|
||||
}
|
||||
ha-button-menu ha-list-item {
|
||||
--mdc-list-item-meta-size: 24px;
|
||||
}
|
||||
ha-list-item.config_entry::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -1217,6 +1378,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
.attention {
|
||||
primary-color: var(--error-color);
|
||||
}
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.state-error {
|
||||
--state-message-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list";
|
||||
import { mdiApplication, mdiCog, mdiDevices, mdiShape } from "@mdi/js";
|
||||
import {
|
||||
mdiCogOutline,
|
||||
mdiDevices,
|
||||
mdiHandExtendedOutline,
|
||||
mdiPuzzleOutline,
|
||||
mdiShapeOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -59,28 +65,28 @@ export class HaIntegrationCard extends LitElement {
|
||||
"debug-logging": Boolean(debugLoggingEnabled),
|
||||
})}
|
||||
>
|
||||
<ha-integration-header
|
||||
.hass=${this.hass}
|
||||
.domain=${this.domain}
|
||||
.localizedDomainName=${this.items[0].localized_domain_name}
|
||||
.banner=${state !== "loaded"
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.state.${state}`
|
||||
)
|
||||
: debugLoggingEnabled
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.debug_logging_enabled"
|
||||
)
|
||||
: undefined}
|
||||
.manifest=${this.manifest}
|
||||
>
|
||||
<a
|
||||
href=${`/config/integrations/integration/${this.domain}`}
|
||||
slot="header-button"
|
||||
<a href=${`/config/integrations/integration/${this.domain}`}>
|
||||
<ha-integration-header
|
||||
.hass=${this.hass}
|
||||
.domain=${this.domain}
|
||||
.localizedDomainName=${this.items[0].localized_domain_name}
|
||||
.banner=${state !== "loaded"
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.state.${state}`
|
||||
)
|
||||
: debugLoggingEnabled
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.debug_logging_enabled"
|
||||
)
|
||||
: undefined}
|
||||
.manifest=${this.manifest}
|
||||
>
|
||||
<ha-icon-button .path=${mdiCog}></ha-icon-button>
|
||||
</a>
|
||||
</ha-integration-header>
|
||||
<ha-icon-button
|
||||
slot="header-button"
|
||||
.path=${mdiCogOutline}
|
||||
></ha-icon-button>
|
||||
</ha-integration-header>
|
||||
</a>
|
||||
|
||||
${this._renderSingleEntry()}
|
||||
</ha-card>
|
||||
@@ -89,7 +95,9 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
private _renderSingleEntry(): TemplateResult {
|
||||
const devices = this._getDevices(this.items, this.hass.devices);
|
||||
const entities = this._getEntities(this.items, this.entityRegistryEntries);
|
||||
const entities = devices.length
|
||||
? []
|
||||
: this._getEntities(this.items, this.entityRegistryEntries);
|
||||
|
||||
const services = !devices.some((device) => device.entry_type !== "service");
|
||||
|
||||
@@ -103,7 +111,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${services ? mdiApplication : mdiDevices}
|
||||
.path=${services ? mdiHandExtendedOutline : mdiDevices}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
@@ -116,13 +124,15 @@ export class HaIntegrationCard extends LitElement {
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${entities.length > 0
|
||||
: entities.length > 0
|
||||
? html`<a
|
||||
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon .path=${mdiShape} slot="graphic"></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
.path=${mdiShapeOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entities`,
|
||||
"count",
|
||||
@@ -131,7 +141,20 @@ export class HaIntegrationCard extends LitElement {
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
: html`<a href=${`/config/integrations/integration/${this.domain}`}>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiPuzzleOutline}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entries`,
|
||||
"count",
|
||||
this.items.length
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -191,6 +214,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
--state-color: var(--divider-color, #e0e0e0);
|
||||
--ha-card-border-color: var(--state-color);
|
||||
--state-message-color: var(--state-color);
|
||||
@@ -219,11 +243,13 @@ export class HaIntegrationCard extends LitElement {
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
--mdc-list-side-padding: 16px;
|
||||
--mdc-list-side-padding-right: 20px;
|
||||
--mdc-list-side-padding-left: 24px;
|
||||
--mdc-list-item-graphic-margin: 24px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
a ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
|
@@ -3,6 +3,7 @@ import { mdiCloud, mdiPackageVariant } from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { domainToName, IntegrationManifest } from "../../../data/integration";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@@ -49,7 +50,7 @@ export class HaIntegrationHeader extends LitElement {
|
||||
icons.push([
|
||||
mdiPackageVariant,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.provided_by_custom_integration"
|
||||
"ui.panel.config.integrations.config_entry.custom_integration"
|
||||
),
|
||||
]);
|
||||
}
|
||||
@@ -94,7 +95,10 @@ export class HaIntegrationHeader extends LitElement {
|
||||
([icon, description]) => html`
|
||||
<span>
|
||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
||||
<simple-tooltip animation-delay="0"
|
||||
<simple-tooltip
|
||||
animation-delay="0"
|
||||
.position=${computeRTL(this.hass) ? "left" : "right"}
|
||||
offset="4"
|
||||
>${description}</simple-tooltip
|
||||
>
|
||||
</span>
|
||||
@@ -184,6 +188,9 @@ export class HaIntegrationHeader extends LitElement {
|
||||
left: 40px;
|
||||
top: 40px;
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
inset-inline-start: 40px;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
.icons.cloud {
|
||||
background: var(--info-color);
|
||||
@@ -191,14 +198,18 @@ export class HaIntegrationHeader extends LitElement {
|
||||
.icons.double {
|
||||
background: var(--warning-color);
|
||||
left: 28px;
|
||||
inset-inline-start: 28px;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
.icons ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 4px;
|
||||
display: block;
|
||||
}
|
||||
.icons span:not(:first-child) ha-svg-icon {
|
||||
margin-left: 0;
|
||||
margin-left: 4px;
|
||||
margin-inline-start: 4px;
|
||||
margin-inline-end: inherit;
|
||||
}
|
||||
simple-tooltip {
|
||||
white-space: nowrap;
|
||||
|
@@ -84,7 +84,7 @@ export class HaIntegrationListItem extends ListItemBase {
|
||||
><ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon
|
||||
><simple-tooltip animation-delay="0" position="left"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.provided_by_custom_integration"
|
||||
"ui.panel.config.integrations.config_entry.custom_integration"
|
||||
)}</simple-tooltip
|
||||
></span
|
||||
>`
|
||||
|
@@ -501,7 +501,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
padding: 24px 8px 32px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
direction: ltr;
|
||||
|
@@ -30,6 +30,7 @@ const VALID_CHANNELS = [
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
];
|
||||
|
||||
@customElement("dialog-zha-change-channel")
|
||||
|
@@ -46,6 +46,8 @@ import {
|
||||
} from "../../../../../data/zha";
|
||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
const MULTIPROTOCOL_ADDON_URL = "socket://core-silabs-multiprotocol:9999";
|
||||
|
||||
export const zhaTabs: PageNavigation[] = [
|
||||
{
|
||||
translationKey: "ui.panel.config.zha.network.caption",
|
||||
@@ -181,6 +183,25 @@ class ZHAConfigDashboard extends LitElement {
|
||||
>${this._networkSettings.radio_type}</span
|
||||
>
|
||||
</ha-settings-row>
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="description">Serial port</span>
|
||||
<span slot="heading"
|
||||
>${this._networkSettings.device.path}</span
|
||||
>
|
||||
</ha-settings-row>
|
||||
|
||||
${this._networkSettings.device.baudrate &&
|
||||
!this._networkSettings.device.path.startsWith("socket://")
|
||||
? html`
|
||||
<ha-settings-row>
|
||||
<span slot="description">Baudrate</span>
|
||||
<span slot="heading"
|
||||
>${this._networkSettings.device.baudrate}</span
|
||||
>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="card-actions">
|
||||
@@ -255,6 +276,19 @@ class ZHAConfigDashboard extends LitElement {
|
||||
}
|
||||
|
||||
private async _showChannelMigrationDialog(): Promise<void> {
|
||||
if (this._networkSettings!.device.path === MULTIPROTOCOL_ADDON_URL) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.channel_dialog.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.channel_dialog.text"
|
||||
),
|
||||
warning: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showZHAChangeChannelDialog(this, {
|
||||
currentChannel: this._networkSettings!.settings.network_info.channel,
|
||||
});
|
||||
@@ -341,11 +375,6 @@ class ZHAConfigDashboard extends LitElement {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.network-settings > div {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.network-settings ha-settings-row {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
@@ -353,6 +382,13 @@ class ZHAConfigDashboard extends LitElement {
|
||||
--paper-item-body-two-line-min-height: 55px;
|
||||
}
|
||||
|
||||
.network-settings ha-settings-row span[slot="heading"] {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
text-indent: -1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.network-settings ha-settings-row ha-icon-button {
|
||||
margin-top: -16px;
|
||||
margin-bottom: -16px;
|
||||
|
@@ -217,7 +217,7 @@ export const showRepairsFlowDialog = (
|
||||
return hass.localize(
|
||||
`component.${issue.domain}.issues.${
|
||||
issue.translation_key || issue.issue_id
|
||||
}.fix_flow.step.${step.step_id}.menu_issues.${option}`,
|
||||
}.fix_flow.step.${step.step_id}.menu_options.${option}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
},
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { LocalStorage } from "../../../common/decorators/local-storage";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { Action, ScriptConfig } from "../../../data/script";
|
||||
import { Clipboard } from "../../../data/automation";
|
||||
import { Action, ScriptConfig } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
@@ -25,7 +26,8 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public config!: ScriptConfig;
|
||||
|
||||
@state() private _clipboard: Clipboard = {};
|
||||
@LocalStorage("automationClipboard", true, false, window.sessionStorage)
|
||||
private _clipboard: Clipboard = {};
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
|
@@ -71,33 +71,43 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
||||
private _columns = memoizeOne(
|
||||
(localize): DataTableColumnContainer => ({
|
||||
displayName: {
|
||||
title: "Name",
|
||||
title: localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.data_table.name"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
},
|
||||
statistic_id: {
|
||||
title: "Statistic id",
|
||||
title: localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.data_table.statistic_id"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
hidden: this.narrow,
|
||||
width: "20%",
|
||||
},
|
||||
statistics_unit_of_measurement: {
|
||||
title: "Statistics unit",
|
||||
title: localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.data_table.statistics_unit"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "10%",
|
||||
forceLTR: true,
|
||||
},
|
||||
source: {
|
||||
title: "Source",
|
||||
title: localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.data_table.source"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "10%",
|
||||
},
|
||||
issues: {
|
||||
title: "Issue",
|
||||
title: localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.data_table.issue"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
|
@@ -34,7 +34,10 @@ import {
|
||||
HistoryStates,
|
||||
subscribeHistoryStatesTimeWindow,
|
||||
} from "../../../data/history";
|
||||
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
||||
import {
|
||||
hasConfigChanged,
|
||||
hasConfigOrEntitiesChanged,
|
||||
} from "../common/has-changed";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
@@ -194,7 +197,15 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasConfigOrEntitiesChanged(this, changedProps);
|
||||
if (this._config?.geo_location_sources) {
|
||||
if (oldHass.states !== this.hass.states) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this._config?.entities
|
||||
? hasConfigOrEntitiesChanged(this, changedProps)
|
||||
: hasConfigChanged(this, changedProps);
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
|
@@ -4,7 +4,10 @@ import { EntityRegistryDisplayEntry } from "../../../data/entity_registry";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { processConfigEntities } from "./process-config-entities";
|
||||
|
||||
function hasConfigChanged(element: any, changedProps: PropertyValues): boolean {
|
||||
export function hasConfigChanged(
|
||||
element: any,
|
||||
changedProps: PropertyValues
|
||||
): boolean {
|
||||
if (changedProps.has("_config")) {
|
||||
return true;
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
tabindex=${ifDefined(pointer ? "0" : undefined)}
|
||||
></state-badge>
|
||||
${!this.hideName
|
||||
? html` <div
|
||||
? html`<div
|
||||
class="info ${classMap({
|
||||
pointer,
|
||||
"text-content": !hasSecondary,
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
array,
|
||||
assert,
|
||||
@@ -13,7 +12,10 @@ import {
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { GaugeCardConfig } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
@@ -41,6 +43,75 @@ const cardConfigStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
name: "entity",
|
||||
selector: {
|
||||
entity: {
|
||||
domain: ["counter", "input_number", "number", "sensor"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{ name: "name", selector: { text: {} } },
|
||||
{ name: "unit", selector: { text: {} } },
|
||||
],
|
||||
},
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "min",
|
||||
default: DEFAULT_MIN,
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
default: DEFAULT_MAX,
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{ name: "needle", selector: { boolean: {} } },
|
||||
{ name: "show_severity", selector: { boolean: {} } },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "conditional",
|
||||
condition: (data) => !!data.show_severity,
|
||||
schema: [
|
||||
{
|
||||
name: "severity",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "green",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "yellow",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[];
|
||||
|
||||
@customElement("hui-gauge-card-editor")
|
||||
export class HuiGaugeCardEditor
|
||||
extends LitElement
|
||||
@@ -55,81 +126,11 @@ export class HuiGaugeCardEditor
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(showSeverity: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "entity",
|
||||
selector: {
|
||||
entity: {
|
||||
domain: ["counter", "input_number", "number", "sensor"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{ name: "name", selector: { text: {} } },
|
||||
{ name: "unit", selector: { text: {} } },
|
||||
],
|
||||
},
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "min",
|
||||
default: DEFAULT_MIN,
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
default: DEFAULT_MAX,
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{ name: "needle", selector: { boolean: {} } },
|
||||
{ name: "show_severity", selector: { boolean: {} } },
|
||||
],
|
||||
},
|
||||
...(showSeverity
|
||||
? ([
|
||||
{
|
||||
name: "severity",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "green",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "yellow",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema(this._config!.severity !== undefined);
|
||||
const data = {
|
||||
show_severity: this._config!.severity !== undefined,
|
||||
...this._config,
|
||||
@@ -139,7 +140,7 @@ export class HuiGaugeCardEditor
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.schema=${SCHEMA}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
@@ -153,7 +154,7 @@ export class HuiGaugeCardEditor
|
||||
config = {
|
||||
...config,
|
||||
severity: {
|
||||
green: config.green || config.severity?.green || 0,
|
||||
green: config.green || config.severity?.green,
|
||||
yellow: config.yellow || config.severity?.yellow || 0,
|
||||
red: config.red || config.severity?.red || 0,
|
||||
},
|
||||
@@ -170,9 +171,7 @@ export class HuiGaugeCardEditor
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
|
||||
switch (schema.name) {
|
||||
case "name":
|
||||
return this.hass!.localize(
|
||||
|
@@ -2,7 +2,7 @@ import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { setDateValue, stateToIsoDateString } from "../../../data/date";
|
||||
import { setDateValue } from "../../../data/date";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
@@ -41,16 +41,14 @@ class HuiDateEntityRow extends LitElement implements LovelaceRow {
|
||||
`;
|
||||
}
|
||||
|
||||
const unavailable = isUnavailableState(stateObj.state);
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
hideName="true"
|
||||
>
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.value=${stateToIsoDateString(stateObj)}
|
||||
.disabled=${unavailable}
|
||||
.value=${unavailable ? "" : stateObj.state}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
@@ -59,9 +57,7 @@ class HuiDateEntityRow extends LitElement implements LovelaceRow {
|
||||
}
|
||||
|
||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
const stateObj = this.hass!.states[this._config!.entity];
|
||||
|
||||
setDateValue(this.hass!, stateObj.entity_id, ev.detail.value);
|
||||
setDateValue(this.hass!, this._config!.entity, ev.detail.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ import "../components/hui-generic-entity-row";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { EntityConfig, LovelaceRow } from "./types";
|
||||
import "../../../components/ha-time-input";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
|
||||
@customElement("hui-datetime-entity-row")
|
||||
class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
@@ -51,30 +52,35 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
`;
|
||||
}
|
||||
|
||||
const dateObj = new Date(stateObj.state);
|
||||
const time = format(dateObj, "HH:mm:ss");
|
||||
const date = format(dateObj, "yyyy-MM-dd");
|
||||
const unavailable = isUnavailableState(stateObj.state);
|
||||
|
||||
const dateObj = unavailable ? undefined : new Date(stateObj.state);
|
||||
const time = dateObj ? format(dateObj, "HH:mm:ss") : "";
|
||||
const date = dateObj ? format(dateObj, "yyyy-MM-dd") : "";
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
hideName="true"
|
||||
hideName
|
||||
>
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${date}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
.value=${time}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.locale=${this.hass.locale}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
<div>
|
||||
<ha-date-input
|
||||
.label=${this._config.name || computeStateName(stateObj)}
|
||||
.locale=${this.hass.locale}
|
||||
.value=${date}
|
||||
.disabled=${unavailable}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
.value=${time}
|
||||
.disabled=${unavailable}
|
||||
.locale=${this.hass.locale}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
</div>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
@@ -103,12 +109,17 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-date-input + ha-time-input {
|
||||
ha-time-input {
|
||||
margin-left: 4px;
|
||||
margin-inline-start: 4px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -62,33 +62,39 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
.hideName=${stateObj.attributes.has_date &&
|
||||
stateObj.attributes.has_time}
|
||||
>
|
||||
${stateObj.attributes.has_date
|
||||
? html`
|
||||
<ha-date-input
|
||||
.label=${stateObj.attributes.has_time ? name : undefined}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.value=${stateToIsoDateString(stateObj)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
`
|
||||
: ``}
|
||||
${stateObj.attributes.has_time
|
||||
? html`
|
||||
<ha-time-input
|
||||
.value=${stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: stateObj.attributes.has_date
|
||||
? stateObj.state.split(" ")[1]
|
||||
: stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
`
|
||||
: ``}
|
||||
<div
|
||||
class=${stateObj.attributes.has_date && stateObj.attributes.has_time
|
||||
? "both"
|
||||
: ""}
|
||||
>
|
||||
${stateObj.attributes.has_date
|
||||
? html`
|
||||
<ha-date-input
|
||||
.label=${stateObj.attributes.has_time ? name : undefined}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.value=${stateToIsoDateString(stateObj)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
`
|
||||
: ``}
|
||||
${stateObj.attributes.has_time
|
||||
? html`
|
||||
<ha-time-input
|
||||
.value=${stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: stateObj.attributes.has_date
|
||||
? stateObj.state.split(" ")[1]
|
||||
: stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
`
|
||||
: ``}
|
||||
</div>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
@@ -126,6 +132,11 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
div.both {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -42,16 +42,14 @@ class HuiTimeEntityRow extends LitElement implements LovelaceRow {
|
||||
`;
|
||||
}
|
||||
|
||||
const unavailable = isUnavailableState(stateObj.state);
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
hideName="true"
|
||||
>
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
<ha-time-input
|
||||
.value=${stateObj.state}
|
||||
.value=${unavailable ? "" : stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.disabled=${unavailable}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
|
@@ -802,7 +802,7 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
if (this._yamlMode) {
|
||||
showAlertDialog(this, {
|
||||
text: "The edit UI is not available when in YAML mode.",
|
||||
text: this.hass!.localize("ui.panel.lovelace.editor.yaml_unsupported"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@@ -320,6 +320,11 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Fix for safari */
|
||||
.column:has(> *) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.column:not(:has(> *:not([hidden]))) {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ export const loadPolyfillIfNeeded = async () => {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new ResizeObserver(() => {});
|
||||
return;
|
||||
} catch (e) {
|
||||
window.ResizeObserver = (
|
||||
await import(
|
||||
|
@@ -3062,7 +3062,8 @@
|
||||
"battery": "Battery",
|
||||
"disabled_by": "Disabled",
|
||||
"no_devices": "No devices",
|
||||
"no_integration": "No integration"
|
||||
"no_integration": "No integration",
|
||||
"unknown": "unknown"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"confirm_delete": "Are you sure you want to delete this device?",
|
||||
@@ -3251,8 +3252,19 @@
|
||||
},
|
||||
"integration_page": {
|
||||
"entries": "Integration entries",
|
||||
"entries_device": "Devices",
|
||||
"entries_hub": "Hubs",
|
||||
"entries_service": "Services",
|
||||
"entries_helper": "Helpers",
|
||||
"entries_hardware": "Hardware",
|
||||
"no_entries": "No entries",
|
||||
"attention_entries": "Needs attention"
|
||||
"attention_entries": "Needs attention",
|
||||
"add_entry": "Add entry",
|
||||
"add_device": "Add device",
|
||||
"add_hub": "Add hub",
|
||||
"add_service": "Add service",
|
||||
"add_helper": "Add helper",
|
||||
"add_hardware": "Add hardware"
|
||||
},
|
||||
"config_entry": {
|
||||
"application_credentials": {
|
||||
@@ -3266,6 +3278,7 @@
|
||||
"devices": "{count} {count, plural,\n one {device}\n other {devices}\n}",
|
||||
"entities": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
"services": "{count} {count, plural,\n one {service}\n other {services}\n}",
|
||||
"entries": "{count} {count, plural,\n one {entry}\n other {entries}\n}",
|
||||
"rename": "Rename",
|
||||
"configure": "Configure",
|
||||
"system_options": "System options",
|
||||
@@ -3307,7 +3320,7 @@
|
||||
"device": "device"
|
||||
}
|
||||
},
|
||||
"provided_by_custom_integration": "Provided by a custom integration",
|
||||
"custom_integration": "Custom integration",
|
||||
"depends_on_cloud": "Depends on the cloud",
|
||||
"yaml_only": "Needs manual configuration",
|
||||
"disabled_polling": "Automatic polling for updated data disabled",
|
||||
@@ -3509,7 +3522,11 @@
|
||||
"download_backup": "Download Backup",
|
||||
"migrate_radio": "Migrate Radio",
|
||||
"network_settings_title": "Network Settings",
|
||||
"change_channel": "Change channel"
|
||||
"change_channel": "Change channel",
|
||||
"channel_dialog": {
|
||||
"title": "Multiprotocol addon in use",
|
||||
"text": "Zigbee and Thread share the same radio and must use the same channel. Change the channel of both networks by reconfiguring multiprotocol from the hardware menu."
|
||||
}
|
||||
},
|
||||
"add_device_page": {
|
||||
"spinner": "Searching for Zigbee devices…",
|
||||
@@ -4268,6 +4285,7 @@
|
||||
},
|
||||
"editor": {
|
||||
"header": "Edit UI",
|
||||
"yaml_unsupported": "The edit UI is not available when in YAML mode.",
|
||||
"menu": {
|
||||
"open": "Open dashboard menu",
|
||||
"raw_editor": "Raw configuration editor",
|
||||
@@ -5253,7 +5271,14 @@
|
||||
"clear": "Delete all old statistic data for this entity"
|
||||
}
|
||||
},
|
||||
"adjust_sum": "Adjust sum"
|
||||
"adjust_sum": "Adjust sum",
|
||||
"data_table": {
|
||||
"name": "Name",
|
||||
"statistic_id": "Statistic id",
|
||||
"statistics_unit": "Statistics unit",
|
||||
"source": "Source",
|
||||
"issue": "Issue"
|
||||
}
|
||||
},
|
||||
"yaml": {
|
||||
"title": "YAML",
|
||||
|
Reference in New Issue
Block a user