@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,10 +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 { useEventBus } from '../../../contexts/EventBusContext';
|
|
6
|
+
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
5
7
|
import type { components, Selector } from '@semiont/api-client';
|
|
8
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/api-client';
|
|
6
9
|
import { TagEntry } from './TagEntry';
|
|
7
|
-
import { useAnnotationPanel } from '../../../hooks/useAnnotationPanel';
|
|
8
10
|
import { PanelHeader } from './PanelHeader';
|
|
9
11
|
import { getAllTagSchemas } from '../../../lib/tag-schemas';
|
|
10
12
|
import './TaggingPanel.css';
|
|
@@ -37,13 +39,7 @@ function getSelectorDisplayText(selector: Selector | Selector[]): string | null
|
|
|
37
39
|
|
|
38
40
|
interface TaggingPanelProps {
|
|
39
41
|
annotations: Annotation[];
|
|
40
|
-
onAnnotationClick: (annotation: Annotation) => void;
|
|
41
|
-
focusedAnnotationId: string | null;
|
|
42
|
-
hoveredAnnotationId?: string | null;
|
|
43
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
44
42
|
annotateMode?: boolean;
|
|
45
|
-
onDetect?: (schemaId: string, categories: string[]) => void | Promise<void>;
|
|
46
|
-
onCreate: (schemaId: string, category: string) => void | Promise<void>;
|
|
47
43
|
isDetecting?: boolean;
|
|
48
44
|
detectionProgress?: {
|
|
49
45
|
status: string;
|
|
@@ -55,24 +51,35 @@ interface TaggingPanelProps {
|
|
|
55
51
|
requestParams?: Array<{ label: string; value: string }>;
|
|
56
52
|
} | null;
|
|
57
53
|
pendingAnnotation: PendingAnnotation | null;
|
|
54
|
+
scrollToAnnotationId?: string | null;
|
|
55
|
+
onScrollCompleted?: () => void;
|
|
56
|
+
hoveredAnnotationId?: string | null;
|
|
58
57
|
}
|
|
59
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
|
+
*/
|
|
60
67
|
export function TaggingPanel({
|
|
61
68
|
annotations,
|
|
62
|
-
onAnnotationClick,
|
|
63
|
-
focusedAnnotationId,
|
|
64
|
-
hoveredAnnotationId,
|
|
65
|
-
onAnnotationHover,
|
|
66
69
|
annotateMode = true,
|
|
67
|
-
onDetect,
|
|
68
|
-
onCreate,
|
|
69
70
|
isDetecting = false,
|
|
70
71
|
detectionProgress,
|
|
71
|
-
pendingAnnotation
|
|
72
|
+
pendingAnnotation,
|
|
73
|
+
scrollToAnnotationId,
|
|
74
|
+
onScrollCompleted,
|
|
75
|
+
hoveredAnnotationId,
|
|
72
76
|
}: TaggingPanelProps) {
|
|
73
77
|
const t = useTranslations('TaggingPanel');
|
|
78
|
+
const eventBus = useEventBus();
|
|
74
79
|
const [selectedSchemaId, setSelectedSchemaId] = useState<string>('legal-irac');
|
|
75
80
|
const [selectedCategories, setSelectedCategories] = useState<Set<string>>(new Set());
|
|
81
|
+
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
82
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
76
83
|
|
|
77
84
|
// Collapsible detection section state - load from localStorage, default expanded
|
|
78
85
|
const [isDetectExpanded, setIsDetectExpanded] = useState(() => {
|
|
@@ -87,8 +94,76 @@ export function TaggingPanel({
|
|
|
87
94
|
localStorage.setItem('detect-section-expanded-tag', String(isDetectExpanded));
|
|
88
95
|
}, [isDetectExpanded]);
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
|
|
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]);
|
|
92
167
|
|
|
93
168
|
const schemas = getAllTagSchemas();
|
|
94
169
|
const selectedSchema = schemas.find(s => s.id === selectedSchemaId);
|
|
@@ -119,12 +194,32 @@ export function TaggingPanel({
|
|
|
119
194
|
};
|
|
120
195
|
|
|
121
196
|
const handleDetect = () => {
|
|
122
|
-
if (
|
|
123
|
-
|
|
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
|
+
});
|
|
124
205
|
setSelectedCategories(new Set()); // Reset after detection
|
|
125
206
|
}
|
|
126
207
|
};
|
|
127
208
|
|
|
209
|
+
// Escape key handler for cancelling pending annotation
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (!pendingAnnotation) return;
|
|
212
|
+
|
|
213
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
214
|
+
if (e.key === 'Escape') {
|
|
215
|
+
eventBus.emit('annotation:cancel-pending', undefined);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
document.addEventListener('keydown', handleEscape);
|
|
220
|
+
return () => document.removeEventListener('keydown', handleEscape);
|
|
221
|
+
}, [pendingAnnotation]);
|
|
222
|
+
|
|
128
223
|
// Color schemes are now handled via CSS data attributes
|
|
129
224
|
|
|
130
225
|
return (
|
|
@@ -178,8 +273,19 @@ export function TaggingPanel({
|
|
|
178
273
|
<select
|
|
179
274
|
className="semiont-select"
|
|
180
275
|
onChange={(e) => {
|
|
181
|
-
if (e.target.value) {
|
|
182
|
-
|
|
276
|
+
if (e.target.value && pendingAnnotation) {
|
|
277
|
+
eventBus.emit('annotation:create', {
|
|
278
|
+
motivation: 'tagging',
|
|
279
|
+
selector: pendingAnnotation.selector,
|
|
280
|
+
body: [
|
|
281
|
+
{
|
|
282
|
+
type: 'TextualBody',
|
|
283
|
+
value: e.target.value,
|
|
284
|
+
purpose: 'tagging',
|
|
285
|
+
schema: selectedSchemaId,
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
});
|
|
183
289
|
}
|
|
184
290
|
}}
|
|
185
291
|
defaultValue=""
|
|
@@ -191,11 +297,22 @@ export function TaggingPanel({
|
|
|
191
297
|
</select>
|
|
192
298
|
</div>
|
|
193
299
|
)}
|
|
300
|
+
|
|
301
|
+
{/* Cancel button */}
|
|
302
|
+
<div className="semiont-annotation-prompt__footer">
|
|
303
|
+
<button
|
|
304
|
+
onClick={() => eventBus.emit('annotation:cancel-pending', undefined)}
|
|
305
|
+
className="semiont-button semiont-button--secondary"
|
|
306
|
+
data-type="tag"
|
|
307
|
+
>
|
|
308
|
+
{t('cancel')}
|
|
309
|
+
</button>
|
|
310
|
+
</div>
|
|
194
311
|
</div>
|
|
195
312
|
)}
|
|
196
313
|
|
|
197
314
|
{/* Detection Section - only in Annotate mode */}
|
|
198
|
-
{annotateMode &&
|
|
315
|
+
{annotateMode && (
|
|
199
316
|
<div className="semiont-panel__section">
|
|
200
317
|
<button
|
|
201
318
|
onClick={() => setIsDetectExpanded(!isDetectExpanded)}
|
|
@@ -363,9 +480,8 @@ export function TaggingPanel({
|
|
|
363
480
|
key={tag.id}
|
|
364
481
|
tag={tag}
|
|
365
482
|
isFocused={tag.id === focusedAnnotationId}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
{...(onAnnotationHover && { onTagHover: onAnnotationHover })}
|
|
483
|
+
isHovered={tag.id === hoveredAnnotationId}
|
|
484
|
+
ref={(el) => setEntryRef(tag.id, el)}
|
|
369
485
|
/>
|
|
370
486
|
))
|
|
371
487
|
)}
|
|
@@ -5,7 +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
8
|
import { StatisticsPanel } from './StatisticsPanel';
|
|
10
9
|
import { HighlightPanel } from './HighlightPanel';
|
|
11
10
|
import { ReferencesPanel } from './ReferencesPanel';
|
|
@@ -28,13 +27,13 @@ interface PendingAnnotation {
|
|
|
28
27
|
const TAB_ORDER: TabKey[] = ['statistics', 'reference', 'highlight', 'assessment', 'comment', 'tag'];
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
|
-
* Simplified UnifiedAnnotationsPanel using
|
|
30
|
+
* Simplified UnifiedAnnotationsPanel using event-driven architecture
|
|
32
31
|
*
|
|
33
32
|
* Key simplifications:
|
|
34
33
|
* - Single annotations array (grouped internally by motivation)
|
|
35
34
|
* - Single focusedAnnotationId (motivation-agnostic)
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
35
|
+
* - Hover state managed via event bus (no props needed)
|
|
36
|
+
* - All operations managed via event bus (no callback props)
|
|
38
37
|
*/
|
|
39
38
|
interface UnifiedAnnotationsPanelProps {
|
|
40
39
|
// All annotations (grouped internally by motivation)
|
|
@@ -43,29 +42,6 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
43
42
|
// Annotators (pure static data - no handlers)
|
|
44
43
|
annotators: Record<string, Annotator>;
|
|
45
44
|
|
|
46
|
-
// Detection context (passed separately so annotators remain stable)
|
|
47
|
-
detectionContext?: {
|
|
48
|
-
client: any;
|
|
49
|
-
rUri: any;
|
|
50
|
-
setDetectingMotivation: (motivation: Motivation | null) => void;
|
|
51
|
-
setMotivationDetectionProgress: (progress: any) => void;
|
|
52
|
-
detectionStreamRef: any;
|
|
53
|
-
cacheManager: any;
|
|
54
|
-
showSuccess: (message: string) => void;
|
|
55
|
-
showError: (message: string) => void;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Unified state (motivation-agnostic)
|
|
59
|
-
focusedAnnotationId: string | null;
|
|
60
|
-
hoveredAnnotationId?: string | null;
|
|
61
|
-
|
|
62
|
-
// Shared UI handlers (same for all annotation types)
|
|
63
|
-
onAnnotationClick: (annotation: Annotation) => void;
|
|
64
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
65
|
-
|
|
66
|
-
// Single generic creation handler
|
|
67
|
-
onCreateAnnotation: (motivation: Motivation, ...args: any[]) => void;
|
|
68
|
-
|
|
69
45
|
// Mode
|
|
70
46
|
annotateMode?: boolean;
|
|
71
47
|
|
|
@@ -84,20 +60,23 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
84
60
|
// Unified pending annotation (for creating new annotations)
|
|
85
61
|
pendingAnnotation: PendingAnnotation | null;
|
|
86
62
|
|
|
87
|
-
// Reference-specific props
|
|
63
|
+
// Reference-specific props
|
|
88
64
|
allEntityTypes?: string[];
|
|
89
65
|
generatingReferenceId?: string | null;
|
|
90
|
-
onGenerateDocument?: (referenceId: string, options: { title: string; prompt?: string }) => void;
|
|
91
|
-
onCreateDocument?: (annotationUri: string, title: string, entityTypes: string[]) => void;
|
|
92
|
-
onSearchDocuments?: (referenceId: string, searchTerm: string) => void;
|
|
93
|
-
onCancelDetection?: () => void;
|
|
94
|
-
mediaType?: string;
|
|
95
66
|
referencedBy?: any[];
|
|
96
67
|
referencedByLoading?: boolean;
|
|
97
68
|
|
|
98
69
|
// Resource context
|
|
99
70
|
resourceId?: string;
|
|
100
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;
|
|
101
80
|
|
|
102
81
|
// Routing
|
|
103
82
|
Link: React.ComponentType<LinkComponentProps>;
|
|
@@ -108,27 +87,25 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
108
87
|
const t = useTranslations('UnifiedAnnotationsPanel');
|
|
109
88
|
|
|
110
89
|
// Group annotations by type using annotators
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (annotator) {
|
|
123
|
-
|
|
124
|
-
groups[annotator.internalType] = [];
|
|
125
|
-
}
|
|
126
|
-
groups[annotator.internalType].push(ann);
|
|
90
|
+
const groups: Record<string, Annotation[]> = {
|
|
91
|
+
highlight: [],
|
|
92
|
+
comment: [],
|
|
93
|
+
assessment: [],
|
|
94
|
+
reference: [],
|
|
95
|
+
tag: []
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
for (const ann of props.annotations) {
|
|
99
|
+
const annotator = Object.values(props.annotators).find(a => a.matchesAnnotation(ann));
|
|
100
|
+
if (annotator) {
|
|
101
|
+
if (!groups[annotator.internalType]) {
|
|
102
|
+
groups[annotator.internalType] = [];
|
|
127
103
|
}
|
|
104
|
+
groups[annotator.internalType].push(ann);
|
|
128
105
|
}
|
|
106
|
+
}
|
|
129
107
|
|
|
130
|
-
|
|
131
|
-
}, [props.annotations, props.annotators]);
|
|
108
|
+
const grouped = groups;
|
|
132
109
|
|
|
133
110
|
// Load tab from localStorage (per-resource)
|
|
134
111
|
const [activeTab, setActiveTab] = useState<TabKey>(() => {
|
|
@@ -157,23 +134,14 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
157
134
|
localStorage.setItem(storageKey, activeTab);
|
|
158
135
|
}, [activeTab, props.resourceId]);
|
|
159
136
|
|
|
160
|
-
// Auto-switch to the appropriate tab when an annotation is focused
|
|
161
|
-
useEffect(() => {
|
|
162
|
-
if (!props.focusedAnnotationId) return;
|
|
163
|
-
|
|
164
|
-
// Find which annotation type this focused annotation belongs to
|
|
165
|
-
const focusedAnnotation = props.annotations.find(ann => ann.id === props.focusedAnnotationId);
|
|
166
|
-
if (!focusedAnnotation) return;
|
|
167
137
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
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);
|
|
174
143
|
}
|
|
175
|
-
|
|
176
|
-
}, [props.focusedAnnotationId]);
|
|
144
|
+
}, [props.initialTabGeneration]); // Only watch generation counter, not the tab itself
|
|
177
145
|
|
|
178
146
|
// Auto-switch to the appropriate tab when creating a new annotation
|
|
179
147
|
useEffect(() => {
|
|
@@ -267,29 +235,22 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
267
235
|
|
|
268
236
|
const annotations = grouped[activeTab] || [];
|
|
269
237
|
const isDetecting = props.detectingMotivation === annotator.motivation;
|
|
270
|
-
|
|
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;
|
|
271
241
|
|
|
272
|
-
|
|
273
|
-
// Create detection handler on-demand if detection is supported and context is provided
|
|
274
|
-
const onDetect = (annotator.detection && props.detectionContext)
|
|
275
|
-
? createDetectionHandler(annotator, props.detectionContext)
|
|
276
|
-
: undefined;
|
|
277
|
-
|
|
278
|
-
// Create wrapper function that calls onCreateAnnotation with the annotator's motivation
|
|
279
|
-
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);
|
|
280
243
|
|
|
244
|
+
// Common props for all annotation panels
|
|
281
245
|
const commonProps = {
|
|
282
246
|
annotations,
|
|
283
|
-
onAnnotationClick: props.onAnnotationClick,
|
|
284
|
-
focusedAnnotationId: props.focusedAnnotationId,
|
|
285
|
-
hoveredAnnotationId: props.hoveredAnnotationId,
|
|
286
|
-
onAnnotationHover: props.onAnnotationHover,
|
|
287
|
-
onDetect,
|
|
288
|
-
onCreate,
|
|
289
247
|
pendingAnnotation: props.pendingAnnotation,
|
|
290
248
|
isDetecting,
|
|
291
249
|
detectionProgress,
|
|
292
|
-
annotateMode: props.annotateMode
|
|
250
|
+
annotateMode: props.annotateMode,
|
|
251
|
+
scrollToAnnotationId: props.scrollToAnnotationId,
|
|
252
|
+
onScrollCompleted: props.onScrollCompleted,
|
|
253
|
+
hoveredAnnotationId: props.hoveredAnnotationId
|
|
293
254
|
};
|
|
294
255
|
|
|
295
256
|
// Render specific panel based on activeTab with full type safety
|
|
@@ -297,8 +258,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
297
258
|
return (
|
|
298
259
|
<HighlightPanel
|
|
299
260
|
{...commonProps}
|
|
300
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
301
|
-
onCreate={onCreate}
|
|
302
261
|
/>
|
|
303
262
|
);
|
|
304
263
|
}
|
|
@@ -307,23 +266,15 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
307
266
|
return (
|
|
308
267
|
<ReferencesPanel
|
|
309
268
|
annotations={commonProps.annotations}
|
|
310
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
311
|
-
focusedAnnotationId={commonProps.focusedAnnotationId}
|
|
312
|
-
hoveredAnnotationId={commonProps.hoveredAnnotationId}
|
|
313
|
-
onAnnotationHover={commonProps.onAnnotationHover}
|
|
314
|
-
onDetect={onDetect}
|
|
315
|
-
onCreate={onCreate}
|
|
316
269
|
pendingAnnotation={commonProps.pendingAnnotation}
|
|
317
270
|
isDetecting={commonProps.isDetecting}
|
|
318
271
|
detectionProgress={commonProps.detectionProgress}
|
|
319
272
|
annotateMode={commonProps.annotateMode}
|
|
273
|
+
scrollToAnnotationId={commonProps.scrollToAnnotationId}
|
|
274
|
+
onScrollCompleted={commonProps.onScrollCompleted}
|
|
275
|
+
hoveredAnnotationId={commonProps.hoveredAnnotationId}
|
|
320
276
|
allEntityTypes={props.allEntityTypes || []}
|
|
321
|
-
onCancelDetection={props.onCancelDetection || (() => {})}
|
|
322
|
-
onGenerateDocument={props.onGenerateDocument}
|
|
323
|
-
onCreateDocument={props.onCreateDocument}
|
|
324
|
-
onSearchDocuments={props.onSearchDocuments}
|
|
325
277
|
generatingReferenceId={props.generatingReferenceId}
|
|
326
|
-
mediaType={props.mediaType}
|
|
327
278
|
referencedBy={props.referencedBy}
|
|
328
279
|
referencedByLoading={props.referencedByLoading}
|
|
329
280
|
Link={props.Link}
|
|
@@ -336,8 +287,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
336
287
|
return (
|
|
337
288
|
<AssessmentPanel
|
|
338
289
|
{...commonProps}
|
|
339
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
340
|
-
onCreate={onCreate}
|
|
341
290
|
/>
|
|
342
291
|
);
|
|
343
292
|
}
|
|
@@ -346,8 +295,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
346
295
|
return (
|
|
347
296
|
<CommentsPanel
|
|
348
297
|
{...commonProps}
|
|
349
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
350
|
-
onCreate={onCreate}
|
|
351
298
|
/>
|
|
352
299
|
);
|
|
353
300
|
}
|
|
@@ -356,8 +303,6 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
356
303
|
return (
|
|
357
304
|
<TaggingPanel
|
|
358
305
|
{...commonProps}
|
|
359
|
-
onAnnotationClick={commonProps.onAnnotationClick!}
|
|
360
|
-
onCreate={onCreate}
|
|
361
306
|
/>
|
|
362
307
|
);
|
|
363
308
|
}
|