20231204.0 (#18882)

This commit is contained in:
Bram Kragten 2023-12-04 12:10:33 +01:00 committed by GitHub
commit ae2e8e7402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 415 additions and 392 deletions

View File

@ -25,10 +25,10 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@babel/runtime": "7.23.4", "@babel/runtime": "7.23.5",
"@braintree/sanitize-url": "6.0.4", "@braintree/sanitize-url": "6.0.4",
"@codemirror/autocomplete": "6.11.1", "@codemirror/autocomplete": "6.11.1",
"@codemirror/commands": "6.3.1", "@codemirror/commands": "6.3.2",
"@codemirror/language": "6.9.3", "@codemirror/language": "6.9.3",
"@codemirror/legacy-modes": "6.3.3", "@codemirror/legacy-modes": "6.3.3",
"@codemirror/search": "6.5.5", "@codemirror/search": "6.5.5",
@ -43,12 +43,12 @@
"@formatjs/intl-numberformat": "8.9.0", "@formatjs/intl-numberformat": "8.9.0",
"@formatjs/intl-pluralrules": "5.2.10", "@formatjs/intl-pluralrules": "5.2.10",
"@formatjs/intl-relativetimeformat": "11.2.10", "@formatjs/intl-relativetimeformat": "11.2.10",
"@fullcalendar/core": "6.1.9", "@fullcalendar/core": "6.1.10",
"@fullcalendar/daygrid": "6.1.9", "@fullcalendar/daygrid": "6.1.10",
"@fullcalendar/interaction": "6.1.9", "@fullcalendar/interaction": "6.1.10",
"@fullcalendar/list": "6.1.9", "@fullcalendar/list": "6.1.10",
"@fullcalendar/luxon3": "6.1.9", "@fullcalendar/luxon3": "6.1.10",
"@fullcalendar/timegrid": "6.1.9", "@fullcalendar/timegrid": "6.1.10",
"@lezer/highlight": "1.2.0", "@lezer/highlight": "1.2.0",
"@lit-labs/context": "0.4.1", "@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.6", "@lit-labs/motion": "1.0.6",
@ -130,7 +130,7 @@
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"roboto-fontface": "0.10.0", "roboto-fontface": "0.10.0",
"rrule": "2.8.1", "rrule": "2.8.1",
"sortablejs": "1.15.0", "sortablejs": "1.15.1",
"stacktrace-js": "2.0.2", "stacktrace-js": "2.0.2",
"superstruct": "1.0.3", "superstruct": "1.0.3",
"tinykeys": "2.1.0", "tinykeys": "2.1.0",
@ -152,11 +152,11 @@
"xss": "1.0.14" "xss": "1.0.14"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.23.3", "@babel/core": "7.23.5",
"@babel/helper-define-polyfill-provider": "0.4.3", "@babel/helper-define-polyfill-provider": "0.4.3",
"@babel/plugin-proposal-decorators": "7.23.3", "@babel/plugin-proposal-decorators": "7.23.5",
"@babel/plugin-transform-runtime": "7.23.4", "@babel/plugin-transform-runtime": "7.23.4",
"@babel/preset-env": "7.23.3", "@babel/preset-env": "7.23.5",
"@babel/preset-typescript": "7.23.3", "@babel/preset-typescript": "7.23.3",
"@bundle-stats/plugin-webpack-filter": "4.8.3", "@bundle-stats/plugin-webpack-filter": "4.8.3",
"@koa/cors": "4.0.0", "@koa/cors": "4.0.0",
@ -178,7 +178,7 @@
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.8", "@types/leaflet": "1.9.8",
"@types/leaflet-draw": "1.0.11", "@types/leaflet-draw": "1.0.11",
"@types/luxon": "3.3.5", "@types/luxon": "3.3.6",
"@types/mocha": "10.0.6", "@types/mocha": "10.0.6",
"@types/qrcode": "1.5.5", "@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4", "@types/serve-handler": "6.1.4",
@ -186,8 +186,8 @@
"@types/tar": "6.1.10", "@types/tar": "6.1.10",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29", "@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "6.12.0", "@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.12.0", "@typescript-eslint/parser": "6.13.1",
"@web/dev-server": "0.1.38", "@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1", "@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",
@ -255,7 +255,7 @@
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch", "@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@material/mwc-button@^0.25.3": "^0.27.0", "@material/mwc-button@^0.25.3": "^0.27.0",
"lit": "2.8.0", "lit": "2.8.0",
"clean-css": "5.3.2", "clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3", "@lit/reactive-element": "1.6.3",
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch", "sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch" "leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20231130.0" version = "20231204.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"

View File

@ -279,6 +279,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
); );
} }
if (window.innerWidth > 450) {
import("../resources/particles");
}
// If we are logging into the instance that is hosting this auth form // If we are logging into the instance that is hosting this auth form
// we will register the service worker to start preloading. // we will register the service worker to start preloading.
if (url.host === location.host) { if (url.host === location.host) {

View File

@ -154,6 +154,10 @@ export class HaLocalAuthFlow extends LitElement {
ha-button { ha-button {
--mdc-typography-button-text-transform: none; --mdc-typography-button-text-transform: none;
} }
.forgot-password-container {
text-align: right;
padding: 8px 0 16px 0;
}
a.forgot-password { a.forgot-password {
color: var(--primary-color); color: var(--primary-color);
text-decoration: none; text-decoration: none;
@ -177,7 +181,7 @@ export class HaLocalAuthFlow extends LitElement {
</style> </style>
${this._error ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>` ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""} : nothing}
${this._step ${this._step
? html`<ha-auth-flow ? html`<ha-auth-flow
.clientId=${this.clientId} .clientId=${this.clientId}
@ -188,72 +192,76 @@ export class HaLocalAuthFlow extends LitElement {
.localize=${this.localize} .localize=${this.localize}
></ha-auth-flow>` ></ha-auth-flow>`
: this._selectedUser : this._selectedUser
? html`<div class="login-form"><div class="person"> ? html`<div class="login-form">
<ha-person-badge <div class="person">
.person=${this._persons[this._selectedUser]} <ha-person-badge
></ha-person-badge> .person=${this._persons[this._selectedUser]}
<p>${this._persons[this._selectedUser].name}</p> ></ha-person-badge>
</div> <p>${this._persons[this._selectedUser].name}</p>
<form>
<input
type="hidden"
name="username"
autocomplete="username"
.value=${this.authProvider.users[this._selectedUser]}
/>
<ha-auth-textfield
.type=${this._unmaskedPassword ? "text" : "password"}
id="password"
name="password"
.label=${this.localize(
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
)}
required
autoValidate
autocomplete
iconTrailing
validationMessage="Required"
>
<ha-icon-button
toggles
.label=${
this.localize(
this._unmaskedPassword
? "ui.panel.page-authorize.form.hide_password"
: "ui.panel.page-authorize.form.show_password"
) || (this._unmaskedPassword ? "Hide password" : "Show password")
}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
</ha-auth-textfield>
</div>
<div class="action space-between">
<mwc-button
@click=${this._restart}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.previous")}
</mwc-button>
<mwc-button
raised
@click=${this._handleSubmit}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.next")}
</mwc-button>
</div> </div>
<div class="action"> <form>
<a class="forgot-password" <input
href="https://www.home-assistant.io/docs/locked_out/#forgot-password" type="hidden"
target="_blank" name="username"
rel="noreferrer noopener" autocomplete="username"
>${this.localize( readonly
"ui.panel.page-authorize.forgot_password" .value=${this.authProvider.users[this._selectedUser]}
)}</a />
<ha-auth-textfield
.type=${this._unmaskedPassword ? "text" : "password"}
autocomplete="current-password"
id="password"
name="password"
.label=${this.localize(
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
)}
required
autoValidate
iconTrailing
validationMessage="Required"
> >
<ha-icon-button
toggles
.label=${this.localize(
this._unmaskedPassword
? "ui.panel.page-authorize.form.hide_password"
: "ui.panel.page-authorize.form.show_password"
) ||
(this._unmaskedPassword
? "Hide password"
: "Show password")}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
</ha-auth-textfield>
<div class="forgot-password-container">
<a
class="forgot-password"
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
target="_blank"
rel="noreferrer noopener"
>${this.localize(
"ui.panel.page-authorize.forgot_password"
)}</a
>
</div> </div>
</form>` <div class="action space-between">
<mwc-button
@click=${this._restart}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.previous")}
</mwc-button>
<mwc-button
raised
@click=${this._handleSubmit}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.next")}
</mwc-button>
</div>
</form>
</div>`
: html`<h1> : html`<h1>
${this.localize("ui.panel.page-authorize.welcome_home")} ${this.localize("ui.panel.page-authorize.welcome_home")}
</h1> </h1>

View File

@ -581,6 +581,28 @@ const clearEnergyCollectionPreferences = (hass: HomeAssistant) => {
}); });
}; };
const scheduleHourlyRefresh = (collection: EnergyCollection) => {
if (collection._refreshTimeout) {
clearTimeout(collection._refreshTimeout);
}
if (collection._active && (!collection.end || collection.end > new Date())) {
// The stats are created every hour
// Schedule a refresh for 20 minutes past the hour
// If the end is larger than the current time.
const nextFetch = new Date();
if (nextFetch.getMinutes() >= 20) {
nextFetch.setHours(nextFetch.getHours() + 1);
}
nextFetch.setMinutes(20, 0, 0);
collection._refreshTimeout = window.setTimeout(
() => collection.refresh(),
nextFetch.getTime() - Date.now()
);
}
};
export const getEnergyDataCollection = ( export const getEnergyDataCollection = (
hass: HomeAssistant, hass: HomeAssistant,
options: { prefs?: EnergyPreferences; key?: string } = {} options: { prefs?: EnergyPreferences; key?: string } = {}
@ -609,28 +631,7 @@ export const getEnergyDataCollection = (
collection.prefs = await getEnergyPreferences(hass); collection.prefs = await getEnergyPreferences(hass);
} }
if (collection._refreshTimeout) { scheduleHourlyRefresh(collection);
clearTimeout(collection._refreshTimeout);
}
if (
collection._active &&
(!collection.end || collection.end > new Date())
) {
// The stats are created every hour
// Schedule a refresh for 20 minutes past the hour
// If the end is larger than the current time.
const nextFetch = new Date();
if (nextFetch.getMinutes() >= 20) {
nextFetch.setHours(nextFetch.getHours() + 1);
}
nextFetch.setMinutes(20, 0, 0);
collection._refreshTimeout = window.setTimeout(
() => collection.refresh(),
nextFetch.getTime() - Date.now()
);
}
return getEnergyData( return getEnergyData(
hass, hass,
@ -647,6 +648,11 @@ export const getEnergyDataCollection = (
collection.subscribe = (subscriber: (data: EnergyData) => void) => { collection.subscribe = (subscriber: (data: EnergyData) => void) => {
const unsub = origSubscribe(subscriber); const unsub = origSubscribe(subscriber);
collection._active++; collection._active++;
if (collection._refreshTimeout === undefined) {
scheduleHourlyRefresh(collection);
}
return () => { return () => {
collection._active--; collection._active--;
if (collection._active < 1) { if (collection._active < 1) {

View File

@ -420,7 +420,8 @@ export const computeHistory = (
hass: HomeAssistant, hass: HomeAssistant,
stateHistory: HistoryStates, stateHistory: HistoryStates,
localize: LocalizeFunc, localize: LocalizeFunc,
sensorNumericalDeviceClasses: string[] sensorNumericalDeviceClasses: string[],
splitDeviceClasses = false
): HistoryResult => { ): HistoryResult => {
const lineChartDevices: { [unit: string]: HistoryStates } = {}; const lineChartDevices: { [unit: string]: HistoryStates } = {};
const timelineDevices: TimelineEntity[] = []; const timelineDevices: TimelineEntity[] = [];
@ -473,7 +474,7 @@ export const computeHistory = (
currentState?.attributes || numericStateFromHistory?.a currentState?.attributes || numericStateFromHistory?.a
)?.device_class; )?.device_class;
const key = computeGroupKey(unit, deviceClass); const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
if (!unit) { if (!unit) {
timelineDevices.push( timelineDevices.push(
@ -487,9 +488,13 @@ export const computeHistory = (
currentState currentState
) )
); );
} else if (key in lineChartDevices && entityId in lineChartDevices[key]) { } else if (
key &&
key in lineChartDevices &&
entityId in lineChartDevices[key]
) {
lineChartDevices[key][entityId].push(...stateInfo); lineChartDevices[key][entityId].push(...stateInfo);
} else { } else if (key) {
if (!(key in lineChartDevices)) { if (!(key in lineChartDevices)) {
lineChartDevices[key] = {}; lineChartDevices[key] = {};
} }
@ -514,5 +519,6 @@ export const computeHistory = (
export const computeGroupKey = ( export const computeGroupKey = (
unit: string | undefined, unit: string | undefined,
device_class: string | undefined device_class: string | undefined,
) => `${unit}_${device_class || ""}`; splitDeviceClasses: boolean
) => (splitDeviceClasses ? `${unit}_${device_class || ""}` : unit);

View File

@ -218,7 +218,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
this._handleProgress(ev) this._handleProgress(ev)
); );
if (window.innerWidth > 450) { if (window.innerWidth > 450) {
import("./particles"); import("../resources/particles");
} }
makeDialogManager(this, this.shadowRoot!); makeDialogManager(this, this.shadowRoot!);
import("../components/ha-language-picker"); import("../components/ha-language-picker");

View File

@ -224,17 +224,19 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
const keys = new Set( const keys = new Set(
historyResult.line historyResult.line
.map((i) => computeGroupKey(i.unit, i.device_class)) .map((i) => computeGroupKey(i.unit, i.device_class, true))
.concat( .concat(
ltsResult.line.map((i) => computeGroupKey(i.unit, i.device_class)) ltsResult.line.map((i) =>
computeGroupKey(i.unit, i.device_class, true)
)
) )
); );
keys.forEach((key) => { keys.forEach((key) => {
const historyItem = historyResult.line.find( const historyItem = historyResult.line.find(
(i) => computeGroupKey(i.unit, i.device_class) === key (i) => computeGroupKey(i.unit, i.device_class, true) === key
); );
const ltsItem = ltsResult.line.find( const ltsItem = ltsResult.line.find(
(i) => computeGroupKey(i.unit, i.device_class) === key (i) => computeGroupKey(i.unit, i.device_class, true) === key
); );
if (historyItem && ltsItem) { if (historyItem && ltsItem) {
const newLineItem: LineChartUnit = { ...historyItem, data: [] }; const newLineItem: LineChartUnit = { ...historyItem, data: [] };
@ -410,7 +412,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
this.hass, this.hass,
statsHistoryStates, statsHistoryStates,
this.hass.localize, this.hass.localize,
sensorNumericDeviceClasses sensorNumericDeviceClasses,
true
); );
// remap states array to statistics array // remap states array to statistics array
(this._statisticsHistory?.line || []).forEach((item) => { (this._statisticsHistory?.line || []).forEach((item) => {
@ -460,7 +463,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
this.hass, this.hass,
history, history,
this.hass.localize, this.hass.localize,
sensorNumericDeviceClasses sensorNumericDeviceClasses,
true
); );
}, },
this._startDate, this._startDate,

View File

@ -118,7 +118,8 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
this.hass!, this.hass!,
combinedHistory, combinedHistory,
this.hass!.localize, this.hass!.localize,
sensorNumericDeviceClasses sensorNumericDeviceClasses,
this._config?.split_device_classes
); );
}, },
this._hoursToShow, this._hoursToShow,

View File

@ -323,6 +323,7 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig {
title?: string; title?: string;
show_names?: boolean; show_names?: boolean;
logarithmic_scale?: boolean; logarithmic_scale?: boolean;
split_device_classes?: boolean;
} }
export interface StatisticsGraphCardConfig extends LovelaceCardConfig { export interface StatisticsGraphCardConfig extends LovelaceCardConfig {

View File

@ -580,7 +580,7 @@ class HUIRoot extends LitElement {
const searchParams = extractSearchParamsObject(); const searchParams = extractSearchParamsObject();
if (searchParams.edit === "1") { if (searchParams.edit === "1") {
this._clearParam("edit"); this._clearParam("edit");
if (this.hass!.user?.is_admin) { if (this.hass!.user?.is_admin && this.lovelace!.mode === "storage") {
this.lovelace!.setEditMode(true); this.lovelace!.setEditMode(true);
} }
} else if (searchParams.conversation === "1") { } else if (searchParams.conversation === "1") {

View File

@ -1,6 +1,6 @@
import { tsParticles } from "tsparticles-engine"; import { tsParticles } from "tsparticles-engine";
import { loadLinksPreset } from "tsparticles-preset-links"; import { loadLinksPreset } from "tsparticles-preset-links";
import { DEFAULT_PRIMARY_COLOR } from "../resources/styles-data"; import { DEFAULT_PRIMARY_COLOR } from "./styles-data";
loadLinksPreset(tsParticles).then(() => { loadLinksPreset(tsParticles).then(() => {
tsParticles.load("particles", { tsParticles.load("particles", {

539
yarn.lock

File diff suppressed because it is too large Load Diff