From 2ab67328d4b65b8e8293ecf8749f0bc2a0332282 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 30 Aug 2023 13:24:11 +0200 Subject: [PATCH] Update upload element (#17654) --- hassio/src/components/hassio-upload-backup.ts | 1 + src/components/ha-file-upload.ts | 372 +++++++++++------- src/components/ha-picture-upload.ts | 89 ++++- .../ha-selector/ha-selector-file.ts | 7 +- .../areas/dialog-area-registry-detail.ts | 19 +- .../dialog-zwave_js-update-firmware-node.ts | 4 +- .../config/person/dialog-person-detail.ts | 4 +- src/translations/en.json | 16 +- 8 files changed, 335 insertions(+), 177 deletions(-) diff --git a/hassio/src/components/hassio-upload-backup.ts b/hassio/src/components/hassio-upload-backup.ts index d40345522e..57046f2818 100644 --- a/hassio/src/components/hassio-upload-backup.ts +++ b/hassio/src/components/hassio-upload-backup.ts @@ -31,6 +31,7 @@ export class HassioUploadBackup extends LitElement { .icon=${mdiFolderUpload} accept="application/x-tar" label="Upload backup" + supports="Supports .TAR files" @file-picked=${this._uploadFile} auto-open-file-dialog > diff --git a/src/components/ha-file-upload.ts b/src/components/ha-file-upload.ts index db6a029bd0..cc0e97a29c 100644 --- a/src/components/ha-file-upload.ts +++ b/src/components/ha-file-upload.ts @@ -1,16 +1,19 @@ -import { styles } from "@material/mwc-textfield/mwc-textfield.css"; -import { mdiClose } from "@mdi/js"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import "@material/mwc-linear-progress/mwc-linear-progress"; +import { mdiDelete, mdiFileUpload } from "@mdi/js"; +import { LitElement, PropertyValues, TemplateResult, css, html } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../common/dom/fire_event"; import { HomeAssistant } from "../types"; -import "./ha-circular-progress"; +import "./ha-button"; import "./ha-icon-button"; +import { blankBeforePercent } from "../common/translations/blank_before_percent"; +import { ensureArray } from "../common/array/ensure-array"; +import { bytesToString } from "../util/bytes-to-string"; declare global { interface HASSDomEvents { - "file-picked": { files: FileList }; + "file-picked": { files: File[] }; } } @@ -22,12 +25,22 @@ export class HaFileUpload extends LitElement { @property() public icon?: string; - @property() public label!: string; + @property() public label?: string; - @property() public value: string | TemplateResult | null = null; + @property() public secondary?: string; + + @property() public supports?: string; + + @property() public value?: File | File[] | FileList | string; + + @property({ type: Boolean }) private multiple = false; + + @property({ type: Boolean, reflect: true }) public disabled: boolean = false; @property({ type: Boolean }) private uploading = false; + @property({ type: Number }) private progress?: number; + @property({ type: Boolean, attribute: "auto-open-file-dialog" }) private autoOpenFileDialog = false; @@ -45,72 +58,102 @@ export class HaFileUpload extends LitElement { public render(): TemplateResult { return html` ${this.uploading - ? html`` - : html` - `} `; } @@ -122,7 +165,12 @@ export class HaFileUpload extends LitElement { ev.preventDefault(); ev.stopPropagation(); if (ev.dataTransfer?.files) { - fireEvent(this, "file-picked", { files: ev.dataTransfer.files }); + fireEvent(this, "file-picked", { + files: + this.multiple || ev.dataTransfer.files.length === 1 + ? Array.from(ev.dataTransfer.files) + : [ev.dataTransfer.files[0]], + }); } this._drag = false; } @@ -140,93 +188,121 @@ export class HaFileUpload extends LitElement { } private _handleFilePicked(ev) { + if (ev.target.files.length === 0) { + return; + } + this.value = ev.target.files; fireEvent(this, "file-picked", { files: ev.target.files }); } private _clearValue(ev: Event) { ev.preventDefault(); - this.value = null; this._input!.value = ""; + this.value = undefined; fireEvent(this, "change"); } static get styles() { - return [ - styles, - css` - :host { - display: block; - } - .mdc-text-field--filled { - height: auto; - padding-top: 16px; - cursor: pointer; - } - .mdc-text-field--filled.mdc-text-field--with-trailing-icon { - padding-top: 28px; - } - .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon { - color: var(--secondary-text-color); - } - .mdc-text-field--filled.mdc-text-field--with-trailing-icon - .mdc-text-field__icon { - align-self: flex-end; - } - .mdc-text-field__icon--leading { - margin-bottom: 12px; - inset-inline-start: initial; - inset-inline-end: 0px; - direction: var(--direction); - } - .mdc-text-field--filled .mdc-floating-label--float-above { - transform: scale(0.75); - top: 8px; - } - .mdc-floating-label { - inset-inline-start: 16px !important; - inset-inline-end: initial !important; - direction: var(--direction); - } - .mdc-text-field--filled .mdc-floating-label { - inset-inline-start: 48px !important; - inset-inline-end: initial !important; - direction: var(--direction); - } - .mdc-text-field__icon--trailing { - pointer-events: auto !important; - } - .dragged:before { - position: var(--layout-fit_-_position); - top: var(--layout-fit_-_top); - right: var(--layout-fit_-_right); - bottom: var(--layout-fit_-_bottom); - left: var(--layout-fit_-_left); - background: currentColor; - content: ""; - opacity: var(--dark-divider-opacity); - pointer-events: none; - border-radius: 4px; - } - .value { - width: 100%; - } - input.file { - display: none; - } - img { - max-width: 100%; - max-height: 125px; - } - ha-icon-button { - --mdc-icon-button-size: 24px; - --mdc-icon-size: 20px; - } - ha-circular-progress { - display: block; - text-align-last: center; - } - `, - ]; + return css` + :host { + display: block; + height: 240px; + } + :host([disabled]) { + pointer-events: none; + color: var(--disabled-text-color); + } + .container { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: solid 1px + var(--mdc-text-field-idle-line-color, rgba(0, 0, 0, 0.42)); + border-radius: var(--mdc-shape-small, 4px); + height: 100%; + } + label.container { + border: dashed 1px + var(--mdc-text-field-idle-line-color, rgba(0, 0, 0, 0.42)); + cursor: pointer; + } + :host([disabled]) .container { + border-color: var(--disabled-color); + } + label.dragged { + border-color: var(--primary-color); + } + .dragged:before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--primary-color); + content: ""; + opacity: var(--dark-divider-opacity); + pointer-events: none; + border-radius: var(--mdc-shape-small, 4px); + } + label.value { + cursor: default; + } + label.value.multiple { + justify-content: unset; + overflow: auto; + } + .highlight { + color: var(--primary-color); + } + .row { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding: 0 16px; + box-sizing: border-box; + } + ha-button { + margin-bottom: 4px; + } + .supports { + color: var(--secondary-text-color); + font-size: 12px; + } + :host([disabled]) .secondary { + color: var(--disabled-text-color); + } + input.file { + display: none; + } + .value { + cursor: pointer; + } + .value ha-svg-icon { + margin-right: 8px; + } + .big-icon { + --mdc-icon-size: 48px; + margin-bottom: 8px; + } + ha-button { + --mdc-button-outline-color: var(--primary-color); + --mdc-icon-button-size: 24px; + } + mwc-linear-progress { + width: 100%; + padding: 16px; + box-sizing: border-box; + } + .header { + font-weight: 500; + } + .progress { + color: var(--secondary-text-color); + } + `; } } diff --git a/src/components/ha-picture-upload.ts b/src/components/ha-picture-upload.ts index 0a21f82ec7..7a23a3b093 100644 --- a/src/components/ha-picture-upload.ts +++ b/src/components/ha-picture-upload.ts @@ -1,5 +1,5 @@ import { mdiImagePlus } from "@mdi/js"; -import { html, LitElement, TemplateResult } from "lit"; +import { LitElement, TemplateResult, css, html } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { createImage, generateImageThumbnailUrl } from "../data/image_upload"; @@ -9,6 +9,7 @@ import { showImageCropperDialog, } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog"; import { HomeAssistant } from "../types"; +import "./ha-button"; import "./ha-circular-progress"; import "./ha-file-upload"; @@ -20,6 +21,12 @@ export class HaPictureUpload extends LitElement { @property() public label?: string; + @property() public secondary?: string; + + @property() public supports?: string; + + @property() public currentImageAltText?: string; + @property({ type: Boolean }) public crop = false; @property({ attribute: false }) public cropOptions?: CropOptions; @@ -29,19 +36,44 @@ export class HaPictureUpload extends LitElement { @state() private _uploading = false; public render(): TemplateResult { - return html` - ` : ""} - @file-picked=${this._handleFilePicked} - @change=${this._handleFileCleared} - accept="image/png, image/jpeg, image/gif" - > - `; + if (!this.value) { + return html` + + `; + } + return html`
+
+ ${this.currentImageAltText + + +
+
`; + } + + private _handleChangeClick() { + this.value = null; + fireEvent(this, "change"); } private async _handleFilePicked(ev) { @@ -100,6 +132,35 @@ export class HaPictureUpload extends LitElement { this._uploading = false; } } + + 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); + } + `; + } } declare global { diff --git a/src/components/ha-selector/ha-selector-file.ts b/src/components/ha-selector/ha-selector-file.ts index f46ca12696..2024d04b54 100644 --- a/src/components/ha-selector/ha-selector-file.ts +++ b/src/components/ha-selector/ha-selector-file.ts @@ -37,9 +37,12 @@ export class HaFileSelector extends LitElement { .label=${this.label} .required=${this.required} .disabled=${this.disabled} - .helper=${this.helper} + .supports=${this.helper} .uploading=${this._busy} - .value=${this.value ? this._filename?.name || "Unknown file" : ""} + .value=${this.value + ? this._filename?.name || + this.hass.localize("ui.components.selectors.file.unknown_file") + : undefined} @file-picked=${this._uploadFile} @change=${this._removeFile} > diff --git a/src/panels/config/areas/dialog-area-registry-detail.ts b/src/panels/config/areas/dialog-area-registry-detail.ts index 4b2d913413..9a1bd141e6 100644 --- a/src/panels/config/areas/dialog-area-registry-detail.ts +++ b/src/panels/config/areas/dialog-area-registry-detail.ts @@ -100,6 +100,14 @@ class DialogAreaDetail extends LitElement { dialogInitialFocus > + +
${this.hass.localize( "ui.panel.config.areas.editor.aliases_section" @@ -132,14 +140,6 @@ class DialogAreaDetail extends LitElement { "ui.panel.config.areas.editor.aliases_description" )}
- - ${entry @@ -229,7 +229,8 @@ class DialogAreaDetail extends LitElement { return [ haStyleDialog, css` - ha-textfield { + ha-textfield, + ha-picture-upload { display: block; margin-bottom: 16px; } diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts index 68d0ff8235..b154162f83 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts @@ -110,10 +110,10 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { .hass=${this.hass} .uploading=${this._uploading} .icon=${mdiFileUpload} - label=${this._firmwareFile?.name ?? - this.hass.localize( + .label=${this.hass.localize( "ui.panel.config.zwave_js.update_firmware.upload_firmware" )} + .value=${this._firmwareFile} @file-picked=${this._uploadFile} > ${this._nodeStatus.is_controller_node diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index 8aee5cb25f..9273ea02f8 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -126,6 +126,7 @@ class DialogPersonDetail extends LitElement { )} required > +