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 }}
- name: Build and release package
run: |
python3 -m pip install twine
python3 -m pip install twine build
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"

View File

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

View File

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

View File

@ -110,7 +110,7 @@
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.0.2",
"lit": "^2.1.2",
"lit-vaadin-helpers": "^0.2.1",
"marked": "^3.0.2",
"memoize-one": "^5.2.1",
@ -236,10 +236,10 @@
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10",
"lit": "^2.0.2",
"lit-html": "2.0.1",
"lit-element": "3.0.1",
"@lit/reactive-element": "1.0.1"
"lit": "^2.1.2",
"lit-html": "2.1.2",
"lit-element": "3.1.2",
"@lit/reactive-element": "1.2.1"
},
"main": "src/home-assistant.js",
"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
rm -rf dist
python3 setup.py -q sdist
rm -rf dist home_assistant_frontend.egg-info
python3 -m build
python3 -m twine upload dist/* --skip-existing

View File

@ -50,14 +50,14 @@ async function main(args) {
return;
}
const setup = fs.readFileSync("setup.py", "utf8");
const setup = fs.readFileSync("setup.cfg", "utf8");
const version = setup.match(/\d{8}\.\d+/)[0];
const newVersion = method(version);
console.log("Current version:", version);
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) {
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(
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,
)
setup()

View File

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

View File

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

View File

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

View File

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

View File

@ -55,7 +55,13 @@ export const computeDeviceName = (
device.name_by_user ||
device.name ||
(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) =>
devices.filter((device) => device.area_id === areaId);

View File

@ -185,15 +185,6 @@ export const browseMediaPlayer = (
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 => {
let progress = stateObj.attributes.media_position!;
@ -321,8 +312,8 @@ export const computeMediaControls = (
return buttons.length > 0 ? buttons : undefined;
};
export const formatMediaTime = (seconds: number): string => {
if (!seconds) {
export const formatMediaTime = (seconds: number | undefined): string => {
if (seconds === undefined) {
return "";
}

View File

@ -1,4 +1,5 @@
import { HomeAssistant } from "../types";
import { MediaPlayerItem } from "./media-player";
export interface ResolvedMediaSource {
url: string;
@ -13,3 +14,12 @@ export const resolveMediaSource = (
type: "media_source/resolve_media",
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
);
} catch (err: any) {
this._step = undefined;
this._params = undefined;
this.closeDialog();
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: this.hass.localize(
text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load"
),
)}: ${err.message || err.body}`,
});
return;
}
@ -177,6 +176,7 @@ class DataEntryFlowDialog extends LitElement {
});
}
this._loading = undefined;
this._step = undefined;
this._params = undefined;
this._devices = undefined;
@ -372,15 +372,14 @@ class DataEntryFlowDialog extends LitElement {
try {
step = await this._params!.flowConfig.createFlow(this.hass, handler);
} catch (err: any) {
this._step = undefined;
this._params = undefined;
this.closeDialog();
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: this.hass.localize(
text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load"
),
)}: ${err.message || err.body}`,
});
return;
} finally {
@ -405,6 +404,15 @@ class DataEntryFlowDialog extends LitElement {
this._loading = "loading_step";
try {
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 {
this._loading = undefined;
}

View File

@ -185,13 +185,14 @@ export class QuickBar extends LitElement {
.label=${this.hass!.localize("ui.common.clear")}
.path=${mdiClose}
></ha-icon-button>`}
${this._narrow &&
html`
<mwc-button
.label=${this.hass!.localize("ui.common.close")}
@click=${this.closeDialog}
></mwc-button>
`}
${this._narrow
? html`
<mwc-button
.label=${this.hass!.localize("ui.common.close")}
@click=${this.closeDialog}
></mwc-button>
`
: ""}
</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 "../resources/ha-style";
import "../resources/roboto";
import "../util/legacy-support";
setPassiveTouchGestures(true);
setCancelSyntheticClickEvents(false);

View File

@ -1,11 +1,14 @@
// 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"),

View File

@ -1,5 +1,6 @@
// 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";
@ -15,6 +16,8 @@ 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>;
@ -47,7 +50,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;

View File

@ -1,11 +1,14 @@
// 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>;

View File

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

View File

@ -137,6 +137,7 @@ export class CloudGooglePref extends LitElement {
"ui.panel.config.cloud.account.google.enter_pin_info"
)}
<mwc-textfield
id="google_secure_devices_pin"
.label=${this.hass.localize(
"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 "@polymer/app-layout/app-header/app-header";
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 { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card";
@ -26,6 +33,7 @@ import "./ha-config-navigation";
import "./ha-config-updates";
import { fireEvent } from "../../../common/dom/fire_event";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { showToast } from "../../../util/toast";
@customElement("ha-config-dashboard")
class HaConfigDashboard extends LitElement {
@ -43,6 +51,8 @@ class HaConfigDashboard extends LitElement {
@property() public showAdvanced!: boolean;
private _notifyUpdates = false;
protected render(): TemplateResult {
return html`
<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 {
showQuickBar(this, {
commandMode: true,
@ -140,6 +170,7 @@ class HaConfigDashboard extends LitElement {
switch (ev.detail.index) {
case 0:
if (isComponentLoaded(this.hass, "hassio")) {
this._notifyUpdates = true;
await refreshSupervisorAvailableUpdates(this.hass);
fireEvent(this, "ha-refresh-supervisor");
return;

View File

@ -23,7 +23,15 @@ export class HaDeviceCard extends LitElement {
protected render(): TemplateResult {
return html`
<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">
${this.device.model

View File

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

View File

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

View File

@ -338,7 +338,7 @@ export class HaConfigDeviceDashboard extends LitElement {
${this.hass.localize("ui.panel.config.devices.disabled")}
</paper-tooltip>
</div>`
: "",
: "",
};
}
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";
const OVERRIDE_DEVICE_CLASSES = {
cover: ["window", "door", "garage"],
cover: ["window", "door", "garage", "gate"],
binary_sensor: ["window", "door", "garage_door", "opening"],
};

View File

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

View File

@ -21,6 +21,7 @@ import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import "../../../../../components/ha-alert";
export const ozwTabs: PageNavigation[] = [];
@ -64,6 +65,30 @@ class OZWConfigDashboard extends LitElement {
.tabs=${ozwTabs}
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}>
<div slot="header">
${this.hass.localize("ui.panel.config.ozw.select_instance.header")}
@ -162,6 +187,13 @@ class OZWConfigDashboard extends LitElement {
:host([narrow]) ha-config-section {
margin-top: -20px;
}
ha-alert {
display: block;
margin: 16px;
}
ha-alert a {
text-decoration: none;
}
ha-card {
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 "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-icon";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-icon-button-arrow-prev";
@ -43,6 +44,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
border-bottom: 1px solid var(--divider-color);
}
ha-alert {
display: block;
margin: 16px;
}
ha-alert a {
text-decoration: none;
}
.content {
margin-top: 24px;
}
@ -101,6 +110,30 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
</app-toolbar>
</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-card
class="content"

View File

@ -7,6 +7,7 @@ import {
import "@polymer/paper-tooltip/paper-tooltip";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoize from "memoize-one";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { navigate } from "../../../../common/navigate";
@ -53,9 +54,19 @@ export class HaConfigLovelaceDashboards extends LitElement {
icon: {
title: "",
type: "icon",
template: (icon) =>
template: (icon, dashboard) =>
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``,
},
title: {
@ -64,7 +75,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
),
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (title, dashboard: any) => {
const titleTemplate = html`
@ -194,12 +204,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
url_path: "lovelace",
mode: defaultMode,
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")) {
result.push({
@ -209,8 +215,19 @@ export class HaConfigLovelaceDashboards extends LitElement {
mode: "storage",
url_path: "energy",
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;
});

View File

@ -51,7 +51,12 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard {
protected render(): TemplateResult {
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
? html`<ha-energy-grid-settings
.hass=${this.hass}

View File

@ -170,6 +170,21 @@ export class HuiEnergyDevicesGraphCard
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 borderColor: string[] = [];
const backgroundColor: string[] = [];

View File

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

View File

@ -28,6 +28,7 @@ import { haStyle } from "../../resources/styles";
import type { HomeAssistant, Route } from "../../types";
import "./ha-bar-media-player";
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
@customElement("ha-panel-media-browser")
class PanelMediaBrowser extends LitElement {
@ -112,6 +113,23 @@ class PanelMediaBrowser extends LitElement {
.split("/");
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;
}

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 "regenerator-runtime/runtime";
import "lit/polyfill-support";

View File

@ -733,6 +733,7 @@
"cover": {
"door": "Door",
"garage": "Garage door",
"gate": "Gate",
"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."
},
"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}",
"unable_to_fetch": "Unable to load updates",
"version_available": "Version {version_available} is available",
@ -2205,18 +2208,18 @@
}
},
"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",
"description": "Manage configured devices",
"device_info": "Device info",
"device_info": "{type} info",
"edit_settings": "Edit settings",
"unnamed_device": "Unnamed device",
"unnamed_device": "Unnamed {type}",
"unknown_error": "Unknown error",
"name": "Name",
"update": "Update",
"no_devices": "No devices",
"enabled_label": "Enable device",
"enabled_cause": "The device is disabled by {cause}.",
"enabled_label": "Enable {type}",
"enabled_cause": "The {type} is disabled by {cause}.",
"disabled_by": {
"user": "User",
"integration": "Integration",
@ -2226,12 +2229,19 @@
"open_configuration_url_device": "Visit device",
"open_configuration_url_service": "Visit service",
"download_diagnostics": "Download diagnostics",
"type": {
"device_heading": "Device",
"device": "device",
"service_heading": "Service",
"service": "service"
},
"automation": {
"automations": "Automations",
"automations_heading": "Automations",
"automations": "automations",
"no_automations": "No automations",
"unknown_automation": "Unknown automation",
"create": "Create automation with device",
"create_disable": "Can't create automation with disabled device",
"create": "Create automation with {type}",
"create_disable": "Can't create automation with disabled {type}",
"triggers": {
"caption": "Do something when…",
"no_triggers": "No triggers",
@ -2250,19 +2260,21 @@
"no_device_automations": "There are no automations available for this device."
},
"script": {
"scripts": "Scripts",
"scripts_heading": "Scripts",
"scripts": "scripts",
"no_scripts": "No scripts",
"create": "Create script with device",
"create_disable": "Can't create script with disabled device"
"create": "Create script with {type}",
"create_disable": "Can't create script with disabled {type}"
},
"scene": {
"scenes": "Scenes",
"scenes_heading": "Scenes",
"scenes": "scenes",
"no_scenes": "No scenes",
"create": "Create scene with device",
"create_disable": "Can't create scene with disabled device"
"create": "Create scene with {type}",
"create_disable": "Can't create scene with disabled {type}"
},
"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",
"control": "Controls",
@ -2274,8 +2286,6 @@
"hide_disabled": "Hide disabled",
"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_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?",
@ -3644,6 +3654,11 @@
"delete_prompt": "Delete this message?",
"delete_button": "Delete"
},
"media-browser": {
"error": {
"player_not_exist": "Media player {name} does not exist"
}
},
"map": {
"edit_zones": "Edit Zones"
},
@ -4096,7 +4111,8 @@
"setup": {
"next": "Next",
"back": "Back",
"done": "Show me my energy dashboard!"
"done": "Show me my energy dashboard!",
"step": "Step {step} of {steps}"
},
"charts": {
"stat_house_energy_meter": "Total energy consumption",

View File

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