20220201.0 (#11508)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Thomas Lovén <thomasloven@gmail.com>
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Yosi Levy <37745463+yosilevy@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Patrick ZAJDA <patrick@zajda.fr>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
This commit is contained in:
Bram Kragten 2022-02-01 18:18:14 +01:00 committed by GitHub
parent 9fee7a2829
commit 7c4b9a0410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 467 additions and 174 deletions

View File

@ -41,7 +41,7 @@ jobs:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Build and release package - name: Build and release package
run: | run: |
python3 -m pip install twine python3 -m pip install twine build
export TWINE_USERNAME="__token__" export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"

View File

@ -1,5 +1,4 @@
include README.md include README.md
include LICENSE.md
graft hass_frontend graft hass_frontend
graft hass_frontend_es5 graft hass_frontend_es5
recursive-exclude * *.py[co] recursive-exclude * *.py[co]

View File

@ -26,11 +26,11 @@ module.exports = {
}, },
version() { version() {
const version = fs const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8") .readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
.match(/\d{8}\.\d+/); .match(/version\W+=\W(\d{8}\.\d)/);
if (!version) { if (!version) {
throw Error("Version not found"); throw Error("Version not found");
} }
return version[0]; return version[1];
}, },
}; };

View File

@ -110,7 +110,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4", "leaflet-draw": "^1.0.4",
"lit": "^2.0.2", "lit": "^2.1.2",
"lit-vaadin-helpers": "^0.2.1", "lit-vaadin-helpers": "^0.2.1",
"marked": "^3.0.2", "marked": "^3.0.2",
"memoize-one": "^5.2.1", "memoize-one": "^5.2.1",
@ -236,10 +236,10 @@
"resolutions": { "resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch", "@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10", "@webcomponents/webcomponentsjs": "^2.2.10",
"lit": "^2.0.2", "lit": "^2.1.2",
"lit-html": "2.0.1", "lit-html": "2.1.2",
"lit-element": "3.0.1", "lit-element": "3.1.2",
"@lit/reactive-element": "1.0.1" "@lit/reactive-element": "1.2.1"
}, },
"main": "src/home-assistant.js", "main": "src/home-assistant.js",
"husky": { "husky": {

3
pyproject.toml Normal file
View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
build-backend = "setuptools.build_meta"

View File

@ -11,6 +11,6 @@ yarn install
script/build_frontend script/build_frontend
rm -rf dist rm -rf dist home_assistant_frontend.egg-info
python3 setup.py -q sdist python3 -m build
python3 -m twine upload dist/* --skip-existing python3 -m twine upload dist/* --skip-existing

View File

@ -50,14 +50,14 @@ async function main(args) {
return; return;
} }
const setup = fs.readFileSync("setup.py", "utf8"); const setup = fs.readFileSync("setup.cfg", "utf8");
const version = setup.match(/\d{8}\.\d+/)[0]; const version = setup.match(/\d{8}\.\d+/)[0];
const newVersion = method(version); const newVersion = method(version);
console.log("Current version:", version); console.log("Current version:", version);
console.log("New version:", newVersion); console.log("New version:", newVersion);
fs.writeFileSync("setup.py", setup.replace(version, newVersion), "utf-8"); fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
if (!commit) { if (!commit) {
return; return;

21
setup.cfg Normal file
View File

@ -0,0 +1,21 @@
[metadata]
name = home-assistant-frontend
version = 20220201.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0
platforms = any
description = The Home Assistant frontend
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/home-assistant/frontend
[options]
packages = find:
zip_safe = False
include_package_data = True
python_requires = >= 3.4.0
[options.packages.find]
include =
hass_frontend*

View File

@ -1,14 +1,7 @@
from setuptools import setup, find_packages """
Entry point for setuptools. Required for editable installs.
TODO: Remove file after updating to pip 21.3
"""
from setuptools import setup
setup( setup()
name="home-assistant-frontend",
version="20220127.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",
author_email="hello@home-assistant.io",
license="Apache-2.0",
packages=find_packages(include=["hass_frontend", "hass_frontend.*"]),
include_package_data=True,
zip_safe=False,
)

View File

@ -183,12 +183,7 @@ class StateHistoryChartLine extends LitElement {
prevValues = datavalues; prevValues = datavalues;
}; };
const addDataSet = ( const addDataSet = (nameY: string, fill = false, color?: string) => {
nameY: string,
step = false,
fill = false,
color?: string
) => {
if (!color) { if (!color) {
color = getGraphColorByIndex(colorIndex, computedStyles); color = getGraphColorByIndex(colorIndex, computedStyles);
colorIndex++; colorIndex++;
@ -198,7 +193,7 @@ class StateHistoryChartLine extends LitElement {
fill: fill ? "origin" : false, fill: fill ? "origin" : false,
borderColor: color, borderColor: color,
backgroundColor: color + "7F", backgroundColor: color + "7F",
stepped: step ? "before" : false, stepped: "before",
pointRadius: 0, pointRadius: 0,
data: [], data: [],
}); });
@ -239,14 +234,12 @@ class StateHistoryChartLine extends LitElement {
addDataSet( addDataSet(
`${this.hass.localize("ui.card.climate.current_temperature", { `${this.hass.localize("ui.card.climate.current_temperature", {
name: name, name: name,
})}`, })}`
true
); );
if (hasHeat) { if (hasHeat) {
addDataSet( addDataSet(
`${this.hass.localize("ui.card.climate.heating", { name: name })}`, `${this.hass.localize("ui.card.climate.heating", { name: name })}`,
true, true,
true,
computedStyles.getPropertyValue("--state-climate-heat-color") computedStyles.getPropertyValue("--state-climate-heat-color")
); );
// The "heating" series uses steppedArea to shade the area below the current // The "heating" series uses steppedArea to shade the area below the current
@ -256,7 +249,6 @@ class StateHistoryChartLine extends LitElement {
addDataSet( addDataSet(
`${this.hass.localize("ui.card.climate.cooling", { name: name })}`, `${this.hass.localize("ui.card.climate.cooling", { name: name })}`,
true, true,
true,
computedStyles.getPropertyValue("--state-climate-cool-color") computedStyles.getPropertyValue("--state-climate-cool-color")
); );
// The "cooling" series uses steppedArea to shade the area below the current // The "cooling" series uses steppedArea to shade the area below the current
@ -268,22 +260,19 @@ class StateHistoryChartLine extends LitElement {
`${this.hass.localize("ui.card.climate.target_temperature_mode", { `${this.hass.localize("ui.card.climate.target_temperature_mode", {
name: name, name: name,
mode: this.hass.localize("ui.card.climate.high"), mode: this.hass.localize("ui.card.climate.high"),
})}`, })}`
true
); );
addDataSet( addDataSet(
`${this.hass.localize("ui.card.climate.target_temperature_mode", { `${this.hass.localize("ui.card.climate.target_temperature_mode", {
name: name, name: name,
mode: this.hass.localize("ui.card.climate.low"), mode: this.hass.localize("ui.card.climate.low"),
})}`, })}`
true
); );
} else { } else {
addDataSet( addDataSet(
`${this.hass.localize("ui.card.climate.target_temperature_entity", { `${this.hass.localize("ui.card.climate.target_temperature_entity", {
name: name, name: name,
})}`, })}`
true
); );
} }
@ -318,14 +307,12 @@ class StateHistoryChartLine extends LitElement {
addDataSet( addDataSet(
`${this.hass.localize("ui.card.humidifier.target_humidity_entity", { `${this.hass.localize("ui.card.humidifier.target_humidity_entity", {
name: name, name: name,
})}`, })}`
true
); );
addDataSet( addDataSet(
`${this.hass.localize("ui.card.humidifier.on_entity", { `${this.hass.localize("ui.card.humidifier.on_entity", {
name: name, name: name,
})}`, })}`,
true,
true true
); );
@ -337,9 +324,7 @@ class StateHistoryChartLine extends LitElement {
pushData(new Date(entityState.last_changed), series); pushData(new Date(entityState.last_changed), series);
}); });
} else { } else {
// Only interpolate for sensors addDataSet(name);
const isStep = domain !== "sensor";
addDataSet(name, isStep);
let lastValue: number; let lastValue: number;
let lastDate: Date; let lastDate: Date;

View File

@ -1,6 +1,5 @@
import "@material/mwc-textfield"; import "@material/mwc-textfield";
import type { TextField } from "@material/mwc-textfield"; import type { TextField } from "@material/mwc-textfield";
import "@material/mwc-slider";
import type { Slider } from "@material/mwc-slider"; import type { Slider } from "@material/mwc-slider";
import { import {
css, css,
@ -14,6 +13,7 @@ import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { HaCheckbox } from "../ha-checkbox"; import { HaCheckbox } from "../ha-checkbox";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types"; import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
import "../ha-slider";
@customElement("ha-form-integer") @customElement("ha-form-integer")
export class HaFormInteger extends LitElement implements HaFormElement { export class HaFormInteger extends LitElement implements HaFormElement {
@ -54,15 +54,16 @@ export class HaFormInteger extends LitElement implements HaFormElement {
></ha-checkbox> ></ha-checkbox>
` `
: ""} : ""}
<mwc-slider <ha-slider
discrete pin
ignore-bar-touch
.value=${this._value} .value=${this._value}
.min=${this.schema.valueMin} .min=${this.schema.valueMin}
.max=${this.schema.valueMax} .max=${this.schema.valueMax}
.disabled=${this.disabled || .disabled=${this.disabled ||
(this.data === undefined && this.schema.optional)} (this.data === undefined && this.schema.optional)}
@change=${this._valueChanged} @change=${this._valueChanged}
></mwc-slider> ></ha-slider>
</div> </div>
</div> </div>
`; `;
@ -168,7 +169,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
.flex { .flex {
display: flex; display: flex;
} }
mwc-slider { ha-slider {
flex: 1; flex: 1;
} }
mwc-textfield { mwc-textfield {

View File

@ -104,7 +104,7 @@ export class HaForm extends LitElement implements HaFormElement {
return css` return css`
.root { .root {
margin-bottom: -24px; margin-bottom: -24px;
overflow: auto; overflow: clip visible;
} }
.root > * { .root > * {
display: block; display: block;

View File

@ -30,7 +30,6 @@ import { debounce } from "../../common/util/debounce";
import { getSignedPath } from "../../data/auth"; import { getSignedPath } from "../../data/auth";
import type { MediaPlayerItem } from "../../data/media-player"; import type { MediaPlayerItem } from "../../data/media-player";
import { import {
browseLocalMediaPlayer,
browseMediaPlayer, browseMediaPlayer,
BROWSER_PLAYER, BROWSER_PLAYER,
MediaClassBrowserSettings, MediaClassBrowserSettings,
@ -50,6 +49,7 @@ import "../ha-circular-progress";
import "../ha-icon-button"; import "../ha-icon-button";
import "../ha-svg-icon"; import "../ha-svg-icon";
import "../ha-fab"; import "../ha-fab";
import { browseLocalMediaPlayer } from "../../data/media_source";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -417,6 +417,9 @@ export class HaMediaPlayerBrowse extends LitElement {
if (!changedProps.has("navigateIds")) { if (!changedProps.has("navigateIds")) {
return; return;
} }
this._setError(undefined);
const oldNavigateIds = changedProps.get("navigateIds") as const oldNavigateIds = changedProps.get("navigateIds") as
| this["navigateIds"] | this["navigateIds"]
| undefined; | undefined;

View File

@ -55,7 +55,13 @@ export const computeDeviceName = (
device.name_by_user || device.name_by_user ||
device.name || device.name ||
(entities && fallbackDeviceName(hass, entities)) || (entities && fallbackDeviceName(hass, entities)) ||
hass.localize("ui.panel.config.devices.unnamed_device"); hass.localize(
"ui.panel.config.devices.unnamed_device",
"type",
hass.localize(
`ui.panel.config.devices.type.${device.entry_type || "device"}`
)
);
export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) => export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) =>
devices.filter((device) => device.area_id === areaId); devices.filter((device) => device.area_id === areaId);

View File

@ -185,15 +185,6 @@ export const browseMediaPlayer = (
media_content_type: mediaContentType, media_content_type: mediaContentType,
}); });
export const browseLocalMediaPlayer = (
hass: HomeAssistant,
mediaContentId?: string
): Promise<MediaPlayerItem> =>
hass.callWS<MediaPlayerItem>({
type: "media_source/browse_media",
media_content_id: mediaContentId,
});
export const getCurrentProgress = (stateObj: MediaPlayerEntity): number => { export const getCurrentProgress = (stateObj: MediaPlayerEntity): number => {
let progress = stateObj.attributes.media_position!; let progress = stateObj.attributes.media_position!;
@ -321,8 +312,8 @@ export const computeMediaControls = (
return buttons.length > 0 ? buttons : undefined; return buttons.length > 0 ? buttons : undefined;
}; };
export const formatMediaTime = (seconds: number): string => { export const formatMediaTime = (seconds: number | undefined): string => {
if (!seconds) { if (seconds === undefined) {
return ""; return "";
} }

View File

@ -1,4 +1,5 @@
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { MediaPlayerItem } from "./media-player";
export interface ResolvedMediaSource { export interface ResolvedMediaSource {
url: string; url: string;
@ -13,3 +14,12 @@ export const resolveMediaSource = (
type: "media_source/resolve_media", type: "media_source/resolve_media",
media_content_id, media_content_id,
}); });
export const browseLocalMediaPlayer = (
hass: HomeAssistant,
mediaContentId?: string
): Promise<MediaPlayerItem> =>
hass.callWS<MediaPlayerItem>({
type: "media_source/browse_media",
media_content_id: mediaContentId,
});

View File

@ -116,15 +116,14 @@ class DataEntryFlowDialog extends LitElement {
params.continueFlowId params.continueFlowId
); );
} catch (err: any) { } catch (err: any) {
this._step = undefined; this.closeDialog();
this._params = undefined;
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error" "ui.panel.config.integrations.config_flow.error"
), ),
text: this.hass.localize( text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load" "ui.panel.config.integrations.config_flow.could_not_load"
), )}: ${err.message || err.body}`,
}); });
return; return;
} }
@ -177,6 +176,7 @@ class DataEntryFlowDialog extends LitElement {
}); });
} }
this._loading = undefined;
this._step = undefined; this._step = undefined;
this._params = undefined; this._params = undefined;
this._devices = undefined; this._devices = undefined;
@ -372,15 +372,14 @@ class DataEntryFlowDialog extends LitElement {
try { try {
step = await this._params!.flowConfig.createFlow(this.hass, handler); step = await this._params!.flowConfig.createFlow(this.hass, handler);
} catch (err: any) { } catch (err: any) {
this._step = undefined; this.closeDialog();
this._params = undefined;
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error" "ui.panel.config.integrations.config_flow.error"
), ),
text: this.hass.localize( text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load" "ui.panel.config.integrations.config_flow.could_not_load"
), )}: ${err.message || err.body}`,
}); });
return; return;
} finally { } finally {
@ -405,6 +404,15 @@ class DataEntryFlowDialog extends LitElement {
this._loading = "loading_step"; this._loading = "loading_step";
try { try {
this._step = await step; this._step = await step;
} catch (err: any) {
this.closeDialog();
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: err.message || err.body,
});
return;
} finally { } finally {
this._loading = undefined; this._loading = undefined;
} }

View File

@ -185,13 +185,14 @@ export class QuickBar extends LitElement {
.label=${this.hass!.localize("ui.common.clear")} .label=${this.hass!.localize("ui.common.clear")}
.path=${mdiClose} .path=${mdiClose}
></ha-icon-button>`} ></ha-icon-button>`}
${this._narrow && ${this._narrow
html` ? html`
<mwc-button <mwc-button
.label=${this.hass!.localize("ui.common.close")} .label=${this.hass!.localize("ui.common.close")}
@click=${this.closeDialog} @click=${this.closeDialog}
></mwc-button> ></mwc-button>
`} `
: ""}
</div> </div>
` `
: ""} : ""}

View File

@ -1,7 +1,11 @@
import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings"; import {
setPassiveTouchGestures,
setCancelSyntheticClickEvents,
} from "@polymer/polymer/lib/utils/settings";
import "../layouts/home-assistant"; import "../layouts/home-assistant";
import "../resources/ha-style"; import "../resources/ha-style";
import "../resources/roboto"; import "../resources/roboto";
import "../util/legacy-support"; import "../util/legacy-support";
setPassiveTouchGestures(true); setPassiveTouchGestures(true);
setCancelSyntheticClickEvents(false);

View File

@ -1,11 +1,14 @@
// Compat needs to be first import // Compat needs to be first import
import "../resources/compatibility"; import "../resources/compatibility";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import "../auth/ha-authorize"; import "../auth/ha-authorize";
import "../resources/ha-style"; import "../resources/ha-style";
import "../resources/roboto"; import "../resources/roboto";
import "../resources/safari-14-attachshadow-patch"; import "../resources/safari-14-attachshadow-patch";
import "../resources/array.flat.polyfill"; import "../resources/array.flat.polyfill";
setCancelSyntheticClickEvents(false);
/* polyfill for paper-dropdown */ /* polyfill for paper-dropdown */
setTimeout( setTimeout(
() => import("web-animations-js/web-animations-next-lite.min"), () => import("web-animations-js/web-animations-next-lite.min"),

View File

@ -1,5 +1,6 @@
// Compat needs to be first import // Compat needs to be first import
import "../resources/compatibility"; import "../resources/compatibility";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import "../resources/safari-14-attachshadow-patch"; import "../resources/safari-14-attachshadow-patch";
import { PolymerElement } from "@polymer/polymer"; import { PolymerElement } from "@polymer/polymer";
@ -15,6 +16,8 @@ import { createCustomPanelElement } from "../util/custom-panel/create-custom-pan
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel"; import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties"; import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
setCancelSyntheticClickEvents(false);
declare global { declare global {
interface Window { interface Window {
loadES5Adapter: () => Promise<unknown>; loadES5Adapter: () => Promise<unknown>;
@ -47,7 +50,7 @@ function initialize(
) { ) {
const style = document.createElement("style"); const style = document.createElement("style");
style.innerHTML = `body { margin:0; } style.innerHTML = `body { margin:0; }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body { body {
background-color: #111111; background-color: #111111;

View File

@ -1,11 +1,14 @@
// Compat needs to be first import // Compat needs to be first import
import "../resources/compatibility"; import "../resources/compatibility";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import "../onboarding/ha-onboarding"; import "../onboarding/ha-onboarding";
import "../resources/ha-style"; import "../resources/ha-style";
import "../resources/roboto"; import "../resources/roboto";
import "../resources/safari-14-attachshadow-patch"; import "../resources/safari-14-attachshadow-patch";
import "../resources/array.flat.polyfill"; import "../resources/array.flat.polyfill";
setCancelSyntheticClickEvents(false);
declare global { declare global {
interface Window { interface Window {
stepsPromise: Promise<Response>; stepsPromise: Promise<Response>;

View File

@ -315,7 +315,7 @@ class HaConfigAreaPage extends LitElement {
? html` ? html`
<ha-card <ha-card
.header=${this.hass.localize( .header=${this.hass.localize(
"ui.panel.config.devices.automation.automations" "ui.panel.config.devices.automation.automations_heading"
)} )}
> >
${groupedAutomations?.length ${groupedAutomations?.length
@ -362,7 +362,7 @@ class HaConfigAreaPage extends LitElement {
? html` ? html`
<ha-card <ha-card
.header=${this.hass.localize( .header=${this.hass.localize(
"ui.panel.config.devices.scene.scenes" "ui.panel.config.devices.scene.scenes_heading"
)} )}
> >
${groupedScenes?.length ${groupedScenes?.length
@ -401,7 +401,7 @@ class HaConfigAreaPage extends LitElement {
? html` ? html`
<ha-card <ha-card
.header=${this.hass.localize( .header=${this.hass.localize(
"ui.panel.config.devices.script.scripts" "ui.panel.config.devices.script.scripts_heading"
)} )}
> >
${groupedScripts?.length ${groupedScripts?.length

View File

@ -137,6 +137,7 @@ export class CloudGooglePref extends LitElement {
"ui.panel.config.cloud.account.google.enter_pin_info" "ui.panel.config.cloud.account.google.enter_pin_info"
)} )}
<mwc-textfield <mwc-textfield
id="google_secure_devices_pin"
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.cloud.account.google.devices_pin" "ui.panel.config.cloud.account.google.devices_pin"
)} )}

View File

@ -3,7 +3,14 @@ import "@material/mwc-list/mwc-list-item";
import type { ActionDetail } from "@material/mwc-list"; import type { ActionDetail } from "@material/mwc-list";
import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card"; import "../../../components/ha-card";
@ -26,6 +33,7 @@ import "./ha-config-navigation";
import "./ha-config-updates"; import "./ha-config-updates";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { showToast } from "../../../util/toast";
@customElement("ha-config-dashboard") @customElement("ha-config-dashboard")
class HaConfigDashboard extends LitElement { class HaConfigDashboard extends LitElement {
@ -43,6 +51,8 @@ class HaConfigDashboard extends LitElement {
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
private _notifyUpdates = false;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-app-layout> <ha-app-layout>
@ -129,6 +139,26 @@ class HaConfigDashboard extends LitElement {
`; `;
} }
protected override updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (!changedProps.has("supervisorUpdates") || !this._notifyUpdates) {
return;
}
this._notifyUpdates = false;
if (this.supervisorUpdates?.length) {
showToast(this, {
message: this.hass.localize(
"ui.panel.config.updates.updates_refreshed"
),
});
} else {
showToast(this, {
message: this.hass.localize("ui.panel.config.updates.no_new_updates"),
});
}
}
private _showQuickBar(): void { private _showQuickBar(): void {
showQuickBar(this, { showQuickBar(this, {
commandMode: true, commandMode: true,
@ -140,6 +170,7 @@ class HaConfigDashboard extends LitElement {
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
if (isComponentLoaded(this.hass, "hassio")) { if (isComponentLoaded(this.hass, "hassio")) {
this._notifyUpdates = true;
await refreshSupervisorAvailableUpdates(this.hass); await refreshSupervisorAvailableUpdates(this.hass);
fireEvent(this, "ha-refresh-supervisor"); fireEvent(this, "ha-refresh-supervisor");
return; return;

View File

@ -23,7 +23,15 @@ export class HaDeviceCard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-card <ha-card
.header=${this.hass.localize("ui.panel.config.devices.device_info")} .header=${this.hass.localize(
"ui.panel.config.devices.device_info",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
this.device.entry_type || "device"
}_heading`
)
)}
> >
<div class="card-content"> <div class="card-content">
${this.device.model ${this.device.model

View File

@ -82,12 +82,26 @@ class DialogDeviceRegistryDetail extends LitElement {
</ha-switch> </ha-switch>
<div> <div>
<div> <div>
${this.hass.localize("ui.panel.config.devices.enabled_label")} ${this.hass.localize(
"ui.panel.config.devices.enabled_label",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
)}
</div> </div>
<div class="secondary"> <div class="secondary">
${this._disabledBy && this._disabledBy !== "user" ${this._disabledBy && this._disabledBy !== "user"
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.devices.enabled_cause", "ui.panel.config.devices.enabled_cause",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
),
"cause", "cause",
this.hass.localize( this.hass.localize(
`config_entry.disabled_by.${this._disabledBy}` `config_entry.disabled_by.${this._disabledBy}`

View File

@ -296,6 +296,10 @@ export class HaConfigDevicePage extends LitElement {
<ha-alert alert-type="warning"> <ha-alert alert-type="warning">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.devices.enabled_cause", "ui.panel.config.devices.enabled_cause",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${device.entry_type || "device"}`
),
"cause", "cause",
this.hass.localize( this.hass.localize(
`ui.panel.config.devices.disabled_by.${device.disabled_by}` `ui.panel.config.devices.disabled_by.${device.disabled_by}`
@ -485,17 +489,29 @@ export class HaConfigDevicePage extends LitElement {
<ha-card> <ha-card>
<h1 class="card-header"> <h1 class="card-header">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.devices.automation.automations" "ui.panel.config.devices.automation.automations_heading"
)} )}
<ha-icon-button <ha-icon-button
@click=${this._showAutomationDialog} @click=${this._showAutomationDialog}
.disabled=${device.disabled_by} .disabled=${device.disabled_by}
.label=${device.disabled_by .label=${device.disabled_by
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.devices.automation.create_disabled" "ui.panel.config.devices.automation.create_disabled",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
) )
: this.hass.localize( : this.hass.localize(
"ui.panel.config.devices.automation.create" "ui.panel.config.devices.automation.create",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
)} )}
.path=${mdiPlusCircle} .path=${mdiPlusCircle}
></ha-icon-button> ></ha-icon-button>
@ -547,6 +563,12 @@ export class HaConfigDevicePage extends LitElement {
"name", "name",
this.hass.localize( this.hass.localize(
"ui.panel.config.devices.automation.automations" "ui.panel.config.devices.automation.automations"
),
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
) )
)} )}
</div> </div>
@ -561,7 +583,7 @@ export class HaConfigDevicePage extends LitElement {
<ha-card> <ha-card>
<h1 class="card-header"> <h1 class="card-header">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.devices.scene.scenes" "ui.panel.config.devices.scene.scenes_heading"
)} )}
<ha-icon-button <ha-icon-button
@ -569,10 +591,22 @@ export class HaConfigDevicePage extends LitElement {
.disabled=${device.disabled_by} .disabled=${device.disabled_by}
.label=${device.disabled_by .label=${device.disabled_by
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.devices.scene.create_disabled" "ui.panel.config.devices.scene.create_disabled",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
) )
: this.hass.localize( : this.hass.localize(
"ui.panel.config.devices.scene.create" "ui.panel.config.devices.scene.create",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
)} )}
.path=${mdiPlusCircle} .path=${mdiPlusCircle}
></ha-icon-button> ></ha-icon-button>
@ -627,6 +661,12 @@ export class HaConfigDevicePage extends LitElement {
"name", "name",
this.hass.localize( this.hass.localize(
"ui.panel.config.devices.scene.scenes" "ui.panel.config.devices.scene.scenes"
),
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
) )
)} )}
</div> </div>
@ -641,17 +681,29 @@ export class HaConfigDevicePage extends LitElement {
<ha-card> <ha-card>
<h1 class="card-header"> <h1 class="card-header">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.devices.script.scripts" "ui.panel.config.devices.script.scripts_heading"
)} )}
<ha-icon-button <ha-icon-button
@click=${this._showScriptDialog} @click=${this._showScriptDialog}
.disabled=${device.disabled_by} .disabled=${device.disabled_by}
.label=${device.disabled_by .label=${device.disabled_by
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.devices.script.create_disabled" "ui.panel.config.devices.script.create_disabled",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
) )
: this.hass.localize( : this.hass.localize(
"ui.panel.config.devices.script.create" "ui.panel.config.devices.script.create",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
)
)} )}
.path=${mdiPlusCircle} .path=${mdiPlusCircle}
></ha-icon-button> ></ha-icon-button>
@ -685,6 +737,12 @@ export class HaConfigDevicePage extends LitElement {
"name", "name",
this.hass.localize( this.hass.localize(
"ui.panel.config.devices.script.scripts" "ui.panel.config.devices.script.scripts"
),
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
device.entry_type || "device"
}`
) )
)} )}
</div> </div>
@ -1065,6 +1123,7 @@ export class HaConfigDevicePage extends LitElement {
align-self: center; align-self: center;
align-items: center; align-items: center;
display: flex; display: flex;
white-space: nowrap;
} }
.column > *:not(:first-child) { .column > *:not(:first-child) {

View File

@ -338,7 +338,7 @@ export class HaConfigDeviceDashboard extends LitElement {
${this.hass.localize("ui.panel.config.devices.disabled")} ${this.hass.localize("ui.panel.config.devices.disabled")}
</paper-tooltip> </paper-tooltip>
</div>` </div>`
: "", : "",
}; };
} }
return columns; return columns;

View File

@ -42,7 +42,7 @@ import type { HomeAssistant } from "../../../types";
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail"; import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
const OVERRIDE_DEVICE_CLASSES = { const OVERRIDE_DEVICE_CLASSES = {
cover: ["window", "door", "garage"], cover: ["window", "door", "garage", "gate"],
binary_sensor: ["window", "door", "garage_door", "opening"], binary_sensor: ["window", "door", "garage_door", "opening"],
}; };

View File

@ -237,7 +237,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
template: (disabled_by) => template: (disabled_by) =>
this.hass.localize( this.hass.localize(
`ui.panel.config.devices.disabled_by.${disabled_by}` `ui.panel.config.devices.disabled_by.${disabled_by}`
) || disabled_by, ) ||
disabled_by ||
"—",
}, },
status: { status: {
title: this.hass.localize( title: this.hass.localize(

View File

@ -21,6 +21,7 @@ import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types"; import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section"; import "../../../ha-config-section";
import "../../../../../components/ha-alert";
export const ozwTabs: PageNavigation[] = []; export const ozwTabs: PageNavigation[] = [];
@ -64,6 +65,30 @@ class OZWConfigDashboard extends LitElement {
.tabs=${ozwTabs} .tabs=${ozwTabs}
back-path="/config/integrations" back-path="/config/integrations"
> >
<ha-alert
alert-type="warning"
title="This integration will stop working soon"
>
The OpenZWave integration is deprecated and will no longer receive any
updates. The technical dependencies will render this integration
unusable in the near future. We strongly advise you to migrate to the
new
<a
href="https://www.home-assistant.io/integrations/zwave_js"
target="_blank"
rel="noreferrer"
>Z-Wave JS integration</a
>.
<a
slot="action"
href="https://alerts.home-assistant.io/#ozw.markdown"
target="_blank"
rel="noreferrer"
>
<mwc-button>learn more</mwc-button>
</a>
</ha-alert>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}> <ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.hass.localize("ui.panel.config.ozw.select_instance.header")} ${this.hass.localize("ui.panel.config.ozw.select_instance.header")}
@ -162,6 +187,13 @@ class OZWConfigDashboard extends LitElement {
:host([narrow]) ha-config-section { :host([narrow]) ha-config-section {
margin-top: -20px; margin-top: -20px;
} }
ha-alert {
display: block;
margin: 16px;
}
ha-alert a {
text-decoration: none;
}
ha-card { ha-card {
overflow: hidden; overflow: hidden;
} }

View File

@ -12,6 +12,7 @@ import { computeStateName } from "../../../../../common/entity/compute_state_nam
import { sortStatesByName } from "../../../../../common/entity/states_sort_by_name"; import { sortStatesByName } from "../../../../../common/entity/states_sort_by_name";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-icon"; import "../../../../../components/ha-icon";
import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-icon-button-arrow-prev"; import "../../../../../components/ha-icon-button-arrow-prev";
@ -43,6 +44,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);
} }
ha-alert {
display: block;
margin: 16px;
}
ha-alert a {
text-decoration: none;
}
.content { .content {
margin-top: 24px; margin-top: 24px;
} }
@ -101,6 +110,30 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
</app-toolbar> </app-toolbar>
</app-header> </app-header>
<ha-alert
alert-type="warning"
title="This integration will stop working soon"
>
This Z-Wave integration is deprecated and will no longer receive any
updates. The technical dependencies will render this integration
unusable in the near future. We strongly advise you to migrate to the
new
<a
href="https://www.home-assistant.io/integrations/zwave_js"
target="_blank"
rel="noreferrer"
>Z-Wave JS integration</a
>.
<a
slot="action"
href="https://alerts.home-assistant.io/#zwave.markdown"
target="_blank"
rel="noreferrer"
>
<mwc-button>learn more</mwc-button>
</a>
</ha-alert>
<ha-config-section is-wide="[[isWide]]"> <ha-config-section is-wide="[[isWide]]">
<ha-card <ha-card
class="content" class="content"

View File

@ -7,6 +7,7 @@ import {
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoize from "memoize-one"; import memoize from "memoize-one";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { navigate } from "../../../../common/navigate"; import { navigate } from "../../../../common/navigate";
@ -53,9 +54,19 @@ export class HaConfigLovelaceDashboards extends LitElement {
icon: { icon: {
title: "", title: "",
type: "icon", type: "icon",
template: (icon) => template: (icon, dashboard) =>
icon icon
? html` <ha-icon slot="item-icon" .icon=${icon}></ha-icon> ` ? html`
<ha-icon
slot="item-icon"
.icon=${icon}
style=${ifDefined(
dashboard.iconColor
? `color: ${dashboard.iconColor}`
: undefined
)}
></ha-icon>
`
: html``, : html``,
}, },
title: { title: {
@ -64,7 +75,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
), ),
sortable: true, sortable: true,
filterable: true, filterable: true,
direction: "asc",
grows: true, grows: true,
template: (title, dashboard: any) => { template: (title, dashboard: any) => {
const titleTemplate = html` const titleTemplate = html`
@ -194,12 +204,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
url_path: "lovelace", url_path: "lovelace",
mode: defaultMode, mode: defaultMode,
filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "", filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "",
iconColor: "var(--primary-color)",
}, },
...dashboards.map((dashboard) => ({
filename: "",
...dashboard,
default: defaultUrlPath === dashboard.url_path,
})),
]; ];
if (isComponentLoaded(this.hass, "energy")) { if (isComponentLoaded(this.hass, "energy")) {
result.push({ result.push({
@ -209,8 +215,19 @@ export class HaConfigLovelaceDashboards extends LitElement {
mode: "storage", mode: "storage",
url_path: "energy", url_path: "energy",
filename: "", filename: "",
iconColor: "var(--label-badge-yellow)",
}); });
} }
result.push(
...dashboards
.sort((a, b) => stringCompare(a.title, b.title))
.map((dashboard) => ({
filename: "",
...dashboard,
default: defaultUrlPath === dashboard.url_path,
}))
);
return result; return result;
}); });

View File

@ -51,7 +51,12 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<p>Step ${this._step + 1} of 5</p> <p>
${this.hass.localize("ui.panel.energy.setup.step", {
step: this._step + 1,
steps: 5,
})}
</p>
${this._step === 0 ${this._step === 0
? html`<ha-energy-grid-settings ? html`<ha-energy-grid-settings
.hass=${this.hass} .hass=${this.hass}

View File

@ -170,6 +170,21 @@ export class HuiEnergyDevicesGraphCard
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour" dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
); );
const startMinHour = addHours(energyData.start, -1);
Object.values(this._data).forEach((stat) => {
// if the start of the first value is after the requested period, we have the first data point, and should add a zero point
if (stat.length && new Date(stat[0].start) > startMinHour) {
stat.unshift({
...stat[0],
start: startMinHour.toISOString(),
end: startMinHour.toISOString(),
sum: 0,
state: 0,
});
}
});
const data: Array<ChartDataset<"bar", ParsedDataType<"bar">>["data"]> = []; const data: Array<ChartDataset<"bar", ParsedDataType<"bar">>["data"]> = [];
const borderColor: string[] = []; const borderColor: string[] = [];
const backgroundColor: string[] = []; const backgroundColor: string[] = [];

View File

@ -115,7 +115,9 @@ class BarMediaPlayer extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
const isBrowser = this.entityId === BROWSER_PLAYER; const isBrowser = this.entityId === BROWSER_PLAYER;
const stateObj = this._stateObj; const stateObj = this._stateObj;
const controls = !this.narrow const controls = !stateObj
? undefined
: !this.narrow
? computeMediaControls(stateObj) ? computeMediaControls(stateObj)
: (stateObj.state === "playing" && : (stateObj.state === "playing" &&
(supportsFeature(stateObj, SUPPORT_PAUSE) || (supportsFeature(stateObj, SUPPORT_PAUSE) ||
@ -144,13 +146,16 @@ class BarMediaPlayer extends LitElement {
}, },
] ]
: [{}]; : [{}];
const mediaDescription = computeMediaDescription(stateObj); const mediaDescription = stateObj ? computeMediaDescription(stateObj) : "";
const mediaDuration = formatMediaTime(stateObj!.attributes.media_duration!); const mediaDuration = formatMediaTime(stateObj?.attributes.media_duration);
const mediaTitleClean = cleanupMediaTitle(stateObj.attributes.media_title); const mediaTitleClean = cleanupMediaTitle(
stateObj?.attributes.media_title || ""
);
const mediaArt = const mediaArt = stateObj
stateObj.attributes.entity_picture_local || ? stateObj.attributes.entity_picture_local ||
stateObj.attributes.entity_picture; stateObj.attributes.entity_picture
: undefined;
return html` return html`
<div <div
@ -216,7 +221,7 @@ class BarMediaPlayer extends LitElement {
slot="trigger" slot="trigger"
.label=${this.narrow .label=${this.narrow
? "" ? ""
: `${computeStateName(stateObj)} : `${stateObj ? computeStateName(stateObj) : this.entityId}
`} `}
> >
<ha-svg-icon <ha-svg-icon
@ -288,7 +293,7 @@ class BarMediaPlayer extends LitElement {
if ( if (
!this._progressInterval && !this._progressInterval &&
this._showProgressBar && this._showProgressBar &&
stateObj.state === "playing" stateObj?.state === "playing"
) { ) {
this._progressInterval = window.setInterval( this._progressInterval = window.setInterval(
() => this._updateProgressBar(), () => this._updateProgressBar(),
@ -296,21 +301,20 @@ class BarMediaPlayer extends LitElement {
); );
} else if ( } else if (
this._progressInterval && this._progressInterval &&
(!this._showProgressBar || stateObj.state !== "playing") (!this._showProgressBar || stateObj?.state !== "playing")
) { ) {
clearInterval(this._progressInterval); clearInterval(this._progressInterval);
this._progressInterval = undefined; this._progressInterval = undefined;
} }
} }
private get _stateObj(): MediaPlayerEntity { private get _stateObj(): MediaPlayerEntity | undefined {
if (this._browserPlayer) { if (this.entityId === BROWSER_PLAYER) {
return this._browserPlayer.toStateObj(); return this._browserPlayer
? this._browserPlayer.toStateObj()
: BrowserMediaPlayer.idleStateObj();
} }
return ( return this.hass!.states[this.entityId] as MediaPlayerEntity | undefined;
(this.hass!.states[this.entityId] as MediaPlayerEntity | undefined) ||
BrowserMediaPlayer.idleStateObj()
);
} }
private _openMoreInfo() { private _openMoreInfo() {
@ -328,6 +332,7 @@ class BarMediaPlayer extends LitElement {
const stateObj = this._stateObj; const stateObj = this._stateObj;
return ( return (
stateObj &&
(stateObj.state === "playing" || stateObj.state === "paused") && (stateObj.state === "playing" || stateObj.state === "paused") &&
"media_duration" in stateObj.attributes && "media_duration" in stateObj.attributes &&
"media_position" in stateObj.attributes "media_position" in stateObj.attributes
@ -343,19 +348,21 @@ class BarMediaPlayer extends LitElement {
} }
private _updateProgressBar(): void { private _updateProgressBar(): void {
if (!this._progressBar || !this._currentProgress) { const stateObj = this._stateObj;
if (!this._progressBar || !this._currentProgress || !stateObj) {
return; return;
} }
if (!this._stateObj.attributes.media_duration) { if (!stateObj.attributes.media_duration) {
this._progressBar.progress = 0; this._progressBar.progress = 0;
this._currentProgress.innerHTML = ""; this._currentProgress.innerHTML = "";
return; return;
} }
const currentProgress = getCurrentProgress(this._stateObj); const currentProgress = getCurrentProgress(stateObj);
this._progressBar.progress = this._progressBar.progress =
currentProgress / this._stateObj.attributes.media_duration; currentProgress / stateObj.attributes.media_duration;
if (this._currentProgress) { if (this._currentProgress) {
this._currentProgress.innerHTML = formatMediaTime(currentProgress); this._currentProgress.innerHTML = formatMediaTime(currentProgress);

View File

@ -28,6 +28,7 @@ import { haStyle } from "../../resources/styles";
import type { HomeAssistant, Route } from "../../types"; import type { HomeAssistant, Route } from "../../types";
import "./ha-bar-media-player"; import "./ha-bar-media-player";
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog"; import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
@customElement("ha-panel-media-browser") @customElement("ha-panel-media-browser")
class PanelMediaBrowser extends LitElement { class PanelMediaBrowser extends LitElement {
@ -112,6 +113,23 @@ class PanelMediaBrowser extends LitElement {
.split("/"); .split("/");
if (routePlayer !== this._entityId) { if (routePlayer !== this._entityId) {
// Detect if picked player doesn't exist (anymore)
// Can happen if URL bookmarked or stored in local storage
if (
routePlayer !== BROWSER_PLAYER &&
this.hass.states[routePlayer] === undefined
) {
navigate(`/media-browser/${BROWSER_PLAYER}`, { replace: true });
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.media-browser.error.player_not_exist",
{
name: routePlayer,
}
),
});
return;
}
this._entityId = routePlayer; this._entityId = routePlayer;
} }

View File

@ -1,3 +1,4 @@
// Caution before editing - For latest builds, this module is replaced with emptiness and thus not imported (see build-scripts/bundle.js)
import "core-js"; import "core-js";
import "regenerator-runtime/runtime"; import "regenerator-runtime/runtime";
import "lit/polyfill-support"; import "lit/polyfill-support";

View File

@ -733,6 +733,7 @@
"cover": { "cover": {
"door": "Door", "door": "Door",
"garage": "Garage door", "garage": "Garage door",
"gate": "Gate",
"window": "Window" "window": "Window"
} }
}, },
@ -1011,6 +1012,8 @@
"description": "You need to run the Home Assistant operating system to be able to check and install updates from the Home Assistant user interface." "description": "You need to run the Home Assistant operating system to be able to check and install updates from the Home Assistant user interface."
}, },
"check_updates": "Check for updates", "check_updates": "Check for updates",
"no_new_updates": "No new updates found",
"updates_refreshed": "Updates refreshed",
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}", "title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
"unable_to_fetch": "Unable to load updates", "unable_to_fetch": "Unable to load updates",
"version_available": "Version {version_available} is available", "version_available": "Version {version_available} is available",
@ -2205,18 +2208,18 @@
} }
}, },
"devices": { "devices": {
"add_prompt": "No {name} have been added using this device yet. You can add one by clicking the + button above.", "add_prompt": "No {name} have been added using this {type} yet. You can add one by clicking the + button above.",
"caption": "Devices", "caption": "Devices",
"description": "Manage configured devices", "description": "Manage configured devices",
"device_info": "Device info", "device_info": "{type} info",
"edit_settings": "Edit settings", "edit_settings": "Edit settings",
"unnamed_device": "Unnamed device", "unnamed_device": "Unnamed {type}",
"unknown_error": "Unknown error", "unknown_error": "Unknown error",
"name": "Name", "name": "Name",
"update": "Update", "update": "Update",
"no_devices": "No devices", "no_devices": "No devices",
"enabled_label": "Enable device", "enabled_label": "Enable {type}",
"enabled_cause": "The device is disabled by {cause}.", "enabled_cause": "The {type} is disabled by {cause}.",
"disabled_by": { "disabled_by": {
"user": "User", "user": "User",
"integration": "Integration", "integration": "Integration",
@ -2226,12 +2229,19 @@
"open_configuration_url_device": "Visit device", "open_configuration_url_device": "Visit device",
"open_configuration_url_service": "Visit service", "open_configuration_url_service": "Visit service",
"download_diagnostics": "Download diagnostics", "download_diagnostics": "Download diagnostics",
"type": {
"device_heading": "Device",
"device": "device",
"service_heading": "Service",
"service": "service"
},
"automation": { "automation": {
"automations": "Automations", "automations_heading": "Automations",
"automations": "automations",
"no_automations": "No automations", "no_automations": "No automations",
"unknown_automation": "Unknown automation", "unknown_automation": "Unknown automation",
"create": "Create automation with device", "create": "Create automation with {type}",
"create_disable": "Can't create automation with disabled device", "create_disable": "Can't create automation with disabled {type}",
"triggers": { "triggers": {
"caption": "Do something when…", "caption": "Do something when…",
"no_triggers": "No triggers", "no_triggers": "No triggers",
@ -2250,19 +2260,21 @@
"no_device_automations": "There are no automations available for this device." "no_device_automations": "There are no automations available for this device."
}, },
"script": { "script": {
"scripts": "Scripts", "scripts_heading": "Scripts",
"scripts": "scripts",
"no_scripts": "No scripts", "no_scripts": "No scripts",
"create": "Create script with device", "create": "Create script with {type}",
"create_disable": "Can't create script with disabled device" "create_disable": "Can't create script with disabled {type}"
}, },
"scene": { "scene": {
"scenes": "Scenes", "scenes_heading": "Scenes",
"scenes": "scenes",
"no_scenes": "No scenes", "no_scenes": "No scenes",
"create": "Create scene with device", "create": "Create scene with {type}",
"create_disable": "Can't create scene with disabled device" "create_disable": "Can't create scene with disabled {type}"
}, },
"cant_edit": "You can only edit items that are created in the UI.", "cant_edit": "You can only edit items that are created in the UI.",
"device_not_found": "Device not found.", "device_not_found": "Device / service not found.",
"entities": { "entities": {
"entities": "Entities", "entities": "Entities",
"control": "Controls", "control": "Controls",
@ -2274,8 +2286,6 @@
"hide_disabled": "Hide disabled", "hide_disabled": "Hide disabled",
"disabled_entities": "+{count} {count, plural,\n one {disabled entity}\n other {disabled entities}\n}" "disabled_entities": "+{count} {count, plural,\n one {disabled entity}\n other {disabled entities}\n}"
}, },
"scripts": "Scripts",
"scenes": "Scenes",
"confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?", "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!", "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!",
"confirm_disable_config_entry": "There are no more devices for the config entry {entry_name}, do you want to instead disable the config entry?", "confirm_disable_config_entry": "There are no more devices for the config entry {entry_name}, do you want to instead disable the config entry?",
@ -3644,6 +3654,11 @@
"delete_prompt": "Delete this message?", "delete_prompt": "Delete this message?",
"delete_button": "Delete" "delete_button": "Delete"
}, },
"media-browser": {
"error": {
"player_not_exist": "Media player {name} does not exist"
}
},
"map": { "map": {
"edit_zones": "Edit Zones" "edit_zones": "Edit Zones"
}, },
@ -4096,7 +4111,8 @@
"setup": { "setup": {
"next": "Next", "next": "Next",
"back": "Back", "back": "Back",
"done": "Show me my energy dashboard!" "done": "Show me my energy dashboard!",
"step": "Step {step} of {steps}"
}, },
"charts": { "charts": {
"stat_house_energy_meter": "Total energy consumption", "stat_house_energy_meter": "Total energy consumption",

View File

@ -1958,10 +1958,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@lit/reactive-element@npm:1.0.1": "@lit/reactive-element@npm:1.2.1":
version: 1.0.1 version: 1.2.1
resolution: "@lit/reactive-element@npm:1.0.1" resolution: "@lit/reactive-element@npm:1.2.1"
checksum: 82f8eb195acb766413fa37dafab8dc853547e5fbb901318d9911b3f9410a15ceea37c9a4b0954ab37710e91140af243148af9f72ec3bd14fe71e1d03faaaad7a checksum: 58f1b62c54b1899180b11cd44009ee91ac35099f9016259d3b9cc2f969fc920ab7e7ec32a816986ce89a27c632e25568542c93fdfed6730c19c850f7db0ba4cf
languageName: node languageName: node
linkType: hard linkType: hard
@ -9187,7 +9187,7 @@ fsevents@^1.2.7:
leaflet: ^1.7.1 leaflet: ^1.7.1
leaflet-draw: ^1.0.4 leaflet-draw: ^1.0.4
lint-staged: ^11.1.2 lint-staged: ^11.1.2
lit: ^2.0.2 lit: ^2.1.2
lit-analyzer: ^1.2.1 lit-analyzer: ^1.2.1
lit-vaadin-helpers: ^0.2.1 lit-vaadin-helpers: ^0.2.1
lodash.template: ^4.5.0 lodash.template: ^4.5.0
@ -10776,22 +10776,22 @@ fsevents@^1.2.7:
languageName: node languageName: node
linkType: hard linkType: hard
"lit-element@npm:3.0.1": "lit-element@npm:3.1.2":
version: 3.0.1 version: 3.1.2
resolution: "lit-element@npm:3.0.1" resolution: "lit-element@npm:3.1.2"
dependencies: dependencies:
"@lit/reactive-element": ^1.0.0 "@lit/reactive-element": ^1.1.0
lit-html: ^2.0.0 lit-html: ^2.1.0
checksum: 3735cd1b96efb44f1ecdcb07406604d7c6feeab07f81cfc904a0f9ab806fab1211e8bf68827513b12e12e61448e3cd6508664d433abd3f0c5c4408b0a6b2d7a6 checksum: 56ae568369af7c51cfe7187136ffb0782308ed3a12aa664085bd6761ff89fb60d3ac2ef286ce15154a2eb179d89f8655f7adaa18b8c083091c03dc75dd2ebf16
languageName: node languageName: node
linkType: hard linkType: hard
"lit-html@npm:2.0.1": "lit-html@npm:2.1.2":
version: 2.0.1 version: 2.1.2
resolution: "lit-html@npm:2.0.1" resolution: "lit-html@npm:2.1.2"
dependencies: dependencies:
"@types/trusted-types": ^2.0.2 "@types/trusted-types": ^2.0.2
checksum: 7ee9e909ec59009539d5b2d7bd07ceb6e182ed5c6535f36da5265dd2f5dc39f9e5f445e8272953a26948ff5724cf110836c792c439883527a2a3b46ecdbbb395 checksum: 0c87d83b3577dbb0a2c50743296b6600a6872eac2f23b48da7ba3e604b8c72a8b0e1e48cfe0e00dd6f4ca921ab57f97e20709756f2115077478ffb05efa6cea0
languageName: node languageName: node
linkType: hard linkType: hard
@ -10804,14 +10804,14 @@ fsevents@^1.2.7:
languageName: node languageName: node
linkType: hard linkType: hard
"lit@npm:^2.0.2": "lit@npm:^2.1.2":
version: 2.0.2 version: 2.1.2
resolution: "lit@npm:2.0.2" resolution: "lit@npm:2.1.2"
dependencies: dependencies:
"@lit/reactive-element": ^1.0.0 "@lit/reactive-element": ^1.1.0
lit-element: ^3.0.0 lit-element: ^3.1.0
lit-html: ^2.0.0 lit-html: ^2.1.0
checksum: 52a04b25164da1683c7295b305087794f175165cd1561e03f0c50ae998823f0e26101c82add05dea773e35472fbe3ed84486a4685294897f588d126fe8419e05 checksum: dc3d6f30d508e48ad665a5777383c26055ae6514a7bd6a5745ac75cfeeeff8db49aa3b0f32db1a19e43bd7eec2bccd7db8523d5ed445ca0035fe37fddd9fb646
languageName: node languageName: node
linkType: hard linkType: hard