Update upload element (#17654)

This commit is contained in:
Bram Kragten
2023-08-30 13:24:11 +02:00
committed by GitHub
parent d350c35c4e
commit 2ab67328d4
8 changed files with 335 additions and 177 deletions

View File

@@ -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`<ha-circular-progress
alt="Uploading"
size="large"
active
></ha-circular-progress>`
: html`
<label
for="input"
class="mdc-text-field mdc-text-field--filled ${classMap({
"mdc-text-field--focused": this._drag,
"mdc-text-field--with-leading-icon": Boolean(this.icon),
"mdc-text-field--with-trailing-icon": Boolean(this.value),
})}"
@drop=${this._handleDrop}
@dragenter=${this._handleDragStart}
@dragover=${this._handleDragStart}
@dragleave=${this._handleDragEnd}
@dragend=${this._handleDragEnd}
>
<span class="mdc-text-field__ripple"></span>
<span
class="mdc-floating-label ${this.value || this._drag
? "mdc-floating-label--float-above"
: ""}"
id="label"
>${this.label}</span
? html`<div class="container">
<div class="row">
<span class="header"
>${this.value
? this.hass?.localize(
"ui.components.file-upload.uploading_name",
{ name: this.value }
)
: this.hass?.localize(
"ui.components.file-upload.uploading"
)}</span
>
${this.icon
? html`<span
class="mdc-text-field__icon mdc-text-field__icon--leading"
>
<ha-icon-button
@click=${this._openFilePicker}
.path=${this.icon}
></ha-icon-button>
</span>`
${this.progress
? html`<span class="progress"
>${this.progress}${blankBeforePercent(
this.hass!.locale
)}%</span
>`
: ""}
<div class="value">${this.value}</div>
<input
id="input"
type="file"
class="mdc-text-field__input file"
accept=${this.accept}
@change=${this._handleFilePicked}
aria-labelledby="label"
/>
${this.value
? html`<span
class="mdc-text-field__icon mdc-text-field__icon--trailing"
</div>
<mwc-linear-progress
.indeterminate=${!this.progress}
.progress=${this.progress ? this.progress / 100 : undefined}
></mwc-linear-progress>
</div>`
: html`<label
for=${this.value ? "" : "input"}
class="container ${classMap({
dragged: this._drag,
multiple: this.multiple,
value: Boolean(this.value),
})}"
@drop=${this._handleDrop}
@dragenter=${this._handleDragStart}
@dragover=${this._handleDragStart}
@dragleave=${this._handleDragEnd}
@dragend=${this._handleDragEnd}
>${!this.value
? html`<ha-svg-icon
class="big-icon"
.path=${this.icon || mdiFileUpload}
></ha-svg-icon>
<ha-button unelevated @click=${this._openFilePicker}>
${this.label ||
this.hass?.localize("ui.components.file-upload.label")}
</ha-button>
<span class="secondary"
>${this.secondary ||
this.hass?.localize(
"ui.components.file-upload.secondary"
)}</span
>
<ha-icon-button
slot="suffix"
@click=${this._clearValue}
.label=${this.hass?.localize("ui.common.close") ||
"close"}
.path=${mdiClose}
></ha-icon-button>
</span>`
: ""}
<span
class="mdc-line-ripple ${this._drag
? "mdc-line-ripple--active"
: ""}"
></span>
</label>
`}
<span class="supports">${this.supports}</span>`
: typeof this.value === "string"
? html`<div class="row">
<div class="value" @click=${this._openFilePicker}>
<ha-svg-icon
.path=${this.icon || mdiFileUpload}
></ha-svg-icon>
${this.value}
</div>
<ha-icon-button
@click=${this._clearValue}
.label=${this.hass?.localize("ui.common.delete") ||
"Delete"}
.path=${mdiDelete}
></ha-icon-button>
</div>`
: (this.value instanceof FileList
? Array.from(this.value)
: ensureArray(this.value)
).map(
(file) =>
html`<div class="row">
<div class="value" @click=${this._openFilePicker}>
<ha-svg-icon
.path=${this.icon || mdiFileUpload}
></ha-svg-icon>
${file.name} - ${bytesToString(file.size)}
</div>
<ha-icon-button
@click=${this._clearValue}
.label=${this.hass?.localize("ui.common.delete") ||
"Delete"}
.path=${mdiDelete}
></ha-icon-button>
</div>`
)}
<input
id="input"
type="file"
class="file"
.accept=${this.accept}
.multiple=${this.multiple}
@change=${this._handleFilePicked}
/></label>`}
`;
}
@@ -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);
}
`;
}
}