Compare commits
50 Commits
20241031.0
...
add-suppor
Author | SHA1 | Date | |
---|---|---|---|
![]() |
010e25b49e | ||
![]() |
a08c7a319f | ||
![]() |
5e8868e4b1 | ||
![]() |
64285d5155 | ||
![]() |
5247b74fd4 | ||
![]() |
26e914290d | ||
![]() |
ed3096157c | ||
![]() |
04a45a4361 | ||
![]() |
5430040b96 | ||
![]() |
4bd70167ad | ||
![]() |
e908fbb48e | ||
![]() |
38da01abfa | ||
![]() |
c3b7ce8dc4 | ||
![]() |
0488d199ac | ||
![]() |
df3e4576db | ||
![]() |
6bd7788815 | ||
![]() |
9cdae4fea7 | ||
![]() |
7adf9f8526 | ||
![]() |
35dcb46703 | ||
![]() |
17db85ebad | ||
![]() |
fa39595c37 | ||
![]() |
4db908171f | ||
![]() |
7306b8c102 | ||
![]() |
928bf3465e | ||
![]() |
0b38143765 | ||
![]() |
2f974078e0 | ||
![]() |
efe90fcc55 | ||
![]() |
01e33f5412 | ||
![]() |
51fdc484c3 | ||
![]() |
3d9fa462a6 | ||
![]() |
32b5d67806 | ||
![]() |
20d3681da3 | ||
![]() |
9b97274bf6 | ||
![]() |
ede0dff030 | ||
![]() |
4cd4635fa5 | ||
![]() |
7832219749 | ||
![]() |
a8d4726caf | ||
![]() |
4b3e20c6ca | ||
![]() |
f9a53743ce | ||
![]() |
89250c0c01 | ||
![]() |
4ef944ea08 | ||
![]() |
5f58c183f4 | ||
![]() |
f71feff916 | ||
![]() |
50fb3b314b | ||
![]() |
06298562cd | ||
![]() |
89e74f3f07 | ||
![]() |
da96c27893 | ||
![]() |
3321dd4ca7 | ||
![]() |
7106d56b33 | ||
![]() |
25cd8a9d9f |
2
.github/workflows/relative-ci.yaml
vendored
@@ -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 }}
|
||||
|
2
.github/workflows/release.yaml
vendored
@@ -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
|
||||
|
@@ -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);
|
||||
});
|
||||
|
||||
|
@@ -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>
|
||||
`;
|
@@ -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: () => ({}),
|
||||
|
@@ -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();
|
||||
|
@@ -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(
|
||||
|
@@ -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
|
||||
|
41
package.json
@@ -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",
|
||||
|
Before Width: | Height: | Size: 5.7 KiB |
BIN
public/static/images/voice-assistant/area.png
Normal file
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 5.9 KiB |
BIN
public/static/images/voice-assistant/change-wake-word.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.8 KiB |
BIN
public/static/images/voice-assistant/error.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
public/static/images/voice-assistant/great-job.png
Normal file
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 6.4 KiB |
BIN
public/static/images/voice-assistant/heart.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 5.9 KiB |
BIN
public/static/images/voice-assistant/hi.png
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 5.9 KiB |
BIN
public/static/images/voice-assistant/ok-nabu.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.3 KiB |
BIN
public/static/images/voice-assistant/sleep.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 6.0 KiB |
BIN
public/static/images/voice-assistant/update.png
Normal file
After Width: | Height: | Size: 21 KiB |
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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`
|
||||
|
@@ -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", {
|
||||
|
@@ -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 {
|
||||
|
@@ -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> = {};
|
||||
|
@@ -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);
|
||||
}
|
||||
|
26
src/components/ha-md-select-option.ts
Normal 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;
|
||||
}
|
||||
}
|
32
src/components/ha-md-select.ts
Normal 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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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(
|
||||
|
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -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`
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
};
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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 });
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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))
|
||||
|
@@ -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>
|
||||
|
@@ -264,6 +264,7 @@ export interface ExternalConfig {
|
||||
hasAssist: boolean;
|
||||
hasBarCodeScanner: number;
|
||||
canSetupImprov: boolean;
|
||||
downloadFileSupported: boolean;
|
||||
}
|
||||
|
||||
export class ExternalMessaging {
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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>`;
|
||||
}
|
||||
|
@@ -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 },
|
||||
});
|
||||
|
@@ -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,
|
||||
});
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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",
|
||||
|
@@ -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}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@@ -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),
|
||||
}
|
||||
),
|
||||
});
|
||||
|
@@ -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),
|
||||
}
|
||||
),
|
||||
});
|
||||
|
@@ -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!),
|
||||
}
|
||||
),
|
||||
});
|
||||
|
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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");
|
||||
|
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -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"),
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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()),
|
||||
})
|
||||
);
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
@@ -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
@@ -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
|
||||
|