1
1
import type { MediaValueType } from '../../property-editors/upload-field/types.js' ;
2
- import { getMimeTypeFromExtension } from './utils.js' ;
3
2
import type { ManifestFileUploadPreview } from './file-upload-preview.extension.js' ;
4
- import { TemporaryFileStatus , UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file' ;
5
- import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file' ;
6
- import { UmbId } from '@umbraco-cms/backoffice/id' ;
3
+ import { getMimeTypeFromExtension } from './utils.js' ;
7
4
import {
8
5
css ,
9
6
html ,
@@ -13,15 +10,18 @@ import {
13
10
property ,
14
11
query ,
15
12
state ,
16
- type PropertyValueMap ,
13
+ when ,
17
14
} from '@umbraco-cms/backoffice/external/lit' ;
18
- import type { UUIFileDropzoneElement , UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui ' ;
19
- import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element ' ;
15
+ import { formatBytes , stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils ' ;
16
+ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry ' ;
20
17
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event' ;
21
-
22
18
import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api' ;
23
- import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry' ;
24
- import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils' ;
19
+ import { UmbId } from '@umbraco-cms/backoffice/id' ;
20
+ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ;
21
+ import { UmbTemporaryFileManager , TemporaryFileStatus } from '@umbraco-cms/backoffice/temporary-file' ;
22
+ import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit' ;
23
+ import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file' ;
24
+ import type { UUIFileDropzoneElement , UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui' ;
25
25
26
26
@customElement ( 'umb-input-upload-field' )
27
27
export class UmbInputUploadFieldElement extends UmbLitElement {
@@ -35,7 +35,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
35
35
temporaryFileId : this . temporaryFile ?. temporaryUnique ,
36
36
} ;
37
37
}
38
-
39
38
#src = '' ;
40
39
41
40
/**
@@ -54,6 +53,9 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
54
53
@state ( )
55
54
public temporaryFile ?: UmbTemporaryFileModel ;
56
55
56
+ @state ( )
57
+ private _progress = 0 ;
58
+
57
59
@state ( )
58
60
private _extensions ?: string [ ] ;
59
61
@@ -67,12 +69,11 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
67
69
68
70
#manifests: Array < ManifestFileUploadPreview > = [ ] ;
69
71
70
- constructor ( ) {
71
- super ( ) ;
72
- }
72
+ #uploadAbort?: AbortController ;
73
73
74
74
override updated ( changedProperties : PropertyValueMap < any > | Map < PropertyKey , unknown > ) {
75
75
super . updated ( changedProperties ) ;
76
+
76
77
if ( changedProperties . has ( 'value' ) && changedProperties . get ( 'value' ) ?. src !== this . value . src ) {
77
78
this . #setPreviewAlias( ) ;
78
79
}
@@ -108,7 +109,13 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
108
109
stringOrStringArrayContains ( manifest . forMimeTypes , '*/*' ) ,
109
110
) ?. alias ;
110
111
111
- const mimeType = this . #getMimeTypeFromPath( this . value . src ) ;
112
+ let mimeType : string | null = null ;
113
+ if ( this . temporaryFile ?. file ) {
114
+ mimeType = this . temporaryFile . file . type ;
115
+ } else {
116
+ mimeType = this . #getMimeTypeFromPath( this . value . src ) ;
117
+ }
118
+
112
119
if ( ! mimeType ) return fallbackAlias ;
113
120
114
121
// Check for an exact match
@@ -148,23 +155,43 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
148
155
149
156
async #onUpload( e : UUIFileDropzoneEvent ) {
150
157
//Property Editor for Upload field will always only have one file.
151
- const item : UmbTemporaryFileModel = {
158
+ this . temporaryFile = {
152
159
temporaryUnique : UmbId . new ( ) ,
160
+ status : TemporaryFileStatus . WAITING ,
153
161
file : e . detail . files [ 0 ] ,
154
162
} ;
155
163
156
- const upload = this . #manager. uploadOne ( item ) ;
164
+ try {
165
+ this . #uploadAbort = new AbortController ( ) ;
166
+ const uploaded = await this . #manager. uploadOne ( {
167
+ ...this . temporaryFile ,
168
+ onProgress : ( p ) => {
169
+ this . _progress = Math . ceil ( p ) ;
170
+ } ,
171
+ abortSignal : this . #uploadAbort. signal ,
172
+ } ) ;
173
+
174
+ if ( uploaded . status === TemporaryFileStatus . SUCCESS ) {
175
+ this . temporaryFile . status = TemporaryFileStatus . SUCCESS ;
157
176
158
- const reader = new FileReader ( ) ;
159
- reader . onload = ( ) => {
160
- this . value = { src : reader . result as string } ;
161
- } ;
162
- reader . readAsDataURL ( item . file ) ;
177
+ const blobUrl = URL . createObjectURL ( this . temporaryFile . file ) ;
178
+ this . value = { src : blobUrl } ;
163
179
164
- const uploaded = await upload ;
165
- if ( uploaded . status === TemporaryFileStatus . SUCCESS ) {
166
- this . temporaryFile = { temporaryUnique : item . temporaryUnique , file : item . file } ;
167
- this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
180
+ this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
181
+ } else {
182
+ this . temporaryFile . status = TemporaryFileStatus . ERROR ;
183
+ this . requestUpdate ( 'temporaryFile' ) ;
184
+ }
185
+ } catch {
186
+ // If we still have a temporary file, set it to error.
187
+ if ( this . temporaryFile ) {
188
+ this . temporaryFile . status = TemporaryFileStatus . ERROR ;
189
+ this . requestUpdate ( 'temporaryFile' ) ;
190
+ }
191
+
192
+ // If the error was caused by the upload being aborted, do not show an error message.
193
+ } finally {
194
+ this . #uploadAbort = undefined ;
168
195
}
169
196
}
170
197
@@ -175,55 +202,103 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
175
202
}
176
203
177
204
override render ( ) {
178
- if ( this . value . src && this . _previewAlias ) {
179
- return this . #renderFile( this . value . src , this . _previewAlias , this . temporaryFile ?. file ) ;
180
- } else {
205
+ if ( ! this . temporaryFile && ! this . value . src ) {
181
206
return this . #renderDropzone( ) ;
182
207
}
208
+
209
+ return html `
210
+ ${ this . temporaryFile ? this . #renderUploader( ) : nothing }
211
+ ${ this . value . src && this . _previewAlias ? this . #renderFile( this . value . src ) : nothing }
212
+ ` ;
183
213
}
184
214
185
215
#renderDropzone( ) {
186
216
return html `
187
217
<uui- file-dropzone
188
- @click = ${ this . #handleBrowse}
189
218
id= "dropzone"
190
219
label = "dropzone"
191
- @change = "${ this . #onUpload} "
192
- accept = "${ ifDefined ( this . _extensions ?. join ( ', ' ) ) } " >
193
- <uui- butto n label= ${ this . localize . term ( 'media_clickToUpload' ) } @click = "${ this . #handleBrowse} " > </ uui- butto n>
220
+ disallowFolderUpload
221
+ accept = ${ ifDefined ( this . _extensions ?. join ( ', ' ) ) }
222
+ @change = ${ this . #onUpload}
223
+ @click = ${ this . #handleBrowse} >
224
+ <uui- butto n label= ${ this . localize . term ( 'media_clickToUpload' ) } @click = ${ this . #handleBrowse} > </ uui- butto n>
194
225
</ uui- file-dropzone>
195
226
` ;
196
227
}
197
228
198
- #renderFile( src : string , previewAlias : string , file ?: File ) {
199
- if ( ! previewAlias ) return 'An error occurred. No previewer found for the file type.' ;
229
+ #renderUploader( ) {
230
+ if ( ! this . temporaryFile ) return nothing ;
231
+
232
+ return html `
233
+ <div id= "temporaryFile" >
234
+ <div id= "fileIcon" >
235
+ ${ when (
236
+ this . temporaryFile . status === TemporaryFileStatus . SUCCESS ,
237
+ ( ) => html `<umb- icon name= "check" color = "green" > </ umb- icon> ` ,
238
+ ) }
239
+ ${ when (
240
+ this . temporaryFile . status === TemporaryFileStatus . ERROR ,
241
+ ( ) => html `<umb- icon name= "wrong" color = "red" > </ umb- icon> ` ,
242
+ ) }
243
+ </ div>
244
+ <div id= "fileDetails" >
245
+ <div id= "fileName" > ${ this . temporaryFile . file . name } </ div>
246
+ <div id= "fileSize" > ${ formatBytes ( this . temporaryFile . file . size , { decimals : 2 } ) } : ${ this . _progress } %</ div>
247
+ ${ when (
248
+ this . temporaryFile . status === TemporaryFileStatus . WAITING ,
249
+ ( ) => html `<div id= "progress" > <uui- loader- bar progress= ${ this . _progress } > </ uui- loader- bar> </ div> ` ,
250
+ ) }
251
+ ${ when (
252
+ this . temporaryFile . status === TemporaryFileStatus . ERROR ,
253
+ ( ) => html `<div id= "error" > An error occured </ div> ` ,
254
+ ) }
255
+ </ div>
256
+ <div id= "fileActions" >
257
+ ${ when (
258
+ this . temporaryFile . status === TemporaryFileStatus . WAITING ,
259
+ ( ) => html `
260
+ <uui- butto n compact @click = ${ this . #handleRemove} label= ${ this . localize . term ( 'general_cancel' ) } >
261
+ <uui- icon name= "remove" > </ uui- icon> ${ this . localize . term ( 'general_cancel' ) }
262
+ </ uui- butto n>
263
+ ` ,
264
+ ( ) => this . #renderButtonRemove( ) ,
265
+ ) }
266
+ </ div>
267
+ </ div>
268
+ ` ;
269
+ }
270
+
271
+ #renderFile( src : string ) {
200
272
return html `
201
273
<div id= "wrapper" >
202
- <div style = "position:relative; display: flex; width: fit-content; max-width: 100% ">
274
+ <div id = "wrapperInner ">
203
275
<umb- extension- slot
204
276
type= "fileUploadPreview"
205
- .props = ${ { path : src , file : file } }
206
- .filter = ${ ( manifest : ManifestFileUploadPreview ) => manifest . alias === previewAlias } >
277
+ .props = ${ { path : src , file : this . temporaryFile ?. file } }
278
+ .filter = ${ ( manifest : ManifestFileUploadPreview ) => manifest . alias === this . _previewAlias } >
207
279
</ umb- extension- slot>
208
- ${ this . temporaryFile ?. status === TemporaryFileStatus . WAITING
209
- ? html `<umb- tempor ary- file-badge> </ umb- tempor ary- file-badge> `
210
- : nothing }
211
280
</ div>
212
281
</ div>
213
282
${ this . #renderButtonRemove( ) }
214
283
` ;
215
284
}
216
285
217
286
#renderButtonRemove( ) {
218
- return html `<uui- butto n compact @click = ${ this . #handleRemove} label= ${ this . localize . term ( 'content_uploadClear' ) } >
219
- <uui- icon name= "icon-trash" > </ uui- icon> ${ this . localize . term ( 'content_uploadClear' ) }
220
- </ uui- butto n> ` ;
287
+ return html `
288
+ <uui- butto n compact @click = ${ this . #handleRemove} label= ${ this . localize . term ( 'content_uploadClear' ) } >
289
+ <uui- icon name= "icon-trash" > </ uui- icon> ${ this . localize . term ( 'content_uploadClear' ) }
290
+ </ uui- butto n>
291
+ ` ;
221
292
}
222
293
223
294
#handleRemove( ) {
224
295
this . value = { src : undefined } ;
225
296
this . temporaryFile = undefined ;
297
+ this . _progress = 0 ;
226
298
this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
299
+
300
+ // If the upload promise happens to be in progress, cancel it.
301
+ this . #uploadAbort?. abort ( ) ;
227
302
}
228
303
229
304
static override readonly styles = [
@@ -249,6 +324,45 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
249
324
border-radius : var (--uui-border-radius );
250
325
}
251
326
327
+ # wrapperInner {
328
+ position : relative;
329
+ display : flex;
330
+ width : fit-content;
331
+ max-width : 100% ;
332
+ }
333
+
334
+ # temporaryFile {
335
+ display : grid;
336
+ grid-template-columns : auto auto auto;
337
+ width : fit-content;
338
+ max-width : 100% ;
339
+ margin : var (--uui-size-layout-1 ) 0 ;
340
+ padding : var (--uui-size-space-3 );
341
+ border : 1px dashed var (--uui-color-divider-emphasis );
342
+ }
343
+
344
+ # fileIcon ,
345
+ # fileActions {
346
+ place-self : center center;
347
+ padding : 0 var (--uui-size-layout-1 );
348
+ }
349
+
350
+ # fileName {
351
+ white-space : nowrap;
352
+ overflow : hidden;
353
+ text-overflow : ellipsis;
354
+ font-size : var (--uui-size-5 );
355
+ }
356
+
357
+ # fileSize {
358
+ font-size : var (--uui-font-size-small );
359
+ color : var (--uui-color-text-alt );
360
+ }
361
+
362
+ # error {
363
+ color : var (--uui-color-danger );
364
+ }
365
+
252
366
uui-file-dropzone {
253
367
position : relative;
254
368
display : block;
0 commit comments