@semiont/react-ui 0.2.33-build.79 → 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-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-ZR4ZV2LY.mjs} +206 -146
- package/dist/chunk-ZR4ZV2LY.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 +645 -471
- package/dist/index.mjs +3461 -3025
- 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/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 -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 +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 +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 +119 -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 +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 +135 -88
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +308 -528
- 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,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ResourceViewerPage -
|
|
2
|
+
* ResourceViewerPage - Self-contained resource viewer component
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Handles all data loading, event subscriptions, and side effects internally.
|
|
5
|
+
* Only requires minimal props from the framework layer (routing, modals).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
9
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
10
|
+
import type { components, ResourceUri } from '@semiont/api-client';
|
|
11
|
+
import { getLanguage, getPrimaryRepresentation, resourceAnnotationUri, getPrimaryMediaType } from '@semiont/api-client';
|
|
12
|
+
import { uriToAnnotationId } from '@semiont/core';
|
|
13
|
+
import { ANNOTATORS } from '@semiont/react-ui';
|
|
12
14
|
import { ErrorBoundary } from '@semiont/react-ui';
|
|
13
|
-
import { useGenerationProgress } from '@semiont/react-ui';
|
|
14
15
|
import { AnnotationHistory } from '@semiont/react-ui';
|
|
15
16
|
import { UnifiedAnnotationsPanel } from '@semiont/react-ui';
|
|
16
17
|
import { ResourceInfoPanel } from '@semiont/react-ui';
|
|
@@ -20,19 +21,26 @@ import { Toolbar } from '@semiont/react-ui';
|
|
|
20
21
|
import { useResourceLoadingAnnouncements } from '@semiont/react-ui';
|
|
21
22
|
import type { GenerationOptions } from '@semiont/react-ui';
|
|
22
23
|
import { ResourceViewer } from '@semiont/react-ui';
|
|
23
|
-
import {
|
|
24
|
+
import { QUERY_KEYS } from '../../../lib/query-keys';
|
|
25
|
+
import { useResources, useEntityTypes } from '../../../lib/api-hooks';
|
|
26
|
+
import { useResourceContent } from '../../../hooks/useResourceContent';
|
|
27
|
+
import { useToast } from '../../../components/Toast';
|
|
28
|
+
import { useTheme } from '../../../hooks/useTheme';
|
|
29
|
+
import { useLineNumbers } from '../../../hooks/useLineNumbers';
|
|
30
|
+
import { useResourceEvents } from '../../../hooks/useResourceEvents';
|
|
31
|
+
import { useDebouncedCallback } from '../../../hooks/useDebounce';
|
|
32
|
+
import { useOpenResources } from '../../../contexts/OpenResourcesContext';
|
|
33
|
+
// Import EventBus hooks directly from context to avoid mocking issues in tests
|
|
34
|
+
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
35
|
+
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
36
|
+
import { useResourceAnnotations } from '../../../contexts/ResourceAnnotationsContext';
|
|
37
|
+
import { useDetectionFlow } from '../../../hooks/useDetectionFlow';
|
|
38
|
+
import { usePanelNavigation } from '../../../hooks/usePanelNavigation';
|
|
39
|
+
import { useAnnotationFlow } from '../../../hooks/useAnnotationFlow';
|
|
40
|
+
import { useGenerationFlow } from '../../../hooks/useGenerationFlow';
|
|
24
41
|
|
|
25
42
|
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
26
43
|
type Annotation = components['schemas']['Annotation'];
|
|
27
|
-
type Motivation = components['schemas']['Motivation'];
|
|
28
|
-
|
|
29
|
-
// Unified pending annotation type - all human-created annotations flow through this
|
|
30
|
-
interface PendingAnnotation {
|
|
31
|
-
selector: Selector | Selector[];
|
|
32
|
-
motivation: Motivation;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
import type { DetectionProgress } from '@semiont/react-ui';
|
|
36
44
|
|
|
37
45
|
export interface ResourceViewerPageProps {
|
|
38
46
|
/**
|
|
@@ -45,97 +53,16 @@ export interface ResourceViewerPageProps {
|
|
|
45
53
|
*/
|
|
46
54
|
rUri: ResourceUri;
|
|
47
55
|
|
|
48
|
-
/**
|
|
49
|
-
* Document content (already loaded)
|
|
50
|
-
*/
|
|
51
|
-
content: string;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Whether content is still loading
|
|
55
|
-
*/
|
|
56
|
-
contentLoading: boolean;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* All annotations for this resource
|
|
60
|
-
*/
|
|
61
|
-
annotations: Annotation[];
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Resources that reference this resource
|
|
65
|
-
*/
|
|
66
|
-
referencedBy: any[];
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Whether referencedBy is loading
|
|
70
|
-
*/
|
|
71
|
-
referencedByLoading: boolean;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* All available entity types
|
|
75
|
-
*/
|
|
76
|
-
allEntityTypes: string[];
|
|
77
|
-
|
|
78
56
|
/**
|
|
79
57
|
* Current locale
|
|
80
58
|
*/
|
|
81
59
|
locale: string;
|
|
82
60
|
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Theme state
|
|
86
|
-
*/
|
|
87
|
-
theme: any;
|
|
88
|
-
onThemeChange: (theme: any) => void;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Line numbers state
|
|
92
|
-
*/
|
|
93
|
-
showLineNumbers: boolean;
|
|
94
|
-
onLineNumbersToggle: () => void;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Active toolbar panel
|
|
98
|
-
*/
|
|
99
|
-
activePanel: any;
|
|
100
|
-
onPanelToggle: (panel: any) => void;
|
|
101
|
-
setActivePanel: (panel: any) => void;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Callbacks for resource actions
|
|
105
|
-
*/
|
|
106
|
-
onArchive: () => Promise<void>;
|
|
107
|
-
onUnarchive: () => Promise<void>;
|
|
108
|
-
onClone: () => Promise<void>;
|
|
109
|
-
onUpdateAnnotationBody: (annotationUri: string, data: any) => Promise<void>;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Annotation CRUD callbacks
|
|
113
|
-
*/
|
|
114
|
-
onCreateAnnotation: (
|
|
115
|
-
rUri: ResourceUri,
|
|
116
|
-
motivation: Motivation,
|
|
117
|
-
selector: any,
|
|
118
|
-
body: any[]
|
|
119
|
-
) => Promise<void>;
|
|
120
|
-
onTriggerSparkleAnimation: (annotationId: string) => void;
|
|
121
|
-
onClearNewAnnotationId: (annotationId: string) => void;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Toast notifications
|
|
125
|
-
*/
|
|
126
|
-
showSuccess: (message: string) => void;
|
|
127
|
-
showError: (message: string) => void;
|
|
128
|
-
|
|
129
61
|
/**
|
|
130
62
|
* Cache manager for detection
|
|
131
63
|
*/
|
|
132
64
|
cacheManager: any;
|
|
133
65
|
|
|
134
|
-
/**
|
|
135
|
-
* API client
|
|
136
|
-
*/
|
|
137
|
-
client: any;
|
|
138
|
-
|
|
139
66
|
/**
|
|
140
67
|
* Link component for routing
|
|
141
68
|
*/
|
|
@@ -147,216 +74,279 @@ export interface ResourceViewerPageProps {
|
|
|
147
74
|
routes: any;
|
|
148
75
|
|
|
149
76
|
/**
|
|
150
|
-
* Component dependencies - passed from
|
|
77
|
+
* Component dependencies - passed from framework layer
|
|
151
78
|
*/
|
|
152
79
|
ToolbarPanels: React.ComponentType<any>;
|
|
153
80
|
SearchResourcesModal: React.ComponentType<any>;
|
|
154
81
|
GenerationConfigModal: React.ComponentType<any>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Callback to refetch document from parent
|
|
85
|
+
*/
|
|
86
|
+
refetchDocument: () => Promise<unknown>;
|
|
155
87
|
}
|
|
156
88
|
|
|
157
|
-
|
|
158
|
-
|
|
89
|
+
/**
|
|
90
|
+
* ResourceViewerPage - Main component
|
|
91
|
+
*
|
|
92
|
+
* Uses hooks directly (NO containers, NO render props, NO ResourceViewerPageContent wrapper)
|
|
93
|
+
*/
|
|
94
|
+
export function ResourceViewerPage({
|
|
159
95
|
resource,
|
|
160
96
|
rUri,
|
|
161
|
-
content,
|
|
162
|
-
contentLoading,
|
|
163
|
-
annotations,
|
|
164
|
-
referencedBy,
|
|
165
|
-
referencedByLoading,
|
|
166
|
-
allEntityTypes,
|
|
167
97
|
locale,
|
|
168
|
-
theme,
|
|
169
|
-
onThemeChange,
|
|
170
|
-
showLineNumbers,
|
|
171
|
-
onLineNumbersToggle,
|
|
172
|
-
activePanel,
|
|
173
|
-
onPanelToggle,
|
|
174
|
-
setActivePanel,
|
|
175
|
-
onArchive,
|
|
176
|
-
onUnarchive,
|
|
177
|
-
onClone,
|
|
178
|
-
onUpdateAnnotationBody,
|
|
179
|
-
onCreateAnnotation,
|
|
180
|
-
onTriggerSparkleAnimation,
|
|
181
|
-
onClearNewAnnotationId,
|
|
182
|
-
showSuccess,
|
|
183
|
-
showError,
|
|
184
98
|
cacheManager,
|
|
185
|
-
client,
|
|
186
99
|
Link,
|
|
187
100
|
routes,
|
|
188
101
|
ToolbarPanels,
|
|
189
102
|
SearchResourcesModal,
|
|
190
103
|
GenerationConfigModal,
|
|
104
|
+
refetchDocument,
|
|
191
105
|
}: ResourceViewerPageProps) {
|
|
192
106
|
// Get unified event bus for subscribing to UI events
|
|
193
|
-
const eventBus =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
} =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
//
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// Unified annotation state (motivation-agnostic) - used by sidebar panels
|
|
218
|
-
const [focusedAnnotationId, _setFocusedAnnotationId] = useState<string | null>(null);
|
|
219
|
-
const [hoveredAnnotationId, setHoveredAnnotationId] = useState<string | null>(null);
|
|
220
|
-
// scrollToAnnotationId removed - ResourceViewer now manages scroll state internally
|
|
221
|
-
|
|
222
|
-
// Unified pending annotation - all human-created annotations flow through this
|
|
223
|
-
const [pendingAnnotation, setPendingAnnotation] = useState<PendingAnnotation | null>(null);
|
|
224
|
-
|
|
225
|
-
// Search state
|
|
226
|
-
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
|
227
|
-
const [searchTerm, setSearchTerm] = useState('');
|
|
228
|
-
const [pendingReferenceId, setPendingReferenceId] = useState<string | null>(null);
|
|
229
|
-
|
|
230
|
-
// Generation config modal state
|
|
231
|
-
const [generationModalOpen, setGenerationModalOpen] = useState(false);
|
|
232
|
-
const [generationReferenceId, setGenerationReferenceId] = useState<string | null>(null);
|
|
233
|
-
const [generationDefaultTitle, setGenerationDefaultTitle] = useState('');
|
|
234
|
-
|
|
235
|
-
// Unified detection state (motivation-based)
|
|
236
|
-
const [detectingMotivation, setDetectingMotivation] = useState<Motivation | null>(null);
|
|
237
|
-
const [motivationDetectionProgress, setMotivationDetectionProgress] = useState<DetectionProgress | null>(null);
|
|
238
|
-
|
|
239
|
-
// SSE stream reference for cancellation
|
|
240
|
-
const detectionStreamRef = React.useRef<any>(null);
|
|
107
|
+
const eventBus = useEventBus();
|
|
108
|
+
const queryClient = useQueryClient();
|
|
109
|
+
|
|
110
|
+
// UI state hooks
|
|
111
|
+
const { showError, showSuccess } = useToast();
|
|
112
|
+
const { theme, setTheme } = useTheme();
|
|
113
|
+
const { showLineNumbers, toggleLineNumbers } = useLineNumbers();
|
|
114
|
+
const { addResource } = useOpenResources();
|
|
115
|
+
const { triggerSparkleAnimation, clearNewAnnotationId } = useResourceAnnotations();
|
|
116
|
+
|
|
117
|
+
// API hooks
|
|
118
|
+
const resources = useResources();
|
|
119
|
+
const entityTypesAPI = useEntityTypes();
|
|
120
|
+
|
|
121
|
+
// Load all data
|
|
122
|
+
const { content, loading: contentLoading } = useResourceContent(rUri, resource);
|
|
123
|
+
|
|
124
|
+
const { data: annotationsData } = resources.annotations.useQuery(rUri);
|
|
125
|
+
const annotations = useMemo(
|
|
126
|
+
() => annotationsData?.annotations || [],
|
|
127
|
+
[annotationsData?.annotations]
|
|
128
|
+
);
|
|
241
129
|
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
setHoveredAnnotationId(annotationId);
|
|
245
|
-
if (annotationId) {
|
|
246
|
-
onTriggerSparkleAnimation(annotationId);
|
|
247
|
-
}
|
|
248
|
-
}, [onTriggerSparkleAnimation]);
|
|
130
|
+
const { data: referencedByData, isLoading: referencedByLoading } = resources.referencedBy.useQuery(rUri);
|
|
131
|
+
const referencedBy = referencedByData?.referencedBy || [];
|
|
249
132
|
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
// ResourceViewer now manages scroll state internally
|
|
253
|
-
}, []);
|
|
133
|
+
const { data: entityTypesData } = entityTypesAPI.list.useQuery();
|
|
134
|
+
const allEntityTypes = (entityTypesData as { entityTypes: string[] } | undefined)?.entityTypes || [];
|
|
254
135
|
|
|
255
|
-
//
|
|
136
|
+
// Flow state hooks (NO CONTAINERS)
|
|
137
|
+
const { detectingMotivation, detectionProgress } = useDetectionFlow(rUri);
|
|
138
|
+
const { activePanel, scrollToAnnotationId, panelInitialTab, onScrollCompleted } = usePanelNavigation();
|
|
139
|
+
const { pendingAnnotation, hoveredAnnotationId } = useAnnotationFlow(rUri);
|
|
256
140
|
const {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
141
|
+
generationProgress,
|
|
142
|
+
generationModalOpen,
|
|
143
|
+
generationReferenceId,
|
|
144
|
+
generationDefaultTitle,
|
|
145
|
+
searchModalOpen,
|
|
146
|
+
pendingReferenceId,
|
|
147
|
+
onGenerateDocument,
|
|
148
|
+
onCloseGenerationModal,
|
|
149
|
+
onCloseSearchModal,
|
|
150
|
+
} = useGenerationFlow(locale, rUri.split('/').pop() || '', showSuccess, showError, cacheManager, clearNewAnnotationId);
|
|
151
|
+
|
|
152
|
+
// Debounced invalidation for real-time events
|
|
153
|
+
const debouncedInvalidateAnnotations = useDebouncedCallback(
|
|
154
|
+
() => {
|
|
155
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.documents.annotations(rUri) });
|
|
156
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.documents.events(rUri) });
|
|
264
157
|
},
|
|
265
|
-
|
|
266
|
-
|
|
158
|
+
500
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Add resource to open tabs when it loads
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (resource && rUri) {
|
|
164
|
+
const resourceIdSegment = rUri.split('/').pop() || '';
|
|
165
|
+
const mediaType = getPrimaryMediaType(resource);
|
|
166
|
+
addResource(resourceIdSegment, resource.name, mediaType || undefined);
|
|
167
|
+
if (typeof localStorage !== 'undefined') {
|
|
168
|
+
localStorage.setItem('lastViewedDocumentId', resourceIdSegment);
|
|
169
|
+
}
|
|
267
170
|
}
|
|
268
|
-
});
|
|
171
|
+
}, [resource, rUri, addResource]);
|
|
269
172
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
client,
|
|
173
|
+
// Real-time document events (SSE)
|
|
174
|
+
useResourceEvents({
|
|
273
175
|
rUri,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
176
|
+
autoConnect: true,
|
|
177
|
+
|
|
178
|
+
// Annotation events - use debounced invalidation to batch rapid updates
|
|
179
|
+
onAnnotationAdded: useCallback((_event: any) => {
|
|
180
|
+
debouncedInvalidateAnnotations();
|
|
181
|
+
}, [debouncedInvalidateAnnotations]),
|
|
182
|
+
|
|
183
|
+
onAnnotationRemoved: useCallback((_event: any) => {
|
|
184
|
+
debouncedInvalidateAnnotations();
|
|
185
|
+
}, [debouncedInvalidateAnnotations]),
|
|
186
|
+
|
|
187
|
+
onAnnotationBodyUpdated: useCallback((event: any) => {
|
|
188
|
+
// Optimistically update annotations cache with body operations
|
|
189
|
+
queryClient.setQueryData(QUERY_KEYS.documents.annotations(rUri), (old: any) => {
|
|
190
|
+
if (!old) return old;
|
|
191
|
+
return {
|
|
192
|
+
...old,
|
|
193
|
+
annotations: old.annotations.map((annotation: any) => {
|
|
194
|
+
const annotationIdSegment = uriToAnnotationId(annotation.id);
|
|
195
|
+
if (annotationIdSegment === event.payload.annotationId) {
|
|
196
|
+
let bodyArray = Array.isArray(annotation.body) ? [...annotation.body] : [];
|
|
197
|
+
|
|
198
|
+
for (const op of event.payload.operations || []) {
|
|
199
|
+
if (op.op === 'add') {
|
|
200
|
+
bodyArray.push(op.item);
|
|
201
|
+
} else if (op.op === 'remove') {
|
|
202
|
+
bodyArray = bodyArray.filter((item: any) =>
|
|
203
|
+
JSON.stringify(item) !== JSON.stringify(op.item)
|
|
204
|
+
);
|
|
205
|
+
} else if (op.op === 'replace') {
|
|
206
|
+
const index = bodyArray.findIndex((item: any) =>
|
|
207
|
+
JSON.stringify(item) === JSON.stringify(op.oldItem)
|
|
208
|
+
);
|
|
209
|
+
if (index !== -1) {
|
|
210
|
+
bodyArray[index] = op.newItem;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
281
214
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
215
|
+
return {
|
|
216
|
+
...annotation,
|
|
217
|
+
body: bodyArray,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return annotation;
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
});
|
|
291
224
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
225
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.documents.events(rUri) });
|
|
226
|
+
}, [queryClient, rUri]),
|
|
227
|
+
|
|
228
|
+
// Document status events
|
|
229
|
+
onDocumentArchived: useCallback((_event: any) => {
|
|
230
|
+
refetchDocument();
|
|
231
|
+
showSuccess('This document has been archived');
|
|
232
|
+
debouncedInvalidateAnnotations();
|
|
233
|
+
}, [refetchDocument, showSuccess, debouncedInvalidateAnnotations]),
|
|
234
|
+
|
|
235
|
+
onDocumentUnarchived: useCallback((_event: any) => {
|
|
236
|
+
refetchDocument();
|
|
237
|
+
showSuccess('This document has been unarchived');
|
|
238
|
+
debouncedInvalidateAnnotations();
|
|
239
|
+
}, [refetchDocument, showSuccess, debouncedInvalidateAnnotations]),
|
|
240
|
+
|
|
241
|
+
// Entity tag events
|
|
242
|
+
onEntityTagAdded: useCallback((_event: any) => {
|
|
243
|
+
refetchDocument();
|
|
244
|
+
debouncedInvalidateAnnotations();
|
|
245
|
+
}, [refetchDocument, debouncedInvalidateAnnotations]),
|
|
246
|
+
|
|
247
|
+
onEntityTagRemoved: useCallback((_event: any) => {
|
|
248
|
+
refetchDocument();
|
|
249
|
+
debouncedInvalidateAnnotations();
|
|
250
|
+
}, [refetchDocument, debouncedInvalidateAnnotations]),
|
|
251
|
+
|
|
252
|
+
onError: useCallback((error: any) => {
|
|
253
|
+
console.error('[RealTime] Event stream error:', error);
|
|
254
|
+
}, []),
|
|
255
|
+
});
|
|
311
256
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
257
|
+
// Event bus subscriptions (combined into single useEventSubscriptions call to prevent hook ordering issues)
|
|
258
|
+
useEventSubscriptions({
|
|
259
|
+
// Resource operations
|
|
260
|
+
'resource:archive': async () => {
|
|
261
|
+
try {
|
|
262
|
+
await resources.update.useMutation().mutateAsync({
|
|
263
|
+
rUri,
|
|
264
|
+
data: { archived: true }
|
|
265
|
+
});
|
|
266
|
+
await refetchDocument();
|
|
267
|
+
showSuccess('Document archived');
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error('Failed to archive document:', err);
|
|
270
|
+
showError('Failed to archive document');
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
'resource:unarchive': async () => {
|
|
274
|
+
try {
|
|
275
|
+
await resources.update.useMutation().mutateAsync({
|
|
276
|
+
rUri,
|
|
277
|
+
data: { archived: false }
|
|
278
|
+
});
|
|
279
|
+
await refetchDocument();
|
|
280
|
+
showSuccess('Document unarchived');
|
|
281
|
+
} catch (err) {
|
|
282
|
+
console.error('Failed to unarchive document:', err);
|
|
283
|
+
showError('Failed to unarchive document');
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
'resource:clone': async () => {
|
|
287
|
+
try {
|
|
288
|
+
const result = await resources.generateCloneToken.useMutation().mutateAsync(rUri);
|
|
289
|
+
const token = result.token;
|
|
290
|
+
const cloneUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/know/clone?token=${token}`;
|
|
291
|
+
|
|
292
|
+
await navigator.clipboard.writeText(cloneUrl);
|
|
293
|
+
showSuccess('Clone link copied to clipboard');
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.error('Failed to generate clone token:', err);
|
|
296
|
+
showError('Failed to generate clone link');
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
// Annotation operations
|
|
301
|
+
'annotation:sparkle': ({ annotationId }) => {
|
|
302
|
+
triggerSparkleAnimation(annotationId);
|
|
303
|
+
},
|
|
304
|
+
'annotation:created': ({ annotation }) => {
|
|
305
|
+
triggerSparkleAnimation(annotation.id);
|
|
306
|
+
debouncedInvalidateAnnotations();
|
|
307
|
+
},
|
|
308
|
+
'annotation:deleted': debouncedInvalidateAnnotations,
|
|
309
|
+
'annotation:create-failed': () => showError('Failed to create annotation'),
|
|
310
|
+
'annotation:delete-failed': () => showError('Failed to delete annotation'),
|
|
311
|
+
'annotation:body-updated': () => {
|
|
312
|
+
// Success - optimistic update already applied via useResourceEvents
|
|
313
|
+
},
|
|
314
|
+
'annotation:body-update-failed': () => showError('Failed to update annotation'),
|
|
315
|
+
|
|
316
|
+
// Settings
|
|
317
|
+
'settings:theme-changed': ({ theme }) => setTheme(theme),
|
|
318
|
+
'settings:line-numbers-toggled': toggleLineNumbers,
|
|
319
|
+
|
|
320
|
+
// Detection/Generation
|
|
321
|
+
'detection:complete': () => showSuccess('Detection complete'),
|
|
322
|
+
'detection:failed': () => showError('Detection failed'),
|
|
323
|
+
'generation:complete': () => showSuccess('Document generated'),
|
|
324
|
+
'generation:failed': () => showError('Failed to generate document'),
|
|
325
|
+
|
|
326
|
+
// Navigation
|
|
327
|
+
'navigation:reference-navigate': ({ documentId }: { documentId: string }) => {
|
|
328
|
+
// Navigate to the referenced document
|
|
329
|
+
if (routes.resource) {
|
|
330
|
+
const path = routes.resource.replace('[resourceId]', encodeURIComponent(documentId));
|
|
331
|
+
eventBus.emit('navigation:router-push', { path, reason: 'reference-link' });
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
'navigation:entity-type-clicked': ({ entityType }: { entityType: string }) => {
|
|
335
|
+
// Navigate to discovery page filtered by entity type
|
|
336
|
+
if (routes.know) {
|
|
337
|
+
const path = `${routes.know}?entityType=${encodeURIComponent(entityType)}`;
|
|
338
|
+
eventBus.emit('navigation:router-push', { path, reason: 'entity-type-filter' });
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
});
|
|
357
342
|
|
|
343
|
+
// Resource loading announcements
|
|
344
|
+
const {
|
|
345
|
+
announceResourceLoading,
|
|
346
|
+
announceResourceLoaded
|
|
347
|
+
} = useResourceLoadingAnnouncements();
|
|
358
348
|
|
|
359
|
-
// Announce content loading state changes
|
|
349
|
+
// Announce content loading state changes (app-level)
|
|
360
350
|
useEffect(() => {
|
|
361
351
|
if (contentLoading) {
|
|
362
352
|
announceResourceLoading(resource.name);
|
|
@@ -365,211 +355,21 @@ function ResourceViewerPageInner({
|
|
|
365
355
|
}
|
|
366
356
|
}, [contentLoading, content, resource.name, announceResourceLoading, announceResourceLoaded]);
|
|
367
357
|
|
|
368
|
-
//
|
|
369
|
-
const
|
|
370
|
-
// Route to appropriate panel tab based on motivation
|
|
371
|
-
const MOTIVATION_TO_TAB: Record<Motivation, string> = {
|
|
372
|
-
highlighting: 'annotations',
|
|
373
|
-
commenting: 'annotations',
|
|
374
|
-
assessing: 'annotations',
|
|
375
|
-
tagging: 'annotations',
|
|
376
|
-
linking: 'annotations',
|
|
377
|
-
bookmarking: 'annotations',
|
|
378
|
-
classifying: 'annotations',
|
|
379
|
-
describing: 'annotations',
|
|
380
|
-
editing: 'annotations',
|
|
381
|
-
identifying: 'annotations',
|
|
382
|
-
moderating: 'annotations',
|
|
383
|
-
questioning: 'annotations',
|
|
384
|
-
replying: 'annotations',
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
setActivePanel(MOTIVATION_TO_TAB[pending.motivation] || 'annotations');
|
|
388
|
-
setPendingAnnotation(pending);
|
|
389
|
-
}, [setActivePanel]);
|
|
390
|
-
|
|
391
|
-
// Subscribe to UI events from ResourceViewer
|
|
392
|
-
useEffect(() => {
|
|
393
|
-
const handleCommentRequested = (selection: any) => {
|
|
394
|
-
handleAnnotationRequested({
|
|
395
|
-
selector: {
|
|
396
|
-
type: 'TextQuoteSelector',
|
|
397
|
-
exact: selection.exact,
|
|
398
|
-
start: selection.start,
|
|
399
|
-
end: selection.end,
|
|
400
|
-
...(selection.prefix && { prefix: selection.prefix }),
|
|
401
|
-
...(selection.suffix && { suffix: selection.suffix })
|
|
402
|
-
},
|
|
403
|
-
motivation: 'commenting'
|
|
404
|
-
});
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
const handleTagRequested = (selection: any) => {
|
|
408
|
-
handleAnnotationRequested({
|
|
409
|
-
selector: {
|
|
410
|
-
type: 'TextQuoteSelector',
|
|
411
|
-
exact: selection.exact,
|
|
412
|
-
start: selection.start,
|
|
413
|
-
end: selection.end,
|
|
414
|
-
...(selection.prefix && { prefix: selection.prefix }),
|
|
415
|
-
...(selection.suffix && { suffix: selection.suffix })
|
|
416
|
-
},
|
|
417
|
-
motivation: 'tagging'
|
|
418
|
-
});
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const handleAssessmentRequested = (selection: any) => {
|
|
422
|
-
handleAnnotationRequested({
|
|
423
|
-
selector: {
|
|
424
|
-
type: 'TextQuoteSelector',
|
|
425
|
-
exact: selection.exact,
|
|
426
|
-
start: selection.start,
|
|
427
|
-
end: selection.end,
|
|
428
|
-
...(selection.prefix && { prefix: selection.prefix }),
|
|
429
|
-
...(selection.suffix && { suffix: selection.suffix })
|
|
430
|
-
},
|
|
431
|
-
motivation: 'assessing'
|
|
432
|
-
});
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const handleReferenceRequested = (selection: any) => {
|
|
436
|
-
// Build selector based on what's present in the selection
|
|
437
|
-
let selector: any;
|
|
438
|
-
|
|
439
|
-
if (selection.svgSelector) {
|
|
440
|
-
selector = {
|
|
441
|
-
type: 'SvgSelector',
|
|
442
|
-
value: selection.svgSelector
|
|
443
|
-
};
|
|
444
|
-
} else if (selection.fragmentSelector) {
|
|
445
|
-
selector = {
|
|
446
|
-
type: 'FragmentSelector',
|
|
447
|
-
value: selection.fragmentSelector,
|
|
448
|
-
...(selection.conformsTo && { conformsTo: selection.conformsTo })
|
|
449
|
-
};
|
|
450
|
-
} else {
|
|
451
|
-
selector = {
|
|
452
|
-
type: 'TextQuoteSelector',
|
|
453
|
-
exact: selection.exact,
|
|
454
|
-
start: selection.start,
|
|
455
|
-
end: selection.end,
|
|
456
|
-
...(selection.prefix && { prefix: selection.prefix }),
|
|
457
|
-
...(selection.suffix && { suffix: selection.suffix })
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
handleAnnotationRequested({
|
|
462
|
-
selector,
|
|
463
|
-
motivation: 'linking'
|
|
464
|
-
});
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
// Handle cancel pending annotation
|
|
468
|
-
const handleCancelPending = () => {
|
|
469
|
-
setPendingAnnotation(null);
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
eventBus.on('ui:selection:comment-requested', handleCommentRequested);
|
|
473
|
-
eventBus.on('ui:selection:tag-requested', handleTagRequested);
|
|
474
|
-
eventBus.on('ui:selection:assessment-requested', handleAssessmentRequested);
|
|
475
|
-
eventBus.on('ui:selection:reference-requested', handleReferenceRequested);
|
|
476
|
-
eventBus.on('ui:annotation:cancel-pending', handleCancelPending);
|
|
477
|
-
|
|
478
|
-
return () => {
|
|
479
|
-
eventBus.off('ui:selection:comment-requested', handleCommentRequested);
|
|
480
|
-
eventBus.off('ui:selection:tag-requested', handleTagRequested);
|
|
481
|
-
eventBus.off('ui:selection:assessment-requested', handleAssessmentRequested);
|
|
482
|
-
eventBus.off('ui:selection:reference-requested', handleReferenceRequested);
|
|
483
|
-
eventBus.off('ui:annotation:cancel-pending', handleCancelPending);
|
|
484
|
-
};
|
|
485
|
-
}, [eventBus, handleAnnotationRequested]);
|
|
486
|
-
|
|
487
|
-
// Manual tag creation handler
|
|
488
|
-
// Shared UI handlers - same across all annotation types
|
|
489
|
-
const handleAnnotationClick = useCallback((annotation: Annotation) => {
|
|
490
|
-
setHoveredAnnotationId(annotation.id);
|
|
491
|
-
setTimeout(() => setHoveredAnnotationId(null), 1500);
|
|
492
|
-
}, []);
|
|
493
|
-
|
|
494
|
-
const handleAnnotationHover = useCallback((annotationId: string | null) => {
|
|
495
|
-
setHoveredAnnotationId(annotationId);
|
|
496
|
-
}, []);
|
|
497
|
-
|
|
498
|
-
// Single generic annotation creation handler - reads config from ANNOTATORS
|
|
499
|
-
const handleCreateAnnotation = useCallback(async (
|
|
500
|
-
motivation: Motivation,
|
|
501
|
-
...args: any[]
|
|
502
|
-
) => {
|
|
503
|
-
if (!pendingAnnotation || pendingAnnotation.motivation !== motivation) return;
|
|
504
|
-
|
|
505
|
-
// Find the config for this motivation
|
|
506
|
-
const annotatorConfig = Object.values(ANNOTATORS).find(a => a.motivation === motivation);
|
|
507
|
-
if (!annotatorConfig) return;
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
let body: any[] = [];
|
|
511
|
-
let selector = pendingAnnotation.selector;
|
|
512
|
-
|
|
513
|
-
// Build body based on config
|
|
514
|
-
switch (annotatorConfig.create.bodyBuilder) {
|
|
515
|
-
case 'empty':
|
|
516
|
-
// args[0] might be selector for highlight/assessment
|
|
517
|
-
if (args[0]) selector = args[0];
|
|
518
|
-
body = [];
|
|
519
|
-
break;
|
|
520
|
-
|
|
521
|
-
case 'text':
|
|
522
|
-
// args[0] is commentText
|
|
523
|
-
body = [{
|
|
524
|
-
type: 'TextualBody',
|
|
525
|
-
value: args[0],
|
|
526
|
-
format: 'text/plain',
|
|
527
|
-
purpose: 'commenting'
|
|
528
|
-
}];
|
|
529
|
-
break;
|
|
530
|
-
|
|
531
|
-
case 'entityTag':
|
|
532
|
-
// args[0] is optional entityType
|
|
533
|
-
if (args[0]) {
|
|
534
|
-
body = [{
|
|
535
|
-
type: 'TextualBody',
|
|
536
|
-
purpose: 'tagging',
|
|
537
|
-
value: args[0]
|
|
538
|
-
}];
|
|
539
|
-
}
|
|
540
|
-
break;
|
|
541
|
-
|
|
542
|
-
case 'dualTag':
|
|
543
|
-
// args[0] is schemaId, args[1] is category
|
|
544
|
-
body = [
|
|
545
|
-
{
|
|
546
|
-
type: 'TextualBody',
|
|
547
|
-
purpose: 'tagging',
|
|
548
|
-
value: args[1] // category
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
type: 'TextualBody',
|
|
552
|
-
purpose: 'classifying',
|
|
553
|
-
value: args[0] // schemaId
|
|
554
|
-
}
|
|
555
|
-
];
|
|
556
|
-
break;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
await onCreateAnnotation(rUri, motivation, selector, body);
|
|
560
|
-
setPendingAnnotation(null);
|
|
358
|
+
// Derived state
|
|
359
|
+
const documentEntityTypes = resource.entityTypes || [];
|
|
561
360
|
|
|
562
|
-
|
|
361
|
+
// Get primary representation metadata
|
|
362
|
+
const primaryRep = getPrimaryRepresentation(resource);
|
|
363
|
+
const primaryMediaType = primaryRep?.mediaType;
|
|
364
|
+
const primaryByteSize = primaryRep?.byteSize;
|
|
563
365
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
} catch (error) {
|
|
569
|
-
console.error(`Failed to create ${annotatorConfig.internalType}:`, error);
|
|
570
|
-
showError(`Failed to create ${annotatorConfig.displayName.toLowerCase()}`);
|
|
366
|
+
// Annotate mode state - local UI state only
|
|
367
|
+
const [annotateMode, _setAnnotateMode] = useState(() => {
|
|
368
|
+
if (typeof window !== 'undefined') {
|
|
369
|
+
return localStorage.getItem('annotateMode') === 'true';
|
|
571
370
|
}
|
|
572
|
-
|
|
371
|
+
return false;
|
|
372
|
+
});
|
|
573
373
|
|
|
574
374
|
// Group annotations by type using static ANNOTATORS
|
|
575
375
|
const result = {
|
|
@@ -595,7 +395,16 @@ function ResourceViewerPageInner({
|
|
|
595
395
|
// Combine resource with content
|
|
596
396
|
const resourceWithContent = { ...resource, content };
|
|
597
397
|
|
|
598
|
-
//
|
|
398
|
+
// Handlers for AnnotationHistory (legacy event-based interaction)
|
|
399
|
+
const handleEventHover = useCallback((annotationId: string | null) => {
|
|
400
|
+
if (annotationId) {
|
|
401
|
+
eventBus.emit('annotation:sparkle', { annotationId });
|
|
402
|
+
}
|
|
403
|
+
}, []); // eventBus is stable singleton - never in deps
|
|
404
|
+
|
|
405
|
+
const handleEventClick = useCallback((_annotationId: string | null) => {
|
|
406
|
+
// ResourceViewer now manages scroll state internally
|
|
407
|
+
}, []);
|
|
599
408
|
|
|
600
409
|
// Document rendering
|
|
601
410
|
return (
|
|
@@ -639,12 +448,11 @@ function ResourceViewerPageInner({
|
|
|
639
448
|
) : (
|
|
640
449
|
<ResourceViewer
|
|
641
450
|
resource={resourceWithContent}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
/>
|
|
451
|
+
annotations={groups}
|
|
452
|
+
generatingReferenceId={generationProgress?.referenceId ?? null}
|
|
453
|
+
showLineNumbers={showLineNumbers}
|
|
454
|
+
hoveredAnnotationId={hoveredAnnotationId}
|
|
455
|
+
/>
|
|
648
456
|
)}
|
|
649
457
|
</ErrorBoundary>
|
|
650
458
|
</div>
|
|
@@ -656,9 +464,7 @@ function ResourceViewerPageInner({
|
|
|
656
464
|
<ToolbarPanels
|
|
657
465
|
activePanel={activePanel}
|
|
658
466
|
theme={theme}
|
|
659
|
-
onThemeChange={onThemeChange}
|
|
660
467
|
showLineNumbers={showLineNumbers}
|
|
661
|
-
onLineNumbersToggle={onLineNumbersToggle}
|
|
662
468
|
width={
|
|
663
469
|
activePanel === 'jsonld' ? 'w-[600px]' :
|
|
664
470
|
activePanel === 'annotations' ? 'w-[400px]' :
|
|
@@ -679,26 +485,20 @@ function ResourceViewerPageInner({
|
|
|
679
485
|
<UnifiedAnnotationsPanel
|
|
680
486
|
annotations={annotations}
|
|
681
487
|
annotators={ANNOTATORS}
|
|
682
|
-
onCreateAnnotation={handleCreateAnnotation}
|
|
683
|
-
detectionContext={detectionContext}
|
|
684
|
-
focusedAnnotationId={focusedAnnotationId}
|
|
685
|
-
hoveredAnnotationId={hoveredAnnotationId}
|
|
686
|
-
onAnnotationClick={handleAnnotationClick}
|
|
687
|
-
onAnnotationHover={handleAnnotationHover}
|
|
688
488
|
annotateMode={annotateMode}
|
|
689
489
|
detectingMotivation={detectingMotivation}
|
|
690
|
-
detectionProgress={
|
|
490
|
+
detectionProgress={detectionProgress}
|
|
691
491
|
pendingAnnotation={pendingAnnotation}
|
|
692
492
|
allEntityTypes={allEntityTypes}
|
|
693
|
-
onGenerateDocument={handleGenerateDocument}
|
|
694
|
-
onCreateDocument={handleCreateDocument}
|
|
695
493
|
generatingReferenceId={generationProgress?.referenceId ?? null}
|
|
696
|
-
onSearchDocuments={handleSearchDocuments}
|
|
697
|
-
onCancelDetection={handleCancelDetection}
|
|
698
|
-
{...(primaryMediaType ? { mediaType: primaryMediaType } : {})}
|
|
699
494
|
referencedBy={referencedBy}
|
|
700
495
|
referencedByLoading={referencedByLoading}
|
|
701
496
|
resourceId={rUri.split('/').pop() || ''}
|
|
497
|
+
scrollToAnnotationId={scrollToAnnotationId}
|
|
498
|
+
hoveredAnnotationId={hoveredAnnotationId}
|
|
499
|
+
onScrollCompleted={onScrollCompleted}
|
|
500
|
+
initialTab={panelInitialTab?.tab as any}
|
|
501
|
+
initialTabGeneration={panelInitialTab?.generation}
|
|
702
502
|
Link={Link}
|
|
703
503
|
routes={routes}
|
|
704
504
|
/>
|
|
@@ -724,9 +524,6 @@ function ResourceViewerPageInner({
|
|
|
724
524
|
primaryMediaType={primaryMediaType}
|
|
725
525
|
primaryByteSize={primaryByteSize}
|
|
726
526
|
isArchived={resource.archived ?? false}
|
|
727
|
-
onClone={onClone}
|
|
728
|
-
onArchive={onArchive}
|
|
729
|
-
onUnarchive={onUnarchive}
|
|
730
527
|
/>
|
|
731
528
|
)}
|
|
732
529
|
|
|
@@ -749,7 +546,6 @@ function ResourceViewerPageInner({
|
|
|
749
546
|
context="document"
|
|
750
547
|
activePanel={activePanel}
|
|
751
548
|
isArchived={resource.archived ?? false}
|
|
752
|
-
onPanelToggle={onPanelToggle}
|
|
753
549
|
/>
|
|
754
550
|
</div>
|
|
755
551
|
</div>
|
|
@@ -757,10 +553,7 @@ function ResourceViewerPageInner({
|
|
|
757
553
|
{/* Search Resources Modal */}
|
|
758
554
|
<SearchResourcesModal
|
|
759
555
|
isOpen={searchModalOpen}
|
|
760
|
-
onClose={
|
|
761
|
-
setSearchModalOpen(false);
|
|
762
|
-
setPendingReferenceId(null);
|
|
763
|
-
}}
|
|
556
|
+
onClose={onCloseSearchModal}
|
|
764
557
|
onSelect={async (documentId: string) => {
|
|
765
558
|
if (pendingReferenceId) {
|
|
766
559
|
try {
|
|
@@ -772,7 +565,8 @@ function ResourceViewerPageInner({
|
|
|
772
565
|
const resourceIdSegment = rUri.split('/').pop() || '';
|
|
773
566
|
const nestedUri = `${window.location.origin}/resources/${resourceIdSegment}/annotations/${annotationIdShort}`;
|
|
774
567
|
|
|
775
|
-
|
|
568
|
+
eventBus.emit('annotation:update-body', {
|
|
569
|
+
annotationUri: resourceAnnotationUri(nestedUri),
|
|
776
570
|
resourceId: resourceIdSegment,
|
|
777
571
|
operations: [{
|
|
778
572
|
op: 'add',
|
|
@@ -785,27 +579,22 @@ function ResourceViewerPageInner({
|
|
|
785
579
|
});
|
|
786
580
|
showSuccess('Reference linked successfully');
|
|
787
581
|
// Cache invalidation now handled by annotation:updated event
|
|
788
|
-
|
|
789
|
-
setPendingReferenceId(null);
|
|
582
|
+
onCloseSearchModal();
|
|
790
583
|
} catch (error) {
|
|
791
584
|
console.error('Failed to link reference:', error);
|
|
792
585
|
showError('Failed to link reference');
|
|
793
586
|
}
|
|
794
587
|
}
|
|
795
588
|
}}
|
|
796
|
-
searchTerm={searchTerm}
|
|
797
589
|
/>
|
|
798
590
|
|
|
799
591
|
{/* Generation Config Modal */}
|
|
800
592
|
<GenerationConfigModal
|
|
801
593
|
isOpen={generationModalOpen}
|
|
802
|
-
onClose={
|
|
803
|
-
setGenerationModalOpen(false);
|
|
804
|
-
setGenerationReferenceId(null);
|
|
805
|
-
}}
|
|
594
|
+
onClose={onCloseGenerationModal}
|
|
806
595
|
onGenerate={(options: GenerationOptions) => {
|
|
807
596
|
if (generationReferenceId) {
|
|
808
|
-
|
|
597
|
+
onGenerateDocument(generationReferenceId, options);
|
|
809
598
|
}
|
|
810
599
|
}}
|
|
811
600
|
referenceId={generationReferenceId || ''}
|
|
@@ -815,12 +604,3 @@ function ResourceViewerPageInner({
|
|
|
815
604
|
</div>
|
|
816
605
|
);
|
|
817
606
|
}
|
|
818
|
-
|
|
819
|
-
// Outer component that wraps MakeMeaningEventBusProvider
|
|
820
|
-
export function ResourceViewerPage(props: ResourceViewerPageProps) {
|
|
821
|
-
return (
|
|
822
|
-
<MakeMeaningEventBusProvider rUri={props.rUri}>
|
|
823
|
-
<ResourceViewerPageInner {...props} />
|
|
824
|
-
</MakeMeaningEventBusProvider>
|
|
825
|
-
);
|
|
826
|
-
}
|