@semiont/react-ui 0.5.9 → 0.5.11
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/README.md +1 -1
- package/dist/{PdfAnnotationCanvas.client-U4EVBZEV.js → PdfAnnotationCanvas.client-KWQ7XDWT.js} +5 -2
- package/dist/PdfAnnotationCanvas.client-KWQ7XDWT.js.map +1 -0
- package/dist/{ar-WA47UUWA.js → ar-ZHKO4XDR.js} +45 -4
- package/dist/ar-ZHKO4XDR.js.map +1 -0
- package/dist/{bn-5ANDRIU6.js → bn-3BUDKAK5.js} +45 -4
- package/dist/bn-3BUDKAK5.js.map +1 -0
- package/dist/{chunk-YBGN4ACY.js → chunk-O23TLLXW.js} +1 -6
- package/dist/{chunk-WHUGODTB.js → chunk-PFUXQPWJ.js} +127 -127
- package/dist/{chunk-WHUGODTB.js.map → chunk-PFUXQPWJ.js.map} +1 -1
- package/dist/{chunk-GERSK2DY.js → chunk-R52YAGHK.js} +42 -1
- package/dist/chunk-R52YAGHK.js.map +1 -0
- package/dist/{cs-3RU7F4JX.js → cs-2EFEUKTW.js} +45 -4
- package/dist/cs-2EFEUKTW.js.map +1 -0
- package/dist/{da-GSW5P5HG.js → da-SUD3Q44Q.js} +45 -4
- package/dist/da-SUD3Q44Q.js.map +1 -0
- package/dist/{de-JUAUYF4Z.js → de-6IC5QRX7.js} +45 -4
- package/dist/de-6IC5QRX7.js.map +1 -0
- package/dist/{el-JNLWCKEC.js → el-Q4NAXFRC.js} +45 -4
- package/dist/el-Q4NAXFRC.js.map +1 -0
- package/dist/{en-RBN2GUHF.js → en-P2L4X27N.js} +5 -3
- package/dist/{es-WPOX225H.js → es-YUKZBD4W.js} +45 -4
- package/dist/es-YUKZBD4W.js.map +1 -0
- package/dist/{fa-TAEP6N77.js → fa-OJ47LILE.js} +45 -4
- package/dist/fa-OJ47LILE.js.map +1 -0
- package/dist/{fi-ZVZHANSP.js → fi-QFFL5CUB.js} +45 -4
- package/dist/fi-QFFL5CUB.js.map +1 -0
- package/dist/{fr-VLZW7M4N.js → fr-BOQZZWP4.js} +45 -4
- package/dist/fr-BOQZZWP4.js.map +1 -0
- package/dist/{he-QFAFYA77.js → he-LXDY3TFA.js} +45 -4
- package/dist/he-LXDY3TFA.js.map +1 -0
- package/dist/{hi-AO3DQPCV.js → hi-HOJER77V.js} +45 -4
- package/dist/hi-HOJER77V.js.map +1 -0
- package/dist/{id-GTXF42WM.js → id-I5WQ6EWD.js} +45 -4
- package/dist/id-I5WQ6EWD.js.map +1 -0
- package/dist/index.css +150 -112
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +403 -45
- package/dist/index.js +4660 -18860
- package/dist/index.js.map +1 -1
- package/dist/integrations/css-modules-helper.js +1 -1
- package/dist/integrations/styled-components-theme.js +1 -1
- package/dist/{it-AS5GM232.js → it-UYH642IJ.js} +45 -4
- package/dist/it-UYH642IJ.js.map +1 -0
- package/dist/{ja-GZ4HLUOF.js → ja-JTVWAC4L.js} +45 -4
- package/dist/ja-JTVWAC4L.js.map +1 -0
- package/dist/{ko-A4XUXFGJ.js → ko-IHXFMOIZ.js} +45 -4
- package/dist/ko-IHXFMOIZ.js.map +1 -0
- package/dist/{ms-YBAO3S6K.js → ms-JH25VFLO.js} +45 -4
- package/dist/ms-JH25VFLO.js.map +1 -0
- package/dist/{nl-3TJGIIIU.js → nl-SURXW7IQ.js} +45 -4
- package/dist/nl-SURXW7IQ.js.map +1 -0
- package/dist/{no-4AXIQPRQ.js → no-HXIKQFDZ.js} +45 -4
- package/dist/no-HXIKQFDZ.js.map +1 -0
- package/dist/{pl-5GP6GKXO.js → pl-7A4KU6XK.js} +45 -4
- package/dist/pl-7A4KU6XK.js.map +1 -0
- package/dist/{pt-26Y6JFG5.js → pt-6MGYP7Q5.js} +45 -4
- package/dist/pt-6MGYP7Q5.js.map +1 -0
- package/dist/{ro-C7UXFRWJ.js → ro-IROCHNIA.js} +45 -4
- package/dist/ro-IROCHNIA.js.map +1 -0
- package/dist/{sv-44DVD76T.js → sv-WDJD75CV.js} +45 -4
- package/dist/sv-WDJD75CV.js.map +1 -0
- package/dist/test-utils.js +9 -4
- package/dist/test-utils.js.map +1 -1
- package/dist/{th-GIQRTBOY.js → th-RIO2E6B3.js} +45 -4
- package/dist/th-RIO2E6B3.js.map +1 -0
- package/dist/{tr-WMQWO4D6.js → tr-TZ7ERCIN.js} +45 -4
- package/dist/tr-TZ7ERCIN.js.map +1 -0
- package/dist/{uk-I7ML6RGG.js → uk-ZFWDKLUE.js} +45 -4
- package/dist/uk-ZFWDKLUE.js.map +1 -0
- package/dist/{vi-FGWECASQ.js → vi-UIOSP2JG.js} +45 -4
- package/dist/vi-UIOSP2JG.js.map +1 -0
- package/dist/{zh-L5FN73XC.js → zh-6R6YUY46.js} +45 -4
- package/dist/zh-6R6YUY46.js.map +1 -0
- package/package.json +18 -9
- package/src/components/AnnotateReferencesProgressWidget.tsx +35 -16
- package/src/components/ResourceTagsInline.tsx +94 -11
- package/src/components/StatusDisplay.css +36 -0
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +41 -91
- package/src/components/__tests__/ResourceTagsInline.test.tsx +101 -3
- package/src/components/annotation/AnnotateToolbar.tsx +53 -28
- package/src/components/annotation/__tests__/AnnotateToolbar.display.test.tsx +76 -0
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +124 -360
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +5 -1
- package/src/components/modals/ConfigureGatherStep.tsx +102 -0
- package/src/components/modals/ContextSummary.tsx +15 -6
- package/src/components/modals/GatherContextStep.tsx +104 -59
- package/src/components/modals/ResourceGenerateModal.tsx +258 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +82 -0
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +107 -0
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +197 -0
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +5 -0
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +6 -6
- package/src/components/resource/AnnotateView.tsx +59 -38
- package/src/components/resource/BrowseView.tsx +159 -133
- package/src/components/resource/ResourceViewer.tsx +119 -128
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +55 -0
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +49 -0
- package/src/components/resource/__tests__/BrowseView.links.test.tsx +60 -0
- package/src/components/resource/__tests__/BrowseView.overlay-async.test.tsx +113 -0
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +161 -0
- package/src/components/resource/__tests__/BrowseView.registry.test.tsx +43 -0
- package/src/components/resource/__tests__/BrowseView.test.tsx +35 -22
- package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +63 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +12 -7
- package/src/components/resource/__tests__/ResourceViewer.toolbar-opt-out.test.tsx +135 -0
- package/src/components/resource/__tests__/ResourceViewer.toolbar-prefs.test.tsx +200 -0
- package/src/components/resource/__tests__/browse-renderers.test.tsx +34 -0
- package/src/components/resource/__tests__/inline-embedding.test.tsx +81 -0
- package/src/components/resource/__tests__/media-completeness.test.tsx +88 -0
- package/src/components/resource/browse-renderers.tsx +63 -0
- package/src/components/resource/panels/AssessmentPanel.tsx +4 -0
- package/src/components/resource/panels/CommentsPanel.tsx +4 -0
- package/src/components/resource/panels/HighlightPanel.tsx +4 -0
- package/src/components/resource/panels/ReferencesPanel.tsx +14 -0
- package/src/components/resource/panels/ResourceInfoPanel.tsx +22 -0
- package/src/components/resource/panels/TaggingPanel.tsx +4 -0
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +5 -2
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -0
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -0
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +16 -16
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +1 -0
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +26 -0
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -0
- package/src/components/toolbar/Toolbar.css +14 -0
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +14 -0
- package/src/features/admin-exchange/state/exchange-state-unit.ts +4 -2
- package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +14 -0
- package/src/features/admin-security/state/admin-security-state-unit.ts +4 -2
- package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +12 -5
- package/src/features/admin-users/state/admin-users-state-unit.ts +4 -2
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +11 -0
- package/src/features/auth-welcome/state/welcome-state-unit.ts +1 -1
- package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +14 -0
- package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +4 -2
- package/src/features/resource-compose/components/ResourceComposePage.tsx +13 -7
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +13 -0
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -2
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +15 -9
- package/src/features/resource-discovery/state/discover-state-unit.ts +5 -2
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +7 -17
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +119 -17
- package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +11 -0
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +30 -0
- package/src/features/resource-viewer/state/resource-loader-state-unit.ts +1 -1
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +7 -16
- package/src/styles/features/resource-viewer.css +19 -0
- package/translations/ar.json +41 -2
- package/translations/bn.json +41 -2
- package/translations/cs.json +41 -2
- package/translations/da.json +41 -2
- package/translations/de.json +41 -2
- package/translations/el.json +41 -2
- package/translations/en.json +39 -0
- package/translations/es.json +41 -2
- package/translations/fa.json +41 -2
- package/translations/fi.json +41 -2
- package/translations/fr.json +41 -2
- package/translations/he.json +41 -2
- package/translations/hi.json +41 -2
- package/translations/id.json +41 -2
- package/translations/it.json +41 -2
- package/translations/ja.json +41 -2
- package/translations/ko.json +41 -2
- package/translations/ms.json +41 -2
- package/translations/nl.json +41 -2
- package/translations/no.json +41 -2
- package/translations/pl.json +41 -2
- package/translations/pt.json +41 -2
- package/translations/ro.json +41 -2
- package/translations/sv.json +41 -2
- package/translations/th.json +41 -2
- package/translations/tr.json +41 -2
- package/translations/uk.json +41 -2
- package/translations/vi.json +41 -2
- package/translations/zh.json +41 -2
- package/dist/PdfAnnotationCanvas.client-U4EVBZEV.js.map +0 -1
- package/dist/ar-WA47UUWA.js.map +0 -1
- package/dist/bn-5ANDRIU6.js.map +0 -1
- package/dist/chunk-GERSK2DY.js.map +0 -1
- package/dist/cs-3RU7F4JX.js.map +0 -1
- package/dist/da-GSW5P5HG.js.map +0 -1
- package/dist/de-JUAUYF4Z.js.map +0 -1
- package/dist/el-JNLWCKEC.js.map +0 -1
- package/dist/es-WPOX225H.js.map +0 -1
- package/dist/fa-TAEP6N77.js.map +0 -1
- package/dist/fi-ZVZHANSP.js.map +0 -1
- package/dist/fr-VLZW7M4N.js.map +0 -1
- package/dist/he-QFAFYA77.js.map +0 -1
- package/dist/hi-AO3DQPCV.js.map +0 -1
- package/dist/id-GTXF42WM.js.map +0 -1
- package/dist/it-AS5GM232.js.map +0 -1
- package/dist/ja-GZ4HLUOF.js.map +0 -1
- package/dist/ko-A4XUXFGJ.js.map +0 -1
- package/dist/ms-YBAO3S6K.js.map +0 -1
- package/dist/nl-3TJGIIIU.js.map +0 -1
- package/dist/no-4AXIQPRQ.js.map +0 -1
- package/dist/pl-5GP6GKXO.js.map +0 -1
- package/dist/pt-26Y6JFG5.js.map +0 -1
- package/dist/ro-C7UXFRWJ.js.map +0 -1
- package/dist/sv-44DVD76T.js.map +0 -1
- package/dist/th-GIQRTBOY.js.map +0 -1
- package/dist/tr-WMQWO4D6.js.map +0 -1
- package/dist/uk-I7ML6RGG.js.map +0 -1
- package/dist/vi-FGWECASQ.js.map +0 -1
- package/dist/zh-L5FN73XC.js.map +0 -1
- /package/dist/{chunk-YBGN4ACY.js.map → chunk-O23TLLXW.js.map} +0 -0
- /package/dist/{en-RBN2GUHF.js.map → en-P2L4X27N.js.map} +0 -0
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnnotateToolbar — purely presentational (TOOLBAR-PREFS-AS-PROPS).
|
|
3
|
+
*
|
|
4
|
+
* The bar renders the given values for the given `parts` and reports choices via
|
|
5
|
+
* the on*Change callbacks. No session, no bus events, no storage — those died with
|
|
6
|
+
* the preference channels; owners (viewer instances / hosts) apply the values.
|
|
7
|
+
*/
|
|
1
8
|
import React from 'react';
|
|
2
9
|
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
|
|
3
10
|
import { vi, beforeEach, describe, it, expect } from 'vitest';
|
|
11
|
+
import '@testing-library/jest-dom';
|
|
4
12
|
import { AnnotateToolbar, type SelectionMotivation, type ClickAction } from '../AnnotateToolbar';
|
|
5
13
|
import { ANNOTATORS } from '../../../lib/annotation-registry';
|
|
6
14
|
import { TranslationProvider } from '../../../contexts/TranslationContext';
|
|
7
|
-
import { createTestSemiontWrapper } from '../../../test-utils';
|
|
8
15
|
import type { TranslationManager } from '../../../types/TranslationManager';
|
|
9
|
-
import type { EventBus } from '@semiont/core';
|
|
10
16
|
|
|
11
|
-
// Mock translations
|
|
12
17
|
const messages: Record<string, Record<string, string>> = {
|
|
13
18
|
AnnotateToolbar: {
|
|
14
19
|
modeGroup: 'Mode',
|
|
@@ -17,6 +22,7 @@ const messages: Record<string, Record<string, string>> = {
|
|
|
17
22
|
clickGroup: 'Click',
|
|
18
23
|
selectionGroup: 'Motivation',
|
|
19
24
|
shapeGroup: 'Shape',
|
|
25
|
+
none: 'None',
|
|
20
26
|
linking: 'Reference',
|
|
21
27
|
highlighting: 'Highlight',
|
|
22
28
|
assessing: 'Assess',
|
|
@@ -28,63 +34,37 @@ const messages: Record<string, Record<string, string>> = {
|
|
|
28
34
|
jsonld: 'JSON-LD',
|
|
29
35
|
rectangle: 'Rectangle',
|
|
30
36
|
circle: 'Circle',
|
|
31
|
-
polygon: 'Polygon'
|
|
32
|
-
}
|
|
37
|
+
polygon: 'Polygon',
|
|
38
|
+
},
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
const translationManager: TranslationManager = {
|
|
36
42
|
t: (namespace, key) => messages[namespace]?.[key] ?? `${namespace}.${key}`,
|
|
37
43
|
};
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const renderWithIntl = (component: React.ReactElement) =>
|
|
46
|
+
render(
|
|
47
|
+
<TranslationProvider translationManager={translationManager}>
|
|
48
|
+
{component}
|
|
49
|
+
</TranslationProvider>,
|
|
50
|
+
);
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
'mark:click-changed',
|
|
54
|
-
'mark:selection-changed',
|
|
55
|
-
'mark:shape-changed',
|
|
56
|
-
] as const;
|
|
57
|
-
trackedEvents.forEach((eventName) => {
|
|
58
|
-
eventBus.get(eventName).subscribe((payload: any) => {
|
|
59
|
-
events.push({ event: eventName, payload });
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
};
|
|
52
|
+
/** Hover a dropdown group open and click an option in its menu. */
|
|
53
|
+
async function pick(group: string, option: string) {
|
|
54
|
+
const trigger = screen.getByLabelText(group);
|
|
55
|
+
fireEvent.mouseEnter(trigger.parentElement!);
|
|
56
|
+
await waitFor(() => {
|
|
57
|
+
const menu = screen.getByRole('menu');
|
|
58
|
+
fireEvent.click(within(menu).getByText(option));
|
|
59
|
+
});
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
const renderWithIntl = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
|
|
67
|
-
const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
|
|
68
|
-
if (tracker) tracker._attach(eventBus);
|
|
69
|
-
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
70
|
-
<SemiontWrapper>
|
|
71
|
-
<TranslationProvider translationManager={translationManager}>
|
|
72
|
-
{children}
|
|
73
|
-
</TranslationProvider>
|
|
74
|
-
</SemiontWrapper>
|
|
75
|
-
);
|
|
76
|
-
const result = render(component, { wrapper: Wrapper });
|
|
77
|
-
return { ...result, eventBus };
|
|
78
|
-
};
|
|
79
|
-
|
|
80
62
|
describe('AnnotateToolbar', () => {
|
|
81
63
|
const defaultProps = {
|
|
82
64
|
selectedMotivation: null as SelectionMotivation | null,
|
|
83
65
|
selectedClick: 'detail' as ClickAction,
|
|
84
|
-
onSelectionChange: vi.fn(),
|
|
85
|
-
onClickChange: vi.fn(),
|
|
86
66
|
annotateMode: false,
|
|
87
|
-
annotators: ANNOTATORS
|
|
67
|
+
annotators: ANNOTATORS,
|
|
88
68
|
};
|
|
89
69
|
|
|
90
70
|
beforeEach(() => {
|
|
@@ -92,382 +72,166 @@ describe('AnnotateToolbar', () => {
|
|
|
92
72
|
});
|
|
93
73
|
|
|
94
74
|
describe('Rendering', () => {
|
|
95
|
-
it('renders with
|
|
75
|
+
it('renders the click group with the selected action', () => {
|
|
96
76
|
renderWithIntl(<AnnotateToolbar {...defaultProps} />);
|
|
97
|
-
// Check for aria-labels (labels only shown when expanded)
|
|
98
77
|
expect(screen.getByLabelText('Click')).toBeInTheDocument();
|
|
99
78
|
expect(screen.getByText('Detail')).toBeInTheDocument();
|
|
100
79
|
});
|
|
101
80
|
|
|
102
|
-
it('shows MODE group', () => {
|
|
81
|
+
it('shows the MODE group with the current mode', () => {
|
|
103
82
|
renderWithIntl(<AnnotateToolbar {...defaultProps} />);
|
|
104
|
-
// Check for aria-label (label only shown when expanded)
|
|
105
83
|
expect(screen.getByLabelText('Mode')).toBeInTheDocument();
|
|
106
84
|
expect(screen.getByText('Browse')).toBeInTheDocument();
|
|
107
85
|
});
|
|
108
86
|
|
|
109
|
-
it('shows selection group by default', () => {
|
|
87
|
+
it('shows the selection group by default (parts default: all)', () => {
|
|
110
88
|
renderWithIntl(<AnnotateToolbar {...defaultProps} />);
|
|
111
|
-
// Check for aria-label (label only shown when expanded)
|
|
112
89
|
expect(screen.getByLabelText('Motivation')).toBeInTheDocument();
|
|
113
90
|
});
|
|
114
91
|
|
|
115
|
-
it('hides
|
|
116
|
-
renderWithIntl(
|
|
117
|
-
<AnnotateToolbar {...defaultProps} showSelectionGroup={false} />
|
|
118
|
-
);
|
|
119
|
-
// Check for aria-label absence
|
|
120
|
-
expect(screen.queryByLabelText('Motivation')).not.toBeInTheDocument();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('hides delete button when showDeleteButton is false', () => {
|
|
124
|
-
renderWithIntl(
|
|
125
|
-
<AnnotateToolbar {...defaultProps} showDeleteButton={false} />
|
|
126
|
-
);
|
|
127
|
-
// Open click dropdown
|
|
92
|
+
it('hides the delete option when showDeleteButton is false', () => {
|
|
93
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} showDeleteButton={false} />);
|
|
128
94
|
const clickGroup = screen.getByLabelText('Click');
|
|
129
|
-
fireEvent.mouseEnter(clickGroup);
|
|
130
|
-
|
|
131
|
-
// Delete option should not be present
|
|
95
|
+
fireEvent.mouseEnter(clickGroup.parentElement!);
|
|
132
96
|
expect(screen.queryByText('Delete')).not.toBeInTheDocument();
|
|
133
97
|
});
|
|
134
98
|
|
|
135
|
-
it('
|
|
136
|
-
renderWithIntl(
|
|
137
|
-
<AnnotateToolbar
|
|
138
|
-
{...defaultProps}
|
|
139
|
-
showShapeGroup={true}
|
|
140
|
-
selectedShape="rectangle"
|
|
141
|
-
/>
|
|
142
|
-
);
|
|
143
|
-
// Check for aria-label (label only shown when expanded)
|
|
144
|
-
expect(screen.getByLabelText('Shape')).toBeInTheDocument();
|
|
145
|
-
expect(screen.getByText('Rectangle')).toBeInTheDocument();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('MODE Group Interactions', () => {
|
|
150
|
-
it('displays current mode correctly', () => {
|
|
151
|
-
const { rerender } = renderWithIntl(
|
|
152
|
-
<AnnotateToolbar
|
|
153
|
-
{...defaultProps}
|
|
154
|
-
annotateMode={false}
|
|
155
|
-
/>
|
|
156
|
-
);
|
|
99
|
+
it('displays the given mode across rerenders (value comes from the owner)', () => {
|
|
100
|
+
const { rerender } = renderWithIntl(<AnnotateToolbar {...defaultProps} annotateMode={false} />);
|
|
157
101
|
expect(screen.getByText('Browse')).toBeInTheDocument();
|
|
158
|
-
|
|
159
102
|
rerender(
|
|
160
|
-
<
|
|
161
|
-
{...defaultProps}
|
|
162
|
-
|
|
163
|
-
/>
|
|
103
|
+
<TranslationProvider translationManager={translationManager}>
|
|
104
|
+
<AnnotateToolbar {...defaultProps} annotateMode={true} />
|
|
105
|
+
</TranslationProvider>,
|
|
164
106
|
);
|
|
165
107
|
expect(screen.getByText('Annotate')).toBeInTheDocument();
|
|
166
108
|
});
|
|
109
|
+
});
|
|
167
110
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
annotateMode={false}
|
|
173
|
-
/>
|
|
111
|
+
describe('parts composition', () => {
|
|
112
|
+
it("parts={['shape']} renders exactly the shape group", () => {
|
|
113
|
+
const { container } = renderWithIntl(
|
|
114
|
+
<AnnotateToolbar {...defaultProps} parts={['shape']} selectedShape="rectangle" mediaType="image/png" />,
|
|
174
115
|
);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const dropdown = screen.getByRole('menu');
|
|
182
|
-
// Both options should be visible in the expanded dropdown menu
|
|
183
|
-
expect(within(dropdown).getByText('Browse')).toBeInTheDocument();
|
|
184
|
-
expect(within(dropdown).getByText('Annotate')).toBeInTheDocument();
|
|
185
|
-
});
|
|
116
|
+
expect(screen.getByLabelText('Shape')).toBeInTheDocument();
|
|
117
|
+
expect(screen.queryByLabelText('Click')).not.toBeInTheDocument();
|
|
118
|
+
expect(screen.queryByLabelText('Mode')).not.toBeInTheDocument();
|
|
119
|
+
expect(screen.queryByLabelText('Motivation')).not.toBeInTheDocument();
|
|
120
|
+
// A lone part renders no separators
|
|
121
|
+
expect(container.querySelector('.semiont-toolbar-separator')).toBeNull();
|
|
186
122
|
});
|
|
187
123
|
|
|
188
|
-
it('
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
<AnnotateToolbar
|
|
192
|
-
{...defaultProps}
|
|
193
|
-
annotateMode={true}
|
|
194
|
-
/>,
|
|
195
|
-
tracker
|
|
124
|
+
it("parts={['clickAction','mode']} renders those two with one separator", () => {
|
|
125
|
+
const { container } = renderWithIntl(
|
|
126
|
+
<AnnotateToolbar {...defaultProps} parts={['clickAction', 'mode']} />,
|
|
196
127
|
);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
await waitFor(() => {
|
|
202
|
-
const browseButton = screen.getByText('Browse');
|
|
203
|
-
fireEvent.click(browseButton);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await waitFor(() => {
|
|
207
|
-
expect(tracker.events.some(e => e.event === 'mark:mode-toggled')).toBe(true);
|
|
208
|
-
});
|
|
128
|
+
expect(screen.getByLabelText('Click')).toBeInTheDocument();
|
|
129
|
+
expect(screen.getByLabelText('Mode')).toBeInTheDocument();
|
|
130
|
+
expect(screen.queryByLabelText('Motivation')).not.toBeInTheDocument();
|
|
131
|
+
expect(container.querySelectorAll('.semiont-toolbar-separator')).toHaveLength(1);
|
|
209
132
|
});
|
|
210
133
|
|
|
211
|
-
it('
|
|
212
|
-
const tracker = createEventTracker();
|
|
134
|
+
it('the default renders all four groups (shape media-gated: none for PDF beyond rectangle)', () => {
|
|
213
135
|
renderWithIntl(
|
|
214
|
-
<AnnotateToolbar
|
|
215
|
-
{...defaultProps}
|
|
216
|
-
annotateMode={false}
|
|
217
|
-
/>,
|
|
218
|
-
tracker
|
|
136
|
+
<AnnotateToolbar {...defaultProps} selectedShape="rectangle" mediaType="image/png" />,
|
|
219
137
|
);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
await waitFor(() => {
|
|
225
|
-
const annotateButton = screen.getByText('Annotate');
|
|
226
|
-
fireEvent.click(annotateButton);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
await waitFor(() => {
|
|
230
|
-
expect(tracker.events.some(e => e.event === 'mark:mode-toggled')).toBe(true);
|
|
231
|
-
});
|
|
138
|
+
expect(screen.getByLabelText('Click')).toBeInTheDocument();
|
|
139
|
+
expect(screen.getByLabelText('Mode')).toBeInTheDocument();
|
|
140
|
+
expect(screen.getByLabelText('Motivation')).toBeInTheDocument();
|
|
141
|
+
expect(screen.getByLabelText('Shape')).toBeInTheDocument();
|
|
232
142
|
});
|
|
143
|
+
});
|
|
233
144
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
tracker
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
const modeGroup = screen.getByLabelText('Mode');
|
|
245
|
-
fireEvent.mouseEnter(modeGroup);
|
|
246
|
-
|
|
247
|
-
await waitFor(() => {
|
|
248
|
-
expect(screen.getByText('Annotate')).toBeInTheDocument();
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const annotateButton = screen.getByText('Annotate');
|
|
252
|
-
fireEvent.click(annotateButton);
|
|
253
|
-
|
|
254
|
-
// Verify the event was emitted
|
|
255
|
-
await waitFor(() => {
|
|
256
|
-
expect(tracker.events.some(e => e.event === 'mark:mode-toggled')).toBe(true);
|
|
257
|
-
});
|
|
145
|
+
describe('Mode control', () => {
|
|
146
|
+
it('reports the next mode via onModeChange when switching to Annotate', async () => {
|
|
147
|
+
const onModeChange = vi.fn();
|
|
148
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} annotateMode={false} onModeChange={onModeChange} />);
|
|
149
|
+
await pick('Mode', 'Annotate');
|
|
150
|
+
expect(onModeChange).toHaveBeenCalledWith(true);
|
|
151
|
+
});
|
|
258
152
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
153
|
+
it('reports false when switching back to Browse from annotate mode', async () => {
|
|
154
|
+
const onModeChange = vi.fn();
|
|
155
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} annotateMode={true} onModeChange={onModeChange} />);
|
|
156
|
+
await pick('Mode', 'Browse');
|
|
157
|
+
expect(onModeChange).toHaveBeenCalledWith(false);
|
|
158
|
+
});
|
|
266
159
|
|
|
267
|
-
|
|
268
|
-
|
|
160
|
+
it('clicking the CURRENT mode reports nothing', async () => {
|
|
161
|
+
const onModeChange = vi.fn();
|
|
162
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} annotateMode={false} onModeChange={onModeChange} />);
|
|
163
|
+
const trigger = screen.getByLabelText('Mode');
|
|
164
|
+
fireEvent.mouseEnter(trigger.parentElement!);
|
|
269
165
|
await waitFor(() => {
|
|
270
|
-
const
|
|
271
|
-
//
|
|
272
|
-
expect(modeLabels.length).toBeGreaterThanOrEqual(1);
|
|
166
|
+
const menu = screen.getByRole('menu');
|
|
167
|
+
fireEvent.click(within(menu).getByText('Browse')); // already in browse
|
|
273
168
|
});
|
|
169
|
+
expect(onModeChange).not.toHaveBeenCalled();
|
|
274
170
|
});
|
|
275
171
|
});
|
|
276
172
|
|
|
277
|
-
describe('
|
|
278
|
-
it('
|
|
279
|
-
const
|
|
280
|
-
renderWithIntl(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const clickGroup = screen.getByLabelText('Click');
|
|
286
|
-
fireEvent.mouseEnter(clickGroup);
|
|
287
|
-
|
|
288
|
-
await waitFor(() => {
|
|
289
|
-
expect(screen.getByText('Follow')).toBeInTheDocument();
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
tracker.clear();
|
|
293
|
-
fireEvent.click(screen.getByText('Follow'));
|
|
294
|
-
await waitFor(() => {
|
|
295
|
-
expect(tracker.events.some(e =>
|
|
296
|
-
e.event === 'mark:click-changed' && e.payload?.action === 'follow'
|
|
297
|
-
)).toBe(true);
|
|
298
|
-
});
|
|
173
|
+
describe('Click-action control', () => {
|
|
174
|
+
it('reports the chosen action via onClickActionChange', async () => {
|
|
175
|
+
const onClickActionChange = vi.fn();
|
|
176
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} onClickActionChange={onClickActionChange} />);
|
|
177
|
+
await pick('Click', 'Follow');
|
|
178
|
+
expect(onClickActionChange).toHaveBeenCalledWith('follow');
|
|
299
179
|
});
|
|
300
180
|
|
|
301
|
-
it('displays selected action', () => {
|
|
302
|
-
renderWithIntl(
|
|
303
|
-
|
|
304
|
-
);
|
|
305
|
-
expect(screen.getByText('Follow')).toBeInTheDocument();
|
|
181
|
+
it('displays the selected action from props', () => {
|
|
182
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} selectedClick="jsonld" />);
|
|
183
|
+
expect(screen.getByText('JSON-LD')).toBeInTheDocument();
|
|
306
184
|
});
|
|
307
185
|
});
|
|
308
186
|
|
|
309
|
-
describe('
|
|
310
|
-
it('
|
|
311
|
-
const
|
|
312
|
-
renderWithIntl(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
const motivationGroup = screen.getByLabelText('Motivation');
|
|
318
|
-
fireEvent.mouseEnter(motivationGroup);
|
|
319
|
-
|
|
320
|
-
await waitFor(() => {
|
|
321
|
-
expect(screen.getByText('Reference')).toBeInTheDocument();
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
tracker.clear();
|
|
325
|
-
fireEvent.click(screen.getByText('Reference'));
|
|
326
|
-
await waitFor(() => {
|
|
327
|
-
expect(tracker.events.some(e =>
|
|
328
|
-
e.event === 'mark:selection-changed' && e.payload?.motivation === 'linking'
|
|
329
|
-
)).toBe(true);
|
|
330
|
-
});
|
|
187
|
+
describe('Selection control', () => {
|
|
188
|
+
it('reports the chosen motivation', async () => {
|
|
189
|
+
const onSelectionChange = vi.fn();
|
|
190
|
+
renderWithIntl(<AnnotateToolbar {...defaultProps} onSelectionChange={onSelectionChange} />);
|
|
191
|
+
await pick('Motivation', 'Highlight');
|
|
192
|
+
expect(onSelectionChange).toHaveBeenCalledWith('highlighting');
|
|
331
193
|
});
|
|
332
194
|
|
|
333
|
-
it('
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
<AnnotateToolbar
|
|
337
|
-
{...defaultProps}
|
|
338
|
-
selectedMotivation={null}
|
|
339
|
-
/>,
|
|
340
|
-
tracker
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
const motivationGroup = screen.getByLabelText('Motivation');
|
|
344
|
-
fireEvent.mouseEnter(motivationGroup);
|
|
345
|
-
|
|
346
|
-
await waitFor(() => {
|
|
347
|
-
const dropdown = screen.getByRole('menu');
|
|
348
|
-
expect(within(dropdown).getByText('Highlight')).toBeInTheDocument();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
const dropdown = screen.getByRole('menu');
|
|
352
|
-
tracker.clear();
|
|
353
|
-
fireEvent.click(within(dropdown).getByText('Highlight'));
|
|
354
|
-
await waitFor(() => {
|
|
355
|
-
expect(tracker.events.some(e =>
|
|
356
|
-
e.event === 'mark:selection-changed' && e.payload?.motivation === 'highlighting'
|
|
357
|
-
)).toBe(true);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// Simulate selection
|
|
361
|
-
rerender(
|
|
362
|
-
<AnnotateToolbar
|
|
363
|
-
{...defaultProps}
|
|
364
|
-
selectedMotivation="highlighting"
|
|
365
|
-
/>
|
|
195
|
+
it('re-picking the current motivation reports null (toggle off)', async () => {
|
|
196
|
+
const onSelectionChange = vi.fn();
|
|
197
|
+
renderWithIntl(
|
|
198
|
+
<AnnotateToolbar {...defaultProps} selectedMotivation="highlighting" onSelectionChange={onSelectionChange} />,
|
|
366
199
|
);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
fireEvent.mouseEnter(motivationGroup);
|
|
370
|
-
await waitFor(() => {
|
|
371
|
-
const dropdown = screen.getByRole('menu');
|
|
372
|
-
expect(within(dropdown).getByText('Highlight')).toBeInTheDocument();
|
|
373
|
-
});
|
|
374
|
-
const dropdown2 = screen.getByRole('menu');
|
|
375
|
-
tracker.clear();
|
|
376
|
-
fireEvent.click(within(dropdown2).getByText('Highlight'));
|
|
377
|
-
await waitFor(() => {
|
|
378
|
-
expect(tracker.events.some(e =>
|
|
379
|
-
e.event === 'mark:selection-changed' && e.payload?.motivation === null
|
|
380
|
-
)).toBe(true);
|
|
381
|
-
});
|
|
200
|
+
await pick('Motivation', 'Highlight');
|
|
201
|
+
expect(onSelectionChange).toHaveBeenCalledWith(null);
|
|
382
202
|
});
|
|
383
|
-
});
|
|
384
203
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const tracker = createEventTracker();
|
|
204
|
+
it('picking None reports null', async () => {
|
|
205
|
+
const onSelectionChange = vi.fn();
|
|
388
206
|
renderWithIntl(
|
|
389
|
-
<AnnotateToolbar
|
|
390
|
-
{...defaultProps}
|
|
391
|
-
showShapeGroup={true}
|
|
392
|
-
selectedShape="rectangle"
|
|
393
|
-
/>,
|
|
394
|
-
tracker
|
|
207
|
+
<AnnotateToolbar {...defaultProps} selectedMotivation="linking" onSelectionChange={onSelectionChange} />,
|
|
395
208
|
);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
fireEvent.mouseEnter(shapeGroup);
|
|
399
|
-
|
|
400
|
-
await waitFor(() => {
|
|
401
|
-
expect(screen.getByText('Circle')).toBeInTheDocument();
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
tracker.clear();
|
|
405
|
-
fireEvent.click(screen.getByText('Circle'));
|
|
406
|
-
await waitFor(() => {
|
|
407
|
-
expect(tracker.events.some(e =>
|
|
408
|
-
e.event === 'mark:shape-changed' && e.payload?.shape === 'circle'
|
|
409
|
-
)).toBe(true);
|
|
410
|
-
});
|
|
209
|
+
await pick('Motivation', 'None');
|
|
210
|
+
expect(onSelectionChange).toHaveBeenCalledWith(null);
|
|
411
211
|
});
|
|
412
212
|
});
|
|
413
213
|
|
|
414
|
-
describe('
|
|
415
|
-
it('
|
|
214
|
+
describe('Shape control', () => {
|
|
215
|
+
it('reports the chosen shape', async () => {
|
|
216
|
+
const onShapeChange = vi.fn();
|
|
416
217
|
renderWithIntl(
|
|
417
|
-
<AnnotateToolbar
|
|
418
|
-
{
|
|
419
|
-
annotateMode={false}
|
|
420
|
-
/>
|
|
218
|
+
<AnnotateToolbar {...defaultProps} parts={['shape']} selectedShape="rectangle"
|
|
219
|
+
mediaType="image/png" onShapeChange={onShapeChange} />,
|
|
421
220
|
);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const modeGroup = screen.getByLabelText('Mode');
|
|
425
|
-
fireEvent.mouseEnter(modeGroup);
|
|
426
|
-
fireEvent.click(modeGroup); // Pin it
|
|
427
|
-
|
|
428
|
-
await waitFor(() => {
|
|
429
|
-
// When expanded, dropdown menu should be visible with both options
|
|
430
|
-
const dropdown = screen.getByRole('menu');
|
|
431
|
-
expect(within(dropdown).getByText('Browse')).toBeInTheDocument();
|
|
432
|
-
expect(within(dropdown).getByText('Annotate')).toBeInTheDocument();
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// Press Escape
|
|
436
|
-
fireEvent.keyDown(document, { key: 'Escape' });
|
|
437
|
-
|
|
438
|
-
// Move mouse away to complete closing
|
|
439
|
-
fireEvent.mouseLeave(modeGroup);
|
|
440
|
-
|
|
441
|
-
await waitFor(() => {
|
|
442
|
-
// After closing, dropdown menu should not be present
|
|
443
|
-
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
|
444
|
-
// But collapsed "Browse" label should still be visible
|
|
445
|
-
expect(screen.getByText('Browse')).toBeInTheDocument();
|
|
446
|
-
});
|
|
221
|
+
await pick('Shape', 'Circle');
|
|
222
|
+
expect(onShapeChange).toHaveBeenCalledWith('circle');
|
|
447
223
|
});
|
|
448
224
|
});
|
|
449
225
|
|
|
450
|
-
describe('
|
|
451
|
-
it('closes
|
|
226
|
+
describe('Dropdown behavior', () => {
|
|
227
|
+
it('closes all dropdowns on Escape', async () => {
|
|
452
228
|
renderWithIntl(<AnnotateToolbar {...defaultProps} />);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
fireEvent.
|
|
457
|
-
|
|
458
|
-
await waitFor(() =>
|
|
459
|
-
expect(screen.getByText('Follow')).toBeInTheDocument();
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Click outside - need to click on an element outside the dropdown
|
|
463
|
-
fireEvent.mouseDown(document.body);
|
|
464
|
-
|
|
465
|
-
// Also move mouse away to ensure hover state is cleared
|
|
466
|
-
fireEvent.mouseLeave(clickGroup);
|
|
467
|
-
|
|
468
|
-
await waitFor(() => {
|
|
469
|
-
expect(screen.queryByText('Follow')).not.toBeInTheDocument();
|
|
470
|
-
});
|
|
229
|
+
const trigger = screen.getByLabelText('Mode');
|
|
230
|
+
fireEvent.click(trigger); // pin open
|
|
231
|
+
await waitFor(() => expect(screen.getByRole('menu')).toBeInTheDocument());
|
|
232
|
+
fireEvent.keyDown(document, { key: 'Escape' });
|
|
233
|
+
fireEvent.mouseLeave(trigger.parentElement!);
|
|
234
|
+
await waitFor(() => expect(screen.queryByRole('menu')).not.toBeInTheDocument());
|
|
471
235
|
});
|
|
472
236
|
});
|
|
473
237
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
|
4
4
|
import type { Annotation } from '@semiont/core';
|
|
5
|
-
import { createRectangleSvg, createCircleSvg, createPolygonSvg, scaleSvgToNative, parseSvgSelector, Point } from '@semiont/core';
|
|
5
|
+
import { createRectangleSvg, createCircleSvg, createPolygonSvg, scaleSvgToNative, parseSvgSelector, Point, resourceId as toResourceId } from '@semiont/core';
|
|
6
6
|
import { AnnotationOverlay } from './AnnotationOverlay';
|
|
7
7
|
import type { SelectionMotivation } from '../annotation/AnnotateToolbar';
|
|
8
8
|
import type { SemiontSession } from '@semiont/sdk';
|
|
@@ -35,6 +35,8 @@ function getMotivationColor(motivation: SelectionMotivation | null): { stroke: s
|
|
|
35
35
|
|
|
36
36
|
interface SvgDrawingCanvasProps {
|
|
37
37
|
imageUrl: string;
|
|
38
|
+
/** The '@id' of the annotated resource — stamped as `source` on mark:requested (multi-viewer routing). */
|
|
39
|
+
resourceUri: string;
|
|
38
40
|
existingAnnotations?: Annotation[];
|
|
39
41
|
drawingMode: DrawingMode;
|
|
40
42
|
selectedMotivation?: SelectionMotivation | null;
|
|
@@ -52,6 +54,7 @@ interface SvgDrawingCanvasProps {
|
|
|
52
54
|
*/
|
|
53
55
|
export function SvgDrawingCanvas({
|
|
54
56
|
imageUrl,
|
|
57
|
+
resourceUri,
|
|
55
58
|
existingAnnotations = [],
|
|
56
59
|
drawingMode,
|
|
57
60
|
selectedMotivation,
|
|
@@ -274,6 +277,7 @@ export function SvgDrawingCanvas({
|
|
|
274
277
|
// Emit annotation:requested event with SvgSelector
|
|
275
278
|
if (session && selectedMotivation) {
|
|
276
279
|
session.client.mark.request(
|
|
280
|
+
toResourceId(resourceUri),
|
|
277
281
|
{ type: 'SvgSelector', value: nativeSvg },
|
|
278
282
|
selectedMotivation,
|
|
279
283
|
);
|