diff --git a/src/TextAnnotator.jsx b/src/TextAnnotator.jsx index ae9f24d..8e58216 100644 --- a/src/TextAnnotator.jsx +++ b/src/TextAnnotator.jsx @@ -14,8 +14,6 @@ export default class TextAnnotator extends Component { state = { selectionBounds: null, selectedAnnotation: null, - - showRelationEditor: false, selectedRelation: null, applyTemplate: null, @@ -77,22 +75,59 @@ export default class TextAnnotator extends Component { /** * A convenience method that allows the external application to - * override the autogenerated Id. + * override the autogenerated Id for an annotation. * * Usually, the override will happen almost immediately after * the annotation is created. But we need to be defensive and assume * that the override might come in with considerable delay, thus * the user might have made further edits already. + * + * A key challenge here is that there may be dependencies between + * the original annotation and relations that were created meanwhile. */ - overrideId = originalId => forcedId => { - // Force the editors to close, otherwise their annotations will be orphaned + overrideAnnotationId = originalAnnotation => forcedId => { + const { id } = originalAnnotation; + + // After the annotation update, we need to update dependencies + // on the annotation layer, if any + const updateDependentRelations = updatedAnnotation => { + // Wait until the highlighter update has come into effect + requestAnimationFrame(() => { + this.relationsLayer.overrideTargetAnnotation(originalAnnotation, updatedAnnotation); + }) + }; + + // Force the editors to close first, otherwise their annotations will be orphaned if (this.state.selectedAnnotation || this.state.selectedRelation) { + this.relationsLayer.resetDrawing(); this.setState({ selectedAnnotation: null, selectedRelation: null - }, () => this.highlighter.overrideId(originalId, forcedId)); + }, () => { + const updated = this.highlighter.overrideId(id, forcedId); + updateDependentRelations(updated); + }); } else { - this.highlighter.overrideId(originalId, forcedId); + const updated = this.highlighter.overrideId(id, forcedId); + updateDependentRelations(updated); + } + } + + /** + * A convenience method that allows the external application to + * override the autogenerated Id for a relation. + * + * This operation is less problematic than .overrideAnnotation(). + * We just need to make sure the RelationEditor is closed, so that + * the annotation doesn't become orphaned. Otherwise, there are + * no dependencies. + */ + overrideRelationId = originalId => forcedId => { + if (this.state.selectedRelation) { + this.setState({ selectedRelation: null }, () => + this.relationsLayer.overrideId(originalId, forcedId)); + } else { + this.relationsLayer.overrideId(originalId, forcedId); } } @@ -107,7 +142,7 @@ export default class TextAnnotator extends Component { if (previous) this.props[method](annotation.clone(), previous.clone()); else - this.props[method](annotation.clone(), this.overrideId(annotation.id)); + this.props[method](annotation.clone(), this.overrideAnnotationId(annotation)); } onDeleteAnnotation = annotation => { @@ -133,7 +168,7 @@ export default class TextAnnotator extends Component { // Shorthand closeRelationsEditor = () => { - this.setState({ showRelationEditor: false }); + this.setState({ selectedRelation: null }); this.relationsLayer.resetDrawing(); } @@ -143,7 +178,6 @@ export default class TextAnnotator extends Component { */ onEditRelation = relation => { this.setState({ - showRelationEditor: true, selectedRelation: relation }); } @@ -158,13 +192,8 @@ export default class TextAnnotator extends Component { // otherwise, fire 'update' const isNew = previous.annotation.bodies.length === 0; - // A convenience method that allows the external application to - // override the autogenerated Id - const overrideId = originalId => forcedId => - this.relationsLayer.overrideId(originalId, forcedId); - if (isNew) - this.props.onAnnotationCreated(relation.annotation.clone(), overrideId(relation.annotation.id)); + this.props.onAnnotationCreated(relation.annotation.clone(), this.overrideRelationId(relation.annotation.id)); else this.props.onAnnotationUpdated(relation.annotation.clone(), previous.annotation.clone()); } @@ -214,7 +243,7 @@ export default class TextAnnotator extends Component { this.relationsLayer.readOnly = false; this.relationsLayer.startDrawing(); } else { - this.setState({ showRelationEditor: false }); + this.setState({ selectedRelation: null }); this.selectionHandler.enabled = true; @@ -247,7 +276,7 @@ export default class TextAnnotator extends Component { } - { this.state.showRelationEditor && + { this.state.selectedRelation && { - const id = annotationOrId.id ? annotationOrId.id : annotationOrId; - - const allSpans = document.querySelectorAll(`.r6o-annotation[data-id="${id}"]`); + overrideId = (originalId, forcedId) => { + const allSpans = document.querySelectorAll(`.r6o-annotation[data-id="${originalId}"]`); const annotation = allSpans[0].annotation; const updatedAnnotation = annotation.clone({ id : forcedId }); diff --git a/src/relations/RelationsLayer.js b/src/relations/RelationsLayer.js index b261696..9eec250 100644 --- a/src/relations/RelationsLayer.js +++ b/src/relations/RelationsLayer.js @@ -93,16 +93,23 @@ export default class RelationsLayer extends EventEmitter { } } - overrideId = (annotationOrId, forcedId) => { - const id = annotationOrId.id ? annotationOrId.id : annotationOrId; - const conn = this.connections.find(c => c.annotation.id == id); - + /** Overrides the ID for an existing relation **/ + overrideRelationId = (originalId, forcedId) => { + const conn = this.connections.find(c => c.annotation.id == originalId); const updatedAnnotation = conn.annotation.clone({ id : forcedId }); conn.annotation = updatedAnnotation; - return conn; } + /** Overrides the given source or target annotation **/ + overrideTargetAnnotation = (originalAnnotation, forcedAnnotation) => { + const affectedFrom = this.connections.filter(c => c.fromNode.annotation == originalAnnotation); + affectedFrom.forEach(c => c.fromNode.annotation = forcedAnnotation); + + const affectedTo = this.connections.filter(c => c.toNode.annotation == originalAnnotation); + affectedTo.forEach(c => c.toNode.annotation = forcedAnnotation); + } + getAllRelations = () => { return this.connections.map(c => c.annotation); }