Compare commits

...

50 Commits

Author SHA1 Message Date
Bram Kragten
010e25b49e Add support for helper text in form boolean 2024-11-07 09:56:10 +01:00
renovate[bot]
a08c7a319f Update formatjs monorepo (#22681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 08:52:53 +02:00
Petar Petrov
5e8868e4b1 Fix import type linter issues (#22702) 2024-11-07 06:39:30 +00:00
Bram Kragten
64285d5155 Add zwave expert UI / Installer settings (#21897)
* Add zwave expert UI / Installer settings

* Fix zwave invoceCC api function name

* Fix function calls of invokeZWaveCCApi

* Add zwave node-installer translations and endpoint separation

* Add zwave capability-control error handling, translations and thermostat setback

* Fix zwave capability thermostat setback

---------

Co-authored-by: Wendelin <w@pe8.at>
2024-11-07 08:00:51 +02:00
Bram Kragten
5247b74fd4 Bumped version to 20241106.0 2024-11-06 13:43:57 +01:00
Wendelin
26e914290d Fix hassio logs translations (#22693)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-11-06 12:33:45 +00:00
Wendelin
ed3096157c Fix logs overflow when there are 0 boots (#22694) 2024-11-06 13:19:17 +01:00
Simon Lamon
04a45a4361 Fix action descriptions switch to English when using search (#22689)
* var mistake

* Update src/panels/config/automation/add-automation-element-dialog.ts

* prettier
2024-11-06 11:17:36 +01:00
Paul Bottein
5430040b96 Fix update more info margin (#22691) 2024-11-06 11:17:14 +01:00
Wendelin
4bd70167ad Add overflow menu to error-log-card (#22684)
* Add overflow menu to error-log-card

* Add toggle line wrap icon-button in error-log-card
2024-11-06 09:59:01 +00:00
Wendelin
e908fbb48e Check for empty logs (#22675)
* Fix download logs default lines + translations + iOS, add live logs indicator

* Fix rtl in error-log-card

* Fix downloadFileSupported
2024-11-06 09:42:46 +00:00
Paul Bottein
38da01abfa Fix icon click in edit card overflow (#22686) 2024-11-06 09:42:38 +00:00
Paul Bottein
c3b7ce8dc4 Revert "More flexible translation keys for logbook binary sensors" (#22687)
Revert "More flexible translation keys for logbook binary sensors (#22257)"

This reverts commit df3e4576db.
2024-11-06 09:30:19 +00:00
Simon Lamon
0488d199ac Localize automation logbook text (#22685)
* localize automation text

* localize domain

* fixup

* split up

* fixup

* prettier
2024-11-06 09:03:41 +00:00
karwosts
df3e4576db More flexible translation keys for logbook binary sensors (#22257) 2024-11-06 10:55:17 +02:00
Paul Bottein
6bd7788815 Use grid options instead of layout options for all cards. (#22676)
* Use grid options for all cards

* Improve min columns for some cards

* Fix button and area card
2024-11-05 18:58:19 +01:00
Paul Bottein
9cdae4fea7 Bumped version to 20241105.0 2024-11-05 18:46:16 +01:00
Bram Kragten
7adf9f8526 fix height of header table rows (#22678) 2024-11-05 16:13:52 +00:00
Bram Kragten
35dcb46703 Group stream picking logic (#22674)
* Group stream picking logic

* MJPEG too

* handle errors when 1 stream type

* correct import

* change to array

* Update ha-camera-stream.ts

* Update ha-camera-stream.ts

* Update ha-camera-stream.ts

* rename
2024-11-05 14:33:34 +00:00
Bram Kragten
17db85ebad Migrate select in md-dialog to md-select (#22670)
* Migrate select in md-dialog to md-select

* Fix md-select in es5 md-dialogs

---------

Co-authored-by: Wendelin <w@pe8.at>
2024-11-05 15:24:09 +01:00
Petar Petrov
fa39595c37 Fix scanning of small QR codes with JS (#22651)
* Fix scanning of small QR codes with JS

* fix filename

* fix qr error and add device button

* fix yarn.lock
2024-11-05 16:00:05 +02:00
Bram Kragten
4db908171f Restrict webrtc logging to dev (#22671)
restrict webrtc logging to dev
2024-11-05 12:33:36 +01:00
Paulus Schoutsen
7306b8c102 Remove sections blog post link (#22663) 2024-11-05 06:07:27 +01:00
Bram Kragten
928bf3465e Bumped version to 20241104.0 2024-11-04 19:03:40 +01:00
Wendelin
0b38143765 Fix load older logs at boot 0 in error-log-card (#22657)
* Fix load older logs at boot 0 in error-log-card

* Refactor fetch boot logs in error-log-card

* Refactor download url boot logs in error-log-card
2024-11-04 16:36:25 +00:00
Bram Kragten
2f974078e0 Only show webrtc if it has video (#22659) 2024-11-04 16:34:08 +00:00
Bram Kragten
efe90fcc55 Show error when using wrong username format during onboarding (#22658) 2024-11-04 16:23:32 +00:00
renovate[bot]
01e33f5412 Update dependency webpack to v5.96.1 (#22655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 14:35:51 +01:00
Bram Kragten
51fdc484c3 Update voice wizard animations (#22656) 2024-11-04 13:11:35 +01:00
Paul Bottein
3d9fa462a6 Improve imported card container style (#22653) 2024-11-04 12:31:22 +01:00
Paul Bottein
32b5d67806 Fix create backup toggle ignored (#22652) 2024-11-04 11:27:04 +01:00
Paulus Schoutsen
20d3681da3 Delay loading IndexedDB to when first icon is requested (#22637) 2024-11-04 11:26:45 +01:00
Paulus Schoutsen
9b97274bf6 CSS Fixes for hui-energy-date-selection-card (#22640)
* CSS Fixes for hui-energy-date-selection-card

* Remove unused class

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-11-04 09:46:21 +00:00
Paulus Schoutsen
ede0dff030 CSS Fixes for ha-toast (#22639) 2024-11-04 10:29:58 +01:00
Simon Lamon
4cd4635fa5 Collection of localization issues (#22615)
* Fix wrong use of 'zero' in ICU formatted string for condition headlines

* Matter: Use setup code consistently

* Matter: Share from Google Home dialog

* Remove question format for settings toggles

* Add translation for current add-on version:" in add-on details

* Missing space

* Localize integration name not localized in single_config_entry alert

* Reword start into restart to indicate that the addon restarts when it crashes

* Rephrase rename description

* localize migrate script / automation

* Fixup script translation
2024-11-04 08:00:27 +01:00
renovate[bot]
7832219749 Update dependency @codemirror/search to v6.5.7 (#22647)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 08:51:06 +02:00
dependabot[bot]
a8d4726caf Bump relative-ci/agent-action from 2.1.12 to 2.1.13 (#22648) 2024-11-04 07:26:16 +01:00
dependabot[bot]
4b3e20c6ca Bump softprops/action-gh-release from 2.0.8 to 2.0.9 (#22649) 2024-11-04 07:25:44 +01:00
karwosts
f9a53743ce Make duration input clearable (#22614) 2024-11-04 08:25:09 +02:00
G Johansson
89250c0c01 Render preview based of entity domain (#21926)
* Render preview based of entity domain

* Add some more

* More

* return string

* Final

* Add image

* Sort

* Missing format
2024-11-04 08:07:12 +02:00
karwosts
4ef944ea08 Fix map zone focus issues (#22623) 2024-11-04 07:58:13 +02:00
renovate[bot]
5f58c183f4 Update dependency webpack to v5.96.0 (#22645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 07:44:41 +02:00
renovate[bot]
f71feff916 Update dependency core-js to v3.39.0 (#22636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 07:12:27 +01:00
renovate[bot]
50fb3b314b Update dependency mocha to v10.8.2 (#22633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 21:07:53 +01:00
renovate[bot]
06298562cd Update dependency @codemirror/autocomplete to v6.18.2 (#22632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 21:07:25 +01:00
renovate[bot]
89e74f3f07 Update dependency mocha to v10.8.1 (#22629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 22:12:25 +01:00
renovate[bot]
da96c27893 Update dependency mocha to v10.8.0 (#22627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 21:41:09 +01:00
renovate[bot]
3321dd4ca7 Update workbox monorepo to v7.3.0 (#22626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 20:21:23 +01:00
Josh McCarty
7106d56b33 Fix NFS proper name (#22620) 2024-11-01 08:53:00 +01:00
ildar170975
25cd8a9d9f Revert height=100% in horizontal-stack-card (#22617)
Update hui-horizontal-stack-card.ts
2024-10-31 20:12:32 +00:00
100 changed files with 2316 additions and 723 deletions

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send bundle stats and build information to RelativeCI
uses: relative-ci/agent-action@v2.1.12
uses: relative-ci/agent-action@v2.1.13
with:
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
token: ${{ github.token }}

View File

@@ -55,7 +55,7 @@ jobs:
script/release
- name: Upload release assets
uses: softprops/action-gh-release@v2.0.8
uses: softprops/action-gh-release@v2.0.9
with:
files: |
dist/*.whl

View File

@@ -106,6 +106,14 @@ function copyMapPanel(staticDir) {
);
}
function copyZXingWasm(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(
npmPath("zxing-wasm/dist/reader/zxing_reader.wasm"),
staticPath("js")
);
}
gulp.task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
@@ -143,6 +151,7 @@ gulp.task("copy-static-app", async () => {
copyMapPanel(staticDir);
// Qr Scanner assets
copyZXingWasm(staticDir);
copyQrScannerWorker(staticDir);
});

View File

@@ -1,16 +0,0 @@
import { html } from "lit";
import type { DemoConfig } from "../types";
export const demoLovelaceDescription: DemoConfig["description"] = (
localize
) => html`
<p>
${localize("ui.panel.page-demo.config.sections.description", {
blog_post: html`<a
href="https://www.home-assistant.io/blog/2024/03/04/dashboard-chapter-1/"
target="_blank"
>${localize("ui.panel.page-demo.config.sections.description_blog_post")}
</a>`,
})}
</p>
`;

View File

@@ -1,5 +1,4 @@
import type { DemoConfig } from "../types";
import { demoLovelaceDescription } from "./description";
import { demoEntitiesSections } from "./entities";
import { demoLovelaceSections } from "./lovelace";
@@ -7,7 +6,6 @@ export const demoSections: DemoConfig = {
authorName: "Home Assistant",
authorUrl: "https://github.com/home-assistant/frontend/",
name: "Home Demo",
description: demoLovelaceDescription,
lovelace: demoLovelaceSections,
entities: demoEntitiesSections,
theme: () => ({}),

View File

@@ -510,6 +510,7 @@ class DemoHaForm extends LitElement {
.computeError=${(error) => translations[error] || error}
.computeLabel=${(schema) =>
translations[schema.name] || schema.name}
.computeHelper=${() => "Helper text"}
@value-changed=${(e) => {
this.data[idx] = e.detail.value;
this.requestUpdate();

View File

@@ -223,7 +223,10 @@ class HassioAddonInfo extends LitElement {
<div class="description light-color">
${this.addon.version
? html`
Current version: ${this.addon.version}
${this.supervisor.localize(
"addon.dashboard.current_version",
{ version: this.addon.version }
)}
<div class="changelog" @click=${this._openChangelog}>
(<span class="changelog-link"
>${this.supervisor.localize(

View File

@@ -38,12 +38,13 @@ class HassioAddonLogDashboard extends LitElement {
@value-changed=${this._filterChanged}
.hass=${this.hass}
.filter=${this._filter}
.label=${this.hass.localize("ui.panel.config.logs.search")}
.label=${this.supervisor.localize("ui.panel.config.logs.search")}
></search-input>
</div>
<div class="content">
<error-log-card
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.header=${this.addon.name}
.provider=${this.addon.slug}
show

View File

@@ -27,22 +27,22 @@
"dependencies": {
"@babel/runtime": "7.26.0",
"@braintree/sanitize-url": "7.1.0",
"@codemirror/autocomplete": "6.18.1",
"@codemirror/autocomplete": "6.18.2",
"@codemirror/commands": "6.7.1",
"@codemirror/language": "6.10.3",
"@codemirror/legacy-modes": "6.4.1",
"@codemirror/search": "6.5.6",
"@codemirror/search": "6.5.7",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.34.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.16.1",
"@formatjs/intl-displaynames": "6.8.1",
"@formatjs/intl-getcanonicallocales": "2.5.1",
"@formatjs/intl-listformat": "7.7.1",
"@formatjs/intl-locale": "4.2.1",
"@formatjs/intl-numberformat": "8.14.1",
"@formatjs/intl-pluralrules": "5.3.1",
"@formatjs/intl-relativetimeformat": "11.4.1",
"@formatjs/intl-datetimeformat": "6.16.3",
"@formatjs/intl-displaynames": "6.8.3",
"@formatjs/intl-getcanonicallocales": "2.5.2",
"@formatjs/intl-listformat": "7.7.3",
"@formatjs/intl-locale": "4.2.3",
"@formatjs/intl-numberformat": "8.14.3",
"@formatjs/intl-pluralrules": "5.3.3",
"@formatjs/intl-relativetimeformat": "11.4.3",
"@fullcalendar/core": "6.1.15",
"@fullcalendar/daygrid": "6.1.15",
"@fullcalendar/interaction": "6.1.15",
@@ -98,10 +98,11 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"barcode-detector": "2.2.11",
"chart.js": "4.4.6",
"color-name": "2.0.0",
"comlink": "4.4.1",
"core-js": "3.38.1",
"core-js": "3.39.0",
"cropperjs": "1.6.2",
"date-fns": "4.1.0",
"date-fns-tz": "3.2.0",
@@ -114,7 +115,7 @@
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
"home-assistant-js-websocket": "9.4.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.7.3",
"intl-messageformat": "10.7.5",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
@@ -142,12 +143,12 @@
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
"workbox-cacheable-response": "7.1.0",
"workbox-core": "7.1.0",
"workbox-expiration": "7.1.0",
"workbox-precaching": "7.1.0",
"workbox-routing": "7.1.0",
"workbox-strategies": "7.1.0",
"workbox-cacheable-response": "7.3.0",
"workbox-core": "7.3.0",
"workbox-expiration": "7.3.0",
"workbox-precaching": "7.3.0",
"workbox-routing": "7.3.0",
"workbox-strategies": "7.3.0",
"xss": "1.0.15"
},
"devDependencies": {
@@ -224,7 +225,7 @@
"lodash.template": "4.5.0",
"magic-string": "0.30.12",
"map-stream": "0.0.7",
"mocha": "10.7.3",
"mocha": "10.8.2",
"object-hash": "3.0.0",
"open": "10.1.0",
"pinst": "3.0.0",
@@ -241,7 +242,7 @@
"transform-async-modules-webpack-plugin": "1.1.1",
"ts-lit-plugin": "2.0.2",
"typescript": "5.6.3",
"webpack": "5.95.0",
"webpack": "5.96.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.1.0",
"webpack-manifest-plugin": "5.0.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

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

View File

@@ -1185,6 +1185,7 @@ export class HaDataTable extends LitElement {
.group-header {
padding-top: 12px;
height: var(--data-table-row-height, 52px);
padding-left: 12px;
padding-inline-start: 12px;
padding-inline-end: initial;

View File

@@ -12,6 +12,7 @@ import {
query,
state as litState,
} from "lit/decorators";
import { classMap } from "lit/directives/class-map";
interface State {
bold: boolean;
@@ -26,12 +27,15 @@ interface State {
export class HaAnsiToHtml extends LitElement {
@property() public content!: string;
@property({ type: Boolean, attribute: "wrap-disabled" }) public wrapDisabled =
false;
@query("pre") private _pre?: HTMLPreElement;
@litState() private _filter = "";
protected render(): TemplateResult | void {
return html`<pre></pre>`;
return html`<pre class=${classMap({ wrap: !this.wrapDisabled })}></pre>`;
}
protected firstUpdated(_changedProperties: PropertyValues): void {
@@ -47,9 +51,11 @@ export class HaAnsiToHtml extends LitElement {
return css`
pre {
overflow-x: auto;
margin: 0;
}
pre.wrap {
white-space: pre-wrap;
overflow-wrap: break-word;
margin: 0;
}
.bold {
font-weight: bold;

View File

@@ -7,6 +7,8 @@ import {
type PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { computeStateName } from "../common/entity/compute_state_name";
import { supportsFeature } from "../common/entity/supports-feature";
import {
@@ -24,6 +26,13 @@ import type { HomeAssistant } from "../types";
import "./ha-hls-player";
import "./ha-web-rtc-player";
const MJPEG_STREAM = "mjpeg";
type Stream = {
type: StreamType | typeof MJPEG_STREAM;
visible: boolean;
};
@customElement("ha-camera-stream")
export class HaCameraStream extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -46,8 +55,6 @@ export class HaCameraStream extends LitElement {
@state() private _capabilities?: CameraCapabilities;
@state() private _streamType?: StreamType;
@state() private _hlsStreams?: { hasAudio: boolean; hasVideo: boolean };
@state() private _webRtcStreams?: { hasAudio: boolean; hasVideo: boolean };
@@ -55,7 +62,6 @@ export class HaCameraStream extends LitElement {
public willUpdate(changedProps: PropertyValues): void {
if (
changedProps.has("stateObj") &&
!this._shouldRenderMJPEG &&
this.stateObj &&
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
this.stateObj.entity_id
@@ -79,50 +85,63 @@ export class HaCameraStream extends LitElement {
if (!this.stateObj) {
return nothing;
}
if (__DEMO__ || this._shouldRenderMJPEG) {
const streams = this._streams(
this._capabilities?.frontend_stream_types,
this._hlsStreams,
this._webRtcStreams
);
return html`${repeat(
streams,
(stream) => stream.type + this.stateObj!.entity_id,
(stream) => this._renderStream(stream)
)}`;
}
private _renderStream(stream: Stream) {
if (!this.stateObj) {
return nothing;
}
if (stream.type === MJPEG_STREAM) {
return html`<img
.src=${__DEMO__
? this.stateObj.attributes.entity_picture!
: this._connected
? computeMJPEGStreamUrl(this.stateObj)
: ""}
: this._posterUrl || ""}
alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
/>`;
}
return html`${this._streamType === STREAM_TYPE_HLS ||
(!this._streamType &&
this._capabilities?.frontend_stream_types.includes(STREAM_TYPE_HLS))
? html`<ha-hls-player
autoplay
playsinline
.allowExoPlayer=${this.allowExoPlayer}
.muted=${this.muted}
.controls=${this.controls}
.hass=${this.hass}
.entityid=${this.stateObj.entity_id}
.posterUrl=${this._posterUrl}
@streams=${this._handleHlsStreams}
class=${!this._streamType && this._webRtcStreams ? "hidden" : ""}
></ha-hls-player>`
: nothing}
${this._streamType === STREAM_TYPE_WEB_RTC ||
(!this._streamType &&
this._capabilities?.frontend_stream_types.includes(STREAM_TYPE_WEB_RTC))
? html`<ha-web-rtc-player
autoplay
playsinline
.muted=${this.muted}
.controls=${this.controls}
.hass=${this.hass}
.entityid=${this.stateObj.entity_id}
.posterUrl=${this._posterUrl}
@streams=${this._handleWebRtcStreams}
class=${this._streamType !== STREAM_TYPE_WEB_RTC &&
!this._webRtcStreams
? "hidden"
: ""}
></ha-web-rtc-player>`
: nothing}`;
if (stream.type === STREAM_TYPE_HLS) {
return html`<ha-hls-player
autoplay
playsinline
.allowExoPlayer=${this.allowExoPlayer}
.muted=${this.muted}
.controls=${this.controls}
.hass=${this.hass}
.entityid=${this.stateObj.entity_id}
.posterUrl=${this._posterUrl}
@streams=${this._handleHlsStreams}
class=${stream.visible ? "" : "hidden"}
></ha-hls-player>`;
}
if (stream.type === STREAM_TYPE_WEB_RTC) {
return html`<ha-web-rtc-player
autoplay
playsinline
.muted=${this.muted}
.controls=${this.controls}
.hass=${this.hass}
.entityid=${this.stateObj.entity_id}
.posterUrl=${this._posterUrl}
@streams=${this._handleWebRtcStreams}
class=${stream.visible ? "" : "hidden"}
></ha-web-rtc-player>`;
}
return nothing;
}
private async _getCapabilities() {
@@ -130,35 +149,13 @@ export class HaCameraStream extends LitElement {
this._hlsStreams = undefined;
this._webRtcStreams = undefined;
if (!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)) {
this._capabilities = { frontend_stream_types: [] };
return;
}
this._capabilities = await fetchCameraCapabilities(
this.hass!,
this.stateObj!.entity_id
);
if (this._capabilities.frontend_stream_types.length === 1) {
this._streamType = this._capabilities.frontend_stream_types[0];
}
}
private get _shouldRenderMJPEG() {
if (!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)) {
// Steaming is not supported by the camera so fallback to MJPEG stream
return true;
}
if (
this._capabilities &&
(!this._capabilities.frontend_stream_types.includes(STREAM_TYPE_HLS) ||
this._hlsStreams?.hasVideo === false) &&
(!this._capabilities.frontend_stream_types.includes(
STREAM_TYPE_WEB_RTC
) ||
this._webRtcStreams?.hasVideo === false)
) {
// No video in HLS stream and no video in WebRTC stream
return true;
}
return false;
}
private async _getPosterUrl(): Promise<void> {
@@ -177,28 +174,87 @@ export class HaCameraStream extends LitElement {
private _handleHlsStreams(ev: CustomEvent) {
this._hlsStreams = ev.detail;
this._pickStreamType();
}
private _handleWebRtcStreams(ev: CustomEvent) {
this._webRtcStreams = ev.detail;
this._pickStreamType();
}
private _pickStreamType() {
if (!this._hlsStreams || !this._webRtcStreams) {
return;
private _streams = memoizeOne(
(
supportedTypes?: StreamType[],
hlsStreams?: { hasAudio: boolean; hasVideo: boolean },
webRtcStreams?: { hasAudio: boolean; hasVideo: boolean }
): Stream[] => {
if (__DEMO__) {
return [{ type: MJPEG_STREAM, visible: true }];
}
if (!supportedTypes) {
return [];
}
if (supportedTypes.length === 0) {
// doesn't support any stream type, fallback to mjpeg
return [{ type: MJPEG_STREAM, visible: true }];
}
if (supportedTypes.length === 1) {
// only 1 stream type, no need to choose
if (
(supportedTypes[0] === STREAM_TYPE_HLS &&
hlsStreams?.hasVideo === false) ||
(supportedTypes[0] === STREAM_TYPE_WEB_RTC &&
webRtcStreams?.hasVideo === false)
) {
// stream failed to load, fallback to mjpeg
return [{ type: MJPEG_STREAM, visible: true }];
}
return [{ type: supportedTypes[0], visible: true }];
}
if (hlsStreams && webRtcStreams) {
// fully loaded
if (
hlsStreams.hasVideo &&
hlsStreams.hasAudio &&
!webRtcStreams.hasAudio
) {
// webRTC stream is missing audio, use HLS
return [{ type: STREAM_TYPE_HLS, visible: true }];
}
if (webRtcStreams.hasVideo) {
return [{ type: STREAM_TYPE_WEB_RTC, visible: true }];
}
// both streams failed to load, fallback to mjpeg
return [{ type: MJPEG_STREAM, visible: true }];
}
if (hlsStreams?.hasVideo !== webRtcStreams?.hasVideo) {
// one of the two streams is loaded, or errored
// choose the one that has video or is still loading
if (hlsStreams?.hasVideo) {
return [
{ type: STREAM_TYPE_HLS, visible: true },
{ type: STREAM_TYPE_WEB_RTC, visible: false },
];
}
if (hlsStreams?.hasVideo === false) {
return [{ type: STREAM_TYPE_WEB_RTC, visible: true }];
}
if (webRtcStreams?.hasVideo) {
return [
{ type: STREAM_TYPE_WEB_RTC, visible: true },
{ type: STREAM_TYPE_HLS, visible: false },
];
}
if (webRtcStreams?.hasVideo === false) {
return [{ type: STREAM_TYPE_HLS, visible: true }];
}
}
return [
{ type: STREAM_TYPE_HLS, visible: true },
{ type: STREAM_TYPE_WEB_RTC, visible: false },
];
}
if (
this._hlsStreams.hasVideo &&
this._hlsStreams.hasAudio &&
!this._webRtcStreams.hasAudio
) {
this._streamType = STREAM_TYPE_HLS;
} else if (this._webRtcStreams.hasVideo) {
this._streamType = STREAM_TYPE_WEB_RTC;
}
}
);
static get styles(): CSSResultGroup {
return css`

View File

@@ -43,6 +43,7 @@ class HaDurationInput extends LitElement {
.label=${this.label}
.helper=${this.helper}
.required=${this.required}
.clearable=${!this.required && this.data !== undefined}
.autoValidate=${this.required}
.disabled=${this.disabled}
errorMessage="Required"
@@ -67,50 +68,79 @@ class HaDurationInput extends LitElement {
}
private get _days() {
return this.data?.days ? Number(this.data.days) : 0;
return this.data?.days
? Number(this.data.days)
: this.required || this.data
? 0
: NaN;
}
private get _hours() {
return this.data?.hours ? Number(this.data.hours) : 0;
return this.data?.hours
? Number(this.data.hours)
: this.required || this.data
? 0
: NaN;
}
private get _minutes() {
return this.data?.minutes ? Number(this.data.minutes) : 0;
return this.data?.minutes
? Number(this.data.minutes)
: this.required || this.data
? 0
: NaN;
}
private get _seconds() {
return this.data?.seconds ? Number(this.data.seconds) : 0;
return this.data?.seconds
? Number(this.data.seconds)
: this.required || this.data
? 0
: NaN;
}
private get _milliseconds() {
return this.data?.milliseconds ? Number(this.data.milliseconds) : 0;
return this.data?.milliseconds
? Number(this.data.milliseconds)
: this.required || this.data
? 0
: NaN;
}
private _durationChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) {
private _durationChanged(ev: CustomEvent<{ value?: TimeChangedEvent }>) {
ev.stopPropagation();
const value = { ...ev.detail.value };
const value = ev.detail.value ? { ...ev.detail.value } : undefined;
if (!this.enableMillisecond && !value.milliseconds) {
// @ts-ignore
delete value.milliseconds;
} else if (value.milliseconds > 999) {
value.seconds += Math.floor(value.milliseconds / 1000);
value.milliseconds %= 1000;
}
if (value) {
value.hours ||= 0;
value.minutes ||= 0;
value.seconds ||= 0;
if (value.seconds > 59) {
value.minutes += Math.floor(value.seconds / 60);
value.seconds %= 60;
}
if ("days" in value) value.days ||= 0;
if ("milliseconds" in value) value.milliseconds ||= 0;
if (value.minutes > 59) {
value.hours += Math.floor(value.minutes / 60);
value.minutes %= 60;
}
if (!this.enableMillisecond && !value.milliseconds) {
// @ts-ignore
delete value.milliseconds;
} else if (value.milliseconds > 999) {
value.seconds += Math.floor(value.milliseconds / 1000);
value.milliseconds %= 1000;
}
if (this.enableDay && value.hours > 24) {
value.days = (value.days ?? 0) + Math.floor(value.hours / 24);
value.hours %= 24;
if (value.seconds > 59) {
value.minutes += Math.floor(value.seconds / 60);
value.seconds %= 60;
}
if (value.minutes > 59) {
value.hours += Math.floor(value.minutes / 60);
value.minutes %= 60;
}
if (this.enableDay && value.hours > 24) {
value.days = (value.days ?? 0) + Math.floor(value.hours / 24);
value.hours %= 24;
}
}
fireEvent(this, "value-changed", {

View File

@@ -1,6 +1,6 @@
import "@material/mwc-formfield";
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type {
@@ -19,6 +19,8 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
@property() public label!: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@query("ha-checkbox", true) private _input?: HTMLElement;
@@ -37,6 +39,12 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
.disabled=${this.disabled}
@change=${this._valueChanged}
></ha-checkbox>
<span slot="label">
<p class="primary">${this.label}</p>
${this.helper
? html`<p class="secondary">${this.helper}</p>`
: nothing}
</span>
</mwc-formfield>
`;
}
@@ -46,6 +54,28 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
value: (ev.target as HaCheckbox).checked,
});
}
static get styles(): CSSResultGroup {
return css`
ha-formfield {
display: flex;
min-height: 56px;
align-items: center;
--mdc-typography-body2-font-size: 1em;
}
p {
margin: 0;
}
.secondary {
direction: var(--direction);
padding-top: 4px;
box-sizing: border-box;
color: var(--secondary-text-color);
font-size: 0.875rem;
font-weight: var(--mdc-typography-body2-font-weight, 400);
}
`;
}
}
declare global {

View File

@@ -8,7 +8,6 @@ import { customIcons } from "../data/custom_icons";
import type { Chunks, Icons } from "../data/iconsets";
import {
MDI_PREFIXES,
checkCacheVersion,
findIconChunk,
getIcon,
writeCache,
@@ -26,11 +25,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {};
const chunks: Chunks = {};
// Supervisor doesn't use icons, and should not update/downgrade the icon DB.
if (!__SUPERVISOR__) {
checkCacheVersion();
}
const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
const cachedIcons: Record<string, string> = {};

View File

@@ -182,6 +182,10 @@ export class HaMdDialog extends MdDialog {
display: contents;
}
.scroller {
overflow: var(--dialog-content-overflow, auto);
}
slot[name="content"]::slotted(*) {
padding: var(--dialog-content-padding, 24px);
}

View File

@@ -0,0 +1,26 @@
import { MdSelectOption } from "@material/web/select/select-option";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-md-select-option")
export class HaMdSelectOption extends MdSelectOption {
static override styles = [
...super.styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-select-option": HaMdSelectOption;
}
}

View File

@@ -0,0 +1,32 @@
import { MdFilledSelect } from "@material/web/select/filled-select";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-md-select")
export class HaMdSelect extends MdFilledSelect {
static override styles = [
...super.styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-surface-container-highest: var(--input-fill-color);
--md-sys-color-on-surface: var(--input-ink-color);
--md-sys-color-surface-container: var(--input-fill-color);
--md-sys-color-secondary-container: var(--input-fill-color);
--md-menu-container-color: var(--card-background-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-select": HaMdSelect;
}
}

View File

@@ -4,6 +4,11 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
// The BarcodeDetector Web API is not yet supported in all browsers,
// and "qr-scanner" defaults to a suboptimal implementation if it is not available.
// The following import makes a better implementation available that is based on a
// WebAssembly port of ZXing:
import { setZXingModuleOverrides } from "barcode-detector";
import type QrScanner from "qr-scanner";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
@@ -16,6 +21,15 @@ import "./ha-list-item";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
setZXingModuleOverrides({
locateFile: (path: string, prefix: string) => {
if (path.endsWith(".wasm")) {
return "/static/js/zxing_reader.wasm";
}
return prefix + path;
},
});
@customElement("ha-qr-scanner")
class HaQrScanner extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -174,7 +188,7 @@ class HaQrScanner extends LitElement {
}
private _qrCodeError = (err: any) => {
if (err === "No QR code found") {
if (err.endsWith("No QR code found")) {
this._qrNotFoundCount++;
if (this._qrNotFoundCount === 250) {
this._reportError(err);

View File

@@ -24,7 +24,7 @@ export class HaToast extends Snackbar {
max-width: 650px;
}
// Revert the default styles set by mwc-snackbar
/* Revert the default styles set by mwc-snackbar */
@media (max-width: 480px), (max-width: 344px) {
.mdc-snackbar__surface {
min-width: inherit;

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement } from "lit";
@@ -108,18 +107,18 @@ class HaWebRtcPlayer extends LitElement {
return;
}
console.time("WebRTC");
this._error = undefined;
console.timeLog("WebRTC", "start clientConfig");
this._startTimer();
this._logEvent("start clientConfig");
this._clientConfig = await fetchWebRtcClientConfiguration(
this.hass,
this.entityid
);
console.timeLog("WebRTC", "end clientConfig", this._clientConfig);
this._logEvent("end clientConfig", this._clientConfig);
this._peerConnection = new RTCPeerConnection(
this._clientConfig.configuration
@@ -141,11 +140,10 @@ class HaWebRtcPlayer extends LitElement {
this._peerConnection.onsignalingstatechange = (ev) => {
switch ((ev.target as RTCPeerConnection).signalingState) {
case "stable":
console.timeLog("WebRTC", "ICE negotiation complete");
this._logEvent("ICE negotiation complete");
break;
default:
console.timeLog(
"WebRTC",
this._logEvent(
"Signaling state changed",
(ev.target as RTCPeerConnection).signalingState
);
@@ -170,7 +168,7 @@ class HaWebRtcPlayer extends LitElement {
offerToReceiveVideo: true,
};
console.timeLog("WebRTC", "start createOffer", offerOptions);
this._logEvent("start createOffer", offerOptions);
const offer: RTCSessionDescriptionInit =
await this._peerConnection.createOffer(offerOptions);
@@ -179,9 +177,9 @@ class HaWebRtcPlayer extends LitElement {
return;
}
console.timeLog("WebRTC", "end createOffer", offer);
this._logEvent("end createOffer", offer);
console.timeLog("WebRTC", "start setLocalDescription");
this._logEvent("start setLocalDescription");
await this._peerConnection.setLocalDescription(offer);
@@ -189,7 +187,7 @@ class HaWebRtcPlayer extends LitElement {
return;
}
console.timeLog("WebRTC", "end setLocalDescription");
this._logEvent("end setLocalDescription");
let candidates = "";
@@ -203,11 +201,7 @@ class HaWebRtcPlayer extends LitElement {
resolve();
}
console.timeLog(
"WebRTC",
"Ice gathering state changed",
iceGatheringState
);
this._logEvent("Ice gathering state changed", iceGatheringState);
};
});
@@ -225,7 +219,7 @@ class HaWebRtcPlayer extends LitElement {
const offer_sdp = offer.sdp! + candidates;
console.timeLog("WebRTC", "start webRtcOffer", offer_sdp);
this._logEvent("start webRtcOffer", offer_sdp);
try {
this._unsub = webRtcOffer(this.hass, this.entityid, offer_sdp, (event) =>
@@ -238,8 +232,7 @@ class HaWebRtcPlayer extends LitElement {
};
private _iceConnectionStateChanged = () => {
console.timeLog(
"WebRTC",
this._logEvent(
"ice connection state change",
this._peerConnection?.iceConnectionState
);
@@ -265,18 +258,19 @@ class HaWebRtcPlayer extends LitElement {
this._candidatesList = [];
}
if (event.type === "answer") {
console.timeLog("WebRTC", "answer", event.answer);
this._logEvent("answer", event.answer);
this._handleAnswer(event);
}
if (event.type === "candidate") {
console.timeLog("WebRTC", "remote ice candidate", event.candidate);
this._logEvent("remote ice candidate", event.candidate);
try {
await this._peerConnection?.addIceCandidate(
new RTCIceCandidate({ candidate: event.candidate, sdpMid: "0" })
);
} catch (err: any) {
// eslint-disable-next-line no-console
console.error(err);
}
}
@@ -291,11 +285,7 @@ class HaWebRtcPlayer extends LitElement {
return;
}
console.timeLog(
"WebRTC",
"local ice candidate",
event.candidate?.candidate
);
this._logEvent("local ice candidate", event.candidate?.candidate);
if (this._sessionId) {
addWebRtcCandidate(
@@ -334,19 +324,16 @@ class HaWebRtcPlayer extends LitElement {
sdp: event.answer,
});
try {
console.timeLog("WebRTC", "start setRemoteDescription", remoteDesc);
this._logEvent("start setRemoteDescription", remoteDesc);
await this._peerConnection.setRemoteDescription(remoteDesc);
} catch (err: any) {
this._error = "Failed to connect WebRTC stream: " + err.message;
this._cleanUp();
}
console.timeLog("WebRTC", "end setRemoteDescription");
this._logEvent("end setRemoteDescription");
}
private _cleanUp() {
console.timeLog("WebRTC", "stopped");
console.timeEnd("WebRTC");
if (this._remoteStream) {
this._remoteStream.getTracks().forEach((track) => {
track.stop();
@@ -372,6 +359,9 @@ class HaWebRtcPlayer extends LitElement {
this._peerConnection.onsignalingstatechange = null;
this._peerConnection = undefined;
this._logEvent("stopped");
this._stopTimer();
}
this._unsub?.then((unsub) => unsub());
this._unsub = undefined;
@@ -380,17 +370,43 @@ class HaWebRtcPlayer extends LitElement {
}
private _loadedData() {
console.timeLog("WebRTC", "loadedData");
console.timeEnd("WebRTC");
const video = this._videoEl;
const stream = video.srcObject as MediaStream;
fireEvent(this, "load");
fireEvent(this, "streams", {
const data = {
hasAudio: Boolean(stream?.getAudioTracks().length),
hasVideo: Boolean(stream?.getVideoTracks().length),
});
};
fireEvent(this, "load");
fireEvent(this, "streams", data);
this._logEvent("loadedData", data);
this._stopTimer();
}
private _startTimer() {
if (!__DEV__) {
return;
}
// eslint-disable-next-line no-console
console.time("WebRTC");
}
private _stopTimer() {
if (!__DEV__) {
return;
}
// eslint-disable-next-line no-console
console.timeEnd("WebRTC");
}
private _logEvent(msg: string, ...args: unknown[]) {
if (!__DEV__) {
return;
}
// eslint-disable-next-line no-console
console.timeLog("WebRTC", msg, ...args);
}
static get styles(): CSSResultGroup {

View File

@@ -86,6 +86,8 @@ export class HaMap extends ReactiveElement {
private _mapZones: Array<Marker | Circle> = [];
private _mapFocusZones: Array<Marker | Circle> = [];
private _mapPaths: Array<Polyline | CircleMarker> = [];
public connectedCallback(): void {
@@ -201,7 +203,11 @@ export class HaMap extends ReactiveElement {
return;
}
if (!this._mapFocusItems.length && !this.layers?.length) {
if (
!this._mapFocusItems.length &&
!this._mapFocusZones.length &&
!this.layers?.length
) {
this.leafletMap.setView(
new this.Leaflet.LatLng(
this.hass.config.latitude,
@@ -218,13 +224,9 @@ export class HaMap extends ReactiveElement {
: []
);
if (this.fitZones) {
this._mapZones?.forEach((zone) => {
bounds.extend(
"getBounds" in zone ? zone.getBounds() : zone.getLatLng()
);
});
}
this._mapFocusZones?.forEach((zone) => {
bounds.extend("getBounds" in zone ? zone.getBounds() : zone.getLatLng());
});
this.layers?.forEach((layer: any) => {
bounds.extend(
@@ -395,6 +397,7 @@ export class HaMap extends ReactiveElement {
if (this._mapZones.length) {
this._mapZones.forEach((marker) => marker.remove());
this._mapZones = [];
this._mapFocusZones = [];
}
if (!this.entities) {
@@ -466,13 +469,18 @@ export class HaMap extends ReactiveElement {
);
// create circle around it
this._mapZones.push(
Leaflet.circle([latitude, longitude], {
interactive: false,
color: passive ? passiveZoneColor : zoneColor,
radius,
})
);
const circle = Leaflet.circle([latitude, longitude], {
interactive: false,
color: passive ? passiveZoneColor : zoneColor,
radius,
});
this._mapZones.push(circle);
if (
this.fitZones &&
(typeof entity === "string" || entity.focus !== false)
) {
this._mapFocusZones.push(circle);
}
continue;
}

View File

@@ -26,7 +26,10 @@ export class HaTraceLogbook extends LitElement {
.entries=${this.logbookEntries}
.narrow=${this.narrow}
></ha-logbook-renderer>
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
<hat-logbook-note
.hass=${this.hass}
.domain=${this.trace.domain}
></hat-logbook-note>
`
: html`<div class="padded-box">
No Logbook entries found for this step.

View File

@@ -291,7 +291,10 @@ export class HaTracePathDetails extends LitElement {
.entries=${entries}
.narrow=${this.narrow}
></ha-logbook-renderer>
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
<hat-logbook-note
.hass=${this.hass}
.domain=${this.trace.domain}
></hat-logbook-note>
`
: html`<div class="padded-box">
${this.hass!.localize(

View File

@@ -28,7 +28,10 @@ export class HaTraceTimeline extends LitElement {
allowPick
>
</hat-trace-timeline>
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
<hat-logbook-note
.hass=${this.hass}
.domain=${this.trace.domain}
></hat-logbook-note>
`;
}

View File

@@ -1,14 +1,22 @@
import { css, html, LitElement } from "lit";
import { css, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
@customElement("hat-logbook-note")
class HatLogbookNote extends LitElement {
@property() public domain = "automation";
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public domain: "automation" | "script" = "automation";
render() {
return html`
Not all shown logbook entries might be related to this ${this.domain}.
`;
if (this.domain === "script") {
return this.hass.localize(
"ui.panel.config.automation.trace.messages.not_all_entries_are_related_script_note"
);
}
return this.hass.localize(
"ui.panel.config.automation.trace.messages.not_all_entries_are_related_automation_note"
);
}
static styles = css`

View File

@@ -193,7 +193,7 @@ export const fetchHassioLogs = async (
) =>
hass.callApiRaw(
"GET",
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs/boots/${boot}`,
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs${boot !== 0 ? `/boots/${boot}` : ""}`,
undefined,
range
? {
@@ -203,20 +203,6 @@ export const fetchHassioLogs = async (
);
export const fetchHassioLogsFollow = async (
hass: HomeAssistant,
provider: string,
signal: AbortSignal,
lines = 100
) =>
hass.callApiRaw(
"GET",
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs/follow?lines=${lines}`,
undefined,
undefined,
signal
);
export const fetchHassioLogsBootFollow = async (
hass: HomeAssistant,
provider: string,
signal: AbortSignal,
@@ -225,7 +211,7 @@ export const fetchHassioLogsBootFollow = async (
) =>
hass.callApiRaw(
"GET",
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs/boots/${boot}/follow?lines=${lines}`,
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs${boot !== 0 ? `/boots/${boot}` : ""}/follow?lines=${lines}`,
undefined,
undefined,
signal
@@ -236,19 +222,14 @@ export const getHassioLogDownloadUrl = (provider: string) =>
provider.includes("_") ? `addons/${provider}` : provider
}/logs`;
export const getHassioLogDownloadLinesUrl = (provider: string, lines: number) =>
`/api/hassio/${
provider.includes("_") ? `addons/${provider}` : provider
}/logs?lines=${lines}`;
export const getHassioLogBootDownloadLinesUrl = (
export const getHassioLogDownloadLinesUrl = (
provider: string,
lines: number,
boot = 0
) =>
`/api/hassio/${
provider.includes("_") ? `addons/${provider}` : provider
}/logs/boots/${boot}?lines=${lines}`;
}/logs${boot !== 0 ? `/boots/${boot}` : ""}?lines=${lines}`;
export const setSupervisorOption = async (
hass: HomeAssistant,

View File

@@ -1,4 +1,5 @@
import { clear, get, set, createStore, promisifyRequest } from "idb-keyval";
import memoizeOne from "memoize-one";
import { promiseTimeout } from "../common/util/promise-timeout";
import { iconMetadata } from "../resources/icon-metadata";
import type { IconMeta } from "../types";
@@ -11,7 +12,23 @@ export interface Chunks {
[key: string]: Promise<Icons>;
}
export const iconStore = createStore("hass-icon-db", "mdi-icon-store");
const getStore = memoizeOne(async () => {
const iconStore = createStore("hass-icon-db", "mdi-icon-store");
// Supervisor doesn't use icons, and should not update/downgrade the icon DB.
if (!__SUPERVISOR__) {
const version = await get("_version", iconStore);
if (!version) {
set("_version", iconMetadata.version, iconStore);
} else if (version !== iconMetadata.version) {
await clear(iconStore);
set("_version", iconMetadata.version, iconStore);
}
}
return iconStore;
});
export const MDI_PREFIXES = ["mdi", "hass", "hassio", "hademo"];
@@ -28,7 +45,10 @@ export const getIcon = (iconName: string) =>
return;
}
const readIcons = () =>
// Start initializing the store, so it's ready when we need it
const iconStoreProm = getStore();
const readIcons = async () => {
const iconStore = await iconStoreProm;
iconStore("readonly", (store) => {
for (const [iconName_, resolve_, reject_] of toRead) {
promisifyRequest<string | undefined>(store.get(iconName_))
@@ -37,6 +57,7 @@ export const getIcon = (iconName: string) =>
}
toRead = [];
});
};
promiseTimeout(1000, readIcons()).catch((e) => {
// Firefox in private mode doesn't support IDB
@@ -62,6 +83,7 @@ export const findIconChunk = (icon: string): string => {
export const writeCache = async (chunks: Chunks) => {
const keys = Object.keys(chunks);
const iconsSets: Icons[] = await Promise.all(Object.values(chunks));
const iconStore = await getStore();
// We do a batch opening the store just once, for (considerable) performance
iconStore("readwrite", (store) => {
iconsSets.forEach((icons, idx) => {
@@ -72,14 +94,3 @@ export const writeCache = async (chunks: Chunks) => {
});
});
};
export const checkCacheVersion = async () => {
const version = await get("_version", iconStore);
if (!version) {
set("_version", iconMetadata.version, iconStore);
} else if (version !== iconMetadata.version) {
await clear(iconStore);
set("_version", iconMetadata.version, iconStore);
}
};

View File

@@ -209,6 +209,17 @@ export interface ZWaveJSNodeStatus {
has_firmware_update_cc: boolean;
}
export type ZWaveJSNodeCapabilities = {
[endpoint: number]: ZWaveJSEndpointCapability[];
};
export interface ZWaveJSEndpointCapability {
id: number;
name: string;
version: number;
is_secure: boolean;
}
export interface ZwaveJSNodeMetadata {
node_id: number;
exclusion: string;
@@ -404,6 +415,25 @@ export interface RequestedGrant {
clientSideAuth: boolean;
}
export const invokeZWaveCCApi = (
hass: HomeAssistant,
device_id: string,
command_class: number,
endpoint: number | undefined,
method_name: string,
parameters: any[],
wait_for_result?: boolean
): Promise<unknown> =>
hass.callWS({
type: "zwave_js/invoke_cc_api",
device_id,
command_class,
endpoint,
method_name,
parameters,
wait_for_result,
});
export const fetchZwaveNetworkStatus = (
hass: HomeAssistant,
device_or_entry_id: {
@@ -579,6 +609,15 @@ export const fetchZwaveNodeStatus = (
device_id,
});
export const fetchZwaveNodeCapabilities = (
hass: HomeAssistant,
device_id: string
): Promise<ZWaveJSNodeCapabilities> =>
hass.callWS({
type: "zwave_js/node_capabilities",
device_id,
});
export const subscribeZwaveNodeStatus = (
hass: HomeAssistant,
device_id: string,

View File

@@ -1,10 +1,24 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { format } from "date-fns";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-climate-state";
import "../../../components/ha-cover-controls";
import "../../../components/ha-cover-tilt-controls";
import "../../../components/ha-date-input";
import "../../../components/ha-humidifier-state";
import "../../../components/ha-select";
import "../../../components/ha-slider";
import "../../../components/ha-time-input";
import "../../../components/entity/ha-entity-toggle";
import "../../../components/entity/state-badge";
import { isTiltOnly } from "../../../data/cover";
import { isUnavailableState } from "../../../data/entity";
import type { ImageEntity } from "../../../data/image";
import { computeImageUrl } from "../../../data/image";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
import "../../../panels/lovelace/components/hui-timestamp-display";
import type { HomeAssistant } from "../../../types";
@@ -28,18 +42,7 @@ class EntityPreviewRow extends LitElement {
<div class="name" .title=${computeStateName(stateObj)}>
${computeStateName(stateObj)}
</div>
<div class="value">
${stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
!isUnavailableState(stateObj.state)
? html`
<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
capitalize
></hui-timestamp-display>
`
: this.hass.formatEntityState(stateObj)}
</div>`;
<div class="value">${this.renderEntityState(stateObj)}</div>`;
}
static get styles(): CSSResultGroup {
@@ -59,8 +62,308 @@ class EntityPreviewRow extends LitElement {
.value {
direction: ltr;
}
.numberflex {
display: flex;
align-items: center;
justify-content: flex-end;
flex-grow: 2;
}
.numberstate {
min-width: 45px;
text-align: end;
}
ha-textfield {
text-align: end;
direction: ltr !important;
}
ha-slider {
width: 100%;
max-width: 200px;
}
ha-time-input {
margin-left: 4px;
margin-inline-start: 4px;
margin-inline-end: initial;
direction: var(--direction);
}
.datetimeflex {
display: flex;
justify-content: flex-end;
width: 100%;
}
mwc-button {
margin-right: -0.57em;
margin-inline-end: -0.57em;
margin-inline-start: initial;
}
img {
display: block;
width: 100%;
}
`;
}
private renderEntityState(stateObj: HassEntity): TemplateResult | string {
const domain = stateObj.entity_id.split(".", 1)[0];
if (domain === "button") {
return html`
<mwc-button .disabled=${isUnavailableState(stateObj.state)}>
${this.hass.localize("ui.card.button.press")}
</mwc-button>
`;
}
const climateDomains = ["climate", "water_heater"];
if (climateDomains.includes(domain)) {
return html`
<ha-climate-state .hass=${this.hass} .stateObj=${stateObj}>
</ha-climate-state>
`;
}
if (domain === "cover") {
return html`
${isTiltOnly(stateObj)
? html`
<ha-cover-tilt-controls
.hass=${this.hass}
.stateObj=${stateObj}
></ha-cover-tilt-controls>
`
: html`
<ha-cover-controls
.hass=${this.hass}
.stateObj=${stateObj}
></ha-cover-controls>
`}
`;
}
if (domain === "date") {
return html`
<ha-date-input
.locale=${this.hass.locale}
.disabled=${isUnavailableState(stateObj.state)}
.value=${isUnavailableState(stateObj.state)
? undefined
: stateObj.state}
>
</ha-date-input>
`;
}
if (domain === "datetime") {
const dateObj = isUnavailableState(stateObj.state)
? undefined
: new Date(stateObj.state);
const time = dateObj ? format(dateObj, "HH:mm:ss") : undefined;
const date = dateObj ? format(dateObj, "yyyy-MM-dd") : undefined;
return html`
<div class="datetimeflex">
<ha-date-input
.label=${computeStateName(stateObj)}
.locale=${this.hass.locale}
.value=${date}
.disabled=${isUnavailableState(stateObj.state)}
>
</ha-date-input>
<ha-time-input
.value=${time}
.disabled=${isUnavailableState(stateObj.state)}
.locale=${this.hass.locale}
></ha-time-input>
</div>
`;
}
if (domain === "event") {
return html`
<div class="when">
${isUnavailableState(stateObj.state)
? this.hass.formatEntityState(stateObj)
: html`<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
capitalize
></hui-timestamp-display>`}
</div>
<div class="what">
${isUnavailableState(stateObj.state)
? nothing
: this.hass.formatEntityAttributeValue(stateObj, "event_type")}
</div>
`;
}
const toggleDomains = ["fan", "light", "remote", "siren", "switch"];
if (toggleDomains.includes(domain)) {
const showToggle =
stateObj.state === "on" ||
stateObj.state === "off" ||
isUnavailableState(stateObj.state);
return html`
${showToggle
? html`
<ha-entity-toggle
.hass=${this.hass}
.stateObj=${stateObj}
></ha-entity-toggle>
`
: this.hass.formatEntityState(stateObj)}
`;
}
if (domain === "humidifier") {
return html`
<ha-humidifier-state .hass=${this.hass} .stateObj=${stateObj}>
</ha-humidifier-state>
`;
}
if (domain === "image") {
const image: string = computeImageUrl(stateObj as ImageEntity);
return html`
<img
alt=${ifDefined(stateObj?.attributes.friendly_name)}
src=${this.hass.hassUrl(image)}
/>
`;
}
if (domain === "lock") {
return html`
<mwc-button
.disabled=${isUnavailableState(stateObj.state)}
class="text-content"
>
${stateObj.state === "locked"
? this.hass!.localize("ui.card.lock.unlock")
: this.hass!.localize("ui.card.lock.lock")}
</mwc-button>
`;
}
if (domain === "number") {
const showNumberSlider =
stateObj.attributes.mode === "slider" ||
(stateObj.attributes.mode === "auto" &&
(Number(stateObj.attributes.max) - Number(stateObj.attributes.min)) /
Number(stateObj.attributes.step) <=
256);
return html`
${showNumberSlider
? html`
<div class="numberflex">
<ha-slider
labeled
.disabled=${isUnavailableState(stateObj.state)}
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
.max=${Number(stateObj.attributes.max)}
.value=${Number(stateObj.state)}
></ha-slider>
<span class="state">
${this.hass.formatEntityState(stateObj)}
</span>
</div>
`
: html` <div class="numberflex numberstate">
<ha-textfield
autoValidate
.disabled=${isUnavailableState(stateObj.state)}
pattern="[0-9]+([\\.][0-9]+)?"
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
.max=${Number(stateObj.attributes.max)}
.value=${stateObj.state}
.suffix=${stateObj.attributes.unit_of_measurement}
type="number"
></ha-textfield>
</div>`}
`;
}
if (domain === "select") {
return html`
<ha-select
.label=${computeStateName(stateObj)}
.value=${stateObj.state}
.disabled=${isUnavailableState(stateObj.state)}
naturalMenuWidth
>
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) => html`
<mwc-list-item .value=${option}>
${this.hass!.formatEntityState(stateObj, option)}
</mwc-list-item>
`
)
: ""}
</ha-select>
`;
}
if (domain === "sensor") {
const showSensor =
stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
!isUnavailableState(stateObj.state);
return html`
${showSensor
? html`
<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
capitalize
></hui-timestamp-display>
`
: this.hass.formatEntityState(stateObj)}
`;
}
if (domain === "text") {
return html`
<ha-textfield
.label=${computeStateName(stateObj)}
.disabled=${isUnavailableState(stateObj.state)}
.value=${stateObj.state}
.minlength=${stateObj.attributes.min}
.maxlength=${stateObj.attributes.max}
.autoValidate=${stateObj.attributes.pattern}
.pattern=${stateObj.attributes.pattern}
.type=${stateObj.attributes.mode}
placeholder=${this.hass!.localize("ui.card.text.emtpy_value")}
></ha-textfield>
`;
}
if (domain === "time") {
return html`
<ha-time-input
.value=${isUnavailableState(stateObj.state)
? undefined
: stateObj.state}
.locale=${this.hass.locale}
.disabled=${isUnavailableState(stateObj.state)}
></ha-time-input>
`;
}
if (domain === "weather") {
return html`
<div>
${isUnavailableState(stateObj.state) ||
stateObj.attributes.temperature === undefined ||
stateObj.attributes.temperature === null
? this.hass.formatEntityState(stateObj)
: this.hass.formatEntityAttributeValue(stateObj, "temperature")}
</div>
`;
}
return this.hass.formatEntityState(stateObj);
}
}
declare global {

View File

@@ -52,59 +52,61 @@ class MoreInfoUpdate extends LitElement {
return html`
<div class="content">
${this.stateObj.attributes.in_progress
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
this.stateObj.attributes.update_percentage !== null
? html`<mwc-linear-progress
.progress=${this.stateObj.attributes.update_percentage / 100}
buffer=""
></mwc-linear-progress>`
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
: nothing}
<h3>${this.stateObj.attributes.title}</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"installed_version"
)}
<div class="summary">
${this.stateObj.attributes.in_progress
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
this.stateObj.attributes.update_percentage !== null
? html`<mwc-linear-progress
.progress=${this.stateObj.attributes.update_percentage / 100}
buffer=""
></mwc-linear-progress>`
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
: nothing}
<h3>${this.stateObj.attributes.title}</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"installed_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.installed_version ??
this.hass.localize("state.default.unavailable")}
</div>
</div>
<div class="value">
${this.stateObj.attributes.installed_version ??
this.hass.localize("state.default.unavailable")}
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"latest_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.latest_version ??
this.hass.localize("state.default.unavailable")}
</div>
</div>
</div>
<div class="row">
<div class="key">
${this.hass.formatEntityAttributeName(
this.stateObj,
"latest_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.latest_version ??
this.hass.localize("state.default.unavailable")}
</div>
</div>
${this.stateObj.attributes.release_url
? html`<div class="row">
<div class="key">
<a
href=${this.stateObj.attributes.release_url}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.dialogs.more_info_control.update.release_announcement"
)}
</a>
</div>
</div>`
: nothing}
${this.stateObj.attributes.release_url
? html`<div class="row">
<div class="key">
<a
href=${this.stateObj.attributes.release_url}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.dialogs.more_info_control.update.release_announcement"
)}
</a>
</div>
</div>`
: nothing}
</div>
${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) &&
!this._error
? this._releaseNotes === undefined
@@ -143,7 +145,7 @@ class MoreInfoUpdate extends LitElement {
)}
</span>
<ha-switch
id="create_backup"
id="create-backup"
checked
.disabled=${updateIsInstalling(this.stateObj)}
></ha-switch>
@@ -293,6 +295,11 @@ class MoreInfoUpdate extends LitElement {
ha-expansion-panel {
margin: 16px 0;
}
.summary {
margin-bottom: 16px;
}
.row {
margin: 0;
display: flex;
@@ -308,7 +315,9 @@ class MoreInfoUpdate extends LitElement {
);
position: sticky;
bottom: 0;
margin: 0 -24px -24px -24px;
margin: 0 -24px 0 -24px;
margin-bottom: calc(-1 * max(env(safe-area-inset-bottom), 24px));
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
display: flex;
flex-direction: column;

View File

@@ -66,6 +66,8 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
private _dialogClosed() {
this._params = undefined;
this._assistConfiguration = undefined;
this._previousSteps = [];
this._nextStep = undefined;
this._step = STEP.INIT;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}

View File

@@ -17,7 +17,7 @@ export class HaVoiceAssistantSetupStepArea extends LitElement {
const device = this.hass.devices[this.deviceId];
return html`<div class="content">
<img src="/static/images/voice-assistant/area.gif" />
<img src="/static/images/voice-assistant/area.png" />
<h1>Select area</h1>
<p class="secondary">
When you voice assistant knows where it is, it can better control the

View File

@@ -21,7 +21,7 @@ export class HaVoiceAssistantSetupStepChangeWakeWord extends LitElement {
protected override render() {
return html`<div class="padding content">
<img src="/static/images/voice-assistant/change-wake-word.gif" />
<img src="/static/images/voice-assistant/change-wake-word.png" />
<h1>Change wake word</h1>
<p class="secondary">
Some wake words are better for

View File

@@ -6,6 +6,7 @@ import "../../components/ha-circular-progress";
import { testAssistSatelliteConnection } from "../../data/assist_satellite";
import type { HomeAssistant } from "../../types";
import { AssistantSetupStyles } from "./styles";
import { documentationUrl } from "../../util/documentation-url";
@customElement("ha-voice-assistant-setup-step-check")
export class HaVoiceAssistantSetupStepCheck extends LitElement {
@@ -35,7 +36,7 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
protected override render() {
return html`<div class="content">
${this._status === "timeout"
? html`<img src="/static/images/voice-assistant/error.gif" />
? html`<img src="/static/images/voice-assistant/error.png" />
<h1>The voice assistant is unable to connect to Home Assistant</h1>
<p class="secondary">
To play audio, the voice assistant device has to connect to Home
@@ -44,12 +45,15 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
</p>
<div class="footer">
<a
href="https://www.home-assistant.io/docs/configuration/remote/#adding-a-remote-url-to-home-assistant"
href=${documentationUrl(
this.hass,
"/voice_control/troubleshooting/#i-dont-get-a-voice-response"
)}
><ha-button>Help me</ha-button></a
>
<ha-button @click=${this._testConnection}>Retry</ha-button>
</div>`
: html`<img src="/static/images/voice-assistant/hi.gif" />
: html`<img src="/static/images/voice-assistant/hi.png" />
<h1>Hi</h1>
<p class="secondary">
Over the next couple steps we're going to personalize your voice

View File

@@ -67,7 +67,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
: undefined;
return html`<div class="content">
<img src="/static/images/voice-assistant/heart.gif" />
<img src="/static/images/voice-assistant/heart.png" />
<h1>Ready to Assist!</h1>
<p class="secondary">
Make any final customizations here. You can always change these in the

View File

@@ -65,7 +65,7 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement {
const progressIsNumeric = stateObj && updateUsesProgress(stateObj);
return html`<div class="content">
<img src="/static/images/voice-assistant/update.gif" />
<img src="/static/images/voice-assistant/update.png" />
<h1>
${stateObj &&
(stateObj.state === "unavailable" || updateIsInstalling(stateObj))

View File

@@ -64,14 +64,14 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
return html`<div class="content">
${!this._detected
? html`
<img src="/static/images/voice-assistant/sleep.gif" />
<img src="/static/images/voice-assistant/sleep.png" />
<h1>
Say “${this._activeWakeWord(this.assistConfiguration)}” to wake the
device up
</h1>
<p class="secondary">Setup will continue once the device is awake.</p>
</div>`
: html`<img src="/static/images/voice-assistant/ok-nabu.gif" />
: html`<img src="/static/images/voice-assistant/ok-nabu.png" />
<h1>
Say “${this._activeWakeWord(this.assistConfiguration)}” again
</h1>

View File

@@ -264,6 +264,7 @@ export interface ExternalConfig {
hasAssist: boolean;
hasBarCodeScanner: number;
canSetupImprov: boolean;
downloadFileSupported: boolean;
}
export class ExternalMessaging {

View File

@@ -16,6 +16,8 @@ import type { ValueChangedEvent } from "../types";
import { onBoardingStyles } from "./styles";
import { debounce } from "../common/util/debounce";
const CHECK_USERNAME_REGEX = /\s|[A-Z]/;
const CREATE_USER_SCHEMA: HaFormSchema[] = [
{
name: "name",
@@ -121,6 +123,7 @@ class OnboardingCreateUser extends LitElement {
ev: ValueChangedEvent<HaFormDataContainer>
): void {
const nameChanged = ev.detail.value.name !== this._newUser.name;
const usernameChanged = ev.detail.value.username !== this._newUser.username;
const passwordChanged =
ev.detail.value.password !== this._newUser.password ||
ev.detail.value.password_confirm !== this._newUser.password_confirm;
@@ -135,6 +138,9 @@ class OnboardingCreateUser extends LitElement {
this._debouncedCheckPasswordMatch();
}
}
if (usernameChanged) {
this._checkUsername();
}
}
private _debouncedCheckPasswordMatch = debounce(
@@ -164,6 +170,21 @@ class OnboardingCreateUser extends LitElement {
const parts = String(this._newUser.name).split(" ");
if (parts.length) {
this._newUser.username = parts[0].toLowerCase();
this._checkUsername();
}
}
private _checkUsername(): void {
const old = this._formError.username;
if (CHECK_USERNAME_REGEX.test(this._newUser.username as string)) {
this._formError.username = this.localize(
"ui.panel.page-onboarding.user.error.username_not_normalized"
);
} else {
this._formError.username = "";
}
if (old !== this._formError.username) {
this.requestUpdate("_formError");
}
}

View File

@@ -49,6 +49,7 @@ export class HaDelayAction extends LitElement implements ActionElement {
.disabled=${this.disabled}
.data=${this._timeData}
enableMillisecond
required
@value-changed=${this._valueChanged}
></ha-duration-input>`;
}

View File

@@ -67,9 +67,6 @@ export class HaWaitForTriggerAction
private _timeoutChanged(ev: CustomEvent<{ value: TimeChangedEvent }>): void {
ev.stopPropagation();
const value = ev.detail.value;
if (!value) {
return;
}
fireEvent(this, "value-changed", {
value: { ...this.action, timeout: value },
});

View File

@@ -370,7 +370,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
}`,
description:
this.hass.localize(
`component.${domain}.services.${service}.description`
`component.${dmn}.services.${service}.description`
) || services[dmn][service]?.description,
});
}

View File

@@ -712,8 +712,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
private async _duplicate() {
const result = this._readOnly
? await showConfirmationDialog(this, {
title: "Migrate automation?",
text: "You can migrate this automation, so it can be edited from the UI. After it is migrated and you have saved it, you will have to manually delete your old automation from your configuration. Do you want to migrate this automation?",
title: this.hass.localize(
"ui.panel.config.automation.picker.migrate_automation"
),
text: this.hass.localize(
"ui.panel.config.automation.picker.migrate_automation_description"
),
})
: await this.confirmUnsavedChanged();
if (result) {

View File

@@ -46,7 +46,7 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement {
],
],
},
{ name: "offset", selector: { duration: {} } },
{ name: "offset", required: true, selector: { duration: {} } },
{
name: "offset_type",
type: "select",

View File

@@ -5,6 +5,7 @@ import {
mdiHospitalBox,
mdiInformation,
mdiUpload,
mdiWrench,
} from "@mdi/js";
import { getConfigEntries } from "../../../../../../data/config_entries";
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
@@ -98,6 +99,13 @@ export const getZwaveDeviceActions = async (
showZWaveJSNodeStatisticsDialog(el, {
device,
}),
},
{
label: hass.localize(
"ui.panel.config.zwave_js.device_info.installer_settings"
),
icon: mdiWrench,
href: `/config/zwave_js/node_installer/${device.id}?config_entry=${entryId}`,
}
);
}

View File

@@ -584,6 +584,10 @@ class AddIntegrationDialog extends LitElement {
});
if (configEntries.length > 0) {
this.closeDialog();
const localize = await this.hass.loadBackendTranslation(
"title",
integration.name
);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.single_config_entry_title"
@@ -591,7 +595,7 @@ class AddIntegrationDialog extends LitElement {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.single_config_entry",
{
integration_name: integration.name,
integration_name: domainToName(localize, integration.name),
}
),
});

View File

@@ -1387,6 +1387,10 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
this._extraConfigEntries || this.configEntries
);
if (entries.length > 0) {
const localize = await this.hass.loadBackendTranslation(
"title",
this._manifest.name
);
await showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.single_config_entry_title"
@@ -1394,7 +1398,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.single_config_entry",
{
integration_name: this._manifest.name,
integration_name: domainToName(localize, this._manifest.name),
}
),
});

View File

@@ -744,6 +744,10 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
if (integration.single_config_entry) {
const configEntries = await getConfigEntries(this.hass, { domain });
if (configEntries.length > 0) {
const localize = await this.hass.loadBackendTranslation(
"title",
integration.name
);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.single_config_entry_title"
@@ -751,7 +755,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.single_config_entry",
{
integration_name: integration.name,
integration_name: domainToName(localize, integration.name!),
}
),
});

View File

@@ -44,7 +44,6 @@ class MatterAddDeviceGoogleHome extends LitElement {
home_assistant: html`<b>Home Assistant</b>`,
}
)}
<br />
<span
class="link"
type="button"
@@ -57,13 +56,13 @@ class MatterAddDeviceGoogleHome extends LitElement {
)}
</span>
</li>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.redirect`
)}
</li>
</ol>
<br />
<p>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.redirect`
)}
</p>
</div>
`;
}

View File

@@ -0,0 +1,152 @@
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../../components/buttons/ha-progress-button";
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import type { HomeAssistant } from "../../../../../../types";
import { invokeZWaveCCApi } from "../../../../../../data/zwave_js";
import "../../../../../../components/ha-textfield";
import "../../../../../../components/ha-select";
import "../../../../../../components/ha-list-item";
import "../../../../../../components/ha-alert";
import "../../../../../../components/ha-formfield";
import "../../../../../../components/ha-switch";
import type { HaProgressButton } from "../../../../../../components/buttons/ha-progress-button";
import type { HaSelect } from "../../../../../../components/ha-select";
import type { HaTextField } from "../../../../../../components/ha-textfield";
import type { HaSwitch } from "../../../../../../components/ha-switch";
import { extractApiErrorMessage } from "../../../../../../data/hassio/common";
@customElement("zwave_js-capability-control-multilevel_switch")
class ZWaveJSCapabilityMultiLevelSwitch extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public device!: DeviceRegistryEntry;
@property({ type: Number }) public endpoint!: number;
@property({ type: Number }) public command_class!: number;
@property({ type: Number }) public version!: number;
@state() private _error?: string;
protected render() {
return html`
<h3>
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.title"
)}
</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-select
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.direction"
)}
id="direction"
>
<ha-list-item .value=${"up"} selected
>${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.up"
)}</ha-list-item
>
<ha-list-item .value=${"down"}
>${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.down"
)}</ha-list-item
>
</ha-select>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.ignore_start_level"
)}
>
<ha-switch id="ignore_start_level"></ha-switch>
</ha-formfield>
<ha-textfield
type="number"
id="start_level"
value="0"
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.start_level"
)}
></ha-textfield>
<div class="actions">
<ha-progress-button
.control=${"startLevelChange"}
@click=${this._controlTransition}
>
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.start_transition"
)}
</ha-progress-button>
<ha-progress-button
.control=${"stopLevelChange"}
@click=${this._controlTransition}
>
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.stop_transition"
)}
</ha-progress-button>
</div>
`;
}
private async _controlTransition(ev: any) {
const control = ev.currentTarget!.control;
const button = ev.currentTarget as HaProgressButton;
button.progress = true;
const direction = (this.shadowRoot!.getElementById("direction") as HaSelect)
.value;
const ignoreStartLevel = (
this.shadowRoot!.getElementById("ignore_start_level") as HaSwitch
).checked;
const startLevel = Number(
(this.shadowRoot!.getElementById("start_level") as HaTextField).value
);
try {
button.actionSuccess();
await invokeZWaveCCApi(
this.hass,
this.device.id,
this.command_class,
this.endpoint,
control,
[{ direction, ignoreStartLevel, startLevel }],
true
);
} catch (err) {
button.actionError();
this._error = this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.control_failed",
{ error: extractApiErrorMessage(err) }
);
}
button.progress = false;
}
static styles = css`
ha-select,
ha-formfield,
ha-textfield {
display: block;
margin-bottom: 8px;
}
.actions {
display: flex;
justify-content: flex-end;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"zwave_js-capability-control-multilevel_switch": ZWaveJSCapabilityMultiLevelSwitch;
}
}

View File

@@ -0,0 +1,241 @@
import { LitElement, css, html } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import type { HomeAssistant } from "../../../../../../types";
import { invokeZWaveCCApi } from "../../../../../../data/zwave_js";
import "../../../../../../components/ha-button";
import "../../../../../../components/buttons/ha-progress-button";
import "../../../../../../components/ha-textfield";
import "../../../../../../components/ha-select";
import "../../../../../../components/ha-list-item";
import "../../../../../../components/ha-alert";
import type { HaSelect } from "../../../../../../components/ha-select";
import type { HaTextField } from "../../../../../../components/ha-textfield";
import { extractApiErrorMessage } from "../../../../../../data/hassio/common";
import type { HaProgressButton } from "../../../../../../components/buttons/ha-progress-button";
// enum with special states
enum SpecialState {
frost_protection = "Frost Protection",
energy_saving = "Energy Saving",
unused = "Unused",
}
const SETBACK_TYPE_OPTIONS = ["none", "temporary", "permanent"];
@customElement("zwave_js-capability-control-thermostat_setback")
class ZWaveJSCapabilityThermostatSetback extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public device!: DeviceRegistryEntry;
@property({ type: Number }) public endpoint!: number;
@property({ type: Number }) public command_class!: number;
@property({ type: Number }) public version!: number;
@state() private _disableSetbackState = false;
@query("#setback_type") private _setbackTypeInput!: HaSelect;
@query("#setback_state") private _setbackStateInput!: HaTextField;
@query("#setback_special_state")
private _setbackSpecialStateSelect!: HaSelect;
@state() private _error?: string;
@state() private _loading = true;
protected render() {
return html`
<h3>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.title`
)}
</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-select
.label=${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_type.label`
)}
id="setback_type"
.value=${"0"}
.disabled=${this._loading}
>
${SETBACK_TYPE_OPTIONS.map(
(translationKey, index) =>
html`<ha-list-item .value=${String(index)}>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_type.${translationKey}`
)}
</ha-list-item>`
)}
</ha-select>
<div class="setback-state">
<ha-textfield
type="number"
id="setback_state"
value="0"
.label=${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_state_label`
)}
min="-12.8"
max="12.0"
step=".1"
.helper=${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_state_helper`
)}
.disabled=${this._disableSetbackState || this._loading}
></ha-textfield>
<ha-select
.label=${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_special_state.label`
)}
id="setback_special_state"
@change=${this._changeSpecialState}
.disabled=${this._loading}
>
<ha-list-item selected> </ha-list-item>
${Object.entries(SpecialState).map(
([translationKey, value]) =>
html`<ha-list-item .value=${value}>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_special_state.${translationKey}`
)}
</ha-list-item>`
)}
</ha-select>
</div>
<div class="actions">
<ha-button
class="clear-button"
@click=${this._clear}
.disabled=${this._loading}
>${this.hass.localize("ui.common.clear")}</ha-button
>
<ha-progress-button
@click=${this._saveSetback}
.disabled=${this._loading}
>
${this.hass.localize("ui.common.save")}
</ha-progress-button>
</div>
`;
}
protected firstUpdated() {
this._loadSetback();
}
private async _loadSetback() {
this._loading = true;
try {
const { setbackType, setbackState } = (await invokeZWaveCCApi(
this.hass,
this.device.id,
this.command_class,
this.endpoint,
"get",
[],
true
)) as { setbackType: number; setbackState: number | SpecialState };
this._setbackTypeInput.value = String(setbackType);
if (typeof setbackState === "number") {
this._setbackStateInput.value = String(setbackState);
this._setbackSpecialStateSelect.value = "";
} else {
this._setbackSpecialStateSelect.value = setbackState;
}
} catch (err) {
this._error = this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.get_setback_failed",
{ error: extractApiErrorMessage(err) }
);
}
this._loading = false;
}
private _changeSpecialState() {
this._disableSetbackState = !!this._setbackSpecialStateSelect.value;
}
private async _saveSetback(ev: CustomEvent) {
const button = ev.currentTarget as HaProgressButton;
button.progress = true;
this._error = undefined;
const setbackType = this._setbackTypeInput.value;
let setbackState: number | string = Number(this._setbackStateInput.value);
if (this._setbackSpecialStateSelect.value) {
setbackState = this._setbackSpecialStateSelect.value;
}
try {
await invokeZWaveCCApi(
this.hass,
this.device.id,
this.command_class,
this.endpoint,
"set",
[Number(setbackType), setbackState],
true
);
button.actionSuccess();
} catch (err) {
button.actionError();
this._error = this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.save_setback_failed",
{ error: extractApiErrorMessage(err) }
);
}
button.progress = false;
}
private _clear() {
this._loadSetback();
}
static styles = css`
:host {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
:host > ha-select {
width: 100%;
}
.actions {
width: 100%;
display: flex;
justify-content: flex-end;
}
.actions .clear-button {
--mdc-theme-primary: var(--red-color);
}
.setback-state {
width: 100%;
display: flex;
gap: 16px;
}
.setback-state ha-select,
ha-textfield {
flex: 1;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"zwave_js-capability-control-thermostat_setback": ZWaveJSCapabilityThermostatSetback;
}
}

View File

@@ -690,9 +690,6 @@ class DialogZWaveJSAddNode extends LitElement {
provisioningInfo
);
this._status = "provisioned";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
} catch (err: any) {
this._error = err.message;
this._status = "failed";
@@ -831,9 +828,6 @@ class DialogZWaveJSAddNode extends LitElement {
if (message.event === "interview completed") {
this._unsubscribe();
this._status = "finished";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
}
if (message.event === "interview stage completed") {
@@ -874,6 +868,9 @@ class DialogZWaveJSAddNode extends LitElement {
}
if (this._entryId) {
stopZwaveInclusion(this.hass, this._entryId);
if (this._params?.onStop) {
this._params.onStop();
}
}
this._requestedGrant = undefined;
this._dsk = undefined;

View File

@@ -2,7 +2,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDialogParams {
entry_id: string;
addedCallback?: () => void;
onStop?: () => void;
}
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");

View File

@@ -564,7 +564,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
private async _addNodeClicked() {
showZWaveJSAddNodeDialog(this, {
entry_id: this.configEntryId!,
addedCallback: () => this._fetchData(),
// refresh the data after the dialog is closed. add a small delay for the inclusion state to update
onStop: () => setTimeout(() => this._fetchData(), 100),
});
}

View File

@@ -48,6 +48,10 @@ class ZWaveJSConfigRouter extends HassRouterPage {
tag: "zwave_js-node-config",
load: () => import("./zwave_js-node-config"),
},
node_installer: {
tag: "zwave_js-node-installer",
load: () => import("./zwave_js-node-installer"),
},
logs: {
tag: "zwave_js-logs",
load: () => import("./zwave_js-logs"),

View File

@@ -0,0 +1,210 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
import "../../../../../components/ha-card";
import { computeDeviceName } from "../../../../../data/device_registry";
import type {
ZWaveJSNodeCapabilities,
ZwaveJSNodeMetadata,
} from "../../../../../data/zwave_js";
import {
fetchZwaveNodeCapabilities,
fetchZwaveNodeMetadata,
} from "../../../../../data/zwave_js";
import "../../../../../layouts/hass-error-screen";
import "../../../../../layouts/hass-loading-screen";
import "../../../../../layouts/hass-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import "./capability-controls/zwave_js-capability-control-multilevel-switch";
import "./capability-controls/zwave_js-capability-control-thermostat-setback";
const CAPABILITY_CONTROLS = {
38: "multilevel_switch",
71: "thermostat_setback",
};
@customElement("zwave_js-node-installer")
class ZWaveJSNodeInstaller extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean }) public isWide = false;
@property() public configEntryId?: string;
@property() public deviceId!: string;
@state() private _nodeMetadata?: ZwaveJSNodeMetadata;
@state() private _capabilities?: ZWaveJSNodeCapabilities;
@state() private _error?: string;
public connectedCallback(): void {
super.connectedCallback();
this.deviceId = this.route.path.substr(1);
}
protected updated(changedProps: PropertyValues): void {
if (!this._capabilities || changedProps.has("deviceId")) {
this._fetchData();
}
}
protected render(): TemplateResult {
if (this._error) {
return html`<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize(
`ui.panel.config.zwave_js.node_config.error_${this._error}`
)}
></hass-error-screen>`;
}
if (!this._capabilities || !this._nodeMetadata) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
const device = this.hass.devices[this.deviceId];
const endpoints = Object.entries(this._capabilities).filter(
([_endpoint, capabilities]) => {
const filteredCapabilities = capabilities.filter(
(capability) => capability.id in CAPABILITY_CONTROLS
);
return filteredCapabilities.length > 0;
}
);
return html`
<hass-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
>
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
vertical
>
<div slot="header">
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.header"
)}
</div>
<div slot="introduction">
${device
? html`
<div class="device-info">
<h2>${computeDeviceName(device, this.hass)}</h2>
<p>${device.manufacturer} ${device.model}</p>
</div>
`
: ``}
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.introduction"
)}
</div>
${endpoints.length
? endpoints.map(
([endpoint, capabilities]) => html`
<h3>
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.endpoint"
)}:
${endpoint}
</h3>
<ha-card>
${capabilities.map(
(capability) => html`
${capability.id in CAPABILITY_CONTROLS
? html` <div class="capability">
<h4>
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.command_class"
)}:
${capability.name}
</h4>
${dynamicElement(
`zwave_js-capability-control-${CAPABILITY_CONTROLS[capability.id]}`,
{
hass: this.hass,
device: device,
endpoint: endpoint,
command_class: capability.id,
version: capability.version,
is_secure: capability.is_secure,
}
)}
</div>`
: nothing}
`
)}
</ha-card>
`
)
: html`<ha-card class="empty"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.no_settings"
)}</ha-card
>`}
</ha-config-section>
</hass-subpage>
`;
}
private async _fetchData() {
if (!this.configEntryId) {
return;
}
const device = this.hass.devices[this.deviceId];
if (!device) {
this._error = "device_not_found";
return;
}
[this._nodeMetadata, this._capabilities] = await Promise.all([
fetchZwaveNodeMetadata(this.hass, device.id),
fetchZwaveNodeCapabilities(this.hass, device.id),
]);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-card {
margin-bottom: 40px;
margin-top: 0;
}
.capability {
border-bottom: 1px solid var(--divider-color);
padding: 4px 16px;
}
.capability:last-child {
border-bottom: none;
}
.empty {
margin-top: 32px;
padding: 24px 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zwave_js-node-installer": ZWaveJSNodeInstaller;
}
}

View File

@@ -2,24 +2,22 @@ import { mdiClose } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import "../../../components/ha-md-dialog";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button";
import "../../../components/ha-dialog-header";
import "../../../components/ha-icon-button";
import "../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../components/ha-md-dialog";
import type { HomeAssistant } from "../../../types";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import { fireEvent } from "../../../common/dom/fire_event";
import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
import "../../../components/ha-select";
import "../../../components/ha-list-item";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import {
getHassioLogDownloadLinesUrl,
getHassioLogBootDownloadLinesUrl,
} from "../../../data/hassio/supervisor";
import "../../../components/ha-md-select";
import "../../../components/ha-md-select-option";
import { getSignedPath } from "../../../data/auth";
import { getHassioLogDownloadLinesUrl } from "../../../data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
const DEFAULT_LINE_COUNT = 500;
@customElement("dialog-download-logs")
class DownloadLogsDialog extends LitElement {
@@ -27,13 +25,13 @@ class DownloadLogsDialog extends LitElement {
@state() private _dialogParams?: DownloadLogsDialogParams;
@state() private _lineCount = 100;
@state() private _lineCount = DEFAULT_LINE_COUNT;
@query("ha-md-dialog") private _dialogElement!: HaMdDialog;
public showDialog(dialogParams: DownloadLogsDialogParams) {
this._dialogParams = dialogParams;
this._lineCount = this._dialogParams?.defaultLineCount ?? 100;
this._lineCount = this._dialogParams?.defaultLineCount || 500;
}
public closeDialog() {
@@ -42,7 +40,7 @@ class DownloadLogsDialog extends LitElement {
private _dialogClosed() {
this._dialogParams = undefined;
this._lineCount = 100;
this._lineCount = DEFAULT_LINE_COUNT;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -52,7 +50,7 @@ class DownloadLogsDialog extends LitElement {
}
const numberOfLinesOptions = [100, 500, 1000, 5000, 10000];
if (!numberOfLinesOptions.includes(this._lineCount)) {
if (!numberOfLinesOptions.includes(this._lineCount) && this._lineCount) {
numberOfLinesOptions.push(this._lineCount);
numberOfLinesOptions.sort((a, b) => a - b);
}
@@ -67,7 +65,7 @@ class DownloadLogsDialog extends LitElement {
.path=${mdiClose}
></ha-icon-button>
<span slot="title" id="dialog-light-color-favorite-title">
${this.hass.localize("ui.panel.config.logs.download_full_log")}
${this.hass.localize("ui.panel.config.logs.download_logs")}
</span>
<span slot="subtitle">
${this._dialogParams.header}${this._dialogParams.boot === 0
@@ -81,28 +79,25 @@ class DownloadLogsDialog extends LitElement {
"ui.panel.config.logs.select_number_of_lines"
)}:
</div>
<ha-select
<ha-md-select
.label=${this.hass.localize("ui.panel.config.logs.lines")}
@selected=${this._setNumberOfLogs}
fixedMenuPosition
naturalMenuWidth
@closed=${stopPropagation}
@change=${this._setNumberOfLogs}
.value=${String(this._lineCount)}
>
${numberOfLinesOptions.map(
(option) => html`
<ha-list-item .value=${String(option)}>
<ha-md-select-option .value=${String(option)}>
${option}
</ha-list-item>
</ha-md-select-option>
`
)}
</ha-select>
</ha-md-select>
</div>
<div slot="actions">
<ha-button @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._dowloadLogs}>
<ha-button @click=${this._downloadLogs}>
${this.hass.localize("ui.common.download")}
</ha-button>
</div>
@@ -110,12 +105,12 @@ class DownloadLogsDialog extends LitElement {
`;
}
private async _dowloadLogs() {
private async _downloadLogs() {
const provider = this._dialogParams!.provider;
const boot = this._dialogParams!.boot;
const timeString = new Date().toISOString().replace(/:/g, "-");
const downloadUrl = this._getDownloadUrlFunction()(
const downloadUrl = getHassioLogDownloadLinesUrl(
provider,
this._lineCount,
boot
@@ -129,13 +124,6 @@ class DownloadLogsDialog extends LitElement {
this.closeDialog();
}
private _getDownloadUrlFunction() {
if (this._dialogParams!.boot === 0) {
return getHassioLogDownloadLinesUrl;
}
return getHassioLogBootDownloadLinesUrl;
}
private _setNumberOfLogs(ev) {
this._lineCount = Number(ev.target.value);
}
@@ -147,6 +135,7 @@ class DownloadLogsDialog extends LitElement {
css`
:host {
direction: var(--direction);
--dialog-content-overflow: visible;
}
.content {
display: flex;

View File

@@ -1,9 +1,16 @@
import "@material/mwc-list/mwc-list-item";
import type { ActionDetail } from "@material/mwc-list";
import {
mdiArrowCollapseDown,
mdiDotsVertical,
mdiCircle,
mdiDownload,
mdiFormatListNumbered,
mdiMenuDown,
mdiRefresh,
mdiWrap,
mdiWrapDisabled,
} from "@mdi/js";
import {
css,
@@ -31,6 +38,8 @@ import "../../../components/chips/ha-assist-chip";
import "../../../components/ha-menu";
import "../../../components/ha-md-menu-item";
import "../../../components/ha-md-divider";
import "../../../components/ha-button-menu";
import "../../../components/ha-list-item";
import { getSignedPath } from "../../../data/auth";
@@ -39,12 +48,15 @@ import { extractApiErrorMessage } from "../../../data/hassio/common";
import {
fetchHassioBoots,
fetchHassioLogs,
fetchHassioLogsBootFollow,
fetchHassioLogsFollow,
getHassioLogDownloadLinesUrl,
getHassioLogDownloadUrl,
} from "../../../data/hassio/supervisor";
import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import {
downloadFileSupported,
fileDownload,
} from "../../../util/file_download";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import type { ConnectionStatus } from "../../../data/connection-status";
import { atLeastVersion } from "../../../common/config/version";
@@ -52,6 +64,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { debounce } from "../../../common/util/debounce";
import { showDownloadLogsDialog } from "./show-dialog-download-logs";
import type { HaMenu } from "../../../components/ha-menu";
import type { LocalizeFunc } from "../../../common/translations/localize";
const NUMBER_OF_LINES = 100;
@@ -59,6 +72,8 @@ const NUMBER_OF_LINES = 100;
class ErrorLogCard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localizeFunc?: LocalizeFunc<any>;
@property() public filter = "";
@property() public header?: string;
@@ -110,30 +125,42 @@ class ErrorLogCard extends LitElement {
@state() private _boots?: number[];
@state() private _showBootsSelect = false;
@state() private _wrapLines = true;
@state() private _downloadSupported;
@state() private _logsFileLink;
protected render(): TemplateResult {
const localize = this.localizeFunc || this.hass.localize;
return html`
<div class="error-log-intro">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
: nothing}
<ha-card outlined class=${classMap({ hidden: this.show === false })}>
<div class="header">
<h1 class="card-header">
${this.header ||
this.hass.localize("ui.panel.config.logs.show_full_logs")}
${this.header || localize("ui.panel.config.logs.show_full_logs")}
</h1>
<div class="action-buttons">
${this._streamSupported && Array.isArray(this._boots)
${this._streamSupported &&
Array.isArray(this._boots) &&
this._showBootsSelect
? html`
<ha-assist-chip
.title=${localize(
"ui.panel.config.logs.haos_boots_title"
)}
.label=${this._boot === 0
? this.hass.localize("ui.panel.config.logs.current")
? localize("ui.panel.config.logs.current")
: this._boot === -1
? this.hass.localize("ui.panel.config.logs.previous")
: this.hass.localize(
"ui.panel.config.logs.startups_ago",
{ boot: this._boot * -1 }
)}
? localize("ui.panel.config.logs.previous")
: localize("ui.panel.config.logs.startups_ago", {
boot: this._boot * -1,
})}
id="boots-anchor"
@click=${this._toggleBootsMenu}
>
@@ -155,14 +182,10 @@ class ErrorLogCard extends LitElement {
.selected=${boot === this._boot}
>
${boot === 0
? this.hass.localize(
"ui.panel.config.logs.current"
)
? localize("ui.panel.config.logs.current")
: boot === -1
? this.hass.localize(
"ui.panel.config.logs.previous"
)
: this.hass.localize(
? localize("ui.panel.config.logs.previous")
: localize(
"ui.panel.config.logs.startups_ago",
{ boot: boot * -1 }
)}
@@ -177,20 +200,61 @@ class ErrorLogCard extends LitElement {
</ha-menu>
`
: nothing}
${this._downloadSupported
? html`
<ha-icon-button
.path=${mdiDownload}
@click=${this._downloadLogs}
.label=${localize("ui.panel.config.logs.download_logs")}
></ha-icon-button>
`
: this._logsFileLink
? html`
<a
href=${this._logsFileLink}
target="_blank"
class="download-link"
>
<ha-icon-button
.path=${mdiDownload}
.label=${localize(
"ui.panel.config.logs.download_logs"
)}
></ha-icon-button>
</a>
`
: nothing}
<ha-icon-button
.path=${mdiDownload}
@click=${this._downloadFullLog}
.label=${this.hass.localize(
"ui.panel.config.logs.download_full_log"
.path=${this._wrapLines ? mdiWrapDisabled : mdiWrap}
@click=${this._toggleLineWrap}
.label=${localize(
`ui.panel.config.logs.${this._wrapLines ? "full_width" : "wrap_lines"}`
)}
></ha-icon-button>
${!this._streamSupported || this._error
? html`<ha-icon-button
.path=${mdiRefresh}
@click=${this._loadLogs}
.label=${this.hass.localize("ui.common.refresh")}
.label=${localize("ui.common.refresh")}
></ha-icon-button>`
: nothing}
${this._streamSupported && Array.isArray(this._boots)
? html`
<ha-button-menu @action=${this._handleOverflowAction}>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiFormatListNumbered}
></ha-svg-icon>
${localize(
`ui.panel.config.logs.${this._showBootsSelect ? "hide" : "show"}_haos_boots`
)}
</ha-list-item>
</ha-button-menu>
`
: nothing}
</div>
</div>
<div class="card-content error-log">
@@ -203,25 +267,22 @@ class ErrorLogCard extends LitElement {
</div>`
: nothing}
${this._loadingState === "loading"
? html`<div>
${this.hass.localize("ui.panel.config.logs.loading_log")}
</div>`
? html`<div>${localize("ui.panel.config.logs.loading_log")}</div>`
: this._loadingState === "empty"
? html`<div>
${this.hass.localize("ui.panel.config.logs.no_errors")}
</div>`
? html`<div>${localize("ui.panel.config.logs.no_errors")}</div>`
: nothing}
${this._loadingState === "loaded" &&
this.filter &&
this._noSearchResults
? html`<div>
${this.hass.localize(
"ui.panel.config.logs.no_issues_search",
{ term: this.filter }
)}
${localize("ui.panel.config.logs.no_issues_search", {
term: this.filter,
})}
</div>`
: nothing}
<ha-ansi-to-html></ha-ansi-to-html>
<ha-ansi-to-html
?wrap-disabled=${!this._wrapLines}
></ha-ansi-to-html>
<div id="scroll-bottom-marker"></div>
</div>
<ha-button
@@ -237,24 +298,36 @@ class ErrorLogCard extends LitElement {
.path=${mdiArrowCollapseDown}
slot="icon"
></ha-svg-icon>
${this.hass.localize("ui.panel.config.logs.scroll_down_button")}
${localize("ui.panel.config.logs.scroll_down_button")}
<ha-svg-icon
.path=${mdiArrowCollapseDown}
slot="trailingIcon"
></ha-svg-icon>
</ha-button>
${this._streamSupported &&
this._loadingState !== "loading" &&
!this._error
? html`<div class="live-indicator">
<ha-svg-icon path=${mdiCircle}></ha-svg-icon>
Live
</div>`
: nothing}
</ha-card>
${this.show === false
? html`
<ha-button outlined @click=${this._downloadFullLog}>
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize("ui.panel.config.logs.download_full_log")}
</ha-button>
${this._downloadSupported
? html`
<ha-button outlined @click=${this._downloadLogs}>
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
${localize("ui.panel.config.logs.download_logs")}
</ha-button>
`
: nothing}
<mwc-button raised @click=${this._showLogs}>
${this.hass.localize("ui.panel.config.logs.load_logs")}
${localize("ui.panel.config.logs.load_logs")}
</mwc-button>
`
: ""}
: nothing}
</div>
`;
}
@@ -269,6 +342,9 @@ class ErrorLogCard extends LitElement {
11
);
}
if (this._downloadSupported === undefined && this.hass) {
this._downloadSupported = downloadFileSupported(this.hass);
}
}
protected firstUpdated(changedProps: PropertyValues) {
@@ -332,7 +408,7 @@ class ErrorLogCard extends LitElement {
);
}
private async _downloadFullLog(): Promise<void> {
private async _downloadLogs(): Promise<void> {
if (this._streamSupported) {
showDownloadLogsDialog(this, {
header: this.header,
@@ -379,7 +455,19 @@ class ErrorLogCard extends LitElement {
isComponentLoaded(this.hass, "hassio") &&
this.provider
) {
const response = await this._fetchLogsFunction()(
// check if there are any logs at all
const testResponse = await fetchHassioLogs(
this.hass,
this.provider,
`entries=:-1:`,
this._boot
);
const testLogs = await testResponse.text();
if (!testLogs.trim()) {
this._loadingState = "empty";
}
const response = await fetchHassioLogsFollow(
this.hass,
this.provider,
this._logStreamAborter.signal,
@@ -439,6 +527,17 @@ class ErrorLogCard extends LitElement {
} else {
this._newLogsIndicator = true;
}
if (!this._downloadSupported) {
const downloadUrl = getHassioLogDownloadLinesUrl(
this.provider,
this._numberOfLines,
this._boot
);
getSignedPath(this.hass, downloadUrl).then((signedUrl) => {
this._logsFileLink = signedUrl.path;
});
}
}
}
} else {
@@ -462,20 +561,16 @@ class ErrorLogCard extends LitElement {
if (err.name === "AbortError") {
return;
}
this._error = this.hass.localize("ui.panel.config.logs.failed_get_logs", {
provider: this.provider,
error: extractApiErrorMessage(err),
});
this._error = (this.localizeFunc || this.hass.localize)(
"ui.panel.config.logs.failed_get_logs",
{
provider: this.provider,
error: extractApiErrorMessage(err),
}
);
}
}
private _fetchLogsFunction = () => {
if (this._boot === 0) {
return fetchHassioLogsFollow;
}
return fetchHassioLogsBootFollow;
};
private _debounceSearch = debounce(() => {
this._noSearchResults = !this._ansiToHtmlElement?.filterLines(this.filter);
@@ -593,6 +688,18 @@ class ErrorLogCard extends LitElement {
}
}
private _toggleLineWrap() {
this._wrapLines = !this._wrapLines;
}
private _handleOverflowAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._showBootsSelect = !this._showBootsSelect;
break;
}
}
private _toggleBootsMenu() {
if (this._bootsMenu) {
this._bootsMenu.open = !this._bootsMenu.open;
@@ -605,6 +712,9 @@ class ErrorLogCard extends LitElement {
}
static styles: CSSResultGroup = css`
:host {
direction: var(--direction);
}
.error-log-intro {
text-align: center;
margin: 16px;
@@ -654,7 +764,7 @@ class ErrorLogCard extends LitElement {
position: relative;
font-family: var(--code-font-family, monospace);
clear: both;
text-align: left;
text-align: start;
padding-top: 12px;
padding-bottom: 12px;
overflow-y: scroll;
@@ -721,6 +831,36 @@ class ErrorLogCard extends LitElement {
--ha-assist-chip-container-shape: 10px;
--md-assist-chip-trailing-space: 8px;
}
@keyframes breathe {
from {
opacity: 0.8;
}
to {
opacity: 0;
}
}
.live-indicator {
position: absolute;
bottom: 0;
inset-inline-end: 16px;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
background-color: var(--primary-color);
color: var(--text-primary-color);
padding: 4px 8px;
opacity: 0.8;
}
.live-indicator ha-svg-icon {
animation: breathe 1s cubic-bezier(0.5, 0, 1, 1) infinite alternate;
height: 14px;
width: 14px;
}
.download-link {
color: var(--text-color);
}
`;
}

View File

@@ -681,8 +681,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
private async _duplicate() {
const result = this._readOnly
? await showConfirmationDialog(this, {
title: "Migrate script?",
text: "You can migrate this script, so it can be edited from the UI. After it is migrated and you have saved it, you will have to manually delete your old script from your configuration. Do you want to migrate this script?",
title: this.hass.localize(
"ui.panel.config.script.picker.migrate_script"
),
text: this.hass.localize(
"ui.panel.config.script.picker.migrate_script_description"
),
})
: await this.confirmUnsavedChanged();
if (result) {

View File

@@ -1,11 +1,11 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../components/ha-card";
import type { HomeAssistant } from "../../../../types";
import { hasConfigChanged } from "../../common/has-changed";
import "../../components/hui-energy-period-selector";
import "../../../../components/ha-card";
import type { LovelaceCard, LovelaceLayoutOptions } from "../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
import type { EnergyCardBaseConfig } from "../types";
@customElement("hui-energy-date-selection-card")
@@ -21,10 +21,10 @@ export class HuiEnergyDateSelectionCard
return 1;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_rows: 1,
grid_columns: 4,
rows: 1,
columns: 12,
};
}
@@ -59,18 +59,12 @@ export class HuiEnergyDateSelectionCard
static get styles(): CSSResultGroup {
return css`
:host {
ha-card {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.padded {
padding-left: 16px !important;
padding-inline-start: 16px !important;
padding-inline-end: initial !important;
}
`;
}
}

View File

@@ -45,7 +45,7 @@ import "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { AreaCardConfig } from "./types";
@@ -534,10 +534,11 @@ export class HuiAreaCard
forwardHaptic("light");
}
getLayoutOptions(): LovelaceLayoutOptions {
getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 4,
grid_rows: 3,
columns: 12,
rows: 3,
min_columns: 3,
};
}

View File

@@ -46,7 +46,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { ButtonCardConfig } from "./types";
@@ -134,20 +134,23 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
);
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
if (
this._config?.show_icon &&
(this._config?.show_name || this._config?.show_state)
) {
return {
grid_rows: 2,
grid_columns: 2,
grid_min_rows: 2,
rows: 2,
columns: 6,
min_columns: 2,
min_rows: 2,
};
}
return {
grid_rows: 1,
grid_columns: 1,
rows: 1,
columns: 3,
min_columns: 2,
min_rows: 1,
};
}

View File

@@ -33,8 +33,8 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
import type {
LovelaceCard,
LovelaceGridOptions,
LovelaceHeaderFooter,
LovelaceLayoutOptions,
} from "../types";
import type { HuiErrorCard } from "./hui-error-card";
import type { EntityCardConfig } from "./types";
@@ -249,12 +249,12 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 2,
grid_rows: 2,
grid_min_columns: 2,
grid_min_rows: 2,
columns: 6,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@@ -16,7 +16,7 @@ import "../heading-badges/hui-heading-badge";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { HeadingCardConfig } from "./types";
@@ -65,10 +65,11 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
return 1;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: "full",
grid_rows: this._config?.heading_style === "subtitle" ? "auto" : 1,
columns: "full",
rows: this._config?.heading_style === "subtitle" ? "auto" : 1,
min_columns: 3,
};
}

View File

@@ -28,6 +28,7 @@ export class HuiHorizontalStackCard extends HuiStackCard {
css`
#root {
display: flex;
height: 100%;
gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px));
}
#root > hui-card {

View File

@@ -19,7 +19,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { HumidifierCardConfig } from "./types";
@@ -171,21 +171,21 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
`;
}
public getLayoutOptions(): LovelaceLayoutOptions {
const grid_columns = 4;
let grid_rows = 5;
let grid_min_rows = 2;
const grid_min_columns = 2;
public getGridOptions(): LovelaceGridOptions {
const columns = 12;
let rows = 5;
let min_rows = 2;
const min_columns = 6;
if (this._config?.features?.length) {
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
grid_rows += featureHeight;
grid_min_rows += featureHeight;
rows += featureHeight;
min_rows += featureHeight;
}
return {
grid_columns,
grid_rows,
grid_min_rows,
grid_min_columns,
columns,
rows,
min_columns,
min_rows,
};
}

View File

@@ -11,7 +11,7 @@ import { IFRAME_SANDBOX } from "../../../util/iframe";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { IframeCardConfig } from "./types";
@@ -113,11 +113,12 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
`;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: "full",
grid_rows: 4,
grid_min_rows: 2,
columns: "full",
rows: 4,
min_columns: 3,
min_rows: 2,
};
}

View File

@@ -11,8 +11,8 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { deepEqual } from "../../../common/util/deep-equal";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import "../../../components/ha-card";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/map/ha-map";
import type {
@@ -23,15 +23,15 @@ import type {
} from "../../../components/map/ha-map";
import type { HistoryStates } from "../../../data/history";
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import {
hasConfigChanged,
hasConfigOrEntitiesChanged,
} from "../common/has-changed";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import { processConfigEntities } from "../common/process-config-entities";
import type { EntityConfig } from "../entity-rows/types";
import type { LovelaceCard, LovelaceLayoutOptions } from "../types";
import type { LovelaceCard, LovelaceGridOptions } from "../types";
import type { MapCardConfig } from "./types";
export const DEFAULT_HOURS_TO_SHOW = 0;
@@ -431,12 +431,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
);
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: "full",
grid_rows: 4,
grid_min_columns: 2,
grid_min_rows: 2,
columns: "full",
rows: 4,
min_columns: 6,
min_rows: 2,
};
}

View File

@@ -6,7 +6,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import type { GraphHeaderFooterConfig } from "../header-footer/types";
import type { LovelaceCardEditor, LovelaceLayoutOptions } from "../types";
import type { LovelaceCardEditor, LovelaceGridOptions } from "../types";
import { HuiEntityCard } from "./hui-entity-card";
import type { EntityCardConfig, SensorCardConfig } from "./types";
@@ -73,12 +73,12 @@ class HuiSensorCard extends HuiEntityCard {
super.setConfig(entityCardConfig);
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 2,
grid_rows: 2,
grid_min_columns: 2,
grid_min_rows: 2,
columns: 6,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@@ -26,7 +26,7 @@ import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceHeaderFooter,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { HuiErrorCard } from "./hui-error-card";
import type { EntityCardConfig, StatisticCardConfig } from "./types";
@@ -249,12 +249,12 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
return {
grid_columns: 2,
grid_rows: 2,
grid_min_columns: 2,
grid_min_rows: 2,
columns: 6,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@@ -19,7 +19,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { ThermostatCardConfig } from "./types";
@@ -163,21 +163,21 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
`;
}
public getLayoutOptions(): LovelaceLayoutOptions {
const grid_columns = 4;
let grid_rows = 5;
let grid_min_rows = 2;
const grid_min_columns = 2;
public getGridOptions(): LovelaceGridOptions {
const columns = 12;
let rows = 5;
let min_rows = 2;
const min_columns = 6;
if (this._config?.features?.length) {
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
grid_rows += featureHeight;
grid_min_rows += featureHeight;
rows += featureHeight;
min_rows += featureHeight;
}
return {
grid_columns,
grid_rows,
grid_min_rows,
grid_min_columns,
columns,
rows,
min_columns,
min_rows,
};
}

View File

@@ -34,7 +34,7 @@ import { hasAction } from "../common/has-action";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import { renderTileBadge } from "./tile/badges/tile-badge";
import type { ThermostatCardConfig, TileCardConfig } from "./types";
@@ -109,22 +109,22 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
);
}
public getLayoutOptions(): LovelaceLayoutOptions {
const grid_columns = 2;
let grid_min_columns = 2;
let grid_rows = 1;
public getGridOptions(): LovelaceGridOptions {
const columns = 6;
let min_columns = 6;
let rows = 1;
if (this._config?.features?.length) {
grid_rows += this._config.features.length;
rows += this._config.features.length;
}
if (this._config?.vertical) {
grid_rows++;
grid_min_columns = 1;
rows++;
min_columns = 3;
}
return {
grid_columns,
grid_rows,
grid_min_rows: grid_rows,
grid_min_columns,
columns,
rows,
min_columns,
min_rows: rows,
};
}

View File

@@ -34,7 +34,7 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
LovelaceGridOptions,
} from "../types";
import type { WeatherForecastCardConfig } from "./types";
@@ -418,31 +418,31 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
return typeof item !== "undefined" && item !== null;
}
public getLayoutOptions(): LovelaceLayoutOptions {
public getGridOptions(): LovelaceGridOptions {
if (
this._config?.show_current !== false &&
this._config?.show_forecast !== false
) {
return {
grid_columns: 4,
grid_min_columns: 2,
grid_rows: 4,
grid_min_rows: 4,
columns: 12,
rows: 4,
min_columns: 6,
min_rows: 4,
};
}
if (this._config?.show_forecast !== false) {
return {
grid_columns: 4,
grid_min_columns: 2,
grid_rows: 3,
grid_min_rows: 3,
columns: 12,
rows: 3,
min_columns: 6,
min_rows: 3,
};
}
return {
grid_columns: 4,
grid_min_columns: 2,
grid_rows: 2,
grid_min_rows: 2,
columns: 12,
rows: 2,
min_columns: 6,
min_rows: 2,
};
}

View File

@@ -233,7 +233,7 @@ export class HuiCardEditMode extends LitElement {
}
private _handleAction(ev) {
switch (ev.target.action) {
switch (ev.currentTarget.action) {
case "edit":
this._editCard();
break;

View File

@@ -64,6 +64,7 @@ const cardConfigStruct = assign(
hours_to_show: optional(number()),
geo_location_sources: optional(array(geoSourcesConfigStruct)),
auto_fit: optional(boolean()),
fit_zones: optional(boolean()),
theme_mode: optional(string()),
})
);

View File

@@ -242,8 +242,9 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
min-height: var(--row-height);
}
.container.edit-mode:not(.import-only) {
border-start-end-radius: 0px;
.container.import-only {
border: none;
padding: 0 !important;
}
.card {

View File

@@ -1,5 +1,11 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { mdiDelete, mdiDrag, mdiPencil, mdiViewGridPlus } from "@mdi/js";
import {
mdiDelete,
mdiDrag,
mdiEyeOff,
mdiPencil,
mdiViewGridPlus,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -245,6 +251,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
<div class="section imported-cards">
<div class="imported-card-header">
<p class="title">
<ha-svg-icon .path=${mdiEyeOff}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.section.imported_cards_title"
)}
@@ -480,9 +487,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
}
.imported-card-header {
margin-top: 24px;
padding: 16px 8px;
border-top: 2px dashed var(--divider-color);
margin-top: 36px;
padding: 32px 0 16px 0;
border-top: 4px dotted var(--divider-color);
}
.imported-card-header .title {
@@ -491,6 +498,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
font-size: 16px;
font-weight: 400;
line-height: 24px;
--mdc-icon-size: 18px;
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.imported-card-header .subtitle {
margin: 0;

View File

@@ -1745,8 +1745,8 @@
"answer_generic": "Other controllers"
},
"google_home": {
"header": "Link Matter app",
"step_1": "Find your device in Google Home. Tap the gear icon to open the device settings.",
"header": "Share from Google Home",
"step_1": "Find your device in the Google Home app. Tap the gear icon to open the device settings.",
"step_2": "Tap {linked_matter_apps_services}.",
"step_3": "Tap {link_apps_services} and choose {home_assistant} from the list.",
"linked_matter_apps_services": "Linked Matter apps and services",
@@ -1776,8 +1776,8 @@
"code_instructions": "Paste the code you just received from the other controller."
},
"generic": {
"header": "Copy setup code",
"code_instructions": "Search for the sharing mode in the app of your controller, and activate it. You will get a sharing code, enter that below.",
"header": "Enter setup code",
"code_instructions": "Search for the sharing mode in the app of your controller, and activate it. You will get a setup code, enter that below.",
"setup_code": "Setup code"
}
}
@@ -2492,10 +2492,15 @@
"show_full_logs": "Show full logs",
"select_number_of_lines": "Select number of lines to download",
"lines": "Lines",
"download_full_log": "Download full log",
"download_logs": "Download logs",
"scroll_down_button": "New logs - Click to scroll",
"provider_not_found": "Log provider not found",
"provider_not_available": "Logs for ''{provider}'' are not available on your system.",
"haos_boots_title": "Logs of HAOS startup",
"show_haos_boots": "Show HAOS startups",
"hide_haos_boots": "Hide HAOS startups",
"full_width": "Full width",
"wrap_lines": "Wrap lines",
"current": "Current",
"previous": "Previous",
"startups_ago": "{boot} startups ago",
@@ -2792,7 +2797,9 @@
},
"empty_header": "Start automating",
"empty_text_1": "Automations make Home Assistant automatically respond to things happening in and around your home.",
"empty_text_2": "Automations connect triggers to actions in a ''when trigger then action'' fashion with optional conditions. For example: ''When the sun sets and if {user} is home, then turn on the lights''."
"empty_text_2": "Automations connect triggers to actions in a ''when trigger then action'' fashion with optional conditions. For example: ''When the sun sets and if {user} is home, then turn on the lights''.",
"migrate_automation": "Migrate automation?",
"migrate_automation_description": "You can migrate this automation, so it can be edited from the UI. After it is migrated and you have saved it, you will have to manually delete your old automation from your configuration. Do you want to migrate this automation?"
},
"dialog_new": {
"header": "Create automation",
@@ -3215,7 +3222,7 @@
"description": {
"picker": "If an entity (or attribute) is in a specific state.",
"no_entity": "Confirm state",
"full": "If{hasAttribute, select, \n true { {attribute} of}\n other {}\n} {numberOfEntities, plural,\n zero {an entity is}\n one {{entities} is}\n other {{entities} are}\n} {numberOfStates, plural,\n zero {a state}\n other {{states}}\n}{hasDuration, select, \n true { for {duration}} \n other {}\n }"
"full": "If{hasAttribute, select, \n true { {attribute} of}\n other {}\n} {numberOfEntities, plural,\n =0 {an entity is}\n one {{entities} is}\n other {{entities} are}\n} {numberOfStates, plural,\n =0 {a state}\n other {{states}}\n}{hasDuration, select, \n true { for {duration}} \n other {}\n }"
}
},
"sun": {
@@ -3579,7 +3586,9 @@
"stopped_unknown_reason": "Stopped because of unknown reason {reason} at {time} (runtime: {executiontime} seconds)",
"disabled": "(disabled)",
"triggered_by": "{triggeredBy, select, \n alias {{alias} triggered}\n other {Triggered} \n} {triggeredPath, select, \n trigger {by the {trigger}}\n other {manually} \n} at {time}",
"path_error": "Unable to extract path {path}. Download trace and report as bug."
"path_error": "Unable to extract path {path}. Download trace and report as bug.",
"not_all_entries_are_related_automation_note": "Not all shown logbook entries might be related to this automation.",
"not_all_entries_are_related_script_note": "Not all shown logbook entries might be related to this script."
}
}
},
@@ -3676,7 +3685,9 @@
"duplicate": "[%key:ui::common::duplicate%]",
"empty_header": "Create your first script",
"empty_text": "A script is a sequence of actions that can be run from a dashboard, an automation, or be triggered by voice. For example, a ''Wake-up routine''' script that gradually turns on the light in the bedroom and opens the blinds after a delay.",
"search": "Search {number} scripts"
"search": "Search {number} scripts",
"migrate_script": "Migrate script?",
"migrate_script_description": "You can migrate this script, so it can be edited from the UI. After it is migrated and you have saved it, you will have to manually delete your old script from your configuration. Do you want to migrate this script?"
},
"dialog_new": {
"header": "Create script",
@@ -4115,7 +4126,7 @@
"hidden": "Hidden"
},
"confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?",
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to update them yourself to use the new entity IDs!",
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to manually edit them yourself to use the new entity IDs!",
"confirm_rename_entity_will_rename": "{count} {count, plural,\n one {entity ID}\n other {entity IDs}\n} will be renamed",
"confirm_rename_new": "New",
"confirm_rename_old": "Old",
@@ -4830,6 +4841,7 @@
"node_id": "ID",
"node_ready": "Ready",
"device_config": "Configure",
"installer_settings": "Installer settings",
"reinterview_device": "Re-interview",
"rebuild_routes": "Rebuild routes",
"remove_failed": "Remove failed",
@@ -5121,6 +5133,45 @@
"subscribed_to_logs": "Subscribed to Z-Wave JS log messages…",
"log_level_changed": "Log Level changed to: {level}",
"download_logs": "Download logs"
},
"node_installer": {
"header": "Installer Settings",
"introduction": "Configure your device installer settings.",
"endpoint": "Endpoint",
"no_settings": "This device does not have any installer settings.",
"command_class": "Command Class",
"capability_controls": {
"thermostat_setback": {
"title": "Thermostat Setback",
"setback_state_label": "Setback in 1/10 degrees (Kelvin)",
"setback_state_helper": "Min: -12.8, max: 12.0",
"setback_special_state": {
"label": "Setback special state",
"frost_protection": "Frost protection",
"energy_saving": "Energy saving",
"unused": "Unused"
},
"setback_type": {
"label": "Setback Type",
"none": "None",
"temporary": "Temporary",
"permanent": "Permanent"
},
"get_setback_failed": "Failed to get setback state. {error}",
"save_setback_failed": "Failed to save setback state. {error}"
},
"multilevel_switch": {
"title": "Transition",
"direction": "Direction",
"up": "Up",
"down": "Down",
"ignore_start_level": "Ignore start level",
"start_level": "Start level",
"start_transition": "Start transition",
"stop_transition": "Stop transition",
"control_failed": "Failed to control transition. {error}"
}
}
}
},
"matter": {
@@ -5306,7 +5357,7 @@
"share": "Share"
},
"mount_type": {
"nfs": "Network file share (NFS)",
"nfs": "Network File System (NFS)",
"cifs": "Samba/Windows (CIFS)"
},
"cifs_versions": {
@@ -5884,7 +5935,7 @@
},
"entities": {
"name": "Entities",
"show_header_toggle": "Show header toggle?",
"show_header_toggle": "Show header toggle",
"toggle": "Toggle entities.",
"description": "The Entities card is the most common type of card. It groups items together into lists.",
"special_row": "special row",
@@ -5931,9 +5982,9 @@
},
"gauge": {
"name": "Gauge",
"needle_gauge": "Display as needle gauge?",
"needle_gauge": "Display as needle gauge",
"severity": {
"define": "Define severity?",
"define": "Define severity",
"green": "Green",
"red": "Red",
"yellow": "Yellow"
@@ -6067,7 +6118,7 @@
"state": "State",
"secondary_info_attribute": "Secondary info attribute",
"search": "Search",
"state_color": "Color icons based on state?",
"state_color": "Show state color",
"suggested_cards": "Suggested cards",
"other_cards": "Other cards",
"custom_cards": "Custom cards",
@@ -6104,7 +6155,6 @@
"name": "Map",
"geo_location_sources": "Geolocation sources",
"no_geo_location_sources": "No geolocation sources available",
"dark_mode": "Dark mode?",
"appearance": "Appearance",
"theme_mode": "Theme Mode",
"theme_modes": {
@@ -6884,8 +6934,6 @@
}
},
"sections": {
"description": "This dashboard is using the sections view released in Home Assistant 2024.3. Learn more about it in this {blog_post}.",
"description_blog_post": "blog post",
"titles": {
"welcome": "Welcome",
"living_room": "Living room",
@@ -7203,6 +7251,7 @@
},
"create_account": "Create account",
"error": {
"username_not_normalized": "Username can only contain lowercase letters, and can not contain whitespace.",
"password_not_match": "Passwords don't match"
}
},
@@ -7372,6 +7421,7 @@
},
"dashboard": {
"changelog": "Changelog",
"current_version": "Current version: {version}",
"cpu_usage": "Add-on CPU usage",
"ram_usage": "Add-on RAM usage",
"hostname": "Hostname",
@@ -7472,7 +7522,7 @@
},
"watchdog": {
"title": "Watchdog",
"description": "This will start the add-on if it crashes"
"description": "This will restart the add-on if it crashes"
},
"auto_update": {
"title": "Auto update",
@@ -7845,6 +7895,62 @@
"restore": "[%key:ui::components::data-table::settings::restore%]"
}
}
},
"panel": {
"config": {
"logs": {
"caption": "[%key:ui::panel::config::logs::caption%]",
"description": "[%key:ui::panel::config::logs::description%]",
"details": "[%key:ui::panel::config::logs::details%]",
"search": "[%key:ui::panel::config::logs::search%]",
"failed_get_logs": "[%key:ui::panel::config::logs::failed_get_logs%]",
"no_issues_search": "[%key:ui::panel::config::logs::no_issues_search%]",
"load_logs": "[%key:ui::panel::config::logs::load_logs%]",
"nr_of_lines": "[%key:ui::panel::config::logs::nr_of_lines%]",
"loading_log": "[%key:ui::panel::config::logs::loading_log%]",
"no_errors": "[%key:ui::panel::config::logs::no_errors%]",
"no_issues": "[%key:ui::panel::config::logs::no_issues%]",
"clear": "[%key:ui::panel::config::logs::clear%]",
"refresh": "[%key:ui::panel::config::logs::refresh%]",
"copy": "[%key:ui::panel::config::logs::copy%]",
"log_provider": "[%key:ui::panel::config::logs::log_provider%]",
"multiple_messages": "[%key:ui::panel::config::logs::multiple_messages%]",
"level": {
"critical": "[%key:ui::panel::config::logs::level::critical%]",
"error": "[%key:ui::panel::config::logs::level::error%]",
"warning": "[%key:ui::panel::config::logs::level::warning%]",
"info": "[%key:ui::panel::config::logs::level::info%]",
"debug": "[%key:ui::panel::config::logs::level::debug%]"
},
"custom_integration": "[%key:ui::panel::config::logs::custom_integration%]",
"error_from_custom_integration": "[%key:ui::panel::config::logs::error_from_custom_integration%]",
"show_full_logs": "[%key:ui::panel::config::logs::show_full_logs%]",
"select_number_of_lines": "[%key:ui::panel::config::logs::select_number_of_lines%]",
"lines": "[%key:ui::panel::config::logs::lines%]",
"download_logs": "[%key:ui::panel::config::logs::download_logs%]",
"scroll_down_button": "[%key:ui::panel::config::logs::scroll_down_button%]",
"provider_not_found": "[%key:ui::panel::config::logs::provider_not_found%]",
"provider_not_available": "[%key:ui::panel::config::logs::provider_not_available%]",
"haos_boots_title": "[%key:ui::panel::config::logs::haos_boots_title%]",
"show_haos_boots": "[%key:ui::panel::config::logs::show_haos_boots%]",
"hide_haos_boots": "[%key:ui::panel::config::logs::hide_haos_boots%]",
"full_width": "[%key:ui::panel::config::logs::full_width%]",
"wrap_lines": "[%key:ui::panel::config::logs::wrap_lines%]",
"current": "[%key:ui::panel::config::logs::current%]",
"previous": "[%key:ui::panel::config::logs::previous%]",
"startups_ago": "[%key:ui::panel::config::logs::startups_ago%]",
"detail": {
"logger": "[%key:ui::panel::config::logs::detail::logger%]",
"source": "[%key:ui::panel::config::logs::detail::source%]",
"integration": "[%key:ui::panel::config::integrations::integration%]",
"documentation": "[%key:ui::panel::config::logs::detail::documentation%]",
"issues": "[%key:ui::panel::config::logs::detail::issues%]",
"first_occurred": "[%key:ui::panel::config::logs::detail::first_occurred%]",
"occurrences": "[%key:ui::panel::config::logs::detail::occurrences%]",
"last_logged": "[%key:ui::panel::config::logs::detail::last_logged%]"
}
}
}
}
}
}

View File

@@ -1,3 +1,6 @@
import type { HomeAssistant } from "../types";
import { isIosApp } from "./is_ios";
export const fileDownload = (href: string, filename = ""): void => {
const a = document.createElement("a");
a.target = "_blank";
@@ -8,3 +11,6 @@ export const fileDownload = (href: string, filename = ""): void => {
a.dispatchEvent(new MouseEvent("click"));
document.body.removeChild(a);
};
export const downloadFileSupported = (hass: HomeAssistant): boolean =>
!isIosApp(hass) || !!hass.auth.external?.config.downloadFileSupported;

5
src/util/is_ios.ts Normal file
View File

@@ -0,0 +1,5 @@
import type { HomeAssistant } from "../types";
import { isSafari } from "./is_safari";
export const isIosApp = (hass: HomeAssistant): boolean =>
isSafari && !!hass.auth.external;

388
yarn.lock
View File

@@ -1265,9 +1265,9 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/autocomplete@npm:6.18.1":
version: 6.18.1
resolution: "@codemirror/autocomplete@npm:6.18.1"
"@codemirror/autocomplete@npm:6.18.2":
version: 6.18.2
resolution: "@codemirror/autocomplete@npm:6.18.2"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
@@ -1278,7 +1278,7 @@ __metadata:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
"@lezer/common": ^1.0.0
checksum: 10/3b56ac6c57214e3e50c6ed79c12ac1822e3774afb033e0e4fb98dffd252f5ae64e5bed67dc2ad9cbd5d784373031be90995ddb1b36a10c16a2eef6af832041e2
checksum: 10/35bd17afb53e8c99b1342964616f0bcc13f5f06a5d4e2d9936afdaea61742b1c20b3856d513c5d5676e3a9b6fd95e997c842467d21dfa106845e65ab1720b2f4
languageName: node
linkType: hard
@@ -1317,14 +1317,14 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/search@npm:6.5.6":
version: 6.5.6
resolution: "@codemirror/search@npm:6.5.6"
"@codemirror/search@npm:6.5.7":
version: 6.5.7
resolution: "@codemirror/search@npm:6.5.7"
dependencies:
"@codemirror/state": "npm:^6.0.0"
"@codemirror/view": "npm:^6.0.0"
crelt: "npm:^1.0.5"
checksum: 10/6668a34b4617e909617d3d831627d74b7a7985e8cd86d396bfcb3e86262f2310fc029fd6c846f1b8f1e6768e75985c9f1b0b18b31e05341f06b5b75c1ffde38d
checksum: 10/0a4c5e23c42231ffb829513940ee43a630585b4277fa8cc919a947f3821c9c2dc095d334bb0e4d51b3ebb50739a34a81ddbcc39ca9c1f6f935fdaa51a86661bf
languageName: node
linkType: hard
@@ -1413,150 +1413,150 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/ecma402-abstract@npm:2.2.1":
version: 2.2.1
resolution: "@formatjs/ecma402-abstract@npm:2.2.1"
"@formatjs/ecma402-abstract@npm:2.2.3":
version: 2.2.3
resolution: "@formatjs/ecma402-abstract@npm:2.2.3"
dependencies:
"@formatjs/fast-memoize": "npm:2.2.2"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/fast-memoize": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/8c281e14cb5f12b8697225be6b0ac13d057911e257d3c23928aad985b535df90b7bb2a235aab22753a6e57aef98f00b826514fc3703e69018ccc98c8d9848f38
checksum: 10/d39e9f0d36c296a635f52aa35e07a67b6aa90383a30a046a0508e5d730676399fd0e67188eff463fe2a4d5febc9f567af45788fdf881e070910be7eb9294dd8c
languageName: node
linkType: hard
"@formatjs/fast-memoize@npm:2.2.2":
version: 2.2.2
resolution: "@formatjs/fast-memoize@npm:2.2.2"
"@formatjs/fast-memoize@npm:2.2.3":
version: 2.2.3
resolution: "@formatjs/fast-memoize@npm:2.2.3"
dependencies:
tslib: "npm:2"
checksum: 10/c6e958753eb41bb0875734762a44126a0d570706a31b32bb409e759cd372184c28e294b02fce0b0f0999c171ef717d513eaf7936862c498d78428b97db446ff8
checksum: 10/a9634acb5e03d051e09881eea5484ab02271f7d6b5f96ae9485674ab3c359aa881bc45fc07a1181ae4b2d6e288dadc169f578d142d698913ebbefa373014cac2
languageName: node
linkType: hard
"@formatjs/icu-messageformat-parser@npm:2.9.1":
version: 2.9.1
resolution: "@formatjs/icu-messageformat-parser@npm:2.9.1"
"@formatjs/icu-messageformat-parser@npm:2.9.3":
version: 2.9.3
resolution: "@formatjs/icu-messageformat-parser@npm:2.9.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/icu-skeleton-parser": "npm:1.8.5"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/icu-skeleton-parser": "npm:1.8.7"
tslib: "npm:2"
checksum: 10/f52c7c55b1dfc141910089a0494abd98d1c13c0a359cfb3bfa0668a5e2015c0c579bf161978fdb3ab40fa9a7374a37ac062f8710ed285429bf60abde4a5d1183
checksum: 10/b24a3db43e4bf612107e981d5b40c077543d2266a08aac5cf01d5f65bf60527d5d16795e2e30063cb180b1d36d401944cd2ffb3a19d79b0cd28fa59751d19b7c
languageName: node
linkType: hard
"@formatjs/icu-skeleton-parser@npm:1.8.5":
version: 1.8.5
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.5"
"@formatjs/icu-skeleton-parser@npm:1.8.7":
version: 1.8.7
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.7"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/ecma402-abstract": "npm:2.2.3"
tslib: "npm:2"
checksum: 10/5b9c57f80b751483bef8897ff9607a9eb215fd7a8d8ae9fa5c631edf6d16fa4532c853395f20b7f3f38d6d4d1a35b98cd06421291203c7ad333f52077ef2a406
checksum: 10/1a39815e5048f3c12a8d6a5b553271437b62e302724fc15c3b6967dc3e24823fcd9b8d3231a064991e163c147e54e588c571a092d557e93e78e738d218c6ef43
languageName: node
linkType: hard
"@formatjs/intl-datetimeformat@npm:6.16.1":
version: 6.16.1
resolution: "@formatjs/intl-datetimeformat@npm:6.16.1"
"@formatjs/intl-datetimeformat@npm:6.16.3":
version: 6.16.3
resolution: "@formatjs/intl-datetimeformat@npm:6.16.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/494868322d396e0eede6a27c16047858944f42fd3b45cf5d155f963df62e694b842ac0bef07e23aa73fa55cf143956d642d05ea62a3e762632101451975b5fc4
checksum: 10/4e213611b92eda40aa6053b9458be71fb752f020616bb0e93fc681efc4fc408dfec408ae33ded8678887730f8ee766568f90b6ca57de6e9d8f1de45dda794f08
languageName: node
linkType: hard
"@formatjs/intl-displaynames@npm:6.8.1":
version: 6.8.1
resolution: "@formatjs/intl-displaynames@npm:6.8.1"
"@formatjs/intl-displaynames@npm:6.8.3":
version: 6.8.3
resolution: "@formatjs/intl-displaynames@npm:6.8.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/627fc625e14b4d1bea5b2bf41e40050eb9775d0f66780e155719e21c062f9b3331d08b488ebcd3608c60999498af5a39e67cb5fd2a6d54a0e7395d7a63bfe643
checksum: 10/46c8d6e6d6d56d5f495c0bfb5784687a0af1ffd9eaeb72c1d9db8e21f8c7eeec346198871f8fe39f6eebfb19d6c3e46cbf92e213e6a6f0dfdb2f55fe96d43bcc
languageName: node
linkType: hard
"@formatjs/intl-enumerator@npm:1.8.1":
version: 1.8.1
resolution: "@formatjs/intl-enumerator@npm:1.8.1"
"@formatjs/intl-enumerator@npm:1.8.3":
version: 1.8.3
resolution: "@formatjs/intl-enumerator@npm:1.8.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/ecma402-abstract": "npm:2.2.3"
tslib: "npm:2"
checksum: 10/0e4250de905e757fb88d6ff072968c72ed3a39de8ddaed73c38c0099825f11530c9b8e224573ae6e46cf49f1318e463f40ba2cdfa25cb7415382ba952b570bdc
checksum: 10/a51ed7e15835cc1612282de46139d0f49553f004439a728a9118d1b9b15a3d05916e8aad4001e18c4909a3d4287fc07c921540c5ba8f32499f3243ac50d68a42
languageName: node
linkType: hard
"@formatjs/intl-getcanonicallocales@npm:2.5.1":
version: 2.5.1
resolution: "@formatjs/intl-getcanonicallocales@npm:2.5.1"
"@formatjs/intl-getcanonicallocales@npm:2.5.2":
version: 2.5.2
resolution: "@formatjs/intl-getcanonicallocales@npm:2.5.2"
dependencies:
tslib: "npm:2"
checksum: 10/5e83c0b3574333e5027c3c4f74ea20800e50e36fb8efa69361457b57f618738f478b5d22777ba30a2b7a15bdff60101d8119169c909b33577244747d52e59614
checksum: 10/0d1738181911635d91d4a788d663fadd1aa045f40f0f05ac8b04adc06cd4f5ee3c50aa7c3a50c63ba7572f23e336720340c8240d6070d899e56adf25d0388f1b
languageName: node
linkType: hard
"@formatjs/intl-listformat@npm:7.7.1":
version: 7.7.1
resolution: "@formatjs/intl-listformat@npm:7.7.1"
"@formatjs/intl-listformat@npm:7.7.3":
version: 7.7.3
resolution: "@formatjs/intl-listformat@npm:7.7.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/a64581f1d2e8e0c0c83c5d56334a3e3786ed251e1a882d7610d2588d8602eacb32c9167032891e2796c30df3437c9ce52c7284786dca6f1f44250301060169ea
checksum: 10/52ae02202a2bb0d8c16ea9a8f142d616e6ecb8400aa96ca618896cf529a3e3f5d88d64cb2644ad6a4ba7e17ee013d8fb3463419802afab5ca25afa51151ab62c
languageName: node
linkType: hard
"@formatjs/intl-locale@npm:4.2.1":
version: 4.2.1
resolution: "@formatjs/intl-locale@npm:4.2.1"
"@formatjs/intl-locale@npm:4.2.3":
version: 4.2.3
resolution: "@formatjs/intl-locale@npm:4.2.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-enumerator": "npm:1.8.1"
"@formatjs/intl-getcanonicallocales": "npm:2.5.1"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-enumerator": "npm:1.8.3"
"@formatjs/intl-getcanonicallocales": "npm:2.5.2"
tslib: "npm:2"
checksum: 10/4cba0fbeded2c7c5806528806f176cb833c43765bf1717470f4e001ab42581d5f0b52bf1893afef9597fba96dc3d4659507e490030f231523d460ec6686b9562
checksum: 10/dab4090653e62f1c3453c074c3047d0e22ee6a3d33ac00afa45f1541b8686b453c671755e8faeeb1253b61131071c02506b56c094941efd6195d40163007182e
languageName: node
linkType: hard
"@formatjs/intl-localematcher@npm:0.5.6":
version: 0.5.6
resolution: "@formatjs/intl-localematcher@npm:0.5.6"
"@formatjs/intl-localematcher@npm:0.5.7":
version: 0.5.7
resolution: "@formatjs/intl-localematcher@npm:0.5.7"
dependencies:
tslib: "npm:2"
checksum: 10/14eac6bb25dcfeedd7960f44dec5a137999729da00b294ddf1133abe760ced4342f37734bc750b4c47f8dd8d5633a7da38d274503f80d7e965bb1f6fb6f2988c
checksum: 10/52201f12212e7e9cba1a4f99020da587b13e44e06e03c4ccd4e5ac0829b411e73dfe0904a9039ef81eeabeea04ed8cfae9e727e6791acd0230745b7bd3ad059e
languageName: node
linkType: hard
"@formatjs/intl-numberformat@npm:8.14.1":
version: 8.14.1
resolution: "@formatjs/intl-numberformat@npm:8.14.1"
"@formatjs/intl-numberformat@npm:8.14.3":
version: 8.14.3
resolution: "@formatjs/intl-numberformat@npm:8.14.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/51152d1b9607a35c64e6089e44b90c7ec90be3b1925ba47ffc559ddb4fd72afae76e83af3d436831ea0fc47dc0e9fee9cd3d576280440f2dce03cb6bd24e0bed
checksum: 10/7a4e52ace65589ceb441032a09b88616e71ba4220605e498b1a064f43672cad5cca8c98b72446ffd7d57ef098c658c245c08a16623e0b1bc10940ff7e71069c7
languageName: node
linkType: hard
"@formatjs/intl-pluralrules@npm:5.3.1":
version: 5.3.1
resolution: "@formatjs/intl-pluralrules@npm:5.3.1"
"@formatjs/intl-pluralrules@npm:5.3.3":
version: 5.3.3
resolution: "@formatjs/intl-pluralrules@npm:5.3.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/fc83c3547a9f0af6331c2970f265234fde967848ff738730f2e87ce816636d8778ead1185f5ecccc692cb8b63c11412dc85deac9d3425f44fe3a6a6c30c8b776
checksum: 10/3679c63aa2b9dde474572998b829ecb5d134f1efe508e1e5a06089480bbff9f2216235a7d5745c434030dc17c8a83385f0639a71a22f1d648fcbc6fff90f57e3
languageName: node
linkType: hard
"@formatjs/intl-relativetimeformat@npm:11.4.1":
version: 11.4.1
resolution: "@formatjs/intl-relativetimeformat@npm:11.4.1"
"@formatjs/intl-relativetimeformat@npm:11.4.3":
version: 11.4.3
resolution: "@formatjs/intl-relativetimeformat@npm:11.4.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/intl-localematcher": "npm:0.5.6"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/intl-localematcher": "npm:0.5.7"
tslib: "npm:2"
checksum: 10/80817403301baed257fbd8c793b9ed077a2e6dd0414a6895b5bfde3619aebc818f30535da9b560a6186fac783cf09561c495d2c6568a980bd635736194655af5
checksum: 10/7c7548ba133031873683a37566d646e4e3f50ea979773de199b41769df23648be2b44b53975809bd53f97a95d1d44038c0a09b1c031e05f0de6f7bba843b1aad
languageName: node
linkType: hard
@@ -3944,7 +3944,41 @@ __metadata:
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5":
"@types/dom-webcodecs@npm:^0.1.13":
version: 0.1.13
resolution: "@types/dom-webcodecs@npm:0.1.13"
checksum: 10/99cb227416725efd4b22175ef18988ae3fc728480fe6ed2192777d7dba52d18af540b4df49fcfa3cf73753d1dcf9d5399b089702bde215d78679284848b078f7
languageName: node
linkType: hard
"@types/emscripten@npm:^1.39.13":
version: 1.39.13
resolution: "@types/emscripten@npm:1.39.13"
checksum: 10/02c0446150f9cc2c74dc3a551f86ce13df266c33d8b98d11d9f17263e2d98a6a6b4d36bdd15066c4e1547ae1ed2d52eed9420116b4935d119009e0f53ddbb041
languageName: node
linkType: hard
"@types/eslint-scope@npm:^3.7.7":
version: 3.7.7
resolution: "@types/eslint-scope@npm:3.7.7"
dependencies:
"@types/eslint": "npm:*"
"@types/estree": "npm:*"
checksum: 10/e2889a124aaab0b89af1bab5959847c5bec09809209255de0e63b9f54c629a94781daa04adb66bffcdd742f5e25a17614fb933965093c0eea64aacda4309380e
languageName: node
linkType: hard
"@types/eslint@npm:*":
version: 9.6.1
resolution: "@types/eslint@npm:9.6.1"
dependencies:
"@types/estree": "npm:*"
"@types/json-schema": "npm:*"
checksum: 10/719fcd255760168a43d0e306ef87548e1e15bffe361d5f4022b0f266575637acc0ecb85604ac97879ee8ae83c6a6d0613b0ed31d0209ddf22a0fe6d608fc56fe
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6":
version: 1.0.6
resolution: "@types/estree@npm:1.0.6"
checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d
@@ -4090,7 +4124,7 @@ __metadata:
languageName: node
linkType: hard
"@types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
version: 7.0.15
resolution: "@types/json-schema@npm:7.0.15"
checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7
@@ -5187,15 +5221,6 @@ __metadata:
languageName: node
linkType: hard
"acorn-import-attributes@npm:^1.9.5":
version: 1.9.5
resolution: "acorn-import-attributes@npm:1.9.5"
peerDependencies:
acorn: ^8
checksum: 10/8bfbfbb6e2467b9b47abb4d095df717ab64fce2525da65eabee073e85e7975fb3a176b6c8bba17c99a7d8ede283a10a590272304eb54a93c4aa1af9790d47a8b
languageName: node
linkType: hard
"acorn-jsx@npm:^5.3.2":
version: 5.3.2
resolution: "acorn-jsx@npm:5.3.2"
@@ -5205,12 +5230,12 @@ __metadata:
languageName: node
linkType: hard
"acorn@npm:^8.5.0, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0":
version: 8.13.0
resolution: "acorn@npm:8.13.0"
"acorn@npm:^8.14.0, acorn@npm:^8.5.0, acorn@npm:^8.8.2, acorn@npm:^8.9.0":
version: 8.14.0
resolution: "acorn@npm:8.14.0"
bin:
acorn: bin/acorn
checksum: 10/33e3a03114b02b3bc5009463b3d9549b31a90ee38ebccd5e66515830a02acf62a90edcc12abfb6c9fb3837b6c17a3ec9b72b3bf52ac31d8ad8248a4af871e0f5
checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2
languageName: node
linkType: hard
@@ -5731,6 +5756,16 @@ __metadata:
languageName: node
linkType: hard
"barcode-detector@npm:2.2.11":
version: 2.2.11
resolution: "barcode-detector@npm:2.2.11"
dependencies:
"@types/dom-webcodecs": "npm:^0.1.13"
zxing-wasm: "npm:1.2.14"
checksum: 10/91f04ac8a73a5fccf15d08c2b148e3f9584448956de9b2506f5c5c3213ba133c504cd6890926f4fde2b1294e42b9943f979820440bc8373388d5a86cb6c764c5
languageName: node
linkType: hard
"bare-events@npm:^2.2.0":
version: 2.5.0
resolution: "bare-events@npm:2.5.0"
@@ -5881,7 +5916,7 @@ __metadata:
languageName: node
linkType: hard
"browserslist@npm:^4.21.10, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0":
"browserslist@npm:^4.23.3, browserslist@npm:^4.24.0":
version: 4.24.0
resolution: "browserslist@npm:4.24.0"
dependencies:
@@ -6540,10 +6575,10 @@ __metadata:
languageName: node
linkType: hard
"core-js@npm:3.38.1":
version: 3.38.1
resolution: "core-js@npm:3.38.1"
checksum: 10/3c25fdf0b2595ed37ceb305213a61e2cf26185f628455e99d1c736dda5f69e2de4de7126e6a1da136f54260c4fcc982c4215e37b5a618790a597930f854c0a37
"core-js@npm:3.39.0":
version: 3.39.0
resolution: "core-js@npm:3.39.0"
checksum: 10/a3d34e669783dfc878e545f1983f60d9ff48a3867cd1d7ff8839b849e053002a208c7c14a5ca354b8e0b54982901e2f83dc87c3d9b95de0a94b4071d1c74e5f6
languageName: node
linkType: hard
@@ -8716,22 +8751,22 @@ __metadata:
"@babel/runtime": "npm:7.26.0"
"@braintree/sanitize-url": "npm:7.1.0"
"@bundle-stats/plugin-webpack-filter": "npm:4.16.0"
"@codemirror/autocomplete": "npm:6.18.1"
"@codemirror/autocomplete": "npm:6.18.2"
"@codemirror/commands": "npm:6.7.1"
"@codemirror/language": "npm:6.10.3"
"@codemirror/legacy-modes": "npm:6.4.1"
"@codemirror/search": "npm:6.5.6"
"@codemirror/search": "npm:6.5.7"
"@codemirror/state": "npm:6.4.1"
"@codemirror/view": "npm:6.34.1"
"@egjs/hammerjs": "npm:2.0.17"
"@formatjs/intl-datetimeformat": "npm:6.16.1"
"@formatjs/intl-displaynames": "npm:6.8.1"
"@formatjs/intl-getcanonicallocales": "npm:2.5.1"
"@formatjs/intl-listformat": "npm:7.7.1"
"@formatjs/intl-locale": "npm:4.2.1"
"@formatjs/intl-numberformat": "npm:8.14.1"
"@formatjs/intl-pluralrules": "npm:5.3.1"
"@formatjs/intl-relativetimeformat": "npm:11.4.1"
"@formatjs/intl-datetimeformat": "npm:6.16.3"
"@formatjs/intl-displaynames": "npm:6.8.3"
"@formatjs/intl-getcanonicallocales": "npm:2.5.2"
"@formatjs/intl-listformat": "npm:7.7.3"
"@formatjs/intl-locale": "npm:4.2.3"
"@formatjs/intl-numberformat": "npm:8.14.3"
"@formatjs/intl-pluralrules": "npm:5.3.3"
"@formatjs/intl-relativetimeformat": "npm:11.4.3"
"@fullcalendar/core": "npm:6.1.15"
"@fullcalendar/daygrid": "npm:6.1.15"
"@fullcalendar/interaction": "npm:6.1.15"
@@ -8822,12 +8857,13 @@ __metadata:
app-datepicker: "npm:5.1.1"
babel-loader: "npm:9.2.1"
babel-plugin-template-html-minifier: "npm:4.1.0"
barcode-detector: "npm:2.2.11"
browserslist-useragent-regexp: "npm:4.1.3"
chai: "npm:5.1.2"
chart.js: "npm:4.4.6"
color-name: "npm:2.0.0"
comlink: "npm:4.4.1"
core-js: "npm:3.38.1"
core-js: "npm:3.39.0"
cropperjs: "npm:1.6.2"
date-fns: "npm:4.1.0"
date-fns-tz: "npm:3.2.0"
@@ -8862,7 +8898,7 @@ __metadata:
husky: "npm:9.1.6"
idb-keyval: "npm:6.2.1"
instant-mocha: "npm:1.5.3"
intl-messageformat: "npm:10.7.3"
intl-messageformat: "npm:10.7.5"
js-yaml: "npm:4.1.0"
jszip: "npm:3.10.1"
leaflet: "npm:1.9.4"
@@ -8877,7 +8913,7 @@ __metadata:
map-stream: "npm:0.0.7"
marked: "npm:14.1.3"
memoize-one: "npm:6.0.0"
mocha: "npm:10.7.3"
mocha: "npm:10.8.2"
node-vibrant: "npm:3.2.1-alpha.1"
object-hash: "npm:3.0.0"
open: "npm:10.1.0"
@@ -8913,7 +8949,7 @@ __metadata:
vis-network: "npm:9.1.9"
vue: "npm:2.7.16"
vue2-daterange-picker: "npm:0.6.8"
webpack: "npm:5.95.0"
webpack: "npm:5.96.1"
webpack-cli: "npm:5.1.4"
webpack-dev-server: "npm:5.1.0"
webpack-manifest-plugin: "npm:5.0.0"
@@ -8921,12 +8957,12 @@ __metadata:
webpackbar: "npm:6.0.1"
weekstart: "npm:2.0.0"
workbox-build: "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
workbox-cacheable-response: "npm:7.1.0"
workbox-core: "npm:7.1.0"
workbox-expiration: "npm:7.1.0"
workbox-precaching: "npm:7.1.0"
workbox-routing: "npm:7.1.0"
workbox-strategies: "npm:7.1.0"
workbox-cacheable-response: "npm:7.3.0"
workbox-core: "npm:7.3.0"
workbox-expiration: "npm:7.3.0"
workbox-precaching: "npm:7.3.0"
workbox-routing: "npm:7.3.0"
workbox-strategies: "npm:7.3.0"
xss: "npm:1.0.15"
languageName: unknown
linkType: soft
@@ -9312,15 +9348,15 @@ __metadata:
languageName: node
linkType: hard
"intl-messageformat@npm:10.7.3":
version: 10.7.3
resolution: "intl-messageformat@npm:10.7.3"
"intl-messageformat@npm:10.7.5":
version: 10.7.5
resolution: "intl-messageformat@npm:10.7.5"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.2.1"
"@formatjs/fast-memoize": "npm:2.2.2"
"@formatjs/icu-messageformat-parser": "npm:2.9.1"
"@formatjs/ecma402-abstract": "npm:2.2.3"
"@formatjs/fast-memoize": "npm:2.2.3"
"@formatjs/icu-messageformat-parser": "npm:2.9.3"
tslib: "npm:2"
checksum: 10/e387f7f37a295d9d386af0c6392ba135a4580e86177161f1f400d470fed1f8c7b3cb6c724cbc2f50a7ded2e20f202977d8bf5e2bbc626f72016a5b5b6752b76d
checksum: 10/8880448d62bd0260eafd4ee7ccfabaea573476f28e6d6bf47e027ee9c1d46d4919a076df7abedaf282422ff80ade02b5c637c69cdf739ee405e4837098bac37e
languageName: node
linkType: hard
@@ -10966,9 +11002,9 @@ __metadata:
languageName: node
linkType: hard
"mocha@npm:10.7.3":
version: 10.7.3
resolution: "mocha@npm:10.7.3"
"mocha@npm:10.8.2":
version: 10.8.2
resolution: "mocha@npm:10.8.2"
dependencies:
ansi-colors: "npm:^4.1.3"
browser-stdout: "npm:^1.3.1"
@@ -10993,7 +11029,7 @@ __metadata:
bin:
_mocha: bin/_mocha
mocha: bin/mocha.js
checksum: 10/5757aeb320df2507338bfba41731070ce16d27177c5876672fff4bcc4f7b7bcf1afe6ec761bfded43a5d28032d7b797b8b905b5b44c9420203f3ee71457732c1
checksum: 10/903bbffcb195ef9d36b27db54e3462c5486de1397289e0953735b3530397a139336c452bcf5188c663496c660d2285bbb6c7213290d36d536ad647b6145cb917
languageName: node
linkType: hard
@@ -14678,17 +14714,17 @@ __metadata:
languageName: node
linkType: hard
"webpack@npm:5.95.0":
version: 5.95.0
resolution: "webpack@npm:5.95.0"
"webpack@npm:5.96.1":
version: 5.96.1
resolution: "webpack@npm:5.96.1"
dependencies:
"@types/estree": "npm:^1.0.5"
"@types/eslint-scope": "npm:^3.7.7"
"@types/estree": "npm:^1.0.6"
"@webassemblyjs/ast": "npm:^1.12.1"
"@webassemblyjs/wasm-edit": "npm:^1.12.1"
"@webassemblyjs/wasm-parser": "npm:^1.12.1"
acorn: "npm:^8.7.1"
acorn-import-attributes: "npm:^1.9.5"
browserslist: "npm:^4.21.10"
acorn: "npm:^8.14.0"
browserslist: "npm:^4.24.0"
chrome-trace-event: "npm:^1.0.2"
enhanced-resolve: "npm:^5.17.1"
es-module-lexer: "npm:^1.2.1"
@@ -14710,7 +14746,7 @@ __metadata:
optional: true
bin:
webpack: bin/webpack.js
checksum: 10/0377ad3a550b041f26237c96fb55754625b0ce6bae83c1c2447e3262ad056b0b0ad770dcbb92b59f188e9a2bd56155ce910add17dcf023cfbe78bdec774380c1
checksum: 10/d3419ffd198252e1d0301bd0c072cee93172f3e47937c745aa8202691d2f5d529d4ba4a1965d1450ad89a1bcd3c1f70ae09e57232b0d01dd38d69c1060e964d5
languageName: node
linkType: hard
@@ -14990,6 +15026,15 @@ __metadata:
languageName: node
linkType: hard
"workbox-cacheable-response@npm:7.3.0":
version: 7.3.0
resolution: "workbox-cacheable-response@npm:7.3.0"
dependencies:
workbox-core: "npm:7.3.0"
checksum: 10/44cd7bc26e509ca96b1b84e3ff5964296efa645853f114f39789d21c0a214ca5fc047259910b303e220bb4052155cddc5639993fcee076fac496b4895ff17a15
languageName: node
linkType: hard
"workbox-core@npm:7.1.0":
version: 7.1.0
resolution: "workbox-core@npm:7.1.0"
@@ -14997,6 +15042,13 @@ __metadata:
languageName: node
linkType: hard
"workbox-core@npm:7.3.0":
version: 7.3.0
resolution: "workbox-core@npm:7.3.0"
checksum: 10/228fb7018a0568c329e21d47d84980f93ebfef9b1eb3f40ddc3516ca6ae58d51dc7ca4dddc829332775b59a3079e62d105c5e1c5c312805d177b963f8bf54393
languageName: node
linkType: hard
"workbox-expiration@npm:7.1.0":
version: 7.1.0
resolution: "workbox-expiration@npm:7.1.0"
@@ -15007,6 +15059,16 @@ __metadata:
languageName: node
linkType: hard
"workbox-expiration@npm:7.3.0":
version: 7.3.0
resolution: "workbox-expiration@npm:7.3.0"
dependencies:
idb: "npm:^7.0.1"
workbox-core: "npm:7.3.0"
checksum: 10/83e021d700e521a65a89907679d1a580aacc0419428286910ec7c6b0a538326f71f05566434f666ebf6c9fbe819ef3ea81428df1d868f9ea92527afe5d11152d
languageName: node
linkType: hard
"workbox-google-analytics@npm:7.1.0":
version: 7.1.0
resolution: "workbox-google-analytics@npm:7.1.0"
@@ -15039,6 +15101,17 @@ __metadata:
languageName: node
linkType: hard
"workbox-precaching@npm:7.3.0":
version: 7.3.0
resolution: "workbox-precaching@npm:7.3.0"
dependencies:
workbox-core: "npm:7.3.0"
workbox-routing: "npm:7.3.0"
workbox-strategies: "npm:7.3.0"
checksum: 10/d14135c471a45de36438c40eed7cb7157cdb336d4216a775486c6307d1ac316794d64231c2e2d0a4c313bb4a4fec623ab77e391cc458b4f2afa64e2487acb2e8
languageName: node
linkType: hard
"workbox-range-requests@npm:7.1.0":
version: 7.1.0
resolution: "workbox-range-requests@npm:7.1.0"
@@ -15071,6 +15144,15 @@ __metadata:
languageName: node
linkType: hard
"workbox-routing@npm:7.3.0":
version: 7.3.0
resolution: "workbox-routing@npm:7.3.0"
dependencies:
workbox-core: "npm:7.3.0"
checksum: 10/0d729f9c5cfc5754404ac1f7b729c7740ddc806203792701ac642151fbec939b4aa0fb289eab2295e49180e8154ad9bb1380effb7e0f0362163b79db4291dba7
languageName: node
linkType: hard
"workbox-strategies@npm:7.1.0":
version: 7.1.0
resolution: "workbox-strategies@npm:7.1.0"
@@ -15080,6 +15162,15 @@ __metadata:
languageName: node
linkType: hard
"workbox-strategies@npm:7.3.0":
version: 7.3.0
resolution: "workbox-strategies@npm:7.3.0"
dependencies:
workbox-core: "npm:7.3.0"
checksum: 10/61ba672075ef8aaa70ad9221460dab80a7d8920e324e14137460f26ebe8b137e5589fb75c664e0efeaf4402e3d8435a9b1818f9a9c61f88863c0e0315af337e7
languageName: node
linkType: hard
"workbox-streams@npm:7.1.0":
version: 7.1.0
resolution: "workbox-streams@npm:7.1.0"
@@ -15399,3 +15490,12 @@ __metadata:
checksum: 10/f2e05b767ed3141e6372a80af9caa4715d60969227f38b1a4370d60bffe153c9c5b33a862905609afc9b375ec57cd40999810d20e5e10229a204e8bde7ef255c
languageName: node
linkType: hard
"zxing-wasm@npm:1.2.14":
version: 1.2.14
resolution: "zxing-wasm@npm:1.2.14"
dependencies:
"@types/emscripten": "npm:^1.39.13"
checksum: 10/02ea0408553f1aebb412a97c5e11887c58da1b2adff636302232535e752b91bdb3f358411259b6cddd34b341df565336bc03f48895e362f61316018d9bbff8c3
languageName: node
linkType: hard