@semiont/react-ui 0.5.27 → 0.5.29

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.
Files changed (192) hide show
  1. package/README.md +3 -3
  2. package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +32 -5
  3. package/dist/{ar-OU3DQGC6.js → ar-AZBRADEB.js} +43 -23
  4. package/dist/{bn-PHWCR6AZ.js → bn-CCEW6AOS.js} +43 -23
  5. package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
  6. package/dist/{chunk-5VDTWRXJ.js → chunk-G4VHL4UA.js} +31 -31
  7. package/dist/{chunk-PCRRBU7H.js → chunk-SDKFZLEX.js} +43 -23
  8. package/dist/{cs-NEXYZ62I.js → cs-HXPF2CTE.js} +43 -23
  9. package/dist/{da-FAT43YCU.js → da-K5NTK7LH.js} +43 -23
  10. package/dist/{de-KRQ3WBCU.js → de-GMRQEDZI.js} +43 -23
  11. package/dist/{el-Q4IU3EKC.js → el-EUB4CI4G.js} +43 -23
  12. package/dist/{en-2KADE7N4.js → en-R5XA26NF.js} +2 -2
  13. package/dist/{es-2IO3R2MK.js → es-Z32DAZ6W.js} +43 -23
  14. package/dist/{fa-Q5GOQFXX.js → fa-G7HN6B32.js} +43 -23
  15. package/dist/{fi-R6NMRWBE.js → fi-3YBLZO7I.js} +43 -23
  16. package/dist/{fr-QY4D5E6J.js → fr-VUBH7BBS.js} +43 -23
  17. package/dist/{he-X6Y77XHY.js → he-WQQEY26B.js} +43 -23
  18. package/dist/{hi-XL7IOEQM.js → hi-HJZ6W4JB.js} +43 -23
  19. package/dist/{id-QZ4ZZS67.js → id-ZOGZ37MI.js} +43 -23
  20. package/dist/index.css +279 -74
  21. package/dist/index.d.ts +318 -393
  22. package/dist/index.js +2619 -2687
  23. package/dist/{it-ME2Q7OB6.js → it-DT6UW66F.js} +43 -23
  24. package/dist/{ja-HX7QASRI.js → ja-RUKQCOLO.js} +43 -23
  25. package/dist/{ko-EQO5AHAV.js → ko-ZH5ADKZM.js} +43 -23
  26. package/dist/{ms-UILFSF3I.js → ms-Y2AFPZTU.js} +43 -23
  27. package/dist/{nl-NNI45PMK.js → nl-OQEWIXNA.js} +43 -23
  28. package/dist/{no-MAL5F6P6.js → no-KPAYJZWY.js} +43 -23
  29. package/dist/{pl-4RMY6JAJ.js → pl-WSXK2UUD.js} +43 -23
  30. package/dist/{pt-VQH2X3QQ.js → pt-NWRJRUCK.js} +43 -23
  31. package/dist/{ro-XXCQO2OG.js → ro-3CGVMOC6.js} +43 -23
  32. package/dist/{sv-7Z6EJ6U4.js → sv-HGSEBA7S.js} +43 -23
  33. package/dist/test-utils.js +16 -7
  34. package/dist/{th-L3DXXYVQ.js → th-NZ4G352Z.js} +43 -23
  35. package/dist/{tr-P3DFVLFJ.js → tr-LGGS4DLW.js} +43 -23
  36. package/dist/{uk-7C5D3PXS.js → uk-AIX536L4.js} +43 -23
  37. package/dist/{vi-ZLTLEO7P.js → vi-V6CVQZLQ.js} +43 -23
  38. package/dist/{zh-5FHVWDIN.js → zh-AGKPGI3L.js} +43 -23
  39. package/package.json +16 -15
  40. package/src/components/AssistProgress.tsx +36 -2
  41. package/src/components/CodeMirrorRenderer.tsx +8 -8
  42. package/src/components/StatusDisplay.tsx +13 -13
  43. package/src/components/__tests__/AssistProgress.test.tsx +59 -0
  44. package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
  45. package/src/components/modals/ComposeStep.tsx +233 -0
  46. package/src/components/modals/ConfigureGatherStep.tsx +10 -18
  47. package/src/components/modals/ConfigureGenerationStep.tsx +178 -34
  48. package/src/components/modals/ConfigureSearchStep.tsx +19 -0
  49. package/src/components/modals/ContextSummary.tsx +185 -72
  50. package/src/components/modals/CorpusPane.tsx +75 -0
  51. package/src/components/modals/DiscardPrompt.tsx +38 -0
  52. package/src/components/modals/GatherContextStep.tsx +237 -107
  53. package/src/components/modals/ReferenceWizardModal.tsx +252 -59
  54. package/src/components/modals/ResourceGenerateModal.tsx +225 -112
  55. package/src/components/modals/SearchModal.css +337 -101
  56. package/src/components/modals/SearchResultsStep.tsx +61 -68
  57. package/src/components/modals/WizardFooter.tsx +7 -1
  58. package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
  59. package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
  60. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +206 -8
  61. package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
  62. package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
  63. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +442 -39
  64. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +300 -86
  65. package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
  66. package/src/components/modals/__tests__/WizardFooter.test.tsx +116 -9
  67. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
  68. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
  69. package/src/components/resource/AnnotateView.tsx +3 -3
  70. package/src/components/resource/BrowseView.tsx +8 -8
  71. package/src/components/resource/DownloadFileLink.tsx +1 -1
  72. package/src/components/resource/ResourceViewer.tsx +4 -4
  73. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
  74. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
  75. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
  76. package/src/components/resource/__tests__/BrowseView.test.tsx +11 -11
  77. package/src/components/resource/__tests__/download-link.test.tsx +6 -6
  78. package/src/components/resource/annotate-renderers.tsx +3 -3
  79. package/src/components/resource/panels/AssistSection.css +35 -0
  80. package/src/components/resource/panels/ReferenceEntry.tsx +11 -4
  81. package/src/components/resource/panels/ReferencesPanel.tsx +9 -1
  82. package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
  83. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
  84. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
  85. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
  86. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +84 -3
  87. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +149 -7
  88. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
  89. package/src/features/auth/components/SignInForm.tsx +53 -53
  90. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
  91. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
  92. package/src/features/resource-compose/components/ResourceComposePage.tsx +35 -133
  93. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
  94. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
  95. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
  96. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
  97. package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
  98. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +73 -17
  99. package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
  100. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +148 -83
  101. package/src/features/resource-viewer/generation-options.ts +29 -0
  102. package/src/styles/core/forms.css +35 -8
  103. package/src/styles/features/resource-viewer.css +0 -22
  104. package/src/styles/features/resource.css +0 -4
  105. package/src/styles/index.css +0 -1
  106. package/translations/ar.json +42 -22
  107. package/translations/bn.json +42 -22
  108. package/translations/cs.json +42 -22
  109. package/translations/da.json +42 -22
  110. package/translations/de.json +42 -22
  111. package/translations/el.json +42 -22
  112. package/translations/en.json +42 -22
  113. package/translations/es.json +42 -22
  114. package/translations/fa.json +42 -22
  115. package/translations/fi.json +42 -22
  116. package/translations/fr.json +42 -22
  117. package/translations/he.json +42 -22
  118. package/translations/hi.json +42 -22
  119. package/translations/id.json +42 -22
  120. package/translations/it.json +42 -22
  121. package/translations/ja.json +42 -22
  122. package/translations/ko.json +42 -22
  123. package/translations/ms.json +42 -22
  124. package/translations/nl.json +42 -22
  125. package/translations/no.json +42 -22
  126. package/translations/pl.json +42 -22
  127. package/translations/pt.json +42 -22
  128. package/translations/ro.json +42 -22
  129. package/translations/sv.json +42 -22
  130. package/translations/th.json +42 -22
  131. package/translations/tr.json +42 -22
  132. package/translations/uk.json +42 -22
  133. package/translations/vi.json +42 -22
  134. package/translations/zh.json +42 -22
  135. package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
  136. package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
  137. package/dist/ar-OU3DQGC6.js.map +0 -1
  138. package/dist/bn-PHWCR6AZ.js.map +0 -1
  139. package/dist/chunk-5VDTWRXJ.js.map +0 -1
  140. package/dist/chunk-BTUG3VJJ.js.map +0 -1
  141. package/dist/chunk-O23TLLXW.js.map +0 -1
  142. package/dist/chunk-PCRRBU7H.js.map +0 -1
  143. package/dist/chunk-QT7LYM72.js.map +0 -1
  144. package/dist/chunk-VXASXU4K.js.map +0 -1
  145. package/dist/cs-NEXYZ62I.js.map +0 -1
  146. package/dist/da-FAT43YCU.js.map +0 -1
  147. package/dist/de-KRQ3WBCU.js.map +0 -1
  148. package/dist/el-Q4IU3EKC.js.map +0 -1
  149. package/dist/en-2KADE7N4.js.map +0 -1
  150. package/dist/es-2IO3R2MK.js.map +0 -1
  151. package/dist/fa-Q5GOQFXX.js.map +0 -1
  152. package/dist/fi-R6NMRWBE.js.map +0 -1
  153. package/dist/fr-QY4D5E6J.js.map +0 -1
  154. package/dist/he-X6Y77XHY.js.map +0 -1
  155. package/dist/hi-XL7IOEQM.js.map +0 -1
  156. package/dist/id-QZ4ZZS67.js.map +0 -1
  157. package/dist/index.css.map +0 -1
  158. package/dist/index.js.map +0 -1
  159. package/dist/integrations/css-modules-helper.js.map +0 -1
  160. package/dist/integrations/styled-components-theme.js.map +0 -1
  161. package/dist/it-ME2Q7OB6.js.map +0 -1
  162. package/dist/ja-HX7QASRI.js.map +0 -1
  163. package/dist/ko-EQO5AHAV.js.map +0 -1
  164. package/dist/ms-UILFSF3I.js.map +0 -1
  165. package/dist/nl-NNI45PMK.js.map +0 -1
  166. package/dist/no-MAL5F6P6.js.map +0 -1
  167. package/dist/pl-4RMY6JAJ.js.map +0 -1
  168. package/dist/pt-VQH2X3QQ.js.map +0 -1
  169. package/dist/ro-XXCQO2OG.js.map +0 -1
  170. package/dist/sv-7Z6EJ6U4.js.map +0 -1
  171. package/dist/test-utils.css.map +0 -1
  172. package/dist/test-utils.js.map +0 -1
  173. package/dist/th-L3DXXYVQ.js.map +0 -1
  174. package/dist/tr-P3DFVLFJ.js.map +0 -1
  175. package/dist/uk-7C5D3PXS.js.map +0 -1
  176. package/dist/vi-ZLTLEO7P.js.map +0 -1
  177. package/dist/zh-5FHVWDIN.js.map +0 -1
  178. package/src/components/modals/ResourceSearchModal.tsx +0 -224
  179. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
  180. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
  181. package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
  182. package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
  183. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
  184. package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
  185. package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
  186. package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
  187. package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
  188. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
  189. package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
  190. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
  191. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
  192. package/src/styles/features/exchange.css +0 -374
@@ -1,148 +0,0 @@
1
- /**
2
- * Tests for ImportCard component
3
- */
4
-
5
- import { describe, it, expect, vi } from 'vitest';
6
- import { render, screen, fireEvent } from '@testing-library/react';
7
- import { ImportCard } from '../components/ImportCard';
8
- import type { ImportCardProps } from '../components/ImportCard';
9
-
10
- const createProps = (overrides?: Partial<ImportCardProps>): ImportCardProps => ({
11
- onFileSelected: vi.fn(),
12
- onImport: vi.fn(),
13
- onCancel: vi.fn(),
14
- selectedFile: null,
15
- preview: null,
16
- isImporting: false,
17
- translations: {
18
- title: 'Import',
19
- description: 'Restore from a file',
20
- dropzoneLabel: 'Drop a file here',
21
- dropzoneActive: 'Drop to upload',
22
- detectedFormat: 'Format',
23
- statsPreview: 'Preview',
24
- importButton: 'Import',
25
- importing: 'Importing…',
26
- importConfirmTitle: 'Confirm Import',
27
- importConfirmMessage: 'This cannot be undone.',
28
- confirmImport: 'Proceed',
29
- cancelImport: 'Cancel',
30
- },
31
- ...overrides,
32
- });
33
-
34
- describe('ImportCard', () => {
35
- it('renders title and description', () => {
36
- render(<ImportCard {...createProps()} />);
37
- expect(screen.getByRole('heading', { name: 'Import' })).toBeInTheDocument();
38
- expect(screen.getByText('Restore from a file')).toBeInTheDocument();
39
- });
40
-
41
- it('renders dropzone with label', () => {
42
- render(<ImportCard {...createProps()} />);
43
- expect(screen.getByText('Drop a file here')).toBeInTheDocument();
44
- });
45
-
46
- it('disables import button when no preview', () => {
47
- render(<ImportCard {...createProps()} />);
48
- expect(screen.getByRole('button', { name: 'Import' })).toBeDisabled();
49
- });
50
-
51
- it('enables import button when preview is available', () => {
52
- render(<ImportCard {...createProps({
53
- selectedFile: new File([''], 'test.jsonl'),
54
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
55
- })} />);
56
- expect(screen.getByRole('button', { name: 'Import' })).not.toBeDisabled();
57
- });
58
-
59
- it('shows file info when a file is selected', () => {
60
- const file = new File(['test'], 'backup.tar.gz');
61
- render(<ImportCard {...createProps({ selectedFile: file })} />);
62
- expect(screen.getByText(/backup\.tar\.gz/)).toBeInTheDocument();
63
- });
64
-
65
- it('shows preview details', () => {
66
- render(<ImportCard {...createProps({
67
- selectedFile: new File([''], 'test.jsonl'),
68
- preview: {
69
- format: 'semiont-backup',
70
- version: 1,
71
- sourceUrl: 'http://example.com',
72
- stats: { streams: 5 },
73
- },
74
- })} />);
75
- expect(screen.getByText('semiont-backup v1')).toBeInTheDocument();
76
- expect(screen.getByText('http://example.com')).toBeInTheDocument();
77
- expect(screen.getByText('5 streams')).toBeInTheDocument();
78
- });
79
-
80
- it('shows confirmation dialog on import click', () => {
81
- render(<ImportCard {...createProps({
82
- selectedFile: new File([''], 'test.jsonl'),
83
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
84
- })} />);
85
-
86
- fireEvent.click(screen.getByRole('button', { name: 'Import' }));
87
- expect(screen.getByText('Confirm Import')).toBeInTheDocument();
88
- expect(screen.getByText('This cannot be undone.')).toBeInTheDocument();
89
- });
90
-
91
- it('calls onImport when confirmation is confirmed', () => {
92
- const onImport = vi.fn();
93
- render(<ImportCard {...createProps({
94
- onImport,
95
- selectedFile: new File([''], 'test.jsonl'),
96
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
97
- })} />);
98
-
99
- fireEvent.click(screen.getByRole('button', { name: 'Import' }));
100
- fireEvent.click(screen.getByRole('button', { name: 'Proceed' }));
101
- expect(onImport).toHaveBeenCalled();
102
- });
103
-
104
- it('hides confirmation when cancel is clicked in confirm dialog', () => {
105
- render(<ImportCard {...createProps({
106
- selectedFile: new File([''], 'test.jsonl'),
107
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
108
- })} />);
109
-
110
- fireEvent.click(screen.getByRole('button', { name: 'Import' }));
111
- expect(screen.getByText('Confirm Import')).toBeInTheDocument();
112
-
113
- // In confirm dialog, the Cancel button
114
- const cancelButtons = screen.getAllByRole('button', { name: 'Cancel' });
115
- fireEvent.click(cancelButtons[0]);
116
- expect(screen.queryByText('Confirm Import')).not.toBeInTheDocument();
117
- });
118
-
119
- it('shows cancel button when file is selected', () => {
120
- render(<ImportCard {...createProps({
121
- selectedFile: new File([''], 'test.jsonl'),
122
- })} />);
123
- expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
124
- });
125
-
126
- it('disables import button when importing', () => {
127
- render(<ImportCard {...createProps({
128
- isImporting: true,
129
- selectedFile: new File([''], 'test.jsonl'),
130
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
131
- })} />);
132
- expect(screen.getByRole('button', { name: 'Importing…' })).toBeDisabled();
133
- });
134
-
135
- it('handles file drop', () => {
136
- const onFileSelected = vi.fn();
137
- render(<ImportCard {...createProps({ onFileSelected })} />);
138
-
139
- const dropzone = screen.getByText('Drop a file here').closest('.semiont-exchange__dropzone')!;
140
- const file = new File(['content'], 'test.tar.gz');
141
-
142
- fireEvent.drop(dropzone, {
143
- dataTransfer: { files: [file] },
144
- });
145
-
146
- expect(onFileSelected).toHaveBeenCalledWith(file);
147
- });
148
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * Tests for ImportProgress component
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { render, screen } from '@testing-library/react';
7
- import { ImportProgress } from '../components/ImportProgress';
8
- import type { ImportProgressProps } from '../components/ImportProgress';
9
-
10
- const translations: ImportProgressProps['translations'] = {
11
- phaseStarted: 'Starting restore…',
12
- phaseComplete: 'Restore complete',
13
- phaseError: 'Restore failed',
14
- statsEventsReplayed: 'Events replayed',
15
- statsResourcesCreated: 'Resources created',
16
- statsAnnotationsCreated: 'Annotations created',
17
- statsEntityTypesAdded: 'Entity types added',
18
- };
19
-
20
- describe('ImportProgress', () => {
21
- it('renders phase label for known phases', () => {
22
- render(<ImportProgress phase="started" translations={translations} />);
23
- expect(screen.getByText('Starting restore…')).toBeInTheDocument();
24
- });
25
-
26
- it('renders raw phase string for unknown phases', () => {
27
- render(<ImportProgress phase="custom-phase" translations={translations} />);
28
- expect(screen.getByText('custom-phase')).toBeInTheDocument();
29
- });
30
-
31
- it('renders message during active phases', () => {
32
- render(<ImportProgress phase="started" message="Restoring backup..." translations={translations} />);
33
- expect(screen.getByText('Restoring backup...')).toBeInTheDocument();
34
- });
35
-
36
- it('does not render message during complete phase', () => {
37
- render(<ImportProgress phase="complete" message="some message" translations={translations} />);
38
- expect(screen.queryByText('some message')).not.toBeInTheDocument();
39
- });
40
-
41
- it('renders complete phase with correct class', () => {
42
- const { container } = render(<ImportProgress phase="complete" translations={translations} />);
43
- const label = container.querySelector('.semiont-exchange__phase-label--complete');
44
- expect(label).toBeInTheDocument();
45
- expect(label).toHaveTextContent('Restore complete');
46
- });
47
-
48
- it('renders error phase with correct class', () => {
49
- const { container } = render(<ImportProgress phase="error" translations={translations} />);
50
- const label = container.querySelector('.semiont-exchange__phase-label--error');
51
- expect(label).toBeInTheDocument();
52
- expect(label).toHaveTextContent('Restore failed');
53
- });
54
-
55
- it('renders error message', () => {
56
- render(<ImportProgress phase="error" message="Connection failed" translations={translations} />);
57
- expect(screen.getByText('Connection failed')).toBeInTheDocument();
58
- });
59
-
60
- it('renders backup restore stats nested under result.stats', () => {
61
- render(<ImportProgress
62
- phase="complete"
63
- result={{ stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
64
- translations={translations}
65
- />);
66
- expect(screen.getByText('42')).toBeInTheDocument();
67
- expect(screen.getByText('Events replayed')).toBeInTheDocument();
68
- expect(screen.getByText('5')).toBeInTheDocument();
69
- expect(screen.getByText('Resources created')).toBeInTheDocument();
70
- expect(screen.getByText('12')).toBeInTheDocument();
71
- expect(screen.getByText('Annotations created')).toBeInTheDocument();
72
- expect(screen.getByText('3')).toBeInTheDocument();
73
- expect(screen.getByText('Entity types added')).toBeInTheDocument();
74
- });
75
-
76
- it('renders linked-data import stats nested under result.stats', () => {
77
- render(<ImportProgress
78
- phase="complete"
79
- result={{ stats: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
80
- translations={translations}
81
- />);
82
- expect(screen.getByText('5')).toBeInTheDocument();
83
- expect(screen.getByText('Resources created')).toBeInTheDocument();
84
- expect(screen.getByText('12')).toBeInTheDocument();
85
- expect(screen.getByText('Annotations created')).toBeInTheDocument();
86
- expect(screen.getByText('3')).toBeInTheDocument();
87
- expect(screen.getByText('Entity types added')).toBeInTheDocument();
88
- });
89
-
90
- it('does not render result section when result lacks a stats object', () => {
91
- const { container } = render(<ImportProgress
92
- phase="complete"
93
- result={{ resourcesCreated: 5 }}
94
- translations={translations}
95
- />);
96
- expect(container.querySelector('.semiont-exchange__result')).not.toBeInTheDocument();
97
- });
98
-
99
- it('renders raw key for unknown stats', () => {
100
- render(<ImportProgress
101
- phase="complete"
102
- result={{ stats: { somethingNew: 7 } }}
103
- translations={translations}
104
- />);
105
- expect(screen.getByText('7')).toBeInTheDocument();
106
- expect(screen.getByText('somethingNew')).toBeInTheDocument();
107
- });
108
-
109
- it('does not render result section during non-complete phases', () => {
110
- const { container } = render(<ImportProgress
111
- phase="started"
112
- result={{ stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
113
- translations={translations}
114
- />);
115
- expect(container.querySelector('.semiont-exchange__result')).not.toBeInTheDocument();
116
- });
117
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * AdminExchangePage — Backup/Restore admin page
3
- *
4
- * Pure React component. All state and handlers passed as props.
5
- */
6
-
7
- import React from 'react';
8
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
9
- import { ExportCard, type ExportCardTranslations } from './ExportCard';
10
- import { ImportCard, type ImportCardProps, type ImportCardTranslations } from './ImportCard';
11
- import { ImportProgress, type ImportProgressTranslations } from './ImportProgress';
12
-
13
- export interface AdminExchangePageTranslations {
14
- title: string;
15
- subtitle: string;
16
- export: ExportCardTranslations;
17
- import: ImportCardTranslations;
18
- progress: ImportProgressTranslations;
19
- }
20
-
21
- export interface AdminExchangePageProps {
22
- // Backup
23
- onExport: () => void;
24
- isExporting: boolean;
25
-
26
- // Import
27
- onFileSelected: (file: File) => void;
28
- onImport: () => void;
29
- onCancelImport: () => void;
30
- selectedFile: File | null;
31
- preview: ImportCardProps['preview'];
32
- isImporting: boolean;
33
-
34
- // Progress
35
- importPhase: string | null;
36
- importMessage?: string | undefined;
37
- importResult?: Record<string, unknown> | undefined;
38
-
39
- // UI state
40
- theme: 'light' | 'dark' | 'system';
41
- activePanel: string | null;
42
-
43
- // Translations
44
- translations: AdminExchangePageTranslations;
45
-
46
- // Component dependencies
47
- ToolbarPanels: React.ComponentType<any>;
48
- Toolbar: React.ComponentType<any>;
49
- }
50
-
51
- export function AdminExchangePage({
52
- onExport,
53
- isExporting,
54
- onFileSelected,
55
- onImport,
56
- onCancelImport,
57
- selectedFile,
58
- preview,
59
- isImporting,
60
- importPhase,
61
- importMessage,
62
- importResult,
63
- theme,
64
- activePanel,
65
- translations: t,
66
- ToolbarPanels,
67
- Toolbar,
68
- }: AdminExchangePageProps) {
69
- return (
70
- <div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
71
- <div className="semiont-page__content">
72
- <div className="semiont-page__header">
73
- <h1 className="semiont-page__title">{t.title}</h1>
74
- <p className="semiont-page__subtitle">{t.subtitle}</p>
75
- </div>
76
-
77
- <div className="semiont-exchange__cards">
78
- <ExportCard
79
- onExport={onExport}
80
- isExporting={isExporting}
81
- translations={t.export}
82
- />
83
-
84
- <ImportCard
85
- onFileSelected={onFileSelected}
86
- onImport={onImport}
87
- onCancel={onCancelImport}
88
- selectedFile={selectedFile}
89
- preview={preview}
90
- isImporting={isImporting}
91
- translations={t.import}
92
- />
93
- </div>
94
-
95
- {importPhase && (
96
- <ImportProgress
97
- phase={importPhase}
98
- message={importMessage}
99
- result={importResult}
100
- translations={t.progress}
101
- />
102
- )}
103
- </div>
104
-
105
- <div className="semiont-page__sidebar">
106
- <ToolbarPanels
107
- activePanel={activePanel}
108
- theme={theme}
109
- />
110
- <Toolbar
111
- context="simple"
112
- activePanel={activePanel}
113
- />
114
- </div>
115
- </div>
116
- );
117
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * ExportCard — Backup trigger
3
- *
4
- * Pure React component. All state and handlers passed as props.
5
- */
6
-
7
- export interface ExportCardTranslations {
8
- title: string;
9
- description: string;
10
- exportButton: string;
11
- exporting: string;
12
- }
13
-
14
- export interface ExportCardProps {
15
- onExport: () => void;
16
- isExporting: boolean;
17
- translations: ExportCardTranslations;
18
- }
19
-
20
- export function ExportCard({ onExport, isExporting, translations: t }: ExportCardProps) {
21
- return (
22
- <div className="semiont-admin__card">
23
- <h2 className="semiont-admin__section-title">{t.title}</h2>
24
- <p className="semiont-admin__section-description">{t.description}</p>
25
-
26
- <button
27
- className="semiont-exchange__button semiont-exchange__button--primary"
28
- onClick={onExport}
29
- disabled={isExporting}
30
- >
31
- {isExporting ? t.exporting : t.exportButton}
32
- </button>
33
- </div>
34
- );
35
- }
@@ -1,183 +0,0 @@
1
- /**
2
- * ImportCard — File drop zone, preview, and import trigger
3
- *
4
- * Pure React component. All state and handlers passed as props.
5
- */
6
-
7
- import React, { useRef, useState, useCallback } from 'react';
8
- import type { ImportPreview } from '../state/exchange-state-unit';
9
- export type { ImportPreview };
10
-
11
- export interface ImportCardTranslations {
12
- title: string;
13
- description: string;
14
- dropzoneLabel: string;
15
- dropzoneActive: string;
16
- detectedFormat: string;
17
- statsPreview: string;
18
- importButton: string;
19
- importing: string;
20
- importConfirmTitle: string;
21
- importConfirmMessage: string;
22
- confirmImport: string;
23
- cancelImport: string;
24
- }
25
-
26
- export interface ImportCardProps {
27
- onFileSelected: (file: File) => void;
28
- onImport: () => void;
29
- onCancel: () => void;
30
- selectedFile: File | null;
31
- preview: ImportPreview | null;
32
- isImporting: boolean;
33
- translations: ImportCardTranslations;
34
- }
35
-
36
- export function ImportCard({
37
- onFileSelected,
38
- onImport,
39
- onCancel,
40
- selectedFile,
41
- preview,
42
- isImporting,
43
- translations: t,
44
- }: ImportCardProps) {
45
- const fileInputRef = useRef<HTMLInputElement>(null);
46
- const [isDragActive, setIsDragActive] = useState(false);
47
- const [showConfirm, setShowConfirm] = useState(false);
48
-
49
- const handleDragOver = useCallback((e: React.DragEvent) => {
50
- e.preventDefault();
51
- setIsDragActive(true);
52
- }, []);
53
-
54
- const handleDragLeave = useCallback(() => {
55
- setIsDragActive(false);
56
- }, []);
57
-
58
- const handleDrop = useCallback((e: React.DragEvent) => {
59
- e.preventDefault();
60
- setIsDragActive(false);
61
- setShowConfirm(false);
62
- const file = e.dataTransfer.files[0];
63
- if (file) onFileSelected(file);
64
- }, [onFileSelected]);
65
-
66
- const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
67
- setShowConfirm(false);
68
- const file = e.target.files?.[0];
69
- if (file) onFileSelected(file);
70
- }, [onFileSelected]);
71
-
72
- const handleImportClick = () => {
73
- setShowConfirm(true);
74
- };
75
-
76
- const handleConfirm = () => {
77
- setShowConfirm(false);
78
- onImport();
79
- };
80
-
81
- const handleCancelConfirm = () => {
82
- setShowConfirm(false);
83
- };
84
-
85
- return (
86
- <div className="semiont-admin__card">
87
- <h2 className="semiont-admin__section-title">{t.title}</h2>
88
- <p className="semiont-admin__section-description">{t.description}</p>
89
-
90
- <div
91
- className={`semiont-exchange__dropzone${isDragActive ? ' semiont-exchange__dropzone--active' : ''}`}
92
- onDragOver={handleDragOver}
93
- onDragLeave={handleDragLeave}
94
- onDrop={handleDrop}
95
- onClick={() => fileInputRef.current?.click()}
96
- >
97
- <p className="semiont-exchange__dropzone-text">
98
- {isDragActive ? t.dropzoneActive : t.dropzoneLabel}
99
- </p>
100
- {selectedFile && (
101
- <p className="semiont-exchange__file-info">
102
- {selectedFile.name} ({(selectedFile.size / 1024).toFixed(1)} KB)
103
- </p>
104
- )}
105
- </div>
106
-
107
- <input
108
- ref={fileInputRef}
109
- type="file"
110
- accept=".tar.gz,.jsonl,.gz"
111
- onChange={handleFileChange}
112
- style={{ display: 'none' }}
113
- />
114
-
115
- {preview && (
116
- <div className="semiont-exchange__preview">
117
- <div className="semiont-exchange__preview-row">
118
- <span className="semiont-exchange__preview-label">{t.detectedFormat}</span>
119
- <span className="semiont-exchange__preview-value">
120
- {preview.format} v{preview.version}
121
- </span>
122
- </div>
123
- {preview.sourceUrl && (
124
- <div className="semiont-exchange__preview-row">
125
- <span className="semiont-exchange__preview-label">Source</span>
126
- <span className="semiont-exchange__preview-value">{preview.sourceUrl}</span>
127
- </div>
128
- )}
129
- {Object.keys(preview.stats).length > 0 && (
130
- <div className="semiont-exchange__preview-row">
131
- <span className="semiont-exchange__preview-label">{t.statsPreview}</span>
132
- <span className="semiont-exchange__preview-value">
133
- {Object.entries(preview.stats).map(([k, v]) => `${v} ${k}`).join(', ')}
134
- </span>
135
- </div>
136
- )}
137
- </div>
138
- )}
139
-
140
- {showConfirm && (
141
- <div className="semiont-exchange__confirm">
142
- <p className="semiont-exchange__confirm-title">{t.importConfirmTitle}</p>
143
- <p className="semiont-exchange__confirm-message">{t.importConfirmMessage}</p>
144
- <div className="semiont-exchange__confirm-actions">
145
- <button
146
- className="semiont-exchange__button semiont-exchange__button--primary"
147
- onClick={handleConfirm}
148
- >
149
- {t.confirmImport}
150
- </button>
151
- <button
152
- className="semiont-exchange__button semiont-exchange__button--secondary"
153
- onClick={handleCancelConfirm}
154
- >
155
- {t.cancelImport}
156
- </button>
157
- </div>
158
- </div>
159
- )}
160
-
161
- {!showConfirm && (
162
- <div className="semiont-exchange__actions">
163
- <button
164
- className="semiont-exchange__button semiont-exchange__button--primary"
165
- onClick={handleImportClick}
166
- disabled={!preview || isImporting}
167
- >
168
- {isImporting ? t.importing : t.importButton}
169
- </button>
170
- {selectedFile && (
171
- <button
172
- className="semiont-exchange__button semiont-exchange__button--secondary"
173
- onClick={onCancel}
174
- disabled={isImporting}
175
- >
176
- {t.cancelImport}
177
- </button>
178
- )}
179
- </div>
180
- )}
181
- </div>
182
- );
183
- }
@@ -1,80 +0,0 @@
1
- /**
2
- * ImportProgress — Shows SSE-driven progress during restore
3
- *
4
- * Pure React component. All state passed as props.
5
- */
6
-
7
- export interface ImportProgressTranslations {
8
- phaseStarted: string;
9
- phaseComplete: string;
10
- phaseError: string;
11
- statsEventsReplayed: string;
12
- statsResourcesCreated: string;
13
- statsAnnotationsCreated: string;
14
- statsEntityTypesAdded: string;
15
- }
16
-
17
- export interface ImportProgressProps {
18
- phase: string;
19
- message?: string;
20
- result?: Record<string, unknown>;
21
- translations: ImportProgressTranslations;
22
- }
23
-
24
- const PHASE_LABELS: Record<string, keyof ImportProgressTranslations> = {
25
- started: 'phaseStarted',
26
- complete: 'phaseComplete',
27
- error: 'phaseError',
28
- };
29
-
30
- const STAT_LABELS: Record<string, keyof ImportProgressTranslations> = {
31
- eventsReplayed: 'statsEventsReplayed',
32
- resourcesCreated: 'statsResourcesCreated',
33
- annotationsCreated: 'statsAnnotationsCreated',
34
- entityTypesAdded: 'statsEntityTypesAdded',
35
- };
36
-
37
- export function ImportProgress({ phase, message, result, translations: t }: ImportProgressProps) {
38
- const labelKey = PHASE_LABELS[phase];
39
- const phaseLabel = labelKey ? t[labelKey] : phase;
40
-
41
- const isComplete = phase === 'complete';
42
- const isError = phase === 'error';
43
-
44
- const stats = result?.stats != null && typeof result.stats === 'object'
45
- ? (result.stats as Record<string, unknown>)
46
- : undefined;
47
- const statEntries = stats
48
- ? Object.entries(stats).filter((entry): entry is [string, number] => typeof entry[1] === 'number')
49
- : [];
50
-
51
- return (
52
- <div className="semiont-exchange__progress">
53
- <div className={`semiont-exchange__phase-label${isError ? ' semiont-exchange__phase-label--error' : isComplete ? ' semiont-exchange__phase-label--complete' : ''}`}>
54
- {phaseLabel}
55
- </div>
56
-
57
- {message && !isComplete && !isError && (
58
- <p className="semiont-exchange__progress-message">{message}</p>
59
- )}
60
-
61
- {isComplete && statEntries.length > 0 && (
62
- <div className="semiont-exchange__result">
63
- {statEntries.map(([key, value]) => {
64
- const statKey = STAT_LABELS[key];
65
- return (
66
- <div key={key} className="semiont-exchange__result-stat">
67
- <span className="semiont-exchange__result-value">{value}</span>
68
- <span className="semiont-exchange__result-label">{statKey ? t[statKey] : key}</span>
69
- </div>
70
- );
71
- })}
72
- </div>
73
- )}
74
-
75
- {isError && message && (
76
- <p className="semiont-exchange__error-message">{message}</p>
77
- )}
78
- </div>
79
- );
80
- }