@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,44 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
4
|
import type { components } from '@semiont/api-client';
|
|
5
5
|
import { getAnnotationExactText } from '@semiont/api-client';
|
|
6
6
|
import { getTagCategory, getTagSchemaId } from '@semiont/ontology';
|
|
7
7
|
import { getTagSchema } from '../../../lib/tag-schemas';
|
|
8
|
+
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
8
9
|
|
|
9
10
|
type Annotation = components['schemas']['Annotation'];
|
|
10
11
|
|
|
11
12
|
interface TagEntryProps {
|
|
12
13
|
tag: Annotation;
|
|
13
14
|
isFocused: boolean;
|
|
14
|
-
|
|
15
|
-
onTagRef: (tagId: string, el: HTMLElement | null) => void;
|
|
16
|
-
onTagHover?: (tagId: string | null) => void;
|
|
15
|
+
isHovered?: boolean;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
onTagRef(tag.id, tagRef.current);
|
|
31
|
-
return () => {
|
|
32
|
-
onTagRef(tag.id, null);
|
|
33
|
-
};
|
|
34
|
-
}, [tag.id, onTagRef]);
|
|
35
|
-
|
|
36
|
-
// Scroll to tag when focused
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (isFocused && tagRef.current) {
|
|
39
|
-
tagRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
40
|
-
}
|
|
41
|
-
}, [isFocused]);
|
|
18
|
+
export const TagEntry = forwardRef<HTMLDivElement, TagEntryProps>(
|
|
19
|
+
function TagEntry(
|
|
20
|
+
{
|
|
21
|
+
tag,
|
|
22
|
+
isFocused,
|
|
23
|
+
isHovered = false,
|
|
24
|
+
},
|
|
25
|
+
ref
|
|
26
|
+
) {
|
|
27
|
+
const eventBus = useEventBus();
|
|
42
28
|
|
|
43
29
|
const selectedText = getAnnotationExactText(tag);
|
|
44
30
|
const category = getTagCategory(tag);
|
|
@@ -47,11 +33,17 @@ export function TagEntry({
|
|
|
47
33
|
|
|
48
34
|
return (
|
|
49
35
|
<div
|
|
50
|
-
ref={
|
|
51
|
-
onClick={
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
ref={ref}
|
|
37
|
+
onClick={() => {
|
|
38
|
+
eventBus.emit('annotation:click', { annotationId: tag.id, motivation: tag.motivation });
|
|
39
|
+
}}
|
|
40
|
+
onMouseEnter={() => {
|
|
41
|
+
eventBus.emit('annotation:hover', { annotationId: tag.id });
|
|
42
|
+
}}
|
|
43
|
+
onMouseLeave={() => {
|
|
44
|
+
eventBus.emit('annotation:hover', { annotationId: null });
|
|
45
|
+
}}
|
|
46
|
+
className={`semiont-annotation-entry${isHovered ? ' semiont-annotation-pulse' : ''}`}
|
|
55
47
|
data-type="tag"
|
|
56
48
|
data-focused={isFocused ? 'true' : 'false'}
|
|
57
49
|
>
|
|
@@ -73,4 +65,4 @@ export function TagEntry({
|
|
|
73
65
|
</div>
|
|
74
66
|
</div>
|
|
75
67
|
);
|
|
76
|
-
}
|
|
68
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useState, useEffect, useRef, 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 { components, Selector } from '@semiont/api-client';
|
|
8
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/api-client';
|
|
7
9
|
import { TagEntry } from './TagEntry';
|
|
8
|
-
import { useAnnotationPanel } from '../../../hooks/useAnnotationPanel';
|
|
9
10
|
import { PanelHeader } from './PanelHeader';
|
|
10
11
|
import { getAllTagSchemas } from '../../../lib/tag-schemas';
|
|
11
12
|
import './TaggingPanel.css';
|
|
@@ -38,13 +39,7 @@ function getSelectorDisplayText(selector: Selector | Selector[]): string | null
|
|
|
38
39
|
|
|
39
40
|
interface TaggingPanelProps {
|
|
40
41
|
annotations: Annotation[];
|
|
41
|
-
onAnnotationClick: (annotation: Annotation) => void;
|
|
42
|
-
focusedAnnotationId: string | null;
|
|
43
|
-
hoveredAnnotationId?: string | null;
|
|
44
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
45
42
|
annotateMode?: boolean;
|
|
46
|
-
onDetect?: (schemaId: string, categories: string[]) => void | Promise<void>;
|
|
47
|
-
onCreate: (schemaId: string, category: string) => void | Promise<void>;
|
|
48
43
|
isDetecting?: boolean;
|
|
49
44
|
detectionProgress?: {
|
|
50
45
|
status: string;
|
|
@@ -56,25 +51,35 @@ interface TaggingPanelProps {
|
|
|
56
51
|
requestParams?: Array<{ label: string; value: string }>;
|
|
57
52
|
} | null;
|
|
58
53
|
pendingAnnotation: PendingAnnotation | null;
|
|
54
|
+
scrollToAnnotationId?: string | null;
|
|
55
|
+
onScrollCompleted?: () => void;
|
|
56
|
+
hoveredAnnotationId?: string | null;
|
|
59
57
|
}
|
|
60
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Panel for managing tag annotations with schema-based detection
|
|
61
|
+
*
|
|
62
|
+
* @emits detection:start - Start tag detection. Payload: { motivation: 'tagging', options: { schemaId: string, categories: string[] } }
|
|
63
|
+
* @emits annotation:cancel-pending - Cancel pending tag annotation. Payload: undefined
|
|
64
|
+
* @emits annotation:create - Create new tag annotation. Payload: { motivation: 'tagging', selector: Selector | Selector[], body: Body[] }
|
|
65
|
+
* @subscribes annotation:click - Annotation clicked. Payload: { annotationId: string }
|
|
66
|
+
*/
|
|
61
67
|
export function TaggingPanel({
|
|
62
68
|
annotations,
|
|
63
|
-
onAnnotationClick,
|
|
64
|
-
focusedAnnotationId,
|
|
65
|
-
hoveredAnnotationId,
|
|
66
|
-
onAnnotationHover,
|
|
67
69
|
annotateMode = true,
|
|
68
|
-
onDetect,
|
|
69
|
-
onCreate,
|
|
70
70
|
isDetecting = false,
|
|
71
71
|
detectionProgress,
|
|
72
|
-
pendingAnnotation
|
|
72
|
+
pendingAnnotation,
|
|
73
|
+
scrollToAnnotationId,
|
|
74
|
+
onScrollCompleted,
|
|
75
|
+
hoveredAnnotationId,
|
|
73
76
|
}: TaggingPanelProps) {
|
|
74
77
|
const t = useTranslations('TaggingPanel');
|
|
75
|
-
const eventBus =
|
|
78
|
+
const eventBus = useEventBus();
|
|
76
79
|
const [selectedSchemaId, setSelectedSchemaId] = useState<string>('legal-irac');
|
|
77
80
|
const [selectedCategories, setSelectedCategories] = useState<Set<string>>(new Set());
|
|
81
|
+
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
82
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
78
83
|
|
|
79
84
|
// Collapsible detection section state - load from localStorage, default expanded
|
|
80
85
|
const [isDetectExpanded, setIsDetectExpanded] = useState(() => {
|
|
@@ -89,8 +94,76 @@ export function TaggingPanel({
|
|
|
89
94
|
localStorage.setItem('detect-section-expanded-tag', String(isDetectExpanded));
|
|
90
95
|
}, [isDetectExpanded]);
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
// Subscribe to click events - update focused state
|
|
98
|
+
// Event handler for annotation clicks (extracted to avoid inline arrow function)
|
|
99
|
+
const handleAnnotationClick = useCallback(({ annotationId }: { annotationId: string }) => {
|
|
100
|
+
setFocusedAnnotationId(annotationId);
|
|
101
|
+
setTimeout(() => setFocusedAnnotationId(null), 3000);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
useEventSubscriptions({
|
|
105
|
+
'annotation:click': handleAnnotationClick,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Direct ref management
|
|
109
|
+
const entryRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
|
110
|
+
|
|
111
|
+
// Sort annotations by their position in the resource
|
|
112
|
+
const sortedAnnotations = useMemo(() => {
|
|
113
|
+
return [...annotations].sort((a, b) => {
|
|
114
|
+
const aSelector = getTextPositionSelector(getTargetSelector(a.target));
|
|
115
|
+
const bSelector = getTextPositionSelector(getTargetSelector(b.target));
|
|
116
|
+
if (!aSelector || !bSelector) return 0;
|
|
117
|
+
return aSelector.start - bSelector.start;
|
|
118
|
+
});
|
|
119
|
+
}, [annotations]);
|
|
120
|
+
|
|
121
|
+
// Ref callback for entry components
|
|
122
|
+
const setEntryRef = useCallback((id: string, element: HTMLDivElement | null) => {
|
|
123
|
+
if (element) {
|
|
124
|
+
entryRefs.current.set(id, element);
|
|
125
|
+
} else {
|
|
126
|
+
entryRefs.current.delete(id);
|
|
127
|
+
}
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
// Handle scrollToAnnotationId (click scroll)
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!scrollToAnnotationId) return;
|
|
133
|
+
const element = entryRefs.current.get(scrollToAnnotationId);
|
|
134
|
+
if (element && containerRef.current) {
|
|
135
|
+
const elementTop = element.offsetTop;
|
|
136
|
+
const containerHeight = containerRef.current.clientHeight;
|
|
137
|
+
const elementHeight = element.offsetHeight;
|
|
138
|
+
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
139
|
+
containerRef.current.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
140
|
+
element.classList.remove('semiont-annotation-pulse');
|
|
141
|
+
void element.offsetWidth;
|
|
142
|
+
element.classList.add('semiont-annotation-pulse');
|
|
143
|
+
if (onScrollCompleted) onScrollCompleted();
|
|
144
|
+
}
|
|
145
|
+
}, [scrollToAnnotationId]);
|
|
146
|
+
|
|
147
|
+
// Handle hoveredAnnotationId (hover scroll only - pulse is handled by isHovered prop)
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!hoveredAnnotationId) return;
|
|
150
|
+
const element = entryRefs.current.get(hoveredAnnotationId);
|
|
151
|
+
if (!element || !containerRef.current) return;
|
|
152
|
+
|
|
153
|
+
const container = containerRef.current;
|
|
154
|
+
const elementRect = element.getBoundingClientRect();
|
|
155
|
+
const containerRect = container.getBoundingClientRect();
|
|
156
|
+
const isVisible = elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
|
|
157
|
+
if (!isVisible) {
|
|
158
|
+
const elementTop = element.offsetTop;
|
|
159
|
+
const containerHeight = container.clientHeight;
|
|
160
|
+
const elementHeight = element.offsetHeight;
|
|
161
|
+
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
162
|
+
container.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Pulse effect is handled by isHovered prop on TagEntry
|
|
166
|
+
}, [hoveredAnnotationId]);
|
|
94
167
|
|
|
95
168
|
const schemas = getAllTagSchemas();
|
|
96
169
|
const selectedSchema = schemas.find(s => s.id === selectedSchemaId);
|
|
@@ -121,8 +194,14 @@ export function TaggingPanel({
|
|
|
121
194
|
};
|
|
122
195
|
|
|
123
196
|
const handleDetect = () => {
|
|
124
|
-
if (
|
|
125
|
-
|
|
197
|
+
if (selectedCategories.size > 0) {
|
|
198
|
+
eventBus.emit('detection:start', {
|
|
199
|
+
motivation: 'tagging',
|
|
200
|
+
options: {
|
|
201
|
+
schemaId: selectedSchemaId,
|
|
202
|
+
categories: Array.from(selectedCategories),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
126
205
|
setSelectedCategories(new Set()); // Reset after detection
|
|
127
206
|
}
|
|
128
207
|
};
|
|
@@ -133,13 +212,13 @@ export function TaggingPanel({
|
|
|
133
212
|
|
|
134
213
|
const handleEscape = (e: KeyboardEvent) => {
|
|
135
214
|
if (e.key === 'Escape') {
|
|
136
|
-
eventBus.emit('
|
|
215
|
+
eventBus.emit('annotation:cancel-pending', undefined);
|
|
137
216
|
}
|
|
138
217
|
};
|
|
139
218
|
|
|
140
219
|
document.addEventListener('keydown', handleEscape);
|
|
141
220
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
142
|
-
}, [pendingAnnotation
|
|
221
|
+
}, [pendingAnnotation]);
|
|
143
222
|
|
|
144
223
|
// Color schemes are now handled via CSS data attributes
|
|
145
224
|
|
|
@@ -194,8 +273,18 @@ export function TaggingPanel({
|
|
|
194
273
|
<select
|
|
195
274
|
className="semiont-select"
|
|
196
275
|
onChange={(e) => {
|
|
197
|
-
if (e.target.value) {
|
|
198
|
-
|
|
276
|
+
if (e.target.value && pendingAnnotation) {
|
|
277
|
+
eventBus.emit('annotation:create', {
|
|
278
|
+
motivation: 'tagging',
|
|
279
|
+
selector: pendingAnnotation.selector,
|
|
280
|
+
body: [
|
|
281
|
+
{
|
|
282
|
+
type: 'TextualBody' as const,
|
|
283
|
+
value: e.target.value,
|
|
284
|
+
purpose: 'tagging' as const,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
});
|
|
199
288
|
}
|
|
200
289
|
}}
|
|
201
290
|
defaultValue=""
|
|
@@ -211,7 +300,7 @@ export function TaggingPanel({
|
|
|
211
300
|
{/* Cancel button */}
|
|
212
301
|
<div className="semiont-annotation-prompt__footer">
|
|
213
302
|
<button
|
|
214
|
-
onClick={() => eventBus.emit('
|
|
303
|
+
onClick={() => eventBus.emit('annotation:cancel-pending', undefined)}
|
|
215
304
|
className="semiont-button semiont-button--secondary"
|
|
216
305
|
data-type="tag"
|
|
217
306
|
>
|
|
@@ -222,7 +311,7 @@ export function TaggingPanel({
|
|
|
222
311
|
)}
|
|
223
312
|
|
|
224
313
|
{/* Detection Section - only in Annotate mode */}
|
|
225
|
-
{annotateMode &&
|
|
314
|
+
{annotateMode && (
|
|
226
315
|
<div className="semiont-panel__section">
|
|
227
316
|
<button
|
|
228
317
|
onClick={() => setIsDetectExpanded(!isDetectExpanded)}
|
|
@@ -390,9 +479,8 @@ export function TaggingPanel({
|
|
|
390
479
|
key={tag.id}
|
|
391
480
|
tag={tag}
|
|
392
481
|
isFocused={tag.id === focusedAnnotationId}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
{...(onAnnotationHover && { onTagHover: onAnnotationHover })}
|
|
482
|
+
isHovered={tag.id === hoveredAnnotationId}
|
|
483
|
+
ref={(el) => setEntryRef(tag.id, el)}
|
|
396
484
|
/>
|
|
397
485
|
))
|
|
398
486
|
)}
|
|
@@ -5,8 +5,6 @@ import { useTranslations } from '../../../contexts/TranslationContext';
|
|
|
5
5
|
import type { components, Selector } from '@semiont/api-client';
|
|
6
6
|
import type { RouteBuilder, LinkComponentProps } from '../../../contexts/RoutingContext';
|
|
7
7
|
import type { Annotator } from '../../../lib/annotation-registry';
|
|
8
|
-
import { createDetectionHandler } from '../../../lib/annotation-registry';
|
|
9
|
-
import { supportsDetection } from '../../../lib/resource-utils';
|
|
10
8
|
import { StatisticsPanel } from './StatisticsPanel';
|
|
11
9
|
import { HighlightPanel } from './HighlightPanel';
|
|
12
10
|
import { ReferencesPanel } from './ReferencesPanel';
|
|
@@ -29,13 +27,13 @@ interface PendingAnnotation {
|
|
|
29
27
|
const TAB_ORDER: TabKey[] = ['statistics', 'reference', 'highlight', 'assessment', 'comment', 'tag'];
|
|
30
28
|
|
|
31
29
|
/**
|
|
32
|
-
* Simplified UnifiedAnnotationsPanel using
|
|
30
|
+
* Simplified UnifiedAnnotationsPanel using event-driven architecture
|
|
33
31
|
*
|
|
34
32
|
* Key simplifications:
|
|
35
33
|
* - Single annotations array (grouped internally by motivation)
|
|
36
34
|
* - Single focusedAnnotationId (motivation-agnostic)
|
|
37
|
-
* -
|
|
38
|
-
* -
|
|
35
|
+
* - Hover state managed via event bus (no props needed)
|
|
36
|
+
* - All operations managed via event bus (no callback props)
|
|
39
37
|
*/
|
|
40
38
|
interface UnifiedAnnotationsPanelProps {
|
|
41
39
|
// All annotations (grouped internally by motivation)
|
|
@@ -44,29 +42,6 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
44
42
|
// Annotators (pure static data - no handlers)
|
|
45
43
|
annotators: Record<string, Annotator>;
|
|
46
44
|
|
|
47
|
-
// Detection context (passed separately so annotators remain stable)
|
|
48
|
-
detectionContext?: {
|
|
49
|
-
client: any;
|
|
50
|
-
rUri: any;
|
|
51
|
-
setDetectingMotivation: (motivation: Motivation | null) => void;
|
|
52
|
-
setMotivationDetectionProgress: (progress: any) => void;
|
|
53
|
-
detectionStreamRef: any;
|
|
54
|
-
cacheManager: any;
|
|
55
|
-
showSuccess: (message: string) => void;
|
|
56
|
-
showError: (message: string) => void;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Unified state (motivation-agnostic)
|
|
60
|
-
focusedAnnotationId: string | null;
|
|
61
|
-
hoveredAnnotationId?: string | null;
|
|
62
|
-
|
|
63
|
-
// Shared UI handlers (same for all annotation types)
|
|
64
|
-
onAnnotationClick: (annotation: Annotation) => void;
|
|
65
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
66
|
-
|
|
67
|
-
// Single generic creation handler
|
|
68
|
-
onCreateAnnotation: (motivation: Motivation, ...args: any[]) => void;
|
|
69
|
-
|
|
70
45
|
// Mode
|
|
71
46
|
annotateMode?: boolean;
|
|
72
47
|
|
|
@@ -85,20 +60,23 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
85
60
|
// Unified pending annotation (for creating new annotations)
|
|
86
61
|
pendingAnnotation: PendingAnnotation | null;
|
|
87
62
|
|
|
88
|
-
// Reference-specific props
|
|
63
|
+
// Reference-specific props
|
|
89
64
|
allEntityTypes?: string[];
|
|
90
65
|
generatingReferenceId?: string | null;
|
|
91
|
-
onGenerateDocument?: (referenceId: string, options: { title: string; prompt?: string }) => void;
|
|
92
|
-
onCreateDocument?: (annotationUri: string, title: string, entityTypes: string[]) => void;
|
|
93
|
-
onSearchDocuments?: (referenceId: string, searchTerm: string) => void;
|
|
94
|
-
onCancelDetection?: () => void;
|
|
95
|
-
mediaType?: string;
|
|
96
66
|
referencedBy?: any[];
|
|
97
67
|
referencedByLoading?: boolean;
|
|
98
68
|
|
|
99
69
|
// Resource context
|
|
100
70
|
resourceId?: string;
|
|
101
71
|
initialTab?: TabKey;
|
|
72
|
+
initialTabGeneration?: number; // Generation counter for tab switching
|
|
73
|
+
|
|
74
|
+
// Scroll coordination (one-time action, will be cleared after use)
|
|
75
|
+
scrollToAnnotationId?: string | null;
|
|
76
|
+
onScrollCompleted?: () => void;
|
|
77
|
+
|
|
78
|
+
// Hover coordination (for bidirectional hover highlighting)
|
|
79
|
+
hoveredAnnotationId?: string | null;
|
|
102
80
|
|
|
103
81
|
// Routing
|
|
104
82
|
Link: React.ComponentType<LinkComponentProps>;
|
|
@@ -156,23 +134,14 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
156
134
|
localStorage.setItem(storageKey, activeTab);
|
|
157
135
|
}, [activeTab, props.resourceId]);
|
|
158
136
|
|
|
159
|
-
// Auto-switch to the appropriate tab when an annotation is focused
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (!props.focusedAnnotationId) return;
|
|
162
|
-
|
|
163
|
-
// Find which annotation type this focused annotation belongs to
|
|
164
|
-
const focusedAnnotation = props.annotations.find(ann => ann.id === props.focusedAnnotationId);
|
|
165
|
-
if (!focusedAnnotation) return;
|
|
166
137
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
138
|
+
// Switch to initialTab when generation counter changes (e.g., when clicking annotations with different motivations)
|
|
139
|
+
// Using generation counter ensures we can switch to the same tab multiple times
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (props.initialTab && props.initialTabGeneration !== undefined) {
|
|
142
|
+
setActiveTab(props.initialTab);
|
|
173
143
|
}
|
|
174
|
-
|
|
175
|
-
}, [props.focusedAnnotationId]);
|
|
144
|
+
}, [props.initialTabGeneration]); // Only watch generation counter, not the tab itself
|
|
176
145
|
|
|
177
146
|
// Auto-switch to the appropriate tab when creating a new annotation
|
|
178
147
|
useEffect(() => {
|
|
@@ -266,38 +235,22 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
266
235
|
|
|
267
236
|
const annotations = grouped[activeTab] || [];
|
|
268
237
|
const isDetecting = props.detectingMotivation === annotator.motivation;
|
|
269
|
-
|
|
238
|
+
// Pass through detectionProgress even when not actively detecting
|
|
239
|
+
// This allows final progress message to display after detection completes
|
|
240
|
+
const detectionProgress = props.detectionProgress;
|
|
270
241
|
|
|
271
|
-
|
|
272
|
-
// Create detection handler on-demand if:
|
|
273
|
-
// 1. Annotator supports detection (has detection config)
|
|
274
|
-
// 2. Detection context is provided (API client, state handlers)
|
|
275
|
-
// 3. API client is available (not undefined/null)
|
|
276
|
-
// 4. Resource supports detection (is a text/* media type)
|
|
277
|
-
const onDetect = (
|
|
278
|
-
annotator.detection &&
|
|
279
|
-
props.detectionContext &&
|
|
280
|
-
props.detectionContext.client &&
|
|
281
|
-
supportsDetection(props.mediaType)
|
|
282
|
-
)
|
|
283
|
-
? createDetectionHandler(annotator, props.detectionContext)
|
|
284
|
-
: undefined;
|
|
285
|
-
|
|
286
|
-
// Create wrapper function that calls onCreateAnnotation with the annotator's motivation
|
|
287
|
-
const onCreate = (...args: any[]) => props.onCreateAnnotation(annotator.motivation, ...args);
|
|
242
|
+
console.log('[UnifiedAnnotationsPanel] activeTab:', activeTab, 'annotator.motivation:', annotator.motivation, 'props.detectingMotivation:', props.detectingMotivation, 'isDetecting:', isDetecting, 'detectionProgress:', detectionProgress);
|
|
288
243
|
|
|
244
|
+
// Common props for all annotation panels
|
|
289
245
|
const commonProps = {
|
|
290
246
|
annotations,
|
|
291
|
-
onAnnotationClick: props.onAnnotationClick,
|
|
292
|
-
focusedAnnotationId: props.focusedAnnotationId,
|
|
293
|
-
hoveredAnnotationId: props.hoveredAnnotationId,
|
|
294
|
-
onAnnotationHover: props.onAnnotationHover,
|
|
295
|
-
onDetect,
|
|
296
|
-
onCreate,
|
|
297
247
|
pendingAnnotation: props.pendingAnnotation,
|
|
298
248
|
isDetecting,
|
|
299
249
|
detectionProgress,
|
|
300
|
-
annotateMode: props.annotateMode
|
|
250
|
+
annotateMode: props.annotateMode,
|
|
251
|
+
scrollToAnnotationId: props.scrollToAnnotationId,
|
|
252
|
+
onScrollCompleted: props.onScrollCompleted,
|
|
253
|
+
hoveredAnnotationId: props.hoveredAnnotationId
|
|
301
254
|
};
|
|
302
255
|
|
|
303
256
|
// Render specific panel based on activeTab with full type safety
|
|
@@ -305,8 +258,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
305
258
|
return (
|
|
306
259
|
<HighlightPanel
|
|
307
260
|
{...commonProps}
|
|
308
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
309
|
-
onCreate={onCreate}
|
|
310
261
|
/>
|
|
311
262
|
);
|
|
312
263
|
}
|
|
@@ -315,21 +266,14 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
315
266
|
return (
|
|
316
267
|
<ReferencesPanel
|
|
317
268
|
annotations={commonProps.annotations}
|
|
318
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
319
|
-
focusedAnnotationId={commonProps.focusedAnnotationId}
|
|
320
|
-
hoveredAnnotationId={commonProps.hoveredAnnotationId}
|
|
321
|
-
onAnnotationHover={commonProps.onAnnotationHover}
|
|
322
|
-
onDetect={onDetect}
|
|
323
|
-
onCreate={onCreate}
|
|
324
269
|
pendingAnnotation={commonProps.pendingAnnotation}
|
|
325
270
|
isDetecting={commonProps.isDetecting}
|
|
326
271
|
detectionProgress={commonProps.detectionProgress}
|
|
327
272
|
annotateMode={commonProps.annotateMode}
|
|
273
|
+
scrollToAnnotationId={commonProps.scrollToAnnotationId}
|
|
274
|
+
onScrollCompleted={commonProps.onScrollCompleted}
|
|
275
|
+
hoveredAnnotationId={commonProps.hoveredAnnotationId}
|
|
328
276
|
allEntityTypes={props.allEntityTypes || []}
|
|
329
|
-
onCancelDetection={props.onCancelDetection || (() => {})}
|
|
330
|
-
onGenerateDocument={props.onGenerateDocument}
|
|
331
|
-
onCreateDocument={props.onCreateDocument}
|
|
332
|
-
onSearchDocuments={props.onSearchDocuments}
|
|
333
277
|
generatingReferenceId={props.generatingReferenceId}
|
|
334
278
|
referencedBy={props.referencedBy}
|
|
335
279
|
referencedByLoading={props.referencedByLoading}
|
|
@@ -343,8 +287,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
343
287
|
return (
|
|
344
288
|
<AssessmentPanel
|
|
345
289
|
{...commonProps}
|
|
346
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
347
|
-
onCreate={onCreate}
|
|
348
290
|
/>
|
|
349
291
|
);
|
|
350
292
|
}
|
|
@@ -353,8 +295,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
353
295
|
return (
|
|
354
296
|
<CommentsPanel
|
|
355
297
|
{...commonProps}
|
|
356
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
357
|
-
onCreate={onCreate}
|
|
358
298
|
/>
|
|
359
299
|
);
|
|
360
300
|
}
|
|
@@ -363,8 +303,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
363
303
|
return (
|
|
364
304
|
<TaggingPanel
|
|
365
305
|
{...commonProps}
|
|
366
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
367
|
-
onCreate={onCreate}
|
|
368
306
|
/>
|
|
369
307
|
);
|
|
370
308
|
}
|