@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,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
8
|
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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,18 +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';
|
|
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';
|
|
23
41
|
|
|
24
42
|
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
25
43
|
type Annotation = components['schemas']['Annotation'];
|
|
26
|
-
type Motivation = components['schemas']['Motivation'];
|
|
27
|
-
|
|
28
|
-
// Unified pending annotation type - all human-created annotations flow through this
|
|
29
|
-
interface PendingAnnotation {
|
|
30
|
-
selector: Selector | Selector[];
|
|
31
|
-
motivation: Motivation;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
import type { DetectionProgress } from '@semiont/react-ui';
|
|
35
44
|
|
|
36
45
|
export interface ResourceViewerPageProps {
|
|
37
46
|
/**
|
|
@@ -44,98 +53,16 @@ export interface ResourceViewerPageProps {
|
|
|
44
53
|
*/
|
|
45
54
|
rUri: ResourceUri;
|
|
46
55
|
|
|
47
|
-
/**
|
|
48
|
-
* Document content (already loaded)
|
|
49
|
-
*/
|
|
50
|
-
content: string;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Whether content is still loading
|
|
54
|
-
*/
|
|
55
|
-
contentLoading: boolean;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* All annotations for this resource
|
|
59
|
-
*/
|
|
60
|
-
annotations: Annotation[];
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Resources that reference this resource
|
|
64
|
-
*/
|
|
65
|
-
referencedBy: any[];
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Whether referencedBy is loading
|
|
69
|
-
*/
|
|
70
|
-
referencedByLoading: boolean;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* All available entity types
|
|
74
|
-
*/
|
|
75
|
-
allEntityTypes: string[];
|
|
76
|
-
|
|
77
56
|
/**
|
|
78
57
|
* Current locale
|
|
79
58
|
*/
|
|
80
59
|
locale: string;
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Theme state
|
|
85
|
-
*/
|
|
86
|
-
theme: any;
|
|
87
|
-
onThemeChange: (theme: any) => void;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Line numbers state
|
|
91
|
-
*/
|
|
92
|
-
showLineNumbers: boolean;
|
|
93
|
-
onLineNumbersToggle: () => void;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Active toolbar panel
|
|
97
|
-
*/
|
|
98
|
-
activePanel: any;
|
|
99
|
-
onPanelToggle: (panel: any) => void;
|
|
100
|
-
setActivePanel: (panel: any) => void;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Callbacks for resource actions
|
|
104
|
-
*/
|
|
105
|
-
onArchive: () => Promise<void>;
|
|
106
|
-
onUnarchive: () => Promise<void>;
|
|
107
|
-
onClone: () => Promise<void>;
|
|
108
|
-
onUpdateAnnotationBody: (annotationUri: string, data: any) => Promise<void>;
|
|
109
|
-
onRefetchAnnotations: () => 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,223 +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
|
|
|
89
|
+
/**
|
|
90
|
+
* ResourceViewerPage - Main component
|
|
91
|
+
*
|
|
92
|
+
* Uses hooks directly (NO containers, NO render props, NO ResourceViewerPageContent wrapper)
|
|
93
|
+
*/
|
|
157
94
|
export function ResourceViewerPage({
|
|
158
95
|
resource,
|
|
159
96
|
rUri,
|
|
160
|
-
content,
|
|
161
|
-
contentLoading,
|
|
162
|
-
annotations,
|
|
163
|
-
referencedBy,
|
|
164
|
-
referencedByLoading,
|
|
165
|
-
allEntityTypes,
|
|
166
97
|
locale,
|
|
167
|
-
theme,
|
|
168
|
-
onThemeChange,
|
|
169
|
-
showLineNumbers,
|
|
170
|
-
onLineNumbersToggle,
|
|
171
|
-
activePanel,
|
|
172
|
-
onPanelToggle,
|
|
173
|
-
setActivePanel,
|
|
174
|
-
onArchive,
|
|
175
|
-
onUnarchive,
|
|
176
|
-
onClone,
|
|
177
|
-
onUpdateAnnotationBody,
|
|
178
|
-
onRefetchAnnotations,
|
|
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
|
-
//
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const [hoveredAnnotationId, setHoveredAnnotationId] = useState<string | null>(null);
|
|
216
|
-
const [scrollToAnnotationId, setScrollToAnnotationId] = useState<string | null>(null);
|
|
217
|
-
|
|
218
|
-
// Unified pending annotation - all human-created annotations flow through this
|
|
219
|
-
const [pendingAnnotation, setPendingAnnotation] = useState<PendingAnnotation | null>(null);
|
|
220
|
-
|
|
221
|
-
// Search state
|
|
222
|
-
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
|
223
|
-
const [searchTerm, setSearchTerm] = useState('');
|
|
224
|
-
const [pendingReferenceId, setPendingReferenceId] = useState<string | null>(null);
|
|
225
|
-
|
|
226
|
-
// Generation config modal state
|
|
227
|
-
const [generationModalOpen, setGenerationModalOpen] = useState(false);
|
|
228
|
-
const [generationReferenceId, setGenerationReferenceId] = useState<string | null>(null);
|
|
229
|
-
const [generationDefaultTitle, setGenerationDefaultTitle] = useState('');
|
|
230
|
-
|
|
231
|
-
// Unified detection state (motivation-based)
|
|
232
|
-
const [detectingMotivation, setDetectingMotivation] = useState<Motivation | null>(null);
|
|
233
|
-
const [motivationDetectionProgress, setMotivationDetectionProgress] = useState<DetectionProgress | null>(null);
|
|
234
|
-
|
|
235
|
-
// SSE stream reference for cancellation
|
|
236
|
-
const detectionStreamRef = React.useRef<any>(null);
|
|
237
|
-
|
|
238
|
-
// Handle event hover - trigger sparkle animation
|
|
239
|
-
const handleEventHover = useCallback((annotationId: string | null) => {
|
|
240
|
-
setHoveredAnnotationId(annotationId);
|
|
241
|
-
if (annotationId) {
|
|
242
|
-
onTriggerSparkleAnimation(annotationId);
|
|
243
|
-
}
|
|
244
|
-
}, [onTriggerSparkleAnimation]);
|
|
106
|
+
// Get unified event bus for subscribing to UI events
|
|
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
|
+
);
|
|
245
129
|
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
setScrollToAnnotationId(annotationId);
|
|
249
|
-
}, []);
|
|
130
|
+
const { data: referencedByData, isLoading: referencedByLoading } = resources.referencedBy.useQuery(rUri);
|
|
131
|
+
const referencedBy = referencedByData?.referencedBy || [];
|
|
250
132
|
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
const newMode = !annotateMode;
|
|
254
|
-
setAnnotateMode(newMode);
|
|
255
|
-
if (typeof window !== 'undefined') {
|
|
256
|
-
localStorage.setItem('annotateMode', newMode.toString());
|
|
257
|
-
}
|
|
258
|
-
}, [annotateMode]);
|
|
133
|
+
const { data: entityTypesData } = entityTypesAPI.list.useQuery();
|
|
134
|
+
const allEntityTypes = (entityTypesData as { entityTypes: string[] } | undefined)?.entityTypes || [];
|
|
259
135
|
|
|
260
|
-
//
|
|
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);
|
|
261
140
|
const {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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) });
|
|
269
157
|
},
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
}
|
|
272
170
|
}
|
|
273
|
-
});
|
|
171
|
+
}, [resource, rUri, addResource]);
|
|
274
172
|
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
// Note: State setters (setDetectingMotivation, setMotivationDetectionProgress) are stable and don't need deps
|
|
278
|
-
const detectionContext = useMemo(() => ({
|
|
279
|
-
client,
|
|
173
|
+
// Real-time document events (SSE)
|
|
174
|
+
useResourceEvents({
|
|
280
175
|
rUri,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
298
214
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
215
|
+
return {
|
|
216
|
+
...annotation,
|
|
217
|
+
body: bodyArray,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return annotation;
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
|
|
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
|
+
});
|
|
318
256
|
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
setSearchTerm(searchTerm);
|
|
362
|
-
setSearchModalOpen(true);
|
|
363
|
-
}, []);
|
|
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
|
+
},
|
|
364
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
|
+
});
|
|
342
|
+
|
|
343
|
+
// Resource loading announcements
|
|
344
|
+
const {
|
|
345
|
+
announceResourceLoading,
|
|
346
|
+
announceResourceLoaded
|
|
347
|
+
} = useResourceLoadingAnnouncements();
|
|
365
348
|
|
|
366
|
-
// Announce content loading state changes
|
|
349
|
+
// Announce content loading state changes (app-level)
|
|
367
350
|
useEffect(() => {
|
|
368
351
|
if (contentLoading) {
|
|
369
352
|
announceResourceLoading(resource.name);
|
|
@@ -372,156 +355,55 @@ export function ResourceViewerPage({
|
|
|
372
355
|
}
|
|
373
356
|
}, [contentLoading, content, resource.name, announceResourceLoading, announceResourceLoaded]);
|
|
374
357
|
|
|
375
|
-
//
|
|
376
|
-
const
|
|
377
|
-
// Route to appropriate panel tab based on motivation
|
|
378
|
-
const MOTIVATION_TO_TAB: Record<Motivation, string> = {
|
|
379
|
-
highlighting: 'annotations',
|
|
380
|
-
commenting: 'annotations',
|
|
381
|
-
assessing: 'annotations',
|
|
382
|
-
tagging: 'annotations',
|
|
383
|
-
linking: 'annotations',
|
|
384
|
-
bookmarking: 'annotations',
|
|
385
|
-
classifying: 'annotations',
|
|
386
|
-
describing: 'annotations',
|
|
387
|
-
editing: 'annotations',
|
|
388
|
-
identifying: 'annotations',
|
|
389
|
-
moderating: 'annotations',
|
|
390
|
-
questioning: 'annotations',
|
|
391
|
-
replying: 'annotations',
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
setActivePanel(MOTIVATION_TO_TAB[pending.motivation] || 'annotations');
|
|
395
|
-
setPendingAnnotation(pending);
|
|
396
|
-
}, []);
|
|
397
|
-
|
|
398
|
-
// Manual tag creation handler
|
|
399
|
-
// Shared UI handlers - same across all annotation types
|
|
400
|
-
const handleAnnotationClick = useCallback((annotation: Annotation) => {
|
|
401
|
-
setHoveredAnnotationId(annotation.id);
|
|
402
|
-
setTimeout(() => setHoveredAnnotationId(null), 1500);
|
|
403
|
-
}, []);
|
|
404
|
-
|
|
405
|
-
const handleAnnotationHover = useCallback((annotationId: string | null) => {
|
|
406
|
-
setHoveredAnnotationId(annotationId);
|
|
407
|
-
}, []);
|
|
408
|
-
|
|
409
|
-
// Single generic annotation creation handler - reads config from ANNOTATORS
|
|
410
|
-
const handleCreateAnnotation = useCallback(async (
|
|
411
|
-
motivation: Motivation,
|
|
412
|
-
...args: any[]
|
|
413
|
-
) => {
|
|
414
|
-
if (!pendingAnnotation || pendingAnnotation.motivation !== motivation) return;
|
|
415
|
-
|
|
416
|
-
// Find the config for this motivation
|
|
417
|
-
const annotatorConfig = Object.values(ANNOTATORS).find(a => a.motivation === motivation);
|
|
418
|
-
if (!annotatorConfig) return;
|
|
419
|
-
|
|
420
|
-
try {
|
|
421
|
-
let body: any[] = [];
|
|
422
|
-
let selector = pendingAnnotation.selector;
|
|
423
|
-
|
|
424
|
-
// Build body based on config
|
|
425
|
-
switch (annotatorConfig.create.bodyBuilder) {
|
|
426
|
-
case 'empty':
|
|
427
|
-
// args[0] might be selector for highlight/assessment
|
|
428
|
-
if (args[0]) selector = args[0];
|
|
429
|
-
body = [];
|
|
430
|
-
break;
|
|
431
|
-
|
|
432
|
-
case 'text':
|
|
433
|
-
// args[0] is commentText
|
|
434
|
-
body = [{
|
|
435
|
-
type: 'TextualBody',
|
|
436
|
-
value: args[0],
|
|
437
|
-
format: 'text/plain',
|
|
438
|
-
purpose: 'commenting'
|
|
439
|
-
}];
|
|
440
|
-
break;
|
|
441
|
-
|
|
442
|
-
case 'entityTag':
|
|
443
|
-
// args[0] is optional entityType
|
|
444
|
-
if (args[0]) {
|
|
445
|
-
body = [{
|
|
446
|
-
type: 'TextualBody',
|
|
447
|
-
purpose: 'tagging',
|
|
448
|
-
value: args[0]
|
|
449
|
-
}];
|
|
450
|
-
}
|
|
451
|
-
break;
|
|
452
|
-
|
|
453
|
-
case 'dualTag':
|
|
454
|
-
// args[0] is schemaId, args[1] is category
|
|
455
|
-
body = [
|
|
456
|
-
{
|
|
457
|
-
type: 'TextualBody',
|
|
458
|
-
purpose: 'tagging',
|
|
459
|
-
value: args[1] // category
|
|
460
|
-
},
|
|
461
|
-
{
|
|
462
|
-
type: 'TextualBody',
|
|
463
|
-
purpose: 'classifying',
|
|
464
|
-
value: args[0] // schemaId
|
|
465
|
-
}
|
|
466
|
-
];
|
|
467
|
-
break;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
await onCreateAnnotation(rUri, motivation, selector, body);
|
|
471
|
-
setPendingAnnotation(null);
|
|
358
|
+
// Derived state
|
|
359
|
+
const documentEntityTypes = resource.entityTypes || [];
|
|
472
360
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
361
|
+
// Get primary representation metadata
|
|
362
|
+
const primaryRep = getPrimaryRepresentation(resource);
|
|
363
|
+
const primaryMediaType = primaryRep?.mediaType;
|
|
364
|
+
const primaryByteSize = primaryRep?.byteSize;
|
|
476
365
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
} catch (error) {
|
|
482
|
-
console.error(`Failed to create ${annotatorConfig.internalType}:`, error);
|
|
483
|
-
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';
|
|
484
370
|
}
|
|
485
|
-
|
|
371
|
+
return false;
|
|
372
|
+
});
|
|
486
373
|
|
|
487
374
|
// Group annotations by type using static ANNOTATORS
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
result[key as keyof typeof result].push(ann);
|
|
503
|
-
}
|
|
375
|
+
const result = {
|
|
376
|
+
highlights: [] as Annotation[],
|
|
377
|
+
references: [] as Annotation[],
|
|
378
|
+
assessments: [] as Annotation[],
|
|
379
|
+
comments: [] as Annotation[],
|
|
380
|
+
tags: [] as Annotation[]
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
for (const ann of annotations) {
|
|
384
|
+
const annotator = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(ann));
|
|
385
|
+
if (annotator) {
|
|
386
|
+
const key = annotator.internalType + 's'; // highlight -> highlights
|
|
387
|
+
if (result[key as keyof typeof result]) {
|
|
388
|
+
result[key as keyof typeof result].push(ann);
|
|
504
389
|
}
|
|
505
390
|
}
|
|
391
|
+
}
|
|
506
392
|
|
|
507
|
-
|
|
508
|
-
}, [annotations]);
|
|
393
|
+
const groups = result;
|
|
509
394
|
|
|
510
|
-
//
|
|
511
|
-
const resourceWithContent =
|
|
512
|
-
() => ({ ...resource, content }),
|
|
513
|
-
[resource, content]
|
|
514
|
-
);
|
|
395
|
+
// Combine resource with content
|
|
396
|
+
const resourceWithContent = { ...resource, content };
|
|
515
397
|
|
|
516
|
-
//
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
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
|
|
520
404
|
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
setFocusedAnnotationId(annotationId);
|
|
524
|
-
setTimeout(() => setFocusedAnnotationId(null), 3000);
|
|
405
|
+
const handleEventClick = useCallback((_annotationId: string | null) => {
|
|
406
|
+
// ResourceViewer now manages scroll state internally
|
|
525
407
|
}, []);
|
|
526
408
|
|
|
527
409
|
// Document rendering
|
|
@@ -566,25 +448,11 @@ export function ResourceViewerPage({
|
|
|
566
448
|
) : (
|
|
567
449
|
<ResourceViewer
|
|
568
450
|
resource={resourceWithContent}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
onCommentClick={handleAnnotationClickAndFocus}
|
|
575
|
-
onReferenceClick={handleAnnotationClickAndFocus}
|
|
576
|
-
onHighlightClick={handleAnnotationClickAndFocus}
|
|
577
|
-
onAssessmentClick={handleAnnotationClickAndFocus}
|
|
578
|
-
onTagClick={handleAnnotationClickAndFocus}
|
|
579
|
-
generatingReferenceId={generationProgress?.referenceId ?? null}
|
|
580
|
-
onAnnotationHover={setHoveredAnnotationId}
|
|
581
|
-
onCommentHover={setHoveredAnnotationId}
|
|
582
|
-
hoveredAnnotationId={hoveredAnnotationId}
|
|
583
|
-
hoveredCommentId={hoveredAnnotationId}
|
|
584
|
-
scrollToAnnotationId={scrollToAnnotationId}
|
|
585
|
-
showLineNumbers={showLineNumbers}
|
|
586
|
-
annotators={ANNOTATORS}
|
|
587
|
-
/>
|
|
451
|
+
annotations={groups}
|
|
452
|
+
generatingReferenceId={generationProgress?.referenceId ?? null}
|
|
453
|
+
showLineNumbers={showLineNumbers}
|
|
454
|
+
hoveredAnnotationId={hoveredAnnotationId}
|
|
455
|
+
/>
|
|
588
456
|
)}
|
|
589
457
|
</ErrorBoundary>
|
|
590
458
|
</div>
|
|
@@ -596,9 +464,7 @@ export function ResourceViewerPage({
|
|
|
596
464
|
<ToolbarPanels
|
|
597
465
|
activePanel={activePanel}
|
|
598
466
|
theme={theme}
|
|
599
|
-
onThemeChange={onThemeChange}
|
|
600
467
|
showLineNumbers={showLineNumbers}
|
|
601
|
-
onLineNumbersToggle={onLineNumbersToggle}
|
|
602
468
|
width={
|
|
603
469
|
activePanel === 'jsonld' ? 'w-[600px]' :
|
|
604
470
|
activePanel === 'annotations' ? 'w-[400px]' :
|
|
@@ -619,26 +485,20 @@ export function ResourceViewerPage({
|
|
|
619
485
|
<UnifiedAnnotationsPanel
|
|
620
486
|
annotations={annotations}
|
|
621
487
|
annotators={ANNOTATORS}
|
|
622
|
-
onCreateAnnotation={handleCreateAnnotation}
|
|
623
|
-
detectionContext={detectionContext}
|
|
624
|
-
focusedAnnotationId={focusedAnnotationId}
|
|
625
|
-
hoveredAnnotationId={hoveredAnnotationId}
|
|
626
|
-
onAnnotationClick={handleAnnotationClick}
|
|
627
|
-
onAnnotationHover={handleAnnotationHover}
|
|
628
488
|
annotateMode={annotateMode}
|
|
629
489
|
detectingMotivation={detectingMotivation}
|
|
630
|
-
detectionProgress={
|
|
490
|
+
detectionProgress={detectionProgress}
|
|
631
491
|
pendingAnnotation={pendingAnnotation}
|
|
632
492
|
allEntityTypes={allEntityTypes}
|
|
633
|
-
onGenerateDocument={handleGenerateDocument}
|
|
634
|
-
onCreateDocument={handleCreateDocument}
|
|
635
493
|
generatingReferenceId={generationProgress?.referenceId ?? null}
|
|
636
|
-
onSearchDocuments={handleSearchDocuments}
|
|
637
|
-
onCancelDetection={handleCancelDetection}
|
|
638
|
-
{...(primaryMediaType ? { mediaType: primaryMediaType } : {})}
|
|
639
494
|
referencedBy={referencedBy}
|
|
640
495
|
referencedByLoading={referencedByLoading}
|
|
641
496
|
resourceId={rUri.split('/').pop() || ''}
|
|
497
|
+
scrollToAnnotationId={scrollToAnnotationId}
|
|
498
|
+
hoveredAnnotationId={hoveredAnnotationId}
|
|
499
|
+
onScrollCompleted={onScrollCompleted}
|
|
500
|
+
initialTab={panelInitialTab?.tab as any}
|
|
501
|
+
initialTabGeneration={panelInitialTab?.generation}
|
|
642
502
|
Link={Link}
|
|
643
503
|
routes={routes}
|
|
644
504
|
/>
|
|
@@ -664,9 +524,6 @@ export function ResourceViewerPage({
|
|
|
664
524
|
primaryMediaType={primaryMediaType}
|
|
665
525
|
primaryByteSize={primaryByteSize}
|
|
666
526
|
isArchived={resource.archived ?? false}
|
|
667
|
-
onClone={onClone}
|
|
668
|
-
onArchive={onArchive}
|
|
669
|
-
onUnarchive={onUnarchive}
|
|
670
527
|
/>
|
|
671
528
|
)}
|
|
672
529
|
|
|
@@ -689,7 +546,6 @@ export function ResourceViewerPage({
|
|
|
689
546
|
context="document"
|
|
690
547
|
activePanel={activePanel}
|
|
691
548
|
isArchived={resource.archived ?? false}
|
|
692
|
-
onPanelToggle={onPanelToggle}
|
|
693
549
|
/>
|
|
694
550
|
</div>
|
|
695
551
|
</div>
|
|
@@ -697,10 +553,7 @@ export function ResourceViewerPage({
|
|
|
697
553
|
{/* Search Resources Modal */}
|
|
698
554
|
<SearchResourcesModal
|
|
699
555
|
isOpen={searchModalOpen}
|
|
700
|
-
onClose={
|
|
701
|
-
setSearchModalOpen(false);
|
|
702
|
-
setPendingReferenceId(null);
|
|
703
|
-
}}
|
|
556
|
+
onClose={onCloseSearchModal}
|
|
704
557
|
onSelect={async (documentId: string) => {
|
|
705
558
|
if (pendingReferenceId) {
|
|
706
559
|
try {
|
|
@@ -712,7 +565,8 @@ export function ResourceViewerPage({
|
|
|
712
565
|
const resourceIdSegment = rUri.split('/').pop() || '';
|
|
713
566
|
const nestedUri = `${window.location.origin}/resources/${resourceIdSegment}/annotations/${annotationIdShort}`;
|
|
714
567
|
|
|
715
|
-
|
|
568
|
+
eventBus.emit('annotation:update-body', {
|
|
569
|
+
annotationUri: resourceAnnotationUri(nestedUri),
|
|
716
570
|
resourceId: resourceIdSegment,
|
|
717
571
|
operations: [{
|
|
718
572
|
op: 'add',
|
|
@@ -724,28 +578,23 @@ export function ResourceViewerPage({
|
|
|
724
578
|
}],
|
|
725
579
|
});
|
|
726
580
|
showSuccess('Reference linked successfully');
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
setPendingReferenceId(null);
|
|
581
|
+
// Cache invalidation now handled by annotation:updated event
|
|
582
|
+
onCloseSearchModal();
|
|
730
583
|
} catch (error) {
|
|
731
584
|
console.error('Failed to link reference:', error);
|
|
732
585
|
showError('Failed to link reference');
|
|
733
586
|
}
|
|
734
587
|
}
|
|
735
588
|
}}
|
|
736
|
-
searchTerm={searchTerm}
|
|
737
589
|
/>
|
|
738
590
|
|
|
739
591
|
{/* Generation Config Modal */}
|
|
740
592
|
<GenerationConfigModal
|
|
741
593
|
isOpen={generationModalOpen}
|
|
742
|
-
onClose={
|
|
743
|
-
setGenerationModalOpen(false);
|
|
744
|
-
setGenerationReferenceId(null);
|
|
745
|
-
}}
|
|
594
|
+
onClose={onCloseGenerationModal}
|
|
746
595
|
onGenerate={(options: GenerationOptions) => {
|
|
747
596
|
if (generationReferenceId) {
|
|
748
|
-
|
|
597
|
+
onGenerateDocument(generationReferenceId, options);
|
|
749
598
|
}
|
|
750
599
|
}}
|
|
751
600
|
referenceId={generationReferenceId || ''}
|