diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ca5bb5e97d..d6033abcd0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,12 +2,13 @@ "name": "Home Assistant Frontend", "build": { "dockerfile": "Dockerfile", - "context": "..", + "context": ".." }, "appPort": "8124:8123", + "postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev", "postStartCommand": "script/bootstrap", "containerEnv": { - "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}", + "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" }, "customizations": { "vscode": { @@ -16,7 +17,7 @@ "esbenp.prettier-vscode", "runem.lit-plugin", "github.vscode-pull-request-github", - "eamodio.gitlens", + "eamodio.gitlens" ], "settings": { "files.eol": "\n", @@ -27,17 +28,17 @@ "editor.renderWhitespace": "boundary", "editor.rulers": [80], "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "files.trimTrailingWhitespace": true, "terminal.integrated.shell.linux": "/usr/bin/zsh", "gitlens.showWelcomeOnInstall": false, "gitlens.showWhatsNewAfterUpgrades": false, - "workbench.startupEditor": "none", - }, - }, - }, + "workbench.startupEditor": "none" + } + } + } } diff --git a/.github/workflows/cast_deployment.yaml b/.github/workflows/cast_deployment.yaml index 2aa9ce5168..c65362b228 100644 --- a/.github/workflows/cast_deployment.yaml +++ b/.github/workflows/cast_deployment.yaml @@ -26,7 +26,7 @@ jobs: ref: dev - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -62,7 +62,7 @@ jobs: ref: master - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2becc88dff..cf36bba4e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,7 +26,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -60,7 +60,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -78,7 +78,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -89,7 +89,7 @@ jobs: env: IS_TEST: "true" - name: Upload bundle stats - uses: actions/upload-artifact@v4.3.0 + uses: actions/upload-artifact@v4.3.1 with: name: frontend-bundle-stats path: build/stats/*.json @@ -102,7 +102,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v4.1.1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -113,7 +113,7 @@ jobs: env: IS_TEST: "true" - name: Upload bundle stats - uses: actions/upload-artifact@v4.3.0 + uses: actions/upload-artifact@v4.3.1 with: name: supervisor-bundle-stats path: build/stats/*.json diff --git a/.github/workflows/demo_deployment.yaml b/.github/workflows/demo_deployment.yaml index 9aa8219845..8e2fa8ea3d 100644 --- a/.github/workflows/demo_deployment.yaml +++ b/.github/workflows/demo_deployment.yaml @@ -27,7 +27,7 @@ jobs: ref: dev - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -63,7 +63,7 @@ jobs: ref: master - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/design_deployment.yaml b/.github/workflows/design_deployment.yaml index 7fd0110210..829dd9ee61 100644 --- a/.github/workflows/design_deployment.yaml +++ b/.github/workflows/design_deployment.yaml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/design_preview.yaml b/.github/workflows/design_preview.yaml index b24148d66d..439e0c05b5 100644 --- a/.github/workflows/design_preview.yaml +++ b/.github/workflows/design_preview.yaml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 1d62f1071f..d382bcfbed 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -28,7 +28,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn @@ -57,14 +57,14 @@ jobs: run: tar -czvf translations.tar.gz translations - name: Upload build artifacts - uses: actions/upload-artifact@v4.3.0 + uses: actions/upload-artifact@v4.3.1 with: name: wheels path: dist/home_assistant_frontend*.whl if-no-files-found: error - name: Upload translations - uses: actions/upload-artifact@v4.3.0 + uses: actions/upload-artifact@v4.3.1 with: name: translations path: translations.tar.gz diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dc843252dd..013ad89f62 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -34,7 +34,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: ".nvmrc" cache: yarn diff --git a/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch b/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch new file mode 100644 index 0000000000..9b177dc614 --- /dev/null +++ b/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch @@ -0,0 +1,13 @@ +diff --git a/simple-tooltip.js b/simple-tooltip.js +index 78a87f6a223925f0e29fbedb268c85a142ec6985..3d686dd6a3d5a93342b4b01408089fc316b408ca 100644 +--- a/simple-tooltip.js ++++ b/simple-tooltip.js +@@ -195,6 +195,8 @@ class SimpleTooltip extends LitElement { + .hidden { + position: absolute; + left: -10000px; ++ inset-inline-start: -10000px; ++ inset-inline-end: initial; + top: auto; + width: 1px; + height: 1px; diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index 6365ef3104..f55d77f345 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -115,7 +115,9 @@ gulp.task("webpack-prod-app", () => gulp.task("webpack-dev-server-demo", () => runDevServer({ - compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })), + compiler: webpack( + createDemoConfig({ isProdBuild: false, latestBuild: true }) + ), contentBase: paths.demo_output_root, port: 8090, }) @@ -131,7 +133,9 @@ gulp.task("webpack-prod-demo", () => gulp.task("webpack-dev-server-cast", () => runDevServer({ - compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })), + compiler: webpack( + createCastConfig({ isProdBuild: false, latestBuild: true }) + ), contentBase: paths.cast_output_root, port: 8080, // Accessible from the network, because that's how Cast hits it. @@ -174,8 +178,9 @@ gulp.task("webpack-prod-hassio", () => gulp.task("webpack-dev-server-gallery", () => runDevServer({ - // We don't use the es5 build, but the dev server will fuck up the publicPath if we don't - compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })), + compiler: webpack( + createGalleryConfig({ isProdBuild: false, latestBuild: true }) + ), contentBase: paths.gallery_output_root, port: 8100, listenHost: "0.0.0.0", diff --git a/cast/src/receiver/layout/hc-launch-screen.ts b/cast/src/receiver/layout/hc-launch-screen.ts index 49be9d0700..ba70fb7452 100644 --- a/cast/src/receiver/layout/hc-launch-screen.ts +++ b/cast/src/receiver/layout/hc-launch-screen.ts @@ -43,11 +43,6 @@ class HcLaunchScreen extends LitElement { max-width: 80%; object-fit: cover; } - .status { - padding-right: 54px; - padding-inline-end: 54px; - padding-inline-start: initial; - } `; } } diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index d748b8184e..01b6f03fb0 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -93,7 +93,7 @@ class HcLovelace extends LitElement { } private get _viewIndex() { - if (this.viewPath === null || this.viewPath === undefined) { + if (this.viewPath === null) { return 0; } const selectedView = this.viewPath; diff --git a/gallery/src/components/demo-black-white-row.ts b/gallery/src/components/demo-black-white-row.ts index 30b2c533b7..851cb5bb71 100644 --- a/gallery/src/components/demo-black-white-row.ts +++ b/gallery/src/components/demo-black-white-row.ts @@ -1,5 +1,5 @@ import { Button } from "@material/mwc-button"; -import { html, LitElement, css, TemplateResult } from "lit"; +import { html, LitElement, css, TemplateResult, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; import { fireEvent } from "../../../src/common/dom/fire_event"; @@ -9,7 +9,7 @@ import "../../../src/components/ha-card"; class DemoBlackWhiteRow extends LitElement { @property() title!: string; - @property() value!: any; + @property() value?: any; @property({ type: Boolean }) public disabled = false; @@ -45,7 +45,9 @@ class DemoBlackWhiteRow extends LitElement { -
${JSON.stringify(this.value, undefined, 2)}
+ ${this.value + ? html`
${JSON.stringify(this.value, undefined, 2)}
` + : nothing} `; diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 78dbe681bb..fceab71c29 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -275,6 +275,14 @@ const SCHEMAS: { selector: { color_temp: {} }, }, color_rgb: { name: "Color", selector: { color_rgb: {} } }, + qr_code: { + name: "QR Code", + selector: { qr_code: { data: "https://home-assistant.io" } }, + }, + constant: { + name: "Constant", + selector: { constant: { value: true, label: "Yes!" } }, + }, }, }, { @@ -501,7 +509,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { this.requestUpdate(); }; return html` - + ${["light", "dark"].map((slot) => Object.entries(info.input).map( ([key, value]) => html` @@ -534,8 +542,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { } static styles = css` - ha-selector { - width: 60; + ha-settings-row { + --paper-item-body-two-line-min-height: 0; } .options { max-width: 800px; diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 8b183fc51d..e90b447de6 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -1263,6 +1263,7 @@ class HassioAddonInfo extends LitElement { .card-actions { justify-content: space-between; display: flex; + direction: var(--direction); } .changelog { display: contents; diff --git a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts index b4904b83cc..62298180ae 100644 --- a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts +++ b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts @@ -154,12 +154,16 @@ class HassioHardwareDialog extends LitElement { ha-icon-button { position: absolute; right: 16px; + inset-inline-end: 16px; + inset-inline-start: initial; top: 10px; text-decoration: none; color: var(--primary-text-color); } h2 { margin: 18px 42px 0 18px; + margin-inline-start: 18px; + margin-inline-end: 42px; color: var(--primary-text-color); } diff --git a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts index 74ce8d5a6a..47f107a6c5 100644 --- a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts +++ b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts @@ -1,7 +1,5 @@ import "@material/mwc-button/mwc-button"; import { mdiDelete, mdiDeleteOff } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -27,6 +25,8 @@ import type { HomeAssistant } from "../../../../src/types"; import { HassioRepositoryDialogParams } from "./show-dialog-repositories"; import type { HaTextField } from "../../../../src/components/ha-textfield"; import "../../../../src/components/ha-textfield"; +import "../../../../src/components/ha-list-new"; +import "../../../../src/components/ha-list-item-new"; @customElement("dialog-hassio-repositories") class HassioRepositoriesDialog extends LitElement { @@ -106,44 +106,46 @@ class HassioRepositoriesDialog extends LitElement { ? html`${this._error}` : ""}
- ${repositories.length - ? repositories.map( - (repo) => html` - - -
${repo.name}
-
${repo.maintainer}
-
${repo.url}
-
-
- - - - ${this._dialogParams!.supervisor.localize( - usedRepositories.includes(repo.slug) - ? "dialog.repositories.used" - : "dialog.repositories.remove" - )} - -
-
- ` - ) - : html` No repositories `} + + ${repositories.length + ? repositories.map( + (repo) => html` + + ${repo.name} +
+
${repo.maintainer}
+
${repo.url}
+
+
+ + + + ${this._dialogParams!.supervisor.localize( + usedRepositories.includes(repo.slug) + ? "dialog.repositories.used" + : "dialog.repositories.remove" + )} + +
+
+ ` + ) + : html` No repositories `} +
(isCore(page) || isLoadedIntegration(hass, page)) && - !hideAdvancedPage(hass, page); + !hideAdvancedPage(hass, page) && + isNotLoadedIntegration(hass, page); const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) => - page.component - ? isComponentLoaded(hass, page.component) - : page.components - ? page.components.some((integration) => - isComponentLoaded(hass, integration) - ) - : true; + !page.component || + ensureArray(page.component).some((integration) => + isComponentLoaded(hass, integration) + ); + +const isNotLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) => + !page.not_component || + !ensureArray(page.not_component).some((integration) => + isComponentLoaded(hass, integration) + ); + const isCore = (page: PageNavigation) => page.core; const isAdvancedPage = (page: PageNavigation) => page.advancedOnly; const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced; diff --git a/src/common/dom/get_main_window.ts b/src/common/dom/get_main_window.ts index a2a5cca7b1..0f727eb244 100644 --- a/src/common/dom/get_main_window.ts +++ b/src/common/dom/get_main_window.ts @@ -1,8 +1,13 @@ import { MAIN_WINDOW_NAME } from "../../data/main_window"; -export const mainWindow = - window.name === MAIN_WINDOW_NAME - ? window - : parent.name === MAIN_WINDOW_NAME - ? parent - : top!; +export const mainWindow = (() => { + try { + return window.name === MAIN_WINDOW_NAME + ? window + : parent.name === MAIN_WINDOW_NAME + ? parent + : top!; + } catch { + return window; + } +})(); diff --git a/src/common/entity/compute_attribute_display.ts b/src/common/entity/compute_attribute_display.ts index 77bc61bbd4..5e66ae757f 100644 --- a/src/common/entity/compute_attribute_display.ts +++ b/src/common/entity/compute_attribute_display.ts @@ -53,9 +53,7 @@ export const computeAttributeValueDisplay = ( if (domain === "weather") { unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute); - } - - if (TEMPERATURE_ATTRIBUTES.has(attribute)) { + } else if (TEMPERATURE_ATTRIBUTES.has(attribute)) { unit = config.unit_system.temperature; } diff --git a/src/common/util/array-move.ts b/src/common/util/array-move.ts index 36a152a019..0985f764c0 100644 --- a/src/common/util/array-move.ts +++ b/src/common/util/array-move.ts @@ -20,14 +20,14 @@ function findNestedItem( }, obj); } -export function nestedArrayMove( - obj: T | T[], +export function nestedArrayMove( + obj: A, oldIndex: number, newIndex: number, oldPath?: ItemPath, newPath?: ItemPath -): T | T[] { - const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; +): A { + const newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A; const from = oldPath ? findNestedItem(newObj, oldPath) : newObj; const to = newPath ? findNestedItem(newObj, newPath, true) : newObj; diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index aa4a90fef8..7bf2acc739 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -5,10 +5,18 @@ import type { ChartOptions, TooltipModel, } from "chart.js"; -import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { + css, + CSSResultGroup, + html, + nothing, + LitElement, + PropertyValues, +} from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; +import { fireEvent } from "../../common/dom/fire_event"; import { clamp } from "../../common/number/clamp"; import { HomeAssistant } from "../../types"; import { debounce } from "../../common/util/debounce"; @@ -27,6 +35,11 @@ interface Tooltip left: string; } +export interface ChartDatasetExtra { + show_legend?: boolean; + legend_label?: string; +} + @customElement("ha-chart-base") export class HaChartBase extends LitElement { public chart?: Chart; @@ -38,6 +51,8 @@ export class HaChartBase extends LitElement { @property({ attribute: false }) public data: ChartData = { datasets: [] }; + @property({ attribute: false }) public extraData?: ChartDatasetExtra[]; + @property({ attribute: false }) public options?: ChartOptions; @property({ attribute: false }) public plugins?: any[]; @@ -46,6 +61,8 @@ export class HaChartBase extends LitElement { @property({ type: Number }) public paddingYAxis = 0; + @property({ type: Boolean }) public externalHidden = false; + @state() private _chartHeight?: number; @state() private _tooltip?: Tooltip; @@ -58,6 +75,8 @@ export class HaChartBase extends LitElement { private _paddingYAxisInternal = 0; + private _datasetOrder: number[] = []; + public disconnectedCallback() { super.disconnectedCallback(); this._releaseCanvas(); @@ -148,6 +167,29 @@ export class HaChartBase extends LitElement { } } + // put the legend labels in sorted order if provided + if (changedProps.has("data")) { + this._datasetOrder = this.data.datasets.map((_, index) => index); + if (this.data?.datasets.some((dataset) => dataset.order)) { + this._datasetOrder.sort( + (a, b) => + (this.data.datasets[a].order || 0) - + (this.data.datasets[b].order || 0) + ); + } + + if (this.externalHidden) { + this._hiddenDatasets = new Set(); + if (this.data?.datasets) { + this.data.datasets.forEach((dataset, index) => { + if (dataset.hidden) { + this._hiddenDatasets.add(index); + } + }); + } + } + } + if (!this.hasUpdated || !this.chart) { return; } @@ -157,7 +199,7 @@ export class HaChartBase extends LitElement { return; } if (changedProps.has("data")) { - if (this._hiddenDatasets.size) { + if (this._hiddenDatasets.size && !this.externalHidden) { this.data.datasets.forEach((dataset, index) => { dataset.hidden = this._hiddenDatasets.has(index); }); @@ -175,26 +217,32 @@ export class HaChartBase extends LitElement { ${this.options?.plugins?.legend?.display === true ? html`
    - ${this.data.datasets.map( - (dataset, index) => - html`
  • -
    { + const dataset = this.data.datasets[index]; + return this.extraData?.[index]?.show_legend === false + ? nothing + : html`
-
${dataset.label}
- ` - )} + .title=${this.extraData?.[index]?.legend_label ?? + dataset.label} + > +
+
+ ${this.extraData?.[index]?.legend_label ?? + dataset.label} +
+ `; + })}
` : ""} @@ -211,9 +259,9 @@ export class HaChartBase extends LitElement { height: `${ this.height ?? this._chartHeight ?? this.clientWidth / 2 }px`, - "padding-left": `${this._paddingYAxisInternal}`, + "padding-left": `${this._paddingYAxisInternal}px`, "padding-right": 0, - "padding-inline-start": `${this._paddingYAxisInternal}`, + "padding-inline-start": `${this._paddingYAxisInternal}px`, "padding-inline-end": 0, })} > @@ -339,9 +387,19 @@ export class HaChartBase extends LitElement { if (this.chart.isDatasetVisible(index)) { this.chart.setDatasetVisibility(index, false); this._hiddenDatasets.add(index); + if (this.externalHidden) { + fireEvent(this, "dataset-hidden", { + index, + }); + } } else { this.chart.setDatasetVisibility(index, true); this._hiddenDatasets.delete(index); + if (this.externalHidden) { + fireEvent(this, "dataset-unhidden", { + index, + }); + } } this.chart.update("none"); this.requestUpdate("_hiddenDatasets"); @@ -486,4 +544,8 @@ declare global { interface HTMLElementTagNameMap { "ha-chart-base": HaChartBase; } + interface HASSDomEvents { + "dataset-hidden": { index: number }; + "dataset-unhidden": { index: number }; + } } diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index 5f6286ecef..0923fb0702 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -111,7 +111,7 @@ export class StateHistoryChartLine extends LitElement { config: this.hass.config, }, }, - suggestedMin: this.startTime, + min: this.startTime, suggestedMax: this.endTime, ticks: { maxRotation: 0, diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index 8bfb0fef8b..c70711ae64 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -114,7 +114,7 @@ export class StateHistoryChartTimeline extends LitElement { config: this.hass.config, }, }, - suggestedMin: this.startTime, + min: this.startTime, suggestedMax: this.endTime, ticks: { autoSkip: true, diff --git a/src/components/chart/state-history-charts.ts b/src/components/chart/state-history-charts.ts index 7f72aa5f2b..eb47c5b31a 100644 --- a/src/components/chart/state-history-charts.ts +++ b/src/components/chart/state-history-charts.ts @@ -233,16 +233,32 @@ export class StateHistoryCharts extends LitElement { new Date().getTime() - 60 * 60 * this.hoursToShow * 1000 ); } else { - this._computedStartTime = new Date( - (this.historyData?.timeline ?? []).reduce( - (minTime, stateInfo) => - Math.min( - minTime, - new Date(stateInfo.data[0].last_changed).getTime() - ), - new Date().getTime() - ) + let minTimeAll = (this.historyData?.timeline ?? []).reduce( + (minTime, stateInfo) => + Math.min( + minTime, + new Date(stateInfo.data[0].last_changed).getTime() + ), + new Date().getTime() ); + + minTimeAll = (this.historyData?.line ?? []).reduce( + (minTimeLine, line) => + Math.min( + minTimeLine, + line.data.reduce( + (minTimeData, data) => + Math.min( + minTimeData, + new Date(data.states[0].last_changed).getTime() + ), + minTimeLine + ) + ), + minTimeAll + ); + + this._computedStartTime = new Date(minTimeAll); } } } diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 0e2fa2a9f5..0c5597878c 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -32,7 +32,11 @@ import { } from "../../data/recorder"; import type { HomeAssistant } from "../../types"; import "./ha-chart-base"; -import type { ChartResizeOptions, HaChartBase } from "./ha-chart-base"; +import type { + ChartResizeOptions, + ChartDatasetExtra, + HaChartBase, +} from "./ha-chart-base"; export const supportedStatTypeMap: Record = { mean: "mean", @@ -79,10 +83,14 @@ export class StatisticsChart extends LitElement { @state() private _chartData: ChartData = { datasets: [] }; + @state() private _chartDatasetExtra: ChartDatasetExtra[] = []; + @state() private _statisticIds: string[] = []; @state() private _chartOptions?: ChartOptions; + @state() private _hiddenStats = new Set(); + @query("ha-chart-base") private _chart?: HaChartBase; private _computedStyle?: CSSStyleDeclaration; @@ -96,6 +104,9 @@ export class StatisticsChart extends LitElement { } public willUpdate(changedProps: PropertyValues) { + if (changedProps.has("legendMode")) { + this._hiddenStats.clear(); + } if ( !this.hasUpdated || changedProps.has("unit") || @@ -110,7 +121,8 @@ export class StatisticsChart extends LitElement { changedProps.has("statisticsData") || changedProps.has("statTypes") || changedProps.has("chartType") || - changedProps.has("hideLegend") + changedProps.has("hideLegend") || + changedProps.has("_hiddenStats") ) { this._generateData(); } @@ -145,14 +157,30 @@ export class StatisticsChart extends LitElement { return html` `; } + private _datasetHidden(ev) { + ev.stopPropagation(); + this._hiddenStats.add(this._statisticIds[ev.detail.index]); + this.requestUpdate("_hiddenStats"); + } + + private _datasetUnhidden(ev) { + ev.stopPropagation(); + this._hiddenStats.delete(this._statisticIds[ev.detail.index]); + this.requestUpdate("_hiddenStats"); + } + private _createOptions(unit?: string) { this._chartOptions = { parsing: false, @@ -274,6 +302,7 @@ export class StatisticsChart extends LitElement { let colorIndex = 0; const statisticsData = Object.entries(this.statisticsData); const totalDataSets: ChartDataset<"line">[] = []; + const totalDatasetExtras: ChartDatasetExtra[] = []; const statisticIds: string[] = []; let endTime: Date; @@ -324,6 +353,7 @@ export class StatisticsChart extends LitElement { // The datasets for the current statistic const statDataSets: ChartDataset<"line">[] = []; + const statDatasetExtras: ChartDatasetExtra[] = []; const pushData = ( start: Date, @@ -384,9 +414,20 @@ export class StatisticsChart extends LitElement { }) : this.statTypes; + let displayed_legend = false; sortedTypes.forEach((type) => { if (statisticsHaveType(stats, type)) { const band = drawBands && (type === "min" || type === "max"); + if (!this.hideLegend) { + const show_legend = hasMean + ? type === "mean" + : displayed_legend === false; + statDatasetExtras.push({ + legend_label: name, + show_legend, + }); + displayed_legend = displayed_legend || show_legend; + } statTypes.push(type); statDataSets.push({ label: name @@ -408,6 +449,9 @@ export class StatisticsChart extends LitElement { band && hasMean ? color + (this.hideLegend ? "00" : "7F") : color, backgroundColor: band ? color + "3F" : color + "7F", pointRadius: 0, + hidden: !this.hideLegend + ? this._hiddenStats.has(statistic_id) + : false, data: [], // @ts-ignore unit: meta?.unit_of_measurement, @@ -446,6 +490,7 @@ export class StatisticsChart extends LitElement { // Concat two arrays Array.prototype.push.apply(totalDataSets, statDataSets); + Array.prototype.push.apply(totalDatasetExtras, statDatasetExtras); }); if (unit) { @@ -455,6 +500,7 @@ export class StatisticsChart extends LitElement { this._chartData = { datasets: totalDataSets, }; + this._chartDatasetExtra = totalDatasetExtras; this._statisticIds = statisticIds; } diff --git a/src/components/chart/timeline-chart/timeline-controller.ts b/src/components/chart/timeline-chart/timeline-controller.ts index 5aeadc6e00..b6b1d88af3 100644 --- a/src/components/chart/timeline-chart/timeline-controller.ts +++ b/src/components/chart/timeline-chart/timeline-controller.ts @@ -205,7 +205,9 @@ export class TimelineController extends BarController { const y = vScale.getPixelForValue(this.index); - const xStart = iScale.getPixelForValue(data.start.getTime()); + const xStart = iScale.getPixelForValue( + Math.max(iScale.min, data.start.getTime()) + ); const xEnd = iScale.getPixelForValue(data.end.getTime()); const width = xEnd - xStart; diff --git a/src/components/chart/timeline-chart/timeline-scale.ts b/src/components/chart/timeline-chart/timeline-scale.ts index 8d5086dafc..e987456f8e 100644 --- a/src/components/chart/timeline-chart/timeline-scale.ts +++ b/src/components/chart/timeline-chart/timeline-scale.ts @@ -49,7 +49,7 @@ export class TimeLineScale extends TimeScale { max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit); // Make sure that max is strictly higher than min (required by the lookup table) - this.min = Math.min(min, max - 1); - this.max = Math.max(min + 1, max); + this.min = adapter.parse(options.min, this) ?? Math.min(min, max - 1); + this.max = adapter.parse(options.max, this) ?? Math.max(min + 1, max); } } diff --git a/src/components/data-table/ha-data-table-icon.ts b/src/components/data-table/ha-data-table-icon.ts index 5e7cada2d5..8724b41fbb 100644 --- a/src/components/data-table/ha-data-table-icon.ts +++ b/src/components/data-table/ha-data-table-icon.ts @@ -44,6 +44,8 @@ class HaDataTableIcon extends LitElement { div { position: absolute; right: 28px; + inset-inline-end: 28px; + inset-inline-start: initial; z-index: 1002; outline: none; font-size: 10px; diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts index 75d077ed51..13b874a5e4 100644 --- a/src/components/entity/state-badge.ts +++ b/src/components/entity/state-badge.ts @@ -32,7 +32,9 @@ export class StateBadge extends LitElement { @property() public overrideImage?: string; - @property({ type: Boolean }) public stateColor = false; + // Cannot be a boolean attribute because undefined is treated different than + // false. When it is undefined, state is still colored for light entities. + @property({ attribute: false }) public stateColor?: boolean; @property() public color?: string; @@ -70,7 +72,7 @@ export class StateBadge extends LitElement { const domain = this.stateObj ? computeStateDomain(this.stateObj) : undefined; - return this.stateColor || (domain === "light" && this.stateColor !== false); + return this.stateColor ?? domain === "light"; } protected render() { diff --git a/src/components/ha-climate-state.ts b/src/components/ha-climate-state.ts index ca5d3a00e9..c2f35979e8 100644 --- a/src/components/ha-climate-state.ts +++ b/src/components/ha-climate-state.ts @@ -156,6 +156,7 @@ class HaClimateState extends LitElement { .current { color: var(--secondary-text-color); + direction: var(--direction); } .state-label { diff --git a/src/components/ha-control-button.ts b/src/components/ha-control-button.ts index 1eee1286e6..58ea4f4400 100644 --- a/src/components/ha-control-button.ts +++ b/src/components/ha-control-button.ts @@ -127,9 +127,11 @@ export class HaControlButton extends LitElement { opacity 180ms ease-in-out; opacity: var(--control-button-background-opacity); } - .button ::slotted(*) { + .button { transition: color 180ms ease-in-out; color: var(--control-button-icon-color); + } + .button ::slotted(*) { pointer-events: none; } .button:disabled { diff --git a/src/components/ha-control-number-buttons.ts b/src/components/ha-control-number-buttons.ts index 36172f4e08..5ee5dc0d1a 100644 --- a/src/components/ha-control-number-buttons.ts +++ b/src/components/ha-control-number-buttons.ts @@ -273,9 +273,13 @@ export class HaControlNumberButton extends LitElement { } .button.minus { left: 0; + inset-inline-start: 0; + inset-inline-end: initial; } .button.plus { right: 0; + inset-inline-start: initial; + inset-inline-end: 0; } .unit { white-space: pre; diff --git a/src/components/ha-drawer.ts b/src/components/ha-drawer.ts index 45a54623d5..8887d722af 100644 --- a/src/components/ha-drawer.ts +++ b/src/components/ha-drawer.ts @@ -3,7 +3,6 @@ import { styles } from "@material/mwc-drawer/mwc-drawer.css"; import { css, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; -import { mainWindow } from "../common/dom/get_main_window"; const blockingElements = (document as any).$blockingElements; @@ -34,7 +33,8 @@ export class HaDrawer extends DrawerBase { protected updated(changedProps: PropertyValues) { super.updated(changedProps); if (changedProps.has("direction")) { - if (mainWindow.document.dir === "rtl") { + this.mdcRoot.dir = this.direction; + if (this.direction === "rtl") { this._rtlStyle = document.createElement("style"); this._rtlStyle.innerHTML = ` .mdc-drawer--animate { diff --git a/src/components/ha-form/compute-initial-ha-form-data.ts b/src/components/ha-form/compute-initial-ha-form-data.ts index 92095b6204..93b1fc4d2d 100644 --- a/src/components/ha-form/compute-initial-ha-form-data.ts +++ b/src/components/ha-form/compute-initial-ha-form-data.ts @@ -54,7 +54,8 @@ export const computeInitialHaFormData = ( "icon" in selector || "template" in selector || "text" in selector || - "theme" in selector + "theme" in selector || + "object" in selector ) { data[field.name] = ""; } else if ("number" in selector) { diff --git a/src/components/ha-formfield.ts b/src/components/ha-formfield.ts index 1c8cc5a019..46837ca65d 100644 --- a/src/components/ha-formfield.ts +++ b/src/components/ha-formfield.ts @@ -1,11 +1,13 @@ import { FormfieldBase } from "@material/mwc-formfield/mwc-formfield-base"; import { styles } from "@material/mwc-formfield/mwc-formfield.css"; import { css } from "lit"; -import { customElement } from "lit/decorators"; +import { customElement, property } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; @customElement("ha-formfield") export class HaFormfield extends FormfieldBase { + @property({ type: Boolean, reflect: true }) public disabled = false; + protected _labelClick() { const input = this.input as HTMLInputElement | undefined; if (!input) return; @@ -44,6 +46,9 @@ export class HaFormfield extends FormfieldBase { padding-inline-start: 4px; padding-inline-end: 0; } + :host([disabled]) label { + color: var(--disabled-text-color); + } `, ]; } diff --git a/src/components/ha-menu-button.ts b/src/components/ha-menu-button.ts index 63481ad1fd..4c70956dd1 100644 --- a/src/components/ha-menu-button.ts +++ b/src/components/ha-menu-button.ts @@ -136,6 +136,8 @@ class HaMenuButton extends LitElement { height: 12px; top: 9px; right: 7px; + inset-inline-end: 7px; + inset-inline-start: initial; border-radius: 50%; border: 2px solid var(--app-header-background-color); } diff --git a/src/components/ha-metric.ts b/src/components/ha-metric.ts index 4a33812929..9d0fd3431e 100644 --- a/src/components/ha-metric.ts +++ b/src/components/ha-metric.ts @@ -19,7 +19,9 @@ class HaMetric extends LitElement { ${this.heading}
- ${roundedValue} % + +
${roundedValue} %
+
50, @@ -70,6 +72,10 @@ class HaMetric extends LitElement { padding-inline-start: initial; flex-shrink: 0; } + .value > div { + direction: ltr; + text-align: var(--float-start); + } `; } } diff --git a/src/components/ha-qr-scanner.ts b/src/components/ha-qr-scanner.ts index 9d437164cc..b65f9ba07a 100644 --- a/src/components/ha-qr-scanner.ts +++ b/src/components/ha-qr-scanner.ts @@ -186,6 +186,8 @@ class HaQrScanner extends LitElement { position: absolute; bottom: 8px; right: 8px; + inset-inline-end: 8px; + inset-inline-start: initial; background: #727272b2; color: white; border-radius: 50%; diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index 069dd157d4..91fb682d7e 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -102,7 +102,10 @@ export class HaSelectSelector extends LitElement { ${this.label} ${options.map( (item: SelectOption) => html` - + `} -
+ ${this.hideDescription + ? nothing + : html` +
+ ${description ? html`

${description}

` : ""} + ${this._manifest + ? html` + + ` + : nothing} +
+ `} ${serviceData && "target" in serviceData ? html` ${hasOptional @@ -517,7 +524,8 @@ export class HaServiceControl extends LitElement { > ` : ""; - })}`; + })} + `; } private _localizeValueCallback = (key: string) => { diff --git a/src/components/ha-service-picker.ts b/src/components/ha-service-picker.ts index c370928521..c27c039a8e 100644 --- a/src/components/ha-service-picker.ts +++ b/src/components/ha-service-picker.ts @@ -114,11 +114,14 @@ class HaServicePicker extends LitElement { if (!filter) { return processedServices; } - return processedServices.filter( - (service) => - service.service.toLowerCase().includes(filter) || - service.name?.toLowerCase().includes(filter) - ); + const split_filter = filter.split(" "); + return processedServices.filter((service) => { + const lower_service_name = service.name.toLowerCase(); + const lower_service = service.service.toLowerCase(); + return split_filter.every( + (f) => lower_service_name.includes(f) || lower_service.includes(f) + ); + }); } ); diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index d36a6a646d..6110c85468 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -1010,8 +1010,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { } .profile paper-icon-item { padding-left: 4px; - margin-inline-start: 4px; - margin-inline-end: auto; + padding-inline-start: 4px; + padding-inline-end: auto; } .profile .item-text { margin-left: 8px; @@ -1040,6 +1040,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { position: absolute; bottom: 14px; left: 26px; + inset-inline-start: 26px; + inset-inline-end: initial; font-size: 0.65em; } diff --git a/src/components/ha-sortable.ts b/src/components/ha-sortable.ts index 5258c2fc5f..1c83eef950 100644 --- a/src/components/ha-sortable.ts +++ b/src/components/ha-sortable.ts @@ -14,9 +14,16 @@ declare global { oldPath?: ItemPath; newPath?: ItemPath; }; + "drag-start": undefined; + "drag-end": undefined; } } +export type HaSortableOptions = Omit< + SortableInstance.SortableOptions, + "onStart" | "onChoose" | "onEnd" +>; + @customElement("ha-sortable") export class HaSortable extends LitElement { private _sortable?: SortableInstance; @@ -36,14 +43,17 @@ export class HaSortable extends LitElement { @property({ type: String, attribute: "handle-selector" }) public handleSelector?: string; - @property({ type: String, attribute: "group" }) - public group?: string; - - @property({ type: Number, attribute: "swap-threshold" }) - public swapThreshold?: number; + @property({ type: String }) + public group?: string | SortableInstance.GroupOptions; @property({ type: Boolean, attribute: "invert-swap" }) - public invertSwap?: boolean; + public invertSwap: boolean = false; + + @property({ attribute: false }) + public options?: HaSortableOptions; + + @property({ type: Boolean }) + public rollback: boolean = true; protected updated(changedProperties: PropertyValues) { if (changedProperties.has("disabled")) { @@ -114,26 +124,20 @@ export class HaSortable extends LitElement { const options: SortableInstance.Options = { animation: 150, - swapThreshold: 1, + ...this.options, onChoose: this._handleChoose, + onStart: this._handleStart, onEnd: this._handleEnd, }; if (this.draggableSelector) { options.draggable = this.draggableSelector; } - - if (this.swapThreshold !== undefined) { - options.swapThreshold = this.swapThreshold; - } - if (this.invertSwap !== undefined) { - options.invertSwap = this.invertSwap; - } if (this.handleSelector) { options.handle = this.handleSelector; } - if (this.draggableSelector) { - options.draggable = this.draggableSelector; + if (this.invertSwap !== undefined) { + options.invertSwap = this.invertSwap; } if (this.group) { options.group = this.group; @@ -143,8 +147,9 @@ export class HaSortable extends LitElement { } private _handleEnd = async (evt: SortableEvent) => { + fireEvent(this, "drag-end"); // put back in original location - if ((evt.item as any).placeholder) { + if (this.rollback && (evt.item as any).placeholder) { (evt.item as any).placeholder.replaceWith(evt.item); delete (evt.item as any).placeholder; } @@ -170,7 +175,12 @@ export class HaSortable extends LitElement { }); }; + private _handleStart = () => { + fireEvent(this, "drag-start"); + }; + private _handleChoose = (evt: SortableEvent) => { + if (!this.rollback) return; (evt.item as any).placeholder = document.createComment("sort-placeholder"); evt.item.after((evt.item as any).placeholder); }; diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index 170c0c179d..175b8ae290 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -90,7 +90,7 @@ export class HaTextField extends TextFieldBase { padding-right: var(--text-field-suffix-padding-right, 0px); padding-inline-start: var(--text-field-suffix-padding-left, 12px); padding-inline-end: var(--text-field-suffix-padding-right, 0px); - direction: var(--direction); + direction: ltr; } .mdc-text-field--with-leading-icon { padding-inline-start: var(--text-field-suffix-padding-left, 0px); @@ -199,7 +199,6 @@ export class HaTextField extends TextFieldBase { // safari workaround - must be explicit mainWindow.document.dir === "rtl" ? css` - .mdc-text-field__affix--suffix, .mdc-text-field--with-leading-icon, .mdc-text-field__icon--leading, .mdc-floating-label, diff --git a/src/components/media-player/dialog-media-player-browse.ts b/src/components/media-player/dialog-media-player-browse.ts index fd8bdba673..e0a66ae79c 100644 --- a/src/components/media-player/dialog-media-player-browse.ts +++ b/src/components/media-player/dialog-media-player-browse.ts @@ -223,7 +223,6 @@ class DialogMediaPlayerBrowse extends LitElement { ha-media-player-browse { --media-browser-max-height: calc(100vh - 65px); - direction: ltr; } :host(.opened) ha-media-player-browse { diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index e46c9e908c..6f205de5fd 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -879,6 +879,7 @@ export class HaMediaPlayerBrowse extends LitElement { display: flex; flex-direction: column; position: relative; + direction: ltr; } ha-circular-progress { diff --git a/src/data/cloud.ts b/src/data/cloud.ts index b498428f9a..c732b2a65f 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -18,6 +18,7 @@ export interface CloudPreferences { google_enabled: boolean; alexa_enabled: boolean; remote_enabled: boolean; + remote_allow_remote_enable: boolean; google_secure_devices_pin: string | undefined; cloudhooks: { [webhookId: string]: CloudWebhook }; alexa_report_state: boolean; @@ -139,6 +140,7 @@ export const updateCloudPref = ( google_report_state?: CloudPreferences["google_report_state"]; google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"]; tts_default_voice?: CloudPreferences["tts_default_voice"]; + remote_allow_remote_enable?: CloudPreferences["remote_allow_remote_enable"]; } ) => hass.callWS({ diff --git a/src/data/data_entry_flow.ts b/src/data/data_entry_flow.ts index 7a830e7d88..d60e55beab 100644 --- a/src/data/data_entry_flow.ts +++ b/src/data/data_entry_flow.ts @@ -33,6 +33,7 @@ export interface DataEntryFlowStepForm { description_placeholders?: Record; last_step: boolean | null; preview?: string; + translation_domain?: string; } export interface DataEntryFlowStepExternal { @@ -42,6 +43,7 @@ export interface DataEntryFlowStepExternal { step_id: string; url: string; description_placeholders: Record; + translation_domain?: string; } export interface DataEntryFlowStepCreateEntry { @@ -53,6 +55,7 @@ export interface DataEntryFlowStepCreateEntry { result?: ConfigEntry; description: string; description_placeholders?: Record; + translation_domain?: string; } export interface DataEntryFlowStepAbort { @@ -61,6 +64,7 @@ export interface DataEntryFlowStepAbort { handler: string; reason: string; description_placeholders?: Record; + translation_domain?: string; } export interface DataEntryFlowStepProgress { @@ -70,6 +74,7 @@ export interface DataEntryFlowStepProgress { step_id: string; progress_action: string; description_placeholders?: Record; + translation_domain?: string; } export interface DataEntryFlowStepMenu { @@ -80,6 +85,7 @@ export interface DataEntryFlowStepMenu { /** If array, use value to lookup translations in strings.json */ menu_options: string[] | Record; description_placeholders?: Record; + translation_domain?: string; } export type DataEntryFlowStep = diff --git a/src/data/energy.ts b/src/data/energy.ts index fe7bf3d1a4..a811199cee 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -331,6 +331,9 @@ export const getReferencedStatisticIds = ( } } } + if (!(includeTypes && !includeTypes.includes("device"))) { + statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption)); + } return statIDs; }; @@ -383,6 +386,7 @@ const getEnergyData = async ( "solar", "battery", "gas", + "device", ]); const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]); @@ -777,7 +781,7 @@ export const getEnergyGasUnit = ( : "ft³"; }; -export const getEnergyWaterUnit = (hass: HomeAssistant): string | undefined => +export const getEnergyWaterUnit = (hass: HomeAssistant): string => hass.config.unit_system.length === "km" ? "L" : "gal"; export const energyStatisticHelpUrl = diff --git a/src/data/history.ts b/src/data/history.ts index baece3da37..e0fc8bd328 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -81,7 +81,7 @@ export interface EntityHistoryState { /** attributes */ a: { [key: string]: any }; /** last_changed; if set, also applies to lu */ - lc: number; + lc?: number; /** last_updated */ lu: number; } @@ -419,17 +419,37 @@ const BLANK_UNIT = " "; export const computeHistory = ( hass: HomeAssistant, stateHistory: HistoryStates, + entityIds: string[], localize: LocalizeFunc, sensorNumericalDeviceClasses: string[], splitDeviceClasses = false ): HistoryResult => { const lineChartDevices: { [unit: string]: HistoryStates } = {}; const timelineDevices: TimelineEntity[] = []; - if (!stateHistory) { + + const localStateHistory: HistoryStates = {}; + + // Create a limited history from stateObj if entity has no recorded history. + const allEntities = new Set([...entityIds, ...Object.keys(stateHistory)]); + allEntities.forEach((entity) => { + if (entity in stateHistory) { + localStateHistory[entity] = stateHistory[entity]; + } else if (hass.states[entity]) { + localStateHistory[entity] = [ + { + s: hass.states[entity].state, + a: hass.states[entity].attributes, + lu: new Date(hass.states[entity].last_updated).getTime() / 1000, + }, + ]; + } + }); + + if (!localStateHistory) { return { line: [], timeline: [] }; } - Object.keys(stateHistory).forEach((entityId) => { - const stateInfo = stateHistory[entityId]; + Object.keys(localStateHistory).forEach((entityId) => { + const stateInfo = localStateHistory[entityId]; if (stateInfo.length === 0) { return; } diff --git a/src/data/icons.ts b/src/data/icons.ts index dcb3952f55..f63a861e08 100644 --- a/src/data/icons.ts +++ b/src/data/icons.ts @@ -198,8 +198,9 @@ export const entryIcon = async ( if (entry.icon) { return entry.icon; } + const stateObj = hass.states[entry.entity_id] as HassEntity | undefined; const domain = computeDomain(entry.entity_id); - return getEntityIcon(hass, domain, undefined, undefined, entry); + return getEntityIcon(hass, domain, stateObj, undefined, entry); }; const getEntityIcon = async ( diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 1d6ba571ac..48de53e7d6 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -10,8 +10,10 @@ import { LovelaceCard, } from "../panels/lovelace/types"; import { HomeAssistant } from "../types"; +import { LovelaceSectionConfig } from "./lovelace/config/section"; import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types"; import { LovelaceViewConfig } from "./lovelace/config/view"; +import { HuiSection } from "../panels/lovelace/sections/hui-section"; export interface LovelacePanelConfig { mode: "yaml" | "storage"; @@ -24,10 +26,21 @@ export interface LovelaceViewElement extends HTMLElement { index?: number; cards?: Array; badges?: LovelaceBadge[]; + sections?: HuiSection[]; isStrategy: boolean; setConfig(config: LovelaceViewConfig): void; } +export interface LovelaceSectionElement extends HTMLElement { + hass?: HomeAssistant; + lovelace?: Lovelace; + viewIndex?: number; + index?: number; + cards?: Array; + isStrategy: boolean; + setConfig(config: LovelaceSectionConfig): void; +} + type LovelaceUpdatedEvent = HassEventBase & { event_type: "lovelace_updated"; data: { diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts new file mode 100644 index 0000000000..1c21688585 --- /dev/null +++ b/src/data/lovelace/config/section.ts @@ -0,0 +1,26 @@ +import type { LovelaceCardConfig } from "./card"; +import type { LovelaceStrategyConfig } from "./strategy"; + +export interface LovelaceBaseSectionConfig { + title?: string; +} + +export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { + type?: string; + cards?: LovelaceCardConfig[]; +} + +export interface LovelaceStrategySectionConfig + extends LovelaceBaseSectionConfig { + strategy: LovelaceStrategyConfig; +} + +export type LovelaceSectionRawConfig = + | LovelaceSectionConfig + | LovelaceStrategySectionConfig; + +export function isStrategySection( + section: LovelaceSectionRawConfig +): section is LovelaceStrategySectionConfig { + return "strategy" in section; +} diff --git a/src/data/lovelace/config/view.ts b/src/data/lovelace/config/view.ts index 0c522b60a1..10b454c15d 100644 --- a/src/data/lovelace/config/view.ts +++ b/src/data/lovelace/config/view.ts @@ -1,5 +1,6 @@ import type { LovelaceBadgeConfig } from "./badge"; import type { LovelaceCardConfig } from "./card"; +import type { LovelaceSectionRawConfig } from "./section"; import type { LovelaceStrategyConfig } from "./strategy"; export interface ShowViewConfig { @@ -23,6 +24,7 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig { type?: string; badges?: Array; cards?: LovelaceCardConfig[]; + sections?: LovelaceSectionRawConfig[]; } export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig { diff --git a/src/data/matter.ts b/src/data/matter.ts index 5b4009178d..d6c4de87a0 100644 --- a/src/data/matter.ts +++ b/src/data/matter.ts @@ -35,6 +35,7 @@ export interface MatterNodeDiagnostics { mac_address?: string; available: boolean; active_fabrics: MatterFabricData[]; + active_fabric_index: number; } export interface MatterPingResult { diff --git a/src/data/ws-area_registry.ts b/src/data/ws-area_registry.ts index 43e3d1f13f..550b9a5db8 100644 --- a/src/data/ws-area_registry.ts +++ b/src/data/ws-area_registry.ts @@ -1,12 +1,17 @@ import { Connection, createCollection } from "home-assistant-js-websocket"; import { Store } from "home-assistant-js-websocket/dist/store"; +import { stringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import { AreaRegistryEntry } from "./area_registry"; const fetchAreaRegistry = (conn: Connection) => - conn.sendMessagePromise({ - type: "config/area_registry/list", - }); + conn + .sendMessagePromise({ + type: "config/area_registry/list", + }) + .then((areas) => + areas.sort((ent1, ent2) => stringCompare(ent1.name, ent2.name)) + ); const subscribeAreaRegistryUpdates = ( conn: Connection, diff --git a/src/dialogs/config-flow/show-dialog-config-flow.ts b/src/dialogs/config-flow/show-dialog-config-flow.ts index 9ccc581840..8b2b6b1667 100644 --- a/src/dialogs/config-flow/show-dialog-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-config-flow.ts @@ -44,7 +44,7 @@ export const showConfigFlowDialog = ( renderAbortDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.config.abort.${step.reason}`, + `component.${step.translation_domain || step.handler}.config.abort.${step.reason}`, step.description_placeholders ); @@ -58,7 +58,7 @@ export const showConfigFlowDialog = ( renderShowFormStepHeader(hass, step) { return ( hass.localize( - `component.${step.handler}.config.step.${step.step_id}.title`, + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.title`, step.description_placeholders ) || hass.localize(`component.${step.handler}.title`) ); @@ -66,7 +66,7 @@ export const showConfigFlowDialog = ( renderShowFormStepDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.config.step.${step.step_id}.description`, + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -84,7 +84,7 @@ export const showConfigFlowDialog = ( renderShowFormStepFieldHelper(hass, step, field) { const description = hass.localize( - `component.${step.handler}.config.step.${step.step_id}.data_description.${field.name}`, + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.data_description.${field.name}`, step.description_placeholders ); return description @@ -95,7 +95,7 @@ export const showConfigFlowDialog = ( renderShowFormStepFieldError(hass, step, error) { return ( hass.localize( - `component.${step.handler}.config.error.${error}`, + `component.${step.translation_domain || step.translation_domain || step.handler}.config.error.${error}`, step.description_placeholders ) || error ); @@ -131,7 +131,7 @@ export const showConfigFlowDialog = ( renderExternalStepDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.config.${step.step_id}.description`, + `component.${step.translation_domain || step.handler}.config.${step.step_id}.description`, step.description_placeholders ); @@ -155,7 +155,7 @@ export const showConfigFlowDialog = ( renderCreateEntryDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.config.create_entry.${ + `component.${step.translation_domain || step.handler}.config.create_entry.${ step.description || "default" }`, step.description_placeholders @@ -190,7 +190,7 @@ export const showConfigFlowDialog = ( renderShowFormProgressDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.config.progress.${step.progress_action}`, + `component.${step.translation_domain || step.handler}.config.progress.${step.progress_action}`, step.description_placeholders ); return description @@ -210,7 +210,7 @@ export const showConfigFlowDialog = ( renderMenuDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.config.step.${step.step_id}.description`, + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -222,7 +222,7 @@ export const showConfigFlowDialog = ( renderMenuOption(hass, step, option) { return hass.localize( - `component.${step.handler}.config.step.${step.step_id}.menu_options.${option}`, + `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.menu_options.${option}`, step.description_placeholders ); }, diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index 05f3acc1b4..491df7e91f 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -53,7 +53,7 @@ export const showOptionsFlowDialog = ( renderAbortDescription(hass, step) { const description = hass.localize( - `component.${configEntry.domain}.options.abort.${step.reason}`, + `component.${step.translation_domain || configEntry.domain}.options.abort.${step.reason}`, step.description_placeholders ); @@ -71,7 +71,7 @@ export const showOptionsFlowDialog = ( renderShowFormStepHeader(hass, step) { return ( hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.title`, + `component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.title`, step.description_placeholders ) || hass.localize(`ui.dialogs.options_flow.form.header`) ); @@ -79,7 +79,7 @@ export const showOptionsFlowDialog = ( renderShowFormStepDescription(hass, step) { const description = hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -101,7 +101,7 @@ export const showOptionsFlowDialog = ( renderShowFormStepFieldHelper(hass, step, field) { const description = hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`, + `component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`, step.description_placeholders ); return description @@ -112,7 +112,7 @@ export const showOptionsFlowDialog = ( renderShowFormStepFieldError(hass, step, error) { return ( hass.localize( - `component.${configEntry.domain}.options.error.${error}`, + `component.${step.translation_domain || configEntry.domain}.options.error.${error}`, step.description_placeholders ) || error ); @@ -159,7 +159,7 @@ export const showOptionsFlowDialog = ( renderShowFormProgressDescription(hass, step) { const description = hass.localize( - `component.${configEntry.domain}.options.progress.${step.progress_action}`, + `component.${step.translation_domain || configEntry.domain}.options.progress.${step.progress_action}`, step.description_placeholders ); return description @@ -183,7 +183,7 @@ export const showOptionsFlowDialog = ( renderMenuDescription(hass, step) { const description = hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.description`, + `component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -199,7 +199,7 @@ export const showOptionsFlowDialog = ( renderMenuOption(hass, step, option) { return hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`, + `component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`, step.description_placeholders ); }, diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 2660b56799..216b79b637 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -89,7 +89,12 @@ class DialogBox extends LitElement {
${confirmPrompt && html` - + ${this._params.dismissText ? this._params.dismissText : this.hass.localize("ui.dialogs.generic.cancel")} @@ -97,7 +102,8 @@ class DialogBox extends LitElement { `} ` : html` `} diff --git a/src/dialogs/more-info/components/lights/ha-more-info-light-favorite-colors.ts b/src/dialogs/more-info/components/lights/ha-more-info-light-favorite-colors.ts index 93a6b1528f..aa9893d000 100644 --- a/src/dialogs/more-info/components/lights/ha-more-info-light-favorite-colors.ts +++ b/src/dialogs/more-info/components/lights/ha-more-info-light-favorite-colors.ts @@ -322,6 +322,8 @@ export class HaMoreInfoLightFavoriteColors extends LitElement { position: absolute; top: -6px; right: -6px; + inset-inline-end: -6px; + inset-inline-start: initial; width: 20px; height: 20px; outline: none; diff --git a/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts b/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts index de42e5b054..a38cbe2b2e 100644 --- a/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts +++ b/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts @@ -446,6 +446,8 @@ class LightRgbColorPicker extends LitElement { position: absolute; top: 0; right: 0; + inset-inline-end: 0; + inset-inline-start: initial; z-index: 1; } diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts index 65375a6f6d..e558910779 100644 --- a/src/dialogs/more-info/const.ts +++ b/src/dialogs/more-info/const.ts @@ -26,6 +26,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [ "light", "lock", "siren", + "script", "switch", "valve", "water_heater", diff --git a/src/dialogs/more-info/controls/more-info-person.ts b/src/dialogs/more-info/controls/more-info-person.ts index bba6427c8f..be00a04095 100644 --- a/src/dialogs/more-info/controls/more-info-person.ts +++ b/src/dialogs/more-info/controls/more-info-person.ts @@ -34,7 +34,6 @@ class MoreInfoPerson extends LitElement { : ""} ${!__DEMO__ && this.hass.user?.is_admin && - this.stateObj.state === "not_home" && this.stateObj.attributes.latitude && this.stateObj.attributes.longitude ? html` diff --git a/src/dialogs/more-info/controls/more-info-script.ts b/src/dialogs/more-info/controls/more-info-script.ts index c53da01be1..c530f05cf5 100644 --- a/src/dialogs/more-info/controls/more-info-script.ts +++ b/src/dialogs/more-info/controls/more-info-script.ts @@ -1,51 +1,220 @@ +import { mdiPlay, mdiStop } from "@mdi/js"; +import "@material/mwc-button"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; import "../../../components/ha-relative-time"; +import "../../../components/ha-service-control"; +import "../../../components/ha-control-button"; +import "../../../components/ha-control-button-group"; +import "../../../components/entity/state-info"; import { HomeAssistant } from "../../../types"; +import { canRun, ScriptEntity } from "../../../data/script"; +import { isUnavailableState } from "../../../data/entity"; +import { computeObjectId } from "../../../common/entity/compute_object_id"; +import { listenMediaQuery } from "../../../common/dom/media_query"; +import "../components/ha-more-info-state-header"; @customElement("more-info-script") class MoreInfoScript extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) public stateObj?: HassEntity; + @property({ attribute: false }) public stateObj?: ScriptEntity; + + @state() private _scriptData: Record = {}; + + @state() private narrow = false; + + private _unsubMediaQuery?: () => void; + + public connectedCallback(): void { + super.connectedCallback(); + this._unsubMediaQuery = listenMediaQuery( + "(max-width: 870px)", + (matches) => { + this.narrow = matches; + } + ); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + if (this._unsubMediaQuery) { + this._unsubMediaQuery(); + this._unsubMediaQuery = undefined; + } + } protected render() { if (!this.hass || !this.stateObj) { return nothing; } + const stateObj = this.stateObj; + + const fields = + this.hass.services.script[computeObjectId(this.stateObj.entity_id)] + ?.fields; + + const hasFields = fields && Object.keys(fields).length > 0; + + const current = stateObj.attributes.current || 0; + const isQueued = stateObj.attributes.mode === "queued"; + const isParallel = stateObj.attributes.mode === "parallel"; + const hasQueue = isQueued && current > 1; return html` -
-
-
- ${this.hass.localize( - "ui.dialogs.more_info_control.script.last_triggered" - )}: -
- ${this.stateObj.attributes.last_triggered + 0 + ? isParallel && current > 1 + ? this.hass.localize("ui.card.script.running_parallel", { + active: current, + }) + : this.hass.localize("ui.card.script.running_single") + : this.hass.localize("ui.card.script.idle")} + .changedOverride=${this.stateObj.attributes.last_triggered || 0} + > + +
+ ${hasQueue ? html` - + ${this.hass.localize("ui.card.script.running_queued", { + queued: current - 1, + })} ` - : this.hass.localize("ui.components.relative_time.never")} + : ""}
+ + ${hasFields + ? html` +
+
+ ${this.hass.localize("ui.card.script.run_script")} +
+ +
+ ` + : nothing} + + + + + ${(isQueued || isParallel) && current > 1 + ? this.hass.localize("ui.card.script.cancel_all") + : this.hass.localize("ui.card.script.cancel")} + + + + ${this.hass!.localize("ui.card.script.run")} + + `; } + protected override willUpdate(changedProperties: PropertyValues): void { + super.willUpdate(changedProperties); + + if (!changedProperties.has("stateObj")) { + return; + } + + const oldState = changedProperties.get("stateObj") as + | HassEntity + | undefined; + const newState = this.stateObj; + + if (newState && (!oldState || oldState.entity_id !== newState.entity_id)) { + this._scriptData = { service: newState.entity_id, data: {} }; + } + } + + private _cancelScript(ev: Event) { + ev.stopPropagation(); + this._callService("turn_off"); + } + + private async _runScript(ev: Event) { + ev.stopPropagation(); + this.hass.callService( + "script", + computeObjectId(this.stateObj!.entity_id), + this._scriptData.data + ); + } + + private _callService(service: string): void { + this.hass.callService("script", service, { + entity_id: this.stateObj!.entity_id, + }); + } + + private _scriptDataChanged(ev: CustomEvent): void { + this._scriptData = { ...this._scriptData, ...ev.detail.value }; + } + + private _canRun() { + if ( + canRun(this.stateObj!) || + // Restart can also always runs. Just cancels other run. + this.stateObj!.attributes.mode === "restart" + ) { + return true; + } + return false; + } + static get styles(): CSSResultGroup { return css` - .flex { - display: flex; - justify-content: space-between; + .queue { + visibility: hidden; + color: var(--secondary-text-color); + text-align: center; + margin-bottom: 16px; + height: 21px; } - hr { - border-color: var(--divider-color); - border-bottom: none; - margin: 16px 0; + .queue.has-queue { + visibility: visible; + } + .fields { + padding: 16px; + border: 1px solid var(--divider-color); + border-radius: 8px; + margin-bottom: 16px; + } + .fields .title { + font-weight: bold; + } + ha-control-button ha-svg-icon { + z-index: -1; + margin-right: 4px; + } + ha-service-control { + --service-control-padding: 0; + --service-control-items-border-top: none; } `; } diff --git a/src/dialogs/more-info/ha-more-info-history.ts b/src/dialogs/more-info/ha-more-info-history.ts index 2adeb515c4..dc5c32f8a1 100644 --- a/src/dialogs/more-info/ha-more-info-history.ts +++ b/src/dialogs/more-info/ha-more-info-history.ts @@ -228,6 +228,7 @@ export class MoreInfoHistory extends LitElement { this._stateHistory = computeHistory( this.hass!, combinedHistory, + [this.entityId], this.hass!.localize, sensorNumericDeviceClasses ); diff --git a/src/dialogs/notifications/notification-drawer.ts b/src/dialogs/notifications/notification-drawer.ts index a1a15f8130..6a77edb730 100644 --- a/src/dialogs/notifications/notification-drawer.ts +++ b/src/dialogs/notifications/notification-drawer.ts @@ -14,6 +14,7 @@ import "./notification-item"; import "../../components/ha-header-bar"; import "../../components/ha-drawer"; import type { HaDrawer } from "../../components/ha-drawer"; +import { computeRTLDirection } from "../../common/util/compute_rtl"; @customElement("notification-drawer") export class HuiNotificationDrawer extends LitElement { @@ -92,7 +93,12 @@ export class HuiNotificationDrawer extends LitElement { }); return html` - +
${this.hass.localize("ui.notification_drawer.title")} diff --git a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts index 700c6e8ea9..086e8b448d 100644 --- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts +++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts @@ -647,6 +647,8 @@ export class HaVoiceCommandDialog extends LitElement { position: absolute; --mdc-icon-size: 16px; right: 5px; + inset-inline-end: 5px; + inset-inline-start: initial; top: 0px; } diff --git a/src/external_app/external_messaging.ts b/src/external_app/external_messaging.ts index 8c346738b7..a994a669b9 100644 --- a/src/external_app/external_messaging.ts +++ b/src/external_app/external_messaging.ts @@ -35,13 +35,22 @@ interface EMOutgoingMessageConfigGet extends EMMessage { type: "config/get"; } -interface EMOutgoingMessageScanQRCode extends EMMessage { - type: "qr_code/scan"; +interface EMOutgoingMessageBarCodeScan extends EMMessage { + type: "bar_code/scan"; title: string; description: string; alternative_option_label?: string; } +interface EMOutgoingMessageBarCodeClose extends EMMessage { + type: "bar_code/close"; +} + +interface EMOutgoingMessageBarCodeNotify extends EMMessage { + type: "bar_code/notify"; + message: string; +} + interface EMOutgoingMessageMatterCommission extends EMMessage { type: "matter/commission"; } @@ -55,13 +64,6 @@ type EMOutgoingMessageWithAnswer = { request: EMOutgoingMessageConfigGet; response: ExternalConfig; }; - "qr_code/scan": { - request: EMOutgoingMessageScanQRCode; - response: - | EMIncomingMessageQRCodeResponseCanceled - | EMIncomingMessageQRCodeResponseAlternativeOptions - | EMIncomingMessageQRCodeResponseScanResult; - }; }; interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage { @@ -124,20 +126,23 @@ interface EMOutgoingMessageAssistShow extends EMMessage { } type EMOutgoingMessageWithoutAnswer = - | EMOutgoingMessageHaptic - | EMOutgoingMessageConnectionStatus + | EMMessageResultError + | EMMessageResultSuccess | EMOutgoingMessageAppConfiguration - | EMOutgoingMessageTagWrite - | EMOutgoingMessageSidebarShow | EMOutgoingMessageAssistShow + | EMOutgoingMessageBarCodeClose + | EMOutgoingMessageBarCodeNotify + | EMOutgoingMessageBarCodeScan + | EMOutgoingMessageConnectionStatus | EMOutgoingMessageExoplayerPlayHLS | EMOutgoingMessageExoplayerResize | EMOutgoingMessageExoplayerStop - | EMOutgoingMessageThemeUpdate - | EMMessageResultSuccess - | EMMessageResultError + | EMOutgoingMessageHaptic + | EMOutgoingMessageImportThreadCredentials | EMOutgoingMessageMatterCommission - | EMOutgoingMessageImportThreadCredentials; + | EMOutgoingMessageSidebarShow + | EMOutgoingMessageTagWrite + | EMOutgoingMessageThemeUpdate; interface EMIncomingMessageRestart { id: number; @@ -172,17 +177,39 @@ interface EMIncomingMessageShowAutomationEditor { }; } -export interface EMIncomingMessageQRCodeResponseCanceled { - action: "canceled"; +export interface EMIncomingMessageBarCodeScanResult { + id: number; + type: "command"; + command: "bar_code/scan_result"; + payload: { + // A string decoded from the barcode data. + rawValue: string; + // https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API#supported_barcode_formats + format: + | "aztec" + | "code_128" + | "code_39" + | "code_93" + | "codabar" + | "data_matrix" + | "ean_13" + | "ean_8" + | "itf" + | "pdf417" + | "qr_code" + | "upc_a" + | "upc_e" + | "unknown"; + }; } -export interface EMIncomingMessageQRCodeResponseAlternativeOptions { - action: "alternative_options"; -} - -export interface EMIncomingMessageQRCodeResponseScanResult { - action: "scan_result"; - result: string; +export interface EMIncomingMessageBarCodeScanAborted { + id: number; + type: "command"; + command: "bar_code/aborted"; + payload: { + reason: "canceled" | "alternative_options"; + }; } export type EMIncomingMessageCommands = @@ -190,7 +217,9 @@ export type EMIncomingMessageCommands = | EMIncomingMessageShowNotifications | EMIncomingMessageToggleSidebar | EMIncomingMessageShowSidebar - | EMIncomingMessageShowAutomationEditor; + | EMIncomingMessageShowAutomationEditor + | EMIncomingMessageBarCodeScanResult + | EMIncomingMessageBarCodeScanAborted; type EMIncomingMessage = | EMMessageResultSuccess @@ -207,7 +236,7 @@ export interface ExternalConfig { canCommissionMatter: boolean; canImportThreadCredentials: boolean; hasAssist: boolean; - hasQRScanner: number; + hasBarCodeScanner: number; } export class ExternalMessaging { diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 7af35ded29..096ec15e30 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -377,6 +377,8 @@ export class HaTabsSubpageDataTable extends LitElement { color: var(--text-primary-color); position: absolute; right: 0; + inset-inline-end: 0; + inset-inline-start: initial; top: 4px; font-size: 0.65em; } diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 9de45b8a4a..3fe46f8456 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -10,7 +10,6 @@ import { import { customElement, eventOptions, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; -import { isComponentLoaded } from "../common/config/is_component_loaded"; import { restoreScroll } from "../common/decorators/restore-scroll"; import { LocalizeFunc } from "../common/translations/localize"; import "../components/ha-icon-button-arrow-prev"; @@ -19,13 +18,14 @@ import "../components/ha-svg-icon"; import "../components/ha-tab"; import { HomeAssistant, Route } from "../types"; import { haStyleScrollbar } from "../resources/styles"; +import { canShowPage } from "../common/config/can_show_page"; export interface PageNavigation { path: string; translationKey?: string; - component?: string; - components?: string[]; + component?: string | string[]; name?: string; + not_component?: string | string[]; core?: boolean; advancedOnly?: boolean; iconPath?: string; @@ -66,19 +66,12 @@ class HassTabsSubpage extends LitElement { ( tabs: PageNavigation[], activeTab: PageNavigation | undefined, - showAdvanced: boolean | undefined, _components, _language, _narrow, localizeFunc ) => { - const shownTabs = tabs.filter( - (page) => - (!page.component || - page.core || - isComponentLoaded(this.hass, page.component)) && - (!page.advancedOnly || showAdvanced) - ); + const shownTabs = tabs.filter((page) => canShowPage(this.hass, page)); if (shownTabs.length < 2) { if (shownTabs.length === 1) { @@ -127,7 +120,6 @@ class HassTabsSubpage extends LitElement { const tabs = this._getTabs( this.tabs, this._activeTab, - this.hass.userData?.showAdvanced, this.hass.config.components, this.hass.language, this.narrow, diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts index 29963ac45d..b984910c50 100644 --- a/src/layouts/home-assistant-main.ts +++ b/src/layouts/home-assistant-main.ts @@ -15,6 +15,7 @@ import "../components/ha-drawer"; import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer"; import type { HomeAssistant, Route } from "../types"; import "./partial-panel-resolver"; +import { computeRTLDirection } from "../common/util/compute_rtl"; declare global { // for fire event @@ -61,6 +62,7 @@ export class HomeAssistantMain extends LitElement { ha-circular-progress { position: relative; left: 12px; + inset-inline-start: 12px; + inset-inline-end: initial; } ha-locations-editor { display: block; diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index e1b3b08296..46ceac93c9 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -188,7 +188,7 @@ export class HAFullCalendar extends LitElement {
-
+
* { + margin-bottom: 5px; + box-sizing: border-box; + } + .today { margin-right: 20px; margin-inline-end: 20px; diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index 62abd48d4e..64b05b126b 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -568,7 +568,7 @@ class HaConfigAreaPage extends LitElement { @@ -710,6 +710,8 @@ class HaConfigAreaPage extends LitElement { position: absolute; top: 4px; right: 4px; + inset-inline-end: 4px; + inset-inline-start: initial; display: none; } .img-container:hover .img-edit-btn { @@ -736,6 +738,11 @@ class HaConfigAreaPage extends LitElement { padding: 16px; color: var(--secondary-text-color); } + + mwc-button > ha-svg-icon { + margin-inline-start: 0; + margin-inline-end: 8px; + } `, ]; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 2cf2984cbe..2722574ac0 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -203,12 +203,14 @@ export default class HaAutomationAction extends LitElement { } private _moveUp(ev) { + ev.stopPropagation(); const index = (ev.target as any).index; const newIndex = index - 1; this._move(index, newIndex); } private _moveDown(ev) { + ev.stopPropagation(); const index = (ev.target as any).index; const newIndex = index + 1; this._move(index, newIndex); diff --git a/src/panels/config/automation/add-automation-element-dialog.ts b/src/panels/config/automation/add-automation-element-dialog.ts index 4145a3397b..10249c8592 100644 --- a/src/panels/config/automation/add-automation-element-dialog.ts +++ b/src/panels/config/automation/add-automation-element-dialog.ts @@ -536,6 +536,8 @@ class DialogAddAutomationElement extends LitElement implements HassDialog { (!this._group || items.find((item) => item.key === this._params!.clipboardItem)) ? html`${this.hass.localize( // @ts-ignore `ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label` diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index f57806f896..2ff983d4c0 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -1,56 +1,23 @@ import "@material/mwc-button/mwc-button"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { nestedArrayMove } from "../../../common/util/array-move"; +import { html } from "lit"; +import { customElement, property } from "lit/decorators"; import "../../../components/ha-alert"; -import "../../../components/ha-blueprint-picker"; -import "../../../components/ha-card"; -import "../../../components/ha-circular-progress"; -import "../../../components/ha-markdown"; -import "../../../components/ha-selector/ha-selector"; -import "../../../components/ha-settings-row"; import { BlueprintAutomationConfig } from "../../../data/automation"; -import { - BlueprintOrError, - Blueprints, - fetchBlueprints, -} from "../../../data/blueprint"; -import { haStyle } from "../../../resources/styles"; -import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; +import { fetchBlueprints } from "../../../data/blueprint"; +import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor"; @customElement("blueprint-automation-editor") -export class HaBlueprintAutomationEditor extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ type: Boolean }) public isWide = false; - - @property({ type: Boolean }) public disabled = false; - - @property({ type: Boolean, reflect: true }) public narrow = false; - +export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor { @property({ attribute: false }) public config!: BlueprintAutomationConfig; @property({ attribute: false }) public stateObj?: HassEntity; - @state() private _blueprints?: Blueprints; - - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - this._getBlueprints(); - } - - private get _blueprint(): BlueprintOrError | undefined { - if (!this._blueprints) { - return undefined; - } - return this._blueprints[this.config.use_blueprint.path]; + protected get _config(): BlueprintAutomationConfig { + return this.config; } protected render() { - const blueprint = this._blueprint; return html` ${this.disabled ? html` @@ -77,167 +44,14 @@ export class HaBlueprintAutomationEditor extends LitElement { ${this.config.description ? html`

${this.config.description}

` : ""} - -
- ${this._blueprints - ? Object.keys(this._blueprints).length - ? html` - - ` - : this.hass.localize( - "ui.panel.config.automation.editor.blueprint.no_blueprints" - ) - : html``} -
- - ${this.config.use_blueprint.path - ? blueprint && "error" in blueprint - ? html`

- There is an error in this Blueprint: ${blueprint.error} -

` - : html`${blueprint?.metadata.description - ? html`` - : ""} - ${blueprint?.metadata?.input && - Object.keys(blueprint.metadata.input).length - ? Object.entries(blueprint.metadata.input).map( - ([key, value]) => { - const selector = value?.selector ?? { text: undefined }; - const type = Object.keys(selector)[0]; - const enhancedSelector = [ - "action", - "condition", - "trigger", - ].includes(type) - ? { - [type]: { - ...selector[type], - path: [key], - }, - } - : selector; - - return html` - ${value?.name || key} - - ${html``} - `; - } - ) - : html`

- ${this.hass.localize( - "ui.panel.config.automation.editor.blueprint.no_inputs" - )} -

`}` - : ""} -
+ ${this.renderCard()} `; } - private async _getBlueprints() { + protected async _getBlueprints() { this._blueprints = await fetchBlueprints(this.hass, "automation"); } - private _blueprintChanged(ev) { - ev.stopPropagation(); - if (this.config.use_blueprint.path === ev.detail.value) { - return; - } - fireEvent(this, "value-changed", { - value: { - ...this.config, - use_blueprint: { - path: ev.detail.value, - }, - }, - }); - } - - private _inputChanged(ev) { - ev.stopPropagation(); - const target = ev.target as any; - const key = target.key; - const value = ev.detail ? ev.detail.value : target.value; - if ( - (this.config.use_blueprint.input && - this.config.use_blueprint.input[key] === value) || - (!this.config.use_blueprint.input && value === "") - ) { - return; - } - const input = { ...this.config.use_blueprint.input, [key]: value }; - - fireEvent(this, "value-changed", { - value: { - ...this.config, - use_blueprint: { - ...this.config.use_blueprint, - input, - }, - }, - }); - } - - private _itemMoved(ev) { - ev.stopPropagation(); - const { oldIndex, newIndex, oldPath, newPath } = ev.detail; - - const input = nestedArrayMove( - this.config.use_blueprint.input, - oldIndex, - newIndex, - oldPath, - newPath - ); - - fireEvent(this, "value-changed", { - value: { - ...this.config, - use_blueprint: { - ...this.config.use_blueprint, - input, - }, - }, - }); - } - private async _enable(): Promise { if (!this.hass || !this.stateObj) { return; @@ -246,69 +60,7 @@ export class HaBlueprintAutomationEditor extends LitElement { entity_id: this.stateObj.entity_id, }); } - - private _duplicate() { - fireEvent(this, "duplicate"); - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - :host { - display: block; - } - ha-card.blueprint { - margin: 0 auto; - } - .padding { - padding: 16px; - } - .link-button-row { - padding: 14px; - } - .blueprint-picker-container { - padding: 0 16px 16px; - } - ha-textfield, - ha-blueprint-picker { - display: block; - } - h3 { - margin: 16px; - } - .introduction { - margin-top: 0; - margin-bottom: 12px; - } - .introduction a { - color: var(--primary-color); - } - p { - margin-bottom: 0; - } - .description { - margin-bottom: 16px; - } - ha-settings-row { - --paper-time-input-justify-content: flex-end; - --settings-row-content-width: 100%; - --settings-row-prefix-display: contents; - border-top: 1px solid var(--divider-color); - } - ha-alert { - margin-bottom: 16px; - display: block; - } - ha-alert.re-order { - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } - `, - ]; - } } - declare global { interface HTMLElementTagNameMap { "blueprint-automation-editor": HaBlueprintAutomationEditor; diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index a615f9a1b4..d164a6ee21 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -227,12 +227,14 @@ export default class HaAutomationCondition extends LitElement { } private _moveUp(ev) { + ev.stopPropagation(); const index = (ev.target as any).index; const newIndex = index - 1; this._move(index, newIndex); } private _moveDown(ev) { + ev.stopPropagation(); const index = (ev.target as any).index; const newIndex = index + 1; this._move(index, newIndex); diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 04343c20ec..11571368c4 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -172,7 +172,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ${stateObj && this._config && this.narrow - ? html`
+ ? html` ${this.hass.localize( "ui.panel.config.automation.editor.show_trace" @@ -563,7 +567,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { if (this._config?.id) { const result = await this.confirmUnsavedChanged(); if (result) { - navigate(`/config/automation/trace/${this._config.id}`); + navigate( + `/config/automation/trace/${encodeURIComponent(this._config.id)}` + ); } } } diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 3bb5802c7d..db23980b2d 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -435,7 +435,9 @@ class HaAutomationPicker extends LitElement { }); return; } - navigate(`/config/automation/trace/${automation.attributes.id}`); + navigate( + `/config/automation/trace/${encodeURIComponent(automation.attributes.id)}` + ); } private async _toggle(automation): Promise { @@ -530,9 +532,11 @@ class HaAutomationPicker extends LitElement { ); if (automation?.attributes.id) { - navigate(`/config/automation/edit/${automation.attributes.id}`); + navigate( + `/config/automation/edit/${encodeURIComponent(automation.attributes.id)}` + ); } else { - navigate(`/config/automation/show/${ev.detail.id}`); + navigate(`/config/automation/show/${encodeURIComponent(ev.detail.id)}`); } } diff --git a/src/panels/config/automation/ha-automation-trace.ts b/src/panels/config/automation/ha-automation-trace.ts index c752e562ed..cd52275ed7 100644 --- a/src/panels/config/automation/ha-automation-trace.ts +++ b/src/panels/config/automation/ha-automation-trace.ts @@ -106,7 +106,9 @@ export class HaAutomationTrace extends LitElement { ? html` @@ -140,7 +142,9 @@ export class HaAutomationTrace extends LitElement { ? html` ${this.hass.localize( diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 4da764f879..65935d8f5d 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -180,12 +180,14 @@ export default class HaAutomationTrigger extends LitElement { } private _moveUp(ev) { + ev.stopPropagation(); const index = (ev.target as any).index; const newIndex = index - 1; this._move(index, newIndex); } private _moveDown(ev) { + ev.stopPropagation(); const index = (ev.target as any).index; const newIndex = index + 1; this._move(index, newIndex); diff --git a/src/panels/config/blueprint/blueprint-generic-editor.ts b/src/panels/config/blueprint/blueprint-generic-editor.ts new file mode 100644 index 0000000000..188ef35e25 --- /dev/null +++ b/src/panels/config/blueprint/blueprint-generic-editor.ts @@ -0,0 +1,275 @@ +import "@material/mwc-button/mwc-button"; +import { css, CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { nestedArrayMove } from "../../../common/util/array-move"; +import "../../../components/ha-alert"; +import "../../../components/ha-blueprint-picker"; +import "../../../components/ha-card"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-markdown"; +import "../../../components/ha-selector/ha-selector"; +import "../../../components/ha-settings-row"; +import { BlueprintAutomationConfig } from "../../../data/automation"; +import { BlueprintOrError, Blueprints } from "../../../data/blueprint"; +import { BlueprintScriptConfig } from "../../../data/script"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; + +@customElement("blueprint-generic-editor") +export abstract class HaBlueprintGenericEditor extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public isWide = false; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean, reflect: true }) public narrow = false; + + @state() protected _blueprints?: Blueprints; + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this._getBlueprints(); + } + + protected get _blueprint(): BlueprintOrError | undefined { + if (!this._blueprints) { + return undefined; + } + return this._blueprints[this._config.use_blueprint.path]; + } + + protected abstract get _config(): + | BlueprintAutomationConfig + | BlueprintScriptConfig; + + protected renderCard() { + const blueprint = this._blueprint; + return html` + +
+ ${this._blueprints + ? Object.keys(this._blueprints).length + ? html` + + ` + : this.hass.localize( + "ui.panel.config.automation.editor.blueprint.no_blueprints" + ) + : html``} +
+ + ${this._config.use_blueprint.path + ? blueprint && "error" in blueprint + ? html`

+ There is an error in this Blueprint: ${blueprint.error} +

` + : html`${blueprint?.metadata.description + ? html`` + : ""} + ${blueprint?.metadata?.input && + Object.keys(blueprint.metadata.input).length + ? Object.entries(blueprint.metadata.input).map( + ([key, value]) => { + const selector = value?.selector ?? { text: undefined }; + const type = Object.keys(selector)[0]; + const enhancedSelector = [ + "action", + "condition", + "trigger", + ].includes(type) + ? { + [type]: { + ...selector[type], + path: [key], + }, + } + : selector; + + return html` + ${value?.name || key} + + ${html``} + `; + } + ) + : html`

+ ${this.hass.localize( + "ui.panel.config.automation.editor.blueprint.no_inputs" + )} +

`}` + : ""} +
+ `; + } + + protected abstract _getBlueprints(); + + private _blueprintChanged(ev) { + ev.stopPropagation(); + if (this._config.use_blueprint.path === ev.detail.value) { + return; + } + fireEvent(this, "value-changed", { + value: { + ...this._config, + use_blueprint: { + path: ev.detail.value, + }, + }, + }); + } + + private _inputChanged(ev) { + ev.stopPropagation(); + const target = ev.target as any; + const key = target.key; + const value = ev.detail ? ev.detail.value : target.value; + if ( + (this._config.use_blueprint.input && + this._config.use_blueprint.input[key] === value) || + (!this._config.use_blueprint.input && value === "") + ) { + return; + } + const input = { ...this._config.use_blueprint.input, [key]: value }; + + fireEvent(this, "value-changed", { + value: { + ...this._config, + use_blueprint: { + ...this._config.use_blueprint, + input, + }, + }, + }); + } + + private _itemMoved(ev) { + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + + const input = nestedArrayMove( + this._config.use_blueprint.input, + oldIndex, + newIndex, + oldPath, + newPath + ); + + fireEvent(this, "value-changed", { + value: { + ...this._config, + use_blueprint: { + ...this._config.use_blueprint, + input, + }, + }, + }); + } + + protected _duplicate() { + fireEvent(this, "duplicate"); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + :host { + display: block; + } + ha-card.blueprint { + margin: 0 auto; + } + .padding { + padding: 16px; + } + .link-button-row { + padding: 14px; + } + .blueprint-picker-container { + padding: 0 16px 16px; + } + ha-textfield, + ha-blueprint-picker { + display: block; + } + h3 { + margin: 16px; + } + .introduction { + margin-top: 0; + margin-bottom: 12px; + } + .introduction a { + color: var(--primary-color); + } + p { + margin-bottom: 0; + } + .description { + margin-bottom: 16px; + } + ha-settings-row { + --paper-time-input-justify-content: flex-end; + --settings-row-content-width: 100%; + --settings-row-prefix-display: contents; + border-top: 1px solid var(--divider-color); + } + ha-alert { + margin-bottom: 16px; + display: block; + } + ha-alert.re-order { + border-radius: var(--ha-card-border-radius, 12px); + overflow: hidden; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "blueprint-generic-editor": HaBlueprintGenericEditor; + } +} diff --git a/src/panels/config/cloud/account/cloud-remote-pref.ts b/src/panels/config/cloud/account/cloud-remote-pref.ts index bcfb4bdde7..cd9d509f81 100644 --- a/src/panels/config/cloud/account/cloud-remote-pref.ts +++ b/src/panels/config/cloud/account/cloud-remote-pref.ts @@ -1,18 +1,22 @@ -import "@material/mwc-button"; import { mdiContentCopy, mdiHelpCircle } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import { copyToClipboard } from "../../../../common/util/copy-clipboard"; import "../../../../components/ha-alert"; +import "../../../../components/ha-button"; import "../../../../components/ha-card"; +import "../../../../components/ha-expansion-panel"; +import "../../../../components/ha-settings-row"; import "../../../../components/ha-switch"; // eslint-disable-next-line +import { formatDate } from "../../../../common/datetime/format_date"; import type { HaSwitch } from "../../../../components/ha-switch"; import { CloudStatusLoggedIn, connectCloudRemote, disconnectCloudRemote, + updateCloudPref, } from "../../../../data/cloud"; import type { HomeAssistant } from "../../../../types"; import { showToast } from "../../../../util/toast"; @@ -29,7 +33,8 @@ export class CloudRemotePref extends LitElement { return nothing; } - const { remote_enabled } = this.cloudStatus.prefs; + const { remote_enabled, remote_allow_remote_enable } = + this.cloudStatus.prefs; const { remote_connected, @@ -123,16 +128,60 @@ export class CloudRemotePref extends LitElement { >. -
-
- - ${this.hass.localize( - "ui.panel.config.cloud.account.remote.certificate_info" + + > + + ${this.hass.localize( + "ui.panel.config.cloud.account.remote.external_activation" + )} + ${this.hass.localize( + "ui.panel.config.cloud.account.remote.external_activation_secondary" + )} + + + + ${this.hass.localize( + "ui.panel.config.cloud.account.remote.certificate_info" + )} + ${this.cloudStatus!.remote_certificate + ? this.hass.localize( + "ui.panel.config.cloud.account.remote.certificate_expire", + { + date: formatDate( + new Date( + this.cloudStatus.remote_certificate.expire_date + ), + this.hass.locale, + this.hass.config + ), + } + ) + : nothing} + + ${this.hass.localize( + "ui.panel.config.cloud.account.remote.more_info" + )} + + +
`; @@ -160,6 +209,20 @@ export class CloudRemotePref extends LitElement { } } + private async _toggleAllowRemoteEnabledChanged(ev) { + const toggle = ev.target as HaSwitch; + + try { + await updateCloudPref(this.hass, { + remote_allow_remote_enable: toggle.checked, + }); + fireEvent(this, "ha-refresh-cloud-status"); + } catch (err: any) { + alert(err.message); + toggle.checked = !toggle.checked; + } + } + private async _copyURL(ev): Promise { const url = ev.currentTarget.url; await copyToClipboard(url); @@ -204,6 +267,8 @@ export class CloudRemotePref extends LitElement { position: absolute; right: 24px; top: 24px; + inset-inline-end: 24px; + inset-inline-start: initial; } .card-actions { display: flex; @@ -216,6 +281,12 @@ export class CloudRemotePref extends LitElement { color: var(--secondary-text-color); cursor: pointer; } + ha-formfield { + margin-top: 8px; + } + ha-expansion-panel { + margin-top: 8px; + } `; } } diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index d594df6952..71a8b0acdf 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -27,7 +27,10 @@ import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types"; import { LovelaceRow } from "../../../lovelace/entity-rows/types"; import { EntityRegistryStateEntry } from "../ha-config-device-page"; -import { computeCards } from "../../../lovelace/common/generate-lovelace-config"; +import { + computeCards, + computeSection, +} from "../../../lovelace/common/generate-lovelace-config"; @customElement("ha-device-entities-card") export class HaDeviceEntitiesCard extends LitElement { @@ -235,6 +238,9 @@ export class HaDeviceEntitiesCard extends LitElement { computeCards(this.hass.states, entities, { title: this.deviceName, }), + computeSection(entities, { + title: this.deviceName, + }), entities ); } diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index cff45a30a1..4ea0dd37d2 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -431,19 +431,18 @@ export class HaConfigDevicePage extends LitElement {
- - - ${computeStateName(entityState)} - - - + ${computeStateName(entityState)} + + ${!entityState.attributes.id ? html` @@ -528,15 +527,14 @@ export class HaConfigDevicePage extends LitElement { : undefined )} > - - - ${computeStateName(entityState)} - - - + ${computeStateName(entityState)} + + ${!entityState.attributes.id ? html` @@ -623,12 +621,10 @@ export class HaConfigDevicePage extends LitElement { return entityState ? html` - - - ${computeStateName(entityState)} - - - + + ${computeStateName(entityState)} + + ` : ""; @@ -1518,11 +1514,6 @@ export class HaConfigDevicePage extends LitElement { margin-top: 0; } - paper-item { - cursor: pointer; - font-size: var(--paper-font-body1_-_font-size); - } - a { text-decoration: none; color: var(--primary-color); diff --git a/src/panels/config/entities/entity-registry-settings-editor.ts b/src/panels/config/entities/entity-registry-settings-editor.ts index 03102930af..5c6b8607fe 100644 --- a/src/panels/config/entities/entity-registry-settings-editor.ts +++ b/src/panels/config/entities/entity-registry-settings-editor.ts @@ -939,9 +939,7 @@ export class EntityRegistrySettingsEditor extends LitElement { > diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index e6aacf6fd5..c0028557ec 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -74,7 +74,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { translationKey: "areas", iconPath: mdiSofa, iconColor: "#E48629", - components: ["zone"], + component: "zone", }, { path: "/hassio", @@ -108,7 +108,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { translationKey: "people", iconPath: mdiAccount, iconColor: "#5A87FA", - components: ["person", "users"], + component: ["person", "users"], }, { path: "#external-app-configuration", @@ -309,6 +309,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconPath: mdiBackupRestore, iconColor: "#0D47A1", component: "backup", + not_component: "hassio", }, { path: "/hassio/backups", @@ -341,7 +342,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { translationKey: "hardware", iconPath: mdiMemory, iconColor: "#301A8E", - components: ["hassio", "hardware"], + component: ["hassio", "hardware"], }, ], about: [ diff --git a/src/panels/config/hardware/dialog-hardware-available.ts b/src/panels/config/hardware/dialog-hardware-available.ts index b11d34410b..3dfd602367 100644 --- a/src/panels/config/hardware/dialog-hardware-available.ts +++ b/src/panels/config/hardware/dialog-hardware-available.ts @@ -171,12 +171,18 @@ class DialogHardwareAvailable extends LitElement implements HassDialog { ha-icon-button { position: absolute; right: 16px; + inset-inline-end: 16px; + inset-inline-start: initial; top: 10px; + inset-inline-end: 16px; + inset-inline-start: initial; text-decoration: none; color: var(--primary-text-color); } h2 { margin: 18px 42px 0 18px; + margin-inline-start: 18px; + margin-inline-end: 42px; color: var(--primary-text-color); } ha-expansion-panel { diff --git a/src/panels/config/helpers/forms/ha-input_select-form.ts b/src/panels/config/helpers/forms/ha-input_select-form.ts index e5a16fb54a..47ebdc6e32 100644 --- a/src/panels/config/helpers/forms/ha-input_select-form.ts +++ b/src/panels/config/helpers/forms/ha-input_select-form.ts @@ -177,8 +177,13 @@ class HaInputSelectForm extends LitElement { const index = (ev.target as any).index; if ( !(await showConfirmationDialog(this, { - title: "Delete this item?", - text: "Are you sure you want to delete this item?", + title: this.hass.localize( + "ui.dialogs.helper_settings.input_select.confirm_delete.delete" + ), + text: this.hass.localize( + "ui.dialogs.helper_settings.input_select.confirm_delete.prompt" + ), + destructive: true, })) ) { return; diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index 8e0ed86b45..7a960576e8 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -877,6 +877,8 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { color: var(--text-primary-color); position: absolute; right: 0px; + inset-inline-end: 0px; + inset-inline-start: initial; top: 4px; font-size: 0.65em; } @@ -884,7 +886,10 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { position: relative; } h1 { - margin: 8px 0 0 16px; + margin-top: 8px; + margin-left: 16px; + margin-inline-start: 16px; + margin-inline-end: initial; } ha-button-menu { color: var(--primary-text-color); diff --git a/src/panels/config/integrations/ha-integration-action-card.ts b/src/panels/config/integrations/ha-integration-action-card.ts index 189b5d4b46..ada2e3b76b 100644 --- a/src/panels/config/integrations/ha-integration-action-card.ts +++ b/src/panels/config/integrations/ha-integration-action-card.ts @@ -90,6 +90,8 @@ export class HaIntegrationActionCard extends LitElement { position: absolute; top: 8px; right: 8px; + inset-inline-end: 8px; + inset-inline-start: initial; } .filler { flex: 1; diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts index f10c634be9..21cf8ed491 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts @@ -20,8 +20,6 @@ import { haStyleDialog } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import { MatterManageFabricsDialogParams } from "./show-dialog-matter-manage-fabrics"; -const NABUCASA_FABRIC = 4939; - @customElement("dialog-matter-manage-fabrics") class DialogMatterManageFabrics extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -61,8 +59,9 @@ class DialogMatterManageFabrics extends LitElement { (fabric) => html`${fabric.vendor_name || fabric.fabric_label || fabric.vendor_id} @@ -99,6 +98,9 @@ class DialogMatterManageFabrics extends LitElement { private async _removeFabric(ev) { const fabric: MatterFabricData = ev.target.fabric; + if (this._nodeDiagnostics!.active_fabric_index === fabric.fabric_index) { + return; + } const fabricName = fabric.vendor_name || fabric.fabric_label || fabric.vendor_id.toString(); const confirm = await showConfirmationDialog(this, { diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts index 77ec91ee24..cbe8dc544f 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts @@ -137,6 +137,7 @@ class DialogMatterPingNode extends LitElement { public closeDialog(): void { this.device_id = undefined; this._status = undefined; + this._pingResult = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } diff --git a/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts b/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts index e8e068ca22..7a5375d8b2 100644 --- a/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts @@ -54,6 +54,7 @@ import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import { brandsUrl } from "../../../../../util/brands-url"; import { fileDownload } from "../../../../../util/file_download"; +import { documentationUrl } from "../../../../../util/documentation-url"; interface ThreadNetwork { name: string; @@ -123,11 +124,16 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { )} - ${this.hass.localize( - "ui.panel.config.thread.add_open_thread_border_router" - )} + ${this.hass.localize( + "ui.panel.config.thread.more_info" + )} +
`} ${networks.networks.length diff --git a/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts index 11d37b1988..a7570e0a46 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts @@ -269,6 +269,8 @@ class ZHAAddDevicesPage extends LitElement { margin-inline-start: initial; top: -6px; right: 0; + inset-inline-end: 0; + inset-inline-start: initial; color: var(--primary-color); } .search-button { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts index 4872fde8e1..d6932b10ff 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts @@ -241,6 +241,8 @@ export class ZHAClusterCommands extends LitElement { float: right; top: -6px; right: 0; + inset-inline-end: 0; + inset-inline-start: initial; padding-right: 0px; padding-inline-end: 0px; padding-inline-start: initial; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts index f21f688e07..0216d0a0df 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts @@ -1,6 +1,5 @@ import "@material/mwc-button"; import { mdiDelete } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; import { CSSResultGroup, LitElement, @@ -32,6 +31,8 @@ import "../../../ha-config-section"; import { formatAsPaddedHex } from "./functions"; import "./zha-device-endpoint-data-table"; import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table"; +import "@material/mwc-list/mwc-list"; +import "../../../../../components/ha-list-item"; @customElement("zha-group-page") export class ZHAGroupPage extends LitElement { @@ -131,20 +132,24 @@ export class ZHAGroupPage extends LitElement { ${this.hass.localize("ui.panel.config.zha.groups.members")}
- ${this.group.members.length - ? this.group.members.map( - (member) => - html` - ${member.device.user_given_name || - member.device.name} + ${this.group.members.length + ? this.group.members.map( + (member) => + html` - ` - ) - : html` This group has no members `} + ${member.device.user_given_name || + member.device.name} + ` + ) + : html` + This group has no members + `} + ${this.group.members.length ? html` diff --git a/src/panels/config/logs/dialog-system-log-detail.ts b/src/panels/config/logs/dialog-system-log-detail.ts index 5e047d7b07..1ea09ae743 100644 --- a/src/panels/config/logs/dialog-system-log-detail.ts +++ b/src/panels/config/logs/dialog-system-log-detail.ts @@ -102,12 +102,17 @@ class DialogSystemLogDetail extends LitElement { : ""}

- Logger: ${item.name}
- Source: ${item.source.join(":")} + ${this.hass.localize("ui.panel.config.logs.detail.logger")}: + ${item.name}
+ ${this.hass.localize("ui.panel.config.logs.detail.source")}: + ${item.source.join(":")} ${integration ? html`
- Integration: ${domainToName(this.hass.localize, integration)} + ${this.hass.localize( + "ui.panel.config.logs.detail.integration" + )}: + ${domainToName(this.hass.localize, integration)} ${!this._manifest || // Can happen with custom integrations !showDocumentation @@ -122,7 +127,9 @@ class DialogSystemLogDetail extends LitElement { : this._manifest.documentation} target="_blank" rel="noreferrer" - >documentation${this.hass.localize( + "ui.panel.config.logs.detail.documentation" + )}${this._manifest.is_built_in || this._manifest.issue_tracker ? html`, @@ -133,7 +140,9 @@ class DialogSystemLogDetail extends LitElement { )} target="_blank" rel="noreferrer" - >issues${this.hass.localize( + "ui.panel.config.logs.detail.issues" + )}` : ""}) `} @@ -142,16 +151,21 @@ class DialogSystemLogDetail extends LitElement {
${item.count > 0 ? html` - First occurred: + ${this.hass.localize( + "ui.panel.config.logs.detail.first_occurred" + )}: ${formatSystemLogTime( item.first_occurred, this.hass!.locale, this.hass!.config )} - (${item.count} occurrences)
+ (${item.count} + ${this.hass.localize( + "ui.panel.config.logs.detail.occurrences" + )})
` : ""} - Last logged: + ${this.hass.localize("ui.panel.config.logs.detail.last_logged")}: ${formatSystemLogTime( item.timestamp, this.hass!.locale, diff --git a/src/panels/config/lovelace/ha-config-lovelace.ts b/src/panels/config/lovelace/ha-config-lovelace.ts index c494232bb8..eda9a8fd56 100644 --- a/src/panels/config/lovelace/ha-config-lovelace.ts +++ b/src/panels/config/lovelace/ha-config-lovelace.ts @@ -8,13 +8,20 @@ import { HomeAssistant } from "../../../types"; export const lovelaceTabs = [ { - component: "lovelace", path: "/config/lovelace/dashboards", translationKey: "ui.panel.config.lovelace.dashboards.caption", iconPath: mdiViewDashboard, }, ]; +export const lovelaceResourcesTabs = [ + { + path: "/config/lovelace/resources", + translationKey: "ui.panel.config.lovelace.resources.caption", + iconPath: mdiViewDashboard, + }, +]; + @customElement("ha-config-lovelace") class HaConfigLovelace extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index 9574f14fce..7713108edd 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -34,7 +34,7 @@ import "../../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant, Route } from "../../../../types"; import { loadLovelaceResources } from "../../../lovelace/common/load-resources"; -import { lovelaceTabs } from "../ha-config-lovelace"; +import { lovelaceResourcesTabs } from "../ha-config-lovelace"; import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail"; @customElement("ha-config-lovelace-resources") @@ -117,7 +117,7 @@ export class HaConfigLovelaceRescources extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .route=${this.route} - .tabs=${lovelaceTabs} + .tabs=${lovelaceResourcesTabs} .columns=${this._columns(this.hass.language)} .data=${this._resources} .noDataText=${this.hass.localize( diff --git a/src/panels/config/script/blueprint-script-editor.ts b/src/panels/config/script/blueprint-script-editor.ts index be455965f4..c233eae662 100644 --- a/src/panels/config/script/blueprint-script-editor.ts +++ b/src/panels/config/script/blueprint-script-editor.ts @@ -1,52 +1,19 @@ -import { css, CSSResultGroup, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { nestedArrayMove } from "../../../common/util/array-move"; +import { html } from "lit"; +import { customElement, property } from "lit/decorators"; import "../../../components/ha-alert"; -import "../../../components/ha-blueprint-picker"; -import "../../../components/ha-card"; -import "../../../components/ha-circular-progress"; -import "../../../components/ha-markdown"; -import "../../../components/ha-selector/ha-selector"; -import "../../../components/ha-settings-row"; -import { - BlueprintOrError, - Blueprints, - fetchBlueprints, -} from "../../../data/blueprint"; import { BlueprintScriptConfig } from "../../../data/script"; -import { haStyle } from "../../../resources/styles"; -import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; +import { fetchBlueprints } from "../../../data/blueprint"; +import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor"; @customElement("blueprint-script-editor") -export class HaBlueprintScriptEditor extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ type: Boolean }) public isWide = false; - - @property({ reflect: true, type: Boolean }) public narrow = false; - - @property({ type: Boolean }) public disabled = false; - +export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor { @property({ attribute: false }) public config!: BlueprintScriptConfig; - @state() private _blueprints?: Blueprints; - - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - this._getBlueprints(); - } - - private get _blueprint(): BlueprintOrError | undefined { - if (!this._blueprints) { - return undefined; - } - return this._blueprints[this.config.use_blueprint.path]; + protected get _config(): BlueprintScriptConfig { + return this.config; } protected render() { - const blueprint = this._blueprint; return html` ${this.disabled ? html` @@ -56,228 +23,14 @@ export class HaBlueprintScriptEditor extends LitElement { ` : ""} - -

- ${this._blueprints - ? Object.keys(this._blueprints).length - ? html` - - ` - : this.hass.localize( - "ui.panel.config.automation.editor.blueprint.no_blueprints" - ) - : html``} -
- ${this.config.use_blueprint.path - ? blueprint && "error" in blueprint - ? html`

- There is an error in this Blueprint: ${blueprint.error} -

` - : html`${blueprint?.metadata.description - ? html`` - : ""} - ${blueprint?.metadata?.input && - Object.keys(blueprint.metadata.input).length - ? Object.entries(blueprint.metadata.input).map( - ([key, value]) => { - const selector = value?.selector ?? { text: undefined }; - const type = Object.keys(selector)[0]; - const enhancedSelector = [ - "action", - "condition", - "trigger", - ].includes(type) - ? { - [type]: { - ...selector[type], - path: [key], - }, - } - : selector; - - return html` - ${value?.name || key} - - ${html``} - `; - } - ) - : html`

- ${this.hass.localize( - "ui.panel.config.automation.editor.blueprint.no_inputs" - )} -

`}` - : ""} - + ${this.renderCard()} `; } - private async _getBlueprints() { + protected async _getBlueprints() { this._blueprints = await fetchBlueprints(this.hass, "script"); } - - private _blueprintChanged(ev) { - ev.stopPropagation(); - if (this.config.use_blueprint.path === ev.detail.value) { - return; - } - fireEvent(this, "value-changed", { - value: { - ...this.config, - use_blueprint: { - path: ev.detail.value, - }, - }, - }); - } - - private _inputChanged(ev) { - ev.stopPropagation(); - const target = ev.target as any; - const key = target.key; - const value = ev.detail ? ev.detail.value : target.value; - if ( - (this.config.use_blueprint.input && - this.config.use_blueprint.input[key] === value) || - (!this.config.use_blueprint.input && value === "") - ) { - return; - } - const input = { ...this.config.use_blueprint.input, [key]: value }; - - fireEvent(this, "value-changed", { - value: { - ...this.config, - use_blueprint: { - ...this.config.use_blueprint, - input, - }, - }, - }); - } - - private _itemMoved(ev) { - ev.stopPropagation(); - const { oldIndex, newIndex, oldPath, newPath } = ev.detail; - - const input = nestedArrayMove( - this.config.use_blueprint.input, - oldIndex, - newIndex, - oldPath, - newPath - ); - - fireEvent(this, "value-changed", { - value: { - ...this.config, - use_blueprint: { - ...this.config.use_blueprint, - input, - }, - }, - }); - } - - private _duplicate() { - fireEvent(this, "duplicate"); - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - :host { - display: block; - } - ha-card.blueprint { - margin: 0 auto; - } - .padding { - padding: 16px; - } - .link-button-row { - padding: 14px; - } - .blueprint-picker-container { - padding: 0 16px 16px; - } - ha-textfield, - ha-blueprint-picker { - display: block; - } - h3 { - margin: 16px; - } - .introduction { - margin-top: 0; - margin-bottom: 12px; - } - .introduction a { - color: var(--primary-color); - } - p { - margin-bottom: 0; - } - .description { - margin-bottom: 16px; - } - ha-settings-row { - --paper-time-input-justify-content: flex-end; - --settings-row-content-width: 100%; - --settings-row-prefix-display: contents; - border-top: 1px solid var(--divider-color); - } - ha-alert { - margin-bottom: 16px; - display: block; - } - ha-alert.re-order { - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } - `, - ]; - } } - declare global { interface HTMLElementTagNameMap { "blueprint-script-editor": HaBlueprintScriptEditor; diff --git a/src/panels/config/storage/ha-config-section-storage.ts b/src/panels/config/storage/ha-config-section-storage.ts index 708f79e861..89b5fc7cf0 100644 --- a/src/panels/config/storage/ha-config-section-storage.ts +++ b/src/panels/config/storage/ha-config-section-storage.ts @@ -334,6 +334,8 @@ class HaConfigSectionStorage extends LitElement { position: relative; top: -10px; right: 10px; + inset-inline-end: 10px; + inset-inline-start: initial; } .no-mounts { diff --git a/src/panels/config/voice-assistants/debug/assist-pipeline-run-debug.ts b/src/panels/config/voice-assistants/debug/assist-pipeline-run-debug.ts index a2190dbc63..6137be81b1 100644 --- a/src/panels/config/voice-assistants/debug/assist-pipeline-run-debug.ts +++ b/src/panels/config/voice-assistants/debug/assist-pipeline-run-debug.ts @@ -50,7 +50,9 @@ export class AssistPipelineRunDebug extends LitElement { ${this._pipelineRuns.length > 0 ? html` @@ -59,13 +61,13 @@ export class AssistPipelineRunDebug extends LitElement { @click=${this._clearConversation} .disabled=${!this._finished} > - Clear + ${this.hass.localize("ui.common.clear")} - Download + ${this.hass.localize("ui.common.download")} ` : ""} @@ -81,7 +83,9 @@ export class AssistPipelineRunDebug extends LitElement { >
- Run Text Pipeline + ${this.hass.localize( + "ui.panel.config.voice_assistants.debug.pipeline.run_text_pipeline" + )} - Run Audio Pipeline + ${this.hass.localize( + "ui.panel.config.voice_assistants.debug.pipeline.run_audio_pipeline" + )} - Run Audio Pipeline with Wake Word detection + ${this.hass.localize( + "ui.panel.config.voice_assistants.debug.pipeline.run_audio_with_wake" + )}
` @@ -107,7 +115,9 @@ export class AssistPipelineRunDebug extends LitElement { ? html` @@ -115,7 +125,9 @@ export class AssistPipelineRunDebug extends LitElement { @click=${this._runTextPipeline} .disabled=${!this._finished} > - Send + ${this.hass.localize( + "ui.panel.config.voice_assistants.debug.pipeline.send" + )} ` : this._finished @@ -123,14 +135,22 @@ export class AssistPipelineRunDebug extends LitElement { "wake_word" ? html` - Continue listening for wake word + ${this.hass.localize( + "ui.panel.config.voice_assistants.debug.pipeline.continue_listening" + )} ` : html` - Continue talking + ${this.hass.localize( + "ui.panel.config.voice_assistants.debug.pipeline.continue_talking" + )} ` : html` - + ${this.narrow diff --git a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts index 6ffa9790da..b32666bde6 100644 --- a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts +++ b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts @@ -34,6 +34,11 @@ import { HomeAssistant } from "../../../types"; import { showToast } from "../../../util/toast"; import type { DialogStatisticsAdjustSumParams } from "./show-dialog-statistics-adjust-sum"; +interface CombinedStat { + hour: StatisticValue | null; + fiveMin: StatisticValue[]; +} + @customElement("dialog-statistics-adjust-sum") export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -196,6 +201,13 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { @value-changed=${this._dateTimeSelectorChanged} >
${stats}
+ { + this._stats5min = undefined; + this._statsHour = undefined; + const statId = this._params!.statistic.statistic_id; + + // Get all the data + const start = new Date(0); + const end = new Date(); + + const statsHourData = await fetchStatistics( + this.hass, + start, + end, + [statId], + "hour" + ); + + const statsHour = statId in statsHourData ? statsHourData[statId] : []; + if (statsHour.length === 0) { + return; + } + + const stats5MinData = await fetchStatistics( + this.hass, + start, + end, + [statId], + "5minute" + ); + + const stats5Min = statId in stats5MinData ? stats5MinData[statId] : []; + // First datapoint of 5 minute data in the history is always junk since it counts the entire sum + // as the change, which we don't want here. + stats5Min.shift(); + + const combinedStatsData: CombinedStat[] = []; + statsHour.forEach((s) => { + combinedStatsData.push({ hour: s, fiveMin: [] }); + }); + + const lasthour: CombinedStat = { hour: null, fiveMin: [] }; + + let i = 0; + stats5Min.forEach((s) => { + let matched = false; + for (i; i < combinedStatsData.length; i++) { + const hour = combinedStatsData[i].hour; + if (hour && s.start >= hour.start && s.end <= hour.end) { + combinedStatsData[i].fiveMin.push(s); + matched = true; + break; + } + } + if (!matched) { + lasthour.fiveMin.push(s); + } + }); + + combinedStatsData.push(lasthour); + + let statsOutliers: StatisticValue[] = []; + let min = 0; + const numOutliers = 10; + + // Track the top 10 values. + const addOutlier = (s) => { + const val = Math.abs(s.change ?? 0); + if (statsOutliers.length < numOutliers || val > min) { + statsOutliers.push(s); + statsOutliers = statsOutliers.sort( + (a, b) => Math.abs(b.change ?? 0) - Math.abs(a.change ?? 0) + ); + statsOutliers = statsOutliers.slice(0, numOutliers); + min = statsOutliers[statsOutliers.length - 1].change ?? 0; + } + }; + + // If an hour has no five minute data, add the hour value + // Otherwise, add the 5 minute values and ignore the hour value + combinedStatsData.forEach((c) => { + if (c.fiveMin.length === 0 && c.hour) { + addOutlier(c.hour); + } else { + c.fiveMin.forEach((s) => { + addOutlier(s); + }); + } + }); + + // Outliers are a possible mix of hour/5minute data, but the distinction + // is not relevant here, as long as only one array is populated. + this._statsHour = statsOutliers; + this._stats5min = []; + } + private async _fixIssue(): Promise { const unit = getDisplayUnit( this.hass, diff --git a/src/panels/developer-tools/template/developer-tools-template.ts b/src/panels/developer-tools/template/developer-tools-template.ts index 7732ad7760..e4162d3077 100644 --- a/src/panels/developer-tools/template/developer-tools-template.ts +++ b/src/panels/developer-tools/template/developer-tools-template.ts @@ -290,6 +290,8 @@ class HaPanelDevTemplate extends LitElement { position: absolute; top: 8px; right: 8px; + inset-inline-end: 8px; + inset-inline-start: initial; } ha-alert { diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts index a9462d0edb..4dd6498ae4 100644 --- a/src/panels/energy/ha-panel-energy.ts +++ b/src/panels/energy/ha-panel-energy.ts @@ -7,7 +7,7 @@ import { html, nothing, } from "lit"; -import { mdiPencil } from "@mdi/js"; +import { mdiPencil, mdiDownload } from "@mdi/js"; import { customElement, property, state } from "lit/decorators"; import "../../components/ha-menu-button"; import "../../components/ha-list-item"; @@ -19,6 +19,18 @@ import "../lovelace/components/hui-energy-period-selector"; import { Lovelace } from "../lovelace/types"; import "../lovelace/views/hui-view"; import { navigate } from "../../common/navigate"; +import { + getEnergyDataCollection, + getEnergyGasUnit, + getEnergyWaterUnit, + GridSourceTypeEnergyPreference, + SolarSourceTypeEnergyPreference, + BatterySourceTypeEnergyPreference, + GasSourceTypeEnergyPreference, + WaterSourceTypeEnergyPreference, + DeviceConsumptionEnergyPreference, +} from "../../data/energy"; +import { fileDownload } from "../../util/file_download"; const ENERGY_LOVELACE_CONFIG: LovelaceConfig = { views: [ @@ -86,6 +98,15 @@ class PanelEnergy extends LitElement { ${this.hass!.localize("ui.panel.energy.configure")} + + + + ${this.hass!.localize("ui.panel.energy.download_data")} + ` : nothing} @@ -122,6 +143,182 @@ class PanelEnergy extends LitElement { navigate("/config/energy?historyBack=1"); } + private async _dumpCSV(ev) { + ev.stopPropagation(); + const energyData = getEnergyDataCollection(this.hass, { + key: "energy_dashboard", + }); + + if (!energyData.prefs || !energyData.state.stats) { + return; + } + + const gasUnit = + getEnergyGasUnit( + this.hass, + energyData.prefs, + energyData.state.statsMetadata + ) || ""; + const waterUnit = getEnergyWaterUnit(this.hass); + const electricUnit = "kWh"; + + const energy_sources = energyData.prefs.energy_sources; + const device_consumption = energyData.prefs.device_consumption; + const stats = energyData.state.stats; + + const timeSet = new Set(); + Object.values(stats).forEach((stat) => { + stat.forEach((datapoint) => { + timeSet.add(datapoint.start); + }); + }); + const times = Array.from(timeSet).sort(); + + const headers = + "entity_id,type,unit," + + times.map((t) => new Date(t).toISOString()).join(",") + + "\n"; + const csv: string[] = []; + csv[0] = headers; + + const processStat = function (stat: string, type: string, unit: string) { + let n = 0; + const row: string[] = []; + if (!stats[stat]) { + return; + } + row.push(stat); + row.push(type); + row.push(unit.normalize("NFKD")); + times.forEach((t) => { + if (stats[stat][n].start > t) { + row.push(""); + } else if (n < stats[stat].length && stats[stat][n].start === t) { + row.push((stats[stat][n].change ?? "").toString()); + n++; + } else { + row.push(""); + } + }); + csv.push(row.join(",") + "\n"); + }; + + const currency = this.hass.config.currency; + + const printCategory = function ( + type: string, + statIds: string[], + unit: string, + costType?: string + ) { + if (statIds.length) { + statIds.forEach((stat) => processStat(stat, type, unit)); + if (costType) { + statIds.forEach((stat) => { + const costStat = energyData.state.info.cost_sensors[stat]; + if (energyData.state.info.cost_sensors[stat]) { + processStat(costStat, costType, currency); + } + }); + } + } + }; + + const grid_consumptions: string[] = []; + const grid_productions: string[] = []; + energy_sources + .filter((s) => s.type === "grid") + .forEach((source) => { + source = source as GridSourceTypeEnergyPreference; + source.flow_from.forEach((flowFrom) => { + grid_consumptions.push(flowFrom.stat_energy_from); + }); + source.flow_to.forEach((flowTo) => { + grid_productions.push(flowTo.stat_energy_to); + }); + }); + + printCategory( + "grid_consumption", + grid_consumptions, + electricUnit, + "grid_consumption_cost" + ); + printCategory( + "grid_return", + grid_productions, + electricUnit, + "grid_return_compensation" + ); + + const battery_ins: string[] = []; + const battery_outs: string[] = []; + energy_sources + .filter((s) => s.type === "battery") + .forEach((source) => { + source = source as BatterySourceTypeEnergyPreference; + battery_ins.push(source.stat_energy_to); + battery_outs.push(source.stat_energy_from); + }); + + printCategory("battery_in", battery_ins, electricUnit); + printCategory("battery_out", battery_outs, electricUnit); + + const solar_productions: string[] = []; + energy_sources + .filter((s) => s.type === "solar") + .forEach((source) => { + source = source as SolarSourceTypeEnergyPreference; + solar_productions.push(source.stat_energy_from); + }); + + printCategory("solar_production", solar_productions, electricUnit); + + const gas_consumptions: string[] = []; + energy_sources + .filter((s) => s.type === "gas") + .forEach((source) => { + source = source as GasSourceTypeEnergyPreference; + gas_consumptions.push(source.stat_energy_from); + }); + + printCategory( + "gas_consumption", + gas_consumptions, + gasUnit, + "gas_consumption_cost" + ); + + const water_consumptions: string[] = []; + energy_sources + .filter((s) => s.type === "water") + .forEach((source) => { + source = source as WaterSourceTypeEnergyPreference; + water_consumptions.push(source.stat_energy_from); + }); + + printCategory( + "water_consumption", + water_consumptions, + waterUnit, + "water_consumption_cost" + ); + + const devices: string[] = []; + device_consumption.forEach((source) => { + source = source as DeviceConsumptionEnergyPreference; + devices.push(source.stat_consumption); + }); + + printCategory("device_consumption", devices, electricUnit); + + const blob = new Blob(csv, { + type: "text/csv", + }); + const url = window.URL.createObjectURL(blob); + fileDownload(url, "energy.csv"); + } + private _reloadView() { // Force strategy to be re-run by make a copy of the view const config = this._lovelace!.config; diff --git a/src/panels/energy/strategies/energy-view-strategy.ts b/src/panels/energy/strategies/energy-view-strategy.ts index 27a0a91371..d41ea668ba 100644 --- a/src/panels/energy/strategies/energy-view-strategy.ts +++ b/src/panels/energy/strategies/energy-view-strategy.ts @@ -154,6 +154,13 @@ export class EnergyViewStrategy extends ReactiveElement { // Only include if we have at least 1 device in the config. if (prefs.device_consumption.length) { + view.cards!.push({ + title: hass.localize( + "ui.panel.energy.cards.energy_devices_detail_graph_title" + ), + type: "energy-devices-detail-graph", + collection_key: "energy_dashboard", + }); view.cards!.push({ title: hass.localize( "ui.panel.energy.cards.energy_devices_graph_title" diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index dae3bae725..49797dda56 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -433,6 +433,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this._statisticsHistory = computeHistory( this.hass, statsHistoryStates, + [], this.hass.localize, sensorNumericDeviceClasses, true @@ -472,6 +473,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this._stateHistory = computeHistory( this.hass, history, + entityIds, this.hass.localize, sensorNumericDeviceClasses, true diff --git a/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts new file mode 100644 index 0000000000..b4701dab84 --- /dev/null +++ b/src/panels/lovelace/card-features/hui-fan-preset-modes-card-feature.ts @@ -0,0 +1,231 @@ +import { mdiTuneVariant } from "@mdi/js"; +import { HassEntity } from "home-assistant-js-websocket"; +import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; +import "../../../components/ha-control-select"; +import type { ControlSelectOption } from "../../../components/ha-control-select"; +import "../../../components/ha-control-select-menu"; +import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; +import { FanEntity, FanEntityFeature } from "../../../data/fan"; +import { UNAVAILABLE } from "../../../data/entity"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { FanPresetModesCardFeatureConfig } from "./types"; + +export const supportsFanPresetModesCardFeature = (stateObj: HassEntity) => { + const domain = computeDomain(stateObj.entity_id); + return ( + domain === "fan" && supportsFeature(stateObj, FanEntityFeature.PRESET_MODE) + ); +}; + +@customElement("hui-fan-preset-modes-card-feature") +class HuiFanPresetModesCardFeature + extends LitElement + implements LovelaceCardFeature +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public stateObj?: FanEntity; + + @state() private _config?: FanPresetModesCardFeatureConfig; + + @state() _currentPresetMode?: string; + + @query("ha-control-select-menu", true) + private _haSelect?: HaControlSelectMenu; + + static getStubConfig( + _, + stateObj?: HassEntity + ): FanPresetModesCardFeatureConfig { + return { + type: "fan-preset-modes", + style: "dropdown", + preset_modes: stateObj?.attributes.preset_modes || [], + }; + } + + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/hui-fan-preset-modes-card-feature-editor" + ); + return document.createElement("hui-fan-preset-modes-card-feature-editor"); + } + + public setConfig(config: FanPresetModesCardFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected willUpdate(changedProp: PropertyValues): void { + super.willUpdate(changedProp); + if (changedProp.has("stateObj") && this.stateObj) { + this._currentPresetMode = this.stateObj.attributes.preset_mode; + } + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (this._haSelect && changedProps.has("hass")) { + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + if ( + this.hass && + this.hass.formatEntityAttributeValue !== + oldHass?.formatEntityAttributeValue + ) { + this._haSelect.layoutOptions(); + } + } + } + + private async _valueChanged(ev: CustomEvent) { + const presetMode = + (ev.detail as any).value ?? ((ev.target as any).value as string); + + const oldPresetMode = this.stateObj!.attributes.preset_mode; + + if (presetMode === oldPresetMode) return; + + this._currentPresetMode = presetMode; + + try { + await this._setMode(presetMode); + } catch (err) { + this._currentPresetMode = oldPresetMode; + } + } + + private async _setMode(mode: string) { + await this.hass!.callService("fan", "set_preset_mode", { + entity_id: this.stateObj!.entity_id, + preset_mode: mode, + }); + } + + protected render(): TemplateResult | null { + if ( + !this._config || + !this.hass || + !this.stateObj || + !supportsFanPresetModesCardFeature(this.stateObj) + ) { + return null; + } + + const stateObj = this.stateObj; + + const modes = stateObj.attributes.preset_modes || []; + + const options = modes + .filter((mode) => (this._config!.preset_modes || []).includes(mode)) + .map((mode) => ({ + value: mode, + label: this.hass!.formatEntityAttributeValue( + this.stateObj!, + "preset_mode", + mode + ), + icon: html``, + })); + + if (this._config.style === "icons") { + return html` +
+ + +
+ `; + } + + return html` +
+ + ${this._currentPresetMode + ? html`` + : html` + + `} + ${options.map( + (option) => html` + + ${option.icon}${option.label} + + ` + )} + +
+ `; + } + + static get styles() { + return css` + ha-control-select-menu { + box-sizing: border-box; + --control-select-menu-height: 40px; + --control-select-menu-border-radius: 10px; + line-height: 1.2; + display: block; + width: 100%; + } + ha-control-select { + --control-select-color: var(--feature-color); + --control-select-padding: 0; + --control-select-thickness: 40px; + --control-select-border-radius: 10px; + --control-select-button-border-radius: 10px; + } + .container { + padding: 0 12px 12px 12px; + width: auto; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-fan-preset-modes-card-feature": HuiFanPresetModesCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index e5c4d38858..f96427b4ba 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -26,6 +26,12 @@ export interface LightColorTempCardFeatureConfig { type: "light-color-temp"; } +export interface FanPresetModesCardFeatureConfig { + type: "fan-preset-modes"; + style?: "dropdown" | "icons"; + preset_modes?: string[]; +} + export interface FanSpeedCardFeatureConfig { type: "fan-speed"; } @@ -123,19 +129,20 @@ export type LovelaceCardFeatureConfig = | CoverPositionCardFeatureConfig | CoverTiltPositionCardFeatureConfig | CoverTiltCardFeatureConfig + | FanPresetModesCardFeatureConfig | FanSpeedCardFeatureConfig | HumidifierToggleCardFeatureConfig | HumidifierModesCardFeatureConfig | LawnMowerCommandsCardFeatureConfig | LightBrightnessCardFeatureConfig | LightColorTempCardFeatureConfig - | VacuumCommandsCardFeatureConfig + | NumericInputCardFeatureConfig + | SelectOptionsCardFeatureConfig | TargetHumidityCardFeatureConfig | TargetTemperatureCardFeatureConfig - | WaterHeaterOperationModesCardFeatureConfig - | SelectOptionsCardFeatureConfig - | NumericInputCardFeatureConfig - | UpdateActionsCardFeatureConfig; + | UpdateActionsCardFeatureConfig + | VacuumCommandsCardFeatureConfig + | WaterHeaterOperationModesCardFeatureConfig; export type LovelaceCardFeatureContext = { entity_id?: string; diff --git a/src/panels/lovelace/cards/energy/common/color.ts b/src/panels/lovelace/cards/energy/common/color.ts new file mode 100644 index 0000000000..f9f02f3674 --- /dev/null +++ b/src/panels/lovelace/cards/energy/common/color.ts @@ -0,0 +1,64 @@ +import colors from "color-name"; +import { + hex2rgb, + lab2rgb, + rgb2hex, + rgb2lab, +} from "../../../../../common/color/convert-color"; +import { labBrighten, labDarken } from "../../../../../common/color/lab"; + +export function getEnergyColor( + computedStyles: CSSStyleDeclaration, + darkMode: boolean, + background: boolean, + compare: boolean, + propertyName: string, + idx?: number +): string { + const themeIdxColor = computedStyles + .getPropertyValue(propertyName + "-" + idx) + .trim(); + + const themeColor = + themeIdxColor.length > 0 + ? themeIdxColor + : computedStyles.getPropertyValue(propertyName).trim(); + + let hexColor; + if (themeColor.startsWith("#")) { + hexColor = themeColor; + } else { + const rgbFromColorName = colors[themeColor]; + if (!rgbFromColorName) { + // We have a named color, and there's nothing in the table, + // so nothing further we can do with it. + // Compare/border/background color will all be the same. + return themeColor; + } + hexColor = rgb2hex(rgbFromColorName); + } + + if (themeIdxColor.length === 0 && idx) { + // Brighten or darken the color based on set position. + // Skip if theme already provides a color for this set. + + hexColor = rgb2hex( + lab2rgb( + darkMode + ? labBrighten(rgb2lab(hex2rgb(hexColor)), idx) + : labDarken(rgb2lab(hex2rgb(hexColor)), idx) + ) + ); + } + + if (compare) { + if (background) { + hexColor += "32"; + } else { + hexColor += "7F"; + } + } else if (background) { + hexColor += "7F"; + } + return hexColor; +} diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts new file mode 100644 index 0000000000..5bd0e8ce33 --- /dev/null +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts @@ -0,0 +1,358 @@ +import { + ChartData, + ChartDataset, + ChartOptions, + ScatterDataPoint, +} from "chart.js"; +import { endOfToday, startOfToday } from "date-fns/esm"; +import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import memoizeOne from "memoize-one"; +import { getColorByIndex } from "../../../../common/color/colors"; +import { ChartDatasetExtra } from "../../../../components/chart/ha-chart-base"; +import "../../../../components/ha-card"; +import { + DeviceConsumptionEnergyPreference, + EnergyData, + getEnergyDataCollection, +} from "../../../../data/energy"; +import { + calculateStatisticSumGrowth, + getStatisticLabel, + Statistics, + StatisticsMetaData, +} from "../../../../data/recorder"; +import { FrontendLocaleData } from "../../../../data/translation"; +import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { EnergyDevicesDetailGraphCardConfig } from "../types"; +import { hasConfigChanged } from "../../common/has-changed"; +import { getCommonOptions } from "./common/energy-chart-options"; + +const UNIT = "kWh"; + +@customElement("hui-energy-devices-detail-graph-card") +export class HuiEnergyDevicesDetailGraphCard + extends SubscribeMixin(LitElement) + implements LovelaceCard +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _config?: EnergyDevicesDetailGraphCardConfig; + + @state() private _chartData: ChartData = { datasets: [] }; + + @state() private _chartDatasetExtra: ChartDatasetExtra[] = []; + + @state() private _data?: EnergyData; + + @state() private _start = startOfToday(); + + @state() private _end = endOfToday(); + + @state() private _compareStart?: Date; + + @state() private _compareEnd?: Date; + + @state() private _hiddenStats = new Set(); + + protected hassSubscribeRequiredHostProps = ["_config"]; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + getEnergyDataCollection(this.hass, { + key: this._config?.collection_key, + }).subscribe((data) => { + this._data = data; + this._processStatistics(); + }), + ]; + } + + public getCardSize(): Promise | number { + return 3; + } + + public setConfig(config: EnergyDevicesDetailGraphCardConfig): void { + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return ( + hasConfigChanged(this, changedProps) || + changedProps.size > 1 || + !changedProps.has("hass") + ); + } + + protected willUpdate(changedProps: PropertyValues) { + if (changedProps.has("_hiddenStats") && this._data) { + this._processStatistics(); + } + } + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + return html` + + ${this._config.title + ? html`

${this._config.title}

` + : ""} +
+ +
+
+ `; + } + + private _datasetHidden(ev) { + ev.stopPropagation(); + this._hiddenStats.add( + this._data!.prefs.device_consumption[ev.detail.index].stat_consumption + ); + this.requestUpdate("_hiddenStats"); + } + + private _datasetUnhidden(ev) { + ev.stopPropagation(); + this._hiddenStats.delete( + this._data!.prefs.device_consumption[ev.detail.index].stat_consumption + ); + this.requestUpdate("_hiddenStats"); + } + + private _createOptions = memoizeOne( + ( + start: Date, + end: Date, + locale: FrontendLocaleData, + config: HassConfig, + unit?: string, + compareStart?: Date, + compareEnd?: Date + ): ChartOptions => { + const commonOptions = getCommonOptions( + start, + end, + locale, + config, + unit, + compareStart, + compareEnd + ); + + const options: ChartOptions = { + ...commonOptions, + interaction: { + mode: "nearest", + }, + plugins: { + ...commonOptions.plugins!, + legend: { + display: true, + labels: { + usePointStyle: true, + }, + }, + }, + }; + return options; + } + ); + + private _processStatistics() { + const energyData = this._data!; + const data = energyData.stats; + const compareData = energyData.statsCompare; + + const growthValues = {}; + energyData.prefs.device_consumption.forEach((device) => { + const value = + device.stat_consumption in data + ? calculateStatisticSumGrowth(data[device.stat_consumption]) || 0 + : 0; + + growthValues[device.stat_consumption] = value; + }); + + const sorted_devices = energyData.prefs.device_consumption.map( + (device) => device.stat_consumption + ); + sorted_devices.sort((a, b) => growthValues[b] - growthValues[a]); + + const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = []; + const datasetExtras: ChartDatasetExtra[] = []; + + datasets.push( + ...this._processDataSet( + data, + energyData.statsMetadata, + energyData.prefs.device_consumption, + sorted_devices + ) + ); + + const items = datasets.length; + datasetExtras.push(...Array(items).fill({})); + + if (compareData) { + // Add empty dataset to align the bars + datasets.push({ + order: 0, + data: [], + }); + datasetExtras.push({ + show_legend: false, + }); + datasets.push({ + order: 999, + data: [], + xAxisID: "xAxisCompare", + }); + datasetExtras.push({ + show_legend: false, + }); + + datasets.push( + ...this._processDataSet( + compareData, + energyData.statsMetadata, + energyData.prefs.device_consumption, + sorted_devices, + true + ) + ); + datasetExtras.push( + ...Array(items).fill({ show_legend: false }) + ); + } + + this._start = energyData.start; + this._end = energyData.end || endOfToday(); + + this._compareStart = energyData.startCompare; + this._compareEnd = energyData.endCompare; + + this._chartData = { + datasets, + }; + this._chartDatasetExtra = datasetExtras; + } + + private _processDataSet( + statistics: Statistics, + statisticsMetaData: Record, + devices: DeviceConsumptionEnergyPreference[], + sorted_devices: string[], + compare = false + ) { + const data: ChartDataset<"bar", ScatterDataPoint[]>[] = []; + + devices.forEach((source, idx) => { + const color = getColorByIndex(idx); + + let prevStart: number | null = null; + + const consumptionData: ScatterDataPoint[] = []; + + // Process gas consumption data. + if (source.stat_consumption in statistics) { + const stats = statistics[source.stat_consumption]; + let end; + + for (const point of stats) { + if (point.change === null || point.change === undefined) { + continue; + } + if (prevStart === point.start) { + continue; + } + const date = new Date(point.start); + consumptionData.push({ + x: date.getTime(), + y: point.change, + }); + prevStart = point.start; + end = point.end; + } + if (consumptionData.length === 1) { + consumptionData.push({ + x: end, + y: 0, + }); + } + } + + data.push({ + label: getStatisticLabel( + this.hass, + source.stat_consumption, + statisticsMetaData[source.stat_consumption] + ), + hidden: this._hiddenStats.has(source.stat_consumption), + borderColor: compare ? color + "7F" : color, + backgroundColor: compare ? color + "32" : color + "7F", + data: consumptionData, + order: 1 + sorted_devices.indexOf(source.stat_consumption), + stack: "devices", + pointStyle: compare ? false : "circle", + xAxisID: compare ? "xAxisCompare" : undefined, + }); + }); + return data; + } + + static get styles(): CSSResultGroup { + return css` + .card-header { + padding-bottom: 0; + } + .content { + padding: 16px; + } + .has-header { + padding-top: 0; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-energy-devices-detail-graph-card": HuiEnergyDevicesDetailGraphCard; + } +} diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts index 64a6b5eb84..19a69db447 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts @@ -6,7 +6,6 @@ import { ScatterDataPoint, } from "chart.js"; import { getRelativePosition } from "chart.js/helpers"; -import { differenceInDays } from "date-fns/esm"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -31,10 +30,7 @@ import "../../../../components/ha-card"; import { EnergyData, getEnergyDataCollection } from "../../../../data/energy"; import { calculateStatisticSumGrowth, - fetchStatistics, getStatisticLabel, - Statistics, - StatisticsUnitConfiguration, } from "../../../../data/recorder"; import { FrontendLocaleData } from "../../../../data/translation"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; @@ -186,47 +182,8 @@ export class HuiEnergyDevicesGraphCard ); private async _getStatistics(energyData: EnergyData): Promise { - const dayDifference = differenceInDays( - energyData.end || new Date(), - energyData.start - ); - - const devices = energyData.prefs.device_consumption.map( - (device) => device.stat_consumption - ); - - const period = - dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"; - - const lengthUnit = this.hass.config.unit_system.length || ""; - const units: StatisticsUnitConfiguration = { - energy: "kWh", - volume: lengthUnit === "km" ? "m³" : "ft³", - }; - - const data = await fetchStatistics( - this.hass, - energyData.start, - energyData.end, - devices, - period, - units, - ["change"] - ); - - let compareData: Statistics | undefined; - - if (energyData.startCompare && energyData.endCompare) { - compareData = await fetchStatistics( - this.hass, - energyData.startCompare, - energyData.endCompare, - devices, - period, - units, - ["change"] - ); - } + const data = energyData.stats; + const compareData = energyData.statsCompare; const chartData: Array>["data"]> = []; diff --git a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts index e55383015b..baf3b2a7a7 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts @@ -17,13 +17,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; -import { - hex2rgb, - lab2rgb, - rgb2hex, - rgb2lab, -} from "../../../../common/color/convert-color"; -import { labBrighten, labDarken } from "../../../../common/color/lab"; +import { getEnergyColor } from "./common/color"; import { formatNumber } from "../../../../common/number/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; @@ -204,16 +198,12 @@ export class HuiEnergyGasGraphCard const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = []; const computedStyles = getComputedStyle(this); - const gasColor = computedStyles - .getPropertyValue("--energy-gas-color") - .trim(); datasets.push( ...this._processDataSet( energyData.stats, energyData.statsMetadata, gasSources, - gasColor, computedStyles ) ); @@ -235,7 +225,6 @@ export class HuiEnergyGasGraphCard energyData.statsCompare, energyData.statsMetadata, gasSources, - gasColor, computedStyles, true ) @@ -257,28 +246,12 @@ export class HuiEnergyGasGraphCard statistics: Statistics, statisticsMetaData: Record, gasSources: GasSourceTypeEnergyPreference[], - gasColor: string, computedStyles: CSSStyleDeclaration, compare = false ) { const data: ChartDataset<"bar", ScatterDataPoint[]>[] = []; gasSources.forEach((source, idx) => { - let borderColor = computedStyles - .getPropertyValue("--energy-gas-color-" + idx) - .trim(); - if (borderColor.length === 0) { - const modifiedColor = - idx > 0 - ? this.hass.themes.darkMode - ? labBrighten(rgb2lab(hex2rgb(gasColor)), idx) - : labDarken(rgb2lab(hex2rgb(gasColor)), idx) - : undefined; - borderColor = modifiedColor - ? rgb2hex(lab2rgb(modifiedColor)) - : gasColor; - } - let prevStart: number | null = null; const gasConsumptionData: ScatterDataPoint[] = []; @@ -317,8 +290,22 @@ export class HuiEnergyGasGraphCard source.stat_energy_from, statisticsMetaData[source.stat_energy_from] ), - borderColor: compare ? borderColor + "7F" : borderColor, - backgroundColor: compare ? borderColor + "32" : borderColor + "7F", + borderColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + false, + compare, + "--energy-gas-color", + idx + ), + backgroundColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + true, + compare, + "--energy-gas-color", + idx + ), data: gasConsumptionData, order: 1, stack: "gas", diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index d5f3e767d0..40d91584dc 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -22,13 +22,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; -import { - hex2rgb, - lab2rgb, - rgb2hex, - rgb2lab, -} from "../../../../common/color/convert-color"; -import { labBrighten, labDarken } from "../../../../common/color/lab"; +import { getEnergyColor } from "./common/color"; import { formatNumber } from "../../../../common/number/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; @@ -226,16 +220,12 @@ export class HuiEnergySolarGraphCard const datasets: ChartDataset<"bar" | "line">[] = []; const computedStyles = getComputedStyle(this); - const solarColor = computedStyles - .getPropertyValue("--energy-solar-color") - .trim(); datasets.push( ...this._processDataSet( energyData.stats, energyData.statsMetadata, solarSources, - solarColor, computedStyles ) ); @@ -257,7 +247,6 @@ export class HuiEnergySolarGraphCard energyData.statsCompare, energyData.statsMetadata, solarSources, - solarColor, computedStyles, true ) @@ -292,28 +281,12 @@ export class HuiEnergySolarGraphCard statistics: Statistics, statisticsMetaData: Record, solarSources: SolarSourceTypeEnergyPreference[], - solarColor: string, computedStyles: CSSStyleDeclaration, compare = false ) { const data: ChartDataset<"bar", ScatterDataPoint[]>[] = []; solarSources.forEach((source, idx) => { - let borderColor = computedStyles - .getPropertyValue("--energy-solar-color-" + idx) - .trim(); - if (borderColor.length === 0) { - const modifiedColor = - idx > 0 - ? this.hass.themes.darkMode - ? labBrighten(rgb2lab(hex2rgb(solarColor)), idx) - : labDarken(rgb2lab(hex2rgb(solarColor)), idx) - : undefined; - borderColor = modifiedColor - ? rgb2hex(lab2rgb(modifiedColor)) - : solarColor; - } - let prevStart: number | null = null; const solarProductionData: ScatterDataPoint[] = []; @@ -357,8 +330,22 @@ export class HuiEnergySolarGraphCard ), } ), - borderColor: compare ? borderColor + "7F" : borderColor, - backgroundColor: compare ? borderColor + "32" : borderColor + "7F", + borderColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + false, + compare, + "--energy-solar-color", + idx + ), + backgroundColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + true, + compare, + "--energy-solar-color", + idx + ), data: solarProductionData, order: 1, stack: "solar", diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts index 2be70f203b..4373c30bd1 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -12,14 +12,8 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; -import { - hex2rgb, - lab2rgb, - rgb2hex, - rgb2lab, -} from "../../../../common/color/convert-color"; -import { labBrighten, labDarken } from "../../../../common/color/lab"; import { formatNumber } from "../../../../common/number/format_number"; +import { getEnergyColor } from "./common/color"; import "../../../../components/ha-card"; import { EnergyData, @@ -38,6 +32,16 @@ import { LovelaceCard } from "../../types"; import { EnergySourcesTableCardConfig } from "../types"; import { hasConfigChanged } from "../../common/has-changed"; +const colorPropertyMap = { + grid_return: "--energy-grid-return-color", + grid_consumption: "--energy-grid-consumption-color", + battery_in: "--energy-battery-in-color", + battery_out: "--energy-battery-out-color", + solar: "--energy-solar-color", + gas: "--energy-gas-color", + water: "--energy-water-color", +}; + @customElement("hui-energy-sources-table-card") export class HuiEnergySourcesTableCard extends SubscribeMixin(LitElement) @@ -77,27 +81,6 @@ export class HuiEnergySourcesTableCard ); } - private _getColor( - computedStyles: CSSStyleDeclaration, - propertyName: string, - baseColor: string, - idx: number - ): string { - let color = computedStyles - .getPropertyValue(propertyName + "-" + idx) - .trim(); - if (color.length === 0) { - const modifiedColor = - idx > 0 - ? this.hass.themes.darkMode - ? labBrighten(rgb2lab(hex2rgb(baseColor)), idx) - : labDarken(rgb2lab(hex2rgb(baseColor)), idx) - : undefined; - color = modifiedColor ? rgb2hex(lab2rgb(modifiedColor)) : baseColor; - } - return color; - } - protected render() { if (!this.hass || !this._config) { return nothing; @@ -133,38 +116,7 @@ export class HuiEnergySourcesTableCard const types = energySourcesByType(this._data.prefs); - const colorPropertyMap = { - grid_return: "--energy-grid-return-color", - grid_consumption: "--energy-grid-consumption-color", - battery_in: "--energy-battery-in-color", - battery_out: "--energy-battery-out-color", - solar: "--energy-solar-color", - gas: "--energy-gas-color", - water: "--energy-water-color", - }; - const computedStyles = getComputedStyle(this); - const solarColor = computedStyles - .getPropertyValue(colorPropertyMap.solar) - .trim(); - const batteryFromColor = computedStyles - .getPropertyValue(colorPropertyMap.battery_out) - .trim(); - const batteryToColor = computedStyles - .getPropertyValue(colorPropertyMap.battery_in) - .trim(); - const returnColor = computedStyles - .getPropertyValue(colorPropertyMap.grid_return) - .trim(); - const consumptionColor = computedStyles - .getPropertyValue(colorPropertyMap.grid_consumption) - .trim(); - const gasColor = computedStyles - .getPropertyValue(colorPropertyMap.gas) - .trim(); - const waterColor = computedStyles - .getPropertyValue(colorPropertyMap.water) - .trim(); const showCosts = types.grid?.[0].flow_from.some( @@ -273,20 +225,27 @@ export class HuiEnergySourcesTableCard 0; totalSolarCompare += compareEnergy; - const color = this._getColor( - computedStyles, - colorPropertyMap.solar, - solarColor, - idx - ); - return html`
@@ -371,26 +330,27 @@ export class HuiEnergySourcesTableCard 0; totalBatteryCompare += energyFromCompare - energyToCompare; - const fromColor = this._getColor( - computedStyles, - colorPropertyMap.battery_out, - batteryFromColor, - idx - ); - const toColor = this._getColor( - computedStyles, - colorPropertyMap.battery_in, - batteryToColor, - idx - ); - return html`
@@ -426,8 +386,22 @@ export class HuiEnergySourcesTableCard
@@ -534,20 +508,27 @@ export class HuiEnergySourcesTableCard totalGridCostCompare += costCompare; } - const color = this._getColor( - computedStyles, - colorPropertyMap.grid_consumption, - consumptionColor, - idx - ); - return html`
@@ -638,20 +619,27 @@ export class HuiEnergySourcesTableCard totalGridCostCompare += costCompare; } - const color = this._getColor( - computedStyles, - colorPropertyMap.grid_return, - returnColor, - idx - ); - return html`
@@ -794,20 +782,27 @@ export class HuiEnergySourcesTableCard totalGasCostCompare += costCompare; } - const color = this._getColor( - computedStyles, - colorPropertyMap.gas, - gasColor, - idx - ); - return html`
@@ -945,20 +940,27 @@ export class HuiEnergySourcesTableCard totalWaterCostCompare += costCompare; } - const color = this._getColor( - computedStyles, - colorPropertyMap.water, - waterColor, - idx - ); - return html`
diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index 5b28a4195b..7b7c45db80 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -17,13 +17,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; -import { - hex2rgb, - lab2rgb, - rgb2hex, - rgb2lab, -} from "../../../../common/color/convert-color"; -import { labBrighten, labDarken } from "../../../../common/color/lab"; +import { getEnergyColor } from "./common/color"; import { formatNumber } from "../../../../common/number/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; @@ -41,10 +35,14 @@ import { EnergyUsageGraphCardConfig } from "../types"; import { hasConfigChanged } from "../../common/has-changed"; import { getCommonOptions } from "./common/energy-chart-options"; -interface ColorSet { - base: string; - overrides?: Record; -} +const colorPropertyMap = { + to_grid: "--energy-grid-return-color", + to_battery: "--energy-battery-in-color", + from_grid: "--energy-grid-consumption-color", + used_grid: "--energy-grid-consumption-color", + used_solar: "--energy-solar-color", + used_battery: "--energy-battery-out-color", +}; @customElement("hui-energy-usage-graph-card") export class HuiEnergyUsageGraphCard @@ -263,47 +261,9 @@ export class HuiEnergyUsageGraphCard const computedStyles = getComputedStyle(this); - const colorPropertyMap = { - to_grid: "--energy-grid-return-color", - to_battery: "--energy-battery-in-color", - from_grid: "--energy-grid-consumption-color", - used_grid: "--energy-grid-consumption-color", - used_solar: "--energy-solar-color", - used_battery: "--energy-battery-out-color", - }; - - const colors = { - to_grid: { - base: computedStyles.getPropertyValue(colorPropertyMap.to_grid).trim(), - }, - to_battery: { - base: computedStyles - .getPropertyValue(colorPropertyMap.to_battery) - .trim(), - }, - from_grid: { - base: computedStyles - .getPropertyValue(colorPropertyMap.from_grid) - .trim(), - }, - used_grid: { - base: computedStyles - .getPropertyValue(colorPropertyMap.used_grid) - .trim(), - }, - used_solar: { - base: computedStyles - .getPropertyValue(colorPropertyMap.used_solar) - .trim(), - }, - used_battery: { - base: computedStyles - .getPropertyValue(colorPropertyMap.used_battery) - .trim(), - }, - }; - - Object.entries(colorPropertyMap).forEach(([key, colorProp]) => { + const colorIndices: Record> = {}; + Object.keys(colorPropertyMap).forEach((key) => { + colorIndices[key] = {}; if ( key === "used_grid" || key === "used_solar" || @@ -311,15 +271,9 @@ export class HuiEnergyUsageGraphCard ) { return; } - colors[key].overrides = []; if (statIds[key]) { Object.values(statIds[key]).forEach((id, idx) => { - const override = computedStyles - .getPropertyValue(colorProp + "-" + idx) - .trim(); - if (override.length > 0) { - colors[key].overrides[id] = override; - } + colorIndices[key][id as string] = idx; }); } }); @@ -347,7 +301,8 @@ export class HuiEnergyUsageGraphCard energyData.stats, energyData.statsMetadata, statIds, - colors, + colorIndices, + computedStyles, labels, false ) @@ -370,7 +325,8 @@ export class HuiEnergyUsageGraphCard energyData.statsCompare, energyData.statsMetadata, statIds, - colors, + colorIndices, + computedStyles, labels, true ) @@ -392,14 +348,8 @@ export class HuiEnergyUsageGraphCard to_battery?: string[] | undefined; from_battery?: string[] | undefined; }, - colors: { - to_grid: ColorSet; - to_battery: ColorSet; - from_grid: ColorSet; - used_grid: ColorSet; - used_solar: ColorSet; - used_battery: ColorSet; - }, + colorIndices: Record>, + computedStyles: CSSStyleDeclaration, labels: { used_grid: string; used_solar: string; @@ -553,19 +503,6 @@ export class HuiEnergyUsageGraphCard Object.entries(combinedData).forEach(([type, sources]) => { Object.entries(sources).forEach(([statId, source], idx) => { - let borderColor = colors[type].overrides?.[statId]; - if (!borderColor) { - const modifiedColor = - idx > 0 - ? this.hass.themes.darkMode - ? labBrighten(rgb2lab(hex2rgb(colors[type].base)), idx) - : labDarken(rgb2lab(hex2rgb(colors[type].base)), idx) - : undefined; - borderColor = modifiedColor - ? rgb2hex(lab2rgb(modifiedColor)) - : colors[type].base; - } - const points: ScatterDataPoint[] = []; // Process chart data. for (const key of uniqueKeys) { @@ -600,8 +537,22 @@ export class HuiEnergyUsageGraphCard : type === "to_battery" ? Object.keys(combinedData).length : idx + 2, - borderColor: compare ? borderColor + "7F" : borderColor, - backgroundColor: compare ? borderColor + "32" : borderColor + "7F", + borderColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + false, + compare, + colorPropertyMap[type], + colorIndices[type]?.[statId] + ), + backgroundColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + true, + compare, + colorPropertyMap[type], + colorIndices[type]?.[statId] + ), stack: "stack", data: points, xAxisID: compare ? "xAxisCompare" : undefined, diff --git a/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts index e05d6fb665..d5011fc9cb 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts @@ -17,13 +17,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; -import { - hex2rgb, - lab2rgb, - rgb2hex, - rgb2lab, -} from "../../../../common/color/convert-color"; -import { labBrighten, labDarken } from "../../../../common/color/lab"; +import { getEnergyColor } from "./common/color"; import { formatNumber } from "../../../../common/number/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; @@ -202,16 +196,12 @@ export class HuiEnergyWaterGraphCard const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = []; const computedStyles = getComputedStyle(this); - const waterColor = computedStyles - .getPropertyValue("--energy-water-color") - .trim(); datasets.push( ...this._processDataSet( energyData.stats, energyData.statsMetadata, waterSources, - waterColor, computedStyles ) ); @@ -233,7 +223,6 @@ export class HuiEnergyWaterGraphCard energyData.statsCompare, energyData.statsMetadata, waterSources, - waterColor, computedStyles, true ) @@ -255,28 +244,12 @@ export class HuiEnergyWaterGraphCard statistics: Statistics, statisticsMetaData: Record, waterSources: WaterSourceTypeEnergyPreference[], - waterColor: string, computedStyles: CSSStyleDeclaration, compare = false ) { const data: ChartDataset<"bar", ScatterDataPoint[]>[] = []; waterSources.forEach((source, idx) => { - let borderColor = computedStyles - .getPropertyValue("--energy-water-color-" + idx) - .trim(); - if (borderColor.length === 0) { - const modifiedColor = - idx > 0 - ? this.hass.themes.darkMode - ? labBrighten(rgb2lab(hex2rgb(waterColor)), idx) - : labDarken(rgb2lab(hex2rgb(waterColor)), idx) - : undefined; - borderColor = modifiedColor - ? rgb2hex(lab2rgb(modifiedColor)) - : waterColor; - } - let prevStart: number | null = null; const waterConsumptionData: ScatterDataPoint[] = []; @@ -315,8 +288,22 @@ export class HuiEnergyWaterGraphCard source.stat_energy_from, statisticsMetaData[source.stat_energy_from] ), - borderColor: compare ? borderColor + "7F" : borderColor, - backgroundColor: compare ? borderColor + "32" : borderColor + "7F", + borderColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + false, + compare, + "--energy-water-color", + idx + ), + backgroundColor: getEnergyColor( + computedStyles, + this.hass.themes.darkMode, + true, + compare, + "--energy-water-color", + idx + ), data: waterConsumptionData, order: 1, stack: "water", diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index d738a1be6c..3a37329b27 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -134,11 +134,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { private getStateColor(stateObj: HassEntity, config: ButtonCardConfig) { const domain = stateObj ? computeStateDomain(stateObj) : undefined; - return ( - config && - (config.state_color || - (domain === "light" && config.state_color !== false)) - ); + return config && (config.state_color ?? domain === "light"); } public getCardSize(): number { @@ -147,6 +143,16 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { ); } + public getGridSize(): [number, number] { + if ( + this._config?.show_icon && + (this._config?.show_name || this._config?.show_state) + ) { + return [2, 2]; + } + return [1, 1]; + } + public setConfig(config: ButtonCardConfig): void { if (config.entity && !isValidEntityId(config.entity)) { throw new Error("Invalid entity"); diff --git a/src/panels/lovelace/cards/hui-calendar-card.ts b/src/panels/lovelace/cards/hui-calendar-card.ts index 047118828a..1a31a6d1cf 100644 --- a/src/panels/lovelace/cards/hui-calendar-card.ts +++ b/src/panels/lovelace/cards/hui-calendar-card.ts @@ -69,8 +69,6 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { @state() private _narrow = false; - @state() private _veryNarrow = false; - @state() private _error?: string = undefined; private _startDate?: Date; @@ -121,9 +119,11 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { return nothing; } - const views: FullCalendarView[] = this._veryNarrow - ? ["listWeek"] - : ["dayGridMonth", "dayGridDay", "listWeek"]; + const views: FullCalendarView[] = [ + "dayGridMonth", + "dayGridDay", + "listWeek", + ]; return html` @@ -206,7 +206,6 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { return; } this._narrow = card.offsetWidth < 870; - this._veryNarrow = card.offsetWidth < 350; } private async _attachObserver(): Promise { diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index 3d5b46449e..4fb12c4661 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -297,9 +297,9 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { private renderEntity(entityConf: LovelaceRowConfig): TemplateResult { const element = createRowElement( (!("type" in entityConf) || entityConf.type === "conditional") && - this._config!.state_color + "state_color" in this._config! ? ({ - state_color: true, + state_color: this._config.state_color, ...(entityConf as EntityConfig), } as EntityConfig) : entityConf diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index fd1b63e13e..e6366cd28b 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -75,11 +75,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { private getStateColor(stateObj: HassEntity, config: EntityCardConfig) { const domain = stateObj ? computeStateDomain(stateObj) : undefined; - return ( - config && - (config.state_color || - (domain === "light" && config.state_color !== false)) - ); + return config && (config.state_color ?? domain === "light"); } public setConfig(config: EntityCardConfig): void { diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index d48fb68cfe..8217da7501 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -89,12 +89,12 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { state_color: true, ...config, }; - const entities = processConfigEntities( - config.entities - ).map((entityConf) => ({ - hold_action: { action: "more-info" } as MoreInfoActionConfig, - ...entityConf, - })); + const entities = processConfigEntities(config.entities).map( + (entityConf) => ({ + hold_action: { action: "more-info" } as MoreInfoActionConfig, + ...entityConf, + }) + ); for (const entity of entities) { if ( @@ -237,7 +237,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { `; } - private renderEntity(entityConf): TemplateResult { + private renderEntity(entityConf: GlanceConfigEntity): TemplateResult { const stateObj = this.hass!.states[entityConf.entity]; if (!stateObj) { @@ -294,8 +294,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { .stateObj=${stateObj} .overrideIcon=${entityConf.icon} .overrideImage=${entityConf.image} - .stateColor=${(entityConf.state_color === false || - entityConf.state_color) ?? + .stateColor=${entityConf.state_color ?? this._config!.state_color} > ` diff --git a/src/panels/lovelace/cards/hui-history-graph-card.ts b/src/panels/lovelace/cards/hui-history-graph-card.ts index 1229ff7190..d8af4508b7 100644 --- a/src/panels/lovelace/cards/hui-history-graph-card.ts +++ b/src/panels/lovelace/cards/hui-history-graph-card.ts @@ -120,6 +120,7 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { this._stateHistory = computeHistory( this.hass!, combinedHistory, + this._entityIds, this.hass!.localize, sensorNumericDeviceClasses, this._config?.split_device_classes diff --git a/src/panels/lovelace/cards/hui-horizontal-stack-card.ts b/src/panels/lovelace/cards/hui-horizontal-stack-card.ts index 83a1c9ae08..502e54db4d 100644 --- a/src/panels/lovelace/cards/hui-horizontal-stack-card.ts +++ b/src/panels/lovelace/cards/hui-horizontal-stack-card.ts @@ -28,27 +28,12 @@ export class HuiHorizontalStackCard extends HuiStackCard { #root { display: flex; height: 100%; + gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px)); } #root > * { flex: 1 1 0; - margin: var( - --horizontal-stack-card-margin, - var(--stack-card-margin, 0 4px) - ); min-width: 0; } - #root[dir="ltr"] > *:first-child { - margin-left: 0; - } - #root[dir="ltr"] > *:last-child { - margin-right: 0; - } - #root[dir="rtl"] > *:first-child { - margin-right: 0; - } - #root[dir="rtl"] > *:last-child { - margin-left: 0; - } `, ]; } diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index 7d5a1d8cc3..d28d0f612f 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -726,6 +726,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { position: absolute; right: 4px; --mdc-icon-size: 24px; + inset-inline-end: 4px; + inset-inline-start: initial; } .top-info { diff --git a/src/panels/lovelace/cards/hui-sensor-card.ts b/src/panels/lovelace/cards/hui-sensor-card.ts index ee7937e544..709a6591d9 100644 --- a/src/panels/lovelace/cards/hui-sensor-card.ts +++ b/src/panels/lovelace/cards/hui-sensor-card.ts @@ -72,6 +72,10 @@ class HuiSensorCard extends HuiEntityCard { super.setConfig(entityCardConfig); } + public getGridSize(): [number, number] { + return [2, 2]; + } + static get styles(): CSSResultGroup { return [ HuiEntityCard.styles, diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index b1c279091a..1d56cc66bd 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -117,7 +117,23 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } public getCardSize(): number { - return 1; + return ( + 1 + + (this._config?.vertical ? 1 : 0) + + (this._config?.features?.length || 0) + ); + } + + public getGridSize(): [number, number] { + const width = 2; + let height = 1; + if (this._config?.features?.length) { + height += Math.ceil((this._config.features.length * 2) / 3); + } + if (this._config?.vertical) { + height++; + } + return [width, height]; } private _handleAction(ev: ActionHandlerEvent) { @@ -437,12 +453,16 @@ export class HuiTileCard extends LitElement implements LovelaceCard { .secondary=${localizedState} >
- + ${this._config.features + ? html` + + ` + : nothing} `; } @@ -465,6 +485,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard { transition: box-shadow 180ms ease-in-out, border-color 180ms ease-in-out; + display: flex; + flex-direction: column; + justify-content: space-between; } ha-card.active { --tile-color: var(--state-icon-color); diff --git a/src/panels/lovelace/cards/hui-todo-list-card.ts b/src/panels/lovelace/cards/hui-todo-list-card.ts index 5094ea3d73..bed42344a7 100644 --- a/src/panels/lovelace/cards/hui-todo-list-card.ts +++ b/src/panels/lovelace/cards/hui-todo-list-card.ts @@ -732,6 +732,8 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard { .deleteItemButton { position: relative; left: 8px; + inset-inline-start: 8px; + inset-inline-end: initial; } ha-textfield { diff --git a/src/panels/lovelace/cards/hui-vertical-stack-card.ts b/src/panels/lovelace/cards/hui-vertical-stack-card.ts index e4915feea8..f03b4524d4 100644 --- a/src/panels/lovelace/cards/hui-vertical-stack-card.ts +++ b/src/panels/lovelace/cards/hui-vertical-stack-card.ts @@ -27,18 +27,7 @@ class HuiVerticalStackCard extends HuiStackCard { display: flex; flex-direction: column; height: 100%; - } - #root > * { - margin: var( - --vertical-stack-card-margin, - var(--stack-card-margin, 4px 0) - ); - } - #root > *:first-child { - margin-top: 0; - } - #root > *:last-child { - margin-bottom: 0; + gap: var(--vertical-stack-card-gap, var(--stack-card-gap, 8px)); } `, ]; diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index d9a26be158..9c353c02b6 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -4,8 +4,10 @@ import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { Statistic, StatisticType } from "../../../data/recorder"; import { ForecastType } from "../../../data/weather"; import { FullCalendarView, TranslationDict } from "../../../types"; +import { LovelaceCardFeatureConfig } from "../card-features/types"; import { Condition, LegacyCondition } from "../common/validate-condition"; import { HuiImage } from "../components/hui-image"; +import { TimestampRenderingFormat } from "../components/types"; import { LovelaceElementConfig } from "../elements/types"; import { EntityConfig, @@ -13,7 +15,6 @@ import { LovelaceRowConfig, } from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; -import { LovelaceCardFeatureConfig } from "../card-features/types"; export type AlarmPanelCardConfigState = | "arm_away" @@ -159,6 +160,13 @@ export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig { max_devices?: number; } +export interface EnergyDevicesDetailGraphCardConfig extends LovelaceCardConfig { + type: "energy-devices-detail-graph"; + title?: string; + collection_key?: string; + max_devices?: number; +} + export interface EnergySourcesTableCardConfig extends LovelaceCardConfig { type: "energy-sources-table"; title?: string; @@ -245,6 +253,7 @@ export interface GlanceConfigEntity extends ConfigEntity { image?: string; show_state?: boolean; state_color?: boolean; + format: TimestampRenderingFormat; } export interface GlanceCardConfig extends LovelaceCardConfig { @@ -253,7 +262,7 @@ export interface GlanceCardConfig extends LovelaceCardConfig { show_icon?: boolean; title?: string; theme?: string; - entities: Array; + entities: (string | GlanceConfigEntity)[]; columns?: number; state_color?: boolean; } @@ -538,7 +547,7 @@ export interface TileCardConfig extends LovelaceCardConfig { state_content?: string | string[]; icon?: string; color?: string; - show_entity_picture?: string; + show_entity_picture?: boolean; vertical?: boolean; tap_action?: ActionConfig; hold_action?: ActionConfig; diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 2b5437a93d..c609f66dbd 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -8,12 +8,14 @@ import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_f import { stringCompare } from "../../../common/string/compare"; import { LocalizeFunc } from "../../../common/translations/localize"; import type { AreaFilterValue } from "../../../components/ha-area-filter"; +import { areaCompare } from "../../../data/area_registry"; import { EnergyPreferences, GridSourceTypeEnergyPreference, } from "../../../data/energy"; import { domainToName } from "../../../data/integration"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { computeUserInitials } from "../../../data/user"; import { HomeAssistant } from "../../../types"; @@ -25,10 +27,10 @@ import { PictureCardConfig, PictureEntityCardConfig, ThermostatCardConfig, + TileCardConfig, } from "../cards/types"; import { EntityConfig } from "../entity-rows/types"; import { ButtonsHeaderFooterConfig } from "../header-footer/types"; -import { areaCompare } from "../../../data/area_registry"; const HIDE_DOMAIN = new Set([ "automation", @@ -100,6 +102,24 @@ const splitByAreaDevice = ( }; }; +export const computeSection = ( + entityIds: string[], + sectionOptions?: Partial +): LovelaceSectionConfig => ({ + type: "grid", + cards: entityIds.map( + (entity) => + ({ + type: "tile", + entity, + show_entity_picture: ["person", "camera", "image"].includes( + computeDomain(entity) + ), + }) as TileCardConfig + ), + ...sectionOptions, +}); + export const computeCards = ( states: HassEntities, entityIds: string[], diff --git a/src/panels/lovelace/components/hui-card-edit-mode.ts b/src/panels/lovelace/components/hui-card-edit-mode.ts new file mode 100644 index 0000000000..bae22490ba --- /dev/null +++ b/src/panels/lovelace/components/hui-card-edit-mode.ts @@ -0,0 +1,303 @@ +import "@material/mwc-button"; +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import { + mdiContentCopy, + mdiContentCut, + mdiContentDuplicate, + mdiDelete, + mdiDotsVertical, + mdiPencil, +} from "@mdi/js"; +import deepClone from "deep-clone-simple"; +import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { storage } from "../../../common/decorators/storage"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-button-menu"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-list-item"; +import "../../../components/ha-svg-icon"; +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; +import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; +import { + LovelaceCardPath, + findLovelaceCards, + getLovelaceContainerPath, + parseLovelaceCardPath, +} from "../editor/lovelace-path"; +import { Lovelace } from "../types"; + +@customElement("hui-card-edit-mode") +export class HuiCardEditMode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace!: Lovelace; + + @property({ type: Array }) public path!: LovelaceCardPath; + + @property({ type: Boolean }) public hiddenOverlay = false; + + @state() + public _menuOpened: boolean = false; + + @state() + public _hover: boolean = false; + + @state() + public _focused: boolean = false; + + @storage({ + key: "lovelaceClipboard", + state: false, + subscribe: false, + storage: "sessionStorage", + }) + protected _clipboard?: LovelaceCardConfig; + + private get _cards() { + const containerPath = getLovelaceContainerPath(this.path!); + return findLovelaceCards(this.lovelace!.config, containerPath)!; + } + + private _touchStarted = false; + + protected firstUpdated(): void { + this.addEventListener("focus", () => { + this._focused = true; + }); + this.addEventListener("blur", () => { + this._focused = false; + }); + this.addEventListener("touchstart", () => { + this._touchStarted = true; + }); + this.addEventListener("touchend", () => { + setTimeout(() => { + this._touchStarted = false; + }, 10); + }); + this.addEventListener("mouseenter", () => { + if (this._touchStarted) return; + this._hover = true; + }); + this.addEventListener("mouseout", () => { + this._hover = false; + }); + this.addEventListener("click", () => { + this._hover = true; + document.addEventListener("click", this._documentClicked); + }); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + document.removeEventListener("click", this._documentClicked); + } + + _documentClicked = (ev) => { + this._hover = ev.composedPath().includes(this); + document.removeEventListener("click", this._documentClicked); + }; + + protected render(): TemplateResult { + const showOverlay = + (this._hover || this._menuOpened || this._focused) && !this.hiddenOverlay; + + return html` +
+
+
+
+ +
+ + + + + + ${this.hass.localize( + "ui.panel.lovelace.editor.edit_card.duplicate" + )} + + + + ${this.hass.localize("ui.panel.lovelace.editor.edit_card.copy")} + + + + ${this.hass.localize("ui.panel.lovelace.editor.edit_card.cut")} + +
  • + + ${this.hass.localize("ui.panel.lovelace.editor.edit_card.delete")} + + +
    +
    + `; + } + + private _handleOpened() { + this._menuOpened = true; + } + + private _handleClosed() { + this._menuOpened = false; + } + + private _handleAction(ev: CustomEvent) { + switch (ev.detail.index) { + case 0: + this._duplicateCard(); + break; + case 1: + this._copyCard(); + break; + case 2: + this._cutCard(); + break; + case 3: + this._deleteCard(true); + break; + } + } + + private _duplicateCard(): void { + const { cardIndex } = parseLovelaceCardPath(this.path!); + const containerPath = getLovelaceContainerPath(this.path!); + const cardConfig = this._cards![cardIndex]; + showEditCardDialog(this, { + lovelaceConfig: this.lovelace!.config, + saveConfig: this.lovelace!.saveConfig, + path: containerPath, + cardConfig, + }); + } + + private _editCard(ev): void { + if (ev.defaultPrevented) { + return; + } + if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + fireEvent(this, "ll-edit-card", { path: this.path! }); + } + + private _cutCard(): void { + this._copyCard(); + this._deleteCard(false); + } + + private _copyCard(): void { + const { cardIndex } = parseLovelaceCardPath(this.path!); + const cardConfig = this._cards[cardIndex]; + this._clipboard = deepClone(cardConfig); + } + + private _deleteCard(confirm: boolean): void { + fireEvent(this, "ll-delete-card", { path: this.path!, confirm }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + .card-overlay { + position: absolute; + opacity: 0; + pointer-events: none; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 180ms ease-in-out; + } + + .card-overlay.visible { + opacity: 1; + pointer-events: auto; + } + + .card-wrapper { + position: relative; + height: 100%; + z-index: 0; + } + + .edit { + outline: none !important; + cursor: pointer; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--ha-card-border-radius, 12px); + z-index: 0; + } + .edit-overlay { + position: absolute; + inset: 0; + opacity: 0.8; + background-color: var(--primary-background-color); + border: 1px solid var(--divider-color); + border-radius: var(--ha-card-border-radius, 12px); + z-index: 0; + } + .edit ha-svg-icon { + display: flex; + position: relative; + color: var(--primary-text-color); + border-radius: 50%; + padding: 12px; + background: var(--secondary-background-color); + --mdc-icon-size: 24px; + } + .more { + position: absolute; + right: -6px; + top: -6px; + } + .more ha-icon-button { + cursor: pointer; + border-radius: 50%; + background: var(--secondary-background-color); + --mdc-icon-button-size: 32px; + --mdc-icon-size: 20px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-card-edit-mode": HuiCardEditMode; + } +} diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts index 7f59aaf3e9..ec444bb954 100644 --- a/src/panels/lovelace/components/hui-card-options.ts +++ b/src/panels/lovelace/components/hui-card-options.ts @@ -28,7 +28,6 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-list-item"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import { saveConfig } from "../../../data/lovelace/config/types"; -import { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { showAlertDialog, showPromptDialog, @@ -41,10 +40,15 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog" import { addCard, deleteCard, - moveCard, - moveCardToPosition, - swapCard, + moveCardToContainer, + moveCardToIndex, } from "../editor/config-util"; +import { + LovelaceCardPath, + findLovelaceCards, + getLovelaceContainerPath, + parseLovelaceCardPath, +} from "../editor/lovelace-path"; import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog"; import { Lovelace, LovelaceCard } from "../types"; @@ -54,7 +58,7 @@ export class HuiCardOptions extends LitElement { @property({ attribute: false }) public lovelace?: Lovelace; - @property({ type: Array }) public path?: [number, number]; + @property({ type: Array }) public path?: LovelaceCardPath; @queryAssignedNodes() private _assignedNodes?: NodeListOf; @@ -76,17 +80,21 @@ export class HuiCardOptions extends LitElement { if (!changedProps.has("path") || !this.path) { return; } + const { viewIndex } = parseLovelaceCardPath(this.path); this.classList.toggle( "panel", - this.lovelace!.config.views[this.path![0]].panel + this.lovelace!.config.views[viewIndex].panel ); } - private get _currentView() { - return this.lovelace!.config.views[this.path![0]] as LovelaceViewConfig; + private get _cards() { + const containerPath = getLovelaceContainerPath(this.path!); + return findLovelaceCards(this.lovelace!.config, containerPath)!; } protected render(): TemplateResult { + const { cardIndex } = parseLovelaceCardPath(this.path!); + return html`
    @@ -107,7 +115,7 @@ export class HuiCardOptions extends LitElement { .path=${mdiMinus} class="move-arrow" @click=${this._decreaseCardPosiion} - ?disabled=${this.path![1] === 0} + ?disabled=${cardIndex === 0} > -
    ${this.path![1] + 1}
    +
    ${cardIndex + 1}
    ` : nothing} @@ -271,13 +278,14 @@ export class HuiCardOptions extends LitElement { } private _duplicateCard(): void { - const path = this.path!; - const cardConfig = this._currentView.cards![path[1]]; + const { cardIndex } = parseLovelaceCardPath(this.path!); + const containerPath = getLovelaceContainerPath(this.path!); + const cardConfig = this._cards![cardIndex]; showEditCardDialog(this, { lovelaceConfig: this.lovelace!.config, - cardConfig, saveConfig: this.lovelace!.saveConfig, - path: [path[0]], + path: containerPath, + cardConfig, }); } @@ -291,30 +299,29 @@ export class HuiCardOptions extends LitElement { } private _copyCard(): void { - const cardConfig = this._currentView.cards![this.path![1]]; + const { cardIndex } = parseLovelaceCardPath(this.path!); + const cardConfig = this._cards[cardIndex]; this._clipboard = deepClone(cardConfig); } private _decreaseCardPosiion(): void { const lovelace = this.lovelace!; const path = this.path!; - lovelace.saveConfig( - swapCard(lovelace.config, path, [path[0], path[1] - 1]) - ); + const { cardIndex } = parseLovelaceCardPath(path); + lovelace.saveConfig(moveCardToIndex(lovelace.config, path, cardIndex - 1)); } private _increaseCardPosition(): void { const lovelace = this.lovelace!; const path = this.path!; - lovelace.saveConfig( - swapCard(lovelace.config, path, [path[0], path[1] + 1]) - ); + const { cardIndex } = parseLovelaceCardPath(path); + lovelace.saveConfig(moveCardToIndex(lovelace.config, path, cardIndex + 1)); } private async _changeCardPosition(): Promise { const lovelace = this.lovelace!; const path = this.path!; - + const { cardIndex } = parseLovelaceCardPath(path); const positionString = await showPromptDialog(this, { title: this.hass!.localize( "ui.panel.lovelace.editor.change_position.title" @@ -324,7 +331,7 @@ export class HuiCardOptions extends LitElement { ), inputType: "number", inputMin: "1", - placeholder: String(path[1] + 1), + placeholder: String(cardIndex + 1), }); if (!positionString) return; @@ -333,7 +340,8 @@ export class HuiCardOptions extends LitElement { if (isNaN(position)) return; - lovelace.saveConfig(moveCardToPosition(lovelace.config, path, position)); + const newIndex = position - 1; + lovelace.saveConfig(moveCardToIndex(lovelace.config, path, newIndex)); } private _moveCard(): void { @@ -345,20 +353,17 @@ export class HuiCardOptions extends LitElement { viewSelectedCallback: async (urlPath, selectedDashConfig, viewIndex) => { if (urlPath === this.lovelace!.urlPath) { this.lovelace!.saveConfig( - moveCard(this.lovelace!.config, this.path!, [viewIndex]) + moveCardToContainer(this.lovelace!.config, this.path!, [viewIndex]) ); showSaveSuccessToast(this, this.hass!); return; } try { + const { cardIndex } = parseLovelaceCardPath(this.path!); await saveConfig( this.hass!, urlPath, - addCard( - selectedDashConfig, - [viewIndex], - this._currentView.cards![this.path![1]] - ) + addCard(selectedDashConfig, [viewIndex], this._cards[cardIndex]) ); this.lovelace!.saveConfig( deleteCard(this.lovelace!.config, this.path!) diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index 1884c79cc1..4a9a02fd67 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -24,6 +24,7 @@ const ALWAYS_LOADED_TYPES = new Set([ "entity-button", "glance", "grid", + "section", "light", "sensor", "thermostat", @@ -44,6 +45,8 @@ const LAZY_LOAD_TYPES = { import("../cards/energy/hui-energy-date-selection-card"), "energy-devices-graph": () => import("../cards/energy/hui-energy-devices-graph-card"), + "energy-devices-detail-graph": () => + import("../cards/energy/hui-energy-devices-detail-graph-card"), "energy-distribution": () => import("../cards/energy/hui-energy-distribution-card"), "energy-gas-graph": () => import("../cards/energy/hui-energy-gas-graph-card"), diff --git a/src/panels/lovelace/create-element/create-card-feature-element.ts b/src/panels/lovelace/create-element/create-card-feature-element.ts index 5508221430..8aec1360e3 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -6,6 +6,7 @@ import "../card-features/hui-cover-open-close-card-feature"; import "../card-features/hui-cover-position-card-feature"; import "../card-features/hui-cover-tilt-card-feature"; import "../card-features/hui-cover-tilt-position-card-feature"; +import "../card-features/hui-fan-preset-modes-card-feature"; import "../card-features/hui-fan-speed-card-feature"; import "../card-features/hui-humidifier-modes-card-feature"; import "../card-features/hui-humidifier-toggle-card-feature"; @@ -16,9 +17,9 @@ import "../card-features/hui-numeric-input-card-feature"; import "../card-features/hui-select-options-card-feature"; import "../card-features/hui-target-temperature-card-feature"; import "../card-features/hui-target-humidity-card-feature"; +import "../card-features/hui-update-actions-card-feature"; import "../card-features/hui-vacuum-commands-card-feature"; import "../card-features/hui-water-heater-operation-modes-card-feature"; -import "../card-features/hui-update-actions-card-feature"; import { LovelaceCardFeatureConfig } from "../card-features/types"; import { @@ -35,6 +36,7 @@ const TYPES: Set = new Set([ "cover-position", "cover-tilt-position", "cover-tilt", + "fan-preset-modes", "fan-speed", "humidifier-modes", "humidifier-toggle", @@ -45,9 +47,9 @@ const TYPES: Set = new Set([ "select-options", "target-humidity", "target-temperature", + "update-actions", "vacuum-commands", "water-heater-operation-modes", - "update-actions", ]); export const createCardFeatureElement = (config: LovelaceCardFeatureConfig) => diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts index d6d4c8d53f..48bf11bef9 100644 --- a/src/panels/lovelace/create-element/create-element-base.ts +++ b/src/panels/lovelace/create-element/create-element-base.ts @@ -1,27 +1,31 @@ import { fireEvent } from "../../../common/dom/fire_event"; -import { LovelaceViewElement } from "../../../data/lovelace"; +import { + LovelaceSectionElement, + LovelaceViewElement, +} from "../../../data/lovelace"; import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { isCustomType, stripCustomPrefix, } from "../../../data/lovelace_custom_cards"; +import { LovelaceCardFeatureConfig } from "../card-features/types"; import type { HuiErrorCard } from "../cards/hui-error-card"; import type { ErrorCardConfig } from "../cards/types"; import { LovelaceElement, LovelaceElementConfig } from "../elements/types"; import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; -import { LovelaceCardFeatureConfig } from "../card-features/types"; import { LovelaceBadge, LovelaceCard, LovelaceCardConstructor, + LovelaceCardFeature, + LovelaceCardFeatureConstructor, LovelaceHeaderFooter, LovelaceHeaderFooterConstructor, LovelaceRowConstructor, - LovelaceCardFeature, - LovelaceCardFeatureConstructor, } from "../types"; const TIMEOUT = 2000; @@ -62,6 +66,11 @@ interface CreateElementConfigTypes { element: LovelaceCardFeature; constructor: LovelaceCardFeatureConstructor; }; + section: { + config: LovelaceSectionConfig; + element: LovelaceSectionElement; + constructor: unknown; + }; } export const createErrorCardElement = (config: ErrorCardConfig) => { diff --git a/src/panels/lovelace/create-element/create-section-element.ts b/src/panels/lovelace/create-element/create-section-element.ts new file mode 100644 index 0000000000..7b63d2742e --- /dev/null +++ b/src/panels/lovelace/create-element/create-section-element.ts @@ -0,0 +1,19 @@ +import { LovelaceSectionElement } from "../../../data/lovelace"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; +import { HuiErrorCard } from "../cards/hui-error-card"; +import "../sections/hui-grid-section"; +import { createLovelaceElement } from "./create-element-base"; + +const ALWAYS_LOADED_LAYOUTS = new Set(["grid"]); + +const LAZY_LOAD_LAYOUTS = {}; + +export const createSectionElement = ( + config: LovelaceSectionConfig +): LovelaceSectionElement | HuiErrorCard => + createLovelaceElement( + "section", + config, + ALWAYS_LOADED_LAYOUTS, + LAZY_LOAD_LAYOUTS + ); diff --git a/src/panels/lovelace/create-element/create-view-element.ts b/src/panels/lovelace/create-element/create-view-element.ts index 158cf2da31..65bd3e30b2 100644 --- a/src/panels/lovelace/create-element/create-view-element.ts +++ b/src/panels/lovelace/create-element/create-view-element.ts @@ -9,6 +9,7 @@ const ALWAYS_LOADED_LAYOUTS = new Set(["masonry"]); const LAZY_LOAD_LAYOUTS = { panel: () => import("../views/hui-panel-view"), sidebar: () => import("../views/hui-sidebar-view"), + sections: () => import("../views/hui-sections-view"), }; export const createViewElement = ( diff --git a/src/panels/lovelace/editor/add-entities-to-view.ts b/src/panels/lovelace/editor/add-entities-to-view.ts index 03b9a44ac2..8706287c03 100644 --- a/src/panels/lovelace/editor/add-entities-to-view.ts +++ b/src/panels/lovelace/editor/add-entities-to-view.ts @@ -1,5 +1,6 @@ import { LovelacePanelConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import { LovelaceConfig, fetchConfig, @@ -15,6 +16,7 @@ export const addEntitiesToLovelaceView = async ( element: HTMLElement, hass: HomeAssistant, cardConfig: LovelaceCardConfig[], + sectionConfig?: LovelaceSectionConfig, entities?: string[] ) => { hass.loadFragmentTranslation("lovelace"); @@ -71,6 +73,7 @@ export const addEntitiesToLovelaceView = async ( // all storage dashboards are generated, but we have YAML dashboards just show the YAML config showSuggestCardDialog(element, { cardConfig, + sectionConfig, entities, yaml: true, }); @@ -93,6 +96,7 @@ export const addEntitiesToLovelaceView = async ( if (!storageDashs.length && lovelaceConfig.views.length === 1) { showSuggestCardDialog(element, { cardConfig, + sectionConfig, lovelaceConfig: lovelaceConfig!, saveConfig: async (newConfig: LovelaceConfig): Promise => { try { @@ -116,6 +120,7 @@ export const addEntitiesToLovelaceView = async ( viewSelectedCallback: (newUrlPath, selectedDashConfig, viewIndex) => { showSuggestCardDialog(element, { cardConfig, + sectionConfig, lovelaceConfig: selectedDashConfig, saveConfig: async (newConfig: LovelaceConfig): Promise => { try { diff --git a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts index 382914e87f..2577822756 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts @@ -15,6 +15,7 @@ import { until } from "lit/directives/until"; import memoizeOne from "memoize-one"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stringCompare } from "../../../../common/string/compare"; import "../../../../components/ha-circular-progress"; import "../../../../components/search-input"; import { isUnavailableState } from "../../../../data/entity"; @@ -46,6 +47,8 @@ interface CardElement { export class HuiCardPicker extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; + @property({ attribute: false }) public suggestedCards?: string[]; + @storage({ key: "lovelaceClipboard", state: true, @@ -92,6 +95,29 @@ export class HuiCardPicker extends LitElement { } ); + private _suggestedCards = memoizeOne( + (cardElements: CardElement[]): CardElement[] => + cardElements.filter( + (cardElement: CardElement) => cardElement.card.isSuggested + ) + ); + + private _customCards = memoizeOne( + (cardElements: CardElement[]): CardElement[] => + cardElements.filter( + (cardElement: CardElement) => + cardElement.card.isCustom && !cardElement.card.isSuggested + ) + ); + + private _otherCards = memoizeOne( + (cardElements: CardElement[]): CardElement[] => + cardElements.filter( + (cardElement: CardElement) => + !cardElement.card.isSuggested && !cardElement.card.isCustom + ) + ); + protected render() { if ( !this.hass || @@ -102,6 +128,10 @@ export class HuiCardPicker extends LitElement { return nothing; } + const suggestedCards = this._suggestedCards(this._cards); + const othersCards = this._otherCards(this._cards); + const customCardsItems = this._customCards(this._cards); + return html`
    - ${this._clipboard && !this._filter - ? html` - ${until( - this._renderCardElement( - { - type: this._clipboard.type, - showElement: true, - isCustom: false, - name: this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.paste" - ), - description: `${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.paste_description", - { - type: this._clipboard.type, - } - )}`, - }, - this._clipboard - ), - html` -
    - -
    - ` + ${this._filter + ? this._filterCards(this._cards, this._filter).map( + (cardElement: CardElement) => cardElement.element + ) + : html` + ${suggestedCards.length > 0 + ? html` +
    + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.suggested_cards` + )} +
    + ` + : nothing} + ${this._renderClipboardCard()} + ${suggestedCards.map( + (cardElement: CardElement) => cardElement.element )} - ` - : nothing} - ${this._filterCards(this._cards, this._filter).map( - (cardElement: CardElement) => cardElement.element - )} + ${suggestedCards.length > 0 + ? html` +
    + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.other_cards` + )} +
    + ` + : nothing} + ${othersCards.map( + (cardElement: CardElement) => cardElement.element + )} + ${customCardsItems.length > 0 + ? html` +
    + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.custom_cards` + )} +
    + ` + : nothing} + ${customCardsItems.map( + (cardElement: CardElement) => cardElement.element + )} + `}
    { + if (a.isSuggested && !b.isSuggested) { + return -1; + } + if (!a.isSuggested && b.isSuggested) { + return 1; + } + return stringCompare( + a.name || a.type, + b.name || b.type, + this.hass?.language + ); + }); + if (customCards.length > 0) { cards = cards.concat( customCards.map((ccard: CustomCardEntry) => ({ @@ -244,6 +300,37 @@ export class HuiCardPicker extends LitElement { })); } + private _renderClipboardCard() { + if (!this._clipboard) { + return nothing; + } + + return html` ${until( + this._renderCardElement( + { + type: this._clipboard.type, + showElement: true, + isCustom: false, + name: this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.paste" + ), + description: `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.paste_description", + { + type: this._clipboard.type, + } + )}`, + }, + this._clipboard + ), + html` +
    + +
    + ` + )}`; + } + private _handleSearchChange(ev: CustomEvent) { const value = ev.detail.value; @@ -381,6 +468,14 @@ export class HuiCardPicker extends LitElement { margin: var(--card-picker-search-margin); } + .cards-container-header { + font-size: 16px; + font-weight: 500; + padding: 12px 8px 4px 8px; + margin: 0; + grid-column: 1 / -1; + } + .cards-container { display: grid; grid-gap: 8px 8px; @@ -455,6 +550,23 @@ export class HuiCardPicker extends LitElement { .manual { max-width: none; } + + .icon { + position: absolute; + top: 8px; + right: 8px + inset-inline-start: 8px; + inset-inline-end: 8px; + border-radius: 50%; + --mdc-icon-size: 16px; + line-height: 16px; + box-sizing: border-box; + color: var(--text-primary-color); + padding: 4px; + } + .icon.custom { + background: var(--warning-color); + } `, ]; } diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index 4ce93735d4..89a20ab0f5 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -12,16 +12,27 @@ import { computeStateName } from "../../../../common/entity/compute_state_name"; import { DataTableRowData } from "../../../../components/data-table/ha-data-table"; import "../../../../components/ha-dialog"; import "../../../../components/ha-dialog-header"; +import { + isStrategySection, + LovelaceSectionConfig, +} from "../../../../data/lovelace/config/section"; import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; +import { + computeCards, + computeSection, +} from "../../common/generate-lovelace-config"; import "./hui-card-picker"; import "./hui-entity-picker-table"; import { CreateCardDialogParams } from "./show-create-card-dialog"; import { showEditCardDialog } from "./show-edit-card-dialog"; import { showSuggestCardDialog } from "./show-suggest-card-dialog"; -import { computeCards } from "../../common/generate-lovelace-config"; +import { + findLovelaceContainer, + parseLovelaceContainerPath, +} from "../lovelace-path"; declare global { interface HASSDomEvents { @@ -42,7 +53,9 @@ export class HuiCreateDialogCard @state() private _params?: CreateCardDialogParams; - @state() private _viewConfig!: LovelaceViewConfig; + @state() private _containerConfig!: + | LovelaceViewConfig + | LovelaceSectionConfig; @state() private _selectedEntities: string[] = []; @@ -50,8 +63,17 @@ export class HuiCreateDialogCard public async showDialog(params: CreateCardDialogParams): Promise { this._params = params; - const [view] = params.path; - this._viewConfig = params.lovelaceConfig.views[view]; + + const containerConfig = findLovelaceContainer( + params.lovelaceConfig, + params.path + ); + + if ("strategy" in containerConfig) { + throw new Error("Can't edit strategy"); + } + + this._containerConfig = containerConfig; } public closeDialog(): boolean { @@ -67,10 +89,10 @@ export class HuiCreateDialogCard return nothing; } - const title = this._viewConfig.title + const title = this._containerConfig.title ? this.hass!.localize( - "ui.panel.lovelace.editor.edit_card.pick_card_view_title", - { name: `"${this._viewConfig.title}"` } + "ui.panel.lovelace.editor.edit_card.pick_card_title", + { name: `"${this._containerConfig.title}"` } ) : this.hass!.localize("ui.panel.lovelace.editor.edit_card.pick_card"); @@ -112,6 +134,7 @@ export class HuiCreateDialogCard this._currTabIndex === 0 ? html` = {}; + + const { sectionIndex } = parseLovelaceContainerPath(this._params!.path); + const isSection = sectionIndex !== undefined; + + // If we are in a section, we want to keep the section options for the preview + if (isSection) { + const containerConfig = findLovelaceContainer( + this._params!.lovelaceConfig!, + this._params!.path! + ) as LovelaceSectionConfig; + if (!isStrategySection(containerConfig)) { + const { cards, title, ...rest } = containerConfig; + sectionOptions = rest; + } + } + + const sectionConfig = computeSection( + this._selectedEntities, + sectionOptions + ); + showSuggestCardDialog(this, { lovelaceConfig: this._params!.lovelaceConfig, saveConfig: this._params!.saveConfig, path: this._params!.path as [number], entities: this._selectedEntities, cardConfig, + sectionConfig, }); this.closeDialog(); diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index 98427c14f4..c27b7024a2 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -1,12 +1,12 @@ import { mdiClose, mdiHelpCircle } from "@mdi/js"; import deepFreeze from "deep-freeze"; import { - css, CSSResultGroup, - html, LitElement, - nothing, PropertyValues, + css, + html, + nothing, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import type { HASSDomEvent } from "../../../../common/dom/fire_event"; @@ -16,6 +16,14 @@ import "../../../../components/ha-circular-progress"; import "../../../../components/ha-dialog"; import "../../../../components/ha-dialog-header"; import "../../../../components/ha-icon-button"; +import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; +import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; +import { + getCustomCardEntry, + isCustomType, + stripCustomPrefix, +} from "../../../../data/lovelace_custom_cards"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyleDialog } from "../../../../resources/styles"; @@ -24,18 +32,12 @@ import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; import { addCard, replaceCard } from "../config-util"; import { getCardDocumentationURL } from "../get-card-documentation-url"; import type { ConfigChangedEvent } from "../hui-element-editor"; +import { findLovelaceContainer } from "../lovelace-path"; import type { GUIModeChangedEvent } from "../types"; import "./hui-card-element-editor"; import type { HuiCardElementEditor } from "./hui-card-element-editor"; import "./hui-card-preview"; import type { EditCardDialogParams } from "./show-edit-card-dialog"; -import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; -import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; -import { - getCustomCardEntry, - isCustomType, - stripCustomPrefix, -} from "../../../../data/lovelace_custom_cards"; declare global { // for fire event @@ -61,7 +63,9 @@ export class HuiDialogEditCard @state() private _cardConfig?: LovelaceCardConfig; - @state() private _viewConfig!: LovelaceViewConfig; + @state() private _containerConfig!: + | LovelaceViewConfig + | LovelaceSectionConfig; @state() private _saving = false; @@ -84,17 +88,29 @@ export class HuiDialogEditCard this._params = params; this._GUImode = true; this._guiModeAvailable = true; - const [view, card] = params.path; - this._viewConfig = params.lovelaceConfig.views[view]; - this._cardConfig = - card !== undefined ? this._viewConfig.cards![card] : params.cardConfig; + + const containerConfig = findLovelaceContainer( + params.lovelaceConfig, + params.path + ); + + if ("strategy" in containerConfig) { + throw new Error("Can't edit strategy"); + } + + this._containerConfig = containerConfig; + + if ("cardConfig" in params) { + this._cardConfig = params.cardConfig; + this._dirty = true; + } else { + this._cardConfig = this._containerConfig.cards?.[params.cardIndex]; + } + this.large = false; if (this._cardConfig && !Object.isFrozen(this._cardConfig)) { this._cardConfig = deepFreeze(this._cardConfig); } - if (params.cardConfig) { - this._dirty = true; - } } public closeDialog(): boolean { @@ -170,10 +186,10 @@ export class HuiDialogEditCard { type: cardName } ); } else if (!this._cardConfig) { - heading = this._viewConfig.title + heading = this._containerConfig.title ? this.hass!.localize( "ui.panel.lovelace.editor.edit_card.pick_card_view_title", - { name: this._viewConfig.title } + { name: this._containerConfig.title } ) : this.hass!.localize("ui.panel.lovelace.editor.edit_card.pick_card"); } else { @@ -368,16 +384,13 @@ export class HuiDialogEditCard return; } this._saving = true; + const path = this._params!.path; await this._params!.saveConfig( - this._params!.path.length === 1 - ? addCard( - this._params!.lovelaceConfig, - this._params!.path as [number], - this._cardConfig! - ) + "cardConfig" in this._params! + ? addCard(this._params!.lovelaceConfig, path, this._cardConfig!) : replaceCard( this._params!.lovelaceConfig, - this._params!.path as [number, number], + [...path, this._params!.cardIndex], this._cardConfig! ) ); diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts index 8cf248ac01..80e517d5ce 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts @@ -5,13 +5,20 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; +import { isStrategyView } from "../../../../data/lovelace/config/view"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; -import { addCards } from "../config-util"; +import { addCards, addSection } from "../config-util"; +import { + LovelaceContainerPath, + parseLovelaceContainerPath, +} from "../lovelace-path"; import "./hui-card-preview"; import { showCreateCardDialog } from "./show-create-card-dialog"; import { SuggestCardDialogParams } from "./show-suggest-card-dialog"; +import { LovelaceConfig } from "../../../../data/lovelace/config/types"; @customElement("hui-dialog-suggest-card") export class HuiDialogSuggestCard extends LitElement { @@ -21,6 +28,8 @@ export class HuiDialogSuggestCard extends LitElement { @state() private _cardConfig?: LovelaceCardConfig[]; + @state() private _sectionConfig?: LovelaceSectionConfig; + @state() private _saving = false; @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; @@ -28,9 +37,13 @@ export class HuiDialogSuggestCard extends LitElement { public showDialog(params: SuggestCardDialogParams): void { this._params = params; this._cardConfig = params.cardConfig; + this._sectionConfig = params.sectionConfig; if (!Object.isFrozen(this._cardConfig)) { this._cardConfig = deepFreeze(this._cardConfig); } + if (!Object.isFrozen(this._sectionConfig)) { + this._sectionConfig = deepFreeze(this._sectionConfig); + } if (this._yamlEditor) { this._yamlEditor.setValue(this._cardConfig); } @@ -42,6 +55,45 @@ export class HuiDialogSuggestCard extends LitElement { fireEvent(this, "dialog-closed", { dialog: this.localName }); } + private get _viewSupportsSection(): boolean { + if (!this._params?.lovelaceConfig || !this._params?.path) { + return false; + } + + const { viewIndex } = parseLovelaceContainerPath(this._params.path); + const viewConfig = this._params!.lovelaceConfig.views[viewIndex]; + + return !isStrategyView(viewConfig) && viewConfig.type === "sections"; + } + + private _renderPreview() { + if (this._sectionConfig && this._viewSupportsSection) { + return html` +
    + +
    + `; + } + if (this._cardConfig) { + return html` +
    + ${this._cardConfig.map( + (cardConfig) => html` + + ` + )} +
    + `; + } + return nothing; + } + protected render() { if (!this._params) { return nothing; @@ -56,20 +108,7 @@ export class HuiDialogSuggestCard extends LitElement { )} >
    - ${this._cardConfig - ? html` -
    - ${this._cardConfig.map( - (cardConfig) => html` - - ` - )} -
    - ` - : ""} + ${this._renderPreview()} ${this._params.yaml && this._cardConfig ? html`
    @@ -79,7 +118,7 @@ export class HuiDialogSuggestCard extends LitElement { >
    ` - : ""} + : nothing}
    { if ( !this._params?.lovelaceConfig || @@ -188,13 +254,12 @@ export class HuiDialogSuggestCard extends LitElement { return; } this._saving = true; - await this._params!.saveConfig( - addCards( - this._params!.lovelaceConfig, - this._params!.path as [number], - this._cardConfig - ) + + const newConfig = this._computeNewConfig( + this._params.lovelaceConfig, + this._params.path ); + await this._params!.saveConfig(newConfig); this._saving = false; showSaveSuccessToast(this, this.hass); this.closeDialog(); diff --git a/src/panels/lovelace/editor/card-editor/hui-section-preview.ts b/src/panels/lovelace/editor/card-editor/hui-section-preview.ts new file mode 100644 index 0000000000..c569fcf4c1 --- /dev/null +++ b/src/panels/lovelace/editor/card-editor/hui-section-preview.ts @@ -0,0 +1,104 @@ +import { PropertyValues, ReactiveElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { LovelaceSectionElement } from "../../../../data/lovelace"; +import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; +import { HomeAssistant } from "../../../../types"; +import { createSectionElement } from "../../create-element/create-section-element"; +import { createErrorSectionConfig } from "../../sections/hui-error-section"; +import { LovelaceConfig } from "../../../../data/lovelace/config/types"; + +@customElement("hui-section-preview") +export class HuiSectionPreview extends ReactiveElement { + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public lovelace?: LovelaceConfig; + + @property({ attribute: false }) public config?: LovelaceSectionConfig; + + private _element?: LovelaceSectionElement; + + private get _error() { + return this._element?.tagName === "HUI-ERROR-SECTION"; + } + + constructor() { + super(); + this.addEventListener("ll-rebuild", () => { + this._cleanup(); + if (this.config) { + this._createSection(this.config); + } + }); + } + + protected createRenderRoot() { + return this; + } + + protected update(changedProperties: PropertyValues) { + super.update(changedProperties); + + if (changedProperties.has("config")) { + const oldConfig = changedProperties.get("config") as + | undefined + | LovelaceSectionConfig; + + if (!this.config) { + this._cleanup(); + return; + } + + if (!this.config.type) { + this._createSection(createErrorSectionConfig("No section type found")); + return; + } + + if (!this._element) { + this._createSection(this.config); + return; + } + + // in case the element was an error element we always want to recreate it + if (!this._error && oldConfig && this.config.type === oldConfig.type) { + try { + this._element.setConfig(this.config); + } catch (err: any) { + this._createSection(createErrorSectionConfig(err.message)); + } + } else { + this._createSection(this.config); + } + } + + if (changedProperties.has("hass")) { + if (this._element) { + this._element.hass = this.hass; + } + } + } + + private _createSection(configValue: LovelaceSectionConfig): void { + this._cleanup(); + this._element = createSectionElement(configValue) as LovelaceSectionElement; + + if (this.hass) { + this._element!.hass = this.hass; + } + + this.appendChild(this._element!); + } + + private _cleanup() { + if (!this._element) { + return; + } + this.removeChild(this._element); + this._element = undefined; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-section-preview": HuiSectionPreview; + } +} diff --git a/src/panels/lovelace/editor/card-editor/show-create-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-create-card-dialog.ts index dedd6d1b6a..96c58879b2 100644 --- a/src/panels/lovelace/editor/card-editor/show-create-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-create-card-dialog.ts @@ -1,10 +1,12 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; +import { LovelaceContainerPath } from "../lovelace-path"; export interface CreateCardDialogParams { lovelaceConfig: LovelaceConfig; saveConfig: (config: LovelaceConfig) => void; - path: [number] | [number, number]; + path: LovelaceContainerPath; + suggestedCards?: string[]; entities?: string[]; // We can pass entity id's that will be added to the config when a card is picked } diff --git a/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts index f6b55e3b0e..40391f6a13 100644 --- a/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts @@ -1,13 +1,20 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import type { LovelaceConfig } from "../../../../data/lovelace/config/types"; +import { LovelaceContainerPath } from "../lovelace-path"; -export interface EditCardDialogParams { +export type EditCardDialogParams = { lovelaceConfig: LovelaceConfig; saveConfig: (config: LovelaceConfig) => void; - path: [number] | [number, number]; - cardConfig?: LovelaceCardConfig; -} + path: LovelaceContainerPath; +} & ( + | { + cardIndex: number; + } + | { + cardConfig: LovelaceCardConfig; + } +); export const importEditCardDialog = () => import("./hui-dialog-edit-card"); diff --git a/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts index ea43443d06..c95fed399e 100644 --- a/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts @@ -1,14 +1,17 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; import { LovelaceConfig } from "../../../../data/lovelace/config/types"; +import { LovelaceContainerPath } from "../lovelace-path"; export interface SuggestCardDialogParams { lovelaceConfig?: LovelaceConfig; yaml?: boolean; saveConfig?: (config: LovelaceConfig) => void; - path?: [number]; + path?: LovelaceContainerPath; entities?: string[]; // We pass this to create dialog when user chooses "Pick own" - cardConfig: LovelaceCardConfig[]; // We can pass a suggested config + cardConfig: LovelaceCardConfig[]; // We can pass a suggested config,s + sectionConfig?: LovelaceSectionConfig; } const importSuggestCardDialog = () => import("./hui-dialog-suggest-card"); diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index d8fe23d0a7..ef1fcdfae0 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -27,6 +27,7 @@ import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature"; import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature"; import { supportsCoverTiltPositionCardFeature } from "../../card-features/hui-cover-tilt-position-card-feature"; +import { supportsFanPresetModesCardFeature } from "../../card-features/hui-fan-preset-modes-card-feature"; import { supportsFanSpeedCardFeature } from "../../card-features/hui-fan-speed-card-feature"; import { supportsHumidifierModesCardFeature } from "../../card-features/hui-humidifier-modes-card-feature"; import { supportsHumidifierToggleCardFeature } from "../../card-features/hui-humidifier-toggle-card-feature"; @@ -55,34 +56,36 @@ const UI_FEATURE_TYPES = [ "cover-position", "cover-tilt-position", "cover-tilt", + "fan-preset-modes", "fan-speed", "humidifier-modes", "humidifier-toggle", "lawn-mower-commands", "light-brightness", "light-color-temp", + "numeric-input", "select-options", "target-humidity", "target-temperature", - "vacuum-commands", "update-actions", + "vacuum-commands", "water-heater-operation-modes", - "numeric-input", ] as const satisfies readonly FeatureType[]; type UiFeatureTypes = (typeof UI_FEATURE_TYPES)[number]; const EDITABLES_FEATURE_TYPES = new Set([ - "vacuum-commands", "alarm-modes", "climate-hvac-modes", - "humidifier-modes", - "water-heater-operation-modes", - "lawn-mower-commands", "climate-fan-modes", "climate-preset-modes", + "fan-preset-modes", + "humidifier-modes", + "lawn-mower-commands", "numeric-input", "update-actions", + "vacuum-commands", + "water-heater-operation-modes", ]); const SUPPORTS_FEATURE_TYPES: Record< @@ -97,6 +100,7 @@ const SUPPORTS_FEATURE_TYPES: Record< "cover-position": supportsCoverPositionCardFeature, "cover-tilt-position": supportsCoverTiltPositionCardFeature, "cover-tilt": supportsCoverTiltCardFeature, + "fan-preset-modes": supportsFanPresetModesCardFeature, "fan-speed": supportsFanSpeedCardFeature, "humidifier-modes": supportsHumidifierModesCardFeature, "humidifier-toggle": supportsHumidifierToggleCardFeature, @@ -104,12 +108,12 @@ const SUPPORTS_FEATURE_TYPES: Record< "light-brightness": supportsLightBrightnessCardFeature, "light-color-temp": supportsLightColorTempCardFeature, "numeric-input": supportsNumericInputCardFeature, + "select-options": supportsSelectOptionsCardFeature, "target-humidity": supportsTargetHumidityCardFeature, "target-temperature": supportsTargetTemperatureCardFeature, + "update-actions": supportsUpdateActionsCardFeature, "vacuum-commands": supportsVacuumCommandsCardFeature, "water-heater-operation-modes": supportsWaterHeaterOperationModesCardFeature, - "select-options": supportsSelectOptionsCardFeature, - "update-actions": supportsUpdateActionsCardFeature, }; const customCardFeatures = getCustomCardFeatures(); diff --git a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts new file mode 100644 index 0000000000..35cfd7b8a6 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts @@ -0,0 +1,133 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { FormatEntityAttributeValueFunc } from "../../../../common/translations/entity-state"; +import { LocalizeFunc } from "../../../../common/translations/localize"; +import "../../../../components/ha-form/ha-form"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { + FanPresetModesCardFeatureConfig, + LovelaceCardFeatureContext, +} from "../../card-features/types"; +import type { LovelaceCardFeatureEditor } from "../../types"; + +@customElement("hui-fan-preset-modes-card-feature-editor") +export class HuiFanPresetModesCardFeatureEditor + extends LitElement + implements LovelaceCardFeatureEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @state() private _config?: FanPresetModesCardFeatureConfig; + + public setConfig(config: FanPresetModesCardFeatureConfig): void { + this._config = config; + } + + private _schema = memoizeOne( + ( + localize: LocalizeFunc, + formatEntityAttributeValue: FormatEntityAttributeValueFunc, + stateObj?: HassEntity + ) => + [ + { + name: "style", + selector: { + select: { + multiple: false, + mode: "list", + options: ["dropdown", "icons"].map((mode) => ({ + value: mode, + label: localize( + `ui.panel.lovelace.editor.features.types.fan-preset-modes.style_list.${mode}` + ), + })), + }, + }, + }, + { + name: "preset_modes", + selector: { + select: { + multiple: true, + mode: "list", + options: + stateObj?.attributes.preset_modes?.map((mode) => ({ + value: mode, + label: formatEntityAttributeValue( + stateObj, + "preset_mode", + mode + ), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[] + ); + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + const stateObj = this.context?.entity_id + ? this.hass.states[this.context?.entity_id] + : undefined; + + const data: FanPresetModesCardFeatureConfig = { + style: "dropdown", + preset_modes: [], + ...this._config, + }; + + const schema = this._schema( + this.hass.localize, + this.hass.formatEntityAttributeValue, + stateObj + ); + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "style": + case "preset_modes": + return this.hass!.localize( + `ui.panel.lovelace.editor.features.types.fan-preset-modes.${schema.name}` + ); + default: + return ""; + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-fan-preset-modes-card-feature-editor": HuiFanPresetModesCardFeatureEditor; + } +} diff --git a/src/panels/lovelace/editor/config-util.ts b/src/panels/lovelace/editor/config-util.ts index f494544fb6..09d2e611fa 100644 --- a/src/panels/lovelace/editor/config-util.ts +++ b/src/panels/lovelace/editor/config-util.ts @@ -1,296 +1,160 @@ import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { LovelaceSectionRawConfig } from "../../../data/lovelace/config/section"; import { LovelaceConfig } from "../../../data/lovelace/config/types"; import { LovelaceViewConfig, isStrategyView, } from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; +import { + LovelaceCardPath, + LovelaceContainerPath, + findLovelaceCards, + findLovelaceContainer, + getLovelaceContainerPath, + parseLovelaceCardPath, + parseLovelaceContainerPath, + updateLovelaceCards, + updateLovelaceContainer, +} from "./lovelace-path"; export const addCard = ( config: LovelaceConfig, - path: [number], + path: LovelaceContainerPath, cardConfig: LovelaceCardConfig ): LovelaceConfig => { - const [viewIndex] = path; - const views: LovelaceViewConfig[] = []; - - config.views.forEach((viewConf, index) => { - if (index !== viewIndex) { - views.push(config.views[index]); - return; - } - - if (isStrategyView(viewConf)) { - throw new Error("You cannot add a card in a strategy view."); - } - - const cards = viewConf.cards - ? [...viewConf.cards, cardConfig] - : [cardConfig]; - - views.push({ - ...viewConf, - cards, - }); - }); - - return { - ...config, - views, - }; + const cards = findLovelaceCards(config, path); + const newCards = cards ? [...cards, cardConfig] : [cardConfig]; + const newConfig = updateLovelaceCards(config, path, newCards); + return newConfig; }; export const addCards = ( config: LovelaceConfig, - path: [number], + path: LovelaceContainerPath, cardConfigs: LovelaceCardConfig[] ): LovelaceConfig => { - const [viewIndex] = path; - const views: LovelaceViewConfig[] = []; - - config.views.forEach((viewConf, index) => { - if (index !== viewIndex) { - views.push(config.views[index]); - return; - } - - if (isStrategyView(viewConf)) { - throw new Error("You cannot add cards in a strategy view."); - } - - const cards = viewConf.cards - ? [...viewConf.cards, ...cardConfigs] - : [...cardConfigs]; - - views.push({ - ...viewConf, - cards, - }); - }); - - return { - ...config, - views, - }; + const cards = findLovelaceCards(config, path); + const newCards = cards ? [...cards, ...cardConfigs] : [...cardConfigs]; + const newConfig = updateLovelaceCards(config, path, newCards); + return newConfig; }; export const replaceCard = ( config: LovelaceConfig, - path: [number, number], + path: LovelaceCardPath, cardConfig: LovelaceCardConfig ): LovelaceConfig => { - const [viewIndex, cardIndex] = path; - const views: LovelaceViewConfig[] = []; + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); - config.views.forEach((viewConf, index) => { - if (index !== viewIndex) { - views.push(config.views[index]); - return; - } + const cards = findLovelaceCards(config, containerPath); - if (isStrategyView(viewConf)) { - throw new Error("You cannot replace a card in a strategy view."); - } + const newCards = (cards ?? []).map((origConf, ind) => + ind === cardIndex ? cardConfig : origConf + ); - views.push({ - ...viewConf, - cards: (viewConf.cards || []).map((origConf, ind) => - ind === cardIndex ? cardConfig : origConf - ), - }); - }); - - return { - ...config, - views, - }; + const newConfig = updateLovelaceCards(config, containerPath, newCards); + return newConfig; }; export const deleteCard = ( config: LovelaceConfig, - path: [number, number] + path: LovelaceCardPath ): LovelaceConfig => { - const [viewIndex, cardIndex] = path; - const views: LovelaceViewConfig[] = []; + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); - config.views.forEach((viewConf, index) => { - if (index !== viewIndex) { - views.push(config.views[index]); - return; - } + const cards = findLovelaceCards(config, containerPath); - if (isStrategyView(viewConf)) { - throw new Error("You cannot delete a card in a strategy view."); - } + const newCards = (cards ?? []).filter((_origConf, ind) => ind !== cardIndex); - views.push({ - ...viewConf, - cards: (viewConf.cards || []).filter( - (_origConf, ind) => ind !== cardIndex - ), - }); - }); - - return { - ...config, - views, - }; + const newConfig = updateLovelaceCards(config, containerPath, newCards); + return newConfig; }; export const insertCard = ( config: LovelaceConfig, - path: [number, number], + path: LovelaceCardPath, cardConfig: LovelaceCardConfig ) => { - const [viewIndex, cardIndex] = path; - const views: LovelaceViewConfig[] = []; + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); - config.views.forEach((viewConf, index) => { - if (index !== viewIndex) { - views.push(config.views[index]); - return; - } + const cards = findLovelaceCards(config, containerPath); - if (isStrategyView(viewConf)) { - throw new Error("You cannot insert a card in a strategy view."); - } + const newCards = cards + ? [...cards.slice(0, cardIndex), cardConfig, ...cards.slice(cardIndex)] + : [cardConfig]; - const cards = viewConf.cards - ? [ - ...viewConf.cards.slice(0, cardIndex), - cardConfig, - ...viewConf.cards.slice(cardIndex), - ] - : [cardConfig]; - - views.push({ - ...viewConf, - cards, - }); - }); - - return { - ...config, - views, - }; + const newConfig = updateLovelaceCards(config, containerPath, newCards); + return newConfig; }; -export const swapCard = ( +export const moveCardToIndex = ( config: LovelaceConfig, - path1: [number, number], - path2: [number, number] + path: LovelaceCardPath, + index: number ): LovelaceConfig => { - const origView1 = config.views[path1[0]]; - const origView2 = config.views[path2[0]]; + const { cardIndex } = parseLovelaceCardPath(path); + const containerPath = getLovelaceContainerPath(path); - if (isStrategyView(origView1) || isStrategyView(origView2)) { - throw new Error("You cannot move swap cards in a strategy view."); - } + const cards = findLovelaceCards(config, containerPath); - const card1 = origView1.cards![path1[1]]; - const card2 = origView2.cards![path2[1]]; + const newCards = cards ? [...cards] : []; - const newView1 = { - ...origView1, - cards: origView1.cards!.map((origCard, index) => - index === path1[1] ? card2 : origCard - ), - }; - - const updatedOrigView2 = path1[0] === path2[0] ? newView1 : origView2; - const newView2 = { - ...updatedOrigView2, - cards: updatedOrigView2.cards!.map((origCard, index) => - index === path2[1] ? card1 : origCard - ), - }; - - return { - ...config, - views: config.views.map((origView, index) => - index === path2[0] ? newView2 : index === path1[0] ? newView1 : origView - ), - }; -}; - -export const moveCardToPosition = ( - config: LovelaceConfig, - path: [number, number], - position: number -): LovelaceConfig => { - const view = config.views[path[0]]; - - if (isStrategyView(view)) { - throw new Error("You cannot move a card in a strategy view."); - } - - const oldIndex = path[1]; - const newIndex = Math.max(Math.min(position - 1, view.cards!.length - 1), 0); - - const newCards = [...view.cards!]; + const oldIndex = cardIndex; + const newIndex = Math.max(Math.min(index, newCards.length - 1), 0); const card = newCards[oldIndex]; newCards.splice(oldIndex, 1); newCards.splice(newIndex, 0, card); - const newView = { - ...view, - cards: newCards, - }; + const newConfig = updateLovelaceCards(config, containerPath, newCards); + return newConfig; +}; - return { - ...config, - views: config.views.map((origView, index) => - index === path[0] ? newView : origView - ), - }; +export const moveCardToContainer = ( + config: LovelaceConfig, + fromPath: LovelaceCardPath, + toPath: LovelaceContainerPath +): LovelaceConfig => { + const { + cardIndex: fromCardIndex, + viewIndex: fromViewIndex, + sectionIndex: fromSectionIndex, + } = parseLovelaceCardPath(fromPath); + const { viewIndex: toViewIndex, sectionIndex: toSectionIndex } = + parseLovelaceContainerPath(toPath); + + if (fromViewIndex === toViewIndex && fromSectionIndex === toSectionIndex) { + throw new Error("You cannot move a card to the view or section it is in."); + } + + const fromContainerPath = getLovelaceContainerPath(fromPath); + const cards = findLovelaceCards(config, fromContainerPath); + const card = cards![fromCardIndex]; + + let newConfig = addCard(config, toPath, card); + newConfig = deleteCard(newConfig, fromPath); + + return newConfig; }; export const moveCard = ( config: LovelaceConfig, - fromPath: [number, number], - toPath: [number] + fromPath: LovelaceCardPath, + toPath: LovelaceCardPath ): LovelaceConfig => { - if (fromPath[0] === toPath[0]) { - throw new Error("You cannot move a card to the view it is in."); - } - const fromView = config.views[fromPath[0]]; - const toView = config.views[toPath[0]]; + const { cardIndex: fromCardIndex } = parseLovelaceCardPath(fromPath); + const fromContainerPath = getLovelaceContainerPath(fromPath); + const cards = findLovelaceCards(config, fromContainerPath); + const card = cards![fromCardIndex]; - if (isStrategyView(fromView)) { - throw new Error("You cannot move a card from a strategy view."); - } + let newConfig = deleteCard(config, fromPath); + newConfig = insertCard(newConfig, toPath, card); - if (isStrategyView(toView)) { - throw new Error("You cannot move a card to a strategy view."); - } - - const card = fromView.cards![fromPath[1]]; - - const newView1 = { - ...fromView, - cards: (fromView.cards || []).filter( - (_origConf, ind) => ind !== fromPath[1] - ), - }; - - const cards = toView.cards ? [...toView.cards, card] : [card]; - - const newView2 = { - ...toView, - cards, - }; - - return { - ...config, - views: config.views.map((origView, index) => - index === toPath[0] - ? newView2 - : index === fromPath[0] - ? newView1 - : origView - ), - }; + return newConfig; }; export const addView = ( @@ -356,3 +220,84 @@ export const deleteView = ( ...config, views: config.views.filter((_origView, index) => index !== viewIndex), }); + +export const addSection = ( + config: LovelaceConfig, + viewIndex: number, + sectionConfig: LovelaceSectionRawConfig +): LovelaceConfig => { + const view = findLovelaceContainer(config, [viewIndex]) as LovelaceViewConfig; + if (isStrategyView(view)) { + throw new Error("Deleting sections in a strategy is not supported."); + } + const sections = view.sections + ? [...view.sections, sectionConfig] + : [sectionConfig]; + + const newConfig = updateLovelaceContainer(config, [viewIndex], { + ...view, + sections, + }); + return newConfig; +}; + +export const deleteSection = ( + config: LovelaceConfig, + viewIndex: number, + sectionIndex: number +): LovelaceConfig => { + const view = findLovelaceContainer(config, [viewIndex]) as LovelaceViewConfig; + if (isStrategyView(view)) { + throw new Error("Deleting sections in a strategy is not supported."); + } + const sections = view.sections?.filter( + (_origSection, index) => index !== sectionIndex + ); + + const newConfig = updateLovelaceContainer(config, [viewIndex], { + ...view, + sections, + }); + return newConfig; +}; + +export const insertSection = ( + config: LovelaceConfig, + viewIndex: number, + sectionIndex: number, + sectionConfig: LovelaceSectionRawConfig +): LovelaceConfig => { + const view = findLovelaceContainer(config, [viewIndex]) as LovelaceViewConfig; + if (isStrategyView(view)) { + throw new Error("Inserting sections in a strategy is not supported."); + } + const sections = view.sections + ? [ + ...view.sections.slice(0, sectionIndex), + sectionConfig, + ...view.sections.slice(sectionIndex), + ] + : [sectionConfig]; + + const newConfig = updateLovelaceContainer(config, [viewIndex], { + ...view, + sections, + }); + return newConfig; +}; + +export const moveSection = ( + config: LovelaceConfig, + fromPath: [number, number], + toPath: [number, number] +): LovelaceConfig => { + const section = findLovelaceContainer( + config, + fromPath + ) as LovelaceSectionRawConfig; + + let newConfig = deleteSection(config, fromPath[0], fromPath[1]); + newConfig = insertSection(newConfig, toPath[0], toPath[1], section); + + return newConfig; +}; diff --git a/src/panels/lovelace/editor/delete-card.ts b/src/panels/lovelace/editor/delete-card.ts index f4b2c08b09..f3264bc023 100644 --- a/src/panels/lovelace/editor/delete-card.ts +++ b/src/panels/lovelace/editor/delete-card.ts @@ -1,22 +1,29 @@ -import { isStrategyView } from "../../../data/lovelace/config/view"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { HomeAssistant } from "../../../types"; import { showDeleteSuccessToast } from "../../../util/toast-deleted-success"; import { Lovelace } from "../types"; import { showDeleteCardDialog } from "./card-editor/show-delete-card-dialog"; import { deleteCard, insertCard } from "./config-util"; +import { + LovelaceCardPath, + findLovelaceContainer, + getLovelaceContainerPath, + parseLovelaceCardPath, +} from "./lovelace-path"; export async function confDeleteCard( element: HTMLElement, hass: HomeAssistant, lovelace: Lovelace, - path: [number, number] + path: LovelaceCardPath ): Promise { - const view = lovelace.config.views[path[0]]; - if (isStrategyView(view)) { - throw new Error("Deleting cards in a strategy view is not supported."); + const containerPath = getLovelaceContainerPath(path); + const { cardIndex } = parseLovelaceCardPath(path); + const containerConfig = findLovelaceContainer(lovelace.config, containerPath); + if ("strategy" in containerConfig) { + throw new Error("Deleting cards in a strategy is not supported."); } - const cardConfig = view.cards![path[1]]; + const cardConfig = containerConfig.cards![cardIndex]; showDeleteCardDialog(element, { cardConfig, deleteCard: async () => { diff --git a/src/panels/lovelace/editor/lovelace-path.ts b/src/panels/lovelace/editor/lovelace-path.ts new file mode 100644 index 0000000000..2eaf9dadc6 --- /dev/null +++ b/src/panels/lovelace/editor/lovelace-path.ts @@ -0,0 +1,197 @@ +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { + LovelaceSectionRawConfig, + isStrategySection, +} from "../../../data/lovelace/config/section"; +import { LovelaceConfig } from "../../../data/lovelace/config/types"; +import { + LovelaceViewRawConfig, + isStrategyView, +} from "../../../data/lovelace/config/view"; + +export type LovelaceCardPath = [number, number] | [number, number, number]; +export type LovelaceContainerPath = [number] | [number, number]; + +export const parseLovelaceCardPath = ( + path: LovelaceCardPath +): { viewIndex: number; sectionIndex?: number; cardIndex: number } => { + if (path.length === 2) { + return { + viewIndex: path[0], + cardIndex: path[1], + }; + } + return { + viewIndex: path[0], + sectionIndex: path[1], + cardIndex: path[2], + }; +}; + +export const parseLovelaceContainerPath = ( + path: LovelaceContainerPath +): { viewIndex: number; sectionIndex?: number } => { + if (path.length === 1) { + return { + viewIndex: path[0], + }; + } + return { + viewIndex: path[0], + sectionIndex: path[1], + }; +}; + +export const getLovelaceContainerPath = ( + path: LovelaceCardPath +): LovelaceContainerPath => path.slice(0, -1) as LovelaceContainerPath; + +export const findLovelaceContainer = ( + config: LovelaceConfig, + path: LovelaceContainerPath +): LovelaceViewRawConfig | LovelaceSectionRawConfig => { + const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); + + const view = config.views[viewIndex]; + + if (!view) { + throw new Error("View does not exist"); + } + if (sectionIndex === undefined) { + return view; + } + if (isStrategyView(view)) { + throw new Error("Can not find section in a strategy view"); + } + + const section = view.sections?.[sectionIndex]; + + if (!section) { + throw new Error("Section does not exist"); + } + return section; +}; + +export const findLovelaceCards = ( + config: LovelaceConfig, + path: LovelaceContainerPath +): LovelaceCardConfig[] | undefined => { + const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); + + const view = config.views[viewIndex]; + + if (!view) { + throw new Error("View does not exist"); + } + if (isStrategyView(view)) { + throw new Error("Can not find cards in a strategy view"); + } + if (sectionIndex === undefined) { + return view.cards; + } + + const section = view.sections?.[sectionIndex]; + + if (!section) { + throw new Error("Section does not exist"); + } + if (isStrategySection(section)) { + throw new Error("Can not find cards in a strategy section"); + } + return section.cards; +}; + +export const updateLovelaceContainer = ( + config: LovelaceConfig, + path: LovelaceContainerPath, + containerConfig: LovelaceViewRawConfig | LovelaceSectionRawConfig +): LovelaceConfig => { + const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); + + let updated = false; + const newViews = config.views.map((view, vIndex) => { + if (vIndex !== viewIndex) return view; + + if (sectionIndex === undefined) { + updated = true; + return containerConfig; + } + + if (isStrategyView(view)) { + throw new Error("Can not update section in a strategy view"); + } + + if (view.sections === undefined) { + throw new Error("Section does not exist"); + } + + const newSections = view.sections.map((section, sIndex) => { + if (sIndex !== sectionIndex) return section; + updated = true; + return containerConfig; + }); + return { + ...view, + sections: newSections, + }; + }); + + if (!updated) { + throw new Error("Can not update cards in a non-existing view/section"); + } + return { + ...config, + views: newViews, + }; +}; + +export const updateLovelaceCards = ( + config: LovelaceConfig, + path: LovelaceContainerPath, + cards: LovelaceCardConfig[] +): LovelaceConfig => { + const { viewIndex, sectionIndex } = parseLovelaceContainerPath(path); + + let updated = false; + const newViews = config.views.map((view, vIndex) => { + if (vIndex !== viewIndex) return view; + if (isStrategyView(view)) { + throw new Error("Can not update cards in a strategy view"); + } + if (sectionIndex === undefined) { + updated = true; + return { + ...view, + cards, + }; + } + + if (view.sections === undefined) { + throw new Error("Section does not exist"); + } + + const newSections = view.sections.map((section, sIndex) => { + if (sIndex !== sectionIndex) return section; + if (isStrategySection(section)) { + throw new Error("Can not update cards in a strategy section"); + } + updated = true; + return { + ...section, + cards, + }; + }); + return { + ...view, + sections: newSections, + }; + }); + + if (!updated) { + throw new Error("Can not update cards in a non-existing view/section"); + } + return { + ...config, + views: newViews, + }; +}; diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index 1edab843d1..44c42beac8 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -62,6 +62,7 @@ export interface Card { description?: string; showElement?: boolean; isCustom?: boolean; + isSuggested?: boolean; } export interface HeaderFooter { diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index 98cd3f3163..dadd34449e 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -21,7 +21,10 @@ import "../card-editor/hui-entity-picker-table"; import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog"; import { showSelectViewDialog } from "../select-view/show-select-view-dialog"; import { LovelaceConfig } from "../../../../data/lovelace/config/types"; -import { computeCards } from "../../common/generate-lovelace-config"; +import { + computeCards, + computeSection, +} from "../../common/generate-lovelace-config"; @customElement("hui-unused-entities") export class HuiUnusedEntities extends LitElement { @@ -132,6 +135,8 @@ export class HuiUnusedEntities extends LitElement { this._selectedEntities, {} ); + const sectionConfig = computeSection(this._selectedEntities, {}); + if (this.lovelace.config.views.length === 1) { showSuggestCardDialog(this, { lovelaceConfig: this.lovelace.config!, @@ -139,6 +144,7 @@ export class HuiUnusedEntities extends LitElement { path: [0], entities: this._selectedEntities, cardConfig, + sectionConfig, }); return; } @@ -152,6 +158,7 @@ export class HuiUnusedEntities extends LitElement { path: [viewIndex], entities: this._selectedEntities, cardConfig, + sectionConfig, }); }, }); diff --git a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts index a16f8237f4..038680b049 100644 --- a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts @@ -4,16 +4,16 @@ import { mdiCheck, mdiClose, mdiDotsVertical } from "@mdi/js"; import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tabs"; import { - css, CSSResultGroup, - html, LitElement, - nothing, PropertyValues, + css, + html, + nothing, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; +import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { navigate } from "../../../../common/navigate"; import { deepEqual } from "../../../../common/util/deep-equal"; @@ -23,6 +23,11 @@ import "../../../../components/ha-dialog"; import "../../../../components/ha-dialog-header"; import "../../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; +import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge"; +import { + LovelaceViewConfig, + isStrategyView, +} from "../../../../data/lovelace/config/view"; import { showAlertDialog, showConfirmationDialog, @@ -33,6 +38,7 @@ import "../../components/hui-entity-editor"; import { DEFAULT_VIEW_LAYOUT, PANEL_VIEW_LAYOUT, + SECTION_VIEW_LAYOUT, VIEWS_NO_BADGE_SUPPORT, } from "../../views/const"; import { addView, deleteView, replaceView } from "../config-util"; @@ -46,12 +52,6 @@ import { import "./hui-view-editor"; import "./hui-view-visibility-editor"; import { EditViewDialogParams } from "./show-edit-view-dialog"; -import { - LovelaceViewConfig, - isStrategyView, -} from "../../../../data/lovelace/config/view"; -import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge"; -import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; @customElement("hui-dialog-edit-view") export class HuiDialogEditView extends LitElement { @@ -63,8 +63,6 @@ export class HuiDialogEditView extends LitElement { @state() private _badges?: LovelaceBadgeConfig[]; - @state() private _cards?: LovelaceCardConfig[]; - @state() private _saving = false; @state() private _curTab?: string; @@ -102,7 +100,6 @@ export class HuiDialogEditView extends LitElement { if (this._params.viewIndex === undefined) { this._config = {}; this._badges = []; - this._cards = []; this._dirty = false; return; } @@ -112,13 +109,11 @@ export class HuiDialogEditView extends LitElement { const { strategy, ...viewConfig } = view; this._config = viewConfig; this._badges = []; - this._cards = []; return; } - const { cards, badges, ...viewConfig } = view; + const { badges, ...viewConfig } = view; this._config = viewConfig; this._badges = badges ? processEditorEntities(badges) : []; - this._cards = cards; } public closeDialog(): void { @@ -181,7 +176,7 @@ export class HuiDialogEditView extends LitElement { )} ` - : ""} + : nothing}
    ${this._badges.map( (badgeConfig) => html` @@ -193,7 +188,7 @@ export class HuiDialogEditView extends LitElement { )}
    ` - : ""} + : nothing} ` - : ""} + : nothing} ${content} ${this._params.viewIndex !== undefined @@ -312,10 +307,7 @@ export class HuiDialogEditView extends LitElement { )}
    ` - : ""} - ${this.hass!.localize("ui.common.cancel")} + : nothing} ` - : ""} + : nothing} ${this.hass!.localize("ui.common.save")} @@ -364,24 +356,28 @@ export class HuiDialogEditView extends LitElement { } } - private _deleteConfirm(): void { - showConfirmationDialog(this, { - title: this.hass!.localize( - `ui.panel.lovelace.views.confirm_delete${ - this._cards?.length ? `_existing_cards` : "" - }` - ), + private async _deleteConfirm() { + const type = this._config?.sections?.length + ? "sections" + : this._config?.cards?.length + ? "cards" + : "only"; + + const named = this._config?.title ? "named" : "unnamed"; + + const confirm = await showConfirmationDialog(this, { + title: this.hass!.localize("ui.panel.lovelace.views.delete_title"), text: this.hass!.localize( - `ui.panel.lovelace.views.confirm_delete${ - this._cards?.length ? "_existing_cards" : "" - }_text`, - { - name: this._config?.title || "Unnamed view", - number: this._cards?.length || 0, - } + `ui.panel.lovelace.views.delete_${named}_view_${type}`, + { name: this._config?.title } ), - confirm: () => this._delete(), + confirmText: this.hass!.localize("ui.common.delete"), + destructive: true, }); + + if (!confirm) return; + + this._delete(); } private _handleTabSelected(ev: CustomEvent): void { @@ -405,9 +401,18 @@ export class HuiDialogEditView extends LitElement { const viewConf: LovelaceViewConfig = { ...this._config, badges: this._badges, - cards: this._cards, }; + if (viewConf.type === SECTION_VIEW_LAYOUT && !viewConf.sections?.length) { + viewConf.sections = [{ cards: [] }]; + } else if (!viewConf.cards?.length) { + viewConf.cards = []; + } + + if (!viewConf.badges?.length) { + delete viewConf.badges; + } + const lovelace = this._params.lovelace!; try { @@ -472,7 +477,7 @@ export class HuiDialogEditView extends LitElement { if (!ev.detail.isValid) { return; } - const { badges = [], ...config } = ev.detail.value; + const { badges, ...config } = ev.detail.value; this._config = config; this._badges = badges; this._dirty = true; diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index b693848a8d..d0c4f24d53 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -5,14 +5,18 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { slugify } from "../../../../common/string/slugify"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; -import type { SchemaUnion } from "../../../../components/ha-form/types"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../../types"; import { DEFAULT_VIEW_LAYOUT, + SECTION_VIEW_LAYOUT, PANEL_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT, } from "../../views/const"; -import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; declare global { interface HASSDomEvents { @@ -33,7 +37,7 @@ export class HuiViewEditor extends LitElement { private _suggestedPath = false; private _schema = memoizeOne( - (localize: LocalizeFunc) => + (localize: LocalizeFunc, currentType: string, isNew: boolean) => [ { name: "title", selector: { text: {} } }, { @@ -53,12 +57,18 @@ export class HuiViewEditor extends LitElement { DEFAULT_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT, PANEL_VIEW_LAYOUT, + SECTION_VIEW_LAYOUT, ] as const ).map((type) => ({ value: type, label: localize( `ui.panel.lovelace.editor.edit_view.types.${type}` ), + disabled: + !isNew && + (currentType === SECTION_VIEW_LAYOUT + ? type !== SECTION_VIEW_LAYOUT + : type === SECTION_VIEW_LAYOUT), })), }, }, @@ -69,7 +79,7 @@ export class HuiViewEditor extends LitElement { boolean: {}, }, }, - ] as const + ] as const satisfies HaFormSchema[] ); set config(config: LovelaceViewConfig) { @@ -90,7 +100,7 @@ export class HuiViewEditor extends LitElement { return nothing; } - const schema = this._schema(this.hass.localize); + const schema = this._schema(this.hass.localize, this._type, this.isNew); const data = { ...this._config, @@ -154,6 +164,15 @@ export class HuiViewEditor extends LitElement { return this.hass.localize( "ui.panel.lovelace.editor.edit_view.subview_helper" ); + case "type": + if (this.isNew) return undefined; + return this._type === "sections" + ? this.hass.localize( + "ui.panel.lovelace.editor.edit_view.type_helper_others" + ) + : this.hass.localize( + "ui.panel.lovelace.editor.edit_view.type_helper_sections" + ); default: return undefined; } diff --git a/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts b/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts index 6c757e22d7..128a80808a 100644 --- a/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts @@ -14,6 +14,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { EntityConfig, LovelaceRow } from "./types"; +import { callProtectedLockService } from "../../../data/lock"; @customElement("hui-lock-entity-row") class HuiLockEntityRow extends LitElement implements LovelaceRow { @@ -75,10 +76,11 @@ class HuiLockEntityRow extends LitElement implements LovelaceRow { private _callService(ev): void { ev.stopPropagation(); const stateObj = this.hass!.states[this._config!.entity]; - this.hass!.callService( - "lock", - stateObj.state === "locked" ? "unlock" : "lock", - { entity_id: stateObj.entity_id } + callProtectedLockService( + this, + this.hass!, + stateObj, + stateObj.state === "locked" ? "unlock" : "lock" ); } } diff --git a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts index 13afe9d3c5..1b57c6ee41 100644 --- a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts @@ -139,6 +139,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { } ha-textfield { text-align: end; + direction: ltr !important; } ha-slider { width: 100%; diff --git a/src/panels/lovelace/entity-rows/hui-script-entity-row.ts b/src/panels/lovelace/entity-rows/hui-script-entity-row.ts index 6fa20512ea..1147d405b4 100644 --- a/src/panels/lovelace/entity-rows/hui-script-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-script-entity-row.ts @@ -15,6 +15,8 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { ActionRowConfig, LovelaceRow } from "./types"; +import { computeObjectId } from "../../../common/entity/compute_object_id"; +import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; @customElement("hui-script-entity-row") class HuiScriptEntityRow extends LitElement implements LovelaceRow { @@ -92,7 +94,15 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow { private _runScript(ev): void { ev.stopPropagation(); - this._callService("turn_on"); + + const fields = + this.hass!.services.script[computeObjectId(this._config!.entity)]?.fields; + + if (fields && Object.keys(fields).length > 0) { + showMoreInfoDialog(this, { entityId: this._config!.entity }); + } else { + this._callService("turn_on"); + } } private _callService(service: string): void { diff --git a/src/panels/lovelace/sections/const.ts b/src/panels/lovelace/sections/const.ts new file mode 100644 index 0000000000..8551f0fe92 --- /dev/null +++ b/src/panels/lovelace/sections/const.ts @@ -0,0 +1,2 @@ +export const GRID_SECTION_LAYOUT = "grid"; +export const DEFAULT_SECTION_LAYOUT = GRID_SECTION_LAYOUT; diff --git a/src/panels/lovelace/sections/hui-error-section.ts b/src/panels/lovelace/sections/hui-error-section.ts new file mode 100644 index 0000000000..801f6f0379 --- /dev/null +++ b/src/panels/lovelace/sections/hui-error-section.ts @@ -0,0 +1,60 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../components/ha-label-badge"; +import "../../../components/ha-svg-icon"; +import { LovelaceSectionElement } from "../../../data/lovelace"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; +import { HomeAssistant } from "../../../types"; + +export interface ErrorSectionConfig extends LovelaceSectionConfig { + error: string; +} + +export const createErrorSectionElement = (config: ErrorSectionConfig) => { + const el = document.createElement( + "hui-error-section" + ) as LovelaceSectionElement; + el.setConfig(config); + return el; +}; + +export const createErrorSectionConfig = ( + error: string +): ErrorSectionConfig => ({ + type: "error", + error, +}); + +@customElement("hui-error-section") +export class HuiErrorSection + extends LitElement + implements LovelaceSectionElement +{ + public hass?: HomeAssistant; + + @property({ type: Boolean }) public isStrategy = false; + + @state() private _config?: ErrorSectionConfig; + + public setConfig(config: ErrorSectionConfig): void { + this._config = config; + } + + protected render() { + if (!this._config) { + return nothing; + } + + // Todo improve + return html` +

    Error

    +

    ${this._config.error}

    + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-error-section": HuiErrorSection; + } +} diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts new file mode 100644 index 0000000000..b63e9fac8f --- /dev/null +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -0,0 +1,246 @@ +import { mdiPlus } from "@mdi/js"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { repeat } from "lit/directives/repeat"; +import { styleMap } from "lit/directives/style-map"; +import { fireEvent } from "../../../common/dom/fire_event"; +import type { HaSortableOptions } from "../../../components/ha-sortable"; +import { LovelaceSectionElement } from "../../../data/lovelace"; +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; +import { haStyle } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; +import { HuiErrorCard } from "../cards/hui-error-card"; +import "../components/hui-card-edit-mode"; +import { moveCard } from "../editor/config-util"; +import type { Lovelace, LovelaceCard } from "../types"; + +const CARD_SORTABLE_OPTIONS: HaSortableOptions = { + delay: 100, + delayOnTouchOnly: true, + direction: "vertical", + invertedSwapThreshold: 0.7, +} as HaSortableOptions; + +export class GridSection extends LitElement implements LovelaceSectionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace?: Lovelace; + + @property({ type: Number }) public index?: number; + + @property({ type: Number }) public viewIndex?: number; + + @property({ type: Boolean }) public isStrategy = false; + + @property({ attribute: false }) public cards: Array< + LovelaceCard | HuiErrorCard + > = []; + + @state() _config?: LovelaceSectionConfig; + + @state() _dragging = false; + + public setConfig(config: LovelaceSectionConfig): void { + this._config = config; + } + + private _cardConfigKeys = new WeakMap(); + + private _getKey(cardConfig: LovelaceCardConfig) { + if (!this._cardConfigKeys.has(cardConfig)) { + this._cardConfigKeys.set(cardConfig, Math.random().toString()); + } + return this._cardConfigKeys.get(cardConfig)!; + } + + render() { + if (!this.cards || !this._config) return nothing; + + const cardsConfig = this._config?.cards ?? []; + + const editMode = Boolean(this.lovelace?.editMode && !this.isStrategy); + + return html` + ${this._config.title || this.lovelace?.editMode + ? html` +

    + ${this._config.title || + this.hass.localize( + "ui.panel.lovelace.editor.section.unnamed_section" + )} +

    + ` + : nothing} + +
    + ${repeat( + cardsConfig, + (cardConfig) => this._getKey(cardConfig), + (_cardConfig, idx) => { + const card = this.cards![idx]; + (card as any).editMode = editMode; + const size = card && (card as any).getGridSize?.(); + return html` +
    + ${editMode + ? html` + + ${card} + + ` + : card} +
    + `; + } + )} + ${editMode + ? html` + + ` + : nothing} +
    +
    + `; + } + + private _cardMoved(ev) { + ev.stopPropagation(); + const { oldIndex, newIndex, oldPath, newPath } = ev.detail; + const newConfig = moveCard( + this.lovelace!.config, + [...oldPath, oldIndex] as [number, number, number], + [...newPath, newIndex] as [number, number, number] + ); + this.lovelace!.saveConfig(newConfig); + } + + private _dragStart() { + this._dragging = true; + } + + private _dragEnd() { + this._dragging = false; + } + + private _addCard() { + fireEvent(this, "ll-create-card", { suggested: ["tile"] }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + :host { + display: flex; + flex-direction: column; + gap: 8px; + } + .container { + --column-count: 4; + display: grid; + grid-template-columns: repeat(var(--column-count), minmax(0, 1fr)); + grid-auto-rows: minmax(66px, auto); + gap: 8px; + padding: 0; + margin: 0 auto; + } + + .container.edit-mode { + padding: 8px; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--divider-color); + min-height: 66px; + } + + .title { + color: var(--primary-text-color); + font-size: 20px; + font-weight: normal; + margin: 0px; + letter-spacing: 0.1px; + line-height: 32px; + min-height: 32px; + display: block; + padding: 24px 10px 10px; + } + + .title.placeholder { + color: var(--secondary-text-color); + font-style: italic; + } + + .card { + border-radius: var(--ha-card-border-radius, 12px); + position: relative; + grid-row: span var(--row-size, 1); + grid-column: span var(--column-size, 4); + } + + .add { + outline: none; + grid-row: span var(--row-size, 1); + grid-column: span var(--column-size, 2); + background: none; + cursor: pointer; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--primary-color); + height: 66px; + order: 1; + } + .add:focus { + border-style: solid; + } + .sortable-ghost { + border-radius: var(--ha-card-border-radius, 12px); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-grid-section": GridSection; + } +} + +customElements.define("hui-grid-section", GridSection); diff --git a/src/panels/lovelace/sections/hui-section.ts b/src/panels/lovelace/sections/hui-section.ts new file mode 100644 index 0000000000..b50b1df18a --- /dev/null +++ b/src/panels/lovelace/sections/hui-section.ts @@ -0,0 +1,247 @@ +import { PropertyValues, ReactiveElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../components/ha-svg-icon"; +import type { LovelaceSectionElement } from "../../../data/lovelace"; +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { + LovelaceSectionConfig, + LovelaceSectionRawConfig, + isStrategySection, +} from "../../../data/lovelace/config/section"; +import type { HomeAssistant } from "../../../types"; +import type { HuiErrorCard } from "../cards/hui-error-card"; +import { createCardElement } from "../create-element/create-card-element"; +import { + createErrorCardConfig, + createErrorCardElement, +} from "../create-element/create-element-base"; +import { createSectionElement } from "../create-element/create-section-element"; +import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog"; +import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; +import { deleteCard } from "../editor/config-util"; +import { confDeleteCard } from "../editor/delete-card"; +import { parseLovelaceCardPath } from "../editor/lovelace-path"; +import { generateLovelaceSectionStrategy } from "../strategies/get-strategy"; +import type { Lovelace, LovelaceCard } from "../types"; +import { DEFAULT_SECTION_LAYOUT } from "./const"; + +@customElement("hui-section") +export class HuiSection extends ReactiveElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace!: Lovelace; + + @property({ attribute: false }) public config!: LovelaceSectionRawConfig; + + @property({ type: Number }) public index!: number; + + @property({ type: Number }) public viewIndex!: number; + + @state() private _cards: Array = []; + + private _layoutElementType?: string; + + private _layoutElement?: LovelaceSectionElement; + + // Public to make demo happy + public createCardElement(cardConfig: LovelaceCardConfig) { + const element = createCardElement(cardConfig) as LovelaceCard; + try { + element.hass = this.hass; + } catch (e: any) { + return createErrorCardElement( + createErrorCardConfig(e.message, cardConfig) + ); + } + element.addEventListener( + "ll-rebuild", + (ev: Event) => { + // In edit mode let it go to hui-root and rebuild whole section. + if (!this.lovelace!.editMode) { + ev.stopPropagation(); + this._rebuildCard(element, cardConfig); + } + }, + { once: true } + ); + return element; + } + + protected createRenderRoot() { + return this; + } + + public willUpdate(changedProperties: PropertyValues): void { + super.willUpdate(changedProperties); + + /* + We need to handle the following use cases: + - initialization: create layout element, populate + - config changed to section with same layout element + - config changed to section with different layout element + - forwarded properties hass/narrow/lovelace/cards change + - cards change if one is rebuild when it was loaded later + - lovelace changes if edit mode is enabled or config has changed + */ + + const oldConfig = changedProperties.get("config"); + + // If config has changed, create element if necessary and set all values. + if ( + changedProperties.has("config") && + (!oldConfig || this.config !== oldConfig) + ) { + this._initializeConfig(); + } + } + + protected update(changedProperties) { + super.update(changedProperties); + + // If no layout element, we're still creating one + if (this._layoutElement) { + // Config has not changed. Just props + if (changedProperties.has("hass")) { + this._cards.forEach((element) => { + try { + element.hass = this.hass; + } catch (e: any) { + this._rebuildCard(element, createErrorCardConfig(e.message, null)); + } + }); + + this._layoutElement.hass = this.hass; + } + if (changedProperties.has("lovelace")) { + this._layoutElement.lovelace = this.lovelace; + } + if (changedProperties.has("_cards")) { + this._layoutElement.cards = this._cards; + } + } + } + + private async _initializeConfig() { + let sectionConfig = { ...this.config }; + let isStrategy = false; + + if (isStrategySection(sectionConfig)) { + isStrategy = true; + sectionConfig = await generateLovelaceSectionStrategy( + sectionConfig.strategy, + this.hass! + ); + } + + sectionConfig = { + ...sectionConfig, + type: sectionConfig.type || DEFAULT_SECTION_LAYOUT, + }; + + // Create a new layout element if necessary. + let addLayoutElement = false; + + if ( + !this._layoutElement || + this._layoutElementType !== sectionConfig.type + ) { + addLayoutElement = true; + this._createLayoutElement(sectionConfig); + } + + this._createCards(sectionConfig); + this._layoutElement!.isStrategy = isStrategy; + this._layoutElement!.hass = this.hass; + this._layoutElement!.lovelace = this.lovelace; + this._layoutElement!.index = this.index; + this._layoutElement!.viewIndex = this.viewIndex; + this._layoutElement!.cards = this._cards; + + if (addLayoutElement) { + while (this.lastChild) { + this.removeChild(this.lastChild); + } + this.appendChild(this._layoutElement!); + } + } + + private _createLayoutElement(config: LovelaceSectionConfig): void { + this._layoutElement = createSectionElement( + config + ) as LovelaceSectionElement; + this._layoutElementType = config.type; + this._layoutElement.addEventListener("ll-create-card", (ev) => { + ev.stopPropagation(); + showCreateCardDialog(this, { + lovelaceConfig: this.lovelace.config, + saveConfig: this.lovelace.saveConfig, + path: [this.viewIndex, this.index], + suggestedCards: ev.detail?.suggested, + }); + }); + this._layoutElement.addEventListener("ll-edit-card", (ev) => { + ev.stopPropagation(); + const { cardIndex } = parseLovelaceCardPath(ev.detail.path); + showEditCardDialog(this, { + lovelaceConfig: this.lovelace.config, + saveConfig: this.lovelace.saveConfig, + path: [this.viewIndex, this.index], + cardIndex, + }); + }); + this._layoutElement.addEventListener("ll-delete-card", (ev) => { + ev.stopPropagation(); + if (ev.detail.confirm) { + confDeleteCard(this, this.hass!, this.lovelace!, ev.detail.path); + } else { + const newLovelace = deleteCard(this.lovelace!.config, ev.detail.path); + this.lovelace.saveConfig(newLovelace); + } + }); + } + + private _createCards(config: LovelaceSectionConfig): void { + if (!config || !config.cards || !Array.isArray(config.cards)) { + this._cards = []; + return; + } + + this._cards = config.cards.map((cardConfig) => { + const element = this.createCardElement(cardConfig); + try { + element.hass = this.hass; + } catch (e: any) { + return createErrorCardElement( + createErrorCardConfig(e.message, cardConfig) + ); + } + return element; + }); + } + + private _rebuildCard( + cardElToReplace: LovelaceCard, + config: LovelaceCardConfig + ): void { + let newCardEl = this.createCardElement(config); + try { + newCardEl.hass = this.hass; + } catch (e: any) { + newCardEl = createErrorCardElement( + createErrorCardConfig(e.message, config) + ); + } + if (cardElToReplace.parentElement) { + cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace); + } + this._cards = this._cards!.map((curCardEl) => + curCardEl === cardElToReplace ? newCardEl : curCardEl + ); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-section": HuiSection; + } +} diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index ee62115275..927089dedd 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -12,6 +12,7 @@ import { AsyncReturnType, HomeAssistant } from "../../../types"; import { cleanLegacyStrategyConfig, isLegacyStrategy } from "./legacy-strategy"; import { LovelaceDashboardStrategy, + LovelaceSectionStrategy, LovelaceStrategy, LovelaceViewStrategy, } from "./types"; @@ -27,13 +28,15 @@ const STRATEGIES: Record> = { "original-states": () => import("./original-states-view-strategy"), energy: () => import("../../energy/strategies/energy-view-strategy"), }, + section: {}, }; -export type LovelaceStrategyConfigType = "dashboard" | "view"; +export type LovelaceStrategyConfigType = "dashboard" | "view" | "section"; type Strategies = { dashboard: LovelaceDashboardStrategy; view: LovelaceViewStrategy; + section: LovelaceSectionStrategy; }; type StrategyConfig = AsyncReturnType< @@ -163,6 +166,24 @@ export const generateLovelaceViewStrategy = async ( hass ); +export const generateLovelaceSectionStrategy = async ( + strategyConfig: LovelaceStrategyConfig, + hass: HomeAssistant +): Promise => + generateStrategy( + "section", + (err) => ({ + cards: [ + { + type: "markdown", + content: `Error loading the section strategy:\n> ${err}`, + }, + ], + }), + strategyConfig, + hass + ); + /** * Find all references to strategies and replaces them with the generated output */ @@ -175,11 +196,24 @@ export const expandLovelaceConfigStrategies = async ( : { ...config }; newConfig.views = await Promise.all( - newConfig.views.map((view) => - isStrategyView(view) - ? generateLovelaceViewStrategy(view.strategy, hass) - : view - ) + newConfig.views.map(async (view) => { + const newView = isStrategyView(view) + ? await generateLovelaceViewStrategy(view.strategy, hass) + : { ...view }; + + if (newView.sections) { + newView.sections = await Promise.all( + newView.sections.map(async (section) => { + const newSection = isStrategyView(section) + ? await generateLovelaceSectionStrategy(section.strategy, hass) + : { ...section }; + return newSection; + }) + ); + } + + return newView; + }) ); return newConfig; diff --git a/src/panels/lovelace/strategies/types.ts b/src/panels/lovelace/strategies/types.ts index da2ab8ee3c..ae1be1bbca 100644 --- a/src/panels/lovelace/strategies/types.ts +++ b/src/panels/lovelace/strategies/types.ts @@ -1,5 +1,6 @@ -import { LovelaceConfig } from "../../../data/lovelace/config/types"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy"; +import { LovelaceConfig } from "../../../data/lovelace/config/types"; import { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import { HomeAssistant } from "../../../types"; import { LovelaceGenericElementEditor } from "../types"; @@ -15,6 +16,9 @@ export interface LovelaceDashboardStrategy export interface LovelaceViewStrategy extends LovelaceStrategy {} +export interface LovelaceSectionStrategy + extends LovelaceStrategy {} + export interface LovelaceStrategyEditor extends LovelaceGenericElementEditor { setConfig(config: LovelaceStrategyConfig): void; } diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index b1d42984b8..31a87961f2 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -44,6 +44,7 @@ export interface LovelaceCard extends HTMLElement { isPanel?: boolean; editMode?: boolean; getCardSize(): number | Promise; + getGridSize?(): [number, number]; setConfig(config: LovelaceCardConfig): void; } diff --git a/src/panels/lovelace/views/const.ts b/src/panels/lovelace/views/const.ts index 5cc4709bbb..5633f05bbc 100644 --- a/src/panels/lovelace/views/const.ts +++ b/src/panels/lovelace/views/const.ts @@ -1,4 +1,9 @@ export const DEFAULT_VIEW_LAYOUT = "masonry"; export const PANEL_VIEW_LAYOUT = "panel"; export const SIDEBAR_VIEW_LAYOUT = "sidebar"; -export const VIEWS_NO_BADGE_SUPPORT = [PANEL_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT]; +export const SECTION_VIEW_LAYOUT = "sections"; +export const VIEWS_NO_BADGE_SUPPORT = [ + PANEL_VIEW_LAYOUT, + SIDEBAR_VIEW_LAYOUT, + SECTION_VIEW_LAYOUT, +]; diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts new file mode 100644 index 0000000000..cbf5af8570 --- /dev/null +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -0,0 +1,321 @@ +import { mdiArrowAll, mdiDelete, mdiPencil, mdiViewGridPlus } from "@mdi/js"; +import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; +import { styleMap } from "lit/directives/style-map"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-sortable"; +import "../../../components/ha-svg-icon"; +import type { LovelaceViewElement } from "../../../data/lovelace"; +import { LovelaceSectionConfig as LovelaceRawSectionConfig } from "../../../data/lovelace/config/section"; +import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; +import { + showConfirmationDialog, + showPromptDialog, +} from "../../../dialogs/generic/show-dialog-box"; +import type { HomeAssistant } from "../../../types"; +import { addSection, deleteSection, moveSection } from "../editor/config-util"; +import { + findLovelaceContainer, + updateLovelaceContainer, +} from "../editor/lovelace-path"; +import { HuiSection } from "../sections/hui-section"; +import type { Lovelace } from "../types"; + +@customElement("hui-sections-view") +export class SectionsView extends LitElement implements LovelaceViewElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public lovelace?: Lovelace; + + @property({ type: Number }) public index?: number; + + @property({ type: Boolean }) public isStrategy = false; + + @property({ attribute: false }) public sections: HuiSection[] = []; + + @state() private _config?: LovelaceViewConfig; + + public setConfig(config: LovelaceViewConfig): void { + this._config = config; + } + + private _sectionConfigKeys = new WeakMap(); + + private _getKey(sectionConfig: LovelaceRawSectionConfig) { + if (!this._sectionConfigKeys.has(sectionConfig)) { + this._sectionConfigKeys.set(sectionConfig, Math.random().toString()); + } + return this._sectionConfigKeys.get(sectionConfig)!; + } + + protected render() { + if (!this.lovelace) return nothing; + + const sectionsConfig = this._config?.sections ?? []; + + const editMode = this.lovelace.editMode; + + return html` + +
    + ${repeat( + sectionsConfig, + (sectionConfig) => this._getKey(sectionConfig), + (_sectionConfig, idx) => { + const section = this.sections[idx]; + (section as any).itemPath = [idx]; + return html` +
    + ${editMode + ? html` +
    +
    + + + +
    +
    + ` + : nothing} +
    ${section}
    +
    + `; + } + )} + ${editMode + ? html` + + ` + : nothing} +
    +
    + `; + } + + private _addSection(): void { + const newConfig = addSection(this.lovelace!.config, this.index!, { + type: "grid", + cards: [], + }); + this.lovelace!.saveConfig(newConfig); + } + + private async _editSection(ev) { + const index = ev.currentTarget.index; + + const path = [this.index!, index] as [number, number]; + + const section = findLovelaceContainer( + this.lovelace!.config, + path + ) as LovelaceRawSectionConfig; + + const newTitle = !section.title; + + const title = await showPromptDialog(this, { + title: this.hass.localize( + `ui.panel.lovelace.editor.edit_section_title.${newTitle ? "title_new" : "title"}` + ), + inputLabel: this.hass.localize( + "ui.panel.lovelace.editor.edit_section_title.input_label" + ), + inputType: "string", + defaultValue: section.title, + confirmText: newTitle + ? this.hass.localize("ui.common.add") + : this.hass.localize("ui.common.save"), + }); + + if (title === null) { + return; + } + + const newConfig = updateLovelaceContainer(this.lovelace!.config, path, { + ...section, + title: title || undefined, + }); + + this.lovelace!.saveConfig(newConfig); + } + + private async _deleteSection(ev) { + const index = ev.currentTarget.index; + + const path = [this.index!, index] as [number, number]; + + const section = findLovelaceContainer( + this.lovelace!.config, + path + ) as LovelaceRawSectionConfig; + + const title = section.title?.trim(); + const cardCount = section.cards?.length; + + if (title || cardCount) { + const named = title ? "named" : "unnamed"; + const type = cardCount ? "cards" : "only"; + + const confirm = await showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.lovelace.editor.delete_section.title" + ), + text: this.hass.localize( + `ui.panel.lovelace.editor.delete_section.text_${named}_section_${type}`, + { name: title } + ), + confirmText: this.hass.localize("ui.common.delete"), + destructive: true, + }); + + if (!confirm) return; + } + + const newConfig = deleteSection(this.lovelace!.config, this.index!, index); + this.lovelace!.saveConfig(newConfig); + } + + private _sectionMoved(ev: CustomEvent) { + ev.stopPropagation(); + const { oldIndex, newIndex } = ev.detail; + + const newConfig = moveSection( + this.lovelace!.config, + [this.index!, oldIndex], + [this.index!, newIndex] + ); + this.lovelace!.saveConfig(newConfig); + } + + static get styles(): CSSResultGroup { + return css` + :host { + display: block; + } + + .section { + position: relative; + border-radius: var(--ha-card-border-radius, 12px); + } + + .container { + --grid-gap: 20px; + --grid-max-width: 1400px; + --grid-cell-max-width: 500px; + --grid-cell-min-width: 320px; + display: grid; + grid-template-columns: repeat( + auto-fit, + minmax(var(--grid-cell-min-width), 1fr) + ); + justify-content: center; + gap: 8px var(--grid-gap); + padding: var(--grid-gap); + box-sizing: border-box; + max-width: min( + calc( + var(--cell-count) * (var(--grid-cell-max-width) + var(--grid-gap)) + + var(--grid-gap) + ), + var(--grid-max-width) + ); + margin: 0 auto; + } + + @media (max-width: 600px) { + .container { + grid-template-columns: 1fr; + --grid-gap: 8px; + } + } + + .section-actions { + position: absolute; + top: 0; + right: 0; + opacity: 1; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.2s ease-in-out; + background-color: rgba(var(--rgb-card-background-color), 0.3); + border-radius: 18px; + background: var(--secondary-background-color); + --mdc-icon-button-size: 36px; + --mdc-icon-size: 20px; + color: var(--primary-text-color); + } + + .handle { + cursor: grab; + padding: 8px; + } + + .add { + margin-top: calc(66px + 8px); + outline: none; + background: none; + cursor: pointer; + border-radius: var(--ha-card-border-radius, 12px); + border: 2px dashed var(--primary-color); + order: 1; + height: 66px; + padding: 8px; + box-sizing: content-box; + } + + .add:focus { + border: 2px solid var(--primary-color); + } + + .sortable-ghost { + border-radius: var(--ha-card-border-radius, 12px); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-sections-view": SectionsView; + } +} diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 1d78c5e7a8..6d35d6a715 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -1,9 +1,17 @@ import { PropertyValues, ReactiveElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-state-label-badge"; import "../../../components/ha-svg-icon"; import type { LovelaceViewElement } from "../../../data/lovelace"; +import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; +import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; +import { + LovelaceViewConfig, + isStrategyView, +} from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; import { createErrorBadgeConfig, @@ -20,24 +28,30 @@ import { import { createViewElement } from "../create-element/create-view-element"; import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog"; import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; -import { confDeleteCard } from "../editor/delete-card"; import { deleteCard } from "../editor/config-util"; +import { confDeleteCard } from "../editor/delete-card"; +import { + LovelaceCardPath, + parseLovelaceCardPath, +} from "../editor/lovelace-path"; +import { createErrorSectionConfig } from "../sections/hui-error-section"; +import "../sections/hui-section"; +import type { HuiSection } from "../sections/hui-section"; import { generateLovelaceViewStrategy } from "../strategies/get-strategy"; import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types"; -import { PANEL_VIEW_LAYOUT, DEFAULT_VIEW_LAYOUT } from "./const"; -import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; -import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; -import { - LovelaceViewConfig, - isStrategyView, -} from "../../../data/lovelace/config/view"; +import { DEFAULT_VIEW_LAYOUT, PANEL_VIEW_LAYOUT } from "./const"; declare global { // for fire event interface HASSDomEvents { - "ll-create-card": undefined; - "ll-edit-card": { path: [number] | [number, number] }; - "ll-delete-card": { path: [number] | [number, number]; confirm: boolean }; + "ll-create-card": { suggested?: string[] } | undefined; + "ll-edit-card": { path: LovelaceCardPath }; + "ll-delete-card": { path: LovelaceCardPath; confirm: boolean }; + } + interface HTMLElementEventMap { + "ll-create-card": HASSDomEvent; + "ll-edit-card": HASSDomEvent; + "ll-delete-card": HASSDomEvent; } } @@ -55,6 +69,8 @@ export class HUIView extends ReactiveElement { @state() private _badges: LovelaceBadge[] = []; + @state() private _sections: HuiSection[] = []; + private _layoutElementType?: string; private _layoutElement?: LovelaceViewElement; @@ -102,6 +118,27 @@ export class HUIView extends ReactiveElement { return element; } + // Public to make demo happy + public createSectionElement(sectionConfig: LovelaceSectionConfig) { + const element = document.createElement("hui-section"); + element.hass = this.hass; + element.lovelace = this.lovelace; + element.config = sectionConfig; + element.viewIndex = this.index; + element.addEventListener( + "ll-rebuild", + (ev: Event) => { + // In edit mode let it go to hui-root and rebuild whole view. + if (!this.lovelace!.editMode) { + ev.stopPropagation(); + this._rebuildSection(element, sectionConfig); + } + }, + { once: true } + ); + return element; + } + protected createRenderRoot() { return this; } @@ -133,7 +170,7 @@ export class HUIView extends ReactiveElement { } } - protected update(changedProperties) { + protected update(changedProperties: PropertyValues) { super.update(changedProperties); // If no layout element, we're still creating one @@ -156,6 +193,14 @@ export class HUIView extends ReactiveElement { } }); + this._sections.forEach((element) => { + try { + element.hass = this.hass; + } catch (e: any) { + this._rebuildSection(element, createErrorSectionConfig(e.message)); + } + }); + this._layoutElement.hass = this.hass; const oldHass = changedProperties.get("hass") as @@ -175,6 +220,14 @@ export class HUIView extends ReactiveElement { } if (changedProperties.has("lovelace")) { this._layoutElement.lovelace = this.lovelace; + this._sections.forEach((element) => { + try { + element.hass = this.hass; + element.lovelace = this.lovelace; + } catch (e: any) { + this._rebuildSection(element, createErrorSectionConfig(e.message)); + } + }); } if (changedProperties.has("_cards")) { this._layoutElement.cards = this._cards; @@ -214,6 +267,7 @@ export class HUIView extends ReactiveElement { this._createBadges(viewConfig); this._createCards(viewConfig); + this._createSections(viewConfig); this._layoutElement!.isStrategy = isStrategy; this._layoutElement!.hass = this.hass; this._layoutElement!.narrow = this.narrow; @@ -221,6 +275,7 @@ export class HUIView extends ReactiveElement { this._layoutElement!.index = this.index; this._layoutElement!.cards = this._cards; this._layoutElement!.badges = this._badges; + this._layoutElement!.sections = this._sections; applyThemesOnElement(this, this.hass.themes, viewConfig.theme); this._viewConfigTheme = viewConfig.theme; @@ -236,18 +291,21 @@ export class HUIView extends ReactiveElement { private _createLayoutElement(config: LovelaceViewConfig): void { this._layoutElement = createViewElement(config) as LovelaceViewElement; this._layoutElementType = config.type; - this._layoutElement.addEventListener("ll-create-card", () => { + this._layoutElement.addEventListener("ll-create-card", (ev) => { showCreateCardDialog(this, { lovelaceConfig: this.lovelace.config, saveConfig: this.lovelace.saveConfig, path: [this.index], + suggestedCards: ev.detail?.suggested, }); }); this._layoutElement.addEventListener("ll-edit-card", (ev) => { + const { cardIndex } = parseLovelaceCardPath(ev.detail.path); showEditCardDialog(this, { lovelaceConfig: this.lovelace.config, saveConfig: this.lovelace.saveConfig, - path: ev.detail.path, + path: [this.index], + cardIndex, }); }); this._layoutElement.addEventListener("ll-delete-card", (ev) => { @@ -297,6 +355,19 @@ export class HUIView extends ReactiveElement { }); } + private _createSections(config: LovelaceViewConfig): void { + if (!config || !config.sections || !Array.isArray(config.sections)) { + this._sections = []; + return; + } + + this._sections = config.sections.map((sectionConfig, index) => { + const element = this.createSectionElement(sectionConfig); + element.index = index; + return element; + }); + } + private _rebuildCard( cardElToReplace: LovelaceCard, config: LovelaceCardConfig @@ -337,6 +408,23 @@ export class HUIView extends ReactiveElement { curBadgeEl === badgeElToReplace ? newBadgeEl : curBadgeEl ); } + + private _rebuildSection( + sectionElToReplace: HuiSection, + config: LovelaceSectionConfig + ): void { + const newSectionEl = this.createSectionElement(config); + newSectionEl.index = sectionElToReplace.index; + if (sectionElToReplace.parentElement) { + sectionElToReplace.parentElement!.replaceChild( + newSectionEl, + sectionElToReplace + ); + } + this._sections = this._sections!.map((curSectionEl) => + curSectionEl === sectionElToReplace ? newSectionEl : curSectionEl + ); + } } declare global { diff --git a/src/panels/mailbox/ha-panel-mailbox.ts b/src/panels/mailbox/ha-panel-mailbox.ts index 643112c7bd..250af9a83a 100644 --- a/src/panels/mailbox/ha-panel-mailbox.ts +++ b/src/panels/mailbox/ha-panel-mailbox.ts @@ -12,8 +12,6 @@ import { formatDateTime } from "../../common/datetime/format_date_time"; import "../../components/ha-card"; import "../../components/ha-menu-button"; import "../../components/ha-tabs"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-tabs/paper-tab"; import { HomeAssistant } from "../../types"; import { fireEvent } from "../../common/dom/fire_event"; @@ -82,25 +80,24 @@ class HaPanelMailbox extends LitElement { : nothing} ${this._messages?.map( (message) => - html` - -
    -
    ${message.caller}
    -
    - ${formatDuration(this.hass.locale, { - seconds: message.duration, - })} -
    -
    -
    - ${message.timestamp} - - ${message.message} -
    -
    -
    ` + + ${message.caller} + + ${formatDuration(this.hass.locale, { + seconds: message.duration, + })} + + + + ${message.timestamp} - + ${message.message} + + ` )}
    @@ -219,10 +216,6 @@ class HaPanelMailbox extends LitElement { overflow: hidden; } - paper-item { - cursor: pointer; - } - ha-tabs { margin-left: max(env(safe-area-inset-left), 24px); margin-right: max(env(safe-area-inset-right), 24px); diff --git a/src/panels/map/ha-panel-map.ts b/src/panels/map/ha-panel-map.ts index b7698ca186..d6c1cbd1fb 100644 --- a/src/panels/map/ha-panel-map.ts +++ b/src/panels/map/ha-panel-map.ts @@ -46,7 +46,7 @@ class HaPanelMap extends LitElement { } private _openZonesEditor() { - navigate("/config/zone"); + navigate("/config/zone?historyBack=1"); } public willUpdate(changedProps: PropertyValues) { diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 8713bed2a4..c5bc566f07 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -343,7 +343,6 @@ class PanelMediaBrowser extends LitElement { ha-media-player-browse { height: calc(100vh - (100px + var(--header-height))); - direction: ltr; } :host([narrow]) ha-media-player-browse { diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index bac84192a2..100eeddf59 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -258,7 +258,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ }, supervisor_logs: { // Moved from Supervisor panel in 2022.5 - redirect: "/config/logs", + redirect: "/config/logs?provider=supervisor", }, supervisor_info: { // Moved from Supervisor panel in 2022.5 diff --git a/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts b/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts index 0ca1b026e6..c7bad0fa14 100644 --- a/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts +++ b/src/panels/profile/dialog-ha-mfa-module-setup-flow.ts @@ -97,7 +97,7 @@ class HaMfaModuleSetupFlow extends LitElement { ? html`

    ${this.hass.localize( "ui.panel.profile.mfa_setup.step_done", - { step: this._step.title } + { step: this._step.title || this._step.handler } )}

    ` : this._step.type === "form" diff --git a/src/panels/profile/ha-long-lived-access-token-dialog.ts b/src/panels/profile/ha-long-lived-access-token-dialog.ts index 4dd9fe1298..904680a2d1 100644 --- a/src/panels/profile/ha-long-lived-access-token-dialog.ts +++ b/src/panels/profile/ha-long-lived-access-token-dialog.ts @@ -74,7 +74,9 @@ export class HaLongLivedAccessTokenDialog extends LitElement { ? this._qrCode : html` - Generate QR code + ${this.hass.localize( + "ui.panel.profile.long_lived_access_tokens.generate_qr_code" + )} `}
    diff --git a/src/state-summary/state-card-lock.ts b/src/state-summary/state-card-lock.ts index c35c4e3007..117a7c992b 100644 --- a/src/state-summary/state-card-lock.ts +++ b/src/state-summary/state-card-lock.ts @@ -11,7 +11,7 @@ import { import { customElement, property } from "lit/decorators"; import { supportsFeature } from "../common/entity/supports-feature"; import "../components/entity/state-info"; -import { LockEntityFeature } from "../data/lock"; +import { callProtectedLockService, LockEntityFeature } from "../data/lock"; import { HomeAssistant } from "../types"; import { haStyle } from "../resources/styles"; @@ -56,10 +56,10 @@ class StateCardLock extends LitElement { private async _callService(ev) { ev.stopPropagation(); const service = ev.target.dataset.service; - const data = { - entity_id: this.stateObj.entity_id, - }; - await this.hass.callService("lock", service, data); + if (!this.hass || !this.stateObj) { + return; + } + await callProtectedLockService(this, this.hass, this.stateObj, service); } static get styles(): CSSResultGroup { diff --git a/src/state-summary/state-card-script.ts b/src/state-summary/state-card-script.ts index f8014684e7..fc375d8d25 100644 --- a/src/state-summary/state-card-script.ts +++ b/src/state-summary/state-card-script.ts @@ -8,6 +8,8 @@ import { isUnavailableState } from "../data/entity"; import { canRun, ScriptEntity } from "../data/script"; import { haStyle } from "../resources/styles"; import { HomeAssistant } from "../types"; +import { computeObjectId } from "../common/entity/compute_object_id"; +import { showMoreInfoDialog } from "../dialogs/more-info/show-ha-more-info-dialog"; @customElement("state-card-script") class StateCardScript extends LitElement { @@ -56,7 +58,16 @@ class StateCardScript extends LitElement { private _runScript(ev: Event) { ev.stopPropagation(); - this._callService("turn_on"); + + const fields = + this.hass!.services.script[computeObjectId(this.stateObj.entity_id)] + ?.fields; + + if (fields && Object.keys(fields).length > 0) { + showMoreInfoDialog(this, { entityId: this.stateObj.entity_id }); + } else { + this._callService("turn_on"); + } } private _callService(service: string): void { diff --git a/src/translations/en.json b/src/translations/en.json index cbeaab0b43..24f63e86a3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -218,8 +218,14 @@ }, "script": { "run": "[%key:ui::card::service::run%]", + "running_single": "Running…", + "running_queued": "{queued} queued", + "running_parallel": "{active} Running…", "cancel": "Cancel", - "cancel_multiple": "Cancel {number}" + "cancel_multiple": "Cancel {number}", + "cancel_all": "Cancel all", + "idle": "Idle", + "run_script": "Run script" }, "service": { "run": "Run" @@ -317,6 +323,7 @@ "refresh": "Refresh", "cancel": "Cancel", "delete": "Delete", + "download": "[%key:supervisor::backup::download%]", "duplicate": "Duplicate", "remove": "Remove", "enable": "Enable", @@ -1370,7 +1377,11 @@ "add_option": "[%key:ui::panel::config::automation::editor::actions::type::choose::add_option%]", "remove_option": "[%key:ui::panel::config::automation::editor::actions::type::choose::remove_option%]", "no_options": "There are no options yet.", - "add": "Add" + "add": "Add", + "confirm_delete": { + "delete": "[%key:ui::components::todo::item::confirm_delete::delete%]", + "prompt": "[%key:ui::components::todo::item::confirm_delete::prompt%]" + } }, "counter": { "minimum": "Minimum value", @@ -2168,7 +2179,17 @@ "show_full_logs": "Show full logs", "download_full_log": "Download full log", "provider_not_found": "Log provider not found", - "provider_not_available": "Logs for ''{provider}'' are not available on your system." + "provider_not_available": "Logs for ''{provider}'' are not available on your system.", + "detail": { + "logger": "Logger", + "source": "Source", + "integration": "[%key:ui::panel::config::integrations::integration%]", + "documentation": "documentation", + "issues": "issues", + "first_occurred": "First occurred", + "occurrences": "occurrences", + "last_logged": "Last logged" + } }, "lovelace": { "caption": "Dashboards", @@ -2347,7 +2368,21 @@ "no_runs_found": "No runs found", "older_run": "Older run", "newer_run": "Newer run", - "start_debug_run": "Start debug run" + "start_debug_run": "Start debug run", + "pipeline": { + "header": "Assist Pipeline", + "run_text_pipeline": "Run text pipeline", + "run_audio_pipeline": "Run audio pipeline", + "run_audio_with_wake": "Run audio pipeline with wake word detection", + "response": "[%key:ui::panel::developer-tools::tabs::services::response%]", + "send": "Send", + "continue_listening": "Continue listening for wake word", + "continue_talking": "Continue talking", + "continue_conversation": "Continue conversation", + "input_text": "Input text", + "run": "[%key:ui::card::service::run%]", + "error_starting": "Error starting pipeline" + } }, "expose": { "caption": "Expose", @@ -2478,7 +2513,7 @@ "triggers": { "name": "Triggers", "header": "When", - "description": "This list of triggers is what starts your automation. A trigger is a specific event happening in or around your home, for example: ''When the sun sets''.", + "description": "A trigger is a specific event happening in or around your home, for example: ''When the sun sets''. Any trigger listed here will start your automation.", "learn_more": "Learn more about triggers", "triggered": "Triggered", "add": "Add trigger", @@ -3479,7 +3514,12 @@ "instance_will_be_available": "Your instance will be available at your", "link_learn_how_it_works": "Learn how it works", "nabu_casa_url": "Nabu Casa URL", - "certificate_info": "Certificate info" + "advanced_options": "Advanced options", + "external_activation": "Allow external activation of remote control", + "external_activation_secondary": "Allows you to turn on remote control from your Nabu Casa account page, even if you're outside your local network", + "certificate_info": "Certificate info", + "certificate_expire": "Will be renewed at {date}", + "more_info": "More info" }, "alexa": { "title": "Alexa", @@ -4098,6 +4138,7 @@ "other_networks": "Other networks", "my_network": "Preferred network", "no_preferred_network": "You don't have a preferred network yet.", + "more_info": "More Info", "add_open_thread_border_router": "Add an OpenThread border router", "reset_border_router": "Reset border router", "add_to_my_network": "Add to preferred network", @@ -4997,10 +5038,13 @@ "saving_failed": "Saving dashboard configuration failed." }, "views": { - "confirm_delete": "Delete view?", - "confirm_delete_text": "Are you sure you want to delete your ''{name}'' view?", - "confirm_delete_existing_cards": "Deleting this view will also remove the cards", - "confirm_delete_existing_cards_text": "Are you sure you want to delete your ''{name}'' view? The view contains {number} cards that will be deleted. This action cannot be undone." + "delete_title": "Delete view", + "delete_named_view_only": "''{name}'' view will be deleted.", + "delete_unnamed_view_only": "This view will be deleted.", + "delete_named_view_cards": "''{name}'' view and all its cards will be deleted.", + "delete_unnamed_view_cards": "This view and all its cards will be deleted.", + "delete_named_view_sections": "''{name}'' view and all its sections will be deleted.", + "delete_unnamed_view_sections": "This view and all its sections will be deleted." }, "menu": { "configure_ui": "Edit dashboard", @@ -5068,10 +5112,14 @@ "select_users": "Select which users should see this view in the navigation" }, "type": "View type", + "type_helper_sections": "You can not change your view to use the 'sections' view type, because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.", + "type_helper_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.", + "types": { "masonry": "Masonry (default)", "sidebar": "Sidebar", - "panel": "Panel (1 card)" + "panel": "Panel (1 card)", + "sections": "Sections (experimental)" }, "subview": "Subview", "subview_helper": "Subviews don't appear in tabs and have a back button.", @@ -5087,7 +5135,7 @@ "header": "Card configuration", "typed_header": "{type} Card configuration", "pick_card": "Which card would you like to add?", - "pick_card_view_title": "Which card would you like to add to your {name} view?", + "pick_card_title": "Which card would you like to add to {name}", "toggle_editor": "Toggle editor", "unsaved_changes": "You have unsaved changes", "confirm_cancel": "Are you sure you want to cancel?", @@ -5125,6 +5173,23 @@ "no_config": "No config found.", "no_views": "No views in this dashboard." }, + "section": { + "unnamed_section": "Unnamed section", + "add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]", + "add_section": "Add section" + }, + "delete_section": { + "title": "Delete section", + "text_named_section_only": "''{name}'' section will be deleted.", + "text_unnamed_section_only": "This section will be deleted.", + "text_named_section_cards": "''{name}'' section and all its cards will be deleted.", + "text_unnamed_section_cards": "This section and all its cards will be deleted." + }, + "edit_section_title": { + "title": "Edit name", + "title_new": "Add name", + "input_label": "Name" + }, "suggest_card": { "header": "We created a suggestion for you", "create_own": "Pick different card", @@ -5430,7 +5495,10 @@ "state": "State", "secondary_info_attribute": "Secondary info attribute", "search": "Search", - "state_color": "Color icons based on state?" + "state_color": "Color icons based on state?", + "suggested_cards": "Suggested cards", + "other_cards": "Other cards", + "custom_cards": "Custom cards" }, "map": { "name": "Map", @@ -5600,6 +5668,15 @@ }, "preset_modes": "Preset modes" }, + "fan-preset-modes": { + "label": "Fan preset modes", + "style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]", + "style_list": { + "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", + "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" + }, + "preset_modes": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::preset_modes%]" + }, "humidifier-toggle": { "label": "Humidifier toggle" }, @@ -5951,7 +6028,8 @@ "prompt_name": "Give the token a name", "prompt_copy_token": "Copy your access token. It will not be shown again.", "empty_state": "You have no long-lived access tokens yet.", - "qr_code_image": "QR code for token {name}" + "qr_code_image": "QR code for token {name}", + "generate_qr_code": "Generate QR Code" } }, "todo": { @@ -6288,6 +6366,7 @@ "end": "End", "new_value": "New value", "adjust": "Adjust", + "outliers": "Outliers", "sum_adjusted": "Statistic sum adjusted", "error_sum_adjusted": "Error adjusting sum: {message}" } @@ -6478,6 +6557,7 @@ } }, "energy": { + "download_data": "[%key:ui::panel::history::download_data%]", "configure": "[%key:ui::dialogs::quick-bar::commands::navigation::energy%]", "compare": { "info": "You are comparing the period {start} with the period {end}" @@ -6500,7 +6580,8 @@ "energy_water_graph_title": "Water consumption", "energy_distribution_title": "Energy distribution", "energy_sources_table_title": "Sources", - "energy_devices_graph_title": "Monitor individual devices" + "energy_devices_graph_title": "Individual devices total usage", + "energy_devices_detail_graph_title": "Individual devices detail usage" } }, "history": { diff --git a/test/panels/lovelace/editor/config-util.spec.ts b/test/panels/lovelace/editor/config-util.spec.ts index a437b28cfa..b625636736 100644 --- a/test/panels/lovelace/editor/config-util.spec.ts +++ b/test/panels/lovelace/editor/config-util.spec.ts @@ -1,63 +1,12 @@ import { assert } from "chai"; +import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; import { - swapCard, - moveCard, + moveCardToContainer, swapView, } from "../../../../src/panels/lovelace/editor/config-util"; -import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; -describe("swapCard", () => { - it("swaps 2 cards in same view", () => { - const config: LovelaceConfig = { - views: [ - {}, - { - cards: [{ type: "card1" }, { type: "card2" }], - }, - ], - }; - - const result = swapCard(config, [1, 0], [1, 1]); - const expected = { - views: [ - {}, - { - cards: [{ type: "card2" }, { type: "card1" }], - }, - ], - }; - assert.deepEqual(expected, result); - }); - - it("swaps 2 cards in different views", () => { - const config: LovelaceConfig = { - views: [ - { - cards: [{ type: "v1-c1" }, { type: "v1-c2" }], - }, - { - cards: [{ type: "v2-c1" }, { type: "v2-c2" }], - }, - ], - }; - - const result = swapCard(config, [0, 0], [1, 1]); - const expected: LovelaceConfig = { - views: [ - { - cards: [{ type: "v2-c2" }, { type: "v1-c2" }], - }, - { - cards: [{ type: "v2-c1" }, { type: "v1-c1" }], - }, - ], - }; - assert.deepEqual(expected, result); - }); -}); - -describe("moveCard", () => { +describe("moveCardToContainer", () => { it("move a card to an empty view", () => { const config: LovelaceConfig = { views: [ @@ -68,7 +17,7 @@ describe("moveCard", () => { ], }; - const result = moveCard(config, [1, 0], [0]); + const result = moveCardToContainer(config, [1, 0], [0]); const expected: LovelaceConfig = { views: [ { @@ -94,7 +43,7 @@ describe("moveCard", () => { ], }; - const result = moveCard(config, [1, 0], [0]); + const result = moveCardToContainer(config, [1, 0], [0]); const expected: LovelaceConfig = { views: [ { @@ -121,12 +70,12 @@ describe("moveCard", () => { }; const result = () => { - moveCard(config, [1, 0], [1]); + moveCardToContainer(config, [1, 0], [1]); }; assert.throws( result, Error, - "You cannot move a card to the view it is in." + "You cannot move a card to the view or section it is in." ); }); }); diff --git a/tsconfig.json b/tsconfig.json index 72918ea5ad..83c5f840ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,13 +42,13 @@ // LitElement "no-property-visibility-mismatch": "error", // CSS - "no-invalid-css": "off", // warning does not work + "no-invalid-css": "off" // warning does not work }, "globalTags": ["google-cast-launcher"], "customHtmlData": [ - "./node_modules/@lrnwebcomponents/simple-tooltip/custom-elements.json", - ], - }, - ], - }, + "./node_modules/@lrnwebcomponents/simple-tooltip/custom-elements.json" + ] + } + ] + } } diff --git a/yarn.lock b/yarn.lock index a42f4f1a04..874b6ffc71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -129,8 +129,8 @@ __metadata: linkType: hard "@babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.23.6, @babel/helper-create-class-features-plugin@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/helper-create-class-features-plugin@npm:7.23.9" + version: 7.23.10 + resolution: "@babel/helper-create-class-features-plugin@npm:7.23.10" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.22.5" "@babel/helper-environment-visitor": "npm:^7.22.20" @@ -143,7 +143,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/91c8aa8888780bd90aa50f511917cb0953ccd61b2ea4abf61915c1d68d99bb14b472969a8ae5b391d7890759dfc22be79104297be07919c38351714a4ce2fe74 + checksum: 10/8b9f02526eeb03ef1d2bc89e3554377ae966b33a74078ab1f88168dfa725dc206ea5ecf4cf417c3651d8a6b3c70204f6939a9aa0401be3d0d32ddbf6024ea3c7 languageName: node linkType: hard @@ -1447,12 +1447,12 @@ __metadata: languageName: node linkType: hard -"@bundle-stats/plugin-webpack-filter@npm:4.9.2": - version: 4.9.2 - resolution: "@bundle-stats/plugin-webpack-filter@npm:4.9.2" +"@bundle-stats/plugin-webpack-filter@npm:4.10.1": + version: 4.10.1 + resolution: "@bundle-stats/plugin-webpack-filter@npm:4.10.1" peerDependencies: core-js: ^3.0.0 - checksum: 10/d57aeff530a1af099929f67d12402f5a563e018333d86de159af2c1714274b040de9e1a9f6dad1b3bbee6b7af0cde6f9668a368138f27ac13d34642beba8e2f0 + checksum: 10/f389188a27a01b8c353bb8b90a3c9fe1eabf4c24ed9f28c52e4063204780490dfd73df109d55ac0b0331a82b61705c0f3dac68b04fe50fcbd0e4e2efff2eda89 languageName: node linkType: hard @@ -1508,32 +1508,32 @@ __metadata: languageName: node linkType: hard -"@codemirror/search@npm:6.5.5": - version: 6.5.5 - resolution: "@codemirror/search@npm:6.5.5" +"@codemirror/search@npm:6.5.6": + version: 6.5.6 + resolution: "@codemirror/search@npm:6.5.6" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.0.0" crelt: "npm:^1.0.5" - checksum: 10/61707efa563edaea1d83f0680db63a953f3f1c125e50bd912d84d07129aa5ba1f3a775f62339f7931c01f5afd69db777c0571215bd0aaef51964c37ee932f6df + checksum: 10/6668a34b4617e909617d3d831627d74b7a7985e8cd86d396bfcb3e86262f2310fc029fd6c846f1b8f1e6768e75985c9f1b0b18b31e05341f06b5b75c1ffde38d languageName: node linkType: hard -"@codemirror/state@npm:6.4.0, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0": - version: 6.4.0 - resolution: "@codemirror/state@npm:6.4.0" - checksum: 10/d9129c456d1589ca376594620bad10c51d3dcdb57950f34637cea0e2ea073a695d426dc1cfc9b909b07365c236a6312da1eaf740c384c853009742493b8c9935 +"@codemirror/state@npm:6.4.1, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0": + version: 6.4.1 + resolution: "@codemirror/state@npm:6.4.1" + checksum: 10/a9ec56c7d7d52034ce8ebea3a9a4d216b9e972d701b32b5000e56c97790d0d46af129aeba0b80bed36648b4024b3ba3e4910cf5bfed11de4a9e89252e0707a70 languageName: node linkType: hard -"@codemirror/view@npm:6.23.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0": - version: 6.23.1 - resolution: "@codemirror/view@npm:6.23.1" +"@codemirror/view@npm:6.24.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0": + version: 6.24.1 + resolution: "@codemirror/view@npm:6.24.1" dependencies: "@codemirror/state": "npm:^6.4.0" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/42e6b73bcad6bf5d2e9578c54d166c63c4b1c7c7c7806b6f6b4bead8683dc7fcca52201a02a1f9b8ccf120a4ad87e7dcd68f09d9d3e416304dad41a75e20da82 + checksum: 10/66428da341241a865f75c88c4e2984234919ae46a76bba24822867166ba3d15a9df0235b120c491a5ffacdfed7f5d748cae083e19c48656ee9b93c40a5f3c7e1 languageName: node linkType: hard @@ -1597,10 +1597,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.56.0": - version: 8.56.0 - resolution: "@eslint/js@npm:8.56.0" - checksum: 10/97a4b5ccf7e24f4d205a1fb0f21cdcd610348ecf685f6798a48dd41ba443f2c1eedd3050ff5a0b8f30b8cf6501ab512aa9b76e531db15e59c9ebaa41f3162e37 +"@eslint/js@npm:8.57.0": + version: 8.57.0 + resolution: "@eslint/js@npm:8.57.0" + checksum: 10/3c501ce8a997cf6cbbaf4ed358af5492875e3550c19b9621413b82caa9ae5382c584b0efa79835639e6e0ddaa568caf3499318e5bdab68643ef4199dce5eb0a0 languageName: node linkType: hard @@ -1749,60 +1749,60 @@ __metadata: languageName: node linkType: hard -"@fullcalendar/core@npm:6.1.10": - version: 6.1.10 - resolution: "@fullcalendar/core@npm:6.1.10" +"@fullcalendar/core@npm:6.1.11": + version: 6.1.11 + resolution: "@fullcalendar/core@npm:6.1.11" dependencies: preact: "npm:~10.12.1" - checksum: 10/0ca26aefd3e553dac9019838e46ab0e73f7df4954f4d2cbbda4ad73f4e8b9889e4a016cdcc7ae5f548d08d147d6315dffede51f8e213eff5ef63f2dcca2d759f + checksum: 10/7624841879d34fdf769c7610e95cef820ea52eb45c74d51be426bb2acc05cb57806785fc1b1a1282b156470c1f04c976314327fbff63e1b115b7108649aeb1c8 languageName: node linkType: hard -"@fullcalendar/daygrid@npm:6.1.10, @fullcalendar/daygrid@npm:~6.1.10": - version: 6.1.10 - resolution: "@fullcalendar/daygrid@npm:6.1.10" +"@fullcalendar/daygrid@npm:6.1.11, @fullcalendar/daygrid@npm:~6.1.11": + version: 6.1.11 + resolution: "@fullcalendar/daygrid@npm:6.1.11" peerDependencies: - "@fullcalendar/core": ~6.1.10 - checksum: 10/6c58038c1a5b6ba439f0b5d3fb9d53e7fabbf23039d18d6d60036acceee399cd1cbf6038d4b8973ee4c54544029fb940d29ff7d26472a332248e24a6e5969bf6 + "@fullcalendar/core": ~6.1.11 + checksum: 10/c6165c22405d0fee19b2e4ac54b35115c8bc7e176e3ef60f06b1d7af93d4da1fa571acbd169faa3ea4743c55f6374c6e7f7cc774026a1ae0b83d80db33c4b391 languageName: node linkType: hard -"@fullcalendar/interaction@npm:6.1.10": - version: 6.1.10 - resolution: "@fullcalendar/interaction@npm:6.1.10" +"@fullcalendar/interaction@npm:6.1.11": + version: 6.1.11 + resolution: "@fullcalendar/interaction@npm:6.1.11" peerDependencies: - "@fullcalendar/core": ~6.1.10 - checksum: 10/890f7809e4587dc8aa37292d29806a932214848ce1de42092e7290d62206ee785957dc77468bf6b741ca2948cc0e8563bb4bc2397d83d52c440bb2b054da06f2 + "@fullcalendar/core": ~6.1.11 + checksum: 10/6d669d59f20615b71d42d0d053af39d512e824381d575554e016c9221f3f98e17b7770341711322f8455a9c4729450479e2cf4bf026ff123540bf671c6f59f33 languageName: node linkType: hard -"@fullcalendar/list@npm:6.1.10": - version: 6.1.10 - resolution: "@fullcalendar/list@npm:6.1.10" +"@fullcalendar/list@npm:6.1.11": + version: 6.1.11 + resolution: "@fullcalendar/list@npm:6.1.11" peerDependencies: - "@fullcalendar/core": ~6.1.10 - checksum: 10/773830aa2b67c7e10e38b64b9ecd9819901067dd6fd7c78c1ec840830c07166ca1280edd57b746a635ed93cbdbb732a8a5f2cf1725e9ba4d668e30765f61f337 + "@fullcalendar/core": ~6.1.11 + checksum: 10/13bb92c7ca4c7808ec57c1ada55a724ff7e8928bb118e1d9bbaf20fab8c6fb2486101e37b9422dc88ef50342354d35c436bbfa161b5ff09379a11c317f26c725 languageName: node linkType: hard -"@fullcalendar/luxon3@npm:6.1.10": - version: 6.1.10 - resolution: "@fullcalendar/luxon3@npm:6.1.10" +"@fullcalendar/luxon3@npm:6.1.11": + version: 6.1.11 + resolution: "@fullcalendar/luxon3@npm:6.1.11" peerDependencies: - "@fullcalendar/core": ~6.1.10 + "@fullcalendar/core": ~6.1.11 luxon: ^3.0.0 - checksum: 10/575c225cddff677f30d835d84c6957f47ac81b8f2a395f3e0d1d11ea4da75b2479b42431b2a3e16284a1caf5589796633289970eb63143fe9aa3ef818b78213f + checksum: 10/fc302aad0d1b080aac800956921358f10682d8b62d49ff78db85fbc6ea15c8799729679d164d70ac71cee77e0744da55cdfc9b295dcb35e0d6bf673649fd805d languageName: node linkType: hard -"@fullcalendar/timegrid@npm:6.1.10": - version: 6.1.10 - resolution: "@fullcalendar/timegrid@npm:6.1.10" +"@fullcalendar/timegrid@npm:6.1.11": + version: 6.1.11 + resolution: "@fullcalendar/timegrid@npm:6.1.11" dependencies: - "@fullcalendar/daygrid": "npm:~6.1.10" + "@fullcalendar/daygrid": "npm:~6.1.11" peerDependencies: - "@fullcalendar/core": ~6.1.10 - checksum: 10/0351679754f110610a87e1a08b60df3833837f828ede0bb804c07632d765d5061bd03913a06384078e567092ae3071826aa82620b79b7e92fb55b4c878000bcb + "@fullcalendar/core": ~6.1.11 + checksum: 10/b2794502a0aa35c33405e35b05d367abab983d15af568a285123a673414bf545c298f7227b661290f99a2ef37e90013513cab3e9d4d0c15840212cd3b429c99d languageName: node linkType: hard @@ -1815,7 +1815,7 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.11.13": +"@humanwhocodes/config-array@npm:^0.11.14": version: 0.11.14 resolution: "@humanwhocodes/config-array@npm:0.11.14" dependencies: @@ -1999,9 +1999,9 @@ __metadata: linkType: hard "@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.1 - resolution: "@jridgewell/resolve-uri@npm:3.1.1" - checksum: 10/64d59df8ae1a4e74315eb1b61e012f1c7bc8aac47a3a1e683f6fe7008eab07bc512a742b7aa7c0405685d1421206de58c9c2e6adbfe23832f8bd69408ffc183e + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d languageName: node linkType: hard @@ -2040,12 +2040,12 @@ __metadata: linkType: hard "@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.21 - resolution: "@jridgewell/trace-mapping@npm:0.3.21" + version: 0.3.22 + resolution: "@jridgewell/trace-mapping@npm:0.3.22" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/925dda0620887e5a24f11b5a3a106f4e8b1a66155b49be6ceee61432174df33a17c243d8a89b2cd79ccebd281d817878759236a2fc42c47325ae9f73dfbfb90d + checksum: 10/48d3e3db00dbecb211613649a1849876ba5544a3f41cf5e6b99ea1130272d6cf18591b5b67389bce20f1c871b4ede5900c3b6446a7aab6d0a3b2fe806a834db7 languageName: node linkType: hard @@ -2089,11 +2089,11 @@ __metadata: linkType: hard "@lezer/lr@npm:^1.0.0": - version: 1.3.14 - resolution: "@lezer/lr@npm:1.3.14" + version: 1.4.0 + resolution: "@lezer/lr@npm:1.4.0" dependencies: "@lezer/common": "npm:^1.0.0" - checksum: 10/9d32701f91fdf7d570073f5e83cda028c80bea7633f928c809eb6977d4f0b5e32424f95fb78cafea98789c4c0dadc9694636903290f0c1c418d2a47ed7f18f46 + checksum: 10/7391d0d08e54cd9e4f4d46e6ee6aa81fbaf079b22ed9c13d01fc9928e0ffd16d0c2d21b2cedd55675ad6c687277db28349ea8db81c9c69222cd7e7c40edd026e languageName: node linkType: hard @@ -2126,9 +2126,9 @@ __metadata: linkType: hard "@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": - version: 1.1.2 - resolution: "@lit-labs/ssr-dom-shim@npm:1.1.2" - checksum: 10/a930f7de57b952dc21317a5754aa0411e000bb4991053cde771c111b7792c4a4cdc896922f0353c832215bed71400431c5ab5a6252c8f4f70bb9ce0b37fe4752 + version: 1.2.0 + resolution: "@lit-labs/ssr-dom-shim@npm:1.2.0" + checksum: 10/33679defe08538ac6fb612854e7d32b4ea1e787cceba2c3373d26fd56baa9833881887da7bade3930a176ba518dc00bb42ce95d82ddb6af6b05b8fbe1fc3169f languageName: node linkType: hard @@ -2167,6 +2167,15 @@ __metadata: languageName: node linkType: hard +"@lrnwebcomponents/simple-tooltip@patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch": + version: 8.0.0 + resolution: "@lrnwebcomponents/simple-tooltip@patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch::version=8.0.0&hash=91f01a" + dependencies: + lit: "npm:^3.1.0" + checksum: 10/25e2ec2e7379004784ede67419da99a57c2aeb5f7c5747069682b0ca4f28a4e4aaec03a6946d17b33b85407b625d9747af7d0e576f2f1172f8b57090f425fbf9 + languageName: node + linkType: hard + "@material/animation@npm:14.0.0-canary.53b3cad2f.0": version: 14.0.0-canary.53b3cad2f.0 resolution: "@material/animation@npm:14.0.0-canary.53b3cad2f.0" @@ -3132,13 +3141,13 @@ __metadata: languageName: node linkType: hard -"@material/web@npm:=1.2.0": - version: 1.2.0 - resolution: "@material/web@npm:1.2.0" +"@material/web@npm:=1.3.0": + version: 1.3.0 + resolution: "@material/web@npm:1.3.0" dependencies: lit: "npm:^2.7.4 || ^3.0.0" tslib: "npm:^2.4.0" - checksum: 10/abf79ee6e247b21f046bf099accdb3439d43586f3be948b5f3fc09ab2b47a3a2b37fcaff01dde4205a9e40a3d22312cf314887f87fe930af755c4dc440a32529 + checksum: 10/7c3427747eccb69628807bed115dca96bb4a8e360e5cb97170a42ab7d469d17f0c925defa417a212ef3237c252f0b06dac9499b56c42945214f9bdeb7f7e5a0b languageName: node linkType: hard @@ -3184,15 +3193,15 @@ __metadata: linkType: hard "@npmcli/agent@npm:^2.0.0": - version: 2.2.0 - resolution: "@npmcli/agent@npm:2.2.0" + version: 2.2.1 + resolution: "@npmcli/agent@npm:2.2.1" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" lru-cache: "npm:^10.0.1" socks-proxy-agent: "npm:^8.0.1" - checksum: 10/822ea077553cd9cfc5cbd6d92380b0950fcb054a7027cd1b63a33bd0cbb16b0c6626ea75d95ec0e804643c8904472d3361d2da8c2444b1fb02a9b525d9c07c41 + checksum: 10/d4a48128f61e47f2f5c89315a5350e265dc619987e635bd62b52b29c7ed93536e724e721418c0ce352ceece86c13043c67aba1b70c3f5cc72fce6bb746706162 languageName: node linkType: hard @@ -3225,8 +3234,8 @@ __metadata: linkType: hard "@octokit/core@npm:^5.0.0": - version: 5.0.2 - resolution: "@octokit/core@npm:5.0.2" + version: 5.1.0 + resolution: "@octokit/core@npm:5.1.0" dependencies: "@octokit/auth-token": "npm:^4.0.0" "@octokit/graphql": "npm:^7.0.0" @@ -3235,7 +3244,7 @@ __metadata: "@octokit/types": "npm:^12.0.0" before-after-hook: "npm:^2.2.0" universal-user-agent: "npm:^6.0.0" - checksum: 10/bb991f88793fab043c4c09f9441432596fe0e6448caf42cd2209f52c1f26807418be488ad2cea7a8293e58e79e5c0019f38dda46e8cf96af5e89e43cca37ec3e + checksum: 10/8062e86a3088f24a691b36d2c3e9f33e864cefcb5f544b0633650358bce280708b111551cbe855ecf6a5190d6fc4fec1220117c329a2c27525940dd97b868614 languageName: node linkType: hard @@ -3308,13 +3317,13 @@ __metadata: linkType: hard "@octokit/plugin-rest-endpoint-methods@npm:^10.0.0": - version: 10.2.0 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:10.2.0" + version: 10.3.0 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:10.3.0" dependencies: - "@octokit/types": "npm:^12.3.0" + "@octokit/types": "npm:^12.4.0" peerDependencies: "@octokit/core": ">=5" - checksum: 10/0f8ca73b3e582b366b400278f19df6309f263efa3809a9d6ba613063e7a26f16d6f8d69c413bf9b23c2431ad4c795e4e06a43717b6acc1367186fb55347cfb69 + checksum: 10/be202aca31a513e9e06eda680ae761d05a944a2900523c2041a8ac6bc43e7a5bab0f76bcba09ef96ed48e3b1fabe4104a174b2f839dfb7a2a6f3d632023b4c6a languageName: node linkType: hard @@ -3343,14 +3352,14 @@ __metadata: linkType: hard "@octokit/request@npm:^8.0.0, @octokit/request@npm:^8.0.1, @octokit/request@npm:^8.0.2": - version: 8.1.6 - resolution: "@octokit/request@npm:8.1.6" + version: 8.2.0 + resolution: "@octokit/request@npm:8.2.0" dependencies: "@octokit/endpoint": "npm:^9.0.0" "@octokit/request-error": "npm:^5.0.0" "@octokit/types": "npm:^12.0.0" universal-user-agent: "npm:^6.0.0" - checksum: 10/aebea1c33d607d23c70f663cd5f8279a8bd932ab77b4ca5cca3b33968a347b4adb47476c886086f3a9aa1acefab3b79adac78ee7aa2dacd67eb1f2a05e272618 + checksum: 10/553ba8b99ea6fe2d3b66b2be6df06f7fe62a0b81e441d334ef25388cf0fcdab4a888fcfe2c1fb1ada262c233bcf7169da3ad5e03f024060ea5723f7753b0de3e languageName: node linkType: hard @@ -3366,12 +3375,12 @@ __metadata: languageName: node linkType: hard -"@octokit/types@npm:^12.0.0, @octokit/types@npm:^12.3.0, @octokit/types@npm:^12.4.0": - version: 12.4.0 - resolution: "@octokit/types@npm:12.4.0" +"@octokit/types@npm:^12.0.0, @octokit/types@npm:^12.4.0": + version: 12.5.0 + resolution: "@octokit/types@npm:12.5.0" dependencies: "@octokit/openapi-types": "npm:^19.1.0" - checksum: 10/b0a893e31fed59a919c2072ae67b671aa5f21e00ee3dee689af325f09f12ddd9175ce07c590b835d183bcb1cd2a2da908e02391b2fc33071881561366b2a35e7 + checksum: 10/a2c8e736e368c781b9389a41fe4ec2cf81563674cf8d74bcc28bf1901dfc4b0c092101c7b333309933b8c14a5a3d1de9ca7eaec93451c41b708b4936e0960894 languageName: node linkType: hard @@ -3869,11 +3878,11 @@ __metadata: linkType: hard "@sinonjs/commons@npm:^3.0.0": - version: 3.0.0 - resolution: "@sinonjs/commons@npm:3.0.0" + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" dependencies: type-detect: "npm:4.0.8" - checksum: 10/086720ae0bc370829322df32612205141cdd44e592a8a9ca97197571f8f970352ea39d3bda75b347c43789013ddab36b34b59e40380a49bdae1c2df3aa85fe4f + checksum: 10/a0af217ba7044426c78df52c23cedede6daf377586f3ac58857c565769358ab1f44ebf95ba04bbe38814fba6e316ca6f02870a009328294fc2c555d0f85a7117 languageName: node linkType: hard @@ -3959,7 +3968,7 @@ __metadata: languageName: node linkType: hard -"@types/bonjour@npm:^3.5.9": +"@types/bonjour@npm:^3.5.13": version: 3.5.13 resolution: "@types/bonjour@npm:3.5.13" dependencies: @@ -3969,12 +3978,12 @@ __metadata: linkType: hard "@types/chrome@npm:*": - version: 0.0.258 - resolution: "@types/chrome@npm:0.0.258" + version: 0.0.260 + resolution: "@types/chrome@npm:0.0.260" dependencies: "@types/filesystem": "npm:*" "@types/har-format": "npm:*" - checksum: 10/301f60ba009832f8eff232159234a4b73db2d7925eae1b67a1a4687da80d56022430bc722e8c4effc0a12c1868c0bb380e981ef8b6ba744bbe52bf47591a8198 + checksum: 10/4e7d6e5e3bd67bc481c6524aacce902568ba8f78b7da3fe1f2999f8f01863240847f1b05a358d38dbd2afdbe112dfd9ff84912a664c40595e9e4266eef3891cc languageName: node linkType: hard @@ -3994,6 +4003,13 @@ __metadata: languageName: node linkType: hard +"@types/color-name@npm:1.1.3": + version: 1.1.3 + resolution: "@types/color-name@npm:1.1.3" + checksum: 10/9060d16d0bce2cdf562d6da54e18c5f23e80308ccb58b725b9173a028818f27d8e01c8a5cd96952e76f11145a7388ed7d2f450fb4652f4760383834f2e698263 + languageName: node + linkType: hard + "@types/command-line-args@npm:^5.0.0": version: 5.2.3 resolution: "@types/command-line-args@npm:5.2.3" @@ -4001,7 +4017,7 @@ __metadata: languageName: node linkType: hard -"@types/connect-history-api-fallback@npm:^1.3.5": +"@types/connect-history-api-fallback@npm:^1.5.4": version: 1.5.4 resolution: "@types/connect-history-api-fallback@npm:1.5.4" dependencies: @@ -4028,14 +4044,14 @@ __metadata: linkType: hard "@types/cookies@npm:*": - version: 0.7.10 - resolution: "@types/cookies@npm:0.7.10" + version: 0.9.0 + resolution: "@types/cookies@npm:0.9.0" dependencies: "@types/connect": "npm:*" "@types/express": "npm:*" "@types/keygrip": "npm:*" "@types/node": "npm:*" - checksum: 10/85d4b434bac9a971d8a4122d5a7c947dcaaca98fee26e90e0b792b1046da1de414dc37ea164b1693653b9b59f72c501927de90412a3a1dff2c7bdb6abadc3608 + checksum: 10/88d2106834fca85cf9dfef984e99bf4969e77d48538d8e8408a29679b4d1f675fe4725d35f2e38d252a336b76d14a2bc84bcb34edc72238a7a8261c0808c7c56 languageName: node linkType: hard @@ -4074,18 +4090,18 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33": - version: 4.17.41 - resolution: "@types/express-serve-static-core@npm:4.17.41" + version: 4.17.43 + resolution: "@types/express-serve-static-core@npm:4.17.43" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10/7647e19d9c3d57ddd18947d2b161b90ef0aedd15875140e5b824209be41c1084ae942d4fb43cd5f2051a6a5f8c044519ef6c9ac1b2ad86b9aa546b4f1f023303 + checksum: 10/9079e137470e0456bb8e77ae66df9505ee12591e94860bde574cfe52c5c60bbc5bf7dd44f5689c3cbb1baf0aa84442d9a21f53dcd921d18745727293cd5a5fd6 languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13": +"@types/express@npm:*, @types/express@npm:^4.17.21": version: 4.17.21 resolution: "@types/express@npm:4.17.21" dependencies: @@ -4107,16 +4123,16 @@ __metadata: linkType: hard "@types/filewriter@npm:*": - version: 0.0.32 - resolution: "@types/filewriter@npm:0.0.32" - checksum: 10/fe2f19239c23c63c009c6d422227d692bc2a0cd1113f8ce31b0fb7048f32ec018003172199949843fdbb1c5988551c29e1e9e2238b9c160969b5e5edbfb76424 + version: 0.0.33 + resolution: "@types/filewriter@npm:0.0.33" + checksum: 10/495a4bb424c27eda967fe9ac3b8f7b781e6b3f9ce59403a991590cb1073022f9c5383d3c7d808ef6956b785550c36664c4fcd502dc0baf69e340bd481171e0ca languageName: node linkType: hard "@types/geojson@npm:*": - version: 7946.0.13 - resolution: "@types/geojson@npm:7946.0.13" - checksum: 10/b3b68457c89bc3f0445dc9eb54d07e6f89658672867c54989bc7f71f87d54e562195b291d43e1b84476493351271d7ccb9f5c6ab2012b29fbafbb0e8e43c4bca + version: 7946.0.14 + resolution: "@types/geojson@npm:7946.0.14" + checksum: 10/ae511bee6488ae3bd5a3a3347aedb0371e997b14225b8983679284e22fa4ebd88627c6e3ff8b08bf4cc35068cb29310c89427311ffc9322c255615821a922e71 languageName: node linkType: hard @@ -4306,11 +4322,11 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 20.11.5 - resolution: "@types/node@npm:20.11.5" + version: 20.11.19 + resolution: "@types/node@npm:20.11.19" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/9f31c471047d7b3e240ce7b77ff29b0d15e83be7e3feafb3d0b0d0931122b438b1eefa302a5a2e1e9849914ff3fd76aafbd8ccb372efb1331ba048da63bce6f8 + checksum: 10/c7f4705d6c84aa21679ad180c33c13ca9567f650e66e14bcee77c7c43d14619c7cd3b4d7b2458947143030b7b1930180efa6d12d999b45366abff9fed7a17472 languageName: node linkType: hard @@ -4397,17 +4413,17 @@ __metadata: languageName: node linkType: hard -"@types/retry@npm:0.12.0": - version: 0.12.0 - resolution: "@types/retry@npm:0.12.0" - checksum: 10/bbd0b88f4b3eba7b7acfc55ed09c65ef6f2e1bcb4ec9b4dca82c66566934351534317d294a770a7cc6c0468d5573c5350abab6e37c65f8ef254443e1b028e44d +"@types/retry@npm:0.12.2": + version: 0.12.2 + resolution: "@types/retry@npm:0.12.2" + checksum: 10/e5675035717b39ce4f42f339657cae9637cf0c0051cf54314a6a2c44d38d91f6544be9ddc0280587789b6afd056be5d99dbe3e9f4df68c286c36321579b1bf4a languageName: node linkType: hard "@types/semver@npm:^7.5.0": - version: 7.5.6 - resolution: "@types/semver@npm:7.5.6" - checksum: 10/e77282b17f74354e17e771c0035cccb54b94cc53d0433fa7e9ba9d23fd5d7edcd14b6c8b7327d58bbd89e83b1c5eda71dfe408e06b929007e2b89586e9b63459 + version: 7.5.7 + resolution: "@types/semver@npm:7.5.7" + checksum: 10/535d88ec577fe59e38211881f79a1e2ba391e9e1516f8fff74e7196a5ba54315bace9c67a4616c334c830c89027d70a9f473a4ceb634526086a9da39180f2f9a languageName: node linkType: hard @@ -4430,7 +4446,7 @@ __metadata: languageName: node linkType: hard -"@types/serve-index@npm:^1.9.1": +"@types/serve-index@npm:^1.9.4": version: 1.9.4 resolution: "@types/serve-index@npm:1.9.4" dependencies: @@ -4439,7 +4455,7 @@ __metadata: languageName: node linkType: hard -"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10": +"@types/serve-static@npm:*, @types/serve-static@npm:^1.15.5": version: 1.15.5 resolution: "@types/serve-static@npm:1.15.5" dependencies: @@ -4450,7 +4466,7 @@ __metadata: languageName: node linkType: hard -"@types/sockjs@npm:^0.3.33": +"@types/sockjs@npm:^0.3.36": version: 0.3.36 resolution: "@types/sockjs@npm:0.3.36" dependencies: @@ -4459,10 +4475,10 @@ __metadata: languageName: node linkType: hard -"@types/sortablejs@npm:1.15.7": - version: 1.15.7 - resolution: "@types/sortablejs@npm:1.15.7" - checksum: 10/422fb6a4862723567a0750339aff0ed8cfdbb4c5bc2601111481c1c2a23de5d6679c7f8dfd76daf2037e2d4df02e42fcd3203251aead64d5629968ead220cf10 +"@types/sortablejs@npm:1.15.8": + version: 1.15.8 + resolution: "@types/sortablejs@npm:1.15.8" + checksum: 10/aea58b08cf45f5e9633707a8df0df1212595c731bbdfd29805487138fdd0d8c51fa5c741999738a645c1e801d43a92ba0d3fb5b45625b52e247c56588aef6c55 languageName: node linkType: hard @@ -4506,7 +4522,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.5": +"@types/ws@npm:^8.5.10": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" dependencies: @@ -4515,15 +4531,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.20.0" +"@typescript-eslint/eslint-plugin@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/eslint-plugin@npm:7.0.2" dependencies: "@eslint-community/regexpp": "npm:^4.5.1" - "@typescript-eslint/scope-manager": "npm:6.20.0" - "@typescript-eslint/type-utils": "npm:6.20.0" - "@typescript-eslint/utils": "npm:6.20.0" - "@typescript-eslint/visitor-keys": "npm:6.20.0" + "@typescript-eslint/scope-manager": "npm:7.0.2" + "@typescript-eslint/type-utils": "npm:7.0.2" + "@typescript-eslint/utils": "npm:7.0.2" + "@typescript-eslint/visitor-keys": "npm:7.0.2" debug: "npm:^4.3.4" graphemer: "npm:^1.4.0" ignore: "npm:^5.2.4" @@ -4531,73 +4547,73 @@ __metadata: semver: "npm:^7.5.4" ts-api-utils: "npm:^1.0.1" peerDependencies: - "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 + "@typescript-eslint/parser": ^7.0.0 + eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/dee6a2392c831e6ae69611ecc4de06e66a7b16f6bf6d8e3bfd25091eb14d88c9d0bb9c9cd634efcfa318902341f7a459cf48f713d55cb1d610145ca1f52af4d3 + checksum: 10/430b2f7ca36ee73dc75c1d677088709f3c9d5bbb4fffa3cfbe1b7d63979ee397f7a4a2a1386e05a04991500fa0ab0dd5272e8603a2b20f42e4bf590603500858 languageName: node linkType: hard -"@typescript-eslint/parser@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/parser@npm:6.20.0" +"@typescript-eslint/parser@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/parser@npm:7.0.2" dependencies: - "@typescript-eslint/scope-manager": "npm:6.20.0" - "@typescript-eslint/types": "npm:6.20.0" - "@typescript-eslint/typescript-estree": "npm:6.20.0" - "@typescript-eslint/visitor-keys": "npm:6.20.0" + "@typescript-eslint/scope-manager": "npm:7.0.2" + "@typescript-eslint/types": "npm:7.0.2" + "@typescript-eslint/typescript-estree": "npm:7.0.2" + "@typescript-eslint/visitor-keys": "npm:7.0.2" debug: "npm:^4.3.4" peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/691062d47cae7977604ede848ffff3689162428a53577f298989f585954aa3a3450e7fd5c2b363d024cd5f16022c163cecf0f1f1d138234bbd78048050b4b8bf + checksum: 10/18d6e1bda64013f7d66164164c57a10390f7979db55b265062ae9337e11e0921bffca10870e252cd0bd198f79ffa2e87a652e57110e5b1b4cc738453154c205c languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/scope-manager@npm:6.20.0" +"@typescript-eslint/scope-manager@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/scope-manager@npm:7.0.2" dependencies: - "@typescript-eslint/types": "npm:6.20.0" - "@typescript-eslint/visitor-keys": "npm:6.20.0" - checksum: 10/2c1a644f2931454b34875f2e6dffad52a1fc7b6ac508d7d1ad3cd9da028a7dff9c6191feeea2c9ca691deba199ac9e83cbd0036914be4cd45b6954437f03c09a + "@typescript-eslint/types": "npm:7.0.2" + "@typescript-eslint/visitor-keys": "npm:7.0.2" + checksum: 10/773ea6e61f741777e69a469641f3db0d3c2301c0102667825fb235ed5a65c95f6d6b31b19e734b9a215acc0c7c576c65497635b8d5928eeddb58653ceb13d2d5 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/type-utils@npm:6.20.0" +"@typescript-eslint/type-utils@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/type-utils@npm:7.0.2" dependencies: - "@typescript-eslint/typescript-estree": "npm:6.20.0" - "@typescript-eslint/utils": "npm:6.20.0" + "@typescript-eslint/typescript-estree": "npm:7.0.2" + "@typescript-eslint/utils": "npm:7.0.2" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.0.1" peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/bc2f2793cfec3463164b5f5ded31b4e169e21c3a1990c1ce4effe70a359c486d92fbbc4cd92758bbf1c30a468ad0839e0fa890bd452c707d0c294cb3a7b14021 + checksum: 10/63bf19c9f5bbcb0f3e127f509d85dc49be4e5e51781d78f58c96786089e7c909b25d35d0248a6a758e2f7d5b5223d2262c2d597ab71f226af6beb499ae950645 languageName: node linkType: hard -"@typescript-eslint/types@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/types@npm:6.20.0" - checksum: 10/74ed1761e27c3c1a29fd260fe51096f42cfb1472b20390d6df6ec41de0420208f379e809de416e81cd7c00fdc3d5550b2391872be56bf4a1b0c595f71db0b1ea +"@typescript-eslint/types@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/types@npm:7.0.2" + checksum: 10/2cba8a0355cc7357db142fa597d02cf39e1d1cb0ec87c80e91daaa2b87f2a794d2649def9d7b2aa435691c3810d2cbd4cdc21668b19b991863f0d54d4a22da82 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.20.0" +"@typescript-eslint/typescript-estree@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/typescript-estree@npm:7.0.2" dependencies: - "@typescript-eslint/types": "npm:6.20.0" - "@typescript-eslint/visitor-keys": "npm:6.20.0" + "@typescript-eslint/types": "npm:7.0.2" + "@typescript-eslint/visitor-keys": "npm:7.0.2" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -4607,34 +4623,34 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/55b280c6e71c79cb009ac80189a7f0e1aa9011bc7206c810bbb52d9703a894aa2817dfd44d947edf64d62f3aa0962e01f3423fcb21d2f39964a4840287d9e196 + checksum: 10/307080e29c22fc69f0ce7ab7101e1629e05f45a9e541c250e03d06b61336ab0ccb5f0a7354ee3da4e38d5cade4dd2fb7bb396cd7cbe74c2c4b3e29706a70abcc languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/utils@npm:6.20.0" +"@typescript-eslint/utils@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/utils@npm:7.0.2" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" "@types/json-schema": "npm:^7.0.12" "@types/semver": "npm:^7.5.0" - "@typescript-eslint/scope-manager": "npm:6.20.0" - "@typescript-eslint/types": "npm:6.20.0" - "@typescript-eslint/typescript-estree": "npm:6.20.0" + "@typescript-eslint/scope-manager": "npm:7.0.2" + "@typescript-eslint/types": "npm:7.0.2" + "@typescript-eslint/typescript-estree": "npm:7.0.2" semver: "npm:^7.5.4" peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - checksum: 10/6d4604be6123e0073dd5e7dd357c95b370c678572d2e982478d0d6937d4d65f0cad0ac207b8b724f3bce239e64ba1ddd6bece11e1592734d8bf691177e6971e6 + eslint: ^8.56.0 + checksum: 10/e68bac777419cd529371f7f29f534efaeca130c90ed9723bfc7aac451d61ca3fc4ebd310e2c015e29e8dc7be4734ae46258ca8755897d7f5e3bb502660d5372f languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.20.0": - version: 6.20.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.20.0" +"@typescript-eslint/visitor-keys@npm:7.0.2": + version: 7.0.2 + resolution: "@typescript-eslint/visitor-keys@npm:7.0.2" dependencies: - "@typescript-eslint/types": "npm:6.20.0" + "@typescript-eslint/types": "npm:7.0.2" eslint-visitor-keys: "npm:^3.4.1" - checksum: 10/df066c73f3880ad78880c442f307e58f026e6047d9caab9d7c356d13276f4fe466fab3e8d19cdb1e6749e87639cb7c4babcfe118f554fcd2d3929ce9f4983216 + checksum: 10/da6c1b0729af99216cde3a65d4e91584a81fc6c9dff7ba291089f01bf7262de375f58c4c4246e5fbc29f51258db7725d9c830f82ccbd1cda812fd13c51480cda languageName: node linkType: hard @@ -4645,128 +4661,128 @@ __metadata: languageName: node linkType: hard -"@vaadin/a11y-base@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/a11y-base@npm:24.3.5" +"@vaadin/a11y-base@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/a11y-base@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.5" + "@vaadin/component-base": "npm:~24.3.6" lit: "npm:^3.0.0" - checksum: 10/17120df44a410e9c0b449b51c0b3380e6a2a0f92b7ea37746c3b20070849df535fa81ca4513219863e15009a5b04427b294fd467c692330f37b89db333d550f2 + checksum: 10/ba101554e829c569fe71cd05642e00437fc3bad7a4a106421843410459bb874eb6f484ca8ebb17f215577aa2e517dd64a84509d07dcd96bd0ab8eb0b62f30346 languageName: node linkType: hard -"@vaadin/combo-box@npm:24.3.5": - version: 24.3.5 - resolution: "@vaadin/combo-box@npm:24.3.5" +"@vaadin/combo-box@npm:24.3.6": + version: 24.3.6 + resolution: "@vaadin/combo-box@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.5" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/field-base": "npm:~24.3.5" - "@vaadin/input-container": "npm:~24.3.5" - "@vaadin/item": "npm:~24.3.5" - "@vaadin/lit-renderer": "npm:~24.3.5" - "@vaadin/overlay": "npm:~24.3.5" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.5" - "@vaadin/vaadin-material-styles": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" - checksum: 10/ca61dc04d997eb2c4ba0e6433f199b139ed8e310a29cad2962c6ed610bafd8103638876c1f01ebe97b284fca535e5f86f7d6c4d48931e5e99f488675df197660 + "@vaadin/a11y-base": "npm:~24.3.6" + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/field-base": "npm:~24.3.6" + "@vaadin/input-container": "npm:~24.3.6" + "@vaadin/item": "npm:~24.3.6" + "@vaadin/lit-renderer": "npm:~24.3.6" + "@vaadin/overlay": "npm:~24.3.6" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.6" + "@vaadin/vaadin-material-styles": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" + checksum: 10/f05cb153a666227163031726b353b81500265ed614c3705cd5e81e9a6ec0235a0e207109b17958e9c5164b2c4d79ca25583400d4286cb1114221a0046d7c3e05 languageName: node linkType: hard -"@vaadin/component-base@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/component-base@npm:24.3.5" +"@vaadin/component-base@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/component-base@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" "@vaadin/vaadin-development-mode-detector": "npm:^2.0.0" "@vaadin/vaadin-usage-statistics": "npm:^2.1.0" lit: "npm:^3.0.0" - checksum: 10/cc9ef67334b0f145952d68a5ccf24fcce6a86f6cd548d07356e540a8f84f08d4567182856a597e8ed3c2650ffc0acbde181fe72502a6eb9ecf024afc3a564dde + checksum: 10/2bf4571a35f1d07a8da1a77c59d280add5b25a67aaf4e54a731ee10424125a028b01c06c54aa861f4892a5ce69cc4b1c84a37a1058588d10cd05bb34514601b6 languageName: node linkType: hard -"@vaadin/field-base@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/field-base@npm:24.3.5" +"@vaadin/field-base@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/field-base@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.5" - "@vaadin/component-base": "npm:~24.3.5" + "@vaadin/a11y-base": "npm:~24.3.6" + "@vaadin/component-base": "npm:~24.3.6" lit: "npm:^3.0.0" - checksum: 10/8ae5844791d1e4385780b254c149f5f320998c36eb932f7d04f08a8fcbb30f41d009e59738823c0b3836ce9214b047a17124721ca3fc622f137ef42030a68c1b + checksum: 10/71f407f8ca1801d7453a49f8d130e66c5a9999c8ddbfd1569a6e5141c372670b71cb195587a2a562a3965d2ac28e5fc022dcef4a98c96568ae78ad759ddb9d7c languageName: node linkType: hard -"@vaadin/icon@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/icon@npm:24.3.5" +"@vaadin/icon@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/icon@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" lit: "npm:^3.0.0" - checksum: 10/f48d647e73e513c4d730faab4f4cd543918574ffdd3bcb6d1586d67b13e5428618b44b034ce49eda68fe2b806ae827f7625fc93eb2d94046128e779783cefe02 + checksum: 10/cffe77e5b1f6feb44b1f839c462580d5162cc1782e1701c292323068584e2a7747794473e46c3aa22c8fe6f1410a98e3bbd5a220ae6805c6759fec511e14675f languageName: node linkType: hard -"@vaadin/input-container@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/input-container@npm:24.3.5" +"@vaadin/input-container@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/input-container@npm:24.3.6" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.5" - "@vaadin/vaadin-material-styles": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.6" + "@vaadin/vaadin-material-styles": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" lit: "npm:^3.0.0" - checksum: 10/b942c6657809e2a65cf317e0526786eb7206abff57dfe9fb0611245a5b5334307a589a78fa358b2d6f02bfe4534691295871db326db34bf4014c50fec5ed1f7f + checksum: 10/6fd86c09ba47ea2937896826f1646155d53d3e9d363031f2a6c81774f6224e8cff1038f400ebf75b9de3c0084c65952226e5309b4b5649b69a3e66ef8ad84e65 languageName: node linkType: hard -"@vaadin/item@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/item@npm:24.3.5" +"@vaadin/item@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/item@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.5" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.5" - "@vaadin/vaadin-material-styles": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" - checksum: 10/1a428afd6f26f3f35465469d891a14198fb0bf46287e624139c585845a17352d9d013a54f96343b1790f8a9de2cb9796f2014ec58bc794d1215329038505313a + "@vaadin/a11y-base": "npm:~24.3.6" + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.6" + "@vaadin/vaadin-material-styles": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" + checksum: 10/d38f8219c8e9ba6acb833444eca2894723e83c06ce7557d0796042969899323ca9c19331639f6c677b49be79cda705da7d0d4251cd8370003df39f448f668835 languageName: node linkType: hard -"@vaadin/lit-renderer@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/lit-renderer@npm:24.3.5" +"@vaadin/lit-renderer@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/lit-renderer@npm:24.3.6" dependencies: lit: "npm:^3.0.0" - checksum: 10/232437d961102cdf5dee1828f0bd1ec7e2791bbce648cc0ac8860fb4e9a8ccad0dd49daa3bf1cd2d30cd5f0ad9dea149b9963bd847fba0299a7ae6775bf5dfdf + checksum: 10/5e2c93f9ccbe037a9d20338954dd4cd01511c3f481b0091623892f31194483661781650cbee52c64ebb8c3be99e1500cac90e460b82d781d6bf4c436ca1f2039 languageName: node linkType: hard -"@vaadin/overlay@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/overlay@npm:24.3.5" +"@vaadin/overlay@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/overlay@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" "@polymer/polymer": "npm:^3.0.0" - "@vaadin/a11y-base": "npm:~24.3.5" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/vaadin-lumo-styles": "npm:~24.3.5" - "@vaadin/vaadin-material-styles": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" - checksum: 10/1e171da246fae2a8cde03ba020773c09bb67cf384fa683501d7e63ea97528dd025e65c7a4a6c7c98d9c743c06b496e6926f24d7b94aac251f68931b65ace2d91 + "@vaadin/a11y-base": "npm:~24.3.6" + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/vaadin-lumo-styles": "npm:~24.3.6" + "@vaadin/vaadin-material-styles": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" + checksum: 10/f2f8a8125a957999a8b184ab56516b29856f0ba3f6ab75f7e0bcde8a6d477d0342f2fd1dbc2ee0ac7b8bc6b320cf9c22227503cf9854a7000792da6644ce2078 languageName: node linkType: hard @@ -4777,36 +4793,36 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-lumo-styles@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/vaadin-lumo-styles@npm:24.3.5" +"@vaadin/vaadin-lumo-styles@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/vaadin-lumo-styles@npm:24.3.6" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/icon": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" - checksum: 10/d19c0bd8ad03aafee5cf24ef49c1f351972581b70af491e9a6b46011a2c761ec46c0897883f5dd9f6a555c85985d747a97455ebe9ee588ca8e4875a452fc6f1e + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/icon": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" + checksum: 10/158bedb18919229dc743821bbac6510b50b825d901265b71512f980d478675f675491a129f65de955c74ca367a1d97d7fd5691283a255f507ed28522722a209e languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/vaadin-material-styles@npm:24.3.5" +"@vaadin/vaadin-material-styles@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/vaadin-material-styles@npm:24.3.6" dependencies: "@polymer/polymer": "npm:^3.0.0" - "@vaadin/component-base": "npm:~24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:~24.3.5" - checksum: 10/4685b035992389bf48745a7591380c8fa13dda32b6ff6df8685b16768e52fb35ab23e7e46805dd5eda419417660adf483ce600699fdd0476e9a74226ed3adc39 + "@vaadin/component-base": "npm:~24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:~24.3.6" + checksum: 10/76835be72e6d08727a38cdec11c54565c7319eb1010ee5404f78acc7b6d8e8f5c363f9c5e9c149507a42a7ee7b075e8e0aae78aeaa375f4db3b0a97ee0cabc15 languageName: node linkType: hard -"@vaadin/vaadin-themable-mixin@npm:24.3.5, @vaadin/vaadin-themable-mixin@npm:~24.3.5": - version: 24.3.5 - resolution: "@vaadin/vaadin-themable-mixin@npm:24.3.5" +"@vaadin/vaadin-themable-mixin@npm:24.3.6, @vaadin/vaadin-themable-mixin@npm:~24.3.6": + version: 24.3.6 + resolution: "@vaadin/vaadin-themable-mixin@npm:24.3.6" dependencies: "@open-wc/dedupe-mixin": "npm:^1.3.0" lit: "npm:^3.0.0" - checksum: 10/faab5b044dfae77a5f558d006ca7debc4f6134e7eef47df538c35fd611ba02be0ae59d66d9574c5ce1aeea60a005fc4213f11259760352f370eefa60c47024b6 + checksum: 10/93b2f78bf2ad8575caedb1a0f7742d6f7e3920c362d74da6addf2d00de152e9a6e588cfa0b47f1aea76322fd031ae65446d80c6015591c7e00e4124afe07ffbb languageName: node linkType: hard @@ -4930,9 +4946,9 @@ __metadata: linkType: hard "@vscode/web-custom-data@npm:^0.4.2": - version: 0.4.8 - resolution: "@vscode/web-custom-data@npm:0.4.8" - checksum: 10/556106530e399321d1412d0074d1a4dcfc04c17fc334df8de4f10469a2ad6f83c7245caf92174817c5bf032ec5ffd894bc418889d76a2a7d163ad0e4c910adf0 + version: 0.4.9 + resolution: "@vscode/web-custom-data@npm:0.4.9" + checksum: 10/6ccc98c43b0ead4f320dab2e33df3570590d4d3e94edf54b722c3b376fc236abb5a6e9e7655942044cf7ebe4f305a58bc58c1d8d4b251b9434d1db1266797e2e languageName: node linkType: hard @@ -5672,13 +5688,13 @@ __metadata: languageName: node linkType: hard -"array-buffer-byte-length@npm:^1.0.0": - version: 1.0.0 - resolution: "array-buffer-byte-length@npm:1.0.0" +"array-buffer-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "array-buffer-byte-length@npm:1.0.1" dependencies: - call-bind: "npm:^1.0.2" - is-array-buffer: "npm:^3.0.1" - checksum: 10/044e101ce150f4804ad19c51d6c4d4cfa505c5b2577bd179256e4aa3f3f6a0a5e9874c78cd428ee566ac574c8a04d7ce21af9fe52e844abfdccb82b33035a7c3 + call-bind: "npm:^1.0.5" + is-array-buffer: "npm:^3.0.4" + checksum: 10/53524e08f40867f6a9f35318fafe467c32e45e9c682ba67b11943e167344d2febc0f6977a17e699b05699e805c3e8f073d876f8bbf1b559ed494ad2cd0fae09e languageName: node linkType: hard @@ -5767,6 +5783,19 @@ __metadata: languageName: node linkType: hard +"array.prototype.filter@npm:^1.0.3": + version: 1.0.3 + resolution: "array.prototype.filter@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + es-array-method-boxes-properly: "npm:^1.0.0" + is-string: "npm:^1.0.7" + checksum: 10/3da2189afb00f95559cc73fc3c50f17a071a65bb705c0b2f2e2a2b2142781215b622442368c8b4387389b6ab251adf09ad347f9a8a4cf29d24404cc5ea1e295c + languageName: node + linkType: hard + "array.prototype.find@npm:^2.2.2": version: 2.2.2 resolution: "array.prototype.find@npm:2.2.2" @@ -5780,15 +5809,15 @@ __metadata: linkType: hard "array.prototype.findlastindex@npm:^1.2.3": - version: 1.2.3 - resolution: "array.prototype.findlastindex@npm:1.2.3" + version: 1.2.4 + resolution: "array.prototype.findlastindex@npm:1.2.4" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.1" - checksum: 10/063cbab8eeac3aa01f3e980eecb9a8c5d87723032b49f7f814ecc6d75c33c03c17e3f43a458127a62e16303cab412f95d6ad9dc7e0ae6d9dc27a9bb76c24df7a + call-bind: "npm:^1.0.5" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.22.3" + es-errors: "npm:^1.3.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/12d7de8da619065b9d4c40550d11c13f2fbbc863c4270ef01d022f49ef16fbe9022441ee9d60b1e952853c661dd4b3e05c21e4348d4631c6d93ddf802a252296 languageName: node linkType: hard @@ -5816,25 +5845,19 @@ __metadata: languageName: node linkType: hard -"arraybuffer.prototype.slice@npm:^1.0.2": - version: 1.0.2 - resolution: "arraybuffer.prototype.slice@npm:1.0.2" +"arraybuffer.prototype.slice@npm:^1.0.3": + version: 1.0.3 + resolution: "arraybuffer.prototype.slice@npm:1.0.3" dependencies: - array-buffer-byte-length: "npm:^1.0.0" - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - get-intrinsic: "npm:^1.2.1" - is-array-buffer: "npm:^3.0.2" + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.5" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.22.3" + es-errors: "npm:^1.2.1" + get-intrinsic: "npm:^1.2.3" + is-array-buffer: "npm:^3.0.4" is-shared-array-buffer: "npm:^1.0.2" - checksum: 10/c200faf437786f5b2c80d4564ff5481c886a16dee642ef02abdc7306c7edd523d1f01d1dd12b769c7eb42ac9bc53874510db19a92a2c035c0f6696172aafa5d3 - languageName: node - linkType: hard - -"asap@npm:~2.0.6": - version: 2.0.6 - resolution: "asap@npm:2.0.6" - checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda + checksum: 10/0221f16c1e3ec7b67da870ee0e1f12b825b5f9189835392b59a22990f715827561a4f4cd5330dc7507de272d8df821be6cd4b0cb569babf5ea4be70e365a2f3d languageName: node linkType: hard @@ -5912,17 +5935,17 @@ __metadata: languageName: node linkType: hard -"available-typed-arrays@npm:^1.0.5": - version: 1.0.5 - resolution: "available-typed-arrays@npm:1.0.5" - checksum: 10/4d4d5e86ea0425696f40717882f66a570647b94ac8d273ddc7549a9b61e5da099e149bf431530ccbd776bd74e02039eb8b5edf426e3e2211ee61af16698a9064 +"available-typed-arrays@npm:^1.0.6": + version: 1.0.6 + resolution: "available-typed-arrays@npm:1.0.6" + checksum: 10/c1e2e3d3a694f21bf60e0a048d8275fa7358131a0b8e6b57714318d618b59522416db67fb9f56973af0ce596f4333ef1336ca12c37a41d5a72ef79885373a7fd languageName: node linkType: hard "axe-core@npm:^4.3.3": - version: 4.8.3 - resolution: "axe-core@npm:4.8.3" - checksum: 10/86d72bcdff867b8a4f223b910f90d1c963ddab1db3bdc5290e111453bae4e319ed14d667cd7a6ac97185ffa141261c88ac9a586aa007d007ffc19ed9ae81ee1d + version: 4.8.4 + resolution: "axe-core@npm:4.8.4" + checksum: 10/c8b3972b17afac33b9e151556fa500b9218086ca763866942de30e7f51ac50582902ad03ec16381796c95afaacfa8c33efb2d02ab4f09b6c09d6be58b95da978 languageName: node linkType: hard @@ -6120,7 +6143,7 @@ __metadata: languageName: node linkType: hard -"bonjour-service@npm:^1.0.11": +"bonjour-service@npm:^1.2.1": version: 1.2.1 resolution: "bonjour-service@npm:1.2.1" dependencies: @@ -6190,17 +6213,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.21.10, browserslist@npm:^4.22.2": - version: 4.22.3 - resolution: "browserslist@npm:4.22.3" +"browserslist@npm:^4.21.10, browserslist@npm:^4.22.2, browserslist@npm:^4.22.3": + version: 4.23.0 + resolution: "browserslist@npm:4.23.0" dependencies: - caniuse-lite: "npm:^1.0.30001580" - electron-to-chromium: "npm:^1.4.648" + caniuse-lite: "npm:^1.0.30001587" + electron-to-chromium: "npm:^1.4.668" node-releases: "npm:^2.0.14" update-browserslist-db: "npm:^1.0.13" bin: browserslist: cli.js - checksum: 10/d46a906c79dfe95d9702c020afbe5b7b4dbe2019b85432e7a020326adff27e63e3c0a52dc8d4e73247060bbe2c13f000714741903cf96a16baae9c216dc74c75 + checksum: 10/496c3862df74565dd942b4ae65f502c575cbeba1fa4a3894dad7aa3b16130dc3033bc502d8848147f7b625154a284708253d9598bcdbef5a1e34cf11dc7bad8e languageName: node linkType: hard @@ -6319,14 +6342,16 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.4, call-bind@npm:^1.0.5": - version: 1.0.5 - resolution: "call-bind@npm:1.0.5" +"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.1" - set-function-length: "npm:^1.1.1" - checksum: 10/246d44db6ef9bbd418828dbd5337f80b46be4398d522eded015f31554cbb2ea33025b0203b75c7ab05a1a255b56ef218880cca1743e4121e306729f9e414da39 + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.1" + checksum: 10/cd6fe658e007af80985da5185bff7b55e12ef4c2b6f41829a26ed1eef254b1f1c12e3dfd5b2b068c6ba8b86aba62390842d81752e67dcbaec4f6f76e7113b6b7 languageName: node linkType: hard @@ -6368,23 +6393,23 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001580": - version: 1.0.30001580 - resolution: "caniuse-lite@npm:1.0.30001580" - checksum: 10/b626d25d792c766383a47c9efe1384f7d3c3b23e0ee70bf121e8b3a628806a96a21def16a44e58b75f4a5e23b5e008f51c2cc1e8be477b8c8d9493dcc170dd0e +"caniuse-lite@npm:^1.0.30001587": + version: 1.0.30001588 + resolution: "caniuse-lite@npm:1.0.30001588" + checksum: 10/09150ef2daa65c75cb2681832d5bc203760a02d9f71eb033dc0401fbfdbe026d3a84e54a8d2085f730a4f51eb074028b89013dd033841e1a0eb3c7323a50ed45 languageName: node linkType: hard -"chai@npm:5.0.3": - version: 5.0.3 - resolution: "chai@npm:5.0.3" +"chai@npm:5.1.0": + version: 5.1.0 + resolution: "chai@npm:5.1.0" dependencies: assertion-error: "npm:^2.0.1" check-error: "npm:^2.0.0" deep-eql: "npm:^5.0.1" loupe: "npm:^3.1.0" pathval: "npm:^2.0.0" - checksum: 10/d9b2bb0e4591b4a73ea98bb7a6423c4e50dd35f43ce4404be26126796af970d844269bb0b07b21866620c542f07957003b34c5cf60095e5a84878af6b120cc2d + checksum: 10/4d4eee5ec61eeff9e9dfe05ae49ef4b48f1cc8e14e4d4c72476482cea85d824aff0e76d5c4d357026fb276870d705341068eef98d240cd90f008b9bd250e7a69 languageName: node linkType: hard @@ -6441,7 +6466,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3, chokidar@npm:^3.4.3, chokidar@npm:^3.5.3": +"chokidar@npm:3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -6483,6 +6508,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.4.3, chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -6707,6 +6751,13 @@ __metadata: languageName: node linkType: hard +"color-name@npm:2.0.0": + version: 2.0.0 + resolution: "color-name@npm:2.0.0" + checksum: 10/10a1addae41de2987d6b90dbd3cfade266c2e6f680ce21749911df4493b4fae07654862c6b5358bdd13e155461acb4eedaa5e0ba172bf13542cdcca10866cf2b + languageName: node + linkType: hard + "color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" @@ -6960,18 +7011,18 @@ __metadata: linkType: hard "core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.34.0": - version: 3.35.1 - resolution: "core-js-compat@npm:3.35.1" + version: 3.36.0 + resolution: "core-js-compat@npm:3.36.0" dependencies: - browserslist: "npm:^4.22.2" - checksum: 10/9a153c66591e23703e182b258ec6bdaff0a7c578dc5f9ac152fdfef2d09e8ec277f192e28d4634a8b576c8e1a6d3b1ac76ff6b8776e72b71b334e609e177a05e + browserslist: "npm:^4.22.3" + checksum: 10/633c49a254fe48981057e33651e5a74a0a14f14731aa5afed5d2e61fbe3c5cbc116ffd4feaa158c683c40d6dc4fd2e6aa0ebe12c45d157cfa571309d08400c98 languageName: node linkType: hard -"core-js@npm:3.35.1": - version: 3.35.1 - resolution: "core-js@npm:3.35.1" - checksum: 10/5d31f22eb05cf66bd1a2088a04b7106faa5d0b91c1ffa5d72c5203e4974c31bd7e11969297f540a806c00c74c23991eaad5639592df8b5dbe4412fff3c075cd5 +"core-js@npm:3.36.0": + version: 3.36.0 + resolution: "core-js@npm:3.36.0" + checksum: 10/896326c6391c1607dc645293c214cd31c6c535d4a77a88b15fc29e787199f9b06dc15986ddfbc798335bf7a7afd1e92152c94aa5a974790a7f97a98121774302 languageName: node linkType: hard @@ -7207,14 +7258,14 @@ __metadata: languageName: node linkType: hard -"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.1": - version: 1.1.1 - resolution: "define-data-property@npm:1.1.1" +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.2": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" dependencies: - get-intrinsic: "npm:^1.2.1" + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.0" - checksum: 10/5573c8df96b5857408cad64d9b91b69152e305ce4b06218e5f49b59c6cafdbb90a8bd8a0bb83c7bc67a8d479c04aa697063c9bc28d849b7282f9327586d6bc7b + checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae languageName: node linkType: hard @@ -7362,9 +7413,9 @@ __metadata: linkType: hard "diff@npm:^5.1.0": - version: 5.1.0 - resolution: "diff@npm:5.1.0" - checksum: 10/f4557032a98b2967fe27b1a91dfcf8ebb6b9a24b1afe616b5c2312465100b861e9b8d4da374be535f2d6b967ce2f53826d7f6edc2a0d32b2ab55abc96acc2f9d + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 10/01b7b440f83a997350a988e9d2f558366c0f90f15be19f4aa7f1bb3109a4e153dfc3b9fbf78e14ea725717017407eeaa2271e3896374a0181e8f52445740846d languageName: node linkType: hard @@ -7486,10 +7537,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.648": - version: 1.4.648 - resolution: "electron-to-chromium@npm:1.4.648" - checksum: 10/a18f06bafce9017ac7b587f76dac77063a0beb7dfcdf9d5971f72b322f56af6315e4fc3c59154a260a9188c168ac7632538797d57a8c53ab57025ace0c9441f2 +"electron-to-chromium@npm:^1.4.668": + version: 1.4.673 + resolution: "electron-to-chromium@npm:1.4.673" + checksum: 10/e9e82bec564f4398040a3ce5a8e344dfebac624e458545c7d9317bb54d65f7000648e552acebc1951cda4562e5d9cebcb7d9e76a376c8ad1e04f7860230935fa languageName: node linkType: hard @@ -7589,11 +7640,11 @@ __metadata: linkType: hard "envinfo@npm:^7.7.3": - version: 7.11.0 - resolution: "envinfo@npm:7.11.0" + version: 7.11.1 + resolution: "envinfo@npm:7.11.1" bin: envinfo: dist/cli.js - checksum: 10/8cba09db181329b243fe02b3384ec275ebf93d5d3663c31e2064697aa96576c7de9b7e1c878a250f8eaec0db8026bace747709dcdc8d8a4ecd9a653cdbc08926 + checksum: 10/5a18ead05954ac1643350170fefce2436a9cb758dc402e36fe4616553ee46469f766fcb6df72379d1741a2e5b55918949b343ff6174502c31c524a5cf75f05cd languageName: node linkType: hard @@ -7622,50 +7673,75 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.22.1": - version: 1.22.3 - resolution: "es-abstract@npm:1.22.3" +"es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3": + version: 1.22.4 + resolution: "es-abstract@npm:1.22.4" dependencies: - array-buffer-byte-length: "npm:^1.0.0" - arraybuffer.prototype.slice: "npm:^1.0.2" - available-typed-arrays: "npm:^1.0.5" - call-bind: "npm:^1.0.5" - es-set-tostringtag: "npm:^2.0.1" + array-buffer-byte-length: "npm:^1.0.1" + arraybuffer.prototype.slice: "npm:^1.0.3" + available-typed-arrays: "npm:^1.0.6" + call-bind: "npm:^1.0.7" + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + es-set-tostringtag: "npm:^2.0.2" es-to-primitive: "npm:^1.2.1" function.prototype.name: "npm:^1.1.6" - get-intrinsic: "npm:^1.2.2" - get-symbol-description: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + get-symbol-description: "npm:^1.0.2" globalthis: "npm:^1.0.3" gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" has-proto: "npm:^1.0.1" has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - internal-slot: "npm:^1.0.5" - is-array-buffer: "npm:^3.0.2" + hasown: "npm:^2.0.1" + internal-slot: "npm:^1.0.7" + is-array-buffer: "npm:^3.0.4" is-callable: "npm:^1.2.7" is-negative-zero: "npm:^2.0.2" is-regex: "npm:^1.1.4" is-shared-array-buffer: "npm:^1.0.2" is-string: "npm:^1.0.7" - is-typed-array: "npm:^1.1.12" + is-typed-array: "npm:^1.1.13" is-weakref: "npm:^1.0.2" object-inspect: "npm:^1.13.1" object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.4" - regexp.prototype.flags: "npm:^1.5.1" - safe-array-concat: "npm:^1.0.1" - safe-regex-test: "npm:^1.0.0" + object.assign: "npm:^4.1.5" + regexp.prototype.flags: "npm:^1.5.2" + safe-array-concat: "npm:^1.1.0" + safe-regex-test: "npm:^1.0.3" string.prototype.trim: "npm:^1.2.8" string.prototype.trimend: "npm:^1.0.7" string.prototype.trimstart: "npm:^1.0.7" - typed-array-buffer: "npm:^1.0.0" + typed-array-buffer: "npm:^1.0.1" typed-array-byte-length: "npm:^1.0.0" typed-array-byte-offset: "npm:^1.0.0" typed-array-length: "npm:^1.0.4" unbox-primitive: "npm:^1.0.2" - which-typed-array: "npm:^1.1.13" - checksum: 10/e1ea9738ece15f810733b7bd71d825b555e01bb8c860272560d7d901467a9db1265214d6cf44f3beeb5d73ae421a609b9ad93a39aa47bbcd8cde510d5e0aa875 + which-typed-array: "npm:^1.1.14" + checksum: 10/062e562a000e280c0c0683ad4a7b81732f97463bc769110c668a8edb739cd5df56975fa55965f5304a3256fd6eee03b9b66a47d863076f8976c2050731946b1f + languageName: node + linkType: hard + +"es-array-method-boxes-properly@npm:^1.0.0": + version: 1.0.0 + resolution: "es-array-method-boxes-properly@npm:1.0.0" + checksum: 10/27a8a21acf20f3f51f69dce8e643f151e380bffe569e95dc933b9ded9fcd89a765ee21b5229c93f9206c93f87395c6b75f80be8ac8c08a7ceb8771e1822ff1fb + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: "npm:^1.2.4" + checksum: 10/f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 + languageName: node + linkType: hard + +"es-errors@npm:^1.0.0, es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 languageName: node linkType: hard @@ -7676,7 +7752,7 @@ __metadata: languageName: node linkType: hard -"es-set-tostringtag@npm:^2.0.1": +"es-set-tostringtag@npm:^2.0.2": version: 2.0.2 resolution: "es-set-tostringtag@npm:2.0.2" dependencies: @@ -7687,7 +7763,7 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0": +"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": version: 1.0.2 resolution: "es-shim-unscopables@npm:1.0.2" dependencies: @@ -7707,14 +7783,15 @@ __metadata: languageName: node linkType: hard -"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.46, es5-ext@npm:^0.10.50": - version: 0.10.62 - resolution: "es5-ext@npm:0.10.62" +"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.46, es5-ext@npm:^0.10.50, es5-ext@npm:^0.10.62, es5-ext@npm:~0.10.14": + version: 0.10.63 + resolution: "es5-ext@npm:0.10.63" dependencies: es6-iterator: "npm:^2.0.3" es6-symbol: "npm:^3.1.3" + esniff: "npm:^2.0.1" next-tick: "npm:^1.1.0" - checksum: 10/3f6a3bcdb7ff82aaf65265799729828023c687a2645da04005b8f1dc6676a0c41fd06571b2517f89dcf143e0268d3d9ef0fdfd536ab74580083204c688d6fb45 + checksum: 10/69e373ffcf4664f12697adfab4a043a5cd93386c5e9d9b3eda4bd59f3591c7bebba3647475a3c9ccb1b48ec941b93c9507bf54727f0311a65e7220bc895eade9 languageName: node linkType: hard @@ -7752,9 +7829,9 @@ __metadata: linkType: hard "escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: 10/afa618e73362576b63f6ca83c975456621095a1ed42ff068174e3f5cea48afc422814dda548c96e6ebb5333e7265140c7292abcc81bbd6ccb1757d50d3a4e182 + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 10/a1e07fea2f15663c30e40b9193d658397846ffe28ce0a3e4da0d8e485fedfeca228ab846aee101a05015829adf39f9934ff45b2a3fca47bed37a29646bd05cd3 languageName: node linkType: hard @@ -7943,18 +8020,18 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-unused-imports@npm:3.0.0": - version: 3.0.0 - resolution: "eslint-plugin-unused-imports@npm:3.0.0" +"eslint-plugin-unused-imports@npm:3.1.0": + version: 3.1.0 + resolution: "eslint-plugin-unused-imports@npm:3.1.0" dependencies: eslint-rule-composer: "npm:^0.3.0" peerDependencies: - "@typescript-eslint/eslint-plugin": ^6.0.0 - eslint: ^8.0.0 + "@typescript-eslint/eslint-plugin": 6 - 7 + eslint: 8 peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true - checksum: 10/9433b80d4efdf3f8e43a38a7662b279b310020f3a80ffd2bbc56a375804b367bedfbe5b611b1969963e2de3b392bf1f389e89d2af810594ea3ab913c7e219ba1 + checksum: 10/8ad49d6d343492f5a5e6398a57d2c66c28651410d83478b16eac8fadb466cf7785b43cd7cfe33e0fb9624c464c7a92b5b78b34ad2daf418e8ac8fed5c554e1ed languageName: node linkType: hard @@ -8011,15 +8088,15 @@ __metadata: languageName: node linkType: hard -"eslint@npm:8.56.0": - version: 8.56.0 - resolution: "eslint@npm:8.56.0" +"eslint@npm:8.57.0": + version: 8.57.0 + resolution: "eslint@npm:8.57.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.6.1" "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.56.0" - "@humanwhocodes/config-array": "npm:^0.11.13" + "@eslint/js": "npm:8.57.0" + "@humanwhocodes/config-array": "npm:^0.11.14" "@humanwhocodes/module-importer": "npm:^1.0.1" "@nodelib/fs.walk": "npm:^1.2.8" "@ungap/structured-clone": "npm:^1.2.0" @@ -8055,7 +8132,19 @@ __metadata: text-table: "npm:^0.2.0" bin: eslint: bin/eslint.js - checksum: 10/ef6193c6e4cef20774b985a5cc2fd4bf6d3c4decd423117cbc4a0196617861745db291217ad3c537bc3a160650cca965bc818f55e1f3e446af1fcb293f9940a5 + checksum: 10/00496e218b23747a7a9817bf58b522276d0dc1f2e546dceb4eea49f9871574088f72f1f069a6b560ef537efa3a75261b8ef70e51ef19033da1cc4c86a755ef15 + languageName: node + linkType: hard + +"esniff@npm:^2.0.1": + version: 2.0.1 + resolution: "esniff@npm:2.0.1" + dependencies: + d: "npm:^1.0.1" + es5-ext: "npm:^0.10.62" + event-emitter: "npm:^0.3.5" + type: "npm:^2.7.2" + checksum: 10/f6a2abd2f8c5fe57c5fcf53e5407c278023313d0f6c3a92688e7122ab9ac233029fd424508a196ae5bc561aa1f67d23f4e2435b1a0d378030f476596129056ac languageName: node linkType: hard @@ -8137,6 +8226,16 @@ __metadata: languageName: node linkType: hard +"event-emitter@npm:^0.3.5": + version: 0.3.5 + resolution: "event-emitter@npm:0.3.5" + dependencies: + d: "npm:1" + es5-ext: "npm:~0.10.14" + checksum: 10/a7f5ea80029193f4869782d34ef7eb43baa49cd397013add1953491b24588468efbe7e3cc9eb87d53f33397e7aab690fd74c079ec440bf8b12856f6bdb6e9396 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.0": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -8408,11 +8507,11 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.16.0 - resolution: "fastq@npm:1.16.0" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10/de151543aab9d91900ed5da88860c46987ece925c628df586fac664235f25e020ec20729e1c032edb5fd2520fd4aa5b537d69e39b689e65e82112cfbecb4479e + checksum: 10/a443180068b527dd7b3a63dc7f2a47ceca2f3e97b9c00a1efe5538757e6cc4056a3526df94308075d7727561baf09ebaa5b67da8dcbddb913a021c5ae69d1f69 languageName: node linkType: hard @@ -8901,15 +9000,16 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2": - version: 1.2.2 - resolution: "get-intrinsic@npm:1.2.2" +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" dependencies: + es-errors: "npm:^1.3.0" function-bind: "npm:^1.1.2" has-proto: "npm:^1.0.1" has-symbols: "npm:^1.0.3" hasown: "npm:^2.0.0" - checksum: 10/aa96db4f809734d26d49b59bc8669d73a0ae792da561514e987735573a1dfaede516cd102f217a078ea2b42d4c4fb1f83d487932cb15d49826b726cc9cd4470b + checksum: 10/85bbf4b234c3940edf8a41f4ecbd4e25ce78e5e6ad4e24ca2f77037d983b9ef943fd72f00f3ee97a49ec622a506b67db49c36246150377efcda1c9eb03e5f06d languageName: node linkType: hard @@ -8934,13 +9034,14 @@ __metadata: languageName: node linkType: hard -"get-symbol-description@npm:^1.0.0": - version: 1.0.0 - resolution: "get-symbol-description@npm:1.0.0" +"get-symbol-description@npm:^1.0.2": + version: 1.0.2 + resolution: "get-symbol-description@npm:1.0.2" dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.1.1" - checksum: 10/7e5f298afe0f0872747dce4a949ce490ebc5d6dd6aefbbe5044543711c9b19a4dfaebdbc627aee99e1299d58a435b2fbfa083458c1d58be6dc03a3bada24d359 + call-bind: "npm:^1.0.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + checksum: 10/e1cb53bc211f9dbe9691a4f97a46837a553c4e7caadd0488dc24ac694db8a390b93edd412b48dcdd0b4bbb4c595de1709effc75fc87c0839deedc6968f5bd973 languageName: node linkType: hard @@ -9029,7 +9130,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.3.10, glob@npm:^10.2.2, glob@npm:^10.3.10": +"glob@npm:10.3.10, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": version: 10.3.10 resolution: "glob@npm:10.3.10" dependencies: @@ -9044,17 +9145,16 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.2.0": - version: 7.2.0 - resolution: "glob@npm:7.2.0" +"glob@npm:8.1.0, glob@npm:^8.0.3": + version: 8.1.0 + resolution: "glob@npm:8.1.0" dependencies: fs.realpath: "npm:^1.0.0" inflight: "npm:^1.0.4" inherits: "npm:2" - minimatch: "npm:^3.0.4" + minimatch: "npm:^5.0.1" once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10/bc78b6ea0735b6e23d20678aba4ae6a4760e8c9527e3c4683ac25b14e70f55f9531245dcf25959b70cbc4aa3dcce1fc37ab65fd026a4cbd70aa3a44880bd396b + checksum: 10/9aab1c75eb087c35dbc41d1f742e51d0507aa2b14c910d96fb8287107a10a22f4bbdce26fc0a3da4c69a20f7b26d62f1640b346a4f6e6becfff47f335bb1dc5e languageName: node linkType: hard @@ -9072,19 +9172,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.3": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^5.0.1" - once: "npm:^1.3.0" - checksum: 10/9aab1c75eb087c35dbc41d1f742e51d0507aa2b14c910d96fb8287107a10a22f4bbdce26fc0a3da4c69a20f7b26d62f1640b346a4f6e6becfff47f335bb1dc5e - languageName: node - linkType: hard - "global-modules@npm:^1.0.0": version: 1.0.0 resolution: "global-modules@npm:1.0.0" @@ -9248,17 +9335,16 @@ __metadata: languageName: node linkType: hard -"gulp-json-transform@npm:0.4.8": - version: 0.4.8 - resolution: "gulp-json-transform@npm:0.4.8" +"gulp-json-transform@npm:0.5.0": + version: 0.5.0 + resolution: "gulp-json-transform@npm:0.5.0" dependencies: ansi-colors: "npm:^1.0.1" fancy-log: "npm:^1.3.2" plugin-error: "npm:^1.0.1" - promise: "npm:^8.0.1" through2: "npm:^2.0.3" vinyl: "npm:^2.1.0" - checksum: 10/5b117e59cd806e530cb4f67089e28eb1a6234bfb1461497e540be2a24cd94c5a8ce573d5ff9222c1481d951316054742a2212c044df0e047256ce10ce7bcd328 + checksum: 10/0879149337450e4ada1363fa72c351d2386b249df31aa8313c699c267cae3ece14ff1ab77dc7bb6a67827902db68ee0e0232d67ebfca55900dc17f3cfe219c42 languageName: node linkType: hard @@ -9348,12 +9434,12 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.1": - version: 1.0.1 - resolution: "has-property-descriptors@npm:1.0.1" +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.1, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" dependencies: - get-intrinsic: "npm:^1.2.2" - checksum: 10/21a47bb080a24e79594aef1ce71e1a18a1c5ab4120308e218088f67ebb7f6f408847541e2d96e5bd00e90eef5c5a49e4ebbdc8fc2d5b365a2c379aef071642f0 + es-define-property: "npm:^1.0.0" + checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 languageName: node linkType: hard @@ -9371,12 +9457,12 @@ __metadata: languageName: node linkType: hard -"has-tostringtag@npm:^1.0.0": - version: 1.0.0 - resolution: "has-tostringtag@npm:1.0.0" +"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.1": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" dependencies: - has-symbols: "npm:^1.0.2" - checksum: 10/95546e7132efc895a9ae64a8a7cf52588601fc3d52e0304ed228f336992cdf0baaba6f3519d2655e560467db35a1ed79f6420c286cc91a13aa0647a31ed92570 + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe languageName: node linkType: hard @@ -9419,12 +9505,12 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0": - version: 2.0.0 - resolution: "hasown@npm:2.0.0" +"hasown@npm:^2.0.0, hasown@npm:^2.0.1": + version: 2.0.1 + resolution: "hasown@npm:2.0.1" dependencies: function-bind: "npm:^1.1.2" - checksum: 10/c330f8d93f9d23fe632c719d4db3d698ef7d7c367d51548b836069e06a90fa9151e868c8e67353cfe98d67865bf7354855db28fa36eb1b18fa5d4a3f4e7f1c90 + checksum: 10/b7f9107387ee68abed88e965c2b99e868b5e0e9d289db1ddd080706ffafb69533b4f538b0e6362585bae8d6cbd080249f65e79702f74c225990f66d6106be3f6 languageName: node linkType: hard @@ -9437,10 +9523,10 @@ __metadata: languageName: node linkType: hard -"hls.js@npm:1.5.3": - version: 1.5.3 - resolution: "hls.js@npm:1.5.3" - checksum: 10/c5b7cb6fddd6ad425a82e1b8b4f818552e6e242254b35c7d7c7b9d3beb4dd40cc85d8bbc32a5d1b57031305d7d9f4973f9d0f80b70351bb8597393f4629072fc +"hls.js@npm:1.5.6": + version: 1.5.6 + resolution: "hls.js@npm:1.5.6" + checksum: 10/795c1d41b5ee682a48c21a78fa995ab8aa2b5badf84593eb12965b0fc58050ca5330df36a115946b8757dff558ba172f9210bb2896b12241416e069269e15d95 languageName: node linkType: hard @@ -9456,14 +9542,14 @@ __metadata: "@babel/preset-typescript": "npm:7.23.3" "@babel/runtime": "npm:7.23.9" "@braintree/sanitize-url": "npm:7.0.0" - "@bundle-stats/plugin-webpack-filter": "npm:4.9.2" + "@bundle-stats/plugin-webpack-filter": "npm:4.10.1" "@codemirror/autocomplete": "npm:6.12.0" "@codemirror/commands": "npm:6.3.3" "@codemirror/language": "npm:6.10.1" "@codemirror/legacy-modes": "npm:6.3.3" - "@codemirror/search": "npm:6.5.5" - "@codemirror/state": "npm:6.4.0" - "@codemirror/view": "npm:6.23.1" + "@codemirror/search": "npm:6.5.6" + "@codemirror/state": "npm:6.4.1" + "@codemirror/view": "npm:6.24.1" "@egjs/hammerjs": "npm:2.0.17" "@formatjs/intl-datetimeformat": "npm:6.12.2" "@formatjs/intl-displaynames": "npm:6.6.6" @@ -9473,12 +9559,12 @@ __metadata: "@formatjs/intl-numberformat": "npm:8.10.0" "@formatjs/intl-pluralrules": "npm:5.2.12" "@formatjs/intl-relativetimeformat": "npm:11.2.12" - "@fullcalendar/core": "npm:6.1.10" - "@fullcalendar/daygrid": "npm:6.1.10" - "@fullcalendar/interaction": "npm:6.1.10" - "@fullcalendar/list": "npm:6.1.10" - "@fullcalendar/luxon3": "npm:6.1.10" - "@fullcalendar/timegrid": "npm:6.1.10" + "@fullcalendar/core": "npm:6.1.11" + "@fullcalendar/daygrid": "npm:6.1.11" + "@fullcalendar/interaction": "npm:6.1.11" + "@fullcalendar/list": "npm:6.1.11" + "@fullcalendar/luxon3": "npm:6.1.11" + "@fullcalendar/timegrid": "npm:6.1.11" "@koa/cors": "npm:5.0.0" "@lezer/highlight": "npm:1.2.0" "@lit-labs/context": "npm:0.4.1" @@ -9486,7 +9572,7 @@ __metadata: "@lit-labs/observers": "npm:2.0.2" "@lit-labs/virtualizer": "npm:2.0.12" "@lokalise/node-api": "npm:12.1.0" - "@lrnwebcomponents/simple-tooltip": "npm:8.0.0" + "@lrnwebcomponents/simple-tooltip": "patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch" "@material/chips": "npm:=14.0.0-canary.53b3cad2f.0" "@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0" "@material/mwc-base": "npm:0.27.0" @@ -9512,7 +9598,7 @@ __metadata: "@material/mwc-top-app-bar": "npm:0.27.0" "@material/mwc-top-app-bar-fixed": "npm:0.27.0" "@material/top-app-bar": "npm:=14.0.0-canary.53b3cad2f.0" - "@material/web": "npm:=1.2.0" + "@material/web": "npm:=1.3.0" "@mdi/js": "npm:7.4.47" "@mdi/svg": "npm:7.4.47" "@octokit/auth-oauth-device": "npm:6.0.1" @@ -9533,6 +9619,7 @@ __metadata: "@types/babel__plugin-transform-runtime": "npm:7.9.5" "@types/chromecast-caf-receiver": "npm:6.0.13" "@types/chromecast-caf-sender": "npm:1.0.8" + "@types/color-name": "npm:1.1.3" "@types/glob": "npm:8.1.0" "@types/html-minifier-terser": "npm:7.0.2" "@types/js-yaml": "npm:4.0.9" @@ -9542,14 +9629,14 @@ __metadata: "@types/mocha": "npm:10.0.6" "@types/qrcode": "npm:1.5.5" "@types/serve-handler": "npm:6.1.4" - "@types/sortablejs": "npm:1.15.7" + "@types/sortablejs": "npm:1.15.8" "@types/tar": "npm:6.1.11" "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" - "@typescript-eslint/eslint-plugin": "npm:6.20.0" - "@typescript-eslint/parser": "npm:6.20.0" - "@vaadin/combo-box": "npm:24.3.5" - "@vaadin/vaadin-themable-mixin": "npm:24.3.5" + "@typescript-eslint/eslint-plugin": "npm:7.0.2" + "@typescript-eslint/parser": "npm:7.0.2" + "@vaadin/combo-box": "npm:24.3.6" + "@vaadin/vaadin-themable-mixin": "npm:24.3.6" "@vibrant/color": "npm:3.2.1-alpha.1" "@vibrant/core": "npm:3.2.1-alpha.1" "@vibrant/quantizer-mmcq": "npm:3.2.1-alpha.1" @@ -9561,10 +9648,11 @@ __metadata: app-datepicker: "npm:5.1.1" babel-loader: "npm:9.1.3" babel-plugin-template-html-minifier: "npm:4.1.0" - chai: "npm:5.0.3" + chai: "npm:5.1.0" chart.js: "npm:4.4.1" + color-name: "npm:2.0.0" comlink: "npm:4.4.1" - core-js: "npm:3.35.1" + core-js: "npm:3.36.0" cropperjs: "npm:1.6.1" date-fns: "npm:2.30.0" date-fns-tz: "npm:2.0.0" @@ -9572,7 +9660,7 @@ __metadata: deep-freeze: "npm:0.0.1" del: "npm:7.1.0" element-internals-polyfill: "npm:1.3.10" - eslint: "npm:8.56.0" + eslint: "npm:8.57.0" eslint-config-airbnb-base: "npm:15.0.0" eslint-config-airbnb-typescript: "npm:17.1.0" eslint-config-prettier: "npm:9.1.0" @@ -9581,7 +9669,7 @@ __metadata: eslint-plugin-import: "npm:2.29.1" eslint-plugin-lit: "npm:1.11.0" eslint-plugin-lit-a11y: "npm:4.1.2" - eslint-plugin-unused-imports: "npm:3.0.0" + eslint-plugin-unused-imports: "npm:3.1.0" eslint-plugin-wc: "npm:2.0.4" fancy-log: "npm:2.0.0" fs-extra: "npm:11.2.0" @@ -9590,14 +9678,14 @@ __metadata: google-timezones-json: "npm:1.2.0" gulp: "npm:4.0.2" gulp-flatmap: "npm:1.0.2" - gulp-json-transform: "npm:0.4.8" + gulp-json-transform: "npm:0.5.0" gulp-merge-json: "npm:2.1.2" gulp-rename: "npm:2.0.0" gulp-zopfli-green: "npm:6.0.1" - hls.js: "npm:1.5.3" + hls.js: "npm:1.5.6" home-assistant-js-websocket: "npm:9.1.0" html-minifier-terser: "npm:7.2.0" - husky: "npm:9.0.10" + husky: "npm:9.0.11" idb-keyval: "npm:6.2.1" instant-mocha: "npm:1.5.2" intl-messageformat: "npm:10.5.11" @@ -9605,21 +9693,21 @@ __metadata: jszip: "npm:3.10.1" leaflet: "npm:1.9.4" leaflet-draw: "npm:1.0.4" - lint-staged: "npm:15.2.1" + lint-staged: "npm:15.2.2" lit: "npm:2.8.0" lit-analyzer: "npm:2.0.3" lodash.template: "npm:4.5.0" luxon: "npm:3.4.4" - magic-string: "npm:0.30.6" + magic-string: "npm:0.30.7" map-stream: "npm:0.0.7" - marked: "npm:11.2.0" + marked: "npm:12.0.0" memoize-one: "npm:6.0.0" - mocha: "npm:10.2.0" + mocha: "npm:10.3.0" node-vibrant: "npm:3.2.1-alpha.1" object-hash: "npm:3.0.0" open: "npm:10.0.3" pinst: "npm:3.0.0" - prettier: "npm:3.2.4" + prettier: "npm:3.2.5" proxy-polyfill: "npm:0.3.2" punycode: "npm:2.3.1" qr-scanner: "npm:1.4.2" @@ -9653,12 +9741,12 @@ __metadata: vis-network: "npm:9.1.9" vue: "npm:2.7.16" vue2-daterange-picker: "npm:0.6.8" - webpack: "npm:5.90.1" + webpack: "npm:5.90.3" webpack-cli: "npm:5.1.4" - webpack-dev-server: "npm:4.15.1" + webpack-dev-server: "npm:5.0.2" webpack-manifest-plugin: "npm:5.0.0" webpack-stats-plugin: "npm:1.1.3" - webpackbar: "npm:6.0.0" + webpackbar: "npm:6.0.1" weekstart: "npm:2.0.0" workbox-build: "npm:7.0.0" workbox-cacheable-response: "npm:7.0.0" @@ -9706,7 +9794,7 @@ __metadata: languageName: node linkType: hard -"html-entities@npm:^2.3.2": +"html-entities@npm:^2.4.0": version: 2.4.0 resolution: "html-entities@npm:2.4.0" checksum: 10/646f2f19214bad751e060ceef4df98520654a1d0cd631b55d45504df2f0aaf8a14d8c0a5a4f92b353be298774d856157ac2d04a031d78889c9011892078ca157 @@ -9817,12 +9905,12 @@ __metadata: linkType: hard "http-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "http-proxy-agent@npm:7.0.0" + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" dependencies: agent-base: "npm:^7.1.0" debug: "npm:^4.3.4" - checksum: 10/dbaaf3d9f3fc4df4a5d7ec45d456ec50f575240b557160fa63427b447d1f812dd7fe4a4f17d2e1ba003d231f07edf5a856ea6d91cb32d533062ff20a7803ccac + checksum: 10/d062acfa0cb82beeb558f1043c6ba770ea892b5fb7b28654dbc70ea2aeea55226dd34c02a294f6c1ca179a5aa483c4ea641846821b182edbd9cc5d89b54c6848 languageName: node linkType: hard @@ -9856,12 +9944,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1": - version: 7.0.2 - resolution: "https-proxy-agent@npm:7.0.2" + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" dependencies: agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10/9ec844f78fd643608239c9c3f6819918631df5cd3e17d104cc507226a39b5d4adda9d790fc9fd63ac0d2bb8a761b2f9f60faa80584a9bf9d7f2e8c5ed0acd330 + checksum: 10/405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 languageName: node linkType: hard @@ -9879,12 +9967,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:9.0.10": - version: 9.0.10 - resolution: "husky@npm:9.0.10" +"husky@npm:9.0.11": + version: 9.0.11 + resolution: "husky@npm:9.0.11" bin: husky: bin.mjs - checksum: 10/c303f1862e2b63873605df55a2b08303155e35c799585d7dd677628f62d716e7304bd984fc7d00ec44e740caac07d51720d1a0abb0a23a70a38859d89eb8e72d + checksum: 10/8a9b7cb9dc8494b470b3b47b386e65d579608c6206da80d3cc8b71d10e37947264af3dfe00092368dad9673b51d2a5ee87afb4b2291e77ba9e7ec1ac36e56cd1 languageName: node linkType: hard @@ -9928,9 +10016,9 @@ __metadata: linkType: hard "ignore@npm:^5.2.0, ignore@npm:^5.2.4": - version: 5.3.0 - resolution: "ignore@npm:5.3.0" - checksum: 10/51594355cea4c6ad6b28b3b85eb81afa7b988a1871feefd7062baf136c95aa06760ee934fa9590e43d967bd377ce84a4cf6135fbeb6063e063f1182a0e9a3bcd + version: 5.3.1 + resolution: "ignore@npm:5.3.1" + checksum: 10/0a884c2fbc8c316f0b9f92beaf84464253b73230a4d4d286697be45fca081199191ca33e1c2e82d9e5f851f5e9a48a78e25a35c951e7eb41e59f150db3530065 languageName: node linkType: hard @@ -10041,14 +10129,14 @@ __metadata: languageName: node linkType: hard -"internal-slot@npm:^1.0.5": - version: 1.0.6 - resolution: "internal-slot@npm:1.0.6" +"internal-slot@npm:^1.0.5, internal-slot@npm:^1.0.7": + version: 1.0.7 + resolution: "internal-slot@npm:1.0.7" dependencies: - get-intrinsic: "npm:^1.2.2" + es-errors: "npm:^1.3.0" hasown: "npm:^2.0.0" side-channel: "npm:^1.0.4" - checksum: 10/bc2022eb1f277f2fcb2a60e7ced451c7ffc7a769b12e63c7a3fb247af8b5a1bed06428ce724046a8bca39ed6eb5b6832501a42f2e9a5ec4a9a7dc4e634431616 + checksum: 10/3e66720508831153ecf37d13def9f6856f9f2960989ec8a0a0476c98f887fca9eff0163127466485cb825c900c2d6fc601aa9117b7783b90ffce23a71ea5d053 languageName: node linkType: hard @@ -10085,17 +10173,20 @@ __metadata: languageName: node linkType: hard -"ip@npm:^1.1.5": - version: 1.1.8 - resolution: "ip@npm:1.1.8" - checksum: 10/52975ebf84a090162d561fc6948fbc4c53775a8054c05371f09cfcb40e30a53aa225b4efb624f630cff5af2dd8124c82dd68e4df065dc1d1ca91d04e850e9cde +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c languageName: node linkType: hard -"ip@npm:^2.0.0": - version: 2.0.0 - resolution: "ip@npm:2.0.0" - checksum: 10/1270b11e534a466fb4cf4426cbcc3a907c429389f7f4e4e3b288b42823562e88d6a509ceda8141a507de147ca506141f745005c0aa144569d94cf24a54eb52bc +"ip@npm:^1.1.5": + version: 1.1.9 + resolution: "ip@npm:1.1.9" + checksum: 10/29261559b806f64929ada21e6d7e3bf4e67f2b43a4cb67500fdb72cead2e655ce97451a2e325eca3f404081c634ff5c3a68472814744b7f2148ddffc0fdfe66c languageName: node linkType: hard @@ -10106,7 +10197,7 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:^2.0.1": +"ipaddr.js@npm:^2.1.0": version: 2.1.0 resolution: "ipaddr.js@npm:2.1.0" checksum: 10/42c16d95cf451399707c2c46e605b88db1ea2b1477b25774b5a7ee96852b0bb1efdc01adbff01fedbe702ff246e1aca5c5e915a6f5a1f1485233a5f7c2eb73c2 @@ -10132,14 +10223,13 @@ __metadata: languageName: node linkType: hard -"is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": - version: 3.0.2 - resolution: "is-array-buffer@npm:3.0.2" +"is-array-buffer@npm:^3.0.4": + version: 3.0.4 + resolution: "is-array-buffer@npm:3.0.4" dependencies: call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.0" - is-typed-array: "npm:^1.1.10" - checksum: 10/dcac9dda66ff17df9cabdc58214172bf41082f956eab30bb0d86bc0fab1e44b690fc8e1f855cf2481245caf4e8a5a006a982a71ddccec84032ed41f9d8da8c14 + get-intrinsic: "npm:^1.2.1" + checksum: 10/34a26213d981d58b30724ef37a1e0682f4040d580fa9ff58fdfdd3cefcb2287921718c63971c1c404951e7b747c50fdc7caf6e867e951353fa71b369c04c969b languageName: node linkType: hard @@ -10403,6 +10493,13 @@ __metadata: languageName: node linkType: hard +"is-network-error@npm:^1.0.0": + version: 1.0.1 + resolution: "is-network-error@npm:1.0.1" + checksum: 10/165d61500c4186c62db5a3a693d6bfa14ca40fe9b471ef4cd4f27b20ef6760880faf5386dc01ca9867531631782941fedaa94521d09959edf71f046e393c7b91 + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -10576,12 +10673,12 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.12, is-typed-array@npm:^1.1.9": - version: 1.1.12 - resolution: "is-typed-array@npm:1.1.12" +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.9": + version: 1.1.13 + resolution: "is-typed-array@npm:1.1.13" dependencies: - which-typed-array: "npm:^1.1.11" - checksum: 10/d953adfd3c41618d5e01b2a10f21817e4cdc9572772fa17211100aebb3811b6e3c2e308a0558cc87d218a30504cb90154b833013437776551bfb70606fb088ca + which-typed-array: "npm:^1.1.14" + checksum: 10/f850ba08286358b9a11aee6d93d371a45e3c59b5953549ee1c1a9a55ba5c1dd1bd9952488ae194ad8f32a9cf5e79c8fa5f0cc4d78c00720aa0bbcf238b38062d languageName: node linkType: hard @@ -10673,9 +10770,9 @@ __metadata: linkType: hard "isbinaryfile@npm:^5.0.0": - version: 5.0.0 - resolution: "isbinaryfile@npm:5.0.0" - checksum: 10/511bb5ce54c903e9881ca46fe7fe06759b29d9d01d6929f3c46cb95a4bf369320d9de858943d5bf4ac446a442b53be095b325b1deebca0e67ccd9f466c53ef74 + version: 5.0.2 + resolution: "isbinaryfile@npm:5.0.2" + checksum: 10/515d7c963b35c2c443457d18c9152d1f655f3a0e2dceb548448e482145c1897e57a92fc024dece7de98c85c2909f5528e34e3d720c307887529cd689d7a7cd36 languageName: node linkType: hard @@ -10790,6 +10887,13 @@ __metadata: languageName: node linkType: hard +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef + languageName: node + linkType: hard + "jsesc@npm:^2.5.1": version: 2.5.2 resolution: "jsesc@npm:2.5.2" @@ -11077,7 +11181,7 @@ __metadata: languageName: node linkType: hard -"launch-editor@npm:^2.6.0": +"launch-editor@npm:^2.6.1": version: 2.6.1 resolution: "launch-editor@npm:2.6.1" dependencies: @@ -11184,9 +11288,9 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:15.2.1": - version: 15.2.1 - resolution: "lint-staged@npm:15.2.1" +"lint-staged@npm:15.2.2": + version: 15.2.2 + resolution: "lint-staged@npm:15.2.2" dependencies: chalk: "npm:5.3.0" commander: "npm:11.1.0" @@ -11200,7 +11304,7 @@ __metadata: yaml: "npm:2.3.4" bin: lint-staged: bin/lint-staged.js - checksum: 10/ee2e858b9afea01378ff3a3ff5fbed000f856c553f0ac570d9be6390e27299e586ae047d1efa77fdf17f15aeef0c53f6c658eeb24432748b9203698d929d0c76 + checksum: 10/5855ae7abf3ffdc2d66e8ad20759915e76544e7c4bcdfef78c82b5c126502284320d9fb0ecde554a6d07747311ab751d0bccbe3468aa5d5a7661774317cd7437 languageName: node linkType: hard @@ -11473,9 +11577,9 @@ __metadata: linkType: hard "lru-cache@npm:^10.0.1, lru-cache@npm:^9.1.1 || ^10.0.0": - version: 10.1.0 - resolution: "lru-cache@npm:10.1.0" - checksum: 10/207278d6fa711fb1f94a0835d4d4737441d2475302482a14785b10515e4c906a57ebf9f35bf060740c9560e91c7c1ad5a04fd7ed030972a9ba18bce2a228e95b + version: 10.2.0 + resolution: "lru-cache@npm:10.2.0" + checksum: 10/502ec42c3309c0eae1ce41afca471f831c278566d45a5273a0c51102dee31e0e250a62fa9029c3370988df33a14188a38e682c16143b794de78668de3643e302 languageName: node linkType: hard @@ -11504,12 +11608,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:0.30.6, magic-string@npm:^0.30.3": - version: 0.30.6 - resolution: "magic-string@npm:0.30.6" +"magic-string@npm:0.30.7, magic-string@npm:^0.30.3": + version: 0.30.7 + resolution: "magic-string@npm:0.30.7" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.4.15" - checksum: 10/046fbf11614a271e7214d32ca02a2af8d18b268403db9d50580f421ff2222261f1cd8bb02a938206130e3c43deefa6423ee4622c4f161212298570f22b4059bb + checksum: 10/883eaaf6792a3263e44f4bcdcd35ace272268e4b98ed5a770ad711947958d2f9fc683e474945e306e2bdc152b7e44d369ee312690d87025b9879fc63fbe1409c languageName: node linkType: hard @@ -11582,12 +11686,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:11.2.0": - version: 11.2.0 - resolution: "marked@npm:11.2.0" +"marked@npm:12.0.0": + version: 12.0.0 + resolution: "marked@npm:12.0.0" bin: marked: bin/marked.js - checksum: 10/0c8c0d263617a04f066db6f5adfed811a8eb78a685850d4d0b8b9ef351e416fb871813ea7ee7f94f4d5f67da98c3a2a4259b8684e4f93a6a673733fe9d9f2868 + checksum: 10/ac2e5a3ebf33f8636e65c1eb7f73267cbe101fea1ad08abab60d51e5b4fda30faa59050e2837dc03fb6dbf58f630485c8d01ae5b9d90d36bf4562d7f40c1d33e languageName: node linkType: hard @@ -11610,7 +11714,7 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.4.3, memfs@npm:^3.5.0": +"memfs@npm:^3.5.0": version: 3.5.3 resolution: "memfs@npm:3.5.3" dependencies: @@ -11619,6 +11723,15 @@ __metadata: languageName: node linkType: hard +"memfs@npm:^4.6.0": + version: 4.7.6 + resolution: "memfs@npm:4.7.6" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10/b2a4c95635aa7ed9162c647b60ebbe6f84c541dd91d99e56602f1e904503447aaf92c46918ddf0b84acfb8e52e9af90bd63a6c0cfb1cc8bfdf31379f2fba8e11 + languageName: node + linkType: hard + "memoize-one@npm:6.0.0": version: 6.0.0 resolution: "memoize-one@npm:6.0.0" @@ -11763,7 +11876,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:3.1.2, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -11927,9 +12040,9 @@ __metadata: languageName: node linkType: hard -"mocha@npm:10.2.0": - version: 10.2.0 - resolution: "mocha@npm:10.2.0" +"mocha@npm:10.3.0": + version: 10.3.0 + resolution: "mocha@npm:10.3.0" dependencies: ansi-colors: "npm:4.1.1" browser-stdout: "npm:1.3.1" @@ -11938,13 +12051,12 @@ __metadata: diff: "npm:5.0.0" escape-string-regexp: "npm:4.0.0" find-up: "npm:5.0.0" - glob: "npm:7.2.0" + glob: "npm:8.1.0" he: "npm:1.2.0" js-yaml: "npm:4.1.0" log-symbols: "npm:4.1.0" minimatch: "npm:5.0.1" ms: "npm:2.1.3" - nanoid: "npm:3.3.3" serialize-javascript: "npm:6.0.0" strip-json-comments: "npm:3.1.1" supports-color: "npm:8.1.1" @@ -11955,7 +12067,7 @@ __metadata: bin: _mocha: bin/_mocha mocha: bin/mocha.js - checksum: 10/f7362898ae65e8fe716cfe62fd014b432d100c9611aaf5abe85ed14efcbfdd82f3bdf32c44bccf00c9059a264c7e8d93a69dd5b830652109052a92beffb7ea35 + checksum: 10/8b30b3fdb3f365fca7e069fce732e13db4192e1bf8f49e63ec67a3b7857c41cbcd162289ad22178db6ef405fb251b8a0dbb54e942d08c72abf45e1ec3fcdd052 languageName: node linkType: hard @@ -12015,15 +12127,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.3": - version: 3.3.3 - resolution: "nanoid@npm:3.3.3" - bin: - nanoid: bin/nanoid.cjs - checksum: 10/c703ed58a234b68245a8a4826dd25c1453a9017d34fa28bc58e7aa8247de87d854582fa2209d7aee04084cff9ce150be8fd30300abe567dc615d4e8e735f2d99 - languageName: node - linkType: hard - "nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" @@ -12081,15 +12184,15 @@ __metadata: linkType: hard "nise@npm:^5.1.5": - version: 5.1.7 - resolution: "nise@npm:5.1.7" + version: 5.1.9 + resolution: "nise@npm:5.1.9" dependencies: "@sinonjs/commons": "npm:^3.0.0" "@sinonjs/fake-timers": "npm:^11.2.2" "@sinonjs/text-encoding": "npm:^0.7.2" just-extend: "npm:^6.2.0" path-to-regexp: "npm:^6.2.1" - checksum: 10/4754e3ae52654f66e947d44d0dd40ae823e594c201474ad7c5115acb2188c839c9b8617504327051857aea8042befac946e82918e1e53b99350cb275140332d0 + checksum: 10/971caf7638d42a0e106eadd63f05adac1217f864b0a7e4519546aea82a0dbfac68586e7ff430704d54a01ff5dbf6cad58f5f67c067e21112a7deacd7789c2172 languageName: node linkType: hard @@ -12266,7 +12369,7 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.1, object-inspect@npm:^1.9.0": +"object-inspect@npm:^1.13.1": version: 1.13.1 resolution: "object-inspect@npm:1.13.1" checksum: 10/92f4989ed83422d56431bc39656d4c780348eb15d397ce352ade6b7fec08f973b53744bd41b94af021901e61acaf78fcc19e65bf464ecc0df958586a672700f0 @@ -12289,7 +12392,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.0.4, object.assign@npm:^4.1.0, object.assign@npm:^4.1.2, object.assign@npm:^4.1.4": +"object.assign@npm:^4.0.4, object.assign@npm:^4.1.0, object.assign@npm:^4.1.2, object.assign@npm:^4.1.5": version: 4.1.5 resolution: "object.assign@npm:4.1.5" dependencies: @@ -12336,14 +12439,15 @@ __metadata: linkType: hard "object.groupby@npm:^1.0.1": - version: 1.0.1 - resolution: "object.groupby@npm:1.0.1" + version: 1.0.2 + resolution: "object.groupby@npm:1.0.2" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - get-intrinsic: "npm:^1.2.1" - checksum: 10/b7123d91403f95d63978513b23a6079c30f503311f64035fafc863c291c787f287b58df3b21ef002ce1d0b820958c9009dd5a8ab696e0eca325639d345e41524 + array.prototype.filter: "npm:^1.0.3" + call-bind: "npm:^1.0.5" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.22.3" + es-errors: "npm:^1.0.0" + checksum: 10/07c1bea1772c45f7967a63358a683ef7b0bd99cabe0563e6fee3e8acc061cc5984d2f01a46472ebf10b2cb439298c46776b2134550dce457fd7240baaaa4f592 languageName: node linkType: hard @@ -12451,7 +12555,7 @@ __metadata: languageName: node linkType: hard -"open@npm:10.0.3": +"open@npm:10.0.3, open@npm:^10.0.3": version: 10.0.3 resolution: "open@npm:10.0.3" dependencies: @@ -12463,7 +12567,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.2, open@npm:^8.0.9, open@npm:^8.4.0": +"open@npm:^8.0.2, open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -12578,13 +12682,14 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:^4.5.0": - version: 4.6.2 - resolution: "p-retry@npm:4.6.2" +"p-retry@npm:^6.2.0": + version: 6.2.0 + resolution: "p-retry@npm:6.2.0" dependencies: - "@types/retry": "npm:0.12.0" + "@types/retry": "npm:0.12.2" + is-network-error: "npm:^1.0.0" retry: "npm:^0.13.1" - checksum: 10/45c270bfddaffb4a895cea16cb760dcc72bdecb6cb45fef1971fa6ea2e91ddeafddefe01e444ac73e33b1b3d5d29fb0dd18a7effb294262437221ddc03ce0f2e + checksum: 10/1a5ac16828c96c03c354f78d643dfc7aa8f8b998e1b60e27533da2c75e5cabfb1c7f88ce312e813e09a80b056011fbb372d384132e9c92d27d052bd7c282a978 languageName: node linkType: hard @@ -12636,12 +12741,12 @@ __metadata: linkType: hard "parse-bmfont-xml@npm:^1.1.4": - version: 1.1.4 - resolution: "parse-bmfont-xml@npm:1.1.4" + version: 1.1.6 + resolution: "parse-bmfont-xml@npm:1.1.6" dependencies: xml-parse-from-string: "npm:^1.0.0" - xml2js: "npm:^0.4.5" - checksum: 10/529d9c65da5e7840723d5382707d5a5177d25616e6ea434b4c474548e6229f1e64d0991bc9b38329762038e885c9097c562343007db78d9e9ca1e9b7157e6d7e + xml2js: "npm:^0.5.0" + checksum: 10/71a202da289a124db7bb7bee1b2a01b8a38b5ba36f93d6a98cea6fc1d140c16c8bc7bcccff48864ec886da035944d337b04cf70723393c411991af952fc6086b languageName: node linkType: hard @@ -13042,13 +13147,13 @@ __metadata: linkType: hard "postcss@npm:^8.4.14": - version: 8.4.33 - resolution: "postcss@npm:8.4.33" + version: 8.4.35 + resolution: "postcss@npm:8.4.35" dependencies: nanoid: "npm:^3.3.7" picocolors: "npm:^1.0.0" source-map-js: "npm:^1.0.2" - checksum: 10/e22a4594c255f26117f38419fb494d7ecab0f596cd409f7aadc8a6173abf180ed7ea970cd13fd366ab12b5840be901d2a09b25197700c2ebcb5a8077326bf519 + checksum: 10/93a7ce50cd6188f5f486a9ca98950ad27c19dfed996c45c414fa242944497e4d084a8760d3537f078630226f2bd3c6ab84b813b488740f4432e7c7039cd73a20 languageName: node linkType: hard @@ -13066,12 +13171,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.2.4": - version: 3.2.4 - resolution: "prettier@npm:3.2.4" +"prettier@npm:3.2.5": + version: 3.2.5 + resolution: "prettier@npm:3.2.5" bin: prettier: bin/prettier.cjs - checksum: 10/e2b735d0552501b3a7ac8bd3ba3b6de2920bb35bd4cd02d08cb9057ebe3e96d83b9a7e4b903d987b7530a50223b12c74d107c154337236ae2c68156ba1e65cd2 + checksum: 10/d509f9da0b70e8cacc561a1911c0d99ec75117faed27b95cc8534cb2349667dee6351b0ca83fa9d5703f14127faa52b798de40f5705f02d843da133fc3aa416a languageName: node linkType: hard @@ -13136,15 +13241,6 @@ __metadata: languageName: node linkType: hard -"promise@npm:^8.0.1": - version: 8.3.0 - resolution: "promise@npm:8.3.0" - dependencies: - asap: "npm:~2.0.6" - checksum: 10/55e9d0d723c66810966bc055c6c77a3658c0af7e4a8cc88ea47aeaf2949ca0bd1de327d9c631df61236f5406ad478384fa19a77afb3f88c0303eba9e5eb0a8d8 - languageName: node - linkType: hard - "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -13423,14 +13519,15 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.0, regexp.prototype.flags@npm:^1.5.1": - version: 1.5.1 - resolution: "regexp.prototype.flags@npm:1.5.1" +"regexp.prototype.flags@npm:^1.5.0, regexp.prototype.flags@npm:^1.5.2": + version: 1.5.2 + resolution: "regexp.prototype.flags@npm:1.5.2" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - set-function-name: "npm:^2.0.0" - checksum: 10/3fa5610b8e411bbc3a43ddfd13162f3a817beb43155fbd8caa24d4fd0ce2f431a8197541808772a5a06e5946cebfb68464c827827115bde0d11720a92fe2981a + call-bind: "npm:^1.0.6" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + set-function-name: "npm:^2.0.1" + checksum: 10/9fffc01da9c4e12670ff95bc5204364615fcc12d86fc30642765af908675678ebb0780883c874b2dbd184505fb52fa603d80073ecf69f461ce7f56b15d10be9c languageName: node linkType: hard @@ -13718,9 +13815,9 @@ __metadata: linkType: hard "rfdc@npm:^1.3.0": - version: 1.3.0 - resolution: "rfdc@npm:1.3.0" - checksum: 10/76dedd9700cdf132947fde7ce1a8838c9cbb7f3e8f9188af0aaf97194cce745f42094dd2cf547426934cc83252ee2c0e432b2e0222a4415ab0db32de82665c69 + version: 1.3.1 + resolution: "rfdc@npm:1.3.1" + checksum: 10/44cc6a82e2fe1db13b7d3c54e9ffd0b40ef070cbde69ffbfbb38dab8cee46bd68ba686784b96365ff08d04798bc121c3465663a0c91f2c421c90546c4366f4a6 languageName: node linkType: hard @@ -13735,6 +13832,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.5": + version: 5.0.5 + resolution: "rimraf@npm:5.0.5" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10/a612c7184f96258b7d1328c486b12ca7b60aa30e04229a08bbfa7e964486deb1e9a1b52d917809311bdc39a808a4055c0f950c0280fba194ba0a09e6f0d404f6 + languageName: node + linkType: hard + "roboto-fontface@npm:0.10.0": version: 0.10.0 resolution: "roboto-fontface@npm:0.10.0" @@ -13832,7 +13940,7 @@ __metadata: languageName: node linkType: hard -"safe-array-concat@npm:^1.0.1": +"safe-array-concat@npm:^1.1.0": version: 1.1.0 resolution: "safe-array-concat@npm:1.1.0" dependencies: @@ -13858,14 +13966,14 @@ __metadata: languageName: node linkType: hard -"safe-regex-test@npm:^1.0.0": - version: 1.0.2 - resolution: "safe-regex-test@npm:1.0.2" +"safe-regex-test@npm:^1.0.3": + version: 1.0.3 + resolution: "safe-regex-test@npm:1.0.3" dependencies: - call-bind: "npm:^1.0.5" - get-intrinsic: "npm:^1.2.2" + call-bind: "npm:^1.0.6" + es-errors: "npm:^1.3.0" is-regex: "npm:^1.1.4" - checksum: 10/0e6a472caa8f44a502c7842ea19749de42c2eb1b41cb00456061dc3746cf3468e907522f56e97a15f3b41d88f660bd3d4f9bdec064a39895f7babae0f7aafc6a + checksum: 10/b04de61114b10274d92e25b6de7ccb5de07f11ea15637ff636de4b5190c0f5cd8823fe586dde718504cf78055437d70fd8804976894df502fcf5a210c970afb3 languageName: node linkType: hard @@ -13903,7 +14011,7 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0": +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0": version: 4.2.0 resolution: "schema-utils@npm:4.2.0" dependencies: @@ -13922,7 +14030,7 @@ __metadata: languageName: node linkType: hard -"selfsigned@npm:^2.1.1": +"selfsigned@npm:^2.4.1": version: 2.4.1 resolution: "selfsigned@npm:2.4.1" dependencies: @@ -13960,13 +14068,13 @@ __metadata: linkType: hard "semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.5.4": - version: 7.5.4 - resolution: "semver@npm:7.5.4" + version: 7.6.0 + resolution: "semver@npm:7.6.0" dependencies: lru-cache: "npm:^6.0.0" bin: semver: bin/semver.js - checksum: 10/985dec0d372370229a262c737063860fabd4a1c730662c1ea3200a2f649117761a42184c96df62a0e885e76fbd5dace41087d6c1ac0351b13c0df5d6bcb1b5ac + checksum: 10/1b41018df2d8aca5a1db4729985e8e20428c650daea60fcd16e926e9383217d00f574fab92d79612771884a98d2ee2a1973f49d630829a8d54d6570defe62535 languageName: node linkType: hard @@ -14068,20 +14176,21 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.1.1": - version: 1.2.0 - resolution: "set-function-length@npm:1.2.0" +"set-function-length@npm:^1.2.1": + version: 1.2.1 + resolution: "set-function-length@npm:1.2.1" dependencies: - define-data-property: "npm:^1.1.1" + define-data-property: "npm:^1.1.2" + es-errors: "npm:^1.3.0" function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.2" + get-intrinsic: "npm:^1.2.3" gopd: "npm:^1.0.1" has-property-descriptors: "npm:^1.0.1" - checksum: 10/6d609cd060c488d7d2178a5d4c3689f8a6afa26fa4c48ff4a0516664ff9b84c1c0898915777f5628092dab55c4fcead205525e2edd15c659423bf86f790fdcae + checksum: 10/9ab1d200149574ab27c1a7acae56d6235e02568fc68655fe8afe63e4e02ccad3c27665f55c32408bd1ff40705939dbb7539abfb9c3a07fda27ecad1ab9e449f5 languageName: node linkType: hard -"set-function-name@npm:^2.0.0": +"set-function-name@npm:^2.0.0, set-function-name@npm:^2.0.1": version: 2.0.1 resolution: "set-function-name@npm:2.0.1" dependencies: @@ -14158,13 +14267,14 @@ __metadata: linkType: hard "side-channel@npm:^1.0.4": - version: 1.0.4 - resolution: "side-channel@npm:1.0.4" + version: 1.0.5 + resolution: "side-channel@npm:1.0.5" dependencies: - call-bind: "npm:^1.0.0" - get-intrinsic: "npm:^1.0.2" - object-inspect: "npm:^1.9.0" - checksum: 10/c4998d9fc530b0e75a7fd791ad868fdc42846f072734f9080ff55cc8dc7d3899abcda24fd896aa6648c3ab7021b4bb478073eb4f44dfd55bce9714bc1a7c5d45 + call-bind: "npm:^1.0.6" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + object-inspect: "npm:^1.13.1" + checksum: 10/27708b70b5d81bf18dc8cc23f38f1b6c9511691a64abc4aaf17956e67d132c855cf8b46f931e2fc5a6262b29371eb60da7755c1b9f4f862eccea8562b469f8f6 languageName: node linkType: hard @@ -14296,12 +14406,12 @@ __metadata: linkType: hard "socks@npm:^2.7.1": - version: 2.7.1 - resolution: "socks@npm:2.7.1" + version: 2.8.0 + resolution: "socks@npm:2.8.0" dependencies: - ip: "npm:^2.0.0" + ip-address: "npm:^9.0.5" smart-buffer: "npm:^4.2.0" - checksum: 10/5074f7d6a13b3155fa655191df1c7e7a48ce3234b8ccf99afa2ccb56591c195e75e8bb78486f8e9ea8168e95a29573cbaad55b2b5e195160ae4d2ea6811ba833 + checksum: 10/ed0224ce2c7daaa7690cb87cf53d9703ffc4e983aca221f6f5b46767b232658df49494fd86acd0bf97ada6de05248ea8ea625c2343d48155d8463fc40d4a340f languageName: node linkType: hard @@ -14425,9 +14535,9 @@ __metadata: linkType: hard "spdx-exceptions@npm:^2.1.0": - version: 2.3.0 - resolution: "spdx-exceptions@npm:2.3.0" - checksum: 10/cb69a26fa3b46305637123cd37c85f75610e8c477b6476fa7354eb67c08128d159f1d36715f19be6f9daf4b680337deb8c65acdcae7f2608ba51931540687ac0 + version: 2.5.0 + resolution: "spdx-exceptions@npm:2.5.0" + checksum: 10/bb127d6e2532de65b912f7c99fc66097cdea7d64c10d3ec9b5e96524dbbd7d20e01cba818a6ddb2ae75e62bb0c63d5e277a7e555a85cbc8ab40044984fa4ae15 languageName: node linkType: hard @@ -14442,9 +14552,9 @@ __metadata: linkType: hard "spdx-license-ids@npm:^3.0.0": - version: 3.0.16 - resolution: "spdx-license-ids@npm:3.0.16" - checksum: 10/6425c54132ca38d717315cdbd2b620235937d1859972c5978bbc95b4c14400438ffe113709d8aabb0d5498cc27a5b89876fca0fe21b4e26f5ce122bc86d0d88e + version: 3.0.17 + resolution: "spdx-license-ids@npm:3.0.17" + checksum: 10/8f6c6ae02ebb25b4ca658b8990d9e8a8f8d8a95e1d8b9fd84d87eed80a7dc8f8073d6a8d50b8a0295c0e8399e1f8814f5c00e2985e6bf3731540a16f7241cbf1 languageName: node linkType: hard @@ -14484,6 +14594,13 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb + languageName: node + linkType: hard + "ssri@npm:^10.0.0": version: 10.0.5 resolution: "ssri@npm:10.0.5" @@ -14561,7 +14678,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.6.0": +"std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" checksum: 10/6ee0cca1add3fd84656b0002cfbc5bfa20340389d9ba4720569840f1caa34bce74322aef4c93f046391583e50649d0cf81a5f8fe1d411e50b659571690a45f12 @@ -14639,13 +14756,13 @@ __metadata: linkType: hard "string-width@npm:^7.0.0": - version: 7.0.0 - resolution: "string-width@npm:7.0.0" + version: 7.1.0 + resolution: "string-width@npm:7.1.0" dependencies: emoji-regex: "npm:^10.3.0" get-east-asian-width: "npm:^1.0.0" strip-ansi: "npm:^7.1.0" - checksum: 10/bc0de5700a2690895169fce447ec4ed44bc62de80312c2093d5606bfd48319bb88e48a99e97f269dff2bc9577448b91c26b3804c16e7d9b389699795e4655c3b + checksum: 10/a183573fe7209e0d294f661846d33f8caf72aa86d983e5b48a0ed45ab15bcccb02c6f0344b58b571988871105457137b8207855ea536827dbc4a376a0f31bf8f languageName: node linkType: hard @@ -14974,8 +15091,8 @@ __metadata: linkType: hard "terser@npm:^5.0.0, terser@npm:^5.15.1, terser@npm:^5.26.0": - version: 5.27.0 - resolution: "terser@npm:5.27.0" + version: 5.27.1 + resolution: "terser@npm:5.27.1" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.8.2" @@ -14983,7 +15100,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10/9b2c5cb00747dea5994034ca064fb3cc7efc1be6b79a35247662d51ab43bdbe9cbf002bbf29170b5f3bd068c811d0212e22d94acd2cf0d8562687b96f1bffc9f + checksum: 10/4b5c8c65548071ae09dc1d9fd64616262876229897eaac9f95cf2e44908a1f4a25d7837c2a38caef1a523cf1cf67d254e74a846e9a854d289c0ad3664d581c3c languageName: node linkType: hard @@ -15189,11 +15306,11 @@ __metadata: linkType: hard "ts-api-utils@npm:^1.0.1": - version: 1.0.3 - resolution: "ts-api-utils@npm:1.0.3" + version: 1.2.1 + resolution: "ts-api-utils@npm:1.2.1" peerDependencies: typescript: ">=4.2.0" - checksum: 10/1350a5110eb1e534e9a6178f4081fb8a4fcc439749e19f4ad699baec9090fcb90fe532d5e191d91a062dc6e454a14a8d7eb2ad202f57135a30c4a44a3024f039 + checksum: 10/6d7f60fd01e3885bb334607f22b9cb1002e72da81dad2e672fef1b0d1a2f640b0f0ff5310369401488fac90c7a7f5d39c89fd18789af59c672c9b5aef4cade3e languageName: node linkType: hard @@ -15226,7 +15343,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.0.2, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.2, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca @@ -15404,14 +15521,14 @@ __metadata: languageName: node linkType: hard -"typed-array-buffer@npm:^1.0.0": - version: 1.0.0 - resolution: "typed-array-buffer@npm:1.0.0" +"typed-array-buffer@npm:^1.0.1": + version: 1.0.1 + resolution: "typed-array-buffer@npm:1.0.1" dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.1" - is-typed-array: "npm:^1.1.10" - checksum: 10/3e0281c79b2a40cd97fe715db803884301993f4e8c18e8d79d75fd18f796e8cd203310fec8c7fdb5e6c09bedf0af4f6ab8b75eb3d3a85da69328f28a80456bd3 + call-bind: "npm:^1.0.6" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.13" + checksum: 10/1d65e46b2b9b7ec2a30df39b9ddf32e55ad08d6119aec33975506a3dba56057796bdc3c64dbeb7fdb61bf340a75e279dfd55b48ce8f3b874f01731e1da6833d2 languageName: node linkType: hard @@ -15428,15 +15545,16 @@ __metadata: linkType: hard "typed-array-byte-offset@npm:^1.0.0": - version: 1.0.0 - resolution: "typed-array-byte-offset@npm:1.0.0" + version: 1.0.1 + resolution: "typed-array-byte-offset@npm:1.0.1" dependencies: - available-typed-arrays: "npm:^1.0.5" - call-bind: "npm:^1.0.2" + available-typed-arrays: "npm:^1.0.6" + call-bind: "npm:^1.0.7" for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" has-proto: "npm:^1.0.1" - is-typed-array: "npm:^1.1.10" - checksum: 10/2d81747faae31ca79f6c597dc18e15ae3d5b7e97f7aaebce3b31f46feeb2a6c1d6c92b9a634d901c83731ffb7ec0b74d05c6ff56076f5ae39db0cd19b16a3f92 + is-typed-array: "npm:^1.1.13" + checksum: 10/b174c0bac20bcd8787d2f5ccd7bd8f5e5a128e060ffe0909ffe27d65e486de50a3552248a307a45e5c9c593fd8ec97f5acdf119c3e13806f11943b7a2ce555be languageName: node linkType: hard @@ -16063,57 +16181,60 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:^5.3.1": - version: 5.3.3 - resolution: "webpack-dev-middleware@npm:5.3.3" +"webpack-dev-middleware@npm:^7.0.0": + version: 7.0.0 + resolution: "webpack-dev-middleware@npm:7.0.0" dependencies: colorette: "npm:^2.0.10" - memfs: "npm:^3.4.3" + memfs: "npm:^4.6.0" mime-types: "npm:^2.1.31" range-parser: "npm:^1.2.1" schema-utils: "npm:^4.0.0" peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 10/31a2f7a11e58a76bdcde1eb8da310b6643844d9b442f9916f48be5b46c103f23490c393c32a9af501ce68226fbb018b811f5a956635ed60a03f9481a4bcd6c76 + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: 10/e94902c35458c2431cd3332088cf33afe9236333e911605e6045392be11f521140600312c698263acf2a6c52e2092b8e8b3ea4b0ed34568df6cb7a79913aa1d3 languageName: node linkType: hard -"webpack-dev-server@npm:4.15.1": - version: 4.15.1 - resolution: "webpack-dev-server@npm:4.15.1" +"webpack-dev-server@npm:5.0.2": + version: 5.0.2 + resolution: "webpack-dev-server@npm:5.0.2" dependencies: - "@types/bonjour": "npm:^3.5.9" - "@types/connect-history-api-fallback": "npm:^1.3.5" - "@types/express": "npm:^4.17.13" - "@types/serve-index": "npm:^1.9.1" - "@types/serve-static": "npm:^1.13.10" - "@types/sockjs": "npm:^0.3.33" - "@types/ws": "npm:^8.5.5" + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.0.11" - chokidar: "npm:^3.5.3" + bonjour-service: "npm:^1.2.1" + chokidar: "npm:^3.6.0" colorette: "npm:^2.0.10" compression: "npm:^1.7.4" connect-history-api-fallback: "npm:^2.0.0" default-gateway: "npm:^6.0.3" express: "npm:^4.17.3" graceful-fs: "npm:^4.2.6" - html-entities: "npm:^2.3.2" + html-entities: "npm:^2.4.0" http-proxy-middleware: "npm:^2.0.3" - ipaddr.js: "npm:^2.0.1" - launch-editor: "npm:^2.6.0" - open: "npm:^8.0.9" - p-retry: "npm:^4.5.0" - rimraf: "npm:^3.0.2" - schema-utils: "npm:^4.0.0" - selfsigned: "npm:^2.1.1" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" + p-retry: "npm:^6.2.0" + rimraf: "npm:^5.0.5" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^2.4.1" serve-index: "npm:^1.9.1" sockjs: "npm:^0.3.24" spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^5.3.1" - ws: "npm:^8.13.0" + webpack-dev-middleware: "npm:^7.0.0" + ws: "npm:^8.16.0" peerDependencies: - webpack: ^4.37.0 || ^5.0.0 + webpack: ^5.0.0 peerDependenciesMeta: webpack: optional: true @@ -16121,7 +16242,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/fd6dfb6c71eb94696b21930ea4c2f25e95ba85fac1bbc15aa5d03af0a90712eba057901fa9131ed3e901665c95b2379208279aca61e9c48e7cda276c3caa95dd + checksum: 10/f47205b56a562c72083ad979fceb499dc60ef35a75a72b6fbcbccd258b6b304ab3a977877dc6ce68aa5fb90cee8ab9387e22ceb8e374a019e3d4ce77ad0c9493 languageName: node linkType: hard @@ -16172,9 +16293,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.90.1": - version: 5.90.1 - resolution: "webpack@npm:5.90.1" +"webpack@npm:5.90.3": + version: 5.90.3 + resolution: "webpack@npm:5.90.3" dependencies: "@types/eslint-scope": "npm:^3.7.3" "@types/estree": "npm:^1.0.5" @@ -16205,13 +16326,13 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 10/6ad23518123f1742238177920cefa61152d981f986adac5901236845c86ba9bb375a3ba75e188925c856c3d2a76a2ba119e95b8a608a51424968389041089075 + checksum: 10/48c9696eca950bfa7c943a24b8235fdf0575acd73a8eb1661f8189d3d1f431362f3a0e158e2941a7e4f0852ea6e32d7d4e89283149247e4389a8aad0fe6c247e languageName: node linkType: hard -"webpackbar@npm:6.0.0": - version: 6.0.0 - resolution: "webpackbar@npm:6.0.0" +"webpackbar@npm:6.0.1": + version: 6.0.1 + resolution: "webpackbar@npm:6.0.1" dependencies: ansi-escapes: "npm:^4.3.2" chalk: "npm:^4.1.2" @@ -16219,11 +16340,11 @@ __metadata: figures: "npm:^3.2.0" markdown-table: "npm:^2.0.0" pretty-time: "npm:^1.1.0" - std-env: "npm:^3.6.0" + std-env: "npm:^3.7.0" wrap-ansi: "npm:^7.0.0" peerDependencies: webpack: 3 || 4 || 5 - checksum: 10/aa26c2dff6c2384f39b77e57375af52a65fbd3c54b9a5a8e1c53e79b0c25eaabc8602469ec3742bf93f7d178f5a96e3d6bd4fcbb256808e730eecb91091df466 + checksum: 10/9da47f8dcbc9173b19e41e3e1049fa451b0c02095ffa003e8c09c56aa2cc544334d1c6fff0797162a807b29090db9cf9a269cd5ec453196142543f9275cbbf70 languageName: node linkType: hard @@ -16300,16 +16421,16 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.13": - version: 1.1.13 - resolution: "which-typed-array@npm:1.1.13" +"which-typed-array@npm:^1.1.14": + version: 1.1.14 + resolution: "which-typed-array@npm:1.1.14" dependencies: - available-typed-arrays: "npm:^1.0.5" - call-bind: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.6" + call-bind: "npm:^1.0.5" for-each: "npm:^0.3.3" gopd: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.0" - checksum: 10/605e3e10b7118af904a0e79d0d50b95275102f06ec902734024989cd71354929f7acee50de43529d3baf5858e2e4eb32c75e6ebd226c888ad976d8140e4a3e71 + has-tostringtag: "npm:^1.0.1" + checksum: 10/56253d2c9d6b41b8a4af96d8c2751bac5508906bd500cdcd0dc5301fb082de0391a4311ab21258bc8d2609ed593f422c1a66f0020fcb3a1e97f719bc928b9018 languageName: node linkType: hard @@ -16640,7 +16761,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.13.0": +"ws@npm:^8.16.0": version: 8.16.0 resolution: "ws@npm:8.16.0" peerDependencies: @@ -16674,13 +16795,13 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.4.5": - version: 0.4.23 - resolution: "xml2js@npm:0.4.23" +"xml2js@npm:^0.5.0": + version: 0.5.0 + resolution: "xml2js@npm:0.5.0" dependencies: sax: "npm:>=0.6.0" xmlbuilder: "npm:~11.0.0" - checksum: 10/52896ef39429f860f32471dd7bb2b89ef25b7e15528e3a4366de0bd5e55a251601565e7814763e70f9e75310c3afe649a42b8826442b74b41eff8a0ae333fccc + checksum: 10/27c4d759214e99be5ec87ee5cb1290add427fa43df509d3b92d10152b3806fd2f7c9609697a18b158ccf2caa01e96af067cdba93196f69ca10c90e4f79a08896 languageName: node linkType: hard