@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EventBusContext-CJjL_cCf.d.mts +462 -0
- package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
- package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
- package/dist/{ar-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-JZIO2A3B.mjs → chunk-QB52Q7EQ.mjs} +206 -146
- package/dist/chunk-QB52Q7EQ.mjs.map +1 -0
- package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -4
- package/dist/id-7U7GGVWY.mjs.map +1 -0
- package/dist/index.css +123 -85
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +715 -574
- package/dist/index.mjs +3898 -3575
- package/dist/index.mjs.map +1 -1
- package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -4
- package/dist/sv-7ZK5EQEB.mjs.map +1 -0
- package/dist/test-utils.d.mts +18 -8
- package/dist/test-utils.mjs +36 -14
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +3 -1
- package/src/components/CodeMirrorRenderer.tsx +66 -93
- package/src/components/DetectionProgressWidget.tsx +16 -5
- package/src/components/ResizeHandle.tsx +10 -4
- package/src/components/SessionExpiryBanner.tsx +2 -3
- package/src/components/SessionTimer.tsx +3 -3
- package/src/components/Toolbar.tsx +18 -9
- package/src/components/__tests__/SessionTimer.test.tsx +33 -33
- package/src/components/annotation/AnnotateToolbar.tsx +17 -15
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
- package/src/components/annotation/annotation-entries.css +10 -0
- package/src/components/annotation-popups/JsonLdView.tsx +8 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
- package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
- package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
- package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
- package/src/components/modals/ResourceSearchModal.tsx +2 -2
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
- package/src/components/navigation/NavigationTabs.css +36 -24
- package/src/components/navigation/ObservableLink.tsx +91 -0
- package/src/components/navigation/SimpleNavigation.tsx +20 -16
- package/src/components/navigation/SortableResourceTab.tsx +11 -5
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
- package/src/components/resource/AnnotateView.tsx +64 -134
- package/src/components/resource/BrowseView.tsx +86 -166
- package/src/components/resource/HistoryEvent.tsx +13 -7
- package/src/components/resource/ResourceViewer.tsx +122 -264
- package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +121 -28
- package/src/components/resource/panels/DetectSection.css +36 -1
- package/src/components/resource/panels/DetectSection.tsx +49 -15
- package/src/components/resource/panels/HighlightEntry.tsx +25 -33
- package/src/components/resource/panels/HighlightPanel.tsx +100 -25
- package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
- package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +118 -30
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
- package/src/components/settings/SettingsPanel.tsx +15 -12
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
- package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
- package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
- package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
- package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +503 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +139 -93
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +341 -524
- package/translations/ar.json +6 -3
- package/translations/bn.json +6 -3
- package/translations/cs.json +6 -3
- package/translations/da.json +6 -3
- package/translations/de.json +6 -3
- package/translations/el.json +6 -3
- package/translations/en.json +6 -3
- package/translations/es.json +6 -3
- package/translations/fa.json +6 -3
- package/translations/fi.json +6 -3
- package/translations/fr.json +6 -3
- package/translations/he.json +6 -3
- package/translations/hi.json +6 -3
- package/translations/id.json +6 -3
- package/translations/it.json +6 -3
- package/translations/ja.json +6 -3
- package/translations/ko.json +6 -3
- package/translations/ms.json +6 -3
- package/translations/nl.json +6 -3
- package/translations/no.json +6 -3
- package/translations/pl.json +6 -3
- package/translations/pt.json +6 -3
- package/translations/ro.json +6 -3
- package/translations/sv.json +6 -3
- package/translations/th.json +6 -3
- package/translations/tr.json +6 -3
- package/translations/uk.json +6 -3
- package/translations/vi.json +6 -3
- package/translations/zh.json +6 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-EMHEHPCJ.mjs.map +0 -1
- package/dist/bn-OVCI4F6X.mjs.map +0 -1
- package/dist/chunk-JZIO2A3B.mjs.map +0 -1
- package/dist/chunk-LIHZTECW.mjs.map +0 -1
- package/dist/cs-FAN66Q2F.mjs.map +0 -1
- package/dist/da-YBBIHI2O.mjs.map +0 -1
- package/dist/de-MAYU33LB.mjs.map +0 -1
- package/dist/el-MKGSWN4O.mjs.map +0 -1
- package/dist/es-52LHUWJD.mjs.map +0 -1
- package/dist/fa-FJICRANB.mjs.map +0 -1
- package/dist/fi-O455XFCR.mjs.map +0 -1
- package/dist/fr-TXIXHOOE.mjs.map +0 -1
- package/dist/he-JBSOX5IN.mjs.map +0 -1
- package/dist/hi-KGHI3XVT.mjs.map +0 -1
- package/dist/id-5OCPPZLO.mjs.map +0 -1
- package/dist/it-PNBBZSM2.mjs.map +0 -1
- package/dist/ja-LDD7R3TJ.mjs.map +0 -1
- package/dist/ko-F47ZDEY3.mjs.map +0 -1
- package/dist/ms-Z7LMXJWL.mjs.map +0 -1
- package/dist/nl-6SJFBPJ3.mjs.map +0 -1
- package/dist/no-YXPBPSGF.mjs.map +0 -1
- package/dist/pl-P4AZ2QME.mjs.map +0 -1
- package/dist/pt-LHWUS6U6.mjs.map +0 -1
- package/dist/ro-EA5J2ZON.mjs.map +0 -1
- package/dist/sv-DATBS3UQ.mjs.map +0 -1
- package/dist/th-WTFJRWPT.mjs.map +0 -1
- package/dist/tr-IKO3RXOX.mjs.map +0 -1
- package/dist/uk-CF6CTTRK.mjs.map +0 -1
- package/dist/vi-AJLTXPZQ.mjs.map +0 -1
- package/dist/zh-U3ORHHYH.mjs.map +0 -1
- /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
4
|
import type { RouteBuilder } from '../../../contexts/RoutingContext';
|
|
5
5
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
6
6
|
import type { components } from '@semiont/api-client';
|
|
7
7
|
import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelector, getSvgSelector, getTargetSelector } from '@semiont/api-client';
|
|
8
8
|
import { getEntityTypes } from '@semiont/ontology';
|
|
9
9
|
import { getResourceIcon } from '../../../lib/resource-utils';
|
|
10
|
+
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
11
|
+
import { useObservableExternalNavigation } from '../../../hooks/useObservableNavigation';
|
|
10
12
|
|
|
11
13
|
type Annotation = components['schemas']['Annotation'];
|
|
12
14
|
|
|
@@ -19,68 +21,27 @@ interface EnrichedAnnotation extends Annotation {
|
|
|
19
21
|
interface ReferenceEntryProps {
|
|
20
22
|
reference: Annotation;
|
|
21
23
|
isFocused: boolean;
|
|
22
|
-
|
|
24
|
+
isHovered?: boolean;
|
|
23
25
|
routes: RouteBuilder;
|
|
24
|
-
onReferenceRef: (referenceId: string, el: HTMLElement | null) => void;
|
|
25
|
-
onReferenceHover?: (referenceId: string | null) => void;
|
|
26
|
-
onGenerateDocument?: (referenceId: string, options: { title: string; prompt?: string }) => void;
|
|
27
|
-
onCreateDocument?: (annotationUri: string, title: string, entityTypes: string[]) => void;
|
|
28
|
-
onSearchDocuments?: (referenceId: string, searchTerm: string) => void;
|
|
29
|
-
onUpdateReference?: (referenceId: string, updates: Partial<Annotation>) => void;
|
|
30
26
|
annotateMode?: boolean;
|
|
31
27
|
isGenerating?: boolean;
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
isGenerating = false,
|
|
47
|
-
}: ReferenceEntryProps) {
|
|
30
|
+
export const ReferenceEntry = forwardRef<HTMLDivElement, ReferenceEntryProps>(
|
|
31
|
+
function ReferenceEntry(
|
|
32
|
+
{
|
|
33
|
+
reference,
|
|
34
|
+
isFocused,
|
|
35
|
+
isHovered = false,
|
|
36
|
+
routes,
|
|
37
|
+
annotateMode = true,
|
|
38
|
+
isGenerating = false,
|
|
39
|
+
},
|
|
40
|
+
ref
|
|
41
|
+
) {
|
|
48
42
|
const t = useTranslations('ReferencesPanel');
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
// Register ref with parent
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
onReferenceRef(reference.id, referenceRef.current);
|
|
54
|
-
return () => {
|
|
55
|
-
onReferenceRef(reference.id, null);
|
|
56
|
-
};
|
|
57
|
-
}, [reference.id, onReferenceRef]);
|
|
58
|
-
|
|
59
|
-
// Scroll to reference when focused - use container.scrollTo to avoid scrolling ancestors
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (isFocused && referenceRef.current) {
|
|
62
|
-
const element = referenceRef.current;
|
|
63
|
-
const container = element.closest('.semiont-toolbar-panels__content') as HTMLElement;
|
|
64
|
-
|
|
65
|
-
if (container) {
|
|
66
|
-
const elementRect = element.getBoundingClientRect();
|
|
67
|
-
const containerRect = container.getBoundingClientRect();
|
|
68
|
-
|
|
69
|
-
const isVisible =
|
|
70
|
-
elementRect.top >= containerRect.top &&
|
|
71
|
-
elementRect.bottom <= containerRect.bottom;
|
|
72
|
-
|
|
73
|
-
if (!isVisible) {
|
|
74
|
-
const elementTop = element.offsetTop;
|
|
75
|
-
const containerHeight = container.clientHeight;
|
|
76
|
-
const elementHeight = element.offsetHeight;
|
|
77
|
-
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
78
|
-
|
|
79
|
-
container.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}, [isFocused]);
|
|
43
|
+
const eventBus = useEventBus();
|
|
44
|
+
const navigate = useObservableExternalNavigation();
|
|
84
45
|
|
|
85
46
|
const selectedText = getAnnotationExactText(reference) || '';
|
|
86
47
|
const isResolved = isBodyResolved(reference.body);
|
|
@@ -103,44 +64,69 @@ export function ReferenceEntry({
|
|
|
103
64
|
if (resolvedResourceUri) {
|
|
104
65
|
const resourceId = resolvedResourceUri.split('/resources/')[1];
|
|
105
66
|
if (resourceId) {
|
|
106
|
-
|
|
67
|
+
// Use observable navigation - emits 'navigation:external-navigate' event
|
|
68
|
+
navigate(routes.resourceDetail(resourceId), { resourceId });
|
|
107
69
|
}
|
|
108
70
|
}
|
|
109
71
|
};
|
|
110
72
|
|
|
111
73
|
const handleComposeDocument = () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
74
|
+
eventBus.emit('reference:create-manual', {
|
|
75
|
+
annotationUri: reference.id,
|
|
76
|
+
title: selectedText,
|
|
77
|
+
entityTypes,
|
|
78
|
+
});
|
|
115
79
|
};
|
|
116
80
|
|
|
117
81
|
const handleUnlink = () => {
|
|
118
|
-
|
|
119
|
-
|
|
82
|
+
// Unlinking removes all body items from the reference annotation
|
|
83
|
+
const sourceUri = typeof reference.target === 'object' && 'source' in reference.target
|
|
84
|
+
? reference.target.source
|
|
85
|
+
: '';
|
|
86
|
+
if (sourceUri) {
|
|
87
|
+
eventBus.emit('annotation:update-body', {
|
|
88
|
+
annotationUri: reference.id,
|
|
89
|
+
resourceId: sourceUri.split('/resources/')[1] || '',
|
|
90
|
+
operations: [{ op: 'remove' }], // Remove all body items
|
|
91
|
+
});
|
|
120
92
|
}
|
|
121
93
|
};
|
|
122
94
|
|
|
123
95
|
const handleGenerate = () => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
96
|
+
const resourceUri = typeof reference.target === 'object' && 'source' in reference.target
|
|
97
|
+
? reference.target.source
|
|
98
|
+
: '';
|
|
99
|
+
|
|
100
|
+
// Emit request to open generation modal
|
|
101
|
+
eventBus.emit('generation:modal-open', {
|
|
102
|
+
annotationUri: reference.id,
|
|
103
|
+
resourceUri,
|
|
104
|
+
defaultTitle: selectedText,
|
|
105
|
+
});
|
|
127
106
|
};
|
|
128
107
|
|
|
129
108
|
const handleSearch = () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
109
|
+
eventBus.emit('reference:link', {
|
|
110
|
+
annotationUri: reference.id,
|
|
111
|
+
searchTerm: selectedText,
|
|
112
|
+
});
|
|
133
113
|
};
|
|
134
114
|
|
|
135
115
|
return (
|
|
136
116
|
<div
|
|
137
|
-
ref={
|
|
138
|
-
className=
|
|
117
|
+
ref={ref}
|
|
118
|
+
className={`semiont-annotation-entry${isHovered ? ' semiont-annotation-pulse' : ''}`}
|
|
139
119
|
data-type="reference"
|
|
140
120
|
data-focused={isFocused ? 'true' : 'false'}
|
|
141
|
-
onClick={
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
onClick={() => {
|
|
122
|
+
eventBus.emit('annotation:click', { annotationId: reference.id, motivation: reference.motivation });
|
|
123
|
+
}}
|
|
124
|
+
onMouseEnter={() => {
|
|
125
|
+
eventBus.emit('annotation:hover', { annotationId: reference.id });
|
|
126
|
+
}}
|
|
127
|
+
onMouseLeave={() => {
|
|
128
|
+
eventBus.emit('annotation:hover', { annotationId: null });
|
|
129
|
+
}}
|
|
144
130
|
>
|
|
145
131
|
{/* Status indicator and text quote */}
|
|
146
132
|
<div className="semiont-annotation-entry__header">
|
|
@@ -235,4 +221,4 @@ export function ReferenceEntry({
|
|
|
235
221
|
</div>
|
|
236
222
|
</div>
|
|
237
223
|
);
|
|
238
|
-
}
|
|
224
|
+
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import {
|
|
5
|
+
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
6
|
+
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
6
7
|
import type { RouteBuilder, LinkComponentProps } from '../../../contexts/RoutingContext';
|
|
7
8
|
import { DetectionProgressWidget } from '../../DetectionProgressWidget';
|
|
8
9
|
import { ReferenceEntry } from './ReferenceEntry';
|
|
9
10
|
import type { components, paths, Selector } from '@semiont/api-client';
|
|
10
|
-
import {
|
|
11
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/api-client';
|
|
11
12
|
import { PanelHeader } from './PanelHeader';
|
|
12
13
|
import './ReferencesPanel.css';
|
|
13
14
|
|
|
@@ -47,12 +48,6 @@ interface DetectionLog {
|
|
|
47
48
|
interface Props {
|
|
48
49
|
// Generic panel props
|
|
49
50
|
annotations?: Annotation[];
|
|
50
|
-
onAnnotationClick?: (annotation: Annotation) => void;
|
|
51
|
-
focusedAnnotationId?: string | null;
|
|
52
|
-
hoveredAnnotationId?: string | null;
|
|
53
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
54
|
-
onDetect?: (selectedTypes: string[], includeDescriptiveReferences?: boolean) => void;
|
|
55
|
-
onCreate: (entityType?: string) => void;
|
|
56
51
|
isDetecting: boolean;
|
|
57
52
|
detectionProgress: any; // TODO: type this properly
|
|
58
53
|
annotateMode?: boolean;
|
|
@@ -61,46 +56,48 @@ interface Props {
|
|
|
61
56
|
|
|
62
57
|
// Reference-specific props
|
|
63
58
|
allEntityTypes: string[];
|
|
64
|
-
onCancelDetection: () => void;
|
|
65
|
-
onSearchDocuments?: (referenceId: string, searchTerm: string) => void;
|
|
66
|
-
onGenerateDocument?: (referenceId: string, options: { title: string; prompt?: string }) => void;
|
|
67
|
-
onCreateDocument?: (annotationUri: string, title: string, entityTypes: string[]) => void;
|
|
68
59
|
generatingReferenceId?: string | null;
|
|
69
60
|
referencedBy?: ReferencedBy[];
|
|
70
61
|
referencedByLoading?: boolean;
|
|
71
62
|
pendingAnnotation: PendingAnnotation | null;
|
|
63
|
+
scrollToAnnotationId?: string | null;
|
|
64
|
+
onScrollCompleted?: () => void;
|
|
65
|
+
hoveredAnnotationId?: string | null;
|
|
72
66
|
}
|
|
73
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Panel for managing reference annotations with entity type detection
|
|
70
|
+
*
|
|
71
|
+
* @emits detection:start - Start reference detection. Payload: { motivation: 'linking', options: { entityTypes: string[], includeDescriptiveReferences: boolean } }
|
|
72
|
+
* @emits annotation:create - Create new reference annotation. Payload: { motivation: 'linking', selector: Selector | Selector[], body: Body[] }
|
|
73
|
+
* @emits annotation:cancel-pending - Cancel pending reference annotation. Payload: undefined
|
|
74
|
+
* @subscribes annotation:click - Annotation clicked. Payload: { annotationId: string }
|
|
75
|
+
*/
|
|
74
76
|
export function ReferencesPanel({
|
|
75
77
|
annotations = [],
|
|
76
|
-
onAnnotationClick,
|
|
77
|
-
focusedAnnotationId,
|
|
78
|
-
hoveredAnnotationId,
|
|
79
|
-
onAnnotationHover,
|
|
80
|
-
onDetect,
|
|
81
|
-
onCreate,
|
|
82
78
|
isDetecting,
|
|
83
79
|
detectionProgress,
|
|
84
80
|
annotateMode = true,
|
|
85
81
|
Link,
|
|
86
82
|
routes,
|
|
87
83
|
allEntityTypes,
|
|
88
|
-
onCancelDetection,
|
|
89
|
-
onSearchDocuments,
|
|
90
|
-
onGenerateDocument,
|
|
91
|
-
onCreateDocument,
|
|
92
84
|
generatingReferenceId,
|
|
93
85
|
referencedBy = [],
|
|
94
86
|
referencedByLoading = false,
|
|
95
87
|
pendingAnnotation,
|
|
88
|
+
scrollToAnnotationId,
|
|
89
|
+
onScrollCompleted,
|
|
90
|
+
hoveredAnnotationId,
|
|
96
91
|
}: Props) {
|
|
97
92
|
const t = useTranslations('DetectPanel');
|
|
98
93
|
const tRef = useTranslations('ReferencesPanel');
|
|
99
|
-
const eventBus =
|
|
94
|
+
const eventBus = useEventBus();
|
|
100
95
|
const [selectedEntityTypes, setSelectedEntityTypes] = useState<string[]>([]);
|
|
101
96
|
const [lastDetectionLog, setLastDetectionLog] = useState<DetectionLog[] | null>(null);
|
|
102
97
|
const [pendingEntityTypes, setPendingEntityTypes] = useState<string[]>([]);
|
|
103
98
|
const [includeDescriptiveReferences, setIncludeDescriptiveReferences] = useState(false);
|
|
99
|
+
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
100
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
104
101
|
|
|
105
102
|
// Collapsible detection section state - load from localStorage, default expanded
|
|
106
103
|
const [isDetectExpanded, setIsDetectExpanded] = useState(() => {
|
|
@@ -115,14 +112,108 @@ export function ReferencesPanel({
|
|
|
115
112
|
localStorage.setItem('detect-section-expanded-reference', String(isDetectExpanded));
|
|
116
113
|
}, [isDetectExpanded]);
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
// Direct ref management - replace useAnnotationPanel hook
|
|
116
|
+
const entryRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
|
117
|
+
|
|
118
|
+
// Sort annotations by their position in the resource
|
|
119
|
+
const sortedAnnotations = useMemo(() => {
|
|
120
|
+
return [...annotations].sort((a, b) => {
|
|
121
|
+
const aSelector = getTextPositionSelector(getTargetSelector(a.target));
|
|
122
|
+
const bSelector = getTextPositionSelector(getTargetSelector(b.target));
|
|
123
|
+
if (!aSelector || !bSelector) return 0;
|
|
124
|
+
return aSelector.start - bSelector.start;
|
|
125
|
+
});
|
|
126
|
+
}, [annotations]);
|
|
127
|
+
|
|
128
|
+
// Ref callback for entry components
|
|
129
|
+
const setEntryRef = useCallback((id: string, element: HTMLDivElement | null) => {
|
|
130
|
+
if (element) {
|
|
131
|
+
entryRefs.current.set(id, element);
|
|
132
|
+
} else {
|
|
133
|
+
entryRefs.current.delete(id);
|
|
134
|
+
}
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
// Handle scrollToAnnotationId (click scroll)
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!scrollToAnnotationId) return;
|
|
140
|
+
|
|
141
|
+
const element = entryRefs.current.get(scrollToAnnotationId);
|
|
142
|
+
|
|
143
|
+
if (element && containerRef.current) {
|
|
144
|
+
// Calculate scroll position to center element in container
|
|
145
|
+
const elementTop = element.offsetTop;
|
|
146
|
+
const containerHeight = containerRef.current.clientHeight;
|
|
147
|
+
const elementHeight = element.offsetHeight;
|
|
148
|
+
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
149
|
+
|
|
150
|
+
// Scroll to center
|
|
151
|
+
containerRef.current.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
152
|
+
|
|
153
|
+
// Add pulse effect
|
|
154
|
+
element.classList.remove('semiont-annotation-pulse');
|
|
155
|
+
void element.offsetWidth; // Force reflow
|
|
156
|
+
element.classList.add('semiont-annotation-pulse');
|
|
157
|
+
|
|
158
|
+
// Notify completion
|
|
159
|
+
if (onScrollCompleted) {
|
|
160
|
+
onScrollCompleted();
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
console.warn('[ReferencesPanel] Element not found for scrollToAnnotationId:', scrollToAnnotationId);
|
|
164
|
+
}
|
|
165
|
+
}, [scrollToAnnotationId]);
|
|
166
|
+
|
|
167
|
+
// Handle hoveredAnnotationId (hover scroll only - pulse is handled by isHovered prop)
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (!hoveredAnnotationId) return;
|
|
170
|
+
|
|
171
|
+
const element = entryRefs.current.get(hoveredAnnotationId);
|
|
172
|
+
|
|
173
|
+
if (!element || !containerRef.current) return;
|
|
174
|
+
|
|
175
|
+
const container = containerRef.current;
|
|
176
|
+
const elementRect = element.getBoundingClientRect();
|
|
177
|
+
const containerRect = container.getBoundingClientRect();
|
|
178
|
+
|
|
179
|
+
// Only scroll if element is not fully visible
|
|
180
|
+
const isVisible =
|
|
181
|
+
elementRect.top >= containerRect.top &&
|
|
182
|
+
elementRect.bottom <= containerRect.bottom;
|
|
183
|
+
|
|
184
|
+
if (!isVisible) {
|
|
185
|
+
const elementTop = element.offsetTop;
|
|
186
|
+
const containerHeight = container.clientHeight;
|
|
187
|
+
const elementHeight = element.offsetHeight;
|
|
188
|
+
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
189
|
+
|
|
190
|
+
container.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Pulse effect is handled by isHovered prop on ReferenceEntry
|
|
194
|
+
}, [hoveredAnnotationId]);
|
|
195
|
+
|
|
196
|
+
// Subscribe to click events - update focused state
|
|
197
|
+
// Event handler for annotation clicks (extracted to avoid inline arrow function)
|
|
198
|
+
const handleAnnotationClick = useCallback(({ annotationId }: { annotationId: string }) => {
|
|
199
|
+
setFocusedAnnotationId(annotationId);
|
|
200
|
+
setTimeout(() => setFocusedAnnotationId(null), 3000);
|
|
201
|
+
}, []);
|
|
202
|
+
|
|
203
|
+
useEventSubscriptions({
|
|
204
|
+
'annotation:click': handleAnnotationClick,
|
|
205
|
+
});
|
|
120
206
|
|
|
121
207
|
// Clear log when starting new detection
|
|
122
208
|
const handleDetect = () => {
|
|
123
|
-
if (!onDetect) return;
|
|
124
209
|
setLastDetectionLog(null);
|
|
125
|
-
|
|
210
|
+
eventBus.emit('detection:start', {
|
|
211
|
+
motivation: 'linking',
|
|
212
|
+
options: {
|
|
213
|
+
entityTypes: selectedEntityTypes,
|
|
214
|
+
includeDescriptiveReferences,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
126
217
|
};
|
|
127
218
|
|
|
128
219
|
// Track whether we've already saved the log for the current detection run
|
|
@@ -157,9 +248,15 @@ export function ReferencesPanel({
|
|
|
157
248
|
};
|
|
158
249
|
|
|
159
250
|
const handleCreateReference = () => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
251
|
+
if (pendingAnnotation) {
|
|
252
|
+
const entityType = pendingEntityTypes.join(',') || undefined;
|
|
253
|
+
eventBus.emit('annotation:create', {
|
|
254
|
+
motivation: 'linking',
|
|
255
|
+
selector: pendingAnnotation.selector,
|
|
256
|
+
body: entityType ? [{ type: 'TextualBody', value: entityType, purpose: 'tagging' }] : [],
|
|
257
|
+
});
|
|
258
|
+
setPendingEntityTypes([]);
|
|
259
|
+
}
|
|
163
260
|
};
|
|
164
261
|
|
|
165
262
|
// Escape key handler for cancelling pending annotation
|
|
@@ -168,14 +265,14 @@ export function ReferencesPanel({
|
|
|
168
265
|
|
|
169
266
|
const handleEscape = (e: KeyboardEvent) => {
|
|
170
267
|
if (e.key === 'Escape') {
|
|
171
|
-
eventBus.emit('
|
|
268
|
+
eventBus.emit('annotation:cancel-pending', undefined);
|
|
172
269
|
setPendingEntityTypes([]);
|
|
173
270
|
}
|
|
174
271
|
};
|
|
175
272
|
|
|
176
273
|
document.addEventListener('keydown', handleEscape);
|
|
177
274
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
178
|
-
}, [pendingAnnotation
|
|
275
|
+
}, [pendingAnnotation]);
|
|
179
276
|
|
|
180
277
|
return (
|
|
181
278
|
<div className="semiont-panel">
|
|
@@ -220,7 +317,7 @@ export function ReferencesPanel({
|
|
|
220
317
|
<div className="semiont-annotation-prompt__actions">
|
|
221
318
|
<button
|
|
222
319
|
onClick={() => {
|
|
223
|
-
eventBus.emit('
|
|
320
|
+
eventBus.emit('annotation:cancel-pending', undefined);
|
|
224
321
|
setPendingEntityTypes([]);
|
|
225
322
|
}}
|
|
226
323
|
className="semiont-button semiont-button--secondary"
|
|
@@ -243,7 +340,7 @@ export function ReferencesPanel({
|
|
|
243
340
|
{/* Scrollable content area */}
|
|
244
341
|
<div ref={containerRef} className="semiont-panel__content">
|
|
245
342
|
{/* Detection Section - only in Annotate mode and for text resources */}
|
|
246
|
-
{annotateMode &&
|
|
343
|
+
{annotateMode && (
|
|
247
344
|
<div className="semiont-panel__section">
|
|
248
345
|
<button
|
|
249
346
|
onClick={() => setIsDetectExpanded(!isDetectExpanded)}
|
|
@@ -338,7 +435,6 @@ export function ReferencesPanel({
|
|
|
338
435
|
{detectionProgress && (
|
|
339
436
|
<DetectionProgressWidget
|
|
340
437
|
progress={detectionProgress}
|
|
341
|
-
onCancel={onCancelDetection}
|
|
342
438
|
annotationType="reference"
|
|
343
439
|
/>
|
|
344
440
|
)}
|
|
@@ -389,15 +485,11 @@ export function ReferencesPanel({
|
|
|
389
485
|
key={reference.id}
|
|
390
486
|
reference={reference}
|
|
391
487
|
isFocused={reference.id === focusedAnnotationId}
|
|
392
|
-
|
|
488
|
+
isHovered={reference.id === hoveredAnnotationId}
|
|
393
489
|
routes={routes}
|
|
394
|
-
onReferenceRef={handleAnnotationRef}
|
|
395
490
|
annotateMode={annotateMode}
|
|
396
491
|
isGenerating={reference.id === generatingReferenceId}
|
|
397
|
-
{
|
|
398
|
-
{...(onGenerateDocument && { onGenerateDocument })}
|
|
399
|
-
{...(onCreateDocument && { onCreateDocument })}
|
|
400
|
-
{...(onSearchDocuments && { onSearchDocuments })}
|
|
492
|
+
ref={(el) => setEntryRef(reference.id, el)}
|
|
401
493
|
/>
|
|
402
494
|
))
|
|
403
495
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
4
|
+
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
4
5
|
import { formatLocaleDisplay } from '@semiont/api-client';
|
|
5
6
|
import './ResourceInfoPanel.css';
|
|
6
7
|
|
|
@@ -10,22 +11,24 @@ interface Props {
|
|
|
10
11
|
primaryMediaType?: string | undefined;
|
|
11
12
|
primaryByteSize?: number | undefined;
|
|
12
13
|
isArchived?: boolean;
|
|
13
|
-
onArchive?: () => void;
|
|
14
|
-
onUnarchive?: () => void;
|
|
15
|
-
onClone?: () => void;
|
|
16
14
|
}
|
|
17
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Panel for displaying resource metadata and management actions
|
|
18
|
+
*
|
|
19
|
+
* @emits resource:clone - Clone this resource. Payload: undefined
|
|
20
|
+
* @emits resource:unarchive - Unarchive this resource. Payload: undefined
|
|
21
|
+
* @emits resource:archive - Archive this resource. Payload: undefined
|
|
22
|
+
*/
|
|
18
23
|
export function ResourceInfoPanel({
|
|
19
24
|
documentEntityTypes,
|
|
20
25
|
documentLocale,
|
|
21
26
|
primaryMediaType,
|
|
22
27
|
primaryByteSize,
|
|
23
28
|
isArchived = false,
|
|
24
|
-
onArchive,
|
|
25
|
-
onUnarchive,
|
|
26
|
-
onClone
|
|
27
29
|
}: Props) {
|
|
28
30
|
const t = useTranslations('ResourceInfoPanel');
|
|
31
|
+
const eventBus = useEventBus();
|
|
29
32
|
|
|
30
33
|
return (
|
|
31
34
|
<div className="semiont-resource-info-panel">
|
|
@@ -92,50 +95,46 @@ export function ResourceInfoPanel({
|
|
|
92
95
|
)}
|
|
93
96
|
|
|
94
97
|
{/* Clone Action */}
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</div>
|
|
107
|
-
)}
|
|
98
|
+
<div className="semiont-resource-info-panel__action-section">
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => eventBus.emit('resource:clone', undefined)}
|
|
101
|
+
className="semiont-resource-button semiont-resource-button--secondary"
|
|
102
|
+
>
|
|
103
|
+
🔗 {t('clone')}
|
|
104
|
+
</button>
|
|
105
|
+
<p className="semiont-resource-info-panel__description">
|
|
106
|
+
{t('cloneDescription')}
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
108
109
|
|
|
109
110
|
{/* Archive/Unarchive Actions */}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
111
|
+
<div className="semiont-resource-info-panel__action-section">
|
|
112
|
+
{isArchived ? (
|
|
113
|
+
<>
|
|
114
|
+
<button
|
|
115
|
+
onClick={() => eventBus.emit('resource:unarchive', undefined)}
|
|
116
|
+
className="semiont-resource-button semiont-resource-button--secondary"
|
|
117
|
+
>
|
|
118
|
+
📤 {t('unarchive')}
|
|
119
|
+
</button>
|
|
120
|
+
<p className="semiont-resource-info-panel__description">
|
|
121
|
+
{t('unarchiveDescription')}
|
|
122
|
+
</p>
|
|
123
|
+
</>
|
|
124
|
+
) : (
|
|
125
|
+
<>
|
|
126
|
+
<button
|
|
127
|
+
onClick={() => eventBus.emit('resource:archive', undefined)}
|
|
128
|
+
className="semiont-resource-button semiont-resource-button--archive"
|
|
129
|
+
>
|
|
130
|
+
📦 {t('archive')}
|
|
131
|
+
</button>
|
|
132
|
+
<p className="semiont-resource-info-panel__description">
|
|
133
|
+
{t('archiveDescription')}
|
|
134
|
+
</p>
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
139
138
|
</div>
|
|
140
139
|
);
|
|
141
140
|
}
|