@semiont/react-ui 0.5.6 → 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 +1 -1
- 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 +97 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +29 -22
- package/dist/index.js +346 -301
- 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 +2 -2
- package/src/components/ProtectedErrorBoundary.css +119 -0
- package/src/components/ProtectedErrorBoundary.tsx +18 -13
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +1 -1
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +1 -1
- 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 +4 -8
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +0 -1
- 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/__tests__/AssessmentEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +95 -424
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -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/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +11 -9
- 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-viewer/components/ResourceViewerPage.tsx +7 -5
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +37 -0
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +9 -5
- 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/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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
ProtectedErrorBoundary Component Styles
|
|
3
|
+
============================================ */
|
|
4
|
+
|
|
5
|
+
.semiont-protected-error-boundary-container {
|
|
6
|
+
min-height: 400px;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
padding: var(--semiont-spacing-md);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.semiont-protected-error-boundary-card {
|
|
14
|
+
max-width: 28rem;
|
|
15
|
+
width: 100%;
|
|
16
|
+
background-color: var(--semiont-color-white);
|
|
17
|
+
border-radius: var(--semiont-radius-lg);
|
|
18
|
+
box-shadow: var(--semiont-shadow-lg);
|
|
19
|
+
padding: var(--semiont-spacing-lg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[data-theme="dark"] .semiont-protected-error-boundary-card {
|
|
23
|
+
background-color: var(--semiont-color-gray-800);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.semiont-protected-error-boundary-header {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 0.75rem;
|
|
30
|
+
margin-bottom: var(--semiont-spacing-md);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.semiont-protected-error-boundary-icon-wrapper {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
width: 2.5rem;
|
|
39
|
+
height: 2.5rem;
|
|
40
|
+
border-radius: var(--semiont-radius-full);
|
|
41
|
+
background-color: var(--semiont-color-red-100);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-theme="dark"] .semiont-protected-error-boundary-icon-wrapper {
|
|
45
|
+
background-color: rgba(127, 29, 29, 0.3);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.semiont-protected-error-boundary-icon {
|
|
49
|
+
width: 1.5rem;
|
|
50
|
+
height: 1.5rem;
|
|
51
|
+
color: var(--semiont-color-red-600);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[data-theme="dark"] .semiont-protected-error-boundary-icon {
|
|
55
|
+
color: var(--semiont-color-red-400);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.semiont-protected-error-boundary-title {
|
|
59
|
+
font-size: var(--semiont-text-xl);
|
|
60
|
+
font-weight: var(--semiont-font-semibold);
|
|
61
|
+
color: var(--semiont-color-gray-900);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[data-theme="dark"] .semiont-protected-error-boundary-title {
|
|
65
|
+
color: var(--semiont-color-white);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.semiont-protected-error-boundary-message {
|
|
69
|
+
color: var(--semiont-color-gray-600);
|
|
70
|
+
margin-bottom: var(--semiont-spacing-lg);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[data-theme="dark"] .semiont-protected-error-boundary-message {
|
|
74
|
+
color: var(--semiont-color-gray-300);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.semiont-protected-error-boundary-details {
|
|
78
|
+
margin-bottom: var(--semiont-spacing-md);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.semiont-protected-error-boundary-summary {
|
|
82
|
+
font-size: var(--semiont-text-sm);
|
|
83
|
+
color: var(--semiont-color-gray-500);
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.semiont-protected-error-boundary-summary:hover {
|
|
88
|
+
color: var(--semiont-color-gray-700);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[data-theme="dark"] .semiont-protected-error-boundary-summary {
|
|
92
|
+
color: var(--semiont-color-gray-500);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
[data-theme="dark"] .semiont-protected-error-boundary-summary:hover {
|
|
96
|
+
color: var(--semiont-color-gray-300);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.semiont-protected-error-boundary-stack {
|
|
100
|
+
margin-top: var(--semiont-spacing-sm);
|
|
101
|
+
font-size: var(--semiont-text-xs);
|
|
102
|
+
background-color: var(--semiont-color-gray-100);
|
|
103
|
+
padding: var(--semiont-spacing-sm);
|
|
104
|
+
border-radius: var(--semiont-radius-sm);
|
|
105
|
+
overflow: auto;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
[data-theme="dark"] .semiont-protected-error-boundary-stack {
|
|
109
|
+
background-color: var(--semiont-color-gray-900);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.semiont-protected-error-boundary-actions {
|
|
113
|
+
display: flex;
|
|
114
|
+
gap: 0.75rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.semiont-protected-error-boundary-actions .semiont-button {
|
|
118
|
+
flex: 1 1 0;
|
|
119
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ErrorBoundary, type FallbackProps } from 'react-error-boundary';
|
|
3
|
+
import './ProtectedErrorBoundary.css';
|
|
3
4
|
|
|
4
5
|
interface ProtectedErrorBoundaryProps {
|
|
5
6
|
children: React.ReactNode;
|
|
@@ -50,45 +51,49 @@ function ProtectedErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
|
|
50
51
|
const message = error instanceof Error ? error.message : String(error);
|
|
51
52
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
52
53
|
return (
|
|
53
|
-
<div className="
|
|
54
|
-
<div className="
|
|
55
|
-
<div className="
|
|
56
|
-
<div className="
|
|
57
|
-
<svg className="
|
|
54
|
+
<div className="semiont-protected-error-boundary-container">
|
|
55
|
+
<div className="semiont-protected-error-boundary-card">
|
|
56
|
+
<div className="semiont-protected-error-boundary-header">
|
|
57
|
+
<div className="semiont-protected-error-boundary-icon-wrapper">
|
|
58
|
+
<svg className="semiont-protected-error-boundary-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
58
59
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
59
60
|
</svg>
|
|
60
61
|
</div>
|
|
61
|
-
<h2 className="
|
|
62
|
+
<h2 className="semiont-protected-error-boundary-title">
|
|
62
63
|
Something went wrong
|
|
63
64
|
</h2>
|
|
64
65
|
</div>
|
|
65
66
|
|
|
66
|
-
<p className="
|
|
67
|
+
<p className="semiont-protected-error-boundary-message">
|
|
67
68
|
An unexpected error occurred. Try again, or refresh the page.
|
|
68
69
|
</p>
|
|
69
70
|
|
|
70
71
|
{process.env.NODE_ENV === 'development' && (
|
|
71
|
-
<details className="
|
|
72
|
-
<summary className="
|
|
72
|
+
<details className="semiont-protected-error-boundary-details">
|
|
73
|
+
<summary className="semiont-protected-error-boundary-summary">
|
|
73
74
|
Error details (development only)
|
|
74
75
|
</summary>
|
|
75
|
-
<pre className="
|
|
76
|
+
<pre className="semiont-protected-error-boundary-stack">
|
|
76
77
|
{message}
|
|
77
78
|
{stack}
|
|
78
79
|
</pre>
|
|
79
80
|
</details>
|
|
80
81
|
)}
|
|
81
82
|
|
|
82
|
-
<div className="
|
|
83
|
+
<div className="semiont-protected-error-boundary-actions">
|
|
83
84
|
<button
|
|
84
85
|
onClick={resetErrorBoundary}
|
|
85
|
-
className="
|
|
86
|
+
className="semiont-button"
|
|
87
|
+
data-variant="secondary"
|
|
88
|
+
data-size="md"
|
|
86
89
|
>
|
|
87
90
|
Try Again
|
|
88
91
|
</button>
|
|
89
92
|
<button
|
|
90
93
|
onClick={() => window.location.reload()}
|
|
91
|
-
className="
|
|
94
|
+
className="semiont-button"
|
|
95
|
+
data-variant="primary"
|
|
96
|
+
data-size="md"
|
|
92
97
|
>
|
|
93
98
|
Refresh Page
|
|
94
99
|
</button>
|
|
@@ -14,7 +14,7 @@ vi.mock('@headlessui/react', () => ({
|
|
|
14
14
|
TransitionChild: ({ children }: any) => <>{children}</>,
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
-
// Mock the
|
|
17
|
+
// Mock the http-transport Observable surface.
|
|
18
18
|
// The session-based useSemiont path: useObservable(useSemiont().activeSession$)?.client
|
|
19
19
|
// We mock useSemiont to return a stable browser whose activeSession$ emits a
|
|
20
20
|
// session-shaped object that carries the mock client.
|
|
@@ -31,7 +31,7 @@ vi.mock('@headlessui/react', () => ({
|
|
|
31
31
|
TransitionChild: ({ children }: any) => <>{children}</>,
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
-
// Mock the
|
|
34
|
+
// Mock the http-transport Observable surface
|
|
35
35
|
const browseResourcesSubject = new BehaviorSubject<any[] | undefined>(undefined);
|
|
36
36
|
const browseResourcesMock = vi.fn(() => browseResourcesSubject.asObservable());
|
|
37
37
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useRef, useEffect, useCallback, lazy, Suspense } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { capabilitiesOf } from '@semiont/core';
|
|
5
5
|
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
6
6
|
import { segmentTextWithAnnotations } from '../../lib/text-segmentation';
|
|
7
7
|
import { buildTextSelectors, fallbackTextPosition } from '../../lib/text-selection-handler';
|
|
@@ -71,7 +71,7 @@ export function AnnotateView({
|
|
|
71
71
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
72
72
|
const session = useObservable(useSemiont().activeSession$);
|
|
73
73
|
|
|
74
|
-
const
|
|
74
|
+
const render = capabilitiesOf(mimeType)?.render ?? 'none';
|
|
75
75
|
|
|
76
76
|
const { highlights, references, assessments, comments, tags } = annotations;
|
|
77
77
|
|
|
@@ -192,8 +192,8 @@ export function AnnotateView({
|
|
|
192
192
|
};
|
|
193
193
|
}, [selectedMotivation, content]);
|
|
194
194
|
|
|
195
|
-
// Route to
|
|
196
|
-
switch (
|
|
195
|
+
// Route to the annotation viewer for this media type's render mode.
|
|
196
|
+
switch (render) {
|
|
197
197
|
case 'text':
|
|
198
198
|
return (
|
|
199
199
|
<div className="semiont-annotate-view" data-mime-type="text" ref={containerRef}>
|
|
@@ -225,40 +225,38 @@ export function AnnotateView({
|
|
|
225
225
|
</div>
|
|
226
226
|
);
|
|
227
227
|
|
|
228
|
-
case '
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
</Suspense>
|
|
256
|
-
)}
|
|
257
|
-
</div>
|
|
228
|
+
case 'pdf':
|
|
229
|
+
// PDF annotation support (spatial, FragmentSelector)
|
|
230
|
+
return (
|
|
231
|
+
<div className="semiont-annotate-view" data-mime-type="pdf" ref={containerRef}>
|
|
232
|
+
<AnnotateToolbar
|
|
233
|
+
selectedMotivation={selectedMotivation}
|
|
234
|
+
selectedClick={selectedClick}
|
|
235
|
+
showShapeGroup={true}
|
|
236
|
+
selectedShape={selectedShape}
|
|
237
|
+
mediaType={mimeType}
|
|
238
|
+
annotateMode={annotateMode}
|
|
239
|
+
annotators={ANNOTATORS}
|
|
240
|
+
/>
|
|
241
|
+
<div className="semiont-annotate-view__content">
|
|
242
|
+
{content && (
|
|
243
|
+
<Suspense fallback={<div className="semiont-annotate-view__loading">Loading PDF viewer...</div>}>
|
|
244
|
+
<PdfAnnotationCanvas
|
|
245
|
+
pdfUrl={content}
|
|
246
|
+
existingAnnotations={allAnnotations}
|
|
247
|
+
drawingMode={selectedMotivation ? selectedShape : null}
|
|
248
|
+
selectedMotivation={selectedMotivation}
|
|
249
|
+
session={session}
|
|
250
|
+
hoveredAnnotationId={hoveredAnnotationId || null}
|
|
251
|
+
hoverDelayMs={hoverDelayMs}
|
|
252
|
+
/>
|
|
253
|
+
</Suspense>
|
|
254
|
+
)}
|
|
258
255
|
</div>
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
261
258
|
|
|
259
|
+
case 'image':
|
|
262
260
|
// PNG, JPEG, etc. - full annotation support
|
|
263
261
|
return (
|
|
264
262
|
<div className="semiont-annotate-view" data-mime-type="image" ref={containerRef}>
|
|
@@ -287,7 +285,7 @@ export function AnnotateView({
|
|
|
287
285
|
</div>
|
|
288
286
|
);
|
|
289
287
|
|
|
290
|
-
case '
|
|
288
|
+
case 'none':
|
|
291
289
|
default:
|
|
292
290
|
return (
|
|
293
291
|
<div ref={containerRef} className="semiont-annotate-view semiont-annotate-view--unsupported" data-mime-type="unsupported">
|
|
@@ -4,7 +4,7 @@ import { useEffect, useRef, useCallback, useMemo, memo, lazy, Suspense } from 'r
|
|
|
4
4
|
import ReactMarkdown from 'react-markdown';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
6
6
|
import { annotationId as toAnnotationId } from '@semiont/core';
|
|
7
|
-
import {
|
|
7
|
+
import { capabilitiesOf } from '@semiont/core';
|
|
8
8
|
import { createHoverHandlers } from '@semiont/sdk';
|
|
9
9
|
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
10
10
|
import { scrollAnnotationIntoView } from '../../lib/scroll-utils';
|
|
@@ -83,7 +83,7 @@ export const BrowseView = memo(function BrowseView({
|
|
|
83
83
|
const session = useObservable(useSemiont().activeSession$);
|
|
84
84
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
85
85
|
|
|
86
|
-
const
|
|
86
|
+
const render = capabilitiesOf(mimeType)?.render ?? 'none';
|
|
87
87
|
|
|
88
88
|
const { highlights, references, assessments, comments, tags } = annotations;
|
|
89
89
|
|
|
@@ -207,8 +207,9 @@ export const BrowseView = memo(function BrowseView({
|
|
|
207
207
|
'beckon:focus': handleAnnotationFocus,
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
// Route to
|
|
211
|
-
|
|
210
|
+
// Route to the viewer for this media type's render mode. The switch is
|
|
211
|
+
// exhaustive over RenderMode, so every path returns.
|
|
212
|
+
switch (render) {
|
|
212
213
|
case 'text':
|
|
213
214
|
return (
|
|
214
215
|
<div className="semiont-browse-view" data-mime-type="text">
|
|
@@ -226,34 +227,31 @@ export const BrowseView = memo(function BrowseView({
|
|
|
226
227
|
</div>
|
|
227
228
|
);
|
|
228
229
|
|
|
229
|
-
case '
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
/>
|
|
250
|
-
</Suspense>
|
|
251
|
-
</div>
|
|
230
|
+
case 'pdf':
|
|
231
|
+
return (
|
|
232
|
+
<div className="semiont-browse-view" data-mime-type="pdf">
|
|
233
|
+
<AnnotateToolbar
|
|
234
|
+
selectedMotivation={null}
|
|
235
|
+
selectedClick={selectedClick}
|
|
236
|
+
showSelectionGroup={false}
|
|
237
|
+
showDeleteButton={false}
|
|
238
|
+
annotateMode={annotateMode}
|
|
239
|
+
annotators={ANNOTATORS}
|
|
240
|
+
/>
|
|
241
|
+
<div ref={containerRef} className="semiont-browse-view__content">
|
|
242
|
+
<Suspense fallback={<div className="semiont-browse-view__loading">Loading PDF viewer...</div>}>
|
|
243
|
+
<PdfAnnotationCanvas
|
|
244
|
+
pdfUrl={content}
|
|
245
|
+
existingAnnotations={allAnnotations}
|
|
246
|
+
drawingMode={null}
|
|
247
|
+
selectedMotivation={null}
|
|
248
|
+
/>
|
|
249
|
+
</Suspense>
|
|
252
250
|
</div>
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
255
253
|
|
|
256
|
-
|
|
254
|
+
case 'image':
|
|
257
255
|
return (
|
|
258
256
|
<div className="semiont-browse-view" data-mime-type="image">
|
|
259
257
|
<AnnotateToolbar
|
|
@@ -274,7 +272,9 @@ export const BrowseView = memo(function BrowseView({
|
|
|
274
272
|
</div>
|
|
275
273
|
);
|
|
276
274
|
|
|
277
|
-
case '
|
|
275
|
+
case 'none':
|
|
276
|
+
// Catalogued type with no preview (render: 'none') or an imported
|
|
277
|
+
// foreign type the registry doesn't know — same UI: metadata + download.
|
|
278
278
|
return (
|
|
279
279
|
<div ref={containerRef} className="semiont-browse-view semiont-browse-view--unsupported" data-mime-type="unsupported">
|
|
280
280
|
<div className="semiont-browse-view__empty">
|
|
@@ -15,18 +15,14 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
|
|
|
15
15
|
})),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
// Mock @semiont/core utilities
|
|
18
|
+
// Mock @semiont/core utilities. The media-type registry (`capabilitiesOf`)
|
|
19
|
+
// is NOT mocked — BrowseView dispatches on the real registry's render mode,
|
|
20
|
+
// so the tested types (text/markdown, image/png, application/octet-stream)
|
|
21
|
+
// resolve through the real source of truth.
|
|
19
22
|
vi.mock('@semiont/core', async () => {
|
|
20
23
|
const actual = await vi.importActual('@semiont/core');
|
|
21
24
|
return {
|
|
22
25
|
...actual,
|
|
23
|
-
getMimeCategory: vi.fn((mimeType: string) => {
|
|
24
|
-
if (mimeType.startsWith('text/')) return 'text';
|
|
25
|
-
if (mimeType.startsWith('image/')) return 'image';
|
|
26
|
-
if (mimeType === 'application/pdf') return 'image';
|
|
27
|
-
return 'unsupported';
|
|
28
|
-
}),
|
|
29
|
-
isPdfMimeType: vi.fn((mimeType: string) => mimeType === 'application/pdf'),
|
|
30
26
|
resourceId: vi.fn((id: string) => id),
|
|
31
27
|
getExactText: vi.fn(() => 'exact text'),
|
|
32
28
|
getTextPositionSelector: vi.fn(() => ({ start: 0, end: 10 })),
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getResourceCreationDetails,
|
|
9
9
|
} from '../event-formatting';
|
|
10
10
|
|
|
11
|
-
// Mock
|
|
11
|
+
// Mock http-transport functions
|
|
12
12
|
vi.mock('@semiont/core', async (importOriginal) => {
|
|
13
13
|
const actual = await importOriginal<typeof import('@semiont/core')>();
|
|
14
14
|
return {
|
|
@@ -7,7 +7,7 @@ import './CollaborationPanel.css';
|
|
|
7
7
|
interface Props {
|
|
8
8
|
/**
|
|
9
9
|
* Connection state from `client.actor.state$`. See
|
|
10
|
-
* `packages/
|
|
10
|
+
* `packages/http-transport/src/state/domain/actor-state-unit.ts`.
|
|
11
11
|
*
|
|
12
12
|
* UI mapping:
|
|
13
13
|
* `open` | `reconnecting` | `initial` | `connecting`
|
|
@@ -8,42 +8,46 @@ import { oneDark } from '@codemirror/theme-one-dark';
|
|
|
8
8
|
import { syntaxHighlighting } from '@codemirror/language';
|
|
9
9
|
import { jsonLightTheme, jsonLightHighlightStyle } from '../../../lib/codemirror-json-theme';
|
|
10
10
|
import { useLineNumbers } from '../../../hooks/useLineNumbers';
|
|
11
|
-
import
|
|
11
|
+
import { useResourceGraph } from '../../../hooks/useResourceGraph';
|
|
12
|
+
import type { ResourceId } from '@semiont/core';
|
|
12
13
|
import './JsonLdPanel.css';
|
|
13
14
|
|
|
14
|
-
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
15
|
-
|
|
16
15
|
interface Props {
|
|
17
|
-
|
|
16
|
+
resourceId: ResourceId;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Dereferences the resource's LD face (`GET /resources/:id/jsonld` via
|
|
21
|
+
* `browse.resourceGraph`) and pretty-prints the full graph — descriptor +
|
|
22
|
+
* annotations + inbound entity references — read-only. This is exactly what
|
|
23
|
+
* an external linked-data client gets when dereferencing the resource's
|
|
24
|
+
* `describedby` URI, so the panel doubles as a living end-to-end test of the
|
|
25
|
+
* LD face. See `.plans/SIMPLER-JSON-LD.md` §5.
|
|
26
|
+
*/
|
|
27
|
+
export function JsonLdPanel({ resourceId }: Props) {
|
|
21
28
|
const editorRef = useRef<HTMLDivElement>(null);
|
|
22
29
|
const viewRef = useRef<EditorView | null>(null);
|
|
23
30
|
const { showLineNumbers } = useLineNumbers();
|
|
31
|
+
const { graph, loading, error } = useResourceGraph(resourceId);
|
|
32
|
+
|
|
33
|
+
const documentText = graph ? JSON.stringify(graph, null, 2) : '';
|
|
24
34
|
|
|
25
|
-
// Initialize CodeMirror
|
|
35
|
+
// Initialize CodeMirror once the graph has loaded.
|
|
26
36
|
useEffect(() => {
|
|
27
|
-
if (!editorRef.current) return;
|
|
37
|
+
if (!editorRef.current || !documentText) return;
|
|
28
38
|
|
|
29
|
-
// Check if dark mode is active
|
|
30
39
|
const isDarkMode = document.documentElement?.classList.contains('dark') ?? false;
|
|
31
40
|
|
|
32
|
-
// Convert resource to JSON-LD format
|
|
33
|
-
const jsonLdContent = JSON.stringify(semiontResource, null, 2);
|
|
34
|
-
|
|
35
41
|
const extensions = [
|
|
36
42
|
json(),
|
|
37
43
|
EditorView.editable.of(false),
|
|
38
44
|
EditorState.readOnly.of(true),
|
|
39
45
|
];
|
|
40
46
|
|
|
41
|
-
// Add line numbers if enabled
|
|
42
47
|
if (showLineNumbers) {
|
|
43
48
|
extensions.push(lineNumbers());
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
// Add theme based on dark/light mode
|
|
47
51
|
if (isDarkMode) {
|
|
48
52
|
extensions.push(oneDark);
|
|
49
53
|
} else {
|
|
@@ -52,7 +56,7 @@ export function JsonLdPanel({ resource: semiontResource }: Props) {
|
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
const state = EditorState.create({
|
|
55
|
-
doc:
|
|
59
|
+
doc: documentText,
|
|
56
60
|
extensions,
|
|
57
61
|
});
|
|
58
62
|
|
|
@@ -67,11 +71,12 @@ export function JsonLdPanel({ resource: semiontResource }: Props) {
|
|
|
67
71
|
view.destroy();
|
|
68
72
|
viewRef.current = null;
|
|
69
73
|
};
|
|
70
|
-
}, [
|
|
74
|
+
}, [documentText, showLineNumbers]);
|
|
71
75
|
|
|
72
76
|
const handleCopyToClipboard = async () => {
|
|
77
|
+
if (!documentText) return;
|
|
73
78
|
try {
|
|
74
|
-
await navigator.clipboard.writeText(
|
|
79
|
+
await navigator.clipboard.writeText(documentText);
|
|
75
80
|
} catch (err) {
|
|
76
81
|
console.error('Failed to copy JSON-LD:', err);
|
|
77
82
|
}
|
|
@@ -88,11 +93,23 @@ export function JsonLdPanel({ resource: semiontResource }: Props) {
|
|
|
88
93
|
onClick={handleCopyToClipboard}
|
|
89
94
|
className="semiont-button semiont-button--icon"
|
|
90
95
|
title="Copy to clipboard"
|
|
96
|
+
disabled={!graph}
|
|
91
97
|
>
|
|
92
98
|
📋 Copy
|
|
93
99
|
</button>
|
|
94
100
|
</div>
|
|
95
101
|
|
|
102
|
+
{loading && (
|
|
103
|
+
<p className="semiont-jsonld-panel__status" role="status">
|
|
104
|
+
Loading JSON-LD…
|
|
105
|
+
</p>
|
|
106
|
+
)}
|
|
107
|
+
{error && !loading && (
|
|
108
|
+
<p className="semiont-jsonld-panel__status semiont-jsonld-panel__status--error" role="alert">
|
|
109
|
+
Failed to load JSON-LD.
|
|
110
|
+
</p>
|
|
111
|
+
)}
|
|
112
|
+
|
|
96
113
|
{/* JSON-LD content rendered with CodeMirror */}
|
|
97
114
|
<div
|
|
98
115
|
ref={editorRef}
|
|
@@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
6
6
|
|
|
7
7
|
import type { Annotation, AnnotationId } from '@semiont/core';
|
|
8
8
|
|
|
9
|
-
// Mock @semiont/
|
|
9
|
+
// Mock @semiont/http-transport
|
|
10
10
|
vi.mock('@semiont/core', async () => {
|
|
11
11
|
const actual = await vi.importActual('@semiont/core');
|
|
12
12
|
return {
|
|
@@ -57,7 +57,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
57
57
|
TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
58
58
|
}));
|
|
59
59
|
|
|
60
|
-
// Mock @semiont/
|
|
60
|
+
// Mock @semiont/http-transport utilities
|
|
61
61
|
vi.mock('@semiont/core', async () => {
|
|
62
62
|
const actual = await vi.importActual('@semiont/core');
|
|
63
63
|
return {
|
|
@@ -21,7 +21,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
21
21
|
TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
|
-
// Mock @semiont/
|
|
24
|
+
// Mock @semiont/http-transport utilities
|
|
25
25
|
vi.mock('@semiont/core', async () => {
|
|
26
26
|
const actual = await vi.importActual('@semiont/core');
|
|
27
27
|
return {
|
|
@@ -56,7 +56,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
56
56
|
TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
57
57
|
}));
|
|
58
58
|
|
|
59
|
-
// Mock @semiont/
|
|
59
|
+
// Mock @semiont/http-transport utilities
|
|
60
60
|
vi.mock('@semiont/core', async () => {
|
|
61
61
|
const actual = await vi.importActual('@semiont/core');
|
|
62
62
|
return {
|
|
@@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
6
6
|
|
|
7
7
|
import type { Annotation, AnnotationId } from '@semiont/core';
|
|
8
8
|
|
|
9
|
-
// Mock @semiont/
|
|
9
|
+
// Mock @semiont/http-transport
|
|
10
10
|
vi.mock('@semiont/core', async () => {
|
|
11
11
|
const actual = await vi.importActual('@semiont/core');
|
|
12
12
|
return {
|