mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20231204.0 (#18882)
This commit is contained in:
commit
ae2e8e7402
32
package.json
32
package.json
@ -25,10 +25,10 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.23.4",
|
||||
"@babel/runtime": "7.23.5",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@codemirror/autocomplete": "6.11.1",
|
||||
"@codemirror/commands": "6.3.1",
|
||||
"@codemirror/commands": "6.3.2",
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.5",
|
||||
@ -43,12 +43,12 @@
|
||||
"@formatjs/intl-numberformat": "8.9.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.10",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.10",
|
||||
"@fullcalendar/core": "6.1.9",
|
||||
"@fullcalendar/daygrid": "6.1.9",
|
||||
"@fullcalendar/interaction": "6.1.9",
|
||||
"@fullcalendar/list": "6.1.9",
|
||||
"@fullcalendar/luxon3": "6.1.9",
|
||||
"@fullcalendar/timegrid": "6.1.9",
|
||||
"@fullcalendar/core": "6.1.10",
|
||||
"@fullcalendar/daygrid": "6.1.10",
|
||||
"@fullcalendar/interaction": "6.1.10",
|
||||
"@fullcalendar/list": "6.1.10",
|
||||
"@fullcalendar/luxon3": "6.1.10",
|
||||
"@fullcalendar/timegrid": "6.1.10",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.6",
|
||||
@ -130,7 +130,7 @@
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.0",
|
||||
"sortablejs": "1.15.1",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "1.0.3",
|
||||
"tinykeys": "2.1.0",
|
||||
@ -152,11 +152,11 @@
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.3",
|
||||
"@babel/core": "7.23.5",
|
||||
"@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/preset-env": "7.23.3",
|
||||
"@babel/preset-env": "7.23.5",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.8.3",
|
||||
"@koa/cors": "4.0.0",
|
||||
@ -178,7 +178,7 @@
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/leaflet": "1.9.8",
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/luxon": "3.3.5",
|
||||
"@types/luxon": "3.3.6",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/serve-handler": "6.1.4",
|
||||
@ -186,8 +186,8 @@
|
||||
"@types/tar": "6.1.10",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "6.12.0",
|
||||
"@typescript-eslint/parser": "6.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.13.1",
|
||||
"@typescript-eslint/parser": "6.13.1",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
@ -255,7 +255,7 @@
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.2",
|
||||
"clean-css": "5.3.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",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20231130.0"
|
||||
version = "20231204.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -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
|
||||
// we will register the service worker to start preloading.
|
||||
if (url.host === location.host) {
|
||||
|
@ -154,6 +154,10 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
ha-button {
|
||||
--mdc-typography-button-text-transform: none;
|
||||
}
|
||||
.forgot-password-container {
|
||||
text-align: right;
|
||||
padding: 8px 0 16px 0;
|
||||
}
|
||||
a.forgot-password {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
@ -177,7 +181,7 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
</style>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._step
|
||||
? html`<ha-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
@ -188,72 +192,76 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
.localize=${this.localize}
|
||||
></ha-auth-flow>`
|
||||
: this._selectedUser
|
||||
? html`<div class="login-form"><div class="person">
|
||||
<ha-person-badge
|
||||
.person=${this._persons[this._selectedUser]}
|
||||
></ha-person-badge>
|
||||
<p>${this._persons[this._selectedUser].name}</p>
|
||||
</div>
|
||||
<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>
|
||||
? html`<div class="login-form">
|
||||
<div class="person">
|
||||
<ha-person-badge
|
||||
.person=${this._persons[this._selectedUser]}
|
||||
></ha-person-badge>
|
||||
<p>${this._persons[this._selectedUser].name}</p>
|
||||
</div>
|
||||
<div class="action">
|
||||
<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
|
||||
<form>
|
||||
<input
|
||||
type="hidden"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
readonly
|
||||
.value=${this.authProvider.users[this._selectedUser]}
|
||||
/>
|
||||
<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>
|
||||
</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>
|
||||
${this.localize("ui.panel.page-authorize.welcome_home")}
|
||||
</h1>
|
||||
|
@ -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 = (
|
||||
hass: HomeAssistant,
|
||||
options: { prefs?: EnergyPreferences; key?: string } = {}
|
||||
@ -609,28 +631,7 @@ export const getEnergyDataCollection = (
|
||||
collection.prefs = await getEnergyPreferences(hass);
|
||||
}
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
scheduleHourlyRefresh(collection);
|
||||
|
||||
return getEnergyData(
|
||||
hass,
|
||||
@ -647,6 +648,11 @@ export const getEnergyDataCollection = (
|
||||
collection.subscribe = (subscriber: (data: EnergyData) => void) => {
|
||||
const unsub = origSubscribe(subscriber);
|
||||
collection._active++;
|
||||
|
||||
if (collection._refreshTimeout === undefined) {
|
||||
scheduleHourlyRefresh(collection);
|
||||
}
|
||||
|
||||
return () => {
|
||||
collection._active--;
|
||||
if (collection._active < 1) {
|
||||
|
@ -420,7 +420,8 @@ export const computeHistory = (
|
||||
hass: HomeAssistant,
|
||||
stateHistory: HistoryStates,
|
||||
localize: LocalizeFunc,
|
||||
sensorNumericalDeviceClasses: string[]
|
||||
sensorNumericalDeviceClasses: string[],
|
||||
splitDeviceClasses = false
|
||||
): HistoryResult => {
|
||||
const lineChartDevices: { [unit: string]: HistoryStates } = {};
|
||||
const timelineDevices: TimelineEntity[] = [];
|
||||
@ -473,7 +474,7 @@ export const computeHistory = (
|
||||
currentState?.attributes || numericStateFromHistory?.a
|
||||
)?.device_class;
|
||||
|
||||
const key = computeGroupKey(unit, deviceClass);
|
||||
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
|
||||
|
||||
if (!unit) {
|
||||
timelineDevices.push(
|
||||
@ -487,9 +488,13 @@ export const computeHistory = (
|
||||
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);
|
||||
} else {
|
||||
} else if (key) {
|
||||
if (!(key in lineChartDevices)) {
|
||||
lineChartDevices[key] = {};
|
||||
}
|
||||
@ -514,5 +519,6 @@ export const computeHistory = (
|
||||
|
||||
export const computeGroupKey = (
|
||||
unit: string | undefined,
|
||||
device_class: string | undefined
|
||||
) => `${unit}_${device_class || ""}`;
|
||||
device_class: string | undefined,
|
||||
splitDeviceClasses: boolean
|
||||
) => (splitDeviceClasses ? `${unit}_${device_class || ""}` : unit);
|
||||
|
@ -218,7 +218,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
this._handleProgress(ev)
|
||||
);
|
||||
if (window.innerWidth > 450) {
|
||||
import("./particles");
|
||||
import("../resources/particles");
|
||||
}
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
import("../components/ha-language-picker");
|
||||
|
@ -224,17 +224,19 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
|
||||
const keys = new Set(
|
||||
historyResult.line
|
||||
.map((i) => computeGroupKey(i.unit, i.device_class))
|
||||
.map((i) => computeGroupKey(i.unit, i.device_class, true))
|
||||
.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) => {
|
||||
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(
|
||||
(i) => computeGroupKey(i.unit, i.device_class) === key
|
||||
(i) => computeGroupKey(i.unit, i.device_class, true) === key
|
||||
);
|
||||
if (historyItem && ltsItem) {
|
||||
const newLineItem: LineChartUnit = { ...historyItem, data: [] };
|
||||
@ -410,7 +412,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
this.hass,
|
||||
statsHistoryStates,
|
||||
this.hass.localize,
|
||||
sensorNumericDeviceClasses
|
||||
sensorNumericDeviceClasses,
|
||||
true
|
||||
);
|
||||
// remap states array to statistics array
|
||||
(this._statisticsHistory?.line || []).forEach((item) => {
|
||||
@ -460,7 +463,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
this.hass,
|
||||
history,
|
||||
this.hass.localize,
|
||||
sensorNumericDeviceClasses
|
||||
sensorNumericDeviceClasses,
|
||||
true
|
||||
);
|
||||
},
|
||||
this._startDate,
|
||||
|
@ -118,7 +118,8 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
this.hass!,
|
||||
combinedHistory,
|
||||
this.hass!.localize,
|
||||
sensorNumericDeviceClasses
|
||||
sensorNumericDeviceClasses,
|
||||
this._config?.split_device_classes
|
||||
);
|
||||
},
|
||||
this._hoursToShow,
|
||||
|
@ -323,6 +323,7 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
show_names?: boolean;
|
||||
logarithmic_scale?: boolean;
|
||||
split_device_classes?: boolean;
|
||||
}
|
||||
|
||||
export interface StatisticsGraphCardConfig extends LovelaceCardConfig {
|
||||
|
@ -580,7 +580,7 @@ class HUIRoot extends LitElement {
|
||||
const searchParams = extractSearchParamsObject();
|
||||
if (searchParams.edit === "1") {
|
||||
this._clearParam("edit");
|
||||
if (this.hass!.user?.is_admin) {
|
||||
if (this.hass!.user?.is_admin && this.lovelace!.mode === "storage") {
|
||||
this.lovelace!.setEditMode(true);
|
||||
}
|
||||
} else if (searchParams.conversation === "1") {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { tsParticles } from "tsparticles-engine";
|
||||
import { loadLinksPreset } from "tsparticles-preset-links";
|
||||
import { DEFAULT_PRIMARY_COLOR } from "../resources/styles-data";
|
||||
import { DEFAULT_PRIMARY_COLOR } from "./styles-data";
|
||||
|
||||
loadLinksPreset(tsParticles).then(() => {
|
||||
tsParticles.load("particles", {
|
Loading…
x
Reference in New Issue
Block a user