@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.
- package/README.md +3 -3
- package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +32 -5
- package/dist/{ar-OU3DQGC6.js → ar-AZBRADEB.js} +43 -23
- package/dist/{bn-PHWCR6AZ.js → bn-CCEW6AOS.js} +43 -23
- package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
- package/dist/{chunk-5VDTWRXJ.js → chunk-G4VHL4UA.js} +31 -31
- package/dist/{chunk-PCRRBU7H.js → chunk-SDKFZLEX.js} +43 -23
- package/dist/{cs-NEXYZ62I.js → cs-HXPF2CTE.js} +43 -23
- package/dist/{da-FAT43YCU.js → da-K5NTK7LH.js} +43 -23
- package/dist/{de-KRQ3WBCU.js → de-GMRQEDZI.js} +43 -23
- package/dist/{el-Q4IU3EKC.js → el-EUB4CI4G.js} +43 -23
- package/dist/{en-2KADE7N4.js → en-R5XA26NF.js} +2 -2
- package/dist/{es-2IO3R2MK.js → es-Z32DAZ6W.js} +43 -23
- package/dist/{fa-Q5GOQFXX.js → fa-G7HN6B32.js} +43 -23
- package/dist/{fi-R6NMRWBE.js → fi-3YBLZO7I.js} +43 -23
- package/dist/{fr-QY4D5E6J.js → fr-VUBH7BBS.js} +43 -23
- package/dist/{he-X6Y77XHY.js → he-WQQEY26B.js} +43 -23
- package/dist/{hi-XL7IOEQM.js → hi-HJZ6W4JB.js} +43 -23
- package/dist/{id-QZ4ZZS67.js → id-ZOGZ37MI.js} +43 -23
- package/dist/index.css +279 -74
- package/dist/index.d.ts +318 -393
- package/dist/index.js +2619 -2687
- package/dist/{it-ME2Q7OB6.js → it-DT6UW66F.js} +43 -23
- package/dist/{ja-HX7QASRI.js → ja-RUKQCOLO.js} +43 -23
- package/dist/{ko-EQO5AHAV.js → ko-ZH5ADKZM.js} +43 -23
- package/dist/{ms-UILFSF3I.js → ms-Y2AFPZTU.js} +43 -23
- package/dist/{nl-NNI45PMK.js → nl-OQEWIXNA.js} +43 -23
- package/dist/{no-MAL5F6P6.js → no-KPAYJZWY.js} +43 -23
- package/dist/{pl-4RMY6JAJ.js → pl-WSXK2UUD.js} +43 -23
- package/dist/{pt-VQH2X3QQ.js → pt-NWRJRUCK.js} +43 -23
- package/dist/{ro-XXCQO2OG.js → ro-3CGVMOC6.js} +43 -23
- package/dist/{sv-7Z6EJ6U4.js → sv-HGSEBA7S.js} +43 -23
- package/dist/test-utils.js +16 -7
- package/dist/{th-L3DXXYVQ.js → th-NZ4G352Z.js} +43 -23
- package/dist/{tr-P3DFVLFJ.js → tr-LGGS4DLW.js} +43 -23
- package/dist/{uk-7C5D3PXS.js → uk-AIX536L4.js} +43 -23
- package/dist/{vi-ZLTLEO7P.js → vi-V6CVQZLQ.js} +43 -23
- package/dist/{zh-5FHVWDIN.js → zh-AGKPGI3L.js} +43 -23
- package/package.json +16 -15
- package/src/components/AssistProgress.tsx +36 -2
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/StatusDisplay.tsx +13 -13
- package/src/components/__tests__/AssistProgress.test.tsx +59 -0
- package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
- package/src/components/modals/ComposeStep.tsx +233 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +178 -34
- package/src/components/modals/ConfigureSearchStep.tsx +19 -0
- package/src/components/modals/ContextSummary.tsx +185 -72
- package/src/components/modals/CorpusPane.tsx +75 -0
- package/src/components/modals/DiscardPrompt.tsx +38 -0
- package/src/components/modals/GatherContextStep.tsx +237 -107
- package/src/components/modals/ReferenceWizardModal.tsx +252 -59
- package/src/components/modals/ResourceGenerateModal.tsx +225 -112
- package/src/components/modals/SearchModal.css +337 -101
- package/src/components/modals/SearchResultsStep.tsx +61 -68
- package/src/components/modals/WizardFooter.tsx +7 -1
- package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
- package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +206 -8
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +442 -39
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +300 -86
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +116 -9
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
- package/src/components/resource/AnnotateView.tsx +3 -3
- package/src/components/resource/BrowseView.tsx +8 -8
- package/src/components/resource/DownloadFileLink.tsx +1 -1
- package/src/components/resource/ResourceViewer.tsx +4 -4
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +11 -11
- package/src/components/resource/__tests__/download-link.test.tsx +6 -6
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/panels/AssistSection.css +35 -0
- package/src/components/resource/panels/ReferenceEntry.tsx +11 -4
- package/src/components/resource/panels/ReferencesPanel.tsx +9 -1
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +84 -3
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +149 -7
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
- package/src/features/auth/components/SignInForm.tsx +53 -53
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +35 -133
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
- package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +73 -17
- package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +148 -83
- package/src/features/resource-viewer/generation-options.ts +29 -0
- package/src/styles/core/forms.css +35 -8
- package/src/styles/features/resource-viewer.css +0 -22
- package/src/styles/features/resource.css +0 -4
- package/src/styles/index.css +0 -1
- package/translations/ar.json +42 -22
- package/translations/bn.json +42 -22
- package/translations/cs.json +42 -22
- package/translations/da.json +42 -22
- package/translations/de.json +42 -22
- package/translations/el.json +42 -22
- package/translations/en.json +42 -22
- package/translations/es.json +42 -22
- package/translations/fa.json +42 -22
- package/translations/fi.json +42 -22
- package/translations/fr.json +42 -22
- package/translations/he.json +42 -22
- package/translations/hi.json +42 -22
- package/translations/id.json +42 -22
- package/translations/it.json +42 -22
- package/translations/ja.json +42 -22
- package/translations/ko.json +42 -22
- package/translations/ms.json +42 -22
- package/translations/nl.json +42 -22
- package/translations/no.json +42 -22
- package/translations/pl.json +42 -22
- package/translations/pt.json +42 -22
- package/translations/ro.json +42 -22
- package/translations/sv.json +42 -22
- package/translations/th.json +42 -22
- package/translations/tr.json +42 -22
- package/translations/uk.json +42 -22
- package/translations/vi.json +42 -22
- package/translations/zh.json +42 -22
- package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
- package/dist/ar-OU3DQGC6.js.map +0 -1
- package/dist/bn-PHWCR6AZ.js.map +0 -1
- package/dist/chunk-5VDTWRXJ.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-PCRRBU7H.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/cs-NEXYZ62I.js.map +0 -1
- package/dist/da-FAT43YCU.js.map +0 -1
- package/dist/de-KRQ3WBCU.js.map +0 -1
- package/dist/el-Q4IU3EKC.js.map +0 -1
- package/dist/en-2KADE7N4.js.map +0 -1
- package/dist/es-2IO3R2MK.js.map +0 -1
- package/dist/fa-Q5GOQFXX.js.map +0 -1
- package/dist/fi-R6NMRWBE.js.map +0 -1
- package/dist/fr-QY4D5E6J.js.map +0 -1
- package/dist/he-X6Y77XHY.js.map +0 -1
- package/dist/hi-XL7IOEQM.js.map +0 -1
- package/dist/id-QZ4ZZS67.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/css-modules-helper.js.map +0 -1
- package/dist/integrations/styled-components-theme.js.map +0 -1
- package/dist/it-ME2Q7OB6.js.map +0 -1
- package/dist/ja-HX7QASRI.js.map +0 -1
- package/dist/ko-EQO5AHAV.js.map +0 -1
- package/dist/ms-UILFSF3I.js.map +0 -1
- package/dist/nl-NNI45PMK.js.map +0 -1
- package/dist/no-MAL5F6P6.js.map +0 -1
- package/dist/pl-4RMY6JAJ.js.map +0 -1
- package/dist/pt-VQH2X3QQ.js.map +0 -1
- package/dist/ro-XXCQO2OG.js.map +0 -1
- package/dist/sv-7Z6EJ6U4.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-L3DXXYVQ.js.map +0 -1
- package/dist/tr-P3DFVLFJ.js.map +0 -1
- package/dist/uk-7C5D3PXS.js.map +0 -1
- package/dist/vi-ZLTLEO7P.js.map +0 -1
- package/dist/zh-5FHVWDIN.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
- package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
- package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
- package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
- package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
- package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
- package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
- package/src/styles/features/exchange.css +0 -374
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { firstValueFrom, of, throwError } from 'rxjs';
|
|
3
|
-
import type { ShellStateUnit } from '../../../../state/shell-state-unit';
|
|
4
|
-
import { createExchangeStateUnit } from '../exchange-state-unit';
|
|
5
|
-
import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
|
|
6
|
-
|
|
7
|
-
function mockBrowse(): ShellStateUnit {
|
|
8
|
-
return { dispose: vi.fn() } as unknown as ShellStateUnit;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function makeMockFile(name: string): File {
|
|
12
|
-
return new File(['content'], name, { type: 'application/gzip' });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('createExchangeStateUnit', () => {
|
|
16
|
-
it('initializes with null/empty state', async () => {
|
|
17
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), vi.fn());
|
|
18
|
-
|
|
19
|
-
expect(await firstValueFrom(stateUnit.selectedFile$)).toBeNull();
|
|
20
|
-
expect(await firstValueFrom(stateUnit.preview$)).toBeNull();
|
|
21
|
-
expect(await firstValueFrom(stateUnit.importPhase$)).toBeNull();
|
|
22
|
-
expect(await firstValueFrom(stateUnit.isExporting$)).toBe(false);
|
|
23
|
-
expect(await firstValueFrom(stateUnit.isImporting$)).toBe(false);
|
|
24
|
-
|
|
25
|
-
stateUnit.dispose();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('selectFile sets file and generates preview', async () => {
|
|
29
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), vi.fn());
|
|
30
|
-
|
|
31
|
-
stateUnit.selectFile(makeMockFile('backup.tar.gz'));
|
|
32
|
-
|
|
33
|
-
const file = await firstValueFrom(stateUnit.selectedFile$);
|
|
34
|
-
expect(file?.name).toBe('backup.tar.gz');
|
|
35
|
-
|
|
36
|
-
const preview = await firstValueFrom(stateUnit.preview$);
|
|
37
|
-
expect(preview?.format).toBe('semiont-linked-data');
|
|
38
|
-
|
|
39
|
-
stateUnit.dispose();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('selectFile detects unknown format', async () => {
|
|
43
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), vi.fn());
|
|
44
|
-
|
|
45
|
-
stateUnit.selectFile(makeMockFile('data.json'));
|
|
46
|
-
|
|
47
|
-
const preview = await firstValueFrom(stateUnit.preview$);
|
|
48
|
-
expect(preview?.format).toBe('unknown');
|
|
49
|
-
|
|
50
|
-
stateUnit.dispose();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('cancelImport resets all state', async () => {
|
|
54
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), vi.fn());
|
|
55
|
-
|
|
56
|
-
stateUnit.selectFile(makeMockFile('backup.tar.gz'));
|
|
57
|
-
stateUnit.cancelImport();
|
|
58
|
-
|
|
59
|
-
expect(await firstValueFrom(stateUnit.selectedFile$)).toBeNull();
|
|
60
|
-
expect(await firstValueFrom(stateUnit.preview$)).toBeNull();
|
|
61
|
-
expect(await firstValueFrom(stateUnit.importPhase$)).toBeNull();
|
|
62
|
-
|
|
63
|
-
stateUnit.dispose();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// jsdom doesn't implement `Blob.prototype.stream()` portably, so build
|
|
67
|
-
// the stream literal — same shape, no environment dependency.
|
|
68
|
-
function streamOf(text: string): ReadableStream<Uint8Array> {
|
|
69
|
-
return new ReadableStream<Uint8Array>({
|
|
70
|
-
start(controller) {
|
|
71
|
-
controller.enqueue(new TextEncoder().encode(text));
|
|
72
|
-
controller.close();
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
it('doExport calls exportFn and returns blob + filename from BackendDownload', async () => {
|
|
78
|
-
// exportFn now returns a BackendDownload — a transport-neutral
|
|
79
|
-
// { stream, contentType, filename? } object. The state unit converts the
|
|
80
|
-
// stream to a Blob and threads filename through.
|
|
81
|
-
const exportFn = vi.fn().mockResolvedValue({
|
|
82
|
-
stream: streamOf('data'),
|
|
83
|
-
contentType: 'application/x-tar',
|
|
84
|
-
filename: 'export.tar.gz',
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), exportFn, vi.fn());
|
|
88
|
-
|
|
89
|
-
const result = await stateUnit.doExport();
|
|
90
|
-
expect(result.filename).toBe('export.tar.gz');
|
|
91
|
-
expect(await result.blob.text()).toBe('data');
|
|
92
|
-
|
|
93
|
-
expect(await firstValueFrom(stateUnit.isExporting$)).toBe(false);
|
|
94
|
-
|
|
95
|
-
stateUnit.dispose();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('doExport falls back to a synthesized filename when the download omits one', async () => {
|
|
99
|
-
const exportFn = vi.fn().mockResolvedValue({
|
|
100
|
-
stream: streamOf('data'),
|
|
101
|
-
contentType: 'application/x-tar',
|
|
102
|
-
// no filename
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), exportFn, vi.fn());
|
|
106
|
-
|
|
107
|
-
const result = await stateUnit.doExport();
|
|
108
|
-
expect(result.filename).toMatch(/^semiont-export-\d+\.tar\.gz$/);
|
|
109
|
-
|
|
110
|
-
stateUnit.dispose();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('doExport propagates errors from exportFn and clears isExporting$', async () => {
|
|
114
|
-
// The state unit no longer inspects HTTP status — non-OK responses are the
|
|
115
|
-
// transport's concern (ky throws on non-OK by default). The state unit just
|
|
116
|
-
// propagates whatever the exportFn rejects with and resets state.
|
|
117
|
-
const exportFn = vi.fn().mockRejectedValue(new Error('transport boom'));
|
|
118
|
-
|
|
119
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), exportFn, vi.fn());
|
|
120
|
-
|
|
121
|
-
await expect(stateUnit.doExport()).rejects.toThrow('transport boom');
|
|
122
|
-
expect(await firstValueFrom(stateUnit.isExporting$)).toBe(false);
|
|
123
|
-
|
|
124
|
-
stateUnit.dispose();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('doImport subscribes to importFn Observable and mirrors each progress event', async () => {
|
|
128
|
-
// importFn now returns Observable<ProgressEvent>. The state unit subscribes,
|
|
129
|
-
// mirrors each emit into its state subjects, and resolves when the
|
|
130
|
-
// observable completes.
|
|
131
|
-
const importFn = vi.fn().mockReturnValue(
|
|
132
|
-
of(
|
|
133
|
-
{ phase: 'uploading', message: '50%' },
|
|
134
|
-
{ phase: 'complete', result: { resources: 10 } },
|
|
135
|
-
),
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), importFn);
|
|
139
|
-
stateUnit.selectFile(makeMockFile('import.tar.gz'));
|
|
140
|
-
|
|
141
|
-
await stateUnit.doImport();
|
|
142
|
-
|
|
143
|
-
expect(importFn).toHaveBeenCalledOnce();
|
|
144
|
-
expect(await firstValueFrom(stateUnit.importResult$)).toEqual({ resources: 10 });
|
|
145
|
-
expect(await firstValueFrom(stateUnit.importPhase$)).toBe('complete');
|
|
146
|
-
expect(await firstValueFrom(stateUnit.isImporting$)).toBe(false);
|
|
147
|
-
|
|
148
|
-
stateUnit.dispose();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('doImport propagates errors from the importFn Observable and clears isImporting$', async () => {
|
|
152
|
-
const importFn = vi.fn().mockReturnValue(throwError(() => new Error('import boom')));
|
|
153
|
-
|
|
154
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), importFn);
|
|
155
|
-
stateUnit.selectFile(makeMockFile('import.tar.gz'));
|
|
156
|
-
|
|
157
|
-
await expect(stateUnit.doImport()).rejects.toThrow('import boom');
|
|
158
|
-
expect(await firstValueFrom(stateUnit.isImporting$)).toBe(false);
|
|
159
|
-
|
|
160
|
-
stateUnit.dispose();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('doImport is no-op without selected file', async () => {
|
|
164
|
-
const importFn = vi.fn();
|
|
165
|
-
const stateUnit = createExchangeStateUnit(mockBrowse(), vi.fn(), importFn);
|
|
166
|
-
|
|
167
|
-
await stateUnit.doImport();
|
|
168
|
-
expect(importFn).not.toHaveBeenCalled();
|
|
169
|
-
|
|
170
|
-
stateUnit.dispose();
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe('ExchangeStateUnit — StateUnit axioms', () => {
|
|
175
|
-
it('satisfies the StateUnit axioms (incl. A7-passed: never disposes the injected browse)', () => {
|
|
176
|
-
assertStateUnitAxioms({
|
|
177
|
-
setup: () => {
|
|
178
|
-
const browse = disposeProbe();
|
|
179
|
-
return { unit: createExchangeStateUnit(browse as unknown as ShellStateUnit, vi.fn(), vi.fn()), passedIn: [browse] };
|
|
180
|
-
},
|
|
181
|
-
surfaces: (u) => [u.selectedFile$, u.preview$, u.importPhase$, u.importMessage$, u.importResult$, u.isExporting$, u.isImporting$],
|
|
182
|
-
invocations: (u) => [() => u.cancelImport(), () => u.doImport()],
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { BehaviorSubject, lastValueFrom, type Observable } from 'rxjs';
|
|
2
|
-
import { tap } from 'rxjs/operators';
|
|
3
|
-
import { createDisposer } from '@semiont/sdk';
|
|
4
|
-
import type { StateUnit } from '@semiont/core';
|
|
5
|
-
import type { ShellStateUnit } from '../../../state/shell-state-unit';
|
|
6
|
-
import type { BackendDownload, ProgressEvent } from '@semiont/core';
|
|
7
|
-
|
|
8
|
-
export interface ImportPreview {
|
|
9
|
-
format: string;
|
|
10
|
-
version: number;
|
|
11
|
-
sourceUrl: string;
|
|
12
|
-
stats: Record<string, number>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ExchangeStateUnit extends StateUnit {
|
|
16
|
-
browse: ShellStateUnit;
|
|
17
|
-
selectedFile$: Observable<File | null>;
|
|
18
|
-
preview$: Observable<ImportPreview | null>;
|
|
19
|
-
importPhase$: Observable<string | null>;
|
|
20
|
-
importMessage$: Observable<string | undefined>;
|
|
21
|
-
importResult$: Observable<Record<string, unknown> | undefined>;
|
|
22
|
-
isExporting$: Observable<boolean>;
|
|
23
|
-
isImporting$: Observable<boolean>;
|
|
24
|
-
selectFile(file: File): void;
|
|
25
|
-
cancelImport(): void;
|
|
26
|
-
doExport(): Promise<{ blob: Blob; filename: string }>;
|
|
27
|
-
doImport(): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function createExchangeStateUnit(
|
|
31
|
-
browse: ShellStateUnit,
|
|
32
|
-
exportFn: (params?: { includeArchived?: boolean }) => Promise<BackendDownload>,
|
|
33
|
-
importFn: (file: File) => Observable<ProgressEvent>,
|
|
34
|
-
): ExchangeStateUnit {
|
|
35
|
-
const disposer = createDisposer();
|
|
36
|
-
// `browse` (ShellStateUnit) is a *passed-in* dependency owned by the caller
|
|
37
|
-
// (`useShellStateUnit`), not this unit — do NOT add it to the disposer (it's the
|
|
38
|
-
// shared, app-scoped shell). See packages/sdk/docs/STATE-UNITS.md (composition rule).
|
|
39
|
-
|
|
40
|
-
const selectedFile$ = new BehaviorSubject<File | null>(null);
|
|
41
|
-
const preview$ = new BehaviorSubject<ImportPreview | null>(null);
|
|
42
|
-
const importPhase$ = new BehaviorSubject<string | null>(null);
|
|
43
|
-
const importMessage$ = new BehaviorSubject<string | undefined>(undefined);
|
|
44
|
-
const importResult$ = new BehaviorSubject<Record<string, unknown> | undefined>(undefined);
|
|
45
|
-
const isExporting$ = new BehaviorSubject<boolean>(false);
|
|
46
|
-
const isImporting$ = new BehaviorSubject<boolean>(false);
|
|
47
|
-
|
|
48
|
-
const selectFile = (file: File): void => {
|
|
49
|
-
selectedFile$.next(file);
|
|
50
|
-
importPhase$.next(null);
|
|
51
|
-
importMessage$.next(undefined);
|
|
52
|
-
importResult$.next(undefined);
|
|
53
|
-
preview$.next({
|
|
54
|
-
format: file.name.endsWith('.tar.gz') || file.name.endsWith('.gz') ? 'semiont-linked-data' : 'unknown',
|
|
55
|
-
version: 1,
|
|
56
|
-
sourceUrl: '',
|
|
57
|
-
stats: {} as Record<string, number>,
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const cancelImport = (): void => {
|
|
62
|
-
selectedFile$.next(null);
|
|
63
|
-
preview$.next(null);
|
|
64
|
-
importPhase$.next(null);
|
|
65
|
-
importMessage$.next(undefined);
|
|
66
|
-
importResult$.next(undefined);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const doExport = async (): Promise<{ blob: Blob; filename: string }> => {
|
|
70
|
-
isExporting$.next(true);
|
|
71
|
-
try {
|
|
72
|
-
const download = await exportFn();
|
|
73
|
-
// Wrap the stream in a Response purely as a Blob-collection helper —
|
|
74
|
-
// BackendDownload itself carries no fetch dependency.
|
|
75
|
-
const blob = await new Response(download.stream).blob();
|
|
76
|
-
const filename = download.filename ?? `semiont-export-${Date.now()}.tar.gz`;
|
|
77
|
-
return { blob, filename };
|
|
78
|
-
} finally {
|
|
79
|
-
isExporting$.next(false);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const doImport = async (): Promise<void> => {
|
|
84
|
-
const file = selectedFile$.getValue();
|
|
85
|
-
if (!file) return;
|
|
86
|
-
isImporting$.next(true);
|
|
87
|
-
importPhase$.next('started');
|
|
88
|
-
importMessage$.next(undefined);
|
|
89
|
-
importResult$.next(undefined);
|
|
90
|
-
try {
|
|
91
|
-
// The importFn is `Observable<ProgressEvent>` — every emit is a
|
|
92
|
-
// progress event; the final emit before complete is the outcome.
|
|
93
|
-
// `tap` mirrors each event into our state subjects; `lastValueFrom`
|
|
94
|
-
// awaits the last value (so callers can `await vm.doImport()`).
|
|
95
|
-
await lastValueFrom(
|
|
96
|
-
importFn(file).pipe(
|
|
97
|
-
tap((event) => {
|
|
98
|
-
importPhase$.next(event.phase);
|
|
99
|
-
importMessage$.next(event.message);
|
|
100
|
-
if (event.result) importResult$.next(event.result);
|
|
101
|
-
}),
|
|
102
|
-
),
|
|
103
|
-
);
|
|
104
|
-
} finally {
|
|
105
|
-
isImporting$.next(false);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
browse,
|
|
111
|
-
selectedFile$: selectedFile$.asObservable(),
|
|
112
|
-
preview$: preview$.asObservable(),
|
|
113
|
-
importPhase$: importPhase$.asObservable(),
|
|
114
|
-
importMessage$: importMessage$.asObservable(),
|
|
115
|
-
importResult$: importResult$.asObservable(),
|
|
116
|
-
isExporting$: isExporting$.asObservable(),
|
|
117
|
-
isImporting$: isImporting$.asObservable(),
|
|
118
|
-
selectFile,
|
|
119
|
-
cancelImport,
|
|
120
|
-
doExport,
|
|
121
|
-
doImport,
|
|
122
|
-
dispose: () => {
|
|
123
|
-
selectedFile$.complete();
|
|
124
|
-
preview$.complete();
|
|
125
|
-
importPhase$.complete();
|
|
126
|
-
importMessage$.complete();
|
|
127
|
-
importResult$.complete();
|
|
128
|
-
isExporting$.complete();
|
|
129
|
-
isImporting$.complete();
|
|
130
|
-
disposer.dispose();
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for LinkedDataPage component
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
-
import { render, screen } from '@testing-library/react';
|
|
7
|
-
import { LinkedDataPage } from '../components/LinkedDataPage';
|
|
8
|
-
import type { LinkedDataPageProps } from '../components/LinkedDataPage';
|
|
9
|
-
|
|
10
|
-
const createProps = (overrides?: Partial<LinkedDataPageProps>): LinkedDataPageProps => ({
|
|
11
|
-
onExport: vi.fn(),
|
|
12
|
-
isExporting: false,
|
|
13
|
-
onFileSelected: vi.fn(),
|
|
14
|
-
onImport: vi.fn(),
|
|
15
|
-
onCancelImport: vi.fn(),
|
|
16
|
-
selectedFile: null,
|
|
17
|
-
preview: null,
|
|
18
|
-
isImporting: false,
|
|
19
|
-
importPhase: null,
|
|
20
|
-
theme: 'light',
|
|
21
|
-
activePanel: null,
|
|
22
|
-
translations: {
|
|
23
|
-
title: 'Export & Import',
|
|
24
|
-
subtitle: 'Exchange knowledge as JSON-LD Linked Data',
|
|
25
|
-
export: {
|
|
26
|
-
title: 'Export',
|
|
27
|
-
description: 'Export your knowledge base as JSON-LD',
|
|
28
|
-
exportButton: 'Export as JSON-LD',
|
|
29
|
-
exporting: 'Exporting…',
|
|
30
|
-
},
|
|
31
|
-
import: {
|
|
32
|
-
title: 'Import',
|
|
33
|
-
description: 'Import from JSON-LD',
|
|
34
|
-
dropzoneLabel: 'Drop a file here',
|
|
35
|
-
dropzoneActive: 'Drop to upload',
|
|
36
|
-
detectedFormat: 'Format',
|
|
37
|
-
statsPreview: 'Preview',
|
|
38
|
-
importButton: 'Import',
|
|
39
|
-
importing: 'Importing…',
|
|
40
|
-
importConfirmTitle: 'Confirm',
|
|
41
|
-
importConfirmMessage: 'This will create new resources.',
|
|
42
|
-
confirmImport: 'Proceed',
|
|
43
|
-
cancelImport: 'Cancel',
|
|
44
|
-
},
|
|
45
|
-
progress: {
|
|
46
|
-
phaseStarted: 'Starting…',
|
|
47
|
-
phaseComplete: 'Complete',
|
|
48
|
-
phaseError: 'Failed',
|
|
49
|
-
statsEventsReplayed: 'Events replayed',
|
|
50
|
-
statsResourcesCreated: 'Resources created',
|
|
51
|
-
statsAnnotationsCreated: 'Annotations created',
|
|
52
|
-
statsEntityTypesAdded: 'Entity types added',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
ToolbarPanels: () => <div data-testid="toolbar-panels" />,
|
|
56
|
-
Toolbar: () => <div data-testid="toolbar" />,
|
|
57
|
-
...overrides,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('LinkedDataPage', () => {
|
|
61
|
-
it('renders page title and subtitle', () => {
|
|
62
|
-
render(<LinkedDataPage {...createProps()} />);
|
|
63
|
-
expect(screen.getByText('Export & Import')).toBeInTheDocument();
|
|
64
|
-
expect(screen.getByText('Exchange knowledge as JSON-LD Linked Data')).toBeInTheDocument();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('renders ExportCard', () => {
|
|
68
|
-
render(<LinkedDataPage {...createProps()} />);
|
|
69
|
-
expect(screen.getByRole('heading', { name: 'Export' })).toBeInTheDocument();
|
|
70
|
-
expect(screen.getByText('Export as JSON-LD')).toBeInTheDocument();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('renders ImportCard', () => {
|
|
74
|
-
render(<LinkedDataPage {...createProps()} />);
|
|
75
|
-
expect(screen.getByRole('heading', { name: 'Import' })).toBeInTheDocument();
|
|
76
|
-
expect(screen.getByText('Import from JSON-LD')).toBeInTheDocument();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('renders toolbar components', () => {
|
|
80
|
-
render(<LinkedDataPage {...createProps()} />);
|
|
81
|
-
expect(screen.getByTestId('toolbar-panels')).toBeInTheDocument();
|
|
82
|
-
expect(screen.getByTestId('toolbar')).toBeInTheDocument();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('does not render ImportProgress when importPhase is null', () => {
|
|
86
|
-
const { container } = render(<LinkedDataPage {...createProps()} />);
|
|
87
|
-
expect(container.querySelector('.semiont-exchange__progress')).not.toBeInTheDocument();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('renders ImportProgress when importPhase is set', () => {
|
|
91
|
-
render(<LinkedDataPage {...createProps({ importPhase: 'started' })} />);
|
|
92
|
-
expect(screen.getByText('Starting…')).toBeInTheDocument();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('renders ImportProgress with result on completion', () => {
|
|
96
|
-
render(<LinkedDataPage {...createProps({
|
|
97
|
-
importPhase: 'complete',
|
|
98
|
-
importResult: { stats: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } },
|
|
99
|
-
})} />);
|
|
100
|
-
expect(screen.getByText('Complete')).toBeInTheDocument();
|
|
101
|
-
expect(screen.getByText('5')).toBeInTheDocument();
|
|
102
|
-
expect(screen.getByText('Resources created')).toBeInTheDocument();
|
|
103
|
-
expect(screen.getByText('12')).toBeInTheDocument();
|
|
104
|
-
expect(screen.getByText('Annotations created')).toBeInTheDocument();
|
|
105
|
-
expect(screen.getByText('3')).toBeInTheDocument();
|
|
106
|
-
expect(screen.getByText('Entity types added')).toBeInTheDocument();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('applies panel-open class when common panel is active', () => {
|
|
110
|
-
const { container } = render(<LinkedDataPage {...createProps({ activePanel: 'user' })} />);
|
|
111
|
-
expect(container.querySelector('.semiont-page--panel-open')).toBeInTheDocument();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('does not apply panel-open class when no panel is active', () => {
|
|
115
|
-
const { container } = render(<LinkedDataPage {...createProps({ activePanel: null })} />);
|
|
116
|
-
expect(container.querySelector('.semiont-page--panel-open')).not.toBeInTheDocument();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LinkedDataPage — JSON-LD Export/Import moderation page
|
|
3
|
-
*
|
|
4
|
-
* Pure React component. All state and handlers passed as props.
|
|
5
|
-
* Reuses ExportCard, ImportCard, and ImportProgress from admin-exchange.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
10
|
-
import { ExportCard, type ExportCardTranslations } from '../../admin-exchange/components/ExportCard';
|
|
11
|
-
import { ImportCard, type ImportCardProps, type ImportCardTranslations } from '../../admin-exchange/components/ImportCard';
|
|
12
|
-
import { ImportProgress, type ImportProgressTranslations } from '../../admin-exchange/components/ImportProgress';
|
|
13
|
-
|
|
14
|
-
export interface LinkedDataPageTranslations {
|
|
15
|
-
title: string;
|
|
16
|
-
subtitle: string;
|
|
17
|
-
export: ExportCardTranslations;
|
|
18
|
-
import: ImportCardTranslations;
|
|
19
|
-
progress: ImportProgressTranslations;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface LinkedDataPageProps {
|
|
23
|
-
// Export
|
|
24
|
-
onExport: () => void;
|
|
25
|
-
isExporting: boolean;
|
|
26
|
-
|
|
27
|
-
// Import
|
|
28
|
-
onFileSelected: (file: File) => void;
|
|
29
|
-
onImport: () => void;
|
|
30
|
-
onCancelImport: () => void;
|
|
31
|
-
selectedFile: File | null;
|
|
32
|
-
preview: ImportCardProps['preview'];
|
|
33
|
-
isImporting: boolean;
|
|
34
|
-
|
|
35
|
-
// Progress
|
|
36
|
-
importPhase: string | null;
|
|
37
|
-
importMessage?: string | undefined;
|
|
38
|
-
importResult?: Record<string, unknown> | undefined;
|
|
39
|
-
|
|
40
|
-
// UI state
|
|
41
|
-
theme: 'light' | 'dark' | 'system';
|
|
42
|
-
activePanel: string | null;
|
|
43
|
-
|
|
44
|
-
// Translations
|
|
45
|
-
translations: LinkedDataPageTranslations;
|
|
46
|
-
|
|
47
|
-
// Component dependencies
|
|
48
|
-
ToolbarPanels: React.ComponentType<any>;
|
|
49
|
-
Toolbar: React.ComponentType<any>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function LinkedDataPage({
|
|
53
|
-
onExport,
|
|
54
|
-
isExporting,
|
|
55
|
-
onFileSelected,
|
|
56
|
-
onImport,
|
|
57
|
-
onCancelImport,
|
|
58
|
-
selectedFile,
|
|
59
|
-
preview,
|
|
60
|
-
isImporting,
|
|
61
|
-
importPhase,
|
|
62
|
-
importMessage,
|
|
63
|
-
importResult,
|
|
64
|
-
theme,
|
|
65
|
-
activePanel,
|
|
66
|
-
translations: t,
|
|
67
|
-
ToolbarPanels,
|
|
68
|
-
Toolbar,
|
|
69
|
-
}: LinkedDataPageProps) {
|
|
70
|
-
return (
|
|
71
|
-
<div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
|
|
72
|
-
<div className="semiont-page__content">
|
|
73
|
-
<div className="semiont-page__header">
|
|
74
|
-
<h1 className="semiont-page__title">{t.title}</h1>
|
|
75
|
-
<p className="semiont-page__subtitle">{t.subtitle}</p>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div className="semiont-exchange__cards">
|
|
79
|
-
<ExportCard
|
|
80
|
-
onExport={onExport}
|
|
81
|
-
isExporting={isExporting}
|
|
82
|
-
translations={t.export}
|
|
83
|
-
/>
|
|
84
|
-
|
|
85
|
-
<ImportCard
|
|
86
|
-
onFileSelected={onFileSelected}
|
|
87
|
-
onImport={onImport}
|
|
88
|
-
onCancel={onCancelImport}
|
|
89
|
-
selectedFile={selectedFile}
|
|
90
|
-
preview={preview}
|
|
91
|
-
isImporting={isImporting}
|
|
92
|
-
translations={t.import}
|
|
93
|
-
/>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
{importPhase && (
|
|
97
|
-
<ImportProgress
|
|
98
|
-
phase={importPhase}
|
|
99
|
-
message={importMessage}
|
|
100
|
-
result={importResult}
|
|
101
|
-
translations={t.progress}
|
|
102
|
-
/>
|
|
103
|
-
)}
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div className="semiont-page__sidebar">
|
|
107
|
-
<ToolbarPanels
|
|
108
|
-
activePanel={activePanel}
|
|
109
|
-
theme={theme}
|
|
110
|
-
/>
|
|
111
|
-
<Toolbar
|
|
112
|
-
context="simple"
|
|
113
|
-
activePanel={activePanel}
|
|
114
|
-
/>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|