From a1b3bf2fd574a7e5f9028c49c39efd01947d45db Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Wed, 24 Jul 2024 18:55:15 +0300 Subject: [PATCH 01/13] Added rects normalization utils --- .../text-annotator/src/state/spatialTree.ts | 20 +++++++++++-------- packages/text-annotator/src/utils/index.ts | 1 + .../src/utils/normalizeRects.ts | 9 +++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 packages/text-annotator/src/utils/normalizeRects.ts diff --git a/packages/text-annotator/src/state/spatialTree.ts b/packages/text-annotator/src/state/spatialTree.ts index 675ffe76..6810a7d7 100644 --- a/packages/text-annotator/src/state/spatialTree.ts +++ b/packages/text-annotator/src/state/spatialTree.ts @@ -1,9 +1,13 @@ import RBush from 'rbush'; import type { Store } from '@annotorious/core'; import type { TextAnnotation, TextAnnotationTarget } from '../model'; -import { isRevived, mergeClientRects } from '../utils'; -import { getClientRectsPonyfill } from '../utils/getClientRectsPonyfill'; -import { reviveSelector } from '../utils'; +import { + getClientRectsPonyfill, + isRevived, + mergeClientRects, + normalizeRectWithOffset, + reviveSelector +} from '../utils'; import type { AnnotationRects } from './TextAnnotationStore'; const isFirefox = false; // navigator.userAgent.match(/firefox|fxios/i); @@ -45,11 +49,11 @@ export const createSpatialTree = (store: Store, container: HTMLE Array.from(revivedRange.getClientRects()); }); - const merged = mergeClientRects(rects) - // Offset the merged client rects so that coords - // are relative to the parent container - .map(({ left, top, right, bottom }) => - new DOMRect(left - offset.left, top - offset.top, right - left, bottom - top)); + /** + * Offset the merged client rects so that coords + * are relative to the parent container + */ + const merged = mergeClientRects(rects).map(rect => normalizeRectWithOffset(rect, offset)); return merged.map(rect => { const { x, y, width, height } = rect; diff --git a/packages/text-annotator/src/utils/index.ts b/packages/text-annotator/src/utils/index.ts index e8b379d8..a9b40012 100644 --- a/packages/text-annotator/src/utils/index.ts +++ b/packages/text-annotator/src/utils/index.ts @@ -11,4 +11,5 @@ export * from './reviveAnnotation'; export * from './reviveSelector'; export * from './reviveTarget'; export * from './splitAnnotatableRanges'; +export * from './normalizeRects'; diff --git a/packages/text-annotator/src/utils/normalizeRects.ts b/packages/text-annotator/src/utils/normalizeRects.ts new file mode 100644 index 00000000..461f222f --- /dev/null +++ b/packages/text-annotator/src/utils/normalizeRects.ts @@ -0,0 +1,9 @@ +export const normalizeRectWithOffset = (rect: DOMRect, offset: DOMRect): DOMRect => { + const { left, top, right, bottom } = rect; + return new DOMRect(left - offset.left, top - offset.top, right - left, bottom - top); +}; + +export const denormalizeRectWithOffset = (rect: DOMRect, offset: DOMRect): DOMRect => { + const { left, top, right, bottom } = rect; + return new DOMRect(left + offset.left, top + offset.top, right - left, bottom - top); +} From 405dfeeb22fca61d582665fad2b6e8d86cdb271c Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Wed, 24 Jul 2024 18:55:45 +0300 Subject: [PATCH 02/13] Added `getAnnotationRects` exposure from the store --- packages/text-annotator/src/state/TextAnnotationStore.ts | 4 +++- packages/text-annotator/src/state/TextAnnotatorState.ts | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/text-annotator/src/state/TextAnnotationStore.ts b/packages/text-annotator/src/state/TextAnnotationStore.ts index c8c643db..347b3564 100644 --- a/packages/text-annotator/src/state/TextAnnotationStore.ts +++ b/packages/text-annotator/src/state/TextAnnotationStore.ts @@ -11,7 +11,9 @@ export interface TextAnnotationStore extends Omit, 'addAnn bulkUpsertAnnotations(annotations: TextAnnotation[], origin?: Origin): TextAnnotation[]; - getAnnotationBounds(id: string, hintX?: number, hintY?: number, buffer?: number): DOMRect; + getAnnotationRects(id: string): DOMRect[]; + + getAnnotationBounds(id: string, hintX?: number, hintY?: number, buffer?: number): DOMRect | undefined; getAt(x: number, y: number, filter?: Filter): TextAnnotation | undefined; diff --git a/packages/text-annotator/src/state/TextAnnotatorState.ts b/packages/text-annotator/src/state/TextAnnotatorState.ts index 98065c8f..1c9cf33a 100644 --- a/packages/text-annotator/src/state/TextAnnotatorState.ts +++ b/packages/text-annotator/src/state/TextAnnotatorState.ts @@ -113,7 +113,7 @@ export const createTextAnnotatorState = ( return filtered.length > 0 ? filtered[0] : undefined; } - const getAnnotationBounds = (id: string, x?: number, y?: number, buffer = 5): DOMRect => { + const getAnnotationBounds = (id: string, x?: number, y?: number, buffer = 5): DOMRect | undefined => { const rects = tree.getAnnotationRects(id); if (rects.length === 0) return; @@ -154,7 +154,8 @@ export const createTextAnnotatorState = ( bulkUpsertAnnotations, getAnnotationBounds, getAt, - getIntersecting: tree.getIntersecting, + getIntersecting: tree.getIntersecting.bind(tree), + getAnnotationRects: tree.getAnnotationRects.bind(tree), recalculatePositions, updateTarget }, From f631f5f3c6e21a29de1bdd5e8a22ae09483f7f23 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Wed, 24 Jul 2024 18:56:02 +0300 Subject: [PATCH 03/13] Added popup positioning using the spatial tree bounds --- .../src/TextAnnotatorPopup.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx index 9549344d..c069fed3 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx @@ -1,6 +1,6 @@ -import { ReactNode, useCallback, useEffect, useState, PointerEvent } from 'react'; +import { PointerEvent, ReactNode, useCallback, useEffect, useState } from 'react'; import { useAnnotator, useSelection } from '@annotorious/react'; -import { type TextAnnotation, type TextAnnotator } from '@recogito/text-annotator'; +import { denormalizeRectWithOffset, type TextAnnotation, type TextAnnotator } from '@recogito/text-annotator'; import { autoUpdate, inline, @@ -62,19 +62,21 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { }, [pointerEvent?.type, selectedKey]); useEffect(() => { - if (!isOpen || !annotation) return; - - const { - target: { - selector: [{ range }] - } - } = annotation; + if (!isOpen || !annotation?.id || !r) return; refs.setPositionReference({ - getBoundingClientRect: range.getBoundingClientRect.bind(range), - getClientRects: range.getClientRects.bind(range) + getBoundingClientRect: () => denormalizeRectWithOffset( + r.state.store.getAnnotationBounds(annotation.id), + r.element.getBoundingClientRect() + ), + getClientRects: () => { + const rects = r.state.store.getAnnotationRects(annotation.id); + return rects.map( + rect => denormalizeRectWithOffset(rect, r.element.getBoundingClientRect()) + ); + } }); - }, [isOpen, annotation, refs]); + }, [isOpen, annotation?.id, r]); // Prevent text-annotator from handling the irrelevant events triggered from the popup const getStopEventsPropagationProps = useCallback( From b59bed66575021dd85ca4be0028e82551c543cee Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Thu, 25 Jul 2024 15:41:30 +0300 Subject: [PATCH 04/13] Added conversion to dom rect list --- .../text-annotator-react/src/TextAnnotatorPopup.tsx | 5 +++-- .../text-annotator/src/utils/mergeClientRects.ts | 13 +++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx index c069fed3..a77bf037 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx @@ -1,6 +1,6 @@ import { PointerEvent, ReactNode, useCallback, useEffect, useState } from 'react'; import { useAnnotator, useSelection } from '@annotorious/react'; -import { denormalizeRectWithOffset, type TextAnnotation, type TextAnnotator } from '@recogito/text-annotator'; +import { toDomRectList, denormalizeRectWithOffset, type TextAnnotation, type TextAnnotator } from '@recogito/text-annotator'; import { autoUpdate, inline, @@ -71,9 +71,10 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { ), getClientRects: () => { const rects = r.state.store.getAnnotationRects(annotation.id); - return rects.map( + const denormalizedRects = rects.map( rect => denormalizeRectWithOffset(rect, r.element.getBoundingClientRect()) ); + return toDomRectList(denormalizedRects); } }); }, [isOpen, annotation?.id, r]); diff --git a/packages/text-annotator/src/utils/mergeClientRects.ts b/packages/text-annotator/src/utils/mergeClientRects.ts index e106fb30..122d8313 100644 --- a/packages/text-annotator/src/utils/mergeClientRects.ts +++ b/packages/text-annotator/src/utils/mergeClientRects.ts @@ -65,7 +65,7 @@ const union = (a: DOMRect, b: DOMRect): DOMRect => { return new DOMRect(left, top, right - left, bottom - top); } -export const mergeClientRects = (rects: DOMRect[]) => rects.reduce((merged, rectA) => { +export const mergeClientRects = (rects: DOMRect[]) => rects.reduce((merged, rectA) => { // Some browser report empty rects - discard if (rectA.width === 0 || rectA.height === 0) return merged; @@ -102,4 +102,13 @@ export const mergeClientRects = (rects: DOMRect[]) => rects.reduce((merged, rect } return wasMerged ? next : [ ...next, rectA ]; -}, [] as DOMRect[]); +}, []); + +export const toDomRectList = (rects: DOMRect[]): DOMRectList => ({ + length: rects.length, + item: (index) => rects[index], + [Symbol.iterator]: function* (): IterableIterator { + for (let i = 0; i < this.length; i++) + yield this.item(i)!; + } +}) From 5eaef4e75f55ed497d726b056cb31bf0c7777a3f Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Tue, 27 Aug 2024 19:49:30 +0300 Subject: [PATCH 05/13] Added repositioning on target update --- packages/text-annotator-react/src/TextAnnotatorPopup.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx index a77bf037..9c6fcf4a 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx @@ -78,6 +78,7 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { } }); }, [isOpen, annotation?.id, r]); + }, [isOpen, annotation?.id, annotation?.target, r]); // Prevent text-annotator from handling the irrelevant events triggered from the popup const getStopEventsPropagationProps = useCallback( From e4a5620df8152d67c9d4ed80ee143c69ebc7c38a Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Tue, 27 Aug 2024 19:50:11 +0300 Subject: [PATCH 06/13] Added reference removal upon closing the popup --- .../src/TextAnnotatorPopup.tsx | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx index 9c6fcf4a..43edf249 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx @@ -62,22 +62,25 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { }, [pointerEvent?.type, selectedKey]); useEffect(() => { - if (!isOpen || !annotation?.id || !r) return; - - refs.setPositionReference({ - getBoundingClientRect: () => denormalizeRectWithOffset( - r.state.store.getAnnotationBounds(annotation.id), - r.element.getBoundingClientRect() - ), - getClientRects: () => { - const rects = r.state.store.getAnnotationRects(annotation.id); - const denormalizedRects = rects.map( - rect => denormalizeRectWithOffset(rect, r.element.getBoundingClientRect()) - ); - return toDomRectList(denormalizedRects); - } - }); - }, [isOpen, annotation?.id, r]); + if (!annotation?.id || !r) return; + + if (isOpen) { + refs.setPositionReference({ + getBoundingClientRect: () => denormalizeRectWithOffset( + r.state.store.getAnnotationBounds(annotation.id), + r.element.getBoundingClientRect() + ), + getClientRects: () => { + const rects = r.state.store.getAnnotationRects(annotation.id); + const denormalizedRects = rects.map( + rect => denormalizeRectWithOffset(rect, r.element.getBoundingClientRect()) + ); + return toDomRectList(denormalizedRects); + } + }); + } else { + refs.setPositionReference(null); + } }, [isOpen, annotation?.id, annotation?.target, r]); // Prevent text-annotator from handling the irrelevant events triggered from the popup From eae94ed5e790949120501ae4740acca19904b0e2 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Tue, 27 Aug 2024 21:16:59 +0300 Subject: [PATCH 07/13] Added reference cleanup when the annotation is removed --- packages/text-annotator-react/src/TextAnnotatorPopup.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx index 43edf249..cde6e96f 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup.tsx @@ -62,9 +62,9 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { }, [pointerEvent?.type, selectedKey]); useEffect(() => { - if (!annotation?.id || !r) return; + if (!r) return; - if (isOpen) { + if (isOpen && annotation?.id) { refs.setPositionReference({ getBoundingClientRect: () => denormalizeRectWithOffset( r.state.store.getAnnotationBounds(annotation.id), @@ -79,6 +79,7 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { } }); } else { + // Don't leave the reference depending on the previously selected annotation refs.setPositionReference(null); } }, [isOpen, annotation?.id, annotation?.target, r]); From 9650d3c6b0615caf1dca1d948dc688452fb30b41 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Mon, 2 Sep 2024 19:24:53 +0300 Subject: [PATCH 08/13] Deduped re-exports --- .../text-annotator/src/state/TextAnnotatorState.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/text-annotator/src/state/TextAnnotatorState.ts b/packages/text-annotator/src/state/TextAnnotatorState.ts index 2b5451a5..1c4d0d0e 100644 --- a/packages/text-annotator/src/state/TextAnnotatorState.ts +++ b/packages/text-annotator/src/state/TextAnnotatorState.ts @@ -11,7 +11,7 @@ import { } from '@annotorious/core'; import { createSpatialTree } from './spatialTree'; import type { TextAnnotation, TextAnnotationTarget } from '../model'; -import type { TextAnnotationStore } from './TextAnnotationStore'; +import type { AnnotationRects, TextAnnotationStore } from './TextAnnotationStore'; import { isRevived, reviveAnnotation, reviveTarget } from '../utils'; export interface TextAnnotatorState extends AnnotatorState { @@ -130,6 +130,13 @@ export const createTextAnnotatorState = ( return tree.getAnnotationBounds(id); } + const getIntersecting = ( + minX: number, + minY: number, + maxX: number, + maxY: number, + ): AnnotationRects[] => tree.getIntersecting(minX, minY, maxX, maxY); + const getAnnotationRects = (id: string): DOMRect[] => tree.getAnnotationRects(id); const recalculatePositions = () => tree.recalculate(); @@ -158,9 +165,8 @@ export const createTextAnnotatorState = ( bulkUpsertAnnotations, getAnnotationBounds, getAnnotationRects, + getIntersecting, getAt, - getIntersecting: tree.getIntersecting.bind(tree), - getAnnotationRects: tree.getAnnotationRects.bind(tree), recalculatePositions, updateTarget }, From 565bb6d1f7098da1a616ef2a6b56b79595e883a8 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Tue, 10 Sep 2024 18:40:30 +0300 Subject: [PATCH 09/13] Aligned `getIntersecting` type --- packages/text-annotator/src/state/TextAnnotatorState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/text-annotator/src/state/TextAnnotatorState.ts b/packages/text-annotator/src/state/TextAnnotatorState.ts index a12b8e2a..f7fc0cb8 100644 --- a/packages/text-annotator/src/state/TextAnnotatorState.ts +++ b/packages/text-annotator/src/state/TextAnnotatorState.ts @@ -135,7 +135,7 @@ export const createTextAnnotatorState = ( minY: number, maxX: number, maxY: number, - ): AnnotationRects[] => tree.getIntersecting(minX, minY, maxX, maxY); + ): AnnotationRects[] => tree.getIntersecting(minX, minY, maxX, maxY); const getAnnotationRects = (id: string): DOMRect[] => tree.getAnnotationRects(id); From 5d5218dddf804ead3cc3bf10fc74501a322a2509 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Thu, 26 Sep 2024 17:03:06 +0300 Subject: [PATCH 10/13] Fixed array iterator type --- packages/text-annotator/src/utils/mergeClientRects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/text-annotator/src/utils/mergeClientRects.ts b/packages/text-annotator/src/utils/mergeClientRects.ts index abd67276..bb08631f 100644 --- a/packages/text-annotator/src/utils/mergeClientRects.ts +++ b/packages/text-annotator/src/utils/mergeClientRects.ts @@ -107,7 +107,7 @@ export const mergeClientRects = (rects: DOMRect[]) => rects.reduce((m export const toDomRectList = (rects: DOMRect[]): DOMRectList => ({ length: rects.length, item: (index) => rects[index], - [Symbol.iterator]: function* (): IterableIterator { + [Symbol.iterator]: function* (): ArrayIterator { for (let i = 0; i < this.length; i++) yield this.item(i)!; } From fb7665730d5029c4f44fe333972c56d8f29fc165 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Thu, 26 Sep 2024 17:03:17 +0300 Subject: [PATCH 11/13] Fixed renamed generic use --- packages/text-annotator/src/state/TextAnnotatorState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/text-annotator/src/state/TextAnnotatorState.ts b/packages/text-annotator/src/state/TextAnnotatorState.ts index 2c1bfe2b..5cb5a4ab 100644 --- a/packages/text-annotator/src/state/TextAnnotatorState.ts +++ b/packages/text-annotator/src/state/TextAnnotatorState.ts @@ -137,7 +137,7 @@ export const createTextAnnotatorState = [] => tree.getIntersecting(minX, minY, maxX, maxY); + ): AnnotationRects[] => tree.getIntersecting(minX, minY, maxX, maxY); const getAnnotationRects = (id: string): DOMRect[] => tree.getAnnotationRects(id); From c6d36b66a007c30d08b606596c255695dabec86d Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Mon, 30 Sep 2024 12:08:25 +0300 Subject: [PATCH 12/13] Merged `main` into `spatial-tree-positioning` --- .../TextAnnotatorPopup/TextAnnotatorPopup.tsx | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx index d001400e..cbb119f5 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx @@ -14,7 +14,12 @@ import { } from '@floating-ui/react'; import { useAnnotator, useSelection } from '@annotorious/react'; -import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator'; +import { + denormalizeRectWithOffset, + toDomRectList, + type TextAnnotation, + type TextAnnotator, +} from '@recogito/text-annotator'; import './TextAnnotatorPopup.css'; @@ -88,22 +93,27 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => { }, [isOpen, selectedKey]); useEffect(() => { - if (isOpen && annotation) { - const { - target: { - selector: [{ range }] - } - } = annotation; + if (!r) return; + if (isOpen && annotation?.id) { refs.setPositionReference({ - getBoundingClientRect: range.getBoundingClientRect.bind(range), - getClientRects: range.getClientRects.bind(range) + getBoundingClientRect: () => denormalizeRectWithOffset( + r.state.store.getAnnotationBounds(annotation.id), + r.element.getBoundingClientRect() + ), + getClientRects: () => { + const rects = r.state.store.getAnnotationRects(annotation.id); + const denormalizedRects = rects.map( + rect => denormalizeRectWithOffset(rect, r.element.getBoundingClientRect()) + ); + return toDomRectList(denormalizedRects); + } }); } else { // Don't leave the reference depending on the previously selected annotation refs.setPositionReference(null); } - }, [isOpen, annotation, refs]); + }, [isOpen, annotation?.id, annotation?.target, r]); // Prevent text-annotator from handling the irrelevant events triggered from the popup const getStopEventsPropagationProps = useCallback( From a5cef013de097766c13fb6bf3e698aea53e570c2 Mon Sep 17 00:00:00 2001 From: Oleksandr Danylchenko Date: Mon, 7 Oct 2024 18:14:49 +0300 Subject: [PATCH 13/13] Fixed imports order --- .../src/TextAnnotatorPopup/TextAnnotatorPopup.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx index 198a59c4..4b04d1c4 100644 --- a/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx +++ b/packages/text-annotator-react/src/TextAnnotatorPopup/TextAnnotatorPopup.tsx @@ -1,6 +1,5 @@ import { PointerEvent, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; -import { useAnnotator, useSelection } from '@annotorious/react'; -import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator'; + import { isMobile } from './isMobile'; import { autoUpdate,