Combine custom component and ngx-file-drop

This commit is contained in:
shamoon 2023-09-26 23:12:19 -07:00
parent 9588f899f9
commit 86cba60755
9 changed files with 261 additions and 86 deletions

View File

@ -1,16 +1,10 @@
<pngx-toasts></pngx-toasts>
<ngx-file-drop dropZoneClassName="main-dropzone" contentClassName="main-content" [disabled]="!dragDropEnabled"
(onFileDrop)="dropped($event)" (onFileOver)="fileOver()" (onFileLeave)="fileLeave()">
<ng-template ngx-file-drop-content-tmp>
<div class="global-dropzone-overlay">
<h2 i18n>Drop files to begin upload</h2>
</div>
<div class="main-wrapper">
<pngx-file-drop>
<ng-container content>
<router-outlet></router-outlet>
</div>
</ng-template>
</ngx-file-drop>
</ng-container>
</pngx-file-drop>
<tour-step-template>
<ng-template #tourStep let-step="step">

View File

@ -5,10 +5,8 @@ import {
fakeAsync,
tick,
} from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { Router } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop'
import { TourService, TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import { Subject } from 'rxjs'
import { routes } from './app-routing.module'
@ -20,8 +18,9 @@ import {
} from './services/consumer-status.service'
import { PermissionsService } from './services/permissions.service'
import { ToastService, Toast } from './services/toast.service'
import { UploadDocumentsService } from './services/upload-documents.service'
import { SettingsService } from './services/settings.service'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { NgxFileDropModule } from 'ngx-file-drop'
describe('AppComponent', () => {
let component: AppComponent
@ -32,11 +31,10 @@ describe('AppComponent', () => {
let toastService: ToastService
let router: Router
let settingsService: SettingsService
let uploadDocumentsService: UploadDocumentsService
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [AppComponent, ToastsComponent],
declarations: [AppComponent, ToastsComponent, FileDropComponent],
providers: [],
imports: [
HttpClientTestingModule,
@ -52,7 +50,6 @@ describe('AppComponent', () => {
settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService)
router = TestBed.inject(Router)
uploadDocumentsService = TestBed.inject(UploadDocumentsService)
fixture = TestBed.createComponent(AppComponent)
component = fixture.componentInstance
})
@ -142,43 +139,4 @@ describe('AppComponent', () => {
fileStatusSubject.next(new FileStatus())
expect(toastSpy).toHaveBeenCalled()
})
it('should enable drag-drop if user has permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.dragDropEnabled).toBeTruthy()
})
it('should disable drag-drop if user does not have permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
expect(component.dragDropEnabled).toBeFalsy()
})
it('should support drag drop', fakeAsync(() => {
expect(settingsService.globalDropzoneActive).toBeFalsy()
component.fileOver()
tick(1)
fixture.detectChanges()
expect(settingsService.globalDropzoneActive).toBeTruthy()
const dropzone = fixture.debugElement.query(
By.css('.global-dropzone-overlay')
)
expect(dropzone).not.toBeNull()
tick(700)
fixture.detectChanges()
// drop
const toastSpy = jest.spyOn(toastService, 'show')
const uploadSpy = jest.spyOn(uploadDocumentsService, 'onNgxFileDrop')
component.dropped([
{
fileEntry: {
isFile: true,
file: () => {},
},
} as unknown as NgxFileDropEntry,
])
expect(settingsService.globalDropzoneActive).toBeFalsy()
tick(3000)
expect(toastSpy).toHaveBeenCalled()
expect(uploadSpy).toHaveBeenCalled()
}))
})

View File

@ -5,8 +5,6 @@ import { Router } from '@angular/router'
import { Subscription, first } from 'rxjs'
import { ConsumerStatusService } from './services/consumer-status.service'
import { ToastService } from './services/toast.service'
import { NgxFileDropEntry } from 'ngx-file-drop'
import { UploadDocumentsService } from './services/upload-documents.service'
import { TasksService } from './services/tasks.service'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import {
@ -30,7 +28,6 @@ export class AppComponent implements OnInit, OnDestroy {
private consumerStatusService: ConsumerStatusService,
private toastService: ToastService,
private router: Router,
private uploadDocumentsService: UploadDocumentsService,
private tasksService: TasksService,
public tourService: TourService,
private renderer: Renderer2,
@ -246,29 +243,4 @@ export class AppComponent implements OnInit, OnDestroy {
})
})
}
public get dragDropEnabled(): boolean {
return (
this.settings.globalDropzoneEnabled &&
this.permissionsService.currentUserCan(
PermissionAction.Add,
PermissionType.Document
)
)
}
public fileOver() {
this.settings.globalDropzoneActive = true
}
public fileLeave() {
this.settings.globalDropzoneActive = false
}
public dropped(files: NgxFileDropEntry[]) {
this.fileLeave()
this.uploadDocumentsService.onNgxFileDrop(files)
if (files.length > 0)
this.toastService.showInfo($localize`Initiating upload...`, 3000)
}
}

View File

@ -100,6 +100,7 @@ import { ConsumptionTemplateEditDialogComponent } from './components/common/edit
import { MailComponent } from './components/manage/mail/mail.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { DndModule } from 'ngx-drag-drop'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import localeAf from '@angular/common/locales/af'
import localeAr from '@angular/common/locales/ar'
@ -242,6 +243,7 @@ function initializeApp(settings: SettingsService) {
ConsumptionTemplateEditDialogComponent,
MailComponent,
UsersAndGroupsComponent,
FileDropComponent,
],
imports: [
BrowserModule,

View File

@ -0,0 +1,14 @@
<div [class.inert]="fileIsOver">
<ng-content select="[content]"></ng-content>
</div>
<div class="global-dropzone-overlay fade" [class.show]="fileIsOver" [class.hide]="hidden">
<h2 class="pe-none" i18n>Drop files to begin upload</h2>
</div>
<ngx-file-drop
dropZoneClassName="visually-hidden"
contentClassName="visually-hidden"
(onFileDrop)="dropped($event)"
#ngxFileDrop>
</ngx-file-drop>

View File

@ -0,0 +1,148 @@
import { HttpClientTestingModule } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
discardPeriodicTasks,
fakeAsync,
tick,
} from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { PermissionsService } from 'src/app/services/permissions.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import { ToastsComponent } from '../common/toasts/toasts.component'
import { FileDropComponent } from './file-drop.component'
import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop'
describe('FileDropComponent', () => {
let component: FileDropComponent
let fixture: ComponentFixture<FileDropComponent>
let permissionsService: PermissionsService
let toastService: ToastService
let settingsService: SettingsService
let uploadDocumentsService: UploadDocumentsService
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FileDropComponent, ToastsComponent],
providers: [],
imports: [HttpClientTestingModule, NgxFileDropModule],
}).compileComponents()
permissionsService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService)
uploadDocumentsService = TestBed.inject(UploadDocumentsService)
fixture = TestBed.createComponent(FileDropComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should enable drag-drop if user has permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.dragDropEnabled).toBeTruthy()
})
it('should disable drag-drop if user does not have permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
expect(component.dragDropEnabled).toBeFalsy()
})
it('should disable drag-drop if disabled in settings', fakeAsync(() => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
settingsService.globalDropzoneEnabled = false
expect(component.dragDropEnabled).toBeFalsy()
component.onDragOver(new Event('dragover') as DragEvent)
tick(1)
fixture.detectChanges()
expect(component.fileIsOver).toBeFalsy()
const dropzone = fixture.debugElement.query(
By.css('.global-dropzone-overlay')
)
expect(dropzone.classes['hide']).toBeTruthy()
component.onDragLeave(new Event('dragleave') as DragEvent)
tick(700)
fixture.detectChanges()
// drop
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
const dragEvent = new Event('drop')
dragEvent['dataTransfer'] = {
files: {
item: () => {},
length: 0,
},
}
component.onDrop(dragEvent as DragEvent)
tick(3000)
expect(uploadSpy).not.toHaveBeenCalled()
}))
it('should support drag drop, initiate upload', fakeAsync(() => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.fileIsOver).toBeFalsy()
component.onDragOver(new Event('dragover') as DragEvent)
tick(1)
fixture.detectChanges()
expect(component.fileIsOver).toBeTruthy()
const dropzone = fixture.debugElement.query(
By.css('.global-dropzone-overlay')
)
component.onDragLeave(new Event('dragleave') as DragEvent)
tick(700)
fixture.detectChanges()
expect(dropzone.classes['hide']).toBeTruthy()
// drop
const toastSpy = jest.spyOn(toastService, 'show')
const uploadSpy = jest.spyOn(
UploadDocumentsService.prototype as any,
'uploadFile'
)
const dragEvent = new Event('drop')
dragEvent['dataTransfer'] = {
files: {
item: () => {
return new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
},
length: 1,
} as unknown as FileList,
}
component.onDrop(dragEvent as DragEvent)
component.dropped([
{
fileEntry: {
isFile: true,
file: (callback) => {
callback(
new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
)
},
},
} as unknown as NgxFileDropEntry,
])
tick(3000)
expect(toastSpy).toHaveBeenCalled()
expect(uploadSpy).toHaveBeenCalled()
discardPeriodicTasks()
}))
it('should ignore events if disabled', fakeAsync(() => {
settingsService.globalDropzoneEnabled = false
expect(settingsService.globalDropzoneActive).toBeFalsy()
component.onDragOver(new Event('dragover') as DragEvent)
expect(settingsService.globalDropzoneActive).toBeFalsy()
settingsService.globalDropzoneActive = true
component.onDragLeave(new Event('dragleave') as DragEvent)
expect(settingsService.globalDropzoneActive).toBeTruthy()
component.onDrop(new Event('drop') as DragEvent)
expect(settingsService.globalDropzoneActive).toBeTruthy()
}))
})

View File

@ -0,0 +1,89 @@
import { Component, HostListener, ViewChild } from '@angular/core'
import { NgxFileDropComponent, NgxFileDropEntry } from 'ngx-file-drop'
import {
PermissionsService,
PermissionAction,
PermissionType,
} from 'src/app/services/permissions.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
@Component({
selector: 'pngx-file-drop',
templateUrl: './file-drop.component.html',
styleUrls: ['./file-drop.component.scss'],
})
export class FileDropComponent {
private fileLeaveTimeoutID: any
fileIsOver: boolean = false
hidden: boolean = true
constructor(
private settings: SettingsService,
private toastService: ToastService,
private uploadDocumentsService: UploadDocumentsService,
private permissionsService: PermissionsService
) {}
public get dragDropEnabled(): boolean {
return (
this.settings.globalDropzoneEnabled &&
this.permissionsService.currentUserCan(
PermissionAction.Add,
PermissionType.Document
)
)
}
@ViewChild('ngxFileDrop') ngxFileDrop: NgxFileDropComponent
@HostListener('dragover', ['$event ']) onDragOver(event: DragEvent) {
if (!this.dragDropEnabled) return
event.preventDefault()
event.stopImmediatePropagation()
this.settings.globalDropzoneActive = true
// allows transition
setTimeout(() => {
this.fileIsOver = true
}, 1)
this.hidden = false
// stop fileLeave timeout
clearTimeout(this.fileLeaveTimeoutID)
}
@HostListener('dragleave', ['$event']) public onDragLeave(
event: DragEvent,
immediate: boolean = false
) {
if (!this.dragDropEnabled) return
event.preventDefault()
event.stopImmediatePropagation()
this.settings.globalDropzoneActive = false
const ms = immediate ? 0 : 500
this.fileLeaveTimeoutID = setTimeout(() => {
this.fileIsOver = false
// await transition completed
setTimeout(() => {
this.hidden = true
}, 150)
}, ms)
}
@HostListener('drop', ['$event']) public onDrop(event: DragEvent) {
if (!this.dragDropEnabled) return
event.preventDefault()
event.stopImmediatePropagation()
this.onDragLeave(event, true)
// pass event onto ngx-file-drop to handle files
this.ngxFileDrop.dropFiles(event)
}
public dropped(files: NgxFileDropEntry[]) {
this.uploadDocumentsService.onNgxFileDrop(files)
if (files.length > 0)
this.toastService.showInfo($localize`Initiating upload...`, 3000)
}
}

View File

@ -505,8 +505,6 @@ table.table {
user-select: none !important;
text-align: center;
padding-top: 25%;
opacity: 0;
transition: opacity 0.05s linear;
h2 {
color: var(--pngx-primary-text-contrast)