This commit is contained in:
karwosts 2024-04-27 22:17:16 +01:00 committed by GitHub
commit d9edc442db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 214 additions and 38 deletions

View File

@ -2,6 +2,7 @@ import { mdiImagePlus } from "@mdi/js";
import { LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { haStyle } from "../resources/styles";
import { createImage, generateImageThumbnailUrl } from "../data/image_upload";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import {
@ -60,13 +61,15 @@ export class HaPictureUpload extends LitElement {
alt=${this.currentImageAltText ||
this.hass.localize("ui.components.picture-upload.current_image_alt")}
/>
<ha-button
@click=${this._handleChangeClick}
.label=${this.hass.localize(
"ui.components.picture-upload.change_picture"
)}
>
</ha-button>
<div>
<ha-button
@click=${this._handleChangeClick}
.label=${this.hass.localize(
"ui.components.picture-upload.change_picture"
)}
>
</ha-button>
</div>
</div>
</div>`;
}
@ -134,32 +137,35 @@ export class HaPictureUpload extends LitElement {
}
static get styles() {
return css`
:host {
display: block;
height: 240px;
}
ha-file-upload {
height: 100%;
}
.center-vertical {
display: flex;
align-items: center;
height: 100%;
}
.value {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
img {
max-width: 100%;
max-height: 200px;
margin-bottom: 4px;
border-radius: var(--file-upload-image-border-radius);
}
`;
return [
haStyle,
css`
:host {
display: block;
height: 240px;
}
ha-file-upload {
height: 100%;
}
.center-vertical {
display: flex;
align-items: center;
height: 100%;
}
.value {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
img {
max-width: 100%;
max-height: 200px;
margin-bottom: 4px;
border-radius: var(--file-upload-image-border-radius);
}
`,
];
}
}

View File

@ -0,0 +1,143 @@
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { ImageSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "../ha-textarea";
import "../ha-textfield";
import "../ha-picture-upload";
import "../ha-radio";
import type { HaPictureUpload } from "../ha-picture-upload";
import { URL_PREFIX } from "../../data/image_upload";
@customElement("ha-selector-image")
export class HaImageSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public value?: any;
@property() public name?: string;
@property() public label?: string;
@property() public placeholder?: string;
@property() public helper?: string;
@property({ attribute: false }) public selector!: ImageSelector;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@state() private showUpload = false;
protected firstUpdated(changedProps): void {
super.firstUpdated(changedProps);
if (!this.value || this.value.startsWith(URL_PREFIX)) {
this.showUpload = true;
}
}
protected render() {
return html`
<div>
<label>
${this.hass.localize("ui.components.selectors.image.select_image")}
<ha-formfield
.label=${this.hass.localize("ui.components.selectors.image.upload")}
>
<ha-radio
name="mode"
value="upload"
.checked=${this.showUpload}
@change=${this._radioGroupPicked}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass.localize("ui.components.selectors.image.url")}
>
<ha-radio
name="mode"
value="url"
.checked=${!this.showUpload}
@change=${this._radioGroupPicked}
></ha-radio>
</ha-formfield>
</label>
${!this.showUpload
? html`
<ha-textfield
.name=${this.name}
.value=${this.value || ""}
.placeholder=${this.placeholder || ""}
.helper=${this.helper}
helperPersistent
.disabled=${this.disabled}
@input=${this._handleChange}
.label=${this.label || ""}
.required=${this.required}
></ha-textfield>
`
: html`
<ha-picture-upload
.hass=${this.hass}
.value=${this.value?.startsWith(URL_PREFIX) ? this.value : null}
@change=${this._pictureChanged}
></ha-picture-upload>
`}
</div>
`;
}
private _radioGroupPicked(ev): void {
this.showUpload = ev.target.value === "upload";
}
private _pictureChanged(ev) {
const value = (ev.target as HaPictureUpload).value;
fireEvent(this, "value-changed", { value: value ?? undefined });
}
private _handleChange(ev) {
let value = ev.target.value;
if (this.value === value) {
return;
}
if (value === "" && !this.required) {
value = undefined;
}
fireEvent(this, "value-changed", { value });
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
position: relative;
}
div {
display: flex;
flex-direction: column;
}
label {
display: flex;
flex-direction: column;
}
ha-textarea,
ha-textfield {
width: 100%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-image": HaImageSelector;
}
}

View File

@ -31,6 +31,7 @@ const LOAD_ELEMENTS = {
statistic: () => import("./ha-selector-statistic"),
file: () => import("./ha-selector-file"),
floor: () => import("./ha-selector-floor"),
image: () => import("./ha-selector-image"),
label: () => import("./ha-selector-label"),
language: () => import("./ha-selector-language"),
navigation: () => import("./ha-selector-navigation"),

View File

@ -8,12 +8,26 @@ interface Image {
id: string;
}
export const URL_PREFIX = "/api/image/serve/";
export interface ImageMutableParams {
name: string;
}
export const getIdFromUrl = (url: string): string | undefined => {
let id;
if (url.startsWith(URL_PREFIX)) {
id = url.substring(URL_PREFIX.length);
const idx = id.indexOf("/");
if (idx >= 0) {
id = id.substring(0, idx);
}
}
return id;
};
export const generateImageThumbnailUrl = (mediaId: string, size: number) =>
`/api/image/serve/${mediaId}/${size}x${size}`;
`${URL_PREFIX}${mediaId}/${size}x${size}`;
export const fetchImages = (hass: HomeAssistant) =>
hass.callWS<Image[]>({ type: "image/list" });
@ -50,5 +64,5 @@ export const updateImage = (
export const deleteImage = (hass: HomeAssistant, id: string) =>
hass.callWS({
type: "image/delete",
media_id: id,
image_id: id,
});

View File

@ -38,6 +38,7 @@ export type Selector =
| LegacyEntitySelector
| FileSelector
| IconSelector
| ImageSelector
| LabelSelector
| LanguageSelector
| LocationSelector
@ -255,6 +256,11 @@ export interface IconSelector {
} | null;
}
export interface ImageSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
image: {} | null;
}
export interface LabelSelector {
label: {
multiple?: boolean;

View File

@ -24,7 +24,7 @@ const cardConfigStruct = assign(
);
const SCHEMA = [
{ name: "image", selector: { text: {} } },
{ name: "image", selector: { image: {} } },
{ name: "image_entity", selector: { entity: { domain: "image" } } },
{ name: "alt_text", selector: { text: {} } },
{ name: "theme", selector: { theme: {} } },

View File

@ -32,7 +32,7 @@ const cardConfigStruct = assign(
const SCHEMA = [
{ name: "entity", required: true, selector: { entity: {} } },
{ name: "name", selector: { text: {} } },
{ name: "image", selector: { text: {} } },
{ name: "image", selector: { image: {} } },
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
{
name: "",

View File

@ -35,7 +35,7 @@ const cardConfigStruct = assign(
const SCHEMA = [
{ name: "title", selector: { text: {} } },
{ name: "image", selector: { text: {} } },
{ name: "image", selector: { image: {} } },
{ name: "image_entity", selector: { entity: { domain: "image" } } },
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
{

View File

@ -82,6 +82,7 @@ export const haStyle = css`
color: var(--error-color);
}
ha-button.warning,
mwc-button.warning {
--mdc-theme-primary: var(--error-color);
}

View File

@ -377,6 +377,11 @@
"upload_failed": "Upload failed",
"unknown_file": "Unknown file"
},
"image": {
"select_image": "Select image",
"upload": "Upload picture",
"url": "Local path or web URL"
},
"location": {
"latitude": "[%key:ui::panel::config::zone::detail::latitude%]",
"longitude": "[%key:ui::panel::config::zone::detail::longitude%]",