Skip to content
Permalink
Browse files
feat(ontology): add properties to a class from other ontologies (DEV-689
 / DEV-880) (#721)

* feat(ontology): add properties to a class from other ontologies (DEV-689)

* refactor(ontology): clean up commented code

* fix(ontology): resolve prop cardinality and cache issues

* refactor(ontology): clean up code and styling

* fix(ontology): disable drag and drop in case of subclass (DEV-880)

* fix(ontology): display classes where a prop is used

* refactor(ontology): remove link class on res class because of missing link
  • Loading branch information
kilchenmann committed May 4, 2022
1 parent 1b7384f commit b90fc70912b6d5e1f10a00f0b01ea61567128bd6
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 127 deletions.
@@ -179,29 +179,23 @@ <h2 class="mat-title">
<mat-sidenav-content class="ontology-editor-canvas drag-drop-stop">
<div class="ontology-editor-grid classes" *ngIf="view === 'classes'">
<!-- list of resource classes -->
<app-resource-class-info *ngFor="let resClass of ontoClasses"
[resourceClass]="resClass"
[projectCode]="projectCode"
[projectStatus]="project.status"
[ontoProperties]="ontoProperties"
[expanded]="expandClasses"
[(lastModificationDate)]="lastModificationDate"
<app-resource-class-info *ngFor="let resClass of ontoClasses" [resourceClass]="resClass"
[projectCode]="projectCode" [projectStatus]="project.status" [ontologies]="ontologies"
[expanded]="expandClasses" [(lastModificationDate)]="lastModificationDate"
(editResourceClass)="openResourceClassForm('editResourceClass', $event)"
(deleteResourceClass)="delete('ResourceClass', $event)"
(updateCardinality)="initOntology($event)">
</app-resource-class-info>
</div>
<div class="ontology-editor-list properties" *ngIf="view === 'properties'">
<!-- list of resource properties -->
<mat-list>
<mat-list-item class="property" *ngFor="let prop of ontoProperties; let odd = odd" [class.odd]="odd">
<!-- list of properties of current ontology -->
<mat-list class="without-padding">
<mat-list-item class="property" *ngFor="let prop of ontoProperties.properties; let odd = odd"
[class.odd]="odd">
<!-- display only properties with guiOrder and if they exist in list of properties;
objectType is not a linkValue (otherwise we have the property twice) -->
<app-property-info
[propDef]="ontology?.properties[prop.id]"
[projectCode]="projectCode"
[projectStatus]="project.status"
[(lastModificationDate)]="lastModificationDate"
if objectType is a linkValue hide it (otherwise we have the property twice) -->
<app-property-info [propDef]="ontology?.properties[prop.id]" [projectCode]="projectCode"
[projectStatus]="project.status" [(lastModificationDate)]="lastModificationDate"
(editResourceProperty)="openPropertyForm('editProperty', $event)"
(deleteResourceProperty)="delete('Property', $event)">
</app-property-info>
@@ -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);
});
@@ -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;
@@ -1,4 +1,4 @@
<div class="property-info" (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">
<div class="property-info" [class.standalone]="!propCard" (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">

<div mat-line class="title" [class.with-line-break]="!propCard">
<span class="icon" *ngIf="propType" [matTooltip]="propType?.group + ': ' + propType?.label + ' (' + propDef.id.split('#')[1] + ')'"
@@ -23,7 +23,7 @@
<span [matTooltip]="'id: ' + propDef.id" matTooltipPosition="above" matTooltipClass="wide-tooltip" class="mat-caption">
{{propDef.id | split: '#':1}}
</span>
<span class="fill-remaining-space center">&nbsp;&middot;&nbsp;</span>
<span class="fill-remaining-space center"></span>
<!-- in case of res class: display cardinality -->
<span *ngIf="propCard">
<mat-icon class="multiple">{{propInfo.multiple ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>
@@ -34,16 +34,16 @@
<!-- in case of ontology property: display corresponding resource classes -->
<span *ngIf="!propCard" class="white-space">
<span *ngIf="resClasses.length; else notUsed">
Property is used in:
<!-- <span>{{ resClasses.join(", ") }}</span> -->
<span class="mat-caption">&gt; Property is used in:</span>
<span *ngFor="let c of resClasses; let last = last">
<span [matTooltip]="c.comment" matTooltipPosition="above" class="link"
(click)="clickedOnClass.emit(c)">{{c.label}}</span>
<span *ngIf="!last">,&nbsp;</span>
<span [matTooltip]="c.comment" matTooltipPosition="above"
(click)="clickedOnClass.emit(c)">{{c.label}}</span><span *ngIf="!last">&nbsp;&middot;</span>
</span>
</span>
<ng-template #notUsed>
<span class="not-used">This property is not used in a class</span>
<span class="mat-caption">&gt;
<span class="not-used">This property is not used in a class.</span>
</span>
</ng-template>
</span>
</div>
@@ -77,8 +77,8 @@
</button>
</span>
<span matTooltipPosition="above"
[matTooltip]="((resClasses.length > 0) ? 'The property can\'t be deleted because it is used in a class' : 'Delete property')">
<button mat-button [disabled]="resClasses.length > 0" class="delete"
[matTooltip]="((!propCanBeDeleted) ? 'The property can\'t be deleted because it is used in a class' : 'Delete property')">
<button mat-button [disabled]="!propCanBeDeleted" class="delete"
(click)="deleteResourceProperty.emit({iri: propDef.id, label: propDef.label})">
<mat-icon>delete</mat-icon>
</button>
@@ -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 {
@@ -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<DefaultClass> = new EventEmitter<DefaultClass>();
@Output() removePropertyFromClass: EventEmitter<DefaultClass> = new EventEmitter<DefaultClass>();

// submit res class iri ot open res class
@Output() clickedOnClass: EventEmitter<ResourceClassDefinitionWithAllLanguages> = new EventEmitter<ResourceClassDefinitionWithAllLanguages>();
// submit res class iri to open res class (not yet implemented)
@Output() clickedOnClass: EventEmitter<ShortInfo> = new EventEmitter<ShortInfo>();

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
);
}

@@ -3,7 +3,7 @@
<mat-card-header class="resource-class-header" cdkDragHandle>
<!-- TODO: the res class icon is missing in ClassDefinition from DSP-JS-Lib; DSP-JS has to be updated first (s. DSP-1366) -->
<!-- <mat-icon mat-card-avatar>{{resourceClass.icon}}</mat-icon> -->
<mat-card-title [matTooltip]="resourceClass.comment + ' (' + resourceClass.id.split('#')[1] + ')'" matTooltipPosition="above">
<mat-card-title [matTooltip]="(resourceClass.comment ? resourceClass.comment : '')" matTooltipPosition="above">
{{resourceClass.label | appTruncate: 24}}
</mat-card-title>
<mat-card-subtitle>
@@ -43,13 +43,14 @@
</mat-menu>
</mat-card-header>
<!-- resource class card content with list of properties -->

<mat-card-content *ngIf="expanded">
<mat-list cdkDropList class="resource-class-properties" (cdkDropListDropped)="drop($event)"
*ngIf="propsToDisplay.length; else noProperties">
<div cdkDrag [cdkDragDisabled]="!ontology.lastModificationDate"
<div cdkDrag [cdkDragDisabled]="!ontology.lastModificationDate || !cardinalityUpdateEnabled"
*ngFor="let prop of propsToDisplay; let i = index;">
<div class="drag-n-drop-placeholder" *cdkDragPlaceholder></div>
<mat-list-item class="property">
<mat-list-item class="property" [disabled]="loadProperty">
<span cdkDragHandle mat-list-icon class="list-icon gui-order">
<span [class.hide-on-hover]="cardinalityUpdateEnabled && lastModificationDate">{{i + 1}})</span>
<span *ngIf="lastModificationDate && cardinalityUpdateEnabled"
@@ -59,8 +60,12 @@
</span>
<!-- display only properties if they exist in list of properties;
objectType is not a linkValue (otherwise we have the property twice) -->
<app-property-info class="property-info" [propDef]="ontology?.properties[prop.propertyIndex]"
[propCard]="propsToDisplay[i]" [projectCode]="projectCode" [projectStatus]="projectStatus" [resourceIri]="resourceClass.id"
<app-property-info class="property-info"
[propDef]="propsToDisplay[i].propDef"
[propCard]="propsToDisplay[i]"
[projectCode]="projectCode"
[projectStatus]="projectStatus"
[resourceIri]="resourceClass.id"
[(lastModificationDate)]="lastModificationDate"
(removePropertyFromClass)="removeProperty($event)">
</app-property-info>
@@ -83,13 +88,12 @@

<mat-menu #addPropertyMenu="matMenu" xPosition="after">
<button mat-menu-item [matMenuTriggerFor]="newFromPropType">Create new from type</button>
<button mat-menu-item [matMenuTriggerFor]="addExistingProp" [disabled]="!ontoProperties.length">Add existing property</button>
<button mat-menu-item [matMenuTriggerFor]="addExistingProp">Add existing property</button>
</mat-menu>
<mat-menu #newFromPropType="matMenu">
<ng-container *ngFor="let type of defaultProperties">
<button mat-menu-item [matMenuTriggerFor]="sub_menu">{{type.group}}</button>
<mat-menu #sub_menu="matMenu" class="default-nested-sub-menu">
<!-- <button *ngFor="let subItem of my_menu[mainItem]" mat-menu-item>{{ subItem }}</button> -->
<button mat-menu-item *ngFor="let ele of type.elements" [value]="ele"
[matTooltip]="ele.description" matTooltipPosition="after"
(click)="addNewProperty(ele)">
@@ -99,11 +103,16 @@
</ng-container>
</mat-menu>
<mat-menu #addExistingProp="matMenu" class="default-nested-sub-menu">
<button mat-menu-item *ngFor="let prop of existingProperties"
[matTooltip]="prop.propDef.comment" matTooltipPosition="after"
(click)="addExistingProperty(prop.propDef)">
<mat-icon>{{prop.propType?.icon}}</mat-icon> {{prop.propDef.label}}
</button>
<ng-container *ngFor="let onto of existingProperties">
<button mat-menu-item [disabled]="!onto.properties.length" [matMenuTriggerFor]="sub_menu">{{ onto.ontologyLabel }}</button>
<mat-menu #sub_menu="matMenu" class="default-nested-sub-menu">
<button mat-menu-item *ngFor="let prop of onto.properties" [matTooltip]="prop.propDef.comment" matTooltipPosition="after"
(click)="addExistingProperty(prop)">
<mat-icon>{{prop.propType?.icon}}</mat-icon> {{prop.propDef.label}}
</button>
</mat-menu>
</ng-container>

</mat-menu>
</mat-card-content>
</mat-card>

0 comments on commit b90fc70

Please sign in to comment.