@semiont/react-ui 0.5.22 → 0.5.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ar-ZHKO4XDR.js → ar-JRTKCMTF.js} +11 -4
- package/dist/ar-JRTKCMTF.js.map +1 -0
- package/dist/{bn-3BUDKAK5.js → bn-LO6EDSCC.js} +11 -4
- package/dist/bn-LO6EDSCC.js.map +1 -0
- package/dist/{chunk-PFUXQPWJ.js → chunk-4QMTEPQ3.js} +92 -60
- package/dist/chunk-4QMTEPQ3.js.map +1 -0
- package/dist/{chunk-R52YAGHK.js → chunk-5Y5JDCKC.js} +11 -4
- package/dist/chunk-5Y5JDCKC.js.map +1 -0
- package/dist/{cs-2EFEUKTW.js → cs-LCHHOR4O.js} +11 -4
- package/dist/cs-LCHHOR4O.js.map +1 -0
- package/dist/{da-SUD3Q44Q.js → da-TARGPMJR.js} +11 -4
- package/dist/da-TARGPMJR.js.map +1 -0
- package/dist/{de-6IC5QRX7.js → de-SBH4QBPJ.js} +11 -4
- package/dist/de-SBH4QBPJ.js.map +1 -0
- package/dist/{el-Q4NAXFRC.js → el-2D2OLGWB.js} +11 -4
- package/dist/el-2D2OLGWB.js.map +1 -0
- package/dist/{en-P2L4X27N.js → en-QFIEG3CA.js} +2 -2
- package/dist/{es-YUKZBD4W.js → es-U6YK2LX6.js} +11 -4
- package/dist/es-U6YK2LX6.js.map +1 -0
- package/dist/{fa-OJ47LILE.js → fa-S7SZE37M.js} +11 -4
- package/dist/fa-S7SZE37M.js.map +1 -0
- package/dist/{fi-QFFL5CUB.js → fi-CBL3NIXN.js} +11 -4
- package/dist/fi-CBL3NIXN.js.map +1 -0
- package/dist/{fr-BOQZZWP4.js → fr-EABUHV6S.js} +11 -4
- package/dist/fr-EABUHV6S.js.map +1 -0
- package/dist/{he-LXDY3TFA.js → he-THWD244Y.js} +11 -4
- package/dist/he-THWD244Y.js.map +1 -0
- package/dist/{hi-HOJER77V.js → hi-GHGFZJ44.js} +11 -4
- package/dist/hi-GHGFZJ44.js.map +1 -0
- package/dist/{id-I5WQ6EWD.js → id-POZOUMDW.js} +11 -4
- package/dist/id-POZOUMDW.js.map +1 -0
- package/dist/index.d.ts +269 -69
- package/dist/index.js +2080 -1861
- package/dist/index.js.map +1 -1
- package/dist/{it-UYH642IJ.js → it-KDYJADSH.js} +11 -4
- package/dist/it-KDYJADSH.js.map +1 -0
- package/dist/{ja-JTVWAC4L.js → ja-Z5Z6ZF3P.js} +11 -4
- package/dist/ja-Z5Z6ZF3P.js.map +1 -0
- package/dist/{ko-IHXFMOIZ.js → ko-7X3XUXRN.js} +11 -4
- package/dist/ko-7X3XUXRN.js.map +1 -0
- package/dist/{ms-JH25VFLO.js → ms-BTWEIW43.js} +11 -4
- package/dist/ms-BTWEIW43.js.map +1 -0
- package/dist/{nl-SURXW7IQ.js → nl-IS2CGF5I.js} +11 -4
- package/dist/nl-IS2CGF5I.js.map +1 -0
- package/dist/{no-HXIKQFDZ.js → no-AAMEVA26.js} +11 -4
- package/dist/no-AAMEVA26.js.map +1 -0
- package/dist/{pl-7A4KU6XK.js → pl-PVXUQYFT.js} +11 -4
- package/dist/pl-PVXUQYFT.js.map +1 -0
- package/dist/{pt-6MGYP7Q5.js → pt-3ITQ76TL.js} +11 -4
- package/dist/pt-3ITQ76TL.js.map +1 -0
- package/dist/{ro-IROCHNIA.js → ro-FRB4IIVS.js} +11 -4
- package/dist/ro-FRB4IIVS.js.map +1 -0
- package/dist/{sv-WDJD75CV.js → sv-DS3RO24M.js} +11 -4
- package/dist/sv-DS3RO24M.js.map +1 -0
- package/dist/test-utils.js +9 -3
- package/dist/test-utils.js.map +1 -1
- package/dist/{th-RIO2E6B3.js → th-AX66MWWB.js} +11 -4
- package/dist/th-AX66MWWB.js.map +1 -0
- package/dist/{tr-TZ7ERCIN.js → tr-7KTB67TT.js} +11 -4
- package/dist/tr-7KTB67TT.js.map +1 -0
- package/dist/{uk-ZFWDKLUE.js → uk-SMQS2MAQ.js} +11 -4
- package/dist/uk-SMQS2MAQ.js.map +1 -0
- package/dist/{vi-UIOSP2JG.js → vi-D3JXL2EZ.js} +11 -4
- package/dist/vi-D3JXL2EZ.js.map +1 -0
- package/dist/{zh-6R6YUY46.js → zh-S754AWFS.js} +11 -4
- package/dist/zh-S754AWFS.js.map +1 -0
- package/package.json +4 -4
- package/src/components/annotation-popups/JsonLdView.tsx +1 -1
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +8 -2
- package/src/components/modals/ReferenceWizardModal.tsx +2 -1
- package/src/components/modals/ResourceGenerateModal.tsx +10 -4
- package/src/components/modals/ResourceSearchModal.tsx +3 -1
- package/src/components/modals/SearchModal.tsx +3 -1
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +5 -0
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +2 -1
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +2 -1
- package/src/components/resource/AnnotateView.tsx +70 -138
- package/src/components/resource/AnnotationHistory.tsx +53 -17
- package/src/components/resource/BrowseView.tsx +3 -2
- package/src/components/resource/ResourceViewer.tsx +39 -22
- package/src/components/resource/__tests__/AnnotateView.registry.test.tsx +114 -0
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +63 -41
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +2 -1
- package/src/components/resource/__tests__/ResourceViewer.multi-mount-invalidation.test.tsx +140 -0
- package/src/components/resource/__tests__/ResourceViewer.registry.test.tsx +89 -0
- package/src/components/resource/annotate-renderers.tsx +155 -0
- package/src/components/resource/panels/JsonLdPanel.tsx +1 -1
- package/src/components/resource/panels/ReferencesPanel.tsx +38 -3
- package/src/components/resource/panels/TagEntry.tsx +5 -1
- package/src/components/resource/panels/TaggingPanel.tsx +6 -1
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +41 -0
- package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +8 -2
- package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +90 -0
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +2 -2
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +2 -2
- package/src/components/resource/panels/__tests__/UnifiedAnnotationsPanel.failure.test.tsx +78 -0
- package/src/components/settings/SettingsPanel.tsx +6 -2
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +28 -3
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +3 -11
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -3
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -1
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -3
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +1 -1
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -8
- package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -3
- package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +10 -9
- package/src/features/admin-security/state/admin-security-state-unit.ts +3 -2
- package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -1
- package/src/features/admin-users/components/AdminUsersPage.tsx +0 -3
- package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +7 -6
- package/src/features/admin-users/state/admin-users-state-unit.ts +3 -2
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +36 -26
- package/src/features/auth-welcome/state/welcome-state-unit.ts +7 -2
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -2
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +0 -3
- package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +37 -12
- package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +9 -10
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +3 -5
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +0 -3
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +3 -5
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +0 -3
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -1
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -3
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +17 -7
- package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -3
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +33 -12
- package/src/features/resource-compose/state/compose-page-state-unit.ts +9 -8
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +3 -2
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +25 -3
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +90 -25
- package/src/features/resource-discovery/state/discover-state-unit.ts +30 -30
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +9 -5
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +70 -40
- package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +111 -8
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +167 -27
- package/src/features/resource-viewer/state/resource-loader-state-unit.ts +74 -9
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +29 -28
- package/translations/ar.json +10 -3
- package/translations/bn.json +10 -3
- package/translations/cs.json +10 -3
- package/translations/da.json +10 -3
- package/translations/de.json +10 -3
- package/translations/el.json +10 -3
- package/translations/en.json +10 -3
- package/translations/es.json +10 -3
- package/translations/fa.json +10 -3
- package/translations/fi.json +10 -3
- package/translations/fr.json +10 -3
- package/translations/he.json +10 -3
- package/translations/hi.json +10 -3
- package/translations/id.json +10 -3
- package/translations/it.json +10 -3
- package/translations/ja.json +10 -3
- package/translations/ko.json +10 -3
- package/translations/ms.json +10 -3
- package/translations/nl.json +10 -3
- package/translations/no.json +10 -3
- package/translations/pl.json +10 -3
- package/translations/pt.json +10 -3
- package/translations/ro.json +10 -3
- package/translations/sv.json +10 -3
- package/translations/th.json +10 -3
- package/translations/tr.json +10 -3
- package/translations/uk.json +10 -3
- package/translations/vi.json +10 -3
- package/translations/zh.json +10 -3
- package/dist/ar-ZHKO4XDR.js.map +0 -1
- package/dist/bn-3BUDKAK5.js.map +0 -1
- package/dist/chunk-PFUXQPWJ.js.map +0 -1
- package/dist/chunk-R52YAGHK.js.map +0 -1
- package/dist/cs-2EFEUKTW.js.map +0 -1
- package/dist/da-SUD3Q44Q.js.map +0 -1
- package/dist/de-6IC5QRX7.js.map +0 -1
- package/dist/el-Q4NAXFRC.js.map +0 -1
- package/dist/es-YUKZBD4W.js.map +0 -1
- package/dist/fa-OJ47LILE.js.map +0 -1
- package/dist/fi-QFFL5CUB.js.map +0 -1
- package/dist/fr-BOQZZWP4.js.map +0 -1
- package/dist/he-LXDY3TFA.js.map +0 -1
- package/dist/hi-HOJER77V.js.map +0 -1
- package/dist/id-I5WQ6EWD.js.map +0 -1
- package/dist/it-UYH642IJ.js.map +0 -1
- package/dist/ja-JTVWAC4L.js.map +0 -1
- package/dist/ko-IHXFMOIZ.js.map +0 -1
- package/dist/ms-JH25VFLO.js.map +0 -1
- package/dist/nl-SURXW7IQ.js.map +0 -1
- package/dist/no-HXIKQFDZ.js.map +0 -1
- package/dist/pl-7A4KU6XK.js.map +0 -1
- package/dist/pt-6MGYP7Q5.js.map +0 -1
- package/dist/ro-IROCHNIA.js.map +0 -1
- package/dist/sv-WDJD75CV.js.map +0 -1
- package/dist/th-RIO2E6B3.js.map +0 -1
- package/dist/tr-TZ7ERCIN.js.map +0 -1
- package/dist/uk-ZFWDKLUE.js.map +0 -1
- package/dist/vi-UIOSP2JG.js.map +0 -1
- package/dist/zh-6R6YUY46.js.map +0 -1
- /package/dist/{en-P2L4X27N.js.map → en-QFIEG3CA.js.map} +0 -0
|
@@ -20,13 +20,14 @@ import { JsonLdPanel } from '@semiont/react-ui';
|
|
|
20
20
|
import { Toolbar } from '@semiont/react-ui';
|
|
21
21
|
import { useResourceLoadingAnnouncements } from '@semiont/react-ui';
|
|
22
22
|
import { ResourceViewer } from '@semiont/react-ui';
|
|
23
|
+
import type { BrowseMediaRenderers, AnnotateMediaRenderers } from '@semiont/react-ui';
|
|
23
24
|
import { useObservable } from '@semiont/react-ui';
|
|
24
25
|
import { useResourceContent } from '../../../hooks/useResourceContent';
|
|
25
26
|
import { useMediaToken } from '../../../hooks/useMediaToken';
|
|
26
27
|
import { useToast } from '../../../components/Toast';
|
|
27
28
|
import { useOutcomeToasts } from '../../../hooks/useOutcomeToasts';
|
|
28
29
|
import { useTheme } from '../../../contexts/ThemeContext';
|
|
29
|
-
import { useLineNumbers } from '../../../
|
|
30
|
+
import { useLineNumbers } from '../../../contexts/LineNumbersContext';
|
|
30
31
|
import { useHoverDelay } from '../../../hooks/useHoverDelay';
|
|
31
32
|
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
32
33
|
import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
|
|
@@ -35,7 +36,7 @@ import { getSelectorType } from '../../../lib/media-shapes';
|
|
|
35
36
|
import { useResourceAnnotations } from '../../../contexts/ResourceAnnotationsContext';
|
|
36
37
|
import { useSemiont } from '../../../session/SemiontProvider';
|
|
37
38
|
import { createResourceViewerPageStateUnit } from '../state/resource-viewer-page-state-unit';
|
|
38
|
-
import {
|
|
39
|
+
import { useSessionStateUnit } from '../../../hooks/useSessionStateUnit';
|
|
39
40
|
import { useShellStateUnit } from '../../../hooks/useShellStateUnit';
|
|
40
41
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
41
42
|
import { ReferenceWizardModal } from '../../../components/modals/ReferenceWizardModal';
|
|
@@ -92,6 +93,14 @@ export interface ResourceViewerPageProps {
|
|
|
92
93
|
* Name of the active knowledge base (for display in panels)
|
|
93
94
|
*/
|
|
94
95
|
knowledgeBaseName?: string | undefined;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Media-renderer overrides, forwarded to `ResourceViewer`. Present at this
|
|
99
|
+
* tier too so a host embedding the whole page — not just the viewer — can
|
|
100
|
+
* still swap a renderer. See .plans/ANNOTATE-RENDERER-REGISTRY.md (D5)
|
|
101
|
+
*/
|
|
102
|
+
browseRenderers?: BrowseMediaRenderers;
|
|
103
|
+
annotateRenderers?: AnnotateMediaRenderers;
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
/**
|
|
@@ -107,8 +116,6 @@ export interface ResourceViewerPageProps {
|
|
|
107
116
|
* @subscribes yield:clone - Clone the current resource
|
|
108
117
|
* @subscribes beckon:sparkle - Trigger sparkle animation
|
|
109
118
|
* @subscribes mark:added - Annotation was created (sparkle)
|
|
110
|
-
* @subscribes settings:theme-changed - UI theme changed
|
|
111
|
-
* @subscribes settings:line-numbers-toggled - Line numbers display toggled
|
|
112
119
|
* @subscribes browse:reference-navigate - Navigate to a referenced document
|
|
113
120
|
* @subscribes browse:entity-type-clicked - Navigate filtered by entity type
|
|
114
121
|
*
|
|
@@ -126,6 +133,8 @@ export function ResourceViewerPage({
|
|
|
126
133
|
refetchDocument,
|
|
127
134
|
streamStatus,
|
|
128
135
|
knowledgeBaseName,
|
|
136
|
+
browseRenderers,
|
|
137
|
+
annotateRenderers,
|
|
129
138
|
}: ResourceViewerPageProps) {
|
|
130
139
|
// Translations
|
|
131
140
|
const tw = useTranslations('ReferenceWizard');
|
|
@@ -148,8 +157,8 @@ export function ResourceViewerPage({
|
|
|
148
157
|
|
|
149
158
|
// UI state hooks
|
|
150
159
|
const { showError, showSuccess } = useToast();
|
|
151
|
-
const { theme
|
|
152
|
-
const { showLineNumbers
|
|
160
|
+
const { theme } = useTheme();
|
|
161
|
+
const { showLineNumbers } = useLineNumbers();
|
|
153
162
|
const { hoverDelayMs } = useHoverDelay();
|
|
154
163
|
const { triggerSparkleAnimation, clearNewAnnotationId, newAnnotationIds } = useResourceAnnotations();
|
|
155
164
|
|
|
@@ -185,27 +194,40 @@ export function ResourceViewerPage({
|
|
|
185
194
|
|
|
186
195
|
// Composite state unit — owns all flow VMs, wizard state, annotations, entity types
|
|
187
196
|
const browseStateUnit = useShellStateUnit();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
const
|
|
197
|
+
// Session-typed + session-keyed (SESSION-TYPED-FACTORIES.md): no `!`, no
|
|
198
|
+
// construction without a session, dispose-first rebuild on session swap.
|
|
199
|
+
const stateUnit = useSessionStateUnit(
|
|
200
|
+
session ?? undefined,
|
|
201
|
+
(s) => createResourceViewerPageStateUnit(s, rUri, locale, browseStateUnit),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const annotations = useObservable(stateUnit?.annotations.value$) ?? [];
|
|
205
|
+
const annotationsError = useObservable(stateUnit?.annotations.error$) ?? null;
|
|
206
|
+
const groups = useObservable(stateUnit?.annotationGroups$);
|
|
207
|
+
const allEntityTypes = useObservable(stateUnit?.entityTypes.value$) ?? [];
|
|
208
|
+
const entityTypesError = useObservable(stateUnit?.entityTypes.error$) ?? null;
|
|
209
|
+
// Three states, not two: a terminally failed list has no value EITHER, so
|
|
210
|
+
// deriving "loading" from `undefined` leaves a dead request spinning for
|
|
211
|
+
// ever. See .plans/PANEL-FAILURE-STATES.md
|
|
212
|
+
const referencedBy = useObservable(stateUnit?.referencedBy.value$) ?? [];
|
|
213
|
+
const referencedByLoading = useObservable(stateUnit?.referencedBy.loading$) ?? true;
|
|
214
|
+
const referencedByError = useObservable(stateUnit?.referencedBy.error$) ?? null;
|
|
215
|
+
const events = useObservable(stateUnit?.events.value$) ?? [];
|
|
216
|
+
const eventsLoading = useObservable(stateUnit?.events.loading$) ?? true;
|
|
217
|
+
const eventsError = useObservable(stateUnit?.events.error$) ?? null;
|
|
218
|
+
const hoveredAnnotationId = useObservable(stateUnit?.beckon.hoveredAnnotationId$) ?? null;
|
|
219
|
+
const pendingAnnotation = useObservable(stateUnit?.mark.pendingAnnotation$) ?? null;
|
|
220
|
+
const assistingMotivation = useObservable(stateUnit?.mark.assistingMotivation$) ?? null;
|
|
221
|
+
const progress = useObservable(stateUnit?.mark.progress$) ?? null;
|
|
222
|
+
const activePanel = useObservable(stateUnit?.browse.activePanel$) ?? null;
|
|
223
|
+
const scrollToAnnotationId = useObservable(stateUnit?.browse.scrollToAnnotationId$) ?? null;
|
|
224
|
+
const panelInitialTab = useObservable(stateUnit?.browse.panelInitialTab$) ?? null;
|
|
225
|
+
const onScrollCompleted = stateUnit?.browse.onScrollCompleted;
|
|
226
|
+
const generationProgress = useObservable(stateUnit?.yield.progress$) ?? null;
|
|
227
|
+
const gatherContext = useObservable(stateUnit?.gather.context$) ?? null;
|
|
228
|
+
const gatherLoading = useObservable(stateUnit?.gather.loading$) ?? false;
|
|
229
|
+
const gatherError = useObservable(stateUnit?.gather.error$) ?? null;
|
|
230
|
+
const wizardState = useObservable(stateUnit?.wizard$);
|
|
209
231
|
const wizardOpen = wizardState?.open ?? false;
|
|
210
232
|
const wizardAnnotationId = wizardState?.annotationId ?? null;
|
|
211
233
|
const wizardResourceId = wizardState?.resourceId ?? null;
|
|
@@ -214,12 +236,12 @@ export function ResourceViewerPage({
|
|
|
214
236
|
const [generateOpen, setGenerateOpen] = useState(false);
|
|
215
237
|
|
|
216
238
|
const handleWizardClose = useCallback(() => {
|
|
217
|
-
stateUnit
|
|
239
|
+
stateUnit?.closeWizard();
|
|
218
240
|
}, [stateUnit]);
|
|
219
241
|
|
|
220
242
|
const handleWizardGenerateSubmit = useCallback((referenceId: string, config: GenerationConfig) => {
|
|
221
243
|
clearNewAnnotationId(annotationId(referenceId));
|
|
222
|
-
stateUnit
|
|
244
|
+
stateUnit?.yield.generate(referenceId, {
|
|
223
245
|
title: config.title,
|
|
224
246
|
storageUri: config.storagePath,
|
|
225
247
|
prompt: config.prompt,
|
|
@@ -239,7 +261,7 @@ export function ResourceViewerPage({
|
|
|
239
261
|
// NOT a toast. `generateFromResource` is Phase 6 (the @semiont/sdk session);
|
|
240
262
|
// this is declared RED until that method lands. Do not re-impl it here.
|
|
241
263
|
const handleResourceGenerateSubmit = useCallback((_resourceId: string, config: GenerationConfig) => {
|
|
242
|
-
stateUnit
|
|
264
|
+
stateUnit?.yield.generateFromResource({
|
|
243
265
|
title: config.title,
|
|
244
266
|
storageUri: config.storagePath,
|
|
245
267
|
...(config.prompt ? { prompt: config.prompt } : {}),
|
|
@@ -286,14 +308,15 @@ export function ResourceViewerPage({
|
|
|
286
308
|
});
|
|
287
309
|
}, [browser]);
|
|
288
310
|
|
|
289
|
-
// Add resource to open tabs when it loads
|
|
311
|
+
// Add resource to open tabs when it loads, and record it as this KB's
|
|
312
|
+
// last-viewed for the /know landing route to resume from. Both are per-KB
|
|
313
|
+
// state owned by the browser — a global last-viewed id sends that redirect
|
|
314
|
+
// into the previously active KB's resource after a switch.
|
|
290
315
|
useEffect(() => {
|
|
291
316
|
if (resource && rUri) {
|
|
292
317
|
const mediaType = getPrimaryMediaType(resource);
|
|
293
318
|
browser.addOpenResource(rUri, resource.name, mediaType || undefined, resource.storageUri);
|
|
294
|
-
|
|
295
|
-
localStorage.setItem('lastViewedDocumentId', rUri);
|
|
296
|
-
}
|
|
319
|
+
browser.setLastViewedResource(rUri);
|
|
297
320
|
}
|
|
298
321
|
}, [resource, rUri, browser]);
|
|
299
322
|
|
|
@@ -355,8 +378,6 @@ export function ResourceViewerPage({
|
|
|
355
378
|
triggerSparkleAnimation(stored.payload.annotation.id);
|
|
356
379
|
}, [triggerSparkleAnimation]);
|
|
357
380
|
|
|
358
|
-
const handleSettingsThemeChanged = useCallback(({ theme }: { theme: any }) => setTheme(theme), [setTheme]);
|
|
359
|
-
|
|
360
381
|
const handleReferenceNavigate = useCallback(({ resourceId }: { resourceId: string }) => {
|
|
361
382
|
if (routes.resourceDetail) {
|
|
362
383
|
const path = routes.resourceDetail(resourceId);
|
|
@@ -386,8 +407,6 @@ export function ResourceViewerPage({
|
|
|
386
407
|
'yield:clone': handleResourceClone,
|
|
387
408
|
'beckon:sparkle': handleAnnotationSparkle,
|
|
388
409
|
'mark:added': handleAnnotationAdded,
|
|
389
|
-
'settings:theme-changed': handleSettingsThemeChanged,
|
|
390
|
-
'settings:line-numbers-toggled': toggleLineNumbers,
|
|
391
410
|
'browse:reference-navigate': handleReferenceNavigate,
|
|
392
411
|
'browse:entity-type-clicked': handleEntityTypeClicked,
|
|
393
412
|
});
|
|
@@ -503,6 +522,8 @@ export function ResourceViewerPage({
|
|
|
503
522
|
showLineNumbers={showLineNumbers}
|
|
504
523
|
hoverDelayMs={hoverDelayMs}
|
|
505
524
|
hoveredAnnotationId={hoveredAnnotationId}
|
|
525
|
+
{...(browseRenderers && { browseRenderers })}
|
|
526
|
+
{...(annotateRenderers && { annotateRenderers })}
|
|
506
527
|
/>
|
|
507
528
|
)}
|
|
508
529
|
</ErrorBoundary>
|
|
@@ -515,7 +536,6 @@ export function ResourceViewerPage({
|
|
|
515
536
|
<ToolbarPanels
|
|
516
537
|
activePanel={activePanel}
|
|
517
538
|
theme={theme}
|
|
518
|
-
showLineNumbers={showLineNumbers}
|
|
519
539
|
width={
|
|
520
540
|
activePanel === 'jsonld' ? 'w-[600px]' :
|
|
521
541
|
activePanel === 'annotations' ? 'w-[400px]' :
|
|
@@ -543,9 +563,14 @@ export function ResourceViewerPage({
|
|
|
543
563
|
progress={progress}
|
|
544
564
|
pendingAnnotation={pendingAnnotation}
|
|
545
565
|
allEntityTypes={allEntityTypes}
|
|
566
|
+
annotationsError={annotationsError}
|
|
567
|
+
onRetryAnnotations={stateUnit?.annotations.retry}
|
|
568
|
+
entityTypesError={entityTypesError}
|
|
546
569
|
generatingReferenceId={generationProgress?.annotationId ?? null}
|
|
547
570
|
referencedBy={referencedBy}
|
|
548
571
|
referencedByLoading={referencedByLoading}
|
|
572
|
+
referencedByError={referencedByError}
|
|
573
|
+
onRetryReferencedBy={stateUnit?.referencedBy.retry}
|
|
549
574
|
resourceId={rUri}
|
|
550
575
|
locale={locale}
|
|
551
576
|
sourceLanguage={getLanguage(resource)}
|
|
@@ -562,7 +587,11 @@ export function ResourceViewerPage({
|
|
|
562
587
|
{/* History Panel */}
|
|
563
588
|
{activePanel === 'history' && (
|
|
564
589
|
<AnnotationHistory
|
|
565
|
-
|
|
590
|
+
events={events}
|
|
591
|
+
eventsLoading={eventsLoading}
|
|
592
|
+
eventsError={eventsError}
|
|
593
|
+
onRetryEvents={stateUnit?.events.retry}
|
|
594
|
+
annotations={annotations}
|
|
566
595
|
hoveredAnnotationId={hoveredAnnotationId}
|
|
567
596
|
onEventHover={handleEventHover}
|
|
568
597
|
onEventClick={handleEventClick}
|
|
@@ -676,6 +705,7 @@ export function ResourceViewerPage({
|
|
|
676
705
|
resourceId={rUri}
|
|
677
706
|
defaultTitle=""
|
|
678
707
|
locale={locale}
|
|
708
|
+
entityTypeOptions={allEntityTypes}
|
|
679
709
|
onGenerateSubmit={handleResourceGenerateSubmit}
|
|
680
710
|
translations={{
|
|
681
711
|
gatherTitle: tg('gatherTitle'),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { asStates } from '../../../../__tests__/test-client';
|
|
3
|
+
import { sessionOf } from '../../../../__tests__/test-client';
|
|
4
|
+
import { BehaviorSubject, Subject, firstValueFrom } from 'rxjs';
|
|
3
5
|
import { filter } from 'rxjs/operators';
|
|
4
6
|
import { resourceId as makeResourceId } from '@semiont/core';
|
|
5
7
|
import type { SemiontClient } from '@semiont/sdk';
|
|
6
8
|
import { createResourceLoaderStateUnit } from '../resource-loader-state-unit';
|
|
7
|
-
import { assertStateUnitAxioms } from '@semiont/core/testing';
|
|
9
|
+
import { assertStateUnitAxioms } from '@semiont/core/testing/axioms';
|
|
8
10
|
|
|
9
11
|
const RID = makeResourceId('res-1');
|
|
10
12
|
|
|
@@ -13,15 +15,47 @@ function mockClient(resource$?: BehaviorSubject<unknown>): SemiontClient {
|
|
|
13
15
|
const invalidate = vi.fn();
|
|
14
16
|
return {
|
|
15
17
|
browse: {
|
|
16
|
-
resource: () => subject.asObservable(),
|
|
18
|
+
resource: () => asStates(subject.asObservable()),
|
|
17
19
|
invalidateResourceDetail: invalidate,
|
|
18
20
|
},
|
|
19
21
|
} as unknown as SemiontClient;
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A client whose `browse.resource()` mirrors the cache's B15 contract: each
|
|
26
|
+
* `observe()` call hands back a FRESH observable (the failure marker is
|
|
27
|
+
* cleared and a new attempt chain starts), and a terminal failure arrives as
|
|
28
|
+
* an RxJS error notification on that key.
|
|
29
|
+
*/
|
|
30
|
+
function failingClient() {
|
|
31
|
+
const attempts: Array<Subject<unknown>> = [];
|
|
32
|
+
const invalidate = vi.fn();
|
|
33
|
+
const client = {
|
|
34
|
+
browse: {
|
|
35
|
+
resource: () => {
|
|
36
|
+
const s = new Subject<unknown>();
|
|
37
|
+
attempts.push(s);
|
|
38
|
+
return s.asObservable();
|
|
39
|
+
},
|
|
40
|
+
invalidateResourceDetail: invalidate,
|
|
41
|
+
},
|
|
42
|
+
} as unknown as SemiontClient;
|
|
43
|
+
return {
|
|
44
|
+
client,
|
|
45
|
+
invalidate,
|
|
46
|
+
/** Fail the most recent attempt the way B15 does. */
|
|
47
|
+
failLatest: (message: string) =>
|
|
48
|
+
attempts[attempts.length - 1]!.next({ status: 'failed', error: new Error(message) }),
|
|
49
|
+
/** Resolve the most recent attempt with a value. */
|
|
50
|
+
resolveLatest: (value: unknown) =>
|
|
51
|
+
attempts[attempts.length - 1]!.next({ status: 'ready', value }),
|
|
52
|
+
attemptCount: () => attempts.length,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
describe('createResourceLoaderStateUnit', () => {
|
|
23
57
|
it('exposes resource from browse namespace', async () => {
|
|
24
|
-
const stateUnit = createResourceLoaderStateUnit(mockClient(), RID);
|
|
58
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(mockClient()), RID);
|
|
25
59
|
const resource = await firstValueFrom(stateUnit.resource$.pipe(filter((r) => r !== undefined)));
|
|
26
60
|
expect((resource as { name: string }).name).toBe('Test');
|
|
27
61
|
stateUnit.dispose();
|
|
@@ -29,7 +63,7 @@ describe('createResourceLoaderStateUnit', () => {
|
|
|
29
63
|
|
|
30
64
|
it('reports loading when resource is undefined', async () => {
|
|
31
65
|
const subject = new BehaviorSubject<unknown>(undefined);
|
|
32
|
-
const stateUnit = createResourceLoaderStateUnit(mockClient(subject), RID);
|
|
66
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(mockClient(subject)), RID);
|
|
33
67
|
expect(await firstValueFrom(stateUnit.isLoading$)).toBe(true);
|
|
34
68
|
|
|
35
69
|
subject.next({ '@id': 'res-1' });
|
|
@@ -39,18 +73,87 @@ describe('createResourceLoaderStateUnit', () => {
|
|
|
39
73
|
|
|
40
74
|
it('invalidate calls browse.invalidateResourceDetail', () => {
|
|
41
75
|
const client = mockClient();
|
|
42
|
-
const stateUnit = createResourceLoaderStateUnit(client, RID);
|
|
76
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(client), RID);
|
|
43
77
|
stateUnit.invalidate();
|
|
44
78
|
expect(client.browse.invalidateResourceDetail).toHaveBeenCalledWith(RID);
|
|
45
79
|
stateUnit.dispose();
|
|
46
80
|
});
|
|
47
81
|
});
|
|
48
82
|
|
|
83
|
+
describe('createResourceLoaderStateUnit — terminal failure (B15)', () => {
|
|
84
|
+
// "Loading" cannot be defined as "no value yet": a key that fails with
|
|
85
|
+
// nothing cached has no value EITHER, so a two-state model reports a dead
|
|
86
|
+
// request as an eternal spinner and drops the reason on the floor. The
|
|
87
|
+
// failure must be a state of its own.
|
|
88
|
+
// See .plans/bugs/resource-page-frozen-on-disposed-client-after-kb-switch.md (D4)
|
|
89
|
+
|
|
90
|
+
it('surfaces a terminal failure on error$ instead of leaving it undelivered', async () => {
|
|
91
|
+
const h = failingClient();
|
|
92
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(h.client), RID);
|
|
93
|
+
const seen: Array<Error | null> = [];
|
|
94
|
+
stateUnit.error$.subscribe((e) => seen.push(e));
|
|
95
|
+
|
|
96
|
+
expect(seen).toEqual([null]);
|
|
97
|
+
h.failLatest('Resource not found');
|
|
98
|
+
|
|
99
|
+
expect(seen.length).toBe(2);
|
|
100
|
+
expect(seen[1]?.message).toBe('Resource not found');
|
|
101
|
+
stateUnit.dispose();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('stops reporting loading once the request has terminally failed', async () => {
|
|
105
|
+
const h = failingClient();
|
|
106
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(h.client), RID);
|
|
107
|
+
const loading: boolean[] = [];
|
|
108
|
+
stateUnit.isLoading$.subscribe((l) => loading.push(l));
|
|
109
|
+
|
|
110
|
+
expect(loading[loading.length - 1]).toBe(true);
|
|
111
|
+
h.failLatest('Resource not found');
|
|
112
|
+
|
|
113
|
+
// The defect: without a failure state this stays true forever, and the
|
|
114
|
+
// page sits on "Loading resource..." with no way out.
|
|
115
|
+
expect(loading[loading.length - 1]).toBe(false);
|
|
116
|
+
stateUnit.dispose();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('an error notification does not tear the unit down — invalidate starts a fresh attempt that can succeed', async () => {
|
|
120
|
+
const h = failingClient();
|
|
121
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(h.client), RID);
|
|
122
|
+
h.failLatest('Resource not found');
|
|
123
|
+
expect(await firstValueFrom(stateUnit.error$)).not.toBeNull();
|
|
124
|
+
|
|
125
|
+
stateUnit.invalidate();
|
|
126
|
+
expect(h.invalidate).toHaveBeenCalledWith(RID);
|
|
127
|
+
// B15: a fresh observe() clears the marker and restarts the chain, so the
|
|
128
|
+
// unit must be listening to a NEW attempt, not the errored one.
|
|
129
|
+
expect(h.attemptCount()).toBeGreaterThan(1);
|
|
130
|
+
expect(await firstValueFrom(stateUnit.error$)).toBeNull();
|
|
131
|
+
|
|
132
|
+
h.resolveLatest({ '@id': 'res-1', name: 'Recovered' });
|
|
133
|
+
const resource = await firstValueFrom(stateUnit.resource$.pipe(filter((r) => r !== undefined)));
|
|
134
|
+
expect((resource as { name: string }).name).toBe('Recovered');
|
|
135
|
+
expect(await firstValueFrom(stateUnit.isLoading$)).toBe(false);
|
|
136
|
+
stateUnit.dispose();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('a value arriving after a failure clears the error', async () => {
|
|
140
|
+
const h = failingClient();
|
|
141
|
+
const stateUnit = createResourceLoaderStateUnit(sessionOf(h.client), RID);
|
|
142
|
+
h.failLatest('transient');
|
|
143
|
+
stateUnit.invalidate();
|
|
144
|
+
h.resolveLatest({ '@id': 'res-1', name: 'Test' });
|
|
145
|
+
|
|
146
|
+
expect(await firstValueFrom(stateUnit.error$)).toBeNull();
|
|
147
|
+
stateUnit.dispose();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
49
151
|
describe('ResourceLoaderStateUnit — StateUnit axioms', () => {
|
|
50
152
|
it('satisfies the StateUnit axioms', () => {
|
|
51
|
-
//
|
|
153
|
+
// Owns resource$/error$ and a live subscription to client.browse, so
|
|
154
|
+
// dispose completes them and detaches; invalidate is inert afterwards.
|
|
52
155
|
assertStateUnitAxioms({
|
|
53
|
-
setup: () => createResourceLoaderStateUnit(mockClient(), RID),
|
|
156
|
+
setup: () => createResourceLoaderStateUnit(sessionOf(mockClient()), RID),
|
|
54
157
|
invocations: (u) => [() => u.invalidate()],
|
|
55
158
|
});
|
|
56
159
|
});
|