From 933eb9f1c4082a1a3aed92e6c5cc504781982b55 Mon Sep 17 00:00:00 2001 From: Rainer Simon Date: Sat, 4 Apr 2020 22:46:33 +0200 Subject: [PATCH] Moved base app over from RecogitoJS module as TextAnnotator --- src/TextAnnotator.jsx | 214 ++++++++++++++++++++++++++++++++++++++++++ src/index.js | 1 + 2 files changed, 215 insertions(+) create mode 100644 src/TextAnnotator.jsx diff --git a/src/TextAnnotator.jsx b/src/TextAnnotator.jsx new file mode 100644 index 0000000..c71395f --- /dev/null +++ b/src/TextAnnotator.jsx @@ -0,0 +1,214 @@ +import React, { Component } from 'react'; +import Editor from './editor/Editor'; +import Highlighter from './highlighter/Highlighter'; +import SelectionHandler from './selection/SelectionHandler'; +import RelationsLayer from './relations/RelationsLayer'; +import RelationEditor from './relations/editor/RelationEditor'; + +/** + * Pulls the strings between the annotation highlight layer + * and the editor popup. + */ +export default class TextAnnotator extends Component { + + state = { + showEditor: false, + selectionBounds: null, + selectedAnnotation: null, + + showRelationEditor: false, + selectedRelation: null + } + + /** Helper **/ + _clearState = () => { + this.setState({ + showEditor: false, + selectionBounds: null, + selectedAnnotation: null + }); + } + + handleEscape = (evt) => { + if (evt.which === 27) + this.onCancelAnnotation(); + } + + componentDidMount() { + this.highlighter = new Highlighter(this.props.contentEl, this.props.formatter); + + this.selectionHandler = new SelectionHandler(this.props.contentEl, this.highlighter); + this.selectionHandler.on('select', this.handleSelect); + + this.relationsLayer = new RelationsLayer(this.props.contentEl); + this.relationsLayer.readOnly = true; // Deactivate by default + + this.relationsLayer.on('createRelation', this.onEditRelation); + this.relationsLayer.on('selectRelation', this.onEditRelation); + this.relationsLayer.on('cancelDrawing', this.closeRelationsEditor); + + document.addEventListener('keydown', this.handleEscape); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.handleEscape); + } + + /**************************/ + /* Annotation CRUD events */ + /**************************/ + + /** Selection on the text **/ + handleSelect = evt => { + const { selection, clientRect } = evt; + if (selection) { + this.setState({ + showEditor: true, + selectionBounds: clientRect, + selectedAnnotation: selection + }); + } else { + this._clearState(); + } + } + + /** Common handler for annotation CREATE or UPDATE **/ + onCreateOrUpdateAnnotation = method => (annotation, previous) => { + // Clear the annotation layer + this._clearState(); + + this.selectionHandler.clearSelection(); + this.highlighter.addOrUpdateAnnotation(annotation, previous); + + // Call CREATE or UPDATE handler + this.props[method](annotation, previous); + } + + onDeleteAnnotation = annotation => { + // Delete connections + const connections = this.relationsLayer.getConnectionsFor(annotation); + connections.forEach(c => c.destroy()); + + this._clearState(); + this.selectionHandler.clearSelection(); + this.highlighter.removeAnnotation(annotation); + } + + /** Cancel button on annotation editor **/ + onCancelAnnotation = () => { + this._clearState(); + this.selectionHandler.clearSelection(); + } + + /************************/ + /* Relation CRUD events */ + /************************/ + + // Shorthand + closeRelationsEditor = () => { + this.setState({ showRelationEditor: false }); + this.relationsLayer.resetDrawing(); + } + + /** + * Selection on the relations layer: open an existing + * or newly created connection for editing. + */ + onEditRelation = relation => { + this.setState({ + showRelationEditor: true, + selectedRelation: relation + }); + } + + /** 'Ok' on the relation editor popup **/ + onCreateOrUpdateRelation = (relation, previous) => { + this.relationsLayer.addOrUpdateRelation(relation, previous); + this.closeRelationsEditor(); + } + + /** 'Delete' on the relation editor popup **/ + onDeleteRelation = relation => { + this.relationsLayer.removeRelation(relation); + this.closeRelationsEditor(); + } + + /****************/ + /* External API */ + /****************/ + + addAnnotation = annotation => { + this.highlighter.addOrUpdateAnnotation(annotation); + } + + removeAnnotation = annotation => { + this.highlighter.removeAnnotation(annotation); + + // If the editor is currently open on this annotation, close it + const { selectedAnnotation } = this.state; + if (selectedAnnotation && annotation.isEqual(selectedAnnotation)) + this.setState({ showEditor: false }); + } + + setAnnotations = annotations => { + this.highlighter.init(annotations); + this.relationsLayer.init(annotations); + } + + getAnnotations = () => { + const annotations = this.highlighter.getAllAnnotations(); + const relations = this.relationsLayer.getAllRelations(); + return annotations.concat(relations); + } + + setMode = mode => { + if (mode === 'RELATIONS') { + this.setState({ showEditor: false }); + + this.selectionHandler.enabled = false; + + this.relationsLayer.readOnly = false; + this.relationsLayer.startDrawing(); + } else { + this.setState({ showRelationEditor: false }); + + this.selectionHandler.enabled = true; + + this.relationsLayer.readOnly = true; + this.relationsLayer.stopDrawing(); + } + } + + render() { + return ( + <> + { this.state.showEditor && + + + {this.props.children} + + + } + + { this.state.showRelationEditor && + + } + + ); + } + +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 7996f0c..2829134 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +export { default as TextAnnotator } from './TextAnnotator'; export { default as WebAnnotation } from './WebAnnotation'; export * from './editor';