Compare commits

..

33 Commits

Author SHA1 Message Date
J. Nick Koston
3d4b2de906 Enable compression for development 2023-02-27 16:10:45 -06:00
Marius
5eafecf95d add domain icons for fan (#15613) 2023-02-27 16:30:14 +00:00
Bram Kragten
7f644530e4 Add delete and add thread dataset (#15619) 2023-02-27 17:27:58 +01:00
wizmo2
3aea416175 Add --ha-label-badge-border-radius (#15597)
allows button shape to be customized in themes.
2023-02-27 16:48:38 +01:00
Paul Bottein
868c414759 Show all hardware button for supervised installs (#15617) 2023-02-27 15:03:34 +00:00
Paul Bottein
f4848964c3 Restart dialog improvements (#15611) 2023-02-27 12:00:02 +01:00
dependabot[bot]
b10c95e803 Bump weekstart from 1.1.0 to 2.0.0 (#15608)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 10:38:15 +01:00
J. Nick Koston
25cf879793 Fix duplicate fetch of stats metadata in more info (#15590) 2023-02-27 10:27:26 +01:00
Steve Repsher
ba3b265b9a Fix invalid style in password manager polyfill and ha-bar (#15603) 2023-02-27 10:22:55 +01:00
Steve Repsher
7568ae5964 Fix unsafe optional chaining (#15604) 2023-02-27 10:11:44 +01:00
Paul Bottein
d4ace99de3 Fix display precision label when unknown or unavailable (#15610) 2023-02-27 10:10:35 +01:00
dependabot[bot]
087bda22d4 Bump systemjs from 6.13.0 to 6.14.0 (#15606) 2023-02-27 00:59:24 -05:00
dependabot[bot]
83ffc754c9 Bump vis-network from 9.1.2 to 9.1.4 (#15609) 2023-02-27 00:56:22 -05:00
dependabot[bot]
7a8ea4a9f9 Bump eslint from 8.34.0 to 8.35.0 (#15607) 2023-02-27 00:55:11 -05:00
Bram Kragten
650f32ba6d Bumped version to 20230224.0 2023-02-24 21:05:47 +01:00
Bram Kragten
01ec9aaf96 Get all entity reg categories (#15581) 2023-02-24 14:33:31 +01:00
Bram Kragten
682f383c9e Fix entity settings for entity with no entity reg (#15577) 2023-02-24 09:29:20 +01:00
Paul Bottein
7724fa02d7 Fix all hardware button (#15579) 2023-02-24 09:27:53 +01:00
dependabot[bot]
0a49bf2d4c Bump @types/glob from 8.0.1 to 8.1.0 (#15558)
Bumps [@types/glob](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/glob) from 8.0.1 to 8.1.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/glob)

---
updated-dependencies:
- dependency-name: "@types/glob"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-23 20:15:11 +01:00
Bram Kragten
12478a2177 Fix dialog stopping scrolling (#15574) 2023-02-23 17:18:36 +00:00
Paul Bottein
96a261d831 Add swipe gesture to control switch (#15567)
* Add swipe gesture to control switch

* Update src/components/ha-control-switch.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Only add necessary listener

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-02-23 17:13:03 +00:00
Bram Kragten
1095088d42 Bumped version to 20230223.0 2023-02-23 17:42:15 +01:00
Bram Kragten
43541f9754 Fix area picker (#15566) 2023-02-23 16:35:25 +01:00
Bram Kragten
f69ae84cc6 Use entity registry display api (#15549) 2023-02-23 16:30:13 +01:00
Paul Bottein
7173b30716 Add margin between logbook and history (#15571) 2023-02-23 15:23:24 +00:00
Paul Bottein
f92deb3225 Add haptic support to light toggle button (#15569) 2023-02-23 16:20:22 +01:00
dependabot[bot]
2e86d739fc Bump @material/web from 1.0.0-pre.2 to 1.0.0-pre.3 (#15556)
Bumps [@material/web](https://github.com/material-components/material-web) from 1.0.0-pre.2 to 1.0.0-pre.3.
- [Release notes](https://github.com/material-components/material-web/releases)
- [Changelog](https://github.com/material-components/material-web/blob/master/CHANGELOG.md)
- [Commits](https://github.com/material-components/material-web/compare/v1.0.0-pre.2...v1.0.0-pre.3)

---
updated-dependencies:
- dependency-name: "@material/web"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-23 15:49:16 +01:00
Bram Kragten
7e0c80ae24 Use correct number format options in hui-gauge (#15565) 2023-02-23 15:41:27 +01:00
Paul Bottein
3b5fe7fd9f Center new more info controls (#15564)
* Center new more info controls

* Center change color view
2023-02-23 15:10:05 +01:00
Steve Repsher
03e3f161f7 Prefer regex literals over constructors (#15553) 2023-02-23 14:06:35 +01:00
dependabot[bot]
ab231eec4f Bump magic-string from 0.29.0 to 0.30.0 (#15557)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-23 10:42:30 +01:00
Bram Kragten
7041d322d6 Allow energy dashboard to be cast (#15397) 2023-02-23 10:38:44 +01:00
Raman Gupta
f786539f15 Re-add target option to zwave-js firmware upload (#15517) 2023-02-23 10:29:30 +01:00
71 changed files with 1276 additions and 713 deletions

View File

@@ -59,8 +59,6 @@
"prefer-destructuring": "off",
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off",
"no-unsafe-optional-chaining": "warn",
"prefer-regex-literals": ["warn"],
"import/prefer-default-export": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",

View File

@@ -2,6 +2,7 @@ const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
const paths = require("./paths.js");
@@ -75,6 +76,7 @@ const createWebpackConfig = ({
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new CompressionWebpackPlugin(),
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
// Only include the JS of entrypoints

View File

@@ -252,6 +252,22 @@ export class HcMain extends HassElement {
msg.urlPath = null;
}
this._lovelacePath = msg.viewPath;
if (msg.urlPath === "energy") {
this._lovelaceConfig = {
views: [
{
strategy: {
type: "energy",
options: { show_date_selection: true },
},
},
],
};
this._urlPath = "energy";
this._lovelacePath = 0;
this._sendStatus();
return;
}
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath;
this._lovelaceConfig = undefined;

View File

@@ -66,7 +66,7 @@ const incrementalUnits = ["clients", "queries", "ads"];
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(
new RegExp("history/period/.+"),
/history\/period\/.+/,
(hass, _method, path, _parameters) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");

View File

@@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
<div class="action">
<span>
${this._action
? describeAction(this.hass, this._action)
? describeAction(this.hass, [], this._action)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
@@ -149,7 +149,7 @@ export class DemoAutomationDescribeAction extends LitElement {
${ACTIONS.map(
(conf) => html`
<div class="action">
<span>${describeAction(this.hass, conf as any)}</span>
<span>${describeAction(this.hass, [], conf as any)}</span>
<pre>${dump(conf)}</pre>
</div>
`

View File

@@ -71,7 +71,7 @@
"@material/mwc-textfield": "^0.27.0",
"@material/mwc-top-app-bar-fixed": "^0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "=1.0.0-pre.2",
"@material/web": "=1.0.0-pre.3",
"@mdi/js": "7.1.96",
"@mdi/svg": "7.1.96",
"@polymer/app-layout": "^3.1.0",
@@ -100,6 +100,7 @@
"app-datepicker": "^5.1.0",
"chart.js": "^3.3.2",
"comlink": "^4.4.1",
"compression-webpack-plugin": "^10.0.0",
"core-js": "^3.28.0",
"cropperjs": "^1.5.13",
"date-fns": "^2.29.3",
@@ -134,10 +135,10 @@
"tsparticles-preset-links": "^2.9.3",
"unfetch": "^5.0.0",
"vis-data": "^7.1.4",
"vis-network": "^9.1.2",
"vis-network": "^9.1.4",
"vue": "^2.7.14",
"vue2-daterange-picker": "^0.6.8",
"weekstart": "^1.1.0",
"weekstart": "^2.0.0",
"workbox-cacheable-response": "^6.5.4",
"workbox-core": "^6.5.4",
"workbox-expiration": "^6.5.4",
@@ -188,7 +189,7 @@
"babel-loader": "^9.1.2",
"chai": "^4.3.7",
"del": "^7.0.0",
"eslint": "^8.34.0",
"eslint": "^8.35.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.6.0",
@@ -216,7 +217,7 @@
"lint-staged": "^13.1.2",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.29.0",
"magic-string": "^0.30.0",
"map-stream": "^0.0.7",
"merge-stream": "^2.0.0",
"mocha": "^10.2.0",
@@ -232,7 +233,7 @@
"serve": "^11.3.2",
"sinon": "^15.0.1",
"source-map-url": "^0.4.1",
"systemjs": "^6.13.0",
"systemjs": "^6.14.0",
"tar": "^6.1.13",
"terser-webpack-plugin": "^5.3.6",
"ts-lit-plugin": "^1.2.1",

View File

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

View File

@@ -1,6 +1,6 @@
/* eslint-disable lit/prefer-static-styles */
import { html, LitElement, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import type { HaFormSchema } from "../components/ha-form/types";
import { autocompleteLoginFields } from "../data/auth";
@@ -29,35 +29,43 @@ export class HaPasswordManagerPolyfill extends LitElement {
@property({ attribute: false }) public boundingRect?: DOMRect;
private _styleElement?: HTMLStyleElement;
public connectedCallback() {
super.connectedCallback();
this._styleElement = document.createElement("style");
this._styleElement.textContent = css`
.password-manager-polyfill {
position: absolute;
opacity: 0;
z-index: -1;
}
.password-manager-polyfill input {
width: 100%;
height: 62px;
padding: 0;
border: 0;
}
.password-manager-polyfill input[type="submit"] {
width: 0;
height: 0;
}
`.toString();
document.head.append(this._styleElement);
}
public disconnectedCallback() {
super.disconnectedCallback();
this._styleElement?.remove();
delete this._styleElement;
}
protected createRenderRoot() {
// Add under document body so the element isn't placed inside any shadow roots
return document.body;
}
private get styles() {
return `
.password-manager-polyfill {
position: absolute;
top: ${this.boundingRect?.y || 148}px;
left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px);
width: ${this.boundingRect?.width || 360}px;
opacity: 0;
z-index: -1;
}
.password-manager-polyfill input {
width: 100%;
height: 62px;
padding: 0;
border: 0;
}
.password-manager-polyfill input[type="submit"] {
width: 0;
height: 0;
}
`;
}
protected render(): TemplateResult {
protected render() {
if (
this.step &&
this.step.type === "form" &&
@@ -67,6 +75,11 @@ export class HaPasswordManagerPolyfill extends LitElement {
return html`
<form
class="password-manager-polyfill"
style=${styleMap({
top: `${this.boundingRect?.y || 148}px`,
left: `calc(50% - ${(this.boundingRect?.width || 360) / 2}px)`,
width: `${this.boundingRect?.width || 360}px`,
})}
aria-hidden="true"
@submit=${this._handleSubmit}
>
@@ -74,16 +87,13 @@ export class HaPasswordManagerPolyfill extends LitElement {
this.render_input(input)
)}
<input type="submit" />
<style>
${this.styles}
</style>
</form>
`;
}
return html``;
return nothing;
}
private render_input(schema: HaFormSchema): TemplateResult | string {
private render_input(schema: HaFormSchema) {
const inputType = schema.name.includes("password") ? "password" : "text";
if (schema.type !== "string") {
return "";

View File

@@ -24,7 +24,6 @@ import {
mdiDatabase,
mdiEarHearing,
mdiEye,
mdiFan,
mdiFlash,
mdiFlower,
mdiFormatListBulleted,
@@ -91,7 +90,6 @@ export const FIXED_DOMAIN_ICONS = {
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
demo: mdiHomeAssistant,
fan: mdiFan,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
homeassistant: mdiHomeAssistant,

View File

@@ -1,5 +1,5 @@
import { HassEntity } from "home-assistant-js-websocket";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { HomeAssistant } from "../../types";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
@@ -15,7 +15,7 @@ export const computeAttributeValueDisplay = (
const attributeValue =
value !== undefined ? value : stateObj.attributes[attribute];
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
const translationKey = entity?.translation_key;
return (
@@ -38,7 +38,7 @@ export const computeAttributeNameDisplay = (
): string => {
const entityId = stateObj.entity_id;
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
const translationKey = entity?.translation_key;
return (

View File

@@ -1,6 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { FrontendLocaleData } from "../../data/translation";
import {
updateIsInstallingFromAttributes,
@@ -49,7 +49,7 @@ export const computeStateDisplayFromEntityAttributes = (
return localize(`state.default.${state}`);
}
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
if (isNumericFromAttributes(attributes)) {

View File

@@ -15,6 +15,8 @@ import {
mdiCheckCircleOutline,
mdiClock,
mdiCloseCircleOutline,
mdiFan,
mdiFanOff,
mdiGestureTapButton,
mdiLanConnect,
mdiLanDisconnect,
@@ -108,6 +110,9 @@ export const domainIconWithoutDefault = (
}
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "fan":
return compareState === "off" ? mdiFanOff : mdiFan;
case "humidifier":
return compareState === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;

View File

@@ -2,7 +2,7 @@ import {
HassEntity,
HassEntityAttributeBase,
} from "home-assistant-js-websocket";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
import { round } from "./round";
@@ -92,11 +92,9 @@ export const formatNumber = (
*/
export const getNumberFormatOptions = (
entityState: HassEntity,
entity?: EntityRegistryEntry
entity?: EntityRegistryDisplayEntry
): Intl.NumberFormatOptions | undefined => {
const precision =
entity?.options?.sensor?.display_precision ??
entity?.options?.sensor?.suggested_display_precision;
const precision = entity?.display_precision;
if (precision != null) {
return {
maximumFractionDigits: precision,

View File

@@ -1,4 +1,4 @@
const isTemplateRegex = new RegExp("{%|{{");
const isTemplateRegex = /{%|{{/;
export const isTemplate = (value: string): boolean =>
isTemplateRegex.test(value);

View File

@@ -332,7 +332,10 @@ class StatisticsChart extends LitElement {
prevEndTime = end;
};
const color = getGraphColorByIndex(colorIndex, this._computedStyle!);
const color = getGraphColorByIndex(
colorIndex,
this._computedStyle || getComputedStyle(this)
);
colorIndex++;
const statTypes: this["statTypes"] = [];

View File

@@ -22,7 +22,7 @@ import {
isNumericState,
} from "../../common/number/format_number";
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { timerTimeRemaining } from "../../data/timer";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
@@ -160,7 +160,7 @@ export class HaStateLabelBadge extends LitElement {
private _computeValue(
domain: string,
entityState: HassEntity,
entry?: EntityRegistryEntry
entry?: EntityRegistryDisplayEntry
) {
switch (domain) {
case "alarm_control_panel":
@@ -200,7 +200,7 @@ export class HaStateLabelBadge extends LitElement {
private _computeShowIcon(
domain: string,
entityState: HassEntity,
entry?: EntityRegistryEntry
entry?: EntityRegistryDisplayEntry
): boolean {
if (entityState.state === UNAVAILABLE) {
return false;

View File

@@ -12,10 +12,11 @@ import {
createAreaRegistryEntry,
} from "../data/area_registry";
import {
DeviceEntityLookup,
DeviceEntityDisplayLookup,
DeviceRegistryEntry,
getDeviceEntityDisplayLookup,
} from "../data/device_registry";
import { EntityRegistryEntry } from "../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
import {
showAlertDialog,
showPromptDialog,
@@ -113,7 +114,7 @@ export class HaAreaPicker extends LitElement {
(
areas: AreaRegistryEntry[],
devices: DeviceRegistryEntry[],
entities: EntityRegistryEntry[],
entities: EntityRegistryDisplayEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"],
@@ -133,111 +134,107 @@ export class HaAreaPicker extends LitElement {
];
}
const deviceEntityLookup: DeviceEntityLookup = {};
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
let inputDevices: DeviceRegistryEntry[] | undefined;
let inputEntities: EntityRegistryEntry[] | undefined;
let inputEntities: EntityRegistryDisplayEntry[] | undefined;
if (
includeDomains ||
excludeDomains ||
includeDeviceClasses ||
deviceFilter ||
entityFilter
) {
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
if (includeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
if (includeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
}
}
if (excludeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
if (excludeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter(
(entity) => !excludeDomains.includes(computeDomain(entity.entity_id))
);
}
}
if (includeDeviceClasses) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
if (includeDeviceClasses) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
}
if (deviceFilter) {
inputDevices = inputDevices!.filter((device) =>
deviceFilter!(device)
);
});
}
}
if (deviceFilter) {
inputDevices = inputDevices!.filter((device) => deviceFilter!(device));
}
if (entityFilter) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
if (entityFilter) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter(stateObj);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter(stateObj);
return entityFilter!(stateObj);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter!(stateObj);
});
}
}
let outputAreas = areas;

View File

@@ -44,7 +44,6 @@ export class HaBar extends LitElement {
}
rect:last-child {
fill: var(--ha-bar-primary-color, var(--primary-color));
rx: var(--ha-bar-border-radius, 4px);
}
svg {
border-radius: var(--ha-bar-border-radius, 4px);

View File

@@ -1,3 +1,10 @@
import {
DIRECTION_HORIZONTAL,
DIRECTION_VERTICAL,
Manager,
Swipe,
Tap,
} from "@egjs/hammerjs";
import {
css,
CSSResultGroup,
@@ -6,7 +13,7 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-svg-icon";
@@ -30,8 +37,11 @@ export class HaControlSwitch extends LitElement {
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
@property({ type: String }) pathOff?: string;
private _mc?: HammerManager;
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this.setupListeners();
this.setAttribute("role", "switch");
if (!this.hasAttribute("tabindex")) {
this.setAttribute("tabindex", "0");
@@ -53,14 +63,70 @@ export class HaControlSwitch extends LitElement {
connectedCallback(): void {
super.connectedCallback();
this.addEventListener("keydown", this._keydown);
this.addEventListener("click", this._toggle);
this.setupListeners();
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.destroyListeners();
}
@query("#switch")
private switch!: HTMLDivElement;
setupListeners() {
if (this.switch && !this._mc) {
this._mc = new Manager(this.switch, {
touchAction: this.vertical ? "pan-x" : "pan-y",
});
this._mc.add(
new Swipe({
direction: this.vertical ? DIRECTION_VERTICAL : DIRECTION_HORIZONTAL,
})
);
this._mc.add(new Tap({ event: "singletap" }));
if (this.vertical) {
this._mc.on("swipeup", () => {
if (this.disabled) return;
this.checked = !!this.reversed;
fireEvent(this, "change");
});
this._mc.on("swipedown", () => {
if (this.disabled) return;
this.checked = !this.reversed;
fireEvent(this, "change");
});
} else {
this._mc.on("swiperight", () => {
if (this.disabled) return;
this.checked = !this.reversed;
fireEvent(this, "change");
});
this._mc.on("swipeleft", () => {
if (this.disabled) return;
this.checked = !!this.reversed;
fireEvent(this, "change");
});
}
this._mc.on("singletap", () => {
if (this.disabled) return;
this._toggle();
});
this.addEventListener("keydown", this._keydown);
}
}
destroyListeners() {
if (this._mc) {
this._mc.destroy();
this._mc = undefined;
}
this.removeEventListener("keydown", this._keydown);
this.removeEventListener("click", this._toggle);
}
private _keydown(ev: any) {
@@ -73,7 +139,7 @@ export class HaControlSwitch extends LitElement {
protected render(): TemplateResult {
return html`
<div class="switch">
<div id="switch" class="switch">
<div class="background"></div>
<div class="button" aria-hidden="true">
${this.checked

View File

@@ -45,6 +45,7 @@ export class HaDialog extends DialogBase {
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.contentElement.removeEventListener("scroll", this._onScroll);
}

View File

@@ -26,6 +26,9 @@ export class Gauge extends LitElement {
@property({ type: Number }) public value = 0;
@property({ attribute: false })
public formatOptions?: Intl.NumberFormatOptions;
@property({ type: String }) public valueText?: string;
@property() public locale!: FrontendLocaleData;
@@ -132,7 +135,8 @@ export class Gauge extends LitElement {
${
this._segment_label
? this._segment_label
: this.valueText || formatNumber(this.value, this.locale)
: this.valueText ||
formatNumber(this.value, this.locale, this.formatOptions)
}${
this._segment_label
? ""

View File

@@ -62,7 +62,7 @@ class HaLabelBadge extends LitElement {
height: var(--ha-label-badge-size, 2.5em);
line-height: var(--ha-label-badge-size, 2.5em);
font-size: var(--ha-label-badge-font-size, 1.5em);
border-radius: 50%;
border-radius: var(--ha-label-badge-border-radius, 50%);
border: 0.1em solid var(--ha-label-badge-color, var(--primary-color));
color: var(--label-badge-text-color, rgb(76, 76, 76));

View File

@@ -30,6 +30,30 @@ export class HaListItem extends ListItemBase {
margin-inline-end: 0px !important;
direction: var(--direction);
}
:host([multiline-secondary]) {
height: auto;
}
:host([multiline-secondary]) .mdc-deprecated-list-item__text {
padding: 8px 0;
}
:host([multiline-secondary]) .mdc-deprecated-list-item__secondary-text {
text-overflow: initial;
white-space: normal;
overflow: auto;
display: inline-block;
margin-top: 10px;
}
:host([multiline-secondary]) .mdc-deprecated-list-item__primary-text {
margin-top: 10px;
}
:host([multiline-secondary])
.mdc-deprecated-list-item__secondary-text::before {
display: none;
}
:host([multiline-secondary])
.mdc-deprecated-list-item__primary-text::before {
display: none;
}
`,
];
}

View File

@@ -1,14 +1,10 @@
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
@@ -18,13 +14,12 @@ import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../ha-area-picker";
import "../ha-areas-picker";
@customElement("ha-selector-area")
export class HaAreaSelector extends SubscribeMixin(LitElement) {
export class HaAreaSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: AreaSelector;
@@ -41,18 +36,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
private _hasIntegration(selector: AreaSelector) {
return (
(selector.area?.entity &&
@@ -127,10 +112,12 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true;
}
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
)
: undefined;
return ensureArray(this.selector.area.device).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)

View File

@@ -2,12 +2,11 @@ import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { AttributeSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../entity/ha-entity-attribute-picker";
@customElement("ha-selector-attribute")
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
export class HaSelectorAttribute extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: AttributeSelector;

View File

@@ -1,14 +1,10 @@
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
@@ -18,21 +14,18 @@ import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker";
import "../device/ha-devices-picker";
@customElement("ha-selector-device")
export class HaDeviceSelector extends SubscribeMixin(LitElement) {
export class HaDeviceSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: DeviceSelector;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property() public value?: any;
@property() public label?: string;
@@ -45,14 +38,6 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
private _hasIntegration(selector: DeviceSelector) {
return (
(selector.device?.filter &&
@@ -118,10 +103,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
if (!this.selector.device?.filter) {
return true;
}
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
)
: undefined;
return ensureArray(this.selector.device.filter).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)

View File

@@ -1,6 +1,7 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import "@material/mwc-button/mwc-button";
import "@material/mwc-menu/mwc-menu-surface";
import {
mdiClose,
mdiDevices,
@@ -9,13 +10,14 @@ import {
mdiUnfoldMoreVertical,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import { ensureArray } from "../common/array/ensure-array";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
@@ -23,7 +25,7 @@ import {
computeDeviceName,
DeviceRegistryEntry,
} from "../data/device_registry";
import { EntityRegistryEntry } from "../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
import { HomeAssistant } from "../types";
import "./device/ha-device-picker";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
@@ -33,8 +35,6 @@ import "./ha-area-picker";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-svg-icon";
import { stopPropagation } from "../common/dom/stop_propagation";
import "@material/mwc-menu/mwc-menu-surface";
@customElement("ha-target-picker")
export class HaTargetPicker extends LitElement {
@@ -551,7 +551,7 @@ export class HaTargetPicker extends LitElement {
return true;
}
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean {
if (entity.entity_category) {
return false;
}

View File

@@ -6,6 +6,7 @@ import {
mdiProgressWrench,
mdiRecordCircleOutline,
} from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
@@ -14,12 +15,16 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import { relativeTime } from "../../common/datetime/relative_time";
import { fireEvent } from "../../common/dom/fire_event";
import { toggleAttribute } from "../../common/dom/toggle_attribute";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { LogbookEntry } from "../../data/logbook";
import {
ChooseAction,
@@ -193,6 +198,7 @@ class ActionRenderer {
constructor(
private hass: HomeAssistant,
private entityReg: EntityRegistryEntry[],
private entries: TemplateResult[],
private trace: AutomationTraceExtended,
private logbookRenderer: LogbookRenderer,
@@ -298,7 +304,7 @@ class ActionRenderer {
this._renderEntry(
path,
describeAction(this.hass, data, actionType),
describeAction(this.hass, this.entityReg, data, actionType),
undefined,
data.enabled === false
);
@@ -441,7 +447,9 @@ class ActionRenderer {
) as RepeatAction;
const disabled = repeatConfig.enabled === false;
const name = repeatConfig.alias || describeAction(this.hass, repeatConfig);
const name =
repeatConfig.alias ||
describeAction(this.hass, this.entityReg, repeatConfig);
this._renderEntry(repeatPath, name, undefined, disabled);
@@ -577,6 +585,16 @@ export class HaAutomationTracer extends LitElement {
@property({ type: Boolean }) public allowPick = false;
@state() private _entityReg: EntityRegistryEntry[] = [];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected render(): TemplateResult {
if (!this.trace) {
return html``;
@@ -592,6 +610,7 @@ export class HaAutomationTracer extends LitElement {
);
const actionRenderer = new ActionRenderer(
this.hass,
this._entityReg,
entries,
this.trace,
logbookRenderer,

View File

@@ -4,7 +4,10 @@ import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import type { HomeAssistant } from "../types";
import type { EntityRegistryEntry } from "./entity_registry";
import type {
EntityRegistryDisplayEntry,
EntityRegistryEntry,
} from "./entity_registry";
import type { EntitySources } from "./entity_sources";
export interface DeviceRegistryEntry {
@@ -25,6 +28,10 @@ export interface DeviceRegistryEntry {
configuration_url: string | null;
}
export interface DeviceEntityDisplayLookup {
[deviceId: string]: EntityRegistryDisplayEntry[];
}
export interface DeviceEntityLookup {
[deviceId: string]: EntityRegistryEntry[];
}
@@ -147,9 +154,25 @@ export const getDeviceEntityLookup = (
return deviceEntityLookup;
};
export const getDeviceEntityDisplayLookup = (
entities: EntityRegistryDisplayEntry[]
): DeviceEntityDisplayLookup => {
const deviceEntityLookup: DeviceEntityDisplayLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
return deviceEntityLookup;
};
export const getDeviceIntegrationLookup = (
entitySources: EntitySources,
entities: EntityRegistryEntry[]
entities: EntityRegistryDisplayEntry[]
): Record<string, string[]> => {
const deviceIntegrations: Record<string, string[]> = {};

View File

@@ -6,6 +6,35 @@ import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
type entityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry {
entity_id: string;
name?: string;
device_id?: string;
area_id?: string;
hidden?: boolean;
entity_category?: entityCategory;
translation_key?: string;
platform?: string;
display_precision?: number;
}
interface EntityRegistryDisplayEntryResponse {
entities: {
ei: string;
di?: string;
ai?: string;
ec?: number;
en?: string;
pl?: string;
tk?: string;
hb?: boolean;
dp?: number;
}[];
entity_categories: Record<number, entityCategory>;
}
export interface EntityRegistryEntry {
id: string;
entity_id: string;
@@ -17,7 +46,7 @@ export interface EntityRegistryEntry {
area_id: string | null;
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: "config" | "diagnostic" | null;
entity_category: entityCategory | null;
has_entity_name: boolean;
original_name?: string;
unique_id: string;
@@ -154,6 +183,11 @@ export const fetchEntityRegistry = (conn: Connection) =>
type: "config/entity_registry/list",
});
export const fetchEntityRegistryDisplay = (conn: Connection) =>
conn.sendMessagePromise<EntityRegistryDisplayEntryResponse>({
type: "config/entity_registry/list_for_display",
});
const subscribeEntityRegistryUpdates = (
conn: Connection,
store: Store<EntityRegistryEntry[]>
@@ -182,6 +216,34 @@ export const subscribeEntityRegistry = (
onChange
);
const subscribeEntityRegistryDisplayUpdates = (
conn: Connection,
store: Store<EntityRegistryDisplayEntryResponse>
) =>
conn.subscribeEvents(
debounce(
() =>
fetchEntityRegistryDisplay(conn).then((entities) =>
store.setState(entities, true)
),
500,
true
),
"entity_registry_updated"
);
export const subscribeEntityRegistryDisplay = (
conn: Connection,
onChange: (entities: EntityRegistryDisplayEntryResponse) => void
) =>
createCollection<EntityRegistryDisplayEntryResponse>(
"_entityRegistryDisplay",
fetchEntityRegistryDisplay,
subscribeEntityRegistryDisplayUpdates,
conn,
onChange
);
export const sortEntityRegistryByName = (
entries: EntityRegistryEntry[],
language: string
@@ -190,10 +252,20 @@ export const sortEntityRegistryByName = (
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
);
export const entityRegistryByEntityId = memoizeOne(
(entries: EntityRegistryEntry[]) => {
const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of entries) {
entities[entity.entity_id] = entity;
}
return entities;
}
);
export const entityRegistryById = memoizeOne(
(entries: HomeAssistant["entities"]) => {
const entities: HomeAssistant["entities"] = {};
for (const entity of Object.values(entries)) {
(entries: EntityRegistryEntry[]) => {
const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of entries) {
entities[entity.id] = entity;
}
return entities;

View File

@@ -11,6 +11,7 @@ import { computeDeviceName } from "./device_registry";
import {
computeEntityRegistryName,
entityRegistryById,
EntityRegistryEntry,
} from "./entity_registry";
import { domainToName } from "./integration";
import {
@@ -33,6 +34,7 @@ import {
export const describeAction = <T extends ActionType>(
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
action: ActionTypes[T],
actionType?: T,
ignoreAlias = false
@@ -91,7 +93,7 @@ export const describeAction = <T extends ActionType>(
targets.push(targetThing);
}
} else {
const entityReg = entityRegistryById(hass.entities)[targetThing];
const entityReg = entityRegistryById(entityRegistry)[targetThing];
if (entityReg) {
targets.push(
computeEntityRegistryName(hass, entityReg) || targetThing

View File

@@ -10,13 +10,13 @@ export interface ThreadRouter {
}
export interface ThreadDataSet {
created;
dataset_id;
extended_pan_id;
network_name: string;
pan_id;
created: string;
dataset_id: string;
preferred: boolean;
source;
source: string;
network_name: string;
extended_pan_id?: string;
pan_id?: string;
}
export interface ThreadRouterDiscoveryEvent {
@@ -26,12 +26,9 @@ export interface ThreadRouterDiscoveryEvent {
}
class DiscoveryStream {
hass: HomeAssistant;
routers: { [key: string]: ThreadRouter };
constructor(hass: HomeAssistant) {
this.hass = hass;
constructor() {
this.routers = {};
}
@@ -49,7 +46,7 @@ export const subscribeDiscoverThreadRouters = (
hass: HomeAssistant,
callbackFunction: (routers: ThreadRouter[]) => void
) => {
const stream = new DiscoveryStream(hass);
const stream = new DiscoveryStream();
return hass.connection.subscribeMessage<ThreadRouterDiscoveryEvent>(
(message) => callbackFunction(stream.processEvent(message)),
{
@@ -64,3 +61,29 @@ export const listThreadDataSets = (
hass.callWS({
type: "thread/list_datasets",
});
export const getThreadDataSetTLV = (
hass: HomeAssistant,
dataset_id: string
): Promise<{ tlv: string }> =>
hass.callWS({ type: "thread/get_dataset_tlv", dataset_id });
export const addThreadDataSet = (
hass: HomeAssistant,
source: string,
tlv: string
): Promise<void> =>
hass.callWS({
type: "thread/add_dataset_tlv",
source,
tlv,
});
export const removeThreadDataSet = (
hass: HomeAssistant,
dataset_id: string
): Promise<void> =>
hass.callWS({
type: "thread/delete_dataset",
dataset_id,
});

View File

@@ -757,10 +757,14 @@ export const fetchZwaveNodeFirmwareUpdateCapabilities = (
export const uploadFirmwareAndBeginUpdate = async (
hass: HomeAssistant,
device_id: string,
file: File
file: File,
target?: number
) => {
const fd = new FormData();
fd.append("file", file);
if (target !== undefined) {
fd.append("target", target.toString());
}
const resp = await hass.fetchWithAuth(
`/api/zwave_js/firmware/upload/${device_id}`,
{

View File

@@ -0,0 +1,24 @@
import { css } from "lit";
export const moreInfoControlStyle = css`
:host {
display: flex;
flex-direction: column;
flex: 1;
justify-content: space-between;
}
.controls {
display: flex;
flex-direction: column;
align-items: center;
}
.controls > *:not(:last-child) {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
`;

View File

@@ -23,7 +23,6 @@ import {
lightSupportsColor,
lightSupportsColorMode,
} from "../../../../data/light";
import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import { LightColorPickerViewParams } from "./show-view-light-color-picker";
@@ -78,117 +77,115 @@ class MoreInfoViewLightColorPicker extends LitElement {
lightSupportsColorMode(this.stateObj, LightColorMode.RGBW);
return html`
<div>
${this._modes.length > 1
${this._modes.length > 1
? html`
<mwc-tab-bar
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
@MDCTabBar:activated=${this._handleTabChanged}
>
${this._modes.map(
(value) =>
html`<mwc-tab
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
></mwc-tab>`
)}
</mwc-tab-bar>
`
: ""}
<div class="content">
${this._mode === LightColorMode.COLOR_TEMP
? html`
<mwc-tab-bar
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
@MDCTabBar:activated=${this._handleTabChanged}
<ha-control-slider
vertical
class="color_temp"
label=${this.hass.localize("ui.card.light.color_temperature")}
min="1"
max="100"
mode="cursor"
.value=${this._ctSliderValue}
@value-changed=${this._ctSliderChanged}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
>
${this._modes.map(
(value) =>
html`<mwc-tab
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
></mwc-tab>`
)}
</mwc-tab-bar>
</ha-control-slider>
`
: ""}
<div class="content">
${this._mode === LightColorMode.COLOR_TEMP
? html`
<ha-control-slider
vertical
class="color_temp"
label=${this.hass.localize("ui.card.light.color_temperature")}
min="1"
max="100"
mode="cursor"
.value=${this._ctSliderValue}
@value-changed=${this._ctSliderChanged}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
${this._mode === "color"
? html`
<div class="segmentationContainer">
<ha-color-picker
class="color"
@colorselected=${this._colorPicked}
.desiredRgbColor=${this._colorPickerColor}
throttle="500"
.hueSegments=${this._hueSegments}
.saturationSegments=${this._saturationSegments}
>
</ha-control-slider>
`
: ""}
${this._mode === "color"
? html`
<div class="segmentationContainer">
<ha-color-picker
class="color"
@colorselected=${this._colorPicked}
.desiredRgbColor=${this._colorPickerColor}
throttle="500"
.hueSegments=${this._hueSegments}
.saturationSegments=${this._saturationSegments}
>
</ha-color-picker>
<ha-icon-button
.path=${mdiPalette}
@click=${this._segmentClick}
class="segmentationButton"
></ha-icon-button>
</div>
</ha-color-picker>
<ha-icon-button
.path=${mdiPalette}
@click=${this._segmentClick}
class="segmentationButton"
></ha-icon-button>
</div>
${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider
${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.color_brightness"
)}
icon="hass:brightness-7"
max="100"
.value=${this._colorBrightnessSliderValue}
@change=${this._colorBrightnessSliderChanged}
pin
></ha-labeled-slider>`
: ""}
${supportsRgbw
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.color_brightness"
"ui.card.light.white_value"
)}
icon="hass:brightness-7"
icon="hass:file-word-box"
max="100"
.value=${this._colorBrightnessSliderValue}
@change=${this._colorBrightnessSliderChanged}
.name=${"wv"}
.value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>`
: ""}
${supportsRgbw
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"wv"}
.value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
${supportsRgbww
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.cold_white_value"
)}
icon="hass:file-word-box-outline"
max="100"
.name=${"cw"}
.value=${this._cwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.warm_white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"ww"}
.value=${this._wwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
`
: ""}
</div>
></ha-labeled-slider>
`
: ""}
${supportsRgbww
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.cold_white_value"
)}
icon="hass:file-word-box-outline"
max="100"
.name=${"cw"}
.value=${this._cwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.warm_white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"ww"}
.value=${this._wwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
`
: ""}
</div>
`;
}
@@ -482,13 +479,18 @@ class MoreInfoViewLightColorPicker extends LitElement {
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
:host {
display: flex;
flex-direction: column;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
justify-content: center;
padding: 24px;
flex: 1;
}
.segmentationContainer {

View File

@@ -94,12 +94,12 @@ export const computeShowLogBookComponent = (
return true;
};
export const computeShowNewMoreInfo = (stateObj: HassEntity) => {
export const computeShowNewMoreInfo = (stateObj: HassEntity): boolean => {
const domain = computeDomain(stateObj.entity_id);
if (domain === "group") {
const groupDomain = computeGroupDomain(stateObj as GroupEntity);
return (
groupDomain &&
groupDomain != null &&
groupDomain !== "group" &&
DOMAINS_WITH_NEW_MORE_INFO.includes(groupDomain)
);

View File

@@ -12,6 +12,7 @@ import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
import { computeGroupDomain, GroupEntity } from "../../../data/group";
import "../../../state-summary/state-card-content";
import { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import {
domainMoreInfoType,
importMoreInfoControl,
@@ -94,12 +95,15 @@ class MoreInfoGroup extends LitElement {
}
static get styles(): CSSResultGroup {
return css`
state-card-content {
display: block;
margin-top: 8px;
}
`;
return [
moreInfoControlStyle,
css`
state-card-content {
display: block;
margin-top: 8px;
}
`,
];
}
}

View File

@@ -23,6 +23,7 @@ import "../../../components/ha-attributes";
import "../../../components/ha-button-menu";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import {
LightColorMode,
LightEntity,
@@ -32,6 +33,7 @@ import {
lightSupportsColorMode,
} from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
import "../components/lights/ha-more-info-light-brightness";
@@ -85,12 +87,12 @@ class MoreInfoLight extends LitElement {
: undefined;
return html`
<div class="content">
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
.stateOverride=${stateOverride}
></ha-more-info-state-header>
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
.stateOverride=${stateOverride}
></ha-more-info-state-header>
<div class="controls">
${supportsBrightness
? html`
<ha-more-info-light-brightness
@@ -184,17 +186,19 @@ class MoreInfoLight extends LitElement {
</div>
`
: null}
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
extra-filters="brightness,color_temp,color_temp_kelvin,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,min_color_temp_kelvin,max_color_temp_kelvin,entity_id,supported_color_modes,color_mode"
></ha-attributes>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
extra-filters="brightness,color_temp,color_temp_kelvin,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,min_color_temp_kelvin,max_color_temp_kelvin,entity_id,supported_color_modes,color_mode"
></ha-attributes>
`;
}
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
this.hass.callService("light", service, {
entity_id: this.stateObj!.entity_id,
});
@@ -230,41 +234,29 @@ class MoreInfoLight extends LitElement {
}
static get styles(): CSSResultGroup {
return css`
.content {
display: flex;
flex-direction: column;
align-items: center;
}
return [
moreInfoControlStyle,
css`
.buttons {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.buttons > * {
margin: 4px;
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.buttons > * {
margin: 4px;
}
ha-more-info-light-brightness,
ha-more-info-toggle {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
md-outlined-icon-button-toggle,
md-outlined-icon-button {
--ha-icon-display: block;
--md-sys-color-on-surface: var(--secondary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
--md-sys-color-outline: var(--secondary-text-color);
}
`;
md-outlined-icon-button-toggle,
md-outlined-icon-button {
--ha-icon-display: block;
--md-sys-color-on-surface: var(--secondary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
--md-sys-color-outline: var(--secondary-text-color);
}
`,
];
}
}

View File

@@ -1,9 +1,10 @@
import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import { LightEntity } from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
@@ -19,41 +20,27 @@ class MoreInfoSiren extends LitElement {
}
return html`
<div class="content">
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<div class="controls">
<ha-more-info-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiVolumeHigh}
.iconPathOff=${mdiVolumeOff}
></ha-more-info-toggle>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
static get styles(): CSSResultGroup {
return css`
.content {
display: flex;
flex-direction: column;
align-items: center;
}
ha-more-info-toggle {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
`;
return moreInfoControlStyle;
}
}

View File

@@ -1,9 +1,10 @@
import { mdiPower, mdiPowerOff } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import { LightEntity } from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
@@ -19,41 +20,27 @@ class MoreInfoSwitch extends LitElement {
}
return html`
<div class="content">
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<div class="controls">
<ha-more-info-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiPower}
.iconPathOff=${mdiPowerOff}
></ha-more-info-toggle>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
static get styles(): CSSResultGroup {
return css`
.content {
display: flex;
flex-direction: column;
align-items: center;
}
ha-more-info-toggle {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
`;
return moreInfoControlStyle;
}
}

View File

@@ -25,7 +25,11 @@ import "../../components/ha-icon-button";
import "../../components/ha-icon-button-prev";
import "../../components/ha-list-item";
import "../../components/ha-related-items";
import { EntityRegistryEntry } from "../../data/entity_registry";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../data/entity_registry";
import { haStyleDialog } from "../../resources/styles";
import "../../state-summary/state-card-content";
import { HomeAssistant } from "../../types";
@@ -77,6 +81,8 @@ export class MoreInfoDialog extends LitElement {
@state() private _childView?: ChildView;
@state() private _entry?: ExtEntityRegistryEntry | null;
public showDialog(params: MoreInfoDialogParams) {
this._entityId = params.entityId;
if (!this._entityId) {
@@ -86,10 +92,26 @@ export class MoreInfoDialog extends LitElement {
this._currView = params.view || "info";
this._childView = undefined;
this.large = false;
this._loadEntityRegistryEntry();
}
private async _loadEntityRegistryEntry() {
if (!this._entityId) {
return;
}
try {
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this._entityId
);
} catch (e) {
this._entry = null;
}
}
public closeDialog() {
this._entityId = undefined;
this._entry = undefined;
this._childView = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -172,7 +194,10 @@ export class MoreInfoDialog extends LitElement {
idToPassThroughUrl = stateObj.attributes.id;
}
if (EDITABLE_DOMAINS_WITH_UNIQUE_ID.includes(domain)) {
idToPassThroughUrl = this.hass.entities[this._entityId!].unique_id;
if (!this._entry) {
return;
}
idToPassThroughUrl = this._entry.unique_id;
}
navigate(`/config/${domain}/edit/${idToPassThroughUrl}`);
@@ -203,13 +228,7 @@ export class MoreInfoDialog extends LitElement {
const isInfoView = this._currView === "info" && !this._childView;
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${title}
hideActions
data-domain=${domain}
>
<ha-dialog open @closed=${this.closeDialog} .heading=${title} hideActions>
<div slot="heading" class="heading">
<ha-header-bar>
${isInfoView
@@ -335,10 +354,14 @@ export class MoreInfoDialog extends LitElement {
@show-child-view=${this._showChildView}
>
${this._childView
? dynamicElement(this._childView.viewTag, {
hass: this.hass,
params: this._childView.viewParams,
})
? html`
<div class="child-view">
${dynamicElement(this._childView.viewTag, {
hass: this.hass,
params: this._childView.viewParams,
})}
</div>
`
: cache(
this._currView === "info"
? html`
@@ -360,6 +383,8 @@ export class MoreInfoDialog extends LitElement {
<ha-more-info-settings
.hass=${this.hass}
.entityId=${this._entityId}
.entry=${this._entry}
@entity-entry-updated=${this._entryUpdated}
></ha-more-info-settings>
`
: this._currView === "related"
@@ -385,12 +410,12 @@ export class MoreInfoDialog extends LitElement {
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_currView")) {
this.setAttribute("view", this._currView);
this._childView = undefined;
}
if (changedProps.has("_childView")) {
this.toggleAttribute("has-child-view", !!this._childView);
}
}
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
this._entry = ev.detail;
}
private _enlarge() {
@@ -407,7 +432,6 @@ export class MoreInfoDialog extends LitElement {
--dialog-content-position: static;
--vertical-align-dialog: flex-start;
--dialog-content-padding: 0;
--content-padding: 24px;
}
ha-header-bar {
@@ -417,6 +441,7 @@ export class MoreInfoDialog extends LitElement {
display: block;
border-bottom: none;
}
.content {
outline: none;
}
@@ -426,22 +451,16 @@ export class MoreInfoDialog extends LitElement {
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
:host([view="settings"]) ha-dialog {
--content-padding: 0;
ha-related-items,
ha-more-info-history-and-logbook {
padding: 24px;
display: block;
}
:host([view="info"]) ha-dialog[data-domain="camera"] {
--content-padding: 0;
/* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */
--video-max-height: calc(100vh - 65px - 72px);
}
:host([has-child-view]) ha-dialog {
--content-padding: 0;
}
.content {
padding: var(--content-padding);
@media all and (max-width: 450px) {
.child-view > * {
min-height: calc(100vh - 56px);
}
}
.main-title {

View File

@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../../types";
import {
@@ -34,6 +34,18 @@ export class MoreInfoHistoryAndLogbook extends LitElement {
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
ha-more-info-history,
ha-more-info-logbook {
display: block;
}
ha-more-info-history + ha-more-info-logbook {
margin-top: 16px;
}
`;
}
}
declare global {

View File

@@ -14,6 +14,7 @@ import {
getStatisticMetadata,
Statistics,
StatisticsTypes,
StatisticsMetaData,
} from "../../data/recorder";
import { HomeAssistant } from "../../types";
import "../../components/chart/statistics-chart";
@@ -47,6 +48,8 @@ export class MoreInfoHistory extends LitElement {
private _error?: string;
private _metadata?: Record<string, StatisticsMetaData>;
protected render(): TemplateResult {
if (!this.entityId) {
return html``;
@@ -70,6 +73,7 @@ export class MoreInfoHistory extends LitElement {
.hass=${this.hass}
.isLoadingData=${!this._statistics}
.statisticsData=${this._statistics}
.metadata=${this._metadata}
.statTypes=${statTypes}
.names=${this._statNames}
hideLegend
@@ -136,15 +140,33 @@ export class MoreInfoHistory extends LitElement {
this._interval = window.setInterval(() => this._redrawGraph(), 1000 * 60);
}
private async _getStatisticsMetaData(statisticIds: string[] | undefined) {
const statsMetadataArray = await getStatisticMetadata(
this.hass,
statisticIds
);
const statisticsMetaData = {};
statsMetadataArray.forEach((x) => {
statisticsMetaData[x.statistic_id] = x;
});
return statisticsMetaData;
}
private async _getStateHistory(): Promise<void> {
if (
isComponentLoaded(this.hass, "recorder") &&
computeDomain(this.entityId) === "sensor"
) {
const metadata = await getStatisticMetadata(this.hass, [this.entityId]);
this._statNames = { [this.entityId]: "" };
if (metadata.length) {
this._statistics = await fetchStatistics(
const stateObj = this.hass.states[this.entityId];
// If there is no state class, the integration providing the entity
// has not opted into statistics so there is no need to check as it
// requires another round-trip to the server.
if (stateObj && stateObj.attributes.state_class) {
// Fire off the metadata and fetch at the same time
// to avoid waiting in sequence so the UI responds
// faster.
const _metadata = this._getStatisticsMetaData([this.entityId]);
const _statistics = fetchStatistics(
this.hass!,
subHours(new Date(), 24),
undefined,
@@ -153,7 +175,16 @@ export class MoreInfoHistory extends LitElement {
undefined,
statTypes
);
return;
const [metadata, statistics] = await Promise.all([
_metadata,
_statistics,
]);
if (metadata && Object.keys(metadata).length) {
this._metadata = metadata;
this._statistics = statistics;
this._statNames = { [this.entityId]: "" };
return;
}
}
}
if (!isComponentLoaded(this.hass, "history") || this._subscribed) {

View File

@@ -16,6 +16,7 @@ import {
} from "./const";
import "./ha-more-info-history";
import "./ha-more-info-logbook";
import "./more-info-content";
@customElement("ha-more-info-info")
export class MoreInfoInfo extends LitElement {
@@ -29,52 +30,59 @@ export class MoreInfoInfo extends LitElement {
const entityId = this.entityId;
const stateObj = this.hass.states[entityId];
const domain = computeDomain(entityId);
const newMoreInfo = computeShowNewMoreInfo(stateObj);
return html`
${!stateObj
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.unavailable"
)}
</ha-alert>`
: ""}
${stateObj?.attributes.restored && this._entityEntry
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.more_info_control.restored.no_longer_provided",
{
integration: this._entityEntry.platform,
}
)}
</ha-alert>`
: ""}
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
? ""
: html`
<state-card-content
in-dialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>
`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowHistoryComponent(this.hass, entityId)
? ""
: html`<ha-more-info-history
<div class="container" data-domain=${domain}>
${!stateObj
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.unavailable"
)}
</ha-alert>`
: ""}
${stateObj?.attributes.restored && this._entityEntry
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.more_info_control.restored.no_longer_provided",
{
integration: this._entityEntry.platform,
}
)}
</ha-alert>`
: ""}
<div class="content">
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
? ""
: html`
<state-card-content
in-dialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>
`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowHistoryComponent(this.hass, entityId)
? ""
: html`<ha-more-info-history
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-history>`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowLogBookComponent(this.hass, entityId)
? ""
: html`<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-logbook>`}
<more-info-content
?full-height=${newMoreInfo}
.stateObj=${stateObj}
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-history>`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowLogBookComponent(this.hass, entityId)
? ""
: html`<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-logbook>`}
<more-info-content
.stateObj=${stateObj}
.hass=${this.hass}
></more-info-content>
></more-info-content>
<div class="toto"></div>
</div>
</div>
`;
}
@@ -91,6 +99,40 @@ export class MoreInfoInfo extends LitElement {
static get styles() {
return css`
.container {
display: flex;
flex-direction: column;
}
@media all and (max-width: 450px) {
.container {
min-height: calc(100vh - 56px);
}
}
.content {
display: flex;
flex-direction: column;
flex: 1;
padding: 24px;
padding-bottom: max(env(safe-area-inset-bottom), 24px);
}
[data-domain="camera"] .content {
padding: 0;
/* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */
--video-max-height: calc(100vh - 65px - 72px);
}
more-info-content {
position: relative;
display: flex;
flex-direction: column;
}
more-info-content[full-height] {
flex: 1;
}
state-card-content,
ha-more-info-history,
ha-more-info-logbook:not(:last-child) {
@@ -100,9 +142,6 @@ export class MoreInfoInfo extends LitElement {
ha-alert {
display: block;
margin: calc(-1 * var(--content-padding, 24px))
calc(-1 * var(--content-padding, 24px)) 16px
calc(-1 * var(--content-padding, 24px));
}
`;
}

View File

@@ -6,12 +6,11 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../data/entity_registry";
import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const";
import "../../panels/config/entities/entity-registry-settings";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import "../../panels/config/entities/entity-registry-settings";
@customElement("ha-more-info-settings")
export class HaMoreInfoSettings extends LitElement {
@@ -19,18 +18,18 @@ export class HaMoreInfoSettings extends LitElement {
@property({ attribute: false }) public entityId!: string;
@state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private _settingsElementTag?: string;
protected render() {
// loading.
if (this._entry === undefined) {
if (this.entry === undefined) {
return html``;
}
// No unique ID
if (this._entry === null) {
if (this.entry === null) {
return html`
<div class="content">
${this.hass.localize(
@@ -54,53 +53,31 @@ export class HaMoreInfoSettings extends LitElement {
}
return html`
<div @entity-entry-updated=${this._entryUpdated}>
${dynamicElement(this._settingsElementTag, {
hass: this.hass,
entry: this._entry,
entityId: this.entityId,
})}
</div>
${dynamicElement(this._settingsElementTag, {
hass: this.hass,
entry: this.entry,
entityId: this.entityId,
})}
`;
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (changedProps.has("entityId")) {
this._entry = undefined;
if (this.entityId) {
this._getEntityReg();
}
}
}
private async _getEntityReg() {
try {
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this.entityId
);
public willUpdate(changedProps: PropertyValues) {
if (changedProps.has("entry")) {
this._loadPlatformSettingTabs();
} catch {
this._entry = null;
}
}
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
this._entry = ev.detail;
}
private async _loadPlatformSettingTabs(): Promise<void> {
if (!this._entry) {
if (!this.entry) {
return;
}
if (
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this.entry.platform)
) {
this._settingsElementTag = "entity-registry-settings";
return;
}
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
const tag = PLATFORMS_WITH_SETTINGS_TAB[this.entry.platform];
await import(`../../panels/config/entities/editor-tabs/settings/${tag}`);
this._settingsElementTag = tag;
}

View File

@@ -7,6 +7,7 @@ import { fireEvent } from "../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
import "../../components/ha-circular-progress";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-expansion-panel";
import "../../components/ha-list-item";
import {
extractApiErrorMessage,
@@ -106,6 +107,7 @@ class DialogRestart extends LitElement {
<ha-list-item
graphic="avatar"
twoline
multiline-secondary
hasMeta
@request-selected=${this._reload}
>
@@ -128,6 +130,7 @@ class DialogRestart extends LitElement {
<ha-list-item
graphic="avatar"
twoline
multiline-secondary
hasMeta
@request-selected=${this._restart}
>
@@ -143,57 +146,62 @@ class DialogRestart extends LitElement {
)}
</span>
</ha-list-item>
${showRebootShutdown
? html`
<div class="divider"></div>
<p class="section">
${this.hass.localize(
"ui.dialogs.restart.advanced_options"
)}
</p>
<ha-list-item
graphic="avatar"
twoline
hasMeta
@request-selected=${this._hostReboot}
>
<div slot="graphic" class="icon-background reboot">
<ha-svg-icon .path=${mdiPowerCycle}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.reboot.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.reboot.description"
)}
</span>
</ha-list-item>
<ha-list-item
graphic="avatar"
twoline
hasMeta
@request-selected=${this._hostShutdown}
>
<div slot="graphic" class="icon-background shutdown">
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.shutdown.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.shutdown.description"
)}
</span>
</ha-list-item>
`
: null}
</mwc-list>
${showRebootShutdown
? html`
<ha-expansion-panel
.header=${this.hass.localize(
"ui.dialogs.restart.advanced_options"
)}
>
<mwc-list>
<ha-list-item
graphic="avatar"
twoline
multiline-secondary
hasMeta
@request-selected=${this._hostReboot}
>
<div slot="graphic" class="icon-background reboot">
<ha-svg-icon .path=${mdiPowerCycle}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.reboot.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.reboot.description"
)}
</span>
</ha-list-item>
<ha-list-item
graphic="avatar"
twoline
multiline-secondary
hasMeta
@request-selected=${this._hostShutdown}
>
<div slot="graphic" class="icon-background shutdown">
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.shutdown.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.shutdown.description"
)}
</span>
</ha-list-item>
</mwc-list>
</ha-expansion-panel>
`
: null}
`}
</ha-dialog>
`;
@@ -340,6 +348,23 @@ class DialogRestart extends LitElement {
ha-dialog {
--dialog-content-padding: 0;
}
@media all and (min-width: 550px) {
ha-dialog {
--mdc-dialog-min-width: 500px;
--mdc-dialog-max-width: 500px;
}
}
ha-expansion-panel {
border-top: 1px solid var(--divider-color);
margin-bottom: 10px;
box-shadow: none;
--expansion-panel-content-padding: 0;
--expansion-panel-summary-padding: 0
var(--mdc-list-side-padding, 20px);
--ha-card-border-radius: 0;
}
.icon-background {
border-radius: 50%;
color: #fff;

View File

@@ -13,10 +13,8 @@ import {
StaleWhileRevalidate,
} from "workbox-strategies";
const noFallBackRegEx = new RegExp(
"/(api|static|auth|frontend_latest|frontend_es5|local)/.*"
);
const noFallBackRegEx =
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;
// Clean up caches from older workboxes and old service workers.
// Will help with cleaning up Workbox v4 stuff
cleanupOutdatedCaches();
@@ -33,22 +31,22 @@ function initRouting() {
// Cache static content (including translations) on first access.
registerRoute(
new RegExp("/(static|frontend_latest|frontend_es5)/.+"),
/\/(static|frontend_latest|frontend_es5)\/.+/,
new CacheFirst({ matchOptions: { ignoreSearch: true } })
);
// Get api from network.
registerRoute(new RegExp("/(api|auth)/.*"), new NetworkOnly());
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());
// Get manifest, service worker, onboarding from network.
registerRoute(
new RegExp("/(service_worker.js|manifest.json|onboarding.html)"),
/\/(service_worker.js|manifest.json|onboarding.html)/,
new NetworkOnly()
);
// For the root "/" we ignore search
registerRoute(
new RegExp(/\/(\?.*)?$/),
/\/(\?.*)?$/,
new StaleWhileRevalidate({ matchOptions: { ignoreSearch: true } })
);
@@ -57,7 +55,7 @@ function initRouting() {
// First access might bring stale data from cache, but a single refresh will bring updated
// file.
registerRoute(
new RegExp(/\/.*/),
/\/.*/,
new StaleWhileRevalidate({
cacheName: "file-cache",
plugins: [

View File

@@ -116,7 +116,7 @@ export const provideHass = (
}
mockAPI(
new RegExp("states/.+"),
/states\/.+/,
(
// @ts-ignore
method,

View File

@@ -11,6 +11,7 @@ import {
mdiStopCircleOutline,
mdiSort,
} from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
@@ -26,6 +27,10 @@ import "../../../../components/ha-icon-button";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { ACTION_TYPES } from "../../../../data/action";
import { validateConfig } from "../../../../data/config";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../../data/entity_registry";
import { Action, getActionType } from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n";
import { callExecuteScript } from "../../../../data/service";
@@ -107,6 +112,8 @@ export default class HaAutomationActionRow extends LitElement {
@property({ type: Boolean }) public reOrderMode = false;
@state() private _entityReg: EntityRegistryEntry[] = [];
@state() private _warnings?: string[];
@state() private _uiModeAvailable = true;
@@ -115,6 +122,14 @@ export default class HaAutomationActionRow extends LitElement {
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected willUpdate(changedProperties: PropertyValues) {
if (!changedProperties.has("action")) {
return;
@@ -156,7 +171,9 @@ export default class HaAutomationActionRow extends LitElement {
class="action-icon"
.path=${ACTION_TYPES[type!]}
></ha-svg-icon>
${capitalizeFirstLetter(describeAction(this.hass, this.action))}
${capitalizeFirstLetter(
describeAction(this.hass, this._entityReg, this.action)
)}
</h3>
<slot name="icons" slot="icons"></slot>
@@ -465,7 +482,7 @@ export default class HaAutomationActionRow extends LitElement {
),
inputType: "string",
placeholder: capitalizeFirstLetter(
describeAction(this.hass, this.action, undefined, true)
describeAction(this.hass, this._entityReg, this.action, undefined, true)
),
defaultValue: this.action.alias,
confirmText: this.hass.localize("ui.common.submit"),

View File

@@ -49,6 +49,7 @@ import {
showAutomationEditor,
triggerAutomationActions,
} from "../../../data/automation";
import { fetchEntityRegistry } from "../../../data/entity_registry";
import {
showAlertDialog,
showConfirmationDialog,
@@ -479,7 +480,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
this._readOnly = false;
this._config = this._normalizeConfig(config);
} catch (err: any) {
const entity = Object.values(this.hass.entities).find(
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
const entity = entityRegistry.find(
(ent) =>
ent.platform === "automation" && ent.unique_id === this.automationId
);

View File

@@ -288,7 +288,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
}
private precisionLabel(precision?: number, stateValue?: string) {
const value = stateValue ?? 0;
const stateValueNumber = Number(stateValue);
const value = !isNaN(stateValueNumber) ? stateValueNumber : 0;
return formatNumber(value, this.hass.locale, {
minimumFractionDigits: precision,
maximumFractionDigits: precision,

View File

@@ -278,7 +278,7 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
`
: ""}
<div class="content">
${boardName
${boardName || isComponentLoaded(this.hass, "hassio")
? html`
<ha-card outlined>
<div class="card-content">
@@ -293,7 +293,9 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
: ""}
<span class="primary-text">
${boardName ||
this.hass.localize("ui.panel.config.hardware.board")}
this.hass.localize(
"ui.panel.config.hardware.generic_hardware"
)}
</span>
${boardId
? html`
@@ -327,16 +329,21 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
: ""}
</mwc-list>
</div>
${boardConfigEntries.length
${boardConfigEntries.length ||
isComponentLoaded(this.hass, "hassio")
? html`<div class="card-actions">
<mwc-button
.entry=${boardConfigEntries[0]}
@click=${this._openOptionsFlow}
>
${this.hass.localize(
"ui.panel.config.hardware.configure"
)}
</mwc-button>
${boardConfigEntries.length
? html`
<mwc-button
.entry=${boardConfigEntries[0]}
@click=${this._openOptionsFlow}
>
${this.hass.localize(
"ui.panel.config.hardware.configure"
)}
</mwc-button>
`
: null}
${isComponentLoaded(this.hass, "hassio")
? html`
<mwc-button @click=${this._openHardware}>

View File

@@ -1,5 +1,10 @@
import "@material/mwc-button";
import { mdiDevices, mdiDotsVertical, mdiInformationOutline } from "@mdi/js";
import {
mdiDeleteOutline,
mdiDevices,
mdiDotsVertical,
mdiInformationOutline,
} from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -11,13 +16,19 @@ import { getSignedPath } from "../../../../../data/auth";
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../../../data/diagnostics";
import { getOTBRInfo } from "../../../../../data/otbr";
import {
addThreadDataSet,
listThreadDataSets,
removeThreadDataSet,
subscribeDiscoverThreadRouters,
ThreadDataSet,
ThreadRouter,
} from "../../../../../data/thread";
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
import {
showAlertDialog,
showConfirmationDialog,
showPromptDialog,
} from "../../../../../dialogs/generic/show-dialog-box";
import "../../../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../../resources/styles";
@@ -66,6 +77,11 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
)}
</mwc-list-item>
</a>
<mwc-list-item @click=${this._addTLV}
>${this.hass.localize(
"ui.panel.config.thread.add_dataset_from_tlv"
)}</mwc-list-item
>
<mwc-list-item @click=${this._addOTBR}
>${this.hass.localize(
"ui.panel.config.thread.add_open_thread_border_router"
@@ -108,11 +124,20 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
return html`<ha-card>
<div class="card-header">
${network.name}${network.dataset
? html`<ha-icon-button
.networkDataset=${network.dataset}
.path=${mdiInformationOutline}
@click=${this._showDatasetInfo}
></ha-icon-button>`
? html`<div>
<ha-icon-button
.networkDataset=${network.dataset}
.path=${mdiInformationOutline}
@click=${this._showDatasetInfo}
></ha-icon-button
>${!network.dataset.preferred && !network.routers?.length
? html`<ha-icon-button
.networkDataset=${network.dataset}
.path=${mdiDeleteOutline}
@click=${this._removeDataset}
></ha-icon-button>`
: ""}
</div>`
: ""}
</div>
${network.routers?.length
@@ -154,7 +179,10 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
const dataset = (ev.currentTarget as any).networkDataset as ThreadDataSet;
if (isComponentLoaded(this.hass, "otbr")) {
const otbrInfo = await getOTBRInfo(this.hass);
if (otbrInfo.active_dataset_tlvs.includes(dataset.extended_pan_id)) {
if (
dataset.extended_pan_id &&
otbrInfo.active_dataset_tlvs?.includes(dataset.extended_pan_id)
) {
showAlertDialog(this, {
title: dataset.network_name,
text: html`Network name: ${dataset.network_name}<br />
@@ -267,6 +295,57 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
});
}
private async _addTLV() {
const tlv = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.thread.add_dataset"),
inputLabel: this.hass.localize(
"ui.panel.config.thread.add_dataset_label"
),
confirmText: this.hass.localize(
"ui.panel.config.thread.add_dataset_button"
),
});
if (!tlv) {
return;
}
try {
await addThreadDataSet(this.hass, "manual", tlv);
} catch (err: any) {
showAlertDialog(this, {
title: "Error",
text: err.message || err,
});
}
this._refresh();
}
private async _removeDataset(ev: Event) {
const dataset = (ev.currentTarget as any).networkDataset as ThreadDataSet;
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.thread.confirm_delete_dataset",
{ name: dataset.network_name }
),
text: this.hass.localize(
"ui.panel.config.thread.confirm_delete_dataset_text"
),
destructive: true,
confirmText: this.hass.localize("ui.common.delete"),
});
if (!confirm) {
return;
}
try {
await removeThreadDataSet(this.hass, dataset.dataset_id);
} catch (err: any) {
showAlertDialog(this, {
title: "Error",
text: err.message || err,
});
}
this._refresh();
}
static styles = [
haStyle,
css`

View File

@@ -72,7 +72,7 @@ class DialogZHAReconfigureDevice extends LitElement {
this.hass,
this.hass.localize(`ui.dialogs.zha_reconfigure_device.heading`) +
": " +
(this._params?.device.user_given_name || this._params?.device.name)
(this._params.device.user_given_name || this._params.device.name)
)}
>
${!this._status

View File

@@ -36,6 +36,15 @@ import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../../dialogs/generic/show-dialog-box";
import { HaFormSchema } from "../../../../../components/ha-form/types";
const firmwareTargetSchema: HaFormSchema[] = [
{
name: "firmware_target",
type: "integer",
valueMin: 0,
},
];
@customElement("dialog-zwave_js-update-firmware-node")
class DialogZWaveJSUpdateFirmwareNode extends LitElement {
@@ -59,6 +68,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
@state() private _nodeStatus?: ZWaveJSNodeStatus;
@state() private _firmwareTarget?: number;
private _subscribedNodeStatus?: Promise<UnsubscribeFunc>;
private _subscribedNodeFirmwareUpdate?: Promise<UnsubscribeFunc>;
@@ -80,6 +91,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._updateFinishedMessage = undefined;
this._firmwareFile = undefined;
this._nodeStatus = undefined;
this._firmwareTarget = undefined;
this._uploading = this._updateInProgress = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -104,6 +116,19 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
)}
@file-picked=${this._uploadFile}
></ha-file-upload>
${this._nodeStatus.is_controller_node
? html``
: html`<p>
${this.hass.localize(
"ui.panel.config.zwave_js.update_firmware.firmware_target_intro"
)}
</p>
<ha-form
.hass=${this.hass}
.data=${{ firmware_target: this._firmwareTarget }}
.schema=${firmwareTargetSchema}
@value-changed=${this._firmwareTargetChanged}
></ha-form>`}
<mwc-button
slot="primaryAction"
@click=${this._beginFirmwareUpdate}
@@ -283,7 +308,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
await uploadFirmwareAndBeginUpdate(
this.hass,
this.device!.id,
this._firmwareFile!
this._firmwareFile!,
this._firmwareTarget
);
this._updateInProgress = true;
this._uploading = false;
@@ -388,6 +414,10 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._subscribedNodeFirmwareUpdate = undefined;
}
private async _firmwareTargetChanged(ev) {
this._firmwareTarget = ev.detail.value.firmware_target;
}
private async _uploadFile(ev) {
this._firmwareFile = ev.detail.files[0];
}

View File

@@ -1,14 +1,19 @@
import { HassEntities } from "home-assistant-js-websocket";
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { debounce } from "../../../common/util/debounce";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { ScriptEntity } from "../../../data/script";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../types";
import "./ha-script-editor";
import "./ha-script-picker";
@@ -21,7 +26,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => {
};
@customElement("ha-config-script")
class HaConfigScript extends HassRouterPage {
class HaConfigScript extends SubscribeMixin(HassRouterPage) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean;
@@ -32,6 +37,16 @@ class HaConfigScript extends HassRouterPage {
@property() public scripts: ScriptEntity[] = [];
@state() private _entityReg: EntityRegistryEntry[] = [];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
routes: {
@@ -78,6 +93,7 @@ class HaConfigScript extends HassRouterPage {
pageEl.isWide = this.isWide;
pageEl.route = this.routeTail;
pageEl.showAdvanced = this.showAdvanced;
pageEl.entityRegistry = this._entityReg;
if (this.hass) {
if (!pageEl.scripts || !changedProps) {

View File

@@ -39,6 +39,7 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import {
deleteScript,
getScriptStateConfig,
@@ -75,6 +76,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@state() private _config?: ScriptConfig;
@state() private _entityId?: string;
@@ -431,7 +434,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._config = this._normalizeConfig(config);
},
(resp) => {
const entity = Object.values(this.hass.entities).find(
const entity = this.entityRegistry.find(
(ent) =>
ent.platform === "script" && ent.unique_id === this.scriptId
);
@@ -477,7 +480,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
getScriptStateConfig(this.hass, this.entityId).then((c) => {
this._config = this._normalizeConfig(c.config);
});
const regEntry = this.hass.entities[this.entityId];
const regEntry = this.entityRegistry.find(
(ent) => ent.entity_id === this.entityId
);
if (regEntry?.unique_id) {
this.scriptId = regEntry.unique_id;
}
@@ -544,7 +549,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
if (!this.scriptId) {
return;
}
const entity = Object.values(this.hass.entities).find(
const entity = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
);
if (!entity) {

View File

@@ -44,6 +44,7 @@ import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@customElement("ha-script-picker")
class HaScriptPicker extends LitElement {
@@ -57,7 +58,9 @@ class HaScriptPicker extends LitElement {
@property() public route!: Route;
@property() private _activeFilters?: string[];
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@state() private _activeFilters?: string[];
@state() private _filteredScripts?: string[] | null;
@@ -266,7 +269,7 @@ class HaScriptPicker extends LitElement {
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const entry = this.hass.entities[ev.detail.id];
const entry = this.entityRegistry.find((e) => e.entity_id === ev.detail.id);
if (entry) {
navigate(`/config/script/edit/${entry.unique_id}`);
} else {
@@ -275,7 +278,12 @@ class HaScriptPicker extends LitElement {
}
private _runScript = async (script: any) => {
const entry = this.hass.entities[script.entity_id];
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (!entry) {
return;
}
await triggerScript(this.hass, entry.unique_id);
showToast(this, {
message: this.hass.localize(
@@ -291,7 +299,9 @@ class HaScriptPicker extends LitElement {
}
private _showTrace(script: any) {
const entry = this.hass.entities[script.entity_id];
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (entry) {
navigate(`/config/script/trace/${entry.unique_id}`);
}
@@ -317,7 +327,12 @@ class HaScriptPicker extends LitElement {
private async _duplicate(script: any) {
try {
const entry = this.hass.entities[script.entity_id];
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (!entry) {
return;
}
const config = await fetchScriptFileConfig(this.hass, entry.unique_id);
showScriptEditor({
...config,
@@ -362,8 +377,12 @@ class HaScriptPicker extends LitElement {
private async _delete(script: any) {
try {
const entry = this.hass.entities[script.entity_id];
await deleteScript(this.hass, entry.unique_id);
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (entry) {
await deleteScript(this.hass, entry.unique_id);
}
} catch (err: any) {
await showAlertDialog(this, {
text:

View File

@@ -39,6 +39,7 @@ import { HomeAssistant, Route } from "../../../types";
import "../../../layouts/hass-subpage";
import "../../../components/ha-button-menu";
import { fireEvent } from "../../../common/dom/fire_event";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@customElement("ha-script-trace")
export class HaScriptTrace extends LitElement {
@@ -54,6 +55,8 @@ export class HaScriptTrace extends LitElement {
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@state() private _entityId?: string;
@state() private _traces?: ScriptTrace[];
@@ -318,7 +321,7 @@ export class HaScriptTrace extends LitElement {
const params = new URLSearchParams(location.search);
this._loadTraces(params.get("run_id") || undefined);
this._entityId = Object.values(this.hass.entities).find(
this._entityId = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
)?.entity_id;
}
@@ -335,7 +338,7 @@ export class HaScriptTrace extends LitElement {
if (this.scriptId) {
this._loadTraces();
this._entityId = Object.values(this.hass.entities).find(
this._entityId = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
)?.entity_id;
}

View File

@@ -18,7 +18,7 @@ import "../lovelace/components/hui-energy-period-selector";
import { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view";
const LOVELACE_CONFIG: LovelaceConfig = {
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
views: [
{
strategy: {
@@ -93,8 +93,8 @@ class PanelEnergy extends LitElement {
private _setLovelace() {
this._lovelace = {
config: LOVELACE_CONFIG,
rawConfig: LOVELACE_CONFIG,
config: ENERGY_LOVELACE_CONFIG,
rawConfig: ENERGY_LOVELACE_CONFIG,
editMode: false,
urlPath: "energy",
mode: "generated",

View File

@@ -56,7 +56,7 @@ export class EnergyStrategy {
(source) => source.type === "water"
);
if (info.narrow) {
if (info.narrow || info.view.strategy?.options?.show_date_selection) {
view.cards!.push({
type: "energy-date-selection",
collection_key: "energy_dashboard",

View File

@@ -884,6 +884,11 @@ class HuiEnergyDistrubutionCard
color: var(--secondary-text-color);
font-size: 12px;
opacity: 1;
height: 20px;
overflow: hidden;
text-overflow: ellipsis;
max-width: 80px;
white-space: nowrap;
}
line,
path {

View File

@@ -13,6 +13,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { getNumberFormatOptions } from "../../../common/number/format_number";
import "../../../components/ha-card";
import "../../../components/ha-gauge";
import { UNAVAILABLE } from "../../../data/entity";
@@ -129,6 +130,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
.min=${this._config.min!}
.max=${this._config.max!}
.value=${stateObj.state}
.formatOptions=${getNumberFormatOptions(
stateObj,
this.hass.entities[stateObj.entity_id]
)}
.locale=${this.hass!.locale}
.label=${this._config!.unit ||
this.hass?.states[this._config!.entity].attributes
@@ -178,7 +183,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
// new format
let segments = this._config!.segments;
if (segments) {
segments = [...segments].sort((a, b) => a?.from - b?.from);
segments = [...segments].sort((a, b) => a.from - b.from);
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];

View File

@@ -278,8 +278,8 @@ const computeDefaultViewStates = (
.filter(
(entry) =>
entry.entity_category ||
HIDE_PLATFORM.has(entry.platform) ||
entry.hidden_by
(entry.platform && HIDE_PLATFORM.has(entry.platform)) ||
entry.hidden
)
.map((entry) => entry.entity_id)
);

View File

@@ -62,7 +62,7 @@ const buttonEntitiesRowConfigStruct = object({
const castEntitiesRowConfigStruct = object({
type: literal("cast"),
view: union([string(), number()]),
view: optional(union([string(), number()])),
dashboard: optional(string()),
name: optional(string()),
icon: optional(string()),

View File

@@ -55,7 +55,7 @@ export interface CastConfig {
type: "cast";
icon?: string;
name?: string;
view: string | number;
view?: string | number;
dashboard?: string;
// Hide the row if either unsupported browser or no API available.
hide_if_unavailable?: boolean;

View File

@@ -29,13 +29,10 @@ class HuiCastRow extends LitElement implements LovelaceRow {
@state() private _noHTTPS = false;
public setConfig(config: CastConfig): void {
if (!config || config.view === undefined || config.view === null) {
throw new Error("View required");
}
this._config = {
icon: "hass:television",
icon: "mdi:television",
name: "Home Assistant Cast",
view: 0,
...config,
};
}
@@ -123,7 +120,7 @@ class HuiCastRow extends LitElement implements LovelaceRow {
castSendShowLovelaceView(
this._castManager!,
this.hass.auth.data.hassUrl,
this._config!.view,
this._config!.view!,
this._config!.dashboard
);
}

View File

@@ -14,7 +14,7 @@ import { polyfillsLoaded } from "../common/translations/localize";
import { subscribeAreaRegistry } from "../data/area_registry";
import { broadcastConnectionStatus } from "../data/connection-status";
import { subscribeDeviceRegistry } from "../data/device_registry";
import { subscribeEntityRegistry } from "../data/entity_registry";
import { subscribeEntityRegistryDisplay } from "../data/entity_registry";
import { subscribeFrontendUserData } from "../data/frontend";
import { forwardHaptic } from "../data/haptics";
import { DEFAULT_PANEL } from "../data/panel";
@@ -188,10 +188,23 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
});
subscribeEntities(conn, (states) => this._updateHass({ states }));
subscribeEntityRegistry(conn, (entityReg) => {
subscribeEntityRegistryDisplay(conn, (entityReg) => {
const entities: HomeAssistant["entities"] = {};
for (const entity of entityReg) {
entities[entity.entity_id] = entity;
for (const entity of entityReg.entities) {
entities[entity.ei] = {
entity_id: entity.ei,
device_id: entity.di,
area_id: entity.ai,
translation_key: entity.tk,
platform: entity.pl,
entity_category:
entity.ec !== undefined
? entityReg.entity_categories[entity.ec]
: undefined,
name: entity.en,
hidden: entity.hb,
display_precision: entity.dp,
};
}
this._updateHass({ entities });
});

View File

@@ -1038,9 +1038,9 @@
"restart": {
"title": "Restart Home Assistant",
"description": "Interrupts all running automations and scripts.",
"confirm_title": "Restart?",
"confirm_title": "Restart Home Assistant?",
"confirm_description": "This will interrupt all running automations and scripts.",
"confirm_action": "Restart Home Assistant",
"confirm_action": "Restart",
"failed": "Failed to restart Home Assistant"
},
"reboot": {
@@ -1801,7 +1801,7 @@
"reboot_moved_link": "Go to system page.",
"processor": "Processor",
"memory": "Memory",
"board": "Board",
"generic_hardware": "Generic Hardware",
"documentation": "Documentation",
"configure": "Configure",
"documentation_description": "Find extra information about your device",
@@ -3288,6 +3288,12 @@
"my_network": "My network",
"no_preferred_network": "You don't have a preferred network yet.",
"add_open_thread_border_router": "Add an OpenThread border router",
"add_dataset_from_tlv": "Add dataset from TLV",
"add_dataset": "Add Thread dataset",
"add_dataset_label": "Operational dataset TLV",
"add_dataset_button": "Add dataset",
"confirm_delete_dataset": "Delete {name} dataset?",
"confirm_delete_dataset_text": "This network will be removed from Home Assistant.",
"no_border_routers": "No border routers found",
"border_routers": "{count} border {count, plural,\n one {router}\n other {routers}\n}",
"managed_by_home_assistant": "Managed by Home Assistant",
@@ -3691,6 +3697,8 @@
"warning_controller": "WARNING: Firmware updates can brick your controller if you do not use the right firmware files, or if you attempt to stop the firmware update before it completes. The Home Assistant and Z-Wave JS teams do not take any responsibility for any damages to your controller as a result of the firmware update and will not be able to help you if you brick your controller. Would you still like to continue?",
"introduction": "Select the firmware file you would like to use to update {device}.",
"introduction_controller": "Select the firmware file you would like to use to update {device}. Note that once you start a firmware update, you MUST wait for the update to complete.",
"firmware_target_intro": "Select the firmware target (0 for the Z-Wave chip, ≥1 for other chips if they exist) for this update.",
"firmware_target": "Firmware Target (chip)",
"upload_firmware": "Upload Firmware",
"upload_failed": "Upload Failed",
"begin_update": "Begin Firmware Update",

View File

@@ -10,7 +10,7 @@ import {
import { LocalizeFunc } from "./common/translations/localize";
import { AreaRegistryEntry } from "./data/area_registry";
import { DeviceRegistryEntry } from "./data/device_registry";
import { EntityRegistryEntry } from "./data/entity_registry";
import { EntityRegistryDisplayEntry } from "./data/entity_registry";
import { CoreFrontendUserData } from "./data/frontend";
import { FrontendLocaleData, getHassTranslations } from "./data/translation";
import { Themes } from "./data/ws-themes";
@@ -189,7 +189,7 @@ export interface HomeAssistant {
connection: Connection;
connected: boolean;
states: HassEntities;
entities: { [id: string]: EntityRegistryEntry };
entities: { [id: string]: EntityRegistryDisplayEntry };
devices: { [id: string]: DeviceRegistryEntry };
areas: { [id: string]: AreaRegistryEntry };
services: HassServices;

109
yarn.lock
View File

@@ -1457,9 +1457,9 @@ __metadata:
languageName: node
linkType: hard
"@eslint/eslintrc@npm:^1.4.1":
version: 1.4.1
resolution: "@eslint/eslintrc@npm:1.4.1"
"@eslint/eslintrc@npm:^2.0.0":
version: 2.0.0
resolution: "@eslint/eslintrc@npm:2.0.0"
dependencies:
ajv: ^6.12.4
debug: ^4.3.2
@@ -1470,7 +1470,14 @@ __metadata:
js-yaml: ^4.1.0
minimatch: ^3.1.2
strip-json-comments: ^3.1.1
checksum: cd3e5a8683db604739938b1c1c8b77927dc04fce3e28e0c88e7f2cd4900b89466baf83dfbad76b2b9e4d2746abdd00dd3f9da544d3e311633d8693f327d04cd7
checksum: 31119c8ca06723d80384f18f5c78e0530d8e6306ad36379868650131a8b10dd7cffd7aff79a5deb3a2e9933660823052623d268532bae9538ded53d5b19a69a6
languageName: node
linkType: hard
"@eslint/js@npm:8.35.0":
version: 8.35.0
resolution: "@eslint/js@npm:8.35.0"
checksum: 6687ceff659a6d617e37823f809dc9c4b096535961a81acead27d26b1a51a4cf608a5e59d831ddd57f24f6f8bb99340a4a0e19f9c99b390fbb4b275f51ed5f5e
languageName: node
linkType: hard
@@ -3095,13 +3102,13 @@ __metadata:
languageName: node
linkType: hard
"@material/web@npm:=1.0.0-pre.2":
version: 1.0.0-pre.2
resolution: "@material/web@npm:1.0.0-pre.2"
"@material/web@npm:=1.0.0-pre.3":
version: 1.0.0-pre.3
resolution: "@material/web@npm:1.0.0-pre.3"
dependencies:
lit: ^2.3.0
tslib: ^2.4.0
checksum: 7c6733fae5fb67c43d7c49fab70f7893defd95e4fcbe996d06057882e47c0121760546cc5d1c407a9dbd11c5f02f3f278016c52922e6a9e97db0c0b52d7133f2
checksum: d6286992cb0d63b094e638a3db484398195608b422bb2cb209102eaf87d220ed24bbe85d29933730054fd3cf99d318dbb3645ae9a2e271fe1a3c2833d829bf4c
languageName: node
linkType: hard
@@ -4157,12 +4164,12 @@ __metadata:
linkType: hard
"@types/glob@npm:^8":
version: 8.0.1
resolution: "@types/glob@npm:8.0.1"
version: 8.1.0
resolution: "@types/glob@npm:8.1.0"
dependencies:
"@types/minimatch": ^5.1.2
"@types/node": "*"
checksum: 98f3d0403c09638348a2f3b30aac2a3d6bdc306bce3ceb868f4794fef4f02727ccdf0dab0c7b7d65fd38a1afa1e48f02de56d29d2babe94ee9b204ca54acb31f
checksum: 9101f3a9061e40137190f70626aa0e202369b5ec4012c3fabe6f5d229cce04772db9a94fa5a0eb39655e2e4ad105c38afbb4af56a56c0996a8c7d4fc72350e3d
languageName: node
linkType: hard
@@ -6853,6 +6860,18 @@ __metadata:
languageName: node
linkType: hard
"compression-webpack-plugin@npm:^10.0.0":
version: 10.0.0
resolution: "compression-webpack-plugin@npm:10.0.0"
dependencies:
schema-utils: ^4.0.0
serialize-javascript: ^6.0.0
peerDependencies:
webpack: ^5.1.0
checksum: 2ac9079b7ab87141639c62ddbb2820a06f105198e27ef4c3860da3186bdbefc00d1e206969833ce7a4b7b26161ddbec7b8d20d30f9f9c1d494818b9b86f0d5cc
languageName: node
linkType: hard
"compression@npm:1.7.3":
version: 1.7.3
resolution: "compression@npm:1.7.3"
@@ -8073,11 +8092,12 @@ __metadata:
languageName: node
linkType: hard
"eslint@npm:^8.34.0":
version: 8.34.0
resolution: "eslint@npm:8.34.0"
"eslint@npm:^8.35.0":
version: 8.35.0
resolution: "eslint@npm:8.35.0"
dependencies:
"@eslint/eslintrc": ^1.4.1
"@eslint/eslintrc": ^2.0.0
"@eslint/js": 8.35.0
"@humanwhocodes/config-array": ^0.11.8
"@humanwhocodes/module-importer": ^1.0.1
"@nodelib/fs.walk": ^1.2.8
@@ -8091,7 +8111,7 @@ __metadata:
eslint-utils: ^3.0.0
eslint-visitor-keys: ^3.3.0
espree: ^9.4.0
esquery: ^1.4.0
esquery: ^1.4.2
esutils: ^2.0.2
fast-deep-equal: ^3.1.3
file-entry-cache: ^6.0.1
@@ -8118,7 +8138,7 @@ __metadata:
text-table: ^0.2.0
bin:
eslint: bin/eslint.js
checksum: 4e13e9eb05ac2248efbb6acae0b2325091235d5c47ba91a4775c7d6760778cbcd358a773ebd42f4629d2ad89e27c02f5d66eb1f737d75d9f5fc411454f83b2e5
checksum: 6212173691d90b1bc94dd3d640e1f210374b30c3905fc0a15e501cf71c6ca52aa3d80ea7a9a245adaaed26d6019169e01fb6881b3f2885b188d37069c749308c
languageName: node
linkType: hard
@@ -8143,12 +8163,12 @@ __metadata:
languageName: node
linkType: hard
"esquery@npm:^1.4.0":
version: 1.4.0
resolution: "esquery@npm:1.4.0"
"esquery@npm:^1.4.2":
version: 1.4.2
resolution: "esquery@npm:1.4.2"
dependencies:
estraverse: ^5.1.0
checksum: a0807e17abd7fbe5fbd4fab673038d6d8a50675cdae6b04fbaa520c34581be0c5fa24582990e8acd8854f671dd291c78bb2efb9e0ed5b62f33bac4f9cf820210
checksum: 2f4ad89c5aafaca61cc2c15e256190f0d6deb4791cae6552d3cb4b1eb8867958cdf27a56aaa3272ff17435e3eaa19ee0d4129fac336ca6373d7354d7b5da7966
languageName: node
linkType: hard
@@ -9562,7 +9582,7 @@ fsevents@~2.3.2:
"@material/mwc-textfield": ^0.27.0
"@material/mwc-top-app-bar-fixed": ^0.27.0
"@material/top-app-bar": =14.0.0-canary.53b3cad2f.0
"@material/web": =1.0.0-pre.2
"@material/web": =1.0.0-pre.3
"@mdi/js": 7.1.96
"@mdi/svg": 7.1.96
"@octokit/auth-oauth-device": ^4.0.4
@@ -9618,6 +9638,7 @@ fsevents@~2.3.2:
chai: ^4.3.7
chart.js: ^3.3.2
comlink: ^4.4.1
compression-webpack-plugin: ^10.0.0
core-js: ^3.28.0
cropperjs: ^1.5.13
date-fns: ^2.29.3
@@ -9625,7 +9646,7 @@ fsevents@~2.3.2:
deep-clone-simple: ^1.1.1
deep-freeze: ^0.0.1
del: ^7.0.0
eslint: ^8.34.0
eslint: ^8.35.0
eslint-config-airbnb-base: ^15.0.0
eslint-config-airbnb-typescript: ^17.0.0
eslint-config-prettier: ^8.6.0
@@ -9663,7 +9684,7 @@ fsevents@~2.3.2:
lit: ^2.6.1
lit-analyzer: ^1.2.1
lodash.template: ^4.5.0
magic-string: ^0.29.0
magic-string: ^0.30.0
map-stream: ^0.0.7
marked: ^4.2.12
memoize-one: ^6.0.0
@@ -9692,7 +9713,7 @@ fsevents@~2.3.2:
sortablejs: ^1.15.0
source-map-url: ^0.4.1
superstruct: ^1.0.3
systemjs: ^6.13.0
systemjs: ^6.14.0
tar: ^6.1.13
terser-webpack-plugin: ^5.3.6
tinykeys: ^1.4.0
@@ -9704,7 +9725,7 @@ fsevents@~2.3.2:
vinyl-buffer: ^1.0.1
vinyl-source-stream: ^2.0.0
vis-data: ^7.1.4
vis-network: ^9.1.2
vis-network: ^9.1.4
vue: ^2.7.14
vue2-daterange-picker: ^0.6.8
webpack: =5.72.1
@@ -9712,7 +9733,7 @@ fsevents@~2.3.2:
webpack-dev-server: ^4.11.1
webpack-manifest-plugin: ^5.0.0
webpackbar: ^5.0.2
weekstart: ^1.1.0
weekstart: ^2.0.0
workbox-build: ^6.5.4
workbox-cacheable-response: ^6.5.4
workbox-core: ^6.5.4
@@ -11590,12 +11611,12 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"magic-string@npm:^0.29.0":
version: 0.29.0
resolution: "magic-string@npm:0.29.0"
"magic-string@npm:^0.30.0":
version: 0.30.0
resolution: "magic-string@npm:0.30.0"
dependencies:
"@jridgewell/sourcemap-codec": ^1.4.13
checksum: 19e5398fcfc44804917127c72ad622c68a19a0a10cbdb8d4f9f9417584a087fe9e117140bfb2463d86743cf1ed9cf4182ae0b0ad1a7536f7fdda257ee4449ffb
checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d
languageName: node
linkType: hard
@@ -15048,10 +15069,10 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"systemjs@npm:^6.13.0":
version: 6.13.0
resolution: "systemjs@npm:6.13.0"
checksum: df8d7374249778291f3a85278fdb3e1b9d81ac07767b0a7f9edeca0ee45d847c19bceb01522c817605e2908d32a4fcfed6bacd707bfb7bd577774ab900d3707d
"systemjs@npm:^6.14.0":
version: 6.14.0
resolution: "systemjs@npm:6.14.0"
checksum: df82c38a5f23012dfee693e97dacfc48eef787a80cc41bc3bd594567ba9441c6392978841c6555d88df7451a2e22c8df9fcbf9c88c78f9f9b9027393ac38d84b
languageName: node
linkType: hard
@@ -16065,18 +16086,18 @@ typescript@^3.8.3:
languageName: node
linkType: hard
"vis-network@npm:^9.1.2":
version: 9.1.2
resolution: "vis-network@npm:9.1.2"
"vis-network@npm:^9.1.4":
version: 9.1.4
resolution: "vis-network@npm:9.1.4"
peerDependencies:
"@egjs/hammerjs": ^2.0.0
component-emitter: ^1.3.0
keycharm: ^0.2.0 || ^0.3.0 || ^0.4.0
timsort: ^0.3.0
uuid: ^3.4.0 || ^7.0.0 || ^8.0.0
uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
vis-data: ^7.0.0
vis-util: ^5.0.1
checksum: 763bee104c9d69f6b229d10675f712f824568f28dd13540d35d0f1f1fc58fcb9dc27cc12da80f6e874076a7cb75793d168b49c3c7783f8cb06f57931d9edfe0e
checksum: 1c3ce02d251bf04ce3284301d597ee2630ad2a76de6c168c78df4dccf8ec4f4d058d67a1d78d507b763ff909d04d0d1afadcb3c3daf664e5090720b25a00aa2f
languageName: node
linkType: hard
@@ -16419,10 +16440,10 @@ typescript@^3.8.3:
languageName: node
linkType: hard
"weekstart@npm:^1.1.0":
version: 1.1.0
resolution: "weekstart@npm:1.1.0"
checksum: afce96e0b95809a30f00fa02b13a0927324d9f76b9c10ce6b3de9bbd5926615156f8a0526c63e2bd1cabdc8ec3da68b8df8d6608b6364ded11b5da300a8cfcb4
"weekstart@npm:^2.0.0":
version: 2.0.0
resolution: "weekstart@npm:2.0.0"
checksum: 9a27d7fe30d847997d50006c814e68ad9b105858a43029b2312bf5757dd49839865a64ad28345d2dcb1b9f25b72f0de81e23d8db50362890b253486defff69e6
languageName: node
linkType: hard