Merge pull request #12193 from home-assistant/dev

This commit is contained in:
Bram Kragten 2022-04-01 18:56:12 +02:00 committed by GitHub
commit 1f65193a97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 290 additions and 216 deletions

View File

@ -31,7 +31,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
version_latest: "3.6.2",
update_available: false,
repository: "a0d7b954",
icon: true,
icon: false,
logo: true,
},
{

Binary file not shown.

View File

@ -128,6 +128,11 @@ const ENTITIES = [
supported_features:
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}),
getEntity("update", "update19", "on", {
...base_attributes,
friendly_name: "Update with auto update",
auto_update: true,
}),
];
@customElement("demo-more-info-update")

View File

@ -32,13 +32,6 @@ interface AddonCheckboxItem extends CheckboxItem {
const _computeFolders = (folders): CheckboxItem[] => {
const list: CheckboxItem[] = [];
if (folders.includes("homeassistant")) {
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: false,
});
}
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: false });
}
@ -100,7 +93,7 @@ export class SupervisorBackupContent extends LitElement {
this.folders = _computeFolders(
this.backup
? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
: ["ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons

View File

@ -1,6 +1,6 @@
[metadata]
name = home-assistant-frontend
version = 20220330.0
version = 20220401.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0

View File

@ -68,7 +68,7 @@ class HaDevicesPicker extends LitElement {
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.label=${this.pickDeviceLabel}
.required=${this.required}
.required=${this.required && !currentDevices.length}
@value-changed=${this._addDevice}
></ha-device-picker>
</div>

View File

@ -110,7 +110,7 @@ class HaEntitiesPickerLight extends LitElement {
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
.entityFilter=${this._entityFilter}
.label=${this.pickEntityLabel}
.required=${this.required}
.required=${this.required && !currentEntities.length}
@value-changed=${this._addEntity}
></ha-entity-picker>
</div>

View File

@ -13,9 +13,12 @@ import { HaComboBox } from "./ha-combo-box";
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
item
) => html`<mwc-list-item twoline>
) => html`<mwc-list-item twoline graphic="icon">
<span>${item.name}</span>
<span slot="secondary">${item.slug}</span>
${item.icon
? html`<img slot="graphic" .src="/api/hassio/addons/${item.slug}/icon" />`
: ""}
</mwc-list-item>`;
@customElement("ha-addon-picker")

View File

@ -97,7 +97,7 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
.entityFilter=${this.entityFilter}
.disabled=${this.disabled}
.placeholder=${this.placeholder}
.required=${this.required}
.required=${this.required && !currentAreas.length}
@value-changed=${this._addArea}
></ha-area-picker>
</div>

View File

@ -108,7 +108,7 @@ export class HaSelectSelector extends LitElement {
.hass=${this.hass}
.label=${this.label}
.disabled=${this.disabled}
.required=${this.required}
.required=${this.required && !value.length}
.value=${this._filter}
.items=${options.filter((item) => !this.value?.includes(item.value))}
@filter-changed=${this._filterChanged}

View File

@ -2,6 +2,7 @@ import type {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { BINARY_STATE_ON } from "../common/const";
import { supportsFeature } from "../common/entity/supports-feature";
import { HomeAssistant } from "../types";
@ -12,6 +13,7 @@ export const UPDATE_SUPPORT_BACKUP = 8;
export const UPDATE_SUPPORT_RELEASE_NOTES = 16;
interface UpdateEntityAttributes extends HassEntityAttributeBase {
auto_update: boolean | null;
current_version: string | null;
in_progress: boolean | number;
latest_version: string | null;
@ -30,9 +32,8 @@ export const updateUsesProgress = (entity: UpdateEntity): boolean =>
typeof entity.attributes.in_progress === "number";
export const updateCanInstall = (entity: UpdateEntity): boolean =>
supportsFeature(entity, UPDATE_SUPPORT_INSTALL) &&
entity.attributes.latest_version !== entity.attributes.current_version &&
entity.attributes.latest_version !== entity.attributes.skipped_version;
entity.state === BINARY_STATE_ON &&
supportsFeature(entity, UPDATE_SUPPORT_INSTALL);
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
updateUsesProgress(entity) || !!entity.attributes.in_progress;

View File

@ -167,12 +167,18 @@ export interface ZwaveJSNodeMetadata {
wakeup: string;
reset: string;
device_database_url: string;
comments: ZWaveJSNodeComment[];
}
export interface ZWaveJSNodeConfigParams {
[key: string]: ZWaveJSNodeConfigParam;
}
export interface ZWaveJSNodeComment {
level: "info" | "warning" | "error";
text: string;
}
export interface ZWaveJSNodeConfigParam {
property: number;
value: any;

View File

@ -96,6 +96,13 @@ class MoreInfoMediaPlayer extends LitElement {
.path=${stateObj.attributes.is_volume_muted
? mdiVolumeOff
: mdiVolumeHigh}
.label=${this.hass.localize(
`ui.card.media_player.${
stateObj.attributes.is_volume_muted
? "media_volume_unmute"
: "media_volume_mute"
}`
)}
@click=${this._toggleMute}
></ha-icon-button>
`
@ -105,11 +112,17 @@ class MoreInfoMediaPlayer extends LitElement {
<ha-icon-button
action="volume_down"
.path=${mdiVolumeMinus}
.label=${this.hass.localize(
"ui.card.media_player.media_volume_down"
)}
@click=${this._handleClick}
></ha-icon-button>
<ha-icon-button
action="volume_up"
.path=${mdiVolumePlus}
.label=${this.hass.localize(
"ui.card.media_player.media_volume_up"
)}
@click=${this._handleClick}
></ha-icon-button>
`

View File

@ -130,14 +130,20 @@ class MoreInfoUpdate extends LitElement {
: ""}
<hr />
<div class="actions">
<mwc-button
@click=${this._handleSkip}
.disabled=${skippedVersion ||
this.stateObj.state === "off" ||
updateIsInstalling(this.stateObj)}
>
${this.hass.localize("ui.dialogs.more_info_control.update.skip")}
</mwc-button>
${this.stateObj.attributes.auto_update
? ""
: html`
<mwc-button
@click=${this._handleSkip}
.disabled=${skippedVersion ||
this.stateObj.state === "off" ||
updateIsInstalling(this.stateObj)}
>
${this.hass.localize(
"ui.dialogs.more_info_control.update.skip"
)}
</mwc-button>
`}
${supportsFeature(this.stateObj, UPDATE_SUPPORT_INSTALL)
? html`
<mwc-button

View File

@ -246,8 +246,8 @@ class ConfigUrlForm extends LitElement {
if (isComponentLoaded(this.hass, "cloud")) {
fetchCloudStatus(this.hass).then((cloudStatus) => {
this._cloudStatus = cloudStatus;
if (cloudStatus.logged_in) {
this._cloudStatus = cloudStatus;
this._showCustomExternalUrl = this._externalUrlValue !== null;
}
});

View File

@ -75,7 +75,7 @@ export class HaDeviceEntitiesCard extends LitElement {
this._entityRows = [];
this.entities.forEach((entry) => {
if (entry.disabled_by || entry.hidden_by) {
if (entry.disabled_by) {
if (this._extDisabledEntityEntries) {
hiddenEntities.push(
this._extDisabledEntityEntries[entry.entity_id] || entry
@ -167,7 +167,11 @@ export class HaDeviceEntitiesCard extends LitElement {
computeStateName(stateObj),
this.deviceName.toLowerCase()
);
if (name) {
if (entry.hidden_by) {
config.name = `${
name || computeStateName(stateObj)
} (${this.hass.localize("ui.panel.config.devices.entities.hidden")})`;
} else if (name) {
config.name = name;
}
}

View File

@ -19,6 +19,7 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { debounce } from "../../../../../common/util/debounce";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
import "../../../../../components/ha-select";
@ -130,7 +131,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
></hass-error-screen>`;
}
if (!this._config) {
if (!this._config || !this._nodeMetadata) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
@ -178,20 +179,27 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
</em>
</p>
</div>
<ha-card>
${this._config
? html`
${Object.entries(this._config).map(
([id, item]) => html` <ha-settings-row
class="config-item"
.configId=${id}
.narrow=${this.narrow}
>
${this._generateConfigBox(id, item)}
</ha-settings-row>`
${this._nodeMetadata.comments.length > 0
? html`
<div>
${this._nodeMetadata.comments.map(
(comment) => html`<ha-alert .alertType=${comment.level}>
${comment.text}
</ha-alert>`
)}
`
: ``}
</div>
`
: ``}
<ha-card>
${Object.entries(this._config).map(
([id, item]) => html` <ha-settings-row
class="config-item"
.configId=${id}
.narrow=${this.narrow}
>
${this._generateConfigBox(id, item)}
</ha-settings-row>`
)}
</ha-card>
</ha-config-section>
</hass-tabs-subpage>

View File

@ -13,6 +13,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import type { InputSelectEntity } from "../../../data/input_select";
import { SelectEntity, setSelectOption } from "../../../data/select";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
@ -106,9 +107,14 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
}
private _selectedChanged(ev): void {
const stateObj = this.hass!.states[this._config!.entity];
const stateObj = this.hass!.states[
this._config!.entity
] as InputSelectEntity;
const option = ev.target.value;
if (option === stateObj.state) {
if (
option === stateObj.state ||
!stateObj.attributes.options.includes(option)
) {
return;
}

View File

@ -12,154 +12,172 @@ import "../../layouts/hass-error-screen";
import { HomeAssistant, Route } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
const REDIRECTS: Redirects = {
developer_states: {
redirect: "/developer-tools/state",
},
developer_services: {
redirect: "/developer-tools/service",
},
developer_call_service: {
redirect: "/developer-tools/service",
params: {
service: "string",
},
},
developer_template: {
redirect: "/developer-tools/template",
},
developer_events: {
redirect: "/developer-tools/event",
},
developer_statistics: {
redirect: "/developer-tools/statistics",
},
config: {
redirect: "/config",
},
cloud: {
component: "cloud",
redirect: "/config/cloud",
},
integrations: {
redirect: "/config/integrations",
},
config_flow_start: {
redirect: "/config/integrations/add",
params: {
domain: "string",
},
},
config_mqtt: {
component: "mqtt",
redirect: "/config/mqtt",
},
config_zha: {
component: "zha",
redirect: "/config/zha/dashboard",
},
config_zwave_js: {
component: "zwave_js",
redirect: "/config/zwave_js/dashboard",
},
config_energy: {
component: "energy",
redirect: "/config/energy/dashboard",
},
devices: {
redirect: "/config/devices/dashboard",
},
entities: {
redirect: "/config/entities",
},
energy: {
component: "energy",
redirect: "/energy",
},
areas: {
redirect: "/config/areas/dashboard",
},
blueprints: {
component: "blueprint",
redirect: "/config/blueprint/dashboard",
},
blueprint_import: {
component: "blueprint",
redirect: "/config/blueprint/dashboard/import",
params: {
blueprint_url: "url",
},
},
automations: {
component: "automation",
redirect: "/config/automation/dashboard",
},
scenes: {
component: "scene",
redirect: "/config/scene/dashboard",
},
scripts: {
component: "script",
redirect: "/config/script/dashboard",
},
helpers: {
redirect: "/config/helpers",
},
tags: {
component: "tag",
redirect: "/config/tags",
},
lovelace_dashboards: {
component: "lovelace",
redirect: "/config/lovelace/dashboards",
},
lovelace_resources: {
component: "lovelace",
redirect: "/config/lovelace/resources",
},
people: {
component: "person",
redirect: "/config/person",
},
zones: {
component: "zone",
redirect: "/config/zone",
},
users: {
redirect: "/config/users",
},
general: {
redirect: "/config/core",
},
server_controls: {
redirect: "/config/server_control",
},
logs: {
redirect: "/config/logs",
},
info: {
redirect: "/config/info",
},
customize: {
// customize was removed in 2021.12, fallback to dashboard
redirect: "/config/dashboard",
},
profile: {
redirect: "/profile/dashboard",
},
logbook: {
component: "logbook",
redirect: "/logbook",
},
history: {
component: "history",
redirect: "/history",
},
media_browser: {
component: "media_source",
redirect: "/media-browser",
},
};
const getRedirect = (
path: string,
hasSupervisor: boolean
): Redirect | undefined =>
((
{
developer_states: {
redirect: "/developer-tools/state",
},
developer_services: {
redirect: "/developer-tools/service",
},
developer_call_service: {
redirect: "/developer-tools/service",
params: {
service: "string",
},
},
developer_template: {
redirect: "/developer-tools/template",
},
developer_events: {
redirect: "/developer-tools/event",
},
developer_statistics: {
redirect: "/developer-tools/statistics",
},
config: {
redirect: "/config",
},
cloud: {
component: "cloud",
redirect: "/config/cloud",
},
integrations: {
redirect: "/config/integrations",
},
config_flow_start: {
redirect: "/config/integrations/add",
params: {
domain: "string",
},
},
config_mqtt: {
component: "mqtt",
redirect: "/config/mqtt",
},
config_zha: {
component: "zha",
redirect: "/config/zha/dashboard",
},
config_zwave_js: {
component: "zwave_js",
redirect: "/config/zwave_js/dashboard",
},
config_energy: {
component: "energy",
redirect: "/config/energy/dashboard",
},
devices: {
redirect: "/config/devices/dashboard",
},
entities: {
redirect: "/config/entities",
},
energy: {
component: "energy",
redirect: "/energy",
},
areas: {
redirect: "/config/areas/dashboard",
},
blueprints: {
component: "blueprint",
redirect: "/config/blueprint/dashboard",
},
blueprint_import: {
component: "blueprint",
redirect: "/config/blueprint/dashboard/import",
params: {
blueprint_url: "url",
},
},
automations: {
component: "automation",
redirect: "/config/automation/dashboard",
},
scenes: {
component: "scene",
redirect: "/config/scene/dashboard",
},
scripts: {
component: "script",
redirect: "/config/script/dashboard",
},
helpers: {
redirect: "/config/helpers",
},
tags: {
component: "tag",
redirect: "/config/tags",
},
lovelace_dashboards: {
component: "lovelace",
redirect: "/config/lovelace/dashboards",
},
lovelace_resources: {
component: "lovelace",
redirect: "/config/lovelace/resources",
},
people: {
component: "person",
redirect: "/config/person",
},
zones: {
component: "zone",
redirect: "/config/zone",
},
users: {
redirect: "/config/users",
},
general: {
redirect: "/config/core",
},
server_controls: {
redirect: "/config/server_control",
},
logs: {
redirect: "/config/logs",
},
info: {
redirect: "/config/info",
},
customize: {
// customize was removed in 2021.12, fallback to dashboard
redirect: "/config/dashboard",
},
profile: {
redirect: "/profile/dashboard",
},
logbook: {
component: "logbook",
redirect: "/logbook",
},
history: {
component: "history",
redirect: "/history",
},
media_browser: {
component: "media_source",
redirect: "/media-browser",
},
backup: {
component: hasSupervisor ? "hassio" : "backup",
redirect: hasSupervisor ? "/hassio/backups" : "/config/backup",
},
supervisor_snapshots: {
component: hasSupervisor ? "hassio" : "backup",
redirect: hasSupervisor ? "/hassio/backups" : "/config/backup",
},
supervisor_backups: {
component: hasSupervisor ? "hassio" : "backup",
redirect: hasSupervisor ? "/hassio/backups" : "/config/backup",
},
} as Redirects
)[path]);
export type ParamType = "url" | "string";
@ -180,12 +198,17 @@ class HaPanelMy extends LitElement {
@state() public _error?: string;
private _redirect?: Redirect;
connectedCallback() {
super.connectedCallback();
const path = this.route.path.substring(1);
const hasSupervisor = isComponentLoaded(this.hass, "hassio");
if (path.startsWith("supervisor")) {
if (!isComponentLoaded(this.hass, "hassio")) {
this._redirect = getRedirect(path, hasSupervisor);
if (path.startsWith("supervisor") && this._redirect === undefined) {
if (!hasSupervisor) {
this._error = "no_supervisor";
return;
}
@ -195,16 +218,14 @@ class HaPanelMy extends LitElement {
return;
}
const redirect = REDIRECTS[path];
if (!redirect) {
if (!this._redirect) {
this._error = "not_supported";
return;
}
if (
redirect.component &&
!isComponentLoaded(this.hass, redirect.component)
this._redirect.component &&
!isComponentLoaded(this.hass, this._redirect.component)
) {
this._error = "no_component";
return;
@ -212,7 +233,7 @@ class HaPanelMy extends LitElement {
let url: string;
try {
url = this._createRedirectUrl(redirect);
url = this._createRedirectUrl();
} catch (err: any) {
this._error = "url_error";
return;
@ -243,10 +264,16 @@ class HaPanelMy extends LitElement {
this.hass.localize(
"ui.panel.my.component_not_loaded",
"integration",
domainToName(
this.hass.localize,
REDIRECTS[this.route.path.substr(1)].component!
)
html`<a
target="_blank"
rel="noreferrer noopener"
href=${documentationUrl(
this.hass,
`/integrations/${this._redirect!.component!}`
)}
>
${domainToName(this.hass.localize, this._redirect!.component!)}
</a>`
) || "This redirect is not supported.";
break;
case "no_supervisor":
@ -269,18 +296,18 @@ class HaPanelMy extends LitElement {
return html``;
}
private _createRedirectUrl(redirect: Redirect): string {
const params = this._createRedirectParams(redirect);
return `${redirect.redirect}${params}`;
private _createRedirectUrl(): string {
const params = this._createRedirectParams();
return `${this._redirect!.redirect}${params}`;
}
private _createRedirectParams(redirect: Redirect): string {
private _createRedirectParams(): string {
const params = extractSearchParamsObject();
if (!redirect.params && !Object.keys(params).length) {
if (!this._redirect!.params && !Object.keys(params).length) {
return "";
}
const resultParams = {};
Object.entries(redirect.params || {}).forEach(([key, type]) => {
Object.entries(this._redirect!.params || {}).forEach(([key, type]) => {
if (!params[key] || !this._checkParamType(type, params[key])) {
throw Error();
}

View File

@ -2463,7 +2463,8 @@
"add_entities_lovelace": "Add to dashboard",
"none": "This device has no entities",
"show_less": "Show less",
"hidden_entities": "+{count} {count, plural,\n one {entity}\n other {entities}\n} not shown"
"hidden_entities": "+{count} {count, plural,\n one {entity}\n other {entities}\n} not shown",
"hidden": "Hidden"
},
"confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?",
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to update them yourself to use the new entity IDs!",
@ -4049,7 +4050,8 @@
"update": "Update the historic statistic values from ''{metadata_unit}'' to ''{state_unit}''",
"clear": "Delete all old statistic data for this entity"
}
}
},
"adjust_sum": "Adjust sum"
}
}
},

View File

@ -5915,9 +5915,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001259":
version: 1.0.30001261
resolution: "caniuse-lite@npm:1.0.30001261"
checksum: d894662312ecbdd772f0a258c4a45cac93605247b127b25649052353e0b981abfd0b445f469650943b612adc236fd510ae61c1293f3e77c68af7411d1b66574a
version: 1.0.30001322
resolution: "caniuse-lite@npm:1.0.30001322"
checksum: 48609d1808c69034a74ab6df9db8cffd847e12da6979e150f364cc8e2a4310fce1f2811382ca57b3b4111c0182f7c67edfde3cd4159c29537fc232596aecf48b
languageName: node
linkType: hard