1 | import { Injectable, Directive, ElementRef, Input, HostListener, HostBinding, EventEmitter, Component, forwardRef, ChangeDetectionStrategy, ViewEncapsulation, Output, ViewChild, NgModule } from '@angular/core';
|
2 | import { Subject, Observable, combineLatest, of, Subscription } from 'rxjs';
|
3 | import { switchMap } from 'rxjs/operators';
|
4 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
5 | import { CommonModule } from '@angular/common';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | class UploadEventsService {
|
16 | constructor() {
|
17 | this._uploads$ = new Subject();
|
18 | this.events$ = this._uploads$.pipe(switchMap(( |
19 |
|
20 |
|
21 |
|
22 | obs => this.mergeIndividualUploads(obs))));
|
23 | }
|
24 |
|
25 | |
26 |
|
27 |
|
28 |
|
29 |
|
30 | readFiles(fileList, dataType = 3 ) {
|
31 |
|
32 | const fileObservables = [];
|
33 | if (fileList) {
|
34 |
|
35 | for (let i = 0; i < fileList.length; i += 1) {
|
36 | fileObservables.push(this.fileReaderObsFactory(fileList[i], dataType));
|
37 | }
|
38 | }
|
39 | this._uploads$.next(fileObservables);
|
40 | }
|
41 | |
42 |
|
43 |
|
44 |
|
45 |
|
46 | mergeIndividualUploads(obs) {
|
47 | return new Observable(( |
48 |
|
49 |
|
50 |
|
51 | observer => {
|
52 |
|
53 | const subs = combineLatest(obs).subscribe({
|
54 | next: ( |
55 |
|
56 |
|
57 |
|
58 | events => {
|
59 |
|
60 | let aborted = false;
|
61 |
|
62 | let completedCount = 0;
|
63 |
|
64 | const next = {
|
65 | files: events,
|
66 | status: 0 ,
|
67 | error: null,
|
68 | };
|
69 | eventLoop: for (const event of events) {
|
70 | switch (event.status) {
|
71 | case 4 :
|
72 | next.status = 4 ;
|
73 | aborted = true;
|
74 | break eventLoop;
|
75 | case 1 :
|
76 | next.status = 1 ;
|
77 | break;
|
78 | case 2 :
|
79 | completedCount += 1;
|
80 | break;
|
81 | default:
|
82 | }
|
83 | }
|
84 | if (aborted) {
|
85 | for (const event of events) {
|
86 | if (event.reader) {
|
87 | event.reader.abort();
|
88 | }
|
89 | }
|
90 | }
|
91 | else if (completedCount === events.length) {
|
92 | next.status = 2 ;
|
93 | }
|
94 | observer.next(next);
|
95 | }),
|
96 | complete: ( |
97 |
|
98 |
|
99 | () => observer.complete()),
|
100 | error: ( |
101 |
|
102 |
|
103 |
|
104 | (err) => observer.error(err)),
|
105 | });
|
106 | return ( |
107 |
|
108 |
|
109 | () => {
|
110 | subs.unsubscribe();
|
111 | });
|
112 | }));
|
113 | }
|
114 |
|
115 | |
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | fileReaderObsFactory(file, dataType) {
|
122 |
|
123 | const fileData = {
|
124 | name: file.name,
|
125 | size: file.size,
|
126 | type: file.type,
|
127 | data: null,
|
128 | lastModified: file.lastModified,
|
129 | total: file.size,
|
130 | loaded: 0,
|
131 | };
|
132 | if (dataType === 3 ) {
|
133 | fileData.data = file;
|
134 | return of(this.eventFactory(fileData, 2 , null));
|
135 | }
|
136 |
|
137 | return new Observable(( |
138 |
|
139 |
|
140 |
|
141 | observer => {
|
142 |
|
143 | let completed = false;
|
144 |
|
145 | const reader = new FileReader();
|
146 | reader.addEventListener('error', ( |
147 |
|
148 |
|
149 | () => {
|
150 | observer.error(this.eventFactory(fileData, 3 , reader));
|
151 | completed = true;
|
152 | observer.complete();
|
153 | }), { once: true });
|
154 | reader.addEventListener('abort', ( |
155 |
|
156 |
|
157 | () => {
|
158 | observer.next(this.eventFactory(fileData, 4 , reader));
|
159 | completed = true;
|
160 | observer.complete();
|
161 | }), { once: true });
|
162 |
|
163 | const onprogress = ( |
164 |
|
165 |
|
166 |
|
167 | (e) => {
|
168 | if (e.lengthComputable) {
|
169 | fileData.total = e.total;
|
170 | fileData.loaded = e.loaded;
|
171 | }
|
172 | observer.next(this.eventFactory(fileData, 1 , reader));
|
173 | });
|
174 | reader.addEventListener('loadstart', ( |
175 |
|
176 |
|
177 | () => {
|
178 | observer.next(this.eventFactory(fileData, 1 , reader));
|
179 | }), { once: true });
|
180 | reader.addEventListener('load', ( |
181 |
|
182 |
|
183 | () => {
|
184 | fileData.loaded = ( (fileData.size));
|
185 | fileData.data = reader.result;
|
186 | observer.next(this.eventFactory(fileData, 2 , reader));
|
187 | completed = true;
|
188 | observer.complete();
|
189 | }));
|
190 | switch (dataType) {
|
191 | case 0 :
|
192 | reader.readAsArrayBuffer(file);
|
193 | break;
|
194 | case 1 :
|
195 | reader.readAsDataURL(file);
|
196 | break;
|
197 | case 2 :
|
198 | reader.readAsText(file);
|
199 | break;
|
200 | default:
|
201 | observer.error(new TypeError('Invalid output type: Select ArrayBuffer, DataURL or Text'));
|
202 | completed = true;
|
203 | observer.complete();
|
204 | return;
|
205 | }
|
206 | return ( |
207 |
|
208 |
|
209 | () => {
|
210 | if (reader) {
|
211 | if (!completed) {
|
212 | reader.abort();
|
213 | }
|
214 | if (onprogress) {
|
215 | reader.removeEventListener('progress', onprogress);
|
216 | }
|
217 | }
|
218 | });
|
219 | }));
|
220 | }
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 | eventFactory(file, status, reader = null) {
|
229 | return {
|
230 | status,
|
231 | reader,
|
232 | file: Object.assign({}, file),
|
233 | };
|
234 | }
|
235 | }
|
236 | UploadEventsService.decorators = [
|
237 | { type: Injectable }
|
238 | ];
|
239 |
|
240 | UploadEventsService.ctorParameters = () => [];
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 | class FileUploadDirective {
|
250 | |
251 |
|
252 |
|
253 |
|
254 | constructor(_elementRef, _uploadEventsService) {
|
255 | this._elementRef = _elementRef;
|
256 | this._uploadEventsService = _uploadEventsService;
|
257 | }
|
258 | |
259 |
|
260 |
|
261 |
|
262 | onInputChange(e) {
|
263 | e.preventDefault();
|
264 |
|
265 | const next = this._elementRef.nativeElement.files &&
|
266 | this._elementRef.nativeElement.files.length
|
267 | ? this._elementRef.nativeElement.files
|
268 | : null;
|
269 | this._uploadEventsService.readFiles(next, this.dataType);
|
270 | }
|
271 | }
|
272 | FileUploadDirective.decorators = [
|
273 | { type: Directive, args: [{
|
274 | selector: 'input[bmFileUpload]',
|
275 | },] }
|
276 | ];
|
277 |
|
278 | FileUploadDirective.ctorParameters = () => [
|
279 | { type: ElementRef },
|
280 | { type: UploadEventsService }
|
281 | ];
|
282 | FileUploadDirective.propDecorators = {
|
283 | dataType: [{ type: Input }],
|
284 | onInputChange: [{ type: HostListener, args: ['change', ['$event'],] }]
|
285 | };
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | class FileUploadDraggableDirective {
|
295 | |
296 |
|
297 |
|
298 | constructor(_uploadEventsService) {
|
299 | this._uploadEventsService = _uploadEventsService;
|
300 | }
|
301 | |
302 |
|
303 |
|
304 |
|
305 | ondragover(event) {
|
306 | if (!this.disabled) {
|
307 | event.preventDefault();
|
308 | this.dragHover = true;
|
309 | }
|
310 | }
|
311 | |
312 |
|
313 |
|
314 | ondragleave() {
|
315 | this.dragHover = false;
|
316 | }
|
317 | |
318 |
|
319 |
|
320 |
|
321 | ondrop(event) {
|
322 | const { dataTransfer } = event;
|
323 | if (!this.disabled && dataTransfer) {
|
324 | dataTransfer.dropEffect = 'copy';
|
325 | this._uploadEventsService.readFiles(dataTransfer.files, this.dataType);
|
326 | event.preventDefault();
|
327 | }
|
328 | }
|
329 | }
|
330 | FileUploadDraggableDirective.decorators = [
|
331 | { type: Directive, args: [{ selector: '[bmFileUploadDraggable]' },] }
|
332 | ];
|
333 |
|
334 | FileUploadDraggableDirective.ctorParameters = () => [
|
335 | { type: UploadEventsService }
|
336 | ];
|
337 | FileUploadDraggableDirective.propDecorators = {
|
338 | disabled: [{ type: Input }],
|
339 | dataType: [{ type: Input }],
|
340 | dragHover: [{ type: HostBinding, args: ['class.drag-hover',] }],
|
341 | ondragover: [{ type: HostListener, args: ['dragover', ['$event'],] }],
|
342 | ondragleave: [{ type: HostListener, args: ['dragleave',] }],
|
343 | ondrop: [{ type: HostListener, args: ['drop', ['$event'],] }]
|
344 | };
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 | class BMFileUploaderComponent {
|
351 | |
352 |
|
353 |
|
354 | constructor(_uploadEventsService) {
|
355 | this._uploadEventsService = _uploadEventsService;
|
356 | this.abort = new EventEmitter();
|
357 | this.done = new EventEmitter();
|
358 | this.empty = new EventEmitter();
|
359 | this.error = new EventEmitter();
|
360 | this.loading = new EventEmitter();
|
361 | this.multipleInt = 0;
|
362 | this._subs = Subscription.EMPTY;
|
363 | }
|
364 | |
365 |
|
366 |
|
367 |
|
368 | set multiple(v) {
|
369 | this.multipleInt = v ? 1 : 0;
|
370 | }
|
371 | |
372 |
|
373 |
|
374 | get multiple() {
|
375 | return this.multipleInt === 1;
|
376 | }
|
377 | |
378 |
|
379 |
|
380 | ngOnInit() {
|
381 | this._uploadEventsService.events$.subscribe({
|
382 |
|
383 | next: ( |
384 |
|
385 |
|
386 |
|
387 | event => {
|
388 | switch (event.status) {
|
389 | case 4 :
|
390 | this.abort.emit(event);
|
391 | if (this._onChangeCb) {
|
392 | this._onChangeCb([]);
|
393 | }
|
394 | break;
|
395 | case 2 :
|
396 | this.done.emit(event);
|
397 | if (this._onChangeCb) {
|
398 | this._onChangeCb(event.files.map(( |
399 |
|
400 |
|
401 |
|
402 | e => e.file)));
|
403 | }
|
404 | break;
|
405 | case 0 :
|
406 | this.empty.emit(event);
|
407 | if (this._onChangeCb) {
|
408 | this._onChangeCb([]);
|
409 | }
|
410 | break;
|
411 | case 3 :
|
412 | this.error.emit(event);
|
413 | if (this._onChangeCb) {
|
414 | this._onChangeCb([]);
|
415 | }
|
416 | break;
|
417 | case 1 :
|
418 | this.loading.emit(event);
|
419 | if (this._onTouchedCb) {
|
420 | this._onTouchedCb();
|
421 | }
|
422 | break;
|
423 | default:
|
424 | }
|
425 | }),
|
426 | });
|
427 | }
|
428 | |
429 |
|
430 |
|
431 | ngOnDestroy() {
|
432 | this._subs.unsubscribe();
|
433 | }
|
434 | |
435 |
|
436 |
|
437 | onFocusin() {
|
438 | if (this._onTouchedCb) {
|
439 | this._onTouchedCb();
|
440 | }
|
441 | }
|
442 | |
443 |
|
444 |
|
445 |
|
446 | writeValue(val) {
|
447 | if (val instanceof FileList) {
|
448 | this._uploadEventsService.readFiles(val, this.dataType);
|
449 | }
|
450 | else {
|
451 | this._uploadEventsService.readFiles(null, this.dataType);
|
452 | }
|
453 | }
|
454 | |
455 |
|
456 |
|
457 |
|
458 | registerOnChange(fn) {
|
459 | this._onChangeCb = fn;
|
460 | }
|
461 | |
462 |
|
463 |
|
464 |
|
465 | registerOnTouched(fn) {
|
466 | this._onTouchedCb = fn;
|
467 | }
|
468 | |
469 |
|
470 |
|
471 |
|
472 | setDisabledState(isDisabled) {
|
473 | this.disabled = isDisabled;
|
474 | }
|
475 | }
|
476 | BMFileUploaderComponent.decorators = [
|
477 | { type: Component, args: [{
|
478 | selector: 'bm-file-upload',
|
479 | template: "<input\n [accept]=\"accept\"\n [dataType]=\"dataType\"\n [disabled]=\"disabled\"\n [multiple]=\"multiple\"\n #inputFile\n bmFileUpload\n class=\"bm-visual-hidden\"\n tabindex=\"-1\"\n type=\"file\"\n/>\n<div\n [dataType]=\"dataType\"\n [disabled]=\"disabled\"\n bmFileUploadDraggable\n class=\"bm-file-upload-drop-area\"\n>\n <ng-content></ng-content>\n <div>\n <i class=\"fas fa-upload bm-file-upload__upload-icon\" aria-hidden=\"true\"></i>\n <div>\n <p i18n>\n Drag & Drop your audio {multipleInt, plural, =0 {file} =1 {files}}\n </p>\n <p>\n <button\n (click)=\"inputFile.click()\"\n (focusin)=\"onFocusin()\"\n [disabled]=\"disabled\"\n class=\"bm-button bm-button--primary\"\n type=\"button\"\n i18n=\"Select files|Text in the button inside the draggable area of the file-uploader\"\n >\n Select {multipleInt, plural, =0 {file} =1 {files}}\n </button>\n </p>\n </div>\n </div>\n <ng-content select=\"[bmFileUploadFooter]\"></ng-content>\n</div>\n",
|
480 | providers: [
|
481 | {
|
482 | provide: NG_VALUE_ACCESSOR,
|
483 |
|
484 | useExisting: forwardRef(( |
485 |
|
486 |
|
487 | () => BMFileUploaderComponent)),
|
488 | multi: true,
|
489 | },
|
490 | UploadEventsService,
|
491 | ],
|
492 | changeDetection: ChangeDetectionStrategy.OnPush,
|
493 |
|
494 | encapsulation: ViewEncapsulation.None,
|
495 | styles: [".bm-file-upload--drag-hover .bm-file-upload-drop-area{border-width:2px}.bm-file-upload__upload-icon{font-size:3em}.bm-file-upload-drop-area{box-sizing:border-box;border:1px dashed var(--header-background-color,#3e71ad);border-radius:3px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:1em}.bm-file-upload-drop-area p{font-size:.9rem;margin:.5rem 0}.bm-file-upload-drop-area *{pointer-events:none}.bm-file-upload-drop-area button{pointer-events:auto}"]
|
496 | }] }
|
497 | ];
|
498 |
|
499 | BMFileUploaderComponent.ctorParameters = () => [
|
500 | { type: UploadEventsService }
|
501 | ];
|
502 | BMFileUploaderComponent.propDecorators = {
|
503 | accept: [{ type: Input }],
|
504 | dataType: [{ type: Input }],
|
505 | disabled: [{ type: Input }],
|
506 | multiple: [{ type: Input }],
|
507 | abort: [{ type: Output }],
|
508 | done: [{ type: Output }],
|
509 | empty: [{ type: Output }],
|
510 | error: [{ type: Output }],
|
511 | loading: [{ type: Output }]
|
512 | };
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 | class BMClickFileUploaderComponent {
|
519 | |
520 |
|
521 |
|
522 | constructor(_uploadEventsService) {
|
523 | this._uploadEventsService = _uploadEventsService;
|
524 | this.abort = new EventEmitter();
|
525 | this.done = new EventEmitter();
|
526 | this.empty = new EventEmitter();
|
527 | this.error = new EventEmitter();
|
528 | this.loading = new EventEmitter();
|
529 | this._subs = Subscription.EMPTY;
|
530 | }
|
531 | |
532 |
|
533 |
|
534 | ngOnInit() {
|
535 | this._uploadEventsService.events$.subscribe({
|
536 |
|
537 | next: ( |
538 |
|
539 |
|
540 |
|
541 | event => {
|
542 | switch (event.status) {
|
543 | case 4 :
|
544 | this.abort.emit(event);
|
545 | if (this._onChangeCb) {
|
546 | this._onChangeCb([]);
|
547 | }
|
548 | break;
|
549 | case 2 :
|
550 | this.done.emit(event);
|
551 | if (this._onChangeCb) {
|
552 | this._onChangeCb(event.files.map(( |
553 |
|
554 |
|
555 |
|
556 | e => e.file)));
|
557 | }
|
558 | break;
|
559 | case 0 :
|
560 | this.empty.emit(event);
|
561 | if (this._onChangeCb) {
|
562 | this._onChangeCb([]);
|
563 | }
|
564 | break;
|
565 | case 3 :
|
566 | this.error.emit(event);
|
567 | if (this._onChangeCb) {
|
568 | this._onChangeCb([]);
|
569 | }
|
570 | break;
|
571 | case 1 :
|
572 | this.loading.emit(event);
|
573 | if (this._onTouchedCb) {
|
574 | this._onTouchedCb();
|
575 | }
|
576 | break;
|
577 | default:
|
578 | }
|
579 | }),
|
580 | });
|
581 | }
|
582 | |
583 |
|
584 |
|
585 | ngOnDestroy() {
|
586 | this._subs.unsubscribe();
|
587 | }
|
588 | |
589 |
|
590 |
|
591 | onFocusin() {
|
592 | if (this._onTouchedCb) {
|
593 | this._onTouchedCb();
|
594 | }
|
595 | }
|
596 | |
597 |
|
598 |
|
599 |
|
600 | writeValue(val) {
|
601 | if (val instanceof FileList) {
|
602 | this._uploadEventsService.readFiles(val, this.dataType);
|
603 | }
|
604 | else {
|
605 | this._inputElement.nativeElement.value = '';
|
606 | this._uploadEventsService.readFiles(null, this.dataType);
|
607 | }
|
608 | }
|
609 | |
610 |
|
611 |
|
612 |
|
613 | registerOnChange(fn) {
|
614 | this._onChangeCb = fn;
|
615 | }
|
616 | |
617 |
|
618 |
|
619 |
|
620 | registerOnTouched(fn) {
|
621 | this._onTouchedCb = fn;
|
622 | }
|
623 | |
624 |
|
625 |
|
626 |
|
627 | setDisabledState(isDisabled) {
|
628 | this.disabled = isDisabled;
|
629 | }
|
630 | }
|
631 | BMClickFileUploaderComponent.decorators = [
|
632 | { type: Component, args: [{
|
633 | selector: 'bm-click-file-upload',
|
634 | template: "<input\n [accept]=\"accept\"\n [dataType]=\"dataType\"\n [disabled]=\"disabled\"\n [multiple]=\"multiple\"\n #inputFile\n bmFileUpload\n class=\"bm-visual-hidden\"\n tabindex=\"-1\"\n type=\"file\"\n/>\n<button\n (click)=\"inputFile.click()\"\n (focusin)=\"onFocusin()\"\n [dataType]=\"dataType\"\n [disabled]=\"disabled\"\n bmFileUploadDraggable\n class=\"bm-click-file-upload-drop-area\"\n>\n <ng-content></ng-content>\n</button>\n",
|
635 | providers: [
|
636 | {
|
637 | provide: NG_VALUE_ACCESSOR,
|
638 |
|
639 | useExisting: forwardRef(( |
640 |
|
641 |
|
642 | () => BMClickFileUploaderComponent)),
|
643 | multi: true,
|
644 | },
|
645 | UploadEventsService,
|
646 | ],
|
647 | changeDetection: ChangeDetectionStrategy.OnPush,
|
648 |
|
649 | encapsulation: ViewEncapsulation.None,
|
650 | styles: [".bm-click-file-upload-drop-area{background:0 0;border:0;display:inline-block}"]
|
651 | }] }
|
652 | ];
|
653 |
|
654 | BMClickFileUploaderComponent.ctorParameters = () => [
|
655 | { type: UploadEventsService }
|
656 | ];
|
657 | BMClickFileUploaderComponent.propDecorators = {
|
658 | accept: [{ type: Input }],
|
659 | dataType: [{ type: Input }],
|
660 | disabled: [{ type: Input }],
|
661 | multiple: [{ type: Input }],
|
662 | abort: [{ type: Output }],
|
663 | done: [{ type: Output }],
|
664 | empty: [{ type: Output }],
|
665 | error: [{ type: Output }],
|
666 | loading: [{ type: Output }],
|
667 | _inputElement: [{ type: ViewChild, args: ['inputFile', { read: ElementRef, static: true },] }]
|
668 | };
|
669 |
|
670 |
|
671 |
|
672 |
|
673 |
|
674 | class UploadersModule {
|
675 | }
|
676 | UploadersModule.decorators = [
|
677 | { type: NgModule, args: [{
|
678 | imports: [CommonModule],
|
679 | declarations: [
|
680 | BMClickFileUploaderComponent,
|
681 | BMFileUploaderComponent,
|
682 | FileUploadDirective,
|
683 | FileUploadDraggableDirective,
|
684 | ],
|
685 | exports: [
|
686 | BMClickFileUploaderComponent,
|
687 | BMFileUploaderComponent,
|
688 | FileUploadDirective,
|
689 | FileUploadDraggableDirective,
|
690 | ],
|
691 | },] }
|
692 | ];
|
693 |
|
694 | export { BMClickFileUploaderComponent, BMFileUploaderComponent, FileUploadDirective, FileUploadDraggableDirective, UploadersModule, UploadEventsService as ɵa };
|
695 |
|