Skip to content

Commit

Permalink
feat(upload)!: add upload form for still images (DSP-1761) (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
kilchenmann committed Jun 28, 2021
1 parent d4222ba commit 2f314a2
Show file tree
Hide file tree
Showing 15 changed files with 791 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Expand Up @@ -91,6 +91,7 @@ import { UserMenuComponent } from './user/user-menu/user-menu.component';
import { UsersComponent } from './system/users/users.component';
import { UsersListComponent } from './system/users/users-list/users-list.component';
import { VisualizerComponent } from './project/ontology/ontology-visualizer/visualizer/visualizer.component';
import { UploadComponent } from './workspace/resource/representation/upload/upload.component';

// translate: AoT requires an exported function for factories
export function httpLoaderFactory(httpClient: HttpClient) {
Expand Down Expand Up @@ -169,6 +170,7 @@ export function httpLoaderFactory(httpClient: HttpClient) {
UsersComponent,
UsersListComponent,
VisualizerComponent,
UploadComponent,
],
imports: [
AppRoutingModule,
Expand Down
@@ -0,0 +1,99 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { AppInitService, Session, SessionService } from '@dasch-swiss/dsp-ui';
import { UploadedFileResponse, UploadFileService } from './upload-file.service';

describe('UploadFileService', () => {
let service: UploadFileService;
let httpTestingController: HttpTestingController;

const file = new File(['1'], 'testfile');
const mockUploadData = new FormData();
mockUploadData.append('test', file);

beforeEach(() => {

const appInitSpy = {
config: {
iiifUrl: 'https://sipi.dasch.swiss/'
}
};

const sessionSpy = jasmine.createSpyObj('SessionService', ['getSession']);

TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
{ provide: AppInitService, useValue: appInitSpy },
{ provide: SessionService, useValue: sessionSpy },
]
});

service = TestBed.inject(UploadFileService);
httpTestingController = TestBed.inject(HttpTestingController);

const sessionServiceSpy = TestBed.inject(SessionService) as jasmine.SpyObj<SessionService>;

sessionServiceSpy.getSession.and.callFake(
() => {
const session: Session = {
id: 12345,
user: {
name: 'username',
jwt: 'myToken',
lang: 'en',
sysAdmin: false,
projectAdmin: []
}
};

return session;
}
);
});

afterEach(() => {
// after every test, assert that there are no more pending requests.
httpTestingController.verify();
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should return expected file response on upload', done => {

const expectedResponse: UploadedFileResponse = {
uploadedFiles: [{
fileType: 'image',
internalFilename: '8R0cJE3TSgB-BssuQyeW1rE.jp2',
originalFilename: 'Screenshot 2020-10-28 at 14.16.34.png',
temporaryUrl: 'http://sipi:1024/tmp/8R0cJE3TSgB-BssuQyeW1rE.jp2'
}]
};

service.upload(mockUploadData).subscribe(
res => {
expect(res.uploadedFiles.length).toEqual(1);
expect(res.uploadedFiles[0].internalFilename).toEqual('8R0cJE3TSgB-BssuQyeW1rE.jp2');
done();
}
);

const httpRequest = httpTestingController.expectOne('https://sipi.dasch.swiss/upload?token=myToken');

expect(httpRequest.request.method).toEqual('POST');

const expectedFormData = new FormData();
const mockFile = new File(['1'], 'testfile', { type: 'image/jpeg' });

expectedFormData.append(mockFile.name, mockFile);
expect(httpRequest.request.body).toEqual(expectedFormData);

httpRequest.flush(expectedResponse);

});

});
@@ -0,0 +1,45 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AppInitService, SessionService } from '@dasch-swiss/dsp-ui';
import { Observable } from 'rxjs';

export interface UploadedFile {
fileType: string;
internalFilename: string;
originalFilename: string;
temporaryUrl: string;
}

export interface UploadedFileResponse {
uploadedFiles: UploadedFile[];
}

@Injectable({
providedIn: 'root'
})
export class UploadFileService {

iiifHost: string = (this._init.config['iiifUrl'].substr(-1) === '/') ? this._init.config['iiifUrl'] : this._init.config['iiifUrl'] + '/';

constructor(
private readonly _init: AppInitService,
private readonly _http: HttpClient,
private readonly _session: SessionService
) { }

/**
* uploads files to SIPI
* @param (file)
*/
upload(file: FormData): Observable<UploadedFileResponse> {
const baseUrl = `${this.iiifHost}upload`;

// checks if user is logged in
const jwt = this._session.getSession()?.user.jwt;
const params = new HttpParams().set('token', jwt);

// --> TODO in order to track the progress change below to true and 'events'
const options = { params, reportProgress: false, observe: 'body' as 'body' };
return this._http.post<UploadedFileResponse>(baseUrl, file, options);
}
}
@@ -0,0 +1,64 @@
<div class="form-container">
<form [formGroup]="form">
<div dspDragDrop
*ngIf="isLoading || !file"
(click)="fileInput.click()"
(fileDropped)="addFile($event)"
class="dd-container">
<input hidden
type="file"
(change)="addFile($event)"
#fileInput />
<mat-icon *ngIf="!isLoading"
class="upload-icon">
cloud_upload
</mat-icon>
<dsp-progress-indicator *ngIf="isLoading"></dsp-progress-indicator>
<div class="title">Upload file</div>
<div class="bottom-line">
Drag and drop or click to upload
</div>
</div>

<ng-container *ngIf="!isLoading && file">
<div class="thumbnail">
<img src="{{ thumbnailUrl }}"
alt="thumbnail" />
<button mat-button
class="delete-file"
title="delete file"
(click)="deleteAttachment()">
<mat-icon>close</mat-icon>
</button>
</div>

<div class="files-list">
<table>
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Last Modified Date</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ file.name }}</td>
<td>{{ convertBytes(file.size) }}</td>
<td>{{ convertDate(file.lastModified) }}</td>
<td>
<button mat-icon-button
class="delete-file"
title="delete file"
(click)="deleteAttachment()">
<mat-icon>delete</mat-icon>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</ng-container>
</form>
</div>
@@ -0,0 +1,86 @@
// overall styles

.full-width {
width: 100%;
}
.delete-file {
&:hover {
color: red;
}
}

// drag & drop input
.dd-container {
height: 200px;
margin: 20px 0;
border: 2px solid #000;
border-radius: 2px;
position: relative;
text-align: center;

&:hover {
cursor: pointer;
background-color: #ddd !important;
}
}
.upload-icon {
width: auto;
font-size: 48px;
padding: 0;
position: inherit;
top: 35%;
}
.title {
bottom: 20%;
}
.bottom-line,
.title {
width: 100%;
position: absolute;
padding: 5px 0;
}
.bottom-line {
bottom: 0;
color: #fff;
background-color: #000;
}

// thumbnail
.thumbnail {
text-align: center;

& button {
position: absolute;
padding: 0;
}

& mat-icon {
position: absolute;
}
}

// files table
.files-list {
display: flex;
justify-content: space-between;
// width: calc(80% + 4px);
margin: 10px auto;
background: #fff;
color: #000;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #9ecbec;
text-align: left;
}
td,
th {
border: 1px solid #000;
padding: 3px;
}
tr td:last-child {
text-align: center;
}

0 comments on commit 2f314a2

Please sign in to comment.