1
|
import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
2
|
import {ActivatedRoute} from '@angular/router';
|
3
|
import {Title} from '@angular/platform-browser';
|
4
|
import {AlertModal} from "../../../openaireLibrary/utils/modal/alert";
|
5
|
import {CuratorService} from "../../../openaireLibrary/connect/curators/curator.service";
|
6
|
import {UtilitiesService} from "../../../openaireLibrary/services/utilities.service";
|
7
|
import {UserManagementService} from "../../../openaireLibrary/services/user-management.service";
|
8
|
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
|
9
|
import {Subscriber} from "rxjs";
|
10
|
import {EnvProperties} from "../../../openaireLibrary/utils/properties/env-properties";
|
11
|
import {properties} from "../../../../environments/environment";
|
12
|
import {User} from "../../../openaireLibrary/login/utils/helper.class";
|
13
|
import {Affiliation, Curator} from "../../../openaireLibrary/utils/entities/CuratorInfo";
|
14
|
import {HelpContentService} from "../../../services/help-content.service";
|
15
|
import {Page} from "../../../domain/page";
|
16
|
import {CommunityService} from "../../../openaireLibrary/connect/community/community.service";
|
17
|
import {StringUtils} from "../../../openaireLibrary/utils/string-utils.class";
|
18
|
|
19
|
declare var UIkit;
|
20
|
|
21
|
@Component({
|
22
|
selector: 'personal-info',
|
23
|
template: `
|
24
|
<div page-content>
|
25
|
<div header>
|
26
|
<users-tabs tab="personal"></users-tabs>
|
27
|
</div>
|
28
|
<div inner>
|
29
|
<div class="uk-card-header">
|
30
|
<div class="uk-flex uk-child-width-1-1 uk-child-width-1-2@m uk-grid" uk-grid>
|
31
|
<div>
|
32
|
<div class="uk-text-small title">
|
33
|
Personal Info & Affiliations
|
34
|
<span *ngIf="!loading && (curatorFb && curatorFb.dirty && !newCurator)"> (unsaved changes)</span>
|
35
|
</div>
|
36
|
</div>
|
37
|
<div class="uk-text-right@m uk-text-center">
|
38
|
<button class="uk-button uk-button-secondary outlined uk-margin-right"
|
39
|
(click)="reset()"
|
40
|
[disabled]="loading || !hasChanged">Reset
|
41
|
</button>
|
42
|
<button class="uk-button uk-button-secondary"
|
43
|
(click)="updateCurator()"
|
44
|
[disabled]="loading || !hasChanged || curatorFb.invalid">Save
|
45
|
</button>
|
46
|
</div>
|
47
|
</div>
|
48
|
</div>
|
49
|
<div class="uk-card uk-card-default uk-position-relative" style="min-height: 60vh">
|
50
|
<div [class.hidden]="loading" style="max-height: 60vh" class="uk-overflow-auto uk-padding-remove-bottom uk-padding-large">
|
51
|
<form *ngIf="curatorFb" [formGroup]="curatorFb">
|
52
|
<div class="uk-grid uk-margin-large-bottom" uk-grid>
|
53
|
<div class="uk-grid uk-width-1-1 uk-flex-middle" uk-grid>
|
54
|
<div>
|
55
|
<div class="image">
|
56
|
<img [src]="photo"/>
|
57
|
<input #fileInput id="photo" type="file" class="uk-hidden" (change)="fileChangeEvent($event)"/>
|
58
|
<icon class="uk-text-secondary clickable" name="photo" ratio="1.5"
|
59
|
(click)="$event.stopPropagation();uploadPhoto(fileInput);$event.preventDefault()"></icon>
|
60
|
<div *ngIf="curator.photo || file" #element
|
61
|
uk-dropdown="mode: click; pos: bottom-left; delay-hide: 0; flip: false">
|
62
|
<ul class="uk-nav uk-dropdown-nav">
|
63
|
<li><a (click)="fileInput.click();hide(element)">Upload a new photo</a></li>
|
64
|
<li><a (click)="removePhoto();hide(element)">Remove this photo</a></li>
|
65
|
</ul>
|
66
|
</div>
|
67
|
</div>
|
68
|
</div>
|
69
|
<div class="uk-width-expand">
|
70
|
<h5 class="uk-text-secondary uk-text-bold">{{user.firstname + ' ' + user.lastname}}</h5>
|
71
|
<!--<div>
|
72
|
<span class="connected">Connected with: </span>
|
73
|
<span class="uk-text-secondary uk-text-bold">{{user.email}}</span>
|
74
|
</div>-->
|
75
|
</div>
|
76
|
</div>
|
77
|
<div dashboard-input class="uk-width-1-1" label="Display Name" placeholder="Write your name"
|
78
|
[formInput]="curatorFb.get('name')"></div>
|
79
|
<div dashboard-input class="uk-width-1-1" label="Biography" placeholder="Write biography..."
|
80
|
[formInput]="curatorFb.get('bio')" type="textarea" rows="4"></div>
|
81
|
<div class="uk-width-1-1">
|
82
|
<h5 class="uk-margin-large uk-text-bold">My Affiliations</h5>
|
83
|
<div class="uk-flex uk-flex-center">
|
84
|
<a (click)="editAffiliationOpen()" class="uk-flex uk-flex-middle uk-text-uppercase">
|
85
|
<button class="large uk-icon-button uk-button-secondary">
|
86
|
<icon name="add"></icon>
|
87
|
</button>
|
88
|
<button class="uk-button uk-button-link uk-margin-small-left uk-text-secondary">
|
89
|
Add New Affiliation
|
90
|
</button>
|
91
|
</a>
|
92
|
</div>
|
93
|
<div class="uk-margin-medium">
|
94
|
<div *ngFor="let affiliation of affiliations.controls; let i=index"
|
95
|
class="uk-card uk-card-default uk-card-body uk-text-small uk-margin-bottom">
|
96
|
<div class="uk-grid uk-grid-divider uk-flex-middle" uk-grid>
|
97
|
<div class="uk-width-expand@m uk-width-1-1 uk-grid uk-flex-middle" uk-grid>
|
98
|
<div class="uk-width-small">
|
99
|
<img [src]="affiliation.value.logo_url | urlPrefix">
|
100
|
</div>
|
101
|
<div class="uk-width-auto">
|
102
|
<h6>{{affiliation.value.name}}</h6>
|
103
|
URL: <a [href]="affiliation.value.website_url | urlPrefix" target="_blank">{{affiliation.value.website_url}}</a>
|
104
|
</div>
|
105
|
</div>
|
106
|
<div class="uk-width-auto@m uk-width-1-1">
|
107
|
<div class="uk-width-1-1 uk-flex uk-flex-center">
|
108
|
<div class="uk-padding-small uk-padding-remove-horizontal">
|
109
|
<a (click)="editAffiliationOpen(i)" class="uk-button action uk-flex uk-flex-middle">
|
110
|
<icon name="edit" ratio="0.7"></icon>
|
111
|
<span class="uk-margin-small-left">Edit Affiliation</span>
|
112
|
</a>
|
113
|
<a (click)="deleteAffiliationOpen(i)" class="uk-button action uk-flex uk-flex-middle uk-margin-small-top">
|
114
|
<icon name="remove" ratio="0.7"></icon>
|
115
|
<span class="uk-margin-small-left">Delete Affiliation</span>
|
116
|
</a>
|
117
|
</div>
|
118
|
</div>
|
119
|
</div>
|
120
|
</div>
|
121
|
</div>
|
122
|
</div>
|
123
|
</div>
|
124
|
<div class="uk-width-1-1 uk-text-small">
|
125
|
Your personal info will be visible in the Curators page of your Community Gateway.
|
126
|
Read <a (click)="privacy()">privacy policy statement</a>.
|
127
|
</div>
|
128
|
</div>
|
129
|
</form>
|
130
|
</div>
|
131
|
<div *ngIf="loading" class="uk-position-center">
|
132
|
<loading></loading>
|
133
|
</div>
|
134
|
</div>
|
135
|
</div>
|
136
|
</div>
|
137
|
<modal-alert #privacyStatement (alertOutput)="privacyStatement.cancel()">
|
138
|
<div class="uk-text-small">
|
139
|
Your personal data and photo are processed by OpenAIRE in conformity with personal data protection legal
|
140
|
framework.
|
141
|
They will be stored safely in our system for as long as OpenAIRE exists. Since you press the "save" button,
|
142
|
you give us the consent to make them public in your Community Gateway to let users know who is
|
143
|
configuring the platform. You always have the right to exercise your rights and ask for access,
|
144
|
rectification, erasure and restriction of your data. Please contact <a href="mailto:rcd@openaire.eu">rcd@openaire.eu</a>
|
145
|
if you have any inquiries.
|
146
|
</div>
|
147
|
</modal-alert>
|
148
|
<modal-alert #affiliationModal [okDisabled]="affiliationFb && affiliationFb.invalid" (alertOutput)="editAffiliation()">
|
149
|
<form *ngIf="affiliationFb" [formGroup]="affiliationFb">
|
150
|
<div class="uk-grid uk-padding uk-padding-remove-horizontal uk-child-width-1-1" uk-grid>
|
151
|
<div dashboard-input label="Name" placeholder="Write affiliation's name" [formInput]="affiliationFb.get('name')"></div>
|
152
|
<div dashboard-input label="Logo URL" type="logoURL" placeholder="Write your affiliation's logo URL" [formInput]="affiliationFb.get('logo_url')"></div>
|
153
|
<div dashboard-input label="Website URL" type="URL" placeholder="Write your affiliation's website URL" [formInput]="affiliationFb.get('website_url')"></div>
|
154
|
</div>
|
155
|
</form>
|
156
|
</modal-alert>
|
157
|
<modal-alert #removeAffiliationModal (alertOutput)="removeAffiliation()">
|
158
|
</modal-alert>
|
159
|
<modal-alert #enableCuratorsModal (alertOutput)="enableCurators()">
|
160
|
<div class="uk-padding uk-padding-remove-horizontal">
|
161
|
Your personal information has been successfully saved.<br><br>
|
162
|
This information will be visible in <span class="uk-text-bold">Curators page</span> of Research Community Dashboard, which is <span class="uk-text-bold">disabled</span>.
|
163
|
Do you want to <span class="uk-text-bold">enable</span> it now?
|
164
|
</div>
|
165
|
</modal-alert>
|
166
|
`,
|
167
|
styleUrls: ['personal-info.component.css']
|
168
|
})
|
169
|
export class PersonalInfoComponent implements OnInit, OnDestroy {
|
170
|
/** Curator information */
|
171
|
public loading = false;
|
172
|
public user: User;
|
173
|
public curator: Curator;
|
174
|
public curatorFb: FormGroup;
|
175
|
public properties: EnvProperties = properties;
|
176
|
public curatorsPage: Page;
|
177
|
public newCurator = false;
|
178
|
public communityId: string;
|
179
|
/** Photo */
|
180
|
public photo: any = null;
|
181
|
private photoChanged: boolean = false;
|
182
|
public file: File = null;
|
183
|
private maxsize: number = 200 * 1024;
|
184
|
private deletePhoto = false;
|
185
|
private subs: any[] = [];
|
186
|
/** Affiliations */
|
187
|
public affiliationFb: FormGroup;
|
188
|
public index: number = -1;
|
189
|
@ViewChild('fileInput') fileInput: ElementRef;
|
190
|
@ViewChild('privacyStatement') privacyStatement: AlertModal;
|
191
|
@ViewChild('affiliationModal') affiliationModal: AlertModal;
|
192
|
@ViewChild('removeAffiliationModal') removeAffiliationModal: AlertModal;
|
193
|
@ViewChild('enableCuratorsModal') enableCuratorsModal: AlertModal;
|
194
|
|
195
|
constructor(private route: ActivatedRoute,
|
196
|
private title: Title,
|
197
|
private fb: FormBuilder,
|
198
|
private curatorService: CuratorService,
|
199
|
private utilitiesService: UtilitiesService,
|
200
|
private helpContentService: HelpContentService,
|
201
|
private communityService: CommunityService,
|
202
|
private userManagementService: UserManagementService) {
|
203
|
}
|
204
|
|
205
|
|
206
|
ngOnInit() {
|
207
|
this.subs.push(this.communityService.getCommunityAsObservable().subscribe(community => {
|
208
|
this.communityId = community.communityId;
|
209
|
this.subs.push(this.userManagementService.getUserInfo().subscribe(user => {
|
210
|
this.user = user;
|
211
|
if (this.user) {
|
212
|
this.title.setTitle(community.communityId.toUpperCase() + " | Personal Info");
|
213
|
this.loading = true;
|
214
|
this.subs.push(this.curatorService.getCurator(properties).subscribe(curator => {
|
215
|
this.initCurator(curator);
|
216
|
this.reset();
|
217
|
this.loading = false;
|
218
|
}, error => {
|
219
|
if (error.status === 404) {
|
220
|
this.initCurator(null);
|
221
|
this.reset();
|
222
|
} else {
|
223
|
console.error(error);
|
224
|
}
|
225
|
this.loading = false;
|
226
|
}));
|
227
|
}
|
228
|
}));
|
229
|
}));
|
230
|
}
|
231
|
|
232
|
ngOnDestroy() {
|
233
|
this.subs.forEach(subscription => {
|
234
|
if (subscription instanceof Subscriber) {
|
235
|
subscription.unsubscribe();
|
236
|
}
|
237
|
})
|
238
|
}
|
239
|
|
240
|
hide(element: any) {
|
241
|
UIkit.dropdown(element).hide();
|
242
|
}
|
243
|
|
244
|
initCurator(curator: Curator) {
|
245
|
if (curator) {
|
246
|
this.curator = curator;
|
247
|
this.curator.email = this.user.email;
|
248
|
} else {
|
249
|
this.newCurator = true;
|
250
|
this.curator = new Curator();
|
251
|
this.curator._id = this.user.id;
|
252
|
this.curator.email = this.user.email;
|
253
|
this.curator.name = this.user.fullname;
|
254
|
this.curator.affiliations = [];
|
255
|
this.curator.bio = '';
|
256
|
this.curator.photo = null;
|
257
|
}
|
258
|
this.curatorsPageStatus();
|
259
|
}
|
260
|
|
261
|
reset() {
|
262
|
this.photoChanged = false;
|
263
|
this.file = null;
|
264
|
if (this.fileInput) {
|
265
|
this.fileInput.nativeElement.value = null;
|
266
|
}
|
267
|
let affiliations: FormArray = this.fb.array([]);
|
268
|
this.curator.affiliations.forEach(affiliation => {
|
269
|
affiliations.push(this.fb.group({
|
270
|
id: this.fb.control(affiliation.id),
|
271
|
name: this.fb.control(affiliation.name, Validators.required),
|
272
|
logo_url: this.fb.control(affiliation.logo_url, [Validators.required, StringUtils.urlValidator()]),
|
273
|
website_url: this.fb.control(affiliation.website_url, [Validators.required, StringUtils.urlValidator()]),
|
274
|
}));
|
275
|
});
|
276
|
this.curatorFb = this.fb.group({
|
277
|
_id: this.fb.control(this.curator._id),
|
278
|
name: this.fb.control(this.curator.name, Validators.required),
|
279
|
bio: this.fb.control(this.curator.bio),
|
280
|
email: this.fb.control(this.curator.email),
|
281
|
photo: this.fb.control(this.curator.photo),
|
282
|
affiliations: affiliations
|
283
|
});
|
284
|
if (this.curator.photo) {
|
285
|
this.photo = this.properties.utilsService + '/download/' + this.curator.photo;
|
286
|
} else {
|
287
|
this.photo = 'assets/common-assets/curator-default.png';
|
288
|
}
|
289
|
}
|
290
|
|
291
|
get affiliations(): FormArray {
|
292
|
return this.curatorFb.get('affiliations') as FormArray;
|
293
|
}
|
294
|
|
295
|
saveCurator() {
|
296
|
this.curatorService.updateCurator(this.properties, this.curatorFb.value).subscribe((curator) => {
|
297
|
if (curator) {
|
298
|
UIkit.notification('Your data has been <b>saved successfully</b>', {
|
299
|
status: 'success',
|
300
|
timeout: 6000,
|
301
|
pos: 'bottom-right'
|
302
|
});
|
303
|
this.newCurator = false;
|
304
|
this.deletePhoto = false;
|
305
|
this.initCurator(curator);
|
306
|
this.reset();
|
307
|
if(!this.curatorsEnabled) {
|
308
|
this.curatorsEnabledOpen();
|
309
|
}
|
310
|
this.loading = false;
|
311
|
}
|
312
|
},
|
313
|
error => {
|
314
|
this.handleUpdateError('An error has occurred. Try again later!');
|
315
|
this.reset();
|
316
|
});
|
317
|
}
|
318
|
|
319
|
|
320
|
updateCurator() {
|
321
|
if (this.curatorFb.valid) {
|
322
|
this.loading = true;
|
323
|
if (this.file) {
|
324
|
this.utilitiesService.uploadPhoto(this.properties.utilsService + '/upload/' + this.curator._id, this.file).subscribe((res) => {
|
325
|
if (this.curator.photo) {
|
326
|
this.utilitiesService.deletePhoto(this.properties.utilsService + '/delete/' + this.curator.photo).subscribe();
|
327
|
}
|
328
|
this.curatorFb.get('photo').setValue(res.filename);
|
329
|
this.saveCurator();
|
330
|
}, error => {
|
331
|
this.handleUpdateError('An error has occurred during photo uploading.');
|
332
|
}
|
333
|
);
|
334
|
} else {
|
335
|
if (this.deletePhoto && this.curator.photo) {
|
336
|
this.utilitiesService.deletePhoto(this.properties.utilsService + '/delete/' + this.curator.photo).subscribe();
|
337
|
this.curatorFb.get('photo').setValue(null);
|
338
|
}
|
339
|
this.saveCurator();
|
340
|
}
|
341
|
}
|
342
|
}
|
343
|
|
344
|
private curatorsPageStatus() {
|
345
|
this.helpContentService.getCommunityPagesByRoute(this.communityId, '/curators', this.properties.adminToolsAPIURL).subscribe((page) => {
|
346
|
this.curatorsPage = page;
|
347
|
});
|
348
|
}
|
349
|
|
350
|
public get curatorsEnabled(): boolean {
|
351
|
return !this.curatorsPage || this.curatorsPage.isEnabled;
|
352
|
}
|
353
|
|
354
|
fileChangeEvent(event) {
|
355
|
this.loading = true;
|
356
|
if (event.target.files && event.target.files[0]) {
|
357
|
this.file = event.target.files[0];
|
358
|
if (this.file.type !== 'image/png' && this.file.type !== 'image/jpeg') {
|
359
|
this.handleUpdateError('You must choose a file with type: image/png or image/jpeg!');
|
360
|
this.file = null;
|
361
|
} else if (this.file.size > this.maxsize) {
|
362
|
this.handleUpdateError('File exceeds size\'s limit! Maximum size 200KB.');
|
363
|
this.file = null;
|
364
|
} else {
|
365
|
const reader = new FileReader();
|
366
|
reader.readAsDataURL(this.file);
|
367
|
reader.onload = () => {
|
368
|
this.photo = reader.result;
|
369
|
this.photoChanged = true;
|
370
|
this.loading = false;
|
371
|
};
|
372
|
}
|
373
|
} else {
|
374
|
this.loading = false;
|
375
|
}
|
376
|
}
|
377
|
|
378
|
removePhoto() {
|
379
|
this.deletePhoto = true;
|
380
|
this.file = null;
|
381
|
this.fileInput.nativeElement.value = null;
|
382
|
this.photoChanged = !!this.curator.photo;
|
383
|
this.photo = 'assets/common-assets/curator-default.png';
|
384
|
}
|
385
|
|
386
|
handleUpdateError(message: string) {
|
387
|
UIkit.notification(message, {
|
388
|
status: 'danger',
|
389
|
timeout: 6000,
|
390
|
pos: 'bottom-right'
|
391
|
});
|
392
|
this.loading = false;
|
393
|
}
|
394
|
|
395
|
privacy() {
|
396
|
this.privacyStatement.cancelButton = false;
|
397
|
this.privacyStatement.okButtonText = 'Close';
|
398
|
this.privacyStatement.alertTitle = 'Privacy policy statement';
|
399
|
this.privacyStatement.open();
|
400
|
}
|
401
|
|
402
|
uploadPhoto(fileInput: HTMLInputElement) {
|
403
|
if (!this.curator.photo && !this.file) {
|
404
|
fileInput.click();
|
405
|
}
|
406
|
}
|
407
|
|
408
|
get hasChanged(): boolean {
|
409
|
return (this.curatorFb && this.curatorFb.dirty) || this.newCurator || this.photoChanged;
|
410
|
}
|
411
|
|
412
|
editAffiliationOpen(index = -1) {
|
413
|
this.index = index;
|
414
|
let affiliation: Affiliation = new Affiliation();
|
415
|
if(index === -1) {
|
416
|
this.affiliationModal.alertTitle = 'Add Affiliation';
|
417
|
this.affiliationModal.okButtonText = 'Add';
|
418
|
} else {
|
419
|
this.affiliationModal.alertTitle = 'Edit Affiliation';
|
420
|
this.affiliationModal.okButtonText = 'Update';
|
421
|
affiliation = this.affiliations.at(index).value;
|
422
|
}
|
423
|
this.affiliationFb = this.fb.group({
|
424
|
id: this.fb.control(affiliation.id),
|
425
|
name: this.fb.control(affiliation.name, Validators.required),
|
426
|
logo_url: this.fb.control(affiliation.logo_url, [Validators.required, StringUtils.urlValidator()]),
|
427
|
website_url: this.fb.control(affiliation.website_url, [Validators.required, StringUtils.urlValidator()])
|
428
|
});
|
429
|
this.affiliationModal.okButtonLeft = false;
|
430
|
this.affiliationModal.cancelButtonText = 'Cancel';
|
431
|
this.affiliationModal.open();
|
432
|
}
|
433
|
|
434
|
deleteAffiliationOpen(index: number) {
|
435
|
this.index = index;
|
436
|
let affiliation: Affiliation = this.affiliations.at(index).value;
|
437
|
this.removeAffiliationModal.alertTitle = 'Delete Affiliation';
|
438
|
this.removeAffiliationModal.message = 'Do you want to remove <b>' +
|
439
|
affiliation.name + '</b> from your Affiliations?';
|
440
|
this.removeAffiliationModal.okButtonText = 'Yes';
|
441
|
this.removeAffiliationModal.cancelButtonText = 'No';
|
442
|
this.removeAffiliationModal.open();
|
443
|
}
|
444
|
|
445
|
editAffiliation() {
|
446
|
if(this.index === -1) {
|
447
|
this.affiliations.push(this.affiliationFb);
|
448
|
} else {
|
449
|
this.affiliations.at(this.index).setValue(this.affiliationFb.value);
|
450
|
}
|
451
|
this.curatorFb.markAsDirty();
|
452
|
}
|
453
|
|
454
|
removeAffiliation() {
|
455
|
this.affiliations.removeAt(this.index);
|
456
|
this.curatorFb.markAsDirty();
|
457
|
}
|
458
|
|
459
|
private curatorsEnabledOpen() {
|
460
|
this.enableCuratorsModal.okButtonLeft = false;
|
461
|
this.enableCuratorsModal.alertTitle = 'Enable Curators Page';
|
462
|
this.enableCuratorsModal.okButtonText = 'Yes';
|
463
|
this.enableCuratorsModal.cancelButtonText = 'No';
|
464
|
this.enableCuratorsModal.open();
|
465
|
}
|
466
|
|
467
|
enableCurators() {
|
468
|
this.helpContentService.togglePages(this.communityId, [this.curatorsPage._id], true, this.properties.adminToolsAPIURL).subscribe(() => {
|
469
|
this.curatorsPage.isEnabled = true;
|
470
|
UIkit.notification('Curators Page has been <b>enabled successfully</b>', {
|
471
|
status: 'success',
|
472
|
timeout: 6000,
|
473
|
pos: 'bottom-right'
|
474
|
});
|
475
|
},error => {
|
476
|
this.handleUpdateError('An error has occurred. Try again later!');
|
477
|
});
|
478
|
}
|
479
|
}
|