diff --git a/src/app/project/ontology/ontology.component.html b/src/app/project/ontology/ontology.component.html index 1fd8a8358d..5e8662e9a9 100644 --- a/src/app/project/ontology/ontology.component.html +++ b/src/app/project/ontology/ontology.component.html @@ -179,29 +179,23 @@

-
- - - + + + - + diff --git a/src/app/project/ontology/ontology.component.ts b/src/app/project/ontology/ontology.component.ts index e3fb9f99aa..78ef38f7f5 100644 --- a/src/app/project/ontology/ontology.component.ts +++ b/src/app/project/ontology/ontology.component.ts @@ -41,6 +41,11 @@ export interface CardinalityInfo { property: PropertyInfoObject; } +export interface OntologyProperties { + ontology: string; + properties: PropertyDefinition[]; +} + @Component({ selector: 'app-ontology', templateUrl: './ontology.component.html', @@ -92,7 +97,7 @@ export class OntologyComponent implements OnInit { expandClasses = true; // all properties in the current ontology - ontoProperties: PropertyDefinition[]; + ontoProperties: OntologyProperties; // form to select ontology from list ontologyForm: FormGroup; @@ -239,6 +244,7 @@ export class OntologyComponent implements OnInit { } if (response.ontologies.length === this.ontologies.length) { this.ontologies = this._sortingService.keySortByAlphabetical(this.ontologies, 'label'); + this._cache.set('currentProjectOntologies', this.ontologies); this.setCache(); } @@ -289,19 +295,24 @@ export class OntologyComponent implements OnInit { } initOntoProperties(allOntoProperties: PropertyDefinition[]) { + // reset the ontology properties - this.ontoProperties = []; + const listOfProperties = []; // display only the properties which are not a subjectType of Standoff allOntoProperties.forEach(resProp => { const standoff = (resProp.subjectType ? resProp.subjectType.includes('Standoff') : false); if (resProp.objectType !== Constants.LinkValue && !standoff) { - this.ontoProperties.push(resProp); + listOfProperties.push(resProp); } }); + // sort properties by label - // --> TODO: add sort functionallity to the gui - this.ontoProperties = this._sortingService.keySortByAlphabetical(this.ontoProperties, 'label'); + this.ontoProperties = { + ontology: this.ontology.id, + properties: this._sortingService.keySortByAlphabetical(listOfProperties, 'label') + }; + } /** @@ -341,7 +352,15 @@ export class OntologyComponent implements OnInit { resetOntologyView(ontology: ReadOntology) { this.ontology = ontology; this.lastModificationDate = this.ontology.lastModificationDate; - this._cache.set('currentOntology', this.ontology); + this._cache.set('currentOntology', ontology); + + this._cache.get('currentProjectOntologies').subscribe( + (ontologies: ReadOntology[]) => { + // update current list of project ontologies + ontologies[ontologies.findIndex(onto => onto.id === ontology.id)] = ontology; + this._cache.set('currentProjectOntologies', ontologies); + } + ); // grab the onto class information to display this.initOntoClasses(ontology.getAllClassDefinitions()); @@ -457,6 +476,8 @@ export class OntologyComponent implements OnInit { ); dialogRef.afterClosed().subscribe(result => { + // get the ontologies for this project + this.initOntologiesList(); // update the view of resource class or list of properties this.initOntology(this.ontologyIri); }); diff --git a/src/app/project/ontology/property-form/property-form.component.ts b/src/app/project/ontology/property-form/property-form.component.ts index 6296a9f04a..2f6f37562d 100644 --- a/src/app/project/ontology/property-form/property-form.component.ts +++ b/src/app/project/ontology/property-form/property-form.component.ts @@ -330,7 +330,6 @@ export class PropertyFormComponent implements OnInit { // reset value of guiAttr this.propertyForm.controls['guiAttr'].setValue(undefined); - // set gui attribute value depending on gui element and existing property (edit mode) if (this.propertyInfo.propDef) { // the gui attribute can't be changed (at the moment?); @@ -562,7 +561,6 @@ export class PropertyFormComponent implements OnInit { this._dspApiConnection.v2.onto.addCardinalityToResourceClass(onto).subscribe( (res: ResourceClassDefinitionWithAllLanguages) => { - this.lastModificationDate = res.lastModificationDate; // close the dialog box this.loading = false; 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 2d7265bfe7..76cd3ccdc3 100644 --- a/src/app/project/ontology/property-info/property-info.component.html +++ b/src/app/project/ontology/property-info/property-info.component.html @@ -1,4 +1,4 @@ -
+
{{propDef.id | split: '#':1}} -  ·  + {{propInfo.multiple ? 'check_box' : 'check_box_outline_blank' }} @@ -34,16 +34,16 @@ - Property is used in: - + > Property is used in: - {{c.label}} - + {{c.label}} · - This property is not used in a class + > + This property is not used in a class. +
@@ -77,8 +77,8 @@ - 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 9af196f37e..2054ba331b 100644 --- a/src/app/project/ontology/property-info/property-info.component.scss +++ b/src/app/project/ontology/property-info/property-info.component.scss @@ -4,6 +4,10 @@ width: 100%; } +.property-info.standalone { + padding: 16px; +} + .additional-info { color: $primary_700; } @@ -44,6 +48,7 @@ .not-used { color: $warn; + font-size: 12px; } .type { 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 f640bc83c8..cd1278464c 100644 --- a/src/app/project/ontology/property-info/property-info.component.ts +++ b/src/app/project/ontology/property-info/property-info.component.ts @@ -55,6 +55,12 @@ export class Property { } } +export interface ShortInfo { + id: string; + label: string; + comment: string; +} + @Component({ selector: 'app-property-info', templateUrl: './property-info.component.html', @@ -103,8 +109,8 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit { @Output() deleteResourceProperty: EventEmitter = new EventEmitter(); @Output() removePropertyFromClass: EventEmitter = new EventEmitter(); - // submit res class iri ot open res class - @Output() clickedOnClass: EventEmitter = new EventEmitter(); + // submit res class iri to open res class (not yet implemented) + @Output() clickedOnClass: EventEmitter = new EventEmitter(); propInfo: Property = new Property(); @@ -123,7 +129,7 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit { defaultProperties: PropertyCategory[] = DefaultProperties.data; // list of resource classes where the property is used - resClasses: ResourceClassDefinitionWithAllLanguages[] = []; + resClasses: ShortInfo[] = []; // disable edit property button in case the property type is not supported in DSP-APP disableEditProperty = false; @@ -140,7 +146,6 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit { this._cache.get('currentOntology').subscribe( (response: ReadOntology) => { this.ontology = response; - } ); } @@ -180,13 +185,11 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit { } // get the default property type for this property - // if (this.propDef.guiElement) { this._ontoService.getDefaultPropType(this.propDef).subscribe( (prop: DefaultProperty) => { this.propType = prop; } ); - // } } @@ -234,19 +237,25 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit { // get all classes where the property is used if (!this.propCard) { - - const classes = this.ontology.getAllClassDefinitions(); - for (const c of classes) { - if (c.propertiesList.find(i => i.propertyIndex === this.propDef.id)) { - this.resClasses.push(c as ResourceClassDefinitionWithAllLanguages); + this.resClasses = []; + this._cache.get('currentProjectOntologies').subscribe( + (ontologies: ReadOntology[]) => { + ontologies.forEach(onto => { + const classes = onto.getAllClassDefinitions(); + classes.forEach(resClass => { + if (resClass.propertiesList.find(prop => prop.propertyIndex === this.propDef.id)) { + // build own resClass object with id, label and comment + const propOfClass: ShortInfo = { + id: resClass.id, + label: resClass.label, + comment: onto.label + (resClass.comment ? ': ' + resClass.comment : '') + }; + this.resClasses.push(propOfClass); + } + }); + }); } - // 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]); - // } - } - + ); } } @@ -290,10 +299,10 @@ export class PropertyInfoComponent implements OnChanges, AfterContentInit { this._dspApiConnection.v2.onto.canDeleteCardinalityFromResourceClass(onto).subscribe( (canDoRes: CanDoResponse) => { this.propCanBeDeleted = canDoRes.canDo; - }, - (error: ApiResponseError) => { - this._errorHandler.showMessage(error); } + // since this request runs on mouseover, it can always + // ends in a EditConflictException because of a wrong lastModificationDate. + // so, it doesn't make sense to handle the error here and to open the snackbar ); } diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.html b/src/app/project/ontology/resource-class-info/resource-class-info.component.html index 39dc1827cd..1d542a4e0c 100644 --- a/src/app/project/ontology/resource-class-info/resource-class-info.component.html +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.html @@ -3,7 +3,7 @@ - + {{resourceClass.label | appTruncate: 24}} @@ -43,13 +43,14 @@ + -
- + {{i + 1}}) - @@ -83,13 +88,12 @@ - + - + + + + + + + diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts b/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts index 15576ca604..fca966a943 100644 --- a/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.spec.ts @@ -14,7 +14,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { CanDoResponse, ClassDefinition, Constants, MockOntology, OntologiesEndpointV2, ReadOntology } from '@dasch-swiss/dsp-js'; +import { CanDoResponse, ClassDefinition, Constants, MockOntology, OntologiesEndpointV2, OntologiesMetadata, OntologyMetadata, ReadOntology } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { CacheService } from 'src/app/main/cache/cache.service'; import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens'; @@ -125,15 +125,25 @@ describe('ResourceClassInfoComponent', () => { beforeEach(() => { // mock cache service for currentOntology - const cacheSpy = TestBed.inject(CacheService); - - (cacheSpy as jasmine.SpyObj).get.and.callFake( + const cacheSpyOnto = TestBed.inject(CacheService); + (cacheSpyOnto as jasmine.SpyObj).get.withArgs('currentOntology').and.callFake ( () => { const response: ReadOntology = MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2'); return of(response); } ); + const cacheSpyProjOnto = TestBed.inject(CacheService); + (cacheSpyProjOnto as jasmine.SpyObj).get.withArgs('currentProjectOntologies').and.callFake ( + () => { + const ontologies: ReadOntology[] = []; + ontologies.push(MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/anything/v2')); + ontologies.push(MockOntology.mockReadOntology('http://0.0.0.0:3333/ontology/0001/minimal/v2')); + const response: ReadOntology[] = ontologies; + return of(response); + } + ); + const dspConnSpy = TestBed.inject(DspApiConnectionToken); (dspConnSpy.v2.onto as jasmine.SpyObj).canDeleteResourceClass.and.callFake( diff --git a/src/app/project/ontology/resource-class-info/resource-class-info.component.ts b/src/app/project/ontology/resource-class-info/resource-class-info.component.ts index 2ca5cb5db1..b901ce92e6 100644 --- a/src/app/project/ontology/resource-class-info/resource-class-info.component.ts +++ b/src/app/project/ontology/resource-class-info/resource-class-info.component.ts @@ -20,11 +20,22 @@ import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens' import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorHandlerService } from 'src/app/main/error/error-handler.service'; import { NotificationService } from 'src/app/main/services/notification.service'; +import { SortingService } from 'src/app/main/services/sorting.service'; import { DefaultProperties, DefaultProperty, PropertyCategory, PropertyInfoObject } from '../default-data/default-properties'; import { DefaultClass, DefaultResourceClasses } from '../default-data/default-resource-classes'; -import { CardinalityInfo } from '../ontology.component'; +import { CardinalityInfo, OntologyProperties } from '../ontology.component'; import { OntologyService } from '../ontology.service'; +export interface PropToDisplay extends IHasProperty { + propDef?: PropertyDefinition; +} + +export interface PropToAdd { + ontologyId: string; + ontologyLabel: string; + properties: PropertyInfoObject[]; +} + @Component({ selector: 'app-resource-class-info', templateUrl: './resource-class-info.component.html', @@ -41,7 +52,7 @@ export class ResourceClassInfoComponent implements OnInit { @Input() projectStatus: boolean; - @Input() ontoProperties: ResourcePropertyDefinitionWithAllLanguages[] = []; + @Input() ontologies: ReadOntology[] = []; @Input() lastModificationDate?: string; @@ -58,9 +69,11 @@ export class ResourceClassInfoComponent implements OnInit { // to update the cardinality we need the information about property (incl. propType) and resource class @Output() updateCardinality: EventEmitter = new EventEmitter(); - ontology: ReadOntology; + // list of all ontologies with their properties + ontoProperties: OntologyProperties[] = []; + cardinalityUpdateEnabled: boolean; classCanBeDeleted: boolean; @@ -68,7 +81,7 @@ export class ResourceClassInfoComponent implements OnInit { classCanReplaceCardinality: boolean; // list of properties that can be displayed (not all of the props should be displayed) - propsToDisplay: IHasProperty[] = []; + propsToDisplay: PropToDisplay[] = []; subClassOfLabel = ''; @@ -79,7 +92,10 @@ export class ResourceClassInfoComponent implements OnInit { defaultProperties: PropertyCategory[] = DefaultProperties.data; // list of existing ontology properties, which are not in this resource class - existingProperties: PropertyInfoObject[]; + existingProperties: PropToAdd[]; + + // load single property (in case of property cardinality action) + loadProperty = false; constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, @@ -87,11 +103,28 @@ export class ResourceClassInfoComponent implements OnInit { private _dialog: MatDialog, private _errorHandler: ErrorHandlerService, private _notification: NotificationService, - private _ontoService: OntologyService + private _ontoService: OntologyService, + private _sortingService: SortingService ) { } ngOnInit(): void { + // grab the onto properties information to display + this.ontoProperties = []; + // get all project ontologies + this._cache.get('currentProjectOntologies').subscribe( + (ontologies: ReadOntology[]) => { + this.ontologies = ontologies; + ontologies.forEach(onto => { + const prepareList: OntologyProperties = { + ontology: onto.id, + properties: this.initOntoProperties(onto.getAllPropertyDefinitions()) + }; + this.ontoProperties.push(prepareList); + }); + } + ); + // get currently selected ontology this._cache.get('currentOntology').subscribe( (response: ReadOntology) => { this.ontology = response; @@ -163,6 +196,22 @@ export class ResourceClassInfoComponent implements OnInit { } + initOntoProperties(allOntoProperties: PropertyDefinition[]): PropertyDefinition[] { + // reset the ontology properties + const listOfProperties = []; + + // display only the properties which are not a subjectType of Standoff + allOntoProperties.forEach(resProp => { + const standoff = (resProp.subjectType ? resProp.subjectType.includes('Standoff') : false); + if (resProp.objectType !== Constants.LinkValue && !standoff) { + listOfProperties.push(resProp); + } + }); + // sort properties by label + // --> TODO: add sort functionallity to the gui + return this._sortingService.keySortByAlphabetical(listOfProperties, 'label'); + } + /** * prepares props to display * Not all props should be displayed; there are some system / API-specific @@ -170,55 +219,75 @@ export class ResourceClassInfoComponent implements OnInit { * * @param props */ - preparePropsToDisplay(classProps: IHasProperty[]) { + preparePropsToDisplay(classProps: PropToDisplay[]) { - const ontoProps = this.ontology.getAllPropertyDefinitions(); - - // reset properties to display - this.propsToDisplay = []; // reset existing properties to select from this.existingProperties = []; - classProps.forEach((hasProp: IHasProperty) => { + classProps.forEach((hasProp: PropToDisplay) => { + + const ontoIri = hasProp.propertyIndex.split(Constants.HashDelimiter)[0]; + // ignore http://api.knora.org/ontology/knora-api/v2 and ignore http://www.w3.org/2000/01/rdf-schema + if (ontoIri !== Constants.KnoraApiV2 && ontoIri !== Constants.Rdfs) { + // get property definition from list of project ontologies + const index = this.ontoProperties.findIndex((item: OntologyProperties) => item.ontology === ontoIri); + hasProp.propDef = this.ontoProperties[index].properties.find((obj: ResourcePropertyDefinitionWithAllLanguages) => + obj.id === hasProp.propertyIndex && + ((obj.subjectType && !obj.subjectType.includes('Standoff')) && obj.objectType !== Constants.LinkValue || !obj.isLinkValueProperty) + ); + + // propDef was found, add hasProp to the properties list which has to be displayed in this resource class + if (hasProp.propDef) { + if (this.propsToDisplay.indexOf(hasProp) === -1) { + this.propsToDisplay.push(hasProp); + } + + // and remove from list of existing properties to avoid double cardinality entries + // because the prop displayed in the class cannot be added a second time, + // so we have to hide it from the list of "Add existing property" + const delProp = this.ontoProperties[index].properties.indexOf(hasProp.propDef, 0); + if (delProp > -1) { + this.ontoProperties[index].properties.splice(delProp, 1); + } + } + } + }); - const propToDisplay = ontoProps.find(obj => - obj.id === hasProp.propertyIndex && - ((obj.subjectType && !obj.subjectType.includes('Standoff')) && obj.objectType !== Constants.LinkValue || !obj.isLinkValueProperty) - ); + this.ontoProperties.forEach((op: OntologyProperties, i: number) => { - if (propToDisplay) { - // add to list of properties to display in res class - this.propsToDisplay.push(hasProp); - // and remove from list of existing properties which can be added - this.ontoProperties = this.ontoProperties.filter(prop => !(prop.id === propToDisplay.id)); - } + this.existingProperties.push({ + ontologyId: op.ontology, + ontologyLabel: this.ontologies[i].label, + properties: [] + }); - }); + op.properties.forEach((availableProp: ResourcePropertyDefinitionWithAllLanguages) => { - this.ontoProperties.forEach((availableProp: ResourcePropertyDefinitionWithAllLanguages) => { - const superProp = this._ontoService.getSuperProperty(availableProp); - if (superProp) { - if (availableProp.subPropertyOf.indexOf(superProp) === -1) { - availableProp.subPropertyOf.push(superProp); + const superProp = this._ontoService.getSuperProperty(availableProp); + if (superProp) { + if (availableProp.subPropertyOf.indexOf(superProp) === -1) { + availableProp.subPropertyOf.push(superProp); + } } - } - let propType: DefaultProperty; - // find corresponding default property to have more prop info - this._ontoService.getDefaultPropType(availableProp).subscribe( - (prop: DefaultProperty) => { - propType = prop; - } - ); + let propType: DefaultProperty; + // find corresponding default property to have more prop info + this._ontoService.getDefaultPropType(availableProp).subscribe( + (prop: DefaultProperty) => { + propType = prop; + } + ); - this.existingProperties.push( - { + const propToAdd: PropertyInfoObject = { propType: propType, propDef: availableProp - } - ); - }); + }; + + this.existingProperties[i].properties.push(propToAdd); + + }); + }); } canBeDeleted() { @@ -255,21 +324,14 @@ export class ResourceClassInfoComponent implements OnInit { this.updateCard(cardinality); } - addExistingProperty(propDef: ResourcePropertyDefinitionWithAllLanguages) { - - // find prop type from list of existing properties - const pos = this.existingProperties.findIndex(item => item.propDef.id === propDef.id); - - const propType = this.existingProperties[pos].propType; - + addExistingProperty(prop: PropertyInfoObject) { const cardinality: CardinalityInfo = { resClass: this.resourceClass, property: { - propType: propType, - propDef: propDef, + propType: prop.propType, + propDef: prop.propDef, } }; - this.updateCard(cardinality); } @@ -279,6 +341,8 @@ export class ResourceClassInfoComponent implements OnInit { */ removeProperty(property: DefaultClass) { + this.loadProperty = true; + const onto = new UpdateOntology(); onto.lastModificationDate = this.lastModificationDate; @@ -291,9 +355,7 @@ export class ResourceClassInfoComponent implements OnInit { delCard.cardinalities = []; - this.propsToDisplay = this.propsToDisplay.filter(prop => (prop.propertyIndex === property.iri)); - - delCard.cardinalities = this.propsToDisplay; + delCard.cardinalities = this.propsToDisplay.filter(prop => (prop.propertyIndex === property.iri)); onto.entity = delCard; this._dspApiConnection.v2.onto.deleteCardinalityFromResourceClass(onto).subscribe( @@ -306,6 +368,8 @@ export class ResourceClassInfoComponent implements OnInit { this.updateCardinality.emit(this.ontology.id); // display success message this._notification.openSnackBar(`You have successfully removed "${property.label}" from "${this.resourceClass.label}".`); + + this.loadProperty = false; }, (error: ApiResponseError) => { this._errorHandler.showMessage(error); @@ -407,8 +471,6 @@ export class ResourceClassInfoComponent implements OnInit { this.lastModificationDate = responseGuiOrder.lastModificationDate; // successful request: update the view - this.preparePropsToDisplay(this.propsToDisplay); - this.lastModificationDateChange.emit(this.lastModificationDate); // display success message diff --git a/src/assets/style/_elements.scss b/src/assets/style/_elements.scss index 1d2e58c35f..8cef300ffd 100644 --- a/src/assets/style/_elements.scss +++ b/src/assets/style/_elements.scss @@ -424,6 +424,17 @@ a, } } +.mat-list.without-padding { + + .mat-list-item { + padding: 0 !important; + + .mat-list-item-content { + padding: 0 !important; + } + } +} + // -------------------------------------- //