mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Add play media action (#11702)
Co-authored-by: Zack Barett <zackbarett@hey.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
cbd0ef6b65
commit
5c5459bcaf
@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import { describeAction } from "../../../../src/data/script_i18n";
|
import { describeAction } from "../../../../src/data/script_i18n";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
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" },
|
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
||||||
{ delay: "0:05" },
|
{ delay: "0:05" },
|
||||||
{ wait_template: "{{ true }}" },
|
{ wait_template: "{{ true }}" },
|
||||||
@ -19,8 +29,20 @@ const actions = [
|
|||||||
device_id: "abcdefgh",
|
device_id: "abcdefgh",
|
||||||
domain: "plex",
|
domain: "plex",
|
||||||
entity_id: "media_player.kitchen",
|
entity_id: "media_player.kitchen",
|
||||||
|
type: "turn_on",
|
||||||
},
|
},
|
||||||
{ scene: "scene.kitchen_morning" },
|
{ 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: [
|
wait_for_trigger: [
|
||||||
{
|
{
|
||||||
@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Actions">
|
<ha-card header="Actions">
|
||||||
${actions.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<span>${describeAction(this.hass, conf as any)}</span>
|
<span>${describeAction(this.hass, conf as any)}</span>
|
||||||
@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
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 { 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 { 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 { 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 { 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 { 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";
|
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||||
|
@ -36,6 +36,8 @@ const SCHEMAS: {
|
|||||||
text_multiline: "Text Multiline",
|
text_multiline: "Text Multiline",
|
||||||
object: "Object",
|
object: "Object",
|
||||||
select: "Select",
|
select: "Select",
|
||||||
|
icon: "Icon",
|
||||||
|
media: "Media",
|
||||||
},
|
},
|
||||||
schema: [
|
schema: [
|
||||||
{ name: "addon", selector: { addon: {} } },
|
{ name: "addon", selector: { addon: {} } },
|
||||||
@ -67,6 +69,12 @@ const SCHEMAS: {
|
|||||||
icon: {},
|
icon: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "media",
|
||||||
|
selector: {
|
||||||
|
media: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,100 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
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: {
|
const SCHEMAS: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -73,13 +167,14 @@ const SCHEMAS: {
|
|||||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||||
},
|
},
|
||||||
icon: { name: "Icon", selector: { icon: {} } },
|
icon: { name: "Icon", selector: { icon: {} } },
|
||||||
|
media: { name: "Media", selector: { media: {} } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-components-ha-selector")
|
@customElement("demo-components-ha-selector")
|
||||||
class DemoHaSelector extends LitElement {
|
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||||
@state() private hass!: HomeAssistant;
|
@state() public hass!: HomeAssistant;
|
||||||
|
|
||||||
private data = SCHEMAS.map(() => ({}));
|
private data = SCHEMAS.map(() => ({}));
|
||||||
|
|
||||||
@ -88,12 +183,130 @@ class DemoHaSelector extends LitElement {
|
|||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
hass.updateTranslations("config", "en");
|
hass.updateTranslations("config", "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
mockEntityRegistry(hass);
|
mockEntityRegistry(hass);
|
||||||
mockDeviceRegistry(hass);
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
mockAreaRegistry(hass);
|
mockAreaRegistry(hass, AREAS);
|
||||||
mockHassioSupervisor(hass);
|
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 {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
@ -132,7 +345,6 @@ class DemoHaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
paper-input,
|
|
||||||
ha-selector {
|
ha-selector {
|
||||||
width: 60;
|
width: 60;
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import "./ha-selector-target";
|
|||||||
import "./ha-selector-text";
|
import "./ha-selector-text";
|
||||||
import "./ha-selector-time";
|
import "./ha-selector-time";
|
||||||
import "./ha-selector-icon";
|
import "./ha-selector-icon";
|
||||||
|
import "./ha-selector-media";
|
||||||
|
|
||||||
@customElement("ha-selector")
|
@customElement("ha-selector")
|
||||||
export class HaSelector extends LitElement {
|
export class HaSelector extends LitElement {
|
||||||
|
@ -28,10 +28,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._navigateIds = [
|
this._navigateIds = params.navigateIds || [
|
||||||
{
|
{
|
||||||
media_content_id: this._params.mediaContentId,
|
media_content_id: undefined,
|
||||||
media_content_type: this._params.mediaContentType,
|
media_content_type: undefined,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,26 @@ import {
|
|||||||
getCloudTtsLanguages,
|
getCloudTtsLanguages,
|
||||||
getCloudTtsSupportedGenders,
|
getCloudTtsSupportedGenders,
|
||||||
} from "../../data/cloud/tts";
|
} from "../../data/cloud/tts";
|
||||||
import { MediaPlayerBrowseAction } from "../../data/media-player";
|
import {
|
||||||
|
MediaPlayerBrowseAction,
|
||||||
|
MediaPlayerItem,
|
||||||
|
} from "../../data/media-player";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-textarea";
|
import "../ha-textarea";
|
||||||
import { buttonLinkStyle } from "../../resources/styles";
|
import { buttonLinkStyle } from "../../resources/styles";
|
||||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
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")
|
@customElement("ha-browse-media-tts")
|
||||||
class BrowseMediaTTS extends LitElement {
|
class BrowseMediaTTS extends LitElement {
|
||||||
@ -32,40 +46,55 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
|
|
||||||
@state() private _cloudTTSInfo?: CloudTTSInfo;
|
@state() private _cloudTTSInfo?: CloudTTSInfo;
|
||||||
|
|
||||||
@LocalStorage("cloudTtsTryMessage", false, false) private _message!: string;
|
@LocalStorage("cloudTtsTryMessage", true, false) private _message!: string;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
<ha-textarea
|
<ha-textarea
|
||||||
autogrow
|
autogrow
|
||||||
.label=${this.hass.localize("ui.panel.media-browser.tts.message")}
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.media-browser.tts.message"
|
||||||
|
)}
|
||||||
.value=${this._message ||
|
.value=${this._message ||
|
||||||
this.hass.localize("ui.panel.media-browser.tts.example_message", {
|
this.hass.localize(
|
||||||
|
"ui.components.media-browser.tts.example_message",
|
||||||
|
{
|
||||||
name: this.hass.user?.name || "",
|
name: this.hass.user?.name || "",
|
||||||
})}
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
</ha-textarea>
|
</ha-textarea>
|
||||||
${this._cloudDefaultOptions ? this._renderCloudOptions() : ""}
|
${this._cloudDefaultOptions ? this._renderCloudOptions() : ""}
|
||||||
<div class="actions">
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
${this._cloudDefaultOptions &&
|
${this._cloudDefaultOptions &&
|
||||||
(this._cloudDefaultOptions![0] !== this._cloudOptions![0] ||
|
(this._cloudDefaultOptions![0] !== this._cloudOptions![0] ||
|
||||||
this._cloudDefaultOptions![1] !== this._cloudOptions![1])
|
this._cloudDefaultOptions![1] !== this._cloudOptions![1])
|
||||||
? html`
|
? html`
|
||||||
<button class="link" @click=${this._storeDefaults}>
|
<button class="link" @click=${this._storeDefaults}>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.media-browser.tts.set_as_default"
|
"ui.components.media-browser.tts.set_as_default"
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: html`<span></span>`}
|
: 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>
|
</div>
|
||||||
`;
|
</ha-card> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderCloudOptions() {
|
private _renderCloudOptions() {
|
||||||
|
if (!this._cloudTTSInfo || !this._cloudOptions) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
const languages = this.getLanguages(this._cloudTTSInfo);
|
const languages = this.getLanguages(this._cloudTTSInfo);
|
||||||
const selectedVoice = this._cloudOptions!;
|
const selectedVoice = this._cloudOptions;
|
||||||
const genders = this.getSupportedGenders(
|
const genders = this.getSupportedGenders(
|
||||||
selectedVoice[0],
|
selectedVoice[0],
|
||||||
this._cloudTTSInfo,
|
this._cloudTTSInfo,
|
||||||
@ -77,9 +106,12 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
<mwc-select
|
<mwc-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.hass.localize("ui.panel.media-browser.tts.language")}
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.media-browser.tts.language"
|
||||||
|
)}
|
||||||
.value=${selectedVoice[0]}
|
.value=${selectedVoice[0]}
|
||||||
@selected=${this._handleLanguageChange}
|
@selected=${this._handleLanguageChange}
|
||||||
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
${languages.map(
|
${languages.map(
|
||||||
([key, label]) =>
|
([key, label]) =>
|
||||||
@ -90,9 +122,10 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
<mwc-select
|
<mwc-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.hass.localize("ui.panel.media-browser.tts.gender")}
|
.label=${this.hass.localize("ui.components.media-browser.tts.gender")}
|
||||||
.value=${selectedVoice[1]}
|
.value=${selectedVoice[1]}
|
||||||
@selected=${this._handleGenderChange}
|
@selected=${this._handleGenderChange}
|
||||||
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
${genders.map(
|
${genders.map(
|
||||||
([key, label]) =>
|
([key, label]) =>
|
||||||
@ -106,6 +139,37 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
protected override willUpdate(changedProps: PropertyValues): void {
|
protected override willUpdate(changedProps: PropertyValues): void {
|
||||||
super.willUpdate(changedProps);
|
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")) {
|
if (changedProps.has("message")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -133,30 +197,12 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
this._cloudOptions = [this._cloudOptions![0], ev.target.value];
|
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 getLanguages = memoizeOne(getCloudTtsLanguages);
|
||||||
|
|
||||||
private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders);
|
private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders);
|
||||||
|
|
||||||
private get isCloudItem(): boolean {
|
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> {
|
private async _ttsClicked(): Promise<void> {
|
||||||
@ -169,9 +215,12 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
query.append("language", this._cloudOptions[0]);
|
query.append("language", this._cloudOptions[0]);
|
||||||
query.append("gender", this._cloudOptions[1]);
|
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;
|
item.can_play = true;
|
||||||
fireEvent(this, "media-picked", { item });
|
item.title = message;
|
||||||
|
fireEvent(this, "tts-picked", { item });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _storeDefaults() {
|
private async _storeDefaults() {
|
||||||
@ -185,7 +234,7 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
this._cloudDefaultOptions = oldDefaults;
|
this._cloudDefaultOptions = oldDefaults;
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
text: this.hass.localize(
|
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 }
|
{ error: err.message || err }
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -210,15 +259,16 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
.cloud-options mwc-select {
|
.cloud-options mwc-select {
|
||||||
width: 48%;
|
width: 48%;
|
||||||
}
|
}
|
||||||
|
ha-textarea {
|
||||||
.actions {
|
width: 100%;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
}
|
||||||
button.link {
|
button.link {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ import "../ha-svg-icon";
|
|||||||
import "../ha-fab";
|
import "../ha-fab";
|
||||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||||
import { isTTSMediaSource } from "../../data/tts";
|
import { isTTSMediaSource } from "../../data/tts";
|
||||||
import "./ha-browse-media-tts";
|
import { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -260,6 +260,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
.item=${currentItem}
|
.item=${currentItem}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.action=${this.action}
|
.action=${this.action}
|
||||||
|
@tts-picked=${this._ttsPicked}
|
||||||
></ha-browse-media-tts>
|
></ha-browse-media-tts>
|
||||||
`
|
`
|
||||||
: !currentItem.children?.length
|
: !currentItem.children?.length
|
||||||
@ -562,7 +563,17 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _runAction(item: MediaPlayerItem): void {
|
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> {
|
private async _childClicked(ev: MouseEvent): Promise<void> {
|
||||||
|
@ -3,13 +3,13 @@ import {
|
|||||||
MediaPickedEvent,
|
MediaPickedEvent,
|
||||||
MediaPlayerBrowseAction,
|
MediaPlayerBrowseAction,
|
||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
|
import { MediaPlayerItemId } from "./ha-media-player-browse";
|
||||||
|
|
||||||
export interface MediaPlayerBrowseDialogParams {
|
export interface MediaPlayerBrowseDialogParams {
|
||||||
action: MediaPlayerBrowseAction;
|
action: MediaPlayerBrowseAction;
|
||||||
entityId: string;
|
entityId: string;
|
||||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => void;
|
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => void;
|
||||||
mediaContentId?: string;
|
navigateIds?: MediaPlayerItemId[];
|
||||||
mediaContentType?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showMediaBrowserDialog = (
|
export const showMediaBrowserDialog = (
|
||||||
|
@ -28,6 +28,7 @@ import type {
|
|||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
|
import { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { UNAVAILABLE_STATES } from "./entity";
|
import { UNAVAILABLE_STATES } from "./entity";
|
||||||
|
|
||||||
@ -147,6 +148,7 @@ export const MediaClassBrowserSettings: {
|
|||||||
|
|
||||||
export interface MediaPickedEvent {
|
export interface MediaPickedEvent {
|
||||||
item: MediaPlayerItem;
|
item: MediaPlayerItem;
|
||||||
|
navigateIds: MediaPlayerItemId[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaPlayerThumbnail {
|
export interface MediaPlayerThumbnail {
|
||||||
|
@ -3,6 +3,17 @@ import {
|
|||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
HassServiceTarget,
|
HassServiceTarget,
|
||||||
} from "home-assistant-js-websocket";
|
} 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 { computeObjectId } from "../common/entity/compute_object_id";
|
||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -12,6 +23,48 @@ import { BlueprintInput } from "./blueprint";
|
|||||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||||
export const MODES_MAX = ["queued", "parallel"];
|
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 {
|
export interface ScriptEntity extends HassEntityBase {
|
||||||
attributes: HassEntityAttributeBase & {
|
attributes: HassEntityAttributeBase & {
|
||||||
last_triggered: string;
|
last_triggered: string;
|
||||||
@ -48,11 +101,12 @@ export interface ServiceAction {
|
|||||||
service_template?: string;
|
service_template?: string;
|
||||||
entity_id?: string;
|
entity_id?: string;
|
||||||
target?: HassServiceTarget;
|
target?: HassServiceTarget;
|
||||||
data?: Record<string, any>;
|
data?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceAction {
|
export interface DeviceAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
|
type: string;
|
||||||
device_id: string;
|
device_id: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
entity_id: string;
|
entity_id: string;
|
||||||
@ -70,9 +124,12 @@ export interface DelayAction {
|
|||||||
delay: number | Partial<DelayActionParts> | string;
|
delay: number | Partial<DelayActionParts> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceSceneAction extends ServiceAction {
|
export interface ServiceSceneAction {
|
||||||
|
alias?: string;
|
||||||
service: "scene.turn_on";
|
service: "scene.turn_on";
|
||||||
metadata: Record<string, any>;
|
target?: { entity_id?: string };
|
||||||
|
entity_id?: string;
|
||||||
|
metadata: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
export interface LegacySceneAction {
|
export interface LegacySceneAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
@ -94,6 +151,15 @@ export interface WaitForTriggerAction {
|
|||||||
continue_on_timeout?: boolean;
|
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 {
|
export interface RepeatAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
repeat: CountRepeat | WhileRepeat | UntilRepeat;
|
repeat: CountRepeat | WhileRepeat | UntilRepeat;
|
||||||
@ -150,6 +216,7 @@ export type Action =
|
|||||||
| RepeatAction
|
| RepeatAction
|
||||||
| ChooseAction
|
| ChooseAction
|
||||||
| VariablesAction
|
| VariablesAction
|
||||||
|
| PlayMediaAction
|
||||||
| UnknownAction;
|
| UnknownAction;
|
||||||
|
|
||||||
export interface ActionTypes {
|
export interface ActionTypes {
|
||||||
@ -158,13 +225,13 @@ export interface ActionTypes {
|
|||||||
check_condition: Condition;
|
check_condition: Condition;
|
||||||
fire_event: EventAction;
|
fire_event: EventAction;
|
||||||
device_action: DeviceAction;
|
device_action: DeviceAction;
|
||||||
legacy_activate_scene: LegacySceneAction;
|
activate_scene: SceneAction;
|
||||||
activate_scene: ServiceSceneAction;
|
|
||||||
repeat: RepeatAction;
|
repeat: RepeatAction;
|
||||||
choose: ChooseAction;
|
choose: ChooseAction;
|
||||||
wait_for_trigger: WaitForTriggerAction;
|
wait_for_trigger: WaitForTriggerAction;
|
||||||
variables: VariablesAction;
|
variables: VariablesAction;
|
||||||
service: ServiceAction;
|
service: ServiceAction;
|
||||||
|
play_media: PlayMediaAction;
|
||||||
unknown: UnknownAction;
|
unknown: UnknownAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +291,7 @@ export const getActionType = (action: Action): ActionType => {
|
|||||||
return "device_action";
|
return "device_action";
|
||||||
}
|
}
|
||||||
if ("scene" in action) {
|
if ("scene" in action) {
|
||||||
return "legacy_activate_scene";
|
return "activate_scene";
|
||||||
}
|
}
|
||||||
if ("repeat" in action) {
|
if ("repeat" in action) {
|
||||||
return "repeat";
|
return "repeat";
|
||||||
@ -240,12 +307,12 @@ export const getActionType = (action: Action): ActionType => {
|
|||||||
}
|
}
|
||||||
if ("service" in action) {
|
if ("service" in action) {
|
||||||
if ("metadata" in action) {
|
if ("metadata" in action) {
|
||||||
if (
|
if (is(action, activateSceneActionStruct)) {
|
||||||
(action as ServiceAction).service === "scene.turn_on" &&
|
|
||||||
!Array.isArray((action as ServiceAction)?.target?.entity_id)
|
|
||||||
) {
|
|
||||||
return "activate_scene";
|
return "activate_scene";
|
||||||
}
|
}
|
||||||
|
if (is(action, playMediaActionStruct)) {
|
||||||
|
return "play_media";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "service";
|
return "service";
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,11 @@ import {
|
|||||||
ActionType,
|
ActionType,
|
||||||
ActionTypes,
|
ActionTypes,
|
||||||
DelayAction,
|
DelayAction,
|
||||||
|
DeviceAction,
|
||||||
EventAction,
|
EventAction,
|
||||||
getActionType,
|
getActionType,
|
||||||
LegacySceneAction,
|
PlayMediaAction,
|
||||||
ServiceSceneAction,
|
SceneAction,
|
||||||
VariablesAction,
|
VariablesAction,
|
||||||
WaitForTriggerAction,
|
WaitForTriggerAction,
|
||||||
} from "./script";
|
} from "./script";
|
||||||
@ -103,19 +104,32 @@ export const describeAction = <T extends ActionType>(
|
|||||||
return `Delay ${duration}`;
|
return `Delay ${duration}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionType === "legacy_activate_scene") {
|
if (actionType === "activate_scene") {
|
||||||
const config = action as LegacySceneAction;
|
const config = action as SceneAction;
|
||||||
const sceneStateObj = hass.states[config.scene];
|
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 ${
|
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") {
|
if (actionType === "play_media") {
|
||||||
const config = action as ServiceSceneAction;
|
const config = action as PlayMediaAction;
|
||||||
const sceneStateObj = hass.states[config.target!.entity_id as string];
|
const entityId = config.target?.entity_id || config.entity_id;
|
||||||
return `Activate scene ${
|
const mediaStateObj = entityId ? hass.states[entityId] : undefined;
|
||||||
sceneStateObj ? computeStateName(sceneStateObj) : config.target!.entity_id
|
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)}`;
|
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;
|
return actionType;
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,8 @@ export type Selector =
|
|||||||
| StringSelector
|
| StringSelector
|
||||||
| ObjectSelector
|
| ObjectSelector
|
||||||
| SelectSelector
|
| SelectSelector
|
||||||
| IconSelector;
|
| IconSelector
|
||||||
|
| MediaSelector;
|
||||||
|
|
||||||
export interface EntitySelector {
|
export interface EntitySelector {
|
||||||
entity: {
|
entity: {
|
||||||
@ -149,3 +150,21 @@ export interface IconSelector {
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
icon: {};
|
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,8 +1,8 @@
|
|||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
|
|
||||||
import "@material/mwc-select";
|
import "@material/mwc-select";
|
||||||
import type { Select } from "@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 { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -11,22 +11,23 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
|||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
|
import "../../../../components/ha-alert";
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-alert";
|
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
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 { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import "./types/ha-automation-action-activate_scene";
|
||||||
import "./types/ha-automation-action-choose";
|
import "./types/ha-automation-action-choose";
|
||||||
import "./types/ha-automation-action-condition";
|
import "./types/ha-automation-action-condition";
|
||||||
import "./types/ha-automation-action-delay";
|
import "./types/ha-automation-action-delay";
|
||||||
import "./types/ha-automation-action-device_id";
|
import "./types/ha-automation-action-device_id";
|
||||||
import "./types/ha-automation-action-event";
|
import "./types/ha-automation-action-event";
|
||||||
|
import "./types/ha-automation-action-play_media";
|
||||||
import "./types/ha-automation-action-repeat";
|
import "./types/ha-automation-action-repeat";
|
||||||
import "./types/ha-automation-action-scene";
|
|
||||||
import "./types/ha-automation-action-service";
|
import "./types/ha-automation-action-service";
|
||||||
import "./types/ha-automation-action-wait_for_trigger";
|
import "./types/ha-automation-action-wait_for_trigger";
|
||||||
import "./types/ha-automation-action-wait_template";
|
import "./types/ha-automation-action-wait_template";
|
||||||
@ -35,7 +36,8 @@ const OPTIONS = [
|
|||||||
"condition",
|
"condition",
|
||||||
"delay",
|
"delay",
|
||||||
"event",
|
"event",
|
||||||
"scene",
|
"play_media",
|
||||||
|
"activate_scene",
|
||||||
"service",
|
"service",
|
||||||
"wait_template",
|
"wait_template",
|
||||||
"wait_for_trigger",
|
"wait_for_trigger",
|
||||||
@ -48,21 +50,8 @@ const getType = (action: Action | undefined) => {
|
|||||||
if (!action) {
|
if (!action) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if ("metadata" in action && action.service) {
|
if ("service" in action || "scene" in action) {
|
||||||
switch (action.service) {
|
return getActionType(action);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return OPTIONS.find((option) => option in 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]))
|
).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) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
if (!changedProperties.has("action")) {
|
if (!changedProperties.has("action")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._uiModeAvailable = Boolean(getType(this.action));
|
if (this._yamlMode) {
|
||||||
if (!this._uiModeAvailable && !this._yamlMode) {
|
|
||||||
this._yamlMode = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const yamlEditor = this._yamlEditor;
|
const yamlEditor = this._yamlEditor;
|
||||||
if (this._yamlMode && yamlEditor && yamlEditor.value !== this.action) {
|
if (yamlEditor && yamlEditor.value !== this.action) {
|
||||||
yamlEditor.setValue(this.action);
|
yamlEditor.setValue(this.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const type = getType(this.action);
|
const type = getType(this.action);
|
||||||
const selected = type ? OPTIONS.indexOf(type) : -1;
|
|
||||||
const yamlMode = this._yamlMode;
|
const yamlMode = this._yamlMode;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@ -225,7 +220,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${yamlMode
|
${yamlMode
|
||||||
? html`
|
? html`
|
||||||
${selected === -1
|
${type === undefined
|
||||||
? html`
|
? html`
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.unsupported_action",
|
"ui.panel.config.automation.editor.actions.unsupported_action",
|
||||||
|
@ -9,7 +9,7 @@ import { ActionElement } from "../ha-automation-action-row";
|
|||||||
|
|
||||||
const includeDomains = ["scene"];
|
const includeDomains = ["scene"];
|
||||||
|
|
||||||
@customElement("ha-automation-action-scene")
|
@customElement("ha-automation-action-activate_scene")
|
||||||
export class HaSceneAction extends LitElement implements ActionElement {
|
export class HaSceneAction extends LitElement implements ActionElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@ -61,6 +61,6 @@ export class HaSceneAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-automation-action-scene": HaSceneAction;
|
"ha-automation-action-activate_scene": HaSceneAction;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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: {} };
|
return { service: "", data: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected willUpdate(changedProperties: PropertyValues) {
|
||||||
if (!changedProperties.has("action")) {
|
if (!changedProperties.has("action")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -317,6 +317,17 @@
|
|||||||
"copied_clipboard": "Copied to clipboard"
|
"copied_clipboard": "Copied to clipboard"
|
||||||
},
|
},
|
||||||
"components": {
|
"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": {
|
"logbook": {
|
||||||
"entries_not_found": "No logbook events found.",
|
"entries_not_found": "No logbook events found.",
|
||||||
"by": "by",
|
"by": "by",
|
||||||
@ -505,6 +516,18 @@
|
|||||||
"clear": "Clear"
|
"clear": "Clear"
|
||||||
},
|
},
|
||||||
"media-browser": {
|
"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",
|
"pick": "Pick",
|
||||||
"play": "Play",
|
"play": "Play",
|
||||||
"play-media": "Play Media",
|
"play-media": "Play Media",
|
||||||
@ -1798,6 +1821,9 @@
|
|||||||
"service": {
|
"service": {
|
||||||
"label": "Call service"
|
"label": "Call service"
|
||||||
},
|
},
|
||||||
|
"play_media": {
|
||||||
|
"label": "Play media"
|
||||||
|
},
|
||||||
"delay": {
|
"delay": {
|
||||||
"label": "Wait for time to pass (delay)",
|
"label": "Wait for time to pass (delay)",
|
||||||
"delay": "Duration"
|
"delay": "Duration"
|
||||||
@ -1836,7 +1862,7 @@
|
|||||||
"flash": "Flash"
|
"flash": "Flash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scene": {
|
"activate_scene": {
|
||||||
"label": "Activate scene"
|
"label": "Activate scene"
|
||||||
},
|
},
|
||||||
"repeat": {
|
"repeat": {
|
||||||
@ -3699,18 +3725,6 @@
|
|||||||
"media-browser": {
|
"media-browser": {
|
||||||
"error": {
|
"error": {
|
||||||
"player_not_exist": "Media player {name} does not exist"
|
"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": {
|
"map": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user