@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
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
|
-
// useRouter removed - using window.location for navigation
|
|
5
4
|
import { useTranslations } from '../../contexts/TranslationContext';
|
|
6
5
|
import { AnnotateView, type SelectionMotivation, type ClickAction, type ShapeType } from './AnnotateView';
|
|
7
6
|
import { BrowseView } from './BrowseView';
|
|
8
7
|
import { PopupContainer } from '../annotation-popups/SharedPopupElements';
|
|
9
8
|
import { JsonLdView } from '../annotation-popups/JsonLdView';
|
|
10
|
-
import type { components
|
|
9
|
+
import type { components } from '@semiont/api-client';
|
|
11
10
|
import { getExactText, getTargetSelector, resourceUri, isHighlight, isAssessment, isReference, isComment, isTag, getBodySource } from '@semiont/api-client';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { useEventBus } from '../../contexts/EventBusContext';
|
|
12
|
+
import { useEventSubscriptions } from '../../contexts/useEventSubscription';
|
|
14
13
|
import { useCacheManager } from '../../contexts/CacheContext';
|
|
15
|
-
import
|
|
14
|
+
import { useObservableExternalNavigation } from '../../hooks/useObservableNavigation';
|
|
15
|
+
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
16
16
|
import type { AnnotationsCollection } from '../../types/annotation-props';
|
|
17
17
|
import { getSelectorType, getSelectedShapeForSelectorType, saveSelectedShapeForSelectorType } from '../../lib/media-shapes';
|
|
18
18
|
|
|
19
19
|
type Annotation = components['schemas']['Annotation'];
|
|
20
20
|
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
21
|
-
type Motivation = components['schemas']['Motivation'];
|
|
22
|
-
|
|
23
|
-
// Unified pending annotation type - all human-created annotations flow through this
|
|
24
|
-
interface PendingAnnotation {
|
|
25
|
-
selector: Selector | Selector[];
|
|
26
|
-
motivation: Motivation;
|
|
27
|
-
}
|
|
28
21
|
|
|
29
22
|
/**
|
|
30
23
|
* ResourceViewer - Display and interact with resource content and annotations
|
|
@@ -49,23 +42,37 @@ interface Props {
|
|
|
49
42
|
annotations: AnnotationsCollection;
|
|
50
43
|
generatingReferenceId?: string | null;
|
|
51
44
|
showLineNumbers?: boolean;
|
|
52
|
-
|
|
53
|
-
annotators: Record<string, Annotator>;
|
|
45
|
+
hoveredAnnotationId?: string | null;
|
|
54
46
|
}
|
|
55
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @emits annotation:delete - User requested to delete annotation. Payload: { annotationId: string }
|
|
50
|
+
* @emits panel:open - Request to open panel with annotation. Payload: { panel: string, scrollToAnnotationId?: string, motivation?: Motivation }
|
|
51
|
+
*
|
|
52
|
+
* @subscribes view:mode-toggled - Toggles between browse and annotate mode. Payload: { mode: 'browse' | 'annotate' }
|
|
53
|
+
* @subscribes annotation:added - New annotation was added. Payload: { annotation: Annotation }
|
|
54
|
+
* @subscribes annotation:removed - Annotation was removed. Payload: { annotationId: string }
|
|
55
|
+
* @subscribes annotation:updated - Annotation was updated. Payload: { annotation: Annotation }
|
|
56
|
+
* @subscribes toolbar:selection-changed - Text selection tool changed. Payload: { selection: boolean }
|
|
57
|
+
* @subscribes toolbar:click-changed - Click annotation tool changed. Payload: { click: 'detail' | 'scroll' | null }
|
|
58
|
+
* @subscribes toolbar:shape-changed - Drawing shape changed. Payload: { shape: string }
|
|
59
|
+
* @subscribes annotation:click - User clicked on annotation. Payload: { annotationId: string }
|
|
60
|
+
*/
|
|
56
61
|
export function ResourceViewer({
|
|
57
62
|
resource,
|
|
58
63
|
annotations,
|
|
59
64
|
generatingReferenceId,
|
|
60
65
|
showLineNumbers = false,
|
|
61
|
-
|
|
62
|
-
annotators
|
|
66
|
+
hoveredAnnotationId: hoveredAnnotationIdProp
|
|
63
67
|
}: Props) {
|
|
64
68
|
const t = useTranslations('ResourceViewer');
|
|
65
69
|
const documentViewerRef = useRef<HTMLDivElement>(null);
|
|
66
70
|
|
|
67
71
|
// Get unified event bus for emitting UI events
|
|
68
|
-
const eventBus =
|
|
72
|
+
const eventBus = useEventBus();
|
|
73
|
+
|
|
74
|
+
// Get observable navigation for event-driven routing
|
|
75
|
+
const navigate = useObservableExternalNavigation();
|
|
69
76
|
|
|
70
77
|
const { highlights, references, assessments, comments, tags } = annotations;
|
|
71
78
|
|
|
@@ -102,50 +109,35 @@ export function ResourceViewer({
|
|
|
102
109
|
}
|
|
103
110
|
}, [annotateMode]);
|
|
104
111
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
112
|
+
// Event handlers (extracted to avoid inline arrow functions)
|
|
113
|
+
const handleViewModeToggle = useCallback(() => {
|
|
107
114
|
setAnnotateMode(prev => !prev);
|
|
108
115
|
}, []);
|
|
109
116
|
|
|
110
117
|
// Determine active view based on annotate mode
|
|
111
118
|
const activeView = annotateMode ? 'annotate' : 'browse';
|
|
112
|
-
const {
|
|
113
|
-
deleteAnnotation,
|
|
114
|
-
createAnnotation
|
|
115
|
-
} = useResourceAnnotations();
|
|
116
119
|
|
|
117
120
|
// Event-based cache invalidation - subscribe to make-meaning events
|
|
118
121
|
// This replaces manual onRefetchAnnotations calls with automatic updates
|
|
119
122
|
const cacheManager = useCacheManager();
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
// Annotation events - invalidate cache when annotations change
|
|
125
|
-
const handleAnnotationAdded = () => {
|
|
124
|
+
const handleAnnotationAdded = useCallback(() => {
|
|
125
|
+
if (cacheManager) {
|
|
126
126
|
cacheManager.invalidateAnnotations(rUri);
|
|
127
|
-
}
|
|
127
|
+
}
|
|
128
|
+
}, [cacheManager, rUri]);
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
const handleAnnotationRemoved = useCallback(() => {
|
|
131
|
+
if (cacheManager) {
|
|
130
132
|
cacheManager.invalidateAnnotations(rUri);
|
|
131
|
-
}
|
|
133
|
+
}
|
|
134
|
+
}, [cacheManager, rUri]);
|
|
132
135
|
|
|
133
|
-
|
|
136
|
+
const handleAnnotationUpdated = useCallback(() => {
|
|
137
|
+
if (cacheManager) {
|
|
134
138
|
cacheManager.invalidateAnnotations(rUri);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Subscribe to make-meaning annotation events
|
|
138
|
-
eventBus.on('annotation:added', handleAnnotationAdded);
|
|
139
|
-
eventBus.on('annotation:removed', handleAnnotationRemoved);
|
|
140
|
-
eventBus.on('annotation:updated', handleAnnotationUpdated);
|
|
141
|
-
|
|
142
|
-
// Cleanup subscriptions
|
|
143
|
-
return () => {
|
|
144
|
-
eventBus.off('annotation:added', handleAnnotationAdded);
|
|
145
|
-
eventBus.off('annotation:removed', handleAnnotationRemoved);
|
|
146
|
-
eventBus.off('annotation:updated', handleAnnotationUpdated);
|
|
147
|
-
};
|
|
148
|
-
}, [eventBus, cacheManager, rUri]);
|
|
139
|
+
}
|
|
140
|
+
}, [cacheManager, rUri]);
|
|
149
141
|
|
|
150
142
|
// Annotation toolbar state - persisted in localStorage
|
|
151
143
|
const [selectedMotivation, setSelectedMotivation] = useState<SelectionMotivation | null>(() => {
|
|
@@ -177,6 +169,19 @@ export function ResourceViewer({
|
|
|
177
169
|
return getSelectedShapeForSelectorType(selectorType);
|
|
178
170
|
});
|
|
179
171
|
|
|
172
|
+
// Toolbar event handlers (extracted to avoid inline arrow functions)
|
|
173
|
+
const handleToolbarSelectionChanged = useCallback(({ motivation }: { motivation: string | null }) => {
|
|
174
|
+
setSelectedMotivation(motivation as SelectionMotivation | null);
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
const handleToolbarClickChanged = useCallback(({ action }: { action: string }) => {
|
|
178
|
+
setSelectedClick(action as ClickAction);
|
|
179
|
+
}, []);
|
|
180
|
+
|
|
181
|
+
const handleToolbarShapeChanged = useCallback(({ shape }: { shape: string }) => {
|
|
182
|
+
setSelectedShape(shape as ShapeType);
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
180
185
|
// Persist toolbar state to localStorage
|
|
181
186
|
useEffect(() => {
|
|
182
187
|
if (selectedMotivation === null) {
|
|
@@ -214,7 +219,8 @@ export function ResourceViewer({
|
|
|
214
219
|
} | null>(null);
|
|
215
220
|
|
|
216
221
|
// Internal UI state for hover, focus, and scroll
|
|
217
|
-
|
|
222
|
+
// Use prop value when provided (controlled by parent), otherwise null
|
|
223
|
+
const hoveredAnnotationId = hoveredAnnotationIdProp ?? null;
|
|
218
224
|
const [hoveredCommentId, _setHoveredCommentId] = useState<string | null>(null);
|
|
219
225
|
const [scrollToAnnotationId, setScrollToAnnotationId] = useState<string | null>(null);
|
|
220
226
|
const [_focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
@@ -241,19 +247,14 @@ export function ResourceViewer({
|
|
|
241
247
|
};
|
|
242
248
|
};
|
|
243
249
|
|
|
244
|
-
// Handle deleting annotations -
|
|
245
|
-
const handleDeleteAnnotation = useCallback(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// Cache invalidation now handled by annotation:removed event
|
|
249
|
-
} catch (err) {
|
|
250
|
-
console.error('Failed to delete annotation:', err);
|
|
251
|
-
}
|
|
252
|
-
}, [deleteAnnotation, rUri]);
|
|
250
|
+
// Handle deleting annotations - emit event instead of direct call
|
|
251
|
+
const handleDeleteAnnotation = useCallback((id: string) => {
|
|
252
|
+
eventBus.emit('annotation:delete', { annotationId: id });
|
|
253
|
+
}, []); // eventBus is stable
|
|
253
254
|
|
|
254
255
|
// Handle annotation clicks - memoized
|
|
255
256
|
const handleAnnotationClick = useCallback((annotation: Annotation, event?: React.MouseEvent) => {
|
|
256
|
-
const metadata = Object.values(
|
|
257
|
+
const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(annotation));
|
|
257
258
|
|
|
258
259
|
// If annotation has a side panel, only open it when Detail mode is active
|
|
259
260
|
// For delete/jsonld/follow modes, let those handlers below process it
|
|
@@ -276,10 +277,10 @@ export function ResourceViewer({
|
|
|
276
277
|
if (selectedClick === 'follow' && isReference(annotation)) {
|
|
277
278
|
const bodySource = getBodySource(annotation.body);
|
|
278
279
|
if (bodySource) {
|
|
279
|
-
// Navigate to the linked resource
|
|
280
|
+
// Navigate to the linked resource - emits 'navigation:external-navigate' event
|
|
280
281
|
const resourceId = bodySource.split('/resources/')[1];
|
|
281
282
|
if (resourceId) {
|
|
282
|
-
|
|
283
|
+
navigate(`/know/resource/${resourceId}`, { resourceId });
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
return;
|
|
@@ -305,202 +306,65 @@ export function ResourceViewer({
|
|
|
305
306
|
setDeleteConfirmation({ annotation, position });
|
|
306
307
|
return;
|
|
307
308
|
}
|
|
308
|
-
}, [annotateMode, selectedClick,
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const annotation = await createAnnotation(
|
|
336
|
-
rUri,
|
|
337
|
-
motivation,
|
|
338
|
-
selectors,
|
|
339
|
-
[]
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
// Focus the new annotation to trigger panel tab switch
|
|
343
|
-
if (annotation) {
|
|
344
|
-
focusAnnotation(annotation.id);
|
|
345
|
-
}
|
|
346
|
-
// Cache invalidation now handled by annotation:added event
|
|
347
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
348
|
-
// Image annotations use generic createAnnotation
|
|
349
|
-
await createAnnotation(
|
|
350
|
-
rUri,
|
|
351
|
-
motivation,
|
|
352
|
-
{ type: 'SvgSelector', value: selector.value },
|
|
353
|
-
[]
|
|
354
|
-
);
|
|
355
|
-
// Cache invalidation now handled by annotation:added event
|
|
356
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
357
|
-
// PDF annotations use FragmentSelector
|
|
358
|
-
await createAnnotation(
|
|
359
|
-
rUri,
|
|
360
|
-
motivation,
|
|
361
|
-
{
|
|
362
|
-
type: 'FragmentSelector',
|
|
363
|
-
value: selector.value,
|
|
364
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
365
|
-
},
|
|
366
|
-
[]
|
|
367
|
-
);
|
|
368
|
-
// Cache invalidation now handled by annotation:added event
|
|
369
|
-
}
|
|
370
|
-
break;
|
|
371
|
-
|
|
372
|
-
case 'commenting':
|
|
373
|
-
if (selector.type === 'TextQuoteSelector' && selector.exact) {
|
|
374
|
-
// Text: emit UI event for comment creation
|
|
375
|
-
eventBus.emit('ui:selection:comment-requested', {
|
|
376
|
-
exact: selector.exact,
|
|
377
|
-
start: selector.start || 0,
|
|
378
|
-
end: selector.end || 0
|
|
379
|
-
});
|
|
380
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
381
|
-
// Image: create annotation, then open panel
|
|
382
|
-
const annotation = await createAnnotation(
|
|
383
|
-
rUri,
|
|
384
|
-
motivation,
|
|
385
|
-
{ type: 'SvgSelector', value: selector.value },
|
|
386
|
-
[]
|
|
387
|
-
);
|
|
388
|
-
if (annotation) {
|
|
389
|
-
focusAnnotation(annotation.id);
|
|
390
|
-
}
|
|
391
|
-
// Cache invalidation now handled by annotation:added event
|
|
392
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
393
|
-
// PDF: create annotation, then open panel
|
|
394
|
-
const annotation = await createAnnotation(
|
|
395
|
-
rUri,
|
|
396
|
-
motivation,
|
|
397
|
-
{
|
|
398
|
-
type: 'FragmentSelector',
|
|
399
|
-
value: selector.value,
|
|
400
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
401
|
-
},
|
|
402
|
-
[]
|
|
403
|
-
);
|
|
404
|
-
if (annotation) {
|
|
405
|
-
focusAnnotation(annotation.id);
|
|
406
|
-
}
|
|
407
|
-
// Cache invalidation now handled by annotation:added event
|
|
408
|
-
}
|
|
409
|
-
break;
|
|
410
|
-
|
|
411
|
-
case 'tagging':
|
|
412
|
-
if (selector.type === 'TextQuoteSelector' && selector.exact) {
|
|
413
|
-
// Text: emit UI event for tag creation
|
|
414
|
-
eventBus.emit('ui:selection:tag-requested', {
|
|
415
|
-
exact: selector.exact,
|
|
416
|
-
start: selector.start || 0,
|
|
417
|
-
end: selector.end || 0
|
|
418
|
-
});
|
|
419
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
420
|
-
// Image: create annotation, then open panel
|
|
421
|
-
const annotation = await createAnnotation(
|
|
422
|
-
rUri,
|
|
423
|
-
motivation,
|
|
424
|
-
{ type: 'SvgSelector', value: selector.value },
|
|
425
|
-
[]
|
|
426
|
-
);
|
|
427
|
-
if (annotation) {
|
|
428
|
-
focusAnnotation(annotation.id);
|
|
429
|
-
}
|
|
430
|
-
// Cache invalidation now handled by annotation:added event
|
|
431
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
432
|
-
// PDF: create annotation, then open panel
|
|
433
|
-
const annotation = await createAnnotation(
|
|
434
|
-
rUri,
|
|
435
|
-
motivation,
|
|
436
|
-
{
|
|
437
|
-
type: 'FragmentSelector',
|
|
438
|
-
value: selector.value,
|
|
439
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
440
|
-
},
|
|
441
|
-
[]
|
|
442
|
-
);
|
|
443
|
-
if (annotation) {
|
|
444
|
-
focusAnnotation(annotation.id);
|
|
445
|
-
}
|
|
446
|
-
// Cache invalidation now handled by annotation:added event
|
|
447
|
-
}
|
|
448
|
-
break;
|
|
449
|
-
|
|
450
|
-
case 'linking':
|
|
451
|
-
// Emit UI event for reference creation (text, image, or PDF selections)
|
|
452
|
-
if (selector.type === 'TextQuoteSelector' && selector.exact) {
|
|
453
|
-
eventBus.emit('ui:selection:reference-requested', {
|
|
454
|
-
exact: selector.exact,
|
|
455
|
-
start: selector.start || 0,
|
|
456
|
-
end: selector.end || 0,
|
|
457
|
-
...(selector.prefix && { prefix: selector.prefix }),
|
|
458
|
-
...(selector.suffix && { suffix: selector.suffix })
|
|
459
|
-
});
|
|
460
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
461
|
-
eventBus.emit('ui:selection:reference-requested', {
|
|
462
|
-
exact: '', // Images don't have exact text
|
|
463
|
-
start: 0,
|
|
464
|
-
end: 0,
|
|
465
|
-
svgSelector: selector.value
|
|
466
|
-
});
|
|
467
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
468
|
-
eventBus.emit('ui:selection:reference-requested', {
|
|
469
|
-
exact: '', // PDFs don't have exact text
|
|
470
|
-
start: 0,
|
|
471
|
-
end: 0,
|
|
472
|
-
fragmentSelector: selector.value,
|
|
473
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
309
|
+
}, [annotateMode, selectedClick, focusAnnotation]);
|
|
310
|
+
|
|
311
|
+
// Annotation click coordinator - handles panel opening and scrolling
|
|
312
|
+
const handleAnnotationClickEvent = useCallback(({ annotationId, motivation }: {
|
|
313
|
+
annotationId: string;
|
|
314
|
+
motivation: components['schemas']['Motivation'];
|
|
315
|
+
}) => {
|
|
316
|
+
// Find the annotation metadata
|
|
317
|
+
const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation({ motivation } as Annotation));
|
|
318
|
+
|
|
319
|
+
if (!metadata?.hasSidePanel) {
|
|
320
|
+
// Annotation doesn't have a side panel - let handleAnnotationClick handle it
|
|
321
|
+
const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags];
|
|
322
|
+
const annotation = allAnnotations.find(a => a.id === annotationId);
|
|
323
|
+
if (annotation) {
|
|
324
|
+
handleAnnotationClick(annotation);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (selectedClick !== 'detail') {
|
|
330
|
+
// Only open panels in detail mode - for other modes, let handleAnnotationClick handle it
|
|
331
|
+
const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags];
|
|
332
|
+
const annotation = allAnnotations.find(a => a.id === annotationId);
|
|
333
|
+
if (annotation) {
|
|
334
|
+
handleAnnotationClick(annotation);
|
|
477
335
|
}
|
|
478
|
-
|
|
479
|
-
console.error('Failed to create annotation:', err);
|
|
336
|
+
return;
|
|
480
337
|
}
|
|
481
|
-
}, [rUri, createAnnotation]);
|
|
482
338
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}, [
|
|
339
|
+
// All annotations open the unified annotations panel
|
|
340
|
+
// The panel internally switches tabs based on the motivation → tab mapping in UnifiedAnnotationsPanel
|
|
341
|
+
eventBus.emit('panel:open', { panel: 'annotations', scrollToAnnotationId: annotationId, motivation });
|
|
342
|
+
}, [highlights, references, assessments, comments, tags, handleAnnotationClick, selectedClick]);
|
|
343
|
+
|
|
344
|
+
// Event subscriptions - Combined into single useEventSubscriptions call to prevent hook ordering issues
|
|
345
|
+
// IMPORTANT: All event subscriptions MUST be in a single call to maintain consistent hook order between renders
|
|
346
|
+
useEventSubscriptions({
|
|
347
|
+
// View mode
|
|
348
|
+
'view:mode-toggled': handleViewModeToggle,
|
|
349
|
+
|
|
350
|
+
// Annotation cache invalidation
|
|
351
|
+
'annotation:added': handleAnnotationAdded,
|
|
352
|
+
'annotation:removed': handleAnnotationRemoved,
|
|
353
|
+
'annotation:updated': handleAnnotationUpdated,
|
|
354
|
+
|
|
355
|
+
// Toolbar state
|
|
356
|
+
'toolbar:selection-changed': handleToolbarSelectionChanged,
|
|
357
|
+
'toolbar:click-changed': handleToolbarClickChanged,
|
|
358
|
+
'toolbar:shape-changed': handleToolbarShapeChanged,
|
|
359
|
+
|
|
360
|
+
// Annotation clicks
|
|
361
|
+
'annotation:click': handleAnnotationClickEvent,
|
|
362
|
+
});
|
|
487
363
|
|
|
488
364
|
// Prepare props for child components
|
|
489
365
|
// Note: These objects are created inline - React's reconciliation handles re-renders efficiently
|
|
490
366
|
const annotationsCollection = { highlights, references, assessments, comments, tags };
|
|
491
367
|
|
|
492
|
-
const handlersForAnnotate = {
|
|
493
|
-
onClick: handleAnnotationClick
|
|
494
|
-
// Note: onHover/onCommentHover removed - component now manages hover state internally
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
const handlersForBrowse = {
|
|
498
|
-
onClick: handleAnnotationClick
|
|
499
|
-
// Note: onCommentHover removed - component now manages hover state internally
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
const creationHandler = { onCreate: handleAnnotationCreate };
|
|
503
|
-
|
|
504
368
|
const uiState = {
|
|
505
369
|
selectedMotivation,
|
|
506
370
|
selectedClick,
|
|
@@ -509,6 +373,13 @@ export function ResourceViewer({
|
|
|
509
373
|
scrollToAnnotationId
|
|
510
374
|
};
|
|
511
375
|
|
|
376
|
+
// Define getTargetDocumentName callback OUTSIDE the conditional
|
|
377
|
+
// IMPORTANT: This must be defined before the return statement to avoid hook ordering violations
|
|
378
|
+
const getTargetDocumentName = useCallback((documentId: string) => {
|
|
379
|
+
const referencedResource = references.find((a: Annotation) => getBodySource(a.body) === documentId);
|
|
380
|
+
return referencedResource ? getExactText(getTargetSelector(referencedResource.target)) : undefined;
|
|
381
|
+
}, [references]);
|
|
382
|
+
|
|
512
383
|
return (
|
|
513
384
|
<div ref={documentViewerRef} className="semiont-resource-viewer">
|
|
514
385
|
{/* Content */}
|
|
@@ -518,8 +389,6 @@ export function ResourceViewer({
|
|
|
518
389
|
mimeType={mimeType}
|
|
519
390
|
resourceUri={resource['@id']}
|
|
520
391
|
annotations={annotationsCollection}
|
|
521
|
-
handlers={handlersForAnnotate}
|
|
522
|
-
creationHandler={creationHandler}
|
|
523
392
|
uiState={uiState}
|
|
524
393
|
onUIStateChange={(updates) => {
|
|
525
394
|
if ('selectedMotivation' in updates) setSelectedMotivation(updates.selectedMotivation!);
|
|
@@ -527,17 +396,10 @@ export function ResourceViewer({
|
|
|
527
396
|
if ('selectedShape' in updates) setSelectedShape(updates.selectedShape!);
|
|
528
397
|
}}
|
|
529
398
|
enableWidgets={true}
|
|
530
|
-
|
|
531
|
-
window.location.href = `/know?entityType=${encodeURIComponent(entityType)}`;
|
|
532
|
-
}}
|
|
533
|
-
onUnresolvedReferenceClick={handleAnnotationClick}
|
|
399
|
+
getTargetDocumentName={getTargetDocumentName}
|
|
534
400
|
{...(generatingReferenceId !== undefined && { generatingReferenceId })}
|
|
535
|
-
onDeleteAnnotation={handleDeleteAnnotationWidget}
|
|
536
401
|
showLineNumbers={showLineNumbers}
|
|
537
402
|
annotateMode={annotateMode}
|
|
538
|
-
onAnnotateModeToggle={toggleAnnotateMode}
|
|
539
|
-
{...(onAnnotationRequested && { onAnnotationRequested })}
|
|
540
|
-
annotators={annotators}
|
|
541
403
|
/>
|
|
542
404
|
) : (
|
|
543
405
|
<BrowseView
|
|
@@ -545,13 +407,9 @@ export function ResourceViewer({
|
|
|
545
407
|
mimeType={mimeType}
|
|
546
408
|
resourceUri={resource['@id']}
|
|
547
409
|
annotations={annotationsCollection}
|
|
548
|
-
handlers={handlersForBrowse}
|
|
549
410
|
hoveredCommentId={hoveredCommentId}
|
|
550
411
|
selectedClick={selectedClick}
|
|
551
|
-
onClickChange={setSelectedClick}
|
|
552
412
|
annotateMode={annotateMode}
|
|
553
|
-
onAnnotateModeToggle={toggleAnnotateMode}
|
|
554
|
-
annotators={annotators}
|
|
555
413
|
/>
|
|
556
414
|
)}
|
|
557
415
|
|
|
@@ -576,7 +434,7 @@ export function ResourceViewer({
|
|
|
576
434
|
{/* Delete Confirmation Modal */}
|
|
577
435
|
{deleteConfirmation && (() => {
|
|
578
436
|
const annotation = deleteConfirmation.annotation;
|
|
579
|
-
const metadata = Object.values(
|
|
437
|
+
const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(annotation));
|
|
580
438
|
const targetSelector = getTargetSelector(annotation.target);
|
|
581
439
|
const selectedText = getExactText(targetSelector);
|
|
582
440
|
const motivationEmoji = metadata?.iconEmoji || '📝';
|
|
@@ -614,8 +472,8 @@ export function ResourceViewer({
|
|
|
614
472
|
{t('deleteConfirmationCancel')}
|
|
615
473
|
</button>
|
|
616
474
|
<button
|
|
617
|
-
onClick={
|
|
618
|
-
|
|
475
|
+
onClick={() => {
|
|
476
|
+
handleDeleteAnnotation(deleteConfirmation.annotation.id);
|
|
619
477
|
setDeleteConfirmation(null);
|
|
620
478
|
}}
|
|
621
479
|
className="semiont-button semiont-button--danger"
|