diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index eefc10c16e..3bfbf2de5b 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -357,7 +357,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { observableCombineLatest( this.authorizationService.isAuthorized(FeatureID.AdministratorOf), - this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME) + // this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME) ).pipe( // TODO uncomment when #635 (https://github.com/DSpace/dspace-angular/issues/635) is fixed; otherwise even in production mode, the metadata export button is only available after a refresh (and not in dev mode) // filter(([authorized, metadataExportScriptExists]: boolean[]) => authorized && metadataExportScriptExists), @@ -416,7 +416,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { observableCombineLatest( this.authorizationService.isAuthorized(FeatureID.AdministratorOf), - this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME) + // this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME) ).pipe( // TODO uncomment when #635 (https://github.com/DSpace/dspace-angular/issues/635) is fixed // filter(([authorized, metadataImportScriptExists]: boolean[]) => authorized && metadataImportScriptExists), diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.html b/src/app/shared/confirmation-modal/confirmation-modal.component.html new file mode 100644 index 0000000000..bb67213ed3 --- /dev/null +++ b/src/app/shared/confirmation-modal/confirmation-modal.component.html @@ -0,0 +1,16 @@ +
+ + +
diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts b/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts new file mode 100644 index 0000000000..63930fc4ed --- /dev/null +++ b/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts @@ -0,0 +1,117 @@ +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; +import { ConfirmationModalComponent } from './confirmation-modal.component'; + +describe('ConfirmationModalComponent', () => { + let component: ConfirmationModalComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ConfirmationModalComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmationModalComponent); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('close', () => { + beforeEach(() => { + component.close(); + }); + it('should call the close method on the active modal', () => { + expect(modalStub.close).toHaveBeenCalled(); + }); + }); + + describe('confirmPressed', () => { + beforeEach(() => { + spyOn(component.response, 'emit'); + component.confirmPressed(); + }); + it('should call the close method on the active modal', () => { + expect(modalStub.close).toHaveBeenCalled(); + }); + it('event emitter should emit true', () => { + expect(component.response.emit).toHaveBeenCalledWith(true); + }); + }); + + describe('cancelPressed', () => { + beforeEach(() => { + spyOn(component.response, 'emit'); + component.cancelPressed(); + }); + it('should call the close method on the active modal', () => { + expect(modalStub.close).toHaveBeenCalled(); + }); + it('event emitter should emit false', () => { + expect(component.response.emit).toHaveBeenCalledWith(false); + }); + }); + + describe('when the click method emits on close button', () => { + beforeEach(fakeAsync(() => { + spyOn(component, 'close'); + debugElement.query(By.css('button.close')).triggerEventHandler('click', {}); + tick(); + fixture.detectChanges(); + })); + it('should call the close method on the component', () => { + expect(component.close).toHaveBeenCalled(); + }); + }); + + describe('when the click method emits on cancel button', () => { + beforeEach(fakeAsync(() => { + spyOn(component, 'close'); + spyOn(component.response, 'emit'); + debugElement.query(By.css('button.cancel')).triggerEventHandler('click', {}); + tick(); + fixture.detectChanges(); + })); + it('should call the close method on the component', () => { + expect(component.close).toHaveBeenCalled(); + }); + it('event emitter should emit false', () => { + expect(component.response.emit).toHaveBeenCalledWith(false); + }); + }); + + describe('when the click method emits on confirm button', () => { + beforeEach(fakeAsync(() => { + spyOn(component, 'close'); + spyOn(component.response, 'emit'); + debugElement.query(By.css('button.confirm')).triggerEventHandler('click', {}); + tick(); + fixture.detectChanges(); + })); + it('should call the close method on the component', () => { + expect(component.close).toHaveBeenCalled(); + }); + it('event emitter should emit true', () => { + expect(component.response.emit).toHaveBeenCalledWith(true); + }); + }); + +}); diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.ts b/src/app/shared/confirmation-modal/confirmation-modal.component.ts new file mode 100644 index 0000000000..e52d26021f --- /dev/null +++ b/src/app/shared/confirmation-modal/confirmation-modal.component.ts @@ -0,0 +1,48 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-confirmation-modal', + templateUrl: 'confirmation-modal.component.html', +}) +export class ConfirmationModalComponent { + @Input() headerLabel: string; + @Input() infoLabel: string; + @Input() cancelLabel: string; + @Input() confirmLabel: string; + @Input() dso: DSpaceObject; + + /** + * An event fired when the cancel or confirm button is clicked, with respectively false or true + */ + @Output() + response = new EventEmitter(); + + constructor(protected activeModal: NgbActiveModal) { + } + + /** + * Confirm the action that led to the modal + */ + confirmPressed() { + this.response.emit(true); + this.close(); + } + + /** + * Cancel the action that led to the modal and close modal + */ + cancelPressed() { + this.response.emit(false); + this.close(); + } + + /** + * Close the modal + */ + close() { + this.activeModal.close(); + } +} diff --git a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts index 4fd01d52ef..647e77584c 100644 --- a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts @@ -4,16 +4,19 @@ import { TranslateService } from '@ngx-translate/core'; import { Observable } from 'rxjs/internal/Observable'; import { take, map } from 'rxjs/operators'; import { of as observableOf } from 'rxjs'; +import { AuthService } from '../../../../core/auth/auth.service'; import { METADATA_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service'; import { RequestEntry } from '../../../../core/data/request.reducer'; import { Collection } from '../../../../core/shared/collection.model'; import { Community } from '../../../../core/shared/community.model'; import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; import { isNotEmpty } from '../../../empty.util'; import { NotificationsService } from '../../../notifications/notifications.service'; +import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; /** @@ -21,7 +24,7 @@ import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-sel * Used to choose a dso from to export metadata of */ @Component({ - selector: 'ds-edit-item-selector', + selector: 'ds-export-metadata-selector', templateUrl: '../dso-selector-modal-wrapper.component.html', }) export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { @@ -31,7 +34,8 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router, protected notificationsService: NotificationsService, protected translationService: TranslateService, - protected scriptDataService: ScriptDataService) { + protected scriptDataService: ScriptDataService, + private modalService: NgbModal) { super(activeModal, route); } @@ -41,9 +45,23 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp */ navigate(dso: DSpaceObject): Observable { if (dso instanceof Collection || dso instanceof Community) { - const startScriptSucceeded = this.startScriptNotifyAndRedirect(dso, dso.handle); - startScriptSucceeded.pipe(take(1)).subscribe(); - return startScriptSucceeded; + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = dso; + modalRef.componentInstance.headerLabel = "confirmation-modal.export-metadata.header"; + modalRef.componentInstance.infoLabel = "confirmation-modal.export-metadata.info"; + modalRef.componentInstance.cancelLabel = "confirmation-modal.export-metadata.cancel"; + modalRef.componentInstance.confirmLabel = "confirmation-modal.export-metadata.confirm"; + + modalRef.componentInstance.response.subscribe((confirm: boolean) => { + if (confirm) { + const startScriptSucceeded = this.startScriptNotifyAndRedirect(dso, dso.handle); + startScriptSucceeded.pipe(take(1)).subscribe(); + return startScriptSucceeded; + } else { + const modalRef = this.modalService.open(ExportMetadataSelectorComponent); + modalRef.componentInstance.dsoRD = createSuccessfulRemoteDataObject(dso); + } + }); } else { this.notificationsService.error(this.translationService.get('dso-selector.export-metadata.notValidDSO')); return observableOf(false); diff --git a/src/app/shared/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts b/src/app/shared/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts index 473250d9f6..0ce822bc0e 100644 --- a/src/app/shared/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts +++ b/src/app/shared/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts @@ -51,15 +51,17 @@ export class FileDropzoneNoUploaderComponent implements OnInit { ngOnInit() { this.uploaderId = 'ds-drag-and-drop-uploader' + uniqueId(); this.isOverDocumentDropZone = observableOf(false); - window.addEventListener('drop', (e: DragEvent) => { - return e && e.preventDefault(); - }, false); this.uploader = new FileUploader({ // required, but using onFileDrop, not uploader url: 'placeholder', }); } + @HostListener('window:drop', ['$event']) + onDrop(event: any) { + event.preventDefault(); + } + @HostListener('window:dragover', ['$event']) onDragOver(event: any) { // Show drop area on the page diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 8e4e958097..66bdea9217 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -10,6 +10,7 @@ import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core' import { NgxPaginationModule } from 'ngx-pagination'; import { ComcolRoleComponent } from './comcol-forms/edit-comcol-page/comcol-role/comcol-role.component'; +import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component'; import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; import { FileDropzoneNoUploaderComponent } from './file-dropzone-no-uploader/file-dropzone-no-uploader.component'; import { PublicationListElementComponent } from './object-list/item-list-element/item-types/publication/publication-list-element.component'; @@ -401,7 +402,8 @@ const COMPONENTS = [ GroupSearchBoxComponent, FileDownloadLinkComponent, CollectionDropdownComponent, - ExportMetadataSelectorComponent + ExportMetadataSelectorComponent, + ConfirmationModalComponent ]; const ENTRY_COMPONENTS = [ @@ -478,7 +480,8 @@ const ENTRY_COMPONENTS = [ ClaimedTaskActionsEditMetadataComponent, FileDownloadLinkComponent, CurationFormComponent, - ExportMetadataSelectorComponent + ExportMetadataSelectorComponent, + ConfirmationModalComponent ]; const SHARED_ITEM_PAGE_COMPONENTS = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a77abbcbd4..d1816847fb 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -995,6 +995,16 @@ + "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", + + "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", + + "confirmation-modal.export-metadata.cancel": "Cancel", + + "confirmation-modal.export-metadata.confirm": "Export", + + + "error.bitstream": "Error fetching bitstream", "error.browse-by": "Error fetching items", diff --git a/src/environments/mock-environment.ts b/src/environments/mock-environment.ts index 6e4d60e268..57a747f374 100644 --- a/src/environments/mock-environment.ts +++ b/src/environments/mock-environment.ts @@ -105,7 +105,7 @@ export const environment: Partial = { }, // Angular Universal settings universal: { - preboot: true, + preboot: false, async: true, time: false },