Production release November 2023 #20

Merged
argiro.kokogiannaki merged 75 commits from develop into master 2023-11-07 09:48:32 +01:00
128 changed files with 7217 additions and 869 deletions

View File

@ -1,6 +1,5 @@
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import {Injectable, Inject, PLATFORM_ID, TransferState} from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
import { TransferState } from '@angular/platform-browser';
import { properties } from "../../environments/environment";
@Injectable({

View File

@ -2,13 +2,12 @@ import {Component, Input, ViewChild} from '@angular/core';
import {Router} from '@angular/router';
import {ContextsService} from './service/contexts.service';
import {ClaimEntity, ShowOptions} from './claimHelper.class';
import {Session} from '../../login/utils/helper.class';
import {LoginErrorCodes} from '../../login/utils/guardHelper.class';
import {EnvProperties} from '../../utils/properties/env-properties';
import {Subscriber} from "rxjs";
import {OpenaireEntities} from "../../utils/properties/searchFields";
import {CommunitiesService} from "../../connect/communities/communities.service";
import {UserManagementService} from "../../services/user-management.service";
import {Session} from "../../login/utils/helper.class";
declare var UIkit: any;
@ -183,10 +182,6 @@ export class ClaimContextSearchFormComponent {
}
getCommunities() {
if (!Session.isLoggedIn()) {
this.saveStateAndRedirectLogin();
} else {
this.loading = true;
this.subscriptions.push(this._contextService.getCommunitiesByState().subscribe(
data => {
@ -220,18 +215,14 @@ export class ClaimContextSearchFormComponent {
this.error = true;
}
));
}
}
getCategories() {
this.loading = true;
// this.categories=[];
if (this.selectedCommunityId != '0') {
if (!Session.isLoggedIn()) {
this.saveStateAndRedirectLogin();
} else {
if (this.categories[this.selectedCommunityId]) {
this.loading = false;
if(this.categories[this.selectedCommunityId].length > 0){
@ -259,7 +250,7 @@ export class ClaimContextSearchFormComponent {
}
));
}
}
}
displaySubcategory(id) {
@ -272,9 +263,6 @@ export class ClaimContextSearchFormComponent {
}
browseConcepts(categoryId) {
if (!Session.isLoggedIn()) {
this.saveStateAndRedirectLogin();
} else {
if (this.conceptsClass[categoryId] != null) {
this.conceptsClassDisplay[categoryId] = !this.conceptsClassDisplay[categoryId];
return;
@ -304,8 +292,6 @@ export class ClaimContextSearchFormComponent {
));
}
}
browseSubConcepts(categoryId, conceptId) {
this.conceptsCategoryLoading[categoryId] = true;
@ -325,24 +311,6 @@ export class ClaimContextSearchFormComponent {
}
saveStateAndRedirectLogin() {
if (this.results != null) {
localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results));
}
if (this.sources != null) {
localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources));
}
this.router.navigate(['/user-info'], {
queryParams: {
"errorCode": LoginErrorCodes.NOT_VALID,
"redirectUrl": this.router.url
}
});
}
private static handleError(message: string, error) {
console.error("Claim context search form (component): " + message, error);
}

View File

@ -1,39 +0,0 @@
import {Component, Input, ViewChild} from '@angular/core';
import {ShowOptions} from './claimHelper.class';
import {MatSelect} from "@angular/material/select";
import {OpenaireEntities} from "../../utils/properties/searchFields";
@Component({
selector: 'claim-enities-selection',
template:`
<span *ngIf="showOptions && showOptions.linkToEntities && showOptions.linkToEntities.length > 0"
class=" entitiesSelection portal-box uk-text-small clickable" style=""
(click)="open()">
<mat-select [(value)]="showOptions.show"
[disableOptionCentering]="true" >
<mat-option *ngIf="showOptions.linkToEntities.indexOf('result')!=-1" value="result">{{openaireEntities.RESULTS}}</mat-option>
<mat-option *ngIf="showOptions.linkToEntities.indexOf('project')!=-1" value="project">{{openaireEntities.PROJECTS}}</mat-option>
<mat-option *ngIf="showOptions.linkToEntities.indexOf('context')!=-1" value="context">{{openaireEntities.COMMUNITIES}}</mat-option>
</mat-select>
</span>
`,
})
export class ClaimEntitiesSelectionComponent{
@ViewChild(MatSelect) matSelect: MatSelect;
@Input() showOptions:ShowOptions = new ShowOptions();
public openaireEntities = OpenaireEntities;
open() {
if (this.matSelect && !this.matSelect.focused) {
this.matSelect.open();
}
}
}

View File

@ -1,11 +1,9 @@
import {NgModule} from '@angular/core';
import { SharedModule } from '../../../openaireLibrary/shared/shared.module';
import {SharedModule} from '../../shared/shared.module';
import {CommonModule} from '@angular/common';
import {ClaimProjectsSearchFormComponent} from './claimProjectSearchForm.component';
// import {LoadingModalModule} from '../../utils/modal/loadingModal.module';
import {ProjectServiceModule} from '../../landingPages/project/projectService.module';
import {ProjectsServiceModule} from '../../services/projectsService.module';
import {EntitiesAutocompleteModule} from '../../utils/entitiesAutoComplete/entitiesAutoComplete.module';
@ -16,8 +14,6 @@ import {ClaimResultsModule} from './claimResults.module';
import {PagingModule} from '../../utils/paging.module';
import {SearchFilterModule} from '../../searchPages/searchUtils/searchFilter.module';
import {RangeFilterModule} from "../../utils/rangeFilter/rangeFilter.module";
import {ClaimEntitiesSelectionComponent} from "./claimEntitiesSelection.component";
import {MatSelectModule} from "@angular/material/select";
import {AdvancedSearchInputModule} from "../../sharedComponents/advanced-search-input/advanced-search-input.module";
import {InputModule} from "../../sharedComponents/input/input.module";
import {DropdownFilterModule} from "../../utils/dropdown-filter/dropdown-filter.module";
@ -27,15 +23,13 @@ import {DropdownFilterModule} from "../../utils/dropdown-filter/dropdown-filter.
SharedModule, CommonModule,
// LoadingModalModule,
ProjectServiceModule, ProjectsServiceModule, EntitiesAutocompleteModule, HelperModule,
PagingModule, SearchFilterModule, ClaimResultsModule, RangeFilterModule, MatSelectModule, AdvancedSearchInputModule, InputModule, DropdownFilterModule
PagingModule, SearchFilterModule, ClaimResultsModule, RangeFilterModule, AdvancedSearchInputModule, InputModule, DropdownFilterModule
],
providers:[
],
declarations: [
ClaimProjectsSearchFormComponent,
ClaimEntitiesSelectionComponent
],
exports: [ClaimProjectsSearchFormComponent, ClaimEntitiesSelectionComponent]
exports: [ClaimProjectsSearchFormComponent]
})
export class ClaimProjectsSearchFormModule { }

View File

@ -10,7 +10,7 @@ export class ClaimsService {
}
private getClaimRequest(size : number, page : number, url :string, fromCache:boolean):any {
return this.http.get(url, CustomOptions.getAuthOptions());
return this.http.get(url, CustomOptions.getAuthOptionsWithBody());
}
getClaims( size : number, page : number, keyword:string, sortby: string, descending: boolean, types: string, apiUrl:string):any {
let url = apiUrl +"claims"+"?offset="+(size*(page-1) + "&limit="+size)+"&keyword="+keyword+"&sortby="+sortby+"&descending="+descending+(types.length>0?"&"+types:types);
@ -44,10 +44,7 @@ export class ClaimsService {
deleteClaimById(claimId:string , apiUrl:string):any{
//console.warn('Trying to delete claim with id : '+claimId);
let url = apiUrl +"claims/"+claimId;
// let headers = new Headers({ 'Content-Type': 'application/json' });
// let options = new RequestOptions({ headers: headers });
return this.http.delete( url, CustomOptions.getAuthOptionsWithBody())//.map(request => <any> request.json())
// .do(request => console.info("After delete" ))
.pipe(catchError(this.handleError));
}
@ -61,10 +58,7 @@ export class ClaimsService {
}
url= apiUrl +"claims/bulk?"+url;
// let headers = new Headers({ 'Content-Type': 'application/json' });
// let options = new RequestOptions({ headers: headers });
return this.http.delete( url, CustomOptions.getAuthOptions())//.map(request => <any> request.json())
// .do(request => console.info("After delete" ))
return this.http.delete( url, CustomOptions.getAuthOptionsWithBody())//.map(request => <any> request.json())
.pipe(catchError(this.handleError));
}
@ -72,37 +66,24 @@ export class ClaimsService {
// console.warn('Trying toinsert claims : '+claims);
let url = apiUrl +"claims/bulk";
let body = JSON.stringify( claims );
//console.warn('Json body: : '+body);
// let headers = new Headers({ 'Content-Type': 'application/json' });
// let options = new RequestOptions({ headers: headers });
return this.http.post(url, body, CustomOptions.getAuthOptionsWithBody())
//.map(res => res.json())
//.do(request => console.info("Insert Response:"+request.status) )
.pipe(catchError(this.handleError));
}
insertClaim(claim, apiUrl:string):any{
//console.warn('Trying toinsert claim : '+claim);
let url = apiUrl +"claims";
let body = JSON.stringify( claim );
// let headers = new Headers({ 'Content-Type': 'application/json' });
// let options = new RequestOptions({ headers: headers });
return this.http.post(url, body, CustomOptions.getAuthOptionsWithBody())
//.map(res => res.json())
//.do(request => console.info("Insert Response:"+request.status) )
.pipe(catchError(this.handleError));
}
insertDirectRecords(records, apiUrl:string):any{
//console.warn('Trying to feedrecords : '+records);
let url = apiUrl +"feed/bulk";
let body = JSON.stringify( records );
//console.warn('Json body: : '+body);
// let headers = new Headers({ 'Content-Type': 'application/json' });
// let options = new RequestOptions({ headers: headers });
return this.http.post(url, body, CustomOptions.getAuthOptionsWithBody())
//.map(res => res.json())
//.do(request => console.info("Insert Response:"+request) )
.pipe(catchError(this.handleError));
}
getStatus(jobId, apiUrl:string):any{
let url = apiUrl +"jobStatus/" + jobId;
return this.http.get(url,CustomOptions.getAuthOptionsWithBody())
.pipe(catchError(this.handleError));
}
@ -113,22 +94,4 @@ export class ClaimsService {
return observableThrowError(error || 'Server error');
}
// getClaim(id:string, apiUrl:string):any {
// let url = apiUrl+"claims/"+id;
// return new Promise((resolve, reject) => {
// this.http.get(url)
// //.map(res => res.json())
// .subscribe(
// data => {
// resolve(data['data']);
// },
// err => {
// reject(err);
// }
// )
// ;
// });
// }
}

View File

@ -18,26 +18,12 @@ export class ClaimsByTokenService {
let key = url;
return this.http.get(url, CustomOptions.getAuthOptions());
//.map(res => <any> res.text())
//.map(request => <any> request.json());
return this.http.get(url, CustomOptions.getAuthOptionsWithBody());
}
/*
getClaims(email: string, token: string, user_token: string):any {
let url = OpenaireProperties.getClaimsAPIURL(); // What else?
let body = JSON.stringify( {"email": email, "token": token} );
console.warn('Json body: : '+body);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(url, body, options)
.map(res => res.json())
.do(request => console.info("Insert Response:"+request.status) )
.catch(this.handleError);
}
*/
updateClaimsCuration( selectedRight: Set<string>, selectedWrong: Set<string>, apiURL:string) {
let url = apiURL + "curate/bulk";

View File

@ -14,7 +14,8 @@ import {
Message
} from "../../claim-utils/claimHelper.class";
import {UserManagementService} from "../../../services/user-management.service";
import {Subscriber} from "rxjs";
import {Subscriber, timer} from "rxjs";
import {map} from "rxjs/operators";
@Component({
selector: 'claim-insert',
@ -30,7 +31,19 @@ import {Subscriber} from "rxjs";
<div class="uk-width-expand uk-margin-small-left">CONFIRM LINKING</div>
</button>
</div>
<modal-loading [message]="'Please wait...'"></modal-loading>
<modal-loading [message]="'Please wait...'">
<div *ngIf="claimsJob">
<div *ngIf="claimsJob && claimsJob.insertedIds.length <1" class="uk-text-meta uk-text-small">
Initiating process....</div>
<div *ngIf="claimsJob && claimsJob.insertedIds.length >0" class="uk-text-meta uk-text-small">
{{claimsJob.insertedIds.length}} out of {{claims2Insert}} links created.</div>
<div *ngIf="feedRecordsJob && feedRecordsJob.insertedIds.length >0" class="uk-text-meta uk-text-small">
{{feedRecordsJob.insertedIds.length}} out of {{records2Insert}} records added in the index...</div>
<div *ngIf="claimsJob.status != 'COMPLETE'" class="uk-text-meta uk-text-small">
Please don't close the window, process is ongoing...</div>
</div>
</modal-loading>
<modal-alert (alertOutput)="confirmClose()">
<h4 class="modal-title uk-text-bold " id="myModalLabel">Confirmation notice</h4>
<p>All the links you provided will be published in the OpenAIRE platform. <br>
@ -60,6 +73,32 @@ export class ClaimInsertComponent {
this.subscriptions.push(this.route.queryParams.subscribe(params => {
this.params = params;
}));
if(localStorage.getItem(this.localStoragePrefix + "claimsJob")){
this.claimsJob = JSON.parse(localStorage.getItem(this.localStoragePrefix + "claimsJob"));
this.feedRecordsJob = JSON.parse(localStorage.getItem(this.localStoragePrefix + "feedRecordsJob"));
if(this.claimsJob.status != "COMPLETE"){
this.claiming = true;
let loadingTimerSubscription = timer(0, 1000).pipe(
map(() => {
if(this.loading) {
this.loading.open();
loadingTimerSubscription.unsubscribe();
}
})
).subscribe();
this.subscriptions.push(loadingTimerSubscription);
let timerSubscription = timer(0, 10000).pipe(
map(() => {
this.getStatus(); // load data contains the http request
})
).subscribe();
this.subscriptions.push(timerSubscription);
}else{
this.claimsJob = null;
}
}
}
params = {};
@ -84,15 +123,15 @@ export class ClaimInsertComponent {
private errorInClaims: ClaimRecord2Insert[] = [];
private insertedRecords = [];
private errorInRecords = [];
public claimsJob;
public feedRecordsJob;
public claims2Insert;
public records2Insert
public insert() {
this.confirmOpen();
}
saveAndNavigate(){
localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results));
if (this.sources != null) {
localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources));
}
this.saveLocalStorage();
this._router.navigate(['/user-info'], {
queryParams: {
"errorCode": LoginErrorCodes.NOT_VALID,
@ -177,12 +216,15 @@ export class ClaimInsertComponent {
//first call direct index service - when call is done (success or error) call isertBulkClaims method to insert claims in DB
// console.log("directclaims");
// console.log(directclaims);
if (directclaims.length > 0 && this.properties.environment != "development"){
if (directclaims.length > 0/* && this.properties.environment != "development"*/){
this.subscriptions.push(this.claimService.insertDirectRecords(directclaims, this.properties.claimsAPIURL).subscribe(
data => {
this.insertedRecords = data.insertedIds;
this.errorInRecords = data.errorInClaims;
this.feedRecordsJob = data.data;
this.records2Insert = directclaims.length;
console.log(data);
// this.insertedRecords = data.insertedIds;
//
// this.errorInRecords = data.errorInClaims;
this.isertBulkClaims(claims);
},
err => {
@ -190,12 +232,12 @@ export class ClaimInsertComponent {
if (err.code && err.code == 403) {
this.saveAndNavigate();
}
if (err.insertedIds && err.insertedIds.length > 0) {
/* if (err.insertedIds && err.insertedIds.length > 0) {
this.insertedRecords = err.insertedIds;
}
if (err.errorInClaims && err.errorInClaims.length > 0) {
this.errorInRecords = err.errorInClaims;
}
}*/
this.isertBulkClaims(claims);
ClaimInsertComponent.handleError("Error inserting direct records: " + JSON.stringify(directclaims), err);
@ -209,30 +251,18 @@ export class ClaimInsertComponent {
}
private isertBulkClaims(claims: ClaimRecord2Insert[]) {
console.log("claims");
console.log(claims);
this.errors.splice(0, this.errors.length);
this.subscriptions.push(this.claimService.insertBulkClaims(claims, this.properties.claimsAPIURL).subscribe(
data => {
this.insertedClaims = data.insertedIds;
this.errorInClaims = data.errorInClaims;
//TODO remove - testing having errors in claims
// this.insertedClaims.pop();
// this.insertedClaims.pop();
// this.errorInClaims.push(claims[1]);
// this.insertedClaims.splice(0,this.insertedClaims.length);
// this.errorInClaims = claims;
//remove till here
if (claims.length != this.insertedClaims.length) {
let error: ClaimsErrorMessage = new ClaimsErrorMessage();
error.type = "claimServiceFail2Insert";
error.inserted = this.insertedClaims.length;
error.failed = this.errorInClaims.length;
this.createErrorMessagesPerEntity((this.insertedClaims.length == 0));
this.errors.push(error);
}
this.afterclaimsInsertion();
this.claims2Insert = claims.length;
this.claimsJob = data.data;
this.saveLocalStorage();
let timerSubscription = timer(0, 10000).pipe(
map(() => {
this.getStatus(); // load data contains the http request
})
).subscribe();
this.subscriptions.push(timerSubscription);
},
err => {
err = err && err.error?err.error:err;
@ -335,13 +365,16 @@ export class ClaimInsertComponent {
}
private afterclaimsInsertion() {
this.loading.close();
this.claiming = false;
this.loading.close();
if (this.errorInClaims.length == 0 && this.insertedClaims.length > 0) {
localStorage.removeItem(this.localStoragePrefix + "sources");
localStorage.removeItem(this.localStoragePrefix + "results");
localStorage.removeItem(this.localStoragePrefix + "claimsJob");
localStorage.removeItem(this.localStoragePrefix + "feedRecordsJob");
this._router.navigate(['/myclaims'], {queryParams: this.params});
}
}
@ -520,5 +553,74 @@ export class ClaimInsertComponent {
}
return buttonClass + "uk-text-center ";
}
getStatus(){
if(this.feedRecordsJob && ! (this.feedRecordsJob.status == "COMPLETE" || this.feedRecordsJob.status == "ERROR") ) {
this.subscriptions.push(this.claimService.getStatus(this.feedRecordsJob.id, this.properties.claimsAPIURL).subscribe(data => {
console.log("feed", data);
this.feedRecordsJob = data.data;
if (this.feedRecordsJob.status == "COMPLETE" || data.data.status == "ERROR") {
this.insertedRecords = this.feedRecordsJob.insertedIds;
this.errorInRecords = this.feedRecordsJob.errorInClaims;
}
}, err => {
let error: ClaimsErrorMessage = new ClaimsErrorMessage();
error.type = "jobError";
this.createErrorMessagesPerEntity((this.insertedClaims.length == 0));
this.errors.push(error);
this.afterclaimsInsertion();
this.feedRecordsJob = null;
localStorage.removeItem(this.localStoragePrefix + "feedRecordsJob");
}
));
}
if(this.claimsJob) {
this.subscriptions.push(this.claimService.getStatus(this.claimsJob.id, this.properties.claimsAPIURL).subscribe(data => {
console.log("claim", data);
this.claimsJob = data.data;
if ((this.claimsJob.status == "COMPLETE" || data.data.status == "ERROR") && ( !this.feedRecordsJob || !(this.feedRecordsJob.status == "COMPLETE" || data.data.status == "ERROR")) ) {
this.insertedClaims = this.claimsJob.insertedIds;
this.errorInClaims = this.claimsJob.errorInClaims;
if (this.claims2Insert != this.insertedClaims.length) {
let error: ClaimsErrorMessage = new ClaimsErrorMessage();
error.type = "claimServiceFail2Insert";
error.inserted = this.insertedClaims.length;
error.failed = this.errorInClaims.length;
this.createErrorMessagesPerEntity((this.insertedClaims.length == 0));
this.errors.push(error);
}
this.afterclaimsInsertion();
}
}, err => {
let error: ClaimsErrorMessage = new ClaimsErrorMessage();
error.type = "jobError";
this.createErrorMessagesPerEntity((this.insertedClaims.length == 0));
this.errors.push(error);
this.afterclaimsInsertion();
this.claimsJob = null;
localStorage.removeItem(this.localStoragePrefix + "claimsJob");
}
));
}
}
saveLocalStorage(){
if (this.results != null) {
localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results));
}
if (this.sources != null) {
localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources));
}
if (this.claimsJob != null) {
localStorage.setItem(this.localStoragePrefix + "claimsJob", JSON.stringify(this.claimsJob));
}
if (this.feedRecordsJob != null) {
localStorage.setItem(this.localStoragePrefix + "feedRecordsJob", JSON.stringify(this.feedRecordsJob));
}
}
}

View File

@ -45,7 +45,7 @@
</a>
</div>
</div>
<div *ngIf="!inlineEntity" uk-sticky="offset: 65; bottom: #pageBottom; media: @m" class="uk-blur-background">
<div *ngIf="!inlineEntity" uk-sticky="offset: 65; end: #pageBottom; media: @m" class="uk-blur-background">
<div class="uk-section-xsmall">
<stepper>
<step [status]="stepStatus('source')" stepId="source" stepNumber="1"
@ -110,7 +110,7 @@
</div>
<!-- Basket-->
<div *ngIf="showOptions.show != 'claim'" class="uk-width-1-3">
<div id="basket" uk-sticky="offset: 220; bottom: !*; media: @m" style="z-index: 0!important;">
<div id="basket" uk-sticky="offset: 220; end: !*; media: @m" style="z-index: 0!important;">
<div class="uk-card uk-card-default linkingBasket">
<div class="uk-card-body uk-padding-small">
<div class="uk-margin-right">

View File

@ -16,6 +16,8 @@ import {OpenaireEntities} from "../../utils/properties/searchFields";
import {StringUtils} from "../../utils/string-utils.class";
import {RouterHelper} from "../../utils/routerHelper.class";
import { Location } from '@angular/common';
import {LoginErrorCodes} from "../../login/utils/guardHelper.class";
import {UserManagementService} from "../../services/user-management.service";
@Component({
selector: 'linking-generic',
@ -51,11 +53,16 @@ export class LinkingGenericComponent {
constructor (private _router: Router, private route: ActivatedRoute, private entitySearch:EntitiesSearchService,
private _meta: Meta, private _title: Title, private _piwikService:PiwikService,
private seoService: SEOService, private helper: HelperService, private cdr: ChangeDetectorRef,
private location: Location) {
private location: Location, private userManagementService: UserManagementService) {
}
subscriptions = [];
ngOnInit() {
this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => {
if (!user) {
this.saveStateAndRedirectLogin();
}
}));
if(this.breadcrumbs.length === 0) {
this.breadcrumbs.push({name: 'home', route: '/'});
this.breadcrumbs.push({name: "Link", route: null});
@ -94,6 +101,11 @@ export class LinkingGenericComponent {
if(localStorage.getItem(this.localStoragePrefix + "sources")){
this.sources = JSON.parse(localStorage.getItem(this.localStoragePrefix + "sources"));
}
if(localStorage.getItem(this.localStoragePrefix + "claimsJob")){
let job = JSON.parse(localStorage.getItem(this.localStoragePrefix + "claimsJob"));
if(job.status != "COMPLETE"){
this.showOptions.show = 'claim'; }
}
}
}
@ -206,4 +218,20 @@ export class LinkingGenericComponent {
this.location.back();
}
}
saveStateAndRedirectLogin() {
if (this.results != null) {
localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results));
}
if (this.sources != null) {
localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources));
}
this._router.navigate(['/user-info'], {
queryParams: {
"errorCode": LoginErrorCodes.NOT_VALID,
"redirectUrl": this._router.url
}
});
}
}

View File

@ -250,6 +250,14 @@
None of the links saved.
</div>
</div>
<div *ngIf="message.type == 'jobError'">
<div class="">
<span class=" uk-text-bold">The saving status of your links can't be fetched. Please check your links or start over. </span>
<br>
<a routerLinkActive="router-link-active" routerLink="/myclaims">Manage your links here</a>
</div>
</div>
</div>
</div>
<div *ngIf="warnings.length > 0"

View File

@ -1,20 +1,13 @@
import {filter, map, mergeMap, take} from 'rxjs/operators';
import {Injectable} from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
CanActivateChild,
Router,
RouterStateSnapshot,
UrlTree
} from '@angular/router';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import {Observable, of} from 'rxjs';
import {Session} from '../../login/utils/helper.class';
import {LoginErrorCodes} from '../../login/utils/guardHelper.class';
import {UserManagementService} from "../../services/user-management.service";
@Injectable()
export class ConnectAdminLoginGuard implements CanActivate, CanActivateChild {
export class ConnectAdminLoginGuard {
constructor(private router: Router,
private userManagementService: UserManagementService) {

View File

@ -1,11 +1,5 @@
import {Injectable} from '@angular/core';
import {
Router,
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanLoad, Route, UrlSegment, CanActivateChild, UrlTree
} from '@angular/router';
import {ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import {Observable} from 'rxjs';
import {ConnectHelper} from '../connectHelper';
@ -14,7 +8,7 @@ import {CommunityService} from "../community/community.service";
import {map} from "rxjs/operators";
@Injectable()
export class IsCommunity implements CanActivate, CanActivateChild {
export class IsCommunity {
constructor(private router: Router,
private communityService: CommunityService) {

View File

@ -9,7 +9,7 @@ export class ConnectHelper {
if(properties.environment == "development" &&
(properties.adminToolsPortalType == "connect" || properties.adminToolsPortalType == "community"
|| properties.adminToolsPortalType == "aggregator" || properties.adminToolsPortalType == "eosc")) {
domain = "test.openaire.eu"; //for testing
domain = "covid-19.openaire.eu"; //for testing
}
domain = domain.indexOf("//") != -1? domain.split("//")[1]:domain; //remove https:// prefix
if (domain.indexOf('eosc-portal.eu') != -1) {

View File

@ -16,7 +16,8 @@ export class ZenodoCommunitiesService {
//.map(res => <any> res.json())
.pipe(map(res => [this.parseZenodoCommunities(res['hits'].hits),res['hits'].total]));
}
getZenodoCommunityById(properties:EnvProperties, url: string) {
getZenodoCommunityById(properties:EnvProperties, id: string) {
let url = properties.zenodoCommunities + "/" + id;
return this.http.get((properties.useLongCache)? (properties.cacheUrl+encodeURIComponent(url)) : url)
//.map(res => <any> res.json())
.pipe(map(res => {
@ -39,14 +40,14 @@ export class ZenodoCommunitiesService {
parseZenodoCommunity(resData:any):ZenodoCommunityInfo {
var result: ZenodoCommunityInfo = new ZenodoCommunityInfo();
result['title'] = resData.title;
let metadata = resData["metadata"];
result['title'] = metadata.title;
result['id'] = resData.id;
result['description'] = resData.description;
result['link'] = resData.links.html;
result['logoUrl'] = resData.logo_url;
result['description'] = metadata.description;
result['link'] = resData.links.self_html;
result['logoUrl'] = resData.links.logo;
result['date'] = resData.updated;
result['page'] = resData.page;
result['page'] = metadata.page;
return result;
}

View File

@ -6,14 +6,6 @@ import {AlertModalModule} from '../../utils/modal/alertModal.module';
import {DivIdsComponent} from './divIds.component';
import {AdminToolServiceModule} from "../../services/adminToolService.module";
import {InputModule} from "../../sharedComponents/input/input.module";
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import { MatCheckboxModule } from "@angular/material/checkbox";
import { MatFormFieldModule } from "@angular/material/form-field";
import {MatChipsModule} from '@angular/material/chips';
import {AdminTabsModule} from "../sharedComponents/admin-tabs/admin-tabs.module";
import {PageContentModule} from "../sharedComponents/page-content/page-content.module";
import {ClassesRoutingModule} from "./classes-routing.module";
@ -24,8 +16,8 @@ import {LoadingModule} from "../../utils/loading/loading.module";
@NgModule({
imports: [
CommonModule, RouterModule, FormsModule,
AlertModalModule, ReactiveFormsModule, AdminToolServiceModule, InputModule, MatAutocompleteModule, MatFormFieldModule, MatChipsModule,
MatCheckboxModule, AdminTabsModule, PageContentModule, ClassesRoutingModule, SearchInputModule, IconsModule, LoadingModule
AlertModalModule, ReactiveFormsModule, AdminToolServiceModule, InputModule,
AdminTabsModule, PageContentModule, ClassesRoutingModule, SearchInputModule, IconsModule, LoadingModule
],
declarations: [DivIdsComponent],
exports: [DivIdsComponent]

View File

@ -8,21 +8,20 @@ import {PageContentFormComponent} from './page-help-content-form.component';
import {PageHelpContentFormRoutingModule} from './page-help-content-form-routing.module';
import {AdminToolServiceModule} from '../../services/adminToolService.module';
import {InputModule} from '../../sharedComponents/input/input.module';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {IconsModule} from '../../utils/icons/icons.module';
import {PageContentModule} from '../sharedComponents/page-content/page-content.module';
import {RouterModule} from '@angular/router';
import {LoadingModule} from '../../utils/loading/loading.module';
import {MatSlideToggleModule} from "@angular/material/slide-toggle";
@NgModule({
imports: [
CommonModule, FormsModule, RouterModule,
SafeHtmlPipeModule, CKEditorModule,
AlertModalModule, ReactiveFormsModule, PageHelpContentFormRoutingModule, AdminToolServiceModule, InputModule, MatSlideToggleModule, IconsModule, PageContentModule, LoadingModule
],
declarations: [
PageContentFormComponent
AlertModalModule, ReactiveFormsModule, PageHelpContentFormRoutingModule, AdminToolServiceModule, InputModule, IconsModule, PageContentModule, LoadingModule, MatSlideToggleModule
],
declarations: [PageContentFormComponent],
exports: [PageContentFormComponent]
})
export class PageHelpContentFormModule {}
export class PageHelpContentFormModule {
}

View File

@ -80,9 +80,6 @@ export class PageContentComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngOnInit() {
if(this.isBrowser) {
this.stickyBugWorkaround();
}
this.subscriptions.push(this.layoutService.isMobile.subscribe(isMobile => {
this.isMobile = isMobile;
if(this.isBrowser) {
@ -132,41 +129,7 @@ export class PageContentComponent implements OnInit, AfterViewInit, OnDestroy {
initFooter() {
let footer_offset = this.calcStickyFooterOffset(this.sticky_footer.nativeElement);
this.sticky.footer = UIkit.sticky(this.sticky_footer.nativeElement, {bottom: true, offset: footer_offset});
}
/**
* Workaround for sticky not update bug when sidebar is toggled.
* TODO when UIKit will be updated => remove
*
* */
stickyBugWorkaround() {
let sidebarOffset = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--dashboard-sidebar-width')) -
Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--dashboard-sidebar-mini-width'));
let transitionDelay = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--dashboard-transition-delay')) * 1000;
this.subscriptions.push(this.layoutService.isOpen.subscribe(isOpen => {
if (this.sticky.header) {
if (isOpen) {
this.sticky.header.$el.style.width = Number.parseInt(this.sticky.header.$el.style.width) - sidebarOffset + 'px';
} else {
this.sticky.header.$el.style.width = Number.parseInt(this.sticky.header.$el.style.width) + sidebarOffset + 'px';
}
setTimeout(() => {
this.sticky.header.$emit();
}, transitionDelay);
}
if (this.sticky.footer) {
if (isOpen) {
this.sticky.footer.$el.style.width = Number.parseInt(this.sticky.footer.$el.style.width) - sidebarOffset + 'px';
} else {
this.sticky.footer.$el.style.width = Number.parseInt(this.sticky.footer.$el.style.width) + sidebarOffset + 'px';
}
setTimeout(() => {
this.sticky.footer.$emit();
}, transitionDelay);
}
this.cdr.detectChanges();
}));
this.sticky.footer = UIkit.sticky(this.sticky_footer.nativeElement, {end: true, offset: footer_offset});
}
/**

View File

@ -79,6 +79,10 @@ export class LayoutService {
* Add hasMenuSearchBar: false/ nothing on data of route config, if the search bar in the menu should not appear, otherwise true.
*/
private hasMenuSearchBarSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/**
* Add hasStickyHeaderOnMobile: true in order to activate uk-sticky in header of mobile/tablet devices.
* */
private hasStickyHeaderOnMobileSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private subscriptions: any[] = [];
@ -127,7 +131,7 @@ export class LayoutService {
data['hasHeader'] === false) {
this.setHasHeader(false);
if (typeof document !== "undefined") {
document.documentElement.style.setProperty('--header-height', '0');
document.documentElement.style.setProperty('--header-height', '0px');
}
} else {
this.setHasHeader(true);
@ -177,6 +181,12 @@ export class LayoutService {
} else {
this.setHasMenuSearchBar(false);
}
if (data['hasStickyHeaderOnMobile'] !== undefined &&
data['hasStickyHeaderOnMobile'] === true) {
this.setHasStickyHeaderOnMobile(true);
} else {
this.setHasStickyHeaderOnMobile(false);
}
}
}));
this.setObserver();
@ -311,4 +321,12 @@ export class LayoutService {
setHasMenuSearchBar(value: boolean) {
this.hasMenuSearchBarSubject.next(value);
}
get hasStickyHeaderOnMobile(): Observable<boolean> {
return this.hasStickyHeaderOnMobileSubject.asObservable();
}
setHasStickyHeaderOnMobile(value: boolean) {
this.hasStickyHeaderOnMobileSubject.next(value);
}
}

View File

@ -1,8 +1,8 @@
<aside id="sidebar_main" class="uk-visible@m">
<div id="sidebar_main" uk-sticky="end: .sidebar_main_swipe;" [attr.offset]="offset" class="uk-visible@m">
<div sidebar-content>
<ng-container *ngTemplateOutlet="menu; context: {mobile: false}"></ng-container>
</div>
</aside>
</div>
<div class="uk-hidden@m">
<div id="sidebar_offcanvas" #sidebar_offcanvas [attr.uk-offcanvas]="'overlay: true'">
<div class="uk-offcanvas-bar uk-padding-remove">
@ -29,7 +29,7 @@
</a>
</div>
<div *ngIf="items.length > 0" class="menu_section uk-margin-large-top" [class.mobile]="mobile" style="min-height: 30vh">
<ul #nav class="uk-list uk-nav uk-nav-parent-icon" [class.uk-list-large]="mobile"
<ul #nav class="uk-list uk-nav" [class.uk-list-large]="mobile"
[class.uk-nav-default]="!mobile" [class.uk-nav-primary]="mobile" uk-nav="duration: 400">
<ng-template ngFor [ngForOf]="items" let-item>
<li [class.uk-active]="item.isActive" [ngClass]="item.customClass"
@ -40,6 +40,7 @@
<icon class="menu-icon" [customClass]="item.icon.class" [name]="item.icon.name" ratio="0.9" [svg]="item.icon.svg" [flex]="true"></icon>
</div>
<span [class.hide-on-close]="item.icon" class="uk-width-expand@l uk-text-truncate uk-margin-small-left">{{item.title}}</span>
<span *ngIf="item.items.length > 0" class="uk-nav-parent-icon hide-on-close"></span>
</a>
<ul *ngIf="item.items?.length > 0 && (isBrowser || item.isActive)" class="uk-nav-sub">
<li *ngFor="let subItem of item.items" [ngClass]="subItem.customClass"

View File

@ -30,6 +30,7 @@ export class SideBarComponent implements OnInit, AfterViewInit, OnDestroy, OnCha
@Input() queryParamsHandling;
@ViewChild("nav") nav: ElementRef;
@ViewChild("sidebar_offcanvas") sidebar_offcanvas: ElementRef;
public offset: number;
public properties = properties;
private subscriptions: any[] = [];
private init: boolean = false;
@ -45,6 +46,9 @@ export class SideBarComponent implements OnInit, AfterViewInit, OnDestroy, OnCha
}
ngOnInit() {
if (typeof document !== "undefined") {
this.offset = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-height'));
}
this.setActiveMenuItem();
}

View File

@ -4,24 +4,18 @@ import {Observable} from 'rxjs';
import {properties} from "../../../environments/environment";
@Injectable()
export class isDevelopmentGuard implements CanActivate, CanLoad {
@Injectable({
providedIn: 'root'
})
export class isDevelopmentGuard {
constructor(private router: Router) {
}
check(): Observable<boolean> | boolean {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree{
if(properties.environment == 'development') {
return true;
} else {
this.router.navigate([properties.errorLink]);
}
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree{
return this.check();
}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.check()
}
}

View File

@ -1,13 +1,13 @@
import {Observable} from 'rxjs';
import {take, tap} from 'rxjs/operators';
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Data, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import { ActivatedRouteSnapshot, Data, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import {ConfigurationService} from '../utils/configuration/configuration.service';
import {ConnectHelper} from '../connect/connectHelper';
import {properties} from "../../../environments/environment";
@Injectable()
export class IsRouteEnabled implements CanActivate {
export class IsRouteEnabled {
constructor(private router: Router,
private config: ConfigurationService) {

View File

@ -49,7 +49,7 @@
<div class="uk-margin-top uk-padding-small">
<div id="parentContainer" class="uk-grid uk-grid-large" uk-grid>
<div class="uk-width-1-4@m uk-visible@m">
<div class="uk-sticky" uk-sticky="bottom: !#parentContainer; offset: 200;">
<div class="uk-sticky" uk-sticky="end: !#parentContainer; offset: 200;">
<ul *ngIf="!keyword" class="uk-tab uk-tab-left">
<li *ngFor="let item of fos; index as i" [class.uk-active]="activeSection === item.id"
class="uk-margin-small-bottom uk-text-capitalize">

View File

@ -2,7 +2,7 @@ import {Injectable, Inject, PLATFORM_ID, Optional} from '@angular/core';
import {HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpHeaders} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {tap} from 'rxjs/operators';
import {TransferState, makeStateKey, StateKey} from '@angular/platform-browser';
import {TransferState, makeStateKey, StateKey} from '@angular/core';
import {isPlatformServer} from '@angular/common';
import {properties} from "../../environments/environment";
import {REQUEST} from "./utils/tokens";

View File

@ -17,7 +17,7 @@
<!-- left box - actions -->
<!-- <div id="landing-left-sidebar" *ngIf="dataProviderInfo" class="uk-visible@s uk-padding-remove-horizontal">
<div class="uk-flex uk-flex-column uk-flex-between uk-flex-center uk-sticky"
uk-sticky="bottom: true" [attr.offset]="offset">
uk-sticky="end: true" [attr.offset]="offset">
<div class="uk-align-center uk-text-center uk-margin-medium-top uk-flex uk-flex-column uk-flex-between">
<ng-container *ngIf="dataProviderInfo && hasMetrics">
<metrics [pageViews]="pageViews"
@ -48,7 +48,8 @@
<div id="landing-center-content" class="uk-width-expand uk-padding-remove uk-background-default">
<ng-template #graph_and_feedback_template>
<div class="uk-container uk-container-xlarge uk-flex uk-flex-between uk-flex-wrap uk-margin-small-top uk-margin-small-bottom" [class.uk-invisible]="!dataProviderInfo">
<div class="uk-padding-xsmall">
<div class="uk-container uk-container-xlarge uk-flex uk-flex-between uk-flex-wrap" [class.uk-invisible]="!dataProviderInfo">
<!-- Last Index Info-->
<a href="https://graph.openaire.eu" target="_blank" class="uk-width-1-1 uk-width-auto@l">
<img src="assets/common-assets/openaire-badge-1.png" alt="Powered by OpenAIRE graph" style="height: 15px;">
@ -63,10 +64,11 @@
<a (click)="showFeedback = true; scroll()" class="uk-text-xsmall">Give us feedback</a>
</div>
</div>
</div>
</ng-template>
<div #graph_and_feedback id="graph_and_feedback" class="uk-blur-background uk-text-xsmall uk-visible@m"
uk-sticky="bottom: true;" [attr.offset]="graph_offset">
uk-sticky="end: true;" [attr.offset]="graph_offset">
<ng-container *ngTemplateOutlet="graph_and_feedback_template"></ng-container>
</div>
@ -229,7 +231,7 @@
</div>
</div>
<div id="main-tabs-div" class="uk-sticky uk-blur-background"
uk-sticky="bottom: true; media: @m" [attr.offset]="offset">
uk-sticky="end: true; media: @m" [attr.offset]="offset">
<div class="uk-padding uk-padding-remove-horizontal uk-padding-remove-bottom">
<landing-header [ngClass]="stickyHeader ? 'uk-visible@m' : 'uk-invisible'"
[properties]="properties" [title]="dataProviderInfo.title.name"

View File

@ -434,7 +434,7 @@ export class DataProviderComponent {
},
err => {
//console.log(err);
this.handleError("Error getting " + this.type + " for " + (this.datasourceId ? ("id: " + this.datasourceId) : ("pid: " + this.identifier.id + " ("+this.identifier.class+")")), err);
this.handleError("Error getting " + this.type + " for " + (this.datasourceId ? ("id: " + this.datasourceId) : ("pid: " + this.identifier.id)), err);
if (err.status == 404) {
this._router.navigate([this.properties.errorLink], {
queryParams: {

View File

@ -24,7 +24,7 @@ export class DataProviderService {
if (id) {
return properties.searchAPIURLLAst + typePathParam + "/" + id + '?format=json';
} else if (identifier) {
return properties.searchAPIURLLAst + "resources2?pid="+encodeURIComponent(identifier.id) + "&pidtype=" + identifier.class + "&type="+typePathParam+"&format=json";
return properties.searchAPIURLLAst + "resources2?query=(pid exact \""+encodeURIComponent(identifier.id) + "\")&type="+typePathParam+"&format=json";
}
}

View File

@ -3,7 +3,6 @@ import {CommonModule} from "@angular/common";
import {FeedbackComponent} from "./feedback.component";
import {LandingHeaderModule} from "../landing-utils/landing-header/landing-header.module";
import {ReactiveFormsModule} from "@angular/forms";
import {MatSelectModule} from "@angular/material/select";
import {AlertModalModule} from "../../utils/modal/alertModal.module";
import {EmailService} from "../../utils/email/email.service";
import {RecaptchaModule} from "ng-recaptcha";
@ -11,7 +10,7 @@ import {IconsModule} from "../../utils/icons/icons.module";
import {InputModule} from "../../sharedComponents/input/input.module";
@NgModule({
imports: [CommonModule, LandingHeaderModule, ReactiveFormsModule, MatSelectModule, AlertModalModule, RecaptchaModule, IconsModule, InputModule],
imports: [CommonModule, LandingHeaderModule, ReactiveFormsModule, AlertModalModule, RecaptchaModule, IconsModule, InputModule],
declarations: [FeedbackComponent],
providers: [EmailService],
exports: [FeedbackComponent]

View File

@ -40,10 +40,11 @@ import {RouterHelper} from "../../utils/routerHelper.class";
<icon [flex]="true" [name]="(isOpen?'arrow_drop_up':'arrow_drop_down')"></icon>
</span>
</a>
<div #dropElement uk-drop="mode: click; pos: bottom-left;"
class="uk-drop download-drop uk-card uk-card-default uk-padding-small uk-padding-remove-horizontal uk-text-small uk-height-max-large uk-overflow-auto">
<div #dropElement uk-drop="mode: click; pos: bottom-left; flip: false; shift: false" class="uk-drop download-drop">
<div class="uk-card uk-card-default uk-padding-small uk-padding-remove-horizontal uk-text-small uk-height-max-large uk-overflow-auto">
<ng-container *ngTemplateOutlet="availableOnList"></ng-container>
</div>
</div>
</ng-container>
<ng-container *ngIf="isMobile">

View File

@ -1,19 +1,9 @@
import {
Component,
Inject,
Input,
OnDestroy,
OnInit,
RendererFactory2,
ViewEncapsulation
} from '@angular/core';
import {Component, Inject, Input, OnDestroy, OnInit, RendererFactory2, ViewEncapsulation} from '@angular/core';
import {Citation, CitationData} from './citation.class';
import {ResultLandingInfo} from "../../../utils/entities/resultLandingInfo";
import {DOCUMENT} from "@angular/common";
import {EnvProperties} from "../../../utils/properties/env-properties";
import {properties} from "../../../../../environments/environment";
import {PiwikService} from "../../../utils/piwik/piwik.service";
import {ResultPreview} from "../../../utils/result-preview/result-preview";
declare var Cite: any;
// Based on https://citation.js.org/api/tutorial-getting_started.html browser release

View File

@ -3,13 +3,12 @@ import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {CiteThisComponent} from './citeThis.component';
import { MatSelectModule } from "@angular/material/select";
import {InputModule} from "../../../sharedComponents/input/input.module";
@NgModule({
imports: [
CommonModule, FormsModule, MatSelectModule, InputModule
CommonModule, FormsModule, InputModule
],
declarations: [
CiteThisComponent

View File

@ -888,7 +888,7 @@ export class ParsingFunctions {
counts.push({name: 'downloads', icon: 'download', value: element.count, order: 1});
measure.downloads = element.count;
}
if (element.id == 'influence_alt') {
if (element.id == 'influence_alt' || element.id == 'citation_count') {
bip.push({name: 'citations', icon: 'cite', value: element.score, order: 2});
measure.citations = element.score;
}

View File

@ -16,7 +16,7 @@
<!-- left column -->
<!-- <div id="landing-left-sidebar" *ngIf="organizationInfo" class="uk-visible@s uk-padding-remove-horizontal">
<div class="uk-flex uk-flex-column uk-flex-right uk-sticky"
uk-sticky="bottom: true" [attr.offset]="offset">
uk-sticky="end: true" [attr.offset]="offset">
<div class="uk-margin-large-bottom uk-align-center">
<div class="uk-text-meta uk-text-uppercase">Actions</div>
<ul class="uk-list">
@ -47,6 +47,7 @@
<!-- Graph and feedback -->
<ng-template #graph_and_feedback_template>
<div class="uk-padding-xsmall">
<div class="uk-container uk-container-xlarge uk-flex uk-flex-between uk-flex-wrap uk-margin-small-top uk-margin-small-bottom" [class.uk-invisible]="!organizationInfo">
<!-- Last Index Info-->
<a href="https://graph.openaire.eu" target="_blank" class="uk-width-1-1 uk-width-auto@l">
@ -62,10 +63,11 @@
<a (click)="showFeedback = true; scroll()" class="uk-text-xsmall">Give us feedback</a>
</div>
</div>
</div>
</ng-template>
<div #graph_and_feedback id="graph_and_feedback" class="uk-blur-background uk-text-xsmall uk-visible@m"
uk-sticky="bottom: true;" [attr.offset]="graph_offset">
uk-sticky="end: true;" [attr.offset]="graph_offset">
<ng-container *ngTemplateOutlet="graph_and_feedback_template"></ng-container>
</div>
@ -171,7 +173,7 @@
</div>
<!-- Tabs section -->
<div id="main-tabs-div" class="uk-sticky uk-blur-background"
uk-sticky="bottom: true; media: @m" [attr.offset]="offset">
uk-sticky="end: true; media: @m" [attr.offset]="offset">
<div class="uk-padding uk-padding-remove-horizontal uk-padding-remove-bottom">
<!-- <showTitle *ngIf="stickyHeader" [titleName]="organizationInfo.title.name" classNames="uk-margin-remove-bottom" class="uk-visible@m"></showTitle>-->
<landing-header [ngClass]="stickyHeader ? 'uk-visible@m' : 'uk-invisible'"

View File

@ -23,7 +23,6 @@ import {HelperModule} from "../../utils/helper/helper.module";
import {OrganizationsDeletedByInferenceModule} from "./deletedByInference/deletedByInference.module";
import {LandingHeaderModule} from "../landing-utils/landing-header/landing-header.module";
import {FeedbackModule} from "../feedback/feedback.module";
import {MatSelectModule} from "@angular/material/select";
import {TabsModule} from "../../utils/tabs/tabs.module";
import {SearchTabModule} from "../../utils/tabs/contents/search-tab.module";
import {LoadingModule} from '../../utils/loading/loading.module';
@ -50,7 +49,6 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod
ProjectsServiceModule,
Schema2jsonldModule, SEOServiceModule, HelperModule,
OrganizationsDeletedByInferenceModule, LandingHeaderModule, FeedbackModule,
MatSelectModule,
TabsModule, SearchTabModule, LoadingModule, IconsModule, InputModule, FullScreenModalModule, EGIDataTransferModule, EntityActionsModule
],
declarations: [

View File

@ -17,7 +17,7 @@
<!-- left box - actions -->
<!-- <div id="landing-left-sidebar" *ngIf="projectInfo" class="uk-visible@s uk-padding-remove-horizontal">
<div class="uk-flex uk-flex-column uk-flex-between uk-flex-center uk-sticky"
uk-sticky="bottom: true" [attr.offset]="offset">
uk-sticky="end: true" [attr.offset]="offset">
<div class="uk-align-center uk-text-center uk-margin-medium-top uk-flex uk-flex-column uk-flex-between">
<ng-container *ngIf="projectInfo && hasMetrics">
<metrics [pageViews]="pageViews"
@ -85,6 +85,7 @@
<div id="landing-center-content" class="uk-width-expand uk-padding-remove uk-background-default">
<ng-template #graph_and_feedback_template>
<div class="uk-padding-xsmall">
<div class="uk-container uk-container-xlarge uk-flex uk-flex-between uk-flex-wrap uk-margin-small-top uk-margin-small-bottom" [class.uk-invisible]="!projectInfo">
<!-- Last Index Info-->
<a href="https://graph.openaire.eu" target="_blank" class="uk-width-1-1 uk-width-auto@l">
@ -100,10 +101,11 @@
<a (click)="showFeedback = true; scroll()" class="uk-text-xsmall">Give us feedback</a>
</div>
</div>
</div>
</ng-template>
<div #graph_and_feedback id="graph_and_feedback" class="uk-blur-background uk-text-xsmall uk-visible@m"
uk-sticky="bottom: true;" [attr.offset]="graph_offset">
uk-sticky="end: true;" [attr.offset]="graph_offset">
<ng-container *ngTemplateOutlet="graph_and_feedback_template"></ng-container>
</div>
@ -363,7 +365,7 @@
</div>
<div id="main-tabs-div" class="uk-sticky uk-blur-background"
uk-sticky="bottom: true; media: @m" [attr.offset]="offset">
uk-sticky="end: true; media: @m" [attr.offset]="offset">
<div class="uk-padding uk-padding-remove-horizontal uk-padding-remove-bottom">
<!-- <showTitle *ngIf="stickyHeader" [titleName]="projectName" classNames="uk-margin-remove-bottom" class="uk-visible@m"></showTitle>-->
<landing-header [ngClass]="stickyHeader ? 'uk-visible@m' : 'uk-invisible'"

View File

@ -2,7 +2,6 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {MatSelectModule} from "@angular/material/select";
import {ProjectComponent} from './project.component';
import {ProjectServiceModule} from './projectService.module';
@ -41,7 +40,7 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod
IFrameModule, ReportsServiceModule,
SearchResearchResultsServiceModule, ProjectServiceModule,
Schema2jsonldModule, SEOServiceModule, HelperModule,
LandingHeaderModule, MatSelectModule, FeedbackModule, AltMetricsModule,
LandingHeaderModule, FeedbackModule, AltMetricsModule,
TabsModule, SearchTabModule, LoadingModule, IconsModule, InputModule,
FullScreenModalModule, SafeHtmlPipeModule, EGIDataTransferModule, EntityActionsModule
],

View File

@ -17,7 +17,7 @@
<!-- left box/sidebar - actions -->
<!-- <div id="landing-left-sidebar" *ngIf="resultLandingInfo" class="uk-visible@s uk-padding-remove-horizontal">
<div class="uk-flex uk-flex-column uk-flex-between uk-flex-center uk-sticky"
uk-sticky="bottom: true" [attr.offset]="offset">
uk-sticky="end: true" [attr.offset]="offset">
<div class="uk-align-center uk-text-center uk-margin-medium-top uk-flex uk-flex-column uk-flex-between">
<ng-container *ngIf="resultLandingInfo && (hasAltMetrics || hasMetrics)">
<metrics *ngIf="hasMetrics" class="uk-margin-bottom"
@ -81,6 +81,7 @@
<div id="landing-center-content" class="uk-width-expand uk-padding-remove uk-background-default">
<ng-template #graph_and_feedback_template>
<div class="uk-padding-xsmall">
<div class="uk-container uk-container-xlarge uk-flex uk-flex-between uk-flex-wrap uk-margin-small-top uk-margin-small-bottom" [class.uk-invisible]="!resultLandingInfo">
<!-- Last Index Info-->
<a href="https://graph.openaire.eu" target="_blank" class="uk-width-1-1 uk-width-auto@l">
@ -96,10 +97,11 @@
<a (click)="feedbackClicked()" class="uk-text-xsmall">Give us feedback</a>
</div>
</div>
</div>
</ng-template>
<div #graph_and_feedback id="graph_and_feedback" class="uk-blur-background uk-text-xsmall uk-visible@m"
uk-sticky="bottom: true;" [attr.offset]="graph_offset">
uk-sticky="end: true;" [attr.offset]="graph_offset">
<ng-container *ngTemplateOutlet="graph_and_feedback_template"></ng-container>
</div>
@ -269,7 +271,7 @@
</div>
<div id="main-tabs-div" class="uk-sticky uk-blur-background"
uk-sticky="bottom: true; media: @m" [attr.offset]="offset">
uk-sticky="end: true; media: @m" [attr.offset]="offset">
<div class="uk-padding uk-padding-remove-horizontal uk-padding-remove-bottom">
<landing-header [ngClass]="stickyHeader ? 'uk-visible@m' : 'uk-invisible'"
[properties]="properties" [title]="resultLandingInfo.title"
@ -349,7 +351,7 @@
<ng-container *ngIf="resultLandingInfo && hasRightSidebarInfo">
<div id="landing-right-sidebar" *ngIf="!rightSidebarOffcanvasClicked" class="uk-width-1-3 uk-width-1-4@l uk-padding-remove uk-text-small uk-visible@m"
[class.uk-animation-right]="viewAll">
<div class="uk-sticky" uk-sticky="bottom: true" [attr.offset]="offset">
<div class="uk-sticky" uk-sticky="end: true" [attr.offset]="offset">
<div class="uk-overflow-auto uk-height-1-1">
<ng-container *ngTemplateOutlet="right_column"></ng-container>
</div>
@ -360,11 +362,11 @@
<!-- right box/ sidebar-->
<ng-container *ngIf="!showFeedback && resultLandingInfo && hasRightSidebarInfo">
<div id="landing-right-sidebar-switcher" uk-toggle href="#right-column-offcanvas"
class="uk-offcanvas-switcher uk-flex uk-flex-center uk-flex-middle uk-hidden@m"
<a id="landing-right-sidebar-switcher" uk-toggle href="#right-column-offcanvas"
class="uk-offcanvas-switcher uk-flex uk-link-reset uk-flex-center uk-flex-middle uk-hidden@m"
(click)="rightSidebarOffcanvasClicked = true;">
<icon name="more" [ratio]="1.5" customClass="uk-text-primary" [flex]="true" visuallyHidden="sidebar"></icon>
</div>
</a>
<div id="right-column-offcanvas" class="uk-offcanvas" uk-offcanvas="flip: true; overlay: true;">
<div class="uk-offcanvas-bar">
<button class="uk-offcanvas-close uk-close uk-icon" type="button"

View File

@ -604,7 +604,7 @@ export class ResultLandingComponent {
}
},
err => {
this.handleError("Error getting " + this.type + " for " + (this.id ? ("id: " + this.id) : ("pid: " + this.identifier.id + " ("+this.identifier.class+")")), err);
this.handleError("Error getting " + this.type + " for " + (this.id ? ("id: " + this.id) : ("pid: " + this.identifier.id)), err);
if (err.status == 404) {
this._router.navigate([this.properties.errorLink], {queryParams: {"page": this._location.path(true), "page_type": this.type}});
}else if(err.name == "TimeoutError"){

View File

@ -28,8 +28,6 @@ import {FeedbackModule} from "../feedback/feedback.module";
import {TabsModule} from "../../utils/tabs/tabs.module";
import {LoadingModule} from "../../utils/loading/loading.module";
import {OrcidModule} from "../../orcid/orcid.module";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatSelectModule} from "@angular/material/select";
import {IconsModule} from "../../utils/icons/icons.module";
import {IconsService} from "../../utils/icons/icons.service";
import {cite, fire, graph, landmark, link, link_to, quotes, rocket, versions} from "../../utils/icons/icons";
@ -48,7 +46,7 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod
AltMetricsModule, Schema2jsonldModule, SEOServiceModule,
DeletedByInferenceModule, ShowAuthorsModule, HelperModule, ResultLandingUtilsModule, AlertModalModule,
AnnotationModule, LandingHeaderModule, NoLoadPaging, ResultPreviewModule, FeedbackModule, TabsModule, LoadingModule,
OrcidModule, MatFormFieldModule, MatSelectModule, IconsModule, InputModule, EGIDataTransferModule, RecaptchaModule,
OrcidModule, IconsModule, InputModule, EGIDataTransferModule, RecaptchaModule,
SdgFosSuggestModule, FullScreenModalModule, SafeHtmlPipeModule, EntityActionsModule
],
declarations: [

View File

@ -39,7 +39,7 @@ export class ResultLandingService {
} else if (identifier) {
// pid = "10.3389/fphys.2014.00466";
let url = properties.searchAPIURLLAst + "resources2";
url += "?pid=" + encodeURIComponent(identifier.id) + "&pidtype=" + identifier.class + "&type=";
url += "?query=(pid exact \"" + encodeURIComponent(identifier.id) + "\")&type=";
if (type === 'publication') {
url += 'publications';
} else if (type === 'dataset') {

View File

@ -1,12 +1,5 @@
import {Injectable} from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
CanActivateChild, Data,
Router,
RouterStateSnapshot,
UrlTree
} from '@angular/router';
import { ActivatedRouteSnapshot, Data, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import {Observable} from 'rxjs';
import {Session} from './utils/helper.class';
import {LoginErrorCodes} from './utils/guardHelper.class';
@ -16,7 +9,7 @@ import {map, tap} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class AdminLoginGuard implements CanActivate, CanActivateChild {
export class AdminLoginGuard {
constructor(private router: Router,
private userManagementService: UserManagementService) {

View File

@ -1,16 +1,10 @@
import { Injectable } from '@angular/core';
import {
Router,
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree
} from '@angular/router';
import { Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import {Observable} from 'rxjs';
import {LoginErrorCodes} from './utils/guardHelper.class';
@Injectable()
export class FreeGuard implements CanActivate {
export class FreeGuard {
constructor(private router: Router) {
}

View File

@ -1,14 +1,5 @@
import {Injectable} from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
CanActivateChild,
CanLoad,
Route,
Router,
RouterStateSnapshot,
UrlTree
} from '@angular/router';
import { ActivatedRouteSnapshot, Route, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import {Observable} from 'rxjs';
import {LoginErrorCodes} from './utils/guardHelper.class';
import {map, tap} from "rxjs/operators";
@ -17,7 +8,7 @@ import {UserManagementService} from "../services/user-management.service";
@Injectable({
providedIn: 'root'
})
export class LoginGuard implements CanActivate, CanLoad, CanActivateChild {
export class LoginGuard {
constructor(private router: Router,
private userManagementService: UserManagementService) {

View File

@ -83,12 +83,13 @@ declare var UIkit;
{{user.fullname}}
</h5>
</div>
<ul class="uk-nav uk-nav-primary uk-list uk-margin-top uk-nav-parent-icon" uk-nav>
<ul class="uk-nav uk-nav-primary uk-list uk-margin-top" uk-nav>
<ng-container *ngFor="let item of userMenuItems ">
<li *ngIf="item.needsAuthorization && isAuthorized || !item.needsAuthorization" [ngClass]="item.customClass">
<a *ngIf="item.route" [routerLink]="item.route" (click)="closeCanvas(account)">{{item.title}}</a>
<a *ngIf="item.route" [routerLink]="item.route" (click)="closeCanvas(account)">
{{item.title}}<span *ngIf="item.items.length > 0" class="uk-nav-parent-icon"></span></a>
<a *ngIf="!item.route && item.url" (click)="closeCanvas(account)" [href]="item.url" [class.custom-external]="item.target != '_self'"
[target]="item.target">{{item.title}}</a>
[target]="item.target">{{item.title}}<span *ngIf="item.items.length > 0" class="uk-nav-parent-icon"></span></a>
</li>
</ng-container>
<ng-container *ngIf="notificationConfiguration">

View File

@ -1,3 +1,5 @@
import {stakeholderTypes} from "../../monitor/entities/stakeholder";
export class User {
email: string;
firstname: string;
@ -98,23 +100,11 @@ export class Session {
}
public static isMonitorCurator(user: User): boolean {
return this.isCommunityCurator(user) || this.isProjectCurator(user) || this.isFunderCurator(user) || this.isOrganizationCurator(user);
return stakeholderTypes.filter(stakeholderType => this.isTypeCurator(stakeholderType.value, user)).length > 0;
}
public static isCommunityCurator(user: User): boolean {
return this.isTypeCurator("Community", user);
}
public static isFunderCurator(user: User): boolean {
return this.isTypeCurator("Funder", user);
}
public static isProjectCurator(user: User): boolean {
return this.isTypeCurator("Project", user);
}
public static isOrganizationCurator(user: User): boolean {
return this.isTypeCurator("Institution", user);
return this.isTypeCurator("community", user);
}
private static isTypeCurator(type: string, user: User): boolean {
@ -122,16 +112,7 @@ export class Session {
}
public static isCurator(type: string, user: User): boolean {
if (type == 'funder') {
return user && this.isFunderCurator(user);
} else if (type == 'ri' || type == 'community') {
return user && this.isCommunityCurator(user);
} else if (type == 'organization' || type == 'institution') {
return user && this.isOrganizationCurator(user);
} else if (type == 'project') {
return user && this.isProjectCurator(user);
}
return false;
return (type === 'community' || stakeholderTypes.find(stakeholderType => stakeholderType.value == type)) && this.isTypeCurator(type, user);
}
public static isPortalAdministrator(user: User): boolean {

View File

@ -0,0 +1,10 @@
.uk-border-circle {
width: 100px;
height: 100px;
position: relative;
& > img {
max-width: 64px;
max-height: 64px;
}
}

View File

@ -0,0 +1,443 @@
import {Component, Input, OnDestroy, ViewChild} from "@angular/core";
import {Stakeholder} from "../../../monitor/entities/stakeholder";
import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms";
import {StakeholderUtils} from "../../utils/indicator-utils";
import {Option} from "../../../sharedComponents/input/input.component";
import {Subscription} from "rxjs";
import {EnvProperties} from "../../../utils/properties/env-properties";
import {properties} from "src/environments/environment";
import {StakeholderService} from "../../../monitor/services/stakeholder.service";
import {UtilitiesService} from "../../../services/utilities.service";
import {Role, Session, User} from "../../../login/utils/helper.class";
import {UserManagementService} from "../../../services/user-management.service";
import {StringUtils} from "../../../utils/string-utils.class";
import {NotifyFormComponent} from "../../../notifications/notify-form/notify-form.component";
import {NotificationUtils} from "../../../notifications/notification-utils";
import {Notification} from "../../../notifications/notifications";
import {NotificationHandler} from "../../../utils/notification-handler";
import {StatsProfilesService} from "../../utils/services/stats-profiles.service";
@Component({
selector: 'edit-stakeholder',
template: `
<form *ngIf="stakeholderFb" [formGroup]="stakeholderFb">
<div class="uk-grid uk-grid-large" uk-grid>
<div class="uk-width-1-2@m">
<div input id="name" [formInput]="stakeholderFb.get('name')"
placeholder="Name"></div>
</div>
<div class="uk-width-1-2@m">
<div input [formInput]="stakeholderFb.get('alias')"
placeholder="URL Alias"></div>
</div>
<div class="uk-width-1-3@m">
<div input [formInput]="stakeholderFb.get('index_id')"
placeholder="Index ID"></div>
</div>
<div class="uk-width-1-3@m">
<div input [formInput]="stakeholderFb.get('index_name')"
placeholder="Index Name"></div>
</div>
<div class="uk-width-1-3@m">
<div input [formInput]="stakeholderFb.get('index_shortName')"
placeholder="Index Short Name"></div>
</div>
<ng-container *ngIf="isCurator">
<div class="uk-width-1-3@m">
<div *ngIf="statsProfiles" input [formInput]="stakeholderFb.get('statsProfile')" [type]="'select'"
[options]="statsProfiles"
placeholder="Stats Profile"></div>
</div>
<div class="uk-width-1-3@m">
<div input [formInput]="stakeholderFb.get('projectUpdateDate')" [type]="'date'"
placeholder="Last Project Update"></div>
</div>
</ng-container>
<div class="uk-width-1-3@m">
<div input [formInput]="stakeholderFb.get('locale')" [type]="'select'"
[options]="stakeholderUtils.locales"
placeholder="Locale"></div>
</div>
<div class="uk-width-1-1">
<div input [type]="'textarea'" placeholder="Description"
[rows]="4" [formInput]="stakeholderFb.get('description')"></div>
</div>
<div class="uk-width-1-1">
<input #file id="photo" type="file" class="uk-hidden" (change)="fileChangeEvent($event)"/>
<div *ngIf="!stakeholderFb.get('isUpload').value" class="uk-grid uk-grid-column-large" uk-grid>
<div class="uk-margin-top uk-width-auto@l uk-width-1-1">
<div class="uk-grid uk-grid-column-large uk-flex-middle" uk-grid>
<div class="uk-width-auto@l uk-width-1-1 uk-flex uk-flex-center">
<button class="uk-button uk-button-primary uk-flex uk-flex-middle uk-flex-wrap"
(click)="file.click()">
<icon name="cloud_upload" [flex]="true"></icon>
<span class="uk-margin-small-left">Upload a file</span>
</button>
</div>
<div class="uk-text-center uk-text-bold uk-width-expand">
OR
</div>
</div>
</div>
<div input class="uk-width-expand" type="logoURL" [placeholder]="'Link to the logo'"
[formInput]="stakeholderFb.get('logoUrl')"></div>
</div>
<div *ngIf="stakeholderFb.get('isUpload').value" class="uk-width-1-1 uk-flex uk-flex-middle">
<div class="uk-card uk-card-default uk-text-center uk-border-circle">
<img class="uk-position-center uk-blend-multiply" [src]="photo">
</div>
<div class="uk-margin-left">
<button (click)="remove()" class="uk-button-danger uk-icon-button uk-icon-button-small">
<icon [flex]="true" ratio="0.8" name="delete"></icon>
</button>
</div>
<div class="uk-margin-small-left">
<button class="uk-button-secondary uk-icon-button uk-icon-button-small"
(click)="file.click()">
<icon [flex]="true" ratio="0.8" name="edit"></icon>
</button>
</div>
</div>
<!-- Full width error message -->
<div *ngIf="uploadError" class="uk-text-danger uk-margin-small-top uk-width-1-1">{{uploadError}}</div>
</div>
<div [class]="canChooseTemplate ? 'uk-width-1-3@m' : 'uk-width-1-2@m'">
<div input [formInput]="stakeholderFb.get('visibility')"
[placeholder]="'Select a status'"
[options]="stakeholderUtils.statuses" type="select"></div>
</div>
<div [class]="canChooseTemplate ? 'uk-width-1-3@m' : 'uk-width-1-2@m'">
<div input [formInput]="stakeholderFb.get('type')"
[placeholder]="'Select a type'"
[options]="types" type="select"></div>
</div>
<ng-container *ngIf="canChooseTemplate">
<div class="uk-width-1-3@m">
<div [placeholder]="'Select a template'"
input [formInput]="stakeholderFb.get('defaultId')"
[options]="defaultStakeholdersOptions" type="select"></div>
</div>
</ng-container>
</div>
</form>
<div #notify [class.uk-hidden]="!stakeholderFb" notify-form
class="uk-width-1-1 uk-margin-large-top uk-margin-medium-bottom"></div>
`,
styleUrls: ['edit-stakeholder.component.less']
})
export class EditStakeholderComponent implements OnDestroy {
@Input()
public disableAlias: boolean = false;
public stakeholderFb: UntypedFormGroup;
public secure: boolean = false;
public stakeholderUtils: StakeholderUtils = new StakeholderUtils();
public defaultStakeholdersOptions: Option[];
public defaultStakeholders: Stakeholder[];
public alias: string[];
public stakeholder: Stakeholder;
public isDefault: boolean;
public isNew: boolean;
public loading: boolean = false;
public types: Option[];
public statsProfiles: string[];
public properties: EnvProperties = properties;
private subscriptions: any[] = [];
/**
* Photo upload
* */
public file: File;
public photo: string | ArrayBuffer;
public uploadError: string;
public deleteCurrentPhoto: boolean = false;
private maxsize: number = 200 * 1024;
user: User;
@ViewChild('notify', {static: true}) notify: NotifyFormComponent;
private notification: Notification;
constructor(private fb: UntypedFormBuilder,
private stakeholderService: StakeholderService,
private statsProfileService: StatsProfilesService,
private utilsService: UtilitiesService, private userManagementService: UserManagementService,) {
}
ngOnDestroy() {
this.reset();
}
public init(stakeholder: Stakeholder, alias: string[], defaultStakeholders: Stakeholder[], isDefault: boolean, isNew: boolean) {
this.reset();
this.deleteCurrentPhoto = false;
this.stakeholder = stakeholder;
this.alias = alias;
this.defaultStakeholders = defaultStakeholders;
this.isDefault = isDefault;
this.isNew = isNew;
this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => {
this.user = user;
if (this.isCurator) {
this.subscriptions.push(this.statsProfileService.getStatsProfiles().subscribe(statsProfiles => {
this.statsProfiles = statsProfiles;
}, error => {
this.statsProfiles = [];
}));
} else {
this.statsProfiles = [];
}
this.types = this.stakeholderUtils.getTypesByUserRoles(this.user, this.stakeholder.alias);
this.stakeholderFb = this.fb.group({
_id: this.fb.control(this.stakeholder._id),
defaultId: this.fb.control(this.stakeholder.defaultId),
name: this.fb.control(this.stakeholder.name, Validators.required),
description: this.fb.control(this.stakeholder.description),
index_name: this.fb.control(this.stakeholder.index_name, Validators.required),
index_id: this.fb.control(this.stakeholder.index_id, Validators.required),
index_shortName: this.fb.control(this.stakeholder.index_shortName, Validators.required),
statsProfile: this.fb.control(this.stakeholder.statsProfile, Validators.required),
locale: this.fb.control(this.stakeholder.locale, Validators.required),
projectUpdateDate: this.fb.control(this.stakeholder.projectUpdateDate),
creationDate: this.fb.control(this.stakeholder.creationDate),
alias: this.fb.control(this.stakeholder.alias,
[
Validators.required,
this.stakeholderUtils.aliasValidatorString(
this.alias.filter(alias => alias !== this.stakeholder.alias)
)]
),
isDefault: this.fb.control((this.isDefault)),
visibility: this.fb.control(this.stakeholder.visibility, Validators.required),
type: this.fb.control(this.stakeholder.type, Validators.required),
topics: this.fb.control(this.stakeholder.topics),
isUpload: this.fb.control(this.stakeholder.isUpload),
logoUrl: this.fb.control(this.stakeholder.logoUrl),
});
if (this.stakeholder.isUpload) {
this.stakeholderFb.get('logoUrl').clearValidators();
this.stakeholderFb.get('logoUrl').updateValueAndValidity();
} else {
this.stakeholderFb.get('logoUrl').setValidators([StringUtils.urlValidator()]);
this.stakeholderFb.get('logoUrl').updateValueAndValidity();
}
this.subscriptions.push(this.stakeholderFb.get('isUpload').valueChanges.subscribe(value => {
if (value == true) {
this.stakeholderFb.get('logoUrl').clearValidators();
this.stakeholderFb.updateValueAndValidity();
} else {
this.stakeholderFb.get('logoUrl').setValidators([StringUtils.urlValidator()]);
this.stakeholderFb.updateValueAndValidity();
}
}));
this.secure = (!this.stakeholderFb.get('logoUrl').value || this.stakeholderFb.get('logoUrl').value.includes('https://'));
this.subscriptions.push(this.stakeholderFb.get('logoUrl').valueChanges.subscribe(value => {
this.secure = (!value || value.includes('https://'));
}));
this.initPhoto();
this.subscriptions.push(this.stakeholderFb.get('type').valueChanges.subscribe(value => {
this.onTypeChange(value, defaultStakeholders);
}));
this.stakeholderFb.setControl('defaultId', this.fb.control(stakeholder.defaultId, (this.isDefault && !this.isNew)?[]:Validators.required));
if (!this.isNew) {
this.notification = NotificationUtils.editStakeholder(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name);
this.notify.reset(this.notification.message);
if (this.isAdmin) {
if (this.disableAlias) {
setTimeout(() => {
this.stakeholderFb.get('alias').disable();
}, 0);
}
} else {
if (!this.isCurator) {
setTimeout(() => {
this.stakeholderFb.get('statsProfile').disable();
}, 0);
}
setTimeout(() => {
this.stakeholderFb.get('alias').disable();
this.stakeholderFb.get('index_id').disable();
this.stakeholderFb.get('index_name').disable();
this.stakeholderFb.get('index_shortName').disable();
}, 0);
}
setTimeout(() => {
this.stakeholderFb.get('type').disable();
}, 0);
} else {
this.notification = NotificationUtils.createStakeholder(this.user.firstname + ' ' + this.user.lastname);
this.notify.reset(this.notification.message);
setTimeout(() => {
this.stakeholderFb.get('type').enable();
}, 0);
}
}));
}
public get isAdmin() {
return Session.isPortalAdministrator(this.user);
}
public get isCurator() {
return this.stakeholder && (this.isAdmin || Session.isCurator(this.stakeholder.type, this.user));
}
public get disabled(): boolean {
return (this.stakeholderFb && this.stakeholderFb.invalid) ||
(this.stakeholderFb && this.stakeholderFb.pristine && !this.isNew && !this.file) ||
(this.uploadError && this.uploadError.length > 0);
}
public get dirty(): boolean {
return this.stakeholderFb && this.stakeholderFb.dirty;
}
public get canChooseTemplate(): boolean {
return this.isNew && this.stakeholderFb.get('type').valid && !!this.defaultStakeholdersOptions;
}
reset() {
this.uploadError = null;
this.stakeholderFb = null;
this.subscriptions.forEach(subscription => {
if (subscription instanceof Subscription) {
subscription.unsubscribe();
}
});
}
onTypeChange(value, defaultStakeholders: Stakeholder[]) {
this.stakeholderFb.setControl('defaultId', this.fb.control(this.stakeholder.defaultId, (this.isDefault && !this.isNew)?[]:Validators.required));
this.defaultStakeholdersOptions = [{
label: 'New blank profile',
value: '-1'
}];
defaultStakeholders.filter(stakeholder => stakeholder.type === value).forEach(stakeholder => {
this.defaultStakeholdersOptions.push({
label: 'Use ' + stakeholder.name + ' profile',
value: stakeholder._id
})
});
}
public save(callback: Function, errorCallback: Function = null) {
this.loading = true;
if (this.file) {
this.subscriptions.push(this.utilsService.uploadPhoto(this.properties.utilsService + "/upload/" + encodeURIComponent(this.stakeholderFb.getRawValue().type) + "/" + encodeURIComponent(this.stakeholderFb.getRawValue().alias), this.file).subscribe(res => {
this.deletePhoto();
this.stakeholderFb.get('logoUrl').setValue(res.filename);
this.removePhoto();
this.saveStakeholder(callback, errorCallback);
}, error => {
this.uploadError = "An error has been occurred during upload your image. Try again later";
this.saveStakeholder(callback, errorCallback);
}));
} else if (this.deleteCurrentPhoto) {
this.deletePhoto();
this.saveStakeholder(callback, errorCallback);
} else {
this.saveStakeholder(callback, errorCallback);
}
}
public saveStakeholder(callback: Function, errorCallback: Function = null) {
if (this.isNew) {
let defaultStakeholder = this.defaultStakeholders.find(value => value._id === this.stakeholderFb.getRawValue().defaultId);
this.stakeholderFb.setValue(this.stakeholderUtils.createFunderFromDefaultProfile(this.stakeholderFb.getRawValue(),
(defaultStakeholder ? defaultStakeholder.topics : []), this.stakeholderFb.getRawValue().isDefault));
this.removePhoto();
if(this.stakeholderFb.getRawValue().isDefault) {
this.stakeholderFb.get('defaultId').setValue(null);
}
this.subscriptions.push(this.stakeholderService.buildStakeholder(this.properties.monitorServiceAPIURL,
this.stakeholderFb.getRawValue()).subscribe(stakeholder => {
this.notification.entity = stakeholder._id;
this.notification.stakeholder = stakeholder.alias;
this.notification.stakeholderType = stakeholder.type;
this.notification.groups = [Role.curator(stakeholder.type)];
this.notify.sendNotification(this.notification);
NotificationHandler.rise(stakeholder.name + ' has been <b>successfully created</b>');
callback(stakeholder);
this.loading = false;
}, error => {
NotificationHandler.rise('An error has occurred. Please try again later', 'danger');
if (errorCallback) {
errorCallback(error)
}
this.loading = false;
}));
} else {
this.subscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, this.stakeholderFb.getRawValue()).subscribe(stakeholder => {
this.notification.entity = stakeholder._id;
this.notification.stakeholder = stakeholder.alias;
this.notification.stakeholderType = stakeholder.type;
this.notification.groups = [Role.curator(stakeholder.type), Role.manager(stakeholder.type, stakeholder.alias)];
this.notify.sendNotification(this.notification);
NotificationHandler.rise(stakeholder.name + ' has been <b>successfully saved</b>');
callback(stakeholder);
this.loading = false;
}, error => {
NotificationHandler.rise('An error has occurred. Please try again later', 'danger');
if (errorCallback) {
errorCallback(error)
}
this.loading = false;
}));
}
}
fileChangeEvent(event) {
if (event.target.files && event.target.files[0]) {
this.file = event.target.files[0];
if (this.file.type !== 'image/png' && this.file.type !== 'image/jpeg') {
this.uploadError = 'You must choose a file with type: image/png or image/jpeg!';
this.stakeholderFb.get('isUpload').setValue(false);
this.stakeholderFb.get('isUpload').markAsDirty();
this.removePhoto();
} else if (this.file.size > this.maxsize) {
this.uploadError = 'File exceeds size\'s limit! Maximum resolution is 256x256 pixels.';
this.stakeholderFb.get('isUpload').setValue(false);
this.stakeholderFb.get('isUpload').markAsDirty();
this.removePhoto();
} else {
this.uploadError = null;
const reader = new FileReader();
reader.readAsDataURL(this.file);
reader.onload = () => {
this.photo = reader.result;
this.stakeholderFb.get('isUpload').setValue(true);
this.stakeholderFb.get('isUpload').markAsDirty();
};
}
}
}
initPhoto() {
if (this.stakeholderFb.getRawValue().isUpload) {
this.photo = this.properties.utilsService + "/download/" + this.stakeholderFb.get('logoUrl').value;
}
}
removePhoto() {
if (this.file) {
if (typeof document != 'undefined') {
(<HTMLInputElement>document.getElementById("photo")).value = "";
}
this.initPhoto();
this.file = null;
}
}
remove() {
this.stakeholderFb.get('isUpload').setValue(false);
this.stakeholderFb.get('isUpload').markAsDirty();
this.removePhoto();
this.stakeholderFb.get('logoUrl').setValue(null);
if (this.stakeholder.isUpload) {
this.deleteCurrentPhoto = true;
}
}
public deletePhoto() {
if (this.stakeholder.logoUrl && this.stakeholder.isUpload) {
this.subscriptions.push(this.utilsService.deletePhoto(this.properties.utilsService + '/delete/' +
encodeURIComponent(this.stakeholder.type) + "/" + encodeURIComponent(this.stakeholder.alias) + "/" + this.stakeholder.logoUrl).subscribe());
}
}
}

View File

@ -0,0 +1,14 @@
import {NgModule} from "@angular/core";
import {EditStakeholderComponent} from "./edit-stakeholder.component";
import {CommonModule} from "@angular/common";
import {InputModule} from "../../../sharedComponents/input/input.module";
import {ReactiveFormsModule} from "@angular/forms";
import {IconsModule} from "../../../utils/icons/icons.module";
import {NotifyFormModule} from "../../../notifications/notify-form/notify-form.module";
@NgModule({
imports: [CommonModule, InputModule, ReactiveFormsModule, IconsModule, NotifyFormModule],
declarations: [EditStakeholderComponent],
exports: [EditStakeholderComponent]
})
export class EditStakeholderModule {}

View File

@ -0,0 +1,19 @@
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard';
import {GeneralComponent} from "./general.component";
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: GeneralComponent,
canDeactivate: [PreviousRouteRecorder],
data: {hasSidebar: true}
}
])
]
})
export class GeneralRoutingModule {
}

View File

@ -0,0 +1,29 @@
<div page-content>
<div actions>
<sidebar-mobile-toggle class="uk-margin-top uk-hidden@m uk-display-block"></sidebar-mobile-toggle>
<div class="uk-section-xsmall uk-container uk-margin-top">
<div class="uk-flex uk-flex-center uk-flex-right@m">
<button class="uk-button uk-button-default uk-margin-right"
(click)="reset()" [class.uk-disabled]="loading || !editStakeholderComponent.dirty"
[disabled]="loading || !editStakeholderComponent.dirty">Reset
</button>
<button class="uk-button uk-button-primary" [class.uk-disabled]="loading || editStakeholderComponent.disabled"
(click)="save()" [disabled]="loading || editStakeholderComponent.disabled">Save
</button>
</div>
</div>
</div>
<div inner>
<div *ngIf="stakeholder" class="uk-container">
<div class="uk-position-relative" style="min-height: 60vh">
<div [class.uk-hidden]="loading" class="uk-section uk-section-small">
<edit-stakeholder #editStakeholderComponent [disableAlias]="true"></edit-stakeholder>
</div>
<div *ngIf="loading" class="uk-position-center">
<loading *ngIf="loading"></loading>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,75 @@
import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {StakeholderService} from "../../monitor/services/stakeholder.service";
import {EnvProperties} from "../../utils/properties/env-properties";
import {Stakeholder} from "../../monitor/entities/stakeholder";
import { Subscription, zip} from "rxjs";
import {EditStakeholderComponent} from "./edit-stakeholder/edit-stakeholder.component";
import {properties} from "src/environments/environment";
import {Title} from "@angular/platform-browser";
@Component({
selector: 'general',
templateUrl: "./general.component.html"
})
export class GeneralComponent implements OnInit, OnDestroy {
public stakeholder: Stakeholder;
public alias: string[];
public properties: EnvProperties = properties;
public defaultStakeholders: Stakeholder[];
public loading: boolean = false;
private subscriptions: any[] = [];
@ViewChild('editStakeholderComponent') editStakeholderComponent: EditStakeholderComponent;
constructor(private stakeholderService: StakeholderService,
private cdr: ChangeDetectorRef,
private title: Title) {
}
ngOnInit() {
this.loading = true;
this.subscriptions.push(this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => {
this.stakeholder = stakeholder;
this.cdr.detectChanges();
if(this.stakeholder) {
this.title.setTitle(this.stakeholder.name + " | General");
let data = zip(
this.stakeholderService.getDefaultStakeholders(this.properties.monitorServiceAPIURL),
this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL)
);
this.subscriptions.push(data.subscribe(res => {
this.defaultStakeholders = res[0];
this.alias = res[1];
this.reset();
this.loading = false;
}));
}
}));
}
public reset() {
this.editStakeholderComponent.init(this.stakeholder, this.alias, this.defaultStakeholders, this.stakeholder.defaultId == null, false)
}
public save() {
this.loading = true;
this.editStakeholderComponent.save((stakeholder) => {
this.stakeholder = stakeholder;
this.stakeholderService.setStakeholder(this.stakeholder);
this.reset();
this.loading = false;
}, (error) => {
console.error(error);
this.loading = false;
});
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => {
if(subscription instanceof Subscription) {
subscription.unsubscribe();
}
});
}
}

View File

@ -0,0 +1,40 @@
import {NgModule} from "@angular/core";
import {GeneralComponent} from "./general.component";
import {GeneralRoutingModule} from "./general-routing.module";
import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard";
import {CommonModule} from "@angular/common";
import {RouterModule} from "@angular/router";
import {InputModule} from "../../sharedComponents/input/input.module";
import {LoadingModule} from "../../utils/loading/loading.module";
import {AlertModalModule} from "../../utils/modal/alertModal.module";
import {ReactiveFormsModule} from "@angular/forms";
import {EditStakeholderModule} from "./edit-stakeholder/edit-stakeholder.module";
import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module";
import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module";
import {
SidebarMobileToggleModule
} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module";
@NgModule({
declarations: [GeneralComponent],
imports: [
GeneralRoutingModule,
CommonModule,
RouterModule,
InputModule,
LoadingModule,
AlertModalModule,
ReactiveFormsModule,
EditStakeholderModule,
PageContentModule,
LogoUrlPipeModule,
SidebarMobileToggleModule
],
providers: [
PreviousRouteRecorder,
],
exports: [GeneralComponent]
})
export class GeneralModule {
}

View File

@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard';
import {ManageStakeholdersComponent} from "./manageStakeholders.component";
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: ManageStakeholdersComponent,
canDeactivate: [PreviousRouteRecorder]
}
])
]
})
export class ManageStakeholdersRoutingModule {
}

View File

@ -0,0 +1,138 @@
<div page-content>
<div header>
<sidebar-mobile-toggle class="uk-margin-top uk-hidden@m uk-display-block"></sidebar-mobile-toggle>
<div *ngIf="isCurator()" class="uk-margin-remove-bottom uk-margin-medium-top">
<slider-tabs [type]="'dynamic'" (activeEmitter)="tab = $event">
<slider-tab tabTitle="All" [tabId]="'all'" [active]="tab === 'all'"></slider-tab>
<slider-tab tabTitle="Profile templates" [tabId]="'templates'" [active]="tab === 'templates'"></slider-tab>
<slider-tab tabTitle="Profiles" [tabId]="'profiles'" [active]="tab === 'profiles'"></slider-tab>
</slider-tabs>
</div>
</div>
<div actions>
<div class="uk-section-xsmall">
<div class="uk-flex uk-flex-right@m uk-flex-center uk-flex-wrap uk-flex-middle">
<div search-input [searchControl]="filters.get('keyword')" [expandable]="true" placeholder="Search Profiles" searchInputClass="outer"
class="uk-width-1-3@xl uk-width-2-5@l uk-width-1-2@m uk-width-1-1 uk-flex uk-flex-right"></div>
</div>
</div>
</div>
<div inner>
<div *ngIf="loading" class="uk-margin-medium-top uk-padding-large">
<loading></loading>
</div>
<div *ngIf="!loading" uk-height-match="target: .titleContainer; row: false">
<div uk-height-match="target: .logoContainer; row: false">
<div *ngIf="tab != 'profiles' && isCurator()" class="uk-section">
<h4>Profile Templates</h4>
<div class="uk-grid uk-child-width-1-3@l uk-child-width-1-2@m uk-child-width-1-1 uk-grid-match" uk-grid>
<ng-template ngFor [ngForOf]="displayDefaultStakeholders" let-stakeholder>
<ng-container *ngTemplateOutlet="stakeholderBox; context: {stakeholder:stakeholder}"></ng-container>
</ng-template>
<div *ngIf="!loading && isCurator()">
<ng-container *ngTemplateOutlet="newBox; context: {text:'Create a new default profile.', isDefault:true}"></ng-container>
</div>
</div>
</div>
<div *ngIf="!isManager()" class="message">
<h4 class="uk-text-center">
No profiles to manage yet
</h4>
</div>
<div *ngIf="tab != 'templates' && isManager()" class="uk-section">
<h4>Profiles</h4>
<div class="uk-grid uk-grid-match uk-child-width-1-3@l uk-child-width-1-2@m uk-child-width-1-1" uk-grid>
<ng-template ngFor [ngForOf]="displayStakeholders" let-stakeholder>
<ng-container *ngTemplateOutlet="stakeholderBox; context: {stakeholder:stakeholder}"></ng-container>
</ng-template>
<div *ngIf="!loading && isCurator()">
<ng-container *ngTemplateOutlet="newBox; context: {text:'Create a new profile by selecting the type ('+typesAsString+') and ' +
'select indicators based on a default or a blank profile.', isDefault:false}"></ng-container>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #stakeholderBox let-stakeholder="stakeholder">
<div *ngIf="stakeholder">
<div class="uk-card uk-card-default uk-card-body uk-position-relative" [ngClass]="stakeholder.type">
<div class="uk-position-top-right uk-margin-small-right uk-margin-small-top">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon [flex]="true" [name]="stakeholderUtils.visibilityIcon.get(stakeholder.visibility)" ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element class="uk-dropdown" uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0;">
<ul class="uk-nav uk-dropdown-nav">
<li>
<a (click)="editStakeholder(stakeholder, !stakeholder.defaultId); hide(element)">Edit</a>
</li>
<li *ngIf="isCurator">
<a (click)="createReport(stakeholder);hide(element)">Cache Indicators</a>
</li>
<li class="uk-nav-divider"></li>
<ng-template ngFor [ngForOf]="stakeholderUtils.visibility" let-v>
<li [class.uk-active]="stakeholder.visibility === v.value">
<a (click)="changeStakeholderStatus(stakeholder, v.value);hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="stakeholder.visibility === v.value" [flex]="true" name="done" class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<hr *ngIf="isProfileManager(stakeholder)" class="uk-nav-divider">
<li *ngIf="isProfileManager(stakeholder)"><a
(click)="deleteStakeholderOpen(stakeholder);hide(element)">Delete</a>
</li>
</ul>
</div>
</div>
<a class="uk-display-block uk-text-center uk-link-reset" [routerLink]="'/admin/' + stakeholder.alias">
<div class="titleContainer uk-h6 uk-margin-remove-bottom uk-margin-top multi-line-ellipsis lines-2">
<p *ngIf="stakeholder.name" class="uk-margin-remove">
{{stakeholder.name}}
</p>
</div>
<div class="logoContainer uk-margin-top uk-flex uk-flex-column uk-flex-center uk-flex-middle">
<img [src]="stakeholder | logoUrl" class="uk-blend-multiply" style="max-height: 80px;">
</div>
</a>
</div>
</div>
</ng-template>
<ng-template #newBox let-text="text" let-isDefault="isDefault">
<ng-container *ngIf="!loading">
<div class="uk-card uk-card-default uk-text-center uk-card-body clickable" (click)="editStakeholder(null, isDefault)">
<div class="uk-text-small uk-text-muted">
{{text}}
</div>
<div class="uk-margin-medium-top uk-margin-small-bottom">
<span class="uk-text-secondary">
<icon name="add" [ratio]="3"></icon>
</span>
</div>
</div>
</ng-container>
</ng-template>
<modal-alert #editStakeholderModal [large]="true" classTitle="uk-background-primary uk-light"
(alertOutput)="editStakeholderComponent.save(callback)" (cancelOutput)="editStakeholderComponent.removePhoto()"
[okDisabled]="editStakeholderComponent.disabled">
<div class="uk-height-large uk-position-relative" *ngIf="editStakeholderComponent.loading">
<loading class="uk-position-center"></loading>
</div>
<div class="uk-padding" [class.uk-hidden]="editStakeholderComponent.loading">
<edit-stakeholder #editStakeholderComponent></edit-stakeholder>
</div>
</modal-alert>
<modal-alert #deleteStakeholderModal [overflowBody]="false" (alertOutput)="deleteStakeholder()">
<div class="uk-height-medium uk-position-relative" *ngIf="deleteLoading">
<loading class="uk-position-center"></loading>
</div>
<div *ngIf="!deleteLoading">
This stakeholder will permanently be deleted. Are you sure you want to proceed?
</div>
</modal-alert>

View File

@ -0,0 +1,23 @@
@import (reference) "~src/assets/openaire-theme/less/color.less";
.setType(@color) {
border-bottom: 4px solid fade(@color, 30%);
& .type {
color: @color;
}
}
.uk-card {
&.funder {
.setType(@funder-color);
}
&.ri {
.setType(@ri-color);
}
&.organization {
.setType(@organization-color);
}
}

View File

@ -0,0 +1,311 @@
import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {StakeholderService} from "../../monitor/services/stakeholder.service";
import {EnvProperties} from "../../utils/properties/env-properties";
import {Stakeholder, StakeholderEntities, Visibility} from "../../monitor/entities/stakeholder";
import {Subscriber, zip} from "rxjs";
import {StakeholderUtils} from "../utils/indicator-utils";
import {UntypedFormBuilder, UntypedFormGroup} from "@angular/forms";
import {AlertModal} from "../../utils/modal/alert";
import {Option} from "../../sharedComponents/input/input.component";
import {Title} from "@angular/platform-browser";
import {UserManagementService} from "../../services/user-management.service";
import {Session} from "../../login/utils/helper.class";
import {EditStakeholderComponent} from "../general/edit-stakeholder/edit-stakeholder.component";
import {properties} from "src/environments/environment";
import {ActivatedRoute} from "@angular/router";
import {CacheIndicatorsService} from "../utils/cache-indicators/cache-indicators.service";
import {NotificationHandler} from "../../utils/notification-handler";
type Tab = 'all' | 'templates'| 'profiles';
declare var UIkit;
@Component({
selector: 'home',
templateUrl: "./manageStakeholders.component.html",
styleUrls: ["./manageStakeholders.component.less"]
})
export class ManageStakeholdersComponent implements OnInit, OnDestroy {
public properties: EnvProperties;
public loading: boolean = true;
public deleteLoading: boolean = false;
public stakeholderUtils: StakeholderUtils = new StakeholderUtils();
public defaultStakeholders: Stakeholder[];
public stakeholders: Stakeholder[];
public alias: string[];
public stakeholder: Stakeholder;
public index: number;
public user = null;
public tab: Tab = 'all';
/**
* Filtered Stakeholders
*/
public displayDefaultStakeholders: Stakeholder[];
public displayStakeholders: Stakeholder[];
/**
* Top filters
*/
public filters: UntypedFormGroup;
public all: Option = {
value: 'all',
label: 'All'
};
public callback: Function;
/**
* Grid or List View
*/
private subscriptions: any[] = [];
@ViewChild('editStakeholderModal', { static: true }) editStakeholderModal: AlertModal;
@ViewChild('deleteStakeholderModal', { static: true }) deleteStakeholderModal: AlertModal;
@ViewChild('editStakeholderComponent', { static: true }) editStakeholderComponent: EditStakeholderComponent;
constructor(private stakeholderService: StakeholderService,
private cacheIndicatorsService: CacheIndicatorsService,
private userManagementService: UserManagementService,
private route: ActivatedRoute,
private title: Title,
private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
this.buildFilters();
this.properties = properties;
this.title.setTitle('Manage profiles');
this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => {
this.user = user;
}));
let data = zip(
this.stakeholderService.getDefaultStakeholders(this.properties.monitorServiceAPIURL),
this.stakeholderService.getMyStakeholders(this.properties.monitorServiceAPIURL),
this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL)
);
this.subscriptions.push(data.subscribe(res => {
this.defaultStakeholders = res[0];
this.stakeholders = res[1];
this.displayDefaultStakeholders = res[0];
this.displayStakeholders = res[1];
this.alias = res[2];
this.loading = false;
}, error => {
this.loading = false;
}));
}
ngOnDestroy(): void {
this.subscriptions.forEach(value => {
if (value instanceof Subscriber) {
value.unsubscribe();
} else if (value instanceof Function) {
value();
}
});
}
hide(element: any) {
UIkit.dropdown(element).hide();
}
private buildFilters() {
this.filters = this.fb.group({
status: this.fb.control('all'),
keyword: this.fb.control('')
});
this.subscriptions.push(this.filters.get('status').valueChanges.subscribe(value => {
this.onStatusChange(value);
}));
this.subscriptions.push(this.filters.get('keyword').valueChanges.subscribe(value => {
this.onKeywordChange(value);
}));
}
onStatusChange(value) {
this.displayDefaultStakeholders = this.filterStatus(this.defaultStakeholders, value);
this.displayStakeholders = this.filterStatus(this.stakeholders, value);
}
onKeywordChange(value) {
this.displayDefaultStakeholders = this.filterByKeyword(this.defaultStakeholders, value);
this.displayStakeholders = this.filterByKeyword(this.stakeholders, value);
}
private filterStatus(stakeholders: Stakeholder[], value): Stakeholder[] {
if (value === 'all') {
return stakeholders;
} else {
return stakeholders.filter(stakeholder => stakeholder.visibility == value);
}
}
private filterByKeyword(stakeholders: Stakeholder[], value): Stakeholder[] {
if (!value) {
return stakeholders;
} else {
return stakeholders.filter(stakeholder =>
stakeholder.name && stakeholder.name.toLowerCase().includes(value.toLowerCase()) ||
stakeholder.type && stakeholder.type.toLowerCase().includes(value.toLowerCase()) ||
stakeholder.index_id && stakeholder.index_id.toLowerCase().includes(value.toLowerCase()) ||
stakeholder.index_shortName && stakeholder.index_shortName.toLowerCase().includes(value.toLowerCase()) ||
stakeholder.index_name && stakeholder.index_name.toLowerCase().includes(value.toLowerCase())
);
}
}
public editStakeholder(stakeholder: Stakeholder = null, isDefault: boolean = false) {
if (isDefault) {
this.index = (stakeholder) ? this.defaultStakeholders.findIndex(value => value._id === stakeholder._id) : -1;
} else {
this.index = (stakeholder) ? this.stakeholders.findIndex(value => value._id === stakeholder._id) : -1;
}
if (!stakeholder) {
this.stakeholder = new Stakeholder(null, null, null,
null, null, null, null, null);
} else {
this.stakeholder = stakeholder;
}
this.editStakeholderComponent.init(this.stakeholder, this.alias, this.defaultStakeholders, isDefault, this.index === -1);
if (this.index !== -1) {
this.callback = (stakeholder: Stakeholder) => {
let index: number;
if (stakeholder.defaultId == null) {
index = this.alias.findIndex(value => value == this.defaultStakeholders[this.index].alias);
this.defaultStakeholders[this.index] = stakeholder;
} else {
index = this.alias.findIndex(value => value == this.stakeholders[this.index].alias);
this.stakeholders[this.index] = stakeholder;
}
if(index != -1) {
this.alias[index] = stakeholder.alias;
}
this.editStakeholderModal.cancel();
};
this.editStakeholderModal.alertTitle = 'Edit ' + this.stakeholder.name;
this.editStakeholderModal.okButtonText = 'Save Changes';
} else {
this.callback = (stakeholder: Stakeholder) => {
if (stakeholder.defaultId === null) {
this.defaultStakeholders.push(stakeholder);
} else {
this.stakeholders.push(stakeholder);
}
this.alias.push(stakeholder.alias);
this.editStakeholderModal.cancel();
};
this.editStakeholderModal.alertTitle = 'Create a new ' + (isDefault?'Default ':'') + 'Profile';
this.editStakeholderModal.okButtonText = 'Create';
}
this.editStakeholderModal.cancelButtonText = 'Cancel';
this.editStakeholderModal.okButtonLeft = false;
this.editStakeholderModal.alertMessage = false;
this.editStakeholderModal.stayOpen = true;
this.editStakeholderModal.open();
}
public createReport(stakeholder: Stakeholder) {
this.cacheIndicatorsService.createReport(stakeholder.alias).subscribe(report => {
NotificationHandler.rise('A caching process for ' + stakeholder.name + ' has been started.' )
}, error => {
console.log(error);
NotificationHandler.rise(error.message(), 'danger');
});
}
public deleteStakeholderOpen(stakeholder: Stakeholder) {
this.stakeholder = stakeholder;
this.deleteStakeholderModal.alertTitle = 'Delete ' + this.stakeholder.index_name;
this.deleteStakeholderModal.cancelButtonText = 'No';
this.deleteStakeholderModal.okButtonText = 'Yes';
this.deleteStakeholderModal.alertMessage = false;
this.deleteStakeholderModal.stayOpen = true;
this.deleteStakeholderModal.open();
}
public deleteStakeholder() {
this.deleteLoading = true;
if (!this.stakeholder.defaultId) {
this.index = (this.stakeholder) ? this.defaultStakeholders.findIndex(value => value._id === this.stakeholder._id) : -1;
} else {
this.index = (this.stakeholder) ? this.stakeholders.findIndex(value => value._id === this.stakeholder._id) : -1;
}
this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, [this.stakeholder._id]).subscribe(() => {
UIkit.notification(this.stakeholder.name+ ' has been <b>successfully deleted</b>', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
if (!this.stakeholder.defaultId) {
this.defaultStakeholders.splice(this.index, 1);
} else {
this.stakeholders.splice(this.index, 1);
}
this.alias = this.alias.filter(item => item !== this.stakeholder.alias);
this.deleteLoading = false;
this.deleteStakeholderModal.cancel();
}, error => {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.deleteLoading = false;
this.deleteStakeholderModal.cancel();
}));
}
changeStakeholderStatus(stakeholder: Stakeholder, visibility: Visibility) {
let path = [
stakeholder._id
];
this.subscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility).subscribe(returnedElement => {
stakeholder.visibility = returnedElement.visibility;
UIkit.notification(stakeholder.name+ '\'s status has been <b>successfully changed</b> to ' + stakeholder.visibility.toLowerCase(), {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
}, error => {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
}));
}
public isManager(): boolean {
return this.isCurator() || (Session.isKindOfMonitorManager(this.user));
}
public isProfileManager(stakeholder: Stakeholder): boolean {
return this.isCurator() || (Session.isManager(stakeholder.type, stakeholder.alias, this.user));
}
public isCurator(): boolean {
return this.isAdmin() || Session.isMonitorCurator(this.user);
}
public isAdmin(): boolean {
return Session.isPortalAdministrator(this.user);
}
get typesAsString() {
return this.stakeholderUtils.types.slice(0, this.stakeholderUtils.types.length - 1).map(type => type.label).join(', ') +
' or ' + this.stakeholderUtils.types[this.stakeholderUtils.types.length - 1].label
}
private isTab(tab: Tab): boolean {
switch (tab) {
case "all":
return true;
case "profiles":
return true;
case "templates":
return true;
default:
return false;
}
}
}

View File

@ -0,0 +1,50 @@
import {NgModule} from "@angular/core";
import {ManageStakeholdersComponent} from "./manageStakeholders.component";
import {ManageStakeholdersRoutingModule} from "./manageStakeholders-routing.module";
import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard";
import {CommonModule} from "@angular/common";
import {RouterModule} from "@angular/router";
import {InputModule} from "../../sharedComponents/input/input.module";
import {LoadingModule} from "../../utils/loading/loading.module";
import {AlertModalModule} from "../../utils/modal/alertModal.module";
import {ReactiveFormsModule} from "@angular/forms";
import {IconsModule} from "../../utils/icons/icons.module";
import {IconsService} from "../../utils/icons/icons.service";
import {earth, incognito, restricted} from "../../utils/icons/icons";
import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module";
import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module";
import {SearchInputModule} from "../../sharedComponents/search-input/search-input.module";
import {
SidebarMobileToggleModule
} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module";
import {SliderTabsModule} from "../../sharedComponents/tabs/slider-tabs.module";
import {EditStakeholderModule} from "../general/edit-stakeholder/edit-stakeholder.module";
@NgModule({
declarations: [ManageStakeholdersComponent],
imports: [
ManageStakeholdersRoutingModule,
CommonModule,
RouterModule,
InputModule,
LoadingModule,
AlertModalModule,
ReactiveFormsModule,
EditStakeholderModule,
IconsModule,
PageContentModule,
LogoUrlPipeModule,
SearchInputModule,
SidebarMobileToggleModule,
SliderTabsModule
],
providers: [
PreviousRouteRecorder,
],
exports: [ManageStakeholdersComponent]
})
export class ManageStakeholdersModule {
constructor(private iconsService: IconsService) {
this.iconsService.registerIcons([earth, incognito, restricted]);
}
}

View File

@ -0,0 +1,490 @@
<div *ngIf="stakeholder && canEdit" class="uk-section">
<div *ngIf="numberSections">
<h5 class="uk-text-bold">Number Indicators</h5>
<div class="uk-grid uk-grid-large uk-child-width-1-1" uk-grid>
<div *ngFor="let number of numbers; let i=index">
<div class="section">
<div class="tools">
<div class="uk-flex uk-flex-middle">
<a [class.uk-disabled]="editing" class="" (click)="createSection(i, 'number')"
uk-tooltip="Create a new section">
<icon name="add" [flex]="true"></icon>
</a>
<a *ngIf="!number.defaultId" [attr.uk-tooltip]="'Delete section'"
(click)="deleteSectionOpen(number, i, 'number', 'delete')">
<icon name="close" [flex]="true"></icon>
</a>
<!-- <ng-container *ngIf="!stakeholder.defaultId">-->
<!-- <button [disabled]="editing || number.defaultId " class="md-btn md-btn-mini"-->
<!-- [title]="(number.defaultId?'Default sections cannot be deleted':'Delete all related sections')"-->
<!-- (click)="deleteSectionOpen(number, i, 'number', 'delete')"><i class="material-icons">highlight_off</i>-->
<!-- </button>-->
<!-- <button [disabled]="editing || number.defaultId " class="md-btn md-btn-mini"-->
<!-- [title]="(number.defaultId?'Default sections cannot be deleted':'Delete section and disconnect related')"-->
<!-- (click)="deleteSectionOpen(number, i, 'number', 'disconnect')"><i class="material-icons">link_off</i>-->
<!-- </button>-->
<!-- </ng-container>-->
</div>
</div>
<div *ngIf="numberSections.at(i)" class="uk-margin-medium-bottom">
<div input [formInput]="numberSections.at(i).get('title')"
(focusEmitter)="saveSection($event, numberSections.at(i), i, 'number')"
class="uk-width-1-3@m uk-width-1-1" placeholder="Title" inputClass="border-bottom"></div>
</div>
<div [id]="'number-' + number._id" class="uk-grid uk-grid-small uk-grid-match" uk-sortable="group: number" uk-grid>
<ng-template ngFor [ngForOf]="number.indicators" let-indicator let-j="index">
<div *ngIf="indicator" [id]="indicator._id"
[ngClass]="getNumberClassBySize(indicator.width)">
<div class="uk-card uk-card-default uk-padding-small number-card uk-position-relative">
<div *ngIf="!dragging"
class="uk-position-top-right uk-margin-small-right uk-margin-small-top">
<a class="uk-link-reset uk-flex uk-flex-middle" [class.uk-disabled]="editing">
<icon [flex]="true" [name]="stakeholderUtils.visibilityIcon.get(indicator.visibility)" ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element class="uk-dropdown" uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0">
<ul class="uk-nav uk-dropdown-nav">
<ng-container *ngIf="isCurator">
<li><a (click)="editNumberIndicatorOpen(number, indicator._id); hide(element)">Edit</a></li>
<li class="uk-nav-divider"></li>
</ng-container>
<ng-template ngFor [ngForOf]="stakeholderUtils.visibility" let-v>
<li>
<a (click)="changeIndicatorStatus(number._id, indicator, v.value);hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="indicator.visibility === v.value" [flex]="true" name="done" class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<ng-container *ngIf="!indicator.defaultId && !editing && isCurator">
<li class="uk-nav-divider">
<li><a (click)="deleteIndicatorOpen(number, indicator._id, 'number', 'delete');hide(element)">Delete</a></li>
</ng-container>
</ul>
</div>
</div>
<div class="uk-text-small uk-text-truncate uk-margin-xsmall-bottom uk-margin-right">{{indicator.name}}</div>
<div class="number uk-text-small uk-text-bold">
<span *ngIf="numberResults.get(i + '-' + j)" [innerHTML]="(indicator.indicatorPaths[0].format == 'NUMBER'?(numberResults.get(i + '-' + j) | numberRound: 2:1:stakeholder.locale):(numberResults.get(i + '-' + j) | numberPercentage: stakeholder.locale))"></span>
<span *ngIf="!numberResults.get(i + '-' + j)">--</span>
</div>
</div>
</div>
</ng-template>
</div>
<div *ngIf="isCurator" class="uk-margin-top">
<div class="uk-grid uk-grid-small" uk-grid>
<div [ngClass]="getNumberClassBySize('small')">
<a class="uk-card uk-card-default number-card uk-padding-small uk-flex uk-flex-middle uk-link-reset" (click)="editNumberIndicatorOpen(number)">
<div class="uk-text-background uk-margin-right">
<icon name="add" [flex]="true" ratio="1.5"></icon>
</div>
<div class="uk-text-bold uk-margin-remove">
Create a number Indicator
</div>
</a>
</div>
</div>
</div>
</div>
</div>
<div>
<ng-container *ngTemplateOutlet="new_section; context:{type: 'number'}"></ng-container>
</div>
</div>
</div>
<div *ngIf="chartSections" class="uk-margin-large-top">
<h5 class="uk-text-bold">Chart Indicators</h5>
<div class="uk-grid uk-grid-large uk-child-width-1-1" uk-grid>
<div *ngFor="let chart of charts; let i=index">
<div class="section uk-margin-top uk-padding-small">
<div class="tools">
<div class="uk-flex uk-flex-middle">
<a [class.uk-disabled]="editing" class="" (click)="createSection(i)"
title="Create a new section">
<icon name="add" [flex]="true"></icon>
</a>
<a *ngIf="!chart.defaultId" [attr.uk-tooltip]="'Delete section'"
(click)="deleteSectionOpen(chart, i, 'chart', 'delete')">
<icon name="close" [flex]="true"></icon>
</a>
<!-- <ng-container *ngIf="!stakeholder.defaultId">-->
<!-- <button [disabled]="editing || chart.defaultId " class="md-btn md-btn-mini"-->
<!-- [title]="(chart.defaultId?'Default sections cannot be deleted':'Delete all related sections')"-->
<!-- (click)="deleteSectionOpen(chart, i, 'chart', 'delete')"><i class="material-icons">highlight_off</i>-->
<!-- </button>-->
<!-- <button [disabled]="editing || chart.defaultId " class="md-btn md-btn-mini"-->
<!-- [title]="(chart.defaultId?'Default sections cannot be deleted':'Delete section and disconnect related')"-->
<!-- (click)="deleteSectionOpen(chart, i, 'chart', 'disconnect')"><i class="material-icons">link_off</i>-->
<!-- </button>-->
<!-- </ng-container>-->
</div>
</div>
<div *ngIf="chartSections.at(i)"
class="uk-margin-medium-bottom">
<div input [formInput]="chartSections.at(i).get('title')"
(focusEmitter)="saveSection($event, chartSections.at(i), i)"
class="uk-width-1-3@m uk-width-1-1" placeholder="Title" inputClass="border-bottom"></div>
</div>
<div [id]="'chart-' + chart._id" class="uk-grid uk-grid-small uk-grid-match" uk-sortable="group: chart" uk-grid>
<ng-template ngFor [ngForOf]="chart.indicators" let-indicator let-j="index">
<div *ngIf="indicator" [id]="indicator._id" [ngClass]="getChartClassBySize(indicator.width)">
<div class="uk-card uk-card-default uk-card-body uk-position-relative">
<div *ngIf="!dragging"
class="uk-position-top-right uk-margin-small-right uk-margin-small-top">
<a class="uk-link-reset uk-flex uk-flex-middle" [class.uk-disabled]="editing">
<icon [flex]="true" [name]="stakeholderUtils.visibilityIcon.get(indicator.visibility)" ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element class="uk-dropdown" uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0">
<ul class="uk-nav uk-dropdown-nav">
<ng-container *ngIf="isCurator">
<li><a (click)="editChartIndicatorOpen(chart, indicator._id); hide(element)">Edit</a></li>
<li class="uk-nav-divider"></li>
</ng-container>
<ng-template ngFor [ngForOf]="stakeholderUtils.visibility" let-v>
<li>
<a (click)="changeIndicatorStatus(chart._id, indicator, v.value);">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="indicator.visibility === v.value" [flex]="true" name="done" class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<ng-container *ngIf="!indicator.defaultId && !editing && isCurator">
<li class="uk-nav-divider">
<li><a (click)="deleteIndicatorOpen(chart, indicator._id, 'chart', 'delete');hide(element)">Delete</a></li>
</ng-container>
</ul>
</div>
</div>
<div>
<div *ngIf="indicator.name" class="uk-text-center uk-text-bold uk-margin-small-bottom">
{{indicator.name}}
</div>
<iframe *ngIf="!properties.disableFrameLoad && indicator.indicatorPaths[0] && indicator.indicatorPaths[0].source !=='image' &&
safeUrls.get(indicatorUtils.getFullUrl(stakeholder, indicator.indicatorPaths[0]))"
allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"
[src]="safeUrls.get(indicatorUtils.getFullUrl(stakeholder, indicator.indicatorPaths[0]))"
class="uk-width-1-1" [ngClass]="'uk-height-' + (indicator.height?indicator.height.toLowerCase():'medium')"
[class.uk-blend-multiply]="!isFullscreen"></iframe>
<div *ngIf="properties.disableFrameLoad && indicator.indicatorPaths &&
indicator.indicatorPaths.length > 0 && indicator.indicatorPaths[0].source !=='image'">
<img class="uk-width-1-1 uk-blend-multiply" [ngClass]="'uk-height-' + (indicator.height?indicator.height.toLowerCase():'medium')"
src="assets/chart-placeholder.png">
</div>
<div *ngIf="indicator.indicatorPaths && indicator.indicatorPaths[0] &&
indicator.indicatorPaths[0].source === 'image'">
<img class="uk-width-1-1 uk-blend-multiply" [ngClass]="'uk-height-' + (indicator.height?indicator.height.toLowerCase():'medium')"
[src]="indicator.indicatorPaths[0].url">
</div>
<!--<ng-container *ngTemplateOutlet="description; context: {indicator:indicator}"></ng-container>-->
</div>
</div>
</div>
</ng-template>
</div>
<div *ngIf="isCurator" class="uk-margin-top">
<div class="uk-grid uk-grid-small uk-grid-match" uk-grid>
<div [ngClass]="getChartClassBySize('small')">
<div class=" uk-card uk-card-default uk-card-body clickable" (click)="editChartIndicatorOpen(chart)">
<h6 class="uk-text-bold uk-text-center">
Create a custom indicator
</h6>
<div class="uk-text-muted uk-text-small">
Use our advance tool to create a custom Indicator that suit the needs of your funding
KPI's.
</div>
<div class="uk-flex uk-flex-center uk-text-background uk-margin-medium-top">
<icon name="add" ratio="3" [flex]="true"></icon>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<ng-container *ngTemplateOutlet="new_section; context:{type: 'chart'}"></ng-container>
</div>
</div>
</div>
</div>
<modal-alert #editNumberModal
[large]="true" classTitle="uk-background-primary uk-light"
(alertOutput)="saveIndicator()"
[okDisabled]="numberIndicatorFb && (numberIndicatorFb.invalid || numberIndicatorFb.pristine)">
<div *ngIf="editing" class="uk-position-relative uk-height-large">
<loading class="uk-position-center"></loading>
</div>
<div [class.uk-hidden]="editing" class="uk-padding-small">
<div *ngIf="numberIndicatorFb" class="uk-grid" [formGroup]="numberIndicatorFb" uk-grid>
<div input class="uk-width-1-1" [formInput]="numberIndicatorFb.get('name')" placeholder="Title"></div>
<div *ngIf="stakeholder.defaultId != '-1' && ( (indicator.description && indicator.description.length > 0) || !stakeholder.defaultId)"
input class="uk-width-1-1" [formInput]="numberIndicatorFb.get('description')" placeholder="Profile description" type="textarea">
</div>
<div input class="uk-width-1-1" *ngIf="stakeholder.defaultId" [formInput]="numberIndicatorFb.get('additionalDescription')"
placeholder="Description" type="textarea">
</div>
<div input class="uk-width-1-2@m" [formInput]="numberIndicatorFb.get('visibility')"
placeholder="Visibility" [options]="stakeholderUtils.visibility" type="select">
</div>
<div input class="uk-width-1-2@m" [formInput]="numberIndicatorFb.get('width')"
placeholder="Number Size" [options]="indicatorUtils.indicatorSizes" type="select">
</div>
<div *ngIf="numberIndicatorPaths" formArrayName="indicatorPaths">
<div *ngFor="let indicatorPath of numberIndicatorPaths.controls; let i=index" [formGroupName]="i">
<div class="uk-grid" uk-grid>
<div class="uk-width-1-1">
<div class="uk-grid" uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-middle">
<div input class="uk-width-expand" [formInput]="indicatorPath.get('url')" placeholder="Number URL">
<div *ngIf="urlParameterizedMessage" warning>{{urlParameterizedMessage}}</div>
</div>
<div class='uk-padding-small'>
<a class="uk-link-reset" (click)="copyToClipboard(indicatorPath.get('url').value)"><icon [flex]="true" name="content_copy"></icon></a>
</div>
</div>
<div *ngIf="showCheckForSchemaEnhancements" class="uk-width-1-1">
<div class="uk-alert uk-alert-warning">
There are schema enhancements that can be applied in this query.<a
(click)="indicatorPath.get('url').setValue(indicatorUtils.applySchemaEnhancements(indicatorPath.get('url').value)); indicatorPath.get('url').markAsDirty()">Apply
now</a>
</div>
</div>
<div class="uk-width-1-2@m">
<div input [formInput]="indicatorPath.get('source')" placeholder="Source"
[options]="isAdministrator?indicatorUtils.allSourceTypes:indicatorUtils.sourceTypes" type="select">
</div>
</div>
<div class="uk-width-1-2@m">
<div input [formInput]="indicatorPath.get('format')" placeholder="Format"
[options]="indicatorUtils.formats" type="select">
</div>
</div>
</div>
</div>
<div formArrayName="jsonPath" class="uk-width-1-1">
<h6 class="uk-text-bold uk-margin-remove-bottom">
<span>JSON Path</span>
</h6>
<div *ngIf="numberIndicatorPaths.at(i).get('result').invalid && numberIndicatorPaths.at(i).get('result').errors.required">
<div class="uk-text-danger uk-text-small">
This JSON path is not valid or the result has not been calculated yet.
Please press calculate on box below to see the result.
</div>
</div>
<div class="uk-grid uk-child-width-1-3@m uk-child-width-1-1 uk-margin-top uk-flex-middle" uk-grid>
<div *ngFor="let jsonPath of getJsonPath(i).controls; let j=index" class="uk-flex uk-flex-middle">
<div input class="uk-width-1-1" [formInput]="jsonPath" [placeholder]="'Level ' + +(j + 1)"></div>
<a [class.uk-invisible]="getJsonPath(i).length === 1 || numberIndicatorFb.get('defaultId').value"
class="uk-margin-small-left uk-text-danger"
[class.uk-disabled]="getJsonPath(i).disabled"
(click)="removeJsonPath(i, j)">
<icon name="close"></icon>
</a>
<span [class.uk-invisible]="getJsonPath(i).disabled || j === (getJsonPath(i).controls.length - 1)" class="uk-text-center uk-margin-small-left">
<icon name="east"></icon>
</span>
</div>
<div *ngIf="indicator.defaultId === null">
<button class="uk-icon-button uk-button-primary" (click)="addJsonPath(i)">
<icon name="add" [flex]="true"></icon>
</button>
</div>
</div>
</div>
<div class="uk-width-1-1 uk-flex uk-flex-center">
<div class="uk-flex uk-position-relative">
<span class="uk-padding number number-preview uk-flex uk-flex-column uk-flex-center uk-text-center">
<span *ngIf="numberIndicatorPaths.at(i).get('result').valid && numberIndicatorPaths.at(i).get('result').value !== 0"
[innerHTML]="(numberIndicatorPaths.at(i).get('format').value == 'NUMBER'?(numberIndicatorPaths.at(i).get('result').value | numberRound: 2:1:stakeholder.locale):(numberIndicatorPaths.at(i).get('result').value | numberPercentage: stakeholder.locale))">
</span>
<span *ngIf="numberIndicatorPaths.at(i).get('result').valid && numberIndicatorPaths.at(i).get('result').value === 0">
--
</span>
</span>
<div *ngIf="numberIndicatorPaths.at(i).get('result').invalid"
class="uk-width-1-1 uk-height-1-1 refresh-indicator">
<div class="uk-position-relative uk-height-1-1">
<a class="uk-position-center uk-text-center uk-text-small uk-link-reset"
[class.uk-disabled]="numberIndicatorPaths.at(i).get('url').invalid"
(click)="validateJsonPath(i, true)">
<div>
<icon name="refresh"></icon>
</div>
<span *ngIf="numberIndicatorPaths.at(i).get('result').errors.required">Calculate</span>
<span *ngIf="numberIndicatorPaths.at(i).get('result').errors.validating">Calculating...</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div #editNumberNotify notify-form class="uk-width-1-1 uk-margin-medium-top"></div>
</div>
</modal-alert>
<modal-alert #editChartModal [large]="true" (alertOutput)="saveIndicator()" classTitle="uk-background-primary uk-light"
[okDisabled]="chartIndicatorFb && (chartIndicatorFb.invalid || chartIndicatorFb.pristine)">
<div *ngIf="editing" class="uk-position-relative uk-height-large">
<loading class="uk-position-center"></loading>
</div>
<div [class.uk-hidden]="editing" class="uk-padding-small">
<div *ngIf="chartIndicatorFb" [formGroup]="chartIndicatorFb" class="uk-grid" uk-grid>
<div input class="uk-width-1-1" [formInput]="chartIndicatorFb.get('name')" placeholder="Title"></div>
<div *ngIf="stakeholder.defaultId != '-1' && ((indicator.description && indicator.description.length > 0) || !stakeholder.defaultId)"
input class="uk-width-1-1" [formInput]="chartIndicatorFb.get('description')" placeholder="Default Description" type="textarea">
</div>
<div *ngIf="stakeholder.defaultId" input class="uk-width-1-1" [formInput]="chartIndicatorFb.get('additionalDescription')"
placeholder="Description" type="textarea">
</div>
<div input class="uk-width-1-2@m" [formInput]="chartIndicatorFb.get('visibility')"
placeholder="Status" [options]="stakeholderUtils.visibility" type="select">
</div>
<div input class="uk-width-1-2@m" [formInput]="chartIndicatorFb.get('width')" placeholder="Chart width"
[options]="indicatorUtils.indicatorSizes" type="select">
</div>
<div input class="uk-width-1-2@m" [formInput]="chartIndicatorFb.get('height')" placeholder="Chart height"
[options]="indicatorUtils.indicatorSizes" type="select">
</div>
<div *ngIf="chartIndicatorPaths" formArrayName="indicatorPaths" class="uk-width-1-1">
<div *ngFor="let indicatorPath of chartIndicatorPaths.controls; let i=index;"
[formGroupName]="i" class="uk-grid" uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-middle">
<div input class="uk-width-expand" [title]="indicatorPath.get('url').disabled?'Default chart URLs cannot change':''"
[formInput]="indicatorPath.get('url')" placeholder="Chart URL">
<div *ngIf="urlParameterizedMessage" warning>{{urlParameterizedMessage}}</div>
</div>
<div class='uk-padding-small'>
<a class="uk-link-reset" (click)="copyToClipboard(indicatorPath.get('url').value)"><icon [flex]="true" name="content_copy"></icon></a>
</div>
</div>
<div *ngIf="showCheckForSchemaEnhancements" class=" uk-width-1-1 ">
<div class="uk-alert uk-alert-warning">
There are schema enhancements that can be applied in this query. <a
(click)="indicatorPath.get('url').setValue(indicatorUtils.applySchemaEnhancements(indicatorPath.get('url').value)); indicatorPath.get('url').markAsDirty()">Apply
now</a>
</div>
</div>
<div class="uk-width-1-1" formArrayName="parameters">
<div class="uk-grid" uk-grid>
<div *ngIf="getParameter(i, 'title')" input class="uk-width-1-1" [formInput]="getParameter(i, 'title').get('value')"
placeholder="Chart Title"></div>
<div *ngIf="getParameter(i, 'subtitle')" input class="uk-width-1-1" placeholder="Chart Subtitle" [formInput]="getParameter(i, 'subtitle').get('value')" label="Chart Subtitle"></div>
<div *ngIf="!getParameter(i, 'type')" input class="uk-width-1-3@s" [formInput]="indicatorPath.get('type')" placeholder="Chart Type"
[options]="(indicatorPath.get('type').value == 'table' && getParameter(i, 'data_title_0'))?indicatorUtils.getChartTypes(indicatorPath.get('type').value):indicatorUtils.allChartTypes"
type="select"></div>
<div *ngIf="getParameter(i, 'type')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'type').get('value')" placeholder="Chart Type"
[options]="indicatorUtils.getChartTypes(getParameter(i, 'type').get('value').value)" type="select"></div>
<div *ngIf="getParameter(i, 'xAxisTitle')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'xAxisTitle').get('value')"
placeholder="X-Axis Title"></div>
<div *ngIf="getParameter(i, 'yAxisTitle')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'yAxisTitle').get('value')"
placeholder="Y-Axis Title"></div>
<div *ngIf="getParameter(i, 'data_title_0')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'data_title_0').get('value')"
placeholder="Data Title"></div>
<div *ngIf="getParameter(i, 'data_title_1')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'data_title_1').get('value')"
placeholder="Data Title"></div>
<div *ngIf="getParameter(i, 'start_year')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'start_year').get('value')"
placeholder="Year (From)"></div>
<div *ngIf="getParameter(i, 'end_year')" input class="uk-width-1-3@s" [formInput]="getParameter(i, 'end_year').get('value')"
placeholder="Year (To)"></div>
</div>
</div>
<div *ngIf="indicator && indicator.indicatorPaths[i] && indicator.indicatorPaths[i].safeResourceUrl"
class="uk-margin-medium-top uk-position-relative uk-width-1-1 uk-flex uk-flex-center">
<div *ngIf="(hasDifference(i)) && !indicatorPath.invalid"
class="uk-width-1-1 uk-height-large refresh-indicator">
<div class="uk-position-relative uk-height-1-1">
<a class="uk-position-center uk-text-center uk-link-reset" (click)="refreshIndicator()">
<div>
<icon name="refresh"></icon>
</div>
<span>Click to refresh the graph view</span>
</a>
</div>
</div>
<iframe *ngIf="indicator.indicatorPaths[i].source !== 'image'" [class.uk-blend-multiply]="!isFullscreen"
[src]="indicator.indicatorPaths[i].safeResourceUrl" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"
class="uk-width-1-1 uk-height-large"></iframe>
<!-- <div *ngIf="properties.disableFrameLoad && indicator.indicatorPaths[i].source !== 'image'" class="uk-alert uk-alert-danger uk-text-center">I frames-->
<!-- preview is disabled</div>-->
<div *ngIf="indicator.indicatorPaths[i].source === 'image'">
<img class="uk-width-1-1 uk-height-large uk-blend-multiply" [src]="indicator.indicatorPaths[i].url">
</div>
</div>
</div>
</div>
</div>
<div #editChartNotify notify-form class="uk-width-1-1 uk-margin-medium-top"></div>
</div>
</modal-alert>
<modal-alert #deleteModal (alertOutput)="deleteIndicator()" [overflowBody]="false" classTitle="uk-background-primary uk-light">
<div [class.uk-invisible]="editing" class="uk-position-relative">
<div *ngIf="editing">
<loading class="uk-position-center"></loading>
</div>
You are about to delete <span class="uk-text-bold" *ngIf="indicator && index !== -1">
"{{indicator.name ? indicator.name : (indicator.indicatorPaths[0]?.parameters?.title?indicator.indicatorPaths[0].parameters.title:'')}}"</span> indicator permanently.
<div *ngIf="indicatorChildrenActionOnDelete == 'delete'" class="uk-text-bold">
Indicators of all profiles based on this default indicator, will be deleted as well.
</div>
<!-- <span *ngIf="indicatorChildrenActionOnDelete == 'disconnect'" class="uk-text-bold">-->
<!-- Indicators of all profiles based on this default indicator, will not be marked as copied from default anymore.-->
<!-- </span>-->
Are you sure you want to proceed?
<div #deleteNotify notify-form class="uk-width-1-1 uk-margin-medium-top"></div>
</div>
</modal-alert>
<!--<modal-alert #deleteAllModal (alertOutput)="deleteIndicator('delete')">
You are about to delete <span class="uk-text-bold" *ngIf="indicator && index !== -1">
"{{indicator.name ? indicator.name : indicator.indicatorPaths[0].parameters.title}}"</span> indicator permanently.
<span class="uk-text-bold">Indicators of all profiles based on this default indicator, will be deleted as well.</span>
Are you sure you want to proceed?
</modal-alert>
<modal-alert #deleteAndDisconnectModal (alertOutput)="deleteIndicator('disconnect')">
You are about to delete <span class="uk-text-bold" *ngIf="indicator && index !== -1">
"{{indicator.name ? indicator.name : indicator.indicatorPaths[0].parameters.title}}"</span> indicator permanently.
<span class="uk-text-bold">Indicators of all profiles based on this default indicator, will not be marked as copied from default anymore.</span>
Are you sure you want to proceed?
</modal-alert>-->
<modal-alert #deleteSectionModal (alertOutput)="deleteSection()" [overflowBody]="false" classTitle="uk-background-primary uk-light">
<div [class.uk-invisible]="editing" class="uk-position-relative">
<div *ngIf="editing">
<loading class="uk-position-center"></loading>
</div>
You are about to delete this section and its indicators permanently.
<div *ngIf="sectionChildrenActionOnDelete == 'delete' && !stakeholder.defaultId" class="uk-text-bold">
Sections of all profiles based on this default section and their contents, will be deleted as well.
</div>
<!-- <span *ngIf="sectionChildrenActionOnDelete == 'disconnect'" class="uk-text-bold">-->
<!-- Sections of all profiles based on this default section and their contents, will not be marked as copied from default anymore.-->
<!-- </span>-->
Are you sure you want to proceed?
</div>
</modal-alert>
<!--<modal-alert #deleteNumberSectionModal (alertOutput)="deleteSection('number')">
You are about to delete this section and its indicators permanently.
Are you sure you want to proceed?
</modal-alert>
<modal-alert #deleteChartSectionModal (alertOutput)="deleteSection()">
You are about to delete this section and its indicators permanently.
Are you sure you want to proceed?
</modal-alert>-->
<ng-template #new_section let-type="type">
<div class="section">
<div class="uk-flex uk-flex-center" (click)="createSection(-1, type)">
<button class="uk-button uk-button-primary uk-flex uk-flex-middle">
<icon name="add" [flex]="true"></icon>
<span class="uk-margin-small-left">New section</span>
</button>
</div>
</div>
</ng-template>

View File

@ -0,0 +1,53 @@
@import (reference) "~src/assets/openaire-theme/less/_import-variables";
.number-preview {
border: @global-border-width solid @global-border;
background: transparent;
border-radius: @global-border-radius;
min-width: 100px;
min-height: 70px;
}
.refresh-indicator {
background-color: @global-overlay-background;
border-radius: @global-border-radius;
position: absolute;
color: @global-inverse-color;
z-index: 1;
}
.section {
padding: 60px 45px;
border-radius: @global-border-radius;
border: @global-border-width solid @global-border;
position: relative;
background: @global-inverse-color;
border-left: 5px @global-primary-background solid;
.tools {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -100%);
max-width: 50px;
padding: 5px 10px;
background-image: @global-primary-gradient;
color: @global-inverse-color;
-webkit-clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%);
clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%);
display: none;
}
&:hover {
.tools {
display: block;
a {
color: currentColor;
&:hover {
text-decoration: none;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard';
import {TopicComponent} from "./topic.component";
import {CanExitGuard} from "../../utils/can-exit.guard";
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: TopicComponent,
canDeactivate: [PreviousRouteRecorder, CanExitGuard]
}
])
]
})
export class TopicRoutingModule {
}

View File

@ -0,0 +1,383 @@
<div class="uk-flex">
<aside *ngIf="stakeholder" uk-sticky="end: .sidebar_main_swipe;" [attr.offset]="offset" id="sidebar_main">
<div sidebar-content>
<div class="back">
<a [routerLink]="'/admin/' + stakeholder.alias" class="uk-flex uk-flex-middle uk-flex-center">
<div class="uk-width-auto">
<icon name="west" [flex]="true" ratio="1.3"></icon>
</div>
<span class="uk-width-expand uk-text-truncate uk-margin-left hide-on-close">Indicators</span>
</a>
</div>
<div class="menu_section uk-margin-large-top">
<ul class="uk-list uk-nav uk-nav-default" transition-group [id]="'topics'"
uk-nav="duration: 400">
<li *ngFor="let topic of stakeholder.topics; let i=index" class="uk-parent" [class.uk-active]="topicIndex === i"
transition-group-item>
<a [routerLink]="'/admin/'+stakeholder.alias + '/indicators/' + topic.alias"
[title]="topic.name" class="uk-visible-toggle uk-flex uk-flex-middle">
<div *ngIf="topic.icon" class="uk-width-auto">
<icon class="menu-icon" [svg]="topic.icon" ratio="0.9" [flex]="true"></icon>
</div>
<span [class.hide-on-close]="topic.icon"
class="uk-width-expand uk-text-truncate uk-margin-small-left">
{{topic.name}}
</span>
<span class="uk-margin-xsmall-left hide-on-close" [class.uk-invisible-hover]="topicIndex !== i"
(click)="$event.stopPropagation();$event.preventDefault()">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon [flex]="true" [name]="stakeholderUtils.visibilityIcon.get(topic.visibility)"
ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element
uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0; flip: false; container: body">
<ul class="uk-nav uk-dropdown-nav">
<ng-container *ngIf="isCurator">
<li>
<a (click)="editTopicOpen(i); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="edit" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Edit</span>
</div>
</a>
</li>
<li *ngIf="i > 0 || i < stakeholder.topics.length - 1" class="uk-nav-divider"></li>
<li *ngIf="i > 0">
<a (click)="hide(element);moveTopic(i)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="north" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Move Up</span>
</div>
</a>
</li>
<li *ngIf="i < stakeholder.topics.length - 1">
<a (click)="hide(element);moveTopic(i, i + 1)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="south" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Move Down</span>
</div>
</a>
</li>
<li class="uk-nav-divider"></li>
</ng-container>
<ng-template ngFor [ngForOf]="stakeholderUtils.visibility" let-v>
<li [class.uk-active]="topic.visibility === v.value">
<a (click)="openVisibilityModal(i, v.value, 'topic'); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="topic.visibility === v.value" [flex]="true" name="done"
class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<ng-container *ngIf="!topic.defaultId && isCurator">
<li class="uk-nav-divider">
<li>
<a (click)="deleteTopicOpen(i, 'delete'); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="delete" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Delete</span>
</div>
</a>
<!--<ng-container *ngIf="!stakeholder.defaultId">
<a (click)="deleteTopicOpen(i, 'delete'); hide(element)">Delete from all profiles</a>
<a (click)="deleteTopicOpen(i, 'disconnect'); hide(element)">Delete and disconnect from all profiles</a>
</ng-container>-->
</li>
</ng-container>
</ul>
</div>
</span>
<span class="uk-nav-parent-icon hide-on-close"></span>
</a>
<ul *ngIf="isBrowser || topicIndex === i" class="uk-nav-sub" [id]="'categories-' + i.toString()"
transition-group>
<li *ngFor="let category of topic.categories; let j=index" transition-group-item class="uk-visible-toggle"
[class.uk-active]="categoryIndex == j">
<a (click)="chooseCategory(j)" [title]="category.name">
<div class="uk-flex uk-flex-middle uk-width-1-1">
<span class="uk-width-expand uk-text-truncate">{{category.name}}</span>
<span class="uk-margin-xsmall-left hide-on-close" [class.uk-invisible-hover]="categoryIndex !== j"
(click)="$event.stopPropagation();$event.preventDefault()">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon [flex]="true" [name]="stakeholderUtils.visibilityIcon.get(category.visibility)"
ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element
uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0; flip: false; container: body">
<ul class="uk-nav uk-dropdown-nav">
<ng-container *ngIf="isCurator">
<li>
<a (click)="editCategoryOpen(j); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="edit" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Edit</span>
</div>
</a>
</li>
<li *ngIf="j > 0 || j < stakeholder.topics[topicIndex].categories.length - 1"
class="uk-nav-divider"></li>
<li *ngIf="j > 0">
<a (click)="hide(element);moveCategory(j)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="north" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Move Up</span>
</div>
</a>
</li>
<li *ngIf="j < stakeholder.topics[topicIndex].categories.length - 1">
<a (click)="hide(element);moveCategory(j, j + 1)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="south" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Move Down</span>
</div>
</a>
</li>
<li class="uk-nav-divider"></li>
</ng-container>
<ng-template ngFor [ngForOf]="stakeholderUtils.visibility" let-v>
<li [class.uk-active]="category.visibility === v.value">
<a (click)="openVisibilityModal(j, v.value, 'category'); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="category.visibility === v.value" [flex]="true" name="done"
class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<ng-container *ngIf="!category.defaultId && isCurator">
<li class="uk-nav-divider">
<li>
<a (click)="deleteCategoryOpen(j, 'delete'); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="delete" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Delete</span>
</div>
</a>
</li>
</ng-container>
</ul>
</div>
</span>
</div>
</a>
</li>
<li *ngIf="isCurator">
<a (click)="editCategoryOpen(); $event.preventDefault()" class="uk-flex uk-flex-middle">
<icon name="add" [flex]="true"></icon>
<span class="hide-on-close">Create new category</span>
</a>
</li>
</ul>
</li>
<li *ngIf="isCurator" class="hide-on-close">
<a (click)="editTopicOpen(-1); $event.preventDefault()">
<div class="uk-flex uk-flex-middle">
<div class="uk-width-auto">
<icon class="menu-icon" name="add" [flex]="true"></icon>
</div>
<span class="uk-width-expand uk-text-truncate uk-margin-small-left hide-on-close">Create new topic</span>
</div>
</a>
</li>
</ul>
</div>
</div>
</aside>
<div #pageContent *ngIf="stakeholder && filters" class="uk-width-1-1" page-content>
<div actions>
<div *ngIf="stakeholder.topics.length > 0" class="uk-flex uk-flex-center uk-margin-medium-top uk-flex-right@m uk-width-1-1">
<button class="uk-button uk-button-primary uk-flex uk-flex-middle">
<icon name="visibility" [flex]="true"></icon>
<span class="uk-margin-small-left uk-margin-small-right">Preview</span>
<icon name="expand_more" [flex]="true"></icon>
</button>
<div #element uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0; flip: false">
<ul class="uk-nav uk-dropdown-nav">
<li><a target="_blank"
[routerLink]="'/' + stakeholder.alias + '/' + stakeholder.topics[topicIndex].alias"
[queryParams]="{view: 'PUBLIC'}"
(click)="hide(element)">Public view</a>
</li>
<li><a target="_blank" [routerLink]="'/' + stakeholder.alias + '/' +
stakeholder.topics[topicIndex].alias"
[queryParams]="{view: 'RESTRICTED'}"
(click)="hide(element)">Restricted view</a>
</li>
<!--<li class="disabled"><a class="uk-disabled uk-text-muted"
uk-tooltip="Note: available only in administration dashboard"
(click)="hide(element)">Private view</a>
</li>-->
</ul>
</div>
</div>
<ul *ngIf="stakeholder.topics.length > 0 && stakeholder.topics[topicIndex].categories.length > 0 && stakeholder.topics[topicIndex].categories[categoryIndex]"
transition-group class="uk-tab uk-margin-xsmall-top" [id]="'subCategories'">
<ng-template ngFor [ngForOf]=" stakeholder.topics[topicIndex].categories[categoryIndex].subCategories"
let-subCategory let-i="index">
<li class="uk-visible-toggle uk-flex" [class.uk-active]="subCategoryIndex === i" transition-group-item>
<a (click)="chooseSubcategory(i)">
<span class="uk-text-uppercase">{{subCategory.name}}</span>
</a>
<span class="uk-flex uk-flex-column uk-flex-center uk-margin-small-left"
[class.uk-invisible-hover]="subCategoryIndex !== i"
(click)="$event.stopPropagation();$event.preventDefault()">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon [flex]="true" [name]="stakeholderUtils.visibilityIcon.get(subCategory.visibility)"
ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0; container: body">
<ul class="uk-nav uk-dropdown-nav">
<ng-container *ngIf="isCurator">
<li>
<a (click)="editSubCategoryOpen(i); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="edit" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Edit</span>
</div>
</a>
</li>
<li *ngIf="i > 0 || i < stakeholder.topics[topicIndex].categories[categoryIndex].subCategories.length - 1"
class="uk-nav-divider"></li>
<li *ngIf="i > 0">
<a (click)="hide(element);moveSubCategory(i)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="west" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Move Left</span>
</div>
</a>
</li>
<li *ngIf="i < stakeholder.topics[topicIndex].categories[categoryIndex].subCategories.length - 1">
<a (click)="hide(element);moveSubCategory(i, i + 1)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="east" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Move Right</span>
</div>
</a>
</li>
<li class="uk-nav-divider"></li>
<li *ngIf="indicators">
<a (click)=" indicators.exportIndicators(i);hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="download" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Export indicators</span>
</div>
</a>
</li>
<li *ngIf="indicators">
<a (click)="file.value = ''; this.index=i; file.click(); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="upload" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Import indicators</span>
</div>
</a>
</li>
<li class="uk-nav-divider"></li>
</ng-container>
<ng-template ngFor [ngForOf]="stakeholderUtils.visibility" let-v>
<li [class.uk-active]="subCategory.visibility === v.value">
<a (click)="openVisibilityModal(i, v.value, 'subcategory'); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="subCategory.visibility === v.value" [flex]="true" name="done"
class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<ng-container *ngIf="!subCategory.defaultId && isCurator">
<li class="uk-nav-divider">
<li>
<a (click)="deleteSubcategoryOpen(i, 'delete'); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="delete" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Delete</span>
</div>
</a>
</li>
</ng-container>
</ul>
</div>
</span>
</li>
</ng-template>
<li *ngIf="isCurator">
<a (click)="editSubCategoryOpen(); $event.preventDefault()" class="uk-flex uk-flex-middle">
<icon name="add" [flex]="true"></icon>
<span class="uk-text-uppercase">Create new subcategory</span>
</a>
</li>
</ul>
</div>
<div inner>
<input #file id="import-file" type="file" class="uk-hidden"
(change)="indicators.fileChangeEvent($event, this.index)"/>
<indicators #indicators [topicIndex]="topicIndex" [categoryIndex]="categoryIndex"
[subcategoryIndex]="subCategoryIndex" [user]="user"
[stakeholder]="stakeholder" [changed]="change.asObservable()"></indicators>
</div>
</div>
</div>
<modal-alert #deleteModal classTitle="uk-background-primary uk-light" (alertOutput)="deleteElement()"
[overflowBody]="false">
<div [class.uk-invisible]="loading" class="uk-position-relative">
<div *ngIf="loading">
<loading class="uk-position-center"></loading>
</div>
You are about to delete <span class="uk-text-bold" *ngIf="element">"{{element.name}}"</span> {{type}} permanently.
<div *ngIf="elementChildrenActionOnDelete == 'delete'" class="uk-text-bold">
{{getPluralTypeName()}} of all profiles based on this default {{type}}, will be deleted as well.
</div>
Are you sure you want to proceed?
</div>
</modal-alert>
<modal-alert #editModal classTitle="uk-background-primary uk-light" (alertOutput)="saveElement()"
[okDisabled]="form && (form.invalid || form.pristine)" [large]="true">
<div *ngIf="loading" class="uk-position-relative uk-height-large">
<loading class="uk-position-center"></loading>
</div>
<div *ngIf="form" [class.uk-hidden]="loading"
class="uk-grid uk-padding uk-padding-remove-horizontal uk-child-width-1-1" [formGroup]="form" uk-grid>
<div input [formInput]="form.get('name')" class="uk-width-1-2@m" placeholder="Title"></div>
<div input [formInput]="form.get('visibility')" class="uk-width-1-2@m" placeholder="Status"
[options]="stakeholderUtils.visibility" type="select"></div>
<div input [formInput]="form.get('description')" placeholder="Description" type="textarea" rows="4"></div>
<div *ngIf="form.get('icon')" input [formInput]="form.get('icon')" placeholder="Icon(SVG)" type="textarea"></div>
</div>
</modal-alert>
<modal-alert #visibilityModal [large]="false" classTitle="uk-background-primary uk-light">
<div class="">
You have the option to change the visibility status of your {{type}}, with or without applying the changed status to
its contents.
</div>
<div class="uk-flex uk-flex-center uk-margin-medium-top uk-margin-medium-bottom">
<button class="uk-button uk-button-primary" (click)="changeElementStatus()">
Change {{type}} status
</button>
</div>
<div class="uk-flex uk-flex-center uk-margin-medium-bottom">
<button class="uk-button uk-button-primary" (click)="changeElementStatus(true)">
Change {{type}} and its contents' status
</button>
</div>
<div class="uk-text-small">
<p class="uk-text-bold uk-text-uppercase uk-margin-remove">Note:</p>
<div>
<span class="uk-text-bold uk-text-italic">The status of the {{type}} prevails the status of its contents.</span>
For example, if a {{type}}'s status is private, while it has
<span *ngIf="type == 'topic'">a category, subcategory or an indicator</span>
<span *ngIf="type == 'category'">a subcategory or an indicator</span>
<span *ngIf="type == 'subcategory'">an indicator</span>
that is public, the private status of the {{type}} dominates.
</div>
</div>
</modal-alert>

View File

@ -0,0 +1,799 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component, Inject,
OnDestroy,
OnInit, PLATFORM_ID,
QueryList,
ViewChild,
ViewChildren
} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {EnvProperties} from '../../utils/properties/env-properties';
import {Category, Stakeholder, SubCategory, Topic, Visibility} from "../../monitor/entities/stakeholder";
import {StakeholderService} from "../../monitor/services/stakeholder.service";
import {HelperFunctions} from "../../utils/HelperFunctions.class";
import {AlertModal} from "../../utils/modal/alert";
import {BehaviorSubject, Subject, Subscriber, Subscription} from "rxjs";
import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms";
import {StakeholderUtils} from "../utils/indicator-utils";
import {StringUtils} from "../../utils/string-utils.class";
import {IDeactivateComponent} from "../../utils/can-exit.guard";
import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service";
import {Option} from "../../sharedComponents/input/input.component";
import {properties} from "src/environments/environment";
import {Session, User} from "../../login/utils/helper.class";
import {UserManagementService} from "../../services/user-management.service";
import {TransitionGroupComponent} from "../../utils/transition-group/transition-group.component";
import {NotificationHandler} from "../../utils/notification-handler";
declare var UIkit;
@Component({
selector: 'topic',
templateUrl: './topic.component.html',
})
export class TopicComponent implements OnInit, OnDestroy, AfterViewInit, IDeactivateComponent {
private topicSubscriptions: any[] = [];
private subscriptions: any[] = [];
public properties: EnvProperties = properties;
public offset: number;
public stakeholderUtils: StakeholderUtils = new StakeholderUtils();
public loading: boolean = false;
public stakeholder: Stakeholder;
public user: User;
/**
* Stakeholder change event
* */
public change: Subject<void> = new Subject<void>();
/**
* Current topic
**/
public topicIndexSubject: BehaviorSubject<number> = new BehaviorSubject<number>(0);
public topicIndex: number = 0;
/**
* Current category
*/
public categoryIndexSubject: BehaviorSubject<number> = new BehaviorSubject<number>(0);
public categoryIndex: number = 0;
/**
* Current Subcategory
*/
public subCategoryIndexSubject: BehaviorSubject<number> = new BehaviorSubject<number>(0);
public subCategoryIndex: number = 0;
/**
* Current element and index of topic, category or subcategory to be deleted.
*/
public form: UntypedFormGroup;
public element: Topic | Category | SubCategory;
public type: 'topic' | 'category' | 'subcategory' = "topic";
public index: number = -1;
public visibility: Visibility;
@ViewChild('deleteModal', {static: true}) deleteModal: AlertModal;
@ViewChild('editModal', {static: true}) editModal: AlertModal;
@ViewChild('visibilityModal', {static: true}) visibilityModal: AlertModal;
@ViewChildren(TransitionGroupComponent) transitions: QueryList<TransitionGroupComponent>;
public elementChildrenActionOnDelete: string;
public filters: UntypedFormGroup;
public all: Option = {
value: 'all',
label: 'All'
};
constructor(
private route: ActivatedRoute,
private router: Router,
private title: Title,
private fb: UntypedFormBuilder,
private stakeholderService: StakeholderService,
private userManagementService: UserManagementService,
private layoutService: LayoutService,
private cdr: ChangeDetectorRef,
@Inject(PLATFORM_ID) private platformId) {
}
public ngOnInit() {
if (typeof document !== "undefined") {
this.offset = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-height'));
}
let subscription: Subscription;
this.subscriptions.push(this.topicIndexSubject.asObservable().subscribe(index => {
this.topicChanged(() => {
this.topicIndex = index;
});
}));
this.subscriptions.push(this.categoryIndexSubject.asObservable().subscribe(index => {
this.categoryChanged(() => {
this.categoryIndex = index;
});
}));
this.subscriptions.push(this.subCategoryIndexSubject.asObservable().subscribe(index => {
this.subCategoryChanged(() => {
this.subCategoryIndex = index;
});
}));
this.subscriptions.push(this.route.params.subscribe(params => {
if (subscription) {
subscription.unsubscribe();
}
subscription = this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => {
if (stakeholder) {
this.stakeholder = stakeholder;
if (params['topic']) {
this.chooseTopic(this.stakeholder.topics.findIndex(topic => topic.alias === params['topic']));
} else {
this.chooseTopic(0);
}
this.chooseCategory(0);
this.filters = this.fb.group({
chartType: this.fb.control('all'),
status: this.fb.control('all'),
keyword: this.fb.control('')
});
if (this.topicIndex === -1) {
this.navigateToError();
} else {
this.title.setTitle(stakeholder.name + " | Indicators");
}
}
});
this.topicSubscriptions.push(subscription);
}));
this.topicSubscriptions.push(this.userManagementService.getUserInfo().subscribe(user => {
this.user = user;
}))
}
ngAfterViewInit() {
if(this.topics) {
let activeIndex = UIkit.nav(this.topics.element.nativeElement).items.findIndex(item => item.classList.contains('uk-open'));
if(activeIndex !== this.topicIndex) {
setTimeout(() => {
UIkit.nav(this.topics.element.nativeElement).toggle(this.topicIndex, true);
});
}
}
}
get isBrowser() {
return this.platformId === 'browser';
}
public ngOnDestroy() {
this.topicSubscriptions.forEach(value => {
if (value instanceof Subscriber) {
value.unsubscribe();
}
});
this.subscriptions.forEach(value => {
if (value instanceof Subscriber) {
value.unsubscribe();
}
});
}
canExit(): boolean {
this.topicSubscriptions.forEach(value => {
if (value instanceof Subscriber) {
value.unsubscribe();
}
});
this.stakeholderService.setStakeholder(this.stakeholder);
return true;
}
private findById(id: string) {
return this.transitions?this.transitions.find(item => item.id === id):null;
}
get topics(): TransitionGroupComponent {
return this.findById('topics');
}
get categories(): TransitionGroupComponent {
return this.findById('categories-' + this.topicIndex);
}
get subCategories(): TransitionGroupComponent {
return this.findById('subCategories');
}
hide(element: any) {
UIkit.dropdown(element).hide();
}
stakeholderChanged() {
this.change.next();
}
public saveElement() {
if (this.type === "topic") {
this.saveTopic();
} else if (this.type === "category") {
this.saveCategory();
} else {
this.saveSubCategory();
}
}
public deleteElement() {
if (this.type === "topic") {
this.deleteTopic();
} else if (this.type === "category") {
this.deleteCategory();
} else {
this.deleteSubcategory();
}
}
public changeElementStatus(propagate: boolean = false) {
if (this.type === "topic") {
this.changeTopicStatus(propagate);
} else if (this.type === "category") {
this.changeCategoryStatus(propagate);
} else {
this.changeSubcategoryStatus(propagate);
}
}
public chooseTopic(topicIndex: number) {
this.topicIndexSubject.next(topicIndex);
}
topicChanged(callback: Function, save: boolean = false) {
if(this.topics && save) {
this.topics.disable();
}
if(this.categories) {
this.categories.disable();
}
if(this.subCategories) {
this.subCategories.disable();
}
if(callback) {
callback();
}
this.cdr.detectChanges();
if(this.topics && save) {
this.topics.init();
this.topics.enable();
}
if(this.categories) {
this.categories.init();
this.categories.enable();
}
if(this.subCategories) {
this.subCategories.init();
this.subCategories.enable();
}
}
private buildTopic(topic: Topic) {
let topics = this.stakeholder.topics.filter(element => element._id !== topic._id);
this.form = this.fb.group({
_id: this.fb.control(topic._id),
name: this.fb.control(topic.name, Validators.required),
description: this.fb.control(topic.description),
creationDate: this.fb.control(topic.creationDate),
alias: this.fb.control(topic.alias, [
Validators.required,
this.stakeholderUtils.aliasValidator(topics)
]
),
visibility: this.fb.control(topic.visibility),
defaultId: this.fb.control(topic.defaultId),
categories: this.fb.control(topic.categories),
icon: this.fb.control(topic.icon)
});
this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => {
let i = 1;
value = this.stakeholderUtils.generateAlias(value);
this.form.controls['alias'].setValue(value);
while (this.form.get('alias').invalid) {
this.form.controls['alias'].setValue(value + i);
i++;
}
}));
}
public editTopicOpen(index = -1) {
this.index = index;
this.type = 'topic';
if (index === -1) {
this.buildTopic(new Topic(null, null, null, "PUBLIC"));
} else {
this.buildTopic(this.stakeholder.topics[index]);
}
this.editOpen();
}
public saveTopic() {
if (!this.form.invalid) {
let path = [this.stakeholder._id];
let callback = (topic: Topic): void => {
this.topicChanged(() => {
if (this.index === -1) {
this.stakeholder.topics.push(topic);
} else {
this.stakeholder.topics[this.index] = HelperFunctions.copy(topic);
}
}, true);
};
if (this.index === -1) {
this.save('Topic has been successfully created', path, this.form.value, callback);
} else {
this.save('Topic has been successfully saved', path, this.form.value, callback);
}
}
}
public changeTopicStatus(propagate: boolean = false) {
let path = [
this.stakeholder._id,
this.stakeholder.topics[this.index]._id
];
let callback = (topic: Topic): void => {
this.topicChanged(() => {
this.stakeholder.topics[this.index] = HelperFunctions.copy(topic);
}, true);
}
this.changeStatus(this.stakeholder.topics[this.index], path, this.visibility, callback, propagate);
this.visibilityModal.cancel();
}
public deleteTopicOpen(index = this.topicIndex, childrenAction: string = null) {
this.type = 'topic';
this.index = index;
this.element = this.stakeholder.topics[this.index];
this.deleteOpen(childrenAction);
}
public deleteTopic() {
let path: string[] = [
this.stakeholder._id,
this.stakeholder.topics[this.index]._id
];
let callback = (): void => {
this.topicChanged(() => {
this.stakeholder.topics.splice(this.index, 1);
if(this.topicIndex === this.index) {
this.chooseTopic(Math.max(0, this.index - 1));
}
}, true);
};
this.delete('Topic has been successfully be deleted', path, callback, (this.topicIndex === this.index));
}
public moveTopic(index: number, newIndex: number = index - 1) {
this.topics.init();
let path = [this.stakeholder._id];
let ids = this.stakeholder.topics.map(topic => topic._id);
HelperFunctions.swap(ids, index, newIndex);
this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => {
HelperFunctions.swap(this.stakeholder.topics, index, newIndex);
if(this.topicIndex === index) {
this.chooseTopic(newIndex);
} else if(this.topicIndex === newIndex) {
this.chooseTopic(index);
}
}, error => {
NotificationHandler.rise(error.error.message)
});
}
public chooseCategory(index: number) {
this.categoryIndexSubject.next(index);
this.chooseSubcategory(0);
}
categoryChanged(callback: Function, save: boolean = false) {
if(this.categories && save) {
this.categories.disable();
}
if(this.subCategories) {
this.subCategories.disable();
}
if(callback) {
callback();
}
this.cdr.detectChanges();
if(this.categories && save) {
this.categories.init();
this.categories.enable();
}
if(this.subCategories) {
this.subCategories.init();
this.subCategories.enable();
}
}
private buildCategory(category: Category) {
let categories = this.stakeholder.topics[this.topicIndex].categories.filter(element => element._id !== category._id);
this.form = this.fb.group({
_id: this.fb.control(category._id),
name: this.fb.control(category.name, Validators.required),
description: this.fb.control(category.description),
creationDate: this.fb.control(category.creationDate),
alias: this.fb.control(category.alias, [
Validators.required,
this.stakeholderUtils.aliasValidator(categories)
]
),
visibility: this.fb.control(category.visibility),
defaultId: this.fb.control(category.defaultId),
subCategories: this.fb.control(category.subCategories)
});
this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => {
let i = 1;
value = this.stakeholderUtils.generateAlias(value);
this.form.controls['alias'].setValue(value);
while (this.form.get('alias').invalid) {
this.form.controls['alias'].setValue(value + i);
i++;
}
}));
}
public editCategoryOpen(index: number = -1) {
this.index = index;
this.type = 'category';
if (index === -1) {
this.buildCategory(new Category(null, null, null, "PUBLIC"));
} else {
this.buildCategory(this.stakeholder.topics[this.topicIndex].categories[index]);
}
this.editOpen();
}
public saveCategory() {
if (!this.form.invalid) {
let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id];
let callback = (category: Category): void => {
this.categoryChanged(() => {
if (this.index === -1) {
this.stakeholder.topics[this.topicIndex].categories.push(category);
this.categories.init();
} else {
this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category);
}
}, true);
};
if (this.index === -1) {
this.save('Category has been successfully created', path, this.form.value, callback);
} else {
this.save('Category has been successfully saved', path, this.form.value, callback);
}
}
}
public changeCategoryStatus(propagate: boolean = false) {
let path = [
this.stakeholder._id,
this.stakeholder.topics[this.topicIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.index]._id
];
let callback = (category: Category): void => {
this.categoryChanged(() => {
this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category);
}, true);
}
this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.index], path, this.visibility, callback, propagate);
this.visibilityModal.cancel();
}
public deleteCategoryOpen(index: number, childrenAction: string = null) {
this.type = 'category';
this.index = index;
this.element = this.stakeholder.topics[this.topicIndex].categories[this.index];
this.deleteOpen(childrenAction);
}
public deleteCategory() {
let path: string[] = [
this.stakeholder._id,
this.stakeholder.topics[this.topicIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.index]._id
];
let callback = (): void => {
this.categoryChanged(() => {
this.stakeholder.topics[this.topicIndex].categories.splice(this.index, 1);
if(this.categoryIndex === this.index) {
this.chooseCategory(Math.max(0, this.index - 1));
}
}, true);
};
this.delete('Category has been successfully be deleted', path, callback);
}
public moveCategory(index: number, newIndex: number = index - 1) {
this.categories.init();
let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id];
let ids = this.stakeholder.topics[this.topicIndex].categories.map(category => category._id);
HelperFunctions.swap(ids, index, newIndex);
this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => {
HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories, index, newIndex);
if(this.categoryIndex === index) {
this.chooseCategory(newIndex);
} else if(this.categoryIndex === newIndex) {
this.chooseCategory(index);
}
}, error => {
NotificationHandler.rise(error.error.message)
});
}
chooseSubcategory(subcategoryIndex: number) {
this.subCategoryIndexSubject.next(subcategoryIndex);
}
subCategoryChanged(callback: Function, save: boolean = false) {
if(this.subCategories && save) {
this.subCategories.disable();
}
if(callback) {
callback();
}
this.cdr.detectChanges();
if(this.subCategories && save) {
this.subCategories.init();
this.subCategories.enable();
}
}
private buildSubcategory(subCategory: SubCategory) {
let subCategories = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.filter(element => element._id !== subCategory._id);
this.form = this.fb.group({
_id: this.fb.control(subCategory._id),
name: this.fb.control(subCategory.name, Validators.required),
description: this.fb.control(subCategory.description),
creationDate: this.fb.control(subCategory.creationDate),
alias: this.fb.control(subCategory.alias, [
Validators.required,
this.stakeholderUtils.aliasValidator(subCategories)
]
),
visibility: this.fb.control(subCategory.visibility),
defaultId: this.fb.control(subCategory.defaultId),
charts: this.fb.control(subCategory.charts),
numbers: this.fb.control(subCategory.numbers)
});
this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => {
let i = 1;
value = this.stakeholderUtils.generateAlias(value);
this.form.controls['alias'].setValue(value);
while (this.form.get('alias').invalid) {
this.form.controls['alias'].setValue(value + i);
i++;
}
}));
}
public editSubCategoryOpen(index: number = -1) {
this.index = index;
this.type = 'subcategory';
if (index === -1) {
this.buildSubcategory(new SubCategory(null, null, null, "PUBLIC"));
} else {
this.buildSubcategory(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[index]);
}
this.editOpen();
}
public saveSubCategory() {
if (!this.form.invalid) {
let path: string[] = [
this.stakeholder._id,
this.stakeholder.topics[this.topicIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id
];
let callback = (subCategory: SubCategory): void => {
this.subCategoryChanged(() => {
if (this.index === -1) {
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.push(subCategory);
} else {
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subCategory);
}
}, true);
};
if (this.index === -1) {
this.save('Subcategory has been successfully created', path, this.form.value, callback);
} else {
this.save('Subcategory has been successfully saved', path, this.form.value, callback);
}
}
}
public changeSubcategoryStatus(propagate: boolean = false) {
let path = [
this.stakeholder._id,
this.stakeholder.topics[this.topicIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id
];
let callback = (subcategory: SubCategory): void => {
this.subCategoryChanged(() => {
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subcategory);
}, true);
}
this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index], path, this.visibility, callback, propagate);
this.visibilityModal.cancel();
}
public deleteSubcategoryOpen(index, childrenAction: string = null) {
this.type = 'subcategory';
this.index = index;
this.element = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index];
this.deleteOpen(childrenAction);
}
public deleteSubcategory() {
let path: string[] = [
this.stakeholder._id,
this.stakeholder.topics[this.topicIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id,
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id
];
let callback = (): void => {
this.subCategoryChanged(() => {
this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.splice(this.index, 1);
if(this.subCategoryIndex === this.index) {
this.chooseSubcategory(Math.max(0, this.index - 1));
}
}, true);
};
this.delete('Subcategory has been successfully be deleted', path, callback);
}
public moveSubCategory(index: number, newIndex: number = index - 1) {
this.subCategories.init();
let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id];
let ids = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.map(subCategory => subCategory._id);
HelperFunctions.swap(ids, index, newIndex);
this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => {
HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories, index, newIndex);
if(this.subCategoryIndex === index) {
this.chooseSubcategory(newIndex);
} else if(this.subCategoryIndex === newIndex) {
this.chooseSubcategory(index);
}
}, error => {
NotificationHandler.rise(error.error.message)
});
}
private navigateToError() {
this.router.navigate([this.properties.errorLink], {queryParams: {'page': this.router.url}});
}
get isCurator(): boolean {
return Session.isPortalAdministrator(this.user) || Session.isCurator(this.stakeholder.type, this.user);
}
private editOpen() {
this.editModal.cancelButtonText = 'Cancel';
this.editModal.okButtonLeft = false;
this.editModal.alertMessage = false;
if (this.index === -1) {
this.editModal.alertTitle = 'Create a new ' + this.type;
this.editModal.okButtonText = 'Create';
} else {
this.editModal.alertTitle = 'Edit ' + this.type + '\'s information ';
this.editModal.okButtonText = 'Save';
}
this.editModal.stayOpen = true;
this.editModal.open();
}
private deleteOpen(childrenAction: string = null) {
this.elementChildrenActionOnDelete = null;
if (childrenAction == "delete") {
this.elementChildrenActionOnDelete = childrenAction;
} else if (childrenAction == "disconnect") {
this.elementChildrenActionOnDelete = childrenAction;
}
this.deleteModal.alertTitle = 'Delete ' + this.type;
this.deleteModal.cancelButtonText = 'No';
this.deleteModal.okButtonText = 'Yes';
// this.deleteModal.cancelButton = false;
// this.deleteModal.okButton = false;
this.deleteModal.stayOpen = true;
this.deleteModal.open();
}
private save(message: string, path: string[], saveElement: any, callback: Function, redirect = false) {
this.loading = true;
this.topicSubscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, saveElement, path).subscribe(saveElement => {
callback(saveElement);
this.stakeholderChanged();
this.loading = false;
this.editModal.cancel();
NotificationHandler.rise(message);
if (redirect) {
this.router.navigate(['../' + saveElement.alias], {
relativeTo: this.route
});
}
}, error => {
this.loading = false;
this.editModal.cancel();
NotificationHandler.rise(error.error.message, 'danger');
}));
}
private delete(message: string, path: string[], callback: Function, redirect = false) {
this.loading = true;
this.topicSubscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.elementChildrenActionOnDelete).subscribe(() => {
callback();
this.stakeholderChanged();
this.loading = false;
this.deleteModal.cancel();
NotificationHandler.rise(message);
if (redirect) {
this.back();
}
}, error => {
this.loading = false;
this.deleteModal.cancel();
NotificationHandler.rise(error.error.message, 'danger');
}));
}
private changeStatus(element: Topic | Category | SubCategory, path: string[], visibility: Visibility, callback: Function = null, propagate: boolean = false) {
this.topicSubscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility, propagate).subscribe(returnedElement => {
if(propagate) {
callback(returnedElement);
NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been <b>successfully changed</b> to ' + returnedElement.visibility.toLowerCase());
} else {
element.visibility = returnedElement.visibility;
NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been <b>successfully changed</b> to ' + element.visibility.toLowerCase());
}
}, error => {
NotificationHandler.rise(error.error.message, 'danger');
}));
}
back() {
this.router.navigate(['../'], {
relativeTo: this.route
});
}
public getPluralTypeName(): string {
if (this.type == "topic") {
return "Topics";
} else if (this.type == "category") {
return "Categories";
} else if (this.type == "subcategory") {
return "Subcategories";
} else {
return this.type;
}
}
public get isSmallScreen() {
return this.layoutService.isSmallScreen;
}
public get open() {
return this.layoutService.open;
}
public toggleOpen(event: MouseEvent) {
event.preventDefault();
this.layoutService.setOpen(!this.open);
}
public openVisibilityModal(index: number, visibility: Visibility, type: any) {
this.index = index;
this.visibility = visibility;
this.type = type;
this.visibilityModal.alertTitle = 'Visibility Status';
this.visibilityModal.alertFooter = false;
this.visibilityModal.open();
}
}

View File

@ -0,0 +1,49 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard';
import {PiwikService} from '../../utils/piwik/piwik.service';
import {TopicComponent} from "./topic.component";
import {TopicRoutingModule} from "./topic-routing.module";
import {RouterModule} from "@angular/router";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {IndicatorsComponent} from "./indicators.component";
import {AlertModalModule} from "../../utils/modal/alertModal.module";
import {InputModule} from "../../sharedComponents/input/input.module";
import {ClickModule} from "../../utils/click/click.module";
import {IconsService} from "../../utils/icons/icons.service";
import {earth, incognito, restricted} from "../../utils/icons/icons";
import {IconsModule} from "../../utils/icons/icons.module";
import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module";
import {LoadingModule} from "../../utils/loading/loading.module";
import {NotifyFormModule} from "../../notifications/notify-form/notify-form.module";
import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module";
import {TransitionGroupModule} from "../../utils/transition-group/transition-group.module";
import {NumberRoundModule} from "../../utils/pipes/number-round.module";
import {SideBarModule} from "../../dashboard/sharedComponents/sidebar/sideBar.module";
import {
SidebarMobileToggleModule
} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module";
@NgModule({
imports: [
CommonModule, TopicRoutingModule, ClickModule, RouterModule, FormsModule, AlertModalModule,
ReactiveFormsModule, InputModule, IconsModule, PageContentModule, LoadingModule, NotifyFormModule, LogoUrlPipeModule, TransitionGroupModule, NumberRoundModule, SideBarModule, SidebarMobileToggleModule
],
declarations: [
TopicComponent, IndicatorsComponent
],
providers: [
PreviousRouteRecorder,
PiwikService
],
exports: [
TopicComponent
]
})
export class TopicModule {
constructor(private iconsService: IconsService) {
this.iconsService.registerIcons([earth, incognito, restricted]);
}
}

View File

@ -0,0 +1,43 @@
import {Injectable} from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import {map, take, tap} from "rxjs/operators";
import {UserManagementService} from "../../services/user-management.service";
import {LoginErrorCodes} from "../../login/utils/guardHelper.class";
import {Session} from "../../login/utils/helper.class";
import {StakeholderService} from "../../monitor/services/stakeholder.service";
import {Observable, zip} from "rxjs";
@Injectable()
export class AdminDashboardGuard {
constructor(private router: Router,
private stakeholderService: StakeholderService,
private userManagementService: UserManagementService) {
}
check(path: string, alias: string): Observable<boolean> | boolean {
let errorCode = LoginErrorCodes.NOT_LOGIN;
return zip(
this.userManagementService.getUserInfo(), this.stakeholderService.getStakeholder(alias)
).pipe(take(1),map(res => {
if(res[0]) {
errorCode = LoginErrorCodes.NOT_ADMIN;
}
return res[0] && res[1] && (Session.isPortalAdministrator(res[0]) ||
Session.isCurator(res[1].type, res[0]) || Session.isManager(res[1].type, res[1].alias, res[0]))
}),tap(authorized => {
if(!authorized){
this.router.navigate(['/user-info'], {queryParams: {'errorCode': errorCode, 'redirectUrl':path}});
}
}));
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.check(state.url, route.params.stakeholder);
}
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.check(state.url, childRoute.params.stakeholder);
}
}

View File

@ -0,0 +1,9 @@
@import (reference) "~src/assets/openaire-theme/less/_import-variables.less";
.cache-progress {
position: fixed;
bottom: 0;
right: 0;
transform: translate(-50%, -50%);
z-index: @global-z-index;
}

View File

@ -0,0 +1,78 @@
import {Component, Inject, Input, OnChanges, OnDestroy, OnInit, PLATFORM_ID, SimpleChanges} from "@angular/core";
import {Report} from "./cache-indicators";
import {CacheIndicatorsService} from "./cache-indicators.service";
import {interval, Subject, Subscription} from "rxjs";
import {map, switchMap, takeUntil} from "rxjs/operators";
@Component({
selector: 'cache-indicators',
template: `
<div *ngIf="report" class="cache-progress">
<div class="uk-position-relative" [attr.uk-tooltip]="'Caching indicators process for ' + alias">
<div class="uk-progress-circle" [attr.percentage]="report.percentage?report.percentage:0" [style]="'--percentage: ' + (report.percentage?report.percentage:0)"></div>
<button *ngIf="report.percentage === 100" (click)="clear()" class="uk-icon-button uk-icon-button-xsmall uk-button-default uk-position-top-right"><icon name="close" [flex]="true" ratio="0.8"></icon></button>
</div>
</div>
`,
styleUrls: ['cache-indicators.component.less']
})
export class CacheIndicatorsComponent implements OnInit, OnChanges, OnDestroy {
report: Report;
subscriptions: Subscription[] = [];
interval: number = 10000;
readonly destroy$ = new Subject();
@Input() alias: string;
constructor(private cacheIndicatorsService: CacheIndicatorsService,
@Inject(PLATFORM_ID) private platformId) {
}
ngOnInit() {
this.getReport();
}
ngOnChanges(changes: SimpleChanges) {
if(changes.alias) {
this.getReport();
}
}
getReport() {
this.clear();
this.subscriptions.push(this.cacheIndicatorsService.getReport(this.alias).subscribe(report => {
this.getReportInterval(report);
}));
}
getReportInterval(report: Report) {
if(this.isBrowser && (this.report || !report?.completed)) {
this.report = report;
this.subscriptions.push(interval(this.interval).pipe(
map(() => this.cacheIndicatorsService.getReport(this.alias)),
switchMap(report => report),
takeUntil(this.destroy$)).subscribe(report => {
console.log(this.alias);
this.report = report;
if(this.report.completed) {
this.destroy$.next();
}
}));
}
}
clear() {
this.subscriptions.forEach(subscription => {
subscription.unsubscribe();
})
this.report = null;
}
get isBrowser() {
return this.platformId === 'browser';
}
ngOnDestroy() {
this.clear();
}
}

View File

@ -0,0 +1,11 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {CacheIndicatorsComponent} from "./cache-indicators.component";
import {IconsModule} from "../../../utils/icons/icons.module";
@NgModule({
imports: [CommonModule, IconsModule],
declarations: [CacheIndicatorsComponent],
exports: [CacheIndicatorsComponent]
})
export class CacheIndicatorsModule {}

View File

@ -0,0 +1,24 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {properties} from "src/environments/environment";
import {CustomOptions} from "../../../services/servicesUtils/customOptions.class";
import {map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class CacheIndicatorsService {
constructor(private http: HttpClient) {
}
createReport(alias: string) {
return this.http.post<any>(properties.domain + properties.baseLink + '/cache/' + alias, {}, CustomOptions.registryOptions())
.pipe(map(res => res.report));
}
getReport(alias: string) {
return this.http.get<any>(properties.domain + properties.baseLink + '/cache/' + alias, CustomOptions.registryOptions())
.pipe(map(res => res.report));
}
}

View File

@ -0,0 +1,261 @@
import {IndicatorType, Stakeholder} from "../../../monitor/entities/stakeholder";
import axios from "axios";
import {IndicatorUtils} from "../indicator-utils";
import {Composer} from "../../../utils/email/composer";
import {properties} from "src/environments/environment";
export interface CacheItem {
reportId: string,
type: IndicatorType,
url: string
}
export class Report {
creator: string;
name: string;
success: number;
errors: {
url: string,
status: number
}[];
total: number;
completed: boolean;
percentage: number
constructor(total: number, name: string, creator: string) {
this.creator = creator;
this.name = name;
this.success = 0;
this.errors = [];
this.total = total;
this.completed = false;
}
setPercentage() {
this.percentage = Math.floor((this.success + this.errors.length) / this.total * 100);
}
}
export class CacheIndicators {
private static BATCH_SIZE = 10;
private reports: Map<string, Report> = new Map<string, Report>();
private queue: CacheItem[] = [];
private process: Promise<void>;
private isFinished: boolean = true;
stakeholderToCacheItems(stakeholder: Stakeholder) {
let cacheItems: CacheItem[] = [];
let indicatorUtils = new IndicatorUtils();
stakeholder.topics.forEach(topic => {
topic.categories.forEach(category => {
category.subCategories.forEach(subCategory => {
subCategory.numbers.forEach(section => {
section.indicators.forEach(indicator => {
indicator.indicatorPaths.forEach(indicatorPath => {
let url = indicatorUtils.getNumberUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath));
cacheItems.push({
reportId: stakeholder._id,
type: 'number',
url: url
});
});
});
});
subCategory.charts.forEach(section => {
section.indicators.forEach(indicator => {
indicator.indicatorPaths.forEach(indicatorPath => {
let url = indicatorUtils.getChartUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath));
cacheItems.push({
reportId: stakeholder._id,
type: 'chart',
url: url
});
});
});
});
});
});
});
return cacheItems;
}
public exists(id: string) {
return this.reports.has(id);
}
public completed(id: string) {
return !this.exists(id) || this.reports.get(id).completed;
}
public createReport(id: string, cacheItems: CacheItem[], name: string, creator: string) {
let report = new Report(cacheItems.length, name, creator);
this.reports.set(id, report);
this.addItemsToQueue(cacheItems);
return report;
}
public getReport(id: string) {
return this.reports.get(id);
}
private async processQueue() {
this.isFinished = false;
while (this.queue.length > 0) {
let batch = this.queue.splice(0, CacheIndicators.BATCH_SIZE);
await this.processBatch(batch);
}
}
private async processBatch(batch: CacheItem[]) {
let promises: Promise<any>[] = [];
let ids = new Set<string>();
batch.forEach(item => {
let promise;
ids.add(item.reportId);
if (item.type === 'chart') {
let [url, json] = item.url.split('?json=');
json = decodeURIComponent(json);
json = statsToolParser(JSON.parse(json));
promise = axios.post(url, json);
} else {
promise = axios.get(item.url);
}
promises.push(promise.then(response => {
let report = this.reports.get(item.reportId);
if (report) {
report.success++;
report.setPercentage();
}
return response;
}).catch(error => {
let report = this.reports.get(item.reportId);
if (report) {
report.errors.push({url: item.url, status: error.response.status});
report.setPercentage();
}
return error.response;
}));
});
await Promise.all(promises);
ids.forEach(id => {
let report = this.reports.get(id);
if (report?.percentage === 100) {
report.completed = true;
this.sendEmail(report);
}
});
}
private addItemsToQueue(cacheItems: CacheItem[]) {
cacheItems.forEach(item => {
this.queue.push(item);
});
if (this.isFinished) {
this.processQueue().then(() => {
this.isFinished = true;
});
}
}
sendEmail(report: Report) {
let email = Composer.composeEmailToReportCachingProcess(report);
axios.post(properties.adminToolsAPIURL + "sendMail/", email).catch(error => {
console.error(error);
});
}
}
export function statsToolParser(dataJSONobj: any): any {
let RequestInfoObj = Object.assign({});
switch (dataJSONobj.library) {
case "GoogleCharts":
//Pass the Chart library to ChartDataFormatter
RequestInfoObj.library = dataJSONobj.library;
RequestInfoObj.orderBy = dataJSONobj.orderBy;
//Create ChartInfo Object Array
RequestInfoObj.chartsInfo = [];
//Create ChartInfo and pass the Chart data queries to ChartDataFormatter
//along with the requested Chart type
RequestInfoObj.chartsInfo = dataJSONobj.chartDescription.queriesInfo;
break;
case "eCharts":
//Pass the Chart library to ChartDataFormatter
RequestInfoObj.library = dataJSONobj.library;
RequestInfoObj.orderBy = dataJSONobj.orderBy;
//Create ChartInfo Object Array
RequestInfoObj.chartsInfo = [];
//Create ChartInfo and pass the Chart data queries to ChartDataFormatter
//along with the requested Chart type
for (let index = 0; index < dataJSONobj.chartDescription.queries.length; index++) {
let element = dataJSONobj.chartDescription.queries[index];
var ChartInfoObj = Object.assign({});
if (element.type === undefined)
ChartInfoObj.type = dataJSONobj.chartDescription.series[index].type;
else
ChartInfoObj.type = element.type;
if (element.name === undefined)
ChartInfoObj.name = null;
else
ChartInfoObj.name = element.name;
ChartInfoObj.query = element.query;
RequestInfoObj.chartsInfo.push(ChartInfoObj);
}
break;
case "HighCharts":
RequestInfoObj.library = dataJSONobj.library;
RequestInfoObj.orderBy = dataJSONobj.orderBy;
//Pass the Chart type to ChartDataFormatter
var defaultType = dataJSONobj.chartDescription.chart.type;
//Create ChartInfo Object Array
RequestInfoObj.chartsInfo = [];
//Create ChartInfo and pass the Chart data queries to ChartDataFormatter
//along with the requested Chart type
dataJSONobj.chartDescription.queries.forEach(element => {
var ChartInfoObj = Object.assign({});
if (element.type === undefined)
ChartInfoObj.type = defaultType;
else
ChartInfoObj.type = element.type;
if (element.name === undefined)
ChartInfoObj.name = null;
else
ChartInfoObj.name = element.name;
ChartInfoObj.query = element.query;
RequestInfoObj.chartsInfo.push(ChartInfoObj);
});
break;
case "HighMaps":
RequestInfoObj.library = dataJSONobj.library;
//Create ChartInfo Object Array
RequestInfoObj.chartsInfo = [];
//Create ChartInfo and pass the Chart data queries to ChartDataFormatter
dataJSONobj.mapDescription.queries.forEach(element => {
var ChartInfoObj = Object.assign({});
if (element.name === undefined)
ChartInfoObj.name = null;
else
ChartInfoObj.name = element.name;
ChartInfoObj.query = element.query;
RequestInfoObj.chartsInfo.push(ChartInfoObj);
});
break;
default:
console.log("Unsupported Library: " + dataJSONobj.library);
}
return RequestInfoObj;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Observable} from "rxjs";
import {SourceType} from "../../../monitor/entities/stakeholder";
import {IndicatorUtils} from "../indicator-utils";
@Injectable({
providedIn: 'root'
})
export class StatisticsService {
indicatorsUtils = new IndicatorUtils();
constructor(private http: HttpClient) {}
getNumbers(source: SourceType, url: string): Observable<any> {
if (source !== null) {
return this.http.get<any>(this.indicatorsUtils.getNumberUrl(source, url));
} else {
return this.http.get<any>(url);
}
}
}

View File

@ -0,0 +1,19 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {properties} from "src/environments/environment";
import {Observable} from "rxjs";
import {map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class StatsProfilesService {
constructor(private http: HttpClient) {
}
getStatsProfiles(): Observable<string[]> {
return this.http.get<any[]>(properties.monitorStatsFrameUrl + 'schema/profiles')
.pipe(map(profiles => profiles.map(profile => profile.name)));
}
}

View File

@ -1,6 +1,7 @@
import {SafeResourceUrl} from "@angular/platform-browser";
import {properties} from "../../../../environments/environment";
import {Session, User} from "../../login/utils/helper.class";
import {Option} from "../../sharedComponents/input/input.component";
export const ChartHelper = {
prefix: "((__",
@ -309,3 +310,10 @@ export enum StakeholderEntities {
ORGANIZATIONS = 'Research Institutions',
PROJECTS = 'Projects'
}
export let stakeholderTypes: Option[] = [
{value: 'funder', label: StakeholderEntities.FUNDER},
{value: 'ri', label: StakeholderEntities.RI},
{value: 'organization', label: StakeholderEntities.ORGANIZATION},
{value: 'project', label: StakeholderEntities.PROJECT}
];

View File

@ -5,6 +5,7 @@ import {Meta, Title} from "@angular/platform-browser";
import {SEOService} from "../../sharedComponents/SEO/SEO.service";
import {Breadcrumb} from "../../utils/breadcrumbs/breadcrumbs.component";
import {Subscriber} from "rxjs";
import {StakeholderEntities} from "../entities/stakeholder";
@Component({
selector: 'indicator-themes-page',
@ -53,9 +54,9 @@ import {Subscriber} from "rxjs";
This is the current set of indicator themes we cover. Well keep enriching it as new requests and data are coming into the <a href="https://graph.openaire.eu" class="text-graph" target="_blank">OpenAIRE Graph</a>. We are at your disposal, should you have any recommendations!
</p>
<p>
Check out the indicator pages (for <a [routerLink]="['../funder']" [relativeTo]="route">funders</a>,
<a [routerLink]="['../organization']" [relativeTo]="route">research institutions</a> and
<a [routerLink]="['../ri']" [relativeTo]="route">research initiatives</a>)
Check out the indicator pages (for <a [routerLink]="['../funder']" [relativeTo]="route" class="uk-text-lowercase">{{entities.FUNDERS}}</a>,
<a [routerLink]="['../organization']" [relativeTo]="route" class="uk-text-lowercase">{{entities.ORGANIZATIONS}}</a> and
<a [routerLink]="['../ri']" [relativeTo]="route" class="uk-text-lowercase">{{entities.RIS}}</a>)
for the specific indicators for each type of dashboard, and the <a [routerLink]="['../../methodology']" [relativeTo]="route">methodology and terminology</a> page on how we produce the metrics.
</p>
</div>
@ -67,6 +68,7 @@ import {Subscriber} from "rxjs";
export class IndicatorThemesComponent implements OnInit, OnDestroy {
private subscriptions: any[] = [];
public properties = properties;
public entities = StakeholderEntities;
public breadcrumbs: Breadcrumb[] = [{name: 'home', route: '/'}, {name: 'Resources - Themes'}];
constructor(private router: Router,

View File

@ -39,9 +39,9 @@ declare var ResizeObserver;
</div>
</div>
<div *ngIf="divContents" uk-scrollspy="target: [uk-scrollspy-class]; cls: uk-animation-fade; delay: 250">
<div id="graph_element" #graph_element class="uk-blur-background" uk-sticky="bottom: true"
<div id="graph_element" #graph_element class="uk-blur-background uk-padding-xsmall" uk-sticky="end: true"
[attr.offset]="graph_offset">
<div class="uk-container uk-container-large uk-margin-small-top uk-margin-small-bottom">
<div class="uk-container uk-container-large">
<a href="https://graph.openaire.eu" target="_blank" class="uk-width-1-1 uk-width-auto@m">
<img src="assets/common-assets/openaire-badge-1.png" alt="Powered by OpenAIRE graph" style="height: 17px;">
</a>
@ -50,7 +50,7 @@ declare var ResizeObserver;
<div class="uk-section uk-container uk-container-large" uk-scrollspy-class>
<div id="parentContainer" class="uk-grid uk-grid-large" uk-grid>
<div class="uk-width-medium uk-margin-top">
<div class="uk-sticky" uk-sticky="bottom: !#parentContainer; offset: 100;">
<div class="uk-sticky" uk-sticky="end: !#parentContainer; offset: 100;">
<slider-tabs type="scrollable" position="left">
<slider-tab tabId="entities" tabTitle="1. Entities"></slider-tab>
<slider-tab tabId="inherited-and-inferred-attributes" tabTitle="2. Inherited and Inferred Attributes"></slider-tab>
@ -83,9 +83,9 @@ declare var ResizeObserver;
</div>
</div>
<div *ngIf="divContents" uk-scrollspy="target: [uk-scrollspy-class]; cls: uk-animation-fade; delay: 250">
<div id="graph_element" #graph_element class="uk-blur-background" uk-sticky="bottom: true"
<div id="graph_element" #graph_element class="uk-blur-background uk-padding-xsmall" uk-sticky="end: true"
[attr.offset]="graph_offset">
<div class="uk-container uk-container-large uk-margin-small-top uk-margin-small-bottom uk-text-xsmall uk-text-right">
<div class="uk-container uk-container-large uk-text-xsmall uk-text-right">
<a href="https://graph.openaire.eu" target="_blank" class="uk-width-1-1 uk-width-auto@m">
<img src="assets/common-assets/openaire-badge-1.png" alt="Powered by OpenAIRE graph" style="height: 17px;">
</a>

View File

@ -27,12 +27,12 @@ export class NotificationConfiguration {
@Component({
selector: 'notification-sidebar',
template: `
<div *ngIf="!mobile" id="notifications-switcher" uk-toggle="" href="#notifications" class="uk-offcanvas-switcher uk-flex uk-flex-middle uk-flex-center">
<a *ngIf="!mobile" id="notifications-switcher" uk-toggle href="#notifications" class="uk-link-reset uk-offcanvas-switcher uk-flex uk-flex-middle uk-flex-center">
<icon name="mail" ratio="1.5" customClass="uk-text-background" flex="true" visuallyHidden="Notifications"></icon>
<span [class.uk-hidden]="unreadCount === 0" class="uk-offcanvas-count uk-flex uk-flex-middle uk-flex-center">
{{unreadCount}}
</span>
</div>
</a>
<ng-template #main>
<ng-container *ngIf="!notification">
<div class="notification-list uk-position-relative">

View File

@ -1,13 +1,12 @@
import {NgModule} from "@angular/core";
import {NotifyFormComponent} from "./notify-form.component";
import {CommonModule} from "@angular/common";
import {MatCheckboxModule} from "@angular/material/checkbox";
import {ReactiveFormsModule} from "@angular/forms";
import {InputModule} from "../../sharedComponents/input/input.module";
import {NotificationUserModule} from "../notification-user/notification-user.module";
@NgModule({
imports: [CommonModule, MatCheckboxModule, ReactiveFormsModule, InputModule, NotificationUserModule],
imports: [CommonModule, ReactiveFormsModule, InputModule, NotificationUserModule],
declarations: [NotifyFormComponent],
exports: [NotifyFormComponent]
})

View File

@ -302,7 +302,7 @@ export class MyOrcidLinksComponent {
for(let work of works) {
for(let pid of work['pids']) {
let identifier: Identifier = Identifier.getIdentifierFromString(pid, false);
this.orcidQuery += (this.orcidQuery ? " or " : "") + ('(pidclassid exact "'+identifier.class+'" and pid="'+StringUtils.URIEncode(identifier.id)+'")');
this.orcidQuery += (this.orcidQuery ? " or " : "") + ('(pid="'+StringUtils.URIEncode(identifier.id)+'")');
}
}
this.showLoading = false;

View File

@ -33,10 +33,7 @@
[options]="fieldIdsOptions" (valueChange)="fieldIdsChanged(i,selectedField.id)" type="select"></div>
<div input [(value)]="selectedField.includes" inputClass="border-bottom" [options]="getNotOperators(selectedField)" type="select"></div>
</div>
<!-- <mat-select [(ngModel)]="selectedField.id" name="selectField_{{i}}" [disableOptionCentering]="true" class="matSelection" panelClass="matSelectionPanel"
(ngModelChange)="fieldIdsChanged(i,selectedField.id)">&lt;!&ndash;(click)="fieldIdsChanged(i)" &ndash;&gt;
<mat-option *ngFor="let id of fieldIds" [value]="id">{{fieldIdsMap[id].name}} </mat-option>
</mat-select>--></td>
</td>
<td *ngIf="selectedField.type == 'keyword' || selectedField.type == 'identifier'">
<div class="uk-inline uk-width-expand">
<a *ngIf="selectedField.value.length > 0" class="uk-form-icon uk-form-icon-flip"
@ -105,12 +102,6 @@
</td>
</tr>
</table>
<!-- <div class="uk-margin-small-top">
<button type="button" (click)="addField()" class="uk-button uk-button-link uk-flex uk-flex-middle">
<icon name="add" [flex]="true"></icon>
<span class="uk-margin-small-left">Add rule</span>
</button>
</div> -->
<div class=" uk-text-center uk-margin-small-top">
<div *ngIf="!validDateFrom && validDateTo" class="uk-text-danger">
Please check your <u>from</u> date

View File

@ -10,7 +10,6 @@ import {DateFilterModule} from './dateFilter.module';
import {SearchFormModule} from './searchForm.module';
import {QuickSelectionsModule} from "./quick-selections.module";
import {EntitiesSelectionModule} from "./entitiesSelection.module";
import {MatSelectModule} from "@angular/material/select";
import {IconsModule} from "../../utils/icons/icons.module";
import {SearchInputModule} from "../../sharedComponents/search-input/search-input.module";
import {AdvancedSearchInputModule} from "../../sharedComponents/advanced-search-input/advanced-search-input.module";
@ -22,7 +21,7 @@ import {filters} from "../../utils/icons/icons";
@NgModule({
imports: [
CommonModule, FormsModule, RouterModule, EntitiesAutocompleteModule, StaticAutocompleteModule, DateFilterModule,
SearchFormModule, QuickSelectionsModule, EntitiesSelectionModule, MatSelectModule, IconsModule, SearchInputModule, AdvancedSearchInputModule, InputModule
SearchFormModule, QuickSelectionsModule, EntitiesSelectionModule, IconsModule, SearchInputModule, AdvancedSearchInputModule, InputModule
],
declarations: [
AdvancedSearchFormComponent,

View File

@ -3,13 +3,12 @@ import {CommonModule} from '@angular/common';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {EntitiesSelectionComponent} from "./entitiesSelection.component";
import { MatSelectModule } from "@angular/material/select";
import {InputModule} from "../../sharedComponents/input/input.module";
@NgModule({
imports: [
CommonModule, FormsModule,
RouterModule, ReactiveFormsModule, MatSelectModule, InputModule
RouterModule, ReactiveFormsModule, InputModule
],
declarations: [
EntitiesSelectionComponent,

View File

@ -182,7 +182,7 @@
(stickyForm?'':' ') :
(+ (stickyForm?'':' uk-section') +' uk-padding-remove-bottom uk-padding-remove-top ' +
((usedBy == 'deposit' || usedBy == 'orcid') ? ' uk-padding-remove-top ' : ' '))"
[attr.uk-sticky]="((stickyForm || (simpleView && mobile))?'{offset:100;top:90;cls-active:uk-active uk-sticky-below;cls-inactive:uk-sticky '+
[attr.uk-sticky]="((stickyForm || (simpleView && mobile))?'{offset:100;start:90;cls-active:uk-active uk-sticky-below;cls-inactive:uk-sticky '+
(usedBy != 'deposit' && usedBy != 'orcid' && (!customFilter || customFilter.queryFieldName != 'communityId') ?
' uk-position-relative ' :(' uk-section ' ))+'}':null)">
<div class="uk-background-norepeat uk-background-bottom-center" [ngClass]="searchForm.class">

View File

@ -982,7 +982,7 @@ export class NewSearchPageComponent implements OnInit, OnDestroy, OnChanges {
doisParams += (doisParams.length > 0 ? " or " : "") + '(authorid="' + StringUtils.URIEncode(identifier.id) + '")';
// doisParams += (doisParams.length > 0 ? " or " : "") +'(authorid="' + StringUtils.URIEncode(identifier.id) + '" and (authoridtype exact "orcid"))';
} else {
doisParams += (doisParams.length > 0 ? " or " : "") + '(pidclassid exact "' + identifier.class + '" and pid="' + StringUtils.URIEncode(identifier.id) + '")';
doisParams += (doisParams.length > 0 ? " or " : "") + '(pid="' + StringUtils.URIEncode(identifier.id) + '")';
// doisParams += (doisParams.length > 0 ? " or " : "") +'(pidclassid exact "' + identifier.class + '" and pid="' + identifier.id + '")';
}
}

View File

@ -6,7 +6,6 @@ import {ConfigurationService} from "../../utils/configuration/configuration.serv
import {Subscription} from "rxjs";
import {ActivatedRoute, Router} from "@angular/router";
import {properties} from "../../../../environments/environment";
import {OpenaireEntities} from "../../utils/properties/searchFields";
@Component({
selector: 'quick-selections',

View File

@ -5,25 +5,21 @@ import { FormsModule } from '@angular/forms';
import {SearchFilterComponent} from './searchFilter.component';
import {SearchFilterModalComponent} from './searchFilterModal.component';
import {ModalModule} from '../../utils/modal/modal.module';
import { MatSelectModule } from "@angular/material/select";
import {RouterModule} from "@angular/router";
import {InputModule} from '../../sharedComponents/input/input.module';
import {IconsModule} from "../../utils/icons/icons.module";
@NgModule({
imports: [
CommonModule, FormsModule, ModalModule, MatSelectModule, RouterModule,
CommonModule, FormsModule, ModalModule, RouterModule,
InputModule, IconsModule
],
declarations: [
SearchFilterComponent, SearchFilterModalComponent
],
providers:[
],
exports: [
SearchFilterComponent, SearchFilterModalComponent
]
})
export class SearchFilterModule { }
export class SearchFilterModule {
}

View File

@ -4,13 +4,11 @@ import { FormsModule } from '@angular/forms';
import {RouterModule} from '@angular/router';
import {SearchSortingComponent} from './searchSorting.component';
import { MatSelectModule } from "@angular/material/select";
import {InputModule} from '../../sharedComponents/input/input.module';
@NgModule({
imports: [
CommonModule, FormsModule, RouterModule, MatSelectModule,
InputModule
CommonModule, FormsModule, RouterModule, InputModule
],
declarations: [
SearchSortingComponent

View File

@ -1,4 +1,3 @@
import {COOKIE} from '../../login/utils/helper.class';
import {HttpHeaders} from "@angular/common/http";
export type MediaType = 'application/json' | 'text/plain'
@ -16,17 +15,12 @@ export class CustomOptions {
return {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'X-XSRF-TOKEN': COOKIE.getCookie(COOKIE.cookieName_id)?COOKIE.getCookie(COOKIE.cookieName_id):''
}), withCredentials: true
};
}
public static getAuthOptions():{} {
return {
headers: new HttpHeaders({
'X-XSRF-TOKEN': (COOKIE.getCookie(COOKIE.cookieName_id)) ? COOKIE.getCookie(COOKIE.cookieName_id) : ''
}), withCredentials: true
};
return this.getAuthOptionsWithBody();
}
}

View File

@ -112,7 +112,7 @@
<li><a target="_blank" href="https://www.openaire.eu/contact-noads">NOADs</a></li>
<li><a target="_blank" href="https://www.openaire.eu/guides">Guides</a></li>
<li><a target="_blank" href="https://www.openaire.eu/faqs">FAQs</a></li>
<li><a target="_blank" href="https://www.openaire.eu/frontpage/webinars">Webinars</a></li>
<li><a target="_blank" href="https://www.openaire.eu/support/webinars">Webinars</a></li>
<li><a target="_blank" href="https://www.openaire.eu/helpdesk">Ask a question</a></li>
</ul>
</div>
@ -123,7 +123,7 @@
<ul class="uk-nav uk-nav-default" uk-nav>
<li><a target="_blank" href="https://www.openaire.eu/news/">News</a></li>
<li><a target="_blank" href="https://www.openaire.eu/events">Events</a></li>
<li><a target="_blank" href="https://www.openaire.eu/blogs/magazine">Blogs</a></li>
<li><a target="_blank" href="https://www.openaire.eu/blogs">Blogs</a></li>
<li><a target="_blank" href="https://www.openaire.eu/newsletters">Newsletter</a></li>
<li><a target="_blank" href="https://www.openaire.eu/public-documents">Documents</a></li>
</ul>
@ -180,8 +180,8 @@
<div class="uk-grid uk-flex-center uk-text-xsmall uk-text-emphasis" uk-grid>
<div class=" uk-flex uk-flex-top">
<a href="https://creativecommons.org/licenses/by/4.0/" rel="license" class="uk-link-reset">
<icon name="cc" [preserveColor]="true" visuallyHidden="Creative-Commons"></icon>
<icon name="by" [preserveColor]="true" visuallyHidden="Licence" class="uk-margin-xsmall-left"></icon>
<icon name="cc" visuallyHidden="Creative-Commons"></icon>
<icon name="by" visuallyHidden="Licence" class="uk-margin-xsmall-left"></icon>
</a>
<span class="uk-margin-small-left uk-width-expand">Unless otherwise indicated, all materials created by OpenAIRE are licenced under</span>
<a class="uk-link-text uk-margin-medium-left" href="http://creativecommons.org/licenses/by/4.0/" rel="license"><u>CC ATTRIBUTION 4.0 INTERNATIONALLICENSE</u></a>
@ -262,8 +262,8 @@
<div class="uk-grid uk-flex-center uk-text-xsmall uk-text-emphasis" uk-grid>
<div class=" uk-flex uk-flex-top">
<a href="https://creativecommons.org/licenses/by/4.0/" rel="license" class="uk-link-reset">
<icon name="cc" [preserveColor]="true" visuallyHidden="Creative-Commons"></icon>
<icon name="by" [preserveColor]="true" visuallyHidden="Licence" class="uk-margin-xsmall-left"></icon>
<icon name="cc" visuallyHidden="Creative-Commons"></icon>
<icon name="by" visuallyHidden="Licence" class="uk-margin-xsmall-left"></icon>
</a>
<span class="uk-margin-small-left uk-width-expand">Unless otherwise indicated, all materials created by OpenAIRE are licenced under</span>
</div>

View File

@ -196,11 +196,14 @@ declare var UIkit;
</div>
</div>
<div *ngIf="!mobile && type === 'date' && opened" class="uk-dropdown" #calendarBox
uk-dropdown="pos: bottom-left; mode: none; boundary-align: true;" [attr.boundary]="'#' + id" (click)="$event.stopPropagation()">
uk-dropdown="pos: bottom-left; mode: none; flip: false ; shift: false" [attr.target]="'#' + id" [attr.boundary]="'#' + id" (click)="$event.stopPropagation()">
<mat-calendar [selected]="selectedDate" [startAt]="selectedDate" (selectedChange)="dateChanged($event)"></mat-calendar>
</div>
<mobile-dropdown *ngIf="mobile && type === 'date' && opened" (onClose)="focus(false)" #mobileDropdown>
<mat-calendar [selected]="selectedDate" [startAt]="selectedDate" (selectedChange)="dateChanged($event)"></mat-calendar>
</mobile-dropdown>
<div *ngIf="!mobile && filteredOptions && filteredOptions.length > 0 && opened" class="options uk-dropdown" #optionBox
uk-dropdown="pos: bottom-justify; mode: none; boundary-align: true;" [attr.boundary]="'#' + id">
uk-dropdown="mode: none; stretch: true; flip: false; shift: false" [attr.boundary]="'#' + id">
<ul class="uk-nav uk-dropdown-nav">
<li *ngFor="let option of filteredOptions; let i=index" [class.uk-hidden]="option.hidden"
[class.uk-active]="(formControl.value === option.value) || selectedIndex === i">
@ -228,7 +231,7 @@ declare var UIkit;
</div>
</mobile-dropdown>
</div>
<span *ngIf="formControl?.invalid && formControl?.touched" class="uk-text-danger">
<span *ngIf="formControl?.invalid && formControl?.touched" class="uk-text-small uk-text-danger">
<span *ngIf="errors?.error">{{errors?.error}}</span>
<span *ngIf="type === 'URL' || type === 'logoURL'">Please provide a valid URL (e.g. https://example.com)</span>
</span>

View File

@ -1,5 +1,5 @@
<div *ngIf="showMenu && activeHeader">
<div id="main-menu-small" class="uk-hidden@m">
<div id="main-menu-small" class="uk-hidden@m" [attr.uk-sticky]="hasStickyHeaderOnMobile?'':null">
<nav class="uk-navbar-container uk-navbar" uk-navbar="delay-hide: 400">
<div *ngIf="!onlyTop || userMenu" class="uk-navbar-left" [class.uk-light]='activeHeader.darkBg'>
<a class="uk-navbar-toggle" href="#tm-mobile" uk-toggle>
@ -31,7 +31,7 @@
<ng-container *ngTemplateOutlet="header_template; context: {mobile: true}"></ng-container>
</div>
</nav>
<ul class="uk-nav uk-nav-primary uk-list uk-list-large uk-margin-large-top uk-nav-parent-icon" uk-nav>
<ul class="uk-nav uk-nav-primary uk-list uk-list-large uk-margin-large-top" uk-nav>
<ng-container *ngIf="!onlyTop">
<li *ngIf="showHomeMenuItem && currentRoute.route !== '/'">
<a routerLink="/" (click)="closeCanvas(canvas)">Home</a>
@ -40,17 +40,15 @@
<li [class.uk-active]="isTheActiveMenu(menu)" [class.uk-parent]="menu.items.length > 0" [ngClass]="menu.customClass"
*ngIf="isAtleastOneEnabled(menu.entitiesRequired,showEntity) && isAtleastOneEnabled(menu.routeRequired, showPage)">
<!--a routerLink="{{menu.rootItem.route}}" [queryParams]=menu.rootItem.params class="uk-offcanvas-close custom-offcanvas-close">{{menu.rootItem.title}}</a-->
<a *ngIf="menu.route && (isEnabled([menu.route], showPage) || !menu.routeRequired)"
[routerLink]="menu.items.length === 0?menu.route:null" (click)="menu.items.length === 0?closeCanvas(canvas):null"
<a *ngIf="!menu.url" [routerLink]="menu.route && (isEnabled([menu.route], showPage) || !menu.routeRequired) && menu.items.length === 0?menu.route:null"
(click)="menu.items.length === 0?closeCanvas(canvas):null"
[queryParams]="menu.params"
[fragment]="menu.fragment">{{menu.title}}</a>
<a *ngIf="!menu.route && menu.url"
[href]="menu.items.length === 0?menu.url:null" (click)="menu.items.length === 0?closeCanvas(canvas):null"
[class.custom-external]="menu.target != '_self'" [target]="menu.target">{{menu.title}}</a>
<a *ngIf="(!menu.route && !menu.url) ||
(menu.route && menu.routeRequired && !isEnabled([menu.route], showPage)
&& isAtleastOneEnabled(menu.routeRequired, showPage))"
(click)="menu.items.length === 0?closeCanvas(canvas):null">{{menu.title}}</a>
[fragment]="menu.fragment">{{menu.title}}<span *ngIf="menu.items.length > 0" class="uk-nav-parent-icon"></span></a>
<a *ngIf="menu.url"
(click)="menu.items.length === 0?closeCanvas(canvas):null"
[href]="menu.items.length === 0?menu.url:null"
[class.custom-external]="menu.url && menu.target != '_self'" [target]="menu.url?menu.target:null">
{{menu.title}}<span *ngIf="menu.items.length > 0" class="uk-nav-parent-icon"></span></a>
<ul *ngIf="menu.items.length > 0" class="uk-nav-sub">
<ng-container *ngFor="let submenu of menu.items">
<li [class.uk-active]="isTheActiveMenu(submenu)" [ngClass]="submenu.customClass"
@ -270,10 +268,9 @@
<a *ngIf="menu.type == 'noAction'">
{{menu.title}}
</a>
<div *ngIf="menu.items.length > 0" class="uk-navbar-dropdown uk-navbar-dropdown-bottom-left"
<div *ngIf="menu.items.length > 0" class="uk-navbar-dropdown uk-navbar-dropdown-bottom-left uk-height-max-medium uk-overflow-auto"
style="top: 80px; left: 0px;" id="{{menu._id}}" uk-toggle>
<div class="uk-navbar-dropdown-grid uk-child-width-1-1 uk-grid uk-grid-stack" uk-grid="">
<div class="uk-first-column uk-height-max-medium uk-overflow-auto">
<div>
<ul class="uk-nav uk-navbar-dropdown-nav">
<ng-container *ngFor="let submenu of menu.items">
<li [class.uk-active]="isTheActiveMenu(submenu)" [ngClass]="submenu.customClass">
@ -293,7 +290,6 @@
</ul>
</div>
</div>
</div>
</li>
</ng-container>
</ng-container>

View File

@ -63,6 +63,7 @@ export class NavigationBarComponent implements OnInit, OnDestroy, OnChanges {
@Input() showLogo: boolean = true;
@Input() notificationConfiguration: NotificationConfiguration;
replaceHeader: boolean = false;
hasStickyHeaderOnMobile: boolean = false;
public activeHeader: Header;
keyword: string = '';
public isAuthorized: boolean = false;
@ -86,6 +87,7 @@ export class NavigationBarComponent implements OnInit, OnDestroy, OnChanges {
value: "Open Access"
};
@ViewChild('search_input') search_input: SearchInputComponent;
@ViewChild('canvas') canvas: ElementRef;
public routerHelper: RouterHelper = new RouterHelper();
constructor(private router: Router,
@ -100,6 +102,9 @@ export class NavigationBarComponent implements OnInit, OnDestroy, OnChanges {
this.searchMode = false;
this.keyword = "";
}));
this.subs.push(this.layoutService.hasStickyHeaderOnMobile.subscribe(hasStickyHeaderOnMobile => {
this.hasStickyHeaderOnMobile = hasStickyHeaderOnMobile;
}))
this.initialize();
}
@ -115,7 +120,7 @@ export class NavigationBarComponent implements OnInit, OnDestroy, OnChanges {
}
}
closeCanvas(element) {
closeCanvas(element = this.canvas.nativeElement) {
UIkit.offcanvas(element).hide();
}

View File

@ -0,0 +1 @@
<p>pick-icon works!</p>

View File

@ -0,0 +1,21 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Icon} from "../../utils/icons/icons";
import {IconsService} from "../../utils/icons/icons.service";
@Component({
selector: 'pick-icon',
templateUrl: './pick-icon.component.html'
})
export class PickIconComponent implements OnInit, OnDestroy{
customIcons: Icon[] = [];
constructor(private iconService: IconsService) {
}
ngOnInit() {
this.customIcons = this.iconService.getAll();
}
ngOnDestroy() {
}
}

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PickIconComponent } from './pick-icon.component';
@NgModule({
declarations: [
PickIconComponent
],
exports: [
PickIconComponent
],
imports: [
CommonModule
]
})
export class PickIconModule { }

View File

@ -1,13 +1,12 @@
import {NgModule} from '@angular/core';
import {SharedModule} from '../../../openaireLibrary/shared/shared.module';
import {SearchInputComponent} from './search-input.component';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {IconsModule} from '../../utils/icons/icons.module';
import {InputModule} from "../input/input.module";
import {ClickModule} from "../../utils/click/click.module";
@NgModule({
imports: [SharedModule, MatAutocompleteModule, IconsModule, InputModule, ClickModule],
imports: [SharedModule, IconsModule, InputModule, ClickModule],
declarations: [SearchInputComponent],
exports: [SearchInputComponent]
})

View File

@ -0,0 +1,78 @@
import {AfterContentInit, Component, ContentChildren, Input, OnDestroy, QueryList} from '@angular/core';
import {SliderItemComponent} from "./slider-item.component";
import {SliderNavItemComponent} from "./slider-nav-item.component";
import {SliderContainerComponent} from "./slider-container.component";
import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service";
import {Subscriber} from "rxjs";
@Component({
selector: 'slider-column',
template: `
<div class="uk-position-relative">
<ng-content></ng-content>
</div>
`
})
export class SliderColumnComponent implements AfterContentInit, OnDestroy {
@Input()
type: 'slider' | 'nav' = null;
@Input()
animation = null;
@ContentChildren(SliderItemComponent) items: QueryList<SliderItemComponent>;
@ContentChildren(SliderNavItemComponent) navItems: QueryList<SliderNavItemComponent>;
public isMobile: boolean;
private subscriptions: any[] = [];
constructor(private layoutService: LayoutService) {
}
ngOnDestroy() {
this.subscriptions.forEach(value => {
if (value instanceof Subscriber) {
value.unsubscribe();
}
});
}
ngAfterContentInit() {
if (this.animation) {
this.slides.forEach(slide => {
slide.init(this.animation);
});
this.navItems.forEach(slide => {
slide.init(this.animation);
});
}
this.subscriptions.push(this.layoutService.isMobile.subscribe(isMobile => {
this.isMobile = isMobile;
}));
}
change(time: number) {
if (this.type === 'slider') {
let slides = this.slides;
for (let i = 0; i < slides.length; i++) {
slides[i].setActive(slides[i].start <= time && (!slides[i + 1] || slides[i + 1].start > time));
}
}
if (this.type === 'nav') {
let slides = this.navItems;
for (let i = 0; i < slides.length; i++) {
slides.get(i).setActive(!this.isMobile || (slides.get(i).start <= time && (!slides.get(i + 1) || slides.get(i + 1).start > time)));
slides.get(i).active = slides.get(i).start <= time && (!slides.get(i + 1) || slides.get(i + 1).start > time);
}
}
}
setContainer(container: SliderContainerComponent) {
if (this.type === 'nav') {
this.navItems.forEach(item => {
item.container = container;
});
}
}
get slides(): SliderItemComponent[] {
return this.items.filter(item => item.type === 'slide');
}
}

View File

@ -0,0 +1,130 @@
import {
AfterContentInit,
ChangeDetectorRef,
Component,
ContentChildren,
ElementRef,
Input, OnDestroy,
OnInit,
QueryList
} from "@angular/core";
import {SliderColumnComponent} from "./slider-column.component";
export class Stage {
value: number;
max: number;
}
@Component({
selector: 'slider-container',
template: `
<div class="uk-grid uk-grid-large uk-flex uk-flex-middle uk-flex-between uk-child-width-1-1" [ngClass]="'uk-child-width-1-' + sliders?.length + '@m'" uk-grid>
<ng-content></ng-content>
</div>
<div *ngIf="navigation === 'progress' && stages.length > 0" class="uk-flex uk-flex-center uk-margin-large-top">
<div class="uk-width-1-3@m uk-width-1-2@s uk-child-width-1-1 uk-grid uk-grid-small uk-flex-middle" [ngClass]="'uk-child-width-1-' + stages.length" uk-grid>
<div *ngFor="let stage of stages; let i=index">
<progress (click)="start(i)" class="uk-progress" [value]="stage.value" [max]="stage.max"></progress>
</div>
</div>
</div>
`
})
export class SliderContainerComponent implements OnInit, OnDestroy, AfterContentInit {
private static INTERVAL = 10;
private static ANIMATION_DURATION = 600; // UIKit progress animation duration
@ContentChildren(SliderColumnComponent) sliders: QueryList<SliderColumnComponent>;
@Input()
navigation: 'progress' | null = null;
@Input()
total: number = 0;
@Input()
period: number = 3000; // in ms (>= 1000ms)
@Input()
infinite: boolean = false;
@Input()
parent: HTMLDivElement;
stages: Stage[] = [];
time: number = 0;
interval: any;
observer: IntersectionObserver;
initialized: boolean = false;
stopped: boolean = true;
constructor(private cdr: ChangeDetectorRef, private element: ElementRef) {
}
ngOnInit() {
this.period = this.period - SliderContainerComponent.ANIMATION_DURATION;
}
ngOnDestroy() {
if(this.observer && typeof IntersectionObserver !== 'undefined') {
this.observer.disconnect();
}
}
ngAfterContentInit() {
this.setObserver();
this.sliders.forEach(slider => {
slider.setContainer(this);
slider.navItems.forEach(item => {
if(this.parent) {
item.background = getComputedStyle(this.parent).backgroundColor;
}
});
});
}
setObserver() {
if(typeof IntersectionObserver !== 'undefined') {
let options = {
root: null,
rootMargin: '0px',
threshold: 0.1
};
this.observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.initialized) {
this.stopped = false;
this.start(0);
this.initialized = true;
} else {
this.initialized = false;
this.stopped = true;
}
});
}, options);
this.observer.observe(this.element.nativeElement);
}
}
start(time: number) {
this.stages = [];
for(let i = 0; i < this.total; i++) {
this.stages.push({value: (time > i?this.period:(time - i)), max: this.period});
}
if(this.interval) {
clearInterval(this.interval);
}
this.cdr.detectChanges();
this.interval = setInterval(() => {
let current = Math.floor(time);
this.stages[current].value += SliderContainerComponent.INTERVAL;
this.time = current + this.stages[current].value/this.stages[current].max;
this.sliders.forEach(slider => {
slider.change(this.time);
});
if(this.stages[current].value >= this.stages[current].max) {
clearInterval(this.interval);
let next = (current + 1 > this.total - 1)?0:current + 1;
if(!this.stopped && (this.infinite || next > current)) {
setTimeout(() => {
this.start(next);
}, SliderContainerComponent.ANIMATION_DURATION);
}
}
}, SliderContainerComponent.INTERVAL);
}
}

View File

@ -0,0 +1,29 @@
import {Component, ElementRef, Input} from "@angular/core";
@Component({
selector: 'slider-item',
template: `
<ng-content></ng-content>
`
})
export class SliderItemComponent {
@Input()
type: 'slide' | 'static' = 'slide';
@Input()
start: number;
constructor(private element: ElementRef) {
}
init(animation: string) {
this.element.nativeElement.classList.add(animation);
}
setActive(active: boolean) {
if(active) {
this.element.nativeElement.classList.remove('uk-hidden');
} else {
this.element.nativeElement.classList.add('uk-hidden');
}
}
}

View File

@ -0,0 +1,51 @@
import {AfterViewInit, Component, ElementRef, Input, ViewChild} from "@angular/core";
import {ActivatedRoute} from "@angular/router";
import {SliderContainerComponent} from "./slider-container.component";
import {SliderItemComponent} from "./slider-item.component";
export interface Link {
routerLink?: {
commands: string[] | string,
queryParams?: Object,
fragment?: string,
relativeTo?: ActivatedRoute
}
href?: string
external?: boolean
}
@Component({
selector: 'slider-nav-item',
template: `
<div *ngIf="container" (click)="container.start(start)" class="uk-flex uk-flex-middle" [class.uk-active]="active">
<div class="uk-width-expand">
<ng-content></ng-content>
</div>
<div *ngIf="link" class="action">
<a *ngIf="link?.routerLink" #linkElement class="uk-text-decoration-none" [routerLink]="link.routerLink.commands" [queryParams]="link.routerLink.queryParams"
[fragment]="link.routerLink.fragment" [relativeTo]="link.routerLink.relativeTo" [target]="link.external?'_blank':'_self'">
<icon name="chevron_right" ratio="1.5" [flex]="true"></icon>
</a>
<a *ngIf="link?.href" #linkElement [href]="link.href" class="uk-text-decoration-none" [target]="link.external?'_blank':'_self'">
<icon name="chevron_right" ratio="1.5" [flex]="true"></icon>
</a>
</div>
</div>
`
})
export class SliderNavItemComponent extends SliderItemComponent implements AfterViewInit {
@Input()
link: Link = null;
@Input()
start: number;
active: boolean = false;
container: SliderContainerComponent;
background: string;
@ViewChild('linkElement') linkElement: ElementRef;
ngAfterViewInit() {
if(this.linkElement) {
this.linkElement.nativeElement.style.background = this.background;
}
}
}

Some files were not shown because too many files have changed in this diff Show More