mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 10:19:25 +00:00
Compare commits
24 Commits
justify-ar
...
context-fo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8f679d12ff | ||
![]() |
20793ecdba | ||
![]() |
afe044d152 | ||
![]() |
dc2038916b | ||
![]() |
cf8e2a6d02 | ||
![]() |
3269b2878b | ||
![]() |
29e1b7b452 | ||
![]() |
3d6d07e5bd | ||
![]() |
7bac41fe41 | ||
![]() |
6e4b027575 | ||
![]() |
728c391b5d | ||
![]() |
8999ca2ea0 | ||
![]() |
4fc0617289 | ||
![]() |
494cc3a569 | ||
![]() |
cc177ef911 | ||
![]() |
eae7e82127 | ||
![]() |
9500ac498c | ||
![]() |
5c5459bcaf | ||
![]() |
cbd0ef6b65 | ||
![]() |
f923228078 | ||
![]() |
b55c7edd70 | ||
![]() |
bfb90632ac | ||
![]() |
3a664d45a9 | ||
![]() |
53607fe8c6 |
@@ -20,7 +20,6 @@ module.exports = [
|
||||
"editor-trigger",
|
||||
"editor-condition",
|
||||
"editor-action",
|
||||
"selectors",
|
||||
"trace",
|
||||
"trace-timeline",
|
||||
],
|
||||
|
@@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import { describeAction } from "../../../../src/data/script_i18n";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
const actions = [
|
||||
const ENTITIES = [
|
||||
getEntity("scene", "kitchen_morning", "scening", {
|
||||
friendly_name: "Kitchen Morning",
|
||||
}),
|
||||
getEntity("media_player", "kitchen", "playing", {
|
||||
friendly_name: "Sonos Kitchen",
|
||||
}),
|
||||
];
|
||||
|
||||
const ACTIONS = [
|
||||
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
||||
{ delay: "0:05" },
|
||||
{ wait_template: "{{ true }}" },
|
||||
@@ -19,8 +29,20 @@ const actions = [
|
||||
device_id: "abcdefgh",
|
||||
domain: "plex",
|
||||
entity_id: "media_player.kitchen",
|
||||
type: "turn_on",
|
||||
},
|
||||
{ scene: "scene.kitchen_morning" },
|
||||
{
|
||||
service: "scene.turn_on",
|
||||
target: { entity_id: "scene.kitchen_morning" },
|
||||
metadata: {},
|
||||
},
|
||||
{
|
||||
service: "media_player.play_media",
|
||||
target: { entity_id: "media_player.kitchen" },
|
||||
data: { media_content_id: "", media_content_type: "" },
|
||||
metadata: { title: "Happy Song" },
|
||||
},
|
||||
{
|
||||
wait_for_trigger: [
|
||||
{
|
||||
@@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<ha-card header="Actions">
|
||||
${actions.map(
|
||||
${ACTIONS.map(
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, conf as any)}</span>
|
||||
@@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
@@ -14,7 +14,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t
|
||||
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-scene";
|
||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene";
|
||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||
|
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Selectors
|
||||
---
|
@@ -1,102 +0,0 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
||||
import { Selector } from "../../../../src/data/selector";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
|
||||
const SCHEMAS: { name: string; selector: Selector }[] = [
|
||||
{ name: "Addon", selector: { addon: {} } },
|
||||
|
||||
{ name: "Entity", selector: { entity: {} } },
|
||||
{ name: "Device", selector: { device: {} } },
|
||||
{ name: "Area", selector: { area: {} } },
|
||||
{ name: "Target", selector: { target: {} } },
|
||||
{
|
||||
name: "Number",
|
||||
selector: {
|
||||
number: {
|
||||
min: 0,
|
||||
max: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: "Boolean", selector: { boolean: {} } },
|
||||
{ name: "Time", selector: { time: {} } },
|
||||
{ name: "Action", selector: { action: {} } },
|
||||
{ name: "Text", selector: { text: { multiline: false } } },
|
||||
{ name: "Text Multiline", selector: { text: { multiline: true } } },
|
||||
{ name: "Object", selector: { object: {} } },
|
||||
{
|
||||
name: "Select",
|
||||
selector: {
|
||||
select: {
|
||||
options: ["Everyone Home", "Some Home", "All gone"],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-automation-selectors")
|
||||
class DemoHaSelector extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
private data: any = SCHEMAS.map(() => undefined);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
mockEntityRegistry(hass);
|
||||
mockDeviceRegistry(hass);
|
||||
mockAreaRegistry(hass);
|
||||
mockHassioSupervisor(hass);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const valueChanged = (ev) => {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
${SCHEMAS.map(
|
||||
(info, sampleIdx) => html`
|
||||
<demo-black-white-row
|
||||
.title=${info.name}
|
||||
.value=${{ selector: info.selector, data: this.data[sampleIdx] }}
|
||||
>
|
||||
${["light", "dark"].map(
|
||||
(slot) =>
|
||||
html`
|
||||
<ha-selector
|
||||
slot=${slot}
|
||||
.hass=${this.hass}
|
||||
.selector=${info.selector}
|
||||
.label=${info.name}
|
||||
.value=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-selector>
|
||||
`
|
||||
)}
|
||||
</demo-black-white-row>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-automation-selectors": DemoHaSelector;
|
||||
}
|
||||
}
|
@@ -36,6 +36,8 @@ const SCHEMAS: {
|
||||
text_multiline: "Text Multiline",
|
||||
object: "Object",
|
||||
select: "Select",
|
||||
icon: "Icon",
|
||||
media: "Media",
|
||||
},
|
||||
schema: [
|
||||
{ name: "addon", selector: { addon: {} } },
|
||||
@@ -67,6 +69,12 @@ const SCHEMAS: {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "media",
|
||||
selector: {
|
||||
media: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -1,3 +1,5 @@
|
||||
---
|
||||
title: Target Selectors
|
||||
title: Selectors
|
||||
---
|
||||
|
||||
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
|
||||
|
@@ -12,6 +12,100 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
friendly_name: "Alarm",
|
||||
}),
|
||||
getEntity("media_player", "livingroom", "playing", {
|
||||
friendly_name: "Livingroom",
|
||||
}),
|
||||
getEntity("media_player", "lounge", "idle", {
|
||||
friendly_name: "Lounge",
|
||||
supported_features: 444983,
|
||||
}),
|
||||
getEntity("light", "bedroom", "on", {
|
||||
friendly_name: "Bedroom",
|
||||
}),
|
||||
getEntity("switch", "coffee", "off", {
|
||||
friendly_name: "Coffee",
|
||||
}),
|
||||
];
|
||||
|
||||
const DEVICES = [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_1"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_1",
|
||||
identifiers: [["demo", "volume1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Dishwasher",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_2"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_2",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Lamp",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_3"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_3",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: "User name",
|
||||
name: "Technical name",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
|
||||
const SCHEMAS: {
|
||||
name: string;
|
||||
@@ -73,13 +167,14 @@ const SCHEMAS: {
|
||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||
},
|
||||
icon: { name: "Icon", selector: { icon: {} } },
|
||||
media: { name: "Media", selector: { media: {} } },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-selector")
|
||||
class DemoHaSelector extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
@state() public hass!: HomeAssistant;
|
||||
|
||||
private data = SCHEMAS.map(() => ({}));
|
||||
|
||||
@@ -88,12 +183,130 @@ class DemoHaSelector extends LitElement {
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockEntityRegistry(hass);
|
||||
mockDeviceRegistry(hass);
|
||||
mockAreaRegistry(hass);
|
||||
mockDeviceRegistry(hass, DEVICES);
|
||||
mockAreaRegistry(hass, AREAS);
|
||||
mockHassioSupervisor(hass);
|
||||
hass.mockWS("auth/sign_path", (params) => params);
|
||||
hass.mockWS("media_player/browse_media", this._browseMedia);
|
||||
}
|
||||
|
||||
public provideHass(el) {
|
||||
el.hass = this.hass;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("show-dialog", this._dialogManager);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener("show-dialog", this._dialogManager);
|
||||
}
|
||||
|
||||
private _browseMedia = ({ media_content_id }) => {
|
||||
if (media_content_id === undefined) {
|
||||
return {
|
||||
title: "Media",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/.",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: "directory",
|
||||
thumbnail: null,
|
||||
children: [
|
||||
{
|
||||
title: "Misc",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/misc",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: null,
|
||||
thumbnail: null,
|
||||
},
|
||||
{
|
||||
title: "Movies",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/movies",
|
||||
can_play: true,
|
||||
can_expand: true,
|
||||
children_media_class: "movie",
|
||||
thumbnail: null,
|
||||
},
|
||||
{
|
||||
title: "Music",
|
||||
media_class: "album",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/music",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: "music",
|
||||
thumbnail: "/images/album_cover_2.jpg",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: "Subfolder",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/sub",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: "directory",
|
||||
thumbnail: null,
|
||||
children: [
|
||||
{
|
||||
title: "audio.mp3",
|
||||
media_class: "music",
|
||||
media_content_type: "audio/mpeg",
|
||||
media_content_id: "media-source://media_source/local/audio.mp3",
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: "/images/album_cover.jpg",
|
||||
},
|
||||
{
|
||||
title: "image.jpg",
|
||||
media_class: "image",
|
||||
media_content_type: "image/jpeg",
|
||||
media_content_id: "media-source://media_source/local/image.jpg",
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: null,
|
||||
},
|
||||
{
|
||||
title: "movie.mp4",
|
||||
media_class: "movie",
|
||||
media_content_type: "image/jpeg",
|
||||
media_content_id: "media-source://media_source/local/movie.mp4",
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
private _dialogManager = (e) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
|
||||
showDialog(
|
||||
this,
|
||||
this.shadowRoot!,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
addHistory
|
||||
);
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
@@ -132,7 +345,6 @@ class DemoHaSelector extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
paper-input,
|
||||
ha-selector {
|
||||
width: 60;
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiKey,
|
||||
mdiLinkLock,
|
||||
mdiNetwork,
|
||||
mdiNumeric1,
|
||||
mdiNumeric2,
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
mdiNumeric4,
|
||||
mdiNumeric5,
|
||||
mdiNumeric6,
|
||||
mdiNumeric7,
|
||||
mdiNumeric8,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
@@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-chip";
|
||||
import "../../../../src/components/ha-chip-set";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
@@ -84,6 +88,8 @@ const RATING_ICON = {
|
||||
4: mdiNumeric4,
|
||||
5: mdiNumeric5,
|
||||
6: mdiNumeric6,
|
||||
7: mdiNumeric7,
|
||||
8: mdiNumeric8,
|
||||
};
|
||||
|
||||
@customElement("hassio-addon-info")
|
||||
@@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement {
|
||||
>`}
|
||||
</div>
|
||||
|
||||
<div class="capabilities">
|
||||
<ha-chip-set class="capabilities">
|
||||
${this.addon.stage !== "stable"
|
||||
? html` <ha-chip
|
||||
hasIcon
|
||||
@@ -234,9 +240,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-chip
|
||||
hasIcon
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||
red: [1, 2].includes(Number(this.addon.rating)),
|
||||
green: Number(this.addon.rating) >= 6,
|
||||
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
|
||||
red: Number(this.addon.rating) >= 2,
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="rating"
|
||||
@@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-chip>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${this.addon.signed
|
||||
? html`
|
||||
<ha-chip hasIcon @click=${this._showMoreInfo} id="signed">
|
||||
<ha-svg-icon slot="icon" .path=${mdiLinkLock}></ha-svg-icon>
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.signed"
|
||||
)}
|
||||
</ha-chip>
|
||||
`
|
||||
: ""}
|
||||
</ha-chip-set>
|
||||
|
||||
<div class="description light-color">
|
||||
${this.addon.description}.<br />
|
||||
|
@@ -106,7 +106,7 @@
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.11",
|
||||
"hls.js": "^1.1.5",
|
||||
"home-assistant-js-websocket": "^6.0.1",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220214.0
|
||||
version = 20220220.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
|
@@ -49,7 +49,7 @@ export interface HaFormSelectSchema extends HaFormBaseSchema {
|
||||
|
||||
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
||||
type: "multi_select";
|
||||
options: Record<string, string> | string[];
|
||||
options: Record<string, string> | string[] | Array<[string, string]>;
|
||||
}
|
||||
|
||||
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
||||
|
264
src/components/ha-selector/ha-selector-media.ts
Normal file
264
src/components/ha-selector/ha-selector-media.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { mdiPlayBox, mdiPlus } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { getSignedPath } from "../../data/auth";
|
||||
import {
|
||||
MediaClassBrowserSettings,
|
||||
MediaPickedEvent,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
} from "../../data/media-player";
|
||||
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-alert";
|
||||
import "../ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../ha-form/types";
|
||||
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
||||
|
||||
const MANUAL_SCHEMA = [
|
||||
{ name: "media_content_id", required: false, selector: { text: {} } },
|
||||
{ name: "media_content_type", required: false, selector: { text: {} } },
|
||||
];
|
||||
|
||||
@customElement("ha-selector-media")
|
||||
export class HaMediaSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: MediaSelector;
|
||||
|
||||
@property({ attribute: false }) public value?: MediaSelectorValue;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@state() private _thumbnailUrl?: string | null;
|
||||
|
||||
willUpdate(changedProps: PropertyValues<this>) {
|
||||
if (changedProps.has("value")) {
|
||||
const thumbnail = this.value?.metadata?.thumbnail;
|
||||
const oldThumbnail = (changedProps.get("value") as this["value"])
|
||||
?.metadata?.thumbnail;
|
||||
if (thumbnail === oldThumbnail) {
|
||||
return;
|
||||
}
|
||||
if (thumbnail && thumbnail.startsWith("/")) {
|
||||
this._thumbnailUrl = undefined;
|
||||
// Thumbnails served by local API require authentication
|
||||
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
||||
this._thumbnailUrl = signedPath.path;
|
||||
});
|
||||
} else {
|
||||
this._thumbnailUrl = thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const stateObj = this.value?.entity_id
|
||||
? this.hass.states[this.value.entity_id]
|
||||
: undefined;
|
||||
|
||||
const supportsBrowse =
|
||||
!this.value?.entity_id ||
|
||||
(stateObj && supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA));
|
||||
|
||||
return html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.entity_id}
|
||||
.label=${this.label ||
|
||||
this.hass.localize("ui.components.selectors.media.pick_media_player")}
|
||||
.disabled=${this.disabled}
|
||||
include-domains='["media_player"]'
|
||||
allow-custom-entity
|
||||
@value-changed=${this._entityChanged}
|
||||
></ha-entity-picker>
|
||||
${!supportsBrowse
|
||||
? html`<ha-alert>
|
||||
${this.hass.localize(
|
||||
"ui.components.selectors.media.browse_not_supported"
|
||||
)}
|
||||
</ha-alert>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.value}
|
||||
.schema=${MANUAL_SCHEMA}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>`
|
||||
: html`<ha-card
|
||||
outlined
|
||||
@click=${this._pickMedia}
|
||||
class=${this.disabled || !this.value?.entity_id ? "disabled" : ""}
|
||||
>
|
||||
<div
|
||||
class="thumbnail ${classMap({
|
||||
portrait:
|
||||
!!this.value?.metadata?.media_class &&
|
||||
MediaClassBrowserSettings[
|
||||
this.value.metadata.children_media_class ||
|
||||
this.value.metadata.media_class
|
||||
].thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${this.value?.metadata?.thumbnail
|
||||
? html`
|
||||
<div
|
||||
class="${classMap({
|
||||
"centered-image":
|
||||
!!this.value.metadata.media_class &&
|
||||
["app", "directory"].includes(
|
||||
this.value.metadata.media_class
|
||||
),
|
||||
})}
|
||||
image"
|
||||
style=${this._thumbnailUrl
|
||||
? `background-image: url(${this._thumbnailUrl});`
|
||||
: ""}
|
||||
></div>
|
||||
`
|
||||
: html`
|
||||
<div class="icon-holder image">
|
||||
<ha-svg-icon
|
||||
class="folder"
|
||||
.path=${!this.value?.media_content_id
|
||||
? mdiPlus
|
||||
: this.value?.metadata?.media_class
|
||||
? MediaClassBrowserSettings[
|
||||
this.value.metadata.media_class === "directory"
|
||||
? this.value.metadata.children_media_class ||
|
||||
this.value.metadata.media_class
|
||||
: this.value.metadata.media_class
|
||||
].icon
|
||||
: mdiPlayBox}
|
||||
></ha-svg-icon>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<div class="title">
|
||||
${!this.value?.media_content_id
|
||||
? this.hass.localize("ui.components.selectors.media.pick_media")
|
||||
: this.value.metadata?.title || this.value.media_content_id}
|
||||
</div>
|
||||
</ha-card>`}`;
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||
this.hass.localize(`ui.components.selectors.media.${schema.name}`);
|
||||
|
||||
private _entityChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
entity_id: ev.detail.value,
|
||||
media_content_id: "",
|
||||
media_content_type: "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _pickMedia() {
|
||||
showMediaBrowserDialog(this, {
|
||||
action: "pick",
|
||||
entityId: this.value!.entity_id!,
|
||||
navigateIds: this.value!.metadata?.navigateIds,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.value,
|
||||
media_content_id: pickedMedia.item.media_content_id,
|
||||
media_content_type: pickedMedia.item.media_content_type,
|
||||
metadata: {
|
||||
title: pickedMedia.item.title,
|
||||
thumbnail: pickedMedia.item.thumbnail,
|
||||
media_class: pickedMedia.item.media_class,
|
||||
children_media_class: pickedMedia.item.children_media_class,
|
||||
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
||||
media_content_type: id.media_content_type,
|
||||
media_content_id: id.media_content_id,
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-entity-picker {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
mwc-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-card.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
ha-card .thumbnail {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
transition: padding-bottom 0.1s ease-out;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
ha-card .thumbnail.portrait {
|
||||
padding-bottom: 150%;
|
||||
}
|
||||
ha-card .image {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
.folder {
|
||||
--mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4);
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
padding-top: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 16px;
|
||||
padding-right: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
.centered-image {
|
||||
margin: 0 8px;
|
||||
background-size: contain;
|
||||
}
|
||||
.icon-holder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-media": HaMediaSelector;
|
||||
}
|
||||
}
|
@@ -22,6 +22,7 @@ export class HaTimeSelector extends LitElement {
|
||||
.value=${this.value}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label}
|
||||
enable-second
|
||||
></ha-time-input>
|
||||
`;
|
||||
|
@@ -18,6 +18,7 @@ import "./ha-selector-target";
|
||||
import "./ha-selector-text";
|
||||
import "./ha-selector-time";
|
||||
import "./ha-selector-icon";
|
||||
import "./ha-selector-media";
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
|
@@ -28,10 +28,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
|
||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||
this._params = params;
|
||||
this._navigateIds = [
|
||||
this._navigateIds = params.navigateIds || [
|
||||
{
|
||||
media_content_id: this._params.mediaContentId,
|
||||
media_content_type: this._params.mediaContentType,
|
||||
media_content_id: undefined,
|
||||
media_content_type: undefined,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@@ -11,12 +11,26 @@ import {
|
||||
getCloudTtsLanguages,
|
||||
getCloudTtsSupportedGenders,
|
||||
} from "../../data/cloud/tts";
|
||||
import { MediaPlayerBrowseAction } from "../../data/media-player";
|
||||
import {
|
||||
MediaPlayerBrowseAction,
|
||||
MediaPlayerItem,
|
||||
} from "../../data/media-player";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-textarea";
|
||||
import { buttonLinkStyle } from "../../resources/styles";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
|
||||
export interface TtsMediaPickedEvent {
|
||||
item: MediaPlayerItem;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"tts-picked": TtsMediaPickedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-browse-media-tts")
|
||||
class BrowseMediaTTS extends LitElement {
|
||||
@@ -32,40 +46,55 @@ class BrowseMediaTTS extends LitElement {
|
||||
|
||||
@state() private _cloudTTSInfo?: CloudTTSInfo;
|
||||
|
||||
@LocalStorage("cloudTtsTryMessage", false, false) private _message!: string;
|
||||
@LocalStorage("cloudTtsTryMessage", true, false) private _message!: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-textarea
|
||||
autogrow
|
||||
.label=${this.hass.localize("ui.panel.media-browser.tts.message")}
|
||||
.value=${this._message ||
|
||||
this.hass.localize("ui.panel.media-browser.tts.example_message", {
|
||||
name: this.hass.user?.name || "",
|
||||
})}
|
||||
>
|
||||
</ha-textarea>
|
||||
${this._cloudDefaultOptions ? this._renderCloudOptions() : ""}
|
||||
<div class="actions">
|
||||
return html`<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-textarea
|
||||
autogrow
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.media-browser.tts.message"
|
||||
)}
|
||||
.value=${this._message ||
|
||||
this.hass.localize(
|
||||
"ui.components.media-browser.tts.example_message",
|
||||
{
|
||||
name: this.hass.user?.name || "",
|
||||
}
|
||||
)}
|
||||
>
|
||||
</ha-textarea>
|
||||
${this._cloudDefaultOptions ? this._renderCloudOptions() : ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this._cloudDefaultOptions &&
|
||||
(this._cloudDefaultOptions![0] !== this._cloudOptions![0] ||
|
||||
this._cloudDefaultOptions![1] !== this._cloudOptions![1])
|
||||
? html`
|
||||
<button class="link" @click=${this._storeDefaults}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.media-browser.tts.set_as_default"
|
||||
"ui.components.media-browser.tts.set_as_default"
|
||||
)}
|
||||
</button>
|
||||
`
|
||||
: html`<span></span>`}
|
||||
<mwc-button raised label="Say" @click=${this._ttsClicked}></mwc-button>
|
||||
|
||||
<mwc-button @click=${this._ttsClicked}>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.tts.action_${this.action}`
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
</ha-card> `;
|
||||
}
|
||||
|
||||
private _renderCloudOptions() {
|
||||
if (!this._cloudTTSInfo || !this._cloudOptions) {
|
||||
return "";
|
||||
}
|
||||
const languages = this.getLanguages(this._cloudTTSInfo);
|
||||
const selectedVoice = this._cloudOptions!;
|
||||
const selectedVoice = this._cloudOptions;
|
||||
const genders = this.getSupportedGenders(
|
||||
selectedVoice[0],
|
||||
this._cloudTTSInfo,
|
||||
@@ -77,9 +106,12 @@ class BrowseMediaTTS extends LitElement {
|
||||
<mwc-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.label=${this.hass.localize("ui.panel.media-browser.tts.language")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.media-browser.tts.language"
|
||||
)}
|
||||
.value=${selectedVoice[0]}
|
||||
@selected=${this._handleLanguageChange}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${languages.map(
|
||||
([key, label]) =>
|
||||
@@ -90,9 +122,10 @@ class BrowseMediaTTS extends LitElement {
|
||||
<mwc-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.label=${this.hass.localize("ui.panel.media-browser.tts.gender")}
|
||||
.label=${this.hass.localize("ui.components.media-browser.tts.gender")}
|
||||
.value=${selectedVoice[1]}
|
||||
@selected=${this._handleGenderChange}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${genders.map(
|
||||
([key, label]) =>
|
||||
@@ -106,6 +139,37 @@ class BrowseMediaTTS extends LitElement {
|
||||
protected override willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (changedProps.has("item")) {
|
||||
if (this.item.media_content_id) {
|
||||
const params = new URLSearchParams(
|
||||
this.item.media_content_id.split("?")[1]
|
||||
);
|
||||
const message = params.get("message");
|
||||
const language = params.get("language");
|
||||
const gender = params.get("gender");
|
||||
if (message) {
|
||||
this._message = message;
|
||||
}
|
||||
if (language && gender) {
|
||||
this._cloudOptions = [language, gender];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isCloudItem && !this._cloudTTSInfo) {
|
||||
getCloudTTSInfo(this.hass).then((info) => {
|
||||
this._cloudTTSInfo = info;
|
||||
});
|
||||
fetchCloudStatus(this.hass).then((status) => {
|
||||
if (status.logged_in) {
|
||||
this._cloudDefaultOptions = status.prefs.tts_default_voice;
|
||||
if (!this._cloudOptions) {
|
||||
this._cloudOptions = { ...this._cloudDefaultOptions };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("message")) {
|
||||
return;
|
||||
}
|
||||
@@ -133,30 +197,12 @@ class BrowseMediaTTS extends LitElement {
|
||||
this._cloudOptions = [this._cloudOptions![0], ev.target.value];
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("item")) {
|
||||
if (this.isCloudItem && !this._cloudTTSInfo) {
|
||||
getCloudTTSInfo(this.hass).then((info) => {
|
||||
this._cloudTTSInfo = info;
|
||||
});
|
||||
fetchCloudStatus(this.hass).then((status) => {
|
||||
if (status.logged_in) {
|
||||
this._cloudDefaultOptions = status.prefs.tts_default_voice;
|
||||
this._cloudOptions = { ...this._cloudDefaultOptions };
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getLanguages = memoizeOne(getCloudTtsLanguages);
|
||||
|
||||
private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders);
|
||||
|
||||
private get isCloudItem(): boolean {
|
||||
return this.item.media_content_id === "media-source://tts/cloud";
|
||||
return this.item.media_content_id.startsWith("media-source://tts/cloud");
|
||||
}
|
||||
|
||||
private async _ttsClicked(): Promise<void> {
|
||||
@@ -169,9 +215,12 @@ class BrowseMediaTTS extends LitElement {
|
||||
query.append("language", this._cloudOptions[0]);
|
||||
query.append("gender", this._cloudOptions[1]);
|
||||
}
|
||||
item.media_content_id += `?${query.toString()}`;
|
||||
item.media_content_id = `${
|
||||
item.media_content_id.split("?")[0]
|
||||
}?${query.toString()}`;
|
||||
item.can_play = true;
|
||||
fireEvent(this, "media-picked", { item });
|
||||
item.title = message;
|
||||
fireEvent(this, "tts-picked", { item });
|
||||
}
|
||||
|
||||
private async _storeDefaults() {
|
||||
@@ -185,7 +234,7 @@ class BrowseMediaTTS extends LitElement {
|
||||
this._cloudDefaultOptions = oldDefaults;
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.media-browser.tts.faild_to_store_defaults",
|
||||
"ui.components.media-browser.tts.faild_to_store_defaults",
|
||||
{ error: err.message || err }
|
||||
),
|
||||
});
|
||||
@@ -210,15 +259,16 @@ class BrowseMediaTTS extends LitElement {
|
||||
.cloud-options mwc-select {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
ha-textarea {
|
||||
width: 100%;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ import "../ha-svg-icon";
|
||||
import "../ha-fab";
|
||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||
import { isTTSMediaSource } from "../../data/tts";
|
||||
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||
import "./ha-browse-media-tts";
|
||||
|
||||
declare global {
|
||||
@@ -260,6 +261,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.item=${currentItem}
|
||||
.hass=${this.hass}
|
||||
.action=${this.action}
|
||||
@tts-picked=${this._ttsPicked}
|
||||
></ha-browse-media-tts>
|
||||
`
|
||||
: !currentItem.children?.length
|
||||
@@ -562,7 +564,17 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
private _runAction(item: MediaPlayerItem): void {
|
||||
fireEvent(this, "media-picked", { item });
|
||||
fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds });
|
||||
}
|
||||
|
||||
private _ttsPicked(ev: CustomEvent<TtsMediaPickedEvent>): void {
|
||||
ev.stopPropagation();
|
||||
const navigateIds = this.navigateIds.slice(0, -1);
|
||||
navigateIds.push(ev.detail.item);
|
||||
fireEvent(this, "media-picked", {
|
||||
...ev.detail,
|
||||
navigateIds,
|
||||
});
|
||||
}
|
||||
|
||||
private async _childClicked(ev: MouseEvent): Promise<void> {
|
||||
|
@@ -3,13 +3,13 @@ import {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
import { MediaPlayerItemId } from "./ha-media-player-browse";
|
||||
|
||||
export interface MediaPlayerBrowseDialogParams {
|
||||
action: MediaPlayerBrowseAction;
|
||||
entityId: string;
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => void;
|
||||
mediaContentId?: string;
|
||||
mediaContentType?: string;
|
||||
navigateIds?: MediaPlayerItemId[];
|
||||
}
|
||||
|
||||
export const showMediaBrowserDialog = (
|
||||
|
@@ -47,6 +47,7 @@ export interface CloudPreferences {
|
||||
export interface CloudStatusLoggedIn {
|
||||
logged_in: true;
|
||||
cloud: "disconnected" | "connecting" | "connected";
|
||||
cloud_last_disconnect_reason: { clean: boolean; reason: string } | null;
|
||||
email: string;
|
||||
google_registered: boolean;
|
||||
google_entities: EntityFilter;
|
||||
|
@@ -84,9 +84,10 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
options: Record<string, unknown>;
|
||||
privileged: any;
|
||||
protected: boolean;
|
||||
rating: "1-6";
|
||||
rating: "1-8";
|
||||
schema: HaFormSchema[] | null;
|
||||
services_role: string[];
|
||||
signed: boolean;
|
||||
slug: string;
|
||||
startup: AddonStartup;
|
||||
stdin: boolean;
|
||||
|
@@ -28,6 +28,7 @@ import type {
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
|
||||
@@ -147,6 +148,7 @@ export const MediaClassBrowserSettings: {
|
||||
|
||||
export interface MediaPickedEvent {
|
||||
item: MediaPlayerItem;
|
||||
navigateIds: MediaPlayerItemId[];
|
||||
}
|
||||
|
||||
export interface MediaPlayerThumbnail {
|
||||
|
@@ -44,15 +44,6 @@ export const subscribeMQTTTopic = (
|
||||
topic,
|
||||
});
|
||||
|
||||
export const removeMQTTDeviceEntry = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "mqtt/device/remove",
|
||||
device_id: deviceId,
|
||||
});
|
||||
|
||||
export const fetchMQTTDebugInfo = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string
|
||||
|
@@ -3,6 +3,17 @@ import {
|
||||
HassEntityBase,
|
||||
HassServiceTarget,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
union,
|
||||
array,
|
||||
assign,
|
||||
literal,
|
||||
is,
|
||||
Describe,
|
||||
} from "superstruct";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
@@ -12,6 +23,48 @@ import { BlueprintInput } from "./blueprint";
|
||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||
export const MODES_MAX = ["queued", "parallel"];
|
||||
|
||||
export const baseActionStruct = object({
|
||||
alias: optional(string()),
|
||||
});
|
||||
|
||||
const targetStruct = object({
|
||||
entity_id: optional(union([string(), array(string())])),
|
||||
device_id: optional(union([string(), array(string())])),
|
||||
area_id: optional(union([string(), array(string())])),
|
||||
});
|
||||
|
||||
export const serviceActionStruct: Describe<ServiceAction> = assign(
|
||||
baseActionStruct,
|
||||
object({
|
||||
service: optional(string()),
|
||||
service_template: optional(string()),
|
||||
entity_id: optional(string()),
|
||||
target: optional(targetStruct),
|
||||
data: optional(object()),
|
||||
})
|
||||
);
|
||||
|
||||
const playMediaActionStruct: Describe<PlayMediaAction> = assign(
|
||||
baseActionStruct,
|
||||
object({
|
||||
service: literal("media_player.play_media"),
|
||||
target: optional(object({ entity_id: optional(string()) })),
|
||||
entity_id: optional(string()),
|
||||
data: object({ media_content_id: string(), media_content_type: string() }),
|
||||
metadata: object(),
|
||||
})
|
||||
);
|
||||
|
||||
const activateSceneActionStruct: Describe<ServiceSceneAction> = assign(
|
||||
baseActionStruct,
|
||||
object({
|
||||
service: literal("scene.turn_on"),
|
||||
target: optional(object({ entity_id: optional(string()) })),
|
||||
entity_id: optional(string()),
|
||||
metadata: object(),
|
||||
})
|
||||
);
|
||||
|
||||
export interface ScriptEntity extends HassEntityBase {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
last_triggered: string;
|
||||
@@ -48,11 +101,12 @@ export interface ServiceAction {
|
||||
service_template?: string;
|
||||
entity_id?: string;
|
||||
target?: HassServiceTarget;
|
||||
data?: Record<string, any>;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DeviceAction {
|
||||
alias?: string;
|
||||
type: string;
|
||||
device_id: string;
|
||||
domain: string;
|
||||
entity_id: string;
|
||||
@@ -70,9 +124,12 @@ export interface DelayAction {
|
||||
delay: number | Partial<DelayActionParts> | string;
|
||||
}
|
||||
|
||||
export interface ServiceSceneAction extends ServiceAction {
|
||||
export interface ServiceSceneAction {
|
||||
alias?: string;
|
||||
service: "scene.turn_on";
|
||||
metadata: Record<string, any>;
|
||||
target?: { entity_id?: string };
|
||||
entity_id?: string;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
export interface LegacySceneAction {
|
||||
alias?: string;
|
||||
@@ -94,6 +151,15 @@ export interface WaitForTriggerAction {
|
||||
continue_on_timeout?: boolean;
|
||||
}
|
||||
|
||||
export interface PlayMediaAction {
|
||||
alias?: string;
|
||||
service: "media_player.play_media";
|
||||
target?: { entity_id?: string };
|
||||
entity_id?: string;
|
||||
data: { media_content_id: string; media_content_type: string };
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface RepeatAction {
|
||||
alias?: string;
|
||||
repeat: CountRepeat | WhileRepeat | UntilRepeat;
|
||||
@@ -150,6 +216,7 @@ export type Action =
|
||||
| RepeatAction
|
||||
| ChooseAction
|
||||
| VariablesAction
|
||||
| PlayMediaAction
|
||||
| UnknownAction;
|
||||
|
||||
export interface ActionTypes {
|
||||
@@ -158,13 +225,13 @@ export interface ActionTypes {
|
||||
check_condition: Condition;
|
||||
fire_event: EventAction;
|
||||
device_action: DeviceAction;
|
||||
legacy_activate_scene: LegacySceneAction;
|
||||
activate_scene: ServiceSceneAction;
|
||||
activate_scene: SceneAction;
|
||||
repeat: RepeatAction;
|
||||
choose: ChooseAction;
|
||||
wait_for_trigger: WaitForTriggerAction;
|
||||
variables: VariablesAction;
|
||||
service: ServiceAction;
|
||||
play_media: PlayMediaAction;
|
||||
unknown: UnknownAction;
|
||||
}
|
||||
|
||||
@@ -224,7 +291,7 @@ export const getActionType = (action: Action): ActionType => {
|
||||
return "device_action";
|
||||
}
|
||||
if ("scene" in action) {
|
||||
return "legacy_activate_scene";
|
||||
return "activate_scene";
|
||||
}
|
||||
if ("repeat" in action) {
|
||||
return "repeat";
|
||||
@@ -240,12 +307,12 @@ export const getActionType = (action: Action): ActionType => {
|
||||
}
|
||||
if ("service" in action) {
|
||||
if ("metadata" in action) {
|
||||
if (
|
||||
(action as ServiceAction).service === "scene.turn_on" &&
|
||||
!Array.isArray((action as ServiceAction)?.target?.entity_id)
|
||||
) {
|
||||
if (is(action, activateSceneActionStruct)) {
|
||||
return "activate_scene";
|
||||
}
|
||||
if (is(action, playMediaActionStruct)) {
|
||||
return "play_media";
|
||||
}
|
||||
}
|
||||
return "service";
|
||||
}
|
||||
|
@@ -9,10 +9,11 @@ import {
|
||||
ActionType,
|
||||
ActionTypes,
|
||||
DelayAction,
|
||||
DeviceAction,
|
||||
EventAction,
|
||||
getActionType,
|
||||
LegacySceneAction,
|
||||
ServiceSceneAction,
|
||||
PlayMediaAction,
|
||||
SceneAction,
|
||||
VariablesAction,
|
||||
WaitForTriggerAction,
|
||||
} from "./script";
|
||||
@@ -103,19 +104,32 @@ export const describeAction = <T extends ActionType>(
|
||||
return `Delay ${duration}`;
|
||||
}
|
||||
|
||||
if (actionType === "legacy_activate_scene") {
|
||||
const config = action as LegacySceneAction;
|
||||
const sceneStateObj = hass.states[config.scene];
|
||||
if (actionType === "activate_scene") {
|
||||
const config = action as SceneAction;
|
||||
let entityId: string | undefined;
|
||||
if ("scene" in config) {
|
||||
entityId = config.scene;
|
||||
} else {
|
||||
entityId = config.target?.entity_id || config.entity_id;
|
||||
}
|
||||
const sceneStateObj = entityId ? hass.states[entityId] : undefined;
|
||||
return `Activate scene ${
|
||||
sceneStateObj ? computeStateName(sceneStateObj) : config.scene
|
||||
sceneStateObj
|
||||
? computeStateName(sceneStateObj)
|
||||
: "scene" in config
|
||||
? config.scene
|
||||
: config.target?.entity_id || config.entity_id
|
||||
}`;
|
||||
}
|
||||
|
||||
if (actionType === "activate_scene") {
|
||||
const config = action as ServiceSceneAction;
|
||||
const sceneStateObj = hass.states[config.target!.entity_id as string];
|
||||
return `Activate scene ${
|
||||
sceneStateObj ? computeStateName(sceneStateObj) : config.target!.entity_id
|
||||
if (actionType === "play_media") {
|
||||
const config = action as PlayMediaAction;
|
||||
const entityId = config.target?.entity_id || config.entity_id;
|
||||
const mediaStateObj = entityId ? hass.states[entityId] : undefined;
|
||||
return `Play ${config.metadata.title || config.data.media_content_id} on ${
|
||||
mediaStateObj
|
||||
? computeStateName(mediaStateObj)
|
||||
: config.target?.entity_id || config.entity_id
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -147,5 +161,13 @@ export const describeAction = <T extends ActionType>(
|
||||
return `Test ${describeCondition(action as Condition)}`;
|
||||
}
|
||||
|
||||
if (actionType === "device_action") {
|
||||
const config = action as DeviceAction;
|
||||
const stateObj = hass.states[config.entity_id as string];
|
||||
return `${config.type || "Perform action with"} ${
|
||||
stateObj ? computeStateName(stateObj) : config.entity_id
|
||||
}`;
|
||||
}
|
||||
|
||||
return actionType;
|
||||
};
|
||||
|
@@ -13,7 +13,8 @@ export type Selector =
|
||||
| StringSelector
|
||||
| ObjectSelector
|
||||
| SelectSelector
|
||||
| IconSelector;
|
||||
| IconSelector
|
||||
| MediaSelector;
|
||||
|
||||
export interface EntitySelector {
|
||||
entity: {
|
||||
@@ -149,3 +150,21 @@ export interface IconSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
icon: {};
|
||||
}
|
||||
|
||||
export interface MediaSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
media: {};
|
||||
}
|
||||
|
||||
export interface MediaSelectorValue {
|
||||
entity_id?: string;
|
||||
media_content_id?: string;
|
||||
media_content_type?: string;
|
||||
metadata?: {
|
||||
title?: string;
|
||||
thumbnail?: string | null;
|
||||
media_class?: string;
|
||||
children_media_class?: string | null;
|
||||
navigateIds?: { media_content_type: string; media_content_id: string }[];
|
||||
};
|
||||
}
|
||||
|
@@ -1,10 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const removeTasmotaDeviceEntry = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "tasmota/device/remove",
|
||||
device_id: deviceId,
|
||||
});
|
@@ -2,6 +2,19 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { DeviceRegistryEntry } from "./device_registry";
|
||||
|
||||
export enum InclusionState {
|
||||
/** The controller isn't doing anything regarding inclusion. */
|
||||
Idle,
|
||||
/** The controller is waiting for a node to be included. */
|
||||
Including,
|
||||
/** The controller is waiting for a node to be excluded. */
|
||||
Excluding,
|
||||
/** The controller is busy including or excluding a node. */
|
||||
Busy,
|
||||
/** The controller listening for SmartStart nodes to announce themselves. */
|
||||
SmartStart,
|
||||
}
|
||||
|
||||
export const enum InclusionStrategy {
|
||||
/**
|
||||
* Always uses Security S2 if supported, otherwise uses Security S0 for certain devices which don't work without encryption and uses no encryption otherwise.
|
||||
@@ -106,16 +119,33 @@ export interface ZWaveJSNetwork {
|
||||
}
|
||||
|
||||
export interface ZWaveJSClient {
|
||||
state: string;
|
||||
state: "connected" | "disconnected";
|
||||
ws_server_url: string;
|
||||
server_version: string;
|
||||
driver_version: string;
|
||||
}
|
||||
|
||||
export interface ZWaveJSController {
|
||||
home_id: string;
|
||||
nodes: number[];
|
||||
home_id: number;
|
||||
library_version: string;
|
||||
type: number;
|
||||
own_node_id: number;
|
||||
is_secondary: boolean;
|
||||
is_using_home_id_from_other_network: boolean;
|
||||
is_sis_present: boolean;
|
||||
was_real_primary: boolean;
|
||||
is_static_update_controller: boolean;
|
||||
is_slave: boolean;
|
||||
serial_api_version: string;
|
||||
manufacturer_id: number;
|
||||
product_id: number;
|
||||
product_type: number;
|
||||
supported_function_types: number[];
|
||||
suc_node_id: number;
|
||||
supports_timers: boolean;
|
||||
is_heal_network_active: boolean;
|
||||
inclusion_state: InclusionState;
|
||||
nodes: number[];
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeStatus {
|
||||
@@ -309,6 +339,12 @@ export const stopZwaveInclusion = (hass: HomeAssistant, entry_id: string) =>
|
||||
entry_id,
|
||||
});
|
||||
|
||||
export const stopZwaveExclusion = (hass: HomeAssistant, entry_id: string) =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/stop_exclusion",
|
||||
entry_id,
|
||||
});
|
||||
|
||||
export const zwaveGrantSecurityClasses = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
|
@@ -47,6 +47,7 @@ class StepFlowForm extends LitElement {
|
||||
? html`<ha-alert alert-type="error">${this._errorMsg}</ha-alert>`
|
||||
: ""}
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${stepData}
|
||||
.disabled=${this._loading}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
|
@@ -5,7 +5,6 @@ import {
|
||||
mdiLoginVariant,
|
||||
mdiMusicNote,
|
||||
mdiPlayBoxMultiple,
|
||||
mdiSend,
|
||||
mdiVolumeHigh,
|
||||
mdiVolumeMinus,
|
||||
mdiVolumeOff,
|
||||
@@ -13,7 +12,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
@@ -22,7 +21,7 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-slider";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
||||
import {
|
||||
computeMediaControls,
|
||||
MediaPickedEvent,
|
||||
@@ -43,8 +42,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: MediaPlayerEntity;
|
||||
|
||||
@query("#ttsInput") private _ttsInput?: HTMLInputElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.stateObj) {
|
||||
return html``;
|
||||
@@ -75,13 +72,17 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
</div>
|
||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||
? html`
|
||||
<ha-icon-button
|
||||
<mwc-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.card.media_player.browse_media"
|
||||
)}
|
||||
.path=${mdiPlayBoxMultiple}
|
||||
@click=${this._showBrowseMedia}
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiPlayBoxMultiple}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -183,21 +184,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
supportsFeature(stateObj, SUPPORT_PLAY_MEDIA)
|
||||
? html`
|
||||
<div class="tts">
|
||||
<paper-input
|
||||
id="ttsInput"
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.label=${this.hass.localize(
|
||||
"ui.card.media_player.text_to_speak"
|
||||
)}
|
||||
@keydown=${this._ttsCheckForEnter}
|
||||
></paper-input>
|
||||
<ha-icon-button
|
||||
.path=${mdiSend}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
@click=${this._sendTTS}
|
||||
></ha-icon-button>
|
||||
Text to speech has moved to the media browser.
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@@ -207,14 +195,14 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
return css`
|
||||
ha-icon-button[action="turn_off"],
|
||||
ha-icon-button[action="turn_on"],
|
||||
ha-slider,
|
||||
#ttsInput {
|
||||
ha-slider {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--mdc-theme-primary: currentColor;
|
||||
}
|
||||
|
||||
.basic-controls {
|
||||
@@ -223,8 +211,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
|
||||
.volume,
|
||||
.source-input,
|
||||
.sound-input,
|
||||
.tts {
|
||||
.sound-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -241,6 +228,15 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
margin-left: 10px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tts {
|
||||
margin-top: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
mwc-button > ha-svg-icon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -295,32 +291,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _ttsCheckForEnter(e: KeyboardEvent) {
|
||||
if (e.keyCode === 13) this._sendTTS();
|
||||
}
|
||||
|
||||
private _sendTTS() {
|
||||
const ttsInput = this._ttsInput;
|
||||
if (!ttsInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const services = this.hass.services.tts;
|
||||
const serviceKeys = Object.keys(services).sort();
|
||||
|
||||
const service = serviceKeys.find((key) => key.indexOf("_say") !== -1);
|
||||
|
||||
if (!service) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callService("tts", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
message: ttsInput.value,
|
||||
});
|
||||
ttsInput.value = "";
|
||||
}
|
||||
|
||||
private _showBrowseMedia(): void {
|
||||
showMediaBrowserDialog(this, {
|
||||
action: "play",
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
|
||||
import "@material/mwc-select";
|
||||
import type { Select } from "@material/mwc-select";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -11,22 +11,23 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { Action, ServiceSceneAction } from "../../../../data/script";
|
||||
import { Action, getActionType } from "../../../../data/script";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./types/ha-automation-action-activate_scene";
|
||||
import "./types/ha-automation-action-choose";
|
||||
import "./types/ha-automation-action-condition";
|
||||
import "./types/ha-automation-action-delay";
|
||||
import "./types/ha-automation-action-device_id";
|
||||
import "./types/ha-automation-action-event";
|
||||
import "./types/ha-automation-action-play_media";
|
||||
import "./types/ha-automation-action-repeat";
|
||||
import "./types/ha-automation-action-scene";
|
||||
import "./types/ha-automation-action-service";
|
||||
import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
@@ -35,7 +36,8 @@ const OPTIONS = [
|
||||
"condition",
|
||||
"delay",
|
||||
"event",
|
||||
"scene",
|
||||
"play_media",
|
||||
"activate_scene",
|
||||
"service",
|
||||
"wait_template",
|
||||
"wait_for_trigger",
|
||||
@@ -48,21 +50,8 @@ const getType = (action: Action | undefined) => {
|
||||
if (!action) {
|
||||
return undefined;
|
||||
}
|
||||
if ("metadata" in action && action.service) {
|
||||
switch (action.service) {
|
||||
case "scene.turn_on":
|
||||
// we dont support arrays of entities
|
||||
if (
|
||||
!Array.isArray(
|
||||
(action as unknown as ServiceSceneAction).target?.entity_id
|
||||
)
|
||||
) {
|
||||
return "scene";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if ("service" in action || "scene" in action) {
|
||||
return getActionType(action);
|
||||
}
|
||||
return OPTIONS.find((option) => option in action);
|
||||
};
|
||||
@@ -133,24 +122,30 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
).sort((a, b) => stringCompare(a[1], b[1]))
|
||||
);
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
}
|
||||
this._uiModeAvailable = getType(this.action) !== undefined;
|
||||
if (!this._uiModeAvailable && !this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
}
|
||||
this._uiModeAvailable = Boolean(getType(this.action));
|
||||
if (!this._uiModeAvailable && !this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
}
|
||||
|
||||
const yamlEditor = this._yamlEditor;
|
||||
if (this._yamlMode && yamlEditor && yamlEditor.value !== this.action) {
|
||||
yamlEditor.setValue(this.action);
|
||||
if (this._yamlMode) {
|
||||
const yamlEditor = this._yamlEditor;
|
||||
if (yamlEditor && yamlEditor.value !== this.action) {
|
||||
yamlEditor.setValue(this.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const type = getType(this.action);
|
||||
const selected = type ? OPTIONS.indexOf(type) : -1;
|
||||
const yamlMode = this._yamlMode;
|
||||
|
||||
return html`
|
||||
@@ -225,7 +220,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
: ""}
|
||||
${yamlMode
|
||||
? html`
|
||||
${selected === -1
|
||||
${type === undefined
|
||||
? html`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.unsupported_action",
|
||||
|
@@ -9,7 +9,7 @@ import { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
const includeDomains = ["scene"];
|
||||
|
||||
@customElement("ha-automation-action-scene")
|
||||
@customElement("ha-automation-action-activate_scene")
|
||||
export class HaSceneAction extends LitElement implements ActionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -61,6 +61,6 @@ export class HaSceneAction extends LitElement implements ActionElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-action-scene": HaSceneAction;
|
||||
"ha-automation-action-activate_scene": HaSceneAction;
|
||||
}
|
||||
}
|
@@ -69,6 +69,7 @@ export class HaDeviceAction extends LitElement {
|
||||
${this._capabilities?.extra_fields
|
||||
? html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._extraFieldsData(this.action, this._capabilities)}
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||
|
@@ -0,0 +1,68 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-selector/ha-selector-media";
|
||||
import { PlayMediaAction } from "../../../../../data/script";
|
||||
import type { MediaSelectorValue } from "../../../../../data/selector";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-play_media")
|
||||
export class HaPlayMediaAction extends LitElement implements ActionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public action!: PlayMediaAction;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
public static get defaultConfig(): PlayMediaAction {
|
||||
return {
|
||||
service: "media_player.play_media",
|
||||
target: { entity_id: "" },
|
||||
data: { media_content_id: "", media_content_type: "" },
|
||||
metadata: {},
|
||||
};
|
||||
}
|
||||
|
||||
private _getSelectorValue = memoizeOne(
|
||||
(action: PlayMediaAction): MediaSelectorValue => ({
|
||||
entity_id: action.target?.entity_id || action.entity_id,
|
||||
media_content_id: action.data?.media_content_id,
|
||||
media_content_type: action.data?.media_content_type,
|
||||
metadata: action.metadata,
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-selector-media
|
||||
.hass=${this.hass}
|
||||
.value=${this._getSelectorValue(this.action)}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector-media>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent<{ value: MediaSelectorValue }>) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
service: "media_player.play_media",
|
||||
target: { entity_id: ev.detail.value.entity_id },
|
||||
data: {
|
||||
media_content_id: ev.detail.value.media_content_id,
|
||||
media_content_type: ev.detail.value.media_content_type,
|
||||
},
|
||||
metadata: ev.detail.value.metadata || {},
|
||||
} as PlayMediaAction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-action-play_media": HaPlayMediaAction;
|
||||
}
|
||||
}
|
@@ -30,7 +30,7 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
return { service: "", data: {} };
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import memoizeOne from "memoize-one";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { Condition } from "../../../../data/automation";
|
||||
|
@@ -27,7 +27,7 @@ export const handleChangeEvent = (
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const newVal = ev.detail.value;
|
||||
const newVal = ev.detail?.value || (ev.currentTarget as any)?.value;
|
||||
|
||||
if ((element.condition[name] || "") === newVal) {
|
||||
return;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
DeviceCondition,
|
||||
fetchDeviceConditionCapabilities,
|
||||
} from "../../../../../data/device_automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("ha-automation-condition-device")
|
||||
export class HaDeviceCondition extends LitElement {
|
||||
@@ -69,6 +69,7 @@ export class HaDeviceCondition extends LitElement {
|
||||
${this._capabilities?.extra_fields
|
||||
? html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._extraFieldsData(this.condition, this._capabilities)}
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||
@@ -147,6 +148,13 @@ export class HaDeviceCondition extends LitElement {
|
||||
`ui.panel.config.automation.editor.conditions.type.device.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,17 +1,20 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { Condition, LogicalCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import type {
|
||||
Condition,
|
||||
LogicalCondition,
|
||||
} from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-condition";
|
||||
import { ConditionElement } from "../ha-automation-condition-row";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
import { HaStateCondition } from "./ha-automation-condition-state";
|
||||
|
||||
@customElement("ha-automation-condition-logical")
|
||||
export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public condition!: LogicalCondition;
|
||||
@property({ attribute: false }) public condition!: LogicalCondition;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
|
@@ -1,17 +1,17 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import { NumericStateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-condition-row";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("ha-automation-condition-numeric_state")
|
||||
export default class HaNumericStateCondition extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public condition!: NumericStateCondition;
|
||||
@property({ attribute: false }) public condition!: NumericStateCondition;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
@@ -19,60 +19,54 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
private _schema = memoizeOne((entityId): HaFormSchema[] => [
|
||||
{ name: "entity_id", required: true, selector: { entity: {} } },
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entityId } },
|
||||
},
|
||||
{ name: "above", selector: { text: {} } },
|
||||
{ name: "below", selector: { text: {} } },
|
||||
{
|
||||
name: "value_template",
|
||||
selector: { text: { multiline: true } },
|
||||
},
|
||||
]);
|
||||
|
||||
public render() {
|
||||
const { value_template, entity_id, attribute, below, above } =
|
||||
this.condition;
|
||||
const schema = this._schema(this.condition.entity_id);
|
||||
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
.value=${entity_id}
|
||||
.name=${"entity_id"}
|
||||
@value-changed=${this._valueChanged}
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<ha-entity-attribute-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${entity_id}
|
||||
.value=${attribute}
|
||||
.name=${"attribute"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.attribute"
|
||||
)}
|
||||
.data=${this.condition}
|
||||
.schema=${schema}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-value
|
||||
></ha-entity-attribute-picker>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.above"
|
||||
)}
|
||||
name="above"
|
||||
.value=${above}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.below"
|
||||
)}
|
||||
name="below"
|
||||
.value=${below}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.value_template"
|
||||
)}
|
||||
name="value_template"
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
ev.stopPropagation();
|
||||
const newTrigger = ev.detail.value;
|
||||
fireEvent(this, "value-changed", { value: newTrigger });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string => {
|
||||
switch (schema.name) {
|
||||
case "entity_id":
|
||||
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||
case "attribute":
|
||||
return this.hass.localize(
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
);
|
||||
default:
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.numeric_state.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,19 +1,14 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { assert, literal, object, optional, string, union } from "superstruct";
|
||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-attribute-picker";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../../components/ha-duration-input";
|
||||
import { StateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import type { StateCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { forDictStruct } from "../../structs";
|
||||
import {
|
||||
ConditionElement,
|
||||
handleChangeEvent,
|
||||
} from "../ha-automation-condition-row";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
|
||||
const stateConditionStruct = object({
|
||||
condition: literal("state"),
|
||||
@@ -27,12 +22,22 @@ const stateConditionStruct = object({
|
||||
export class HaStateCondition extends LitElement implements ConditionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public condition!: StateCondition;
|
||||
@property({ attribute: false }) public condition!: StateCondition;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { entity_id: "", state: "" };
|
||||
}
|
||||
|
||||
private _schema = memoizeOne((entityId) => [
|
||||
{ name: "entity_id", required: true, selector: { entity: {} } },
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entityId } },
|
||||
},
|
||||
{ name: "state", selector: { text: {} } },
|
||||
{ name: "for", selector: { duration: {} } },
|
||||
]);
|
||||
|
||||
public shouldUpdate(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("condition")) {
|
||||
try {
|
||||
@@ -46,50 +51,52 @@ export class HaStateCondition extends LitElement implements ConditionElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { entity_id, attribute, state } = this.condition;
|
||||
const forTime = createDurationData(this.condition.for);
|
||||
const trgFor = createDurationData(this.condition.for);
|
||||
const data = { ...this.condition, ...{ for: trgFor } };
|
||||
const schema = this._schema(this.condition.entity_id);
|
||||
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
.value=${entity_id}
|
||||
.name=${"entity_id"}
|
||||
@value-changed=${this._valueChanged}
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<ha-entity-attribute-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${entity_id}
|
||||
.value=${attribute}
|
||||
.name=${"attribute"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.attribute"
|
||||
)}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-value
|
||||
></ha-entity-attribute-picker>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.state.state"
|
||||
)}
|
||||
.name=${"state"}
|
||||
.value=${state}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<ha-duration-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.for"
|
||||
)}
|
||||
.name=${"for"}
|
||||
.data=${forTime}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-duration-input>
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
ev.stopPropagation();
|
||||
const newTrigger = ev.detail.value;
|
||||
|
||||
Object.keys(newTrigger).forEach((key) =>
|
||||
newTrigger[key] === undefined || newTrigger[key] === ""
|
||||
? delete newTrigger[key]
|
||||
: {}
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", { value: newTrigger });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string => {
|
||||
switch (schema.name) {
|
||||
case "entity_id":
|
||||
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||
case "attribute":
|
||||
return this.hass.localize(
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
);
|
||||
case "for":
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.state.for`
|
||||
);
|
||||
default:
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.state.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,16 +1,12 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { SunCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
ConditionElement,
|
||||
handleChangeEvent,
|
||||
} from "../ha-automation-condition-row";
|
||||
import "../../../../../components/ha-radio";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
|
||||
@customElement("ha-automation-condition-sun")
|
||||
export class HaSunCondition extends LitElement implements ConditionElement {
|
||||
@@ -22,111 +18,72 @@ export class HaSunCondition extends LitElement implements ConditionElement {
|
||||
return {};
|
||||
}
|
||||
|
||||
private _schema = memoizeOne((localize: LocalizeFunc) => [
|
||||
{
|
||||
name: "before",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
[
|
||||
"sunrise",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||
),
|
||||
],
|
||||
[
|
||||
"sunset",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
{ name: "before_offset", selector: { text: {} } },
|
||||
{
|
||||
name: "after",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
[
|
||||
"sunrise",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||
),
|
||||
],
|
||||
[
|
||||
"sunset",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
{ name: "after_offset", selector: { text: {} } },
|
||||
]);
|
||||
|
||||
protected render() {
|
||||
const { after, after_offset, before, before_offset } = this.condition;
|
||||
const schema = this._schema(this.hass.localize);
|
||||
return html`
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.before"
|
||||
)}
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
name="before"
|
||||
value="sunrise"
|
||||
.checked=${before === "sunrise"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
name="before"
|
||||
value="sunset"
|
||||
.checked=${before === "sunset"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.before_offset"
|
||||
)}
|
||||
name="before_offset"
|
||||
.value=${before_offset}
|
||||
<ha-form
|
||||
.schema=${schema}
|
||||
.data=${this.condition}
|
||||
.hass=${this.hass}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.after"
|
||||
)}
|
||||
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
name="after"
|
||||
value="sunrise"
|
||||
.checked=${after === "sunrise"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
name="after"
|
||||
value="sunset"
|
||||
.checked=${after === "sunset"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.after_offset"
|
||||
)}
|
||||
name="after_offset"
|
||||
.value=${after_offset}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
private _radioGroupPicked(ev: CustomEvent) {
|
||||
const key = (ev.target as HaRadio).name;
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.condition,
|
||||
[key]: (ev.target as HaRadio).value,
|
||||
},
|
||||
});
|
||||
const newTrigger = ev.detail.value;
|
||||
fireEvent(this, "value-changed", { value: newTrigger });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.sun.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html, LitElement } from "lit";
|
||||
import "../../../../../components/ha-textarea";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { TemplateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import type { TemplateCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-condition-row";
|
||||
|
||||
@customElement("ha-automation-condition-template")
|
||||
export class HaTemplateCondition extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public condition!: TemplateCondition;
|
||||
@property({ attribute: false }) public condition!: TemplateCondition;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { value_template: "" };
|
||||
@@ -18,19 +18,32 @@ export class HaTemplateCondition extends LitElement {
|
||||
protected render() {
|
||||
const { value_template } = this.condition;
|
||||
return html`
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
name="value_template"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.template.value_template"
|
||||
)}
|
||||
name="value_template"
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
autogrow
|
||||
></ha-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-textarea {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-condition-template": HaTemplateCondition;
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,12 @@
|
||||
import { Radio } from "@material/mwc-radio";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../../../../common/util/compute_rtl";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-radio";
|
||||
import { HaSwitch } from "../../../../../components/ha-switch";
|
||||
import { TimeCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
ConditionElement,
|
||||
handleChangeEvent,
|
||||
} from "../ha-automation-condition-row";
|
||||
import "../../../../../components/ha-time-input";
|
||||
|
||||
const includeDomains = ["input_datetime"];
|
||||
import type { TimeCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
|
||||
const DAYS = {
|
||||
mon: 1,
|
||||
@@ -26,10 +18,6 @@ const DAYS = {
|
||||
sun: 7,
|
||||
};
|
||||
|
||||
interface WeekdayHaSwitch extends HaSwitch {
|
||||
day: string;
|
||||
}
|
||||
|
||||
@customElement("ha-automation-condition-time")
|
||||
export class HaTimeCondition extends LitElement implements ConditionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -44,176 +32,136 @@ export class HaTimeCondition extends LitElement implements ConditionElement {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { after, before, weekday } = this.condition;
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
inputModeAfter?: boolean,
|
||||
inputModeBefore?: boolean
|
||||
): HaFormSchema[] => {
|
||||
const modeAfterSchema = inputModeAfter
|
||||
? { name: "after", selector: { entity: { domain: "input_datetime" } } }
|
||||
: { name: "after", selector: { time: {} } };
|
||||
|
||||
const modeBeforeSchema = inputModeBefore
|
||||
? { name: "before", selector: { entity: { domain: "input_datetime" } } }
|
||||
: { name: "before", selector: { time: {} } };
|
||||
|
||||
return [
|
||||
{
|
||||
name: "mode_after",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
[
|
||||
"value",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_value"
|
||||
),
|
||||
],
|
||||
[
|
||||
"input",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_input"
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
modeAfterSchema,
|
||||
{
|
||||
name: "mode_before",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
[
|
||||
"value",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_value"
|
||||
),
|
||||
],
|
||||
[
|
||||
"input",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_input"
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
modeBeforeSchema,
|
||||
{
|
||||
type: "multi_select",
|
||||
name: "weekday",
|
||||
options: Object.keys(DAYS).map((day) => [
|
||||
day,
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.time.weekdays.${day}`
|
||||
),
|
||||
]),
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const inputModeBefore =
|
||||
this._inputModeBefore ?? before?.startsWith("input_datetime.");
|
||||
this._inputModeBefore ??
|
||||
this.condition.before?.startsWith("input_datetime.");
|
||||
const inputModeAfter =
|
||||
this._inputModeAfter ?? after?.startsWith("input_datetime.");
|
||||
this._inputModeAfter ??
|
||||
this.condition.after?.startsWith("input_datetime.");
|
||||
|
||||
const schema: HaFormSchema[] = this._schema(
|
||||
this.hass.localize,
|
||||
inputModeAfter,
|
||||
inputModeBefore
|
||||
);
|
||||
|
||||
const data = {
|
||||
mode_before: "value",
|
||||
mode_after: "value",
|
||||
...this.condition,
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_value"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_after"
|
||||
value="value"
|
||||
?checked=${!inputModeAfter}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_input"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_after"
|
||||
value="input"
|
||||
?checked=${inputModeAfter}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
${inputModeAfter
|
||||
? html`<ha-entity-picker
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.after"
|
||||
)}
|
||||
.includeDomains=${includeDomains}
|
||||
.name=${"after"}
|
||||
.value=${after?.startsWith("input_datetime.") ? after : ""}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`
|
||||
: html`<ha-time-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.after"
|
||||
)}
|
||||
.locale=${this.hass.locale}
|
||||
.name=${"after"}
|
||||
.value=${after?.startsWith("input_datetime.") ? "" : after}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>`}
|
||||
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_value"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_before"
|
||||
value="value"
|
||||
?checked=${!inputModeBefore}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_input"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_before"
|
||||
value="input"
|
||||
?checked=${inputModeBefore}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
${inputModeBefore
|
||||
? html`<ha-entity-picker
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.before"
|
||||
)}
|
||||
.includeDomains=${includeDomains}
|
||||
.name=${"before"}
|
||||
.value=${before?.startsWith("input_datetime.") ? before : ""}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`
|
||||
: html`<ha-time-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.before"
|
||||
)}
|
||||
.name=${"before"}
|
||||
.locale=${this.hass.locale}
|
||||
.value=${before?.startsWith("input_datetime.") ? "" : before}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>`}
|
||||
${Object.keys(DAYS).map(
|
||||
(day) => html`
|
||||
<ha-formfield
|
||||
alignEnd
|
||||
spaceBetween
|
||||
class="weekday-toggle"
|
||||
.label=${this.hass!.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.time.weekdays.${day}`
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass!)}
|
||||
>
|
||||
<ha-switch
|
||||
.day=${day}
|
||||
.checked=${!weekday || weekday === day || weekday.includes(day)}
|
||||
@change=${this._dayValueChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
`
|
||||
)}
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleModeChanged(ev: Event) {
|
||||
const target = ev.target as Radio;
|
||||
if (target.getAttribute("name") === "mode_after") {
|
||||
this._inputModeAfter = target.value === "input";
|
||||
} else {
|
||||
this._inputModeBefore = target.value === "input";
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
private _dayValueChanged(ev: CustomEvent): void {
|
||||
const daySwitch = ev.currentTarget as WeekdayHaSwitch;
|
||||
const newModeAfter = newValue.mode_after === "input";
|
||||
const newModeBefore = newValue.mode_before === "input";
|
||||
|
||||
let days: string[];
|
||||
|
||||
if (!this.condition.weekday) {
|
||||
days = Object.keys(DAYS);
|
||||
} else {
|
||||
days = !Array.isArray(this.condition.weekday)
|
||||
? [this.condition.weekday]
|
||||
: this.condition.weekday;
|
||||
if (newModeAfter !== this._inputModeAfter) {
|
||||
this._inputModeAfter = newModeAfter;
|
||||
newValue.after = undefined;
|
||||
}
|
||||
|
||||
if (daySwitch.checked) {
|
||||
days.push(daySwitch.day);
|
||||
} else {
|
||||
days = days.filter((d) => d !== daySwitch.day);
|
||||
if (newModeBefore !== this._inputModeBefore) {
|
||||
this._inputModeBefore = newModeBefore;
|
||||
newValue.before = undefined;
|
||||
}
|
||||
|
||||
days.sort((a: string, b: string) => DAYS[a] - DAYS[b]);
|
||||
Object.keys(newValue).forEach((key) =>
|
||||
newValue[key] === undefined || newValue[key] === ""
|
||||
? delete newValue[key]
|
||||
: {}
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.condition, weekday: days },
|
||||
});
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.weekday-toggle {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.time.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -5,12 +5,12 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../../../../common/ensure-array";
|
||||
import {
|
||||
import type {
|
||||
AutomationConfig,
|
||||
Trigger,
|
||||
TriggerCondition,
|
||||
} from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("ha-automation-condition-trigger")
|
||||
export class HaTriggerCondition extends LitElement {
|
||||
@@ -18,7 +18,7 @@ export class HaTriggerCondition extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public condition!: TriggerCondition;
|
||||
|
||||
@state() private _triggers?: Trigger | Trigger[];
|
||||
@state() private _triggers: Trigger[] = [];
|
||||
|
||||
private _unsub?: UnsubscribeFunc;
|
||||
|
||||
@@ -44,7 +44,8 @@ export class HaTriggerCondition extends LitElement {
|
||||
|
||||
protected render() {
|
||||
const { id } = this.condition;
|
||||
if (!this._triggers) {
|
||||
|
||||
if (!this._triggers.length) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.trigger.no_triggers"
|
||||
);
|
||||
@@ -56,20 +57,19 @@ export class HaTriggerCondition extends LitElement {
|
||||
.value=${id}
|
||||
@selected=${this._triggerPicked}
|
||||
>
|
||||
${ensureArray(this._triggers).map((trigger) =>
|
||||
trigger.id
|
||||
? html`
|
||||
<mwc-list-item .value=${trigger.id}>
|
||||
${trigger.id}
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""
|
||||
${this._triggers.map(
|
||||
(trigger) =>
|
||||
html`
|
||||
<mwc-list-item .value=${trigger.id}> ${trigger.id} </mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>`;
|
||||
}
|
||||
|
||||
private _automationUpdated(config?: AutomationConfig) {
|
||||
this._triggers = config?.trigger;
|
||||
this._triggers = config?.trigger
|
||||
? ensureArray(config.trigger).filter((t) => t.id)
|
||||
: [];
|
||||
}
|
||||
|
||||
private _triggerPicked(ev) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||
@@ -71,6 +71,13 @@ export class HaZoneCondition extends LitElement {
|
||||
value: { ...this.condition, zone: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-entity-picker {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -69,6 +69,7 @@ export class HaDeviceTrigger extends LitElement {
|
||||
${this._capabilities?.extra_fields
|
||||
? html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._extraFieldsData(this.trigger, this._capabilities)}
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||
@@ -154,7 +155,7 @@ export class HaDeviceTrigger extends LitElement {
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -16,19 +16,18 @@ export class HaNumericStateTrigger extends LitElement {
|
||||
@property() public trigger!: NumericStateTrigger;
|
||||
|
||||
private _schema = memoizeOne((entityId): HaFormSchema[] => [
|
||||
{ name: "entity_id", selector: { entity: {} } },
|
||||
{ name: "entity_id", required: true, selector: { entity: {} } },
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entityId } },
|
||||
},
|
||||
{ name: "above", required: false, selector: { text: {} } },
|
||||
{ name: "below", required: false, selector: { text: {} } },
|
||||
{ name: "above", selector: { text: {} } },
|
||||
{ name: "below", selector: { text: {} } },
|
||||
{
|
||||
name: "value_template",
|
||||
required: false,
|
||||
selector: { text: { multiline: true } },
|
||||
},
|
||||
{ name: "for", required: false, selector: { duration: {} } },
|
||||
{ name: "for", selector: { duration: {} } },
|
||||
]);
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
|
@@ -32,14 +32,6 @@ const stateTriggerStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const SCHEMA = [
|
||||
{ name: "entity_id", selector: { entity: {} } },
|
||||
{ name: "attribute", selector: { attribute: { entity_id: "" } } },
|
||||
{ name: "from", required: false, selector: { text: {} } },
|
||||
{ name: "to", required: false, selector: { text: {} } },
|
||||
{ name: "for", required: false, selector: { duration: {} } },
|
||||
];
|
||||
|
||||
@customElement("ha-automation-trigger-state")
|
||||
export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -50,14 +42,16 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
return { entity_id: "" };
|
||||
}
|
||||
|
||||
private _schema = memoizeOne((entityId) => {
|
||||
const schema = [...SCHEMA];
|
||||
schema[1] = {
|
||||
private _schema = memoizeOne((entityId) => [
|
||||
{ name: "entity_id", required: true, selector: { entity: {} } },
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entityId } },
|
||||
};
|
||||
return schema;
|
||||
});
|
||||
},
|
||||
{ name: "from", selector: { text: {} } },
|
||||
{ name: "to", selector: { text: {} } },
|
||||
{ name: "for", selector: { duration: {} } },
|
||||
]);
|
||||
|
||||
public shouldUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("trigger")) {
|
||||
@@ -118,13 +112,12 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
fireEvent(this, "value-changed", { value: newTrigger });
|
||||
}
|
||||
|
||||
private _computeLabelCallback(schema: HaFormSchema): string {
|
||||
return this.hass.localize(
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
schema.name === "entity_id"
|
||||
? "ui.components.entity.entity-picker.entity"
|
||||
: `ui.panel.config.automation.editor.triggers.type.state.${schema.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -45,9 +45,10 @@ export class HaSunTrigger extends LitElement implements TriggerElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const schema = this._schema(this.hass.localize);
|
||||
return html`
|
||||
<ha-form
|
||||
.schema=${this._schema(this.hass.localize)}
|
||||
.schema=${schema}
|
||||
.data=${this.trigger}
|
||||
.hass=${this.hass}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html, LitElement } from "lit";
|
||||
import "../../../../../components/ha-textarea";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { TemplateTrigger } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import type { TemplateTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
|
||||
@customElement("ha-automation-trigger-template")
|
||||
export class HaTemplateTrigger extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trigger!: TemplateTrigger;
|
||||
@property({ attribute: false }) public trigger!: TemplateTrigger;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { value_template: "" };
|
||||
@@ -18,19 +18,32 @@ export class HaTemplateTrigger extends LitElement {
|
||||
protected render() {
|
||||
const { value_template } = this.trigger;
|
||||
return html`
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
name="value_template"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.template.value_template"
|
||||
)}
|
||||
name="value_template"
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
autogrow
|
||||
></ha-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-textarea {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-trigger-template": HaTemplateTrigger;
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +1,19 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-radio";
|
||||
import { TimeTrigger } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
handleChangeEvent,
|
||||
TriggerElement,
|
||||
} from "../ha-automation-trigger-row";
|
||||
import "../../../../../components/ha-time-input";
|
||||
import type { TimeTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { TriggerElement } from "../ha-automation-trigger-row";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
|
||||
const includeDomains = ["input_datetime"];
|
||||
@customElement("ha-automation-trigger-time")
|
||||
export class HaTimeTrigger extends LitElement implements TriggerElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trigger!: TimeTrigger;
|
||||
@property({ attribute: false }) public trigger!: TimeTrigger;
|
||||
|
||||
@state() private _inputMode?: boolean;
|
||||
|
||||
@@ -25,6 +21,37 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
|
||||
return { at: "" };
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc, inputMode?: boolean): HaFormSchema[] => {
|
||||
const modeSchema = inputMode
|
||||
? { name: "at", selector: { entity: { domain: "input_datetime" } } }
|
||||
: { name: "at", selector: { time: {} } };
|
||||
|
||||
return [
|
||||
{
|
||||
name: "mode",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
[
|
||||
"value",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.time.type_value"
|
||||
),
|
||||
],
|
||||
[
|
||||
"input",
|
||||
localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.time.type_input"
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
modeSchema,
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("trigger")) {
|
||||
return;
|
||||
@@ -50,67 +77,43 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
|
||||
this._inputMode ??
|
||||
(at?.startsWith("input_datetime.") || at?.startsWith("sensor."));
|
||||
|
||||
return html`<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.time.type_value"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode"
|
||||
value="value"
|
||||
?checked=${!inputMode}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.time.type_input"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode"
|
||||
value="input"
|
||||
?checked=${inputMode}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
const schema: HaFormSchema[] = this._schema(this.hass.localize, inputMode);
|
||||
|
||||
${inputMode
|
||||
? html`<ha-entity-picker
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.time.at"
|
||||
)}
|
||||
.includeDomains=${includeDomains}
|
||||
.name=${"at"}
|
||||
.value=${at?.startsWith("input_datetime.") ||
|
||||
at?.startsWith("sensor.")
|
||||
? at
|
||||
: ""}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`
|
||||
: html`<ha-time-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.time.at"
|
||||
)}
|
||||
.name=${"at"}
|
||||
.value=${at?.startsWith("input_datetime.") ||
|
||||
at?.startsWith("sensor.")
|
||||
? ""
|
||||
: at}
|
||||
.locale=${this.hass.locale}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>`} `;
|
||||
}
|
||||
const data = {
|
||||
mode: "value",
|
||||
...this.trigger,
|
||||
};
|
||||
|
||||
private _handleModeChanged(ev: Event) {
|
||||
this._inputMode = (ev.target as any).value === "input";
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
this._inputMode = newValue.mode.value === "input";
|
||||
|
||||
Object.keys(newValue).forEach((key) =>
|
||||
newValue[key] === undefined || newValue[key] === ""
|
||||
? delete newValue[key]
|
||||
: {}
|
||||
);
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.time.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||
import { hasLocation } from "../../../../../common/entity/has_location";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import type { ZoneTrigger } from "../../../../../data/automation";
|
||||
import type { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-radio";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
|
||||
function zoneAndLocationFilter(stateObj) {
|
||||
@@ -116,6 +115,10 @@ export class HaZoneTrigger extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-entity-picker {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -10,8 +10,10 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/buttons/ha-call-api-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import { debounce } from "../../../../common/util/debounce";
|
||||
import {
|
||||
cloudLogout,
|
||||
CloudStatusLoggedIn,
|
||||
@@ -105,6 +107,17 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
</paper-item-body>
|
||||
</div>
|
||||
|
||||
${this.cloudStatus.cloud === "connecting" &&
|
||||
this.cloudStatus.cloud_last_disconnect_reason
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.cloudStatus.cloud_last_disconnect_reason
|
||||
.reason}
|
||||
></ha-alert>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="account-row">
|
||||
<paper-item-body>
|
||||
${this.hass.localize(
|
||||
@@ -219,11 +232,15 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected override hassSubscribe() {
|
||||
const googleCheck = () => {
|
||||
if (!this.cloudStatus?.google_registered) {
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
};
|
||||
const googleCheck = debounce(
|
||||
() => {
|
||||
if (this.cloudStatus && !this.cloudStatus.google_registered) {
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
},
|
||||
10000,
|
||||
true
|
||||
);
|
||||
return [
|
||||
this.hass.connection.subscribeEvents(() => {
|
||||
if (!this.cloudStatus?.alexa_registered) {
|
||||
|
@@ -192,8 +192,11 @@ class HaConfigDashboard extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card:last-child {
|
||||
margin-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
:host(:not([narrow])) ha-card:last-child {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: max(24px, env(safe-area-inset-bottom));
|
||||
}
|
||||
ha-config-section {
|
||||
margin: auto;
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { removeMQTTDeviceEntry } from "../../../../../../data/mqtt";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
@@ -16,24 +14,9 @@ export class HaDeviceActionsMqtt extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-button @click=${this._showDebugInfo}> MQTT Info </mwc-button>
|
||||
<mwc-button class="warning" @click=${this._confirmDeleteEntry}>
|
||||
${this.hass.localize("ui.panel.config.devices.delete")}
|
||||
</mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _confirmDeleteEntry(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeMQTTDeviceEntry(this.hass!, this.device.id);
|
||||
}
|
||||
|
||||
private async _showDebugInfo(): Promise<void> {
|
||||
const device = this.device;
|
||||
await showMQTTDeviceDebugInfoDialog(this, { device });
|
||||
|
@@ -1,46 +0,0 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { removeTasmotaDeviceEntry } from "../../../../../../data/tasmota";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
@customElement("ha-device-actions-tasmota")
|
||||
export class HaDeviceActionsTasmota extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-button class="warning" @click=${this._confirmDeleteEntry}>
|
||||
${this.hass.localize("ui.panel.config.devices.delete")}
|
||||
</mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _confirmDeleteEntry(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeTasmotaDeviceEntry(this.hass!, this.device.id);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -860,17 +860,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
></ha-device-actions-ozw>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("tasmota")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/tasmota/ha-device-actions-tasmota"
|
||||
);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-tasmota
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-tasmota>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("zha")) {
|
||||
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
|
||||
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
||||
|
@@ -74,6 +74,7 @@ export class DialogEnergyBatterySettings
|
||||
"ui.panel.config.energy.battery.dialog.energy_into_battery"
|
||||
)}
|
||||
@value-changed=${this._statisticToChanged}
|
||||
dialogInitialFocus
|
||||
></ha-statistic-picker>
|
||||
|
||||
<ha-statistic-picker
|
||||
|
@@ -75,6 +75,7 @@ export class DialogEnergyDeviceSettings
|
||||
"ui.panel.config.energy.device_consumption.dialog.device_consumption_energy"
|
||||
)}
|
||||
@value-changed=${this._statisticChanged}
|
||||
dialogInitialFocus
|
||||
></ha-statistic-picker>
|
||||
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
|
@@ -107,6 +107,7 @@ export class DialogEnergyGasSettings
|
||||
: "m³"
|
||||
})`}
|
||||
@value-changed=${this._statisticChanged}
|
||||
dialogInitialFocus
|
||||
></ha-statistic-picker>
|
||||
|
||||
<p>
|
||||
|
@@ -104,6 +104,7 @@ export class DialogEnergyGridFlowSettings
|
||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.energy_stat`
|
||||
)}
|
||||
@value-changed=${this._statisticChanged}
|
||||
dialogInitialFocus
|
||||
></ha-statistic-picker>
|
||||
|
||||
<p>
|
||||
|
@@ -86,6 +86,7 @@ export class DialogEnergySolarSettings
|
||||
"ui.panel.config.energy.solar.dialog.solar_production_energy"
|
||||
)}
|
||||
@value-changed=${this._statisticChanged}
|
||||
dialogInitialFocus
|
||||
></ha-statistic-picker>
|
||||
|
||||
<h3>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
} from "../../../data/entity_registry";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-registry-basic-editor")
|
||||
@@ -123,16 +122,16 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
computeDomain(this.entry.entity_id);
|
||||
|
||||
return html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
error-message="Domain needs to stay the same"
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.entity_id"
|
||||
)}
|
||||
error-message="Domain needs to stay the same"
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
@input=${this._entityIdChanged}
|
||||
></ha-textfield>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
@@ -177,8 +176,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
this._areaId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._entityId = ev.detail.value;
|
||||
private _entityIdChanged(ev): void {
|
||||
this._entityId = ev.target.value;
|
||||
}
|
||||
|
||||
private _disabledByChanged(ev: Event): void {
|
||||
@@ -199,6 +198,10 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "../../../components/ha-alert";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -19,6 +19,7 @@ import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
@@ -36,7 +37,6 @@ import {
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
||||
@@ -137,15 +137,18 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="form container">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
@value-changed=${this._nameChanged}
|
||||
.label=${this.hass.localize("ui.dialogs.entity_registry.editor.name")}
|
||||
.placeholder=${this.entry.original_name}
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
.placeholder=${this.entry.original_name}
|
||||
@input=${this._nameChanged}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
@value-changed=${this._iconChanged}
|
||||
@@ -176,16 +179,16 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</mwc-select>`
|
||||
: ""}
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
error-message="Domain needs to stay the same"
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.entity_id"
|
||||
)}
|
||||
error-message="Domain needs to stay the same"
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
@input=${this._entityIdChanged}
|
||||
></ha-textfield>
|
||||
${!this.entry.device_id
|
||||
? html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
@@ -282,19 +285,19 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _nameChanged(ev: PolymerChangedEvent<string>): void {
|
||||
private _nameChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._name = ev.detail.value;
|
||||
this._name = ev.target.value;
|
||||
}
|
||||
|
||||
private _iconChanged(ev: PolymerChangedEvent<string>): void {
|
||||
private _iconChanged(ev: CustomEvent): void {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
private _entityIdChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._entityId = ev.detail.value;
|
||||
this._entityId = ev.target.value;
|
||||
}
|
||||
|
||||
private _deviceClassChanged(ev): void {
|
||||
@@ -423,6 +426,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
ha-switch {
|
||||
margin-right: 16px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.row {
|
||||
margin: 8px 0;
|
||||
color: var(--primary-text-color);
|
||||
|
@@ -116,36 +116,35 @@ export class DialogHelperDetail extends LitElement {
|
||||
${Object.keys(HELPERS).map((platform: string) => {
|
||||
const isLoaded = isComponentLoaded(this.hass, platform);
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-icon-item
|
||||
.disabled=${!isLoaded}
|
||||
@click=${this._platformPicked}
|
||||
@keydown=${this._handleEnter}
|
||||
.platform=${platform}
|
||||
dialogInitialFocus
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${domainIcon(platform)}
|
||||
></ha-svg-icon>
|
||||
<span class="item-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${platform}`
|
||||
) || platform}
|
||||
</span>
|
||||
</paper-icon-item>
|
||||
${!isLoaded
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
"platform",
|
||||
platform
|
||||
)}</paper-tooltip
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<mwc-list-item
|
||||
.disabled=${!isLoaded}
|
||||
.platform=${platform}
|
||||
@click=${this._platformPicked}
|
||||
@keydown=${this._handleEnter}
|
||||
dialogInitialFocus
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${domainIcon(platform)}
|
||||
></ha-svg-icon>
|
||||
<span class="item-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${platform}`
|
||||
) || platform}
|
||||
</span>
|
||||
</mwc-list-item>
|
||||
${!isLoaded
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
"platform",
|
||||
platform
|
||||
)}</paper-tooltip
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
})}
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
@@ -208,9 +207,6 @@ export class DialogHelperDetail extends LitElement {
|
||||
ha-dialog.button-left {
|
||||
--justify-action-buttons: flex-start;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import { Counter } from "../../../../data/counter";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -68,10 +68,10 @@ class HaCounterForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -80,7 +80,7 @@ class HaCounterForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -89,44 +89,44 @@ class HaCounterForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-picker>
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._minimum}
|
||||
.configValue=${"minimum"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.minimum"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._maximum}
|
||||
.configValue=${"maximum"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.maximum"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.initial"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._step}
|
||||
.configValue=${"step"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.step"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${this._restore}
|
||||
@@ -155,12 +155,12 @@ class HaCounterForm extends LitElement {
|
||||
const configValue = target.configValue;
|
||||
const value =
|
||||
target.type === "number"
|
||||
? ev.detail.value !== ""
|
||||
? Number(ev.detail.value)
|
||||
? target.value !== ""
|
||||
? Number(target.value)
|
||||
: undefined
|
||||
: target.localName === "ha-switch"
|
||||
? (ev.target as HaSwitch).checked
|
||||
: ev.detail.value;
|
||||
: ev.detail?.value || target.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -192,6 +192,10 @@ class HaCounterForm extends LitElement {
|
||||
.row div {
|
||||
margin-left: 16px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { InputBoolean } from "../../../../data/input_boolean";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -46,10 +46,10 @@ class HaInputBooleanForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -58,7 +58,7 @@ class HaInputBooleanForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -77,7 +77,7 @@ class HaInputBooleanForm extends LitElement {
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value || (ev.target as any).value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class HaInputBooleanForm extends LitElement {
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -102,6 +102,10 @@ class HaInputBooleanForm extends LitElement {
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { InputButton } from "../../../../data/input_button";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -46,10 +46,10 @@ class HaInputButtonForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -58,7 +58,7 @@ class HaInputButtonForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -77,7 +77,7 @@ class HaInputButtonForm extends LitElement {
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value || (ev.target as any).value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class HaInputButtonForm extends LitElement {
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -102,6 +102,10 @@ class HaInputButtonForm extends LitElement {
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-radio";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaRadio } from "../../../../components/ha-radio";
|
||||
import { InputDateTime } from "../../../../data/input_datetime";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -60,10 +60,10 @@ class HaInputDateTimeForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -72,7 +72,7 @@ class HaInputDateTimeForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -142,7 +142,7 @@ class HaInputDateTimeForm extends LitElement {
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value || (ev.target as any).value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -150,7 +150,7 @@ class HaInputDateTimeForm extends LitElement {
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -167,6 +167,10 @@ class HaInputDateTimeForm extends LitElement {
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { InputNumber } from "../../../../data/input_number";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -73,10 +73,10 @@ class HaInputNumberForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -85,7 +85,7 @@ class HaInputNumberForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -94,24 +94,24 @@ class HaInputNumberForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-picker>
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._min}
|
||||
.configValue=${"min"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.min"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._max}
|
||||
.configValue=${"max"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.max"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="layout horizontal center justified">
|
||||
@@ -143,24 +143,24 @@ class HaInputNumberForm extends LitElement {
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._step}
|
||||
.configValue=${"step"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.step"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
|
||||
<paper-input
|
||||
.value=${this._unit_of_measurement}
|
||||
<ha-textfield
|
||||
.value=${this._unit_of_measurement || ""}
|
||||
.configValue=${"unit_of_measurement"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.unit_of_measurement"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -181,7 +181,10 @@ class HaInputNumberForm extends LitElement {
|
||||
const target = ev.target as any;
|
||||
const configValue = target.configValue;
|
||||
const value =
|
||||
target.type === "number" ? Number(ev.detail.value) : ev.detail.value;
|
||||
target.type === "number"
|
||||
? Number(target.value)
|
||||
: ev.detail?.value || target.value;
|
||||
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +192,7 @@ class HaInputNumberForm extends LitElement {
|
||||
if (value === undefined || value === "") {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -203,6 +206,11 @@ class HaInputNumberForm extends LitElement {
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import type { InputSelect } from "../../../../data/input_select";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -28,7 +27,7 @@ class HaInputSelectForm extends LitElement {
|
||||
|
||||
@state() private _options: string[] = [];
|
||||
|
||||
@query("#option_input", true) private _optionInput?: PaperInputElement;
|
||||
@query("#option_input", true) private _optionInput?: HaTextField;
|
||||
|
||||
set item(item: InputSelect) {
|
||||
this._item = item;
|
||||
@@ -59,19 +58,19 @@ class HaInputSelectForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
<ha-textfield
|
||||
dialogInitialFocus
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
.value=${this._name}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -86,9 +85,10 @@ class HaInputSelectForm extends LitElement {
|
||||
${this._options.length
|
||||
? this._options.map(
|
||||
(option, index) => html`
|
||||
<paper-item class="option">
|
||||
<paper-item-body> ${option} </paper-item-body>
|
||||
<mwc-list-item class="option" hasMeta noninteractive>
|
||||
${option}
|
||||
<ha-icon-button
|
||||
slot="meta"
|
||||
.index=${index}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.remove_option"
|
||||
@@ -96,25 +96,25 @@ class HaInputSelectForm extends LitElement {
|
||||
@click=${this._removeOption}
|
||||
.path=${mdiDelete}
|
||||
></ha-icon-button>
|
||||
</paper-item>
|
||||
</mwc-list-item>
|
||||
`
|
||||
)
|
||||
: html`
|
||||
<paper-item>
|
||||
<mwc-list-item noninteractive>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.no_options"
|
||||
)}
|
||||
</paper-item>
|
||||
</mwc-list-item>
|
||||
`}
|
||||
<div class="layout horizontal bottom">
|
||||
<paper-input
|
||||
<div class="layout horizontal center">
|
||||
<ha-textfield
|
||||
class="flex-auto"
|
||||
id="option_input"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add_option"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<mwc-button @click=${this._addOption}
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add"
|
||||
@@ -135,7 +135,7 @@ class HaInputSelectForm extends LitElement {
|
||||
|
||||
private _addOption() {
|
||||
const input = this._optionInput;
|
||||
if (!input || !input.value) {
|
||||
if (!input?.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -167,7 +167,8 @@ class HaInputSelectForm extends LitElement {
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value || (ev.target as any).value;
|
||||
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +176,7 @@ class HaInputSelectForm extends LitElement {
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -193,10 +194,18 @@ class HaInputSelectForm extends LitElement {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
#option_input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaRadio } from "../../../../components/ha-radio";
|
||||
import { InputText } from "../../../../data/input_text";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -64,10 +65,10 @@ class HaInputTextForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -76,7 +77,7 @@ class HaInputTextForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -87,28 +88,28 @@ class HaInputTextForm extends LitElement {
|
||||
></ha-icon-picker>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._min}
|
||||
.configValue=${"min"}
|
||||
type="number"
|
||||
min="0"
|
||||
max="255"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.min"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._max}
|
||||
.configValue=${"max"}
|
||||
min="0"
|
||||
max="255"
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.max"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<div class="layout horizontal center justified">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.mode"
|
||||
@@ -138,14 +139,14 @@ class HaInputTextForm extends LitElement {
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<paper-input
|
||||
.value=${this._pattern}
|
||||
<ha-textfield
|
||||
.value=${this._pattern || ""}
|
||||
.configValue=${"pattern"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.pattern"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -164,7 +165,7 @@ class HaInputTextForm extends LitElement {
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value || (ev.target as any).value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -172,7 +173,7 @@ class HaInputTextForm extends LitElement {
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -189,6 +190,10 @@ class HaInputTextForm extends LitElement {
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { DurationDict, Timer } from "../../../../data/timer";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -49,10 +50,10 @@ class HaTimerForm extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
@@ -61,7 +62,7 @@ class HaTimerForm extends LitElement {
|
||||
)}
|
||||
.invalid=${nameInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@@ -70,14 +71,14 @@ class HaTimerForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-picker>
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.configValue=${"duration"}
|
||||
.value=${this._duration}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.timer.duration"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -88,7 +89,7 @@ class HaTimerForm extends LitElement {
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value || (ev.target as any).value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +97,7 @@ class HaTimerForm extends LitElement {
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
newValue[configValue] = value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -110,6 +111,10 @@ class HaTimerForm extends LitElement {
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -111,6 +111,7 @@ class ZHAConfigDashboard extends LitElement {
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.schema=${schema}
|
||||
.data=${this._configuration!.data[section]}
|
||||
@value-changed=${this._dataChanged}
|
||||
|
@@ -19,7 +19,11 @@ import {
|
||||
fetchZwaveNetworkStatus,
|
||||
fetchZwaveNodeStatus,
|
||||
fetchZwaveProvisioningEntries,
|
||||
InclusionState,
|
||||
setZwaveDataCollectionPreference,
|
||||
stopZwaveExclusion,
|
||||
stopZwaveInclusion,
|
||||
ZWaveJSClient,
|
||||
ZWaveJSNetwork,
|
||||
ZWaveJSNodeStatus,
|
||||
ZwaveJSProvisioningEntry,
|
||||
@@ -60,7 +64,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
|
||||
@state() private _provisioningEntries?: ZwaveJSProvisioningEntry[];
|
||||
|
||||
@state() private _status = "unknown";
|
||||
@state() private _status?: ZWaveJSClient["state"];
|
||||
|
||||
@state() private _icon = mdiCircle;
|
||||
|
||||
@@ -107,13 +111,38 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
"ui.panel.config.zwave_js.dashboard.introduction"
|
||||
)}
|
||||
</div>
|
||||
${this._network &&
|
||||
this._status === "connected" &&
|
||||
(this._network?.controller.inclusion_state ===
|
||||
InclusionState.Including ||
|
||||
this._network?.controller.inclusion_state ===
|
||||
InclusionState.Excluding)
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.common.in_progress_inclusion_exclusion`
|
||||
)}
|
||||
<mwc-button
|
||||
slot="action"
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.common.cancel_inclusion_exclusion`
|
||||
)}
|
||||
@click=${this._network?.controller.inclusion_state ===
|
||||
InclusionState.Including
|
||||
? this._cancelInclusion
|
||||
: this._cancelExclusion}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: ""}
|
||||
${this._network
|
||||
? html`
|
||||
<ha-card class="content network-status">
|
||||
<div class="card-content">
|
||||
<div class="heading">
|
||||
<div class="icon">
|
||||
${this._status === "connecting"
|
||||
${this._status === "disconnected"
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
></ha-circular-progress>`
|
||||
@@ -121,13 +150,13 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
<ha-svg-icon
|
||||
.path=${this._icon}
|
||||
class="network-status-icon ${classMap({
|
||||
[this._status]: true,
|
||||
[this._status!]: true,
|
||||
})}"
|
||||
slot="item-icon"
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
</div>
|
||||
${this._status !== "connecting"
|
||||
${this._status !== "disconnected"
|
||||
? html`
|
||||
<div class="details">
|
||||
${this.hass.localize(
|
||||
@@ -207,7 +236,9 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button
|
||||
@click=${this._removeNodeClicked}
|
||||
.disabled=${this._status === "connecting"}
|
||||
.disabled=${this._status !== "connected" ||
|
||||
this._network?.controller.inclusion_state !==
|
||||
InclusionState.Idle}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.remove_node"
|
||||
@@ -215,16 +246,13 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._healNetworkClicked}
|
||||
.disabled=${this._status === "connecting"}
|
||||
.disabled=${this._status === "disconnected"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.heal_network"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._openOptionFlow}
|
||||
.disabled=${this._status === "connecting"}
|
||||
>
|
||||
<mwc-button @click=${this._openOptionFlow}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.reconfigure_server"
|
||||
)}
|
||||
@@ -272,10 +300,11 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.add_node"
|
||||
)}
|
||||
.disabled=${this._status === "connecting"}
|
||||
extended
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
@click=${this._addNodeClicked}
|
||||
.disabled=${this._status !== "connected" ||
|
||||
this._network?.controller.inclusion_state !== InclusionState.Idle}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
@@ -412,6 +441,16 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _cancelInclusion() {
|
||||
stopZwaveInclusion(this.hass!, this.configEntryId!);
|
||||
await this._fetchData();
|
||||
}
|
||||
|
||||
private async _cancelExclusion() {
|
||||
stopZwaveExclusion(this.hass!, this.configEntryId!);
|
||||
await this._fetchData();
|
||||
}
|
||||
|
||||
private _dataCollectionToggled(ev) {
|
||||
setZwaveDataCollectionPreference(
|
||||
this.hass!,
|
||||
|
@@ -3,6 +3,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { load } from "js-yaml";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-code-editor";
|
||||
@@ -51,6 +52,10 @@ class HaPanelDevTemplate extends LitElement {
|
||||
|
||||
private _template = "";
|
||||
|
||||
private _context_data = "";
|
||||
|
||||
private _context_variables?;
|
||||
|
||||
private _inited = false;
|
||||
|
||||
public connectedCallback() {
|
||||
@@ -65,8 +70,14 @@ class HaPanelDevTemplate extends LitElement {
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
if (localStorage && localStorage["panel-dev-template-template"]) {
|
||||
this._template = localStorage["panel-dev-template-template"];
|
||||
if (localStorage) {
|
||||
if (localStorage["panel-dev-template-template"]) {
|
||||
this._template = localStorage["panel-dev-template-template"];
|
||||
}
|
||||
if (localStorage["panel-dev-template-context-data"]) {
|
||||
this._context_data = localStorage["panel-dev-template-context-data"];
|
||||
this._context_variables = undefined;
|
||||
}
|
||||
} else {
|
||||
this._template = DEMO_TEMPLATE;
|
||||
}
|
||||
@@ -141,6 +152,17 @@ class HaPanelDevTemplate extends LitElement {
|
||||
"ui.panel.developer-tools.tabs.templates.reset"
|
||||
)}
|
||||
</mwc-button>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.context_data"
|
||||
)}
|
||||
</p>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
.value=${this._context_data}
|
||||
@value-changed=${this._contextDataChanged}
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
|
||||
<div class="render-pane">
|
||||
@@ -302,6 +324,15 @@ class HaPanelDevTemplate extends LitElement {
|
||||
false
|
||||
);
|
||||
|
||||
private _contextDataChanged(ev) {
|
||||
this._context_data = ev.detail.value;
|
||||
this._context_variables = undefined;
|
||||
if (this._error) {
|
||||
this._error = undefined;
|
||||
}
|
||||
this._debounceRender();
|
||||
}
|
||||
|
||||
private _templateChanged(ev) {
|
||||
this._template = ev.detail.value;
|
||||
if (this._error) {
|
||||
@@ -314,6 +345,9 @@ class HaPanelDevTemplate extends LitElement {
|
||||
this._rendering = true;
|
||||
await this._unsubscribeTemplate();
|
||||
try {
|
||||
if (this._context_data && !this._context_variables) {
|
||||
this._context_variables = load(this._context_data);
|
||||
}
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
@@ -322,6 +356,7 @@ class HaPanelDevTemplate extends LitElement {
|
||||
},
|
||||
{
|
||||
template: this._template,
|
||||
variables: this._context_variables,
|
||||
timeout: 3,
|
||||
}
|
||||
);
|
||||
@@ -360,12 +395,16 @@ class HaPanelDevTemplate extends LitElement {
|
||||
if (!this._inited) {
|
||||
return;
|
||||
}
|
||||
localStorage["panel-dev-template-context-data"] = this._context_data;
|
||||
localStorage["panel-dev-template-template"] = this._template;
|
||||
}
|
||||
|
||||
private _restoreDemo() {
|
||||
this._context_data = "";
|
||||
this._context_variables = undefined;
|
||||
this._template = DEMO_TEMPLATE;
|
||||
this._subscribeTemplate();
|
||||
delete localStorage["panel-dev-template-context-data"];
|
||||
delete localStorage["panel-dev-template-template"];
|
||||
}
|
||||
}
|
||||
|
@@ -84,7 +84,6 @@ export class HuiButtonsBase extends LitElement {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
state-badge {
|
||||
display: inline-flex;
|
||||
|
@@ -28,7 +28,7 @@ export class HuiThemeSelectEditor extends LitElement {
|
||||
@selected=${this._changed}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidt
|
||||
naturalMenuWidth
|
||||
>
|
||||
<mwc-list-item value="remove"
|
||||
>${this.hass!.localize(
|
||||
|
@@ -91,6 +91,8 @@ export class HuiCalendarCardEditor
|
||||
.configValue=${"initial_view"}
|
||||
@selected=${this._viewChanged}
|
||||
@closed=${stopPropagation}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
>
|
||||
${views.map(
|
||||
(view) => html`
|
||||
|
@@ -160,6 +160,8 @@ export class HuiConditionalCardEditor
|
||||
.configValue=${"invert"}
|
||||
@selected=${this._changeCondition}
|
||||
@closed=${stopPropagation}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
>
|
||||
<mwc-list-item value="false">
|
||||
${this.hass!.localize(
|
||||
|
@@ -5,6 +5,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||
import "../../../../components/ha-formfield";
|
||||
@@ -103,9 +104,12 @@ export class HuiGenericEntityRowEditor
|
||||
</div>
|
||||
<mwc-select
|
||||
label="Secondary Info"
|
||||
.selected=${this._config.secondary_info || "none"}
|
||||
.configValue=${"secondary_info"}
|
||||
@selected=${this._valueChanged}
|
||||
@closed=${stopPropagation}
|
||||
.value=${this._config.secondary_info || "none"}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
>
|
||||
<mwc-list-item value=""
|
||||
>${this.hass!.localize(
|
||||
|
@@ -335,6 +335,7 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._guiSupported = false;
|
||||
this.GUImode = false;
|
||||
}
|
||||
} catch (err: any) {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
|
@@ -15,7 +15,7 @@ export class BrowserMediaPlayer {
|
||||
|
||||
constructor(
|
||||
public hass: HomeAssistant,
|
||||
private item: MediaPlayerItem,
|
||||
public item: MediaPlayerItem,
|
||||
private onChange: () => void
|
||||
) {}
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
@@ -164,7 +165,11 @@ class BarMediaPlayer extends LitElement {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="info ${!isBrowser ? "pointer" : ""}"
|
||||
class=${classMap({
|
||||
info: true,
|
||||
pointer: !isBrowser,
|
||||
app: this._browserPlayer?.item.media_class === "app",
|
||||
})}
|
||||
@click=${this._openMoreInfo}
|
||||
>
|
||||
${mediaArt ? html`<img src=${this.hass.hassUrl(mediaArt)} />` : ""}
|
||||
@@ -498,6 +503,11 @@ class BarMediaPlayer extends LitElement {
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.app img {
|
||||
max-height: 68px;
|
||||
margin: 16px 0 16px 16px;
|
||||
}
|
||||
|
||||
ha-button-menu mwc-button {
|
||||
line-height: 1;
|
||||
}
|
||||
|
@@ -291,6 +291,7 @@ class PanelMediaBrowser extends LitElement {
|
||||
"change",
|
||||
async () => {
|
||||
const files = input.files!;
|
||||
document.body.removeChild(input);
|
||||
const target = this._currentItem!.media_content_id!;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
@@ -315,6 +316,9 @@ class PanelMediaBrowser extends LitElement {
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
// https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only
|
||||
input.style.display = "none";
|
||||
document.body.append(input);
|
||||
input.click();
|
||||
}
|
||||
|
||||
|
@@ -112,6 +112,7 @@ class HaMfaModuleSetupFlow extends LitElement {
|
||||
)}
|
||||
></ha-markdown>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._stepData}
|
||||
.schema=${this._step.data_schema}
|
||||
.error=${this._step.errors}
|
||||
|
@@ -317,6 +317,17 @@
|
||||
"copied_clipboard": "Copied to clipboard"
|
||||
},
|
||||
"components": {
|
||||
"selectors": {
|
||||
"media": {
|
||||
"pick_media_player": "Select media player",
|
||||
"browse_not_supported": "Media player does not support browsing media.",
|
||||
"pick_media": "Pick media",
|
||||
"browse_media": "Browse media",
|
||||
"manual": "Manually enter Media ID",
|
||||
"media_content_id": "Media content ID",
|
||||
"media_content_type": "Media content type"
|
||||
}
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "No logbook events found.",
|
||||
"by": "by",
|
||||
@@ -505,6 +516,18 @@
|
||||
"clear": "Clear"
|
||||
},
|
||||
"media-browser": {
|
||||
"tts": {
|
||||
"message": "Message",
|
||||
"example_message": "Hello {name}, you can play any text on any supported media player!",
|
||||
"language": "Language",
|
||||
"gender": "Gender",
|
||||
"gender_male": "Male",
|
||||
"gender_female": "Female",
|
||||
"action_play": "Say",
|
||||
"action_pick": "Select",
|
||||
"set_as_default": "Set as default options",
|
||||
"faild_to_store_defaults": "Failed to store defaults: {error}"
|
||||
},
|
||||
"pick": "Pick",
|
||||
"play": "Play",
|
||||
"play-media": "Play Media",
|
||||
@@ -1630,7 +1653,7 @@
|
||||
"label": "Geolocation",
|
||||
"source": "Source",
|
||||
"zone": "Zone",
|
||||
"event": "Event:",
|
||||
"event": "Event",
|
||||
"enter": "Enter",
|
||||
"leave": "Leave"
|
||||
},
|
||||
@@ -1676,7 +1699,8 @@
|
||||
"type_value": "Fixed time",
|
||||
"type_input": "Value of a date/time helper",
|
||||
"label": "Time",
|
||||
"at": "At time"
|
||||
"at": "At time",
|
||||
"mode": "Mode"
|
||||
},
|
||||
"time_pattern": {
|
||||
"label": "Time Pattern",
|
||||
@@ -1744,8 +1768,8 @@
|
||||
},
|
||||
"sun": {
|
||||
"label": "[%key:ui::panel::config::automation::editor::triggers::type::sun::label%]",
|
||||
"before": "Before:",
|
||||
"after": "After:",
|
||||
"before": "Before",
|
||||
"after": "After",
|
||||
"before_offset": "Before offset (optional)",
|
||||
"after_offset": "After offset (optional)",
|
||||
"sunrise": "Sunrise",
|
||||
@@ -1761,6 +1785,9 @@
|
||||
"label": "[%key:ui::panel::config::automation::editor::triggers::type::time::label%]",
|
||||
"after": "After",
|
||||
"before": "Before",
|
||||
"weekday": "Weekdays",
|
||||
"mode_after": "[%key:ui::panel::config::automation::editor::conditions::type::time::after%]",
|
||||
"mode_before": "[%key:ui::panel::config::automation::editor::conditions::type::time::before%]",
|
||||
"weekdays": {
|
||||
"mon": "Monday",
|
||||
"tue": "Tuesday",
|
||||
@@ -1772,9 +1799,9 @@
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"label": "Trigger",
|
||||
"no_triggers": "No triggers available",
|
||||
"id": "Trigger ID"
|
||||
"label": "Triggered by",
|
||||
"no_triggers": "No triggers have an ID set. You can set an ID using the trigger menu button.",
|
||||
"id": "Trigger"
|
||||
},
|
||||
"zone": {
|
||||
"label": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]",
|
||||
@@ -1798,6 +1825,9 @@
|
||||
"service": {
|
||||
"label": "Call service"
|
||||
},
|
||||
"play_media": {
|
||||
"label": "Play media"
|
||||
},
|
||||
"delay": {
|
||||
"label": "Wait for time to pass (delay)",
|
||||
"delay": "Duration"
|
||||
@@ -1836,7 +1866,7 @@
|
||||
"flash": "Flash"
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"activate_scene": {
|
||||
"label": "Activate scene"
|
||||
},
|
||||
"repeat": {
|
||||
@@ -2956,7 +2986,9 @@
|
||||
"add_node": "Add device",
|
||||
"remove_node": "Remove device",
|
||||
"reconfigure_server": "Re-configure Server",
|
||||
"heal_network": "Heal Network"
|
||||
"heal_network": "Heal Network",
|
||||
"in_progress_inclusion_exclusion": "Z-Wave JS is searching for devices",
|
||||
"cancel_inclusion_exclusion": "Stop Searching"
|
||||
},
|
||||
"dashboard": {
|
||||
"header": "Manage your Z-Wave Network",
|
||||
@@ -3697,18 +3729,6 @@
|
||||
"media-browser": {
|
||||
"error": {
|
||||
"player_not_exist": "Media player {name} does not exist"
|
||||
},
|
||||
"tts": {
|
||||
"message": "Message",
|
||||
"example_message": "Hello {name}, you can play any text on any supported media player!",
|
||||
"language": "Language",
|
||||
"gender": "Gender",
|
||||
"gender_male": "Male",
|
||||
"gender_female": "Female",
|
||||
"action_play": "Say",
|
||||
"action_pick": "Select",
|
||||
"set_as_default": "Set as default options",
|
||||
"faild_to_store_defaults": "Failed to store defaults: {error}"
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
@@ -4073,6 +4093,7 @@
|
||||
"no_listeners": "This template does not listen for any events and will not update automatically.",
|
||||
"listeners": "This template listens for the following state changed events:",
|
||||
"entity": "Entity",
|
||||
"context_data": "Context data for template",
|
||||
"domain": "Domain"
|
||||
},
|
||||
"statistics": {
|
||||
@@ -4285,6 +4306,10 @@
|
||||
"title": "Ingress",
|
||||
"description": "This add-on is using Ingress to embed its interface securely into Home Assistant."
|
||||
},
|
||||
"signed": {
|
||||
"title": "Signed",
|
||||
"description": "This add-on signed and verified with Codenotary Community Attestation Service (CAS)."
|
||||
},
|
||||
"label": {
|
||||
"core": "Core",
|
||||
"rating": "rating",
|
||||
@@ -4295,7 +4320,8 @@
|
||||
"host_pid": "host pid",
|
||||
"apparmor": "apparmor",
|
||||
"auth": "auth",
|
||||
"ingress": "ingress"
|
||||
"ingress": "ingress",
|
||||
"signed": "Signed"
|
||||
},
|
||||
"stages": {
|
||||
"experimental": "Experimental",
|
||||
|
10
yarn.lock
10
yarn.lock
@@ -9075,10 +9075,10 @@ fsevents@^1.2.7:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hls.js@npm:^1.0.11":
|
||||
version: 1.0.11
|
||||
resolution: "hls.js@npm:1.0.11"
|
||||
checksum: 0375871cf8ffef3374f44284028d235bb122dc5ee2a36b765124e8caca1ec97ba96738ef8de261fbdecf78669e7bf3fb8aee1070f239c7791add44fad50df180
|
||||
"hls.js@npm:^1.1.5":
|
||||
version: 1.1.5
|
||||
resolution: "hls.js@npm:1.1.5"
|
||||
checksum: 7363eb8be6ad35be73fe3497a2fa1d493b42c76382ccc6b05f298394545eb75d6c466fb233748f3226859360d7aeea99bc5ea3e372ec074c6c2e354dcc5f6435
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9228,7 +9228,7 @@ fsevents@^1.2.7:
|
||||
gulp-merge-json: ^1.3.1
|
||||
gulp-rename: ^2.0.0
|
||||
gulp-zopfli-green: ^3.0.1
|
||||
hls.js: ^1.0.11
|
||||
hls.js: ^1.1.5
|
||||
home-assistant-js-websocket: ^6.0.1
|
||||
html-minifier: ^4.0.0
|
||||
husky: ^1.3.1
|
||||
|
Reference in New Issue
Block a user