Skip to content

Commit

Permalink
update upload and add remove to upload-file component
Browse files Browse the repository at this point in the history
  • Loading branch information
Yanek Voloshchuk committed Sep 16, 2024
1 parent a834e56 commit f035d4e
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,30 @@
#fileInput
(change)="uploadFile($event.target.files)"
(click)="fileInput.value = null"
[accept]="acceptForInput"
[accept]="acceptedFileTypes"
[id]="id"
[multiple]="multiple"
hidden
type="file"
/>
<mat-card-content>
<mat-icon>vertical_align_top</mat-icon>
{{ text ? text : 'Upload' }}
<ng-container *ngIf="hasSingleFile(); else uploadDefault">
{{ fileList[0].name }}
</ng-container>

<ng-template #uploadDefault>
<mat-icon>vertical_align_top</mat-icon>
{{ text ? text : 'Upload' }}
</ng-template>
</mat-card-content>
</mat-card>

<ng-container *ngIf="showFileList">
<mat-chip-listbox>
<mat-chip *ngFor="let file of fileList" (click)="openFile(file)" class="download">{{ file.name }}</mat-chip>
<mat-chip *ngFor="let file of fileList" (click)="openFile(file)" class="download"
[removable]="removable" (removed)="remove(file)">
{{ file.name }}
<mat-icon matChipRemove *ngIf="removable">delete_forever</mat-icon>
</mat-chip>
</mat-chip-listbox>
</ng-container>
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,83 @@ export class FileUploadComponent implements OnInit {
@Input() accept: string[];
@Input() text: string;
@Input() showFileList: boolean = false;
@Input() removable: boolean = true;
@Output() fileEmitter = new EventEmitter<FileList>();
@Output() errorEmitter = new EventEmitter<UploadError>();

fileList: File[] = [];
acceptForInput: string[] = [];
private uploadError: boolean = false;
acceptedFileTypes: string[] = [];

ngOnInit(): void {
this.setAcceptedFileTypes();
}

private setAcceptedFileTypes(): void {
if (this.accept?.length) {
this.accept.forEach((accepted) => this.acceptForInput.push(`.${accepted}`));
this.acceptedFileTypes = this.accept.map(ext => `.${ext.toLowerCase()}`);
}
}

uploadFile(fileList: FileList): void {
if (!this.multiple && (fileList.length > 1 || this.fileList.length === 1)) {
this.errorEmitter.emit('ONLY_SINGLE_FILE');
this.uploadError = false;
uploadFile(files: FileList): void {
const fileArray = Array.from(files);

if (!this.validateFileList(fileArray)) {
return;
}
if (this.accept && this.accept.length > 0) {
for (let i = 0; i < fileList.length; i++) {
this.getFileEnding(fileList.item(i).name);
}

this.addFiles(fileArray);
this.fileEmitter.emit(this.createFileListFromArray(this.fileList));
}

private validateFileList(fileArray: File[]): boolean {
if (!this.multiple && fileArray.length > 1) {
this.emitError('ONLY_SINGLE_FILE');
return false;
}
if (!this.uploadError) {
for (let i = 0; i < fileList.length; i++) {
this.fileList.push(fileList.item(i));

for (const file of fileArray) {
if (!this.isAcceptedFileType(file.name)) {
this.emitError('FILETYPE_NOT_SUPPORTED');
return false;
}
this.fileEmitter.emit(fileList);
}
this.uploadError = false;

return true;
}

private emitError(errorType: UploadError): void {
this.errorEmitter.emit(errorType);
}

private isAcceptedFileType(fileName: string): boolean {
const fileExtension = fileName.split('.').pop()?.toLowerCase();
return this.acceptedFileTypes.includes(`.${fileExtension}`);
}

getFileEnding(name: string): void {
const ending = name.substring(name.lastIndexOf('.') + 1);
if (this.accept.filter((a) => a.toLowerCase() === ending.toLowerCase()).length === 0) {
this.errorEmitter.emit('FILETYPE_NOT_SUPPORTED');
this.uploadError = true;
private addFiles(fileArray: File[]): void {
if (!this.multiple) {
this.fileList = [];
}
this.fileList.push(...fileArray);
}

openFile(file: File) {
openFile(file: File): void {
window.open(window.URL.createObjectURL(file));
}

remove(file: File): void {
this.fileList = this.fileList.filter(f => f !== file);
this.fileEmitter.emit(this.createFileListFromArray(this.fileList));
}

private createFileListFromArray(files: File[]): FileList {
const dataTransfer = new DataTransfer();
files.forEach(file => dataTransfer.items.add(file));
return dataTransfer.files;
}

hasSingleFile(): boolean {
return !this.multiple && this.fileList.length === 1;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<div class="api-specification">
<h2>Properties</h2>
<table>
<thead>
<tr>
<th>Property</th>
<th>Type</th>
<th>Default Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td><code>string</code></td>
<td><code>''</code></td>
<td>
HTML id of the element.
</td>
</tr>
<tr>
<td><code>multiple</code></td>
<td><code>boolean</code></td>
<td><code></code></td>
<td>if <code>false</code> only 1 document can be added, otherwise you can add as much documents as you want.</td>
</tr>
<tr>
<td><code>accept</code></td>
<td><code>string[]</code></td>
<td><code>'[]'</code></td>
<td>is a string array which contains all valid document types.</td>
</tr>
<tr>
<td><code>text</code></td>
<td><code>string</code></td>
<td><code>'Upload'</code></td>
<td>Text in the Upload Field.</td>
</tr>
<tr>
<td><code>showFileList</code></td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>Shows the list of added files.</td>
</tr>
<tr>
<td><code>removable</code></td>
<td><code>boolean</code></td>
<td><code>true</code></td>
<td>Uploaded file can be deleted using icon in chip.</td>
</tr>
</tbody>
</table>

<h2>Events</h2>
<table>
<thead>
<tr>
<th>Event</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>fileEmitter</code></td>
<td>Emits when file is uploaded/removed. Contains file list</td>
</tr>
<tr>
<td><code>errorEmitter</code></td>
<td>(ErrorType) ONLY_SINGLE_FILE: if only 1 File is allowed and user tries to add more than 1 File in 1 dialog.</td>
</tr>
<tr>
<td><code>errorEmitter</code></td>
<td>(ErrorType) FILETYPE_NOT_SUPPORTED: if user tries to add unsupported file type</td>
</tr>
</tbody>
</table>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.api-specification {
font-family: Arial, sans-serif;
}

.api-specification h2 {
color: #333;
}

.api-specification table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}

.api-specification th,
.api-specification td {
border: 1px solid #ccc;
padding: 8px;
text-align: left;
}

.api-specification th {
background-color: #f2f2f2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-upload-demo-api-spec',
standalone: true,
imports: [],
templateUrl: './upload-demo-api-spec.component.html',
styleUrl: './upload-demo-api-spec.component.scss'
})
export class UploadDemoApiSpecComponent {

}
16 changes: 5 additions & 11 deletions src/app/component-demos/upload-demo/upload-demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,10 @@
<br />
<h2>Behavior</h2>
<ul>
<li>clickable with dialog to select files</li>
<li>Clickable with dialog to select files</li>
<li>Drag And Drop Functionality</li>
</ul>

<br />
<h2>Inputs</h2>
<ul>
<li>accept: is a string array which contains all valid document types</li>
<li>multiple: if false only 1 document can be added, otherwise you can add as much documents as you want</li>
<li>text: Text in the Upload Field</li>
<li>id: HTML id of the element</li>
<li>showFileList: Shows the list of added files</li>
<li>Replace file with a new one in single upload mode</li>
<li>Show the </li>
</ul>

<br />
Expand All @@ -37,3 +29,5 @@ <h2>Output</h2>
</p>

<example-viewer [example]="uploadComponent"></example-viewer>
<h2>API Specification</h2>
<app-upload-demo-api-spec></app-upload-demo-api-spec>
3 changes: 2 additions & 1 deletion src/app/component-demos/upload-demo/upload-demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { Example } from '../../components/example-viewer/example.class';
import { UploadFileComponent } from '../../example-components/upload-file/upload-file.component';
import { ExampleViewerComponent } from '../../components/example-viewer/example-viewer.component';
import { TextCodeComponent } from '../../components/text-code/text-code.component';
import {UploadDemoApiSpecComponent} from './upload-demo-api-spec/upload-demo-api-spec.component';

@Component({
selector: 'app-upload-demo',
templateUrl: './upload-demo.component.html',
styleUrl: './upload-demo.component.scss',
standalone: true,
imports: [TextCodeComponent, ExampleViewerComponent],
imports: [TextCodeComponent, ExampleViewerComponent, UploadDemoApiSpecComponent],
})
export class UploadDemoComponent {
uploadComponent = new Example(UploadFileComponent, 'upload-file', 'upload');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

<mat-checkbox [(ngModel)]="multiple">{{ 'Allow Multiple Uploads' }}</mat-checkbox>
<mat-checkbox [(ngModel)]="showFileList">{{ 'Show File List' }}</mat-checkbox>
<mat-checkbox [(ngModel)]="removable">{{ 'Removable File' }}</mat-checkbox>

<mad-file-upload
[accept]="accept"
[multiple]="multiple"
[text]="'Upload File'"
[showFileList]="showFileList"
[removable]="removable"
[id]="'upload'"
(fileEmitter)="filesEmitted($event)"
(errorEmitter)="errorReceived($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class UploadFileComponent {

possibleFileEndings = ['pdf', 'doc', 'jpg', 'xls', 'xlsx', 'docx', 'doc', 'gif', 'png'];
showFileList: boolean = false;
removable: boolean = false;

filesEmitted(fileList: FileList) {
for (let i = 0; i < fileList.length; i++) {
Expand Down

0 comments on commit f035d4e

Please sign in to comment.