recogito / text-annotator-js

A JavaScript library for text annotation.
BSD 3-Clause "New" or "Revised" License
21 stars 7 forks source link

Controlling selection behaviour #153

Open Aierie opened 1 month ago

Aierie commented 1 month ago

Hello! Wondering about controlling selection behaviour from userland.

I am making an app that has different behaviours on selection when different keys are pressed, and needs to be able to conditionally select a group of related annotations when one of them are clicked, and toggle selection state of a single annotation without interfering with other annotations. Initially, looking at the interface of clickAnnotation event here, I thought I would be able to retrieve whether modifier keys are pressed from the original event. However, it seems like clickAnnotation is not implemented for @recogito/text-annotator.

I then tried listening for DOM click events and checking whether they were on a range of an annotation:

document.querySelector('#annotator-target-example-6').addEventListener('click',(e) => {
    let range,textNode,offset;
    if (document.caretPositionFromPoint) {
        range = document.caretPositionFromPoint(
            e.clientX,
            e.clientY,
        );
        textNode = range.offsetNode;
        offset = range.offset;
    } else if (document.caretRangeFromPoint) {
        // Use WebKit-proprietary fallback method
        range = document.caretRangeFromPoint(e.clientX, e.clientY);
        textNode = range.startContainer;
        offset = range.startOffset;
    } else {
        throw new Error('document.caretPositionFromPoint and document.caretRangeFromPoint are not supported');
    }
    let annotations = annotator.getAnnotations();
    let annotationId = annotations.map((v) => [v.id, v.target.selector[0].range]).find(([k, r]) => { return r.isPointInRange(textNode, offset); })[0];
    if (!annotationId) return;
    if (e.altKey) {
        let selectedIds = new Set(annotator.getSelected().map((a) => a.id));
        if (selectedIds.has(annotationId)) {
            selectedIds.delete(annotationId);
        } else {
            selectedIds.add(annotationId);
        }
        annotator.setSelected(
            [...selectedIds]
        )
    } else {
        annotator.setSelected(
            [
                annotationId
            ]
        )
    }
});

But it seems like internally, even with userSelectAction set to UserSelectAction.NONE, TextAnnotatorState is calling userSelect here:

https://github.com/recogito/text-annotator-js/blob/ead48404841271de4f3df97da8f2d368736956fb/packages/text-annotator/src/SelectionHandler.ts#L197

Which in turn will either set the selected annotations to an empty array, or to a single one.

Is there any way around this? Would I need to maintain selection state in my own app or perhaps in an annotation's properties?

Aierie commented 1 month ago

Perhaps I am using selection state in a way that doesn't quite match its intended use...

rsimon commented 1 month ago

Hi,

it's all still a bit work in progress in here ;-) I'll take a look when I get the chance. But I think all you might be missing is the clickAnnotation event which, indeed, isn't implemented in the text annotator library yet (only in Annotorious). The UserSelectionAction.NONE setting might still have issues, too, I believe.

Aierie commented 1 month ago

Thanks! Are any of these things that I could help with?

If you're open to me helping with these things, it would be good to understand how I could address the main blocking one (UserSelectAction.NONE) - what would be the best way to address this with Annotorious' architecture?

rsimon commented 1 month ago

Definitely. The UserSelectionAction.NONE one is a bit tricky. But not really rocket science. Basically, it should be a few well-placed if conditions in the SelectionHandler.

You'd also have to hand the userSelectAction setting into the SelectionHandler in the first place, allow changes to the setting via a .set method etc. Therefore, it's not exactly trivial either. But at least it should be (mostly) constrained to that one source file. Feel free to give it a try and send a PR!

oleksandr-danylchenko commented 1 month ago

You'd also have to hand the userSelectAction setting into the SelectionHandler in the first place, allow changes to the setting via a .set method etc.

That's already been handled by the parent's annotorious library ✅ Here are the PRs where I made the userSelectAction interactive and accessible from outside: