diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d1770a0c29..9cd43f04ea 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -80,6 +80,11 @@ const routes: Routes = [ component: OntologyComponent, canActivate: [AuthGuard] }, + { + path: 'ontologies/:id/:view', + component: OntologyComponent, + canActivate: [AuthGuard] + }, { path: 'lists', component: ListComponent, diff --git a/src/app/project/ontology/ontology.component.html b/src/app/project/ontology/ontology.component.html index f9edc6c85a..08e887478f 100644 --- a/src/app/project/ontology/ontology.component.html +++ b/src/app/project/ontology/ontology.component.html @@ -1,8 +1,5 @@
-

- This is a first version of the data model editor. Some features may not work as intended. -

@@ -59,25 +56,34 @@

-

{{ontology?.label}}

- - -

Last modification date

-

{{ontology?.label}}

+

- {{ontology.lastModificationDate | date:'medium'}}

+ + Updated on: {{ontology.lastModificationDate | date:'medium'}} + + + + + This data model can't be edited because of missing "lastModificationDate"! + + +

+

Display as

- - @@ -87,47 +93,64 @@

{{ontology?.label}}

Data model configuration

- + - + + + -
+ + + + Classes + + + + + + + + Properties + + -
+ -
- - - - -
-
+ -
-
+ +
{{ontology?.label}}

{{resClass.label | dspTruncate: 24}}

- + + + - - - @@ -162,9 +183,9 @@

- @@ -174,8 +195,19 @@

-
-
+
+ + + + + + + + +
+ +
diff --git a/src/app/project/ontology/ontology.component.scss b/src/app/project/ontology/ontology.component.scss index fe740f0cd6..b385f4c099 100644 --- a/src/app/project/ontology/ontology.component.scss +++ b/src/app/project/ontology/ontology.component.scss @@ -27,7 +27,6 @@ $width: 340px; max-width: 1280px; min-height: calc(100vh - #{$header-height} - #{$tab-bar-margin} - 100px); border: 1px dotted $primary_200; - background: $primary_200; position: relative; margin: 0 auto 60px auto; } @@ -45,8 +44,18 @@ $width: 340px; height: $sub-header-height; } + .ontology-editor-sidenav { + width: 160px; + + button { + width: 100%; + text-align: left; + } + } + .ontology-editor-canvas { min-height: calc(100vh - #{$sub-header-height} - #{$header-height} - #{$tab-bar-margin} - 100px); + border-right: 1px solid rgba(0, 0, 0, 0.12); .ontology-editor-grid { display: grid; @@ -54,6 +63,9 @@ $width: 340px; grid-template-columns: repeat(auto-fill, minmax($width, 1fr)); grid-gap: 6px; } + .ontology-editor-list { + margin: 16px; + } } } @@ -68,7 +80,7 @@ $width: 340px; background-color: #fff; .resource-class-header { - margin: -12px -12px 12px -12px; + margin: -12px -12px 0 -12px; width: calc(100% + 24px); min-height: 48px !important; height: 48px; @@ -98,47 +110,15 @@ $width: 340px; .switch-view-label { margin-right: 16px; } - - button.active { - background-color: $black-12-opacity; - } } -/* -.ontology-container { - // display: flex; - // flex-direction: row; - width: 100%; - - .ontology-editor { - overflow: hidden; - } - - .ontology-default-elements { - width: 220px; - // TODO: fix height - height: 100vh; - position: fixed; - right: 0; - top: 0; - padding: 72px 6px 6px; - background: rgb(255, 255, 255); - - .list-item { - border: 1px solid black; - padding: 6px; - margin: 6px; - } - ul { - padding-inline-start: 0; +.active { + background-color: $black-12-opacity; +} - li { - list-style-type: none; - } - } - } +.note { + padding: 2px; } - */ .add-resource-class { position: absolute; @@ -160,6 +140,5 @@ $width: 340px; } button.space-reducer { - // max-height: 32px; margin: 0; } diff --git a/src/app/project/ontology/ontology.component.ts b/src/app/project/ontology/ontology.component.ts index 92241996cd..11abd9f27e 100644 --- a/src/app/project/ontology/ontology.component.ts +++ b/src/app/project/ontology/ontology.component.ts @@ -15,12 +15,13 @@ import { OntologiesMetadata, OntologyMetadata, ProjectResponse, + PropertyDefinition, ReadOntology, ReadProject, ResourceClassDefinition, UpdateOntology } from '@dasch-swiss/dsp-js'; -import { DspApiConnectionToken, Session, SessionService } from '@dasch-swiss/dsp-ui'; +import { DspApiConnectionToken, Session, SessionService, SortingService } from '@dasch-swiss/dsp-ui'; import { CacheService } from 'src/app/main/cache/cache.service'; import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorHandlerService } from 'src/app/main/error/error-handler.service'; @@ -67,6 +68,8 @@ export class OntologyComponent implements OnInit { ontoClasses: ClassDefinition[]; + ontoProperties: PropertyDefinition[]; + // selected ontology id ontologyIri: string = undefined; @@ -74,7 +77,7 @@ export class OntologyComponent implements OnInit { ontologyForm: FormGroup; // display resource classes as grid or as graph - view: 'grid' | 'graph' = 'grid'; + view: 'classes' | 'properties' | 'graph' = 'classes'; // i18n setup itemPluralMapping = { @@ -105,6 +108,7 @@ export class OntologyComponent implements OnInit { private _route: ActivatedRoute, private _router: Router, private _session: SessionService, + private _sortingService: SortingService, private _titleService: Title ) { @@ -113,10 +117,19 @@ export class OntologyComponent implements OnInit { this.projectcode = params.get('shortcode'); }); - // get ontology iri from route - if (this._route.snapshot && this._route.snapshot.params.id) { - this.ontologyIri = decodeURIComponent(this._route.snapshot.params.id); - // set the page title in case of only one project ontology + if (this._route.snapshot) { + // get ontology iri from route + if (this._route.snapshot.params.id) { + this.ontologyIri = decodeURIComponent(this._route.snapshot.params.id); + } + // get view from route: classes, properties or graph + this.view = (this._route.snapshot.params.view ? this._route.snapshot.params.view : 'classes'); + } + + // + + // set the page title + if (this.ontologyIri) { this._titleService.setTitle('Project ' + this.projectcode + ' | Data model'); } else { // set the page title in case of more than one existing project ontologies @@ -217,7 +230,7 @@ export class OntologyComponent implements OnInit { // because there will be no form to select ontlogy if (response.ontologies.length === 1) { // open this ontology - this.openOntologyRoute(response.ontologies[0].id); + this.openOntologyRoute(response.ontologies[0].id, this.view); this.ontologyIri = response.ontologies[0].id; } loadAndCache(); @@ -246,10 +259,14 @@ export class OntologyComponent implements OnInit { } - // open ontology route by iri - openOntologyRoute(id: string) { - this.loadOntology = true; - const goto = 'project/' + this.projectcode + '/ontologies/' + encodeURIComponent(id); + /** + * Opens ontology route by iri + * @param id ontology id/iri + * @param view 'classes' | 'properties' | ' graph' + */ + openOntologyRoute(id: string, view: 'classes' | 'properties' | 'graph' = 'classes') { + this.view = view; + const goto = 'project/' + this.projectcode + '/ontologies/' + encodeURIComponent(id) + '/' + view; this._router.navigateByUrl(goto, { skipLocationChange: false }); } @@ -274,10 +291,24 @@ export class OntologyComponent implements OnInit { for (const c of classKeys) { const splittedSubClass = this.ontology.classes[c].subClassOf[0].split('#'); - if (splittedSubClass[0] !== Constants.StandoffOntology && splittedSubClass[1] !== 'StandoffTag' && splittedSubClass[1] !== 'StandoffLinkTag') { + if (splittedSubClass[0] !== Constants.StandoffOntology && splittedSubClass[1] !== 'StandoffTag' && splittedSubClass[1] !== 'StandoffLinkTag' && splittedSubClass[1] !== 'StandoffEventTag') { this.ontoClasses.push(this.ontology.classes[c]); } } + this.ontoClasses = this._sortingService.keySortByAlphabetical(this.ontoClasses, 'label'); + + // grab the onto properties information to display + this.ontoProperties = []; + const propKeys: string[] = Object.keys(response.properties); + // create list of resource classes without standoff classes + for (const p of propKeys) { + const standoff = (this.ontology.properties[p].subjectType ? this.ontology.properties[p].subjectType.includes('Standoff') : false); + if (this.ontology.properties[p].objectType !== Constants.LinkValue && !standoff) { + this.ontoProperties.push(this.ontology.properties[p]); + } + } + + this.ontoProperties = this._sortingService.keySortByAlphabetical(this.ontoProperties, 'label'); this.loadOntology = false; } @@ -294,7 +325,7 @@ export class OntologyComponent implements OnInit { this.ontology = undefined; this.ontoClasses = []; - this.openOntologyRoute(id); + this.openOntologyRoute(id, this.view); this.getOntology(id); } @@ -478,14 +509,6 @@ export class OntologyComponent implements OnInit { }); } - /** - * - * @param view 'grid' | ' graph' - */ - toggleView(view: 'grid' | 'graph') { - this.view = view; - } - setCache() { // set cache for current ontology this._cache.set('currentOntology', this.ontology); diff --git a/src/app/project/ontology/property-info/property-info.component.html b/src/app/project/ontology/property-info/property-info.component.html index c5d1168f6a..a26e046820 100644 --- a/src/app/project/ontology/property-info/property-info.component.html +++ b/src/app/project/ontology/property-info/property-info.component.html @@ -1,5 +1,5 @@ - {{propCard.guiOrder}}) + {{propCard.guiOrder}})
@@ -17,7 +17,25 @@
- {{propInfo.multiple ? 'check_box' : 'check_box_outline_blank' }} multiple - {{propInfo.required ? 'check_box' : 'check_box_outline_blank' }} required + + + {{propInfo.multiple ? 'check_box' : 'check_box_outline_blank' }} multiple + {{propInfo.required ? 'check_box' : 'check_box_outline_blank' }} required + + + + + Property is used in: + + + {{c.label}} + + + + + This property is not used in a class + + +
diff --git a/src/app/project/ontology/property-info/property-info.component.scss b/src/app/project/ontology/property-info/property-info.component.scss index 29793d34ca..160e1defdd 100644 --- a/src/app/project/ontology/property-info/property-info.component.scss +++ b/src/app/project/ontology/property-info/property-info.component.scss @@ -3,6 +3,10 @@ .mat-list-item { height: 56px !important; + margin: 4px 0; + &:hover { + background-color: $black-12-opacity; + } } .additional-info { @@ -35,3 +39,7 @@ position: relative; } } + +.not-used { + color: $warn; +} diff --git a/src/app/project/ontology/property-info/property-info.component.ts b/src/app/project/ontology/property-info/property-info.component.ts index 7a7f2479b4..3af7aac457 100644 --- a/src/app/project/ontology/property-info/property-info.component.ts +++ b/src/app/project/ontology/property-info/property-info.component.ts @@ -2,12 +2,11 @@ import { AfterContentInit, Component, Input, OnInit } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { - ApiResponseData, Constants, IHasProperty, ListNodeInfo, - ListsResponse, ReadOntology, + ResourceClassDefinitionWithAllLanguages, ResourcePropertyDefinitionWithAllLanguages } from '@dasch-swiss/dsp-js'; import { CacheService } from 'src/app/main/cache/cache.service'; @@ -23,7 +22,7 @@ export class PropertyInfoComponent implements OnInit, AfterContentInit { @Input() propDef: ResourcePropertyDefinitionWithAllLanguages; - @Input() propCard: IHasProperty; + @Input() propCard?: IHasProperty; @Input() projectcode: string; @@ -36,6 +35,9 @@ export class PropertyInfoComponent implements OnInit, AfterContentInit { propAttribute: string; + // list of resource classes where the property is used + resClasses: ResourceClassDefinitionWithAllLanguages[] = []; + constructor( private _cache: CacheService, private _domSanitizer: DomSanitizer, @@ -55,23 +57,26 @@ export class PropertyInfoComponent implements OnInit, AfterContentInit { ngOnInit(): void { // convert cardinality from js-lib convention to app convention - switch (this.propCard.cardinality) { - case 0: - this.propInfo.multiple = false; - this.propInfo.required = true; - break; - case 1: - this.propInfo.multiple = false; - this.propInfo.required = false; - break; - case 2: - this.propInfo.multiple = true; - this.propInfo.required = false; - break; - case 3: - this.propInfo.multiple = true; - this.propInfo.required = true; - break; + // if cardinality is defined; only in resource class view + if (this.propCard) { + switch (this.propCard.cardinality) { + case 0: + this.propInfo.multiple = false; + this.propInfo.required = true; + break; + case 1: + this.propInfo.multiple = false; + this.propInfo.required = false; + break; + case 2: + this.propInfo.multiple = true; + this.propInfo.required = false; + break; + case 3: + this.propInfo.multiple = true; + this.propInfo.required = true; + break; + } } // find gui ele from list of default property-types to set type value @@ -130,6 +135,28 @@ export class PropertyInfoComponent implements OnInit, AfterContentInit { ); } + // get all classes where the property is used + if (!this.propCard) { + this._cache.get('currentOntology').subscribe( + (ontology: ReadOntology) => { + const classes = ontology.getAllClassDefinitions(); + for (const c of classes) { + if (c.propertiesList.find(i => i.propertyIndex === this.propDef.id)) { + this.resClasses.push(c as ResourceClassDefinitionWithAllLanguages); + } + // const splittedSubClass = ontology.classes[c].subClassOf[0].split('#'); + + // if (splittedSubClass[0] !== Constants.StandoffOntology && splittedSubClass[1] !== 'StandoffTag' && splittedSubClass[1] !== 'StandoffLinkTag') { + // this.ontoClasses.push(this.ontology.classes[c]); + // } + } + } + ); + + + + } + } }