@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
|
@@ -3,22 +3,22 @@ import { render, screen } from '@testing-library/react';
|
|
|
3
3
|
import { SessionTimer } from '../SessionTimer';
|
|
4
4
|
|
|
5
5
|
// Mock the hooks
|
|
6
|
-
vi.mock('
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
6
|
+
vi.mock('../../hooks/useSessionExpiry', () => ({
|
|
7
|
+
useSessionExpiry: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../../lib/formatTime', () => ({
|
|
11
|
+
formatTime: vi.fn(),
|
|
12
|
+
}));
|
|
14
13
|
|
|
15
|
-
import { useSessionExpiry
|
|
14
|
+
import { useSessionExpiry } from '../../hooks/useSessionExpiry';
|
|
15
|
+
import { formatTime } from '../../lib/formatTime';
|
|
16
16
|
|
|
17
17
|
describe('SessionTimer', () => {
|
|
18
18
|
describe('Rendering', () => {
|
|
19
19
|
it('should render formatted time when available', () => {
|
|
20
20
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 300000 } as any);
|
|
21
|
-
vi.mocked(
|
|
21
|
+
vi.mocked(formatTime).mockReturnValue('5:00');
|
|
22
22
|
|
|
23
23
|
render(<SessionTimer />);
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ describe('SessionTimer', () => {
|
|
|
28
28
|
|
|
29
29
|
it('should have correct class name', () => {
|
|
30
30
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 300000 } as any);
|
|
31
|
-
vi.mocked(
|
|
31
|
+
vi.mocked(formatTime).mockReturnValue('5:00');
|
|
32
32
|
|
|
33
33
|
const { container } = render(<SessionTimer />);
|
|
34
34
|
|
|
@@ -37,7 +37,7 @@ describe('SessionTimer', () => {
|
|
|
37
37
|
|
|
38
38
|
it('should display complete message with formatted time', () => {
|
|
39
39
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 120000 } as any);
|
|
40
|
-
vi.mocked(
|
|
40
|
+
vi.mocked(formatTime).mockReturnValue('2:00');
|
|
41
41
|
|
|
42
42
|
render(<SessionTimer />);
|
|
43
43
|
|
|
@@ -49,7 +49,7 @@ describe('SessionTimer', () => {
|
|
|
49
49
|
describe('Null Cases', () => {
|
|
50
50
|
it('should return null when formattedTime is null', () => {
|
|
51
51
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
|
|
52
|
-
vi.mocked(
|
|
52
|
+
vi.mocked(formatTime).mockReturnValue(null);
|
|
53
53
|
|
|
54
54
|
const { container } = render(<SessionTimer />);
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@ describe('SessionTimer', () => {
|
|
|
58
58
|
|
|
59
59
|
it('should return null when formattedTime is undefined', () => {
|
|
60
60
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
|
|
61
|
-
vi.mocked(
|
|
61
|
+
vi.mocked(formatTime).mockReturnValue(undefined as any);
|
|
62
62
|
|
|
63
63
|
const { container } = render(<SessionTimer />);
|
|
64
64
|
|
|
@@ -67,7 +67,7 @@ describe('SessionTimer', () => {
|
|
|
67
67
|
|
|
68
68
|
it('should return null when formattedTime is empty string', () => {
|
|
69
69
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
|
|
70
|
-
vi.mocked(
|
|
70
|
+
vi.mocked(formatTime).mockReturnValue('');
|
|
71
71
|
|
|
72
72
|
const { container } = render(<SessionTimer />);
|
|
73
73
|
|
|
@@ -79,31 +79,31 @@ describe('SessionTimer', () => {
|
|
|
79
79
|
it('should call useSessionExpiry hook', () => {
|
|
80
80
|
const mockUseSessionExpiry = vi.mocked(useSessionExpiry);
|
|
81
81
|
mockUseSessionExpiry.mockReturnValue({ timeRemaining: 100000 } as any);
|
|
82
|
-
vi.mocked(
|
|
82
|
+
vi.mocked(formatTime).mockReturnValue('1:40');
|
|
83
83
|
|
|
84
84
|
render(<SessionTimer />);
|
|
85
85
|
|
|
86
86
|
expect(mockUseSessionExpiry).toHaveBeenCalled();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
it('should call
|
|
90
|
-
const
|
|
89
|
+
it('should call formatTime with timeRemaining', () => {
|
|
90
|
+
const mockFormatTime = vi.mocked(formatTime);
|
|
91
91
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 300000 } as any);
|
|
92
|
-
|
|
92
|
+
mockFormatTime.mockReturnValue('5:00');
|
|
93
93
|
|
|
94
94
|
render(<SessionTimer />);
|
|
95
95
|
|
|
96
|
-
expect(
|
|
96
|
+
expect(mockFormatTime).toHaveBeenCalledWith(300000);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
it('should pass correct timeRemaining to
|
|
100
|
-
const
|
|
99
|
+
it('should pass correct timeRemaining to formatTime', () => {
|
|
100
|
+
const mockFormatTime = vi.mocked(formatTime);
|
|
101
101
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 12345 } as any);
|
|
102
|
-
|
|
102
|
+
mockFormatTime.mockReturnValue('0:12');
|
|
103
103
|
|
|
104
104
|
render(<SessionTimer />);
|
|
105
105
|
|
|
106
|
-
expect(
|
|
106
|
+
expect(mockFormatTime).toHaveBeenCalledWith(12345);
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -118,7 +118,7 @@ describe('SessionTimer', () => {
|
|
|
118
118
|
|
|
119
119
|
testCases.forEach(({ timeRemaining, formatted }) => {
|
|
120
120
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining } as any);
|
|
121
|
-
vi.mocked(
|
|
121
|
+
vi.mocked(formatTime).mockReturnValue(formatted);
|
|
122
122
|
|
|
123
123
|
const { unmount } = render(<SessionTimer />);
|
|
124
124
|
|
|
@@ -132,17 +132,17 @@ describe('SessionTimer', () => {
|
|
|
132
132
|
describe('Re-rendering', () => {
|
|
133
133
|
it('should update when timeRemaining changes', () => {
|
|
134
134
|
const mockUseSessionExpiry = vi.mocked(useSessionExpiry);
|
|
135
|
-
const
|
|
135
|
+
const mockFormatTime = vi.mocked(formatTime);
|
|
136
136
|
|
|
137
137
|
mockUseSessionExpiry.mockReturnValue({ timeRemaining: 60000 } as any);
|
|
138
|
-
|
|
138
|
+
mockFormatTime.mockReturnValue('1:00');
|
|
139
139
|
|
|
140
140
|
const { rerender } = render(<SessionTimer />);
|
|
141
141
|
expect(screen.getByText('Session: 1:00 remaining')).toBeInTheDocument();
|
|
142
142
|
|
|
143
143
|
// Simulate time passing
|
|
144
144
|
mockUseSessionExpiry.mockReturnValue({ timeRemaining: 30000 } as any);
|
|
145
|
-
|
|
145
|
+
mockFormatTime.mockReturnValue('0:30');
|
|
146
146
|
|
|
147
147
|
rerender(<SessionTimer />);
|
|
148
148
|
expect(screen.getByText('Session: 0:30 remaining')).toBeInTheDocument();
|
|
@@ -150,13 +150,13 @@ describe('SessionTimer', () => {
|
|
|
150
150
|
|
|
151
151
|
it('should hide when formattedTime becomes null', () => {
|
|
152
152
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 60000 } as any);
|
|
153
|
-
vi.mocked(
|
|
153
|
+
vi.mocked(formatTime).mockReturnValue('1:00');
|
|
154
154
|
|
|
155
155
|
const { container, rerender } = render(<SessionTimer />);
|
|
156
156
|
expect(screen.getByText('Session: 1:00 remaining')).toBeInTheDocument();
|
|
157
157
|
|
|
158
158
|
// Time expires
|
|
159
|
-
vi.mocked(
|
|
159
|
+
vi.mocked(formatTime).mockReturnValue(null);
|
|
160
160
|
|
|
161
161
|
rerender(<SessionTimer />);
|
|
162
162
|
expect(container.firstChild).toBeNull();
|
|
@@ -166,7 +166,7 @@ describe('SessionTimer', () => {
|
|
|
166
166
|
describe('Edge Cases', () => {
|
|
167
167
|
it('should handle zero timeRemaining', () => {
|
|
168
168
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 0 } as any);
|
|
169
|
-
vi.mocked(
|
|
169
|
+
vi.mocked(formatTime).mockReturnValue('0:00');
|
|
170
170
|
|
|
171
171
|
render(<SessionTimer />);
|
|
172
172
|
|
|
@@ -175,7 +175,7 @@ describe('SessionTimer', () => {
|
|
|
175
175
|
|
|
176
176
|
it('should handle negative timeRemaining', () => {
|
|
177
177
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: -1000 } as any);
|
|
178
|
-
vi.mocked(
|
|
178
|
+
vi.mocked(formatTime).mockReturnValue('expired');
|
|
179
179
|
|
|
180
180
|
render(<SessionTimer />);
|
|
181
181
|
|
|
@@ -184,7 +184,7 @@ describe('SessionTimer', () => {
|
|
|
184
184
|
|
|
185
185
|
it('should handle very large timeRemaining', () => {
|
|
186
186
|
vi.mocked(useSessionExpiry).mockReturnValue({ timeRemaining: 999999999 } as any);
|
|
187
|
-
vi.mocked(
|
|
187
|
+
vi.mocked(formatTime).mockReturnValue('16666:39');
|
|
188
188
|
|
|
189
189
|
render(<SessionTimer />);
|
|
190
190
|
|
|
@@ -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
|
};
|