Compare commits

..

9 Commits

Author SHA1 Message Date
Aidan Timson
f6c6835de5 Fix dialog position 2025-10-15 09:27:43 +01:00
Aidan Timson
13346a21d7 Use vars 2025-10-15 09:14:17 +01:00
Aidan Timson
fe364b0687 Lint 2025-10-15 09:06:30 +01:00
Aidan Timson
eb760d27b9 Format 2025-10-14 16:36:13 +01:00
Aidan Timson
b62b7c6167 Use bottom sheet on mobile 2025-10-14 16:35:58 +01:00
Aidan Timson
61238aad3a Force specific width 2025-10-14 15:26:32 +01:00
Aidan Timson
b4b7348646 Set small width 2025-10-14 15:26:32 +01:00
Aidan Timson
f51d37f972 Restore original handling 2025-10-14 15:26:32 +01:00
Aidan Timson
bca926f36c Migrate favorite color dialog to ha-wa-dialog 2025-10-14 15:26:32 +01:00
17 changed files with 251 additions and 363 deletions

View File

@@ -25,7 +25,6 @@ import "../ha-sortable";
interface EntityNameOption {
primary: string;
secondary?: string;
field_label: string;
value: string;
}
@@ -42,23 +41,6 @@ const KNOWN_TYPES = new Set(["entity", "device", "area", "floor"]);
const UNIQUE_TYPES = new Set(["entity", "device", "area", "floor"]);
const formatOptionValue = (item: EntityNameItem) => {
if (item.type === "text" && item.text) {
return item.text;
}
return `___${item.type}___`;
};
const parseOptionValue = (value: string): EntityNameItem => {
if (value.startsWith("___") && value.endsWith("___")) {
const type = value.slice(3, -3);
if (KNOWN_TYPES.has(type)) {
return { type: type as EntityNameType };
}
}
return { type: "text", text: value };
};
@customElement("ha-entity-name-picker")
export class HaEntityNamePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -139,23 +121,13 @@ export class HaEntityNamePicker extends LitElement {
return {
primary,
secondary,
field_label: primary,
value: formatOptionValue({ type: name }),
value: name,
};
});
return items;
});
private _customNameOption = memoizeOne((text: string) => ({
primary: this.hass.localize(
"ui.components.entity.entity-name-picker.custom_name"
),
secondary: `"${text}"`,
field_label: text,
value: formatOptionValue({ type: "text", text }),
}));
private _formatItem = (item: EntityNameItem) => {
if (item.type === "text") {
return `"${item.text}"`;
@@ -242,7 +214,7 @@ export class HaEntityNamePicker extends LitElement {
allow-custom-value
item-id-path="value"
item-value-path="value"
item-label-path="field_label"
item-label-path="primary"
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._comboBoxValueChanged}
@@ -314,13 +286,14 @@ export class HaEntityNamePicker extends LitElement {
const initialItem =
this._editIndex != null ? this._value[this._editIndex] : undefined;
const initialValue = initialItem ? formatOptionValue(initialItem) : "";
const initialValue = initialItem
? initialItem.type === "text"
? initialItem.text
: initialItem.type
: "";
const filteredItems = this._filterSelectedOptions(options, initialValue);
if (initialItem && initialItem.type === "text" && initialItem.text) {
filteredItems.push(this._customNameOption(initialItem.text));
}
this._comboBox.filteredItems = filteredItems;
this._comboBox.setInputValue(initialValue);
} else {
@@ -353,7 +326,11 @@ export class HaEntityNamePicker extends LitElement {
const currentItem =
this._editIndex != null ? this._value[this._editIndex] : undefined;
const currentValue = currentItem ? formatOptionValue(currentItem) : "";
const currentValue = currentItem
? currentItem.type === "text"
? currentItem.text
: currentItem.type
: "";
this._comboBox.filteredItems = this._filterSelectedOptions(
options,
@@ -375,7 +352,6 @@ export class HaEntityNamePicker extends LitElement {
const fuse = new Fuse(this._comboBox.filteredItems, fuseOptions);
const filteredItems = fuse.search(filter).map((result) => result.item);
filteredItems.push(this._customNameOption(input));
this._comboBox.filteredItems = filteredItems;
}
@@ -409,7 +385,9 @@ export class HaEntityNamePicker extends LitElement {
return;
}
const item: EntityNameItem = parseOptionValue(value);
const item: EntityNameItem = KNOWN_TYPES.has(value as any)
? { type: value as EntityNameType }
: { type: "text", text: value };
const newValue = [...this._value];

View File

@@ -107,15 +107,14 @@ export class HaMediaSelector extends LitElement {
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA));
if (this.selector.media?.image_upload && !this.value) {
return html`${this.label ? html`<label>${this.label}</label>` : nothing}
<ha-picture-upload
.hass=${this.hass}
.value=${null}
.contentIdHelper=${this.selector.media?.content_id_helper}
select-media
full-media
@media-picked=${this._pictureUploadMediaPicked}
></ha-picture-upload>`;
return html`<ha-picture-upload
.hass=${this.hass}
.value=${null}
.contentIdHelper=${this.selector.media?.content_id_helper}
select-media
full-media
@media-picked=${this._pictureUploadMediaPicked}
></ha-picture-upload>`;
}
return html`
@@ -142,7 +141,6 @@ export class HaMediaSelector extends LitElement {
`}
${!supportsBrowse
? html`
${this.label ? html`<label>${this.label}</label>` : nothing}
<ha-alert>
${this.hass.localize(
"ui.components.selectors.media.browse_not_supported"
@@ -156,8 +154,7 @@ export class HaMediaSelector extends LitElement {
.computeHelper=${this._computeHelperCallback}
></ha-form>
`
: html`${this.label ? html`<label>${this.label}</label>` : nothing}
<ha-card
: html`<ha-card
outlined
tabindex="0"
role="button"

View File

@@ -79,7 +79,6 @@ export interface DataEntryFlowStepAbort {
reason: string;
description_placeholders?: Record<string, string>;
translation_domain?: string;
next_flow?: [FlowType, string]; // [flow_type, flow_id]
}
export interface DataEntryFlowStepProgress {

View File

@@ -472,10 +472,7 @@ class DataEntryFlowDialog extends LitElement {
this._step = undefined;
await this.updateComplete;
this._step = _step;
if (
(_step.type === "create_entry" || _step.type === "abort") &&
_step.next_flow
) {
if (_step.type === "create_entry" && _step.next_flow) {
// skip device rename if there is a chained flow
this._step = undefined;
this._handler = undefined;
@@ -489,36 +486,32 @@ class DataEntryFlowDialog extends LitElement {
carryOverDevices: this._devices(
this._params!.flowConfig.showDevices,
Object.values(this.hass.devices),
_step.type === "create_entry" ? _step.result?.entry_id : undefined,
_step.result?.entry_id,
this._params!.carryOverDevices
).map((device) => device.id),
dialogClosedCallback: this._params!.dialogClosedCallback,
});
} else if (_step.next_flow[0] === "options_flow") {
if (_step.type === "create_entry") {
showOptionsFlowDialog(
this._params!.dialogParentElement!,
_step.result!,
{
continueFlowId: _step.next_flow[1],
navigateToResult: this._params!.navigateToResult,
dialogClosedCallback: this._params!.dialogClosedCallback,
}
);
}
showOptionsFlowDialog(
this._params!.dialogParentElement!,
_step.result!,
{
continueFlowId: _step.next_flow[1],
navigateToResult: this._params!.navigateToResult,
dialogClosedCallback: this._params!.dialogClosedCallback,
}
);
} else if (_step.next_flow[0] === "config_subentries_flow") {
if (_step.type === "create_entry") {
showSubConfigFlowDialog(
this._params!.dialogParentElement!,
_step.result!,
_step.next_flow[0],
{
continueFlowId: _step.next_flow[1],
navigateToResult: this._params!.navigateToResult,
dialogClosedCallback: this._params!.dialogClosedCallback,
}
);
}
showSubConfigFlowDialog(
this._params!.dialogParentElement!,
_step.result!,
_step.next_flow[0],
{
continueFlowId: _step.next_flow[1],
navigateToResult: this._params!.navigateToResult,
dialogClosedCallback: this._params!.dialogClosedCallback,
}
);
} else {
this.closeDialog();
showAlertDialog(this._params!.dialogParentElement!, {

View File

@@ -1,17 +1,16 @@
import { mdiClose } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { mdiClose } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-bottom-sheet";
import "../../../../components/ha-button";
import {
getMobileOpenFromBottomAnimation,
getMobileCloseToBottomAnimation,
} from "../../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-icon-button-toggle";
import "../../../../components/ha-wa-dialog";
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
import type { LightColor, LightEntity } from "../../../../data/light";
import {
@@ -41,7 +40,11 @@ class DialogLightColorFavorite extends LitElement {
@state() private _modes: LightPickerMode[] = [];
@query("ha-md-dialog") private _dialog?: HaMdDialog;
@state() private _open = false;
@state() private _narrow = false;
private _mediaQuery?: MediaQueryList;
public async showDialog(
dialogParams: LightColorFavoriteDialogParams
@@ -50,12 +53,22 @@ class DialogLightColorFavorite extends LitElement {
this._dialogParams = dialogParams;
this._color = dialogParams.initialColor ?? this._computeCurrentColor();
this._updateModes();
this._mediaQuery = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
);
this._narrow = this._mediaQuery.matches;
this._mediaQuery.addEventListener("change", this._handleMediaChange);
this._open = true;
}
public closeDialog(): void {
this._dialog?.close();
this._open = false;
}
private _handleMediaChange = (ev: MediaQueryListEvent) => {
this._narrow = ev.matches;
};
private _updateModes() {
const supportsTemp = lightSupportsColorMode(
this.stateObj!,
@@ -120,16 +133,16 @@ class DialogLightColorFavorite extends LitElement {
);
}
private async _cancel() {
private _cancel() {
this._dialogParams?.cancel?.();
}
private _cancelDialog() {
this._cancel();
this.closeDialog();
}
private _dialogClosed(): void {
if (this._mediaQuery) {
this._mediaQuery.removeEventListener("change", this._handleMediaChange);
this._mediaQuery = undefined;
}
this._dialogParams = undefined;
this._entry = undefined;
this._color = undefined;
@@ -138,7 +151,6 @@ class DialogLightColorFavorite extends LitElement {
private async _save() {
if (!this._color) {
this._cancel();
return;
}
this._dialogParams?.submit?.(this._color);
@@ -153,89 +165,126 @@ class DialogLightColorFavorite extends LitElement {
this._mode = newMode;
}
private _renderModes() {
if (this._modes.length <= 1) {
return nothing;
}
return html`
<div class="modes">
${this._modes.map(
(value) => html`
<ha-icon-button-toggle
border-only
.selected=${value === this._mode}
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
.mode=${value}
@click=${this._modeChanged}
>
<span class="wheel ${classMap({ [value]: true })}"></span>
</ha-icon-button-toggle>
`
)}
</div>
`;
}
private _renderColorPicker() {
return html`
${this._mode === "color_temp"
? html`
<light-color-temp-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-changed=${this._colorChanged}
>
</light-color-temp-picker>
`
: nothing}
${this._mode === "color"
? html`
<light-color-rgb-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-changed=${this._colorChanged}
>
</light-color-rgb-picker>
`
: nothing}
`;
}
private _renderButtons() {
return html`
<ha-button
slot="secondaryAction"
appearance="plain"
@click=${this._cancel}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
slot="primaryAction"
appearance="accent"
@click=${this._save}
.disabled=${!this._color}
>
${this.hass.localize("ui.common.save")}
</ha-button>
`;
}
protected render() {
if (!this._entry || !this.stateObj) {
return nothing;
}
if (this._narrow) {
return html`
<ha-bottom-sheet .open=${this._open} @closed=${this._dialogClosed}>
<div class="bottom-sheet-container">
<ha-dialog-header>
<ha-icon-button
slot="navigationIcon"
@click=${this.closeDialog}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<span slot="title">${this._dialogParams?.title}</span>
</ha-dialog-header>
<div class="header">${this._renderModes()}</div>
<div class="content">${this._renderColorPicker()}</div>
<div class="buttons">
<ha-button appearance="plain" @click=${this._cancel}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
appearance="accent"
@click=${this._save}
.disabled=${!this._color}
>
${this.hass.localize("ui.common.save")}
</ha-button>
</div>
</div>
</ha-bottom-sheet>
`;
}
return html`
<ha-md-dialog
open
@cancel=${this._cancel}
<ha-wa-dialog
.hass=${this.hass}
.open=${this._open}
.headerTitle=${this._dialogParams?.title ?? ""}
@closed=${this._dialogClosed}
aria-labelledby="dialog-light-color-favorite-title"
.getOpenAnimation=${getMobileOpenFromBottomAnimation}
.getCloseAnimation=${getMobileCloseToBottomAnimation}
>
<ha-dialog-header slot="headline">
<ha-icon-button
slot="navigationIcon"
@click=${this.closeDialog}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<span slot="title" id="dialog-light-color-favorite-title"
>${this._dialogParams?.title}</span
>
</ha-dialog-header>
<div slot="content">
<div class="header">
${this._modes.length > 1
? html`
<div class="modes">
${this._modes.map(
(value) => html`
<ha-icon-button-toggle
border-only
.selected=${value === this._mode}
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
.mode=${value}
@click=${this._modeChanged}
>
<span
class="wheel ${classMap({ [value]: true })}"
></span>
</ha-icon-button-toggle>
`
)}
</div>
`
: nothing}
</div>
<div class="content">
${this._mode === "color_temp"
? html`
<light-color-temp-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-changed=${this._colorChanged}
>
</light-color-temp-picker>
`
: nothing}
${this._mode === "color"
? html`
<light-color-rgb-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-changed=${this._colorChanged}
>
</light-color-rgb-picker>
`
: nothing}
</div>
</div>
<div slot="actions">
<ha-button appearance="plain" @click=${this._cancelDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._save} .disabled=${!this._color}
>${this.hass.localize("ui.common.save")}</ha-button
>
</div>
</ha-md-dialog>
<div class="header">${this._renderModes()}</div>
<div class="content">${this._renderColorPicker()}</div>
<ha-dialog-footer slot="footer"
>${this._renderButtons()}</ha-dialog-footer
>
</ha-wa-dialog>
`;
}
@@ -243,24 +292,39 @@ class DialogLightColorFavorite extends LitElement {
return [
haStyleDialog,
css`
ha-md-dialog {
min-width: 420px; /* prevent width jumps when switching modes */
max-height: min(
600px,
100% - 48px
); /* prevent scrolling on desktop */
ha-wa-dialog {
--ha-dialog-width-md: 420px;
--dialog-content-padding: 0;
--dialog-surface-position: fixed;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-md-dialog {
min-width: 100%;
min-height: auto;
max-height: calc(100% - 100px);
margin-bottom: 0;
ha-bottom-sheet {
--ha-bottom-sheet-max-width: 560px;
--ha-bottom-sheet-padding: 0;
--ha-bottom-sheet-surface-background: var(--card-background-color);
}
--md-dialog-container-shape-start-start: 28px;
--md-dialog-container-shape-start-end: 28px;
}
.bottom-sheet-container {
display: flex;
flex-direction: column;
height: 100%;
}
.bottom-sheet-container .header {
padding: 0 24px;
}
.bottom-sheet-container .content {
flex: 1;
overflow-y: auto;
}
.buttons {
display: flex;
justify-content: flex-end;
gap: var(--ha-space-2);
padding: var(--ha-space-4) var(--ha-space-6);
padding-bottom: max(var(--ha-space-4), env(safe-area-inset-bottom));
}
.content {
@@ -268,14 +332,14 @@ class DialogLightColorFavorite extends LitElement {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
padding: var(--ha-space-6);
flex: 1;
}
.modes {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 0 24px;
padding: 0 var(--ha-space-6);
}
.wheel {
width: 30px;

View File

@@ -1,4 +1,3 @@
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -14,7 +13,6 @@ import {
import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import type { LocalizeKeys } from "../../../../common/translations/localize";
@@ -22,16 +20,7 @@ import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
import type { ActionSidebarConfig } from "../../../../data/automation";
import {
floorsContext,
fullEntitiesContext,
labelsContext,
} from "../../../../data/context";
import type { EntityRegistryEntry } from "../../../../data/entity_registry";
import type { FloorRegistryEntry } from "../../../../data/floor_registry";
import type { LabelRegistryEntry } from "../../../../data/label_registry";
import type { RepeatAction } from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
@@ -59,18 +48,6 @@ export default class HaAutomationSidebarAction extends LitElement {
@state() private _warnings?: string[];
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@state()
@consume({ context: labelsContext, subscribe: true })
_labelReg!: LabelRegistryEntry[];
@state()
@consume({ context: floorsContext, subscribe: true })
_floorReg!: Record<string, FloorRegistryEntry>;
@query(".sidebar-editor")
public editor?: HaAutomationConditionEditor;
@@ -101,20 +78,15 @@ export default class HaAutomationSidebarAction extends LitElement {
const isBuildingBlock = ACTION_BUILDING_BLOCKS.includes(type || "");
const title = capitalizeFirstLetter(
describeAction(
this.hass,
this._entityReg,
this._labelReg,
this._floorReg,
actionConfig
)
);
const subtitle = this.hass.localize(
"ui.panel.config.automation.editor.actions.action"
);
const title =
this.hass.localize(
`ui.panel.config.automation.editor.actions.type.${type}.label` as LocalizeKeys
) || type;
const description = isBuildingBlock
? this.hass.localize(
`ui.panel.config.automation.editor.actions.type.${type}.description.picker` as LocalizeKeys

View File

@@ -126,16 +126,7 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
return nothing;
}
let image: string | undefined =
(typeof this._config?.image === "object" &&
this._config.image.media_content_id) ||
(this._config.image as string | undefined);
const darkModeImage: string | undefined =
(typeof this._config?.dark_mode_image === "object" &&
this._config.dark_mode_image.media_content_id) ||
(this._config.dark_mode_image as string | undefined);
let image: string | undefined = this._config.image;
if (this._config.image_entity) {
const stateObj: ImageEntity | PersonEntity | undefined =
this.hass.states[this._config.image_entity];
@@ -165,7 +156,7 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
.entity=${this._config.entity}
.aspectRatio=${this._config.aspect_ratio}
.darkModeFilter=${this._config.dark_mode_filter}
.darkModeImage=${darkModeImage}
.darkModeImage=${this._config.dark_mode_image}
></hui-image>
${this._elements}
</div>

View File

@@ -179,10 +179,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
return nothing;
}
let image: string | undefined =
(typeof this._config?.image === "object" &&
this._config.image.media_content_id) ||
(this._config.image as string | undefined);
let image: string | undefined = this._config.image;
if (this._config.image_entity) {
const stateObj: ImageEntity | PersonEntity | undefined =
this.hass.states[this._config.image_entity];

View File

@@ -459,7 +459,7 @@ export interface PictureCardConfig extends LovelaceCardConfig {
export interface PictureElementsCardConfig extends LovelaceCardConfig {
title?: string;
image?: string | MediaSelectorValue;
image?: string;
image_entity?: string;
camera_image?: string;
camera_view?: HuiImage["cameraView"];
@@ -469,7 +469,7 @@ export interface PictureElementsCardConfig extends LovelaceCardConfig {
entity?: string;
elements: LovelaceElementConfig[];
theme?: string;
dark_mode_image?: string | MediaSelectorValue;
dark_mode_image?: string;
dark_mode_filter?: string;
}
@@ -494,7 +494,7 @@ export interface PictureEntityCardConfig extends LovelaceCardConfig {
export interface PictureGlanceCardConfig extends LovelaceCardConfig {
entities: (string | PictureGlanceEntityConfig)[];
title?: string;
image?: string | MediaSelectorValue;
image?: string;
image_entity?: string;
camera_image?: string;
camera_view?: HuiImage["cameraView"];

View File

@@ -2,15 +2,7 @@ import memoizeOne from "memoize-one";
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
any,
assert,
literal,
object,
optional,
string,
union,
} from "superstruct";
import { any, assert, literal, object, optional, string } from "superstruct";
import type { LocalizeFunc } from "../../../../../common/translations/localize";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-form/ha-form";
@@ -23,7 +15,7 @@ import { actionConfigStruct } from "../../structs/action-struct";
const imageElementConfigStruct = object({
type: literal("image"),
entity: optional(string()),
image: optional(union([string(), object()])),
image: optional(string()),
style: optional(any()),
title: optional(string()),
tap_action: optional(actionConfigStruct),
@@ -95,20 +87,7 @@ export class HuiImageElementEditor
},
],
},
{
name: "image",
selector: {
media: {
accept: ["image/*"] as string[],
clearable: true,
image_upload: true,
hide_content_type: true,
content_id_helper: localize(
"ui.panel.lovelace.editor.card.picture.content_id_helper"
),
},
},
},
{ name: "image", selector: { image: {} } },
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
{
name: "camera_view",
@@ -140,7 +119,7 @@ export class HuiImageElementEditor
return html`
<ha-form
.hass=${this.hass}
.data=${this._processData(this._config)}
.data=${this._config}
.schema=${this._schema(this.hass.localize)}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
@@ -148,13 +127,6 @@ export class HuiImageElementEditor
`;
}
private _processData = memoizeOne((config: ImageElementConfig) => ({
...config,
...(typeof config.image === "string"
? { image: { media_content_id: config.image } }
: {}),
}));
private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value });
}

View File

@@ -11,7 +11,6 @@ import {
optional,
string,
type,
union,
} from "superstruct";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
@@ -38,14 +37,14 @@ const genericElementConfigStruct = type({
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
image: optional(union([string(), object()])),
image: optional(string()),
camera_image: optional(string()),
camera_view: optional(string()),
elements: array(genericElementConfigStruct),
title: optional(string()),
state_filter: optional(any()),
theme: optional(string()),
dark_mode_image: optional(union([string(), object()])),
dark_mode_image: optional(string()),
dark_mode_filter: optional(any()),
})
);
@@ -77,34 +76,8 @@ export class HuiPictureElementsCardEditor
),
schema: [
{ name: "title", selector: { text: {} } },
{
name: "image",
selector: {
media: {
accept: ["image/*"] as string[],
clearable: true,
image_upload: true,
hide_content_type: true,
content_id_helper: localize(
"ui.panel.lovelace.editor.card.picture.content_id_helper"
),
},
},
},
{
name: "dark_mode_image",
selector: {
media: {
accept: ["image/*"] as string[],
clearable: true,
image_upload: true,
hide_content_type: true,
content_id_helper: localize(
"ui.panel.lovelace.editor.card.picture.content_id_helper"
),
},
},
},
{ name: "image", selector: { image: {} } },
{ name: "dark_mode_image", selector: { image: {} } },
{
name: "camera_image",
selector: { entity: { domain: "camera" } },
@@ -151,7 +124,7 @@ export class HuiPictureElementsCardEditor
return html`
<ha-form
.hass=${this.hass}
.data=${this._processData(this._config)}
.data=${this._config}
.schema=${this._schema(this.hass.localize)}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._formChanged}
@@ -165,16 +138,6 @@ export class HuiPictureElementsCardEditor
`;
}
private _processData = memoizeOne((config: PictureElementsCardConfig) => ({
...config,
...(typeof config.image === "string"
? { image: { media_content_id: config.image } }
: {}),
...(typeof config.dark_mode_image === "string"
? { dark_mode_image: { media_content_id: config.dark_mode_image } }
: {}),
}));
private _formChanged(ev: CustomEvent): void {
ev.stopPropagation();
if (!this._config || !this.hass) {

View File

@@ -11,7 +11,6 @@ import {
object,
optional,
string,
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../common/translations/localize";
@@ -41,7 +40,7 @@ const cardConfigStruct = assign(
object({
title: optional(string()),
entity: optional(string()),
image: optional(union([string(), object()])),
image: optional(string()),
image_entity: optional(string()),
camera_image: optional(string()),
camera_view: optional(enums(["auto", "live"])),
@@ -72,20 +71,7 @@ export class HuiPictureGlanceCardEditor
(localize: LocalizeFunc) =>
[
{ name: "title", selector: { text: {} } },
{
name: "image",
selector: {
media: {
accept: ["image/*"] as string[],
clearable: true,
image_upload: true,
hide_content_type: true,
content_id_helper: localize(
"ui.panel.lovelace.editor.card.picture.content_id_helper"
),
},
},
},
{ name: "image", selector: { image: {} } },
{
name: "image_entity",
selector: { entity: { domain: ["image", "person"] } },
@@ -246,10 +232,12 @@ export class HuiPictureGlanceCardEditor
`;
}
const data = { camera_view: "auto", fit_mode: "cover", ...this._config };
return html`
<ha-form
.hass=${this.hass}
.data=${this._processData(this._config)}
.data=${data}
.schema=${this._schema(this.hass.localize)}
.computeLabel=${this._computeLabelCallback}
.computeHelper=${this._computeHelperCallback}
@@ -267,15 +255,6 @@ export class HuiPictureGlanceCardEditor
`;
}
private _processData = memoizeOne((config: PictureGlanceCardConfig) => ({
camera_view: "auto",
fit_mode: "cover",
...config,
...(typeof config.image === "string"
? { image: { media_content_id: config.image } }
: {}),
}));
private _goBack(): void {
this._subElementEditorConfig = undefined;
}

View File

@@ -153,21 +153,13 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
(element as ServiceButtonElementConfig).service ??
""
);
case "image": {
if (element.title) {
return element.title;
}
const config = element as ImageElementConfig;
if (config.image) {
if (typeof config.image === "string") {
return config.image;
}
return (
config.image.metadata?.title || config.image.media_content_id || ""
);
}
return config.camera_image || "";
}
case "image":
return (
element.title ??
(element as ImageElementConfig).image ??
(element as ImageElementConfig).camera_image ??
""
);
case "conditional":
return (
element.title ??

View File

@@ -50,12 +50,6 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
stateObj = this.hass.states[this._config.image_entity] as ImageEntity;
}
const image = stateObj
? computeImageUrl(stateObj)
: (typeof this._config?.image === "object" &&
this._config.image.media_content_id) ||
(this._config.image as string | undefined);
return html`
<div
@action=${this._handleAction}
@@ -73,7 +67,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
<hui-image
.hass=${this.hass}
.entity=${this._config.entity}
.image=${image}
.image=${stateObj ? computeImageUrl(stateObj) : this._config.image}
.stateImage=${this._config.state_image}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}

View File

@@ -3,7 +3,6 @@ import type { ActionConfig } from "../../../data/lovelace/config/action";
import type { HomeAssistant } from "../../../types";
import type { Condition } from "../common/validate-condition";
import type { HuiImage } from "../components/hui-image";
import type { MediaSelectorValue } from "../../../data/selector";
interface LovelaceElementConfigBase {
type: string;
@@ -46,7 +45,7 @@ export interface ImageElementConfig extends LovelaceElementConfigBase {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
image?: string | MediaSelectorValue;
image?: string;
image_entity?: string;
state_image?: string;
camera_image?: string;

View File

@@ -518,7 +518,6 @@ class HUIRoot extends LitElement {
${isSubview
? html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
slot="navigationIcon"
@click=${this._goBack}
></ha-icon-button-arrow-prev>

View File

@@ -667,8 +667,7 @@
"floor_missing": "No floor assigned",
"device_missing": "No related device"
},
"add": "Add",
"custom_name": "Custom name"
"add": "Add"
},
"entity-attribute-picker": {
"attribute": "Attribute",