Secretmapper/react-image-annotation

handle onClick event on Top annotation

Closed this issue · 5 comments

gr- commented

I really like your library. Very nice shot!

I would expect a way to overload the onClick event for the annotations themselves (rather than the annotatable object only). Actually, I am looking for a way to trigger a dedicated behavior each time one click on the so-called top annotation in the source code.

Is there a trick I could use to do that ?

Hello @gr-!

I think that's interesting, can you share your use case?

My only concern is that in the default behaviour, clicks trigger 'annotation' (i.e. adding point, starting drawing of rectangle) so this would conflict. However, it should be possible to override that.

Closing. Please feel free to comment or open with more details, thank you!

I think I'm running into a similar need. I'm looking to create 'edit' functionality so when I click on an existing annotation it appends the selection object for that annotation; launching the editor.

With the current implementation, when I click on an existing POINT annotation it creates a new one on top of it.

I thought that perhaps making a custom selector (copied the RectangleSelector) would lead somewhere but none of the methods within that custom selector are being triggered at any point in an annotations/lifecycle.

I've been unable to figure out how to implement this so far so I've just been handling editing & deleting outside of the annotation window by rendering all of the annotations in a list next to the image.

If someone else is coming across this that has the same issue, I'm handling it like this with the POINT selector. It's kind of hacky but it gets the job done.

onChange = (annotation) => {
    const x = annotation.geometry.x;
    const y = annotation.geometry.y;

    // Determine if there are any annotations that exist that are within 1 unit of where the user clicked
    let closeAnnotations = _.filter(this.state.annotations, function(stateAnnotation) {
      let distanceFromX = Math.abs(x - stateAnnotation.geometry.x);
      let distanceFromY = Math.abs(y - stateAnnotation.geometry.y);

      return distanceFromX < 1 && distanceFromY < 1;
    });

    // If there are existing annotations we want to activate one of them instead of creating a new one
    let setAnnotation = annotation;
    if (closeAnnotations.length > 0) {
      setAnnotation = JSON.parse(JSON.stringify(closeAnnotations[0])); // we wrap the object with parse and stringfy to copy the object without holding a reference to the annotation in the state
    }

    // If the set annotation an existing one we need to manually update the text on the object or else it won't ever change
    if (annotation.data && annotation.data.text) {
      setAnnotation.data.text = annotation.data.text
    }

    this.activateAnnotation(setAnnotation);
  }

  activateAnnotation = (annotation) => {
    this.setState({
      annotation: {
        selection: {
          showEditor: true,
          mode: 'EDITING'
        },
        id: annotation.id,
        geometry: annotation.geometry,
        data: annotation.data
      }
    })
  }

Hello @hunterfortuin, thanks for the detailed description. I think the use case you presented is a valid and quite common one.

Ass I mentioned the only problem is logic conflicts, however, surfacing common patterns/work-arounds like this (hit test w/ 1 pixel off covering new annotations) might be useful in a README, or as an alternative selector type.

I will look into this.