@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EventBusContext-7GvDyO0d.d.mts +414 -0
- package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
- package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
- package/dist/{ar-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-JZIO2A3B.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
- package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
- package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -4
- package/dist/id-7U7GGVWY.mjs.map +1 -0
- package/dist/index.css +123 -85
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +645 -471
- package/dist/index.mjs +3461 -3025
- package/dist/index.mjs.map +1 -1
- package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -4
- package/dist/sv-7ZK5EQEB.mjs.map +1 -0
- package/dist/test-utils.d.mts +18 -8
- package/dist/test-utils.mjs +36 -14
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +3 -1
- package/src/components/CodeMirrorRenderer.tsx +66 -93
- package/src/components/DetectionProgressWidget.tsx +16 -5
- package/src/components/LiveRegion.tsx +18 -18
- package/src/components/ResizeHandle.tsx +10 -4
- package/src/components/SessionTimer.tsx +2 -2
- package/src/components/Toolbar.tsx +18 -9
- package/src/components/__tests__/SessionTimer.test.tsx +9 -9
- package/src/components/annotation/AnnotateToolbar.tsx +17 -15
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
- package/src/components/annotation/annotation-entries.css +10 -0
- package/src/components/annotation-popups/JsonLdView.tsx +8 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
- package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
- package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
- package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
- package/src/components/modals/ResourceSearchModal.tsx +2 -2
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
- package/src/components/navigation/NavigationTabs.css +36 -24
- package/src/components/navigation/ObservableLink.tsx +91 -0
- package/src/components/navigation/SimpleNavigation.tsx +20 -16
- package/src/components/navigation/SortableResourceTab.tsx +11 -5
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
- package/src/components/resource/AnnotateView.tsx +64 -134
- package/src/components/resource/BrowseView.tsx +86 -166
- package/src/components/resource/HistoryEvent.tsx +13 -7
- package/src/components/resource/ResourceViewer.tsx +122 -264
- package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +121 -28
- package/src/components/resource/panels/DetectSection.css +36 -1
- package/src/components/resource/panels/DetectSection.tsx +38 -10
- package/src/components/resource/panels/HighlightEntry.tsx +25 -33
- package/src/components/resource/panels/HighlightPanel.tsx +100 -25
- package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
- package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +119 -30
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
- package/src/components/settings/SettingsPanel.tsx +15 -12
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
- package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
- package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
- package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
- package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +504 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +135 -88
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +308 -528
- package/translations/ar.json +6 -3
- package/translations/bn.json +6 -3
- package/translations/cs.json +6 -3
- package/translations/da.json +6 -3
- package/translations/de.json +6 -3
- package/translations/el.json +6 -3
- package/translations/en.json +6 -3
- package/translations/es.json +6 -3
- package/translations/fa.json +6 -3
- package/translations/fi.json +6 -3
- package/translations/fr.json +6 -3
- package/translations/he.json +6 -3
- package/translations/hi.json +6 -3
- package/translations/id.json +6 -3
- package/translations/it.json +6 -3
- package/translations/ja.json +6 -3
- package/translations/ko.json +6 -3
- package/translations/ms.json +6 -3
- package/translations/nl.json +6 -3
- package/translations/no.json +6 -3
- package/translations/pl.json +6 -3
- package/translations/pt.json +6 -3
- package/translations/ro.json +6 -3
- package/translations/sv.json +6 -3
- package/translations/th.json +6 -3
- package/translations/tr.json +6 -3
- package/translations/uk.json +6 -3
- package/translations/vi.json +6 -3
- package/translations/zh.json +6 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-EMHEHPCJ.mjs.map +0 -1
- package/dist/bn-OVCI4F6X.mjs.map +0 -1
- package/dist/chunk-JZIO2A3B.mjs.map +0 -1
- package/dist/chunk-LIHZTECW.mjs.map +0 -1
- package/dist/cs-FAN66Q2F.mjs.map +0 -1
- package/dist/da-YBBIHI2O.mjs.map +0 -1
- package/dist/de-MAYU33LB.mjs.map +0 -1
- package/dist/el-MKGSWN4O.mjs.map +0 -1
- package/dist/es-52LHUWJD.mjs.map +0 -1
- package/dist/fa-FJICRANB.mjs.map +0 -1
- package/dist/fi-O455XFCR.mjs.map +0 -1
- package/dist/fr-TXIXHOOE.mjs.map +0 -1
- package/dist/he-JBSOX5IN.mjs.map +0 -1
- package/dist/hi-KGHI3XVT.mjs.map +0 -1
- package/dist/id-5OCPPZLO.mjs.map +0 -1
- package/dist/it-PNBBZSM2.mjs.map +0 -1
- package/dist/ja-LDD7R3TJ.mjs.map +0 -1
- package/dist/ko-F47ZDEY3.mjs.map +0 -1
- package/dist/ms-Z7LMXJWL.mjs.map +0 -1
- package/dist/nl-6SJFBPJ3.mjs.map +0 -1
- package/dist/no-YXPBPSGF.mjs.map +0 -1
- package/dist/pl-P4AZ2QME.mjs.map +0 -1
- package/dist/pt-LHWUS6U6.mjs.map +0 -1
- package/dist/ro-EA5J2ZON.mjs.map +0 -1
- package/dist/sv-DATBS3UQ.mjs.map +0 -1
- package/dist/th-WTFJRWPT.mjs.map +0 -1
- package/dist/tr-IKO3RXOX.mjs.map +0 -1
- package/dist/uk-CF6CTTRK.mjs.map +0 -1
- package/dist/vi-AJLTXPZQ.mjs.map +0 -1
- package/dist/zh-U3ORHHYH.mjs.map +0 -1
- /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { useTranslations } from '../../contexts/TranslationContext';
|
|
5
|
+
import { useEventBus } from '../../contexts/EventBusContext';
|
|
5
6
|
import { getSupportedShapes } from '../../lib/media-shapes';
|
|
6
7
|
import type { Annotator } from '../../lib/annotation-registry';
|
|
7
8
|
import './annotations.css';
|
|
@@ -15,18 +16,14 @@ export type ShapeType = 'rectangle' | 'circle' | 'polygon';
|
|
|
15
16
|
interface AnnotateToolbarProps {
|
|
16
17
|
selectedMotivation: SelectionMotivation | null;
|
|
17
18
|
selectedClick: ClickAction;
|
|
18
|
-
onSelectionChange: (motivation: SelectionMotivation | null) => void;
|
|
19
|
-
onClickChange: (motivation: ClickAction) => void;
|
|
20
19
|
showSelectionGroup?: boolean;
|
|
21
20
|
showDeleteButton?: boolean;
|
|
22
21
|
showShapeGroup?: boolean;
|
|
23
22
|
selectedShape?: ShapeType;
|
|
24
|
-
onShapeChange?: (shape: ShapeType) => void;
|
|
25
23
|
mediaType?: string | null; // MIME type to determine supported shapes
|
|
26
24
|
|
|
27
25
|
// Mode props
|
|
28
26
|
annotateMode: boolean;
|
|
29
|
-
onAnnotateModeToggle: () => void;
|
|
30
27
|
|
|
31
28
|
// Annotators for emoji lookup
|
|
32
29
|
annotators: Record<string, Annotator>;
|
|
@@ -102,22 +99,27 @@ function DropdownGroup({
|
|
|
102
99
|
);
|
|
103
100
|
}
|
|
104
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Toolbar for annotation controls with mode, selection, click, and shape options
|
|
104
|
+
*
|
|
105
|
+
* @emits toolbar:selection-changed - Selection motivation changed. Payload: { motivation: SelectionMotivation | null }
|
|
106
|
+
* @emits toolbar:click-changed - Click action mode changed. Payload: { action: ClickAction }
|
|
107
|
+
* @emits toolbar:shape-changed - Drawing shape changed. Payload: { shape: ShapeType }
|
|
108
|
+
* @emits view:mode-toggled - View mode toggled between browse and annotate. Payload: undefined
|
|
109
|
+
*/
|
|
105
110
|
export function AnnotateToolbar({
|
|
106
111
|
selectedMotivation,
|
|
107
112
|
selectedClick,
|
|
108
|
-
onSelectionChange,
|
|
109
|
-
onClickChange,
|
|
110
113
|
showSelectionGroup = true,
|
|
111
114
|
showDeleteButton = true,
|
|
112
115
|
showShapeGroup = false,
|
|
113
116
|
selectedShape = 'rectangle',
|
|
114
|
-
onShapeChange,
|
|
115
117
|
mediaType,
|
|
116
118
|
annotateMode = false,
|
|
117
|
-
onAnnotateModeToggle,
|
|
118
119
|
annotators
|
|
119
120
|
}: AnnotateToolbarProps) {
|
|
120
121
|
const t = useTranslations('AnnotateToolbar');
|
|
122
|
+
const eventBus = useEventBus();
|
|
121
123
|
|
|
122
124
|
// Helper to get emoji from annotators by motivation (with fallback for safety)
|
|
123
125
|
const getMotivationEmoji = (motivation: SelectionMotivation): string => {
|
|
@@ -186,9 +188,11 @@ export function AnnotateToolbar({
|
|
|
186
188
|
const handleSelectionClick = (motivation: SelectionMotivation | null) => {
|
|
187
189
|
// If null is clicked, always deselect. Otherwise toggle.
|
|
188
190
|
if (motivation === null) {
|
|
189
|
-
|
|
191
|
+
eventBus.emit('toolbar:selection-changed', { motivation: null });
|
|
190
192
|
} else {
|
|
191
|
-
|
|
193
|
+
eventBus.emit('toolbar:selection-changed', {
|
|
194
|
+
motivation: selectedMotivation === motivation ? null : motivation
|
|
195
|
+
});
|
|
192
196
|
}
|
|
193
197
|
// Close dropdown after selection
|
|
194
198
|
setSelectionPinned(false);
|
|
@@ -196,23 +200,21 @@ export function AnnotateToolbar({
|
|
|
196
200
|
};
|
|
197
201
|
|
|
198
202
|
const handleClickClick = (action: ClickAction) => {
|
|
199
|
-
|
|
203
|
+
eventBus.emit('toolbar:click-changed', { action });
|
|
200
204
|
// Close dropdown after selection
|
|
201
205
|
setClickPinned(false);
|
|
202
206
|
setClickHovered(false);
|
|
203
207
|
};
|
|
204
208
|
|
|
205
209
|
const handleShapeClick = (shape: ShapeType) => {
|
|
206
|
-
|
|
207
|
-
onShapeChange(shape);
|
|
208
|
-
}
|
|
210
|
+
eventBus.emit('toolbar:shape-changed', { shape });
|
|
209
211
|
// Close dropdown after selection
|
|
210
212
|
setShapePinned(false);
|
|
211
213
|
setShapeHovered(false);
|
|
212
214
|
};
|
|
213
215
|
|
|
214
216
|
const handleModeToggle = () => {
|
|
215
|
-
|
|
217
|
+
eventBus.emit('view:mode-toggled', undefined);
|
|
216
218
|
setModePinned(false);
|
|
217
219
|
setModeHovered(false);
|
|
218
220
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
|
|
3
|
-
import { vi } from 'vitest';
|
|
3
|
+
import { vi, beforeEach, describe, it, expect } from 'vitest';
|
|
4
4
|
import { NextIntlClientProvider } from 'next-intl';
|
|
5
5
|
import { AnnotateToolbar, type SelectionMotivation, type ClickAction } from '../AnnotateToolbar';
|
|
6
6
|
import { ANNOTATORS } from '../../../lib/annotation-registry';
|
|
7
|
+
import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../../contexts/EventBusContext';
|
|
7
8
|
|
|
8
9
|
// Mock translations
|
|
9
10
|
const messages = {
|
|
@@ -29,11 +30,75 @@ const messages = {
|
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
// Composition-based event tracker
|
|
34
|
+
interface TrackedEvent {
|
|
35
|
+
event: string;
|
|
36
|
+
payload: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createEventTracker() {
|
|
40
|
+
const events: TrackedEvent[] = [];
|
|
41
|
+
|
|
42
|
+
function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
|
|
43
|
+
const eventBus = useEventBus();
|
|
44
|
+
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
const handlers: Array<() => void> = [];
|
|
47
|
+
|
|
48
|
+
// Track toolbar-related events
|
|
49
|
+
const trackEvent = (eventName: string) => (payload: any) => {
|
|
50
|
+
events.push({ event: eventName, payload });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const toolbarEvents = [
|
|
54
|
+
'view:mode-toggled',
|
|
55
|
+
'toolbar:click-changed',
|
|
56
|
+
'toolbar:selection-changed',
|
|
57
|
+
'toolbar:shape-changed',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
toolbarEvents.forEach(eventName => {
|
|
61
|
+
const handler = trackEvent(eventName);
|
|
62
|
+
eventBus.on(eventName, handler);
|
|
63
|
+
handlers.push(() => eventBus.off(eventName, handler));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
handlers.forEach(cleanup => cleanup());
|
|
68
|
+
};
|
|
69
|
+
}, [eventBus]);
|
|
70
|
+
|
|
71
|
+
return <>{children}</>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
EventTrackingWrapper,
|
|
76
|
+
events,
|
|
77
|
+
clear: () => {
|
|
78
|
+
events.length = 0;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const renderWithIntl = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
|
|
84
|
+
if (tracker) {
|
|
85
|
+
return render(
|
|
86
|
+
<EventBusProvider>
|
|
87
|
+
<NextIntlClientProvider locale="en" messages={messages}>
|
|
88
|
+
<tracker.EventTrackingWrapper>
|
|
89
|
+
{component}
|
|
90
|
+
</tracker.EventTrackingWrapper>
|
|
91
|
+
</NextIntlClientProvider>
|
|
92
|
+
</EventBusProvider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
33
96
|
return render(
|
|
34
|
-
<
|
|
35
|
-
{
|
|
36
|
-
|
|
97
|
+
<EventBusProvider>
|
|
98
|
+
<NextIntlClientProvider locale="en" messages={messages}>
|
|
99
|
+
{component}
|
|
100
|
+
</NextIntlClientProvider>
|
|
101
|
+
</EventBusProvider>
|
|
37
102
|
);
|
|
38
103
|
};
|
|
39
104
|
|
|
@@ -44,11 +109,11 @@ describe('AnnotateToolbar', () => {
|
|
|
44
109
|
onSelectionChange: vi.fn(),
|
|
45
110
|
onClickChange: vi.fn(),
|
|
46
111
|
annotateMode: false,
|
|
47
|
-
onAnnotateModeToggle: vi.fn(),
|
|
48
112
|
annotators: ANNOTATORS
|
|
49
113
|
};
|
|
50
114
|
|
|
51
115
|
beforeEach(() => {
|
|
116
|
+
resetEventBusForTesting();
|
|
52
117
|
vi.clearAllMocks();
|
|
53
118
|
});
|
|
54
119
|
|
|
@@ -113,19 +178,19 @@ describe('AnnotateToolbar', () => {
|
|
|
113
178
|
<AnnotateToolbar
|
|
114
179
|
{...defaultProps}
|
|
115
180
|
annotateMode={false}
|
|
116
|
-
onAnnotateModeToggle={vi.fn()}
|
|
117
181
|
/>
|
|
118
182
|
);
|
|
119
183
|
expect(screen.getByText('Browse')).toBeInTheDocument();
|
|
120
184
|
|
|
121
185
|
rerender(
|
|
122
|
-
<
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
186
|
+
<EventBusProvider>
|
|
187
|
+
<NextIntlClientProvider locale="en" messages={messages}>
|
|
188
|
+
<AnnotateToolbar
|
|
189
|
+
{...defaultProps}
|
|
190
|
+
annotateMode={true}
|
|
191
|
+
/>
|
|
192
|
+
</NextIntlClientProvider>
|
|
193
|
+
</EventBusProvider>
|
|
129
194
|
);
|
|
130
195
|
expect(screen.getByText('Annotate')).toBeInTheDocument();
|
|
131
196
|
});
|
|
@@ -135,7 +200,6 @@ describe('AnnotateToolbar', () => {
|
|
|
135
200
|
<AnnotateToolbar
|
|
136
201
|
{...defaultProps}
|
|
137
202
|
annotateMode={false}
|
|
138
|
-
onAnnotateModeToggle={vi.fn()}
|
|
139
203
|
/>
|
|
140
204
|
);
|
|
141
205
|
|
|
@@ -151,14 +215,14 @@ describe('AnnotateToolbar', () => {
|
|
|
151
215
|
});
|
|
152
216
|
});
|
|
153
217
|
|
|
154
|
-
it('
|
|
155
|
-
const
|
|
218
|
+
it('emits view:mode-toggled event when Browse is clicked in Annotate mode', async () => {
|
|
219
|
+
const tracker = createEventTracker();
|
|
156
220
|
renderWithIntl(
|
|
157
221
|
<AnnotateToolbar
|
|
158
222
|
{...defaultProps}
|
|
159
223
|
annotateMode={true}
|
|
160
|
-
|
|
161
|
-
|
|
224
|
+
/>,
|
|
225
|
+
tracker
|
|
162
226
|
);
|
|
163
227
|
|
|
164
228
|
const modeGroup = screen.getByLabelText('Mode');
|
|
@@ -169,17 +233,19 @@ describe('AnnotateToolbar', () => {
|
|
|
169
233
|
fireEvent.click(browseButton);
|
|
170
234
|
});
|
|
171
235
|
|
|
172
|
-
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(tracker.events.some(e => e.event === 'view:mode-toggled')).toBe(true);
|
|
238
|
+
});
|
|
173
239
|
});
|
|
174
240
|
|
|
175
|
-
it('
|
|
176
|
-
const
|
|
241
|
+
it('emits view:mode-toggled event when Annotate is clicked in Browse mode', async () => {
|
|
242
|
+
const tracker = createEventTracker();
|
|
177
243
|
renderWithIntl(
|
|
178
244
|
<AnnotateToolbar
|
|
179
245
|
{...defaultProps}
|
|
180
246
|
annotateMode={false}
|
|
181
|
-
|
|
182
|
-
|
|
247
|
+
/>,
|
|
248
|
+
tracker
|
|
183
249
|
);
|
|
184
250
|
|
|
185
251
|
const modeGroup = screen.getByLabelText('Mode');
|
|
@@ -190,17 +256,19 @@ describe('AnnotateToolbar', () => {
|
|
|
190
256
|
fireEvent.click(annotateButton);
|
|
191
257
|
});
|
|
192
258
|
|
|
193
|
-
|
|
259
|
+
await waitFor(() => {
|
|
260
|
+
expect(tracker.events.some(e => e.event === 'view:mode-toggled')).toBe(true);
|
|
261
|
+
});
|
|
194
262
|
});
|
|
195
263
|
|
|
196
264
|
it('closes dropdown after selection', async () => {
|
|
197
|
-
const
|
|
265
|
+
const tracker = createEventTracker();
|
|
198
266
|
const { rerender } = renderWithIntl(
|
|
199
267
|
<AnnotateToolbar
|
|
200
268
|
{...defaultProps}
|
|
201
269
|
annotateMode={false}
|
|
202
|
-
|
|
203
|
-
|
|
270
|
+
/>,
|
|
271
|
+
tracker
|
|
204
272
|
);
|
|
205
273
|
|
|
206
274
|
const modeGroup = screen.getByLabelText('Mode');
|
|
@@ -213,18 +281,23 @@ describe('AnnotateToolbar', () => {
|
|
|
213
281
|
const annotateButton = screen.getByText('Annotate');
|
|
214
282
|
fireEvent.click(annotateButton);
|
|
215
283
|
|
|
216
|
-
// Verify the
|
|
217
|
-
|
|
284
|
+
// Verify the event was emitted
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(tracker.events.some(e => e.event === 'view:mode-toggled')).toBe(true);
|
|
287
|
+
});
|
|
218
288
|
|
|
219
289
|
// Simulate mode change by rerendering with new mode
|
|
220
290
|
rerender(
|
|
221
|
-
<
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
291
|
+
<EventBusProvider>
|
|
292
|
+
<NextIntlClientProvider locale="en" messages={messages}>
|
|
293
|
+
<tracker.EventTrackingWrapper>
|
|
294
|
+
<AnnotateToolbar
|
|
295
|
+
{...defaultProps}
|
|
296
|
+
annotateMode={true}
|
|
297
|
+
/>
|
|
298
|
+
</tracker.EventTrackingWrapper>
|
|
299
|
+
</NextIntlClientProvider>
|
|
300
|
+
</EventBusProvider>
|
|
228
301
|
);
|
|
229
302
|
|
|
230
303
|
// After mode change, the collapsed content should show "Annotate"
|
|
@@ -238,10 +311,11 @@ describe('AnnotateToolbar', () => {
|
|
|
238
311
|
});
|
|
239
312
|
|
|
240
313
|
describe('CLICK Group Interactions', () => {
|
|
241
|
-
it('
|
|
242
|
-
const
|
|
314
|
+
it('emits toolbar:click-changed event when clicking an action', async () => {
|
|
315
|
+
const tracker = createEventTracker();
|
|
243
316
|
renderWithIntl(
|
|
244
|
-
<AnnotateToolbar {...defaultProps}
|
|
317
|
+
<AnnotateToolbar {...defaultProps} />,
|
|
318
|
+
tracker
|
|
245
319
|
);
|
|
246
320
|
|
|
247
321
|
const clickGroup = screen.getByLabelText('Click');
|
|
@@ -251,8 +325,13 @@ describe('AnnotateToolbar', () => {
|
|
|
251
325
|
expect(screen.getByText('Follow')).toBeInTheDocument();
|
|
252
326
|
});
|
|
253
327
|
|
|
328
|
+
tracker.clear();
|
|
254
329
|
fireEvent.click(screen.getByText('Follow'));
|
|
255
|
-
|
|
330
|
+
await waitFor(() => {
|
|
331
|
+
expect(tracker.events.some(e =>
|
|
332
|
+
e.event === 'toolbar:click-changed' && e.payload?.action === 'follow'
|
|
333
|
+
)).toBe(true);
|
|
334
|
+
});
|
|
256
335
|
});
|
|
257
336
|
|
|
258
337
|
it('displays selected action', () => {
|
|
@@ -264,10 +343,11 @@ describe('AnnotateToolbar', () => {
|
|
|
264
343
|
});
|
|
265
344
|
|
|
266
345
|
describe('MOTIVATION Group Interactions', () => {
|
|
267
|
-
it('
|
|
268
|
-
const
|
|
346
|
+
it('emits toolbar:selection-changed event when clicking a motivation', async () => {
|
|
347
|
+
const tracker = createEventTracker();
|
|
269
348
|
renderWithIntl(
|
|
270
|
-
<AnnotateToolbar {...defaultProps}
|
|
349
|
+
<AnnotateToolbar {...defaultProps} />,
|
|
350
|
+
tracker
|
|
271
351
|
);
|
|
272
352
|
|
|
273
353
|
const motivationGroup = screen.getByLabelText('Motivation');
|
|
@@ -277,18 +357,23 @@ describe('AnnotateToolbar', () => {
|
|
|
277
357
|
expect(screen.getByText('Reference')).toBeInTheDocument();
|
|
278
358
|
});
|
|
279
359
|
|
|
360
|
+
tracker.clear();
|
|
280
361
|
fireEvent.click(screen.getByText('Reference'));
|
|
281
|
-
|
|
362
|
+
await waitFor(() => {
|
|
363
|
+
expect(tracker.events.some(e =>
|
|
364
|
+
e.event === 'toolbar:selection-changed' && e.payload?.motivation === 'linking'
|
|
365
|
+
)).toBe(true);
|
|
366
|
+
});
|
|
282
367
|
});
|
|
283
368
|
|
|
284
369
|
it('toggles motivation on/off', async () => {
|
|
285
|
-
const
|
|
370
|
+
const tracker = createEventTracker();
|
|
286
371
|
const { rerender } = renderWithIntl(
|
|
287
372
|
<AnnotateToolbar
|
|
288
373
|
{...defaultProps}
|
|
289
374
|
selectedMotivation={null}
|
|
290
|
-
|
|
291
|
-
|
|
375
|
+
/>,
|
|
376
|
+
tracker
|
|
292
377
|
);
|
|
293
378
|
|
|
294
379
|
const motivationGroup = screen.getByLabelText('Motivation');
|
|
@@ -300,18 +385,26 @@ describe('AnnotateToolbar', () => {
|
|
|
300
385
|
});
|
|
301
386
|
|
|
302
387
|
const dropdown = screen.getByRole('menu');
|
|
388
|
+
tracker.clear();
|
|
303
389
|
fireEvent.click(within(dropdown).getByText('Highlight'));
|
|
304
|
-
|
|
390
|
+
await waitFor(() => {
|
|
391
|
+
expect(tracker.events.some(e =>
|
|
392
|
+
e.event === 'toolbar:selection-changed' && e.payload?.motivation === 'highlighting'
|
|
393
|
+
)).toBe(true);
|
|
394
|
+
});
|
|
305
395
|
|
|
306
396
|
// Simulate selection
|
|
307
397
|
rerender(
|
|
308
|
-
<
|
|
309
|
-
<
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
398
|
+
<EventBusProvider>
|
|
399
|
+
<NextIntlClientProvider locale="en" messages={messages}>
|
|
400
|
+
<tracker.EventTrackingWrapper>
|
|
401
|
+
<AnnotateToolbar
|
|
402
|
+
{...defaultProps}
|
|
403
|
+
selectedMotivation="highlighting"
|
|
404
|
+
/>
|
|
405
|
+
</tracker.EventTrackingWrapper>
|
|
406
|
+
</NextIntlClientProvider>
|
|
407
|
+
</EventBusProvider>
|
|
315
408
|
);
|
|
316
409
|
|
|
317
410
|
// Click again to deselect
|
|
@@ -321,21 +414,26 @@ describe('AnnotateToolbar', () => {
|
|
|
321
414
|
expect(within(dropdown).getByText('Highlight')).toBeInTheDocument();
|
|
322
415
|
});
|
|
323
416
|
const dropdown2 = screen.getByRole('menu');
|
|
417
|
+
tracker.clear();
|
|
324
418
|
fireEvent.click(within(dropdown2).getByText('Highlight'));
|
|
325
|
-
|
|
419
|
+
await waitFor(() => {
|
|
420
|
+
expect(tracker.events.some(e =>
|
|
421
|
+
e.event === 'toolbar:selection-changed' && e.payload?.motivation === null
|
|
422
|
+
)).toBe(true);
|
|
423
|
+
});
|
|
326
424
|
});
|
|
327
425
|
});
|
|
328
426
|
|
|
329
427
|
describe('SHAPE Group Interactions', () => {
|
|
330
|
-
it('
|
|
331
|
-
const
|
|
428
|
+
it('emits toolbar:shape-changed event when clicking a shape', async () => {
|
|
429
|
+
const tracker = createEventTracker();
|
|
332
430
|
renderWithIntl(
|
|
333
431
|
<AnnotateToolbar
|
|
334
432
|
{...defaultProps}
|
|
335
433
|
showShapeGroup={true}
|
|
336
434
|
selectedShape="rectangle"
|
|
337
|
-
|
|
338
|
-
|
|
435
|
+
/>,
|
|
436
|
+
tracker
|
|
339
437
|
);
|
|
340
438
|
|
|
341
439
|
const shapeGroup = screen.getByLabelText('Shape');
|
|
@@ -345,8 +443,13 @@ describe('AnnotateToolbar', () => {
|
|
|
345
443
|
expect(screen.getByText('Circle')).toBeInTheDocument();
|
|
346
444
|
});
|
|
347
445
|
|
|
446
|
+
tracker.clear();
|
|
348
447
|
fireEvent.click(screen.getByText('Circle'));
|
|
349
|
-
|
|
448
|
+
await waitFor(() => {
|
|
449
|
+
expect(tracker.events.some(e =>
|
|
450
|
+
e.event === 'toolbar:shape-changed' && e.payload?.shape === 'circle'
|
|
451
|
+
)).toBe(true);
|
|
452
|
+
});
|
|
350
453
|
});
|
|
351
454
|
});
|
|
352
455
|
|
|
@@ -356,7 +459,6 @@ describe('AnnotateToolbar', () => {
|
|
|
356
459
|
<AnnotateToolbar
|
|
357
460
|
{...defaultProps}
|
|
358
461
|
annotateMode={false}
|
|
359
|
-
onAnnotateModeToggle={vi.fn()}
|
|
360
462
|
/>
|
|
361
463
|
);
|
|
362
464
|
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
z-index: 10;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* Pulse effect for hover synchronization */
|
|
32
|
+
.semiont-annotation-pulse {
|
|
33
|
+
background: var(--semiont-bg-hover) !important;
|
|
34
|
+
transition: background 0.3s ease-in-out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[data-theme="dark"] .semiont-annotation-pulse {
|
|
38
|
+
background: var(--semiont-bg-hover) !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
/* Motivation-specific styles are handled in /styles/motivations/*.css */
|
|
32
42
|
|
|
33
43
|
/* Entry structure */
|
|
@@ -22,17 +22,23 @@ export function JsonLdView({ annotation, onBack }: JsonLdViewProps) {
|
|
|
22
22
|
const viewRef = useRef<EditorView | null>(null);
|
|
23
23
|
const { showLineNumbers } = useLineNumbers();
|
|
24
24
|
|
|
25
|
+
// Store callback in ref to avoid including in dependency arrays
|
|
26
|
+
const onBackRef = useRef(onBack);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
onBackRef.current = onBack;
|
|
29
|
+
});
|
|
30
|
+
|
|
25
31
|
// Handle escape key
|
|
26
32
|
useEffect(() => {
|
|
27
33
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
28
34
|
if (e.key === 'Escape') {
|
|
29
|
-
|
|
35
|
+
onBackRef.current();
|
|
30
36
|
}
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
window.addEventListener('keydown', handleKeyDown);
|
|
34
40
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
35
|
-
}, [
|
|
41
|
+
}, []);
|
|
36
42
|
|
|
37
43
|
// Initialize CodeMirror
|
|
38
44
|
useEffect(() => {
|