Compare commits

..

18 Commits

Author SHA1 Message Date
renovate[bot]
a9766ed66b Update dependency core-js to v3.45.1 (#26667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 22:03:05 +02:00
renovate[bot]
26a83feeb0 Update Yarn to v4.9.3 (#26662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 22:02:49 +02:00
karwosts
8c5dd7cdba Fix shortcuts quickbar translation (#26666) 2025-08-23 22:02:35 +02:00
renovate[bot]
0d025a2355 Update dependency @rsdoctor/rspack-plugin to v1.2.3 (#26659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 10:33:08 +02:00
karwosts
8216778d0c Fix last backup status counter on system page (#26655) 2025-08-23 09:46:43 +02:00
Paul Bottein
fe16b689a8 Remove floors from the area overview dashboard (#26622)
* Remove floors from the area overview dashboard

* Refactor summaries view code

* Update src/panels/lovelace/strategies/overview/overview-home-view-strategy.ts

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-08-23 07:44:47 +00:00
Aidan Timson
2653f6c874 Move section to other views/dashboards from edit dialog menu (#26621)
* Init

* Pass in lovelace

* Working move

* Finalise
2025-08-23 09:22:34 +02:00
Jan-Philipp Benecke
8093f7f4cb Add background opacity option to section styling (#26651)
* Add background opacity option to section styling

* Run updated prettier
2025-08-22 18:58:47 +02:00
Jan-Philipp Benecke
b26c914ff9 Use dedicated translation key for none option in section background type (#26652) 2025-08-22 18:13:04 +02:00
Jan-Philipp Benecke
b2fa97b6dc Add background styling options to section settings (#26369)
* Add background styling options to section settings

* Improve

* Fix linter

* Move to subkey

* Use hex instead of rgb to save in yaml

* Remove or
2025-08-22 12:51:07 +02:00
renovate[bot]
8a8bba422a Update dependency typescript-eslint to v8.40.0 (#26648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 08:34:15 +03:00
Aidan Timson
76ca66b1b5 Change home and other zones edit dialog title to location name (#26637)
* Change home and other zone edit dialog title to location name

* Remove no longer used translations

* Use name as context

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

* Use name as context

* Add translation

* Use name as context

* Actually use the right key

* Use name as context

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-08-22 08:13:10 +03:00
Paul Bottein
280dbfc958 Add "add" button to lovelace dashboard toolbar (#26644)
* Add add button to lovelace dashboard toolbar

* Add person and area

* Fix typing
2025-08-21 20:45:57 +02:00
karwosts
0b10ad3e78 No multiline strings in state-history-chart-timeline (#26646) 2025-08-21 17:39:54 +02:00
Aidan Timson
04d0aa2f22 Remove "hassio" option from commands in quick bar (#26640) 2025-08-21 16:42:40 +03:00
Aidan Timson
4b901101da Fix voice assist setup success step translations (#26638) 2025-08-21 16:41:16 +03:00
Aidan Timson
4960284e2d Media player playback controls card feature (#26608)
* Setup

* Add buttons

* Fix

* Move to function

* Clean

* Cleanup

* Check client size

* Get width from host component

* Fix

* Spacing

* use current target

* Ensure state updates update render

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/panels/lovelace/card-features/hui-media-player-playback-card-feature.ts

* Resolve code suggestion type errors

* Resize observer not needed

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 10:28:43 +00:00
renovate[bot]
11d32300e9 Update dependency eslint-plugin-unused-imports to v4.2.0 (#26641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-21 13:26:40 +03:00
39 changed files with 1593 additions and 847 deletions

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.9.2.cjs
yarnPath: .yarn/releases/yarn-4.9.3.cjs

View File

@@ -99,7 +99,7 @@
"barcode-detector": "3.0.5",
"color-name": "2.0.0",
"comlink": "4.4.2",
"core-js": "3.45.0",
"core-js": "3.45.1",
"cropperjs": "1.6.2",
"culori": "4.0.2",
"date-fns": "4.1.0",
@@ -158,7 +158,7 @@
"@octokit/auth-oauth-device": "8.0.1",
"@octokit/plugin-retry": "8.0.1",
"@octokit/rest": "22.0.0",
"@rsdoctor/rspack-plugin": "1.2.2",
"@rsdoctor/rspack-plugin": "1.2.3",
"@rspack/cli": "1.4.11",
"@rspack/core": "1.4.11",
"@types/babel__plugin-transform-runtime": "7.9.5",
@@ -191,7 +191,7 @@
"eslint-plugin-import": "2.32.0",
"eslint-plugin-lit": "2.1.1",
"eslint-plugin-lit-a11y": "5.1.1",
"eslint-plugin-unused-imports": "4.1.4",
"eslint-plugin-unused-imports": "4.2.0",
"eslint-plugin-wc": "3.0.1",
"fancy-log": "2.0.0",
"fs-extra": "11.3.1",
@@ -218,7 +218,7 @@
"terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.2",
"typescript-eslint": "8.39.1",
"typescript-eslint": "8.40.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"webpack-stats-plugin": "1.1.3",
@@ -237,5 +237,5 @@
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"@vaadin/vaadin-themable-mixin": "24.8.5"
},
"packageManager": "yarn@4.9.2"
"packageManager": "yarn@4.9.3"
}

View File

@@ -101,7 +101,7 @@ export class StateHistoryChartTimeline extends LitElement {
fill: api.value(4) as string,
},
};
const text = api.value(3) as string;
const text = (api.value(3) as string).replaceAll("\n", " ");
const textWidth = measureTextWidth(text, 12);
const LABEL_PADDING = 4;
if (textWidth < rectShape.width - LABEL_PADDING * 2) {

View File

@@ -2,10 +2,16 @@ import type { Condition } from "../../../panels/lovelace/common/validate-conditi
import type { LovelaceCardConfig } from "./card";
import type { LovelaceStrategyConfig } from "./strategy";
export interface LovelaceSectionStyleConfig {
background_color?: string;
background_opacity?: number;
}
export interface LovelaceBaseSectionConfig {
visibility?: Condition[];
column_span?: number;
row_span?: number;
style?: LovelaceSectionStyleConfig;
/**
* @deprecated Use heading card instead.
*/

View File

@@ -12,6 +12,7 @@ export interface Zone {
}
export interface HomeZoneMutableParams {
name?: string;
latitude: number;
longitude: number;
radius: number;

View File

@@ -840,7 +840,9 @@ export class QuickBar extends LitElement {
const additionalItems = [
{
path: "",
primaryText: this.hass.localize("ui.panel.config.info.shortcuts"),
primaryText: this.hass.localize(
"ui.dialogs.quick-bar.commands.navigation.shortcuts"
),
action: () => showShortcutsDialog(this),
iconPath: mdiKeyboard,
},
@@ -856,7 +858,9 @@ export class QuickBar extends LitElement {
private _generateNavigationPanelCommands(): BaseNavigationCommand[] {
return Object.keys(this.hass.panels)
.filter((panelKey) => panelKey !== "_my_redirect")
.filter(
(panelKey) => panelKey !== "_my_redirect" && panelKey !== "hassio"
)
.map((panelKey) => {
const panel = this.hass.panels[panelKey];
const translationKey = getPanelNameTranslationKey(panel);

View File

@@ -112,7 +112,9 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
this.assistConfiguration.available_wake_words.length > 1
? html`<div class="row">
<ha-select
.label=${"Wake word"}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.form.wake_word_id"
)}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
@@ -144,7 +146,9 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
${pipelineEntity
? html`<div class="row">
<ha-select
.label=${"Assistant"}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.devices.pipeline"
)}
@closed=${stopPropagation}
.value=${pipelineEntity?.state}
fixedMenuPosition

View File

@@ -15,8 +15,6 @@ import type { CloudStatus } from "../../../data/cloud";
import { fetchCloudStatus } from "../../../data/cloud";
import type { HardwareInfo } from "../../../data/hardware";
import { BOARD_NAMES } from "../../../data/hardware";
import type { HassioBackup } from "../../../data/hassio/backup";
import { fetchHassioBackups } from "../../../data/hassio/backup";
import type {
HassioHassOSInfo,
HassioHostInfo,
@@ -44,7 +42,7 @@ class HaConfigSystemNavigation extends LitElement {
@property({ attribute: false }) public showAdvanced = false;
@state() private _latestBackupDate?: string;
@state() private _latestBackupDate?: Date;
@state() private _boardName?: string;
@@ -63,7 +61,7 @@ class HaConfigSystemNavigation extends LitElement {
description = this._latestBackupDate
? this.hass.localize("ui.panel.config.backup.description", {
relative_time: relativeTime(
new Date(this._latestBackupDate),
this._latestBackupDate,
this.hass.locale
),
})
@@ -155,26 +153,24 @@ class HaConfigSystemNavigation extends LitElement {
this._fetchNetworkStatus();
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
this._fetchBackupInfo(isHassioLoaded);
this._fetchBackupInfo();
this._fetchHardwareInfo(isHassioLoaded);
if (isHassioLoaded) {
this._fetchStorageInfo();
}
}
private async _fetchBackupInfo(isHassioLoaded: boolean) {
const backups: BackupContent[] | HassioBackup[] = isHassioLoaded
? await fetchHassioBackups(this.hass)
: isComponentLoaded(this.hass, "backup")
? await fetchBackupInfo(this.hass).then(
(backupData) => backupData.backups
)
: [];
private async _fetchBackupInfo() {
const backups: BackupContent[] = isComponentLoaded(this.hass, "backup")
? await fetchBackupInfo(this.hass).then(
(backupData) => backupData.backups
)
: [];
if (backups.length > 0) {
this._latestBackupDate = (backups as any[]).reduce((a, b) =>
a.date > b.date ? a : b
).date;
this._latestBackupDate = backups
.map((backup) => new Date(backup.date))
.reduce((a, b) => (a > b ? a : b));
}
}

View File

@@ -85,7 +85,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
this._deviceTrackers = this._params.entry.device_trackers || [];
this._picture = this._params.entry.picture || null;
this._user = this._userId
? this._params.users.find((user) => user.id === this._userId)
? this._params.users?.find((user) => user.id === this._userId)
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._localOnly = this._user?.local_only;
@@ -372,10 +372,10 @@ class DialogPersonDetail extends LitElement implements HassDialog {
userAddedCallback: async (user?: User) => {
if (user) {
target.checked = true;
if (this._params!.entry) {
if (this._params!.entry && this._params!.updateEntry) {
await this._params!.updateEntry({ user_id: user.id });
}
this._params?.refreshUsers();
this._params?.refreshUsers?.();
this._user = user;
this._userId = user.id;
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
@@ -403,7 +403,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
return;
}
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._params?.refreshUsers?.();
this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
@@ -466,7 +466,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
if (newUsername) {
try {
await adminChangeUsername(this.hass, this._user.id, newUsername);
this._params?.refreshUsers();
this._params?.refreshUsers?.();
this._user = { ...this._user, username: newUsername };
showAlertDialog(this, {
text: this.hass.localize(
@@ -500,7 +500,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
],
local_only: this._localOnly,
});
this._params?.refreshUsers();
this._params?.refreshUsers?.();
}
const values: PersonMutableParams = {
name: this._name.trim(),
@@ -509,9 +509,9 @@ class DialogPersonDetail extends LitElement implements HassDialog {
picture: this._picture,
};
if (this._params!.entry) {
await this._params!.updateEntry(values);
await this._params!.updateEntry?.(values);
} else {
await this._params!.createEntry(values);
await this._params!.createEntry?.(values);
this._personExists = true;
}
this.closeDialog();
@@ -525,7 +525,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
private async _deleteEntry() {
this._submitting = true;
try {
if (await this._params!.removeEntry()) {
if (await this._params!.removeEntry?.()) {
if (this._params!.entry!.user_id) {
deleteUser(this.hass, this._params!.entry!.user_id);
}

View File

@@ -4,22 +4,22 @@ import type { User } from "../../../data/user";
export interface PersonDetailDialogParams {
entry?: Person;
users: User[];
refreshUsers: () => void;
createEntry: (values: PersonMutableParams) => Promise<unknown>;
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry: () => Promise<boolean>;
users?: User[];
refreshUsers?: () => void;
createEntry?: (values: PersonMutableParams) => Promise<unknown>;
updateEntry?: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry?: () => Promise<boolean>;
}
export const loadPersonDetailDialog = () => import("./dialog-person-detail");
export const showPersonDetailDialog = (
element: HTMLElement,
systemLogDetailParams: PersonDetailDialogParams
params: PersonDetailDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-person-detail",
dialogImport: loadPersonDetailDialog,
dialogParams: systemLogDetailParams,
dialogParams: params,
});
};

View File

@@ -34,6 +34,7 @@ class DialogHomeZoneDetail extends LitElement {
this._params = params;
this._error = undefined;
this._data = {
name: this.hass.config.location_name,
latitude: this.hass.config.latitude,
longitude: this.hass.config.longitude,
radius: this.hass.config.radius,
@@ -63,7 +64,7 @@ class DialogHomeZoneDetail extends LitElement {
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass!.localize("ui.panel.config.zone.edit_home")
this.hass!.localize("ui.common.edit_item", { name: this._data.name })
)}
>
<div>

View File

@@ -85,7 +85,9 @@ class DialogZoneDetail extends LitElement {
.heading=${createCloseHeading(
this.hass,
this._params.entry
? this._params.entry.name
? this.hass!.localize("ui.common.edit_item", {
name: this._params.entry.name,
})
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")
)}
>

View File

@@ -165,9 +165,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
.entry=${entry}
@click=${this._openEditEntry}
.path=${mdiPencil}
.label=${hass.localize(
"ui.panel.config.zone.edit_zone"
)}
.label=${hass.localize("ui.common.edit_item", {
name: entry.name,
})}
></ha-icon-button>
</div>
`
@@ -218,9 +218,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
this._canEditCore
? mdiPencil
: mdiPencilOff}
.label=${stateObject.entity_id === "zone.home"
? hass.localize("ui.panel.config.zone.edit_home")
: hass.localize("ui.panel.config.zone.edit_zone")}
.label=${hass.localize("ui.common.edit_item", {
name: hass.config.location_name,
})}
@click=${this._editHomeZone}
></ha-icon-button>
</ha-tooltip>`}

View File

@@ -0,0 +1,323 @@
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
mdiPause,
mdiPlay,
mdiPlayPause,
mdiPower,
mdiSkipNext,
mdiSkipPrevious,
mdiStop,
} from "@mdi/js";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
LovelaceCardFeatureContext,
MediaPlayerPlaybackCardFeatureConfig,
} from "./types";
import type {
ControlButton,
MediaPlayerEntity,
} from "../../../data/media-player";
import { MediaPlayerEntityFeature } from "../../../data/media-player";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { stateActive } from "../../../common/entity/state_active";
import { isUnavailableState } from "../../../data/entity";
import { hasConfigChanged } from "../common/has-changed";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-button";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon";
export const supportsMediaPlayerPlaybackCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
) => {
const stateObj = context.entity_id
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
const domain = computeDomain(stateObj.entity_id);
return domain === "media_player";
};
@customElement("hui-media-player-playback-card-feature")
class HuiMediaPlayerPlaybackCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state() private _config?: MediaPlayerPlaybackCardFeatureConfig;
@state() private _narrow?: boolean = false;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as
| MediaPlayerEntity
| undefined;
}
static getStubConfig(): MediaPlayerPlaybackCardFeatureConfig {
return {
type: "media-player-playback",
};
}
public setConfig(config: MediaPlayerPlaybackCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
public willUpdate(): void {
if (!this.hasUpdated) {
this._measureCard();
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const entityId = this.context?.entity_id;
return (
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
(!oldHass ||
!entityId ||
oldHass.states[entityId] !== this.hass!.states[entityId]))
);
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!supportsMediaPlayerPlaybackCardFeature(this.hass, this.context) ||
!this._stateObj
) {
return nothing;
}
const buttons = this._computeButtons(this._stateObj);
return html`
<ha-control-button-group>
${supportsFeature(this._stateObj, MediaPlayerEntityFeature.TURN_OFF) &&
stateActive(this._stateObj)
? html`
<ha-control-button
.label=${this.hass.localize("ui.card.media_player.turn_off")}
@click=${this._togglePower}
>
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</ha-control-button>
`
: ""}
${supportsFeature(this._stateObj, MediaPlayerEntityFeature.TURN_ON) &&
!stateActive(this._stateObj) &&
!isUnavailableState(this._stateObj.state)
? html`
<ha-control-button
.label=${this.hass.localize("ui.card.media_player.turn_on")}
@click=${this._togglePower}
>
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</ha-control-button>
`
: buttons.map(
(button) => html`
<ha-control-button
key=${button.action}
.label=${this.hass?.localize(
`ui.card.media_player.${button.action}`
)}
@click=${this._action}
>
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
</ha-control-button>
`
)}
</ha-control-button-group>
`;
}
private _measureCard() {
if (!this.isConnected) {
return;
}
const host = (this.getRootNode() as ShadowRoot).host as
| HTMLElement
| undefined;
const width = host?.clientWidth ?? this.clientWidth ?? 0;
this._narrow = width < 300;
}
private _computeControlButton(stateObj: MediaPlayerEntity): ControlButton {
return stateObj.state === "on"
? { icon: mdiPlayPause, action: "media_play_pause" }
: stateObj.state !== "playing"
? { icon: mdiPlay, action: "media_play" }
: supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE)
? { icon: mdiPause, action: "media_pause" }
: { icon: mdiStop, action: "media_stop" };
}
private _computeButtons(stateObj: MediaPlayerEntity): ControlButton[] {
const controlButton = this._computeControlButton(stateObj);
const assumedState = stateObj.attributes.assumed_state === true;
const controls: ControlButton[] = [];
if (
!this._narrow &&
(stateObj.state === "playing" || assumedState) &&
supportsFeature(stateObj, MediaPlayerEntityFeature.PREVIOUS_TRACK)
) {
controls.push({ icon: mdiSkipPrevious, action: "media_previous_track" });
}
if (
!assumedState &&
((stateObj.state === "playing" &&
(supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE) ||
supportsFeature(stateObj, MediaPlayerEntityFeature.STOP))) ||
((stateObj.state === "paused" || stateObj.state === "idle") &&
supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY)) ||
(stateObj.state === "on" &&
(supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY) ||
supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE))))
) {
controls.push({ icon: controlButton.icon, action: controlButton.action });
}
if (assumedState) {
if (supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY)) {
controls.push({ icon: mdiPlay, action: "media_play" });
}
if (supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE)) {
controls.push({ icon: mdiPause, action: "media_pause" });
}
if (supportsFeature(stateObj, MediaPlayerEntityFeature.STOP)) {
controls.push({ icon: mdiStop, action: "media_stop" });
}
}
if (
(stateObj.state === "playing" || assumedState) &&
supportsFeature(stateObj, MediaPlayerEntityFeature.NEXT_TRACK)
) {
controls.push({ icon: mdiSkipNext, action: "media_next_track" });
}
return controls;
}
private _togglePower(): void {
if (!this._stateObj) return;
this.hass!.callService(
"media_player",
stateActive(this._stateObj) ? "turn_off" : "turn_on",
{
entity_id: this._stateObj.entity_id,
}
);
}
private _action(e: Event): void {
const action = (e.currentTarget as HTMLElement).getAttribute("key");
if (!action) return;
switch (action) {
case "media_play_pause":
this._playPauseStop();
break;
case "media_play":
this._play();
break;
case "media_pause":
this._pause();
break;
case "media_stop":
this._stop();
break;
case "media_previous_track":
this._previousTrack();
break;
case "media_next_track":
this._nextTrack();
break;
}
}
private _playPauseStop(): void {
if (!this._stateObj) return;
const service =
this._stateObj.state !== "playing"
? "media_play"
: supportsFeature(this._stateObj, MediaPlayerEntityFeature.PAUSE)
? "media_pause"
: "media_stop";
this.hass!.callService("media_player", service, {
entity_id: this._stateObj.entity_id,
});
}
private _play(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_play", {
entity_id: this._stateObj.entity_id,
});
}
private _pause(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_pause", {
entity_id: this._stateObj.entity_id,
});
}
private _stop(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_stop", {
entity_id: this._stateObj.entity_id,
});
}
private _previousTrack(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_previous_track", {
entity_id: this._stateObj.entity_id,
});
}
private _nextTrack(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_next_track", {
entity_id: this._stateObj.entity_id,
});
}
static styles = cardFeatureStyles;
}
declare global {
interface HTMLElementTagNameMap {
"hui-media-player-playback-card-feature": HuiMediaPlayerPlaybackCardFeature;
}
}

View File

@@ -39,6 +39,10 @@ export interface LockOpenDoorCardFeatureConfig {
type: "lock-open-door";
}
export interface MediaPlayerPlaybackCardFeatureConfig {
type: "media-player-playback";
}
export interface MediaPlayerVolumeSliderCardFeatureConfig {
type: "media-player-volume-slider";
}
@@ -242,6 +246,7 @@ export type LovelaceCardFeatureConfig =
| LightColorTempCardFeatureConfig
| LockCommandsCardFeatureConfig
| LockOpenDoorCardFeatureConfig
| MediaPlayerPlaybackCardFeatureConfig
| MediaPlayerVolumeSliderCardFeatureConfig
| NumericInputCardFeatureConfig
| SelectOptionsCardFeatureConfig

View File

@@ -9,6 +9,7 @@ import {
type TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
@@ -84,8 +85,10 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const displayType =
config.display_type || (config.show_camera ? "camera" : "picture");
const vertical = displayType === "compact" ? config.vertical : false;
this._config = {
...config,
vertical,
display_type: displayType,
};
@@ -109,7 +112,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const featuresCount = this._config?.features?.length || 0;
return (
1 +
(displayType === "compact" ? 0 : 2) +
(displayType === "compact" ? (this._config?.vertical ? 1 : 0) : 2) +
(featuresPosition === "inline" ? 0 : featuresCount)
);
}
@@ -133,6 +136,11 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const displayType = this._config?.display_type || "picture";
if (this._config?.vertical) {
rows++;
min_columns = 3;
}
if (displayType !== "compact") {
if (featurePosition === "inline" && featuresCount > 0) {
rows += 3;
@@ -397,9 +405,12 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
return sensorStates;
}
private _featurePosition = memoizeOne(
(config: AreaCardConfig) => config.features_position || "bottom"
);
private _featurePosition = memoizeOne((config: AreaCardConfig) => {
if (config.vertical) {
return "bottom";
}
return config.features_position || "bottom";
});
private _displayedFeatures = memoizeOne((config: AreaCardConfig) => {
const features = config.features || [];
@@ -439,6 +450,8 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
`;
}
const contentClasses = { vertical: Boolean(this._config.vertical) };
const icon = area.icon;
const name = this._config.name || computeAreaName(area);
@@ -518,7 +531,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
</div>
`}
<div class="container ${containerOrientationClass}">
<div class="content">
<div class="content ${classMap(contentClasses)}">
<ha-tile-icon>
${displayType === "compact"
? this._renderAlertSensorBadge()
@@ -656,6 +669,16 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
gap: 10px;
}
.vertical {
flex-direction: column;
text-align: center;
justify-content: center;
}
.vertical ha-tile-info {
width: 100%;
flex: none;
}
ha-tile-icon {
--tile-icon-color: var(--tile-color);
position: relative;

View File

@@ -163,8 +163,20 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
return html`
<ha-card>
<div
class="image-container"
<hui-image
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${domain === "camera"
? this._config.entity
: this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
.fitMode=${this._config.fit_mode}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
@@ -175,28 +187,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
? "0"
: undefined
)}
role=${ifDefined(
hasAction(this._config.tap_action) || this._config.entity
? "0"
: undefined
)}
>
<hui-image
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${domain === "camera"
? this._config.entity
: this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
.fitMode=${this._config.fit_mode}
></hui-image>
</div>
></hui-image>
${footer}
</ha-card>
`;
@@ -211,14 +202,8 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
box-sizing: border-box;
}
.image-container {
height: 100%;
cursor: pointer;
}
hui-image {
cursor: pointer;
pointer-events: none;
height: 100%;
}

View File

@@ -219,13 +219,13 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
return html`
<ha-card>
<div
class="image-container ${classMap({
<hui-image
class=${classMap({
clickable:
hasTapAction ||
hasAction(this._config.hold_action) ||
hasAction(this._config.double_tap_action),
})}"
})}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasTap: hasTapAction,
@@ -233,23 +233,19 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
hasDoubleClick: hasAction(this._config.double_tap_action),
})}
tabindex=${ifDefined(hasTapAction ? "0" : undefined)}
role=${ifDefined(hasTapAction ? "button" : undefined)}
.config=${this._config}
>
<hui-image
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.fitMode=${this._config.fit_mode}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
></hui-image>
</div>
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.fitMode=${this._config.fit_mode}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
></hui-image>
<div class="box">
${this._config.title
? html`<div class="title">${this._config.title}</div>`
@@ -346,16 +342,12 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
height: 100%;
box-sizing: border-box;
}
.image-container {
height: 100%;
}
.image-container.clickable {
cursor: pointer;
}
hui-image {
pointer-events: none;
height: 100%;
}
hui-image.clickable {
cursor: pointer;
}
.box {
position: absolute;
left: 0;

View File

@@ -251,8 +251,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
const entityId = this._config.entity;
const stateObj = entityId ? this.hass.states[entityId] : undefined;
const contentClasses = { vertical: Boolean(this._config.vertical) };
if (!stateObj) {
return html`
<hui-warning .hass=${this.hass}>
@@ -261,6 +259,8 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
`;
}
const contentClasses = { vertical: Boolean(this._config.vertical) };
const name = this._config.name || computeStateName(stateObj);
const active = stateActive(stateObj);
const color = this._computeStateColor(stateObj, this._config.color);

View File

@@ -104,12 +104,13 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
state_color?: boolean;
}
export type AreaCardDisplayType = "compact" | "icon" | "picture" | "camera";
export interface AreaCardConfig extends LovelaceCardConfig {
area?: string;
name?: string;
color?: string;
navigation_path?: string;
display_type?: "compact" | "icon" | "picture" | "camera";
display_type?: AreaCardDisplayType;
/** @deprecated Use `display_type` instead */
show_camera?: boolean;
camera_view?: HuiImage["cameraView"];

View File

@@ -22,6 +22,7 @@ import "../card-features/hui-light-brightness-card-feature";
import "../card-features/hui-light-color-temp-card-feature";
import "../card-features/hui-lock-commands-card-feature";
import "../card-features/hui-lock-open-door-card-feature";
import "../card-features/hui-media-player-playback-card-feature";
import "../card-features/hui-media-player-volume-slider-card-feature";
import "../card-features/hui-numeric-input-card-feature";
import "../card-features/hui-select-options-card-feature";
@@ -70,6 +71,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
"light-color-temp",
"lock-commands",
"lock-open-door",
"media-player-playback",
"media-player-volume-slider",
"numeric-input",
"select-options",

View File

@@ -36,7 +36,7 @@ import {
DEVICE_CLASSES,
type AreaCardFeatureContext,
} from "../../cards/hui-area-card";
import type { AreaCardConfig } from "../../cards/types";
import type { AreaCardConfig, AreaCardDisplayType } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import type { EditDetailElementEvent, EditSubElementEvent } from "../types";
@@ -52,6 +52,7 @@ const cardConfigStruct = assign(
navigation_path: optional(string()),
show_camera: optional(boolean()),
display_type: optional(enums(["compact", "icon", "picture", "camera"])),
vertical: optional(boolean()),
camera_view: optional(string()),
alert_classes: optional(array(string())),
sensor_classes: optional(array(string())),
@@ -78,7 +79,7 @@ export class HuiAreaCardEditor
private _schema = memoizeOne(
(
localize: LocalizeFunc,
showCamera: boolean,
displayType: AreaCardDisplayType,
binaryClasses: SelectOption[],
sensorClasses: SelectOption[]
) =>
@@ -113,7 +114,28 @@ export class HuiAreaCardEditor
},
},
},
...(showCamera
...(displayType === "compact"
? ([
{
name: "content_layout",
required: true,
selector: {
select: {
mode: "dropdown",
options: ["horizontal", "vertical"].map(
(value) => ({
label: localize(
`ui.panel.lovelace.editor.card.area.content_layout_options.${value}`
),
value,
})
),
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
...(displayType === "camera"
? ([
{
name: "camera_view",
@@ -282,7 +304,7 @@ export class HuiAreaCardEditor
}
private _featuresSchema = memoizeOne(
(localize: LocalizeFunc) =>
(localize: LocalizeFunc, vertical: boolean) =>
[
{
name: "features_position",
@@ -303,6 +325,7 @@ export class HuiAreaCardEditor
src_dark: `/static/images/form/tile_features_position_${value}_dark.svg`,
flip_rtl: true,
},
disabled: vertical && value === "inline",
})),
},
},
@@ -338,31 +361,35 @@ export class HuiAreaCardEditor
this._config.sensor_classes || DEVICE_CLASSES.sensor
);
const showCamera = this._config.display_type === "camera";
const displayType =
this._config.display_type || this._config.show_camera
? "camera"
: "picture";
this._config.display_type ||
(this._config.show_camera ? "camera" : "picture");
const schema = this._schema(
this.hass.localize,
showCamera,
displayType,
binarySelectOptions,
sensorSelectOptions
);
const featuresSchema = this._featuresSchema(this.hass.localize);
const vertical = this._config.vertical && displayType === "compact";
const featuresSchema = this._featuresSchema(this.hass.localize, vertical);
const data = {
camera_view: "auto",
alert_classes: DEVICE_CLASSES.binary_sensor,
sensor_classes: DEVICE_CLASSES.sensor,
features_position: "bottom",
display_type: displayType,
content_layout: vertical ? "vertical" : "horizontal",
...this._config,
};
// Default features position to bottom and force it to bottom in vertical mode
if (!data.features_position || vertical) {
data.features_position = "bottom";
}
const hasCompatibleFeatures = this._hasCompatibleFeatures(
this._featureContext
);
@@ -420,6 +447,12 @@ export class HuiAreaCardEditor
delete config.camera_view;
}
// Convert content_layout to vertical
if (config.content_layout) {
config.vertical = config.content_layout === "vertical";
delete config.content_layout;
}
fireEvent(this, "config-changed", { config });
}

View File

@@ -42,6 +42,7 @@ import { supportsLightBrightnessCardFeature } from "../../card-features/hui-ligh
import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature";
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
@@ -95,6 +96,7 @@ const UI_FEATURE_TYPES = [
"light-color-temp",
"lock-commands",
"lock-open-door",
"media-player-playback",
"media-player-volume-slider",
"numeric-input",
"select-options",
@@ -162,6 +164,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
"light-color-temp": supportsLightColorTempCardFeature,
"lock-commands": supportsLockCommandsCardFeature,
"lock-open-door": supportsLockOpenDoorCardFeature,
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
"numeric-input": supportsNumericInputCardFeature,
"select-options": supportsSelectOptionsCardFeature,

View File

@@ -261,18 +261,17 @@ export class HuiTileCardEditor
this._config.hide_state ?? false
);
const featuresSchema = this._featuresSchema(
this.hass.localize,
this._config.vertical ?? false
);
const vertical = this._config.vertical ?? false;
const featuresSchema = this._featuresSchema(this.hass.localize, vertical);
const data = {
...this._config,
content_layout: this._config.vertical ? "vertical" : "horizontal",
content_layout: vertical ? "vertical" : "horizontal",
};
// Default features position to bottom and force it to bottom in vertical mode
if (!data.features_position || data.vertical) {
if (!data.features_position || vertical) {
data.features_position = "bottom";
}

View File

@@ -1,5 +1,10 @@
import type { ActionDetail } from "@material/mwc-list";
import { mdiClose, mdiDotsVertical, mdiPlaylistEdit } from "@mdi/js";
import {
mdiClose,
mdiDotsVertical,
mdiFileMoveOutline,
mdiPlaylistEdit,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
@@ -16,7 +21,10 @@ import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import "../../../../components/sl-tab-group";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import {
isStrategyView,
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";
@@ -27,6 +35,12 @@ import {
import "./hui-section-settings-editor";
import "./hui-section-visibility-editor";
import type { EditSectionDialogParams } from "./show-edit-section-dialog";
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import { saveConfig } from "../../../../data/lovelace/config/types";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import { addSection, deleteSection, moveSection } from "../config-util";
import type { Lovelace } from "../../types";
const TABS = ["tab-settings", "tab-visibility"] as const;
@@ -37,6 +51,8 @@ export class HuiDialogEditSection
{
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: Lovelace;
@state() private _params?: EditSectionDialogParams;
@state() private _config?: LovelaceSectionRawConfig;
@@ -61,6 +77,8 @@ export class HuiDialogEditSection
public async showDialog(params: EditSectionDialogParams): Promise<void> {
this._params = params;
this.lovelace = params.lovelace;
this._config = findLovelaceContainer(this._params.lovelaceConfig, [
this._params.viewIndex,
this._params.sectionIndex,
@@ -165,6 +183,15 @@ export class HuiDialogEditSection
.path=${mdiPlaylistEdit}
></ha-svg-icon>
</ha-list-item>
<ha-list-item graphic="icon">
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_to_view"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiFileMoveOutline}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
${!this._yamlMode
? html`
@@ -222,9 +249,138 @@ export class HuiDialogEditSection
case 0:
this._yamlMode = !this._yamlMode;
break;
case 1:
this._openSelectView();
break;
}
}
private _openSelectView(): void {
if (!this._params || !this.lovelace) {
return;
}
showSelectViewDialog(this, {
lovelaceConfig: this._params.lovelaceConfig,
urlPath: this.lovelace.urlPath,
allowDashboardChange: true,
header: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.header"
),
viewSelectedCallback: this._moveSectionToView,
});
}
private _moveSectionToView = async (
urlPath: string | null,
selectedDashConfig: LovelaceConfig,
viewIndex: number
) => {
if (!this._params || !this.lovelace) {
return;
}
const toView = selectedDashConfig.views[viewIndex];
if (isStrategyView(toView)) {
showAlertDialog(this, {
title: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error_title"
),
text: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error_text_strategy"
),
warning: true,
});
return;
}
const fromViewIndex = this._params.viewIndex;
const fromSectionIndex = this._params.sectionIndex;
// Same dashboard
if (urlPath === this.lovelace.urlPath) {
const oldConfig = this.lovelace.config;
const toIndex = toView.sections?.length ?? 0;
try {
await this.lovelace.saveConfig(
moveSection(
oldConfig,
[fromViewIndex, fromSectionIndex],
[viewIndex, toIndex]
)
);
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.success"
),
duration: 4000,
action: {
action: async () => {
await this.lovelace!.saveConfig(oldConfig);
},
text: this.hass!.localize("ui.common.undo"),
},
});
this.closeDialog();
} catch (err: any) {
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error"
),
});
// eslint-disable-next-line no-console
console.error(err);
}
return;
}
// Cross dashboard
const oldFromConfig = this.lovelace.config;
const oldToConfig = selectedDashConfig;
try {
const section = findLovelaceContainer(oldFromConfig, [
fromViewIndex,
fromSectionIndex,
]) as LovelaceSectionRawConfig;
await saveConfig(
this.hass!,
urlPath,
addSection(oldToConfig, viewIndex, section)
);
await this.lovelace.saveConfig(
deleteSection(oldFromConfig, fromViewIndex, fromSectionIndex)
);
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.success"
),
duration: 4000,
action: {
action: async () => {
await saveConfig(this.hass!, urlPath, oldToConfig);
await this.lovelace!.saveConfig(oldFromConfig);
},
text: this.hass!.localize("ui.common.undo"),
},
});
this.closeDialog();
} catch (err: any) {
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error"
),
});
// eslint-disable-next-line no-console
console.error(err);
}
};
private _viewYamlChanged(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.isValid) {

View File

@@ -1,6 +1,8 @@
import type { PropertyValues } from "lit";
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { mdiFormatColorFill } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import type {
HaFormSchema,
@@ -10,9 +12,13 @@ import "../../../../components/ha-form/ha-form";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import { hex2rgb, rgb2hex } from "../../../../common/color/convert-color";
interface SettingsData {
column_span?: number;
background_type: "none" | "color";
background_color?: number[];
background_opacity?: number;
}
@customElement("hui-section-settings-editor")
@@ -23,8 +29,10 @@ export class HuiDialogEditSection extends LitElement {
@property({ attribute: false }) public viewConfig!: LovelaceViewConfig;
@state() private _selectorBackgroundType: "none" | "color" = "none";
private _schema = memoizeOne(
(maxColumns: number) =>
(maxColumns: number, enableBackground: boolean) =>
[
{
name: "column_span",
@@ -36,15 +44,91 @@ export class HuiDialogEditSection extends LitElement {
},
},
},
{
name: "styling",
type: "expandable",
flatten: true,
iconPath: mdiFormatColorFill,
schema: [
{
name: "background_settings",
flatten: true,
type: "grid",
schema: [
{
name: "background_type",
required: true,
selector: {
select: {
mode: "dropdown",
options: [
{
value: "none",
label: this.hass.localize(
"ui.panel.lovelace.editor.edit_section.settings.background_type_none_option"
),
},
{
value: "color",
label: this.hass.localize(
"ui.panel.lovelace.editor.edit_section.settings.background_type_color_option"
),
},
],
},
},
},
{
name: "background_color",
selector: {
color_rgb: {},
},
disabled: !enableBackground,
},
{
name: "background_opacity",
selector: {
number: {
min: 0,
max: 100,
step: 1,
mode: "slider",
unit_of_measurement: "%",
},
},
disabled: !enableBackground,
},
],
},
],
},
] as const satisfies HaFormSchema[]
);
protected firstUpdated(_changedProperties: PropertyValues) {
super.firstUpdated(_changedProperties);
if (this.config.style?.background_color) {
this._selectorBackgroundType = "color";
} else {
this._selectorBackgroundType = "none";
}
}
render() {
const data: SettingsData = {
column_span: this.config.column_span || 1,
background_type: this._selectorBackgroundType,
background_color: this.config.style?.background_color
? hex2rgb(this.config.style?.background_color as any)
: [],
background_opacity: this.config.style?.background_opacity || 100,
};
const schema = this._schema(this.viewConfig.max_columns || 4);
const schema = this._schema(
this.viewConfig.max_columns || 4,
this._selectorBackgroundType === "color"
);
return html`
<ha-form
@@ -76,11 +160,27 @@ export class HuiDialogEditSection extends LitElement {
ev.stopPropagation();
const newData = ev.detail.value as SettingsData;
this._selectorBackgroundType = newData.background_type;
const newConfig: LovelaceSectionRawConfig = {
...this.config,
column_span: newData.column_span,
};
if (newData.background_type === "color") {
newConfig.style = {
...newConfig.style,
background_color: rgb2hex(newData.background_color as any),
background_opacity: newData.background_opacity,
};
} else {
newConfig.style = {
...newConfig.style,
background_color: undefined,
background_opacity: undefined,
};
}
fireEvent(this, "value-changed", { value: newConfig });
}
}

View File

@@ -1,7 +1,9 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { Lovelace } from "../../types";
export interface EditSectionDialogParams {
lovelace: Lovelace;
lovelaceConfig: LovelaceConfig;
saveConfig: (config: LovelaceConfig) => void;
viewIndex: number;

View File

@@ -51,7 +51,19 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
}
return html`
<div
<hui-image
.hass=${this.hass}
.entity=${this._config.entity}
.image=${stateObj ? computeImageUrl(stateObj) : this._config.image}
.stateImage=${this._config.state_image}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.filter=${this._config.filter}
.stateFilter=${this._config.state_filter}
.title=${computeTooltip(this.hass, this._config)}
.aspectRatio=${this._config.aspect_ratio}
.darkModeImage=${this._config.dark_mode_image}
.darkModeFilter=${this._config.dark_mode_filter}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
@@ -60,25 +72,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
tabindex=${ifDefined(
hasAction(this._config.tap_action) ? "0" : undefined
)}
role=${ifDefined(
hasAction(this._config.tap_action) ? "button" : undefined
)}
>
<hui-image
.hass=${this.hass}
.entity=${this._config.entity}
.image=${stateObj ? computeImageUrl(stateObj) : this._config.image}
.stateImage=${this._config.state_image}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.filter=${this._config.filter}
.stateFilter=${this._config.state_filter}
.title=${computeTooltip(this.hass, this._config)}
.aspectRatio=${this._config.aspect_ratio}
.darkModeImage=${this._config.dark_mode_image}
.darkModeFilter=${this._config.dark_mode_filter}
></hui-image>
</div>
></hui-image>
`;
}
@@ -90,12 +84,9 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
}
hui-image {
-webkit-user-select: none !important;
pointer-events: none;
}
div:focus {
hui-image:focus {
outline: none;
}
div:focus hui-image {
background: var(--divider-color);
border-radius: 100%;
}

View File

@@ -1,7 +1,9 @@
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import {
mdiAccount,
mdiCodeBraces,
mdiCommentProcessingOutline,
mdiDevices,
mdiDotsVertical,
mdiFileMultiple,
mdiFormatListBulletedTriangle,
@@ -10,7 +12,9 @@ import {
mdiPencil,
mdiPlus,
mdiRefresh,
mdiRobot,
mdiShape,
mdiSofa,
mdiViewDashboard,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
@@ -52,6 +56,7 @@ import {
updateDashboard,
} from "../../data/lovelace/dashboard";
import { getPanelTitle } from "../../data/panel";
import { createPerson } from "../../data/person";
import {
showAlertDialog,
showConfirmationDialog,
@@ -66,7 +71,10 @@ import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, PanelInfo } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { showNewAutomationDialog } from "../config/automation/show-dialog-new-automation";
import { showAddIntegrationDialog } from "../config/integrations/show-add-integration-dialog";
import { showDashboardDetailDialog } from "../config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail";
import { showPersonDetailDialog } from "../config/person/show-dialog-person-detail";
import { swapView } from "./editor/config-util";
import { showDashboardStrategyEditorDialog } from "./editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor";
import { showSaveDialog } from "./editor/show-save-config-dialog";
@@ -78,6 +86,28 @@ import "./views/hui-view";
import type { HUIView } from "./views/hui-view";
import "./views/hui-view-background";
import "./views/hui-view-container";
import { showAreaRegistryDetailDialog } from "../config/areas/show-dialog-area-registry-detail";
import { createAreaRegistryEntry } from "../../data/area_registry";
import { showToast } from "../../util/toast";
interface ActionItem {
icon: string;
key: LocalizeKeys;
overflowAction?: any;
buttonAction?: any;
visible: boolean | undefined;
overflow: boolean;
overflow_can_promote?: boolean;
suffix?: string;
subItems?: SubActionItem[];
}
interface SubActionItem {
icon: string;
key: LocalizeKeys;
action?: any;
visible: boolean | undefined;
}
@customElement("hui-root")
class HUIRoot extends LitElement {
@@ -145,16 +175,7 @@ class HUIRoot extends LitElement {
);
}
const items: {
icon: string;
key: LocalizeKeys;
overflowAction?: any;
buttonAction?: any;
visible: boolean | undefined;
overflow: boolean;
overflow_can_promote?: boolean;
suffix?: string;
}[] = [
const items: ActionItem[] = [
{
icon: mdiFormatListBulletedTriangle,
key: "ui.panel.lovelace.unused_entities.title",
@@ -183,13 +204,45 @@ class HUIRoot extends LitElement {
visible: this._editMode && this.hass.userData?.showAdvanced,
overflow: true,
},
{
icon: mdiPlus,
key: "ui.panel.lovelace.menu.add",
visible: !this._editMode && this.hass.user?.is_admin,
overflow: false,
subItems: [
{
icon: mdiDevices,
key: "ui.panel.lovelace.menu.add_device",
visible: true,
action: this._handleAddDevice,
},
{
icon: mdiRobot,
key: "ui.panel.lovelace.menu.create_automation",
visible: true,
action: this._handleACreateAutomation,
},
{
icon: mdiSofa,
key: "ui.panel.lovelace.menu.add_area",
visible: true,
action: this._handleAddArea,
},
{
icon: mdiAccount,
key: "ui.panel.lovelace.menu.invite_person",
visible: true,
action: this._handleInvitePerson,
},
],
},
{
icon: mdiMagnify,
key: "ui.panel.lovelace.menu.search_entities",
buttonAction: this._showQuickBar,
overflowAction: this._handleShowQuickBar,
visible: !this._editMode,
overflow: this.narrow,
overflow: false,
suffix: this.hass.enableShortcuts ? "(E)" : undefined,
},
{
@@ -199,7 +252,7 @@ class HUIRoot extends LitElement {
overflowAction: this._handleShowVoiceCommandDialog,
visible:
!this._editMode && this._conversation(this.hass.config.components),
overflow: this.narrow,
overflow: false,
suffix: this.hass.enableShortcuts ? "(A)" : undefined,
},
{
@@ -247,20 +300,50 @@ class HUIRoot extends LitElement {
(i) => i.visible && (!i.overflow || overflowCanPromote)
);
buttonItems.forEach((i) => {
result.push(
html`<ha-tooltip
slot="actionItems"
placement="bottom"
.content=${[this.hass!.localize(i.key), i.suffix].join(" ")}
>
<ha-icon-button
.path=${i.icon}
@click=${i.buttonAction}
></ha-icon-button>
</ha-tooltip>`
);
buttonItems.forEach((item) => {
const label = [this.hass!.localize(item.key), item.suffix].join(" ");
const button = item.subItems
? html`
<ha-button-menu
slot="actionItems"
corner="BOTTOM_END"
menu-corner="END"
>
<ha-icon-button
.label=${label}
.path=${item.icon}
slot="trigger"
></ha-icon-button>
${item.subItems
.filter((subItem) => subItem.visible)
.map(
(subItem) => html`
<ha-list-item
graphic="icon"
.key=${subItem.key}
@request-selected=${subItem.action}
>
${this.hass!.localize(subItem.key)}
<ha-svg-icon
slot="graphic"
.path=${subItem.icon}
></ha-svg-icon>
</ha-list-item>
`
)}
</ha-button-menu>
`
: html`
<ha-tooltip slot="actionItems" placement="bottom" .content=${label}>
<ha-icon-button
.path=${item.icon}
@click=${item.buttonAction}
></ha-icon-button>
</ha-tooltip>
`;
result.push(button);
});
if (overflowItems.length && !overflowCanPromote) {
const listItems: TemplateResult[] = [];
overflowItems.forEach((i) => {
@@ -373,10 +456,15 @@ class HUIRoot extends LitElement {
})}
</sl-tab-group>`;
const isSubview = curViewConfig?.subview;
const hasTabViews = views.filter((view) => !view.subview).length > 1;
const showTabBar = this._editMode || (!isSubview && hasTabViews);
return html`
<div
class=${classMap({
"edit-mode": this._editMode,
narrow: this.narrow,
})}
>
<div class="header">
@@ -399,7 +487,7 @@ class HUIRoot extends LitElement {
<div class="action-items">${this._renderActionItems()}</div>
`
: html`
${curViewConfig?.subview
${isSubview
? html`
<ha-icon-button-arrow-prev
slot="navigationIcon"
@@ -413,34 +501,37 @@ class HUIRoot extends LitElement {
.narrow=${this.narrow}
></ha-menu-button>
`}
${curViewConfig?.subview
${isSubview
? html`<div class="main-title">${curViewConfig.title}</div>`
: views.filter((view) => !view.subview).length > 1
: hasTabViews && !showTabBar
? tabs
: html`
<div class="main-title">
${views[0]?.title ?? dashboardTitle}
${curViewConfig?.title ?? dashboardTitle}
</div>
`}
<div class="action-items">${this._renderActionItems()}</div>
`}
</div>
${this._editMode
${showTabBar
? html`<div class="edit-tab-bar">
${tabs}
<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>
${this._editMode
? html`<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>`
: nothing}
</div>`
: nothing}
</div>
<hui-view-container
class=${showTabBar ? "has-tab-bar" : ""}
.hass=${this.hass}
.theme=${curViewConfig?.theme}
id="view"
@@ -696,6 +787,79 @@ class HUIRoot extends LitElement {
}
}
private async _handleAddDevice(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showAddIntegrationDialog(this);
}
private async _handleACreateAutomation(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showNewAutomationDialog(this, { mode: "automation" });
}
private async _handleAddArea(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
const area = await createAreaRegistryEntry(this.hass, values);
showToast(this, {
message: this.hass.localize(
"ui.panel.lovelace.menu.add_area_success"
),
action: {
action: () => {
navigate(`/config/areas/area/${area.area_id}`);
},
text: this.hass.localize("ui.panel.lovelace.menu.add_area_action"),
},
});
},
});
}
private async _handleInvitePerson(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showPersonDetailDialog(this, {
users: [],
createEntry: async (values) => {
await createPerson(this.hass!, values);
showToast(this, {
message: this.hass.localize(
"ui.panel.lovelace.menu.add_person_success"
),
action: {
action: () => {
navigate(`/config/person`);
},
text: this.hass.localize(
"ui.panel.lovelace.menu.add_person_action"
),
},
});
},
});
}
private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
@@ -1050,6 +1214,14 @@ class HUIRoot extends LitElement {
margin: var(--margin-title);
line-height: var(--ha-line-height-normal);
flex-grow: 1;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
min-width: 0;
}
.narrow .main-title {
margin: 0;
margin-inline-start: 8px;
}
.action-items {
white-space: nowrap;
@@ -1114,9 +1286,6 @@ class HUIRoot extends LitElement {
--ha-tab-active-text-color: var(--app-header-edit-text-color, #fff);
--ha-tab-indicator-color: var(--app-header-edit-text-color, #fff);
}
.edit-mode sl-tab {
height: 54px;
}
sl-tab {
height: calc(var(--header-height, 56px) - 2px);
}
@@ -1188,9 +1357,10 @@ class HUIRoot extends LitElement {
/**
* In edit mode we have the tab bar on a new line *
*/
.edit-mode hui-view-container {
hui-view-container.has-tab-bar {
padding-top: calc(
var(--header-height) + 48px + var(--safe-area-inset-top)
var(--header-height) + calc(var(--header-height, 56px) - 2px) +
var(--safe-area-inset-top)
);
}
.hide-tab {

View File

@@ -20,6 +20,7 @@ import "../components/hui-card-edit-mode";
import { moveCard } from "../editor/config-util";
import type { LovelaceCardPath } from "../editor/lovelace-path";
import type { Lovelace } from "../types";
import { hex2rgb } from "../../../common/color/convert-color";
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
delay: 100,
@@ -86,6 +87,12 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
? IMPORT_MODE_CARD_SORTABLE_OPTIONS
: CARD_SORTABLE_OPTIONS;
const backgroundOpacity =
(this._config.style?.background_opacity || 100) / 100;
const background = this._config.style?.background_color
? `rgba(${hex2rgb(this._config.style.background_color)}, ${backgroundOpacity})`
: undefined;
return html`
<ha-sortable
.disabled=${!editMode}
@@ -103,7 +110,11 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
class="container ${classMap({
"edit-mode": editMode,
"import-only": this.importOnly,
"has-background": Boolean(background),
})}"
style=${styleMap({
background: background,
})}
>
${repeat(
cardsConfig,
@@ -250,6 +261,10 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
border: none;
padding: 0 !important;
}
.container.has-background {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
}
.card {
border-radius: var(--ha-card-border-radius, 12px);

View File

@@ -3,6 +3,7 @@ import { customElement } from "lit/decorators";
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
@@ -11,11 +12,11 @@ import {
getAreas,
getFloors,
} from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import {
findEntities,
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
export interface OverviewClimateViewStrategyConfig {
type: "overview-climate";
@@ -24,10 +25,9 @@ export interface OverviewClimateViewStrategyConfig {
const processAreasForClimate = (
areaIds: string[],
hass: HomeAssistant,
entities: string[],
computeTileCard: (entityId: string) => any
): any[] => {
const cards: any[] = [];
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];
@@ -38,6 +38,8 @@ const processAreasForClimate = (
});
const areaEntities = entities.filter(areaFilter);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
if (areaEntities.length > 0) {
cards.push({
heading_style: "subtitle",
@@ -81,8 +83,6 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
// Process floors
for (const floorStructure of home.floors) {
const floorId = floorStructure.id;
@@ -100,12 +100,7 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForClimate(
areaIds,
hass,
entities,
computeTileCard
);
const areaCards = processAreasForClimate(areaIds, hass, entities);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);
@@ -126,12 +121,7 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForClimate(
home.areas,
hass,
entities,
computeTileCard
);
const areaCards = processAreasForClimate(home.areas, hass, entities);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);

View File

@@ -1,22 +1,16 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { AreaRegistryEntry } from "../../../../data/area_registry";
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
import { AREA_CONTROLS, type AreaControl } from "../../card-features/types";
import type {
AreaCardConfig,
ButtonCardConfig,
HeadingCardConfig,
MarkdownCardConfig,
TileCardConfig,
} from "../../cards/types";
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import { getAreas } from "../areas/helpers/areas-strategy-helper";
import { OVERVIEW_SUMMARIES_ICONS } from "./helpers/overview-summaries";
export interface OverviewHomeViewStrategyConfig {
@@ -31,42 +25,22 @@ const computeAreaCard = (
const area = hass.areas[areaId] as AreaRegistryEntry | undefined;
const path = `areas-${areaId}`;
const controls: AreaControl[] = AREA_CONTROLS.filter(
(a) => a !== "switch" // Exclude switches control for areas as we don't know what the switches control
);
const controlEntities = getAreaControlEntities(controls, areaId, [], hass);
const filteredControls = controls.filter(
(control) => controlEntities[control].length > 0
);
const sensorClasses: string[] = [];
if (area?.temperature_entity_id) {
sensorClasses.push("temperature");
}
if (area?.humidity_entity_id) {
sensorClasses.push("humidity");
}
return {
type: "area",
area: areaId,
display_type: "compact",
sensor_classes: sensorClasses,
features: filteredControls.length
? [
{
type: "area-controls",
controls: filteredControls,
},
]
: [],
grid_options: {
rows: 1,
columns: 12,
},
features_position: "inline",
navigation_path: path,
vertical: true,
grid_options: {
rows: 2,
columns: 4,
},
};
};
@@ -76,58 +50,25 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
config: OverviewHomeViewStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const floors = getFloors(hass.floors);
const areas = getAreas(hass.areas);
const home = getHomeStructure(floors, areas);
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const floorsSections: LovelaceSectionConfig[] = home.floors.map(
(floorStructure) => {
const floorId = floorStructure.id;
const areaIds = floorStructure.areas;
const floor = hass.floors[floorId];
const headingCard: HeadingCardConfig = {
const areasSection: LovelaceSectionConfig = {
type: "grid",
column_span: 2,
cards: [
{
type: "heading",
heading_style: "title",
heading: floorCount > 1 ? floor.name : "Areas",
icon: floor.icon || floorDefaultIcon(floor),
};
const areasCards = areaIds.map<AreaCardConfig>((areaId) =>
computeAreaCard(areaId, hass)
);
return {
max_columns: 3,
type: "grid",
cards: [headingCard, ...areasCards],
};
}
);
if (home.areas.length > 0) {
floorsSections.push({
type: "grid",
max_columns: 3,
cards: [
{
type: "heading",
heading_style: "title",
icon: "mdi:home",
heading: floorCount > 1 ? "Other areas" : "Areas",
},
...home.areas.map<AreaCardConfig>((areaId) =>
computeAreaCard(areaId, hass)
),
],
} as LovelaceSectionConfig);
}
heading: "Areas",
},
...areas.map<AreaCardConfig>((area) =>
computeAreaCard(area.area_id, hass)
),
],
};
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
const maxColumns = clamp(floorsSections.length, 2, 3);
const maxColumns = 2;
const favoriteSection: LovelaceSectionConfig = {
type: "grid",
@@ -143,7 +84,8 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
favoriteSection.cards!.push(
{
type: "heading",
heading: "Quick actions",
heading: "",
heading_style: "subtitle",
},
...favoriteEntities.map(
(entityId) =>
@@ -167,6 +109,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.lights,
name: "Lights",
icon_height: "24px",
grid_options: {
rows: 2,
columns: 4,
@@ -180,6 +123,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.climate,
name: "Climate",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -193,6 +137,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.security,
name: "Security",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -206,6 +151,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.media_players,
name: "Media Players",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -219,6 +165,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: "mdi:lightning-bolt",
name: "Energy",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -234,7 +181,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
const sections = [
...(favoriteSection.cards ? [favoriteSection] : []),
summarySection,
...floorsSections,
areasSection,
];
return {
type: "sections",

View File

@@ -3,6 +3,7 @@ import { customElement } from "lit/decorators";
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
@@ -11,11 +12,11 @@ import {
getAreas,
getFloors,
} from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import {
findEntities,
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
export interface OverviewLightsViewStrategyConfig {
type: "overview-lights";
@@ -24,10 +25,9 @@ export interface OverviewLightsViewStrategyConfig {
const processAreasForLights = (
areaIds: string[],
hass: HomeAssistant,
entities: string[],
computeTileCard: (entityId: string) => any
): any[] => {
const cards: any[] = [];
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];
@@ -38,6 +38,8 @@ const processAreasForLights = (
});
const areaLights = entities.filter(areaFilter);
const computeTileCard = computeAreaTileCardConfig(hass, "", false);
if (areaLights.length > 0) {
cards.push({
heading_style: "subtitle",
@@ -81,8 +83,6 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
// Process floors
for (const floorStructure of home.floors) {
const floorId = floorStructure.id;
@@ -100,12 +100,7 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForLights(
areaIds,
hass,
entities,
computeTileCard
);
const areaCards = processAreasForLights(areaIds, hass, entities);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);
@@ -126,12 +121,7 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForLights(
home.areas,
hass,
entities,
computeTileCard
);
const areaCards = processAreasForLights(home.areas, hass, entities);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);

View File

@@ -3,16 +3,17 @@ import { customElement } from "lit/decorators";
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import type { MediaControlCardConfig } from "../../cards/types";
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import {
findEntities,
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
export interface OvervieMediaPlayersViewStrategyConfig {
type: "overview-media-players";
@@ -22,8 +23,8 @@ const processAreasForMediaPlayers = (
areaIds: string[],
hass: HomeAssistant,
entities: string[]
): any[] => {
const cards: any[] = [];
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];

View File

@@ -16,6 +16,7 @@ import {
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
export interface OverviewSecurityViewStrategyConfig {
type: "overview-security";
@@ -24,10 +25,9 @@ export interface OverviewSecurityViewStrategyConfig {
const processAreasForSecurity = (
areaIds: string[],
hass: HomeAssistant,
entities: string[],
computeTileCard: (entityId: string) => any
): any[] => {
const cards: any[] = [];
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];
@@ -38,6 +38,8 @@ const processAreasForSecurity = (
});
const areaEntities = entities.filter(areaFilter);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
if (areaEntities.length > 0) {
cards.push({
heading_style: "subtitle",
@@ -81,8 +83,6 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
// Process floors
for (const floorStructure of home.floors) {
const floorId = floorStructure.id;
@@ -100,12 +100,7 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForSecurity(
areaIds,
hass,
entities,
computeTileCard
);
const areaCards = processAreasForSecurity(areaIds, hass, entities);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);
@@ -126,12 +121,7 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForSecurity(
home.areas,
hass,
entities,
computeTileCard
);
const areaCards = processAreasForSecurity(home.areas, hass, entities);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);

View File

@@ -379,6 +379,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const index = ev.currentTarget.index;
showEditSectionDialog(this, {
lovelace: this.lovelace!,
lovelaceConfig: this.lovelace!.config,
saveConfig: (newConfig) => {
this.lovelace!.saveConfig(newConfig);

View File

@@ -413,6 +413,7 @@
"add": "Add",
"create": "Create",
"edit": "Edit",
"edit_item": "Edit {name}",
"submit": "Submit",
"rename": "Rename",
"search": "[%key:ui::components::data-table::search%]",
@@ -439,7 +440,8 @@
"replace": "Replace",
"append": "Append",
"supports_markdown": "Supports {markdown_help_link}",
"markdown": "Markdown"
"markdown": "Markdown",
"none": "None"
},
"components": {
"selectors": {
@@ -1322,7 +1324,8 @@
"system": "[%key:ui::panel::config::dashboard::system::main%]",
"addon_dashboard": "Add-on dashboard",
"addon_store": "Add-on store",
"addon_info": "{addon} info"
"addon_info": "{addon} info",
"shortcuts": "[%key:ui::panel::config::info::shortcuts%]"
}
},
"filter_placeholder": "Search entities",
@@ -5337,8 +5340,6 @@
"introduction": "Zones allow you to specify certain regions on Earth. When a person is within a zone, the state will take the name from the zone. Zones can also be used as a trigger or condition inside automation setups.",
"no_zones_created_yet": "Looks like you have not created any zones yet.",
"create_zone": "Create zone",
"edit_zone": "Edit zone",
"edit_home": "Edit home",
"confirm_delete": "Are you sure you want to delete this zone?",
"can_not_edit": "Unable to edit zone",
"configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.",
@@ -6968,7 +6969,16 @@
"assist_tooltip": "Assist",
"reload_resources": "Reload resources",
"exit_edit_mode": "Done",
"close": "Close"
"close": "Close",
"add": "Add to Home Assistant",
"add_device": "Add device",
"create_automation": "Create automation",
"add_area": "Add area",
"add_area_success": "Area added",
"add_area_action": "View area",
"add_person_success": "Person added",
"add_person_action": "View persons",
"invite_person": "Invite person"
},
"reload_resources": {
"refresh_header": "Do you want to refresh?",
@@ -7095,7 +7105,8 @@
"error_same_url": "You cannot save a view with the same URL as a different existing view.",
"error_invalid_path": "URL contains invalid/reserved characters. Please enter a simple string only for the path of this view.",
"error_number": "URL may not be a number.",
"move_to_dashboard": "Move to dashboard"
"move_to_dashboard": "Move to dashboard",
"move_to_view": "Move to view"
},
"edit_view_header": {
"add_title": "Add title",
@@ -7201,6 +7212,13 @@
"header": "[%key:ui::panel::lovelace::editor::suggest_card::header%]",
"add": "[%key:ui::panel::lovelace::editor::suggest_card::add%]"
},
"move_section": {
"header": "Choose a view to move the section to",
"strategy_error_title": "Impossible to move the section",
"strategy_error_text_strategy": "Moving a section to an auto-generated view is not supported.",
"success": "Section moved successfully",
"error": "Error while moving section"
},
"move_card": {
"header": "Choose a view to move the card to",
"strategy_error_title": "Impossible to move the card",
@@ -7249,7 +7267,13 @@
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
"settings": {
"column_span": "Width",
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)"
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)",
"styling": "Styling",
"background_type": "Background type",
"background_type_none_option": "None",
"background_type_color_option": "Color",
"background_color": "Background color",
"background_opacity": "Background opacity"
},
"visibility": {
"explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown."
@@ -7363,6 +7387,11 @@
"sensor_classes": "Sensor classes",
"description": "The Area card automatically displays entities of a specific area.",
"display_type": "Display type",
"content_layout": "[%key:ui::panel::lovelace::editor::card::tile::content_layout%]",
"content_layout_options": {
"horizontal": "[%key:ui::panel::lovelace::editor::card::tile::content_layout_options::horizontal%]",
"vertical": "[%key:ui::panel::lovelace::editor::card::tile::content_layout_options::vertical%]"
},
"display_type_options": {
"compact": "Compact",
"icon": "Area icon",
@@ -7905,6 +7934,9 @@
"lock-open-door": {
"label": "Lock open door"
},
"media-player-playback": {
"label": "Media player playback controls"
},
"media-player-volume-slider": {
"label": "Media player volume slider"
},

263
yarn.lock
View File

@@ -3869,22 +3869,22 @@ __metadata:
languageName: node
linkType: hard
"@rsdoctor/client@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/client@npm:1.2.2"
checksum: 10/ae2e8dbbf17522c63ab1042391514706116cce73e9435293c1cced593bcacc5034b85cb7b0bcc09cf36d6350ff21680cc4e14d08728a95d98119db07c163f2d4
"@rsdoctor/client@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/client@npm:1.2.3"
checksum: 10/4e02c72ded9f7cfa5dc9e224513d4f0cb4d36908fbd62353170148516ad742cd4c9573e2ee90b335d243f8c56f33b2bbd0cc51a3b65000f1a166c2b4c8dd9bd7
languageName: node
linkType: hard
"@rsdoctor/core@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/core@npm:1.2.2"
"@rsdoctor/core@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/core@npm:1.2.3"
dependencies:
"@rsbuild/plugin-check-syntax": "npm:1.3.0"
"@rsdoctor/graph": "npm:1.2.2"
"@rsdoctor/sdk": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
"@rsdoctor/graph": "npm:1.2.3"
"@rsdoctor/sdk": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
axios: "npm:^1.11.0"
browserslist-load-config: "npm:^1.0.0"
enhanced-resolve: "npm:5.12.0"
@@ -3894,70 +3894,67 @@ __metadata:
path-browserify: "npm:1.0.1"
semver: "npm:^7.7.2"
source-map: "npm:^0.7.4"
source-map-js: "npm:^1.2.1"
checksum: 10/85fe4ef72d1ca7223a6df638ae7b8ca491665d2df37b115dc229569b3a6fe9b6855f7895e4975c48f3a8056ccc66e4cc941c1b2c5c58f3bb7c1061434e24983a
checksum: 10/45c39b966fb879f8770ced60d47d16b42098fc6642064bc528c6b1a9cd0724210b0af5f17f031ae9738884c75b034ea402f34f3d2737e608ce3ebef08fb6909c
languageName: node
linkType: hard
"@rsdoctor/graph@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/graph@npm:1.2.2"
"@rsdoctor/graph@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/graph@npm:1.2.3"
dependencies:
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
lodash.unionby: "npm:^4.8.0"
socket.io: "npm:4.8.1"
source-map: "npm:^0.7.4"
checksum: 10/e17b30fb96e8fb5f9344fe08edcfc7a8a37dd4d8e111cbd1fe5ee0f5748143beb1c77a98d90b9fa1a50eb636c8a427e35560a548ec8a55cacea8ec79a58d3852
checksum: 10/fea12dfdad880f440ea2c3a06c95a92dcbcd78efac6ff03af50c56e40007239b63303ae267ca00e1360e2af559513173cdd0743c7635721cb8ac16e19cda170d
languageName: node
linkType: hard
"@rsdoctor/rspack-plugin@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/rspack-plugin@npm:1.2.2"
"@rsdoctor/rspack-plugin@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/rspack-plugin@npm:1.2.3"
dependencies:
"@rsdoctor/core": "npm:1.2.2"
"@rsdoctor/graph": "npm:1.2.2"
"@rsdoctor/sdk": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
"@rsdoctor/core": "npm:1.2.3"
"@rsdoctor/graph": "npm:1.2.3"
"@rsdoctor/sdk": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
lodash: "npm:^4.17.21"
peerDependencies:
"@rspack/core": "*"
peerDependenciesMeta:
"@rspack/core":
optional: true
checksum: 10/040cd21f9c9382edbbea6b1e9b9c621956a8a56271321a7de55fff138ab49f3eb75e1dd26414487bca7d2034c975ecc9ecb1b394b1466c97f6592721ae76fb4e
checksum: 10/59abb902046fb791a754e02b0eff5e1674417cfc3f6556d164fc6bed0af0eb0aa52bc087c0b0e8d1c42659d4cbaffd497550376799cbedbae4f4f76882b26924
languageName: node
linkType: hard
"@rsdoctor/sdk@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/sdk@npm:1.2.2"
"@rsdoctor/sdk@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/sdk@npm:1.2.3"
dependencies:
"@rsdoctor/client": "npm:1.2.2"
"@rsdoctor/graph": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
"@rsdoctor/client": "npm:1.2.3"
"@rsdoctor/graph": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
"@types/fs-extra": "npm:^11.0.4"
body-parser: "npm:1.20.3"
cors: "npm:2.8.5"
dayjs: "npm:1.11.13"
fs-extra: "npm:^11.1.1"
json-cycle: "npm:^1.5.0"
lodash: "npm:^4.17.21"
open: "npm:^8.4.2"
sirv: "npm:2.0.4"
socket.io: "npm:4.8.1"
source-map: "npm:^0.7.4"
tapable: "npm:2.2.2"
checksum: 10/15aa9298c01747dce5732698ec646b7db54df3a7753db417b14c2eaaaf55c769080334db07598039d0dda97017f7c127277ca69531de4756c24e8c8049486262
checksum: 10/3d39ff6a4cd074080b517a6daccf480d88b4f75bd0148748b64f5bd074a05e30a1de74c526d1d6e764a7b7763abc83e53e9ac717eb09cf786eb721b411609a5c
languageName: node
linkType: hard
"@rsdoctor/types@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/types@npm:1.2.2"
"@rsdoctor/types@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/types@npm:1.2.3"
dependencies:
"@types/connect": "npm:3.4.38"
"@types/estree": "npm:1.0.5"
@@ -3971,16 +3968,16 @@ __metadata:
optional: true
webpack:
optional: true
checksum: 10/67d340e25fcc060eacff632ac5c5556df646f91853d423776ae3a1ee677e2a6a14a17e5838e6e9487256646efbabb168ca96acfb27f4a4d147f4a3fdd0f31ef4
checksum: 10/e2971d683ee0b138387e383f7d171c573e97ad2b91a906dc7a2e091a3b17cf83a4d5c921f69de6929d408c1c87cc9b2b409235ad5b02d7b8a15506137b04e568
languageName: node
linkType: hard
"@rsdoctor/utils@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/utils@npm:1.2.2"
"@rsdoctor/utils@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/utils@npm:1.2.3"
dependencies:
"@babel/code-frame": "npm:7.26.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.3"
"@types/estree": "npm:1.0.5"
acorn: "npm:^8.10.0"
acorn-import-attributes: "npm:^1.9.5"
@@ -3996,7 +3993,7 @@ __metadata:
picocolors: "npm:^1.1.1"
rslog: "npm:^1.2.9"
strip-ansi: "npm:^6.0.1"
checksum: 10/7290b2239a3bba1207171b4942c3598ac19ced09667cf6ca682af2e96b3e578b6611a36861f68a186a74e2a983087c1b3436bf5dbfdd0edbc43d5042c6a08d1e
checksum: 10/4e6bcce13fd81872766e18eea97439d9bd117b2aba275e2a5a9ff7ec4d65c3ad44b8535ee3adcec70d16969865f4a53144dc692af6f66aaacedd656f63b2da57
languageName: node
linkType: hard
@@ -4994,76 +4991,67 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.39.1"
"@typescript-eslint/eslint-plugin@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.40.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.39.1"
"@typescript-eslint/type-utils": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
"@typescript-eslint/scope-manager": "npm:8.40.0"
"@typescript-eslint/type-utils": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.39.1
"@typescript-eslint/parser": ^8.40.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/446050aa43d54c0107c7c927ae1f68a4384c2bba514d5c22edabbe355426cb37bd5bb5a3faf240a6be8ef06f68de6099c2a53d9cbb1849ed35a152fb156171e2
checksum: 10/9df4d4ac58734a34964b791622dcb94ffc6c49c1d0f4fd0480b3fc0e026527df7167ff78a4f8bbd29089d605756c28c1a90b2f0653df34b40ac8b969bc6c92e9
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/parser@npm:8.39.1"
"@typescript-eslint/parser@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/parser@npm:8.40.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
"@typescript-eslint/scope-manager": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/ff45ce76353ed564e0f9db47b02b4b20895c96182b3693c610ef3dbceda373c476037a99f90d9f28633c192f301e5d554c89e1ba72da216763f960648ddf1f34
checksum: 10/1e60f70e9d02f930553db7f4684c27c376fadf345db155414a22d1a32cd21def7d36496bd63c1acbf3afbec9fb8794947e880f88c1143b83e1d3c45146cec41a
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/project-service@npm:8.39.1"
"@typescript-eslint/project-service@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/project-service@npm:8.40.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.39.1"
"@typescript-eslint/types": "npm:^8.39.1"
"@typescript-eslint/tsconfig-utils": "npm:^8.40.0"
"@typescript-eslint/types": "npm:^8.40.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/1970633d1a338190f0125e186beaa39b3ef912f287e4815934faf64b72f140e87fdf7d861962683635a450d270dd76faf0c865d72bfd57b471a36739f943676b
checksum: 10/86491aa65c1dd78c9784dddd8467601aef8be652c5fb3a901e8b1995cf07c1dbe11d0ab4610d770e3f4063c0c254a6c6aa5fb7cf724bf12fa4ee56f47f3a2955
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/scope-manager@npm:8.39.1"
"@typescript-eslint/scope-manager@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/scope-manager@npm:8.40.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
checksum: 10/8874f7479043b3fc878f2c04b2c565051deceb7e425a8e4e79a7f40f1ee696bb979bd91fff619e016fe6793f537b30609c0ee8a5c40911c4829fa264863f7a70
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
checksum: 10/0c5aa10208bfbb506bf3925a420c3de667298064bde400f03ee52c19cd0785dd05c2c820e05724d005737e2920d925aff0318ec3308156f9b81c84736a1fe46b
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.39.1"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/38c1e1982504e606e525ad0ce47fdb4c7acc686a28a94c2b30fe988c439977e991ce69cb88a1724a41a8096fc2d18d7ced7fe8725e42879d841515ff36a37ecf
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:^8.39.1":
"@typescript-eslint/tsconfig-utils@npm:8.40.0, @typescript-eslint/tsconfig-utils@npm:^8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.40.0"
peerDependencies:
@@ -5072,44 +5060,37 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/type-utils@npm:8.39.1"
"@typescript-eslint/type-utils@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/type-utils@npm:8.40.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/1195d65970f79f820558810f7e1edf0ea360bbeee55841fdbb71b5b40c09f1a65741b67a70b85c2834ae1f9a027b82da4234d01f42ab4e85dceef3eea84bfdaa
checksum: 10/296c718330b2ac4408840258c30c01072de01ffe1c009be00c5049be1b19a71cbb2e258363ae349150760bcd2d34799df305b4cfc4d7f3b2fa9760ac8ffb3f75
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/types@npm:8.39.1"
checksum: 10/8013f4f48a98da0de270d5fef1ff28b35407de82fce5acf3efa212fce60bc92a81bbb15b4b358d9facf4f161e49feec856fbf1a6d96f5027d013b542f2fe1bcc
languageName: node
linkType: hard
"@typescript-eslint/types@npm:^8.39.1":
"@typescript-eslint/types@npm:8.40.0, @typescript-eslint/types@npm:^8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/types@npm:8.40.0"
checksum: 10/f3931d0920a42b3bc69e9cdeb67a0c710597271cdd9d7c736302bdc52d21df1c962082df7cd712eeabd2c47658415d0a4b7d72f819cb38f82f4e234b48dbaa57
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/typescript-estree@npm:8.39.1"
"@typescript-eslint/typescript-estree@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/typescript-estree@npm:8.40.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.39.1"
"@typescript-eslint/tsconfig-utils": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
"@typescript-eslint/project-service": "npm:8.40.0"
"@typescript-eslint/tsconfig-utils": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -5118,32 +5099,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/07ed9d7ab4d146ee3ce6cf82ffebf947e045a9289b01522e11b3985b64f590c00cac0ca10366df828ca213bf08216a67c7b2b76e7c8be650df2511a7e6385425
checksum: 10/2e61ecfecb933f644799a7c11e4c7a730df57290c8d0482082cff7739b2401b0cf3b1ebef7b08a54a90285978957a49850d1a53061e8770164da651172ebee32
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/utils@npm:8.39.1"
"@typescript-eslint/utils@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/utils@npm:8.40.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/scope-manager": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/39bb105f26aa1ba234ad7d284c277cbd66df9d51e245094892db140aac80d3656d0480f133b2db54e87af3ef9c371a12973120c9cfbff71e8e85865f9e1d0077
checksum: 10/b4cd1e6a4f55cc6475189de12e6bd418080a227e794745a2af304ab21655b031c28dae6387d4e9b54dd2f420696cec4f77cca9c66db405ed2281e0e09c95ba1c
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/visitor-keys@npm:8.39.1"
"@typescript-eslint/visitor-keys@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/visitor-keys@npm:8.40.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.40.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10/6d4e4d0b19ebb3f21b692bbb0dcf9961876ca28cdf502296888a78eb4cd802a2ec8d3d5721d19970411edfd1c06f3e272e4057014c859ee1f0546804d07945e3
checksum: 10/191f47998001a5e9cdde7491b0215d9c6c45c637aedd7d32cafd35ce2a4a0f4b8582edab015e09238f48e025a788b99efd8e70e4e3200e32143f91c95112abcd
languageName: node
linkType: hard
@@ -7036,10 +7017,10 @@ __metadata:
languageName: node
linkType: hard
"core-js@npm:3.45.0":
version: 3.45.0
resolution: "core-js@npm:3.45.0"
checksum: 10/162cd63b4d4cb2fed7a8635e69b760f4247e5d39ea99348e0b66af8e8899395a34de1b93504eb8db2adf4c47a0756475545c3da9c24875c7c33396fa8dc061c1
"core-js@npm:3.45.1":
version: 3.45.1
resolution: "core-js@npm:3.45.1"
checksum: 10/b9dca79b1af8bb4f0d4af0752ea98d694fe157abaf55513fd4084df32dfd4398f0fc57898b32cdb643c1cecb87b9231c2a2ce535797c80ae328eac6d6078ee61
languageName: node
linkType: hard
@@ -8157,16 +8138,16 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-unused-imports@npm:4.1.4":
version: 4.1.4
resolution: "eslint-plugin-unused-imports@npm:4.1.4"
"eslint-plugin-unused-imports@npm:4.2.0":
version: 4.2.0
resolution: "eslint-plugin-unused-imports@npm:4.2.0"
peerDependencies:
"@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
eslint: ^9.0.0 || ^8.0.0
peerDependenciesMeta:
"@typescript-eslint/eslint-plugin":
optional: true
checksum: 10/8e987028ad925ce1e04c01dcae70adbf44c2878a8b15c4327b33a2861e471d7fe00f6fe213fbd2b936f3fcefc8ccabb0d778aa1d6e0e0387a3dc7fe150cd4ed4
checksum: 10/99285bd61a46ad5eaacd54525c04564feed3529dc2ae7800c736b7f4f011daa370133adb481677ad840f020681c0f409f3edfe3bc8f9f234c42bf4674bc4b576
languageName: node
linkType: hard
@@ -9459,7 +9440,7 @@ __metadata:
"@octokit/plugin-retry": "npm:8.0.1"
"@octokit/rest": "npm:22.0.0"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.2.2"
"@rsdoctor/rspack-plugin": "npm:1.2.3"
"@rspack/cli": "npm:1.4.11"
"@rspack/core": "npm:1.4.11"
"@shoelace-style/shoelace": "npm:2.20.1"
@@ -9499,7 +9480,7 @@ __metadata:
browserslist-useragent-regexp: "npm:4.1.3"
color-name: "npm:2.0.0"
comlink: "npm:4.4.2"
core-js: "npm:3.45.0"
core-js: "npm:3.45.1"
cropperjs: "npm:1.6.2"
culori: "npm:4.0.2"
date-fns: "npm:4.1.0"
@@ -9517,7 +9498,7 @@ __metadata:
eslint-plugin-import: "npm:2.32.0"
eslint-plugin-lit: "npm:2.1.1"
eslint-plugin-lit-a11y: "npm:5.1.1"
eslint-plugin-unused-imports: "npm:4.1.4"
eslint-plugin-unused-imports: "npm:4.2.0"
eslint-plugin-wc: "npm:3.0.1"
fancy-log: "npm:2.0.0"
fs-extra: "npm:11.3.1"
@@ -9571,7 +9552,7 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.2"
typescript-eslint: "npm:8.39.1"
typescript-eslint: "npm:8.40.0"
ua-parser-js: "npm:2.0.4"
vite-tsconfig-paths: "npm:5.1.4"
vitest: "npm:3.2.4"
@@ -14619,18 +14600,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.39.1":
version: 8.39.1
resolution: "typescript-eslint@npm:8.39.1"
"typescript-eslint@npm:8.40.0":
version: 8.40.0
resolution: "typescript-eslint@npm:8.40.0"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.39.1"
"@typescript-eslint/parser": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
"@typescript-eslint/eslint-plugin": "npm:8.40.0"
"@typescript-eslint/parser": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/1c29c18f2e93b8b74d019590196b158006d7c65be87a56a4c953e52a9c4c40280a42f8ff1464fea870b3a1a4d54925b5cb7d54b4dc86b23cdf65a9b7787585b5
checksum: 10/b96dc4e70bd551e5399b928e946957cce622cba89f0ff87521f9e93f223acbe406930a1ebee845b158f586959cb7d85f15ea2250b97341aa87f50a3c987d068a
languageName: node
linkType: hard