UNPKG

react-annotator

Version:

A React mixin to allow for user annotations directly on images.

238 lines (188 loc) 6.65 kB
'use strict'; var React = require('react/addons'); var _ = require('lodash'); var Indicator = require('./Indicator'); var Annotation = require('./Annotation'); var AnnotatorForm = require('./AnnotatorForm'); module.exports = function(settings) { var mixin = { _target: null, _annotationTarget: null, _addCallback: settings.addCallback || function() {}, getInitialState: function() { return { annotations: settings.annotations || [], visibleAnnotation: null, annotationXPos: null, annotationYPos: null, annotationPosition: null, addingAnnotation: false, newAnnotationXPos: null, newAnnotationYPos: null }; }, _renderAnnotatorLayer: function() { // By calling this method in componentDidMount() and componentDidUpdate(), you're effectively // creating a "wormhole" that funnels React's hierarchical updates through to a DOM node on an // entirely different part of the page. if ( !this._target ) { this._target = document.createElement('div'); document.body.appendChild(this._target); } React.render(this.renderAnnotationOrForm(), this._target); }, _unrenderAnnotatorLayer: function() { if ( this._target ) { React.unmountComponentAtNode(this._target); } }, _beginAddProcess: function(evt) { var xPos = evt.clientX; var yPos = evt.clientY; if ( !this.state.visibleAnnotation ) { void 0; this.setState({ addingAnnotation: true, newAnnotationXPos: xPos, newAnnotationYPos: yPos }, this._renderAnnotatorLayer); } }, _saveAnnotation: function(text) { var rect = this._annotationTarget.getBoundingClientRect(); var annotation = { text: text, xPos: this.state.newAnnotationXPos - rect.left, yPos: this.state.newAnnotationYPos - rect.top }; this.addAnnotation(annotation, function() { this._addCallback(annotation); this._closeForm(); }.bind(this)); }, _calculateAnnotationPosition: function(options) { var position = null; if ( options.yPos + 250 > document.body.offsetHeight ) { position = 'right'; } else { position = 'bottom'; } return position; }, _setVisibleAnnotation: function(options) { var rect = this._annotationTarget.getBoundingClientRect(); options.xPos += rect.left; options.yPos += rect.top; void 0; this.setState({ addingAnnotation: false, newAnnotationXPos: null, newAnnotationYPos: null, visibleAnnotation: options.annotation, annotationXPos: options.xPos, annotationYPos: options.yPos, annotationPosition: this._calculateAnnotationPosition(options) }, this._renderAnnotatorLayer); }, _closeAnnotation: function() { this.setState({ visibleAnnotation: null, annotationXPos: null, annotationYPos: null, annotationPosition: null }, this._unrenderAnnotatorLayer); }, _closeForm: function() { this.setState({ addingAnnotation: false, newAnnotationXPos: null, newAnnotationYPos: null }, this._unrenderAnnotatorLayer); }, _bindClick: function() { this._annotationTarget = settings.element ? document.querySelector(settings.element) : null; if ( this._annotationTarget ) { this._annotationTarget.addEventListener('click', this._beginAddProcess, false); } }, _unbindClick: function() { if ( this._annotationTarget ) { this._annotationTarget.removeEventListener('click', this._beginAddProcess, false); } }, componentDidMount: function() { if ( this.state.visibleAnnotation ) { // Appending to the body is easier than managing the z-index of everything on the page. // It's also better for accessibility and makes stacking a snap (since components will stack // in mount order). this._renderAnnotatorLayer(); } this._bindClick(); }, componentDidUpdate: function() { this._bindClick(); }, componentWillUnmount: function() { var isRendered = this.state.addingAnnotation || this.state.visibleAnnotation; if ( this._target && isRendered ) { this._unrenderAnnotatorLayer(); document.body.removeChild(this._target); } this._unbindClick(); }, setAnnotations: function(annotations, cb) { this.setState({ annotations: annotations || [] }, cb); }, setAddCallback: function(cb) { this._addCallback = cb || function() {}; }, getAnnotations: function() { return this.state.annotations; }, addAnnotation: function(annotation, cb) { var annotationsCopy = this.state.annotations; cb = cb || function() {}; annotationsCopy.push(annotation); this.setState({ annotations: annotationsCopy }, cb); }, removeAnnotation: function(annotationToDelete, cb) { var annotationsCopy = _.reject(this.state.annotations, function(annotation) { return _.isEqual(annotation, annotationToDelete); }); cb = cb || function() {}; this.setState({ annotations: annotationsCopy }, cb); }, renderAnnotationIndicators: function() { return _.map(this.state.annotations, function(annotation, index) { return ( React.createElement(Indicator, {key: index, annotation: annotation, showAnnotation: this._setVisibleAnnotation, closeAnnotation: this._closeAnnotation}) ); }.bind(this)); }, renderAnnotationOrForm: function() { var element = null; if ( this.state.addingAnnotation ) { element = ( React.createElement(AnnotatorForm, {xPos: this.state.newAnnotationXPos, yPos: this.state.newAnnotationYPos, saveAnnotation: this._saveAnnotation, closeForm: this._closeForm}) ); } else if ( this.state.visibleAnnotation ) { element = ( React.createElement(Annotation, {xPos: this.state.annotationXPos, yPos: this.state.annotationYPos, position: this.state.annotationPosition, annotation: this.state.visibleAnnotation}) ); } return element; } }; return mixin; };