@semiont/react-ui 0.2.33-build.78 → 0.2.33-build.80
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-7GvDyO0d.d.mts +414 -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-RNNSPLQB.mjs → ar-4ZEORRW2.mjs} +8 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-S2CDL7EC.mjs → bn-SEDE5BQJ.mjs} +8 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-UDX2Q35T.mjs → chunk-D7NBW4RV.mjs} +8 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-35LLVRFK.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
- package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
- package/dist/{cs-RSV675WU.mjs → cs-7W4WF5WD.mjs} +8 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-CHXNPWJC.mjs → da-75XGBCBK.mjs} +8 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-KPEZ53D4.mjs → de-ODJVFLHM.mjs} +8 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MW2BME5T.mjs → el-C4PM4WB3.mjs} +8 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-EVMIX24Y.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-HQ24NYS3.mjs → es-WD33R7QL.mjs} +8 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-W34LRLHG.mjs → fa-2BP6V56P.mjs} +8 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-3U44IGOA.mjs → fi-USRRW24J.mjs} +8 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-N7DKX6NN.mjs → fr-EC5S6WVF.mjs} +8 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-CS4WRXN3.mjs → he-7TBVIKAA.mjs} +8 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-GJDY46KA.mjs → hi-FO4VIZLA.mjs} +8 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-WAEZJK2Y.mjs → id-7U7GGVWY.mjs} +8 -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 +699 -529
- package/dist/index.mjs +4291 -3491
- package/dist/index.mjs.map +1 -1
- package/dist/{it-VDNDMZPU.mjs → it-Y4OPL6I2.mjs} +8 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-5PEH56J5.mjs → ja-PK7SQL55.mjs} +8 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-JYPL3WVA.mjs → ko-L25PXMYD.mjs} +8 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-5PZVW76T.mjs → ms-STH777QM.mjs} +8 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-YXES36KM.mjs → nl-Y7LECDDR.mjs} +8 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-XRA2UCQD.mjs → no-KEKCEWU6.mjs} +8 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-WH6LJA5G.mjs → pl-7A7OC75O.mjs} +8 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-7GAG57BM.mjs → pt-35HTM7RA.mjs} +8 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-BTDDRB7N.mjs → ro-VAWL5KQA.mjs} +8 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-7V5C2IT4.mjs → sv-7ZK5EQEB.mjs} +8 -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-LPKYLBX5.mjs → th-UDWZ4X34.mjs} +8 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-DU4RQL4M.mjs → tr-4WMPK3UX.mjs} +8 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-36UHTDDI.mjs → uk-SSLASQYJ.mjs} +8 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-GDHOUZDH.mjs → vi-IF42Z5PU.mjs} +8 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-TYUID4XZ.mjs → zh-HRQTNTAI.mjs} +8 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +8 -2
- package/src/components/CodeMirrorRenderer.tsx +66 -93
- package/src/components/DetectionProgressWidget.tsx +16 -5
- package/src/components/LiveRegion.tsx +18 -18
- package/src/components/ResizeHandle.tsx +10 -4
- package/src/components/SessionTimer.tsx +2 -2
- package/src/components/Toolbar.tsx +18 -9
- package/src/components/__tests__/SessionTimer.test.tsx +9 -9
- 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 -138
- package/src/components/resource/AnnotationHistory.tsx +12 -13
- package/src/components/resource/BrowseView.tsx +89 -177
- package/src/components/resource/HistoryEvent.tsx +16 -11
- package/src/components/resource/ResourceViewer.tsx +201 -370
- 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/event-formatting.ts +316 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +137 -31
- package/src/components/resource/panels/CollaborationPanel.tsx +20 -13
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +153 -31
- package/src/components/resource/panels/DetectSection.css +36 -1
- package/src/components/resource/panels/DetectSection.tsx +38 -10
- 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 +166 -49
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/StatisticsPanel.tsx +9 -19
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +141 -25
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +46 -101
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +566 -0
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +146 -141
- 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 +228 -103
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +586 -0
- 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 +16 -27
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -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 +504 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +145 -91
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +325 -476
- package/src/styles/motivations/motivation-assessment.css +28 -0
- package/src/styles/patterns/panel-helpers.css +26 -0
- package/translations/ar.json +7 -3
- package/translations/bn.json +7 -3
- package/translations/cs.json +7 -3
- package/translations/da.json +7 -3
- package/translations/de.json +7 -3
- package/translations/el.json +7 -3
- package/translations/en.json +7 -3
- package/translations/es.json +7 -3
- package/translations/fa.json +7 -3
- package/translations/fi.json +7 -3
- package/translations/fr.json +7 -3
- package/translations/he.json +7 -3
- package/translations/hi.json +7 -3
- package/translations/id.json +7 -3
- package/translations/it.json +7 -3
- package/translations/ja.json +7 -3
- package/translations/ko.json +7 -3
- package/translations/ms.json +7 -3
- package/translations/nl.json +7 -3
- package/translations/no.json +7 -3
- package/translations/pl.json +7 -3
- package/translations/pt.json +7 -3
- package/translations/ro.json +7 -3
- package/translations/sv.json +7 -3
- package/translations/th.json +7 -3
- package/translations/tr.json +7 -3
- package/translations/uk.json +7 -3
- package/translations/vi.json +7 -3
- package/translations/zh.json +7 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-RNNSPLQB.mjs.map +0 -1
- package/dist/bn-S2CDL7EC.mjs.map +0 -1
- package/dist/chunk-35LLVRFK.mjs.map +0 -1
- package/dist/chunk-UDX2Q35T.mjs.map +0 -1
- package/dist/cs-RSV675WU.mjs.map +0 -1
- package/dist/da-CHXNPWJC.mjs.map +0 -1
- package/dist/de-KPEZ53D4.mjs.map +0 -1
- package/dist/el-MW2BME5T.mjs.map +0 -1
- package/dist/es-HQ24NYS3.mjs.map +0 -1
- package/dist/fa-W34LRLHG.mjs.map +0 -1
- package/dist/fi-3U44IGOA.mjs.map +0 -1
- package/dist/fr-N7DKX6NN.mjs.map +0 -1
- package/dist/he-CS4WRXN3.mjs.map +0 -1
- package/dist/hi-GJDY46KA.mjs.map +0 -1
- package/dist/id-WAEZJK2Y.mjs.map +0 -1
- package/dist/it-VDNDMZPU.mjs.map +0 -1
- package/dist/ja-5PEH56J5.mjs.map +0 -1
- package/dist/ko-JYPL3WVA.mjs.map +0 -1
- package/dist/ms-5PZVW76T.mjs.map +0 -1
- package/dist/nl-YXES36KM.mjs.map +0 -1
- package/dist/no-XRA2UCQD.mjs.map +0 -1
- package/dist/pl-WH6LJA5G.mjs.map +0 -1
- package/dist/pt-7GAG57BM.mjs.map +0 -1
- package/dist/ro-BTDDRB7N.mjs.map +0 -1
- package/dist/sv-7V5C2IT4.mjs.map +0 -1
- package/dist/th-LPKYLBX5.mjs.map +0 -1
- package/dist/tr-DU4RQL4M.mjs.map +0 -1
- package/dist/uk-36UHTDDI.mjs.map +0 -1
- package/dist/vi-GDHOUZDH.mjs.map +0 -1
- package/dist/zh-TYUID4XZ.mjs.map +0 -1
- /package/dist/{en-EVMIX24Y.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -1,115 +1,78 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect, useCallback, useRef
|
|
4
|
-
// useRouter removed - using window.location for navigation
|
|
3
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
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';
|
|
13
|
+
import { useCacheManager } from '../../contexts/CacheContext';
|
|
14
|
+
import { useObservableExternalNavigation } from '../../hooks/useObservableNavigation';
|
|
15
|
+
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
14
16
|
import type { AnnotationsCollection } from '../../types/annotation-props';
|
|
15
17
|
import { getSelectorType, getSelectedShapeForSelectorType, saveSelectedShapeForSelectorType } from '../../lib/media-shapes';
|
|
16
18
|
|
|
17
19
|
type Annotation = components['schemas']['Annotation'];
|
|
18
20
|
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
19
|
-
type Motivation = components['schemas']['Motivation'];
|
|
20
|
-
|
|
21
|
-
// Unified pending annotation type - all human-created annotations flow through this
|
|
22
|
-
interface PendingAnnotation {
|
|
23
|
-
selector: Selector | Selector[];
|
|
24
|
-
motivation: Motivation;
|
|
25
|
-
}
|
|
26
21
|
|
|
22
|
+
/**
|
|
23
|
+
* ResourceViewer - Display and interact with resource content and annotations
|
|
24
|
+
*
|
|
25
|
+
* This component uses event-driven architecture for real-time updates:
|
|
26
|
+
* - Subscribes to make-meaning events (annotation:added, annotation:removed, annotation:updated)
|
|
27
|
+
* - Automatically invalidates cache when annotations change
|
|
28
|
+
* - No manual refetch needed - events handle cache invalidation
|
|
29
|
+
*
|
|
30
|
+
* Requirements:
|
|
31
|
+
* - Must be wrapped in MakeMeaningEventBusProvider (provides event bus)
|
|
32
|
+
* - Must be wrapped in CacheContext (provides cache manager)
|
|
33
|
+
*
|
|
34
|
+
* Event flow:
|
|
35
|
+
* make-meaning → EventLog → SSE → EventBus → ResourceViewer → Cache invalidation
|
|
36
|
+
*
|
|
37
|
+
* Phase 2 complete: Event-based cache invalidation replaces manual refetch
|
|
38
|
+
* Phase 3 complete: Fully event-driven - all user interactions use unified event bus
|
|
39
|
+
*/
|
|
27
40
|
interface Props {
|
|
28
41
|
resource: SemiontResource & { content: string };
|
|
29
42
|
annotations: AnnotationsCollection;
|
|
30
|
-
onRefetchAnnotations?: () => void;
|
|
31
|
-
annotateMode: boolean;
|
|
32
|
-
onAnnotateModeToggle: () => void;
|
|
33
43
|
generatingReferenceId?: string | null;
|
|
34
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
35
|
-
onCommentHover?: (commentId: string | null) => void;
|
|
36
|
-
hoveredAnnotationId?: string | null;
|
|
37
|
-
hoveredCommentId?: string | null;
|
|
38
|
-
scrollToAnnotationId?: string | null;
|
|
39
44
|
showLineNumbers?: boolean;
|
|
40
|
-
|
|
41
|
-
onCommentCreationRequested?: (selection: { exact: string; start: number; end: number; svgSelector?: string; fragmentSelector?: string; conformsTo?: string }) => void;
|
|
42
|
-
onTagCreationRequested?: (selection: { exact: string; start: number; end: number; svgSelector?: string; fragmentSelector?: string; conformsTo?: string }) => void;
|
|
43
|
-
onAssessmentCreationRequested?: (selection: { exact: string; start: number; end: number; svgSelector?: string; fragmentSelector?: string; conformsTo?: string }) => void;
|
|
44
|
-
onReferenceCreationRequested?: (selection: {
|
|
45
|
-
exact: string;
|
|
46
|
-
start: number;
|
|
47
|
-
end: number;
|
|
48
|
-
prefix?: string;
|
|
49
|
-
suffix?: string;
|
|
50
|
-
svgSelector?: string;
|
|
51
|
-
fragmentSelector?: string;
|
|
52
|
-
conformsTo?: string;
|
|
53
|
-
}) => void;
|
|
54
|
-
onCommentClick?: (commentId: string) => void;
|
|
55
|
-
onReferenceClick?: (referenceId: string) => void;
|
|
56
|
-
onHighlightClick?: (highlightId: string) => void;
|
|
57
|
-
onAssessmentClick?: (assessmentId: string) => void;
|
|
58
|
-
onTagClick?: (tagId: string) => void;
|
|
59
|
-
annotators: Record<string, Annotator>;
|
|
45
|
+
hoveredAnnotationId?: string | null;
|
|
60
46
|
}
|
|
61
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
|
+
*/
|
|
62
61
|
export function ResourceViewer({
|
|
63
62
|
resource,
|
|
64
63
|
annotations,
|
|
65
|
-
onRefetchAnnotations,
|
|
66
|
-
annotateMode,
|
|
67
|
-
onAnnotateModeToggle,
|
|
68
64
|
generatingReferenceId,
|
|
69
|
-
onAnnotationHover,
|
|
70
|
-
onCommentHover,
|
|
71
|
-
hoveredAnnotationId,
|
|
72
|
-
hoveredCommentId,
|
|
73
|
-
scrollToAnnotationId,
|
|
74
65
|
showLineNumbers = false,
|
|
75
|
-
|
|
76
|
-
onCommentCreationRequested,
|
|
77
|
-
onTagCreationRequested,
|
|
78
|
-
onAssessmentCreationRequested,
|
|
79
|
-
onReferenceCreationRequested,
|
|
80
|
-
onCommentClick,
|
|
81
|
-
onReferenceClick,
|
|
82
|
-
onHighlightClick,
|
|
83
|
-
onAssessmentClick,
|
|
84
|
-
onTagClick,
|
|
85
|
-
annotators
|
|
66
|
+
hoveredAnnotationId: hoveredAnnotationIdProp
|
|
86
67
|
}: Props) {
|
|
87
68
|
const t = useTranslations('ResourceViewer');
|
|
88
69
|
const documentViewerRef = useRef<HTMLDivElement>(null);
|
|
89
70
|
|
|
90
|
-
//
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const onCommentClickRef = useRef(onCommentClick);
|
|
96
|
-
const onReferenceClickRef = useRef(onReferenceClick);
|
|
97
|
-
const onHighlightClickRef = useRef(onHighlightClick);
|
|
98
|
-
const onAssessmentClickRef = useRef(onAssessmentClick);
|
|
99
|
-
const onTagClickRef = useRef(onTagClick);
|
|
100
|
-
|
|
101
|
-
// Keep refs up to date
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
onRefetchAnnotationsRef.current = onRefetchAnnotations;
|
|
104
|
-
onCommentCreationRequestedRef.current = onCommentCreationRequested;
|
|
105
|
-
onTagCreationRequestedRef.current = onTagCreationRequested;
|
|
106
|
-
onReferenceCreationRequestedRef.current = onReferenceCreationRequested;
|
|
107
|
-
onCommentClickRef.current = onCommentClick;
|
|
108
|
-
onReferenceClickRef.current = onReferenceClick;
|
|
109
|
-
onHighlightClickRef.current = onHighlightClick;
|
|
110
|
-
onAssessmentClickRef.current = onAssessmentClick;
|
|
111
|
-
onTagClickRef.current = onTagClick;
|
|
112
|
-
});
|
|
71
|
+
// Get unified event bus for emitting UI events
|
|
72
|
+
const eventBus = useEventBus();
|
|
73
|
+
|
|
74
|
+
// Get observable navigation for event-driven routing
|
|
75
|
+
const navigate = useObservableExternalNavigation();
|
|
113
76
|
|
|
114
77
|
const { highlights, references, assessments, comments, tags } = annotations;
|
|
115
78
|
|
|
@@ -131,12 +94,50 @@ export function ResourceViewer({
|
|
|
131
94
|
|
|
132
95
|
const mimeType = getMimeType();
|
|
133
96
|
|
|
134
|
-
//
|
|
97
|
+
// Annotate mode state - persisted in localStorage
|
|
98
|
+
const [annotateMode, setAnnotateMode] = useState<boolean>(() => {
|
|
99
|
+
if (typeof window !== 'undefined') {
|
|
100
|
+
return localStorage.getItem('annotateMode') === 'true';
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Persist annotateMode to localStorage
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (typeof window !== 'undefined') {
|
|
108
|
+
localStorage.setItem('annotateMode', annotateMode.toString());
|
|
109
|
+
}
|
|
110
|
+
}, [annotateMode]);
|
|
111
|
+
|
|
112
|
+
// Event handlers (extracted to avoid inline arrow functions)
|
|
113
|
+
const handleViewModeToggle = useCallback(() => {
|
|
114
|
+
setAnnotateMode(prev => !prev);
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
// Determine active view based on annotate mode
|
|
135
118
|
const activeView = annotateMode ? 'annotate' : 'browse';
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
119
|
+
|
|
120
|
+
// Event-based cache invalidation - subscribe to make-meaning events
|
|
121
|
+
// This replaces manual onRefetchAnnotations calls with automatic updates
|
|
122
|
+
const cacheManager = useCacheManager();
|
|
123
|
+
|
|
124
|
+
const handleAnnotationAdded = useCallback(() => {
|
|
125
|
+
if (cacheManager) {
|
|
126
|
+
cacheManager.invalidateAnnotations(rUri);
|
|
127
|
+
}
|
|
128
|
+
}, [cacheManager, rUri]);
|
|
129
|
+
|
|
130
|
+
const handleAnnotationRemoved = useCallback(() => {
|
|
131
|
+
if (cacheManager) {
|
|
132
|
+
cacheManager.invalidateAnnotations(rUri);
|
|
133
|
+
}
|
|
134
|
+
}, [cacheManager, rUri]);
|
|
135
|
+
|
|
136
|
+
const handleAnnotationUpdated = useCallback(() => {
|
|
137
|
+
if (cacheManager) {
|
|
138
|
+
cacheManager.invalidateAnnotations(rUri);
|
|
139
|
+
}
|
|
140
|
+
}, [cacheManager, rUri]);
|
|
140
141
|
|
|
141
142
|
// Annotation toolbar state - persisted in localStorage
|
|
142
143
|
const [selectedMotivation, setSelectedMotivation] = useState<SelectionMotivation | null>(() => {
|
|
@@ -168,6 +169,19 @@ export function ResourceViewer({
|
|
|
168
169
|
return getSelectedShapeForSelectorType(selectorType);
|
|
169
170
|
});
|
|
170
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
|
+
|
|
171
185
|
// Persist toolbar state to localStorage
|
|
172
186
|
useEffect(() => {
|
|
173
187
|
if (selectedMotivation === null) {
|
|
@@ -204,8 +218,24 @@ export function ResourceViewer({
|
|
|
204
218
|
position: { x: number; y: number };
|
|
205
219
|
} | null>(null);
|
|
206
220
|
|
|
221
|
+
// Internal UI state for hover, focus, and scroll
|
|
222
|
+
// Use prop value when provided (controlled by parent), otherwise null
|
|
223
|
+
const hoveredAnnotationId = hoveredAnnotationIdProp ?? null;
|
|
224
|
+
const [hoveredCommentId, _setHoveredCommentId] = useState<string | null>(null);
|
|
225
|
+
const [scrollToAnnotationId, setScrollToAnnotationId] = useState<string | null>(null);
|
|
226
|
+
const [_focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
227
|
+
|
|
228
|
+
// Focus annotation helper
|
|
229
|
+
const focusAnnotation = useCallback((annotationId: string) => {
|
|
230
|
+
setFocusedAnnotationId(annotationId);
|
|
231
|
+
setScrollToAnnotationId(annotationId);
|
|
232
|
+
|
|
233
|
+
// Clear focus after 3 seconds
|
|
234
|
+
setTimeout(() => setFocusedAnnotationId(null), 3000);
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
207
237
|
// Calculate centered position for JSON-LD modal
|
|
208
|
-
const
|
|
238
|
+
const getJsonLdModalPosition = () => {
|
|
209
239
|
if (typeof window === 'undefined') return { x: 0, y: 0 };
|
|
210
240
|
|
|
211
241
|
const popupWidth = 800;
|
|
@@ -215,47 +245,24 @@ export function ResourceViewer({
|
|
|
215
245
|
x: Math.max(0, (window.innerWidth - popupWidth) / 2),
|
|
216
246
|
y: Math.max(0, (window.innerHeight - popupHeight) / 2),
|
|
217
247
|
};
|
|
218
|
-
}
|
|
248
|
+
};
|
|
219
249
|
|
|
220
|
-
// Handle deleting annotations -
|
|
221
|
-
const handleDeleteAnnotation = useCallback(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
onRefetchAnnotationsRef.current?.();
|
|
225
|
-
} catch (err) {
|
|
226
|
-
console.error('Failed to delete annotation:', err);
|
|
227
|
-
}
|
|
228
|
-
}, [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
|
|
229
254
|
|
|
230
255
|
// Handle annotation clicks - memoized
|
|
231
256
|
const handleAnnotationClick = useCallback((annotation: Annotation, event?: React.MouseEvent) => {
|
|
232
|
-
const metadata = Object.values(
|
|
257
|
+
const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(annotation));
|
|
233
258
|
|
|
234
259
|
// If annotation has a side panel, only open it when Detail mode is active
|
|
235
260
|
// For delete/jsonld/follow modes, let those handlers below process it
|
|
236
261
|
if (metadata?.hasSidePanel) {
|
|
237
262
|
if (selectedClick === 'detail') {
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
if (isReference(annotation) && onReferenceClickRef.current) {
|
|
244
|
-
onReferenceClickRef.current(annotation.id);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (isHighlight(annotation) && onHighlightClickRef.current) {
|
|
248
|
-
onHighlightClickRef.current(annotation.id);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (isAssessment(annotation) && onAssessmentClickRef.current) {
|
|
252
|
-
onAssessmentClickRef.current(annotation.id);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (isTag(annotation) && onTagClickRef.current) {
|
|
256
|
-
onTagClickRef.current(annotation.id);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
263
|
+
// Focus annotation (sets internal focus and scroll state, plus calls parent callback for backward compat)
|
|
264
|
+
focusAnnotation(annotation.id);
|
|
265
|
+
return;
|
|
259
266
|
}
|
|
260
267
|
// Don't return early for delete/jsonld/follow modes - let them be handled below
|
|
261
268
|
if (selectedClick !== 'deleting' && selectedClick !== 'jsonld' && selectedClick !== 'follow') {
|
|
@@ -270,10 +277,10 @@ export function ResourceViewer({
|
|
|
270
277
|
if (selectedClick === 'follow' && isReference(annotation)) {
|
|
271
278
|
const bodySource = getBodySource(annotation.body);
|
|
272
279
|
if (bodySource) {
|
|
273
|
-
// Navigate to the linked resource
|
|
280
|
+
// Navigate to the linked resource - emits 'navigation:external-navigate' event
|
|
274
281
|
const resourceId = bodySource.split('/resources/')[1];
|
|
275
282
|
if (resourceId) {
|
|
276
|
-
|
|
283
|
+
navigate(`/know/resource/${resourceId}`, { resourceId });
|
|
277
284
|
}
|
|
278
285
|
}
|
|
279
286
|
return;
|
|
@@ -299,238 +306,79 @@ export function ResourceViewer({
|
|
|
299
306
|
setDeleteConfirmation({ annotation, position });
|
|
300
307
|
return;
|
|
301
308
|
}
|
|
302
|
-
}, [annotateMode, selectedClick,
|
|
303
|
-
|
|
304
|
-
//
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
exact: selector.exact,
|
|
319
|
-
...(selector.prefix && { prefix: selector.prefix }),
|
|
320
|
-
...(selector.suffix && { suffix: selector.suffix })
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
type: 'TextPositionSelector',
|
|
324
|
-
start: selector.start || 0,
|
|
325
|
-
end: selector.end || 0
|
|
326
|
-
}
|
|
327
|
-
];
|
|
328
|
-
|
|
329
|
-
const annotation = await createAnnotation(
|
|
330
|
-
rUri,
|
|
331
|
-
motivation,
|
|
332
|
-
selectors,
|
|
333
|
-
[]
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
// Focus the new annotation to trigger panel tab switch
|
|
337
|
-
if (annotation) {
|
|
338
|
-
if (motivation === 'highlighting' && onHighlightClickRef.current) {
|
|
339
|
-
onHighlightClickRef.current(annotation.id);
|
|
340
|
-
} else if (motivation === 'assessing' && onAssessmentClickRef.current) {
|
|
341
|
-
onAssessmentClickRef.current(annotation.id);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
onRefetchAnnotationsRef.current?.();
|
|
345
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
346
|
-
// Image annotations use generic createAnnotation
|
|
347
|
-
await createAnnotation(
|
|
348
|
-
rUri,
|
|
349
|
-
motivation,
|
|
350
|
-
{ type: 'SvgSelector', value: selector.value },
|
|
351
|
-
[]
|
|
352
|
-
);
|
|
353
|
-
onRefetchAnnotationsRef.current?.();
|
|
354
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
355
|
-
// PDF annotations use FragmentSelector
|
|
356
|
-
await createAnnotation(
|
|
357
|
-
rUri,
|
|
358
|
-
motivation,
|
|
359
|
-
{
|
|
360
|
-
type: 'FragmentSelector',
|
|
361
|
-
value: selector.value,
|
|
362
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
363
|
-
},
|
|
364
|
-
[]
|
|
365
|
-
);
|
|
366
|
-
onRefetchAnnotationsRef.current?.();
|
|
367
|
-
}
|
|
368
|
-
break;
|
|
369
|
-
|
|
370
|
-
case 'commenting':
|
|
371
|
-
if (selector.type === 'TextQuoteSelector' && selector.exact) {
|
|
372
|
-
// Text: notify parent to open Comments Panel
|
|
373
|
-
if (onCommentCreationRequestedRef.current) {
|
|
374
|
-
onCommentCreationRequestedRef.current({
|
|
375
|
-
exact: selector.exact,
|
|
376
|
-
start: selector.start || 0,
|
|
377
|
-
end: selector.end || 0
|
|
378
|
-
});
|
|
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 && onCommentClickRef.current) {
|
|
389
|
-
onCommentClickRef.current(annotation.id);
|
|
390
|
-
}
|
|
391
|
-
onRefetchAnnotationsRef.current?.();
|
|
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 && onCommentClickRef.current) {
|
|
405
|
-
onCommentClickRef.current(annotation.id);
|
|
406
|
-
}
|
|
407
|
-
onRefetchAnnotationsRef.current?.();
|
|
408
|
-
}
|
|
409
|
-
break;
|
|
410
|
-
|
|
411
|
-
case 'tagging':
|
|
412
|
-
if (selector.type === 'TextQuoteSelector' && selector.exact) {
|
|
413
|
-
// Text: notify parent to open Tags Panel
|
|
414
|
-
if (onTagCreationRequestedRef.current) {
|
|
415
|
-
onTagCreationRequestedRef.current({
|
|
416
|
-
exact: selector.exact,
|
|
417
|
-
start: selector.start || 0,
|
|
418
|
-
end: selector.end || 0
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
422
|
-
// Image: create annotation, then open panel
|
|
423
|
-
const annotation = await createAnnotation(
|
|
424
|
-
rUri,
|
|
425
|
-
motivation,
|
|
426
|
-
{ type: 'SvgSelector', value: selector.value },
|
|
427
|
-
[]
|
|
428
|
-
);
|
|
429
|
-
if (annotation && onTagClickRef.current) {
|
|
430
|
-
onTagClickRef.current(annotation.id);
|
|
431
|
-
}
|
|
432
|
-
onRefetchAnnotationsRef.current?.();
|
|
433
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
434
|
-
// PDF: create annotation, then open panel
|
|
435
|
-
const annotation = await createAnnotation(
|
|
436
|
-
rUri,
|
|
437
|
-
motivation,
|
|
438
|
-
{
|
|
439
|
-
type: 'FragmentSelector',
|
|
440
|
-
value: selector.value,
|
|
441
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
442
|
-
},
|
|
443
|
-
[]
|
|
444
|
-
);
|
|
445
|
-
if (annotation && onTagClickRef.current) {
|
|
446
|
-
onTagClickRef.current(annotation.id);
|
|
447
|
-
}
|
|
448
|
-
onRefetchAnnotationsRef.current?.();
|
|
449
|
-
}
|
|
450
|
-
break;
|
|
451
|
-
|
|
452
|
-
case 'linking':
|
|
453
|
-
// Call onReferenceCreationRequested for text, image, and PDF selections
|
|
454
|
-
if (onReferenceCreationRequestedRef.current) {
|
|
455
|
-
if (selector.type === 'TextQuoteSelector' && selector.exact) {
|
|
456
|
-
const selection = {
|
|
457
|
-
exact: selector.exact,
|
|
458
|
-
start: selector.start || 0,
|
|
459
|
-
end: selector.end || 0,
|
|
460
|
-
...(selector.prefix && { prefix: selector.prefix }),
|
|
461
|
-
...(selector.suffix && { suffix: selector.suffix })
|
|
462
|
-
};
|
|
463
|
-
onReferenceCreationRequestedRef.current(selection);
|
|
464
|
-
} else if (selector.type === 'SvgSelector' && selector.value) {
|
|
465
|
-
const selection = {
|
|
466
|
-
exact: '', // Images don't have exact text
|
|
467
|
-
start: 0,
|
|
468
|
-
end: 0,
|
|
469
|
-
svgSelector: selector.value
|
|
470
|
-
};
|
|
471
|
-
onReferenceCreationRequestedRef.current(selection);
|
|
472
|
-
} else if (selector.type === 'FragmentSelector' && selector.value) {
|
|
473
|
-
const selection = {
|
|
474
|
-
exact: '', // PDFs don't have exact text
|
|
475
|
-
start: 0,
|
|
476
|
-
end: 0,
|
|
477
|
-
fragmentSelector: selector.value,
|
|
478
|
-
...(selector.conformsTo && { conformsTo: selector.conformsTo })
|
|
479
|
-
};
|
|
480
|
-
onReferenceCreationRequestedRef.current(selection);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
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);
|
|
484
325
|
}
|
|
485
|
-
|
|
486
|
-
console.error('Failed to create annotation:', err);
|
|
326
|
+
return;
|
|
487
327
|
}
|
|
488
|
-
}, [rUri, createAnnotation]);
|
|
489
328
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
);
|
|
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);
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
500
338
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
+
});
|
|
509
363
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
...(onCommentHover && { onCommentHover })
|
|
514
|
-
}),
|
|
515
|
-
[handleAnnotationClick, onCommentHover]
|
|
516
|
-
);
|
|
364
|
+
// Prepare props for child components
|
|
365
|
+
// Note: These objects are created inline - React's reconciliation handles re-renders efficiently
|
|
366
|
+
const annotationsCollection = { highlights, references, assessments, comments, tags };
|
|
517
367
|
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
368
|
+
const uiState = {
|
|
369
|
+
selectedMotivation,
|
|
370
|
+
selectedClick,
|
|
371
|
+
selectedShape,
|
|
372
|
+
hoveredAnnotationId,
|
|
373
|
+
scrollToAnnotationId
|
|
374
|
+
};
|
|
522
375
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
...(hoveredCommentId !== undefined && { hoveredCommentId }),
|
|
530
|
-
...(scrollToAnnotationId !== undefined && { scrollToAnnotationId })
|
|
531
|
-
}),
|
|
532
|
-
[selectedMotivation, selectedClick, selectedShape, hoveredAnnotationId, hoveredCommentId, scrollToAnnotationId]
|
|
533
|
-
);
|
|
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]);
|
|
534
382
|
|
|
535
383
|
return (
|
|
536
384
|
<div ref={documentViewerRef} className="semiont-resource-viewer">
|
|
@@ -541,8 +389,6 @@ export function ResourceViewer({
|
|
|
541
389
|
mimeType={mimeType}
|
|
542
390
|
resourceUri={resource['@id']}
|
|
543
391
|
annotations={annotationsCollection}
|
|
544
|
-
handlers={handlersForAnnotate}
|
|
545
|
-
creationHandler={creationHandler}
|
|
546
392
|
uiState={uiState}
|
|
547
393
|
onUIStateChange={(updates) => {
|
|
548
394
|
if ('selectedMotivation' in updates) setSelectedMotivation(updates.selectedMotivation!);
|
|
@@ -550,21 +396,10 @@ export function ResourceViewer({
|
|
|
550
396
|
if ('selectedShape' in updates) setSelectedShape(updates.selectedShape!);
|
|
551
397
|
}}
|
|
552
398
|
enableWidgets={true}
|
|
553
|
-
|
|
554
|
-
window.location.href = `/know?entityType=${encodeURIComponent(entityType)}`;
|
|
555
|
-
}}
|
|
556
|
-
onUnresolvedReferenceClick={handleAnnotationClick}
|
|
399
|
+
getTargetDocumentName={getTargetDocumentName}
|
|
557
400
|
{...(generatingReferenceId !== undefined && { generatingReferenceId })}
|
|
558
|
-
onDeleteAnnotation={handleDeleteAnnotationWidget}
|
|
559
401
|
showLineNumbers={showLineNumbers}
|
|
560
402
|
annotateMode={annotateMode}
|
|
561
|
-
onAnnotateModeToggle={onAnnotateModeToggle}
|
|
562
|
-
{...(onAnnotationRequested && { onAnnotationRequested })}
|
|
563
|
-
{...(onCommentCreationRequested && { onCommentCreationRequested })}
|
|
564
|
-
{...(onTagCreationRequested && { onTagCreationRequested })}
|
|
565
|
-
{...(onAssessmentCreationRequested && { onAssessmentCreationRequested })}
|
|
566
|
-
{...(onReferenceCreationRequested && { onReferenceCreationRequested })}
|
|
567
|
-
annotators={annotators}
|
|
568
403
|
/>
|
|
569
404
|
) : (
|
|
570
405
|
<BrowseView
|
|
@@ -572,13 +407,9 @@ export function ResourceViewer({
|
|
|
572
407
|
mimeType={mimeType}
|
|
573
408
|
resourceUri={resource['@id']}
|
|
574
409
|
annotations={annotationsCollection}
|
|
575
|
-
|
|
576
|
-
{...(hoveredCommentId !== undefined && { hoveredCommentId })}
|
|
410
|
+
hoveredCommentId={hoveredCommentId}
|
|
577
411
|
selectedClick={selectedClick}
|
|
578
|
-
onClickChange={setSelectedClick}
|
|
579
412
|
annotateMode={annotateMode}
|
|
580
|
-
onAnnotateModeToggle={onAnnotateModeToggle}
|
|
581
|
-
annotators={annotators}
|
|
582
413
|
/>
|
|
583
414
|
)}
|
|
584
415
|
|
|
@@ -587,7 +418,7 @@ export function ResourceViewer({
|
|
|
587
418
|
<PopupContainer
|
|
588
419
|
isOpen={showJsonLdView}
|
|
589
420
|
onClose={() => setShowJsonLdView(false)}
|
|
590
|
-
position={
|
|
421
|
+
position={getJsonLdModalPosition()}
|
|
591
422
|
wide={true}
|
|
592
423
|
>
|
|
593
424
|
<JsonLdView
|
|
@@ -603,7 +434,7 @@ export function ResourceViewer({
|
|
|
603
434
|
{/* Delete Confirmation Modal */}
|
|
604
435
|
{deleteConfirmation && (() => {
|
|
605
436
|
const annotation = deleteConfirmation.annotation;
|
|
606
|
-
const metadata = Object.values(
|
|
437
|
+
const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(annotation));
|
|
607
438
|
const targetSelector = getTargetSelector(annotation.target);
|
|
608
439
|
const selectedText = getExactText(targetSelector);
|
|
609
440
|
const motivationEmoji = metadata?.iconEmoji || '📝';
|
|
@@ -641,8 +472,8 @@ export function ResourceViewer({
|
|
|
641
472
|
{t('deleteConfirmationCancel')}
|
|
642
473
|
</button>
|
|
643
474
|
<button
|
|
644
|
-
onClick={
|
|
645
|
-
|
|
475
|
+
onClick={() => {
|
|
476
|
+
handleDeleteAnnotation(deleteConfirmation.annotation.id);
|
|
646
477
|
setDeleteConfirmation(null);
|
|
647
478
|
}}
|
|
648
479
|
className="semiont-button semiont-button--danger"
|