-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #149 from mobi/go_file_upload
Go File Upload
- Loading branch information
Showing
16 changed files
with
817 additions
and
1 deletion.
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
projects/go-lib/src/lib/components/go-file-upload/go-dragon-drop.directive.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Directive, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core'; | ||
|
||
@Directive({ | ||
selector: '[goDragonDrop]' | ||
}) | ||
export class DragonDropDirective { | ||
active: boolean = false; | ||
@Input() class: string = ''; // override the standard class attr with a new one. | ||
@Input() activeClass: string = ''; | ||
@Output() dropped: EventEmitter<any> = new EventEmitter<any>(); | ||
|
||
@HostBinding('class') | ||
get hostClasses(): string { | ||
return [ | ||
this.class, | ||
this.active ? this.activeClass : '', | ||
].join(' '); | ||
} | ||
|
||
// Dragover listener | ||
@HostListener('dragover', ['$event']) onDragOver(evt: Event): void { | ||
evt.preventDefault(); | ||
evt.stopPropagation(); | ||
this.active = true; | ||
} | ||
|
||
// Dragleave listener | ||
@HostListener('dragleave', ['$event']) public onDragLeave(evt: Event): void { | ||
evt.preventDefault(); | ||
evt.stopPropagation(); | ||
this.active = false; | ||
} | ||
|
||
// Drop listener | ||
@HostListener('drop', ['$event']) public ondrop(evt: any): void { | ||
evt.preventDefault(); | ||
evt.stopPropagation(); | ||
this.active = false; | ||
this.dropped.emit(evt); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
projects/go-lib/src/lib/components/go-file-upload/go-file-upload.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<label class="go-form__label" | ||
[attr.for]="id" | ||
[ngClass]="{'go-form__label--dark': theme === 'dark'}"> | ||
{{ label }} | ||
</label> | ||
<div [ngClass]="{ 'go-file-upload--disabled': state === 'selected' || isLoading, 'go-file-upload--dark': theme === 'dark' }"> | ||
<div | ||
goDragonDrop | ||
class="go-file-upload go-file-upload__container" | ||
activeClass="go-file-upload--active" | ||
(click)="filePicker.click()" | ||
(dropped)="onFilePicked($event)" | ||
> | ||
<div class="go-file-upload__container" *ngIf="state === 'selecting' && !isLoading"> | ||
<go-icon icon="publish" iconModifier="large"></go-icon> | ||
Drag and Drop File or Click to Browse | ||
</div> | ||
<div class="go-file-upload__container go-file-upload__container--loading" *ngIf="isLoading"> | ||
<go-loader *ngIf="isLoading" loaderSize="small"></go-loader> | ||
<span class="go-file-upload__container--loading-text">Uploading File...</span> | ||
</div> | ||
<div class="go-file-upload__container go-file-upload__container--selected" *ngIf="state === 'selected' && !isLoading"> | ||
<go-icon icon="check" class="go-file-upload__selected-checkmark" iconClass="go-icon--large"></go-icon> | ||
File Selected | ||
</div> | ||
<input | ||
type="file" | ||
[id]="id" | ||
#filePicker | ||
class="go-file-upload__input" | ||
(change)="onFilePicked($event.target.files)" | ||
[attr.multiple]="multiple ? true : null"> | ||
</div> | ||
</div> | ||
|
||
<div *ngIf="filePreview.length" class="go-file-upload__file-name"> | ||
<label class="go-form__label file-name__label" [ngClass]="{'go-form__label--dark': theme === 'dark'}" *ngIf="multiple">Files</label> | ||
<label class="go-form__label file-name__label" [ngClass]="{'go-form__label--dark': theme === 'dark'}" *ngIf="!multiple">File Name</label> | ||
<p *ngFor="let file of filePreview; let i = index" class="go-file-upload__file-list"> | ||
<span class="go-file-upload__file-name" [ngClass]="{'go-file-upload__file-name--dark': theme === 'dark'}">{{ file }}</span> | ||
<go-icon-button buttonIcon="close" (handleClick)="removeFile(i)"></go-icon-button> | ||
</p> | ||
</div> | ||
|
||
<go-hint *ngFor="let hint of hints" [message]="hint" [theme]="theme"> | ||
</go-hint> | ||
|
||
<div *ngIf="control?.errors?.length"> | ||
<go-hint | ||
*ngFor="let error of control.errors" | ||
[message]="error.message" | ||
[label]="error.type" | ||
[theme]="theme" | ||
type="negative" | ||
></go-hint> | ||
</div> |
87 changes: 87 additions & 0 deletions
87
projects/go-lib/src/lib/components/go-file-upload/go-file-upload.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
@import '../../../styles/variables'; | ||
|
||
@mixin upload-active { | ||
cursor: pointer; | ||
background-color: $theme-light-bg-hover; | ||
opacity: 0.8; | ||
} | ||
|
||
.go-file-upload__input { | ||
visibility: hidden; | ||
} | ||
|
||
.go-file-upload { | ||
height: 10rem; | ||
border: 1px dashed $theme-light-border; | ||
border-radius: $global-radius; | ||
} | ||
|
||
.go-file-upload--disabled { | ||
color: lighten($theme-light-color, 40); | ||
cursor: not-allowed; | ||
pointer-events: none; | ||
border-style: solid; | ||
} | ||
|
||
.go-file-upload--dark { | ||
border: 1px dashed $theme-dark-border; | ||
background-color: $theme-dark-bg; | ||
border-radius: $global-radius; | ||
color: $theme-dark-color; | ||
|
||
.go-file-upload:hover { | ||
background-color: $theme-dark-bg-hover; | ||
color: $theme-dark-color-hover | ||
} | ||
|
||
.go-file-upload--active { | ||
color: $theme-dark-color-hover; | ||
background-color: $theme-dark-bg-hover; | ||
} | ||
} | ||
|
||
.go-file-upload:hover { | ||
@include upload-active; | ||
} | ||
|
||
.go-file-upload--active { | ||
@include upload-active; | ||
border-style: solid; | ||
} | ||
|
||
.go-file-upload__container { | ||
justify-content: center; | ||
display: flex; | ||
flex-direction: column; | ||
text-align: center; | ||
} | ||
|
||
.go-file-upload__file-list { | ||
display: flex; | ||
justify-content: space-between; | ||
} | ||
|
||
.go-file-upload__file-name { | ||
margin-top: .5rem; | ||
padding-right: .5rem; | ||
} | ||
|
||
.go-file-upload__container--loading-text { | ||
padding-top: .5rem; | ||
} | ||
|
||
.go-file-upload__container--loading { | ||
padding-top: 1rem; | ||
} | ||
|
||
.file-name__label { | ||
padding-bottom: .25rem; | ||
} | ||
|
||
.go-file-upload__file-name--dark { | ||
color: $theme-dark-color; | ||
} | ||
|
||
.go-file-upload__selected-checkmark { | ||
color: $ui-color-positive; | ||
} |
113 changes: 113 additions & 0 deletions
113
projects/go-lib/src/lib/components/go-file-upload/go-file-upload.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { GoFileUploadComponent } from './go-file-upload.component'; | ||
import { CommonModule } from '@angular/common'; | ||
import { FormsModule, ReactiveFormsModule, FormArray, FormGroup, FormControl } from '@angular/forms'; | ||
import { GoButtonModule } from '../go-button/go-button.module'; | ||
import { GoHintModule } from '../go-hint/go-hint.module'; | ||
import { GoIconButtonModule } from '../go-icon-button/go-icon-button.module'; | ||
import { GoIconModule } from '../go-icon/go-icon.module'; | ||
import { GoLoaderModule } from '../go-loader/go-loader.module'; | ||
import { createHostListener } from '@angular/compiler/src/core'; | ||
|
||
describe('GoFileUploadComponent', () => { | ||
let component: GoFileUploadComponent; | ||
let fixture: ComponentFixture<GoFileUploadComponent>; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [GoFileUploadComponent], | ||
imports: [ | ||
CommonModule, | ||
FormsModule, | ||
GoButtonModule, | ||
GoHintModule, | ||
GoIconModule, | ||
GoIconButtonModule, | ||
GoLoaderModule, | ||
ReactiveFormsModule | ||
] | ||
}) | ||
.compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(GoFileUploadComponent); | ||
component = fixture.componentInstance; | ||
component.control = new FormControl(''); | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
describe('removeFile', () => { | ||
beforeEach(() => { | ||
const file: FormGroup = new FormGroup({ | ||
file: new FormControl('whatever') | ||
}); | ||
component.files = new FormArray([file]); | ||
component.filePreview = ['whatever']; | ||
}); | ||
|
||
it('should set state to selecting if it was previously selected', () => { | ||
component.state = 'selected'; | ||
component.removeFile(0); | ||
expect(component.state).toBe('selecting'); | ||
}); | ||
|
||
it('should not change the state if the state was selecting', () => { | ||
component.state = 'selecting'; | ||
component.removeFile(0); | ||
expect(component.state).toBe('selecting'); | ||
}); | ||
|
||
it('should remove the file at the given index from the form control', () => { | ||
component.removeFile(0); | ||
expect(component.control.value.length).toBe(0); | ||
}); | ||
|
||
it('should remove the file at the given index from our file Preview Array', () => { | ||
component.removeFile(0); | ||
expect(component.filePreview.length).toBe(0); | ||
}); | ||
}); | ||
|
||
describe('ngOnInit()', () => { | ||
beforeEach(() => { | ||
component.id = undefined; | ||
}); | ||
|
||
it('sets the id of the input to `key` if one is passed in', () => { | ||
expect(component.id).toBeUndefined(); | ||
|
||
component.key = 'a-specific-id'; | ||
component.ngOnInit(); | ||
|
||
expect(component.id).toBe(component.key); | ||
}); | ||
|
||
it('generates a semi-random id based on the label if key is undefined', () => { | ||
expect(component.key).toBeUndefined(); | ||
expect(component.id).toBeUndefined(); | ||
|
||
component.label = 'test label'; | ||
component.ngOnInit(); | ||
|
||
expect(component.id).toBeDefined(); | ||
expect(component.id).toContain('test-label-'); | ||
}); | ||
|
||
it('generates a semi-random id if a label and key are undefined', () => { | ||
expect(component.key).toBeUndefined(); | ||
expect(component.label).toBeUndefined(); | ||
expect(component.id).toBeUndefined(); | ||
|
||
component.ngOnInit(); | ||
|
||
expect(component.id).toBeDefined(); | ||
expect(component.id).toContain('file-upload-'); | ||
}); | ||
}); | ||
}); |
75 changes: 75 additions & 0 deletions
75
projects/go-lib/src/lib/components/go-file-upload/go-file-upload.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; | ||
import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; | ||
|
||
@Component({ | ||
selector: 'go-file-upload', | ||
templateUrl: './go-file-upload.component.html', | ||
styleUrls: ['./go-file-upload.component.scss'] | ||
}) | ||
export class GoFileUploadComponent implements OnInit { | ||
form: FormGroup; | ||
files: FormArray; | ||
fb: FormBuilder; | ||
filePreview: Array<string> = []; | ||
id: string; | ||
|
||
@Input() control: FormControl; | ||
@Input() hints: Array<string> = []; | ||
@Input() isLoading: boolean = false; | ||
@Input() key: string; | ||
@Input() label: string; | ||
@Input() multiple: boolean = false; | ||
@Input() state: 'selecting' | 'selected' = 'selecting'; | ||
@Input() theme: 'light' | 'dark' = 'light'; | ||
|
||
constructor() { } | ||
|
||
ngOnInit(): void { | ||
this.id = this.key || this.generateId(this.label); | ||
this.fb = new FormBuilder(); | ||
this.form = this.fb.group({ | ||
filesArray: this.fb.array([]) | ||
}); | ||
this.files = <FormArray>this.form.controls['filesArray']; | ||
this.files.valueChanges.subscribe( (fileChanges: any) => { | ||
this.control.setValue(fileChanges); | ||
}); | ||
} | ||
|
||
onFilePicked(evt: any): void { | ||
const files: File[] = evt.dataTransfer.files; | ||
if (files.length > 0) { | ||
Array.from(files).forEach((file: any) => { | ||
this.files.push(this.patchValues(file)); | ||
this.filePreview.push(file.name); | ||
}); | ||
if (!this.multiple) { | ||
this.state = 'selected'; | ||
} | ||
} | ||
} | ||
|
||
removeFile(index: number): void { | ||
this.files.removeAt(index); | ||
this.filePreview.splice(index, 1); | ||
if (this.state === 'selected') { | ||
this.state = 'selecting'; | ||
} | ||
} | ||
|
||
private patchValues(file: any): AbstractControl { | ||
return this.fb.group({ | ||
file: [file] | ||
}); | ||
} | ||
|
||
private generateId(label: string): string { | ||
const labelText: string = label || 'file-upload'; | ||
const idArray: Array<string> = labelText.split(' '); | ||
|
||
// NOTE: There is only a one in a million chance that this number is not unique. | ||
idArray.push(String(Math.round(Math.random() * 1000000))); | ||
|
||
return idArray.join('-'); | ||
} | ||
} |
Oops, something went wrong.