mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-11 03:51:07 +00:00
Add media selector to picture-card-editor (#26317)
This commit is contained in:
@@ -39,6 +39,15 @@ export class HaPictureUpload extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "select-media" }) public selectMedia =
|
@property({ type: Boolean, attribute: "select-media" }) public selectMedia =
|
||||||
false;
|
false;
|
||||||
|
|
||||||
|
// This property is set when this component is used inside a media selector.
|
||||||
|
// When set, it returns selected media or uploaded files as MediaSelectorValue
|
||||||
|
// When unset, it only allows selecting images from image-upload, and returns
|
||||||
|
// selected or uploaded images as a string starting with /api/...
|
||||||
|
@property({ type: Boolean, attribute: "full-media" }) public fullMedia =
|
||||||
|
false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public contentIdHelper?: string;
|
||||||
|
|
||||||
@property({ attribute: false }) public cropOptions?: CropOptions;
|
@property({ attribute: false }) public cropOptions?: CropOptions;
|
||||||
|
|
||||||
@property({ type: Boolean }) public original = false;
|
@property({ type: Boolean }) public original = false;
|
||||||
@@ -164,12 +173,33 @@ export class HaPictureUpload extends LitElement {
|
|||||||
this._uploading = true;
|
this._uploading = true;
|
||||||
try {
|
try {
|
||||||
const media = await createImage(this.hass, file);
|
const media = await createImage(this.hass, file);
|
||||||
this.value = generateImageThumbnailUrl(
|
if (this.fullMedia) {
|
||||||
media.id,
|
const item = {
|
||||||
this.size,
|
media_content_id: `${MEDIA_PREFIX}/${media.id}`,
|
||||||
this.original
|
media_content_type: media.content_type,
|
||||||
);
|
title: media.name,
|
||||||
fireEvent(this, "change");
|
media_class: "image" as const,
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
can_search: false,
|
||||||
|
thumbnail: generateImageThumbnailUrl(media.id, 256),
|
||||||
|
} as const;
|
||||||
|
const navigateIds = [
|
||||||
|
{},
|
||||||
|
{ media_content_type: "app", media_content_id: MEDIA_PREFIX },
|
||||||
|
];
|
||||||
|
fireEvent(this, "media-picked", {
|
||||||
|
item,
|
||||||
|
navigateIds,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.value = generateImageThumbnailUrl(
|
||||||
|
media.id,
|
||||||
|
this.size,
|
||||||
|
this.original
|
||||||
|
);
|
||||||
|
fireEvent(this, "change");
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
text: err.toString(),
|
text: err.toString(),
|
||||||
@@ -183,15 +213,24 @@ export class HaPictureUpload extends LitElement {
|
|||||||
showMediaBrowserDialog(this, {
|
showMediaBrowserDialog(this, {
|
||||||
action: "pick",
|
action: "pick",
|
||||||
entityId: "browser",
|
entityId: "browser",
|
||||||
navigateIds: [
|
accept: ["image/*"],
|
||||||
{ media_content_id: undefined, media_content_type: undefined },
|
navigateIds: this.fullMedia
|
||||||
{
|
? undefined
|
||||||
media_content_id: MEDIA_PREFIX,
|
: [
|
||||||
media_content_type: "app",
|
{ media_content_id: undefined, media_content_type: undefined },
|
||||||
},
|
{
|
||||||
],
|
media_content_id: MEDIA_PREFIX,
|
||||||
minimumNavigateLevel: 2,
|
media_content_type: "app",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
minimumNavigateLevel: this.fullMedia ? undefined : 2,
|
||||||
|
hideContentType: true,
|
||||||
|
contentIdHelper: this.contentIdHelper,
|
||||||
mediaPickedCallback: async (pickedMedia: MediaPickedEvent) => {
|
mediaPickedCallback: async (pickedMedia: MediaPickedEvent) => {
|
||||||
|
if (this.fullMedia) {
|
||||||
|
fireEvent(this, "media-picked", pickedMedia);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mediaId = getIdFromUrl(pickedMedia.item.media_content_id);
|
const mediaId = getIdFromUrl(pickedMedia.item.media_content_id);
|
||||||
if (mediaId) {
|
if (mediaId) {
|
||||||
if (this.crop) {
|
if (this.crop) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import "../ha-form/ha-form";
|
|||||||
import type { SchemaUnion } from "../ha-form/types";
|
import type { SchemaUnion } from "../ha-form/types";
|
||||||
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
|
import "../ha-picture-upload";
|
||||||
|
|
||||||
const MANUAL_SCHEMA = [
|
const MANUAL_SCHEMA = [
|
||||||
{ name: "media_content_id", required: false, selector: { text: {} } },
|
{ name: "media_content_id", required: false, selector: { text: {} } },
|
||||||
@@ -105,6 +106,17 @@ export class HaMediaSelector extends LitElement {
|
|||||||
(stateObj &&
|
(stateObj &&
|
||||||
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA));
|
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA));
|
||||||
|
|
||||||
|
if (this.selector.media?.image_upload && !this.value) {
|
||||||
|
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`
|
return html`
|
||||||
${this._hasAccept ||
|
${this._hasAccept ||
|
||||||
(this._contextEntities && this._contextEntities.length <= 1)
|
(this._contextEntities && this._contextEntities.length <= 1)
|
||||||
@@ -142,8 +154,7 @@ export class HaMediaSelector extends LitElement {
|
|||||||
.computeHelper=${this._computeHelperCallback}
|
.computeHelper=${this._computeHelperCallback}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
`
|
`
|
||||||
: html`
|
: html`<ha-card
|
||||||
<ha-card
|
|
||||||
outlined
|
outlined
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -203,7 +214,20 @@ export class HaMediaSelector extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`}
|
${this.selector.media?.clearable
|
||||||
|
? html`<div>
|
||||||
|
<ha-button
|
||||||
|
appearance="plain"
|
||||||
|
size="small"
|
||||||
|
variant="danger"
|
||||||
|
@click=${this._clearValue}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.picture-upload.clear_picture"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
</div>`
|
||||||
|
: nothing}`}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +272,8 @@ export class HaMediaSelector extends LitElement {
|
|||||||
accept: this.selector.media?.accept,
|
accept: this.selector.media?.accept,
|
||||||
defaultId: this.value?.media_content_id,
|
defaultId: this.value?.media_content_id,
|
||||||
defaultType: this.value?.media_content_type,
|
defaultType: this.value?.media_content_type,
|
||||||
|
hideContentType: this.selector.media?.hide_content_type,
|
||||||
|
contentIdHelper: this.selector.media?.content_id_helper,
|
||||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: {
|
value: {
|
||||||
@@ -289,6 +315,31 @@ export class HaMediaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _pictureUploadMediaPicked(ev) {
|
||||||
|
const pickedMedia = ev.detail as MediaPickedEvent;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.value,
|
||||||
|
media_content_id: pickedMedia.item.media_content_id,
|
||||||
|
media_content_type: pickedMedia.item.media_content_type,
|
||||||
|
metadata: {
|
||||||
|
title: pickedMedia.item.title,
|
||||||
|
thumbnail: pickedMedia.item.thumbnail,
|
||||||
|
media_class: pickedMedia.item.media_class,
|
||||||
|
children_media_class: pickedMedia.item.children_media_class,
|
||||||
|
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
||||||
|
media_content_type: id.media_content_type,
|
||||||
|
media_content_id: id.media_content_id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _clearValue() {
|
||||||
|
fireEvent(this, "value-changed", { value: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
ha-entity-picker {
|
ha-entity-picker {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -167,6 +167,8 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
.accept=${this._params.accept}
|
.accept=${this._params.accept}
|
||||||
.defaultId=${this._params.defaultId}
|
.defaultId=${this._params.defaultId}
|
||||||
.defaultType=${this._params.defaultType}
|
.defaultType=${this._params.defaultType}
|
||||||
|
.hideContentType=${this._params.hideContentType}
|
||||||
|
.contentIdHelper=${this._params.contentIdHelper}
|
||||||
@close-dialog=${this.closeDialog}
|
@close-dialog=${this.closeDialog}
|
||||||
@media-picked=${this._mediaPicked}
|
@media-picked=${this._mediaPicked}
|
||||||
@media-browsed=${this._mediaBrowsed}
|
@media-browsed=${this._mediaBrowsed}
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ class BrowseMediaManual extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public item!: MediaPlayerItemId;
|
@property({ attribute: false }) public item!: MediaPlayerItemId;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public hideContentType = false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public contentIdHelper?: string;
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
private _schema = memoizeOne(
|
||||||
() =>
|
(hideContentType: boolean) =>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: "media_content_id",
|
name: "media_content_id",
|
||||||
@@ -29,13 +33,17 @@ class BrowseMediaManual extends LitElement {
|
|||||||
text: {},
|
text: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
...(hideContentType
|
||||||
name: "media_content_type",
|
? []
|
||||||
required: false,
|
: [
|
||||||
selector: {
|
{
|
||||||
text: {},
|
name: "media_content_type",
|
||||||
},
|
required: false,
|
||||||
},
|
selector: {
|
||||||
|
text: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
] as const
|
] as const
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -45,7 +53,7 @@ class BrowseMediaManual extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.schema=${this._schema()}
|
.schema=${this._schema(this.hideContentType)}
|
||||||
.data=${this.item}
|
.data=${this.item}
|
||||||
.computeLabel=${this._computeLabel}
|
.computeLabel=${this._computeLabel}
|
||||||
.computeHelper=${this._computeHelper}
|
.computeHelper=${this._computeHelper}
|
||||||
@@ -69,13 +77,35 @@ class BrowseMediaManual extends LitElement {
|
|||||||
|
|
||||||
private _computeLabel = (
|
private _computeLabel = (
|
||||||
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
): string =>
|
): string => {
|
||||||
this.hass.localize(`ui.components.selectors.media.${entry.name}`);
|
switch (entry.name) {
|
||||||
|
case "media_content_id":
|
||||||
|
case "media_content_type":
|
||||||
|
return this.hass.localize(
|
||||||
|
`ui.components.selectors.media.${entry.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return entry.name;
|
||||||
|
};
|
||||||
|
|
||||||
private _computeHelper = (
|
private _computeHelper = (
|
||||||
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
): string =>
|
): string => {
|
||||||
this.hass.localize(`ui.components.selectors.media.${entry.name}_detail`);
|
switch (entry.name) {
|
||||||
|
case "media_content_id":
|
||||||
|
return (
|
||||||
|
this.contentIdHelper ||
|
||||||
|
this.hass.localize(
|
||||||
|
`ui.components.selectors.media.${entry.name}_detail`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
case "media_content_type":
|
||||||
|
return this.hass.localize(
|
||||||
|
`ui.components.selectors.media.${entry.name}_detail`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
private _mediaPicked() {
|
private _mediaPicked() {
|
||||||
fireEvent(this, "manual-media-picked", {
|
fireEvent(this, "manual-media-picked", {
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaPlayerItemId {
|
export interface MediaPlayerItemId {
|
||||||
media_content_id: string | undefined;
|
media_content_id?: string | undefined;
|
||||||
media_content_type: string | undefined;
|
media_content_type?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MANUAL_ITEM: MediaPlayerItem = {
|
const MANUAL_ITEM: MediaPlayerItem = {
|
||||||
@@ -113,6 +113,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public defaultType?: string;
|
@property({ attribute: false }) public defaultType?: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public hideContentType = false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public contentIdHelper?: string;
|
||||||
|
|
||||||
// @todo Consider reworking to eliminate need for attribute since it is manipulated internally
|
// @todo Consider reworking to eliminate need for attribute since it is manipulated internally
|
||||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
@@ -521,6 +525,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
media_content_type: this.defaultType || "",
|
media_content_type: this.defaultType || "",
|
||||||
}}
|
}}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
.hideContentType=${this.hideContentType}
|
||||||
|
.contentIdHelper=${this.contentIdHelper}
|
||||||
@manual-media-picked=${this._manualPicked}
|
@manual-media-picked=${this._manualPicked}
|
||||||
></ha-browse-media-manual>`
|
></ha-browse-media-manual>`
|
||||||
: isTTSMediaSource(currentItem.media_content_id)
|
: isTTSMediaSource(currentItem.media_content_id)
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export interface MediaPlayerBrowseDialogParams {
|
|||||||
accept?: string[];
|
accept?: string[];
|
||||||
defaultId?: string;
|
defaultId?: string;
|
||||||
defaultType?: string;
|
defaultType?: string;
|
||||||
|
hideContentType?: boolean;
|
||||||
|
contentIdHelper?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showMediaBrowserDialog = (
|
export const showMediaBrowserDialog = (
|
||||||
|
|||||||
@@ -312,6 +312,10 @@ export interface LocationSelectorValue {
|
|||||||
export interface MediaSelector {
|
export interface MediaSelector {
|
||||||
media: {
|
media: {
|
||||||
accept?: string[];
|
accept?: string[];
|
||||||
|
image_upload?: boolean;
|
||||||
|
clearable?: boolean;
|
||||||
|
hide_content_type?: boolean;
|
||||||
|
content_id_helper?: string;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,17 +93,21 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
|||||||
changedProps.has("_config") &&
|
changedProps.has("_config") &&
|
||||||
changedProps.get("_config")?.image !== this._config?.image;
|
changedProps.get("_config")?.image !== this._config?.image;
|
||||||
|
|
||||||
|
const image =
|
||||||
|
(typeof this._config?.image === "object" &&
|
||||||
|
this._config.image.media_content_id) ||
|
||||||
|
(this._config.image as string | undefined);
|
||||||
if (
|
if (
|
||||||
(firstHass || imageChanged) &&
|
(firstHass || imageChanged) &&
|
||||||
typeof this._config?.image === "string" &&
|
typeof image === "string" &&
|
||||||
isMediaSourceContentId(this._config.image)
|
isMediaSourceContentId(image)
|
||||||
) {
|
) {
|
||||||
this._resolvedImage = undefined;
|
this._resolvedImage = undefined;
|
||||||
resolveMediaSource(this.hass, this._config?.image).then((result) => {
|
resolveMediaSource(this.hass, image).then((result) => {
|
||||||
this._resolvedImage = result.url;
|
this._resolvedImage = result.url;
|
||||||
});
|
});
|
||||||
} else if (imageChanged) {
|
} else if (imageChanged) {
|
||||||
this._resolvedImage = this._config?.image;
|
this._resolvedImage = image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import type {
|
|||||||
import type { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
import type { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||||
import type { LovelaceHeadingBadgeConfig } from "../heading-badges/types";
|
import type { LovelaceHeadingBadgeConfig } from "../heading-badges/types";
|
||||||
import type { HomeSummary } from "../strategies/home/helpers/home-summaries";
|
import type { HomeSummary } from "../strategies/home/helpers/home-summaries";
|
||||||
|
import type { MediaSelectorValue } from "../../../data/selector";
|
||||||
|
|
||||||
export type AlarmPanelCardConfigState =
|
export type AlarmPanelCardConfigState =
|
||||||
| "arm_away"
|
| "arm_away"
|
||||||
@@ -441,7 +442,7 @@ export interface StatisticCardConfig extends LovelaceCardConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PictureCardConfig extends LovelaceCardConfig {
|
export interface PictureCardConfig extends LovelaceCardConfig {
|
||||||
image?: string;
|
image?: string | MediaSelectorValue;
|
||||||
image_entity?: string;
|
image_entity?: string;
|
||||||
tap_action?: ActionConfig;
|
tap_action?: ActionConfig;
|
||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { mdiGestureTap } from "@mdi/js";
|
import { mdiGestureTap } from "@mdi/js";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { assert, assign, object, optional, string } from "superstruct";
|
import { assert, assign, object, optional, string, union } from "superstruct";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||||
import "../../../../components/ha-theme-picker";
|
import "../../../../components/ha-theme-picker";
|
||||||
@@ -11,11 +12,12 @@ import "../../components/hui-action-editor";
|
|||||||
import type { LovelaceCardEditor } from "../../types";
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
import { actionConfigStruct } from "../structs/action-struct";
|
import { actionConfigStruct } from "../structs/action-struct";
|
||||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
|
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
|
|
||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
object({
|
object({
|
||||||
image: optional(string()),
|
image: optional(union([string(), object()])),
|
||||||
image_entity: optional(string()),
|
image_entity: optional(string()),
|
||||||
tap_action: optional(actionConfigStruct),
|
tap_action: optional(actionConfigStruct),
|
||||||
hold_action: optional(actionConfigStruct),
|
hold_action: optional(actionConfigStruct),
|
||||||
@@ -25,47 +27,6 @@ const cardConfigStruct = assign(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const SCHEMA = [
|
|
||||||
{ name: "image", selector: { image: {} } },
|
|
||||||
{
|
|
||||||
name: "image_entity",
|
|
||||||
selector: { entity: { domain: ["image", "person"] } },
|
|
||||||
},
|
|
||||||
{ name: "alt_text", selector: { text: {} } },
|
|
||||||
{ name: "theme", selector: { theme: {} } },
|
|
||||||
{
|
|
||||||
name: "interactions",
|
|
||||||
type: "expandable",
|
|
||||||
flatten: true,
|
|
||||||
iconPath: mdiGestureTap,
|
|
||||||
schema: [
|
|
||||||
{
|
|
||||||
name: "tap_action",
|
|
||||||
selector: {
|
|
||||||
ui_action: {
|
|
||||||
default_action: "more-info",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
type: "optional_actions",
|
|
||||||
flatten: true,
|
|
||||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
|
||||||
(action) => ({
|
|
||||||
name: action,
|
|
||||||
selector: {
|
|
||||||
ui_action: {
|
|
||||||
default_action: "none" as const,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
@customElement("hui-picture-card-editor")
|
@customElement("hui-picture-card-editor")
|
||||||
export class HuiPictureCardEditor
|
export class HuiPictureCardEditor
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@@ -75,6 +36,63 @@ export class HuiPictureCardEditor
|
|||||||
|
|
||||||
@state() private _config?: PictureCardConfig;
|
@state() private _config?: PictureCardConfig;
|
||||||
|
|
||||||
|
private _schema = memoizeOne(
|
||||||
|
(localize: LocalizeFunc) =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
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_entity",
|
||||||
|
selector: { entity: { domain: ["image", "person"] } },
|
||||||
|
},
|
||||||
|
{ name: "alt_text", selector: { text: {} } },
|
||||||
|
{ name: "theme", selector: { theme: {} } },
|
||||||
|
{
|
||||||
|
name: "interactions",
|
||||||
|
type: "expandable",
|
||||||
|
flatten: true,
|
||||||
|
iconPath: mdiGestureTap,
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "tap_action",
|
||||||
|
selector: {
|
||||||
|
ui_action: {
|
||||||
|
default_action: "more-info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "optional_actions",
|
||||||
|
flatten: true,
|
||||||
|
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||||
|
(action) => ({
|
||||||
|
name: action,
|
||||||
|
selector: {
|
||||||
|
ui_action: {
|
||||||
|
default_action: "none" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
);
|
||||||
|
|
||||||
public setConfig(config: PictureCardConfig): void {
|
public setConfig(config: PictureCardConfig): void {
|
||||||
assert(config, cardConfigStruct);
|
assert(config, cardConfigStruct);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
@@ -88,19 +106,28 @@ export class HuiPictureCardEditor
|
|||||||
return html`
|
return html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${this._config}
|
.data=${this._processData(this._config)}
|
||||||
.schema=${SCHEMA}
|
.schema=${this._schema(this.hass.localize)}
|
||||||
.computeLabel=${this._computeLabelCallback}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _processData = memoizeOne((config: PictureCardConfig) => ({
|
||||||
|
...config,
|
||||||
|
...(typeof config.image === "string"
|
||||||
|
? { image: { media_content_id: config.image } }
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
) => {
|
||||||
switch (schema.name) {
|
switch (schema.name) {
|
||||||
case "theme":
|
case "theme":
|
||||||
return `${this.hass!.localize(
|
return `${this.hass!.localize(
|
||||||
|
|||||||
@@ -7880,7 +7880,8 @@
|
|||||||
},
|
},
|
||||||
"picture": {
|
"picture": {
|
||||||
"name": "Picture",
|
"name": "Picture",
|
||||||
"description": "The Picture card allows you to set an image to use for navigation to various paths in your interface or to perform an action."
|
"description": "The Picture card allows you to set an image to use for navigation to various paths in your interface or to perform an action.",
|
||||||
|
"content_id_helper": "Enter a media_source id or a URL for the image to be displayed."
|
||||||
},
|
},
|
||||||
"picture-elements": {
|
"picture-elements": {
|
||||||
"name": "Picture elements",
|
"name": "Picture elements",
|
||||||
|
|||||||
Reference in New Issue
Block a user