From a1d186cfd88b7cc1b350eb7148dd2dec5daef0b7 Mon Sep 17 00:00:00 2001
From: mdelez <60604010+mdelez@users.noreply.github.com>
Date: Fri, 13 Aug 2021 11:22:02 +0200
Subject: [PATCH 01/10] refactor(resource): migrates directives from ui-lib to
resources directory
---
package-lock.json | 10 +-
src/app/app.module.ts | 4 +
.../directives/drag-drop.directive.spec.ts | 124 ++++++++++++++++
.../directives/drag-drop.directive.ts | 34 +++++
.../text-value-html-link.directive.spec.ts | 134 ++++++++++++++++++
.../text-value-html-link.directive.ts | 44 ++++++
.../upload/upload.component.html | 2 +-
7 files changed, 348 insertions(+), 4 deletions(-)
create mode 100644 src/app/workspace/resource/directives/drag-drop.directive.spec.ts
create mode 100644 src/app/workspace/resource/directives/drag-drop.directive.ts
create mode 100644 src/app/workspace/resource/directives/text-value-html-link.directive.spec.ts
create mode 100644 src/app/workspace/resource/directives/text-value-html-link.directive.ts
diff --git a/package-lock.json b/package-lock.json
index d81119e53b..28ca850e47 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
+ "name": "dsp-app",
"version": "5.3.0",
"dependencies": {
"@angular/animations": "^11.2.9",
@@ -4916,6 +4917,7 @@
"version": "9.0.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"tslib": "^1.10.0"
}
@@ -4924,6 +4926,7 @@
"version": "9.0.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"rxjs": "^6.5.3",
"tslib": "^1.10.0",
@@ -12311,6 +12314,7 @@
"node_modules/pdfjs-dist": {
"version": "2.7.570",
"license": "Apache-2.0",
+ "peer": true,
"peerDependencies": {
"worker-loader": "^3.0.7"
}
@@ -21888,8 +21892,6 @@
"version": "6.0.2",
"dev": true,
"requires": {
- "@angular/compiler": "9.0.0",
- "@angular/core": "9.0.0",
"app-root-path": "^3.0.0",
"aria-query": "^3.0.0",
"axobject-query": "2.0.2",
@@ -21907,11 +21909,13 @@
"@angular/compiler": {
"version": "9.0.0",
"dev": true,
+ "peer": true,
"requires": {}
},
"@angular/core": {
"version": "9.0.0",
"dev": true,
+ "peer": true,
"requires": {}
},
"source-map": {
@@ -26180,7 +26184,6 @@
"ng2-pdf-viewer": {
"version": "7.0.1",
"requires": {
- "pdfjs-dist": "~2.7.570",
"tslib": "^2.0.0"
}
},
@@ -26985,6 +26988,7 @@
},
"pdfjs-dist": {
"version": "2.7.570",
+ "peer": true,
"requires": {}
},
"performance-now": {
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 6969afeda0..05255034c6 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -98,6 +98,8 @@ import { ResultsComponent } from './workspace/results/results.component';
import { AudioComponent } from './workspace/resource/representation/audio/audio.component';
import { IntermediateComponent } from './workspace/intermediate/intermediate.component';
import { ResourceLinkFormComponent } from './workspace/resource/resource-link-form/resource-link-form.component';
+import { DragDropDirective } from './workspace/resource/directives/drag-drop.directive';
+import { TextValueHtmlLinkDirective } from './workspace/resource/directives/text-value-html-link.directive';
// translate: AoT requires an exported function for factories
export function httpLoaderFactory(httpClient: HttpClient) {
@@ -182,6 +184,8 @@ export function httpLoaderFactory(httpClient: HttpClient) {
AudioComponent,
IntermediateComponent,
ResourceLinkFormComponent,
+ DragDropDirective,
+ TextValueHtmlLinkDirective,
],
imports: [
AppRoutingModule,
diff --git a/src/app/workspace/resource/directives/drag-drop.directive.spec.ts b/src/app/workspace/resource/directives/drag-drop.directive.spec.ts
new file mode 100644
index 0000000000..6fd3ddebd2
--- /dev/null
+++ b/src/app/workspace/resource/directives/drag-drop.directive.spec.ts
@@ -0,0 +1,124 @@
+import { Component, DebugElement } from '@angular/core';
+import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatSnackBarModule } from '@angular/material/snack-bar';
+import { By } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DragDropDirective } from './drag-drop.directive';
+
+/**
+ * test host component to simulate parent component.
+ */
+@Component({
+ template: `
+
`
+})
+class TestHostComponent {
+
+ files: FileList;
+
+ filesDropped(files: FileList) {
+ this.files = files;
+ }
+
+}
+
+describe('DragDropDirective', () => {
+ let testHostComponent: TestHostComponent;
+ let testHostFixture: ComponentFixture;
+ let dragDropInput: DebugElement;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ DragDropDirective,
+ TestHostComponent
+ ],
+ imports: [
+ BrowserAnimationsModule,
+ MatIconModule,
+ MatInputModule,
+ MatSnackBarModule,
+ ReactiveFormsModule
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ dragDropInput = testHostFixture.debugElement.query(By.css('.dd-container'));
+
+ expect(testHostComponent).toBeTruthy();
+ });
+
+ it('should create an instance', () => {
+ const directive = new DragDropDirective();
+ expect(directive).toBeTruthy();
+ });
+
+ it('should change background-color of input on dragover event', () => {
+ const dragOver = new DragEvent('dragover');
+ const color = 'rgb(221, 221, 221)'; // = #ddd
+
+ spyOn(dragOver, 'preventDefault');
+ spyOn(dragOver, 'stopPropagation');
+
+ dragDropInput.triggerEventHandler('dragover', dragOver);
+ testHostFixture.detectChanges();
+
+ expect(dragDropInput.nativeElement.style.backgroundColor).toBe(color);
+
+ expect(dragOver.stopPropagation).toHaveBeenCalled();
+ expect(dragOver.preventDefault).toHaveBeenCalled();
+ });
+
+ it('should change background-color of input on dragleave event', () => {
+ const dragLeave = new DragEvent('dragleave');
+ const color = 'rgb(242, 242, 242)'; // = #f2f2f2
+
+ spyOn(dragLeave, 'preventDefault');
+ spyOn(dragLeave, 'stopPropagation');
+
+ dragDropInput.triggerEventHandler('dragleave', dragLeave);
+ testHostFixture.detectChanges();
+
+ expect(dragDropInput.nativeElement.style.backgroundColor).toBe(color);
+
+ expect(dragLeave.stopPropagation).toHaveBeenCalled();
+ expect(dragLeave.preventDefault).toHaveBeenCalled();
+ });
+
+ it('should change background-color of input on drop event', () => {
+ const mockFile = new File(['1'], 'testfile');
+
+ // https://stackoverflow.com/questions/57080760/fake-file-drop-event-for-unit-testing
+ const drop = {
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ dataTransfer: { files: [mockFile] }
+ };
+
+ const color = 'rgb(242, 242, 242)'; // = #f2f2f2
+
+ spyOn(drop, 'preventDefault');
+ spyOn(drop, 'stopPropagation');
+
+ expect(testHostComponent.files).toBeUndefined();
+
+ dragDropInput.triggerEventHandler('drop', drop);
+ testHostFixture.detectChanges();
+
+ expect(dragDropInput.nativeElement.style.backgroundColor).toBe(color);
+ expect(testHostComponent.files.length).toEqual(1);
+ expect(testHostComponent.files[0].name).toEqual('testfile');
+
+ expect(drop.stopPropagation).toHaveBeenCalled();
+ expect(drop.preventDefault).toHaveBeenCalled();
+ });
+});
diff --git a/src/app/workspace/resource/directives/drag-drop.directive.ts b/src/app/workspace/resource/directives/drag-drop.directive.ts
new file mode 100644
index 0000000000..537e524208
--- /dev/null
+++ b/src/app/workspace/resource/directives/drag-drop.directive.ts
@@ -0,0 +1,34 @@
+import { Directive, EventEmitter, HostBinding, HostListener, Output } from '@angular/core';
+
+@Directive({
+ selector: '[appDragDrop]'
+})
+export class DragDropDirective {
+
+ @HostBinding('style.background-color') background = '#f2f2f2';
+
+ @Output() fileDropped = new EventEmitter();
+
+ @HostListener('dragover', ['$event']) onDragOver(event: DragEvent): void {
+ event.preventDefault();
+ event.stopPropagation();
+ this.background = '#ddd';
+ }
+
+ @HostListener('dragleave', ['$event']) onDragLeave(event: DragEvent): void {
+ event.preventDefault();
+ event.stopPropagation();
+ this.background = '#f2f2f2';
+ }
+
+ @HostListener('drop', ['$event']) onDrop(event: DragEvent): void {
+ event.preventDefault();
+ event.stopPropagation();
+ this.background = '#f2f2f2';
+ const files = event.dataTransfer.files;
+ if (files.length > 0) {
+ this.fileDropped.emit(files);
+ }
+ }
+
+}
diff --git a/src/app/workspace/resource/directives/text-value-html-link.directive.spec.ts b/src/app/workspace/resource/directives/text-value-html-link.directive.spec.ts
new file mode 100644
index 0000000000..9d20510fe3
--- /dev/null
+++ b/src/app/workspace/resource/directives/text-value-html-link.directive.spec.ts
@@ -0,0 +1,134 @@
+import { Component } from '@angular/core';
+import { TextValueHtmlLinkDirective } from './text-value-html-link.directive';
+import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { By } from '@angular/platform-browser';
+
+/**
+ * test host component to simulate parent component.
+ */
+@Component({
+ template: `
+ `
+})
+class TestHostComponent {
+
+ // the href attribute of the external link is empty
+ // because otherwise the test browser would attempt to access it
+ html = 'This is a test external link and a test internal link';
+
+ internalLinkClickedIri: string;
+
+ internalLinkHoveredIri: string;
+
+ clicked(iri: string) {
+ this.internalLinkClickedIri = iri;
+ }
+
+ hovered(iri: string) {
+ this.internalLinkHoveredIri = iri;
+ }
+}
+
+describe('TextValueHtmlLinkDirective', () => {
+ let testHostComponent: TestHostComponent;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ BrowserAnimationsModule
+ ],
+ declarations: [
+ TextValueHtmlLinkDirective,
+ TestHostComponent
+ ]
+ }).compileComponents();
+
+ }));
+
+ beforeEach(() => {
+
+ testHostFixture = TestBed.createComponent(TestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent).toBeTruthy();
+ });
+
+ it('should create an instance', () => {
+ expect(testHostComponent).toBeTruthy();
+ });
+
+ it('should react to clicking on an internal link', () => {
+ expect(testHostComponent).toBeTruthy();
+
+ const hostCompDe = testHostFixture.debugElement;
+ const directiveDe = hostCompDe.query(By.directive(TextValueHtmlLinkDirective));
+
+ const internalLinkDe = directiveDe.query(By.css('a.salsah-link'));
+
+ internalLinkDe.nativeElement.click();
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.internalLinkClickedIri).toEqual('http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw');
+
+ });
+
+ it('should not react to clicking on an external link', () => {
+ expect(testHostComponent).toBeTruthy();
+
+ const hostCompDe = testHostFixture.debugElement;
+ const directiveDe = hostCompDe.query(By.directive(TextValueHtmlLinkDirective));
+
+ const externalLinkDe = directiveDe.query(By.css('a:not(.salsah-link)'));
+
+ externalLinkDe.nativeElement.click();
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.internalLinkClickedIri).toBeUndefined();
+
+ });
+
+ it('should react to hovering over an internal link', () => {
+ expect(testHostComponent).toBeTruthy();
+
+ const hostCompDe = testHostFixture.debugElement;
+ const directiveDe = hostCompDe.query(By.directive(TextValueHtmlLinkDirective));
+
+ const internalLinkDe = directiveDe.query(By.css('a.salsah-link'));
+
+ internalLinkDe.nativeElement.dispatchEvent(new MouseEvent('mouseover', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ }));
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.internalLinkHoveredIri).toEqual('http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw');
+
+ });
+
+ it('should not react to hovering over an external link', () => {
+ expect(testHostComponent).toBeTruthy();
+
+ const hostCompDe = testHostFixture.debugElement;
+ const directiveDe = hostCompDe.query(By.directive(TextValueHtmlLinkDirective));
+
+ const externalLinkDe = directiveDe.query(By.css('a:not(.salsah-link)'));
+
+ externalLinkDe.nativeElement.dispatchEvent(new MouseEvent('mouseover', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ }));
+
+ testHostFixture.detectChanges();
+
+ expect(testHostComponent.internalLinkHoveredIri).toBeUndefined();
+
+ });
+});
diff --git a/src/app/workspace/resource/directives/text-value-html-link.directive.ts b/src/app/workspace/resource/directives/text-value-html-link.directive.ts
new file mode 100644
index 0000000000..72c9549cd0
--- /dev/null
+++ b/src/app/workspace/resource/directives/text-value-html-link.directive.ts
@@ -0,0 +1,44 @@
+import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
+import { Constants } from '@dasch-swiss/dsp-js';
+
+@Directive({
+ selector: '[appHtmlLink]'
+})
+export class TextValueHtmlLinkDirective {
+
+ @Output() internalLinkClicked = new EventEmitter();
+ @Output() internalLinkHovered = new EventEmitter();
+
+ /**
+ * react to a click event for an internal link.
+ *
+ * @param targetElement the element that was clicked.
+ */
+ @HostListener('click', ['$event.target'])
+ onClick(targetElement) {
+ if (targetElement.nodeName.toLowerCase() === 'a'
+ && targetElement.className.toLowerCase().indexOf(Constants.SalsahLink) !== -1) {
+ this.internalLinkClicked.emit(targetElement.href);
+
+ // preventDefault (propagation)
+ return false;
+ }
+ }
+
+ /**
+ * react to a mouseover event for an internal link.
+ *
+ * @param targetElement the element that was hovered.
+ */
+ @HostListener('mouseover', ['$event.target'])
+ onMouseOver(targetElement) {
+ if (targetElement.nodeName.toLowerCase() === 'a'
+ && targetElement.className.toLowerCase().indexOf(Constants.SalsahLink) !== -1) {
+ this.internalLinkHovered.emit(targetElement.href);
+
+ // preventDefault (propagation)
+ return false;
+ }
+ }
+
+}
diff --git a/src/app/workspace/resource/representation/upload/upload.component.html b/src/app/workspace/resource/representation/upload/upload.component.html
index 3611bdf72b..a61d2c605f 100644
--- a/src/app/workspace/resource/representation/upload/upload.component.html
+++ b/src/app/workspace/resource/representation/upload/upload.component.html
@@ -1,6 +1,6 @@