mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 10:16:46 +00:00
Add a picture uploader to picture-card-editor (#18695)
* Add a picture uploader to picture-card-editor * add imageSelector * lint * Add delete button to picture-upload * updates from feedback * fix lint * Update en.json * Update selector.ts * remove delete
This commit is contained in:
parent
d88670034a
commit
064c51f487
@ -2,6 +2,7 @@ import { mdiImagePlus } from "@mdi/js";
|
|||||||
import { LitElement, TemplateResult, css, html } from "lit";
|
import { LitElement, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { haStyle } from "../resources/styles";
|
||||||
import { createImage, generateImageThumbnailUrl } from "../data/image_upload";
|
import { createImage, generateImageThumbnailUrl } from "../data/image_upload";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
import {
|
import {
|
||||||
@ -62,13 +63,15 @@ export class HaPictureUpload extends LitElement {
|
|||||||
alt=${this.currentImageAltText ||
|
alt=${this.currentImageAltText ||
|
||||||
this.hass.localize("ui.components.picture-upload.current_image_alt")}
|
this.hass.localize("ui.components.picture-upload.current_image_alt")}
|
||||||
/>
|
/>
|
||||||
<ha-button
|
<div>
|
||||||
@click=${this._handleChangeClick}
|
<ha-button
|
||||||
.label=${this.hass.localize(
|
@click=${this._handleChangeClick}
|
||||||
"ui.components.picture-upload.change_picture"
|
.label=${this.hass.localize(
|
||||||
)}
|
"ui.components.picture-upload.change_picture"
|
||||||
>
|
)}
|
||||||
</ha-button>
|
>
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@ -140,32 +143,35 @@ export class HaPictureUpload extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return [
|
||||||
:host {
|
haStyle,
|
||||||
display: block;
|
css`
|
||||||
height: 240px;
|
:host {
|
||||||
}
|
display: block;
|
||||||
ha-file-upload {
|
height: 240px;
|
||||||
height: 100%;
|
}
|
||||||
}
|
ha-file-upload {
|
||||||
.center-vertical {
|
height: 100%;
|
||||||
display: flex;
|
}
|
||||||
align-items: center;
|
.center-vertical {
|
||||||
height: 100%;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
.value {
|
height: 100%;
|
||||||
width: 100%;
|
}
|
||||||
display: flex;
|
.value {
|
||||||
flex-direction: column;
|
width: 100%;
|
||||||
align-items: center;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
img {
|
align-items: center;
|
||||||
max-width: 100%;
|
}
|
||||||
max-height: 200px;
|
img {
|
||||||
margin-bottom: 4px;
|
max-width: 100%;
|
||||||
border-radius: var(--file-upload-image-border-radius);
|
max-height: 200px;
|
||||||
}
|
margin-bottom: 4px;
|
||||||
`;
|
border-radius: var(--file-upload-image-border-radius);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
143
src/components/ha-selector/ha-selector-image.ts
Normal file
143
src/components/ha-selector/ha-selector-image.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ const LOAD_ELEMENTS = {
|
|||||||
file: () => import("./ha-selector-file"),
|
file: () => import("./ha-selector-file"),
|
||||||
floor: () => import("./ha-selector-floor"),
|
floor: () => import("./ha-selector-floor"),
|
||||||
label: () => import("./ha-selector-label"),
|
label: () => import("./ha-selector-label"),
|
||||||
|
image: () => import("./ha-selector-image"),
|
||||||
language: () => import("./ha-selector-language"),
|
language: () => import("./ha-selector-language"),
|
||||||
navigation: () => import("./ha-selector-navigation"),
|
navigation: () => import("./ha-selector-navigation"),
|
||||||
number: () => import("./ha-selector-number"),
|
number: () => import("./ha-selector-number"),
|
||||||
|
@ -8,10 +8,24 @@ interface Image {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const URL_PREFIX = "/api/image/serve/";
|
||||||
|
|
||||||
export interface ImageMutableParams {
|
export interface ImageMutableParams {
|
||||||
name: string;
|
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 = (
|
export const generateImageThumbnailUrl = (
|
||||||
mediaId: string,
|
mediaId: string,
|
||||||
size?: number,
|
size?: number,
|
||||||
@ -61,5 +75,5 @@ export const updateImage = (
|
|||||||
export const deleteImage = (hass: HomeAssistant, id: string) =>
|
export const deleteImage = (hass: HomeAssistant, id: string) =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "image/delete",
|
type: "image/delete",
|
||||||
media_id: id,
|
image_id: id,
|
||||||
});
|
});
|
||||||
|
@ -40,6 +40,7 @@ export type Selector =
|
|||||||
| FileSelector
|
| FileSelector
|
||||||
| IconSelector
|
| IconSelector
|
||||||
| LabelSelector
|
| LabelSelector
|
||||||
|
| ImageSelector
|
||||||
| LanguageSelector
|
| LanguageSelector
|
||||||
| LocationSelector
|
| LocationSelector
|
||||||
| MediaSelector
|
| MediaSelector
|
||||||
@ -256,6 +257,11 @@ export interface IconSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImageSelector {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
image: {} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LabelSelector {
|
export interface LabelSelector {
|
||||||
label: {
|
label: {
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
|
@ -24,7 +24,7 @@ const cardConfigStruct = assign(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
{ name: "image", selector: { text: {} } },
|
{ name: "image", selector: { image: {} } },
|
||||||
{ name: "image_entity", selector: { entity: { domain: "image" } } },
|
{ name: "image_entity", selector: { entity: { domain: "image" } } },
|
||||||
{ name: "alt_text", selector: { text: {} } },
|
{ name: "alt_text", selector: { text: {} } },
|
||||||
{ name: "theme", selector: { theme: {} } },
|
{ name: "theme", selector: { theme: {} } },
|
||||||
|
@ -32,7 +32,7 @@ const cardConfigStruct = assign(
|
|||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
{ name: "entity", required: true, selector: { entity: {} } },
|
{ name: "entity", required: true, selector: { entity: {} } },
|
||||||
{ name: "name", selector: { text: {} } },
|
{ name: "name", selector: { text: {} } },
|
||||||
{ name: "image", selector: { text: {} } },
|
{ name: "image", selector: { image: {} } },
|
||||||
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
|
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
|
||||||
{
|
{
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -35,7 +35,7 @@ const cardConfigStruct = assign(
|
|||||||
|
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
{ name: "title", selector: { text: {} } },
|
{ name: "title", selector: { text: {} } },
|
||||||
{ name: "image", selector: { text: {} } },
|
{ name: "image", selector: { image: {} } },
|
||||||
{ name: "image_entity", selector: { entity: { domain: "image" } } },
|
{ name: "image_entity", selector: { entity: { domain: "image" } } },
|
||||||
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
|
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
|
||||||
{
|
{
|
||||||
|
@ -82,6 +82,7 @@ export const haStyle = css`
|
|||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-button.warning,
|
||||||
mwc-button.warning {
|
mwc-button.warning {
|
||||||
--mdc-theme-primary: var(--error-color);
|
--mdc-theme-primary: var(--error-color);
|
||||||
}
|
}
|
||||||
|
@ -377,6 +377,11 @@
|
|||||||
"upload_failed": "Upload failed",
|
"upload_failed": "Upload failed",
|
||||||
"unknown_file": "Unknown file"
|
"unknown_file": "Unknown file"
|
||||||
},
|
},
|
||||||
|
"image": {
|
||||||
|
"select_image": "Select image",
|
||||||
|
"upload": "Upload picture",
|
||||||
|
"url": "Local path or web URL"
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"latitude": "[%key:ui::panel::config::zone::detail::latitude%]",
|
"latitude": "[%key:ui::panel::config::zone::detail::latitude%]",
|
||||||
"longitude": "[%key:ui::panel::config::zone::detail::longitude%]",
|
"longitude": "[%key:ui::panel::config::zone::detail::longitude%]",
|
||||||
@ -412,6 +417,11 @@
|
|||||||
"manual": "Manual Entry"
|
"manual": "Manual Entry"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"image": {
|
||||||
|
"select_image": "Select image",
|
||||||
|
"upload": "Upload picture",
|
||||||
|
"url": "Local path or web URL"
|
||||||
|
},
|
||||||
"text": {
|
"text": {
|
||||||
"show_password": "Show password",
|
"show_password": "Show password",
|
||||||
"hide_password": "Hide password"
|
"hide_password": "Hide password"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user