diff --git a/build-scripts/gulp/translations.js b/build-scripts/gulp/translations.js index dde4dd89f4c8..1127a5d3f5eb 100755 --- a/build-scripts/gulp/translations.js +++ b/build-scripts/gulp/translations.js @@ -244,11 +244,11 @@ const createTranslations = async () => { // TODO: This is a naive interpretation of BCP47 that should be improved. // Will be OK for now as long as we don't have anything more complicated // than a base translation + region. - gulp + const masterStream = gulp .src(`${workDir}/en.json`) - .pipe(new PassThrough({ objectMode: true })) - .pipe(hashStream, { end: false }); - const mergesFinished = []; + .pipe(new PassThrough({ objectMode: true })); + masterStream.pipe(hashStream, { end: false }); + const mergesFinished = [finished(masterStream)]; for (const translationFile of translationFiles) { const locale = basename(translationFile, ".json"); const subtags = locale.split("-"); diff --git a/build-scripts/webpack.cjs b/build-scripts/webpack.cjs index 0679e3e2baec..5ba0f35d2383 100644 --- a/build-scripts/webpack.cjs +++ b/build-scripts/webpack.cjs @@ -74,6 +74,9 @@ const createWebpackConfig = ({ resolve: { fullySpecified: false, }, + parser: { + worker: ["*context.audioWorklet.addModule()", "..."], + }, }, { test: /\.css$/, @@ -92,11 +95,15 @@ const createWebpackConfig = ({ moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", splitChunks: { - // Disable splitting for web workers with ESM output - // Imports of external chunks are broken - chunks: latestBuild - ? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name) - : undefined, + // Disable splitting for web workers and worklets because imports of + // external chunks are broken for: + // - ESM output: https://github.com/webpack/webpack/issues/17014 + // - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543 + chunks: (chunk) => + !chunk.canBeInitial() && + !new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test( + chunk.name + ), }, }, plugins: [ diff --git a/cast/src/html/faq.html.template b/cast/src/html/faq.html.template index c67a5b477a95..80fc487bef21 100644 --- a/cast/src/html/faq.html.template +++ b/cast/src/html/faq.html.template @@ -232,17 +232,5 @@ http:

- - diff --git a/cast/src/html/media.html.template b/cast/src/html/media.html.template index e91fbb881cbe..49633e084021 100644 --- a/cast/src/html/media.html.template +++ b/cast/src/html/media.html.template @@ -14,12 +14,6 @@ --background-color: #41bdf5; } - <%= renderTemplate("../../../src/html/_js_base.html.template") %> diff --git a/cast/src/html/receiver.html.template b/cast/src/html/receiver.html.template index 582e9ca865d4..d326ce55ff13 100644 --- a/cast/src/html/receiver.html.template +++ b/cast/src/html/receiver.html.template @@ -11,10 +11,4 @@ font-size: initial; } - diff --git a/demo/src/configs/sections/lovelace.ts b/demo/src/configs/sections/lovelace.ts index 70146c0bdb39..36b4abb2fe9c 100644 --- a/demo/src/configs/sections/lovelace.ts +++ b/demo/src/configs/sections/lovelace.ts @@ -1,3 +1,4 @@ +import { isFrontpageEmbed } from "../../util/is_frontpage"; import { DemoConfig } from "../types"; export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ @@ -5,36 +6,20 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ views: [ { type: "sections", - title: "Demo", + title: isFrontpageEmbed ? "Home Assistant" : "Demo", path: "home", icon: "mdi:home-assistant", sections: [ - { - title: "Welcome 👋", - cards: [{ type: "custom:ha-demo-card" }], - }, + ...(isFrontpageEmbed + ? [] + : [ + { + title: "Welcome 👋", + cards: [{ type: "custom:ha-demo-card" }], + }, + ]), { cards: [ - { - type: "tile", - entity: "cover.living_room_garden_shutter", - name: "Garden", - }, - { - type: "tile", - entity: "cover.living_room_graveyard_shutter", - name: "Rear", - }, - { - type: "tile", - entity: "cover.living_room_left_shutter", - name: "Left", - }, - { - type: "tile", - entity: "cover.living_room_right_shutter", - name: "Right", - }, { type: "tile", entity: "light.floor_lamp", @@ -60,6 +45,11 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ detail: 1, name: "Temperature", }, + { + type: "tile", + entity: "cover.living_room_garden_shutter", + name: "Blinds", + }, { type: "tile", entity: "media_player.living_room_nest_mini", diff --git a/demo/src/entrypoint.ts b/demo/src/entrypoint.ts index ca58e546e357..e02049326436 100644 --- a/demo/src/entrypoint.ts +++ b/demo/src/entrypoint.ts @@ -1,4 +1,5 @@ import "../../src/resources/safari-14-attachshadow-patch"; +import "./util/is_frontpage"; import "./ha-demo"; import("../../src/resources/ha-style"); diff --git a/demo/src/html/index.html.template b/demo/src/html/index.html.template index c2be4e7cc39c..6b044e44a355 100644 --- a/demo/src/html/index.html.template +++ b/demo/src/html/index.html.template @@ -93,16 +93,5 @@ } <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> - diff --git a/demo/src/util/is_frontpage.ts b/demo/src/util/is_frontpage.ts new file mode 100644 index 000000000000..4de52935ba6a --- /dev/null +++ b/demo/src/util/is_frontpage.ts @@ -0,0 +1 @@ +export const isFrontpageEmbed = document.location.search === "?frontpage"; diff --git a/package.json b/package.json index 2f1c2b10a773..292983f4ad45 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@material/mwc-top-app-bar": "0.27.0", "@material/mwc-top-app-bar-fixed": "0.27.0", "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", - "@material/web": "1.5.0", + "@material/web": "1.5.1", "@mdi/js": "7.4.47", "@mdi/svg": "7.4.47", "@polymer/paper-item": "3.0.1", @@ -200,7 +200,7 @@ "eslint-import-resolver-webpack": "0.13.8", "eslint-plugin-import": "2.29.1", "eslint-plugin-lit": "1.14.0", - "eslint-plugin-lit-a11y": "4.1.2", + "eslint-plugin-lit-a11y": "4.1.3", "eslint-plugin-unused-imports": "4.0.0", "eslint-plugin-wc": "2.1.0", "fancy-log": "2.0.0", diff --git a/pyproject.toml b/pyproject.toml index 4d2f4be0602b..a045f384a64a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240628.0" +version = "20240702.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/components/ha-grid-size-picker.ts b/src/components/ha-grid-size-picker.ts index dca2cbd2fbe1..453a24cb8d30 100644 --- a/src/components/ha-grid-size-picker.ts +++ b/src/components/ha-grid-size-picker.ts @@ -7,6 +7,7 @@ import { mdiRestore } from "@mdi/js"; import { styleMap } from "lit/directives/style-map"; import { fireEvent } from "../common/dom/fire_event"; import { HomeAssistant } from "../types"; +import { conditionalClamp } from "../common/number/clamp"; type GridSizeValue = { rows?: number; @@ -42,6 +43,10 @@ export class HaGridSizeEditor extends LitElement { } protected render() { + const disabledColumns = + this.columnMin !== undefined && this.columnMin === this.columnMax; + const disabledRows = + this.rowMin !== undefined && this.rowMin === this.rowMax; return html`
${!this.isDefault ? html` @@ -100,17 +107,11 @@ export class HaGridSizeEditor extends LitElement { .map((_, index) => { const row = Math.floor(index / this.columns) + 1; const column = (index % this.columns) + 1; - const disabled = - (this.rowMin !== undefined && row < this.rowMin) || - (this.rowMax !== undefined && row > this.rowMax) || - (this.columnMin !== undefined && column < this.columnMin) || - (this.columnMax !== undefined && column > this.columnMax); return html`
`; @@ -126,11 +127,16 @@ export class HaGridSizeEditor extends LitElement { _cellClick(ev) { const cell = ev.currentTarget as HTMLElement; - if (cell.getAttribute("disabled") !== null) return; const rows = Number(cell.getAttribute("data-row")); const columns = Number(cell.getAttribute("data-column")); + const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax); + const clampedColumn = conditionalClamp( + columns, + this.columnMin, + this.columnMax + ); fireEvent(this, "value-changed", { - value: { rows, columns }, + value: { rows: clampedRow, columns: clampedColumn }, }); } diff --git a/src/fake_data/entity.ts b/src/fake_data/entity.ts index b001b4b7fcd0..2ea81b3feacf 100644 --- a/src/fake_data/entity.ts +++ b/src/fake_data/entity.ts @@ -394,6 +394,7 @@ class GroupEntity extends Entity { } const TYPES = { + automation: ToggleEntity, alarm_control_panel: AlarmControlPanelEntity, climate: ClimateEntity, cover: CoverEntity, diff --git a/src/panels/config/application_credentials/ha-config-application-credentials.ts b/src/panels/config/application_credentials/ha-config-application-credentials.ts index e11f0515f431..d31c8b274d18 100644 --- a/src/panels/config/application_credentials/ha-config-application-credentials.ts +++ b/src/panels/config/application_credentials/ha-config-application-credentials.ts @@ -77,12 +77,13 @@ export class HaConfigApplicationCredentials extends LitElement { private _filter = ""; private _columns = memoizeOne( - (narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => { + (localize: LocalizeFunc): DataTableColumnContainer => { const columns: DataTableColumnContainer = { name: { title: localize( "ui.panel.config.application_credentials.picker.headers.name" ), + main: true, sortable: true, filterable: true, direction: "asc", @@ -94,7 +95,6 @@ export class HaConfigApplicationCredentials extends LitElement { ), filterable: true, width: "30%", - hidden: narrow, }, localizedDomain: { title: localize( @@ -109,6 +109,9 @@ export class HaConfigApplicationCredentials extends LitElement { title: "", width: "64px", type: "overflow-menu", + showNarrow: true, + hideable: false, + moveable: false, template: (credential) => html` - ${this.hass.localize("ui.panel.config.automation.editor.read_only")} - - ${this.hass.localize("ui.panel.config.automation.editor.migrate")} - - ` - : nothing} ${this.stateObj?.state === "off" ? html` diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 939c00af531c..cd7ab59477a7 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -81,9 +81,9 @@ declare global { unsub?: UnsubscribeFunc; }; "ui-mode-not-available": Error; - duplicate: undefined; "move-down": undefined; "move-up": undefined; + duplicate: undefined; } } @@ -116,6 +116,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { @state() private _validationErrors?: (string | TemplateResult)[]; + @state() private _blueprintConfig?: BlueprintAutomationConfig; + private _configSubscriptions: Record< string, (config?: AutomationConfig) => void @@ -200,7 +202,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ${this.hass.localize("ui.panel.config.automation.editor.rename")} @@ -224,7 +228,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { : nothing} @@ -244,7 +249,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ${this.hass.localize( "ui.panel.config.automation.editor.take_control" @@ -337,6 +342,32 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { : nothing} ` : ""} + ${this._blueprintConfig + ? html` + ${this.hass.localize( + "ui.panel.config.automation.editor.confirm_take_control" + )} +
+ ${this.hass.localize("ui.common.yes")} + ${this.hass.localize("ui.common.no")} +
+
` + : this._readOnly + ? html`${this.hass.localize( + "ui.panel.config.automation.editor.read_only" + )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.migrate" + )} + + ` + : nothing} ${this._mode === "gui" ? html`
` : html` @@ -366,25 +396,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { .config=${this._config} .disabled=${Boolean(this._readOnly)} @value-changed=${this._valueChanged} - @duplicate=${this._duplicate} > `}
` : this._mode === "yaml" - ? html` ${this._readOnly - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.read_only" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.migrate" - )} - - ` - : nothing} - ${stateObj?.state === "off" + ? html`${stateObj?.state === "off" ? html` ${this.hass.localize( @@ -409,7 +426,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
ha-alert { margin: 0 auto; max-width: 1040px; padding: 28px 20px 0; + display: block; } ha-yaml-editor { flex-grow: 1; diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index ceb4da52e033..3e7a23c2f948 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -38,14 +38,6 @@ export class HaManualAutomationEditor extends LitElement { protected render() { return html` - ${this.disabled - ? html` - ${this.hass.localize("ui.panel.config.automation.editor.read_only")} - - ${this.hass.localize("ui.panel.config.automation.editor.migrate")} - - ` - : nothing} ${this.stateObj?.state === "off" ? html` @@ -238,10 +230,6 @@ export class HaManualAutomationEditor extends LitElement { }); } - private _duplicate() { - fireEvent(this, "duplicate"); - } - static get styles(): CSSResultGroup { return [ haStyle, @@ -280,12 +268,6 @@ export class HaManualAutomationEditor extends LitElement { font-weight: normal; line-height: 0; } - ha-alert.re-order { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } `, ]; } diff --git a/src/panels/config/blueprint/blueprint-generic-editor.ts b/src/panels/config/blueprint/blueprint-generic-editor.ts index bae63f6dc7e3..85d7083a8a8e 100644 --- a/src/panels/config/blueprint/blueprint-generic-editor.ts +++ b/src/panels/config/blueprint/blueprint-generic-editor.ts @@ -3,7 +3,6 @@ import { css, CSSResultGroup, html, LitElement, nothing } 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"; @@ -126,14 +125,14 @@ export abstract class HaBlueprintGenericEditor extends LitElement { ); const expanded = !section.collapsed || anyRequired; - return html`
${section?.icon - ? html` ` @@ -261,10 +260,6 @@ export abstract class HaBlueprintGenericEditor extends LitElement { }); } - protected _duplicate() { - fireEvent(this, "duplicate"); - } - static get styles(): CSSResultGroup { return [ haStyle, @@ -318,14 +313,6 @@ export abstract class HaBlueprintGenericEditor extends LitElement { margin-left: 8px; margin-right: 8px; } - ha-alert { - margin-bottom: 16px; - display: block; - } - ha-alert.re-order { - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } div.section-header { display: flex; vertical-align: middle; @@ -333,6 +320,10 @@ export abstract class HaBlueprintGenericEditor extends LitElement { ha-icon.section-header { padding-right: 10px; } + ha-alert { + display: block; + margin-bottom: 16px; + } `, ]; } diff --git a/src/panels/config/script/blueprint-script-editor.ts b/src/panels/config/script/blueprint-script-editor.ts index bc7b624a0cb3..dbe96677de50 100644 --- a/src/panels/config/script/blueprint-script-editor.ts +++ b/src/panels/config/script/blueprint-script-editor.ts @@ -1,7 +1,6 @@ import "@material/mwc-button/mwc-button"; import { html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; -import "../../../components/ha-alert"; import "../../../components/ha-markdown"; import { fetchBlueprints } from "../../../data/blueprint"; import { BlueprintScriptConfig } from "../../../data/script"; @@ -17,14 +16,6 @@ export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor { protected render() { return html` - ${this.disabled - ? html` - ${this.hass.localize("ui.panel.config.script.editor.read_only")} - - ${this.hass.localize("ui.panel.config.script.editor.migrate")} - - ` - : nothing} ${this.config.description ? html` @@ -236,7 +239,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { ${this.hass.localize( "ui.panel.config.script.editor.take_control" @@ -312,6 +315,32 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { : nothing} ` : ""} + ${this._blueprintConfig + ? html` + ${this.hass.localize( + "ui.panel.config.script.editor.confirm_take_control" + )} +
+ ${this.hass.localize("ui.common.yes")} + ${this.hass.localize("ui.common.no")} +
+
` + : this._readOnly + ? html`${this.hass.localize( + "ui.panel.config.script.editor.read_only" + )} + + ${this.hass.localize( + "ui.panel.config.script.editor.migrate" + )} + + ` + : nothing} ${this._mode === "gui" ? html`
` : html` @@ -339,31 +367,18 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { .config=${this._config} .disabled=${this._readOnly} @value-changed=${this._valueChanged} - @duplicate=${this._duplicate} > `}
` : this._mode === "yaml" - ? html` ${this._readOnly - ? html` - ${this.hass.localize( - "ui.panel.config.script.editor.read_only" - )} - - ${this.hass.localize( - "ui.panel.config.script.editor.migrate" - )} - - ` - : nothing} - ` + ? html`` : nothing}
ha-alert { margin: 0 auto; max-width: 1040px; padding: 28px 20px 0; + display: block; } .config-container ha-alert { margin-bottom: 16px; diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index f5f946229dd7..63f0b555e4f5 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -60,14 +60,6 @@ export class HaManualScriptEditor extends LitElement { protected render() { return html` - ${this.disabled - ? html` - ${this.hass.localize("ui.panel.config.script.editor.read_only")} - - ${this.hass.localize("ui.panel.config.script.editor.migrate")} - - ` - : nothing} ${this.config.description ? html` { + private _unsubscribe() { if (this._subscribed) { - const unsub = await this._subscribed; - if (unsub) { - try { - await unsub(); - } catch (e) { - // The backend will cancel the subscription if - // we subscribe to entities that will all be - // filtered away - } - } + this._subscribed.then((unsub) => unsub?.()); this._subscribed = undefined; } } @@ -239,8 +230,8 @@ export class HaLogbook extends LitElement { * Setting this._logbookEntries to undefined * will put the page in a loading state. */ - private async _unsubscribeSetLoading() { - await this._unsubscribe(); + private _unsubscribeSetLoading() { + this._unsubscribe(); this._logbookEntries = undefined; this._pendingStreamMessages = []; } @@ -249,8 +240,8 @@ export class HaLogbook extends LitElement { * Setting this._logbookEntries to an empty * list will show a no results message. */ - private async _unsubscribeNoResults() { - await this._unsubscribe(); + private _unsubscribeNoResults() { + this._unsubscribe(); this._logbookEntries = []; this._pendingStreamMessages = []; } diff --git a/src/panels/lovelace/cards/hui-area-card.ts b/src/panels/lovelace/cards/hui-area-card.ts index 5bd7e44e317b..5ebf4b4eae05 100644 --- a/src/panels/lovelace/cards/hui-area-card.ts +++ b/src/panels/lovelace/cards/hui-area-card.ts @@ -55,7 +55,11 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../types"; import "../components/hui-image"; import "../components/hui-warning"; -import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { + LovelaceCard, + LovelaceCardEditor, + LovelaceLayoutOptions, +} from "../types"; import { AreaCardConfig } from "./types"; export const DEFAULT_ASPECT_RATIO = "16:9"; @@ -102,6 +106,9 @@ export class HuiAreaCard @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) + public layout?: string; + @state() private _config?: AreaCardConfig; @state() private _entities?: EntityRegistryEntry[]; @@ -405,13 +412,17 @@ export class HuiAreaCard if (this._config.show_camera && "camera" in entitiesByDomain) { cameraEntityId = entitiesByDomain.camera[0].entity_id; } + cameraEntityId = "camera.demo_camera"; const imageClass = area.picture || cameraEntityId; + + const ignoreAspectRatio = imageClass || this.layout === "grid"; + return html` 0 && ratio.h > 0) { - padding = `${((100 * ratio.h) / ratio.w).toFixed(2)}%`; + const ignoreAspectRatio = this.isPanel || this.layout === "grid"; + if (!ignoreAspectRatio) { + if (this._config.aspect_ratio) { + const ratio = parseAspectRatio(this._config.aspect_ratio); + if (ratio && ratio.w > 0 && ratio.h > 0) { + padding = `${((100 * ratio.h) / ratio.w).toFixed(2)}%`; + } + } else { + padding = "50%"; } - } else if (!this.isPanel) { - padding = "50%"; } const target_protocol = new URL(this._config.url, location.toString()) @@ -105,24 +115,26 @@ export class HuiIframeCard extends LitElement implements LovelaceCard { `; } + public getLayoutOptions(): LovelaceLayoutOptions { + return { + grid_columns: 4, + grid_rows: 4, + grid_min_rows: 2, + }; + } + static get styles(): CSSResultGroup { return css` - :host([ispanel]) ha-card { - width: 100%; - height: 100%; - } - ha-card { overflow: hidden; + width: 100%; + height: 100%; } #root { width: 100%; - position: relative; - } - - :host([ispanel]) #root { height: 100%; + position: relative; } iframe { diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index 65189e351360..0c810280c29c 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -39,7 +39,7 @@ import { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; import { processConfigEntities } from "../common/process-config-entities"; import { EntityConfig } from "../entity-rows/types"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceLayoutOptions } from "../types"; import { MapCardConfig } from "./types"; export const DEFAULT_HOURS_TO_SHOW = 0; @@ -57,6 +57,9 @@ class HuiMapCard extends LitElement implements LovelaceCard { @property({ type: Boolean, reflect: true }) public isPanel = false; + @property({ attribute: false }) + public layout?: string; + @state() private _stateHistory?: HistoryStates; @state() @@ -297,7 +300,9 @@ class HuiMapCard extends LitElement implements LovelaceCard { private _computePadding(): void { const root = this.shadowRoot!.getElementById("root"); - if (!this._config || this.isPanel || !root) { + + const ignoreAspectRatio = this.isPanel || this.layout === "grid"; + if (!this._config || ignoreAspectRatio || !root) { return; } @@ -423,6 +428,15 @@ class HuiMapCard extends LitElement implements LovelaceCard { } ); + public getLayoutOptions(): LovelaceLayoutOptions { + return { + grid_columns: 4, + grid_rows: 4, + grid_min_columns: 2, + grid_min_rows: 2, + }; + } + static get styles(): CSSResultGroup { return css` ha-card { diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index ffdfe2c34a2f..4cd3e0f1058d 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -40,7 +40,11 @@ import { findEntities } from "../common/find-entities"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-marquee"; import { createEntityNotFoundWarning } from "../components/hui-warning"; -import type { LovelaceCard, LovelaceCardEditor } from "../types"; +import type { + LovelaceCard, + LovelaceCardEditor, + LovelaceLayoutOptions, +} from "../types"; import { MediaControlCardConfig } from "./types"; @customElement("hui-media-control-card") @@ -582,6 +586,15 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { } } + public getLayoutOptions(): LovelaceLayoutOptions { + return { + grid_columns: 4, + grid_min_columns: 2, + grid_rows: 3, + grid_min_rows: 3, + }; + } + static get styles(): CSSResultGroup { return css` ha-card { diff --git a/src/panels/lovelace/cards/hui-sensor-card.ts b/src/panels/lovelace/cards/hui-sensor-card.ts index 76ba0fa63626..c456d427b6ab 100644 --- a/src/panels/lovelace/cards/hui-sensor-card.ts +++ b/src/panels/lovelace/cards/hui-sensor-card.ts @@ -76,6 +76,8 @@ class HuiSensorCard extends HuiEntityCard { return { grid_columns: 2, grid_rows: 2, + grid_min_columns: 2, + grid_min_rows: 2, }; } diff --git a/src/panels/lovelace/cards/hui-statistic-card.ts b/src/panels/lovelace/cards/hui-statistic-card.ts index e5beb4a209cc..62338e7a65f3 100644 --- a/src/panels/lovelace/cards/hui-statistic-card.ts +++ b/src/panels/lovelace/cards/hui-statistic-card.ts @@ -1,10 +1,10 @@ import { HassEntity } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, + css, + html, nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -16,12 +16,12 @@ import "../../../components/ha-alert"; import "../../../components/ha-card"; import "../../../components/ha-state-icon"; import { + StatisticsMetaData, fetchStatistic, getDisplayUnit, getStatisticLabel, getStatisticMetadata, isExternalStatistic, - StatisticsMetaData, } from "../../../data/recorder"; import { HomeAssistant } from "../../../types"; import { computeCardSize } from "../common/compute-card-size"; @@ -32,6 +32,7 @@ import { LovelaceCard, LovelaceCardEditor, LovelaceHeaderFooter, + LovelaceLayoutOptions, } from "../types"; import { HuiErrorCard } from "./hui-error-card"; import { EntityCardConfig, StatisticCardConfig } from "./types"; @@ -254,6 +255,15 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard { fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); } + public getLayoutOptions(): LovelaceLayoutOptions { + return { + grid_columns: 2, + grid_rows: 2, + grid_min_columns: 2, + grid_min_rows: 2, + }; + } + static get styles(): CSSResultGroup { return [ css` diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index d8d58ee9c776..620f17a20562 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -22,7 +22,11 @@ import { HomeAssistant } from "../../../types"; import "../card-features/hui-card-features"; import { findEntities } from "../common/find-entities"; import { createEntityNotFoundWarning } from "../components/hui-warning"; -import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { + LovelaceCard, + LovelaceCardEditor, + LovelaceLayoutOptions, +} from "../types"; import { ThermostatCardConfig } from "./types"; @customElement("hui-thermostat-card") @@ -165,6 +169,24 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { `; } + public getLayoutOptions(): LovelaceLayoutOptions { + const grid_columns = 4; + let grid_rows = 5; + let grid_min_rows = 2; + const grid_min_columns = 2; + if (this._config?.features?.length) { + const featureHeight = Math.ceil((this._config.features.length * 2) / 3); + grid_rows += featureHeight; + grid_min_rows += featureHeight; + } + return { + grid_columns, + grid_rows, + grid_min_rows, + grid_min_columns, + }; + } + static get styles(): CSSResultGroup { return css` :host { diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index fa8c2cc6d067..19a05e3bb4ab 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -122,17 +122,21 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } public getLayoutOptions(): LovelaceLayoutOptions { - const options = { - grid_columns: 2, - grid_rows: 1, - }; + const grid_columns = 2; + let grid_rows = 1; if (this._config?.features?.length) { - options.grid_rows += Math.ceil((this._config.features.length * 2) / 3); + const featureHeight = Math.ceil((this._config.features.length * 2) / 3); + grid_rows += featureHeight; } if (this._config?.vertical) { - options.grid_rows++; + grid_rows!++; } - return options; + return { + grid_columns, + grid_rows, + grid_min_rows: grid_rows, + grid_min_columns: grid_columns, + }; } private _handleAction(ev: ActionHandlerEvent) { diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 40649ed32ae2..fadca1dd5aef 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -38,7 +38,11 @@ import { handleAction } from "../common/handle-action"; import { hasAction } from "../common/has-action"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; -import type { LovelaceCard, LovelaceCardEditor } from "../types"; +import type { + LovelaceCard, + LovelaceCardEditor, + LovelaceLayoutOptions, +} from "../types"; import type { WeatherForecastCardConfig } from "./types"; @customElement("hui-weather-forecast-card") @@ -421,6 +425,26 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { return typeof item !== "undefined" && item !== null; } + public getLayoutOptions(): LovelaceLayoutOptions { + if ( + this._config?.show_current !== false && + this._config?.show_forecast !== false + ) { + return { + grid_columns: 4, + grid_min_columns: 2, + grid_rows: 3, + grid_min_rows: 3, + }; + } + return { + grid_columns: 4, + grid_min_columns: 2, + grid_rows: 2, + grid_min_rows: 1, + }; + } + static get styles(): CSSResultGroup { return [ weatherSVGStyles, diff --git a/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts b/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts index 2ef6a61f0c40..ab1bebf00fcc 100644 --- a/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts +++ b/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts @@ -255,15 +255,14 @@ export class HaGridLayoutSlider extends LitElement { >
-
-
-
+
+
${this.value !== undefined ? html`
` @@ -323,11 +322,12 @@ export class HaGridLayoutSlider extends LitElement { position: absolute; inset: 0; background: var(--disabled-color); - opacity: 0.5; + opacity: 0.2; } .active { position: absolute; background: grey; + opacity: 0.7; top: 0; right: calc(var(--max) * 100%); bottom: 0; @@ -375,6 +375,9 @@ export class HaGridLayoutSlider extends LitElement { :host(:disabled) .slider { cursor: not-allowed; } + :host(:disabled) .handle:after { + background: var(--disabled-color); + } .pressed .handle { transition: none; } diff --git a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts index 68c0487beee4..5a5690727ce6 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts @@ -19,7 +19,7 @@ import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { HuiCard } from "../../cards/hui-card"; -import { DEFAULT_GRID_OPTIONS } from "../../sections/hui-grid-section"; +import { computeSizeOnGrid } from "../../sections/hui-grid-section"; import { LovelaceLayoutOptions } from "../../types"; @customElement("hui-card-layout-editor") @@ -38,28 +38,29 @@ export class HuiCardLayoutEditor extends LitElement { private _cardElement?: HuiCard; - private _gridSizeValue = memoizeOne( + private _mergedOptions = memoizeOne( ( options?: LovelaceLayoutOptions, defaultOptions?: LovelaceLayoutOptions ) => ({ - rows: - options?.grid_rows ?? - defaultOptions?.grid_rows ?? - DEFAULT_GRID_OPTIONS.grid_rows, - columns: - options?.grid_columns ?? - defaultOptions?.grid_columns ?? - DEFAULT_GRID_OPTIONS.grid_columns, + ...defaultOptions, + ...options, }) ); + private _gridSizeValue = memoizeOne(computeSizeOnGrid); + private _isDefault = memoizeOne( (options?: LovelaceLayoutOptions) => options?.grid_columns === undefined && options?.grid_rows === undefined ); render() { + const options = this._mergedOptions( + this.config.layout_options, + this._defaultLayoutOptions + ); + return html`

@@ -123,12 +124,13 @@ export class HuiCardLayoutEditor extends LitElement { : html` `} `; diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index 11fd8aa0afd3..0cc5c74824c0 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -15,6 +15,7 @@ import { HuiCard } from "../cards/hui-card"; import "../components/hui-card-edit-mode"; import { moveCard } from "../editor/config-util"; import type { Lovelace, LovelaceLayoutOptions } from "../types"; +import { conditionalClamp } from "../../../common/number/clamp"; const CARD_SORTABLE_OPTIONS: HaSortableOptions = { delay: 100, @@ -23,9 +24,41 @@ const CARD_SORTABLE_OPTIONS: HaSortableOptions = { invertedSwapThreshold: 0.7, } as HaSortableOptions; -export const DEFAULT_GRID_OPTIONS: LovelaceLayoutOptions = { +export const DEFAULT_GRID_OPTIONS = { grid_columns: 4, grid_rows: 1, +} as const satisfies LovelaceLayoutOptions; + +type GridSizeValue = { + rows?: number; + columns?: number; +}; + +export const computeSizeOnGrid = ( + options: LovelaceLayoutOptions +): GridSizeValue => { + const rows = + typeof options.grid_rows === "number" + ? conditionalClamp( + options.grid_rows, + options.grid_min_rows, + options.grid_max_rows + ) + : DEFAULT_GRID_OPTIONS.grid_rows; + + const columns = + typeof options.grid_columns === "number" + ? conditionalClamp( + options.grid_columns, + options.grid_min_columns, + options.grid_max_columns + ) + : DEFAULT_GRID_OPTIONS.grid_columns; + + return { + rows, + columns, + }; }; export class GridSection extends LitElement implements LovelaceSectionElement { @@ -98,17 +131,16 @@ export class GridSection extends LitElement implements LovelaceSectionElement { (cardConfig) => this._getKey(cardConfig), (_cardConfig, idx) => { const card = this.cards![idx]; + card.layout = "grid"; const layoutOptions = card.getLayoutOptions(); - const columnSize = - layoutOptions.grid_columns ?? DEFAULT_GRID_OPTIONS.grid_columns; - const rowSize = - layoutOptions.grid_rows ?? DEFAULT_GRID_OPTIONS.grid_rows; + const { rows, columns } = computeSizeOnGrid(layoutOptions); + return html`

; getLayoutOptions?(): LovelaceLayoutOptions; setConfig(config: LovelaceCardConfig): void; diff --git a/src/translations/en.json b/src/translations/en.json index 4ee62dc063c6..9772f4114b39 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2771,11 +2771,7 @@ "migrate": "Migrate", "duplicate": "[%key:ui::common::duplicate%]", "take_control": "Take control", - "take_control_confirmation": { - "title": "Take control of automation?", - "text": "This automation is using a blueprint. By taking control, your automation will be converted into a regular automation using triggers, conditions and actions. You will be able to edit it directly and you won't be able to convert it back to a blueprint.", - "action": "Take control" - }, + "confirm_take_control": "Your are viewing a preview of the automation config, do you want to take control?", "run": "[%key:ui::panel::config::automation::editor::actions::run%]", "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", "show_trace": "Traces", @@ -3648,11 +3644,7 @@ "rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]", "change_mode": "[%key:ui::panel::config::automation::editor::change_mode%]", "take_control": "[%key:ui::panel::config::automation::editor::take_control%]", - "take_control_confirmation": { - "title": "Take control of script?", - "text": "This script is using a blueprint. By taking control, your script will be converted into a regular automation using actions. You will be able to edit it directly and you won't be able to convert it back to a blueprint.", - "action": "[%key:ui::panel::config::automation::editor::take_control_confirmation::action%]" - }, + "confirm_take_control": "Your are viewing a preview of the script config, do you want to take control?", "read_only": "This script cannot be edited from the UI, because it is not stored in the ''scripts.yaml'' file.", "unavailable": "Script is unavailable", "migrate": "Migrate", diff --git a/src/util/audio-recorder.ts b/src/util/audio-recorder.ts index b26f0f49229c..30bf78793709 100644 --- a/src/util/audio-recorder.ts +++ b/src/util/audio-recorder.ts @@ -70,17 +70,18 @@ export class AudioRecorder { } private async _createContext() { - // @ts-ignore-next-line - this._context = new (window.AudioContext || window.webkitAudioContext)(); + // @ts-expect-error webkitAudioContext is not recognized + const context = new (AudioContext || webkitAudioContext)(); this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - - await this._context.audioWorklet.addModule( - new URL("./recorder.worklet.js", import.meta.url) + // Syntax here must match an item of `parser.worker` in Webpack config in + // order for module to be parsed and a chunk to be properly created. + await context.audioWorklet.addModule( + /* webpackChunkName: "recorder-worklet" */ + new URL("./recorder-worklet.js", import.meta.url) ); - + this._context = context; this._source = this._context.createMediaStreamSource(this._stream); - this._recorder = new AudioWorkletNode(this._context, "recorder.worklet"); - + this._recorder = new AudioWorkletNode(this._context, "recorder-worklet"); this._recorder.port.onmessage = (e) => { if (!this._active) { return; diff --git a/src/util/recorder.worklet.js b/src/util/recorder-worklet.js similarity index 89% rename from src/util/recorder.worklet.js rename to src/util/recorder-worklet.js index b8fef662e727..0d54b3a607c9 100644 --- a/src/util/recorder.worklet.js +++ b/src/util/recorder-worklet.js @@ -18,4 +18,4 @@ class RecorderProcessor extends AudioWorkletProcessor { } } -registerProcessor("recorder.worklet", RecorderProcessor); +registerProcessor("recorder-worklet", RecorderProcessor); diff --git a/yarn.lock b/yarn.lock index f26b8a6153bc..255077cb166b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3235,13 +3235,13 @@ __metadata: languageName: node linkType: hard -"@material/web@npm:1.5.0": - version: 1.5.0 - resolution: "@material/web@npm:1.5.0" +"@material/web@npm:1.5.1": + version: 1.5.1 + resolution: "@material/web@npm:1.5.1" dependencies: lit: "npm:^2.7.4 || ^3.0.0" tslib: "npm:^2.4.0" - checksum: 10/6bf651e8eaf33332b7f83aa04d473f6844a1f8280d5a2025a30583fbe03aa718de348260c5b9466d587f166772759aba0d100137e3e13d4d7c6fba6ffb79efa4 + checksum: 10/9be6019068fbc4ed6873837ad549fd672c24beaacd9123bc9f3d72b7dfd67a1acdafab43bd484b50d40300e27e3742314915f4d6e6723b38be223b4547ae206f languageName: node linkType: hard @@ -3974,6 +3974,15 @@ __metadata: languageName: node linkType: hard +"@thepassle/axobject-query@npm:^4.0.0": + version: 4.0.0 + resolution: "@thepassle/axobject-query@npm:4.0.0" + dependencies: + dequal: "npm:^2.0.3" + checksum: 10/919cb6ed90259cd0398b7e485dfbacae42423ff4202d5753c6545d3dfa9dc3d63e7f34941d6b94608c2730ec1539d30805411d9501c86951966e0d4aa0c4ae44 + languageName: node + linkType: hard + "@thomasloven/round-slider@npm:0.6.0": version: 0.6.0 resolution: "@thomasloven/round-slider@npm:0.6.0" @@ -5825,13 +5834,6 @@ __metadata: languageName: node linkType: hard -"axobject-query@npm:^2.2.0": - version: 2.2.0 - resolution: "axobject-query@npm:2.2.0" - checksum: 10/25de4b5ba6b28f5856fab60d86ea20fea941586bc38f33c81b78d66cd7e9c5792a9b9a9e60a38407aa634e01fee6a34133fbbd1d1d3d24cc686de83c6bb1e634 - languageName: node - linkType: hard - "b4a@npm:^1.6.4": version: 1.6.6 resolution: "b4a@npm:1.6.6" @@ -7579,13 +7581,13 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-lit-a11y@npm:4.1.2": - version: 4.1.2 - resolution: "eslint-plugin-lit-a11y@npm:4.1.2" +"eslint-plugin-lit-a11y@npm:4.1.3": + version: 4.1.3 + resolution: "eslint-plugin-lit-a11y@npm:4.1.3" dependencies: + "@thepassle/axobject-query": "npm:^4.0.0" aria-query: "npm:^5.1.3" axe-core: "npm:^4.3.3" - axobject-query: "npm:^2.2.0" dom5: "npm:^3.0.1" emoji-regex: "npm:^10.2.1" eslint-plugin-lit: "npm:^1.10.1" @@ -7596,7 +7598,7 @@ __metadata: requireindex: "npm:~1.2.0" peerDependencies: eslint: ">= 5" - checksum: 10/2d70f0b9fa6afc7f259877acd7e69c14f0104a69a019efb594d5de603e12b982e4a96fec5b169005fab244655951f85bff77f469d9aeadb885974f963a7d9996 + checksum: 10/730a82cfefbeba87e604172db8c29fc18d3361b5c913531c05e83af26edbe612df955d5f124daf6066c3703b0e25f2352a8ea9cae8b2f8a3e6121937c297d3a9 languageName: node linkType: hard @@ -8949,7 +8951,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.5.0" + "@material/web": "npm:1.5.1" "@mdi/js": "npm:7.4.47" "@mdi/svg": "npm:7.4.47" "@octokit/auth-oauth-device": "npm:7.1.1" @@ -9018,7 +9020,7 @@ __metadata: eslint-import-resolver-webpack: "npm:0.13.8" eslint-plugin-import: "npm:2.29.1" eslint-plugin-lit: "npm:1.14.0" - eslint-plugin-lit-a11y: "npm:4.1.2" + eslint-plugin-lit-a11y: "npm:4.1.3" eslint-plugin-unused-imports: "npm:4.0.0" eslint-plugin-wc: "npm:2.1.0" fancy-log: "npm:2.0.0"