mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-15 12:19:25 +00:00
Compare commits
3 Commits
justify-ar
...
fix-search
Author | SHA1 | Date | |
---|---|---|---|
![]() |
21e441b682 | ||
![]() |
737f7ba6b9 | ||
![]() |
a5862b86ca |
@@ -16,9 +16,6 @@
|
||||
"runem.lit-plugin",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
"containerEnv": {
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"files.eol": "\n",
|
||||
|
@@ -33,10 +33,6 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
require.resolve(
|
||||
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);
|
||||
|
||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
|
@@ -2,3 +2,8 @@ import "../../src/resources/ha-style";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./ha-demo";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
setTimeout(() => {
|
||||
import("web-animations-js/web-animations-next-lite.min");
|
||||
}, 1000);
|
||||
|
@@ -61,12 +61,6 @@ const SCHEMAS: {
|
||||
select: { options: ["Everyone Home", "Some Home", "All gone"] },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
selector: {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -72,7 +72,6 @@ const SCHEMAS: {
|
||||
name: "Select",
|
||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||
},
|
||||
icon: { name: "Icon", selector: { icon: {} } },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@@ -221,14 +221,13 @@ class HassioAddonStore extends LitElement {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.search {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
padding: 0 16px;
|
||||
background: var(--sidebar-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
.search search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.advanced {
|
||||
padding: 12px;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-select";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
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/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
@@ -56,44 +57,49 @@ class HassioAddonAudio extends LitElement {
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${this._inputDevices &&
|
||||
html`<mwc-select
|
||||
|
||||
<paper-dropdown-menu
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.input"
|
||||
)}
|
||||
@selected=${this._setInputDevice}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._selectedInput!}
|
||||
@iron-select=${this._setInputDevice}
|
||||
>
|
||||
${this._inputDevices.map(
|
||||
(item) => html`
|
||||
<mwc-list-item .value=${item.device || ""}>
|
||||
${item.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>`}
|
||||
${this._outputDevices &&
|
||||
html`<mwc-select
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedInput!}
|
||||
>
|
||||
${this._inputDevices &&
|
||||
this._inputDevices.map(
|
||||
(item) => html`
|
||||
<paper-item device=${item.device || ""}>
|
||||
${item.name}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-dropdown-menu
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.output"
|
||||
)}
|
||||
@selected=${this._setOutputDevice}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._selectedOutput!}
|
||||
@iron-select=${this._setOutputDevice}
|
||||
>
|
||||
${this._outputDevices.map(
|
||||
(item) => html`
|
||||
<mwc-list-item .value=${item.device || ""}
|
||||
>${item.name}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</mwc-select>`}
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedOutput!}
|
||||
>
|
||||
${this._outputDevices &&
|
||||
this._outputDevices.map(
|
||||
(item) => html`
|
||||
<paper-item device=${item.device || ""}
|
||||
>${item.name}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._saveSettings}>
|
||||
@@ -110,7 +116,8 @@ class HassioAddonAudio extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
:host,
|
||||
ha-card {
|
||||
ha-card,
|
||||
paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
paper-item {
|
||||
@@ -119,30 +126,24 @@ class HassioAddonAudio extends LitElement {
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
}
|
||||
mwc-select {
|
||||
width: 100%;
|
||||
}
|
||||
mwc-select:last-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
this._addonChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _setInputDevice(ev): void {
|
||||
const device = ev.target.value;
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
this._selectedInput = device;
|
||||
}
|
||||
|
||||
private _setOutputDevice(ev): void {
|
||||
const device = ev.target.value;
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
this._selectedOutput = device;
|
||||
}
|
||||
|
||||
|
@@ -148,6 +148,7 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
--paper-item-body-two-line-min-height: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
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 { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -89,19 +90,18 @@ class HassioDatadiskDialog extends LitElement {
|
||||
)}
|
||||
<br /><br />
|
||||
|
||||
<mwc-select
|
||||
<paper-dropdown-menu
|
||||
.label=${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.select_device"
|
||||
)}
|
||||
@selected=${this._select_device}
|
||||
@value-changed=${this._select_device}
|
||||
>
|
||||
${this.devices.map(
|
||||
(device) =>
|
||||
html`<mwc-list-item .value=${device}
|
||||
>${device}</mwc-list-item
|
||||
>`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${this.devices.map(
|
||||
(device) => html`<paper-item>${device}</paper-item>`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
`
|
||||
: this.devices === undefined
|
||||
? this.dialogParams.supervisor.localize(
|
||||
@@ -130,8 +130,8 @@ class HassioDatadiskDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _select_device(ev) {
|
||||
this.selectedDevice = ev.target.value;
|
||||
private _select_device(event) {
|
||||
this.selectedDevice = event.detail.value;
|
||||
}
|
||||
|
||||
private async _moveDatadisk() {
|
||||
@@ -156,7 +156,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
mwc-select {
|
||||
paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
ha-circular-progress {
|
||||
|
@@ -178,7 +178,7 @@ class HassioHardwareDialog extends LitElement {
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
search-input {
|
||||
margin: 8px 16px 0;
|
||||
margin: 0 16px;
|
||||
display: block;
|
||||
}
|
||||
.device-property {
|
||||
|
@@ -1,4 +1,7 @@
|
||||
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 { customElement, property, state } from "lit/decorators";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
@@ -70,19 +73,24 @@ class HassioSupervisorLog extends LitElement {
|
||||
: ""}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<mwc-select
|
||||
<paper-dropdown-menu
|
||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||
@selected=${this._setLogProvider}
|
||||
.value=${this._selectedLogProvider}
|
||||
@iron-select=${this._setLogProvider}
|
||||
>
|
||||
${logProviders.map(
|
||||
(provider) => html`
|
||||
<mwc-list-item .value=${provider.key}>
|
||||
${provider.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="provider"
|
||||
.selected=${this._selectedLogProvider}
|
||||
>
|
||||
${logProviders.map(
|
||||
(provider) => html`
|
||||
<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> {
|
||||
const provider = ev.target.value;
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
this._selectedLogProvider = provider;
|
||||
this._loadData();
|
||||
}
|
||||
@@ -145,9 +153,9 @@ class HassioSupervisorLog extends LitElement {
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
mwc-select {
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
paper-dropdown-menu {
|
||||
padding: 0 2%;
|
||||
width: 96%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/common/search/search-input";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
|
18
script/core
18
script/core
@@ -4,8 +4,6 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
|
||||
|
||||
if [ -z "${DEVCONTAINER}" ]; then
|
||||
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||
exit 1
|
||||
@@ -18,9 +16,9 @@ if [ -z $(which hass) ]; then
|
||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "${WD}/config" ]; then
|
||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
||||
echo "Creating default configuration."
|
||||
mkdir -p "${WD}/config";
|
||||
mkdir -p "/workspaces/frontend/config";
|
||||
hass --script ensure_config -c config
|
||||
echo "demo:
|
||||
|
||||
@@ -28,24 +26,24 @@ logger:
|
||||
default: info
|
||||
logs:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> "${WD}/config/configuration.yaml"
|
||||
" >> /workspaces/frontend/config/configuration.yaml
|
||||
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: ${WD}
|
||||
# development_repo: /workspaces/frontend
|
||||
|
||||
hassio:
|
||||
development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
else
|
||||
echo "
|
||||
frontend:
|
||||
development_repo: ${WD}
|
||||
development_repo: /workspaces/frontend
|
||||
|
||||
# hassio:
|
||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
hass -c "${WD}/config"
|
||||
hass -c /workspaces/frontend/config
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220214.0
|
||||
version = 20220203.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
||||
const services = hass.services[domain];
|
||||
|
@@ -1,30 +1,14 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { canToggleDomain } from "./can_toggle_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { supportsFeature } from "./supports-feature";
|
||||
|
||||
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
if (domain === "group") {
|
||||
if (
|
||||
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;
|
||||
return stateObj.state === "on" || stateObj.state === "off";
|
||||
}
|
||||
|
||||
if (domain === "climate") {
|
||||
return supportsFeature(stateObj, 4096);
|
||||
}
|
||||
|
@@ -123,11 +123,7 @@ export const computeStateDisplay = (
|
||||
domain === "scene" ||
|
||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
||||
) {
|
||||
try {
|
||||
return formatDateTime(new Date(compareState), locale);
|
||||
} catch (_err) {
|
||||
return compareState;
|
||||
}
|
||||
return formatDateTime(new Date(compareState), locale);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -1,32 +1,24 @@
|
||||
const SUFFIXES = [" ", ": "];
|
||||
|
||||
/**
|
||||
* Strips a device name from an 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
|
||||
*/
|
||||
export const stripPrefixFromEntityName = (
|
||||
entityName: string,
|
||||
lowerCasedPrefix: string
|
||||
lowerCasedPrefixWithSpaceSuffix: string
|
||||
) => {
|
||||
const lowerCasedEntityName = entityName.toLowerCase();
|
||||
|
||||
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);
|
||||
}
|
||||
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@@ -14,9 +14,6 @@ class SearchInput extends LitElement {
|
||||
|
||||
@property() public filter?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public suffix = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public autofocus = false;
|
||||
|
||||
@@ -36,7 +33,7 @@ class SearchInput extends LitElement {
|
||||
.label=${this.label || "Search"}
|
||||
.value=${this.filter || ""}
|
||||
.icon=${true}
|
||||
.iconTrailing=${this.filter || this.suffix}
|
||||
.iconTrailing=${this.filter}
|
||||
@input=${this._filterInputChanged}
|
||||
>
|
||||
<slot name="prefix" slot="leadingIcon">
|
||||
@@ -46,18 +43,16 @@ class SearchInput extends LitElement {
|
||||
.path=${mdiMagnify}
|
||||
></ha-svg-icon>
|
||||
</slot>
|
||||
<div class="trailing" slot="trailingIcon">
|
||||
${this.filter &&
|
||||
html`
|
||||
<ha-icon-button
|
||||
@click=${this._clearSearch}
|
||||
.label=${this.hass.localize("ui.common.clear")}
|
||||
.path=${mdiClose}
|
||||
class="clear-button"
|
||||
></ha-icon-button>
|
||||
`}
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
${this.filter &&
|
||||
html`
|
||||
<ha-icon-button
|
||||
slot="trailingIcon"
|
||||
@click=${this._clearSearch}
|
||||
.label=${this.hass.localize("ui.common.clear")}
|
||||
.path=${mdiClose}
|
||||
class="clear-button"
|
||||
></ha-icon-button>
|
||||
`}
|
||||
</ha-textfield>
|
||||
`;
|
||||
}
|
||||
@@ -86,16 +81,15 @@ class SearchInput extends LitElement {
|
||||
ha-svg-icon {
|
||||
outline: none;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
.clear-button {
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-textfield {
|
||||
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="paused"],
|
||||
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="sun"][data-state="above_horizon"],
|
||||
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 { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
|
@@ -4,7 +4,6 @@ import { mdiFilterVariant } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { computeDeviceName } from "../data/device_registry";
|
||||
import { findRelated, RelatedResult } from "../data/search";
|
||||
@@ -66,7 +65,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
.fullwidth=${this.narrow}
|
||||
.corner=${this.corner}
|
||||
@closed=${this._onClosed}
|
||||
@input=${stopPropagation}
|
||||
>
|
||||
<ha-area-picker
|
||||
.label=${this.hass.localize(
|
||||
@@ -76,7 +74,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
.value=${this.value?.area}
|
||||
no-add
|
||||
@value-changed=${this._areaPicked}
|
||||
@click=${this._preventDefault}
|
||||
></ha-area-picker>
|
||||
<ha-device-picker
|
||||
.label=${this.hass.localize(
|
||||
@@ -85,7 +82,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.device}
|
||||
@value-changed=${this._devicePicked}
|
||||
@click=${this._preventDefault}
|
||||
></ha-device-picker>
|
||||
<ha-entity-picker
|
||||
.label=${this.hass.localize(
|
||||
@@ -95,7 +91,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
.value=${this.value?.entity}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
@value-changed=${this._entityPicked}
|
||||
@click=${this._preventDefault}
|
||||
></ha-entity-picker>
|
||||
</mwc-menu-surface>
|
||||
`;
|
||||
@@ -108,17 +103,11 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
private _onClosed(ev): void {
|
||||
ev.stopPropagation();
|
||||
private _onClosed(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _preventDefault(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private async _entityPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const entityId = ev.detail.value;
|
||||
if (!entityId) {
|
||||
fireEvent(this, "related-changed", { value: undefined });
|
||||
@@ -138,7 +127,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
}
|
||||
|
||||
private async _devicePicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const deviceId = ev.detail.value;
|
||||
if (!deviceId) {
|
||||
fireEvent(this, "related-changed", { value: undefined });
|
||||
@@ -162,7 +150,6 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
}
|
||||
|
||||
private async _areaPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const areaId = ev.detail.value;
|
||||
if (!areaId) {
|
||||
fireEvent(this, "related-changed", { value: undefined });
|
||||
@@ -186,7 +173,9 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
:host {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
--mdc-menu-min-width: 250px;
|
||||
}
|
||||
:host([narrow]) {
|
||||
position: static;
|
||||
}
|
||||
ha-area-picker,
|
||||
ha-device-picker,
|
||||
@@ -196,15 +185,8 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
padding: 4px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
ha-area-picker {
|
||||
padding-top: 16px;
|
||||
}
|
||||
ha-entity-picker {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
:host([narrow]) ha-area-picker,
|
||||
:host([narrow]) ha-device-picker,
|
||||
:host([narrow]) ha-entity-picker {
|
||||
:host([narrow]) ha-device-picker {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { mdiCalendar } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatDateNumeric } from "../common/datetime/format_date";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
|
||||
|
||||
@@ -38,17 +38,17 @@ export class HaDateInput extends LitElement {
|
||||
@property() public label?: string;
|
||||
|
||||
render() {
|
||||
return html`<ha-textfield
|
||||
return html`<paper-input
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
iconTrailing="calendar"
|
||||
no-label-float
|
||||
@click=${this._openDialog}
|
||||
.value=${this.value
|
||||
? formatDateNumeric(new Date(this.value), this.locale)
|
||||
: ""}
|
||||
>
|
||||
<ha-svg-icon slot="trailingIcon" .path=${mdiCalendar}></ha-svg-icon>
|
||||
</ha-textfield>`;
|
||||
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
|
||||
</paper-input>`;
|
||||
}
|
||||
|
||||
private _openDialog() {
|
||||
@@ -73,6 +73,9 @@ export class HaDateInput extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
paper-input {
|
||||
width: 110px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -11,8 +11,7 @@ import {
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-button-menu";
|
||||
import "../ha-check-list-item";
|
||||
import type { HaCheckListItem } from "../ha-check-list-item";
|
||||
import { HaCheckListItem } from "../ha-check-list-item";
|
||||
import "../ha-checkbox";
|
||||
import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-formfield";
|
||||
|
@@ -32,12 +32,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
||||
|
||||
@property() public computeLabel?: (
|
||||
schema: HaFormSchema,
|
||||
data?: HaFormDataContainer
|
||||
) => string;
|
||||
|
||||
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
||||
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
||||
|
||||
public focus() {
|
||||
const root = this.shadowRoot?.querySelector(".root");
|
||||
@@ -76,7 +71,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
: ""}
|
||||
${this.schema.map((item) => {
|
||||
const error = getValue(this.error, item);
|
||||
|
||||
return html`
|
||||
${error
|
||||
? html`
|
||||
@@ -91,15 +85,14 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
.hass=${this.hass}
|
||||
.selector=${item.selector}
|
||||
.value=${getValue(this.data, item)}
|
||||
.label=${this._computeLabel(item, this.data)}
|
||||
.label=${this._computeLabel(item)}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this._computeHelper(item)}
|
||||
.required=${item.required || false}
|
||||
></ha-selector>`
|
||||
: dynamicElement(`ha-form-${item.type}`, {
|
||||
schema: item,
|
||||
data: getValue(this.data, item),
|
||||
label: this._computeLabel(item, this.data),
|
||||
label: this._computeLabel(item),
|
||||
disabled: this.disabled,
|
||||
})}
|
||||
`;
|
||||
@@ -114,7 +107,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
root.addEventListener("value-changed", (ev) => {
|
||||
ev.stopPropagation();
|
||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.data, [schema.name]: ev.detail.value },
|
||||
});
|
||||
@@ -122,18 +114,14 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
return root;
|
||||
}
|
||||
|
||||
private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) {
|
||||
private _computeLabel(schema: HaFormSchema) {
|
||||
return this.computeLabel
|
||||
? this.computeLabel(schema, data)
|
||||
? this.computeLabel(schema)
|
||||
: schema
|
||||
? schema.name
|
||||
: "";
|
||||
}
|
||||
|
||||
private _computeHelper(schema: HaFormSchema) {
|
||||
return this.computeHelper ? this.computeHelper(schema) : "";
|
||||
}
|
||||
|
||||
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
||||
return this.computeError ? this.computeError(error, schema) : error;
|
||||
}
|
||||
|
@@ -43,8 +43,6 @@ class HaHLSPlayer extends LitElement {
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _errorIsFatal = false;
|
||||
|
||||
private _hlsPolyfillInstance?: HlsLite;
|
||||
|
||||
private _exoPlayer = false;
|
||||
@@ -55,7 +53,6 @@ class HaHLSPlayer extends LitElement {
|
||||
super.connectedCallback();
|
||||
HaHLSPlayer.streamCount += 1;
|
||||
if (this.hasUpdated) {
|
||||
this._resetError();
|
||||
this._startHls();
|
||||
}
|
||||
}
|
||||
@@ -67,23 +64,16 @@ class HaHLSPlayer extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||
}
|
||||
return html`
|
||||
${this._error
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
class=${this._errorIsFatal ? "fatal" : "retry"}
|
||||
>
|
||||
${this._error}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${!this._errorIsFatal
|
||||
? html`<video
|
||||
?autoplay=${this.autoPlay}
|
||||
.muted=${this.muted}
|
||||
?playsinline=${this.playsInline}
|
||||
?controls=${this.controls}
|
||||
></video>`
|
||||
: ""}
|
||||
<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._resetError();
|
||||
this._startHls();
|
||||
}
|
||||
|
||||
private async _startHls(): Promise<void> {
|
||||
this._error = undefined;
|
||||
|
||||
const masterPlaylistPromise = fetch(this.url);
|
||||
|
||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
||||
@@ -119,8 +110,8 @@ class HaHLSPlayer extends LitElement {
|
||||
}
|
||||
|
||||
if (!hlsSupported) {
|
||||
this._setFatalError(
|
||||
this.hass.localize("ui.components.media-browser.video_not_supported")
|
||||
this._error = this.hass.localize(
|
||||
"ui.components.media-browser.video_not_supported"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -228,16 +219,9 @@ class HaHLSPlayer extends LitElement {
|
||||
this._hlsPolyfillInstance = hls;
|
||||
hls.attachMedia(videoEl);
|
||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||
this._resetError();
|
||||
hls.loadSource(url);
|
||||
});
|
||||
hls.on(Hls.Events.FRAG_LOADED, (_event, _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.
|
||||
hls.on(Hls.Events.ERROR, (_, data: any) => {
|
||||
if (!data.fatal) {
|
||||
return;
|
||||
}
|
||||
@@ -257,22 +241,22 @@ class HaHLSPlayer extends LitElement {
|
||||
error += " (" + data.response.code + ")";
|
||||
}
|
||||
}
|
||||
this._setRetryableError(error);
|
||||
break;
|
||||
this._error = error;
|
||||
return;
|
||||
}
|
||||
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
||||
this._setRetryableError("Timeout while starting stream");
|
||||
break;
|
||||
this._error = "Timeout while starting stream";
|
||||
return;
|
||||
default:
|
||||
this._setRetryableError("Stream network error");
|
||||
break;
|
||||
this._error = "Unknown stream network error (" + data.details + ")";
|
||||
return;
|
||||
}
|
||||
hls.startLoad();
|
||||
this._error = "Error with media stream contents (" + data.details + ")";
|
||||
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
||||
this._setRetryableError("Error with media stream contents");
|
||||
hls.recoverMediaError();
|
||||
this._error = "Error with media stream contents (" + data.details + ")";
|
||||
} 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 {
|
||||
return css`
|
||||
:host,
|
||||
@@ -327,14 +296,10 @@ class HaHLSPlayer extends LitElement {
|
||||
max-height: var(--video-max-height, calc(100vh - 97px));
|
||||
}
|
||||
|
||||
.fatal {
|
||||
ha-alert {
|
||||
display: block;
|
||||
padding: 100px 16px;
|
||||
}
|
||||
|
||||
.retry {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
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 "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
@@ -1,39 +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}
|
||||
@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;
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@ import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { SelectOption, SelectSelector } from "../../data/selector";
|
||||
import { SelectSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
@@ -17,8 +17,6 @@ export class HaSelectSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
@@ -27,17 +25,15 @@ export class HaSelectSelector extends LitElement {
|
||||
naturalMenuWidth
|
||||
.label=${this.label}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@closed=${stopPropagation}
|
||||
@selected=${this._valueChanged}
|
||||
>
|
||||
${this.selector.select.options.map((item: string | SelectOption) => {
|
||||
const value = typeof item === "object" ? item.value : item;
|
||||
const label = typeof item === "object" ? item.label : item;
|
||||
|
||||
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
|
||||
})}
|
||||
${this.selector.select.options.map(
|
||||
(item: string) => html`
|
||||
<mwc-list-item .value=${item}>${item}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>`;
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,6 @@ import "./ha-selector-select";
|
||||
import "./ha-selector-target";
|
||||
import "./ha-selector-text";
|
||||
import "./ha-selector-time";
|
||||
import "./ha-selector-icon";
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
@@ -29,8 +28,6 @@ export class HaSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: any;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -55,7 +52,6 @@ export class HaSelector extends LitElement {
|
||||
placeholder: this.placeholder,
|
||||
disabled: this.disabled,
|
||||
required: this.required,
|
||||
helper: this.helper,
|
||||
id: "selector",
|
||||
})}
|
||||
`;
|
||||
|
@@ -489,6 +489,9 @@ export class HaServiceControl extends LitElement {
|
||||
margin: var(--service-control-padding, 0 16px);
|
||||
padding: 16px 0;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
width: 60%;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 60%;
|
||||
}
|
||||
|
@@ -68,14 +68,6 @@ export class HaTextField extends TextFieldBase {
|
||||
:host([no-spinner]) input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.mdc-text-field__ripple {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mdc-text-field {
|
||||
overflow: var(--text-field-overflow);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,230 +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 } 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";
|
||||
|
||||
@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", false, false) private _message!: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-textarea
|
||||
autogrow
|
||||
.label=${this.hass.localize("ui.panel.media-browser.tts.message")}
|
||||
.value=${this._message ||
|
||||
this.hass.localize("ui.panel.media-browser.tts.example_message", {
|
||||
name: this.hass.user?.name || "",
|
||||
})}
|
||||
>
|
||||
</ha-textarea>
|
||||
${this._cloudDefaultOptions ? this._renderCloudOptions() : ""}
|
||||
<div class="actions">
|
||||
${this._cloudDefaultOptions &&
|
||||
(this._cloudDefaultOptions![0] !== this._cloudOptions![0] ||
|
||||
this._cloudDefaultOptions![1] !== this._cloudOptions![1])
|
||||
? html`
|
||||
<button class="link" @click=${this._storeDefaults}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.media-browser.tts.set_as_default"
|
||||
)}
|
||||
</button>
|
||||
`
|
||||
: html`<span></span>`}
|
||||
<mwc-button raised label="Say" @click=${this._ttsClicked}></mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderCloudOptions() {
|
||||
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.panel.media-browser.tts.language")}
|
||||
.value=${selectedVoice[0]}
|
||||
@selected=${this._handleLanguageChange}
|
||||
>
|
||||
${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.panel.media-browser.tts.gender")}
|
||||
.value=${selectedVoice[1]}
|
||||
@selected=${this._handleGenderChange}
|
||||
>
|
||||
${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("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];
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("item")) {
|
||||
if (this.isCloudItem && !this._cloudTTSInfo) {
|
||||
getCloudTTSInfo(this.hass).then((info) => {
|
||||
this._cloudTTSInfo = info;
|
||||
});
|
||||
fetchCloudStatus(this.hass).then((status) => {
|
||||
if (status.logged_in) {
|
||||
this._cloudDefaultOptions = status.prefs.tts_default_voice;
|
||||
this._cloudOptions = { ...this._cloudDefaultOptions };
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getLanguages = memoizeOne(getCloudTtsLanguages);
|
||||
|
||||
private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders);
|
||||
|
||||
private get isCloudItem(): boolean {
|
||||
return this.item.media_content_id === "media-source://tts/cloud";
|
||||
}
|
||||
|
||||
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 += `?${query.toString()}`;
|
||||
item.can_play = true;
|
||||
fireEvent(this, "media-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.panel.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%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-browse-media-tts": BrowseMediaTTS;
|
||||
}
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
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 {
|
||||
css,
|
||||
@@ -48,20 +49,11 @@ import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "../ha-fab";
|
||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||
import { isTTSMediaSource } from "../../data/tts";
|
||||
import "./ha-browse-media-tts";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"media-picked": MediaPickedEvent;
|
||||
"media-browsed": {
|
||||
// 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;
|
||||
};
|
||||
"media-browsed": { ids: MediaPlayerItemId[]; current?: MediaPlayerItem };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,159 +246,159 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${this._renderError(this._error)}
|
||||
</div>
|
||||
`
|
||||
: isTTSMediaSource(currentItem.media_content_id)
|
||||
? html`
|
||||
<ha-browse-media-tts
|
||||
.item=${currentItem}
|
||||
.hass=${this.hass}
|
||||
.action=${this.action}
|
||||
></ha-browse-media-tts>
|
||||
`
|
||||
: !currentItem.children?.length
|
||||
? html`
|
||||
<div class="container no-items">
|
||||
${currentItem.media_content_id ===
|
||||
"media-source://media_source/local/."
|
||||
? html`
|
||||
<div class="highlight-add-button">
|
||||
<span>
|
||||
<ha-svg-icon
|
||||
.path=${mdiArrowUpRight}
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.components.media-browser.file_management.highlight_button"
|
||||
)}
|
||||
</span>
|
||||
: currentItem.children?.length
|
||||
? childrenMediaClass.layout === "grid"
|
||||
? html`
|
||||
<div
|
||||
class="children ${classMap({
|
||||
portrait:
|
||||
childrenMediaClass.thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${currentItem.children.map(
|
||||
(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>
|
||||
`
|
||||
: this.hass.localize(
|
||||
"ui.components.media-browser.no_items"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: childrenMediaClass.layout === "grid"
|
||||
? html`
|
||||
<div
|
||||
class="children ${classMap({
|
||||
portrait:
|
||||
childrenMediaClass.thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${currentItem.children.map(
|
||||
(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"
|
||||
)}
|
||||
</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)}
|
||||
>
|
||||
<ha-icon-button
|
||||
class="play ${classMap({
|
||||
show:
|
||||
!mediaClass.show_list_images ||
|
||||
!child.thumbnail,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
<div
|
||||
class=${classMap({
|
||||
graphic: true,
|
||||
lazythumbnail:
|
||||
mediaClass.show_list_images === true,
|
||||
})}
|
||||
data-src=${ifDefined(
|
||||
mediaClass.show_list_images && child.thumbnail
|
||||
? child.thumbnail
|
||||
: undefined
|
||||
)}
|
||||
.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>
|
||||
`
|
||||
slot="graphic"
|
||||
>
|
||||
<ha-icon-button
|
||||
class="play ${classMap({
|
||||
show:
|
||||
!mediaClass.show_list_images ||
|
||||
!child.thumbnail,
|
||||
})}"
|
||||
.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>
|
||||
@@ -433,8 +425,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
if (changedProps.has("entityId")) {
|
||||
this._setError(undefined);
|
||||
} else if (!changedProps.has("navigateIds")) {
|
||||
// Neither entity ID or navigateIDs changed, nothing to fetch
|
||||
}
|
||||
if (!changedProps.has("navigateIds")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -443,7 +435,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
const oldNavigateIds = changedProps.get("navigateIds") as
|
||||
| this["navigateIds"]
|
||||
| undefined;
|
||||
const navigateIds = this.navigateIds;
|
||||
|
||||
// We're navigating. Reset the shizzle.
|
||||
this._content?.scrollTo(0, 0);
|
||||
@@ -452,9 +443,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
const oldParentItem = this._parentItem;
|
||||
this._currentItem = undefined;
|
||||
this._parentItem = undefined;
|
||||
const currentId = navigateIds[navigateIds.length - 1];
|
||||
const currentId = this.navigateIds[this.navigateIds.length - 1];
|
||||
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 parentProm: Promise<MediaPlayerItem> | undefined;
|
||||
|
||||
@@ -463,9 +456,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
if (
|
||||
// Check if we navigated to a child
|
||||
oldNavigateIds &&
|
||||
navigateIds.length === oldNavigateIds.length + 1 &&
|
||||
this.navigateIds.length > oldNavigateIds.length &&
|
||||
oldNavigateIds.every((oldVal, idx) => {
|
||||
const curVal = navigateIds[idx];
|
||||
const curVal = this.navigateIds[idx];
|
||||
return (
|
||||
curVal.media_content_id === oldVal.media_content_id &&
|
||||
curVal.media_content_type === oldVal.media_content_type
|
||||
@@ -476,8 +469,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
} else if (
|
||||
// Check if we navigated to a parent
|
||||
oldNavigateIds &&
|
||||
navigateIds.length === oldNavigateIds.length - 1 &&
|
||||
navigateIds.every((curVal, idx) => {
|
||||
this.navigateIds.length < oldNavigateIds.length &&
|
||||
this.navigateIds.every((curVal, idx) => {
|
||||
const oldVal = oldNavigateIds[idx];
|
||||
return (
|
||||
curVal.media_content_id === oldVal.media_content_id &&
|
||||
@@ -500,33 +493,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
(item) => {
|
||||
this._currentItem = item;
|
||||
fireEvent(this, "media-browsed", {
|
||||
ids: navigateIds,
|
||||
ids: this.navigateIds,
|
||||
current: item,
|
||||
});
|
||||
},
|
||||
(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);
|
||||
}
|
||||
}
|
||||
(err) => this._setError(err)
|
||||
);
|
||||
// Fetch parent
|
||||
if (!parentProm && parentId !== undefined) {
|
||||
@@ -765,18 +736,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
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 {
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
|
@@ -186,3 +186,10 @@ export const updateCloudAlexaEntityConfig = (
|
||||
entity_id: entityId,
|
||||
...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]));
|
||||
};
|
@@ -261,10 +261,8 @@ export const computeMediaControls = (
|
||||
});
|
||||
}
|
||||
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
|
||||
if (
|
||||
(state === "playing" || state === "paused" || assumedState) &&
|
||||
(state === "playing" || state === "paused") &&
|
||||
supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
||||
) {
|
||||
buttons.push({
|
||||
@@ -274,15 +272,14 @@ export const computeMediaControls = (
|
||||
}
|
||||
|
||||
if (
|
||||
!assumedState &&
|
||||
((state === "playing" &&
|
||||
(state === "playing" &&
|
||||
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
||||
((state === "paused" || state === "idle") &&
|
||||
supportsFeature(stateObj, SUPPORT_PLAY)) ||
|
||||
(state === "on" &&
|
||||
(supportsFeature(stateObj, SUPPORT_PLAY) ||
|
||||
supportsFeature(stateObj, SUPPORT_PAUSE))))
|
||||
((state === "paused" || state === "idle") &&
|
||||
supportsFeature(stateObj, SUPPORT_PLAY)) ||
|
||||
(state === "on" &&
|
||||
(supportsFeature(stateObj, SUPPORT_PLAY) ||
|
||||
supportsFeature(stateObj, SUPPORT_PAUSE)))
|
||||
) {
|
||||
buttons.push({
|
||||
icon:
|
||||
@@ -302,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 (
|
||||
(state === "playing" || state === "paused" || assumedState) &&
|
||||
(state === "playing" || state === "paused") &&
|
||||
supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
||||
) {
|
||||
buttons.push({
|
||||
|
@@ -22,7 +22,6 @@ export interface MQTTEntityDebugInfo {
|
||||
entity_id: string;
|
||||
discovery_data: MQTTDiscoveryDebugInfo;
|
||||
subscriptions: MQTTTopicDebugInfo[];
|
||||
transmitted: MQTTTopicDebugInfo[];
|
||||
}
|
||||
|
||||
export interface MQTTTriggerDebugInfo {
|
||||
|
@@ -70,15 +70,10 @@ export interface DelayAction {
|
||||
delay: number | Partial<DelayActionParts> | string;
|
||||
}
|
||||
|
||||
export interface ServiceSceneAction extends ServiceAction {
|
||||
service: "scene.turn_on";
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
export interface LegacySceneAction {
|
||||
export interface SceneAction {
|
||||
alias?: string;
|
||||
scene: string;
|
||||
}
|
||||
export type SceneAction = ServiceSceneAction | LegacySceneAction;
|
||||
|
||||
export interface WaitAction {
|
||||
alias?: string;
|
||||
@@ -158,8 +153,7 @@ export interface ActionTypes {
|
||||
check_condition: Condition;
|
||||
fire_event: EventAction;
|
||||
device_action: DeviceAction;
|
||||
legacy_activate_scene: LegacySceneAction;
|
||||
activate_scene: ServiceSceneAction;
|
||||
activate_scene: SceneAction;
|
||||
repeat: RepeatAction;
|
||||
choose: ChooseAction;
|
||||
wait_for_trigger: WaitForTriggerAction;
|
||||
@@ -224,7 +218,7 @@ export const getActionType = (action: Action): ActionType => {
|
||||
return "device_action";
|
||||
}
|
||||
if ("scene" in action) {
|
||||
return "legacy_activate_scene";
|
||||
return "activate_scene";
|
||||
}
|
||||
if ("repeat" in action) {
|
||||
return "repeat";
|
||||
@@ -239,14 +233,6 @@ export const getActionType = (action: Action): ActionType => {
|
||||
return "variables";
|
||||
}
|
||||
if ("service" in action) {
|
||||
if ("metadata" in action) {
|
||||
if (
|
||||
(action as ServiceAction).service === "scene.turn_on" &&
|
||||
!Array.isArray((action as ServiceAction)?.target?.entity_id)
|
||||
) {
|
||||
return "activate_scene";
|
||||
}
|
||||
}
|
||||
return "service";
|
||||
}
|
||||
return "unknown";
|
||||
|
@@ -11,8 +11,7 @@ import {
|
||||
DelayAction,
|
||||
EventAction,
|
||||
getActionType,
|
||||
LegacySceneAction,
|
||||
ServiceSceneAction,
|
||||
SceneAction,
|
||||
VariablesAction,
|
||||
WaitForTriggerAction,
|
||||
} from "./script";
|
||||
@@ -103,22 +102,14 @@ export const describeAction = <T extends ActionType>(
|
||||
return `Delay ${duration}`;
|
||||
}
|
||||
|
||||
if (actionType === "legacy_activate_scene") {
|
||||
const config = action as LegacySceneAction;
|
||||
if (actionType === "activate_scene") {
|
||||
const config = action as SceneAction;
|
||||
const sceneStateObj = hass.states[config.scene];
|
||||
return `Activate scene ${
|
||||
sceneStateObj ? computeStateName(sceneStateObj) : config.scene
|
||||
}`;
|
||||
}
|
||||
|
||||
if (actionType === "activate_scene") {
|
||||
const config = action as ServiceSceneAction;
|
||||
const sceneStateObj = hass.states[config.target!.entity_id as string];
|
||||
return `Activate scene ${
|
||||
sceneStateObj ? computeStateName(sceneStateObj) : config.target!.entity_id
|
||||
}`;
|
||||
}
|
||||
|
||||
if (actionType === "wait_for_trigger") {
|
||||
const config = action as WaitForTriggerAction;
|
||||
return `Wait for ${ensureArray(config.wait_for_trigger)
|
||||
|
@@ -12,8 +12,7 @@ export type Selector =
|
||||
| ActionSelector
|
||||
| StringSelector
|
||||
| ObjectSelector
|
||||
| SelectSelector
|
||||
| IconSelector;
|
||||
| SelectSelector;
|
||||
|
||||
export interface EntitySelector {
|
||||
entity: {
|
||||
@@ -134,18 +133,8 @@ export interface ObjectSelector {
|
||||
object: {};
|
||||
}
|
||||
|
||||
export interface SelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface SelectSelector {
|
||||
select: {
|
||||
options: string[] | SelectOption[];
|
||||
options: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IconSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
icon: {};
|
||||
}
|
||||
|
@@ -10,11 +10,3 @@ export const convertTextToSpeech = (
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
) => 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);
|
||||
|
@@ -126,7 +126,6 @@ export interface ZWaveJSNodeStatus {
|
||||
is_routing: boolean | null;
|
||||
zwave_plus_version: number | null;
|
||||
highest_security_class: SecurityClass | null;
|
||||
is_controller_node: boolean;
|
||||
}
|
||||
|
||||
export interface ZwaveJSNodeMetadata {
|
||||
|
@@ -83,7 +83,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
.checked=${!this._disableNewEntities}
|
||||
@change=${this._disableNewEntitiesChanged}
|
||||
.disabled=${this._submitting}
|
||||
dialogInitialFocus
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
${this._allowUpdatePolling()
|
||||
|
@@ -1,36 +1,30 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../common/search/search-input";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../../components/ha-icon-next";
|
||||
import { getConfigEntries } from "../../data/config_entries";
|
||||
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 { brandsUrl } from "../../util/brands-url";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
|
||||
interface HandlerObj {
|
||||
name: string;
|
||||
slug: string;
|
||||
is_add?: boolean;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -82,17 +76,6 @@ class StepFlowPickHandler extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
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`
|
||||
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
|
||||
<search-input
|
||||
@@ -103,20 +86,39 @@ class StepFlowPickHandler extends LitElement {
|
||||
.label=${this.hass.localize("ui.panel.config.integrations.search")}
|
||||
@keypress=${this._maybeSubmit}
|
||||
></search-input>
|
||||
<mwc-list
|
||||
<div
|
||||
style=${styleMap({
|
||||
width: `${this._width}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.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`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
@@ -139,32 +141,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
>.
|
||||
</p>
|
||||
`}
|
||||
</mwc-list>
|
||||
`;
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -185,7 +162,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
) {
|
||||
// Store the width and height so that when we search, box doesn't jump
|
||||
const boundingRect =
|
||||
this.shadowRoot!.querySelector("mwc-list")!.getBoundingClientRect();
|
||||
this.shadowRoot!.querySelector("div")!.getBoundingClientRect();
|
||||
this._width = boundingRect.width;
|
||||
this._height = boundingRect.height;
|
||||
}
|
||||
@@ -212,31 +189,8 @@ class StepFlowPickHandler extends LitElement {
|
||||
}
|
||||
|
||||
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", {
|
||||
handler: handler.slug,
|
||||
handler: ev.currentTarget.handler.slug,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -264,23 +218,20 @@ class StepFlowPickHandler extends LitElement {
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
margin: 16px 16px 0;
|
||||
margin: 8px 16px 0;
|
||||
}
|
||||
ha-icon-next {
|
||||
margin-right: 8px;
|
||||
}
|
||||
mwc-list {
|
||||
div {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
.divider {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
h2 {
|
||||
padding-right: 66px;
|
||||
}
|
||||
@media all and (max-height: 900px) {
|
||||
mwc-list {
|
||||
div {
|
||||
max-height: calc(100vh - 134px);
|
||||
}
|
||||
}
|
||||
|
@@ -615,6 +615,10 @@ class MoreInfoLight extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: var(--divider-color);
|
||||
border-bottom: none;
|
||||
|
@@ -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 { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-attributes";
|
||||
@@ -66,6 +66,14 @@ class MoreInfoRemote extends LitElement {
|
||||
activity: newVal,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -241,6 +241,9 @@ class MoreInfoVacuum extends LitElement {
|
||||
.status-subtitle {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.flex-horizontal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@@ -338,9 +338,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
}
|
||||
.content {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-header-bar {
|
||||
--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));
|
||||
}
|
||||
|
||||
.content {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 90vw;
|
||||
|
@@ -1,11 +1,7 @@
|
||||
import {
|
||||
setPassiveTouchGestures,
|
||||
setCancelSyntheticClickEvents,
|
||||
} from "@polymer/polymer/lib/utils/settings";
|
||||
import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../layouts/home-assistant";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
import "../util/legacy-support";
|
||||
|
||||
setPassiveTouchGestures(true);
|
||||
setCancelSyntheticClickEvents(false);
|
||||
|
@@ -1,10 +1,13 @@
|
||||
// Compat needs to be first import
|
||||
import "../resources/compatibility";
|
||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../auth/ha-authorize";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
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
|
||||
import "../resources/compatibility";
|
||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
|
||||
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 { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
|
||||
|
||||
setCancelSyntheticClickEvents(false);
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
loadES5Adapter: () => Promise<unknown>;
|
||||
@@ -50,8 +47,7 @@ function initialize(
|
||||
) {
|
||||
const style = document.createElement("style");
|
||||
|
||||
style.innerHTML = `
|
||||
body { margin:0; }
|
||||
style.innerHTML = `body { margin:0; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #111111;
|
||||
|
@@ -1,14 +1,11 @@
|
||||
// Compat needs to be first import
|
||||
import "../resources/compatibility";
|
||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../onboarding/ha-onboarding";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
import "../resources/array.flat.polyfill";
|
||||
|
||||
setCancelSyntheticClickEvents(false);
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
stepsPromise: Promise<Response>;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiFilterVariant } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
@@ -156,31 +157,30 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
: hiddenLabel;
|
||||
|
||||
const headerToolbar = html`<search-input
|
||||
.hass=${this.hass}
|
||||
.filter=${this.filter}
|
||||
.suffix=${!this.narrow}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.searchLabel ||
|
||||
this.hass.localize("ui.components.data-table.search")}
|
||||
>
|
||||
${!this.narrow
|
||||
? html`<div
|
||||
class="filters"
|
||||
slot="suffix"
|
||||
@click=${this._preventDefault}
|
||||
>
|
||||
${filterInfo
|
||||
? html`<div class="active-filters">
|
||||
${filterInfo}
|
||||
<mwc-button @click=${this._clearFilter}>
|
||||
${this.hass.localize("ui.components.data-table.clear")}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
: ""}
|
||||
<slot name="filter-menu"></slot>
|
||||
</div>`
|
||||
: ""}
|
||||
</search-input>`;
|
||||
.hass=${this.hass}
|
||||
.filter=${this.filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.searchLabel ||
|
||||
this.hass.localize("ui.components.data-table.search")}
|
||||
>
|
||||
</search-input>
|
||||
<div class="filters">
|
||||
${filterInfo
|
||||
? html`<div class="active-filters">
|
||||
${this.narrow
|
||||
? html`<div>
|
||||
<ha-svg-icon .path=${mdiFilterVariant}></ha-svg-icon>
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${filterInfo}
|
||||
</paper-tooltip>
|
||||
</div>`
|
||||
: filterInfo}
|
||||
<mwc-button @click=${this._clearFilter}>
|
||||
${this.hass.localize("ui.components.data-table.clear")}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
: ""}<slot name="filter-menu"></slot>
|
||||
</div>`;
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
@@ -195,16 +195,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
.mainPage=${this.mainPage}
|
||||
.supervisor=${this.supervisor}
|
||||
>
|
||||
<div slot="toolbar-icon">
|
||||
${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>
|
||||
<div slot="toolbar-icon"><slot name="toolbar-icon"></slot></div>
|
||||
${this.narrow
|
||||
? html`
|
||||
<div slot="header">
|
||||
@@ -242,10 +233,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _preventDefault(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
if (this.filter === ev.detail.value) {
|
||||
return;
|
||||
@@ -280,12 +267,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
align-items: center;
|
||||
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 {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@@ -295,19 +276,16 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
}
|
||||
.search-toolbar search-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-text-field-fill-color: transparant;
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
--mdc-ripple-color: transparant;
|
||||
}
|
||||
.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;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
margin-right: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.active-filters {
|
||||
color: var(--primary-text-color);
|
||||
@@ -317,8 +295,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
padding: 2px 2px 2px 8px;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
width: max-content;
|
||||
cursor: initial;
|
||||
}
|
||||
.active-filters ha-svg-icon {
|
||||
color: var(--primary-color);
|
||||
@@ -337,24 +313,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
left: 0;
|
||||
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-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
|
@@ -78,7 +78,8 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initializeHass();
|
||||
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._updateHass({ suspendWhenHidden: ev.detail.suspend });
|
||||
storeState(this.hass!);
|
||||
|
@@ -140,6 +140,8 @@ class OnboardingIntegrations extends LitElement {
|
||||
this._scanUSBDevices();
|
||||
loadConfigFlowDialog();
|
||||
this._loadConfigEntries();
|
||||
/* polyfill for paper-dropdown */
|
||||
import("web-animations-js/web-animations-next-lite.min");
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
|
@@ -16,7 +16,7 @@ import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { Action, ServiceSceneAction } from "../../../../data/script";
|
||||
import type { Action } from "../../../../data/script";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -44,28 +44,8 @@ const OPTIONS = [
|
||||
"device_id",
|
||||
];
|
||||
|
||||
const getType = (action: Action | undefined) => {
|
||||
if (!action) {
|
||||
return undefined;
|
||||
}
|
||||
if ("metadata" in action && action.service) {
|
||||
switch (action.service) {
|
||||
case "scene.turn_on":
|
||||
// we dont support arrays of entities
|
||||
if (
|
||||
!Array.isArray(
|
||||
(action as unknown as ServiceSceneAction).target?.entity_id
|
||||
)
|
||||
) {
|
||||
return "scene";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return OPTIONS.find((option) => option in action);
|
||||
};
|
||||
const getType = (action: Action | undefined) =>
|
||||
action ? OPTIONS.find((option) => option in action) : undefined;
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
@@ -16,23 +16,11 @@ export class HaSceneAction extends LitElement implements ActionElement {
|
||||
@property() public action!: SceneAction;
|
||||
|
||||
public static get defaultConfig(): SceneAction {
|
||||
return {
|
||||
service: "scene.turn_on",
|
||||
target: {
|
||||
entity_id: "",
|
||||
},
|
||||
metadata: {},
|
||||
};
|
||||
return { scene: "" };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
let scene;
|
||||
|
||||
if ("scene" in this.action) {
|
||||
scene = this.action.scene;
|
||||
} else {
|
||||
scene = this.action.target?.entity_id;
|
||||
}
|
||||
const { scene } = this.action;
|
||||
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
@@ -48,13 +36,7 @@ export class HaSceneAction extends LitElement implements ActionElement {
|
||||
private _entityPicked(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
service: "scene.turn_on",
|
||||
target: {
|
||||
entity_id: ev.detail.value,
|
||||
},
|
||||
metadata: {},
|
||||
},
|
||||
value: { ...this.action, scene: ev.detail.value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { any, assert, object, optional, string } from "superstruct";
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -49,35 +48,41 @@ export class HaTriggerCondition extends LitElement {
|
||||
"ui.panel.config.automation.editor.conditions.type.trigger.no_triggers"
|
||||
);
|
||||
}
|
||||
return html`<mwc-select
|
||||
return html`<paper-dropdown-menu-light
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.trigger.id"
|
||||
)}
|
||||
.value=${id}
|
||||
@selected=${this._triggerPicked}
|
||||
no-animations
|
||||
>
|
||||
${ensureArray(this._triggers).map((trigger) =>
|
||||
trigger.id
|
||||
? html`
|
||||
<mwc-list-item .value=${trigger.id}>
|
||||
${trigger.id}
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
</mwc-select>`;
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${id}
|
||||
attr-for-selected="data-trigger-id"
|
||||
@selected-item-changed=${this._triggerPicked}
|
||||
>
|
||||
${ensureArray(this._triggers).map((trigger) =>
|
||||
trigger.id
|
||||
? html`
|
||||
<paper-item data-trigger-id=${trigger.id}>
|
||||
${trigger.id}
|
||||
</paper-item>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>`;
|
||||
}
|
||||
|
||||
private _automationUpdated(config?: AutomationConfig) {
|
||||
this._triggers = config?.trigger;
|
||||
}
|
||||
|
||||
private _triggerPicked(ev) {
|
||||
private _triggerPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.target.value) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
const newTrigger = ev.target.value;
|
||||
const newTrigger = ev.detail.value.dataset.triggerId;
|
||||
if (this.condition.id === newTrigger) {
|
||||
return;
|
||||
}
|
||||
|
@@ -85,7 +85,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
)}
|
||||
.value=${this.config.mode ? MODES.indexOf(this.config.mode) : 0}
|
||||
@selected=${this._modeChanged}
|
||||
fixedMenuPosition
|
||||
>
|
||||
${MODES.map(
|
||||
(mode) => html`
|
||||
@@ -318,9 +317,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
ha-entity-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
mwc-select {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
@@ -150,13 +150,6 @@ export class HaDeviceTrigger extends LitElement {
|
||||
`ui.panel.config.automation.editor.triggers.type.device.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||
import type { NumericStateTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import { NumericStateTrigger } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
import "../../../../../components/ha-duration-input";
|
||||
|
||||
@customElement("ha-automation-trigger-numeric_state")
|
||||
export class HaNumericStateTrigger extends LitElement {
|
||||
@@ -15,22 +17,6 @@ export class HaNumericStateTrigger extends LitElement {
|
||||
|
||||
@property() public trigger!: NumericStateTrigger;
|
||||
|
||||
private _schema = memoizeOne((entityId): HaFormSchema[] => [
|
||||
{ name: "entity_id", selector: { entity: {} } },
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entityId } },
|
||||
},
|
||||
{ name: "above", required: false, selector: { text: {} } },
|
||||
{ name: "below", required: false, selector: { text: {} } },
|
||||
{
|
||||
name: "value_template",
|
||||
required: false,
|
||||
selector: { text: { multiline: true } },
|
||||
},
|
||||
{ name: "for", required: false, selector: { duration: {} } },
|
||||
]);
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("trigger")) {
|
||||
return;
|
||||
@@ -52,46 +38,67 @@ export class HaNumericStateTrigger extends LitElement {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { value_template, entity_id, attribute, below, above } = this.trigger;
|
||||
const trgFor = createDurationData(this.trigger.for);
|
||||
|
||||
const data = { ...this.trigger, for: trgFor };
|
||||
const schema = this._schema(this.trigger.entity_id);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
<ha-entity-picker
|
||||
.value=${entity_id}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>
|
||||
.name=${"entity_id"}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<ha-entity-attribute-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${entity_id}
|
||||
.value=${attribute}
|
||||
.name=${"attribute"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.attribute"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-value
|
||||
></ha-entity-attribute-picker>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.above"
|
||||
)}
|
||||
name="above"
|
||||
.value=${above}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.below"
|
||||
)}
|
||||
name="below"
|
||||
.value=${below}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.value_template"
|
||||
)}
|
||||
name="value_template"
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
<ha-duration-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.for"
|
||||
)}
|
||||
.name=${"for"}
|
||||
.data=${trgFor}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-duration-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const newTrigger = ev.detail.value;
|
||||
fireEvent(this, "value-changed", { value: newTrigger });
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string => {
|
||||
switch (schema.name) {
|
||||
case "entity_id":
|
||||
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||
case "attribute":
|
||||
return this.hass.localize(
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
);
|
||||
case "for":
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.state.for`
|
||||
);
|
||||
default:
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.numeric_state.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../../../../../common/string/compare";
|
||||
import { TagTrigger } from "../../../../../data/automation";
|
||||
import { fetchTags, Tag } from "../../../../../data/tag";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { TriggerElement } from "../ha-automation-trigger-row";
|
||||
import "../../../../../components/ha-paper-dropdown-menu";
|
||||
import { caseInsensitiveStringCompare } from "../../../../../common/string/compare";
|
||||
|
||||
@customElement("ha-automation-trigger-tag")
|
||||
export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
@@ -29,22 +29,27 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
protected render() {
|
||||
const { tag_id } = this.trigger;
|
||||
return html`
|
||||
<mwc-select
|
||||
<ha-paper-dropdown-menu
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.tag.label"
|
||||
)}
|
||||
.disabled=${this._tags.length === 0}
|
||||
.value=${tag_id}
|
||||
@selected=${this._tagChanged}
|
||||
?disabled=${this._tags.length === 0}
|
||||
>
|
||||
${this._tags.map(
|
||||
(tag) => html`
|
||||
<mwc-list-item .value=${tag.id}>
|
||||
${tag.name || tag.id}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${tag_id}
|
||||
attr-for-selected="tag_id"
|
||||
@iron-select=${this._tagChanged}
|
||||
>
|
||||
${this._tags.map(
|
||||
(tag) => html`
|
||||
<paper-item tag_id=${tag.id} .tag=${tag}>
|
||||
${tag.name || tag.id}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -59,14 +64,8 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.trigger,
|
||||
tag_id: ev.target.value,
|
||||
tag_id: ev.detail.item.tag.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-trigger-tag": HaTagTrigger;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
@@ -5,17 +5,18 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-switch";
|
||||
import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
|
||||
import {
|
||||
CloudStatusLoggedIn,
|
||||
CloudTTSInfo,
|
||||
getCloudTTSInfo,
|
||||
getCloudTtsLanguages,
|
||||
getCloudTtsSupportedGenders,
|
||||
} from "../../../../data/cloud/tts";
|
||||
updateCloudPref,
|
||||
} from "../../../../data/cloud";
|
||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { translationMetadata } from "../../../../resources/translations-metadata";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showTryTtsDialog } from "./show-dialog-cloud-tts-try";
|
||||
|
||||
@@ -36,11 +37,7 @@ export class CloudTTSPref extends LitElement {
|
||||
|
||||
const languages = this.getLanguages(this.ttsInfo);
|
||||
const defaultVoice = this.cloudStatus.prefs.tts_default_voice;
|
||||
const genders = this.getSupportedGenders(
|
||||
defaultVoice[0],
|
||||
this.ttsInfo,
|
||||
this.hass.localize
|
||||
);
|
||||
const genders = this.getSupportedGenders(defaultVoice[0], this.ttsInfo);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
@@ -103,9 +100,61 @@ export class CloudTTSPref extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private getLanguages = memoizeOne(getCloudTtsLanguages);
|
||||
private getLanguages = memoizeOne((info?: CloudTTSInfo) => {
|
||||
const languages: Array<[string, string]> = [];
|
||||
|
||||
private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders);
|
||||
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]));
|
||||
});
|
||||
|
||||
private getSupportedGenders = memoizeOne(
|
||||
(language: string, info?: CloudTTSInfo) => {
|
||||
const genders: Array<[string, string]> = [];
|
||||
|
||||
if (!info) {
|
||||
return genders;
|
||||
}
|
||||
|
||||
for (const [curLang, gender] of info.languages) {
|
||||
if (curLang === language) {
|
||||
genders.push([
|
||||
gender,
|
||||
this.hass.localize(`ui.panel.config.cloud.account.tts.${gender}`) ||
|
||||
gender,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
|
||||
}
|
||||
);
|
||||
|
||||
private _openTryDialog() {
|
||||
showTryTtsDialog(this, {
|
||||
@@ -121,11 +170,7 @@ export class CloudTTSPref extends LitElement {
|
||||
const language = ev.target.value;
|
||||
|
||||
const curGender = this.cloudStatus!.prefs.tts_default_voice[1];
|
||||
const genders = this.getSupportedGenders(
|
||||
language,
|
||||
this.ttsInfo,
|
||||
this.hass.localize
|
||||
);
|
||||
const genders = this.getSupportedGenders(language, this.ttsInfo);
|
||||
const newGender = genders.find((item) => item[0] === curGender)
|
||||
? curGender
|
||||
: genders[0][0];
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
|
@@ -266,6 +266,9 @@ export class CloudRegister extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
@@ -165,7 +165,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
const stateObj = this.hass.states[entry.entity_id];
|
||||
const name = stripPrefixFromEntityName(
|
||||
computeStateName(stateObj),
|
||||
this.deviceName.toLowerCase()
|
||||
`${this.deviceName} `.toLowerCase()
|
||||
);
|
||||
if (name) {
|
||||
config.name = name;
|
||||
@@ -198,7 +198,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
${name
|
||||
? stripPrefixFromEntityName(
|
||||
name,
|
||||
this.deviceName.toLowerCase()
|
||||
`${this.deviceName} `.toLowerCase()
|
||||
) || name
|
||||
: entry.entity_id}
|
||||
</div>
|
||||
|
@@ -71,7 +71,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
<ha-switch
|
||||
.checked=${this._showDeserialized}
|
||||
@change=${this._showDeserializedChanged}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -165,7 +164,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
<code>${topic.topic}</code>
|
||||
<mqtt-messages
|
||||
.hass=${this.hass}
|
||||
direction="Received"
|
||||
.messages=${topic.messages}
|
||||
.showDeserialized=${this._showDeserialized}
|
||||
.showAsYaml=${this._showAsYaml}
|
||||
@@ -176,31 +174,7 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
topic.messages.length
|
||||
)}
|
||||
>
|
||||
</mqtt-rx-messages>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
Transmitted messages:
|
||||
<ul>
|
||||
${entity.transmitted.map(
|
||||
(topic) => html`
|
||||
<li>
|
||||
<code>${topic.topic}</code>
|
||||
<mqtt-messages
|
||||
.hass=${this.hass}
|
||||
direction="Transmitted"
|
||||
.messages=${topic.messages}
|
||||
.showDeserialized=${this._showDeserialized}
|
||||
.showAsYaml=${this._showAsYaml}
|
||||
.subscribedTopic=${topic.topic}
|
||||
.summary=${this.hass!.localize(
|
||||
"ui.dialogs.mqtt_device_debug_info.recent_tx_messages",
|
||||
"n",
|
||||
topic.messages.length
|
||||
)}
|
||||
>
|
||||
</mqtt-tx-messages>
|
||||
</mqtt-messages>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
|
@@ -12,8 +12,6 @@ class MQTTMessages extends LitElement {
|
||||
|
||||
@property() public messages!: MQTTMessage[];
|
||||
|
||||
@property() public direction!: string;
|
||||
|
||||
@property() public showAsYaml = false;
|
||||
|
||||
@property() public showDeserialized = false;
|
||||
@@ -52,7 +50,7 @@ class MQTTMessages extends LitElement {
|
||||
(message) => html`
|
||||
<li class="message">
|
||||
<div class="time">
|
||||
${this.direction}
|
||||
Received
|
||||
${formatTimeWithSeconds(
|
||||
new Date(message.time),
|
||||
this.hass.locale
|
||||
|
@@ -10,10 +10,8 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
getZwaveJsIdentifiersFromDevice,
|
||||
ZWaveJSNodeIdentifiers,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
@@ -31,67 +29,43 @@ export class HaDeviceActionsZWaveJS extends LitElement {
|
||||
|
||||
@state() private _nodeId?: number;
|
||||
|
||||
@state() private _node?: ZWaveJSNodeStatus;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("device")) {
|
||||
this._entryId = this.device.config_entries[0];
|
||||
|
||||
const identifiers: ZWaveJSNodeIdentifiers | undefined =
|
||||
getZwaveJsIdentifiersFromDevice(this.device);
|
||||
if (!identifiers) {
|
||||
return;
|
||||
}
|
||||
this._nodeId = identifiers.node_id;
|
||||
this._entryId = this.device.config_entries[0];
|
||||
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
protected async _fetchNodeDetails() {
|
||||
if (!this._nodeId || !this._entryId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._node = await fetchZwaveNodeStatus(
|
||||
this.hass,
|
||||
this._entryId,
|
||||
this._nodeId
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._node) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<a
|
||||
.href=${`/config/zwave_js/node_config/${this.device.id}?config_entry=${this._entryId}`}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._reinterviewClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._healNodeClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.heal_node"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._removeFailedNode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<a
|
||||
.href=${`/config/zwave_js/node_config/${this.device.id}?config_entry=${this._entryId}`}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._reinterviewClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._healNodeClicked}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.device_info.heal_node")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._removeFailedNode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
)}
|
||||
</mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -103,58 +103,52 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperItemElement } from "@polymer/paper-item/paper-item";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -18,6 +17,7 @@ import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import {
|
||||
@@ -158,23 +158,28 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
></ha-icon-picker>
|
||||
${OVERRIDE_DEVICE_CLASSES[domain]?.includes(this._deviceClass) ||
|
||||
(domain === "cover" && this.entry.original_device_class === null)
|
||||
? html`<mwc-select
|
||||
? html`<ha-paper-dropdown-menu
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.device_class"
|
||||
)}
|
||||
.value=${this._deviceClass}
|
||||
@selected=${this._deviceClassChanged}
|
||||
>
|
||||
${OVERRIDE_DEVICE_CLASSES[domain].map(
|
||||
(deviceClass: string) => html`
|
||||
<mwc-list-item .value=${deviceClass}>
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}`
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>`
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-value"
|
||||
.selected=${this._deviceClass}
|
||||
@selected-item-changed=${this._deviceClassChanged}
|
||||
>
|
||||
${OVERRIDE_DEVICE_CLASSES[domain].map(
|
||||
(deviceClass: string) => html`
|
||||
<paper-item .itemValue=${deviceClass}>
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}`
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>`
|
||||
: ""}
|
||||
<paper-input
|
||||
.value=${this._entityId}
|
||||
@@ -297,9 +302,12 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
this._entityId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _deviceClassChanged(ev): void {
|
||||
private _deviceClassChanged(ev: PolymerChangedEvent<PaperItemElement>): void {
|
||||
this._error = undefined;
|
||||
this._deviceClass = ev.target.value;
|
||||
if (ev.detail.value === null) {
|
||||
return;
|
||||
}
|
||||
this._deviceClass = (ev.detail.value as any).itemValue;
|
||||
}
|
||||
|
||||
private _areaPicked(ev: CustomEvent) {
|
||||
@@ -417,7 +425,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||
background-color: var(--mdc-theme-surface, #fff);
|
||||
}
|
||||
mwc-select {
|
||||
ha-paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
ha-switch {
|
||||
|
@@ -22,6 +22,10 @@ documentContainer.innerHTML = `<dom-module id="ha-form-style">
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-start;
|
||||
}
|
||||
|
||||
paper-dropdown-menu.form-control {
|
||||
margin: -9px 0;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
</dom-module>`;
|
||||
|
@@ -21,6 +21,8 @@ import {
|
||||
mdiTools,
|
||||
mdiViewDashboard,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
@@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
|
@@ -297,36 +297,29 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
this._filter
|
||||
);
|
||||
|
||||
const filterMenu = html`<div
|
||||
slot=${ifDefined(this.narrow ? "toolbar-icon" : "suffix")}
|
||||
const filterMenu = html`<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
multi
|
||||
slot=${ifDefined(this.narrow ? "toolbar-icon" : undefined)}
|
||||
@action=${this._handleMenuAction}
|
||||
>
|
||||
${!this._showDisabled && this.narrow && disabledCount
|
||||
? html`<span class="badge">${disabledCount}</span>`
|
||||
: ""}
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
multi
|
||||
@action=${this._handleMenuAction}
|
||||
@click=${this._preventDefault}
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiFilterVariant}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiFilterVariant}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-check-list-item left .selected=${this._showIgnored}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.show_ignored"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
<ha-check-list-item left .selected=${this._showDisabled}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show_disabled"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
</ha-button-menu>
|
||||
</div>`;
|
||||
</ha-icon-button>
|
||||
<ha-check-list-item left .selected=${this._showIgnored}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.show_ignored"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
<ha-check-list-item left .selected=${this._showDisabled}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show_disabled"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
</ha-button-menu>`;
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
@@ -343,6 +336,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
class="header"
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.search"
|
||||
@@ -355,33 +350,29 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
suffix
|
||||
no-label-float
|
||||
no-underline
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.search"
|
||||
)}
|
||||
>
|
||||
${!this._showDisabled && disabledCount
|
||||
? html`<div
|
||||
class="active-filters"
|
||||
slot="suffix"
|
||||
@click=${this._preventDefault}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.disabled_integrations",
|
||||
{ number: disabledCount }
|
||||
></search-input>
|
||||
${!this._showDisabled && disabledCount
|
||||
? html`<div class="active-filters">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.disabled_integrations",
|
||||
{ number: disabledCount }
|
||||
)}
|
||||
<mwc-button
|
||||
@click=${this._toggleShowDisabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show"
|
||||
)}
|
||||
<mwc-button
|
||||
@click=${this._toggleShowDisabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show"
|
||||
)}
|
||||
></mwc-button>
|
||||
</div>`
|
||||
: ""}
|
||||
${filterMenu}
|
||||
</search-input>
|
||||
></mwc-button>
|
||||
</div>`
|
||||
: ""}
|
||||
${filterMenu}
|
||||
</div>
|
||||
`}
|
||||
|
||||
@@ -512,10 +503,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _preventDefault(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private _loadConfigEntries() {
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
this._configEntries = configEntries
|
||||
@@ -696,15 +683,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
.empty-message h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
search-input {
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
--text-field-overflow: visible;
|
||||
}
|
||||
|
||||
search-input.header {
|
||||
display: block;
|
||||
color: var(--secondary-text-color);
|
||||
margin-left: 8px;
|
||||
--mdc-text-field-fill-color: transparant;
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
--mdc-ripple-color: transparant;
|
||||
}
|
||||
.search {
|
||||
@@ -732,8 +717,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
align-items: center;
|
||||
padding: 2px 2px 2px 8px;
|
||||
font-size: 14px;
|
||||
width: max-content;
|
||||
cursor: initial;
|
||||
}
|
||||
.active-filters mwc-button {
|
||||
margin-left: 8px;
|
||||
@@ -749,24 +732,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
left: 0;
|
||||
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: 14px;
|
||||
top: 8px;
|
||||
font-size: 0.65em;
|
||||
}
|
||||
ha-button-menu {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { Select } from "@material/mwc-select";
|
||||
import { Cluster, ZHADevice } from "../../../../../data/zha";
|
||||
|
||||
export interface PickerTarget extends EventTarget {
|
||||
selected: number;
|
||||
}
|
||||
|
||||
export interface ItemSelectedEvent {
|
||||
target?: Select;
|
||||
target?: PickerTarget;
|
||||
}
|
||||
|
||||
export interface ZHADeviceRemovedEvent {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
@@ -48,7 +48,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
|
||||
@state() private _attributes: Attribute[] = [];
|
||||
|
||||
@state() private _selectedAttributeId?: number;
|
||||
@state() private _selectedAttributeIndex = -1;
|
||||
|
||||
@state() private _attributeValue?: any = "";
|
||||
|
||||
@@ -60,7 +60,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("selectedCluster")) {
|
||||
this._attributes = [];
|
||||
this._selectedAttributeId = undefined;
|
||||
this._selectedAttributeIndex = -1;
|
||||
this._attributeValue = "";
|
||||
this._fetchAttributesForCluster();
|
||||
}
|
||||
@@ -92,25 +92,29 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="attribute-picker">
|
||||
<mwc-select
|
||||
.label=${this.hass!.localize(
|
||||
<paper-dropdown-menu
|
||||
label=${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.attributes_of_cluster"
|
||||
)}
|
||||
class="menu"
|
||||
.value=${String(this._selectedAttributeId)}
|
||||
@selected=${this._selectedAttributeChanged}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${this._attributes.map(
|
||||
(entry) => html`
|
||||
<mwc-list-item .value=${String(entry.id)}>
|
||||
${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._selectedAttributeIndex}
|
||||
@iron-select=${this._selectedAttributeChanged}
|
||||
>
|
||||
${this._attributes.map(
|
||||
(entry) => html`
|
||||
<paper-item
|
||||
>${entry.name +
|
||||
" (id: " +
|
||||
formatAsPaddedHex(entry.id) +
|
||||
")"}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this.showHelp
|
||||
? html`
|
||||
@@ -121,7 +125,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._selectedAttributeId !== undefined
|
||||
${this._selectedAttributeIndex !== -1
|
||||
? this._renderAttributeInteractions()
|
||||
: ""}
|
||||
</ha-card>
|
||||
@@ -214,7 +218,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
endpoint_id: this.selectedCluster!.endpoint_id,
|
||||
cluster_id: this.selectedCluster!.id,
|
||||
cluster_type: this.selectedCluster!.type,
|
||||
attribute: this._selectedAttributeId!,
|
||||
attribute: this._attributes[this._selectedAttributeIndex].id,
|
||||
manufacturer: this._manufacturerCodeOverride
|
||||
? parseInt(this._manufacturerCodeOverride as string, 10)
|
||||
: undefined,
|
||||
@@ -232,7 +236,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
endpoint_id: this.selectedCluster!.endpoint_id,
|
||||
cluster_id: this.selectedCluster!.id,
|
||||
cluster_type: this.selectedCluster!.type,
|
||||
attribute: this._selectedAttributeId!,
|
||||
attribute: this._attributes[this._selectedAttributeIndex].id,
|
||||
value: this._attributeValue,
|
||||
manufacturer: this._manufacturerCodeOverride
|
||||
? parseInt(this._manufacturerCodeOverride as string, 10)
|
||||
@@ -262,7 +266,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
}
|
||||
|
||||
private _selectedAttributeChanged(event: ItemSelectedEvent): void {
|
||||
this._selectedAttributeId = Number(event.target!.value);
|
||||
this._selectedAttributeIndex = event.target!.selected;
|
||||
this._attributeValue = "";
|
||||
}
|
||||
|
||||
@@ -270,10 +274,6 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
mwc-select {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
@@ -26,7 +26,11 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import { ChangeEvent, IssueCommandServiceData } from "./types";
|
||||
import {
|
||||
ChangeEvent,
|
||||
IssueCommandServiceData,
|
||||
ItemSelectedEvent,
|
||||
} from "./types";
|
||||
|
||||
export class ZHAClusterCommands extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@@ -41,7 +45,7 @@ export class ZHAClusterCommands extends LitElement {
|
||||
|
||||
@state() private _commands: Command[] = [];
|
||||
|
||||
@state() private _selectedCommandId?: number;
|
||||
@state() private _selectedCommandIndex = -1;
|
||||
|
||||
@state() private _manufacturerCodeOverride?: number;
|
||||
|
||||
@@ -51,7 +55,7 @@ export class ZHAClusterCommands extends LitElement {
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("selectedCluster")) {
|
||||
this._commands = [];
|
||||
this._selectedCommandId = undefined;
|
||||
this._selectedCommandIndex = -1;
|
||||
this._fetchCommandsForCluster();
|
||||
}
|
||||
super.update(changedProperties);
|
||||
@@ -82,25 +86,29 @@ export class ZHAClusterCommands extends LitElement {
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="command-picker">
|
||||
<mwc-select
|
||||
.label=${this.hass!.localize(
|
||||
<paper-dropdown-menu
|
||||
label=${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_commands.commands_of_cluster"
|
||||
)}
|
||||
class="menu"
|
||||
.value=${String(this._selectedCommandId)}
|
||||
@selected=${this._selectedCommandChanged}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${this._commands.map(
|
||||
(entry) => html`
|
||||
<mwc-list-item .value=${String(entry.id)}>
|
||||
${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._selectedCommandIndex}
|
||||
@iron-select=${this._selectedCommandChanged}
|
||||
>
|
||||
${this._commands.map(
|
||||
(entry) => html`
|
||||
<paper-item
|
||||
>${entry.name +
|
||||
" (id: " +
|
||||
formatAsPaddedHex(entry.id) +
|
||||
")"}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this._showHelp
|
||||
? html`
|
||||
@@ -111,7 +119,7 @@ export class ZHAClusterCommands extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._selectedCommandId !== undefined
|
||||
${this._selectedCommandIndex !== -1
|
||||
? html`
|
||||
<div class="input-text">
|
||||
<paper-input
|
||||
@@ -179,10 +187,8 @@ export class ZHAClusterCommands extends LitElement {
|
||||
endpoint_id: this.selectedCluster!.endpoint_id,
|
||||
cluster_id: this.selectedCluster!.id,
|
||||
cluster_type: this.selectedCluster!.type,
|
||||
command: this._selectedCommandId!,
|
||||
command_type: this._commands.find(
|
||||
(command) => command.id === this._selectedCommandId
|
||||
)!.type,
|
||||
command: this._commands[this._selectedCommandIndex].id,
|
||||
command_type: this._commands[this._selectedCommandIndex].type,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,8 +202,8 @@ export class ZHAClusterCommands extends LitElement {
|
||||
this._showHelp = !this._showHelp;
|
||||
}
|
||||
|
||||
private _selectedCommandChanged(event): void {
|
||||
this._selectedCommandId = Number(event.target.value);
|
||||
private _selectedCommandChanged(event: ItemSelectedEvent): void {
|
||||
this._selectedCommandIndex = event.target!.selected;
|
||||
this._issueClusterCommandServiceData =
|
||||
this._computeIssueClusterCommandServiceData();
|
||||
}
|
||||
@@ -206,9 +212,6 @@ export class ZHAClusterCommands extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
mwc-select {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
@@ -25,6 +25,7 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { computeClusterKey } from "./functions";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -78,25 +79,24 @@ export class ZHAClusters extends LitElement {
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="node-picker">
|
||||
<mwc-select
|
||||
<paper-dropdown-menu
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.clusters"
|
||||
)}
|
||||
class="menu"
|
||||
.value=${String(this._selectedClusterIndex)}
|
||||
@selected=${this._selectedClusterChanged}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${this._clusters.map(
|
||||
(entry, idx) => html`
|
||||
<mwc-list-item .value=${String(idx)}
|
||||
>${computeClusterKey(entry)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._selectedClusterIndex}
|
||||
@iron-select=${this._selectedClusterChanged}
|
||||
>
|
||||
${this._clusters.map(
|
||||
(entry) => html`
|
||||
<paper-item>${computeClusterKey(entry)}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this.showHelp
|
||||
? html`
|
||||
@@ -122,8 +122,8 @@ export class ZHAClusters extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _selectedClusterChanged(event): void {
|
||||
this._selectedClusterIndex = Number(event.target!.value);
|
||||
private _selectedClusterChanged(event: ItemSelectedEvent): void {
|
||||
this._selectedClusterIndex = event.target!.selected;
|
||||
fireEvent(this, "zha-cluster-selected", {
|
||||
cluster: this._clusters[this._selectedClusterIndex],
|
||||
});
|
||||
@@ -137,9 +137,6 @@ export class ZHAClusters extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
mwc-select {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiFolderMultipleOutline, mdiLan, mdiNetwork, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -20,7 +21,6 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
|
||||
@customElement("zha-device-binding-control")
|
||||
export class ZHADeviceBindingControl extends LitElement {
|
||||
@@ -62,25 +62,23 @@ export class ZHADeviceBindingControl extends LitElement {
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="command-picker">
|
||||
<mwc-select
|
||||
label="Bindable Devices"
|
||||
class="menu"
|
||||
.value=${String(this._bindTargetIndex)}
|
||||
@selected=${this._bindTargetIndexChanged}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${this.bindableDevices.map(
|
||||
(device, idx) => html`
|
||||
<mwc-list-item .value=${String(idx)}>
|
||||
${device.user_given_name
|
||||
? device.user_given_name
|
||||
: device.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-dropdown-menu label="Bindable Devices" class="menu">
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._bindTargetIndex}
|
||||
@iron-select=${this._bindTargetIndexChanged}
|
||||
>
|
||||
${this.bindableDevices.map(
|
||||
(device) => html`
|
||||
<paper-item
|
||||
>${device.user_given_name
|
||||
? device.user_given_name
|
||||
: device.name}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this._showHelp
|
||||
? html`
|
||||
@@ -113,7 +111,7 @@ export class ZHADeviceBindingControl extends LitElement {
|
||||
}
|
||||
|
||||
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
|
||||
this._bindTargetIndex = Number(event.target!.value);
|
||||
this._bindTargetIndex = event.target!.selected;
|
||||
this._deviceToBind =
|
||||
this._bindTargetIndex === -1
|
||||
? undefined
|
||||
|
@@ -1,5 +1,8 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -10,7 +13,6 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/ha-card";
|
||||
@@ -93,24 +95,22 @@ export class ZHAGroupBindingControl extends LitElement {
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="command-picker">
|
||||
<mwc-select
|
||||
<paper-dropdown-menu
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zha.group_binding.group_picker_label"
|
||||
)}
|
||||
class="menu"
|
||||
.value=${String(this._bindTargetIndex)}
|
||||
@selected=${this._bindTargetIndexChanged}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${this.groups.map(
|
||||
(group, idx) =>
|
||||
html`<mwc-list-item .value=${String(idx)}
|
||||
>${group.name}</mwc-list-item
|
||||
> `
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._bindTargetIndex}
|
||||
@iron-select=${this._bindTargetIndexChanged}
|
||||
>
|
||||
${this.groups.map(
|
||||
(group) => html` <paper-item>${group.name}</paper-item> `
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
${this._showHelp
|
||||
? html`
|
||||
@@ -179,7 +179,7 @@ export class ZHAGroupBindingControl extends LitElement {
|
||||
}
|
||||
|
||||
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
|
||||
this._bindTargetIndex = Number(event.target!.value);
|
||||
this._bindTargetIndex = event.target!.selected;
|
||||
this._groupToBind =
|
||||
this._bindTargetIndex === -1
|
||||
? undefined
|
||||
|
@@ -440,15 +440,19 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
|
||||
search-input {
|
||||
flex: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
search-input.header {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-device-picker {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
|
@@ -4,7 +4,6 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@@ -514,20 +513,11 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
import("web-animations-js/web-animations-next-lite.min");
|
||||
this.addEventListener("hass-service-called", (ev) =>
|
||||
this.serviceCalled(ev)
|
||||
);
|
||||
}
|
||||
|
||||
attached() {
|
||||
setCancelSyntheticClickEvents(true);
|
||||
}
|
||||
|
||||
detached() {
|
||||
setCancelSyntheticClickEvents(false);
|
||||
}
|
||||
|
||||
serviceCalled(ev) {
|
||||
if (ev.detail.success && ev.detail.service === "set_poll_intensity") {
|
||||
this._saveEntity();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import { mdiDownload } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultArray, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@@ -77,20 +77,26 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
||||
<div class="card-content">
|
||||
${this._logConfig
|
||||
? html`
|
||||
<mwc-select
|
||||
<paper-dropdown-menu
|
||||
dynamic-align
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.logs.log_level"
|
||||
)}
|
||||
.value=${this._logConfig.level}
|
||||
@selected=${this._dropdownSelected}
|
||||
>
|
||||
<mwc-list-item value="error">Error</mwc-list-item>
|
||||
<mwc-list-item value="warn">Warn</mwc-list-item>
|
||||
<mwc-list-item value="info">Info</mwc-list-item>
|
||||
<mwc-list-item value="verbose">Verbose</mwc-list-item>
|
||||
<mwc-list-item value="debug">Debug</mwc-list-item>
|
||||
<mwc-list-item value="silly">Silly</mwc-list-item>
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._logConfig.level}
|
||||
attr-for-selected="value"
|
||||
@iron-select=${this._dropdownSelected}
|
||||
>
|
||||
<paper-item value="error">Error</paper-item>
|
||||
<paper-item value="warn">Warn</paper-item>
|
||||
<paper-item value="info">Info</paper-item>
|
||||
<paper-item value="verbose">Verbose</paper-item>
|
||||
<paper-item value="debug">Debug</paper-item>
|
||||
<paper-item value="silly">Silly</paper-item>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -136,7 +142,7 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
||||
if (ev.target === undefined || this._logConfig === undefined) {
|
||||
return;
|
||||
}
|
||||
const selected = ev.target.value;
|
||||
const selected = ev.target.selected;
|
||||
if (this._logConfig.level === selected) {
|
||||
return;
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import {
|
||||
mdiCheckCircle,
|
||||
mdiCircle,
|
||||
mdiCloseCircle,
|
||||
mdiProgressClock,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -286,20 +287,26 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
return html`
|
||||
${labelAndDescription}
|
||||
<div class="flex">
|
||||
<mwc-select
|
||||
<paper-dropdown-menu
|
||||
dynamic-align
|
||||
.disabled=${!item.metadata.writeable}
|
||||
.value=${item.value}
|
||||
.key=${id}
|
||||
.property=${item.property}
|
||||
.propertyKey=${item.property_key}
|
||||
@selected=${this._dropdownSelected}
|
||||
>
|
||||
${Object.entries(item.metadata.states).map(
|
||||
([key, entityState]) => html`
|
||||
<mwc-list-item .value=${key}>${entityState}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${item.value}
|
||||
attr-for-selected="value"
|
||||
.key=${id}
|
||||
.property=${item.property}
|
||||
.propertyKey=${item.property_key}
|
||||
@iron-select=${this._dropdownSelected}
|
||||
>
|
||||
${Object.entries(item.metadata.states).map(
|
||||
([key, entityState]) => html`
|
||||
<paper-item .value=${key}>${entityState}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -344,12 +351,12 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
if (ev.target === undefined || this._config![ev.target.key] === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this._config![ev.target.key].value === ev.target.value) {
|
||||
if (this._config![ev.target.key].value === ev.target.selected) {
|
||||
return;
|
||||
}
|
||||
this.setResult(ev.target.key, undefined);
|
||||
|
||||
this._updateConfigParameter(ev.target, Number(ev.target.value));
|
||||
this._updateConfigParameter(ev.target, Number(ev.target.selected));
|
||||
}
|
||||
|
||||
private debouncedUpdate = debounce((target, value) => {
|
||||
@@ -455,7 +462,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
.flex .config-label,
|
||||
.flex mwc-select {
|
||||
.flex paper-dropdown-menu {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
@@ -104,7 +104,7 @@ class DialogSystemLogDetail extends LitElement {
|
||||
)}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
<div class="contents" tabindex="-1" dialogInitialFocus>
|
||||
<div class="contents">
|
||||
<p>
|
||||
Logger: ${item.name}<br />
|
||||
Source: ${item.source.join(":")}
|
||||
@@ -227,7 +227,6 @@ class DialogSystemLogDetail extends LitElement {
|
||||
}
|
||||
.contents {
|
||||
padding: 16px;
|
||||
outline: none;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
|
@@ -105,10 +105,10 @@ export class HaConfigLogs extends LitElement {
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
}
|
||||
search-input.header {
|
||||
--mdc-text-field-fill-color: transparant;
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
--mdc-ripple-color: transparant;
|
||||
}
|
||||
.content {
|
||||
|
@@ -207,7 +207,6 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
slot="primaryAction"
|
||||
@click=${this._updateDashboard}
|
||||
.disabled=${urlInvalid || titleInvalid || this._submitting}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this._params.urlPath
|
||||
? this._params.dashboard?.id
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-paper-dropdown-menu";
|
||||
import {
|
||||
LovelaceResource,
|
||||
LovelaceResourcesMutableParams,
|
||||
@@ -11,9 +14,6 @@ import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
|
||||
const detectResourceType = (url: string) => {
|
||||
const ext = url.split(".").pop() || "";
|
||||
@@ -102,44 +102,48 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
<br />
|
||||
<mwc-select
|
||||
<ha-paper-dropdown-menu
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.type"
|
||||
)}
|
||||
.value=${this._type}
|
||||
@selected=${this._typeChanged}
|
||||
@closed=${stopPropagation}
|
||||
.invalid=${!this._type}
|
||||
>
|
||||
<mwc-list-item value="module">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.module"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${this._type === "js"
|
||||
? html`
|
||||
<mwc-list-item value="js">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.js"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
<mwc-list-item value="css">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.css"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${this._type === "html"
|
||||
? html`
|
||||
<mwc-list-item value="html">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.html"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
</mwc-select>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._type}
|
||||
@iron-select=${this._typeChanged}
|
||||
attr-for-selected="type"
|
||||
.invalid=${!this._type}
|
||||
>
|
||||
<paper-item type="module">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.module"
|
||||
)}
|
||||
</paper-item>
|
||||
${this._type === "js"
|
||||
? html`
|
||||
<paper-item type="js">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.js"
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
: ""}
|
||||
<paper-item type="css">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.css"
|
||||
)}
|
||||
</paper-item>
|
||||
${this._type === "html"
|
||||
? html`
|
||||
<paper-item type="html">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.html"
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
: ""}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
</div>
|
||||
</div>
|
||||
${this._params.resource
|
||||
@@ -181,8 +185,8 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _typeChanged(ev) {
|
||||
this._type = ev.target.value;
|
||||
private _typeChanged(ev: CustomEvent) {
|
||||
this._type = ev.detail.item.getAttribute("type");
|
||||
}
|
||||
|
||||
private async _updateResource() {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoize from "memoize-one";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user