@semiont/react-ui 0.5.5 → 0.5.7
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 +59 -55
- package/dist/{PdfAnnotationCanvas.client-CN3C3S55.js → PdfAnnotationCanvas.client-NIMALXNZ.js} +7 -27
- package/dist/PdfAnnotationCanvas.client-NIMALXNZ.js.map +1 -0
- package/dist/{ar-U2EXWUMQ.js → ar-SONK6MON.js} +3 -7
- package/dist/ar-SONK6MON.js.map +1 -0
- package/dist/{bn-DRJGV772.js → bn-ZKPRITNG.js} +3 -7
- package/dist/bn-ZKPRITNG.js.map +1 -0
- package/dist/{chunk-3Q3TUKWP.js → chunk-Y2EEAOMZ.js} +29 -29
- package/dist/{cs-PTWDM23V.js → cs-LPXQ7NHQ.js} +3 -7
- package/dist/cs-LPXQ7NHQ.js.map +1 -0
- package/dist/{da-KSNIKYSS.js → da-6TKY7MCY.js} +6 -10
- package/dist/da-6TKY7MCY.js.map +1 -0
- package/dist/{de-F2XBEWFY.js → de-C3GNII74.js} +3 -7
- package/dist/de-C3GNII74.js.map +1 -0
- package/dist/{el-DLD2GWAP.js → el-UBCXQDJ7.js} +3 -7
- package/dist/el-UBCXQDJ7.js.map +1 -0
- package/dist/{es-WLPYWGB5.js → es-BQ23TRI7.js} +11 -15
- package/dist/es-BQ23TRI7.js.map +1 -0
- package/dist/{fa-BAXHSDZG.js → fa-AFTBZB77.js} +3 -7
- package/dist/fa-AFTBZB77.js.map +1 -0
- package/dist/{fi-FCHSYVOT.js → fi-WOYNLZC2.js} +3 -7
- package/dist/fi-WOYNLZC2.js.map +1 -0
- package/dist/{fr-3UERBSL6.js → fr-NDSMIFJM.js} +3 -7
- package/dist/fr-NDSMIFJM.js.map +1 -0
- package/dist/{he-F6F3FV2K.js → he-VJXVRDOY.js} +3 -7
- package/dist/he-VJXVRDOY.js.map +1 -0
- package/dist/{hi-4BK6IK7Q.js → hi-BF6PHIE2.js} +3 -7
- package/dist/hi-BF6PHIE2.js.map +1 -0
- package/dist/{id-7ECCWP3J.js → id-GXG5QCZY.js} +3 -7
- package/dist/id-GXG5QCZY.js.map +1 -0
- package/dist/index.css +103 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +271 -120
- package/dist/index.js +877 -698
- package/dist/index.js.map +1 -1
- package/dist/{it-234Z6XK6.js → it-XKHHCBAF.js} +3 -7
- package/dist/it-XKHHCBAF.js.map +1 -0
- package/dist/{ja-PJWQI4OQ.js → ja-TX7VM4XD.js} +3 -7
- package/dist/ja-TX7VM4XD.js.map +1 -0
- package/dist/{ko-APUEW2RS.js → ko-DNC7EQ7J.js} +3 -7
- package/dist/ko-DNC7EQ7J.js.map +1 -0
- package/dist/{ms-PJBZWZWD.js → ms-POZGBKPH.js} +3 -7
- package/dist/ms-POZGBKPH.js.map +1 -0
- package/dist/{nl-L4C3ZBCU.js → nl-IRMTKI7Z.js} +4 -11
- package/dist/nl-IRMTKI7Z.js.map +1 -0
- package/dist/{no-QE5N5KNG.js → no-ZUDJA4S6.js} +20 -24
- package/dist/no-ZUDJA4S6.js.map +1 -0
- package/dist/{pl-5Q2D23PD.js → pl-2NGAXL5U.js} +3 -7
- package/dist/pl-2NGAXL5U.js.map +1 -0
- package/dist/{pt-AIGUOIOC.js → pt-ABMCXZUM.js} +118 -122
- package/dist/pt-ABMCXZUM.js.map +1 -0
- package/dist/{ro-T56CSHTY.js → ro-VOJP6O5X.js} +3 -7
- package/dist/ro-VOJP6O5X.js.map +1 -0
- package/dist/{sv-L4TJQ2UH.js → sv-4HVFIIE5.js} +43 -47
- package/dist/sv-4HVFIIE5.js.map +1 -0
- package/dist/test-utils.js +2 -2
- package/dist/test-utils.js.map +1 -1
- package/dist/{th-6O7Y6O2Q.js → th-IFPZP3HQ.js} +3 -7
- package/dist/th-IFPZP3HQ.js.map +1 -0
- package/dist/{tr-D4CQCSNO.js → tr-2GYEAMJ4.js} +3 -7
- package/dist/tr-2GYEAMJ4.js.map +1 -0
- package/dist/{uk-2HMQG6ND.js → uk-XCJBVLLD.js} +3 -7
- package/dist/uk-XCJBVLLD.js.map +1 -0
- package/dist/{vi-XVJ4RUEJ.js → vi-4FR7CB2F.js} +3 -7
- package/dist/vi-4FR7CB2F.js.map +1 -0
- package/dist/{zh-K2KDPGHK.js → zh-NSKFOINB.js} +3 -7
- package/dist/zh-NSKFOINB.js.map +1 -0
- package/package.json +17 -13
- package/src/components/Button/__tests__/Button.test.tsx +0 -2
- package/src/components/CodeMirrorRenderer.tsx +2 -0
- package/src/components/ErrorBoundary.tsx +0 -9
- package/src/components/ProtectedErrorBoundary.css +119 -0
- package/src/components/ProtectedErrorBoundary.tsx +24 -15
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +0 -1
- package/src/components/__tests__/ErrorBoundary.test.tsx +20 -13
- package/src/components/__tests__/LiveRegion.hooks.test.tsx +1 -1
- package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +2 -1
- package/src/components/__tests__/ResizeHandle.test.tsx +0 -1
- package/src/components/__tests__/SessionExpiryBanner.test.tsx +0 -1
- package/src/components/__tests__/StatusDisplay.test.tsx +0 -1
- package/src/components/__tests__/Toast.test.tsx +2 -3
- package/src/components/__tests__/Toolbar.test.tsx +0 -1
- package/src/components/annotation/annotations.css +14 -0
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +3 -5
- package/src/components/annotation-popups/__tests__/SharedPopupElements.test.tsx +0 -1
- package/src/components/branding/__tests__/SemiontBranding.test.tsx +1 -2
- package/src/components/layout/__tests__/LeftSidebar.test.tsx +5 -6
- package/src/components/layout/__tests__/PageLayout.test.tsx +1 -3
- package/src/components/layout/__tests__/SkipLinks.a11y.test.tsx +8 -8
- package/src/components/layout/__tests__/UnifiedHeader.test.tsx +12 -1
- package/src/components/modals/__tests__/KeyboardShortcutsHelpModal.test.tsx +0 -1
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +3 -4
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +1 -2
- package/src/components/modals/__tests__/SearchModal.basic.test.tsx +1 -1
- package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +0 -5
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +1 -2
- package/src/components/modals/__tests__/SearchModal.visual.test.tsx +2 -2
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +0 -1
- package/src/components/navigation/NavigationMenu.tsx +1 -1
- package/src/components/navigation/__tests__/Footer.a11y.test.tsx +4 -0
- package/src/components/navigation/__tests__/Footer.test.tsx +3 -6
- package/src/components/navigation/__tests__/NavigationMenu.a11y.test.tsx +1 -1
- package/src/components/navigation/__tests__/NavigationMenu.test.tsx +7 -9
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +0 -1
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +1 -2
- package/src/components/navigation/__tests__/SortableResourceTab.test.tsx +0 -1
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +6 -4
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +10 -19
- package/src/components/resource/AnnotateView.tsx +35 -37
- package/src/components/resource/BrowseView.tsx +31 -31
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +0 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +12 -14
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +0 -5
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +4 -6
- package/src/components/resource/__tests__/event-formatting.test.ts +1 -1
- package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
- package/src/components/resource/panels/JsonLdPanel.tsx +33 -16
- package/src/components/resource/panels/ReferencesPanel.tsx +1 -1
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -5
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +8 -7
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +14 -10
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +31 -18
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +7 -6
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +5 -6
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +19 -13
- package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +95 -426
- package/src/components/resource/panels/__tests__/PanelHeader.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +5 -5
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +40 -7
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +30 -32
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +6 -6
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +7 -6
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +0 -1
- package/src/components/viewers/__tests__/ImageViewer.test.tsx +0 -1
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +7 -10
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +38 -27
- package/src/features/admin-exchange/components/ImportProgress.tsx +28 -34
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +2 -0
- package/src/features/auth/__tests__/SignUpForm.a11y.test.tsx +11 -12
- package/src/features/auth/__tests__/SignUpForm.test.tsx +3 -3
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -0
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +11 -9
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +2 -1
- package/src/features/resource-compose/components/ResourceComposePage.tsx +36 -9
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -8
- package/src/features/resource-discovery/__tests__/ResourceCard.test.tsx +0 -1
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +33 -35
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +12 -11
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +204 -11
- package/src/features/resource-discovery/state/discover-state-unit.ts +70 -11
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +2 -2
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +10 -7
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +37 -1
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +14 -7
- package/src/integrations/__tests__/css-modules-helper.test.tsx +2 -3
- package/src/integrations/__tests__/styled-components-theme.test.ts +1 -3
- package/src/styles/features/exchange.css +0 -30
- package/src/styles/index.css +1 -0
- package/translations/ar.json +1 -3
- package/translations/bn.json +1 -3
- package/translations/cs.json +1 -3
- package/translations/da.json +4 -6
- package/translations/de.json +1 -3
- package/translations/el.json +1 -3
- package/translations/es.json +9 -11
- package/translations/fa.json +1 -3
- package/translations/fi.json +1 -3
- package/translations/fr.json +1 -3
- package/translations/he.json +1 -3
- package/translations/hi.json +1 -3
- package/translations/id.json +1 -3
- package/translations/it.json +1 -3
- package/translations/ja.json +1 -3
- package/translations/ko.json +1 -3
- package/translations/ms.json +1 -3
- package/translations/nl.json +2 -7
- package/translations/no.json +18 -20
- package/translations/pl.json +1 -3
- package/translations/pt.json +116 -118
- package/translations/ro.json +1 -3
- package/translations/sv.json +41 -43
- package/translations/th.json +1 -3
- package/translations/tr.json +1 -3
- package/translations/uk.json +1 -3
- package/translations/vi.json +1 -3
- package/translations/zh.json +1 -3
- package/dist/PdfAnnotationCanvas.client-CN3C3S55.js.map +0 -1
- package/dist/ar-U2EXWUMQ.js.map +0 -1
- package/dist/bn-DRJGV772.js.map +0 -1
- package/dist/cs-PTWDM23V.js.map +0 -1
- package/dist/da-KSNIKYSS.js.map +0 -1
- package/dist/de-F2XBEWFY.js.map +0 -1
- package/dist/el-DLD2GWAP.js.map +0 -1
- package/dist/es-WLPYWGB5.js.map +0 -1
- package/dist/fa-BAXHSDZG.js.map +0 -1
- package/dist/fi-FCHSYVOT.js.map +0 -1
- package/dist/fr-3UERBSL6.js.map +0 -1
- package/dist/he-F6F3FV2K.js.map +0 -1
- package/dist/hi-4BK6IK7Q.js.map +0 -1
- package/dist/id-7ECCWP3J.js.map +0 -1
- package/dist/it-234Z6XK6.js.map +0 -1
- package/dist/ja-PJWQI4OQ.js.map +0 -1
- package/dist/ko-APUEW2RS.js.map +0 -1
- package/dist/ms-PJBZWZWD.js.map +0 -1
- package/dist/nl-L4C3ZBCU.js.map +0 -1
- package/dist/no-QE5N5KNG.js.map +0 -1
- package/dist/pl-5Q2D23PD.js.map +0 -1
- package/dist/pt-AIGUOIOC.js.map +0 -1
- package/dist/ro-T56CSHTY.js.map +0 -1
- package/dist/sv-L4TJQ2UH.js.map +0 -1
- package/dist/th-6O7Y6O2Q.js.map +0 -1
- package/dist/tr-D4CQCSNO.js.map +0 -1
- package/dist/uk-2HMQG6ND.js.map +0 -1
- package/dist/vi-XVJ4RUEJ.js.map +0 -1
- package/dist/zh-K2KDPGHK.js.map +0 -1
- /package/dist/{chunk-3Q3TUKWP.js.map → chunk-Y2EEAOMZ.js.map} +0 -0
|
@@ -6,16 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
export interface ImportProgressTranslations {
|
|
8
8
|
phaseStarted: string;
|
|
9
|
-
phaseEntityTypes: string;
|
|
10
|
-
phaseResources: string;
|
|
11
|
-
phaseAnnotations: string;
|
|
12
9
|
phaseComplete: string;
|
|
13
10
|
phaseError: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
blobs: string;
|
|
11
|
+
statsEventsReplayed: string;
|
|
12
|
+
statsResourcesCreated: string;
|
|
13
|
+
statsAnnotationsCreated: string;
|
|
14
|
+
statsEntityTypesAdded: string;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
export interface ImportProgressProps {
|
|
@@ -27,13 +23,17 @@ export interface ImportProgressProps {
|
|
|
27
23
|
|
|
28
24
|
const PHASE_LABELS: Record<string, keyof ImportProgressTranslations> = {
|
|
29
25
|
started: 'phaseStarted',
|
|
30
|
-
'entity-types': 'phaseEntityTypes',
|
|
31
|
-
resources: 'phaseResources',
|
|
32
|
-
annotations: 'phaseAnnotations',
|
|
33
26
|
complete: 'phaseComplete',
|
|
34
27
|
error: 'phaseError',
|
|
35
28
|
};
|
|
36
29
|
|
|
30
|
+
const STAT_LABELS: Record<string, keyof ImportProgressTranslations> = {
|
|
31
|
+
eventsReplayed: 'statsEventsReplayed',
|
|
32
|
+
resourcesCreated: 'statsResourcesCreated',
|
|
33
|
+
annotationsCreated: 'statsAnnotationsCreated',
|
|
34
|
+
entityTypesAdded: 'statsEntityTypesAdded',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
37
|
export function ImportProgress({ phase, message, result, translations: t }: ImportProgressProps) {
|
|
38
38
|
const labelKey = PHASE_LABELS[phase];
|
|
39
39
|
const phaseLabel = labelKey ? t[labelKey] : phase;
|
|
@@ -41,6 +41,13 @@ export function ImportProgress({ phase, message, result, translations: t }: Impo
|
|
|
41
41
|
const isComplete = phase === 'complete';
|
|
42
42
|
const isError = phase === 'error';
|
|
43
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
|
+
|
|
44
51
|
return (
|
|
45
52
|
<div className="semiont-exchange__progress">
|
|
46
53
|
<div className={`semiont-exchange__phase-label${isError ? ' semiont-exchange__phase-label--error' : isComplete ? ' semiont-exchange__phase-label--complete' : ''}`}>
|
|
@@ -51,30 +58,17 @@ export function ImportProgress({ phase, message, result, translations: t }: Impo
|
|
|
51
58
|
<p className="semiont-exchange__progress-message">{message}</p>
|
|
52
59
|
)}
|
|
53
60
|
|
|
54
|
-
{isComplete &&
|
|
61
|
+
{isComplete && statEntries.length > 0 && (
|
|
55
62
|
<div className="semiont-exchange__result">
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<span className="semiont-exchange__result-value">{value}</span>
|
|
66
|
-
<span className="semiont-exchange__result-label">{label}</span>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
})}
|
|
70
|
-
</>
|
|
71
|
-
)}
|
|
72
|
-
|
|
73
|
-
{result.hashChainValid !== undefined && (
|
|
74
|
-
<div className={`semiont-exchange__hash-badge${result.hashChainValid ? ' semiont-exchange__hash-badge--valid' : ' semiont-exchange__hash-badge--invalid'}`}>
|
|
75
|
-
{result.hashChainValid ? t.hashChainValid : t.hashChainInvalid}
|
|
76
|
-
</div>
|
|
77
|
-
)}
|
|
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
|
+
})}
|
|
78
72
|
</div>
|
|
79
73
|
)}
|
|
80
74
|
|
|
@@ -45,6 +45,8 @@ const mockTranslations = {
|
|
|
45
45
|
errorEmailRequired: 'Email is required',
|
|
46
46
|
errorPasswordRequired: 'Password is required',
|
|
47
47
|
errorBackendUrlRequired: 'Backend URL is required',
|
|
48
|
+
errorBackendUrlInvalid: 'Backend URL is not valid',
|
|
49
|
+
errorBackendUrlUnreachable: 'Backend URL is unreachable',
|
|
48
50
|
tagline: 'make meaning',
|
|
49
51
|
};
|
|
50
52
|
|
|
@@ -54,7 +54,7 @@ describe('SignUpForm - Accessibility', () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it('should have no accessibility violations during loading state', async () => {
|
|
57
|
-
const onSignUp = vi.fn(() => new Promise(() => {})); // Never resolves
|
|
57
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>(() => {})); // Never resolves
|
|
58
58
|
|
|
59
59
|
const { container } = render(
|
|
60
60
|
<SignUpForm
|
|
@@ -138,7 +138,7 @@ describe('SignUpForm - Accessibility', () => {
|
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
it('should disable button during loading state', async () => {
|
|
141
|
-
const onSignUp = vi.fn(() => new Promise(() => {}));
|
|
141
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>(() => {}));
|
|
142
142
|
|
|
143
143
|
render(
|
|
144
144
|
<SignUpForm
|
|
@@ -197,13 +197,12 @@ describe('SignUpForm - Accessibility', () => {
|
|
|
197
197
|
it('should have Google icon inside button', () => {
|
|
198
198
|
const onSignUp = vi.fn();
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
200
|
+
render(
|
|
201
|
+
<SignUpForm
|
|
202
|
+
onSignUp={onSignUp}
|
|
203
|
+
Link={MockLink}
|
|
204
|
+
translations={mockTranslations} />
|
|
205
|
+
);
|
|
207
206
|
|
|
208
207
|
const button = screen.getByRole('button');
|
|
209
208
|
const svg = button.querySelector('svg');
|
|
@@ -214,7 +213,7 @@ describe('SignUpForm - Accessibility', () => {
|
|
|
214
213
|
});
|
|
215
214
|
|
|
216
215
|
it('should show loading spinner during sign-up', async () => {
|
|
217
|
-
const onSignUp = vi.fn(() => new Promise(() => {}));
|
|
216
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>(() => {}));
|
|
218
217
|
|
|
219
218
|
const { container } = render(
|
|
220
219
|
<SignUpForm
|
|
@@ -349,7 +348,7 @@ describe('SignUpForm - Accessibility', () => {
|
|
|
349
348
|
|
|
350
349
|
describe('Loading State Accessibility', () => {
|
|
351
350
|
it('should announce loading state to screen readers', async () => {
|
|
352
|
-
const onSignUp = vi.fn(() => new Promise(() => {}));
|
|
351
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>(() => {}));
|
|
353
352
|
|
|
354
353
|
render(
|
|
355
354
|
<SignUpForm
|
|
@@ -368,7 +367,7 @@ describe('SignUpForm - Accessibility', () => {
|
|
|
368
367
|
});
|
|
369
368
|
|
|
370
369
|
it('should maintain button focus during loading', async () => {
|
|
371
|
-
const onSignUp = vi.fn(() => new Promise(() => {}));
|
|
370
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>(() => {}));
|
|
372
371
|
|
|
373
372
|
render(
|
|
374
373
|
<SignUpForm
|
|
@@ -53,7 +53,7 @@ describe('SignUpForm', () => {
|
|
|
53
53
|
|
|
54
54
|
describe('Sign-Up Interaction', () => {
|
|
55
55
|
it('calls onSignUp when button is clicked', async () => {
|
|
56
|
-
const onSignUp = vi.fn<
|
|
56
|
+
const onSignUp = vi.fn<() => Promise<void>>().mockResolvedValue(undefined);
|
|
57
57
|
render(<SignUpForm onSignUp={onSignUp} Link={MockLink} translations={mockTranslations} />);
|
|
58
58
|
|
|
59
59
|
const button = screen.getByRole('button', { name: /Continue with Google/i });
|
|
@@ -63,7 +63,7 @@ describe('SignUpForm', () => {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it('shows loading state while signing up', async () => {
|
|
66
|
-
const onSignUp = vi.fn<
|
|
66
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>((resolve) => setTimeout(resolve, 100)));
|
|
67
67
|
render(<SignUpForm onSignUp={onSignUp} Link={MockLink} translations={mockTranslations} />);
|
|
68
68
|
|
|
69
69
|
const button = screen.getByRole('button');
|
|
@@ -83,7 +83,7 @@ describe('SignUpForm', () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it('disables button during loading', async () => {
|
|
86
|
-
const onSignUp = vi.fn<
|
|
86
|
+
const onSignUp = vi.fn<() => Promise<void>>(() => new Promise<void>((resolve) => setTimeout(resolve, 100)));
|
|
87
87
|
render(<SignUpForm onSignUp={onSignUp} Link={MockLink} translations={mockTranslations} />);
|
|
88
88
|
|
|
89
89
|
const button = screen.getByRole('button');
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from '@heroicons/react/24/outline';
|
|
14
14
|
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
15
15
|
import type { TagSchema } from '@semiont/sdk';
|
|
16
|
+
export type { TagSchema };
|
|
16
17
|
|
|
17
18
|
export interface TagSchemasPageProps {
|
|
18
19
|
// Data props
|
|
@@ -45,16 +45,12 @@ const createProps = (overrides?: Partial<LinkedDataPageProps>): LinkedDataPagePr
|
|
|
45
45
|
},
|
|
46
46
|
progress: {
|
|
47
47
|
phaseStarted: 'Starting…',
|
|
48
|
-
phaseEntityTypes: 'Entity types…',
|
|
49
|
-
phaseResources: 'Resources…',
|
|
50
|
-
phaseAnnotations: 'Annotations…',
|
|
51
48
|
phaseComplete: 'Complete',
|
|
52
49
|
phaseError: 'Failed',
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
blobs: 'Entity types',
|
|
50
|
+
statsEventsReplayed: 'Events replayed',
|
|
51
|
+
statsResourcesCreated: 'Resources created',
|
|
52
|
+
statsAnnotationsCreated: 'Annotations created',
|
|
53
|
+
statsEntityTypesAdded: 'Entity types added',
|
|
58
54
|
},
|
|
59
55
|
},
|
|
60
56
|
ToolbarPanels: () => <div data-testid="toolbar-panels" />,
|
|
@@ -100,9 +96,15 @@ describe('LinkedDataPage', () => {
|
|
|
100
96
|
it('renders ImportProgress with result on completion', () => {
|
|
101
97
|
render(<LinkedDataPage {...createProps({
|
|
102
98
|
importPhase: 'complete',
|
|
103
|
-
importResult: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 },
|
|
99
|
+
importResult: { stats: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } },
|
|
104
100
|
})} />);
|
|
105
101
|
expect(screen.getByText('Complete')).toBeInTheDocument();
|
|
102
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByText('Resources created')).toBeInTheDocument();
|
|
104
|
+
expect(screen.getByText('12')).toBeInTheDocument();
|
|
105
|
+
expect(screen.getByText('Annotations created')).toBeInTheDocument();
|
|
106
|
+
expect(screen.getByText('3')).toBeInTheDocument();
|
|
107
|
+
expect(screen.getByText('Entity types added')).toBeInTheDocument();
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
it('applies panel-open class when common panel is active', () => {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { describe, it, expect, vi } from 'vitest';
|
|
9
9
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
10
10
|
import { ResourceComposePage } from '../components/ResourceComposePage';
|
|
11
|
-
import type { ResourceComposePageProps
|
|
11
|
+
import type { ResourceComposePageProps } from '../components/ResourceComposePage';
|
|
12
12
|
import { createTestSemiontWrapper } from '../../../test-utils';
|
|
13
13
|
|
|
14
14
|
// Mock CodeMirrorRenderer to avoid CodeMirror dependencies
|
|
@@ -63,6 +63,7 @@ const createMockProps = (overrides?: Partial<ResourceComposePageProps>): Resourc
|
|
|
63
63
|
initialLocale: 'en',
|
|
64
64
|
theme: 'light',
|
|
65
65
|
showLineNumbers: false,
|
|
66
|
+
hoverDelayMs: 0,
|
|
66
67
|
activePanel: null,
|
|
67
68
|
onSaveResource: vi.fn().mockResolvedValue(undefined),
|
|
68
69
|
onCancel: vi.fn(),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import React, { useState, useEffect } from 'react';
|
|
10
10
|
import type { GatheredContext } from '@semiont/core';
|
|
11
|
-
import {
|
|
11
|
+
import { capabilitiesOf, AUTHORABLE_MEDIA_TYPES, MEDIA_TYPES, mediaTypeForExtension, isSupportedMediaType, LOCALES } from '@semiont/core';
|
|
12
12
|
import type { UploadProgress } from '@semiont/sdk';
|
|
13
13
|
import { type CloneData, type ReferenceData } from '../state/compose-page-state-unit';
|
|
14
14
|
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
@@ -17,6 +17,29 @@ import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
|
|
|
17
17
|
import { useFormAnnouncements } from '../../../components/LiveRegion';
|
|
18
18
|
import { UploadProgressBar } from './UploadProgressBar';
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Big tent: every registry type is uploadable. The `accept` hint lists all
|
|
22
|
+
* known extensions — it's only a hint; `handleFileUpload` never rejects.
|
|
23
|
+
*/
|
|
24
|
+
const UPLOAD_ACCEPT = Object.values(MEDIA_TYPES).map((c) => c.extension).join(',');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detection chain for uploaded files (same spirit as the CLI):
|
|
28
|
+
* 1. the browser's `file.type`, if it names a registry member;
|
|
29
|
+
* 2. otherwise the filename extension (`file.type` is routinely empty for
|
|
30
|
+
* `.md` and friends);
|
|
31
|
+
* 3. otherwise `application/octet-stream` — itself a registry member, so
|
|
32
|
+
* under the big tent every file is uploadable.
|
|
33
|
+
*/
|
|
34
|
+
function detectUploadMediaType(file: File): string {
|
|
35
|
+
if (file.type && isSupportedMediaType(file.type)) return file.type;
|
|
36
|
+
if (file.name.includes('.')) {
|
|
37
|
+
const byExt = mediaTypeForExtension(file.name.slice(file.name.lastIndexOf('.')));
|
|
38
|
+
if (byExt) return byExt;
|
|
39
|
+
}
|
|
40
|
+
return 'application/octet-stream';
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
export interface ResourceComposePageProps {
|
|
21
44
|
mode: 'new' | 'clone' | 'reference';
|
|
22
45
|
cloneData?: CloneData | null;
|
|
@@ -170,8 +193,9 @@ export function ResourceComposePage({
|
|
|
170
193
|
const file = e.target.files?.[0];
|
|
171
194
|
if (!file) return;
|
|
172
195
|
|
|
196
|
+
const detectedMediaType = detectUploadMediaType(file);
|
|
173
197
|
setUploadedFile(file);
|
|
174
|
-
setFileMimeType(
|
|
198
|
+
setFileMimeType(detectedMediaType);
|
|
175
199
|
setInputMethod('upload');
|
|
176
200
|
|
|
177
201
|
// Set file name as default resource name if empty
|
|
@@ -180,8 +204,9 @@ export function ResourceComposePage({
|
|
|
180
204
|
setNewResourceName(nameWithoutExt);
|
|
181
205
|
}
|
|
182
206
|
|
|
183
|
-
//
|
|
184
|
-
|
|
207
|
+
// Images and PDFs get an object-URL preview; everything else reads as text.
|
|
208
|
+
const detectedRender = capabilitiesOf(detectedMediaType)?.render;
|
|
209
|
+
if (detectedRender === 'image' || detectedRender === 'pdf') {
|
|
185
210
|
const previewUrl = URL.createObjectURL(file);
|
|
186
211
|
setFilePreviewUrl(previewUrl);
|
|
187
212
|
} else {
|
|
@@ -521,7 +546,7 @@ export function ResourceComposePage({
|
|
|
521
546
|
<div className="semiont-form__upload-area">
|
|
522
547
|
<input
|
|
523
548
|
type="file"
|
|
524
|
-
accept=
|
|
549
|
+
accept={UPLOAD_ACCEPT}
|
|
525
550
|
onChange={handleFileUpload}
|
|
526
551
|
className="semiont-form__upload-input"
|
|
527
552
|
disabled={isCreating}
|
|
@@ -563,7 +588,7 @@ export function ResourceComposePage({
|
|
|
563
588
|
)}
|
|
564
589
|
|
|
565
590
|
{/* Image Preview */}
|
|
566
|
-
{uploadedFile && filePreviewUrl &&
|
|
591
|
+
{uploadedFile && filePreviewUrl && capabilitiesOf(fileMimeType)?.render === 'image' && (
|
|
567
592
|
<div className="semiont-form__image-preview">
|
|
568
593
|
<p className="semiont-form__image-preview-label">Preview:</p>
|
|
569
594
|
<div className="semiont-form__image-preview-container">
|
|
@@ -594,9 +619,11 @@ export function ResourceComposePage({
|
|
|
594
619
|
disabled={isCreating}
|
|
595
620
|
className="semiont-select"
|
|
596
621
|
>
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
622
|
+
{AUTHORABLE_MEDIA_TYPES.map((mt) => (
|
|
623
|
+
<option key={mt} value={mt}>
|
|
624
|
+
{capabilitiesOf(mt)?.label ?? mt} ({mt})
|
|
625
|
+
</option>
|
|
626
|
+
))}
|
|
600
627
|
</select>
|
|
601
628
|
</div>
|
|
602
629
|
)}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { BehaviorSubject, type Observable, map } from 'rxjs';
|
|
2
|
-
import type { GatheredContext, AnnotationId,
|
|
2
|
+
import type { GatheredContext, AnnotationId, AccessToken, ResourceDescriptor, ResourceId } from '@semiont/core';
|
|
3
3
|
import { resourceId as makeResourceId, annotationId as makeAnnotationId } from '@semiont/core';
|
|
4
4
|
import { createDisposer } from '@semiont/sdk';
|
|
5
5
|
import type { StateUnit } from '@semiont/sdk';
|
|
6
6
|
import type { ShellStateUnit } from '../../../state/shell-state-unit';
|
|
7
7
|
import type { SemiontClient } from '@semiont/sdk';
|
|
8
|
-
import {
|
|
8
|
+
import { decodeWithCharset, extensionForMediaType } from '@semiont/core';
|
|
9
9
|
import type { UploadProgress } from '@semiont/sdk';
|
|
10
10
|
|
|
11
11
|
export type ComposeMode = 'new' | 'clone' | 'reference';
|
|
@@ -108,11 +108,8 @@ export function createComposePageStateUnit(
|
|
|
108
108
|
const tokenResult = await client.yield.fromToken(params.token!);
|
|
109
109
|
if (tokenResult && auth) {
|
|
110
110
|
const rId = makeResourceId(tokenResult['@id']);
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
accept: mediaType as ContentFormat,
|
|
114
|
-
});
|
|
115
|
-
const content = decodeWithCharset(data, mediaType);
|
|
111
|
+
const { data, contentType } = await client.browse.resourceRepresentation(rId);
|
|
112
|
+
const content = decodeWithCharset(data, contentType);
|
|
116
113
|
cloneData$.next({ sourceResource: tokenResult, sourceContent: content });
|
|
117
114
|
}
|
|
118
115
|
} catch {
|
|
@@ -143,7 +140,7 @@ export function createComposePageStateUnit(
|
|
|
143
140
|
mimeType = saveParams.format ?? 'application/octet-stream';
|
|
144
141
|
} else {
|
|
145
142
|
const blob = new Blob([saveParams.content || ''], { type: saveParams.format ?? 'application/octet-stream' });
|
|
146
|
-
const extension = saveParams.format
|
|
143
|
+
const extension = extensionForMediaType(saveParams.format ?? 'application/octet-stream');
|
|
147
144
|
fileToUpload = new File([blob], saveParams.name + extension, { type: saveParams.format ?? 'application/octet-stream' });
|
|
148
145
|
mimeType = saveParams.format ?? 'application/octet-stream';
|
|
149
146
|
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { describe, it, expect, vi } from 'vitest';
|
|
8
8
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
9
9
|
import { ResourceCard } from '../components/ResourceCard';
|
|
10
|
-
import type { ResourceCardProps } from '../components/ResourceCard';
|
|
11
10
|
|
|
12
11
|
const createMockResource = (overrides?: any) => ({
|
|
13
12
|
'@context': 'https://www.w3.org/ns/anno.jsonld',
|
|
@@ -10,10 +10,12 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|
|
10
10
|
import { ResourceDiscoveryPage } from '../components/ResourceDiscoveryPage';
|
|
11
11
|
import type { ResourceDiscoveryPageProps } from '../components/ResourceDiscoveryPage';
|
|
12
12
|
import { createTestSemiontWrapper } from '../../../test-utils';
|
|
13
|
+
import { resourceId } from '@semiont/core';
|
|
14
|
+
import type { ResourceDescriptor } from '@semiont/core';
|
|
13
15
|
|
|
14
|
-
const createMockResource = (id: string, name: string, entityTypes: string[] = []) => ({
|
|
16
|
+
const createMockResource = (id: string, name: string, entityTypes: string[] = []): ResourceDescriptor => ({
|
|
15
17
|
'@context': 'https://www.w3.org/ns/anno.jsonld',
|
|
16
|
-
'@id': id,
|
|
18
|
+
'@id': resourceId(id),
|
|
17
19
|
'@type': 'schema:DigitalDocument',
|
|
18
20
|
name,
|
|
19
21
|
description: `Description for ${name}`,
|
|
@@ -31,6 +33,8 @@ const createMockProps = (overrides?: Partial<ResourceDiscoveryPageProps>): Resou
|
|
|
31
33
|
isSearching: false,
|
|
32
34
|
searchQuery: '',
|
|
33
35
|
onSearchQueryChange: vi.fn(),
|
|
36
|
+
selectedEntityType: '',
|
|
37
|
+
onSelectedEntityTypeChange: vi.fn(),
|
|
34
38
|
theme: 'light',
|
|
35
39
|
showLineNumbers: false,
|
|
36
40
|
activePanel: null,
|
|
@@ -254,65 +258,59 @@ describe('ResourceDiscoveryPage', () => {
|
|
|
254
258
|
expect(screen.getByRole('button', { name: 'Report' })).toBeInTheDocument();
|
|
255
259
|
});
|
|
256
260
|
|
|
257
|
-
it('
|
|
261
|
+
it('calls onSelectedEntityTypeChange when a filter chip is clicked', () => {
|
|
262
|
+
const onSelectedEntityTypeChange = vi.fn();
|
|
258
263
|
const props = createMockProps({
|
|
259
|
-
recentDocuments: [
|
|
260
|
-
createMockResource('1', 'Doc 1', ['Document']),
|
|
261
|
-
createMockResource('2', 'Doc 2', ['Article']),
|
|
262
|
-
createMockResource('3', 'Doc 3', ['Document']),
|
|
263
|
-
],
|
|
264
264
|
entityTypes: ['Document', 'Article'],
|
|
265
|
+
onSelectedEntityTypeChange,
|
|
265
266
|
});
|
|
266
267
|
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
267
268
|
|
|
268
|
-
|
|
269
|
-
expect(
|
|
270
|
-
expect(screen.getByText('Doc 2')).toBeInTheDocument();
|
|
271
|
-
expect(screen.getByText('Doc 3')).toBeInTheDocument();
|
|
272
|
-
|
|
273
|
-
// Filter by Document
|
|
274
|
-
const documentButton = screen.getByRole('button', { name: 'Document' });
|
|
275
|
-
fireEvent.click(documentButton);
|
|
269
|
+
fireEvent.click(screen.getByRole('button', { name: 'Document' }));
|
|
270
|
+
expect(onSelectedEntityTypeChange).toHaveBeenCalledWith('Document');
|
|
276
271
|
|
|
277
|
-
|
|
278
|
-
expect(
|
|
279
|
-
expect(screen.getByText('Doc 3')).toBeInTheDocument();
|
|
272
|
+
fireEvent.click(screen.getByRole('button', { name: 'Article' }));
|
|
273
|
+
expect(onSelectedEntityTypeChange).toHaveBeenCalledWith('Article');
|
|
280
274
|
});
|
|
281
275
|
|
|
282
|
-
it('shows filtered heading when
|
|
276
|
+
it('shows filtered heading when selectedEntityType prop is set', () => {
|
|
283
277
|
const props = createMockProps({
|
|
284
278
|
recentDocuments: [createMockResource('1', 'Doc 1', ['Document'])],
|
|
285
279
|
entityTypes: ['Document'],
|
|
280
|
+
selectedEntityType: 'Document',
|
|
286
281
|
});
|
|
287
282
|
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
288
283
|
|
|
289
|
-
const documentButton = screen.getByRole('button', { name: 'Document' });
|
|
290
|
-
fireEvent.click(documentButton);
|
|
291
|
-
|
|
292
284
|
expect(screen.getByText('Documents tagged with Document')).toBeInTheDocument();
|
|
293
285
|
});
|
|
294
286
|
|
|
295
|
-
it('
|
|
287
|
+
it('calls onSelectedEntityTypeChange with empty string when "All" is clicked', () => {
|
|
288
|
+
const onSelectedEntityTypeChange = vi.fn();
|
|
289
|
+
const props = createMockProps({
|
|
290
|
+
entityTypes: ['Document', 'Article'],
|
|
291
|
+
selectedEntityType: 'Document',
|
|
292
|
+
onSelectedEntityTypeChange,
|
|
293
|
+
});
|
|
294
|
+
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
295
|
+
|
|
296
|
+
fireEvent.click(screen.getByRole('button', { name: 'All' }));
|
|
297
|
+
expect(onSelectedEntityTypeChange).toHaveBeenCalledWith('');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('renders the recentDocuments prop as-is without applying any post-filter', () => {
|
|
301
|
+
// The component is now controlled — backend filtering means
|
|
302
|
+
// `recentDocuments` already contains only the resources matching the
|
|
303
|
+
// active `selectedEntityType`. The component must not re-filter.
|
|
296
304
|
const props = createMockProps({
|
|
297
305
|
recentDocuments: [
|
|
298
306
|
createMockResource('1', 'Doc 1', ['Document']),
|
|
299
307
|
createMockResource('2', 'Doc 2', ['Article']),
|
|
300
308
|
],
|
|
301
309
|
entityTypes: ['Document', 'Article'],
|
|
310
|
+
selectedEntityType: 'Document',
|
|
302
311
|
});
|
|
303
312
|
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
304
313
|
|
|
305
|
-
// Filter by Document
|
|
306
|
-
const documentButton = screen.getByRole('button', { name: 'Document' });
|
|
307
|
-
fireEvent.click(documentButton);
|
|
308
|
-
|
|
309
|
-
expect(screen.getByText('Doc 1')).toBeInTheDocument();
|
|
310
|
-
expect(screen.queryByText('Doc 2')).not.toBeInTheDocument();
|
|
311
|
-
|
|
312
|
-
// Click All
|
|
313
|
-
const allButton = screen.getByRole('button', { name: 'All' });
|
|
314
|
-
fireEvent.click(allButton);
|
|
315
|
-
|
|
316
314
|
expect(screen.getByText('Doc 1')).toBeInTheDocument();
|
|
317
315
|
expect(screen.getByText('Doc 2')).toBeInTheDocument();
|
|
318
316
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* All dependencies passed as props - no Next.js hooks!
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, {
|
|
8
|
+
import React, { useCallback, useRef } from 'react';
|
|
9
9
|
import { getResourceId } from '@semiont/core';
|
|
10
10
|
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
11
11
|
import { useRovingTabIndex } from '../../../hooks/useRovingTabIndex';
|
|
@@ -26,6 +26,11 @@ export interface ResourceDiscoveryPageProps {
|
|
|
26
26
|
searchQuery: string;
|
|
27
27
|
onSearchQueryChange: (query: string) => void;
|
|
28
28
|
|
|
29
|
+
// Controlled entity-type filter — owned by the state unit so filtering
|
|
30
|
+
// pushes to the backend rather than running as a post-fetch array filter.
|
|
31
|
+
selectedEntityType: string;
|
|
32
|
+
onSelectedEntityTypeChange: (entityType: string) => void;
|
|
33
|
+
|
|
29
34
|
// UI state props
|
|
30
35
|
theme: 'light' | 'dark';
|
|
31
36
|
showLineNumbers: boolean;
|
|
@@ -67,6 +72,8 @@ export function ResourceDiscoveryPage({
|
|
|
67
72
|
isSearching,
|
|
68
73
|
searchQuery,
|
|
69
74
|
onSearchQueryChange,
|
|
75
|
+
selectedEntityType,
|
|
76
|
+
onSelectedEntityTypeChange,
|
|
70
77
|
theme,
|
|
71
78
|
showLineNumbers,
|
|
72
79
|
activePanel,
|
|
@@ -75,17 +82,11 @@ export function ResourceDiscoveryPage({
|
|
|
75
82
|
translations: t,
|
|
76
83
|
ToolbarPanels,
|
|
77
84
|
}: ResourceDiscoveryPageProps) {
|
|
78
|
-
const [selectedEntityType, setSelectedEntityType] = useState<string>('');
|
|
79
|
-
|
|
80
85
|
const hasSearchQuery = searchQuery.trim() !== '';
|
|
81
86
|
|
|
82
87
|
// When searching, render search results; otherwise render recent.
|
|
83
|
-
|
|
84
|
-
const filteredResources =
|
|
85
|
-
? baseDocuments
|
|
86
|
-
: baseDocuments.filter((resource: ResourceDescriptor) =>
|
|
87
|
-
resource.entityTypes && resource.entityTypes.includes(selectedEntityType)
|
|
88
|
-
);
|
|
88
|
+
// Both already arrive entity-type-filtered from the backend — no post-filter here.
|
|
89
|
+
const filteredResources = hasSearchQuery ? searchDocuments : recentDocuments;
|
|
89
90
|
|
|
90
91
|
// Roving tabindex for entity type filters
|
|
91
92
|
const entityFilterRoving = useRovingTabIndex<HTMLDivElement>(
|
|
@@ -105,8 +106,8 @@ export function ResourceDiscoveryPage({
|
|
|
105
106
|
|
|
106
107
|
// Memoized callbacks
|
|
107
108
|
const handleEntityTypeFilter = useCallback((entityType: string) => {
|
|
108
|
-
|
|
109
|
-
}, []);
|
|
109
|
+
onSelectedEntityTypeChange(entityType);
|
|
110
|
+
}, [onSelectedEntityTypeChange]);
|
|
110
111
|
|
|
111
112
|
const openResource = useCallback((resource: ResourceDescriptor) => {
|
|
112
113
|
const resourceId = getResourceId(resource);
|