Skip to content

Commit 5fb3cab

Browse files
karwostsMindFreeze
andauthored
Add media support to hui-image and picture-entity-card (#27450)
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
1 parent d1093b1 commit 5fb3cab

File tree

4 files changed

+88
-20
lines changed

4 files changed

+88
-20
lines changed

src/panels/lovelace/cards/hui-picture-entity-card.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
148148
}
149149

150150
const domain: string = computeDomain(this._config.entity);
151-
let image: string | undefined = this._config.image;
151+
let image: string | undefined =
152+
(typeof this._config?.image === "object" &&
153+
this._config.image.media_content_id) ||
154+
(this._config.image as string | undefined);
152155
if (!image) {
153156
switch (domain) {
154157
case "image":

src/panels/lovelace/cards/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ export interface PictureElementsCardConfig extends LovelaceCardConfig {
476476
export interface PictureEntityCardConfig extends LovelaceCardConfig {
477477
entity: string;
478478
name?: string | EntityNameItem | EntityNameItem[];
479-
image?: string;
479+
image?: string | MediaSelectorValue;
480480
camera_image?: string;
481481
camera_view?: HuiImage["cameraView"];
482482
state_image?: Record<string, unknown>;

src/panels/lovelace/components/hui-image.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import { UNAVAILABLE } from "../../../data/entity";
1515
import type { ImageEntity } from "../../../data/image";
1616
import { computeImageUrl } from "../../../data/image";
1717
import type { HomeAssistant } from "../../../types";
18+
import {
19+
isMediaSourceContentId,
20+
resolveMediaSource,
21+
} from "../../../data/media_source";
1822

1923
const UPDATE_INTERVAL = 10000;
2024
const DEFAULT_FILTER = "grayscale(100%)";
@@ -67,6 +71,12 @@ export class HuiImage extends LitElement {
6771

6872
@state() private _loadedImageSrc?: string;
6973

74+
@state() private _resolvedImageSrc?: string;
75+
76+
@state() private _resolvedDarkModeImageSrc?: string;
77+
78+
@state() private _resolvedStateImages: Record<string, string> = {};
79+
7080
@state() private _lastImageHeight?: number;
7181

7282
private _intersectionObserver?: IntersectionObserver;
@@ -130,6 +140,46 @@ export class HuiImage extends LitElement {
130140
if (this._loadState === LoadState.Loading && !this.cameraImage) {
131141
this._loadState = LoadState.Loaded;
132142
}
143+
144+
const firstHass = changedProps.has("hass") && !changedProps.get("hass");
145+
if (this.hass && (changedProps.has("image") || firstHass)) {
146+
if (this.image && isMediaSourceContentId(this.image)) {
147+
resolveMediaSource(this.hass, this.image).then((result) => {
148+
this._resolvedImageSrc = result.url;
149+
});
150+
} else {
151+
this._resolvedImageSrc = this.image;
152+
}
153+
}
154+
if (this.hass && (changedProps.has("darkModeImage") || firstHass)) {
155+
if (this.darkModeImage && isMediaSourceContentId(this.darkModeImage)) {
156+
resolveMediaSource(this.hass, this.darkModeImage).then((result) => {
157+
this._resolvedDarkModeImageSrc = result.url;
158+
});
159+
} else {
160+
this._resolvedDarkModeImageSrc = this.darkModeImage;
161+
}
162+
}
163+
if (changedProps.has("stateImage") || firstHass) {
164+
this._resolvedStateImages = {};
165+
Object.entries(this.stateImage || {}).forEach((entry) => {
166+
const key = entry[0] as string;
167+
const value = entry[1] as any;
168+
const image =
169+
(typeof value === "object" && value.media_content_id) ||
170+
(value as string | undefined);
171+
if (isMediaSourceContentId(image)) {
172+
resolveMediaSource(this.hass!, image).then((result) => {
173+
this._resolvedStateImages = {
174+
...this._resolvedStateImages,
175+
[key]: result.url,
176+
};
177+
});
178+
} else {
179+
this._resolvedStateImages![key] = image;
180+
}
181+
});
182+
}
133183
}
134184

135185
protected render() {
@@ -155,20 +205,20 @@ export class HuiImage extends LitElement {
155205
imageSrc = this._cameraImageSrc;
156206
}
157207
} else if (this.stateImage) {
158-
const stateImage = this.stateImage[entityState];
208+
const stateImage = this._resolvedStateImages[entityState];
159209

160210
if (stateImage) {
161211
imageSrc = stateImage;
162212
} else {
163-
imageSrc = this.image;
213+
imageSrc = this._resolvedImageSrc;
164214
imageFallback = true;
165215
}
166216
} else if (this.darkModeImage && this.hass.themes.darkMode) {
167-
imageSrc = this.darkModeImage;
217+
imageSrc = this._resolvedDarkModeImageSrc;
168218
} else if (stateObj && computeDomain(stateObj.entity_id) === "image") {
169219
imageSrc = computeImageUrl(stateObj as ImageEntity);
170220
} else {
171-
imageSrc = this.image;
221+
imageSrc = this._resolvedImageSrc;
172222
}
173223

174224
if (imageSrc) {

src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
object,
1212
optional,
1313
string,
14+
union,
1415
} from "superstruct";
1516
import { fireEvent } from "../../../../common/dom/fire_event";
1617
import { computeDomain } from "../../../../common/entity/compute_domain";
@@ -34,7 +35,7 @@ const cardConfigStruct = assign(
3435
baseLovelaceCardConfig,
3536
object({
3637
entity: optional(string()),
37-
image: optional(string()),
38+
image: optional(union([string(), object()])),
3839
name: optional(entityNameStruct),
3940
camera_image: optional(string()),
4041
camera_view: optional(enums(["auto", "live"])),
@@ -76,7 +77,20 @@ export class HuiPictureEntityCardEditor
7677
},
7778
context: { entity: "entity" },
7879
},
79-
{ name: "image", selector: { image: {} } },
80+
{
81+
name: "image",
82+
selector: {
83+
media: {
84+
accept: ["image/*"] as string[],
85+
clearable: true,
86+
image_upload: true,
87+
hide_content_type: true,
88+
content_id_helper: localize(
89+
"ui.panel.lovelace.editor.card.picture.content_id_helper"
90+
),
91+
},
92+
},
93+
},
8094
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
8195
{
8296
name: "",
@@ -169,21 +183,11 @@ export class HuiPictureEntityCardEditor
169183
return nothing;
170184
}
171185

172-
const data = {
173-
show_state: true,
174-
show_name: true,
175-
camera_view: "auto",
176-
fit_mode: "cover",
177-
...this._config,
178-
};
179-
180-
const schema = this._schema(this.hass.localize);
181-
182186
return html`
183187
<ha-form
184188
.hass=${this.hass}
185-
.data=${data}
186-
.schema=${schema}
189+
.data=${this._processData(this._config)}
190+
.schema=${this._schema(this.hass.localize)}
187191
.computeLabel=${this._computeLabelCallback}
188192
.computeHelper=${this._computeHelperCallback}
189193
@value-changed=${this._valueChanged}
@@ -192,6 +196,17 @@ export class HuiPictureEntityCardEditor
192196
`;
193197
}
194198

199+
private _processData = memoizeOne((config: PictureEntityCardConfig) => ({
200+
show_state: true,
201+
show_name: true,
202+
camera_view: "auto",
203+
fit_mode: "cover",
204+
...config,
205+
...(typeof config.image === "string"
206+
? { image: { media_content_id: config.image } }
207+
: {}),
208+
}));
209+
195210
private _valueChanged(ev: CustomEvent): void {
196211
const config = ev.detail.value;
197212
if (

0 commit comments

Comments
 (0)