diff --git a/apps/dsp-app/src/app/app-routing.module.ts b/apps/dsp-app/src/app/app-routing.module.ts index 45df451afa..acd89038b2 100644 --- a/apps/dsp-app/src/app/app-routing.module.ts +++ b/apps/dsp-app/src/app/app-routing.module.ts @@ -26,8 +26,9 @@ import { UserComponent } from './user/user.component'; import { ResourceComponent } from './workspace/resource/resource.component'; import { ResultsComponent } from './workspace/results/results.component'; import { AdvancedSearchContainerComponent } from './workspace/search/advanced-search/advanced-search-container.component'; -import { ProjectFormComponent } from "@dsp-app/src/app/project/project-form/project-form.component"; import { RouteConstants } from '@dasch-swiss/vre/shared/app-config'; +import { OntologyClassInstanceGuard } from './main/guard/ontology-class-instance.guard'; +import { ProjectFormComponent } from './project/project-form/project-form.component'; const routes: Routes = [ { @@ -75,10 +76,12 @@ const routes: Routes = [ canActivate: [AuthGuard], }, { + canActivate: [OntologyClassInstanceGuard], path: RouteConstants.OntologyClassRelative, component: OntologyClassInstanceComponent, }, { + canActivate: [OntologyClassInstanceGuard], path: RouteConstants.OntologyClassInstanceRelative, component: OntologyClassInstanceComponent, }, diff --git a/apps/dsp-app/src/app/main/guard/ontology-class-instance.guard.ts b/apps/dsp-app/src/app/main/guard/ontology-class-instance.guard.ts new file mode 100644 index 0000000000..5e5151ed96 --- /dev/null +++ b/apps/dsp-app/src/app/main/guard/ontology-class-instance.guard.ts @@ -0,0 +1,49 @@ +import { ProjectService } from '@dasch-swiss/vre/shared/app-helper-services'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; +import { Observable, combineLatest } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { AuthService } from '@dasch-swiss/vre/shared/app-session'; +import { RouteConstants } from '@dasch-swiss/vre/shared/app-config'; +import { Select } from '@ngxs/store'; +import { UserSelectors } from '@dasch-swiss/vre/shared/app-state'; +import { StoredProject } from '@dasch-swiss/dsp-js'; + + +@Injectable({ + providedIn: 'root' +}) +export class OntologyClassInstanceGuard implements CanActivate { + isLoggedIn$: Observable = this.authService.isLoggedIn$; + + @Select(UserSelectors.isSysAdmin) isSysAdmin$: Observable; + @Select(UserSelectors.userProjects) userProjects$: Observable; + + constructor( + private authService: AuthService, + private projectService: ProjectService, + private router: Router, + ) { + } + + canActivate(activatedRoute: ActivatedRouteSnapshot): Observable { + const instanceId = activatedRoute.params[RouteConstants.instanceParameter]; + return combineLatest([this.isLoggedIn$, this.isSysAdmin$, this.userProjects$]) + .pipe( + map(([isLoggedIn, isSysAdmin, userProjects]) => { + const projectUuid = activatedRoute.parent.params[RouteConstants.uuidParameter]; + if (!isLoggedIn && instanceId === RouteConstants.addClassInstance) { + this.router.navigateByUrl(`/${RouteConstants.project}/${projectUuid}`); + return false; + } + + return (instanceId === RouteConstants.addClassInstance + && (userProjects?.some((p) => p.id === this.projectService.uuidToIri(projectUuid)) // project member + || isSysAdmin // system admin + ) + ); + }) + ) + } +} + diff --git a/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.html b/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.html index fa43818997..65d90f3130 100644 --- a/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.html +++ b/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.html @@ -1,9 +1,9 @@ -
+
- @@ -26,15 +26,15 @@ -
-

Create new resource of type: {{resClass?.label}}

+
+

Create new resource of type: {{(resClass$ | async)?.label}}

-
- +
+
diff --git a/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.ts b/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.ts index e18f6e808f..0c0efae726 100644 --- a/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.ts +++ b/apps/dsp-app/src/app/project/ontology-classes/ontology-class-instance/ontology-class-instance.component.ts @@ -1,16 +1,17 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnChanges, OnInit } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { ResourceClassDefinition } from '@dasch-swiss/dsp-js'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ResourceClassDefinition, StoredProject } from '@dasch-swiss/dsp-js'; import { AppConfigService, getAllEntityDefinitionsAsArray, RouteConstants } from '@dasch-swiss/vre/shared/app-config'; import { OntologyService, ProjectService } from '@dasch-swiss/vre/shared/app-helper-services'; import { FilteredResources, SearchParams } from '@dsp-app/src/app/workspace/results/list-view/list-view.component'; import { SplitSize } from '@dsp-app/src/app/workspace/results/results.component'; import { ProjectBase } from '../../project-base'; -import { Actions, Store } from '@ngxs/store'; +import { Actions, Select, Store } from '@ngxs/store'; import { Title } from '@angular/platform-browser'; -import { OntologiesSelectors, UserSelectors } from '@dasch-swiss/vre/shared/app-state'; -import { AuthService } from '@dasch-swiss/vre/shared/app-session'; -import { filter } from 'rxjs/operators'; +import { IProjectOntologiesKeyValuePairs, OntologiesSelectors, UserSelectors } from '@dasch-swiss/vre/shared/app-state'; +import { map, takeWhile } from 'rxjs/operators'; +import { takeUntil } from 'rxjs/operators'; +import { Observable, Subject, combineLatest } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -18,20 +19,97 @@ import { filter } from 'rxjs/operators'; templateUrl: './ontology-class-instance.component.html', styleUrls: ['./ontology-class-instance.component.scss'], }) -export class OntologyClassInstanceComponent extends ProjectBase implements OnInit, OnChanges { - ontoId: string; +export class OntologyClassInstanceComponent extends ProjectBase implements OnInit, OnDestroy { + private ngUnsubscribe: Subject = new Subject(); + + routeConstants = RouteConstants; + + get ontoId$(): Observable { + return combineLatest([this.project$, this._route.params]) + .pipe( + takeWhile(([project]) => project !== undefined), + takeUntil(this.ngUnsubscribe), + map(([project, params]) => { + const iriBase = this._ontologyService.getIriBaseUrl(); + const ontologyName = params[RouteConstants.ontoParameter]; + // get the resource ids from the route. Do not use the RouteConstants ontology route constant here, + // because the ontology and class ids are not defined within the apps domain. They are defined by + // the api and can not be changed generically via route constants. + return `${iriBase}/ontology/${project.shortcode}/${ontologyName}/v2`; + }) + ) + } + + // uuid of resource instance + get instanceId$(): Observable { + return this._route.params.pipe( + takeUntil(this.ngUnsubscribe), + map(params => { + return params[RouteConstants.instanceParameter] + }) + ) + } // id (iri) of resource class - classId: string; + get classId$(): Observable { + return combineLatest([this.ontoId$, this._route.params]) + .pipe( + takeUntil(this.ngUnsubscribe), + map(([ontoId, params]) => { + const className = params[RouteConstants.classParameter]; + return `${ontoId}#${className}`; + }) + ) + } - resClass: ResourceClassDefinition; + get resClass$(): Observable { + return combineLatest([this.projectOntologies$, this.classId$, this.ontoId$, this.instanceId$, this.userProjects$, this.isSysAdmin$]) + .pipe( + takeUntil(this.ngUnsubscribe), + map(([projectOntologies, classId, ontoId, instanceId, userProjects, isSysAdmin]) => { + if ( + (instanceId !== RouteConstants.addClassInstance + || (instanceId === RouteConstants.addClassInstance && !(userProjects?.some((p) => p.id === this.projectIri) || isSysAdmin))) + || !projectOntologies[this.projectIri] + ) { + return; + } + + const ontology = projectOntologies[this.projectIri].readOntologies.find((onto) => onto.id === ontoId); + if (ontology) { + // find ontology of current resource class to get the class label + const classes = getAllEntityDefinitionsAsArray(ontology.classes); + return (classes[classes.findIndex((res) => res.id === classId)]); + } + }) + ) + } - // uuid of resource instance - instanceId: string; // id (iri) or resource instance - resourceIri: string; + get resourceIri$(): Observable { + return this.instanceId$.pipe( + takeUntil(this.ngUnsubscribe), + map(instanceId => instanceId !== RouteConstants.addClassInstance + ? `${this._acs.dspAppConfig.iriBase}/${this.projectUuid}/${instanceId}` + : '' + ) + ) + } - searchParams: SearchParams; + get searchParams$(): Observable { + return combineLatest([this.classId$, this.instanceId$, this.project$]) + .pipe( + takeWhile(([project]) => project !== undefined), + takeUntil(this.ngUnsubscribe), + map(([classId, instanceId]) => !instanceId + ? { + query: this._setGravsearch(classId), + mode: 'gravsearch', + } + : null + ) + ) + } // which resources are selected? selectedResources: FilteredResources; @@ -41,9 +119,12 @@ export class OntologyClassInstanceComponent extends ProjectBase implements OnIni splitSizeChanged: SplitSize; + @Select(OntologiesSelectors.projectOntologies) projectOntologies$: Observable; + @Select(UserSelectors.isSysAdmin) isSysAdmin$: Observable; + @Select(UserSelectors.userProjects) userProjects$: Observable; + constructor( private _acs: AppConfigService, - private _authService: AuthService, protected _route: ActivatedRoute, private _ontologyService: OntologyService, protected _projectService: ProjectService, @@ -57,26 +138,13 @@ export class OntologyClassInstanceComponent extends ProjectBase implements OnIni } ngOnInit() { - // this._route.params.subscribe((params) => { - // this.initProject(params); - // }); - - this.project$.pipe( - filter(project => project !== undefined), - ).subscribe((project) => { - this.project = project; - this.initProject(this._route.snapshot.params); - }); + // waits for current project data to be loaded to the state if not already loaded + this.project$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(() => this._cdr.markForCheck()); } - ngOnChanges() { - const projectOntologies = this._store.selectSnapshot(OntologiesSelectors.projectOntologies)[this.projectIri]; - // find ontology of current resource class to get the class label - const ontology = projectOntologies.readOntologies.find((onto) => onto.id === this.ontoId); - const classes = getAllEntityDefinitionsAsArray(ontology.classes); - this.resClass = ( - classes[classes.findIndex((res) => res.id === this.classId)] - ); + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); } openSelectedResources(res: FilteredResources) { @@ -93,55 +161,6 @@ export class OntologyClassInstanceComponent extends ProjectBase implements OnIni this._cdr.detectChanges(); } - private initProject(params: Params): void { - const iriBase = this._ontologyService.getIriBaseUrl(); - const ontologyName = params[RouteConstants.ontoParameter]; - const className = params[RouteConstants.classParameter]; - - // get the resource ids from the route. Do not use the RouteConstants ontology route constant here, - // because the ontology and class ids are not defined within the apps domain. They are defined by - // the api and can not be changed generically via route constants. - this.ontoId = `${iriBase}/ontology/${this.project.shortcode}/${ontologyName}/v2`; - this.classId = `${this.ontoId}#${className}`; - - this.instanceId = params[RouteConstants.instanceParameter]; - if (this.instanceId) { - // single instance view - - if (this.instanceId === RouteConstants.addClassInstance) { - if (!this._authService.isLoggedIn) { - // user isn't signed in, redirect to project description - this._router.navigateByUrl(`/${RouteConstants.project}/${this.projectUuid}`); - } else { - const isSysAdmin = this._store.selectSnapshot(UserSelectors.isSysAdmin); - const usersProjects = this._store.selectSnapshot(UserSelectors.userProjects); - if ( - usersProjects?.some((p) => p.id === this.projectIri) || // project member - isSysAdmin // system admin - ) { - // user has permission, display create resource instance form - this.ngOnChanges(); - } else { - // user is not a member of the project or a systemAdmin, redirect to project description - this._router.navigateByUrl( - `/${RouteConstants.project}/${this.projectUuid}`, - ); - } - } - } else { - // get the single resource instance - this.resourceIri = `${this._acs.dspAppConfig.iriBase}/${this.projectUuid}/${this.instanceId}`; - } - } else { - // display all resource instances of this resource class - this.searchParams = { - query: this._setGravsearch(this.classId), - mode: 'gravsearch', - }; - this._cdr.markForCheck(); - } - } - private _setGravsearch(iri: string): string { return ` PREFIX knora-api: diff --git a/apps/dsp-app/src/app/project/ontology/resource-class-info/resource-class-property-info/resource-class-property-info.component.ts b/apps/dsp-app/src/app/project/ontology/resource-class-info/resource-class-property-info/resource-class-property-info.component.ts index c242cb7f46..d922449ce3 100644 --- a/apps/dsp-app/src/app/project/ontology/resource-class-info/resource-class-property-info/resource-class-property-info.component.ts +++ b/apps/dsp-app/src/app/project/ontology/resource-class-info/resource-class-property-info/resource-class-property-info.component.ts @@ -134,12 +134,11 @@ export class ResourceClassPropertyInfoComponent } ngAfterContentInit() { - if (this.propDef.isLinkProperty) { + // get current ontology to get linked res class information + const ontology = this._store.selectSnapshot(OntologiesSelectors.currentOntology); + const currentProjectOntologies = this._store.selectSnapshot(OntologiesSelectors.currentProjectOntologies); + if (ontology && currentProjectOntologies && this.propDef.isLinkProperty) { // this property is a link property to another resource class - // get current ontology to get linked res class information - - const ontology = this._store.selectSnapshot(OntologiesSelectors.currentOntology); - const currentProjectOntologies = this._store.selectSnapshot(OntologiesSelectors.currentProjectOntologies); // get the base ontology of object type const baseOnto = this.propDef.objectType.split('#')[0]; if (baseOnto !== ontology.id) { @@ -162,10 +161,10 @@ export class ResourceClassPropertyInfoComponent } } - if (this.propDef.objectType === Constants.ListValue) { + // get current ontology lists to get linked list information + const currentOntologyLists = this._store.selectSnapshot(ListsSelectors.listsInProject); + if (currentOntologyLists && this.propDef.objectType === Constants.ListValue) { // this property is a list property - // get current ontology lists to get linked list information - const currentOntologyLists = this._store.selectSnapshot(ListsSelectors.listsInProject); const re = /\<([^)]+)\>/; const listIri = this.propDef.guiAttributes[0].match(re)[1]; const listUrl = `/project/${ diff --git a/apps/dsp-app/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts b/apps/dsp-app/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts index 3baf26f47d..aa47ce7e63 100644 --- a/apps/dsp-app/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts +++ b/apps/dsp-app/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts @@ -102,6 +102,10 @@ export class ResourceInstanceFormComponent implements OnInit, OnChanges { ) {} ngOnInit(): void { + if (!this.resourceClassIri) { + return; + } + // get ontology iri from res class iri const splitIri = this.resourceClassIri.split('#'); this.ontologyIri = splitIri[0]; diff --git a/apps/dsp-app/src/app/workspace/results/list-view/list-view.component.ts b/apps/dsp-app/src/app/workspace/results/list-view/list-view.component.ts index 9bd467f46e..23a4ae5dd2 100644 --- a/apps/dsp-app/src/app/workspace/results/list-view/list-view.component.ts +++ b/apps/dsp-app/src/app/workspace/results/list-view/list-view.component.ts @@ -87,6 +87,7 @@ export class ListViewComponent implements OnChanges, OnInit, OnDestroy { private ngUnsubscribe: Subject = new Subject(); @Input() search: SearchParams; + currentSearch: SearchParams; /** * set to true if multiple resources can be selected for comparison @@ -148,6 +149,16 @@ export class ListViewComponent implements OnChanges, OnInit, OnDestroy { } ngOnChanges(): void { + if (this.isCurrentSearch()) { + this.currentSearch = this.search; + this.initSearch(); + } + } + + isCurrentSearch = (): boolean => + this.search.query !== this.currentSearch?.query || this.currentSearch.query === undefined; + + initSearch(): void { // reset this.currentIndex = 0; this.currentRangeStart = 1;