Skip to content

Commit 2f314a2

Browse files
authored
feat(upload)!: add upload form for still images (DSP-1761) (#472)
1 parent d4222ba commit 2f314a2

15 files changed

Lines changed: 791 additions & 16 deletions

‎src/app/app.module.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import { UserMenuComponent } from './user/user-menu/user-menu.component';
9191
import { UsersComponent } from './system/users/users.component';
9292
import { UsersListComponent } from './system/users/users-list/users-list.component';
9393
import { VisualizerComponent } from './project/ontology/ontology-visualizer/visualizer/visualizer.component';
94+
import { UploadComponent } from './workspace/resource/representation/upload/upload.component';
9495

9596
// translate: AoT requires an exported function for factories
9697
export function httpLoaderFactory(httpClient: HttpClient) {
@@ -169,6 +170,7 @@ export function httpLoaderFactory(httpClient: HttpClient) {
169170
UsersComponent,
170171
UsersListComponent,
171172
VisualizerComponent,
173+
UploadComponent,
172174
],
173175
imports: [
174176
AppRoutingModule,
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
2+
import { TestBed } from '@angular/core/testing';
3+
import { AppInitService, Session, SessionService } from '@dasch-swiss/dsp-ui';
4+
import { UploadedFileResponse, UploadFileService } from './upload-file.service';
5+
6+
describe('UploadFileService', () => {
7+
let service: UploadFileService;
8+
let httpTestingController: HttpTestingController;
9+
10+
const file = new File(['1'], 'testfile');
11+
const mockUploadData = new FormData();
12+
mockUploadData.append('test', file);
13+
14+
beforeEach(() => {
15+
16+
const appInitSpy = {
17+
config: {
18+
iiifUrl: 'https://sipi.dasch.swiss/'
19+
}
20+
};
21+
22+
const sessionSpy = jasmine.createSpyObj('SessionService', ['getSession']);
23+
24+
TestBed.configureTestingModule({
25+
imports: [
26+
HttpClientTestingModule
27+
],
28+
providers: [
29+
{ provide: AppInitService, useValue: appInitSpy },
30+
{ provide: SessionService, useValue: sessionSpy },
31+
]
32+
});
33+
34+
service = TestBed.inject(UploadFileService);
35+
httpTestingController = TestBed.inject(HttpTestingController);
36+
37+
const sessionServiceSpy = TestBed.inject(SessionService) as jasmine.SpyObj<SessionService>;
38+
39+
sessionServiceSpy.getSession.and.callFake(
40+
() => {
41+
const session: Session = {
42+
id: 12345,
43+
user: {
44+
name: 'username',
45+
jwt: 'myToken',
46+
lang: 'en',
47+
sysAdmin: false,
48+
projectAdmin: []
49+
}
50+
};
51+
52+
return session;
53+
}
54+
);
55+
});
56+
57+
afterEach(() => {
58+
// after every test, assert that there are no more pending requests.
59+
httpTestingController.verify();
60+
});
61+
62+
it('should be created', () => {
63+
expect(service).toBeTruthy();
64+
});
65+
66+
it('should return expected file response on upload', done => {
67+
68+
const expectedResponse: UploadedFileResponse = {
69+
uploadedFiles: [{
70+
fileType: 'image',
71+
internalFilename: '8R0cJE3TSgB-BssuQyeW1rE.jp2',
72+
originalFilename: 'Screenshot 2020-10-28 at 14.16.34.png',
73+
temporaryUrl: 'http://sipi:1024/tmp/8R0cJE3TSgB-BssuQyeW1rE.jp2'
74+
}]
75+
};
76+
77+
service.upload(mockUploadData).subscribe(
78+
res => {
79+
expect(res.uploadedFiles.length).toEqual(1);
80+
expect(res.uploadedFiles[0].internalFilename).toEqual('8R0cJE3TSgB-BssuQyeW1rE.jp2');
81+
done();
82+
}
83+
);
84+
85+
const httpRequest = httpTestingController.expectOne('https://sipi.dasch.swiss/upload?token=myToken');
86+
87+
expect(httpRequest.request.method).toEqual('POST');
88+
89+
const expectedFormData = new FormData();
90+
const mockFile = new File(['1'], 'testfile', { type: 'image/jpeg' });
91+
92+
expectedFormData.append(mockFile.name, mockFile);
93+
expect(httpRequest.request.body).toEqual(expectedFormData);
94+
95+
httpRequest.flush(expectedResponse);
96+
97+
});
98+
99+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { HttpClient, HttpParams } from '@angular/common/http';
2+
import { Injectable } from '@angular/core';
3+
import { AppInitService, SessionService } from '@dasch-swiss/dsp-ui';
4+
import { Observable } from 'rxjs';
5+
6+
export interface UploadedFile {
7+
fileType: string;
8+
internalFilename: string;
9+
originalFilename: string;
10+
temporaryUrl: string;
11+
}
12+
13+
export interface UploadedFileResponse {
14+
uploadedFiles: UploadedFile[];
15+
}
16+
17+
@Injectable({
18+
providedIn: 'root'
19+
})
20+
export class UploadFileService {
21+
22+
iiifHost: string = (this._init.config['iiifUrl'].substr(-1) === '/') ? this._init.config['iiifUrl'] : this._init.config['iiifUrl'] + '/';
23+
24+
constructor(
25+
private readonly _init: AppInitService,
26+
private readonly _http: HttpClient,
27+
private readonly _session: SessionService
28+
) { }
29+
30+
/**
31+
* uploads files to SIPI
32+
* @param (file)
33+
*/
34+
upload(file: FormData): Observable<UploadedFileResponse> {
35+
const baseUrl = `${this.iiifHost}upload`;
36+
37+
// checks if user is logged in
38+
const jwt = this._session.getSession()?.user.jwt;
39+
const params = new HttpParams().set('token', jwt);
40+
41+
// --> TODO in order to track the progress change below to true and 'events'
42+
const options = { params, reportProgress: false, observe: 'body' as 'body' };
43+
return this._http.post<UploadedFileResponse>(baseUrl, file, options);
44+
}
45+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<div class="form-container">
2+
<form [formGroup]="form">
3+
<div dspDragDrop
4+
*ngIf="isLoading || !file"
5+
(click)="fileInput.click()"
6+
(fileDropped)="addFile($event)"
7+
class="dd-container">
8+
<input hidden
9+
type="file"
10+
(change)="addFile($event)"
11+
#fileInput />
12+
<mat-icon *ngIf="!isLoading"
13+
class="upload-icon">
14+
cloud_upload
15+
</mat-icon>
16+
<dsp-progress-indicator *ngIf="isLoading"></dsp-progress-indicator>
17+
<div class="title">Upload file</div>
18+
<div class="bottom-line">
19+
Drag and drop or click to upload
20+
</div>
21+
</div>
22+
23+
<ng-container *ngIf="!isLoading && file">
24+
<div class="thumbnail">
25+
<img src="{{ thumbnailUrl }}"
26+
alt="thumbnail" />
27+
<button mat-button
28+
class="delete-file"
29+
title="delete file"
30+
(click)="deleteAttachment()">
31+
<mat-icon>close</mat-icon>
32+
</button>
33+
</div>
34+
35+
<div class="files-list">
36+
<table>
37+
<thead>
38+
<tr>
39+
<th>Name</th>
40+
<th>Size</th>
41+
<th>Last Modified Date</th>
42+
<th>Delete</th>
43+
</tr>
44+
</thead>
45+
<tbody>
46+
<tr>
47+
<td>{{ file.name }}</td>
48+
<td>{{ convertBytes(file.size) }}</td>
49+
<td>{{ convertDate(file.lastModified) }}</td>
50+
<td>
51+
<button mat-icon-button
52+
class="delete-file"
53+
title="delete file"
54+
(click)="deleteAttachment()">
55+
<mat-icon>delete</mat-icon>
56+
</button>
57+
</td>
58+
</tr>
59+
</tbody>
60+
</table>
61+
</div>
62+
</ng-container>
63+
</form>
64+
</div>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// overall styles
2+
3+
.full-width {
4+
width: 100%;
5+
}
6+
.delete-file {
7+
&:hover {
8+
color: red;
9+
}
10+
}
11+
12+
// drag & drop input
13+
.dd-container {
14+
height: 200px;
15+
margin: 20px 0;
16+
border: 2px solid #000;
17+
border-radius: 2px;
18+
position: relative;
19+
text-align: center;
20+
21+
&:hover {
22+
cursor: pointer;
23+
background-color: #ddd !important;
24+
}
25+
}
26+
.upload-icon {
27+
width: auto;
28+
font-size: 48px;
29+
padding: 0;
30+
position: inherit;
31+
top: 35%;
32+
}
33+
.title {
34+
bottom: 20%;
35+
}
36+
.bottom-line,
37+
.title {
38+
width: 100%;
39+
position: absolute;
40+
padding: 5px 0;
41+
}
42+
.bottom-line {
43+
bottom: 0;
44+
color: #fff;
45+
background-color: #000;
46+
}
47+
48+
// thumbnail
49+
.thumbnail {
50+
text-align: center;
51+
52+
& button {
53+
position: absolute;
54+
padding: 0;
55+
}
56+
57+
& mat-icon {
58+
position: absolute;
59+
}
60+
}
61+
62+
// files table
63+
.files-list {
64+
display: flex;
65+
justify-content: space-between;
66+
// width: calc(80% + 4px);
67+
margin: 10px auto;
68+
background: #fff;
69+
color: #000;
70+
}
71+
table {
72+
width: 100%;
73+
border-collapse: collapse;
74+
}
75+
th {
76+
background-color: #9ecbec;
77+
text-align: left;
78+
}
79+
td,
80+
th {
81+
border: 1px solid #000;
82+
padding: 3px;
83+
}
84+
tr td:last-child {
85+
text-align: center;
86+
}

0 commit comments

Comments
 (0)