@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EventBusContext-CJjL_cCf.d.mts +462 -0
- package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
- package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
- package/dist/{ar-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-JZIO2A3B.mjs → chunk-QB52Q7EQ.mjs} +206 -146
- package/dist/chunk-QB52Q7EQ.mjs.map +1 -0
- package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -4
- package/dist/id-7U7GGVWY.mjs.map +1 -0
- package/dist/index.css +123 -85
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +715 -574
- package/dist/index.mjs +3898 -3575
- package/dist/index.mjs.map +1 -1
- package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -4
- package/dist/sv-7ZK5EQEB.mjs.map +1 -0
- package/dist/test-utils.d.mts +18 -8
- package/dist/test-utils.mjs +36 -14
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +3 -1
- package/src/components/CodeMirrorRenderer.tsx +66 -93
- package/src/components/DetectionProgressWidget.tsx +16 -5
- package/src/components/ResizeHandle.tsx +10 -4
- package/src/components/SessionExpiryBanner.tsx +2 -3
- package/src/components/SessionTimer.tsx +3 -3
- package/src/components/Toolbar.tsx +18 -9
- package/src/components/__tests__/SessionTimer.test.tsx +33 -33
- package/src/components/annotation/AnnotateToolbar.tsx +17 -15
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
- package/src/components/annotation/annotation-entries.css +10 -0
- package/src/components/annotation-popups/JsonLdView.tsx +8 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
- package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
- package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
- package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
- package/src/components/modals/ResourceSearchModal.tsx +2 -2
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
- package/src/components/navigation/NavigationTabs.css +36 -24
- package/src/components/navigation/ObservableLink.tsx +91 -0
- package/src/components/navigation/SimpleNavigation.tsx +20 -16
- package/src/components/navigation/SortableResourceTab.tsx +11 -5
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
- package/src/components/resource/AnnotateView.tsx +64 -134
- package/src/components/resource/BrowseView.tsx +86 -166
- package/src/components/resource/HistoryEvent.tsx +13 -7
- package/src/components/resource/ResourceViewer.tsx +122 -264
- package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +121 -28
- package/src/components/resource/panels/DetectSection.css +36 -1
- package/src/components/resource/panels/DetectSection.tsx +49 -15
- package/src/components/resource/panels/HighlightEntry.tsx +25 -33
- package/src/components/resource/panels/HighlightPanel.tsx +100 -25
- package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
- package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +118 -30
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
- package/src/components/settings/SettingsPanel.tsx +15 -12
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
- package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
- package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
- package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
- package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +503 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +139 -93
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +341 -524
- package/translations/ar.json +6 -3
- package/translations/bn.json +6 -3
- package/translations/cs.json +6 -3
- package/translations/da.json +6 -3
- package/translations/de.json +6 -3
- package/translations/el.json +6 -3
- package/translations/en.json +6 -3
- package/translations/es.json +6 -3
- package/translations/fa.json +6 -3
- package/translations/fi.json +6 -3
- package/translations/fr.json +6 -3
- package/translations/he.json +6 -3
- package/translations/hi.json +6 -3
- package/translations/id.json +6 -3
- package/translations/it.json +6 -3
- package/translations/ja.json +6 -3
- package/translations/ko.json +6 -3
- package/translations/ms.json +6 -3
- package/translations/nl.json +6 -3
- package/translations/no.json +6 -3
- package/translations/pl.json +6 -3
- package/translations/pt.json +6 -3
- package/translations/ro.json +6 -3
- package/translations/sv.json +6 -3
- package/translations/th.json +6 -3
- package/translations/tr.json +6 -3
- package/translations/uk.json +6 -3
- package/translations/vi.json +6 -3
- package/translations/zh.json +6 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-EMHEHPCJ.mjs.map +0 -1
- package/dist/bn-OVCI4F6X.mjs.map +0 -1
- package/dist/chunk-JZIO2A3B.mjs.map +0 -1
- package/dist/chunk-LIHZTECW.mjs.map +0 -1
- package/dist/cs-FAN66Q2F.mjs.map +0 -1
- package/dist/da-YBBIHI2O.mjs.map +0 -1
- package/dist/de-MAYU33LB.mjs.map +0 -1
- package/dist/el-MKGSWN4O.mjs.map +0 -1
- package/dist/es-52LHUWJD.mjs.map +0 -1
- package/dist/fa-FJICRANB.mjs.map +0 -1
- package/dist/fi-O455XFCR.mjs.map +0 -1
- package/dist/fr-TXIXHOOE.mjs.map +0 -1
- package/dist/he-JBSOX5IN.mjs.map +0 -1
- package/dist/hi-KGHI3XVT.mjs.map +0 -1
- package/dist/id-5OCPPZLO.mjs.map +0 -1
- package/dist/it-PNBBZSM2.mjs.map +0 -1
- package/dist/ja-LDD7R3TJ.mjs.map +0 -1
- package/dist/ko-F47ZDEY3.mjs.map +0 -1
- package/dist/ms-Z7LMXJWL.mjs.map +0 -1
- package/dist/nl-6SJFBPJ3.mjs.map +0 -1
- package/dist/no-YXPBPSGF.mjs.map +0 -1
- package/dist/pl-P4AZ2QME.mjs.map +0 -1
- package/dist/pt-LHWUS6U6.mjs.map +0 -1
- package/dist/ro-EA5J2ZON.mjs.map +0 -1
- package/dist/sv-DATBS3UQ.mjs.map +0 -1
- package/dist/th-WTFJRWPT.mjs.map +0 -1
- package/dist/tr-IKO3RXOX.mjs.map +0 -1
- package/dist/uk-CF6CTTRK.mjs.map +0 -1
- package/dist/vi-AJLTXPZQ.mjs.map +0 -1
- package/dist/zh-U3ORHHYH.mjs.map +0 -1
- /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -7,37 +7,41 @@ import { remarkAnnotations, type PreparedAnnotation } from '../../lib/remark-ann
|
|
|
7
7
|
import { rehypeRenderAnnotations } from '../../lib/rehype-render-annotations';
|
|
8
8
|
import type { components } from '@semiont/api-client';
|
|
9
9
|
import { getExactText, getTextPositionSelector, getTargetSelector, getBodySource, getMimeCategory, isPdfMimeType, resourceUri as toResourceUri } from '@semiont/api-client';
|
|
10
|
-
import
|
|
10
|
+
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
11
11
|
import { ImageViewer } from '../viewers';
|
|
12
12
|
import { AnnotateToolbar, type ClickAction } from '../annotation/AnnotateToolbar';
|
|
13
|
-
import type { AnnotationsCollection
|
|
13
|
+
import type { AnnotationsCollection } from '../../types/annotation-props';
|
|
14
14
|
|
|
15
15
|
// Lazy load PDF component to avoid SSR issues with browser PDF.js loading
|
|
16
16
|
const PdfAnnotationCanvas = lazy(() => import('../pdf-annotation/PdfAnnotationCanvas.client').then(mod => ({ default: mod.PdfAnnotationCanvas })));
|
|
17
17
|
|
|
18
18
|
type Annotation = components['schemas']['Annotation'];
|
|
19
19
|
import { useResourceAnnotations } from '../../contexts/ResourceAnnotationsContext';
|
|
20
|
+
import { useEventBus } from '../../contexts/EventBusContext';
|
|
21
|
+
import { useEventSubscriptions } from '../../contexts/useEventSubscription';
|
|
20
22
|
|
|
21
23
|
interface Props {
|
|
22
24
|
content: string;
|
|
23
25
|
mimeType: string;
|
|
24
26
|
resourceUri: string;
|
|
25
27
|
annotations: AnnotationsCollection;
|
|
26
|
-
handlers?: AnnotationHandlers;
|
|
27
28
|
hoveredAnnotationId?: string | null;
|
|
28
29
|
hoveredCommentId?: string | null;
|
|
29
30
|
selectedClick?: ClickAction;
|
|
30
|
-
onClickChange?: (motivation: ClickAction) => void;
|
|
31
31
|
annotateMode: boolean;
|
|
32
|
-
onAnnotateModeToggle: () => void;
|
|
33
|
-
annotators: Record<string, Annotator>;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
/**
|
|
37
35
|
* Convert W3C Annotations to simplified format for remark plugin.
|
|
38
36
|
* Extracts position info and converts start/end to offset/length.
|
|
39
37
|
*/
|
|
40
|
-
function prepareAnnotations(annotations: Annotation[]
|
|
38
|
+
function prepareAnnotations(annotations: Annotation[]): PreparedAnnotation[] {
|
|
39
|
+
/**
|
|
40
|
+
* View component for browsing resources with rendered annotations
|
|
41
|
+
*
|
|
42
|
+
* @emits annotation:click - Annotation clicked in browse view. Payload: { annotationId: string, motivation: Motivation }
|
|
43
|
+
* @emits annotation:hover - Annotation hovered in browse view. Payload: { annotationId: string | null }
|
|
44
|
+
*/
|
|
41
45
|
return annotations
|
|
42
46
|
.map(ann => {
|
|
43
47
|
const targetSelector = getTargetSelector(ann.target);
|
|
@@ -45,8 +49,8 @@ function prepareAnnotations(annotations: Annotation[], annotators: Record<string
|
|
|
45
49
|
const start = posSelector?.start ?? 0;
|
|
46
50
|
const end = posSelector?.end ?? 0;
|
|
47
51
|
|
|
48
|
-
// Use
|
|
49
|
-
const type = Object.values(
|
|
52
|
+
// Use ANNOTATORS registry to determine type
|
|
53
|
+
const type = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(ann))?.internalType || 'highlight';
|
|
50
54
|
|
|
51
55
|
return {
|
|
52
56
|
id: ann.id,
|
|
@@ -59,121 +63,82 @@ function prepareAnnotations(annotations: Annotation[], annotators: Record<string
|
|
|
59
63
|
});
|
|
60
64
|
}
|
|
61
65
|
|
|
66
|
+
/**
|
|
67
|
+
* View component for browsing annotated resources in read-only mode
|
|
68
|
+
*
|
|
69
|
+
* @emits annotation:click - User clicked on annotation. Payload: { annotationId: string, motivation: Motivation }
|
|
70
|
+
* @emits annotation:hover - User hovered over annotation. Payload: { annotationId: string | null }
|
|
71
|
+
*
|
|
72
|
+
* @subscribes annotation:hover - Highlight annotation on hover. Payload: { annotationId: string | null }
|
|
73
|
+
* @subscribes annotation:focus - Scroll to and highlight annotation. Payload: { annotationId: string }
|
|
74
|
+
*/
|
|
62
75
|
export function BrowseView({
|
|
63
76
|
content,
|
|
64
77
|
mimeType,
|
|
65
78
|
resourceUri,
|
|
66
79
|
annotations,
|
|
67
|
-
handlers,
|
|
68
|
-
hoveredAnnotationId,
|
|
69
|
-
hoveredCommentId,
|
|
70
80
|
selectedClick = 'detail',
|
|
71
|
-
|
|
72
|
-
annotateMode,
|
|
73
|
-
onAnnotateModeToggle,
|
|
74
|
-
annotators
|
|
81
|
+
annotateMode
|
|
75
82
|
}: Props) {
|
|
76
83
|
const { newAnnotationIds } = useResourceAnnotations();
|
|
84
|
+
const eventBus = useEventBus();
|
|
77
85
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
78
86
|
|
|
79
87
|
const category = getMimeCategory(mimeType);
|
|
80
88
|
|
|
81
89
|
const { highlights, references, assessments, comments, tags } = annotations;
|
|
82
90
|
|
|
83
|
-
// Extract individual handlers from grouped object
|
|
84
|
-
const onAnnotationClick = handlers?.onClick;
|
|
85
|
-
const onAnnotationHover = handlers?.onHover;
|
|
86
|
-
const onCommentHover = handlers?.onCommentHover;
|
|
87
|
-
|
|
88
91
|
const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags];
|
|
89
92
|
|
|
90
|
-
const preparedAnnotations = prepareAnnotations(allAnnotations
|
|
91
|
-
|
|
92
|
-
// Create a map of annotation ID -> full annotation for click handling
|
|
93
|
-
const map = new Map<string, Annotation>();
|
|
94
|
-
for (const ann of allAnnotations) {
|
|
95
|
-
map.set(ann.id, ann);
|
|
96
|
-
}
|
|
97
|
-
const annotationMap = map;
|
|
98
|
-
|
|
99
|
-
// Wrapper for annotation hover that routes based on registry metadata
|
|
100
|
-
const handleAnnotationHover = useCallback((annotationId: string | null) => {
|
|
101
|
-
if (annotationId) {
|
|
102
|
-
const annotation = annotationMap.get(annotationId);
|
|
103
|
-
const metadata = annotation ? Object.values(annotators).find(a => a.matchesAnnotation(annotation!)) : null;
|
|
104
|
-
|
|
105
|
-
// Route to side panel if annotation type has one
|
|
106
|
-
if (metadata?.hasSidePanel) {
|
|
107
|
-
// Clear the other hover state when switching
|
|
108
|
-
if (onAnnotationHover) onAnnotationHover(null);
|
|
109
|
-
if (onCommentHover) onCommentHover(annotationId);
|
|
110
|
-
return;
|
|
111
|
-
} else {
|
|
112
|
-
// Clear the other hover state when switching
|
|
113
|
-
if (onCommentHover) onCommentHover(null);
|
|
114
|
-
if (onAnnotationHover) onAnnotationHover(annotationId);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Clear both when null
|
|
119
|
-
if (onAnnotationHover) onAnnotationHover(null);
|
|
120
|
-
if (onCommentHover) onCommentHover(null);
|
|
121
|
-
}, [annotationMap, onAnnotationHover, onCommentHover, annotators]);
|
|
93
|
+
const preparedAnnotations = prepareAnnotations(allAnnotations);
|
|
122
94
|
|
|
123
|
-
// Attach click
|
|
95
|
+
// Attach click handler, hover handler, and animations after render
|
|
124
96
|
useEffect(() => {
|
|
125
97
|
if (!containerRef.current) return;
|
|
126
98
|
|
|
127
99
|
const container = containerRef.current;
|
|
128
100
|
|
|
129
|
-
//
|
|
130
|
-
const
|
|
101
|
+
// Single click handler for the container
|
|
102
|
+
const handleClick = (e: MouseEvent) => {
|
|
103
|
+
const target = e.target as HTMLElement;
|
|
104
|
+
const annotationElement = target.closest('[data-annotation-id]');
|
|
105
|
+
if (!annotationElement) return;
|
|
131
106
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const target = e.currentTarget as HTMLElement;
|
|
135
|
-
const annotationId = target.getAttribute('data-annotation-id');
|
|
136
|
-
const annotationType = target.getAttribute('data-annotation-type');
|
|
107
|
+
const annotationId = annotationElement.getAttribute('data-annotation-id');
|
|
108
|
+
const annotationType = annotationElement.getAttribute('data-annotation-type');
|
|
137
109
|
|
|
138
|
-
if (annotationId && annotationType === 'reference'
|
|
139
|
-
const annotation =
|
|
110
|
+
if (annotationId && annotationType === 'reference') {
|
|
111
|
+
const annotation = allAnnotations.find(a => a.id === annotationId);
|
|
140
112
|
if (annotation) {
|
|
141
|
-
|
|
113
|
+
eventBus.emit('annotation:click', { annotationId, motivation: annotation.motivation });
|
|
142
114
|
}
|
|
143
115
|
}
|
|
144
116
|
};
|
|
145
117
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
148
|
-
const target = e.
|
|
149
|
-
const
|
|
118
|
+
// Single mouseover handler for the container - fires once on enter
|
|
119
|
+
const handleMouseOver = (e: MouseEvent) => {
|
|
120
|
+
const target = e.target as HTMLElement;
|
|
121
|
+
const annotationElement = target.closest('[data-annotation-id]');
|
|
122
|
+
const annotationId = annotationElement?.getAttribute('data-annotation-id');
|
|
123
|
+
|
|
150
124
|
if (annotationId) {
|
|
151
|
-
|
|
125
|
+
eventBus.emit('annotation:hover', { annotationId });
|
|
152
126
|
}
|
|
153
127
|
};
|
|
154
128
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
129
|
+
// Single mouseout handler for the container - fires once on exit
|
|
130
|
+
const handleMouseOut = (e: MouseEvent) => {
|
|
131
|
+
const target = e.target as HTMLElement;
|
|
132
|
+
const annotationElement = target.closest('[data-annotation-id]');
|
|
158
133
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
annotationSpans.forEach((span) => {
|
|
163
|
-
const annotationType = span.getAttribute('data-annotation-type');
|
|
164
|
-
if (annotationType === 'reference') {
|
|
165
|
-
span.addEventListener('click', handleClick);
|
|
166
|
-
clickHandlers.push({ element: span, handler: handleClick });
|
|
134
|
+
if (annotationElement) {
|
|
135
|
+
eventBus.emit('annotation:hover', { annotationId: null });
|
|
167
136
|
}
|
|
168
|
-
|
|
169
|
-
// Add hover handlers for all annotation types
|
|
170
|
-
span.addEventListener('mouseenter', handleMouseEnter);
|
|
171
|
-
span.addEventListener('mouseleave', handleMouseLeave);
|
|
172
|
-
hoverHandlers.push({ element: span, enterHandler: handleMouseEnter, leaveHandler: handleMouseLeave });
|
|
173
|
-
});
|
|
137
|
+
};
|
|
174
138
|
|
|
175
139
|
// Apply animation classes to new annotations
|
|
176
140
|
if (newAnnotationIds) {
|
|
141
|
+
const annotationSpans = container.querySelectorAll('[data-annotation-id]');
|
|
177
142
|
annotationSpans.forEach((span) => {
|
|
178
143
|
const annotationId = span.getAttribute('data-annotation-id');
|
|
179
144
|
if (annotationId && newAnnotationIds.has(annotationId)) {
|
|
@@ -182,27 +147,26 @@ export function BrowseView({
|
|
|
182
147
|
});
|
|
183
148
|
}
|
|
184
149
|
|
|
185
|
-
|
|
150
|
+
container.addEventListener('click', handleClick);
|
|
151
|
+
container.addEventListener('mouseover', handleMouseOver);
|
|
152
|
+
container.addEventListener('mouseout', handleMouseOut);
|
|
153
|
+
|
|
186
154
|
return () => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
hoverHandlers.forEach(({ element, enterHandler, leaveHandler }) => {
|
|
191
|
-
element.removeEventListener('mouseenter', enterHandler);
|
|
192
|
-
element.removeEventListener('mouseleave', leaveHandler);
|
|
193
|
-
});
|
|
155
|
+
container.removeEventListener('click', handleClick);
|
|
156
|
+
container.removeEventListener('mouseover', handleMouseOver);
|
|
157
|
+
container.removeEventListener('mouseout', handleMouseOut);
|
|
194
158
|
};
|
|
195
|
-
}, [content, allAnnotations,
|
|
159
|
+
}, [content, allAnnotations, newAnnotationIds]);
|
|
196
160
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
if (!containerRef.current || !
|
|
161
|
+
// Helper to scroll annotation into view with pulse effect
|
|
162
|
+
const scrollToAnnotation = useCallback((annotationId: string | null, removePulse = false) => {
|
|
163
|
+
if (!containerRef.current || !annotationId) return;
|
|
200
164
|
|
|
201
165
|
const element = containerRef.current.querySelector(
|
|
202
|
-
`[data-annotation-id="${CSS.escape(
|
|
166
|
+
`[data-annotation-id="${CSS.escape(annotationId)}"]`
|
|
203
167
|
) as HTMLElement;
|
|
204
168
|
|
|
205
|
-
if (!element) return
|
|
169
|
+
if (!element) return;
|
|
206
170
|
|
|
207
171
|
// Find the scroll container
|
|
208
172
|
const scrollContainer = element.closest('.semiont-browse-view__content') as HTMLElement;
|
|
@@ -228,59 +192,28 @@ export function BrowseView({
|
|
|
228
192
|
}
|
|
229
193
|
|
|
230
194
|
// Add pulse effect
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
clearTimeout(timeoutId);
|
|
237
|
-
element.classList.remove('annotation-pulse');
|
|
238
|
-
};
|
|
239
|
-
}, [hoveredAnnotationId]);
|
|
240
|
-
|
|
241
|
-
// Handle hoveredCommentId - scroll and pulse
|
|
242
|
-
useEffect(() => {
|
|
243
|
-
if (!containerRef.current || !hoveredCommentId) return undefined;
|
|
244
|
-
|
|
245
|
-
const element = containerRef.current.querySelector(
|
|
246
|
-
`[data-annotation-id="${CSS.escape(hoveredCommentId)}"]`
|
|
247
|
-
) as HTMLElement;
|
|
248
|
-
|
|
249
|
-
if (!element) return undefined;
|
|
250
|
-
|
|
251
|
-
// Find the scroll container
|
|
252
|
-
const scrollContainer = element.closest('.semiont-browse-view__content') as HTMLElement;
|
|
253
|
-
|
|
254
|
-
if (scrollContainer) {
|
|
255
|
-
// Check visibility within the scroll container
|
|
256
|
-
const elementRect = element.getBoundingClientRect();
|
|
257
|
-
const containerRect = scrollContainer.getBoundingClientRect();
|
|
258
|
-
|
|
259
|
-
const isVisible =
|
|
260
|
-
elementRect.top >= containerRect.top &&
|
|
261
|
-
elementRect.bottom <= containerRect.bottom;
|
|
262
|
-
|
|
263
|
-
if (!isVisible) {
|
|
264
|
-
// Scroll using container.scrollTo to avoid scrolling ancestors
|
|
265
|
-
const elementTop = element.offsetTop;
|
|
266
|
-
const containerHeight = scrollContainer.clientHeight;
|
|
267
|
-
const elementHeight = element.offsetHeight;
|
|
268
|
-
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
269
|
-
|
|
270
|
-
scrollContainer.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
271
|
-
}
|
|
195
|
+
element.classList.add('annotation-pulse');
|
|
196
|
+
if (removePulse) {
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
element.classList.remove('annotation-pulse');
|
|
199
|
+
}, 2000);
|
|
272
200
|
}
|
|
201
|
+
}, []);
|
|
273
202
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
203
|
+
// Handle hover events for scrolling
|
|
204
|
+
// Event handlers (extracted to avoid inline arrow functions)
|
|
205
|
+
const handleAnnotationHover = useCallback(({ annotationId }: { annotationId: string | null }) => {
|
|
206
|
+
scrollToAnnotation(annotationId);
|
|
207
|
+
}, [scrollToAnnotation]);
|
|
278
208
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
209
|
+
const handleAnnotationFocus = useCallback(({ annotationId }: { annotationId: string | null }) => {
|
|
210
|
+
scrollToAnnotation(annotationId, true);
|
|
211
|
+
}, [scrollToAnnotation]);
|
|
212
|
+
|
|
213
|
+
useEventSubscriptions({
|
|
214
|
+
'annotation:hover': handleAnnotationHover,
|
|
215
|
+
'annotation:focus': handleAnnotationFocus,
|
|
216
|
+
});
|
|
284
217
|
|
|
285
218
|
// Route to appropriate viewer based on MIME type category
|
|
286
219
|
switch (category) {
|
|
@@ -290,13 +223,10 @@ export function BrowseView({
|
|
|
290
223
|
<AnnotateToolbar
|
|
291
224
|
selectedMotivation={null}
|
|
292
225
|
selectedClick={selectedClick}
|
|
293
|
-
onSelectionChange={() => {}}
|
|
294
|
-
onClickChange={onClickChange || (() => {})}
|
|
295
226
|
showSelectionGroup={false}
|
|
296
227
|
showDeleteButton={false}
|
|
297
228
|
annotateMode={annotateMode}
|
|
298
|
-
|
|
299
|
-
annotators={annotators}
|
|
229
|
+
annotators={ANNOTATORS}
|
|
300
230
|
/>
|
|
301
231
|
<div ref={containerRef} className="semiont-browse-view__content">
|
|
302
232
|
<ReactMarkdown
|
|
@@ -322,13 +252,10 @@ export function BrowseView({
|
|
|
322
252
|
<AnnotateToolbar
|
|
323
253
|
selectedMotivation={null}
|
|
324
254
|
selectedClick={selectedClick}
|
|
325
|
-
onSelectionChange={() => {}}
|
|
326
|
-
onClickChange={onClickChange || (() => {})}
|
|
327
255
|
showSelectionGroup={false}
|
|
328
256
|
showDeleteButton={false}
|
|
329
257
|
annotateMode={annotateMode}
|
|
330
|
-
|
|
331
|
-
annotators={annotators}
|
|
258
|
+
annotators={ANNOTATORS}
|
|
332
259
|
/>
|
|
333
260
|
<div ref={containerRef} className="semiont-browse-view__content">
|
|
334
261
|
<Suspense fallback={<div className="semiont-browse-view__loading">Loading PDF viewer...</div>}>
|
|
@@ -337,10 +264,6 @@ export function BrowseView({
|
|
|
337
264
|
existingAnnotations={allAnnotations}
|
|
338
265
|
drawingMode={null}
|
|
339
266
|
selectedMotivation={null}
|
|
340
|
-
onAnnotationCreate={() => {}}
|
|
341
|
-
{...(onAnnotationClick && { onAnnotationClick })}
|
|
342
|
-
{...(onAnnotationHover && { onAnnotationHover })}
|
|
343
|
-
hoveredAnnotationId={hoveredCommentId || hoveredAnnotationId || null}
|
|
344
267
|
/>
|
|
345
268
|
</Suspense>
|
|
346
269
|
</div>
|
|
@@ -354,13 +277,10 @@ export function BrowseView({
|
|
|
354
277
|
<AnnotateToolbar
|
|
355
278
|
selectedMotivation={null}
|
|
356
279
|
selectedClick={selectedClick}
|
|
357
|
-
onSelectionChange={() => {}}
|
|
358
|
-
onClickChange={onClickChange || (() => {})}
|
|
359
280
|
showSelectionGroup={false}
|
|
360
281
|
showDeleteButton={false}
|
|
361
282
|
annotateMode={annotateMode}
|
|
362
|
-
|
|
363
|
-
annotators={annotators}
|
|
283
|
+
annotators={ANNOTATORS}
|
|
364
284
|
/>
|
|
365
285
|
<div ref={containerRef} className="semiont-browse-view__content">
|
|
366
286
|
<ImageViewer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useRef, useCallback } from 'react';
|
|
3
|
+
import React, { useRef, useCallback, useEffect } from 'react';
|
|
4
4
|
import type { RouteBuilder, LinkComponentProps } from '../../contexts/RoutingContext';
|
|
5
5
|
import type { StoredEvent, ResourceEventType } from '@semiont/core';
|
|
6
6
|
import { getAnnotationUriFromEvent } from '@semiont/core';
|
|
@@ -46,9 +46,15 @@ export function HistoryEvent({
|
|
|
46
46
|
const entityTypes = getEventEntityTypes(event);
|
|
47
47
|
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
48
48
|
|
|
49
|
+
// Store callback in ref to avoid including in dependency arrays
|
|
50
|
+
const onEventHoverRef = useRef(onEventHover);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
onEventHoverRef.current = onEventHover;
|
|
53
|
+
});
|
|
54
|
+
|
|
49
55
|
// Handle hover on emoji icon with 300ms delay
|
|
50
56
|
const handleEmojiMouseEnter = useCallback(() => {
|
|
51
|
-
if (!annotationUri || !
|
|
57
|
+
if (!annotationUri || !onEventHoverRef.current) return;
|
|
52
58
|
|
|
53
59
|
// Clear any existing timeout
|
|
54
60
|
if (hoverTimeoutRef.current) {
|
|
@@ -57,9 +63,9 @@ export function HistoryEvent({
|
|
|
57
63
|
|
|
58
64
|
// Set new timeout for 300ms delay
|
|
59
65
|
hoverTimeoutRef.current = setTimeout(() => {
|
|
60
|
-
|
|
66
|
+
onEventHoverRef.current?.(annotationUri);
|
|
61
67
|
}, 300);
|
|
62
|
-
}, [annotationUri
|
|
68
|
+
}, [annotationUri]);
|
|
63
69
|
|
|
64
70
|
const handleEmojiMouseLeave = useCallback(() => {
|
|
65
71
|
// Clear the timeout if mouse leaves before 500ms
|
|
@@ -69,10 +75,10 @@ export function HistoryEvent({
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
// Clear the hover state
|
|
72
|
-
if (
|
|
73
|
-
|
|
78
|
+
if (onEventHoverRef.current) {
|
|
79
|
+
onEventHoverRef.current(null);
|
|
74
80
|
}
|
|
75
|
-
}, [
|
|
81
|
+
}, []);
|
|
76
82
|
|
|
77
83
|
// Interactive events should be buttons for keyboard accessibility
|
|
78
84
|
const EventWrapper = annotationUri ? 'button' : 'div';
|