mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-24 00:29:26 +00:00
Compare commits
3 Commits
newsletter
...
fix-search
Author | SHA1 | Date | |
---|---|---|---|
![]() |
21e441b682 | ||
![]() |
737f7ba6b9 | ||
![]() |
a5862b86ca |
@@ -16,9 +16,6 @@
|
|||||||
"runem.lit-plugin",
|
"runem.lit-plugin",
|
||||||
"ms-python.vscode-pylance"
|
"ms-python.vscode-pylance"
|
||||||
],
|
],
|
||||||
"containerEnv": {
|
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.shell.linux": "/bin/bash",
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
@@ -33,10 +33,6 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
|||||||
require.resolve(
|
require.resolve(
|
||||||
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
||||||
),
|
),
|
||||||
isHassioBuild &&
|
|
||||||
require.resolve(
|
|
||||||
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
|
|
||||||
),
|
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||||
|
@@ -2,3 +2,8 @@ import "../../src/resources/ha-style";
|
|||||||
import "../../src/resources/roboto";
|
import "../../src/resources/roboto";
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./ha-demo";
|
import "./ha-demo";
|
||||||
|
|
||||||
|
/* polyfill for paper-dropdown */
|
||||||
|
setTimeout(() => {
|
||||||
|
import("web-animations-js/web-animations-next-lite.min");
|
||||||
|
}, 1000);
|
||||||
|
@@ -20,6 +20,7 @@ module.exports = [
|
|||||||
"editor-trigger",
|
"editor-trigger",
|
||||||
"editor-condition",
|
"editor-condition",
|
||||||
"editor-action",
|
"editor-action",
|
||||||
|
"selectors",
|
||||||
"trace",
|
"trace",
|
||||||
"trace-timeline",
|
"trace-timeline",
|
||||||
],
|
],
|
||||||
|
@@ -3,20 +3,10 @@ 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 ENTITIES = [
|
const actions = [
|
||||||
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 }}" },
|
||||||
@@ -29,20 +19,8 @@ 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: [
|
||||||
{
|
{
|
||||||
@@ -74,7 +52,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>
|
||||||
@@ -90,7 +68,6 @@ 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-activate_scene";
|
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-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";
|
||||||
|
3
gallery/src/pages/automation/selectors.markdown
Normal file
3
gallery/src/pages/automation/selectors.markdown
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Selectors
|
||||||
|
---
|
102
gallery/src/pages/automation/selectors.ts
Normal file
102
gallery/src/pages/automation/selectors.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/* 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,8 +36,6 @@ 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: {} } },
|
||||||
@@ -63,18 +61,6 @@ const SCHEMAS: {
|
|||||||
select: { options: ["Everyone Home", "Some Home", "All gone"] },
|
select: { options: ["Everyone Home", "Some Home", "All gone"] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "icon",
|
|
||||||
selector: {
|
|
||||||
icon: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "media",
|
|
||||||
selector: {
|
|
||||||
media: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
---
|
---
|
||||||
title: Selectors
|
title: Target Selectors
|
||||||
---
|
---
|
||||||
|
|
||||||
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
|
|
||||||
|
@@ -12,100 +12,6 @@ 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;
|
||||||
@@ -166,15 +72,13 @@ const SCHEMAS: {
|
|||||||
name: "Select",
|
name: "Select",
|
||||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||||
},
|
},
|
||||||
icon: { name: "Icon", selector: { icon: {} } },
|
|
||||||
media: { name: "Media", selector: { media: {} } },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-components-ha-selector")
|
@customElement("demo-components-ha-selector")
|
||||||
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
class DemoHaSelector extends LitElement {
|
||||||
@state() public hass!: HomeAssistant;
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
private data = SCHEMAS.map(() => ({}));
|
private data = SCHEMAS.map(() => ({}));
|
||||||
|
|
||||||
@@ -183,130 +87,12 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
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, DEVICES);
|
mockDeviceRegistry(hass);
|
||||||
mockAreaRegistry(hass, AREAS);
|
mockAreaRegistry(hass);
|
||||||
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) => {
|
||||||
@@ -345,6 +131,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
paper-input,
|
||||||
ha-selector {
|
ha-selector {
|
||||||
width: 60;
|
width: 60;
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,6 @@ const createConfigEntry = (
|
|||||||
source: "zeroconf",
|
source: "zeroconf",
|
||||||
state: "loaded",
|
state: "loaded",
|
||||||
supports_options: false,
|
supports_options: false,
|
||||||
supports_remove_device: false,
|
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
|
@@ -221,14 +221,13 @@ class HassioAddonStore extends LitElement {
|
|||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.search {
|
.search {
|
||||||
position: sticky;
|
padding: 0 16px;
|
||||||
top: 0;
|
background: var(--sidebar-background-color);
|
||||||
z-index: 2;
|
border-bottom: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
search-input {
|
.search search-input {
|
||||||
display: block;
|
position: relative;
|
||||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
top: 2px;
|
||||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
|
||||||
}
|
}
|
||||||
.advanced {
|
.advanced {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-select";
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -10,7 +11,7 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
import "web-animations-js/web-animations-next-lite.min";
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
@@ -56,44 +57,49 @@ class HassioAddonAudio extends LitElement {
|
|||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._inputDevices &&
|
|
||||||
html`<mwc-select
|
<paper-dropdown-menu
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.input"
|
"addon.configuration.audio.input"
|
||||||
)}
|
)}
|
||||||
@selected=${this._setInputDevice}
|
@iron-select=${this._setInputDevice}
|
||||||
@closed=${stopPropagation}
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
.value=${this._selectedInput!}
|
|
||||||
>
|
>
|
||||||
${this._inputDevices.map(
|
<paper-listbox
|
||||||
(item) => html`
|
slot="dropdown-content"
|
||||||
<mwc-list-item .value=${item.device || ""}>
|
attr-for-selected="device"
|
||||||
${item.name}
|
.selected=${this._selectedInput!}
|
||||||
</mwc-list-item>
|
>
|
||||||
`
|
${this._inputDevices &&
|
||||||
)}
|
this._inputDevices.map(
|
||||||
</mwc-select>`}
|
(item) => html`
|
||||||
${this._outputDevices &&
|
<paper-item device=${item.device || ""}>
|
||||||
html`<mwc-select
|
${item.name}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu>
|
||||||
|
<paper-dropdown-menu
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.output"
|
"addon.configuration.audio.output"
|
||||||
)}
|
)}
|
||||||
@selected=${this._setOutputDevice}
|
@iron-select=${this._setOutputDevice}
|
||||||
@closed=${stopPropagation}
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
.value=${this._selectedOutput!}
|
|
||||||
>
|
>
|
||||||
${this._outputDevices.map(
|
<paper-listbox
|
||||||
(item) => html`
|
slot="dropdown-content"
|
||||||
<mwc-list-item .value=${item.device || ""}
|
attr-for-selected="device"
|
||||||
>${item.name}</mwc-list-item
|
.selected=${this._selectedOutput!}
|
||||||
>
|
>
|
||||||
`
|
${this._outputDevices &&
|
||||||
)}
|
this._outputDevices.map(
|
||||||
</mwc-select>`}
|
(item) => html`
|
||||||
|
<paper-item device=${item.device || ""}
|
||||||
|
>${item.name}</paper-item
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button @click=${this._saveSettings}>
|
<ha-progress-button @click=${this._saveSettings}>
|
||||||
@@ -110,7 +116,8 @@ class HassioAddonAudio extends LitElement {
|
|||||||
hassioStyle,
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
:host,
|
:host,
|
||||||
ha-card {
|
ha-card,
|
||||||
|
paper-dropdown-menu {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
paper-item {
|
paper-item {
|
||||||
@@ -119,30 +126,24 @@ class HassioAddonAudio extends LitElement {
|
|||||||
.card-actions {
|
.card-actions {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
mwc-select {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
mwc-select:last-child {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues): void {
|
protected update(changedProperties: PropertyValues): void {
|
||||||
super.willUpdate(changedProperties);
|
super.update(changedProperties);
|
||||||
if (changedProperties.has("addon")) {
|
if (changedProperties.has("addon")) {
|
||||||
this._addonChanged();
|
this._addonChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setInputDevice(ev): void {
|
private _setInputDevice(ev): void {
|
||||||
const device = ev.target.value;
|
const device = ev.detail.item.getAttribute("device");
|
||||||
this._selectedInput = device;
|
this._selectedInput = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setOutputDevice(ev): void {
|
private _setOutputDevice(ev): void {
|
||||||
const device = ev.target.value;
|
const device = ev.detail.item.getAttribute("device");
|
||||||
this._selectedOutput = device;
|
this._selectedOutput = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,6 @@ import {
|
|||||||
mdiFlask,
|
mdiFlask,
|
||||||
mdiHomeAssistant,
|
mdiHomeAssistant,
|
||||||
mdiKey,
|
mdiKey,
|
||||||
mdiLinkLock,
|
|
||||||
mdiNetwork,
|
mdiNetwork,
|
||||||
mdiNumeric1,
|
mdiNumeric1,
|
||||||
mdiNumeric2,
|
mdiNumeric2,
|
||||||
@@ -17,8 +16,6 @@ import {
|
|||||||
mdiNumeric4,
|
mdiNumeric4,
|
||||||
mdiNumeric5,
|
mdiNumeric5,
|
||||||
mdiNumeric6,
|
mdiNumeric6,
|
||||||
mdiNumeric7,
|
|
||||||
mdiNumeric8,
|
|
||||||
mdiPound,
|
mdiPound,
|
||||||
mdiShield,
|
mdiShield,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@@ -34,7 +31,6 @@ import "../../../../src/components/buttons/ha-progress-button";
|
|||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-chip";
|
import "../../../../src/components/ha-chip";
|
||||||
import "../../../../src/components/ha-chip-set";
|
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
@@ -88,8 +84,6 @@ const RATING_ICON = {
|
|||||||
4: mdiNumeric4,
|
4: mdiNumeric4,
|
||||||
5: mdiNumeric5,
|
5: mdiNumeric5,
|
||||||
6: mdiNumeric6,
|
6: mdiNumeric6,
|
||||||
7: mdiNumeric7,
|
|
||||||
8: mdiNumeric8,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("hassio-addon-info")
|
@customElement("hassio-addon-info")
|
||||||
@@ -215,7 +209,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
>`}
|
>`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ha-chip-set class="capabilities">
|
<div class="capabilities">
|
||||||
${this.addon.stage !== "stable"
|
${this.addon.stage !== "stable"
|
||||||
? html` <ha-chip
|
? html` <ha-chip
|
||||||
hasIcon
|
hasIcon
|
||||||
@@ -240,9 +234,9 @@ class HassioAddonInfo extends LitElement {
|
|||||||
<ha-chip
|
<ha-chip
|
||||||
hasIcon
|
hasIcon
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
green: Number(this.addon.rating) >= 6,
|
green: [5, 6].includes(Number(this.addon.rating)),
|
||||||
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
|
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||||
red: Number(this.addon.rating) >= 2,
|
red: [1, 2].includes(Number(this.addon.rating)),
|
||||||
})}
|
})}
|
||||||
@click=${this._showMoreInfo}
|
@click=${this._showMoreInfo}
|
||||||
id="rating"
|
id="rating"
|
||||||
@@ -370,17 +364,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
</ha-chip>
|
</ha-chip>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this.addon.signed
|
</div>
|
||||||
? 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">
|
<div class="description light-color">
|
||||||
${this.addon.description}.<br />
|
${this.addon.description}.<br />
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||||
@@ -92,8 +92,6 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
|
|
||||||
@property() public confirmBackupPassword = "";
|
@property() public confirmBackupPassword = "";
|
||||||
|
|
||||||
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
public willUpdate(changedProps) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
@@ -111,10 +109,6 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override focus() {
|
|
||||||
this._focusTarget?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _localize = (string: string) =>
|
private _localize = (string: string) =>
|
||||||
this.supervisor?.localize(`backup.${string}`) ||
|
this.supervisor?.localize(`backup.${string}`) ||
|
||||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||||
@@ -175,23 +169,24 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.backupType === "partial"
|
${this.backupType === "partial"
|
||||||
? html`<div class="partial-picker">
|
? html`<div class="partial-picker">
|
||||||
<ha-formfield
|
${this.backup && this.backup.homeassistant
|
||||||
.label=${html`<supervisor-formfield-label
|
? html`
|
||||||
label="Home Assistant"
|
<ha-formfield
|
||||||
.iconPath=${mdiHomeAssistant}
|
.label=${html`<supervisor-formfield-label
|
||||||
.version=${this.backup
|
label="Home Assistant"
|
||||||
? this.backup.homeassistant
|
.iconPath=${mdiHomeAssistant}
|
||||||
: this.hass.config.version}
|
.version=${this.backup.homeassistant}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@click=${this.toggleHomeAssistant}
|
@click=${this.toggleHomeAssistant}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
${foldersSection?.templates.length
|
${foldersSection?.templates.length
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
|
@@ -148,6 +148,7 @@ export class HassioUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
--paper-item-body-two-line-min-height: 32px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -64,7 +64,6 @@ export class DialogHassioBackupUpload
|
|||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
slot="actionItems"
|
slot="actionItems"
|
||||||
dialogAction="cancel"
|
dialogAction="cancel"
|
||||||
dialogInitialFocus
|
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -92,7 +92,6 @@ class HassioBackupDialog
|
|||||||
.backup=${this._backup}
|
.backup=${this._backup}
|
||||||
.onboarding=${this._dialogParams.onboarding || false}
|
.onboarding=${this._dialogParams.onboarding || false}
|
||||||
.localize=${this._dialogParams.localize}
|
.localize=${this._dialogParams.localize}
|
||||||
dialogInitialFocus
|
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@@ -61,7 +61,6 @@ class HassioCreateBackupDialog extends LitElement {
|
|||||||
: html`<supervisor-backup-content
|
: html`<supervisor-backup-content
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this._dialogParams.supervisor}
|
.supervisor=${this._dialogParams.supervisor}
|
||||||
dialogInitialFocus
|
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
import "@material/mwc-select";
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -89,20 +90,18 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<mwc-select
|
<paper-dropdown-menu
|
||||||
.label=${this.dialogParams.supervisor.localize(
|
.label=${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.select_device"
|
"dialog.datadisk_move.select_device"
|
||||||
)}
|
)}
|
||||||
@selected=${this._select_device}
|
@value-changed=${this._select_device}
|
||||||
dialogInitialFocus
|
|
||||||
>
|
>
|
||||||
${this.devices.map(
|
<paper-listbox slot="dropdown-content">
|
||||||
(device) =>
|
${this.devices.map(
|
||||||
html`<mwc-list-item .value=${device}
|
(device) => html`<paper-item>${device}</paper-item>`
|
||||||
>${device}</mwc-list-item
|
)}
|
||||||
>`
|
</paper-listbox>
|
||||||
)}
|
</paper-dropdown-menu>
|
||||||
</mwc-select>
|
|
||||||
`
|
`
|
||||||
: this.devices === undefined
|
: this.devices === undefined
|
||||||
? this.dialogParams.supervisor.localize(
|
? this.dialogParams.supervisor.localize(
|
||||||
@@ -112,11 +111,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
"dialog.datadisk_move.no_devices"
|
"dialog.datadisk_move.no_devices"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<mwc-button
|
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||||
slot="secondaryAction"
|
|
||||||
@click=${this.closeDialog}
|
|
||||||
dialogInitialFocus
|
|
||||||
>
|
|
||||||
${this.dialogParams.supervisor.localize(
|
${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.cancel"
|
"dialog.datadisk_move.cancel"
|
||||||
)}
|
)}
|
||||||
@@ -135,8 +130,8 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _select_device(ev) {
|
private _select_device(event) {
|
||||||
this.selectedDevice = ev.target.value;
|
this.selectedDevice = event.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _moveDatadisk() {
|
private async _moveDatadisk() {
|
||||||
@@ -161,7 +156,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
mwc-select {
|
paper-dropdown-menu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
|
@@ -80,7 +80,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
dialogInitialFocus
|
autofocus
|
||||||
no-label-float
|
no-label-float
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
@@ -178,7 +178,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
margin: 8px 16px 0;
|
margin: 0 16px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.device-property {
|
.device-property {
|
||||||
|
@@ -37,10 +37,7 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${createCloseHeading(this.hass, this.title)}
|
.heading=${createCloseHeading(this.hass, this.title)}
|
||||||
>
|
>
|
||||||
<ha-markdown
|
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||||
.content=${this.content || ""}
|
|
||||||
dialogInitialFocus
|
|
||||||
></ha-markdown>
|
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -119,7 +119,6 @@ export class DialogHassioNetwork
|
|||||||
html`<mwc-tab
|
html`<mwc-tab
|
||||||
.id=${device.interface}
|
.id=${device.interface}
|
||||||
.label=${device.interface}
|
.label=${device.interface}
|
||||||
dialogInitialFocus
|
|
||||||
>
|
>
|
||||||
</mwc-tab>`
|
</mwc-tab>`
|
||||||
)}
|
)}
|
||||||
@@ -316,7 +315,6 @@ export class DialogHassioNetwork
|
|||||||
value="auto"
|
value="auto"
|
||||||
name="${version}method"
|
name="${version}method"
|
||||||
.checked=${this._interface![version]?.method === "auto"}
|
.checked=${this._interface![version]?.method === "auto"}
|
||||||
dialogInitialFocus
|
|
||||||
>
|
>
|
||||||
</ha-radio>
|
</ha-radio>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
@@ -80,7 +80,6 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
.schema=${SCHEMA}
|
.schema=${SCHEMA}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.computeLabel=${this._computeLabel}
|
.computeLabel=${this._computeLabel}
|
||||||
dialogInitialFocus
|
|
||||||
></ha-form>
|
></ha-form>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@@ -125,7 +124,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
`}
|
`}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
<mwc-button @click=${this._addRegistry}>
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
"dialog.registries.add_new_registry"
|
"dialog.registries.add_new_registry"
|
||||||
)}
|
)}
|
||||||
|
@@ -139,7 +139,6 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
"dialog.repositories.add"
|
"dialog.repositories.add"
|
||||||
)}
|
)}
|
||||||
@keydown=${this._handleKeyAdd}
|
@keydown=${this._handleKeyAdd}
|
||||||
dialogInitialFocus
|
|
||||||
></paper-input>
|
></paper-input>
|
||||||
<mwc-button @click=${this._addRepository}>
|
<mwc-button @click=${this._addRepository}>
|
||||||
${this._processing
|
${this._processing
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
@@ -70,19 +73,24 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.hass.userData?.showAdvanced
|
${this.hass.userData?.showAdvanced
|
||||||
? html`
|
? html`
|
||||||
<mwc-select
|
<paper-dropdown-menu
|
||||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||||
@selected=${this._setLogProvider}
|
@iron-select=${this._setLogProvider}
|
||||||
.value=${this._selectedLogProvider}
|
|
||||||
>
|
>
|
||||||
${logProviders.map(
|
<paper-listbox
|
||||||
(provider) => html`
|
slot="dropdown-content"
|
||||||
<mwc-list-item .value=${provider.key}>
|
attr-for-selected="provider"
|
||||||
${provider.name}
|
.selected=${this._selectedLogProvider}
|
||||||
</mwc-list-item>
|
>
|
||||||
`
|
${logProviders.map(
|
||||||
)}
|
(provider) => html`
|
||||||
</mwc-select>
|
<paper-item provider=${provider.key}>
|
||||||
|
${provider.name}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
@@ -102,7 +110,7 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _setLogProvider(ev): Promise<void> {
|
private async _setLogProvider(ev): Promise<void> {
|
||||||
const provider = ev.target.value;
|
const provider = ev.detail.item.getAttribute("provider");
|
||||||
this._selectedLogProvider = provider;
|
this._selectedLogProvider = provider;
|
||||||
this._loadData();
|
this._loadData();
|
||||||
}
|
}
|
||||||
@@ -145,9 +153,9 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
mwc-select {
|
paper-dropdown-menu {
|
||||||
width: 100%;
|
padding: 0 2%;
|
||||||
margin-bottom: 4px;
|
width: 96%;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
import "../../../src/common/search/search-input";
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
|
@@ -106,7 +106,7 @@
|
|||||||
"deep-freeze": "^0.0.1",
|
"deep-freeze": "^0.0.1",
|
||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"hls.js": "^1.1.5",
|
"hls.js": "^1.0.11",
|
||||||
"home-assistant-js-websocket": "^6.0.1",
|
"home-assistant-js-websocket": "^6.0.1",
|
||||||
"idb-keyval": "^5.1.3",
|
"idb-keyval": "^5.1.3",
|
||||||
"intl-messageformat": "^9.9.1",
|
"intl-messageformat": "^9.9.1",
|
||||||
|
18
script/core
18
script/core
@@ -4,8 +4,6 @@
|
|||||||
# Stop on errors
|
# Stop on errors
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
|
|
||||||
|
|
||||||
if [ -z "${DEVCONTAINER}" ]; then
|
if [ -z "${DEVCONTAINER}" ]; then
|
||||||
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -18,9 +16,9 @@ if [ -z $(which hass) ]; then
|
|||||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "${WD}/config" ]; then
|
if [ ! -d "/workspaces/frontend/config" ]; then
|
||||||
echo "Creating default configuration."
|
echo "Creating default configuration."
|
||||||
mkdir -p "${WD}/config";
|
mkdir -p "/workspaces/frontend/config";
|
||||||
hass --script ensure_config -c config
|
hass --script ensure_config -c config
|
||||||
echo "demo:
|
echo "demo:
|
||||||
|
|
||||||
@@ -28,24 +26,24 @@ logger:
|
|||||||
default: info
|
default: info
|
||||||
logs:
|
logs:
|
||||||
homeassistant.components.frontend: debug
|
homeassistant.components.frontend: debug
|
||||||
" >> "${WD}/config/configuration.yaml"
|
" >> /workspaces/frontend/config/configuration.yaml
|
||||||
|
|
||||||
if [ ! -z "${HASSIO}" ]; then
|
if [ ! -z "${HASSIO}" ]; then
|
||||||
echo "
|
echo "
|
||||||
# frontend:
|
# frontend:
|
||||||
# development_repo: ${WD}
|
# development_repo: /workspaces/frontend
|
||||||
|
|
||||||
hassio:
|
hassio:
|
||||||
development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||||
else
|
else
|
||||||
echo "
|
echo "
|
||||||
frontend:
|
frontend:
|
||||||
development_repo: ${WD}
|
development_repo: /workspaces/frontend
|
||||||
|
|
||||||
# hassio:
|
# hassio:
|
||||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
hass -c "${WD}/config"
|
hass -c /workspaces/frontend/config
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = home-assistant-frontend
|
name = home-assistant-frontend
|
||||||
version = 20220220.0
|
version = 20220203.0
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import type { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
||||||
const services = hass.services[domain];
|
const services = hass.services[domain];
|
||||||
|
@@ -1,30 +1,14 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { canToggleDomain } from "./can_toggle_domain";
|
import { canToggleDomain } from "./can_toggle_domain";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
import { supportsFeature } from "./supports-feature";
|
import { supportsFeature } from "./supports-feature";
|
||||||
|
|
||||||
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
|
||||||
if (domain === "group") {
|
if (domain === "group") {
|
||||||
if (
|
return stateObj.state === "on" || stateObj.state === "off";
|
||||||
stateObj.attributes?.entity_id?.some((entity) => {
|
|
||||||
const entityStateObj = hass.states[entity];
|
|
||||||
if (!entityStateObj) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityDomain = computeStateDomain(entityStateObj);
|
|
||||||
return canToggleDomain(hass, entityDomain);
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return stateObj.state === "on" || stateObj.state === "off";
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "climate") {
|
if (domain === "climate") {
|
||||||
return supportsFeature(stateObj, 4096);
|
return supportsFeature(stateObj, 4096);
|
||||||
}
|
}
|
||||||
|
@@ -123,11 +123,7 @@ export const computeStateDisplay = (
|
|||||||
domain === "scene" ||
|
domain === "scene" ||
|
||||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
||||||
) {
|
) {
|
||||||
try {
|
return formatDateTime(new Date(compareState), locale);
|
||||||
return formatDateTime(new Date(compareState), locale);
|
|
||||||
} catch (_err) {
|
|
||||||
return compareState;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,32 +1,24 @@
|
|||||||
const SUFFIXES = [" ", ": "];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips a device name from an entity name.
|
* Strips a device name from an entity name.
|
||||||
* @param entityName the entity name
|
* @param entityName the entity name
|
||||||
* @param lowerCasedPrefix the prefix to strip, lower cased
|
* @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const stripPrefixFromEntityName = (
|
export const stripPrefixFromEntityName = (
|
||||||
entityName: string,
|
entityName: string,
|
||||||
lowerCasedPrefix: string
|
lowerCasedPrefixWithSpaceSuffix: string
|
||||||
) => {
|
) => {
|
||||||
const lowerCasedEntityName = entityName.toLowerCase();
|
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
|
||||||
|
return undefined;
|
||||||
for (const suffix of SUFFIXES) {
|
|
||||||
const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`;
|
|
||||||
|
|
||||||
if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
|
|
||||||
const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
|
|
||||||
|
|
||||||
// If first word already has an upper case letter (e.g. from brand name)
|
|
||||||
// leave as-is, otherwise capitalize the first word.
|
|
||||||
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
|
||||||
? newName
|
|
||||||
: newName[0].toUpperCase() + newName.slice(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
|
||||||
|
|
||||||
|
// If first word already has an upper case letter (e.g. from brand name)
|
||||||
|
// leave as-is, otherwise capitalize the first word.
|
||||||
|
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||||
|
? newName
|
||||||
|
: newName[0].toUpperCase() + newName.slice(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
||||||
|
@@ -14,9 +14,6 @@ class SearchInput extends LitElement {
|
|||||||
|
|
||||||
@property() public filter?: string;
|
@property() public filter?: string;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
public suffix = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public autofocus = false;
|
public autofocus = false;
|
||||||
|
|
||||||
@@ -36,7 +33,7 @@ class SearchInput extends LitElement {
|
|||||||
.label=${this.label || "Search"}
|
.label=${this.label || "Search"}
|
||||||
.value=${this.filter || ""}
|
.value=${this.filter || ""}
|
||||||
.icon=${true}
|
.icon=${true}
|
||||||
.iconTrailing=${this.filter || this.suffix}
|
.iconTrailing=${this.filter}
|
||||||
@input=${this._filterInputChanged}
|
@input=${this._filterInputChanged}
|
||||||
>
|
>
|
||||||
<slot name="prefix" slot="leadingIcon">
|
<slot name="prefix" slot="leadingIcon">
|
||||||
@@ -46,18 +43,16 @@ class SearchInput extends LitElement {
|
|||||||
.path=${mdiMagnify}
|
.path=${mdiMagnify}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</slot>
|
</slot>
|
||||||
<div class="trailing" slot="trailingIcon">
|
${this.filter &&
|
||||||
${this.filter &&
|
html`
|
||||||
html`
|
<ha-icon-button
|
||||||
<ha-icon-button
|
slot="trailingIcon"
|
||||||
@click=${this._clearSearch}
|
@click=${this._clearSearch}
|
||||||
.label=${this.hass.localize("ui.common.clear")}
|
.label=${this.hass.localize("ui.common.clear")}
|
||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`}
|
`}
|
||||||
<slot name="suffix"></slot>
|
|
||||||
</div>
|
|
||||||
</ha-textfield>
|
</ha-textfield>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -86,16 +81,15 @@ class SearchInput extends LitElement {
|
|||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
--mdc-icon-button-size: 24px;
|
||||||
|
}
|
||||||
.clear-button {
|
.clear-button {
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
display: inherit;
|
display: inherit;
|
||||||
}
|
}
|
||||||
.trailing {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,6 @@ export const iconColorCSS = css`
|
|||||||
ha-state-icon[data-domain="media_player"][data-state="on"],
|
ha-state-icon[data-domain="media_player"][data-state="on"],
|
||||||
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
||||||
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
||||||
ha-state-icon[data-domain="remote"][data-state="on"],
|
|
||||||
ha-state-icon[data-domain="script"][data-state="on"],
|
ha-state-icon[data-domain="script"][data-state="on"],
|
||||||
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
||||||
ha-state-icon[data-domain="switch"][data-state="on"],
|
ha-state-icon[data-domain="switch"][data-state="on"],
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
|
@@ -4,7 +4,6 @@ import { mdiFilterVariant } from "@mdi/js";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { computeDeviceName } from "../data/device_registry";
|
import { computeDeviceName } from "../data/device_registry";
|
||||||
import { findRelated, RelatedResult } from "../data/search";
|
import { findRelated, RelatedResult } from "../data/search";
|
||||||
@@ -66,7 +65,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.fullwidth=${this.narrow}
|
.fullwidth=${this.narrow}
|
||||||
.corner=${this.corner}
|
.corner=${this.corner}
|
||||||
@closed=${this._onClosed}
|
@closed=${this._onClosed}
|
||||||
@input=${stopPropagation}
|
|
||||||
>
|
>
|
||||||
<ha-area-picker
|
<ha-area-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -76,7 +74,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.value=${this.value?.area}
|
.value=${this.value?.area}
|
||||||
no-add
|
no-add
|
||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
@click=${this._preventDefault}
|
|
||||||
></ha-area-picker>
|
></ha-area-picker>
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -85,7 +82,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value?.device}
|
.value=${this.value?.device}
|
||||||
@value-changed=${this._devicePicked}
|
@value-changed=${this._devicePicked}
|
||||||
@click=${this._preventDefault}
|
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -95,7 +91,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.value=${this.value?.entity}
|
.value=${this.value?.entity}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
@value-changed=${this._entityPicked}
|
@value-changed=${this._entityPicked}
|
||||||
@click=${this._preventDefault}
|
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
</mwc-menu-surface>
|
</mwc-menu-surface>
|
||||||
`;
|
`;
|
||||||
@@ -108,17 +103,11 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
this._open = true;
|
this._open = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onClosed(ev): void {
|
private _onClosed(): void {
|
||||||
ev.stopPropagation();
|
|
||||||
this._open = false;
|
this._open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _preventDefault(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _entityPicked(ev: CustomEvent) {
|
private async _entityPicked(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
|
||||||
const entityId = ev.detail.value;
|
const entityId = ev.detail.value;
|
||||||
if (!entityId) {
|
if (!entityId) {
|
||||||
fireEvent(this, "related-changed", { value: undefined });
|
fireEvent(this, "related-changed", { value: undefined });
|
||||||
@@ -138,7 +127,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _devicePicked(ev: CustomEvent) {
|
private async _devicePicked(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
|
||||||
const deviceId = ev.detail.value;
|
const deviceId = ev.detail.value;
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
fireEvent(this, "related-changed", { value: undefined });
|
fireEvent(this, "related-changed", { value: undefined });
|
||||||
@@ -162,7 +150,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _areaPicked(ev: CustomEvent) {
|
private async _areaPicked(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
|
||||||
const areaId = ev.detail.value;
|
const areaId = ev.detail.value;
|
||||||
if (!areaId) {
|
if (!areaId) {
|
||||||
fireEvent(this, "related-changed", { value: undefined });
|
fireEvent(this, "related-changed", { value: undefined });
|
||||||
@@ -186,7 +173,9 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
--mdc-menu-min-width: 250px;
|
}
|
||||||
|
:host([narrow]) {
|
||||||
|
position: static;
|
||||||
}
|
}
|
||||||
ha-area-picker,
|
ha-area-picker,
|
||||||
ha-device-picker,
|
ha-device-picker,
|
||||||
@@ -196,15 +185,8 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
ha-area-picker {
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
ha-entity-picker {
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
:host([narrow]) ha-area-picker,
|
:host([narrow]) ha-area-picker,
|
||||||
:host([narrow]) ha-device-picker,
|
:host([narrow]) ha-device-picker {
|
||||||
:host([narrow]) ha-entity-picker {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { mdiCalendar } from "@mdi/js";
|
import { mdiCalendar } from "@mdi/js";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { formatDateNumeric } from "../common/datetime/format_date";
|
import { formatDateNumeric } from "../common/datetime/format_date";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./ha-textfield";
|
|
||||||
|
|
||||||
const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
|
const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
|
||||||
|
|
||||||
@@ -38,17 +38,17 @@ export class HaDateInput extends LitElement {
|
|||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<ha-textfield
|
return html`<paper-input
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
iconTrailing="calendar"
|
no-label-float
|
||||||
@click=${this._openDialog}
|
@click=${this._openDialog}
|
||||||
.value=${this.value
|
.value=${this.value
|
||||||
? formatDateNumeric(new Date(this.value), this.locale)
|
? formatDateNumeric(new Date(this.value), this.locale)
|
||||||
: ""}
|
: ""}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="trailingIcon" .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
</ha-textfield>`;
|
</paper-input>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openDialog() {
|
private _openDialog() {
|
||||||
@@ -73,6 +73,9 @@ export class HaDateInput extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
paper-input {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import "@material/mwc-list/mwc-list";
|
|||||||
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 { mdiCalendar } from "@mdi/js";
|
import { mdiCalendar } from "@mdi/js";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -18,7 +19,6 @@ import { computeRTLDirection } from "../common/util/compute_rtl";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./date-range-picker";
|
import "./date-range-picker";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./ha-textfield";
|
|
||||||
|
|
||||||
export interface DateRangePickerRanges {
|
export interface DateRangePickerRanges {
|
||||||
[key: string]: [Date, Date];
|
[key: string]: [Date, Date];
|
||||||
@@ -61,7 +61,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
>
|
>
|
||||||
<div slot="input" class="date-range-inputs">
|
<div slot="input" class="date-range-inputs">
|
||||||
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
||||||
<ha-textfield
|
<paper-input
|
||||||
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.date-range-picker.start_date"
|
"ui.components.date-range-picker.start_date"
|
||||||
@@ -69,16 +69,16 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@click=${this._handleInputClick}
|
@click=${this._handleInputClick}
|
||||||
readonly
|
readonly
|
||||||
></ha-textfield>
|
></paper-input>
|
||||||
<ha-textfield
|
<paper-input
|
||||||
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
||||||
.label=${this.hass.localize(
|
label=${this.hass.localize(
|
||||||
"ui.components.date-range-picker.end_date"
|
"ui.components.date-range-picker.end_date"
|
||||||
)}
|
)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@click=${this._handleInputClick}
|
@click=${this._handleInputClick}
|
||||||
readonly
|
readonly
|
||||||
></ha-textfield>
|
></paper-input>
|
||||||
</div>
|
</div>
|
||||||
${this.ranges
|
${this.ranges
|
||||||
? html`<div
|
? html`<div
|
||||||
@@ -158,13 +158,13 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-textfield {
|
paper-input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-textfield:last-child {
|
paper-input:last-child {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
@media only screen and (max-width: 500px) {
|
||||||
ha-textfield {
|
paper-input {
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,73 +0,0 @@
|
|||||||
import "./ha-form";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import type {
|
|
||||||
HaFormGridSchema,
|
|
||||||
HaFormDataContainer,
|
|
||||||
HaFormElement,
|
|
||||||
HaFormSchema,
|
|
||||||
} from "./types";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
|
||||||
|
|
||||||
@customElement("ha-form-grid")
|
|
||||||
export class HaFormGrid extends LitElement implements HaFormElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public data!: HaFormDataContainer;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public schema!: HaFormGridSchema;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
|
||||||
|
|
||||||
@property() public computeLabel?: (
|
|
||||||
schema: HaFormSchema,
|
|
||||||
data?: HaFormDataContainer
|
|
||||||
) => string;
|
|
||||||
|
|
||||||
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
|
||||||
|
|
||||||
protected firstUpdated() {
|
|
||||||
this.setAttribute("own-margin", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
${this.schema.schema.map(
|
|
||||||
(item) =>
|
|
||||||
html`
|
|
||||||
<ha-form
|
|
||||||
.hass=${this.hass}
|
|
||||||
.data=${this.data}
|
|
||||||
.schema=${[item]}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.computeLabel=${this.computeLabel}
|
|
||||||
.computeHelper=${this.computeHelper}
|
|
||||||
></ha-form>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: repeat(
|
|
||||||
var(--form-grid-column-count, auto-fit),
|
|
||||||
minmax(var(--form-grid-min-width, 200px), 1fr)
|
|
||||||
);
|
|
||||||
grid-gap: 8px;
|
|
||||||
}
|
|
||||||
:host > ha-form {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-form-grid": HaFormGrid;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,8 +11,7 @@ import {
|
|||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-button-menu";
|
import "../ha-button-menu";
|
||||||
import "../ha-check-list-item";
|
import { HaCheckListItem } from "../ha-check-list-item";
|
||||||
import type { HaCheckListItem } from "../ha-check-list-item";
|
|
||||||
import "../ha-checkbox";
|
import "../ha-checkbox";
|
||||||
import type { HaCheckbox } from "../ha-checkbox";
|
import type { HaCheckbox } from "../ha-checkbox";
|
||||||
import "../ha-formfield";
|
import "../ha-formfield";
|
||||||
|
@@ -1,18 +1,10 @@
|
|||||||
import {
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-alert";
|
import "../ha-alert";
|
||||||
import "./ha-form-boolean";
|
import "./ha-form-boolean";
|
||||||
import "./ha-form-constant";
|
import "./ha-form-constant";
|
||||||
import "./ha-form-grid";
|
|
||||||
import "./ha-form-float";
|
import "./ha-form-float";
|
||||||
import "./ha-form-integer";
|
import "./ha-form-integer";
|
||||||
import "./ha-form-multi_select";
|
import "./ha-form-multi_select";
|
||||||
@@ -22,18 +14,17 @@ import "./ha-form-string";
|
|||||||
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
const getValue = (obj, item) =>
|
const getValue = (obj, item) => (obj ? obj[item.name] : null);
|
||||||
obj ? (!item.name ? obj : obj[item.name]) : null;
|
|
||||||
|
|
||||||
let selectorImported = false;
|
let selectorImported = false;
|
||||||
|
|
||||||
@customElement("ha-form")
|
@customElement("ha-form")
|
||||||
export class HaForm extends LitElement implements HaFormElement {
|
export class HaForm extends LitElement implements HaFormElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public data!: HaFormDataContainer;
|
@property() public data!: HaFormDataContainer;
|
||||||
|
|
||||||
@property({ attribute: false }) public schema!: HaFormSchema[];
|
@property() public schema!: HaFormSchema[];
|
||||||
|
|
||||||
@property() public error?: Record<string, string>;
|
@property() public error?: Record<string, string>;
|
||||||
|
|
||||||
@@ -41,12 +32,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
||||||
|
|
||||||
@property() public computeLabel?: (
|
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
||||||
schema: HaFormSchema,
|
|
||||||
data?: HaFormDataContainer
|
|
||||||
) => string;
|
|
||||||
|
|
||||||
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
const root = this.shadowRoot?.querySelector(".root");
|
const root = this.shadowRoot?.querySelector(".root");
|
||||||
@@ -73,7 +59,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="root">
|
<div class="root">
|
||||||
${this.error && this.error.base
|
${this.error && this.error.base
|
||||||
@@ -85,7 +71,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.schema.map((item) => {
|
${this.schema.map((item) => {
|
||||||
const error = getValue(this.error, item);
|
const error = getValue(this.error, item);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${error
|
${error
|
||||||
? html`
|
? html`
|
||||||
@@ -100,19 +85,15 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${item.selector}
|
.selector=${item.selector}
|
||||||
.value=${getValue(this.data, item)}
|
.value=${getValue(this.data, item)}
|
||||||
.label=${this._computeLabel(item, this.data)}
|
.label=${this._computeLabel(item)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this._computeHelper(item)}
|
|
||||||
.required=${item.required || false}
|
.required=${item.required || false}
|
||||||
></ha-selector>`
|
></ha-selector>`
|
||||||
: dynamicElement(`ha-form-${item.type}`, {
|
: dynamicElement(`ha-form-${item.type}`, {
|
||||||
schema: item,
|
schema: item,
|
||||||
data: getValue(this.data, item),
|
data: getValue(this.data, item),
|
||||||
label: this._computeLabel(item, this.data),
|
label: this._computeLabel(item),
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
hass: this.hass,
|
|
||||||
computeLabel: this.computeLabel,
|
|
||||||
computeHelper: this.computeHelper,
|
|
||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
@@ -126,30 +107,21 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
root.addEventListener("value-changed", (ev) => {
|
root.addEventListener("value-changed", (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||||
|
|
||||||
const newValue = !schema.name
|
|
||||||
? ev.detail.value
|
|
||||||
: { [schema.name]: ev.detail.value };
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.data, ...newValue },
|
value: { ...this.data, [schema.name]: ev.detail.value },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) {
|
private _computeLabel(schema: HaFormSchema) {
|
||||||
return this.computeLabel
|
return this.computeLabel
|
||||||
? this.computeLabel(schema, data)
|
? this.computeLabel(schema)
|
||||||
: schema
|
: schema
|
||||||
? schema.name
|
? schema.name
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeHelper(schema: HaFormSchema) {
|
|
||||||
return this.computeHelper ? this.computeHelper(schema) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
||||||
return this.computeError ? this.computeError(error, schema) : error;
|
return this.computeError ? this.computeError(error, schema) : error;
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,7 @@ export type HaFormSchema =
|
|||||||
| HaFormSelectSchema
|
| HaFormSelectSchema
|
||||||
| HaFormMultiSelectSchema
|
| HaFormMultiSelectSchema
|
||||||
| HaFormTimeSchema
|
| HaFormTimeSchema
|
||||||
| HaFormSelector
|
| HaFormSelector;
|
||||||
| HaFormGridSchema;
|
|
||||||
|
|
||||||
export interface HaFormBaseSchema {
|
export interface HaFormBaseSchema {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -26,12 +25,6 @@ export interface HaFormBaseSchema {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HaFormGridSchema extends HaFormBaseSchema {
|
|
||||||
type: "grid";
|
|
||||||
name: "";
|
|
||||||
schema: HaFormSchema[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormSelector extends HaFormBaseSchema {
|
export interface HaFormSelector extends HaFormBaseSchema {
|
||||||
type?: never;
|
type?: never;
|
||||||
selector: Selector;
|
selector: Selector;
|
||||||
@@ -56,7 +49,7 @@ export interface HaFormSelectSchema extends HaFormBaseSchema {
|
|||||||
|
|
||||||
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
||||||
type: "multi_select";
|
type: "multi_select";
|
||||||
options: Record<string, string> | string[] | Array<[string, string]>;
|
options: Record<string, string> | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
||||||
|
@@ -43,8 +43,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _errorIsFatal = false;
|
|
||||||
|
|
||||||
private _hlsPolyfillInstance?: HlsLite;
|
private _hlsPolyfillInstance?: HlsLite;
|
||||||
|
|
||||||
private _exoPlayer = false;
|
private _exoPlayer = false;
|
||||||
@@ -55,7 +53,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
HaHLSPlayer.streamCount += 1;
|
HaHLSPlayer.streamCount += 1;
|
||||||
if (this.hasUpdated) {
|
if (this.hasUpdated) {
|
||||||
this._resetError();
|
|
||||||
this._startHls();
|
this._startHls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,23 +64,16 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
if (this._error) {
|
||||||
|
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
${this._error
|
<video
|
||||||
? html`<ha-alert
|
?autoplay=${this.autoPlay}
|
||||||
alert-type="error"
|
.muted=${this.muted}
|
||||||
class=${this._errorIsFatal ? "fatal" : "retry"}
|
?playsinline=${this.playsInline}
|
||||||
>
|
?controls=${this.controls}
|
||||||
${this._error}
|
></video>
|
||||||
</ha-alert>`
|
|
||||||
: ""}
|
|
||||||
${!this._errorIsFatal
|
|
||||||
? html`<video
|
|
||||||
?autoplay=${this.autoPlay}
|
|
||||||
.muted=${this.muted}
|
|
||||||
?playsinline=${this.playsInline}
|
|
||||||
?controls=${this.controls}
|
|
||||||
></video>`
|
|
||||||
: ""}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +87,12 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._cleanUp();
|
this._cleanUp();
|
||||||
this._resetError();
|
|
||||||
this._startHls();
|
this._startHls();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _startHls(): Promise<void> {
|
private async _startHls(): Promise<void> {
|
||||||
|
this._error = undefined;
|
||||||
|
|
||||||
const masterPlaylistPromise = fetch(this.url);
|
const masterPlaylistPromise = fetch(this.url);
|
||||||
|
|
||||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
||||||
@@ -119,8 +110,8 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hlsSupported) {
|
if (!hlsSupported) {
|
||||||
this._setFatalError(
|
this._error = this.hass.localize(
|
||||||
this.hass.localize("ui.components.media-browser.video_not_supported")
|
"ui.components.media-browser.video_not_supported"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -228,16 +219,9 @@ class HaHLSPlayer extends LitElement {
|
|||||||
this._hlsPolyfillInstance = hls;
|
this._hlsPolyfillInstance = hls;
|
||||||
hls.attachMedia(videoEl);
|
hls.attachMedia(videoEl);
|
||||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||||
this._resetError();
|
|
||||||
hls.loadSource(url);
|
hls.loadSource(url);
|
||||||
});
|
});
|
||||||
hls.on(Hls.Events.FRAG_LOADED, (_event, _data: any) => {
|
hls.on(Hls.Events.ERROR, (_, data: any) => {
|
||||||
this._resetError();
|
|
||||||
});
|
|
||||||
hls.on(Hls.Events.ERROR, (_event, data: any) => {
|
|
||||||
// Some errors are recovered automatically by the hls player itself, and the others handled
|
|
||||||
// in this function require special actions to recover. Errors retried in this function
|
|
||||||
// are done with backoff to not cause unecessary failures.
|
|
||||||
if (!data.fatal) {
|
if (!data.fatal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -257,22 +241,22 @@ class HaHLSPlayer extends LitElement {
|
|||||||
error += " (" + data.response.code + ")";
|
error += " (" + data.response.code + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._setRetryableError(error);
|
this._error = error;
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
||||||
this._setRetryableError("Timeout while starting stream");
|
this._error = "Timeout while starting stream";
|
||||||
break;
|
return;
|
||||||
default:
|
default:
|
||||||
this._setRetryableError("Stream network error");
|
this._error = "Unknown stream network error (" + data.details + ")";
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
hls.startLoad();
|
this._error = "Error with media stream contents (" + data.details + ")";
|
||||||
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
||||||
this._setRetryableError("Error with media stream contents");
|
this._error = "Error with media stream contents (" + data.details + ")";
|
||||||
hls.recoverMediaError();
|
|
||||||
} else {
|
} else {
|
||||||
this._setFatalError("Error playing stream");
|
this._error =
|
||||||
|
"Unknown error with stream (" + data.type + ", " + data.details + ")";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -300,21 +284,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _resetError() {
|
|
||||||
this._error = undefined;
|
|
||||||
this._errorIsFatal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setFatalError(errorMessage: string) {
|
|
||||||
this._error = errorMessage;
|
|
||||||
this._errorIsFatal = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setRetryableError(errorMessage: string) {
|
|
||||||
this._error = errorMessage;
|
|
||||||
this._errorIsFatal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host,
|
:host,
|
||||||
@@ -327,14 +296,10 @@ class HaHLSPlayer extends LitElement {
|
|||||||
max-height: var(--video-max-height, calc(100vh - 97px));
|
max-height: var(--video-max-height, calc(100vh - 97px));
|
||||||
}
|
}
|
||||||
|
|
||||||
.fatal {
|
ha-alert {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 100px 16px;
|
padding: 100px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.retry {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,145 +0,0 @@
|
|||||||
import "./ha-icon-button";
|
|
||||||
import "./ha-circular-progress";
|
|
||||||
import "@material/mwc-button/mwc-button";
|
|
||||||
import "./ha-card";
|
|
||||||
import "./ha-textfield";
|
|
||||||
import { LitElement, TemplateResult, html, CSSResultGroup, css } from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import { mdiClose } from "@mdi/js";
|
|
||||||
import type { HaTextField } from "./ha-textfield";
|
|
||||||
import type { HomeAssistant } from "../types";
|
|
||||||
import { LocalStorage } from "../common/decorators/local-storage";
|
|
||||||
|
|
||||||
@customElement("ha-newsletter")
|
|
||||||
class HaNewsletter extends LitElement {
|
|
||||||
@property({ attribute: false }) hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@query("ha-textfield")
|
|
||||||
private _emailField?: HaTextField;
|
|
||||||
|
|
||||||
@LocalStorage("dismissNewsletter", true)
|
|
||||||
private _dismissNewsletter = false;
|
|
||||||
|
|
||||||
private _requestStatus?: "inprogress" | "complete";
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (this._dismissNewsletter) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-card>
|
|
||||||
<div class="header">
|
|
||||||
${this.hass.localize("ui.newsletter.newsletter")}
|
|
||||||
<ha-icon-button
|
|
||||||
label="Dismiss"
|
|
||||||
.path=${mdiClose}
|
|
||||||
@click=${this._dismiss}
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
<div class="newsletter">
|
|
||||||
${this._requestStatus === "complete"
|
|
||||||
? html`<span>${this.hass.localize("ui.newsletter.thanks")}</span>`
|
|
||||||
: html`
|
|
||||||
<ha-textfield
|
|
||||||
required
|
|
||||||
type="email"
|
|
||||||
.label=${this.hass.localize("ui.newsletter.email")}
|
|
||||||
.validationMessage=${this.hass.localize(
|
|
||||||
"ui.newsletter.validation"
|
|
||||||
)}
|
|
||||||
></ha-textfield>
|
|
||||||
${this._requestStatus === "inprogress"
|
|
||||||
? html`
|
|
||||||
<ha-circular-progress
|
|
||||||
active
|
|
||||||
alt="Loading"
|
|
||||||
></ha-circular-progress>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<mwc-button
|
|
||||||
raised
|
|
||||||
.label=${this.hass.localize("ui.newsletter.subscribe")}
|
|
||||||
@click=${this._subscribe}
|
|
||||||
></mwc-button>
|
|
||||||
`}
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _subscribe(): void {
|
|
||||||
if (!this._emailField?.reportValidity()) {
|
|
||||||
this._emailField!.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._requestStatus = "inprogress";
|
|
||||||
|
|
||||||
fetch(
|
|
||||||
`https://newsletter.home-assistant.io/subscribe?email=${
|
|
||||||
this._emailField!.value
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this._requestStatus = "complete";
|
|
||||||
setTimeout(this._dismiss, 2000);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// Reset request so user can re-enter email
|
|
||||||
this._requestStatus = undefined;
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dismiss(): void {
|
|
||||||
this._dismissNewsletter = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.newsletter {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 0 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
color: var(--ha-card-header-color, --primary-text-color);
|
|
||||||
font-family: var(--ha-card-header-font-family, inherit);
|
|
||||||
font-size: var(--ha-card-header-font-size, 24px);
|
|
||||||
letter-spacing: -0.012em;
|
|
||||||
line-height: 48px;
|
|
||||||
padding: 12px 16px 16px;
|
|
||||||
margin-block-start: 0px;
|
|
||||||
margin-block-end: 0px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-textfield {
|
|
||||||
flex: 1;
|
|
||||||
display: block;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-button {
|
|
||||||
padding-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon-button {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-newsletter": HaNewsletter;
|
|
||||||
}
|
|
||||||
}
|
|
28
src/components/ha-paper-dropdown-menu.ts
Normal file
28
src/components/ha-paper-dropdown-menu.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
|
import { PolymerElement } from "@polymer/polymer";
|
||||||
|
import { Constructor } from "../types";
|
||||||
|
|
||||||
|
const paperDropdownClass = customElements.get(
|
||||||
|
"paper-dropdown-menu"
|
||||||
|
) as Constructor<PolymerElement>;
|
||||||
|
|
||||||
|
// patches paper drop down to properly support RTL - https://github.com/PolymerElements/paper-dropdown-menu/issues/183
|
||||||
|
export class HaPaperDropdownClass extends paperDropdownClass {
|
||||||
|
public ready() {
|
||||||
|
super.ready();
|
||||||
|
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.getComputedStyle(this).direction === "rtl") {
|
||||||
|
this.style.textAlign = "right";
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-paper-dropdown-menu": HaPaperDropdownClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("ha-paper-dropdown-menu", HaPaperDropdownClass);
|
@@ -1,4 +1,5 @@
|
|||||||
import { mdiImagePlus } from "@mdi/js";
|
import { mdiImagePlus } from "@mdi/js";
|
||||||
|
import "@polymer/paper-input/paper-input-container";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
@@ -35,12 +35,9 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
|
||||||
height: 56px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-formfield {
|
ha-formfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 16px 0;
|
||||||
--mdc-typography-body2-font-size: 1em;
|
--mdc-typography-body2-font-size: 1em;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
import "../ha-icon-picker";
|
|
||||||
import { html, LitElement } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { IconSelector } from "../../data/selector";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
|
|
||||||
@customElement("ha-selector-icon")
|
|
||||||
export class HaIconSelector extends LitElement {
|
|
||||||
@property() public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public selector!: IconSelector;
|
|
||||||
|
|
||||||
@property() public value?: string;
|
|
||||||
|
|
||||||
@property() public label?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<ha-icon-picker
|
|
||||||
.label=${this.label}
|
|
||||||
.value=${this.value}
|
|
||||||
.fallbackPath=${this.selector.icon.fallbackPath}
|
|
||||||
.placeholder=${this.selector.icon.placeholder}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-icon-picker>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
|
||||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-selector-icon": HaIconSelector;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,264 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,7 +2,7 @@ import { css, CSSResultGroup, html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
import { SelectOption, SelectSelector } from "../../data/selector";
|
import { SelectSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "@material/mwc-select/mwc-select";
|
import "@material/mwc-select/mwc-select";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
@@ -17,8 +17,6 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -27,17 +25,15 @@ export class HaSelectSelector extends LitElement {
|
|||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
@selected=${this._valueChanged}
|
@selected=${this._valueChanged}
|
||||||
>
|
>
|
||||||
${this.selector.select.options.map((item: string | SelectOption) => {
|
${this.selector.select.options.map(
|
||||||
const value = typeof item === "object" ? item.value : item;
|
(item: string) => html`
|
||||||
const label = typeof item === "object" ? item.label : item;
|
<mwc-list-item .value=${item}>${item}</mwc-list-item>
|
||||||
|
`
|
||||||
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
|
)}
|
||||||
})}
|
|
||||||
</mwc-select>`;
|
</mwc-select>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -69,13 +69,10 @@ export class HaTextSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev) {
|
private _handleChange(ev) {
|
||||||
let value = ev.target.value;
|
const value = ev.target.value;
|
||||||
if (this.value === value) {
|
if (this.value === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value === "" && !this.required) {
|
|
||||||
value = undefined;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", { value });
|
fireEvent(this, "value-changed", { value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
import "../../panels/lovelace/components/hui-theme-select-editor";
|
|
||||||
import { html, LitElement } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
|
||||||
import type { ThemeSelector } from "../../data/selector";
|
|
||||||
|
|
||||||
@customElement("ha-selector-theme")
|
|
||||||
export class HaThemeSelector extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public selector!: ThemeSelector;
|
|
||||||
|
|
||||||
@property() public value?: string;
|
|
||||||
|
|
||||||
@property() public label?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<hui-theme-select-editor
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this.value}
|
|
||||||
.label=${this.label}
|
|
||||||
></hui-theme-select-editor>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-selector-theme": HaThemeSelector;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -22,7 +22,6 @@ export class HaTimeSelector extends LitElement {
|
|||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.label=${this.label}
|
|
||||||
enable-second
|
enable-second
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
`;
|
`;
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import type { Selector } from "../../data/selector";
|
import { Selector } from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "./ha-selector-action";
|
import "./ha-selector-action";
|
||||||
import "./ha-selector-addon";
|
import "./ha-selector-addon";
|
||||||
import "./ha-selector-area";
|
import "./ha-selector-area";
|
||||||
@@ -17,9 +17,6 @@ import "./ha-selector-select";
|
|||||||
import "./ha-selector-target";
|
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-media";
|
|
||||||
import "./ha-selector-theme";
|
|
||||||
|
|
||||||
@customElement("ha-selector")
|
@customElement("ha-selector")
|
||||||
export class HaSelector extends LitElement {
|
export class HaSelector extends LitElement {
|
||||||
@@ -31,8 +28,6 @@ export class HaSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property() public placeholder?: any;
|
@property() public placeholder?: any;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -57,7 +52,6 @@ export class HaSelector extends LitElement {
|
|||||||
placeholder: this.placeholder,
|
placeholder: this.placeholder,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
required: this.required,
|
required: this.required,
|
||||||
helper: this.helper,
|
|
||||||
id: "selector",
|
id: "selector",
|
||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
|
@@ -489,6 +489,9 @@ export class HaServiceControl extends LitElement {
|
|||||||
margin: var(--service-control-padding, 0 16px);
|
margin: var(--service-control-padding, 0 16px);
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
:host(:not([narrow])) ha-settings-row paper-input {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
@@ -68,14 +68,6 @@ export class HaTextField extends TextFieldBase {
|
|||||||
:host([no-spinner]) input[type="number"] {
|
:host([no-spinner]) input[type="number"] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-text-field__ripple {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdc-text-field {
|
|
||||||
overflow: var(--text-field-overflow);
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -43,7 +43,7 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
|
|
||||||
private _remoteStream?: MediaStream;
|
private _remoteStream?: MediaStream;
|
||||||
|
|
||||||
protected override render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (this._error) {
|
if (this._error) {
|
||||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||||
}
|
}
|
||||||
@@ -58,19 +58,12 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override connectedCallback() {
|
public disconnectedCallback() {
|
||||||
super.connectedCallback();
|
|
||||||
if (this.hasUpdated) {
|
|
||||||
this._startWebRtc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._cleanUp();
|
this._cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override updated(changedProperties: PropertyValues<this>) {
|
protected updated(changedProperties: PropertyValues<this>) {
|
||||||
if (!changedProperties.has("entityid")) {
|
if (!changedProperties.has("entityid")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -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 = params.navigateIds || [
|
this._navigateIds = [
|
||||||
{
|
{
|
||||||
media_content_id: undefined,
|
media_content_id: this._params.mediaContentId,
|
||||||
media_content_type: undefined,
|
media_content_type: this._params.mediaContentType,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,280 +0,0 @@
|
|||||||
import "@material/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import { css, html, LitElement, PropertyValues } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
import { fetchCloudStatus, updateCloudPref } from "../../data/cloud";
|
|
||||||
import {
|
|
||||||
CloudTTSInfo,
|
|
||||||
getCloudTTSInfo,
|
|
||||||
getCloudTtsLanguages,
|
|
||||||
getCloudTtsSupportedGenders,
|
|
||||||
} from "../../data/cloud/tts";
|
|
||||||
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 {
|
|
||||||
@property() public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public item;
|
|
||||||
|
|
||||||
@property() public action!: MediaPlayerBrowseAction;
|
|
||||||
|
|
||||||
@state() private _cloudDefaultOptions?: [string, string];
|
|
||||||
|
|
||||||
@state() private _cloudOptions?: [string, string];
|
|
||||||
|
|
||||||
@state() private _cloudTTSInfo?: CloudTTSInfo;
|
|
||||||
|
|
||||||
@LocalStorage("cloudTtsTryMessage", true, false) private _message!: string;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
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.components.media-browser.tts.set_as_default"
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: html`<span></span>`}
|
|
||||||
|
|
||||||
<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 genders = this.getSupportedGenders(
|
|
||||||
selectedVoice[0],
|
|
||||||
this._cloudTTSInfo,
|
|
||||||
this.hass.localize
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="cloud-options">
|
|
||||||
<mwc-select
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.media-browser.tts.language"
|
|
||||||
)}
|
|
||||||
.value=${selectedVoice[0]}
|
|
||||||
@selected=${this._handleLanguageChange}
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
>
|
|
||||||
${languages.map(
|
|
||||||
([key, label]) =>
|
|
||||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
|
||||||
)}
|
|
||||||
</mwc-select>
|
|
||||||
|
|
||||||
<mwc-select
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
.label=${this.hass.localize("ui.components.media-browser.tts.gender")}
|
|
||||||
.value=${selectedVoice[1]}
|
|
||||||
@selected=${this._handleGenderChange}
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
>
|
|
||||||
${genders.map(
|
|
||||||
([key, label]) =>
|
|
||||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
|
||||||
)}
|
|
||||||
</mwc-select>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-rendering can reset message because textarea content is newer than local storage.
|
|
||||||
// But we don't want to write every keystroke to local storage.
|
|
||||||
// So instead we just do it when we're going to render.
|
|
||||||
const message = this.shadowRoot!.querySelector("ha-textarea")?.value;
|
|
||||||
if (message !== undefined && message !== this._message) {
|
|
||||||
this._message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _handleLanguageChange(ev) {
|
|
||||||
if (ev.target.value === this._cloudOptions![0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._cloudOptions = [ev.target.value, this._cloudOptions![1]];
|
|
||||||
}
|
|
||||||
|
|
||||||
async _handleGenderChange(ev) {
|
|
||||||
if (ev.target.value === this._cloudOptions![1]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._cloudOptions = [this._cloudOptions![0], ev.target.value];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getLanguages = memoizeOne(getCloudTtsLanguages);
|
|
||||||
|
|
||||||
private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders);
|
|
||||||
|
|
||||||
private get isCloudItem(): boolean {
|
|
||||||
return this.item.media_content_id.startsWith("media-source://tts/cloud");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _ttsClicked(): Promise<void> {
|
|
||||||
const message = this.shadowRoot!.querySelector("ha-textarea")!.value;
|
|
||||||
this._message = message;
|
|
||||||
const item = { ...this.item };
|
|
||||||
const query = new URLSearchParams();
|
|
||||||
query.append("message", message);
|
|
||||||
if (this._cloudOptions) {
|
|
||||||
query.append("language", this._cloudOptions[0]);
|
|
||||||
query.append("gender", this._cloudOptions[1]);
|
|
||||||
}
|
|
||||||
item.media_content_id = `${
|
|
||||||
item.media_content_id.split("?")[0]
|
|
||||||
}?${query.toString()}`;
|
|
||||||
item.can_play = true;
|
|
||||||
item.title = message;
|
|
||||||
fireEvent(this, "tts-picked", { item });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _storeDefaults() {
|
|
||||||
const oldDefaults = this._cloudDefaultOptions!;
|
|
||||||
this._cloudDefaultOptions = [...this._cloudOptions!];
|
|
||||||
try {
|
|
||||||
await updateCloudPref(this.hass, {
|
|
||||||
tts_default_voice: this._cloudDefaultOptions,
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
this._cloudDefaultOptions = oldDefaults;
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.components.media-browser.tts.faild_to_store_defaults",
|
|
||||||
{ error: err.message || err }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static override styles = [
|
|
||||||
buttonLinkStyle,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
margin: 16px auto;
|
|
||||||
padding: 0 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
.cloud-options {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.cloud-options mwc-select {
|
|
||||||
width: 48%;
|
|
||||||
}
|
|
||||||
ha-textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
button.link {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-browse-media-tts": BrowseMediaTTS;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +1,8 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list";
|
import "@material/mwc-list/mwc-list";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiArrowUpRight, mdiPlay, mdiPlus } from "@mdi/js";
|
import { mdiPlay, mdiPlus } from "@mdi/js";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -48,21 +49,11 @@ import "../ha-icon-button";
|
|||||||
import "../ha-svg-icon";
|
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 type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
|
||||||
import "./ha-browse-media-tts";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"media-picked": MediaPickedEvent;
|
"media-picked": MediaPickedEvent;
|
||||||
"media-browsed": {
|
"media-browsed": { ids: MediaPlayerItemId[]; current?: MediaPlayerItem };
|
||||||
// Items of the new browse stack
|
|
||||||
ids: MediaPlayerItemId[];
|
|
||||||
// Current fetched item for this browse stack
|
|
||||||
current?: MediaPlayerItem;
|
|
||||||
// If the new stack should replace the old stack
|
|
||||||
replace?: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,160 +246,159 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
${this._renderError(this._error)}
|
${this._renderError(this._error)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: isTTSMediaSource(currentItem.media_content_id)
|
: currentItem.children?.length
|
||||||
? html`
|
? childrenMediaClass.layout === "grid"
|
||||||
<ha-browse-media-tts
|
? html`
|
||||||
.item=${currentItem}
|
<div
|
||||||
.hass=${this.hass}
|
class="children ${classMap({
|
||||||
.action=${this.action}
|
portrait:
|
||||||
@tts-picked=${this._ttsPicked}
|
childrenMediaClass.thumbnail_ratio === "portrait",
|
||||||
></ha-browse-media-tts>
|
})}"
|
||||||
`
|
>
|
||||||
: !currentItem.children?.length
|
${currentItem.children.map(
|
||||||
? html`
|
(child) => html`
|
||||||
<div class="container no-items">
|
<div
|
||||||
${currentItem.media_content_id ===
|
class="child"
|
||||||
"media-source://media_source/local/."
|
.item=${child}
|
||||||
? html`
|
@click=${this._childClicked}
|
||||||
<div class="highlight-add-button">
|
>
|
||||||
<span>
|
<ha-card outlined>
|
||||||
<ha-svg-icon
|
<div class="thumbnail">
|
||||||
.path=${mdiArrowUpRight}
|
${child.thumbnail
|
||||||
></ha-svg-icon>
|
? html`
|
||||||
</span>
|
<div
|
||||||
<span>
|
class="${[
|
||||||
${this.hass.localize(
|
"app",
|
||||||
"ui.components.media-browser.file_management.highlight_button"
|
"directory",
|
||||||
)}
|
].includes(child.media_class)
|
||||||
</span>
|
? "centered-image"
|
||||||
|
: ""} image lazythumbnail"
|
||||||
|
data-src=${child.thumbnail}
|
||||||
|
></div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div class="icon-holder image">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="folder"
|
||||||
|
.path=${MediaClassBrowserSettings[
|
||||||
|
child.media_class === "directory"
|
||||||
|
? child.children_media_class ||
|
||||||
|
child.media_class
|
||||||
|
: child.media_class
|
||||||
|
].icon}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
${child.can_play
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
class="play ${classMap({
|
||||||
|
can_expand: child.can_expand,
|
||||||
|
})}"
|
||||||
|
.item=${child}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}-media`
|
||||||
|
)}
|
||||||
|
.path=${this.action === "play"
|
||||||
|
? mdiPlay
|
||||||
|
: mdiPlus}
|
||||||
|
@click=${this._actionClicked}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
${child.title}
|
||||||
|
<paper-tooltip
|
||||||
|
fitToVisibleBounds
|
||||||
|
position="top"
|
||||||
|
offset="4"
|
||||||
|
>${child.title}</paper-tooltip
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: this.hass.localize(
|
)}
|
||||||
"ui.components.media-browser.no_items"
|
</div>
|
||||||
)}
|
`
|
||||||
</div>
|
: html`
|
||||||
`
|
<mwc-list>
|
||||||
: childrenMediaClass.layout === "grid"
|
${currentItem.children.map(
|
||||||
? html`
|
(child) => html`
|
||||||
<div
|
<mwc-list-item
|
||||||
class="children ${classMap({
|
@click=${this._childClicked}
|
||||||
portrait:
|
.item=${child}
|
||||||
childrenMediaClass.thumbnail_ratio === "portrait",
|
.graphic=${mediaClass.show_list_images
|
||||||
})}"
|
? "medium"
|
||||||
>
|
: "avatar"}
|
||||||
${currentItem.children.map(
|
dir=${computeRTLDirection(this.hass)}
|
||||||
(child) => html`
|
|
||||||
<div
|
|
||||||
class="child"
|
|
||||||
.item=${child}
|
|
||||||
@click=${this._childClicked}
|
|
||||||
>
|
|
||||||
<ha-card outlined>
|
|
||||||
<div class="thumbnail">
|
|
||||||
${child.thumbnail
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="${["app", "directory"].includes(
|
|
||||||
child.media_class
|
|
||||||
)
|
|
||||||
? "centered-image"
|
|
||||||
: ""} image lazythumbnail"
|
|
||||||
data-src=${child.thumbnail}
|
|
||||||
></div>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<div class="icon-holder image">
|
|
||||||
<ha-svg-icon
|
|
||||||
class="folder"
|
|
||||||
.path=${MediaClassBrowserSettings[
|
|
||||||
child.media_class === "directory"
|
|
||||||
? child.children_media_class ||
|
|
||||||
child.media_class
|
|
||||||
: child.media_class
|
|
||||||
].icon}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
${child.can_play
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
class="play ${classMap({
|
|
||||||
can_expand: child.can_expand,
|
|
||||||
})}"
|
|
||||||
.item=${child}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}-media`
|
|
||||||
)}
|
|
||||||
.path=${this.action === "play"
|
|
||||||
? mdiPlay
|
|
||||||
: mdiPlus}
|
|
||||||
@click=${this._actionClicked}
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
<div class="title">
|
|
||||||
${child.title}
|
|
||||||
<paper-tooltip
|
|
||||||
fitToVisibleBounds
|
|
||||||
position="top"
|
|
||||||
offset="4"
|
|
||||||
>${child.title}</paper-tooltip
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<mwc-list>
|
|
||||||
${currentItem.children.map(
|
|
||||||
(child) => html`
|
|
||||||
<mwc-list-item
|
|
||||||
@click=${this._childClicked}
|
|
||||||
.item=${child}
|
|
||||||
.graphic=${mediaClass.show_list_images
|
|
||||||
? "medium"
|
|
||||||
: "avatar"}
|
|
||||||
dir=${computeRTLDirection(this.hass)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=${classMap({
|
|
||||||
graphic: true,
|
|
||||||
lazythumbnail:
|
|
||||||
mediaClass.show_list_images === true,
|
|
||||||
})}
|
|
||||||
data-src=${ifDefined(
|
|
||||||
mediaClass.show_list_images && child.thumbnail
|
|
||||||
? child.thumbnail
|
|
||||||
: undefined
|
|
||||||
)}
|
|
||||||
slot="graphic"
|
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<div
|
||||||
class="play ${classMap({
|
class=${classMap({
|
||||||
show:
|
graphic: true,
|
||||||
!mediaClass.show_list_images ||
|
lazythumbnail:
|
||||||
!child.thumbnail,
|
mediaClass.show_list_images === true,
|
||||||
})}"
|
})}
|
||||||
.item=${child}
|
data-src=${ifDefined(
|
||||||
.label=${this.hass.localize(
|
mediaClass.show_list_images && child.thumbnail
|
||||||
`ui.components.media-browser.${this.action}-media`
|
? child.thumbnail
|
||||||
|
: undefined
|
||||||
)}
|
)}
|
||||||
.path=${this.action === "play"
|
slot="graphic"
|
||||||
? mdiPlay
|
>
|
||||||
: mdiPlus}
|
<ha-icon-button
|
||||||
@click=${this._actionClicked}
|
class="play ${classMap({
|
||||||
></ha-icon-button>
|
show:
|
||||||
</div>
|
!mediaClass.show_list_images ||
|
||||||
<span class="title">${child.title}</span>
|
!child.thumbnail,
|
||||||
</mwc-list-item>
|
})}"
|
||||||
<li divider role="separator"></li>
|
.item=${child}
|
||||||
`
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}-media`
|
||||||
|
)}
|
||||||
|
.path=${this.action === "play"
|
||||||
|
? mdiPlay
|
||||||
|
: mdiPlus}
|
||||||
|
@click=${this._actionClicked}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
<span class="title">${child.title}</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
<li divider role="separator"></li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</mwc-list>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div class="container no-items">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.no_items"
|
||||||
)}
|
)}
|
||||||
</mwc-list>
|
<br />
|
||||||
|
${currentItem.media_content_id ===
|
||||||
|
"media-source://media_source/local/."
|
||||||
|
? html`<br />${this.hass.localize(
|
||||||
|
"ui.components.media-browser.learn_adding_local_media",
|
||||||
|
"documentation",
|
||||||
|
html`<a
|
||||||
|
href=${documentationUrl(
|
||||||
|
this.hass,
|
||||||
|
"/more-info/local-media/add-media"
|
||||||
|
)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.media-browser.documentation"
|
||||||
|
)}</a
|
||||||
|
>`
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.local_media_files"
|
||||||
|
)}`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -435,8 +425,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
if (changedProps.has("entityId")) {
|
if (changedProps.has("entityId")) {
|
||||||
this._setError(undefined);
|
this._setError(undefined);
|
||||||
} else if (!changedProps.has("navigateIds")) {
|
}
|
||||||
// Neither entity ID or navigateIDs changed, nothing to fetch
|
if (!changedProps.has("navigateIds")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +435,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
const oldNavigateIds = changedProps.get("navigateIds") as
|
const oldNavigateIds = changedProps.get("navigateIds") as
|
||||||
| this["navigateIds"]
|
| this["navigateIds"]
|
||||||
| undefined;
|
| undefined;
|
||||||
const navigateIds = this.navigateIds;
|
|
||||||
|
|
||||||
// We're navigating. Reset the shizzle.
|
// We're navigating. Reset the shizzle.
|
||||||
this._content?.scrollTo(0, 0);
|
this._content?.scrollTo(0, 0);
|
||||||
@@ -454,9 +443,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
const oldParentItem = this._parentItem;
|
const oldParentItem = this._parentItem;
|
||||||
this._currentItem = undefined;
|
this._currentItem = undefined;
|
||||||
this._parentItem = undefined;
|
this._parentItem = undefined;
|
||||||
const currentId = navigateIds[navigateIds.length - 1];
|
const currentId = this.navigateIds[this.navigateIds.length - 1];
|
||||||
const parentId =
|
const parentId =
|
||||||
navigateIds.length > 1 ? navigateIds[navigateIds.length - 2] : undefined;
|
this.navigateIds.length > 1
|
||||||
|
? this.navigateIds[this.navigateIds.length - 2]
|
||||||
|
: undefined;
|
||||||
let currentProm: Promise<MediaPlayerItem> | undefined;
|
let currentProm: Promise<MediaPlayerItem> | undefined;
|
||||||
let parentProm: Promise<MediaPlayerItem> | undefined;
|
let parentProm: Promise<MediaPlayerItem> | undefined;
|
||||||
|
|
||||||
@@ -465,9 +456,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
if (
|
if (
|
||||||
// Check if we navigated to a child
|
// Check if we navigated to a child
|
||||||
oldNavigateIds &&
|
oldNavigateIds &&
|
||||||
navigateIds.length === oldNavigateIds.length + 1 &&
|
this.navigateIds.length > oldNavigateIds.length &&
|
||||||
oldNavigateIds.every((oldVal, idx) => {
|
oldNavigateIds.every((oldVal, idx) => {
|
||||||
const curVal = navigateIds[idx];
|
const curVal = this.navigateIds[idx];
|
||||||
return (
|
return (
|
||||||
curVal.media_content_id === oldVal.media_content_id &&
|
curVal.media_content_id === oldVal.media_content_id &&
|
||||||
curVal.media_content_type === oldVal.media_content_type
|
curVal.media_content_type === oldVal.media_content_type
|
||||||
@@ -478,8 +469,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
} else if (
|
} else if (
|
||||||
// Check if we navigated to a parent
|
// Check if we navigated to a parent
|
||||||
oldNavigateIds &&
|
oldNavigateIds &&
|
||||||
navigateIds.length === oldNavigateIds.length - 1 &&
|
this.navigateIds.length < oldNavigateIds.length &&
|
||||||
navigateIds.every((curVal, idx) => {
|
this.navigateIds.every((curVal, idx) => {
|
||||||
const oldVal = oldNavigateIds[idx];
|
const oldVal = oldNavigateIds[idx];
|
||||||
return (
|
return (
|
||||||
curVal.media_content_id === oldVal.media_content_id &&
|
curVal.media_content_id === oldVal.media_content_id &&
|
||||||
@@ -502,33 +493,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
(item) => {
|
(item) => {
|
||||||
this._currentItem = item;
|
this._currentItem = item;
|
||||||
fireEvent(this, "media-browsed", {
|
fireEvent(this, "media-browsed", {
|
||||||
ids: navigateIds,
|
ids: this.navigateIds,
|
||||||
current: item,
|
current: item,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => this._setError(err)
|
||||||
// When we change entity ID, we will first try to see if the new entity is
|
|
||||||
// able to resolve the new path. If that results in an error, browse the root.
|
|
||||||
const isNewEntityWithSamePath =
|
|
||||||
oldNavigateIds &&
|
|
||||||
changedProps.has("entityId") &&
|
|
||||||
navigateIds.length === oldNavigateIds.length &&
|
|
||||||
oldNavigateIds.every(
|
|
||||||
(oldItem, idx) =>
|
|
||||||
navigateIds[idx].media_content_id === oldItem.media_content_id &&
|
|
||||||
navigateIds[idx].media_content_type === oldItem.media_content_type
|
|
||||||
);
|
|
||||||
if (isNewEntityWithSamePath) {
|
|
||||||
fireEvent(this, "media-browsed", {
|
|
||||||
ids: [
|
|
||||||
{ media_content_id: undefined, media_content_type: undefined },
|
|
||||||
],
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._setError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
// Fetch parent
|
// Fetch parent
|
||||||
if (!parentProm && parentId !== undefined) {
|
if (!parentProm && parentId !== undefined) {
|
||||||
@@ -564,17 +533,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _runAction(item: MediaPlayerItem): void {
|
private _runAction(item: MediaPlayerItem): void {
|
||||||
fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds });
|
fireEvent(this, "media-picked", { item });
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
||||||
@@ -777,18 +736,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
padding-left: 32px;
|
padding-left: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight-add-button {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
margin-right: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight-add-button ha-svg-icon {
|
|
||||||
position: relative;
|
|
||||||
top: -0.5em;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@@ -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;
|
||||||
navigateIds?: MediaPlayerItemId[];
|
mediaContentId?: string;
|
||||||
|
mediaContentType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showMediaBrowserDialog = (
|
export const showMediaBrowserDialog = (
|
||||||
|
@@ -47,7 +47,6 @@ export interface CloudPreferences {
|
|||||||
export interface CloudStatusLoggedIn {
|
export interface CloudStatusLoggedIn {
|
||||||
logged_in: true;
|
logged_in: true;
|
||||||
cloud: "disconnected" | "connecting" | "connected";
|
cloud: "disconnected" | "connecting" | "connected";
|
||||||
cloud_last_disconnect_reason: { clean: boolean; reason: string } | null;
|
|
||||||
email: string;
|
email: string;
|
||||||
google_registered: boolean;
|
google_registered: boolean;
|
||||||
google_entities: EntityFilter;
|
google_entities: EntityFilter;
|
||||||
@@ -187,3 +186,10 @@ export const updateCloudAlexaEntityConfig = (
|
|||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
...values,
|
...values,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface CloudTTSInfo {
|
||||||
|
languages: Array<[string, string]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCloudTTSInfo = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<CloudTTSInfo>({ type: "cloud/tts/info" });
|
||||||
|
@@ -1,70 +0,0 @@
|
|||||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
|
||||||
import { LocalizeFunc } from "../../common/translations/localize";
|
|
||||||
import { translationMetadata } from "../../resources/translations-metadata";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
|
|
||||||
export interface CloudTTSInfo {
|
|
||||||
languages: Array<[string, string]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCloudTTSInfo = (hass: HomeAssistant) =>
|
|
||||||
hass.callWS<CloudTTSInfo>({ type: "cloud/tts/info" });
|
|
||||||
|
|
||||||
export const getCloudTtsLanguages = (info?: CloudTTSInfo) => {
|
|
||||||
const languages: Array<[string, string]> = [];
|
|
||||||
|
|
||||||
if (!info) {
|
|
||||||
return languages;
|
|
||||||
}
|
|
||||||
|
|
||||||
const seen = new Set<string>();
|
|
||||||
for (const [lang] of info.languages) {
|
|
||||||
if (seen.has(lang)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(lang);
|
|
||||||
|
|
||||||
let label = lang;
|
|
||||||
|
|
||||||
if (lang in translationMetadata.translations) {
|
|
||||||
label = translationMetadata.translations[lang].nativeName;
|
|
||||||
} else {
|
|
||||||
const [langFamily, dialect] = lang.split("-");
|
|
||||||
if (langFamily in translationMetadata.translations) {
|
|
||||||
label = `${translationMetadata.translations[langFamily].nativeName}`;
|
|
||||||
|
|
||||||
if (langFamily.toLowerCase() !== dialect.toLowerCase()) {
|
|
||||||
label += ` (${dialect})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
languages.push([lang, label]);
|
|
||||||
}
|
|
||||||
return languages.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCloudTtsSupportedGenders = (
|
|
||||||
language: string,
|
|
||||||
info: CloudTTSInfo | undefined,
|
|
||||||
localize: LocalizeFunc
|
|
||||||
) => {
|
|
||||||
const genders: Array<[string, string]> = [];
|
|
||||||
|
|
||||||
if (!info) {
|
|
||||||
return genders;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [curLang, gender] of info.languages) {
|
|
||||||
if (curLang === language) {
|
|
||||||
genders.push([
|
|
||||||
gender,
|
|
||||||
localize(`ui.panel.media-browser.tts.gender_${gender}`) ||
|
|
||||||
localize(`ui.panel.config.cloud.account.tts.${gender}`) ||
|
|
||||||
gender,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
|
|
||||||
};
|
|
@@ -13,7 +13,6 @@ export interface ConfigEntry {
|
|||||||
| "not_loaded"
|
| "not_loaded"
|
||||||
| "failed_unload";
|
| "failed_unload";
|
||||||
supports_options: boolean;
|
supports_options: boolean;
|
||||||
supports_remove_device: boolean;
|
|
||||||
supports_unload: boolean;
|
supports_unload: boolean;
|
||||||
pref_disable_new_entities: boolean;
|
pref_disable_new_entities: boolean;
|
||||||
pref_disable_polling: boolean;
|
pref_disable_polling: boolean;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
@@ -78,26 +77,12 @@ export const updateDeviceRegistryEntry = (
|
|||||||
...updates,
|
...updates,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const removeConfigEntryFromDevice = (
|
export const fetchDeviceRegistry = (conn) =>
|
||||||
hass: HomeAssistant,
|
conn.sendMessagePromise({
|
||||||
deviceId: string,
|
|
||||||
configEntryId: string
|
|
||||||
) =>
|
|
||||||
hass.callWS<DeviceRegistryEntry>({
|
|
||||||
type: "config/device_registry/remove_config_entry",
|
|
||||||
device_id: deviceId,
|
|
||||||
config_entry_id: configEntryId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchDeviceRegistry = (conn: Connection) =>
|
|
||||||
conn.sendMessagePromise<DeviceRegistryEntry[]>({
|
|
||||||
type: "config/device_registry/list",
|
type: "config/device_registry/list",
|
||||||
});
|
});
|
||||||
|
|
||||||
const subscribeDeviceRegistryUpdates = (
|
const subscribeDeviceRegistryUpdates = (conn, store) =>
|
||||||
conn: Connection,
|
|
||||||
store: Store<DeviceRegistryEntry[]>
|
|
||||||
) =>
|
|
||||||
conn.subscribeEvents(
|
conn.subscribeEvents(
|
||||||
debounce(
|
debounce(
|
||||||
() =>
|
() =>
|
||||||
|
@@ -84,10 +84,9 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
|||||||
options: Record<string, unknown>;
|
options: Record<string, unknown>;
|
||||||
privileged: any;
|
privileged: any;
|
||||||
protected: boolean;
|
protected: boolean;
|
||||||
rating: "1-8";
|
rating: "1-6";
|
||||||
schema: HaFormSchema[] | null;
|
schema: HaFormSchema[] | null;
|
||||||
services_role: string[];
|
services_role: string[];
|
||||||
signed: boolean;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
startup: AddonStartup;
|
startup: AddonStartup;
|
||||||
stdin: boolean;
|
stdin: boolean;
|
||||||
|
@@ -28,13 +28,11 @@ 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";
|
||||||
|
|
||||||
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||||
media_content_id?: string;
|
media_content_type?: any;
|
||||||
media_content_type?: string;
|
|
||||||
media_artist?: string;
|
media_artist?: string;
|
||||||
media_playlist?: string;
|
media_playlist?: string;
|
||||||
media_series_title?: string;
|
media_series_title?: string;
|
||||||
@@ -149,7 +147,6 @@ export const MediaClassBrowserSettings: {
|
|||||||
|
|
||||||
export interface MediaPickedEvent {
|
export interface MediaPickedEvent {
|
||||||
item: MediaPlayerItem;
|
item: MediaPlayerItem;
|
||||||
navigateIds: MediaPlayerItemId[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaPlayerThumbnail {
|
export interface MediaPlayerThumbnail {
|
||||||
@@ -264,10 +261,8 @@ export const computeMediaControls = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(state === "playing" || state === "paused" || assumedState) &&
|
(state === "playing" || state === "paused") &&
|
||||||
supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
||||||
) {
|
) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
@@ -277,15 +272,14 @@ export const computeMediaControls = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!assumedState &&
|
(state === "playing" &&
|
||||||
((state === "playing" &&
|
|
||||||
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||||
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
||||||
((state === "paused" || state === "idle") &&
|
((state === "paused" || state === "idle") &&
|
||||||
supportsFeature(stateObj, SUPPORT_PLAY)) ||
|
supportsFeature(stateObj, SUPPORT_PLAY)) ||
|
||||||
(state === "on" &&
|
(state === "on" &&
|
||||||
(supportsFeature(stateObj, SUPPORT_PLAY) ||
|
(supportsFeature(stateObj, SUPPORT_PLAY) ||
|
||||||
supportsFeature(stateObj, SUPPORT_PAUSE))))
|
supportsFeature(stateObj, SUPPORT_PAUSE)))
|
||||||
) {
|
) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
icon:
|
icon:
|
||||||
@@ -305,29 +299,8 @@ export const computeMediaControls = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assumedState && supportsFeature(stateObj, SUPPORT_PLAY)) {
|
|
||||||
buttons.push({
|
|
||||||
icon: mdiPlay,
|
|
||||||
action: "media_play",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assumedState && supportsFeature(stateObj, SUPPORT_PAUSE)) {
|
|
||||||
buttons.push({
|
|
||||||
icon: mdiPause,
|
|
||||||
action: "media_pause",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assumedState && supportsFeature(stateObj, SUPPORT_STOP)) {
|
|
||||||
buttons.push({
|
|
||||||
icon: mdiStop,
|
|
||||||
action: "media_stop",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(state === "playing" || state === "paused" || assumedState) &&
|
(state === "playing" || state === "paused") &&
|
||||||
supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
||||||
) {
|
) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
@@ -340,7 +313,7 @@ export const computeMediaControls = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const formatMediaTime = (seconds: number | undefined): string => {
|
export const formatMediaTime = (seconds: number | undefined): string => {
|
||||||
if (seconds === undefined || seconds === Infinity) {
|
if (seconds === undefined) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,7 +22,6 @@ export interface MQTTEntityDebugInfo {
|
|||||||
entity_id: string;
|
entity_id: string;
|
||||||
discovery_data: MQTTDiscoveryDebugInfo;
|
discovery_data: MQTTDiscoveryDebugInfo;
|
||||||
subscriptions: MQTTTopicDebugInfo[];
|
subscriptions: MQTTTopicDebugInfo[];
|
||||||
transmitted: MQTTTopicDebugInfo[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MQTTTriggerDebugInfo {
|
export interface MQTTTriggerDebugInfo {
|
||||||
@@ -44,6 +43,15 @@ export const subscribeMQTTTopic = (
|
|||||||
topic,
|
topic,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const removeMQTTDeviceEntry = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
deviceId: string
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "mqtt/device/remove",
|
||||||
|
device_id: deviceId,
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchMQTTDebugInfo = (
|
export const fetchMQTTDebugInfo = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
deviceId: string
|
deviceId: string
|
||||||
|
@@ -3,17 +3,6 @@ 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";
|
||||||
@@ -23,48 +12,6 @@ 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;
|
||||||
@@ -101,12 +48,11 @@ export interface ServiceAction {
|
|||||||
service_template?: string;
|
service_template?: string;
|
||||||
entity_id?: string;
|
entity_id?: string;
|
||||||
target?: HassServiceTarget;
|
target?: HassServiceTarget;
|
||||||
data?: Record<string, unknown>;
|
data?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -124,18 +70,10 @@ export interface DelayAction {
|
|||||||
delay: number | Partial<DelayActionParts> | string;
|
delay: number | Partial<DelayActionParts> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceSceneAction {
|
export interface SceneAction {
|
||||||
alias?: string;
|
|
||||||
service: "scene.turn_on";
|
|
||||||
target?: { entity_id?: string };
|
|
||||||
entity_id?: string;
|
|
||||||
metadata: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
export interface LegacySceneAction {
|
|
||||||
alias?: string;
|
alias?: string;
|
||||||
scene: string;
|
scene: string;
|
||||||
}
|
}
|
||||||
export type SceneAction = ServiceSceneAction | LegacySceneAction;
|
|
||||||
|
|
||||||
export interface WaitAction {
|
export interface WaitAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
@@ -151,15 +89,6 @@ 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;
|
||||||
@@ -216,7 +145,6 @@ export type Action =
|
|||||||
| RepeatAction
|
| RepeatAction
|
||||||
| ChooseAction
|
| ChooseAction
|
||||||
| VariablesAction
|
| VariablesAction
|
||||||
| PlayMediaAction
|
|
||||||
| UnknownAction;
|
| UnknownAction;
|
||||||
|
|
||||||
export interface ActionTypes {
|
export interface ActionTypes {
|
||||||
@@ -231,7 +159,6 @@ export interface ActionTypes {
|
|||||||
wait_for_trigger: WaitForTriggerAction;
|
wait_for_trigger: WaitForTriggerAction;
|
||||||
variables: VariablesAction;
|
variables: VariablesAction;
|
||||||
service: ServiceAction;
|
service: ServiceAction;
|
||||||
play_media: PlayMediaAction;
|
|
||||||
unknown: UnknownAction;
|
unknown: UnknownAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,14 +233,6 @@ export const getActionType = (action: Action): ActionType => {
|
|||||||
return "variables";
|
return "variables";
|
||||||
}
|
}
|
||||||
if ("service" in action) {
|
if ("service" in action) {
|
||||||
if ("metadata" in action) {
|
|
||||||
if (is(action, activateSceneActionStruct)) {
|
|
||||||
return "activate_scene";
|
|
||||||
}
|
|
||||||
if (is(action, playMediaActionStruct)) {
|
|
||||||
return "play_media";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "service";
|
return "service";
|
||||||
}
|
}
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
@@ -9,10 +9,8 @@ import {
|
|||||||
ActionType,
|
ActionType,
|
||||||
ActionTypes,
|
ActionTypes,
|
||||||
DelayAction,
|
DelayAction,
|
||||||
DeviceAction,
|
|
||||||
EventAction,
|
EventAction,
|
||||||
getActionType,
|
getActionType,
|
||||||
PlayMediaAction,
|
|
||||||
SceneAction,
|
SceneAction,
|
||||||
VariablesAction,
|
VariablesAction,
|
||||||
WaitForTriggerAction,
|
WaitForTriggerAction,
|
||||||
@@ -106,30 +104,9 @@ export const describeAction = <T extends ActionType>(
|
|||||||
|
|
||||||
if (actionType === "activate_scene") {
|
if (actionType === "activate_scene") {
|
||||||
const config = action as SceneAction;
|
const config = action as SceneAction;
|
||||||
let entityId: string | undefined;
|
const sceneStateObj = hass.states[config.scene];
|
||||||
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
|
sceneStateObj ? computeStateName(sceneStateObj) : config.scene
|
||||||
? computeStateName(sceneStateObj)
|
|
||||||
: "scene" in config
|
|
||||||
? config.scene
|
|
||||||
: config.target?.entity_id || config.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
|
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,13 +138,5 @@ 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;
|
||||||
};
|
};
|
||||||
|
@@ -12,10 +12,7 @@ export type Selector =
|
|||||||
| ActionSelector
|
| ActionSelector
|
||||||
| StringSelector
|
| StringSelector
|
||||||
| ObjectSelector
|
| ObjectSelector
|
||||||
| SelectSelector
|
| SelectSelector;
|
||||||
| IconSelector
|
|
||||||
| MediaSelector
|
|
||||||
| ThemeSelector;
|
|
||||||
|
|
||||||
export interface EntitySelector {
|
export interface EntitySelector {
|
||||||
entity: {
|
entity: {
|
||||||
@@ -136,43 +133,8 @@ export interface ObjectSelector {
|
|||||||
object: {};
|
object: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectOption {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectSelector {
|
export interface SelectSelector {
|
||||||
select: {
|
select: {
|
||||||
options: string[] | SelectOption[];
|
options: string[];
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IconSelector {
|
|
||||||
icon: {
|
|
||||||
placeholder?: string;
|
|
||||||
fallbackPath?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ThemeSelector {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
theme: {};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }[];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
10
src/data/tasmota.ts
Normal file
10
src/data/tasmota.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export const removeTasmotaDeviceEntry = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
deviceId: string
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "tasmota/device/remove",
|
||||||
|
device_id: deviceId,
|
||||||
|
});
|
@@ -10,11 +10,3 @@ export const convertTextToSpeech = (
|
|||||||
options?: Record<string, unknown>;
|
options?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
) => hass.callApi<{ url: string; path: string }>("POST", "tts_get_url", data);
|
) => hass.callApi<{ url: string; path: string }>("POST", "tts_get_url", data);
|
||||||
|
|
||||||
const TTS_MEDIA_SOURCE_PREFIX = "media-source://tts/";
|
|
||||||
|
|
||||||
export const isTTSMediaSource = (mediaContentId: string) =>
|
|
||||||
mediaContentId.startsWith(TTS_MEDIA_SOURCE_PREFIX);
|
|
||||||
|
|
||||||
export const getProviderFromTTSMediaSource = (mediaContentId: string) =>
|
|
||||||
mediaContentId.substring(TTS_MEDIA_SOURCE_PREFIX.length);
|
|
||||||
|
@@ -2,19 +2,6 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { DeviceRegistryEntry } from "./device_registry";
|
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 {
|
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.
|
* Always uses Security S2 if supported, otherwise uses Security S0 for certain devices which don't work without encryption and uses no encryption otherwise.
|
||||||
@@ -119,33 +106,16 @@ export interface ZWaveJSNetwork {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ZWaveJSClient {
|
export interface ZWaveJSClient {
|
||||||
state: "connected" | "disconnected";
|
state: string;
|
||||||
ws_server_url: string;
|
ws_server_url: string;
|
||||||
server_version: string;
|
server_version: string;
|
||||||
driver_version: string;
|
driver_version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZWaveJSController {
|
export interface ZWaveJSController {
|
||||||
home_id: number;
|
home_id: string;
|
||||||
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[];
|
nodes: number[];
|
||||||
|
is_heal_network_active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZWaveJSNodeStatus {
|
export interface ZWaveJSNodeStatus {
|
||||||
@@ -156,7 +126,6 @@ export interface ZWaveJSNodeStatus {
|
|||||||
is_routing: boolean | null;
|
is_routing: boolean | null;
|
||||||
zwave_plus_version: number | null;
|
zwave_plus_version: number | null;
|
||||||
highest_security_class: SecurityClass | null;
|
highest_security_class: SecurityClass | null;
|
||||||
is_controller_node: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZwaveJSNodeMetadata {
|
export interface ZwaveJSNodeMetadata {
|
||||||
@@ -339,12 +308,6 @@ export const stopZwaveInclusion = (hass: HomeAssistant, entry_id: string) =>
|
|||||||
entry_id,
|
entry_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const stopZwaveExclusion = (hass: HomeAssistant, entry_id: string) =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "zwave_js/stop_exclusion",
|
|
||||||
entry_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zwaveGrantSecurityClasses = (
|
export const zwaveGrantSecurityClasses = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
|
@@ -83,7 +83,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
|||||||
.checked=${!this._disableNewEntities}
|
.checked=${!this._disableNewEntities}
|
||||||
@change=${this._disableNewEntitiesChanged}
|
@change=${this._disableNewEntitiesChanged}
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting}
|
||||||
dialogInitialFocus
|
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
${this._allowUpdatePolling()
|
${this._allowUpdatePolling()
|
||||||
|
@@ -47,7 +47,6 @@ class StepFlowForm extends LitElement {
|
|||||||
? html`<ha-alert alert-type="error">${this._errorMsg}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._errorMsg}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
|
||||||
.data=${stepData}
|
.data=${stepData}
|
||||||
.disabled=${this._loading}
|
.disabled=${this._loading}
|
||||||
@value-changed=${this._stepDataChanged}
|
@value-changed=${this._stepDataChanged}
|
||||||
|
@@ -1,36 +1,30 @@
|
|||||||
import "@material/mwc-list/mwc-list";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
PropertyValues,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { navigate } from "../../common/navigate";
|
|
||||||
import "../../common/search/search-input";
|
import "../../common/search/search-input";
|
||||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../common/translations/localize";
|
import { LocalizeFunc } from "../../common/translations/localize";
|
||||||
import "../../components/ha-icon-next";
|
import "../../components/ha-icon-next";
|
||||||
import { getConfigEntries } from "../../data/config_entries";
|
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { brandsUrl } from "../../util/brands-url";
|
import { brandsUrl } from "../../util/brands-url";
|
||||||
import { documentationUrl } from "../../util/documentation-url";
|
import { documentationUrl } from "../../util/documentation-url";
|
||||||
import { configFlowContentStyles } from "./styles";
|
import { configFlowContentStyles } from "./styles";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
|
||||||
interface HandlerObj {
|
interface HandlerObj {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
is_add?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -82,17 +76,6 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const handlers = this._getHandlers();
|
const handlers = this._getHandlers();
|
||||||
|
|
||||||
const addDeviceRows: HandlerObj[] = ["zha", "zwave_js"]
|
|
||||||
.filter((domain) => isComponentLoaded(this.hass, domain))
|
|
||||||
.map((domain) => ({
|
|
||||||
name: this.hass.localize(
|
|
||||||
`ui.panel.config.integrations.add_${domain}_device`
|
|
||||||
),
|
|
||||||
slug: domain,
|
|
||||||
is_add: true,
|
|
||||||
}))
|
|
||||||
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name));
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
|
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
|
||||||
<search-input
|
<search-input
|
||||||
@@ -103,20 +86,39 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
.label=${this.hass.localize("ui.panel.config.integrations.search")}
|
.label=${this.hass.localize("ui.panel.config.integrations.search")}
|
||||||
@keypress=${this._maybeSubmit}
|
@keypress=${this._maybeSubmit}
|
||||||
></search-input>
|
></search-input>
|
||||||
<mwc-list
|
<div
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
width: `${this._width}px`,
|
width: `${this._width}px`,
|
||||||
height: `${this._height}px`,
|
height: `${this._height}px`,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${addDeviceRows.length
|
|
||||||
? html`
|
|
||||||
${addDeviceRows.map((handler) => this._renderRow(handler))}
|
|
||||||
<li divider padded class="divider" role="separator"></li>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${handlers.length
|
${handlers.length
|
||||||
? handlers.map((handler) => this._renderRow(handler))
|
? handlers.map(
|
||||||
|
(handler: HandlerObj) =>
|
||||||
|
html`
|
||||||
|
<mwc-list-item
|
||||||
|
graphic="medium"
|
||||||
|
hasMeta
|
||||||
|
@click=${this._handlerPicked}
|
||||||
|
.handler=${handler}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
slot="graphic"
|
||||||
|
loading="lazy"
|
||||||
|
src=${brandsUrl({
|
||||||
|
domain: handler.slug,
|
||||||
|
type: "icon",
|
||||||
|
useFallback: true,
|
||||||
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
|
})}
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
/>
|
||||||
|
|
||||||
|
${handler.name}
|
||||||
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
|
</mwc-list-item>
|
||||||
|
`
|
||||||
|
)
|
||||||
: html`
|
: html`
|
||||||
<p>
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@@ -139,32 +141,7 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
`}
|
`}
|
||||||
</mwc-list>
|
</div>
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderRow(handler: HandlerObj) {
|
|
||||||
return html`
|
|
||||||
<mwc-list-item
|
|
||||||
graphic="medium"
|
|
||||||
.hasMeta=${!handler.is_add}
|
|
||||||
.handler=${handler}
|
|
||||||
@click=${this._handlerPicked}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
slot="graphic"
|
|
||||||
loading="lazy"
|
|
||||||
src=${brandsUrl({
|
|
||||||
domain: handler.slug,
|
|
||||||
type: "icon",
|
|
||||||
useFallback: true,
|
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
|
||||||
})}
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
/>
|
|
||||||
<span>${handler.name}</span>
|
|
||||||
${handler.is_add ? "" : html`<ha-icon-next slot="meta"></ha-icon-next>`}
|
|
||||||
</mwc-list-item>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +162,7 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
) {
|
) {
|
||||||
// Store the width and height so that when we search, box doesn't jump
|
// Store the width and height so that when we search, box doesn't jump
|
||||||
const boundingRect =
|
const boundingRect =
|
||||||
this.shadowRoot!.querySelector("mwc-list")!.getBoundingClientRect();
|
this.shadowRoot!.querySelector("div")!.getBoundingClientRect();
|
||||||
this._width = boundingRect.width;
|
this._width = boundingRect.width;
|
||||||
this._height = boundingRect.height;
|
this._height = boundingRect.height;
|
||||||
}
|
}
|
||||||
@@ -212,31 +189,8 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _handlerPicked(ev) {
|
private async _handlerPicked(ev) {
|
||||||
const handler: HandlerObj = ev.currentTarget.handler;
|
|
||||||
|
|
||||||
if (handler.is_add) {
|
|
||||||
if (handler.slug === "zwave_js") {
|
|
||||||
const entries = await getConfigEntries(this.hass);
|
|
||||||
const entry = entries.find((ent) => ent.domain === "zwave_js");
|
|
||||||
|
|
||||||
if (!entry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showZWaveJSAddNodeDialog(this, {
|
|
||||||
entry_id: entry.entry_id,
|
|
||||||
});
|
|
||||||
} else if (handler.slug === "zha") {
|
|
||||||
navigate("/config/zha/add");
|
|
||||||
}
|
|
||||||
|
|
||||||
// This closes dialog.
|
|
||||||
fireEvent(this, "flow-update");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fireEvent(this, "handler-picked", {
|
fireEvent(this, "handler-picked", {
|
||||||
handler: handler.slug,
|
handler: ev.currentTarget.handler.slug,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,23 +218,20 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 16px 16px 0;
|
margin: 8px 16px 0;
|
||||||
}
|
}
|
||||||
ha-icon-next {
|
ha-icon-next {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
mwc-list {
|
div {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
}
|
}
|
||||||
.divider {
|
|
||||||
border-bottom-color: var(--divider-color);
|
|
||||||
}
|
|
||||||
h2 {
|
h2 {
|
||||||
padding-right: 66px;
|
padding-right: 66px;
|
||||||
}
|
}
|
||||||
@media all and (max-height: 900px) {
|
@media all and (max-height: 900px) {
|
||||||
mwc-list {
|
div {
|
||||||
max-height: calc(100vh - 134px);
|
max-height: calc(100vh - 134px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-dialog";
|
import "../../components/ha-dialog";
|
||||||
import "../../components/ha-switch";
|
import "../../components/ha-switch";
|
||||||
import "../../components/ha-textfield";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { DialogBoxParams } from "./show-dialog-box";
|
import { DialogBoxParams } from "./show-dialog-box";
|
||||||
@@ -70,18 +71,18 @@ class DialogBox extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this._params.prompt
|
${this._params.prompt
|
||||||
? html`
|
? html`
|
||||||
<ha-textfield
|
<paper-input
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
@keyup=${this._handleKeyUp}
|
@keyup=${this._handleKeyUp}
|
||||||
@change=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.label=${this._params.inputLabel
|
.label=${this._params.inputLabel
|
||||||
? this._params.inputLabel
|
? this._params.inputLabel
|
||||||
: ""}
|
: ""}
|
||||||
.type=${this._params.inputType
|
.type=${this._params.inputType
|
||||||
? this._params.inputType
|
? this._params.inputType
|
||||||
: "text"}
|
: "text"}
|
||||||
></ha-textfield>
|
></paper-input>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -106,8 +107,8 @@ class DialogBox extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
this._value = ev.target.value;
|
this._value = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dismiss(): void {
|
private _dismiss(): void {
|
||||||
|
@@ -615,6 +615,10 @@ class MoreInfoLight extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border-color: var(--divider-color);
|
border-color: var(--divider-color);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
@@ -5,13 +5,15 @@ import {
|
|||||||
mdiLoginVariant,
|
mdiLoginVariant,
|
||||||
mdiMusicNote,
|
mdiMusicNote,
|
||||||
mdiPlayBoxMultiple,
|
mdiPlayBoxMultiple,
|
||||||
|
mdiSend,
|
||||||
mdiVolumeHigh,
|
mdiVolumeHigh,
|
||||||
mdiVolumeMinus,
|
mdiVolumeMinus,
|
||||||
mdiVolumeOff,
|
mdiVolumeOff,
|
||||||
mdiVolumePlus,
|
mdiVolumePlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
@@ -20,7 +22,7 @@ import "../../../components/ha-icon-button";
|
|||||||
import "../../../components/ha-slider";
|
import "../../../components/ha-slider";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||||
import {
|
import {
|
||||||
computeMediaControls,
|
computeMediaControls,
|
||||||
MediaPickedEvent,
|
MediaPickedEvent,
|
||||||
@@ -41,6 +43,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public stateObj?: MediaPlayerEntity;
|
@property({ attribute: false }) public stateObj?: MediaPlayerEntity;
|
||||||
|
|
||||||
|
@query("#ttsInput") private _ttsInput?: HTMLInputElement;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.stateObj) {
|
if (!this.stateObj) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -71,17 +75,13 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||||
? html`
|
? html`
|
||||||
<mwc-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.card.media_player.browse_media"
|
"ui.card.media_player.browse_media"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiPlayBoxMultiple}
|
||||||
@click=${this._showBrowseMedia}
|
@click=${this._showBrowseMedia}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiPlayBoxMultiple}
|
|
||||||
slot="icon"
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-button>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -183,8 +183,21 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
supportsFeature(stateObj, SUPPORT_PLAY_MEDIA)
|
supportsFeature(stateObj, SUPPORT_PLAY_MEDIA)
|
||||||
? html`
|
? html`
|
||||||
<div class="tts">
|
<div class="tts">
|
||||||
Text to speech has moved to the media browser.
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
`;
|
`;
|
||||||
@@ -194,14 +207,14 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
return css`
|
return css`
|
||||||
ha-icon-button[action="turn_off"],
|
ha-icon-button[action="turn_off"],
|
||||||
ha-icon-button[action="turn_on"],
|
ha-icon-button[action="turn_on"],
|
||||||
ha-slider {
|
ha-slider,
|
||||||
|
#ttsInput {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
--mdc-theme-primary: currentColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.basic-controls {
|
.basic-controls {
|
||||||
@@ -210,7 +223,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
|
|
||||||
.volume,
|
.volume,
|
||||||
.source-input,
|
.source-input,
|
||||||
.sound-input {
|
.sound-input,
|
||||||
|
.tts {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -227,15 +241,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-button > ha-svg-icon {
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +295,32 @@ 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 {
|
private _showBrowseMedia(): void {
|
||||||
showMediaBrowserDialog(this, {
|
showMediaBrowserDialog(this, {
|
||||||
action: "play",
|
action: "play",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import "../../../components/ha-attributes";
|
import "../../../components/ha-attributes";
|
||||||
@@ -66,6 +66,14 @@ class MoreInfoRemote extends LitElement {
|
|||||||
activity: newVal,
|
activity: newVal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -241,6 +241,9 @@ class MoreInfoVacuum extends LitElement {
|
|||||||
.status-subtitle {
|
.status-subtitle {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.flex-horizontal {
|
.flex-horizontal {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@@ -338,9 +338,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.content {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
ha-header-bar {
|
ha-header-bar {
|
||||||
--mdc-theme-primary: var(--app-header-background-color);
|
--mdc-theme-primary: var(--app-header-background-color);
|
||||||
@@ -354,6 +352,10 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||||
ha-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-max-width: 90vw;
|
--mdc-dialog-max-width: 90vw;
|
||||||
|
@@ -1,11 +1,7 @@
|
|||||||
import {
|
import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings";
|
||||||
setPassiveTouchGestures,
|
|
||||||
setCancelSyntheticClickEvents,
|
|
||||||
} from "@polymer/polymer/lib/utils/settings";
|
|
||||||
import "../layouts/home-assistant";
|
import "../layouts/home-assistant";
|
||||||
import "../resources/ha-style";
|
import "../resources/ha-style";
|
||||||
import "../resources/roboto";
|
import "../resources/roboto";
|
||||||
import "../util/legacy-support";
|
import "../util/legacy-support";
|
||||||
|
|
||||||
setPassiveTouchGestures(true);
|
setPassiveTouchGestures(true);
|
||||||
setCancelSyntheticClickEvents(false);
|
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
|
||||||
import "../auth/ha-authorize";
|
import "../auth/ha-authorize";
|
||||||
import "../resources/ha-style";
|
import "../resources/ha-style";
|
||||||
import "../resources/roboto";
|
import "../resources/roboto";
|
||||||
import "../resources/safari-14-attachshadow-patch";
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
import "../resources/array.flat.polyfill";
|
import "../resources/array.flat.polyfill";
|
||||||
|
|
||||||
setCancelSyntheticClickEvents(false);
|
/* polyfill for paper-dropdown */
|
||||||
|
setTimeout(
|
||||||
|
() => import("web-animations-js/web-animations-next-lite.min"),
|
||||||
|
2000
|
||||||
|
);
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
|
||||||
import "../resources/safari-14-attachshadow-patch";
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import { PolymerElement } from "@polymer/polymer";
|
import { PolymerElement } from "@polymer/polymer";
|
||||||
@@ -16,8 +15,6 @@ import { createCustomPanelElement } from "../util/custom-panel/create-custom-pan
|
|||||||
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
|
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
|
||||||
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
|
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
|
||||||
|
|
||||||
setCancelSyntheticClickEvents(false);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
loadES5Adapter: () => Promise<unknown>;
|
loadES5Adapter: () => Promise<unknown>;
|
||||||
@@ -50,8 +47,7 @@ function initialize(
|
|||||||
) {
|
) {
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
|
|
||||||
style.innerHTML = `
|
style.innerHTML = `body { margin:0; }
|
||||||
body { margin:0; }
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background-color: #111111;
|
background-color: #111111;
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
|
||||||
import "../onboarding/ha-onboarding";
|
import "../onboarding/ha-onboarding";
|
||||||
import "../resources/ha-style";
|
import "../resources/ha-style";
|
||||||
import "../resources/roboto";
|
import "../resources/roboto";
|
||||||
import "../resources/safari-14-attachshadow-patch";
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
import "../resources/array.flat.polyfill";
|
import "../resources/array.flat.polyfill";
|
||||||
|
|
||||||
setCancelSyntheticClickEvents(false);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
stepsPromise: Promise<Response>;
|
stepsPromise: Promise<Response>;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import { mdiFilterVariant } from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
@@ -156,31 +157,30 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
: hiddenLabel;
|
: hiddenLabel;
|
||||||
|
|
||||||
const headerToolbar = html`<search-input
|
const headerToolbar = html`<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.filter=${this.filter}
|
.filter=${this.filter}
|
||||||
.suffix=${!this.narrow}
|
@value-changed=${this._handleSearchChange}
|
||||||
@value-changed=${this._handleSearchChange}
|
.label=${this.searchLabel ||
|
||||||
.label=${this.searchLabel ||
|
this.hass.localize("ui.components.data-table.search")}
|
||||||
this.hass.localize("ui.components.data-table.search")}
|
>
|
||||||
>
|
</search-input>
|
||||||
${!this.narrow
|
<div class="filters">
|
||||||
? html`<div
|
${filterInfo
|
||||||
class="filters"
|
? html`<div class="active-filters">
|
||||||
slot="suffix"
|
${this.narrow
|
||||||
@click=${this._preventDefault}
|
? html`<div>
|
||||||
>
|
<ha-svg-icon .path=${mdiFilterVariant}></ha-svg-icon>
|
||||||
${filterInfo
|
<paper-tooltip animation-delay="0" position="left">
|
||||||
? html`<div class="active-filters">
|
${filterInfo}
|
||||||
${filterInfo}
|
</paper-tooltip>
|
||||||
<mwc-button @click=${this._clearFilter}>
|
</div>`
|
||||||
${this.hass.localize("ui.components.data-table.clear")}
|
: filterInfo}
|
||||||
</mwc-button>
|
<mwc-button @click=${this._clearFilter}>
|
||||||
</div>`
|
${this.hass.localize("ui.components.data-table.clear")}
|
||||||
: ""}
|
</mwc-button>
|
||||||
<slot name="filter-menu"></slot>
|
</div>`
|
||||||
</div>`
|
: ""}<slot name="filter-menu"></slot>
|
||||||
: ""}
|
</div>`;
|
||||||
</search-input>`;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
@@ -195,16 +195,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
.mainPage=${this.mainPage}
|
.mainPage=${this.mainPage}
|
||||||
.supervisor=${this.supervisor}
|
.supervisor=${this.supervisor}
|
||||||
>
|
>
|
||||||
<div slot="toolbar-icon">
|
<div slot="toolbar-icon"><slot name="toolbar-icon"></slot></div>
|
||||||
${this.narrow
|
|
||||||
? html`<div class="filter-menu">
|
|
||||||
${this.numHidden || this.activeFilters
|
|
||||||
? html`<span class="badge">${this.numHidden || "!"}</span>`
|
|
||||||
: ""}
|
|
||||||
<slot name="filter-menu"></slot>
|
|
||||||
</div>`
|
|
||||||
: ""}<slot name="toolbar-icon"></slot>
|
|
||||||
</div>
|
|
||||||
${this.narrow
|
${this.narrow
|
||||||
? html`
|
? html`
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
@@ -242,10 +233,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _preventDefault(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent) {
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
if (this.filter === ev.detail.value) {
|
if (this.filter === ev.detail.value) {
|
||||||
return;
|
return;
|
||||||
@@ -280,12 +267,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
search-input {
|
|
||||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
|
||||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
|
||||||
--text-field-overflow: visible;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
.table-header search-input {
|
.table-header search-input {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -295,19 +276,16 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
.search-toolbar search-input {
|
.search-toolbar search-input {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
--mdc-text-field-fill-color: transparant;
|
||||||
|
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||||
--mdc-ripple-color: transparant;
|
--mdc-ripple-color: transparant;
|
||||||
}
|
}
|
||||||
.filters {
|
.filters {
|
||||||
--mdc-text-field-fill-color: var(--input-fill-color);
|
|
||||||
--mdc-text-field-idle-line-color: var(--input-idle-line-color);
|
|
||||||
--mdc-shape-small: 4px;
|
|
||||||
--text-field-overflow: initial;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
}
|
||||||
.active-filters {
|
.active-filters {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
@@ -317,8 +295,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
padding: 2px 2px 2px 8px;
|
padding: 2px 2px 2px 8px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
width: max-content;
|
|
||||||
cursor: initial;
|
|
||||||
}
|
}
|
||||||
.active-filters ha-svg-icon {
|
.active-filters ha-svg-icon {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
@@ -337,24 +313,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
left: 0;
|
left: 0;
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
.badge {
|
|
||||||
min-width: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-weight: 400;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
line-height: 20px;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0px 4px;
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 4px;
|
|
||||||
font-size: 0.65em;
|
|
||||||
}
|
|
||||||
.filter-menu {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -272,7 +272,6 @@ class HassTabsSubpage extends LitElement {
|
|||||||
ha-menu-button,
|
ha-menu-button,
|
||||||
ha-icon-button-arrow-prev,
|
ha-icon-button-arrow-prev,
|
||||||
::slotted([slot="toolbar-icon"]) {
|
::slotted([slot="toolbar-icon"]) {
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
color: var(--sidebar-icon-color);
|
color: var(--sidebar-icon-color);
|
||||||
|
@@ -78,7 +78,8 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._initializeHass();
|
this._initializeHass();
|
||||||
setTimeout(() => registerServiceWorker(this), 1000);
|
setTimeout(() => registerServiceWorker(this), 1000);
|
||||||
|
/* polyfill for paper-dropdown */
|
||||||
|
import("web-animations-js/web-animations-next-lite.min");
|
||||||
this.addEventListener("hass-suspend-when-hidden", (ev) => {
|
this.addEventListener("hass-suspend-when-hidden", (ev) => {
|
||||||
this._updateHass({ suspendWhenHidden: ev.detail.suspend });
|
this._updateHass({ suspendWhenHidden: ev.detail.suspend });
|
||||||
storeState(this.hass!);
|
storeState(this.hass!);
|
||||||
|
@@ -30,7 +30,7 @@ import { HomeAssistant } from "../types";
|
|||||||
import "./action-badge";
|
import "./action-badge";
|
||||||
import "./integration-badge";
|
import "./integration-badge";
|
||||||
|
|
||||||
const HIDDEN_DOMAINS = new Set(["hassio", "met", "radio_browser", "rpi_power"]);
|
const HIDDEN_DOMAINS = new Set(["met", "rpi_power", "hassio"]);
|
||||||
|
|
||||||
@customElement("onboarding-integrations")
|
@customElement("onboarding-integrations")
|
||||||
class OnboardingIntegrations extends LitElement {
|
class OnboardingIntegrations extends LitElement {
|
||||||
@@ -140,6 +140,8 @@ class OnboardingIntegrations extends LitElement {
|
|||||||
this._scanUSBDevices();
|
this._scanUSBDevices();
|
||||||
loadConfigFlowDialog();
|
loadConfigFlowDialog();
|
||||||
this._loadConfigEntries();
|
this._loadConfigEntries();
|
||||||
|
/* polyfill for paper-dropdown */
|
||||||
|
import("web-animations-js/web-animations-next-lite.min");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createFlow() {
|
private _createFlow() {
|
||||||
|
@@ -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,23 +11,22 @@ 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 { Action, getActionType } from "../../../../data/script";
|
import type { Action } 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";
|
||||||
@@ -36,8 +35,7 @@ const OPTIONS = [
|
|||||||
"condition",
|
"condition",
|
||||||
"delay",
|
"delay",
|
||||||
"event",
|
"event",
|
||||||
"play_media",
|
"scene",
|
||||||
"activate_scene",
|
|
||||||
"service",
|
"service",
|
||||||
"wait_template",
|
"wait_template",
|
||||||
"wait_for_trigger",
|
"wait_for_trigger",
|
||||||
@@ -46,15 +44,8 @@ const OPTIONS = [
|
|||||||
"device_id",
|
"device_id",
|
||||||
];
|
];
|
||||||
|
|
||||||
const getType = (action: Action | undefined) => {
|
const getType = (action: Action | undefined) =>
|
||||||
if (!action) {
|
action ? OPTIONS.find((option) => option in action) : undefined;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if ("service" in action || "scene" in action) {
|
|
||||||
return getActionType(action);
|
|
||||||
}
|
|
||||||
return OPTIONS.find((option) => option in action);
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@@ -73,7 +64,7 @@ export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newVal = ev.detail?.value || (ev.target as any).value;
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
if ((element.action[name] || "") === newVal) {
|
if ((element.action[name] || "") === newVal) {
|
||||||
return;
|
return;
|
||||||
@@ -122,30 +113,24 @@ 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;
|
||||||
}
|
}
|
||||||
if (this._yamlMode) {
|
this._uiModeAvailable = Boolean(getType(this.action));
|
||||||
const yamlEditor = this._yamlEditor;
|
if (!this._uiModeAvailable && !this._yamlMode) {
|
||||||
if (yamlEditor && yamlEditor.value !== this.action) {
|
this._yamlMode = true;
|
||||||
yamlEditor.setValue(this.action);
|
}
|
||||||
}
|
|
||||||
|
const yamlEditor = this._yamlEditor;
|
||||||
|
if (this._yamlMode && yamlEditor && yamlEditor.value !== 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`
|
||||||
@@ -220,7 +205,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${yamlMode
|
${yamlMode
|
||||||
? html`
|
? html`
|
||||||
${type === undefined
|
${selected === -1
|
||||||
? html`
|
? html`
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.unsupported_action",
|
"ui.panel.config.automation.editor.actions.unsupported_action",
|
||||||
@@ -376,7 +361,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
mwc-select {
|
mwc-select {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { mdiDelete } from "@mdi/js";
|
import { mdiDelete } from "@mdi/js";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
@@ -69,7 +69,6 @@ export class HaDeviceAction extends LitElement {
|
|||||||
${this._capabilities?.extra_fields
|
${this._capabilities?.extra_fields
|
||||||
? html`
|
? html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
|
||||||
.data=${this._extraFieldsData(this.action, this._capabilities)}
|
.data=${this._extraFieldsData(this.action, this._capabilities)}
|
||||||
.schema=${this._capabilities.extra_fields}
|
.schema=${this._capabilities.extra_fields}
|
||||||
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
.computeLabel=${this._extraFieldsComputeLabelCallback(
|
||||||
@@ -143,12 +142,9 @@ export class HaDeviceAction extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
ha-device-picker {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
ha-device-action-picker {
|
ha-device-action-picker {
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import { html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import "../../../../../components/entity/ha-entity-picker";
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
import "../../../../../components/ha-service-picker";
|
import "../../../../../components/ha-service-picker";
|
||||||
import "../../../../../components/ha-textfield";
|
|
||||||
import "../../../../../components/ha-yaml-editor";
|
import "../../../../../components/ha-yaml-editor";
|
||||||
import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
||||||
import type { EventAction } from "../../../../../data/script";
|
import type { EventAction } from "../../../../../data/script";
|
||||||
@@ -40,13 +40,14 @@ export class HaEventAction extends LitElement implements ActionElement {
|
|||||||
const { event, event_data } = this.action;
|
const { event, event_data } = this.action;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-textfield
|
<paper-input
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.type.event.event"
|
"ui.panel.config.automation.editor.actions.type.event.event"
|
||||||
)}
|
)}
|
||||||
|
name="event"
|
||||||
.value=${event}
|
.value=${event}
|
||||||
@change=${this._eventChanged}
|
@value-changed=${this._eventChanged}
|
||||||
></ha-textfield>
|
></paper-input>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -71,17 +72,9 @@ export class HaEventAction extends LitElement implements ActionElement {
|
|||||||
private _eventChanged(ev: CustomEvent): void {
|
private _eventChanged(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.action, event: (ev.target as any).value },
|
value: { ...this.action, event: ev.detail.value },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
ha-textfield {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,67 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
@@ -9,11 +10,10 @@ import {
|
|||||||
WhileRepeat,
|
WhileRepeat,
|
||||||
} from "../../../../../data/script";
|
} from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
import { Condition } from "../../../../lovelace/common/validate-condition";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import "../../../../../components/ha-textfield";
|
import { ActionElement } from "../ha-automation-action-row";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
|
||||||
|
|
||||||
const OPTIONS = ["count", "while", "until"];
|
const OPTIONS = ["count", "while", "until"];
|
||||||
|
|
||||||
@@ -53,16 +53,14 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-select>
|
</mwc-select>
|
||||||
${type === "count"
|
${type === "count"
|
||||||
? html`
|
? html`<paper-input
|
||||||
<ha-textfield
|
.label=${this.hass.localize(
|
||||||
.label=${this.hass.localize(
|
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
||||||
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
)}
|
||||||
)}
|
name="count"
|
||||||
name="count"
|
.value=${(action as CountRepeat).count || "0"}
|
||||||
.value=${(action as CountRepeat).count || "0"}
|
@value-changed=${this._countChanged}
|
||||||
@change=${this._countChanged}
|
></paper-input>`
|
||||||
></ha-textfield>
|
|
||||||
`
|
|
||||||
: ""}
|
: ""}
|
||||||
${type === "while"
|
${type === "while"
|
||||||
? html` <h3>
|
? html` <h3>
|
||||||
@@ -144,7 +142,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _countChanged(ev: CustomEvent): void {
|
private _countChanged(ev: CustomEvent): void {
|
||||||
const newVal = (ev.target as any).value;
|
const newVal = ev.detail.value;
|
||||||
if ((this.action.repeat as CountRepeat).count === newVal) {
|
if ((this.action.repeat as CountRepeat).count === newVal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user