forked from UoEMainLibrary/dspace-datashare-angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuploader.component.ts
More file actions
277 lines (248 loc) · 9.56 KB
/
Copy pathuploader.component.ts
File metadata and controls
277 lines (248 loc) · 9.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
import { CommonModule } from '@angular/common';
import { HttpXsrfTokenExtractor } from '@angular/common/http';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
HostListener,
Input,
OnInit,
Output,
ViewEncapsulation,
} from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import uniqueId from 'lodash/uniqueId';
import {
FileUploader,
FileUploadModule,
} from 'ng2-file-upload';
import { of as observableOf } from 'rxjs';
import { DragService } from '../../../core/drag.service';
import { CookieService } from '../../../core/services/cookie.service';
import {
XSRF_COOKIE,
XSRF_REQUEST_HEADER,
XSRF_RESPONSE_HEADER,
} from '../../../core/xsrf/xsrf.constants';
import { BtnDisabledDirective } from '../../btn-disabled.directive';
import {
hasValue,
isNotEmpty,
isUndefined,
} from '../../empty.util';
import { UploaderCompleteEvent } from './uploader-complete-event.model';
import { UploaderError } from './uploader-error.model';
import { UploaderOptions } from './uploader-options.model';
import { UploaderProperties } from './uploader-properties.model';
@Component({
selector: 'ds-uploader',
templateUrl: 'uploader.component.html',
styleUrls: ['uploader.component.scss'],
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
standalone: true,
imports: [TranslateModule, FileUploadModule, CommonModule, BtnDisabledDirective],
})
export class UploaderComponent implements OnInit, AfterViewInit {
/**
* Header key to impersonate a user
*/
private readonly ON_BEHALF_HEADER = 'X-On-Behalf-Of';
/**
* The message to show when drag files on the drop zone
*/
@Input() dropMsg: string;
/**
* The message to show when drag files on the window document
*/
@Input() dropOverDocumentMsg: string;
/**
* The message to show when drag files on the window document
*/
@Input() enableDragOverDocument: boolean;
/**
* The function to call before an upload
*/
@Input() onBeforeUpload: () => void;
/**
* Configuration for the ng2-file-upload component.
*/
@Input() uploadFilesOptions: UploaderOptions;
/**
* Extra properties to be passed with the form-data of the upload
*/
@Input() uploadProperties: UploaderProperties;
/**
* The aria label to describe what kind of files need to be uploaded
*/
@Input() ariaLabel: string;
/**
* The function to call when upload is completed
*/
@Output() onCompleteItem: EventEmitter<any> = new EventEmitter<any>();
/**
* The function to call when upload is completed, carrying the parsed response together with the
* client-side file name. Emitted alongside {@link onCompleteItem} so existing consumers are unaffected.
*/
@Output() onCompleteItemWithFile: EventEmitter<UploaderCompleteEvent> = new EventEmitter<UploaderCompleteEvent>();
/**
* The function to call on error occurred
*/
@Output() onUploadError: EventEmitter<UploaderError> = new EventEmitter<UploaderError>();
/**
* The function to call when a file is selected
*/
@Output() onFileSelected: EventEmitter<any> = new EventEmitter<any>();
public uploader: FileUploader;
public uploaderId: string;
public isOverBaseDropZone = observableOf(false);
public isOverDocumentDropZone = observableOf(false);
@HostListener('window:dragover', ['$event'])
onDragOver(event: any) {
if (this.enableDragOverDocument && this.dragService.isAllowedDragOverPage()) {
// Show drop area on the page
event.preventDefault();
if ((event.target as any).tagName !== 'HTML') {
this.isOverDocumentDropZone = observableOf(true);
}
}
}
constructor(
private cdr: ChangeDetectorRef,
private dragService: DragService,
private tokenExtractor: HttpXsrfTokenExtractor,
private cookieService: CookieService,
) {
}
/**
* Method provided by Angular. Invoked after the constructor.
*/
ngOnInit(): void {
this.uploaderId = 'ds-drag-and-drop-uploader' + uniqueId();
this.checkConfig(this.uploadFilesOptions);
this.uploader = new FileUploader({
url: this.uploadFilesOptions.url,
authToken: this.uploadFilesOptions.authToken,
disableMultipart: this.uploadFilesOptions.disableMultipart,
itemAlias: this.uploadFilesOptions.itemAlias,
removeAfterUpload: true,
autoUpload: this.uploadFilesOptions.autoUpload,
method: this.uploadFilesOptions.method,
queueLimit: this.uploadFilesOptions.maxFileNumber,
});
if (isUndefined(this.enableDragOverDocument)) {
this.enableDragOverDocument = false;
}
if (isUndefined(this.dropMsg)) {
this.dropMsg = 'uploader.drag-message';
}
if (isUndefined(this.dropOverDocumentMsg)) {
this.dropOverDocumentMsg = 'uploader.drag-message';
}
}
ngAfterViewInit(): void {
this.uploader.onAfterAddingAll = ((items) => {
this.onFileSelected.emit(items);
});
if (isUndefined(this.onBeforeUpload)) {
this.onBeforeUpload = () => {return;};
}
this.uploader.onBeforeUploadItem = (item) => {
if (item.url !== this.uploader.options.url) {
item.url = this.uploader.options.url;
}
// Ensure the current XSRF token is included in every upload request (token may change between items uploaded)
// Ensure the behalf header is set if impersonating
this.uploader.options.headers = [
{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() },
];
if (hasValue(this.uploadFilesOptions.impersonatingID)) {
this.uploader.options.headers.push({ name: this.ON_BEHALF_HEADER, value: this.uploadFilesOptions.impersonatingID });
}
this.onBeforeUpload();
this.isOverDocumentDropZone = observableOf(false);
};
if (hasValue(this.uploadProperties)) {
this.uploader.onBuildItemForm = (item, form) => {
form.append('properties', JSON.stringify(this.uploadProperties));
};
}
this.uploader.onCompleteItem = (item: any, response: any, status: any, headers: any) => {
// Check for a changed XSRF token in response & save new token if found (to both cookie & header for next request)
// NOTE: this is only necessary because ng2-file-upload doesn't use an Http service and therefore never
// triggers our xsrf.interceptor.ts. See this bug: https://github.com/valor-software/ng2-file-upload/issues/950
const token = headers[XSRF_RESPONSE_HEADER.toLowerCase()];
if (isNotEmpty(token)) {
this.saveXsrfToken(token);
this.uploader.options.headers = [{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() }];
}
if (isNotEmpty(response)) {
const responsePath = JSON.parse(response);
this.onCompleteItem.emit(responsePath);
const fileName = item?.file?.name;
this.onCompleteItemWithFile.emit(hasValue(fileName) ? { response: responsePath, fileName } : { response: responsePath });
}
};
this.uploader.onErrorItem = (item: any, response: any, status: any, headers: any) => {
// Check for a changed XSRF token in response & save new token if found (to both cookie & header for next request)
// NOTE: this is only necessary because ng2-file-upload doesn't use an Http service and therefore never
// triggers our xsrf.interceptor.ts. See this bug: https://github.com/valor-software/ng2-file-upload/issues/950
const token = headers[XSRF_RESPONSE_HEADER.toLowerCase()];
if (isNotEmpty(token)) {
this.saveXsrfToken(token);
this.uploader.options.headers = [{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() }];
}
this.onUploadError.emit({ item: item, response: response, status: status, headers: headers });
this.uploader.cancelAll();
};
this.uploader.onProgressAll = () => this.onProgress();
this.uploader.onProgressItem = () => this.onProgress();
}
/**
* Called when files are dragged on the base drop area.
*/
public fileOverBase(isOver: boolean): void {
this.isOverBaseDropZone = observableOf(isOver);
}
/**
* Called when files are dragged on the window document drop area.
*/
public fileOverDocument(isOver: boolean) {
if (!isOver) {
this.isOverDocumentDropZone = observableOf(isOver);
}
}
private onProgress() {
this.cdr.detectChanges();
}
/**
* Ensure options passed contains the required properties.
*
* @param fileUploadOptions
* The upload-files options object.
*/
private checkConfig(fileUploadOptions: any) {
const required = ['url', 'authToken', 'disableMultipart', 'itemAlias'];
const missing = required.filter((prop) => {
return !((prop in fileUploadOptions) && fileUploadOptions[prop] !== '');
});
if (0 < missing.length) {
throw new Error('UploadFiles: Argument is missing the following required properties: ' + missing.join(', '));
}
}
/**
* Save XSRF token found in response. This is a temporary copy of the method in xsrf.interceptor.ts
* It can be removed once ng2-file-upload supports interceptors (see https://github.com/valor-software/ng2-file-upload/issues/950),
* or we switch to a new upload library (see https://github.com/DSpace/dspace-angular/issues/820)
* @param token token found
*/
private saveXsrfToken(token: string) {
// Save token value as a *new* value of our client-side XSRF-TOKEN cookie.
// This is the cookie that is parsed by Angular's tokenExtractor(),
// which we will send back in the X-XSRF-TOKEN header per Angular best practices.
this.cookieService.remove(XSRF_COOKIE);
this.cookieService.set(XSRF_COOKIE, token);
}
}