@semiont/react-ui 0.2.33 → 0.2.34-build.89
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/EventBusContext-BmzEcGHZ.d.mts +177 -0
- package/dist/{PdfAnnotationCanvas.client-FGV33CWN.mjs → PdfAnnotationCanvas.client-VLNA5O5M.mjs} +7 -7
- package/dist/PdfAnnotationCanvas.client-VLNA5O5M.mjs.map +1 -0
- package/dist/{chunk-YPYLOBA2.mjs → chunk-C63BARI7.mjs} +3 -2
- package/dist/chunk-C63BARI7.mjs.map +1 -0
- package/dist/{chunk-FC6SGLLT.mjs → chunk-M7SZRRIE.mjs} +24 -16
- package/dist/chunk-M7SZRRIE.mjs.map +1 -0
- package/dist/chunk-ULIET3MW.mjs +31 -0
- package/dist/chunk-ULIET3MW.mjs.map +1 -0
- package/dist/index.d.mts +33 -60
- package/dist/index.mjs +171 -363
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +3 -5
- package/dist/test-utils.mjs +2 -2
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +2 -3
- package/src/components/CodeMirrorRenderer.tsx +4 -4
- package/src/components/DetectionProgressWidget.tsx +3 -3
- package/src/components/LiveRegion.tsx +1 -1
- package/src/components/Toolbar.tsx +1 -1
- package/src/components/annotation/AnnotateToolbar.tsx +5 -5
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +4 -4
- package/src/components/annotation-popups/JsonLdView.tsx +1 -1
- package/src/components/image-annotation/AnnotationOverlay.tsx +9 -9
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +4 -4
- package/src/components/navigation/ObservableLink.tsx +1 -1
- package/src/components/navigation/SimpleNavigation.tsx +1 -1
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +6 -6
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +2 -2
- package/src/components/resource/AnnotateView.tsx +5 -4
- package/src/components/resource/AnnotationHistory.tsx +1 -1
- package/src/components/resource/BrowseView.tsx +5 -4
- package/src/components/resource/ResourceViewer.tsx +5 -4
- package/src/components/resource/__tests__/BrowseView.test.tsx +11 -22
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +1 -1
- package/src/components/resource/event-formatting.ts +1 -1
- package/src/components/resource/panels/AssessmentEntry.tsx +2 -2
- package/src/components/resource/panels/AssessmentPanel.tsx +4 -4
- package/src/components/resource/panels/CommentEntry.tsx +2 -2
- package/src/components/resource/panels/CommentsPanel.tsx +4 -4
- package/src/components/resource/panels/DetectSection.tsx +3 -3
- package/src/components/resource/panels/HighlightEntry.tsx +2 -2
- package/src/components/resource/panels/HighlightPanel.tsx +2 -2
- package/src/components/resource/panels/JsonLdPanel.tsx +1 -1
- package/src/components/resource/panels/ReferenceEntry.tsx +6 -6
- package/src/components/resource/panels/ReferencesPanel.tsx +5 -5
- package/src/components/resource/panels/ResourceInfoPanel.tsx +3 -3
- package/src/components/resource/panels/StatisticsPanel.tsx +1 -1
- package/src/components/resource/panels/TagEntry.tsx +2 -2
- package/src/components/resource/panels/TaggingPanel.tsx +5 -5
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +1 -1
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +5 -5
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +10 -10
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +5 -5
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +9 -9
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +5 -5
- package/src/components/settings/SettingsPanel.tsx +5 -5
- package/src/components/viewers/ImageViewer.tsx +1 -1
- package/src/features/resource-compose/components/ResourceComposePage.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceCard.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +7 -5
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +5 -4
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +5 -5
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +29 -43
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +20 -39
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +38 -46
- package/src/features/resource-viewer/__tests__/ResolutionFlowIntegration.test.tsx +36 -43
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +8 -8
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +14 -21
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +8 -7
- package/dist/EventBusContext-CJjL_cCf.d.mts +0 -462
- package/dist/PdfAnnotationCanvas.client-FGV33CWN.mjs.map +0 -1
- package/dist/chunk-FC6SGLLT.mjs.map +0 -1
- package/dist/chunk-XS27QKGP.mjs +0 -55
- package/dist/chunk-XS27QKGP.mjs.map +0 -1
- package/dist/chunk-YPYLOBA2.mjs.map +0 -1
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
import mitt from 'mitt';
|
|
4
|
-
import { ResourceEvent } from '@semiont/core';
|
|
5
|
-
import { GenerationProgress as GenerationProgress$1, Selector, components, ResourceUri, GenerationContext } from '@semiont/api-client';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Open Resources Manager Interface
|
|
9
|
-
*
|
|
10
|
-
* Manages a list of open resources (documents/files) with persistence.
|
|
11
|
-
* This interface allows apps to provide their own implementation of resource management
|
|
12
|
-
* (localStorage, sessionStorage, database, etc.) while components remain framework-agnostic.
|
|
13
|
-
*
|
|
14
|
-
* Components accept this manager as a prop instead of consuming from Context.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```tsx
|
|
18
|
-
* // In app (e.g., frontend/src/hooks/useOpenResourcesManager.ts)
|
|
19
|
-
* export function useOpenResourcesManager(): OpenResourcesManager {
|
|
20
|
-
* const [openResources, setOpenResources] = useState<OpenResource[]>([]);
|
|
21
|
-
*
|
|
22
|
-
* // Implementation details...
|
|
23
|
-
*
|
|
24
|
-
* return {
|
|
25
|
-
* openResources,
|
|
26
|
-
* addResource,
|
|
27
|
-
* removeResource,
|
|
28
|
-
* updateResourceName,
|
|
29
|
-
* reorderResources
|
|
30
|
-
* };
|
|
31
|
-
* }
|
|
32
|
-
*
|
|
33
|
-
* // Pass to components as props
|
|
34
|
-
* <KnowledgeNavigation openResourcesManager={openResourcesManager} />
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
interface OpenResource {
|
|
38
|
-
/** Unique identifier for the resource */
|
|
39
|
-
id: string;
|
|
40
|
-
/** Display name of the resource */
|
|
41
|
-
name: string;
|
|
42
|
-
/** Timestamp when the resource was opened */
|
|
43
|
-
openedAt: number;
|
|
44
|
-
/** Order/position for manual sorting (optional for backward compatibility) */
|
|
45
|
-
order?: number;
|
|
46
|
-
/** Media type for icon display (e.g., 'application/pdf', 'text/plain') */
|
|
47
|
-
mediaType?: string;
|
|
48
|
-
}
|
|
49
|
-
interface OpenResourcesManager {
|
|
50
|
-
/** List of currently open resources */
|
|
51
|
-
openResources: OpenResource[];
|
|
52
|
-
/**
|
|
53
|
-
* Add a new resource to the open list or update if already exists
|
|
54
|
-
* @param id - Unique resource identifier
|
|
55
|
-
* @param name - Display name of the resource
|
|
56
|
-
* @param mediaType - Optional media type for icon display
|
|
57
|
-
*/
|
|
58
|
-
addResource: (id: string, name: string, mediaType?: string) => void;
|
|
59
|
-
/**
|
|
60
|
-
* Remove a resource from the open list
|
|
61
|
-
* @param id - Resource identifier to remove
|
|
62
|
-
*/
|
|
63
|
-
removeResource: (id: string) => void;
|
|
64
|
-
/**
|
|
65
|
-
* Update the display name of an open resource
|
|
66
|
-
* @param id - Resource identifier
|
|
67
|
-
* @param name - New display name
|
|
68
|
-
*/
|
|
69
|
-
updateResourceName: (id: string, name: string) => void;
|
|
70
|
-
/**
|
|
71
|
-
* Reorder resources by moving from one index to another
|
|
72
|
-
* @param oldIndex - Current position index
|
|
73
|
-
* @param newIndex - Desired position index
|
|
74
|
-
*/
|
|
75
|
-
reorderResources: (oldIndex: number, newIndex: number) => void;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Session management interface for handling authentication state and session expiry
|
|
80
|
-
* Apps implement this interface and pass it to SessionProvider
|
|
81
|
-
*/
|
|
82
|
-
interface SessionState {
|
|
83
|
-
/** Whether the user is currently authenticated */
|
|
84
|
-
isAuthenticated: boolean;
|
|
85
|
-
/** When the session expires (null if not authenticated) */
|
|
86
|
-
expiresAt: Date | null;
|
|
87
|
-
/** Time in milliseconds until session expires (null if not authenticated) */
|
|
88
|
-
timeUntilExpiry: number | null;
|
|
89
|
-
/** Whether the session is expiring soon (< 5 minutes) */
|
|
90
|
-
isExpiringSoon: boolean;
|
|
91
|
-
}
|
|
92
|
-
interface SessionManager extends SessionState {
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Translation management interface
|
|
97
|
-
* Apps implement this to provide translations using their preferred i18n library
|
|
98
|
-
*/
|
|
99
|
-
interface TranslationManager {
|
|
100
|
-
/**
|
|
101
|
-
* Translate a key within a namespace
|
|
102
|
-
* @param namespace - Translation namespace (e.g., 'Toolbar', 'ResourceViewer')
|
|
103
|
-
* @param key - Translation key within the namespace
|
|
104
|
-
* @param params - Optional parameters for interpolation
|
|
105
|
-
* @returns Translated string
|
|
106
|
-
*/
|
|
107
|
-
t: (namespace: string, key: string, params?: Record<string, any>) => string;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Progress type definitions for detection and generation flows
|
|
112
|
-
*/
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Common detection progress fields shared across all motivation types.
|
|
116
|
-
*
|
|
117
|
-
* The five motivations have different SSE progress shapes
|
|
118
|
-
* (ReferenceDetectionProgress uses entity-type steps; the others use percentage).
|
|
119
|
-
* This local type captures the subset of fields used by the detection UI
|
|
120
|
-
* (DetectionProgressWidget, useDetectionFlow).
|
|
121
|
-
*/
|
|
122
|
-
interface DetectionProgress {
|
|
123
|
-
status: string;
|
|
124
|
-
message?: string;
|
|
125
|
-
/** Reference detection: currently scanning entity type */
|
|
126
|
-
currentEntityType?: string;
|
|
127
|
-
/** Reference detection: completed entity types with counts (frontend-only) */
|
|
128
|
-
completedEntityTypes?: Array<{
|
|
129
|
-
entityType: string;
|
|
130
|
-
foundCount: number;
|
|
131
|
-
}>;
|
|
132
|
-
/** Percentage-based motivations (highlight, assessment, comment, tag) */
|
|
133
|
-
percentage?: number;
|
|
134
|
-
/** Category-based motivations (tag) */
|
|
135
|
-
currentCategory?: string;
|
|
136
|
-
processedCategories?: number;
|
|
137
|
-
totalCategories?: number;
|
|
138
|
-
/** Request parameters for display in progress UI (frontend-only, added by annotation-registry) */
|
|
139
|
-
requestParams?: Array<{
|
|
140
|
-
label: string;
|
|
141
|
-
value: string;
|
|
142
|
-
}>;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Generation progress type (no extensions needed, re-export API type)
|
|
146
|
-
*/
|
|
147
|
-
type GenerationProgress = GenerationProgress$1;
|
|
148
|
-
|
|
149
|
-
type Annotation = components['schemas']['Annotation'];
|
|
150
|
-
type Motivation = components['schemas']['Motivation'];
|
|
151
|
-
interface SelectionData {
|
|
152
|
-
exact: string;
|
|
153
|
-
start: number;
|
|
154
|
-
end: number;
|
|
155
|
-
svgSelector?: string;
|
|
156
|
-
fragmentSelector?: string;
|
|
157
|
-
conformsTo?: string;
|
|
158
|
-
prefix?: string;
|
|
159
|
-
suffix?: string;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Unified event map for all application events
|
|
163
|
-
*
|
|
164
|
-
* Consolidates events from:
|
|
165
|
-
* - MakeMeaningEventBus (document/annotation operations)
|
|
166
|
-
* - NavigationEventBus (navigation and sidebar UI)
|
|
167
|
-
* - GlobalSettingsEventBus (app-wide settings)
|
|
168
|
-
*/
|
|
169
|
-
type EventMap = {
|
|
170
|
-
'make-meaning:event': ResourceEvent;
|
|
171
|
-
'detection:started': Extract<ResourceEvent, {
|
|
172
|
-
type: 'job.started';
|
|
173
|
-
}>;
|
|
174
|
-
'detection:entity-found': Extract<ResourceEvent, {
|
|
175
|
-
type: 'annotation.added';
|
|
176
|
-
}>;
|
|
177
|
-
'detection:completed': Extract<ResourceEvent, {
|
|
178
|
-
type: 'job.completed';
|
|
179
|
-
}>;
|
|
180
|
-
'detection:failed': Extract<ResourceEvent, {
|
|
181
|
-
type: 'job.failed';
|
|
182
|
-
}>;
|
|
183
|
-
'detection:progress': DetectionProgress;
|
|
184
|
-
'annotation:added': Extract<ResourceEvent, {
|
|
185
|
-
type: 'annotation.added';
|
|
186
|
-
}>;
|
|
187
|
-
'annotation:removed': Extract<ResourceEvent, {
|
|
188
|
-
type: 'annotation.removed';
|
|
189
|
-
}>;
|
|
190
|
-
'annotation:updated': Extract<ResourceEvent, {
|
|
191
|
-
type: 'annotation.body.updated';
|
|
192
|
-
}>;
|
|
193
|
-
'entity-tag:added': Extract<ResourceEvent, {
|
|
194
|
-
type: 'entitytag.added';
|
|
195
|
-
}>;
|
|
196
|
-
'entity-tag:removed': Extract<ResourceEvent, {
|
|
197
|
-
type: 'entitytag.removed';
|
|
198
|
-
}>;
|
|
199
|
-
'resource:archived': Extract<ResourceEvent, {
|
|
200
|
-
type: 'resource.archived';
|
|
201
|
-
}>;
|
|
202
|
-
'resource:unarchived': Extract<ResourceEvent, {
|
|
203
|
-
type: 'resource.unarchived';
|
|
204
|
-
}>;
|
|
205
|
-
'selection:comment-requested': SelectionData;
|
|
206
|
-
'selection:tag-requested': SelectionData;
|
|
207
|
-
'selection:assessment-requested': SelectionData;
|
|
208
|
-
'selection:reference-requested': SelectionData;
|
|
209
|
-
'annotation:requested': {
|
|
210
|
-
selector: Selector | Selector[];
|
|
211
|
-
motivation: Motivation;
|
|
212
|
-
};
|
|
213
|
-
'annotation:cancel-pending': void;
|
|
214
|
-
'annotation:hover': {
|
|
215
|
-
annotationId: string | null;
|
|
216
|
-
};
|
|
217
|
-
'annotation:click': {
|
|
218
|
-
annotationId: string;
|
|
219
|
-
motivation: Motivation;
|
|
220
|
-
};
|
|
221
|
-
'annotation:focus': {
|
|
222
|
-
annotationId: string | null;
|
|
223
|
-
};
|
|
224
|
-
'annotation:sparkle': {
|
|
225
|
-
annotationId: string;
|
|
226
|
-
};
|
|
227
|
-
'panel:toggle': {
|
|
228
|
-
panel: string;
|
|
229
|
-
};
|
|
230
|
-
'panel:open': {
|
|
231
|
-
panel: string;
|
|
232
|
-
scrollToAnnotationId?: string;
|
|
233
|
-
motivation?: string;
|
|
234
|
-
};
|
|
235
|
-
'panel:close': void;
|
|
236
|
-
'view:mode-toggled': void;
|
|
237
|
-
'toolbar:selection-changed': {
|
|
238
|
-
motivation: string | null;
|
|
239
|
-
};
|
|
240
|
-
'toolbar:click-changed': {
|
|
241
|
-
action: string;
|
|
242
|
-
};
|
|
243
|
-
'toolbar:shape-changed': {
|
|
244
|
-
shape: string;
|
|
245
|
-
};
|
|
246
|
-
'navigation:sidebar-toggle': void;
|
|
247
|
-
'navigation:resource-close': {
|
|
248
|
-
resourceId: string;
|
|
249
|
-
};
|
|
250
|
-
'navigation:resource-reorder': {
|
|
251
|
-
oldIndex: number;
|
|
252
|
-
newIndex: number;
|
|
253
|
-
};
|
|
254
|
-
'navigation:link-clicked': {
|
|
255
|
-
href: string;
|
|
256
|
-
label?: string;
|
|
257
|
-
};
|
|
258
|
-
'navigation:router-push': {
|
|
259
|
-
path: string;
|
|
260
|
-
reason?: string;
|
|
261
|
-
};
|
|
262
|
-
'navigation:external-navigate': {
|
|
263
|
-
url: string;
|
|
264
|
-
resourceId?: string;
|
|
265
|
-
cancelFallback: () => void;
|
|
266
|
-
};
|
|
267
|
-
'navigation:reference-navigate': {
|
|
268
|
-
documentId: string;
|
|
269
|
-
};
|
|
270
|
-
'navigation:entity-type-clicked': {
|
|
271
|
-
entityType: string;
|
|
272
|
-
};
|
|
273
|
-
'settings:theme-changed': {
|
|
274
|
-
theme: 'light' | 'dark' | 'system';
|
|
275
|
-
};
|
|
276
|
-
'settings:line-numbers-toggled': void;
|
|
277
|
-
'settings:locale-changed': {
|
|
278
|
-
locale: string;
|
|
279
|
-
};
|
|
280
|
-
'resource:archive': void;
|
|
281
|
-
'resource:unarchive': void;
|
|
282
|
-
'resource:clone': void;
|
|
283
|
-
'job:cancel-requested': {
|
|
284
|
-
jobType: 'detection' | 'generation';
|
|
285
|
-
};
|
|
286
|
-
'annotation:create': {
|
|
287
|
-
motivation: Motivation;
|
|
288
|
-
selector: Selector | Selector[];
|
|
289
|
-
body: components['schemas']['AnnotationBody'][];
|
|
290
|
-
};
|
|
291
|
-
'annotation:created': {
|
|
292
|
-
annotation: Annotation;
|
|
293
|
-
};
|
|
294
|
-
'annotation:create-failed': {
|
|
295
|
-
error: Error;
|
|
296
|
-
};
|
|
297
|
-
'annotation:delete': {
|
|
298
|
-
annotationId: string;
|
|
299
|
-
};
|
|
300
|
-
'annotation:deleted': {
|
|
301
|
-
annotationId: string;
|
|
302
|
-
};
|
|
303
|
-
'annotation:delete-failed': {
|
|
304
|
-
error: Error;
|
|
305
|
-
};
|
|
306
|
-
'annotation:update-body': {
|
|
307
|
-
annotationUri: string;
|
|
308
|
-
resourceId: string;
|
|
309
|
-
operations: Array<{
|
|
310
|
-
op: 'add' | 'remove' | 'replace';
|
|
311
|
-
item?: components['schemas']['AnnotationBody'];
|
|
312
|
-
oldItem?: components['schemas']['AnnotationBody'];
|
|
313
|
-
newItem?: components['schemas']['AnnotationBody'];
|
|
314
|
-
}>;
|
|
315
|
-
};
|
|
316
|
-
'annotation:body-updated': {
|
|
317
|
-
annotationUri: string;
|
|
318
|
-
};
|
|
319
|
-
'annotation:body-update-failed': {
|
|
320
|
-
error: Error;
|
|
321
|
-
};
|
|
322
|
-
'detection:start': {
|
|
323
|
-
motivation: Motivation;
|
|
324
|
-
options: {
|
|
325
|
-
instructions?: string;
|
|
326
|
-
/** Comment tone */
|
|
327
|
-
tone?: 'scholarly' | 'explanatory' | 'conversational' | 'technical' | 'analytical' | 'critical' | 'balanced' | 'constructive';
|
|
328
|
-
density?: number;
|
|
329
|
-
entityTypes?: string[];
|
|
330
|
-
includeDescriptiveReferences?: boolean;
|
|
331
|
-
schemaId?: string;
|
|
332
|
-
categories?: string[];
|
|
333
|
-
};
|
|
334
|
-
};
|
|
335
|
-
'detection:complete': {
|
|
336
|
-
motivation?: Motivation;
|
|
337
|
-
resourceUri?: ResourceUri;
|
|
338
|
-
progress?: DetectionProgress;
|
|
339
|
-
};
|
|
340
|
-
'detection:cancelled': void;
|
|
341
|
-
'detection:dismiss-progress': void;
|
|
342
|
-
'generation:start': {
|
|
343
|
-
annotationUri: string;
|
|
344
|
-
resourceUri: string;
|
|
345
|
-
options: {
|
|
346
|
-
title: string;
|
|
347
|
-
prompt?: string;
|
|
348
|
-
language?: string;
|
|
349
|
-
temperature?: number;
|
|
350
|
-
maxTokens?: number;
|
|
351
|
-
context: GenerationContext;
|
|
352
|
-
};
|
|
353
|
-
};
|
|
354
|
-
'generation:progress': GenerationProgress;
|
|
355
|
-
'generation:complete': {
|
|
356
|
-
annotationUri: string;
|
|
357
|
-
progress: GenerationProgress;
|
|
358
|
-
};
|
|
359
|
-
'generation:failed': {
|
|
360
|
-
error: Error;
|
|
361
|
-
};
|
|
362
|
-
'generation:modal-open': {
|
|
363
|
-
annotationUri: string;
|
|
364
|
-
resourceUri: string;
|
|
365
|
-
defaultTitle: string;
|
|
366
|
-
};
|
|
367
|
-
'reference:create-manual': {
|
|
368
|
-
annotationUri: string;
|
|
369
|
-
title: string;
|
|
370
|
-
entityTypes: string[];
|
|
371
|
-
};
|
|
372
|
-
'reference:link': {
|
|
373
|
-
annotationUri: string;
|
|
374
|
-
searchTerm: string;
|
|
375
|
-
};
|
|
376
|
-
'resolution:search-requested': {
|
|
377
|
-
referenceId: string;
|
|
378
|
-
searchTerm: string;
|
|
379
|
-
};
|
|
380
|
-
'context:retrieval-requested': {
|
|
381
|
-
annotationUri: string;
|
|
382
|
-
resourceUri: string;
|
|
383
|
-
};
|
|
384
|
-
'context:retrieval-complete': {
|
|
385
|
-
annotationUri: string;
|
|
386
|
-
context: GenerationContext;
|
|
387
|
-
};
|
|
388
|
-
'context:retrieval-failed': {
|
|
389
|
-
annotationUri: string;
|
|
390
|
-
error: Error;
|
|
391
|
-
};
|
|
392
|
-
};
|
|
393
|
-
type EventBus = ReturnType<typeof mitt<EventMap>> & {
|
|
394
|
-
busId: string;
|
|
395
|
-
};
|
|
396
|
-
/**
|
|
397
|
-
* Reset the global event bus - FOR TESTING ONLY.
|
|
398
|
-
*
|
|
399
|
-
* Call this in test setup (beforeEach) to ensure test isolation.
|
|
400
|
-
* Each test gets a fresh event bus with no lingering subscriptions.
|
|
401
|
-
*
|
|
402
|
-
* @example
|
|
403
|
-
* ```typescript
|
|
404
|
-
* beforeEach(() => {
|
|
405
|
-
* resetEventBusForTesting();
|
|
406
|
-
* });
|
|
407
|
-
* ```
|
|
408
|
-
*/
|
|
409
|
-
declare function resetEventBusForTesting(): void;
|
|
410
|
-
interface EventBusProviderProps {
|
|
411
|
-
children: ReactNode;
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Unified event bus provider for all application events
|
|
415
|
-
*
|
|
416
|
-
* Consolidates three previous event buses:
|
|
417
|
-
* - MakeMeaningEventBus (document/annotation operations)
|
|
418
|
-
* - NavigationEventBus (navigation and sidebar UI)
|
|
419
|
-
* - GlobalSettingsEventBus (app-wide settings)
|
|
420
|
-
*
|
|
421
|
-
* Benefits:
|
|
422
|
-
* - Single import: useEventBus()
|
|
423
|
-
* - No decision fatigue about which bus to use
|
|
424
|
-
* - Easier cross-domain coordination
|
|
425
|
-
* - Simpler provider hierarchy
|
|
426
|
-
*
|
|
427
|
-
* NOTE: This provider uses a global singleton event bus to ensure all components
|
|
428
|
-
* share the same instance. Multiple providers in the tree will all reference the
|
|
429
|
-
* same global bus.
|
|
430
|
-
*
|
|
431
|
-
* Operation handlers (API calls triggered by events) are set up separately via
|
|
432
|
-
* the useResolutionFlow hook, which should be called at the resource page level.
|
|
433
|
-
*/
|
|
434
|
-
declare function EventBusProvider({ children, }: EventBusProviderProps): react_jsx_runtime.JSX.Element;
|
|
435
|
-
/**
|
|
436
|
-
* Hook to access the unified event bus
|
|
437
|
-
*
|
|
438
|
-
* Use this everywhere instead of:
|
|
439
|
-
* - useMakeMeaningEvents()
|
|
440
|
-
* - useNavigationEvents()
|
|
441
|
-
* - useGlobalSettingsEvents()
|
|
442
|
-
*
|
|
443
|
-
* @example
|
|
444
|
-
* ```typescript
|
|
445
|
-
* const eventBus = useEventBus();
|
|
446
|
-
*
|
|
447
|
-
* // Emit any event
|
|
448
|
-
* eventBus.emit('annotation:hover', { annotationId: '123' });
|
|
449
|
-
* eventBus.emit('navigation:sidebar-toggle', undefined);
|
|
450
|
-
* eventBus.emit('settings:theme-changed', { theme: 'dark' });
|
|
451
|
-
*
|
|
452
|
-
* // Subscribe to any event
|
|
453
|
-
* useEffect(() => {
|
|
454
|
-
* const handler = ({ annotationId }) => console.log(annotationId);
|
|
455
|
-
* eventBus.on('annotation:hover', handler);
|
|
456
|
-
* return () => eventBus.off('annotation:hover', handler);
|
|
457
|
-
* }, []);
|
|
458
|
-
* ```
|
|
459
|
-
*/
|
|
460
|
-
declare function useEventBus(): EventBus;
|
|
461
|
-
|
|
462
|
-
export { type DetectionProgress as D, type EventBus as E, type GenerationProgress as G, type OpenResourcesManager as O, type SessionManager as S, type TranslationManager as T, type EventMap as a, type OpenResource as b, type SessionState as c, type EventBusProviderProps as d, EventBusProvider as e, resetEventBusForTesting as r, useEventBus as u };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/pdf-annotation/PdfAnnotationCanvas.tsx","../src/lib/pdf-coordinates.ts","../src/lib/browser-pdfjs.ts"],"sourcesContent":["'use client';\n\nimport React, { useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { createHoverHandlers } from '../../hooks/useAttentionFlow';\nimport type { components, ResourceUri } from '@semiont/api-client';\nimport { getTargetSelector } from '@semiont/api-client';\nimport type { SelectionMotivation } from '../annotation/AnnotateToolbar';\nimport type { EventBus } from '../../contexts/EventBusContext';\nimport {\n canvasToPdfCoordinates,\n pdfToCanvasCoordinates,\n createFragmentSelector,\n parseFragmentSelector,\n getPageFromFragment,\n type CanvasRectangle\n} from '../../lib/pdf-coordinates';\nimport {\n loadPdfDocument,\n renderPdfPageToDataUrl,\n type PDFDocumentProxy\n} from '../../lib/browser-pdfjs';\nimport './PdfAnnotationCanvas.css';\n\ntype Annotation = components['schemas']['Annotation'];\n\nexport type DrawingMode = 'rectangle' | 'circle' | 'polygon' | null;\n\n/**\n * Get color for annotation based on motivation\n */\nfunction getMotivationColor(motivation: SelectionMotivation | null): { stroke: string; fill: string } {\n if (!motivation) {\n return { stroke: 'rgb(156, 163, 175)', fill: 'rgba(156, 163, 175, 0.2)' };\n }\n\n switch (motivation) {\n case 'highlighting':\n return { stroke: 'rgb(250, 204, 21)', fill: 'rgba(250, 204, 21, 0.3)' };\n case 'linking':\n return { stroke: 'rgb(59, 130, 246)', fill: 'rgba(59, 130, 246, 0.2)' };\n case 'assessing':\n return { stroke: 'rgb(239, 68, 68)', fill: 'rgba(239, 68, 68, 0.2)' };\n case 'commenting':\n return { stroke: 'rgb(255, 255, 255)', fill: 'rgba(255, 255, 255, 0.2)' };\n default:\n return { stroke: 'rgb(156, 163, 175)', fill: 'rgba(156, 163, 175, 0.2)' };\n }\n}\n\ninterface PdfAnnotationCanvasProps {\n resourceUri: ResourceUri;\n existingAnnotations?: Annotation[];\n drawingMode: DrawingMode;\n selectedMotivation?: SelectionMotivation | null;\n eventBus?: EventBus;\n hoveredAnnotationId?: string | null;\n selectedAnnotationId?: string | null;\n}\n\n/**\n * PDF annotation canvas with page navigation and rectangle drawing\n *\n * @emits annotation:click - Annotation clicked on PDF. Payload: { annotationId: string, motivation: Motivation }\n * @emits annotation:requested - New annotation drawn on PDF. Payload: { selector: FragmentSelector, motivation: SelectionMotivation }\n * @emits annotation:hover - Annotation hovered or unhovered. Payload: { annotationId: string | null }\n */\nexport function PdfAnnotationCanvas({\n resourceUri,\n existingAnnotations = [],\n drawingMode,\n selectedMotivation,\n eventBus,\n hoveredAnnotationId,\n selectedAnnotationId\n}: PdfAnnotationCanvasProps) {\n const pdfUrl = useMemo(() => {\n const resourceId = resourceUri.split('/').pop();\n return `/api/resources/${resourceId}`;\n }, [resourceUri]);\n\n // Removed excessive logging\n\n // PDF state\n const [pdfDoc, setPdfDoc] = useState<PDFDocumentProxy | null>(null);\n const [numPages, setNumPages] = useState<number>(0);\n const [pageNumber, setPageNumber] = useState(1);\n const [pageImageUrl, setPageImageUrl] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [pageDimensions, setPageDimensions] = useState<{ width: number; height: number } | null>(null);\n const [displayDimensions, setDisplayDimensions] = useState<{ width: number; height: number } | null>(null);\n const [scale] = useState(1.5); // Fixed scale for better quality\n\n // Drawing state\n const [isDrawing, setIsDrawing] = useState(false);\n const [selection, setSelection] = useState<CanvasRectangle | null>(null);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const imageRef = useRef<HTMLImageElement>(null);\n\n // Load PDF document on mount\n useEffect(() => {\n let cancelled = false;\n\n async function loadPdf() {\n try {\n setIsLoading(true);\n setError(null);\n\n const doc = await loadPdfDocument(pdfUrl);\n\n if (cancelled) return;\n\n setPdfDoc(doc);\n setNumPages(doc.numPages);\n setIsLoading(false);\n } catch (err) {\n if (cancelled) return;\n\n console.error('Error loading PDF:', err);\n setError('Failed to load PDF');\n setIsLoading(false);\n }\n }\n\n loadPdf();\n\n return () => {\n cancelled = true;\n };\n }, [pdfUrl]);\n\n // Load current page when page number changes\n useEffect(() => {\n if (!pdfDoc) return;\n\n let cancelled = false;\n const doc = pdfDoc;\n\n async function loadPage() {\n try {\n const page = await doc.getPage(pageNumber);\n\n if (cancelled) return;\n\n // Get page dimensions (at scale 1.0)\n const viewport = page.getViewport({ scale: 1.0 });\n setPageDimensions({\n width: viewport.width,\n height: viewport.height\n });\n\n // Render page to image\n const { dataUrl } = await renderPdfPageToDataUrl(page, scale);\n\n if (cancelled) return;\n\n setPageImageUrl(dataUrl);\n } catch (err) {\n if (cancelled) return;\n\n console.error('Error loading page:', err);\n setError('Failed to load page');\n }\n }\n\n loadPage();\n\n return () => {\n cancelled = true;\n };\n }, [pdfDoc, pageNumber, scale]);\n\n // Update display dimensions on resize\n useEffect(() => {\n const updateDisplayDimensions = () => {\n if (imageRef.current) {\n setDisplayDimensions({\n width: imageRef.current.clientWidth,\n height: imageRef.current.clientHeight\n });\n }\n };\n\n updateDisplayDimensions();\n\n // Use ResizeObserver to detect image element size changes\n // This catches: sidebar open/close, window resize, font size changes, etc.\n let resizeObserver: ResizeObserver | null = null;\n\n try {\n resizeObserver = new ResizeObserver(updateDisplayDimensions);\n if (imageRef.current) {\n resizeObserver.observe(imageRef.current);\n }\n } catch (error) {\n // Fallback for browsers without ResizeObserver support\n console.warn('ResizeObserver not supported, falling back to window resize listener');\n window.addEventListener('resize', updateDisplayDimensions);\n }\n\n return () => {\n if (resizeObserver) {\n resizeObserver.disconnect();\n } else {\n window.removeEventListener('resize', updateDisplayDimensions);\n }\n };\n }, [pageImageUrl]);\n\n // Mouse event handlers for drawing\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\n if (!drawingMode) return;\n if (!imageRef.current) return;\n\n const rect = imageRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n // Clear any previous selection when starting new drawing\n setIsDrawing(true);\n setSelection({\n startX: x,\n startY: y,\n endX: x,\n endY: y\n });\n }, [drawingMode]);\n\n const handleMouseMove = useCallback((e: React.MouseEvent) => {\n if (!isDrawing || !selection || !imageRef.current) return;\n\n const rect = imageRef.current.getBoundingClientRect();\n\n setSelection({\n ...selection,\n endX: e.clientX - rect.left,\n endY: e.clientY - rect.top\n });\n }, [isDrawing, selection]);\n\n const handleMouseUp = useCallback(() => {\n if (!isDrawing || !selection || !pageDimensions || !displayDimensions || !eventBus) {\n setIsDrawing(false);\n setSelection(null);\n return;\n }\n\n // Calculate drag distance\n const dragDistance = Math.sqrt(\n Math.pow(selection.endX - selection.startX, 2) +\n Math.pow(selection.endY - selection.startY, 2)\n );\n\n // Minimum drag threshold in pixels (10px)\n const MIN_DRAG_DISTANCE = 10;\n\n if (dragDistance < MIN_DRAG_DISTANCE) {\n // This was a click, not a drag - check if we clicked an existing annotation\n if (existingAnnotations.length > 0) {\n const clickedAnnotation = pageAnnotations.find(ann => {\n const fragmentSel = getFragmentSelector(ann.target);\n if (!fragmentSel) return false;\n\n const pdfCoord = parseFragmentSelector(fragmentSel.value);\n if (!pdfCoord) return false;\n\n const rect = pdfToCanvasCoordinates(pdfCoord, pageDimensions.height, 1.0);\n\n // Scale to display coordinates\n const scaleX = displayDimensions.width / pageDimensions.width;\n const scaleY = displayDimensions.height / pageDimensions.height;\n\n const displayX = rect.x * scaleX;\n const displayY = rect.y * scaleY;\n const displayWidth = rect.width * scaleX;\n const displayHeight = rect.height * scaleY;\n\n return (\n selection.endX >= displayX &&\n selection.endX <= displayX + displayWidth &&\n selection.endY >= displayY &&\n selection.endY <= displayY + displayHeight\n );\n });\n\n if (clickedAnnotation) {\n eventBus?.emit('annotation:click', { annotationId: clickedAnnotation.id, motivation: clickedAnnotation.motivation });\n setIsDrawing(false);\n setSelection(null);\n return;\n }\n }\n\n // Click on empty space - do nothing\n setIsDrawing(false);\n setSelection(null);\n return;\n }\n\n // This was a drag - create new annotation\n // Scale selection from display coordinates to native page coordinates\n const scaleX = pageDimensions.width / displayDimensions.width;\n const scaleY = pageDimensions.height / displayDimensions.height;\n\n const nativeSelection: CanvasRectangle = {\n startX: selection.startX * scaleX,\n startY: selection.startY * scaleY,\n endX: selection.endX * scaleX,\n endY: selection.endY * scaleY\n };\n\n // Convert canvas coordinates to PDF coordinates\n const pdfCoord = canvasToPdfCoordinates(\n nativeSelection,\n pageNumber,\n pageDimensions.width,\n pageDimensions.height,\n 1.0 // Use scale 1.0 since we already scaled to native coords\n );\n\n // Create FragmentSelector\n const fragmentSelector = createFragmentSelector(pdfCoord);\n\n // Emit annotation:requested event with FragmentSelector\n if (selectedMotivation) {\n eventBus.emit('annotation:requested', {\n selector: {\n type: 'FragmentSelector',\n conformsTo: 'http://tools.ietf.org/rfc/rfc3778',\n value: fragmentSelector\n },\n motivation: selectedMotivation\n });\n }\n\n // Keep drawing state active to show preview until annotation is persisted\n // The parent component should clear this by changing drawingMode after save\n setIsDrawing(false);\n // Note: We keep selection so the preview remains visible\n // It will be cleared when drawingMode changes or user starts new selection\n }, [isDrawing, selection, pageNumber, pageDimensions, displayDimensions, selectedMotivation, existingAnnotations]);\n\n // Helper to get FragmentSelector from annotation target\n const getFragmentSelector = (target: Annotation['target']) => {\n const selector = getTargetSelector(target);\n if (!selector) return null;\n const selectors = Array.isArray(selector) ? selector : [selector];\n\n const found = selectors.find(s => s.type === 'FragmentSelector');\n if (!found || found.type !== 'FragmentSelector') return null;\n return found as { type: 'FragmentSelector'; value: string; conformsTo?: string };\n };\n\n // Filter annotations for current page\n const pageAnnotations = existingAnnotations.filter(ann => {\n const fragmentSel = getFragmentSelector(ann.target);\n if (!fragmentSel) return false;\n const page = getPageFromFragment(fragmentSel.value);\n return page === pageNumber;\n });\n\n // Hover handlers with currentHover guard and dwell delay\n const { handleMouseEnter, handleMouseLeave } = useMemo(\n () => createHoverHandlers((annotationId) => eventBus?.emit('annotation:hover', { annotationId })),\n [eventBus]\n );\n\n // Calculate motivation color\n const { stroke, fill } = getMotivationColor(selectedMotivation ?? null);\n\n if (error) {\n return <div className=\"semiont-pdf-annotation-canvas__error\">{error}</div>;\n }\n\n return (\n <div className=\"semiont-pdf-annotation-canvas\">\n {isLoading && <div className=\"semiont-pdf-annotation-canvas__loading\">Loading PDF...</div>}\n\n <div\n ref={containerRef}\n className=\"semiont-pdf-annotation-canvas__container\"\n style={{ display: isLoading ? 'none' : undefined }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onMouseLeave={() => {\n if (isDrawing) {\n setIsDrawing(false);\n setSelection(null);\n }\n }}\n data-drawing-mode={drawingMode || 'none'}\n >\n {/* PDF page rendered as image */}\n {pageImageUrl && (\n <img\n ref={imageRef}\n src={pageImageUrl}\n alt={`PDF page ${pageNumber}`}\n className=\"semiont-pdf-annotation-canvas__image\"\n draggable={false}\n style={{ pointerEvents: 'none' }}\n onLoad={() => {\n // Use double RAF to ensure layout is complete even in onLoad\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (imageRef.current) {\n setDisplayDimensions({\n width: imageRef.current.clientWidth,\n height: imageRef.current.clientHeight\n });\n }\n });\n });\n }}\n />\n )}\n\n {/* SVG overlay for annotations */}\n {displayDimensions && pageDimensions && (\n <div className=\"semiont-pdf-annotation-canvas__overlay-container\">\n <div className=\"semiont-pdf-annotation-canvas__overlay\">\n <svg\n className=\"semiont-pdf-annotation-canvas__svg\"\n width={displayDimensions.width}\n height={displayDimensions.height}\n >\n {/* Render existing annotations for this page */}\n {pageAnnotations.map(ann => {\n const fragmentSel = getFragmentSelector(ann.target);\n if (!fragmentSel) return null;\n\n const pdfCoord = parseFragmentSelector(fragmentSel.value);\n if (!pdfCoord) return null;\n\n const rect = pdfToCanvasCoordinates(pdfCoord, pageDimensions.height, 1.0);\n\n // Scale to display coordinates\n const scaleX = displayDimensions.width / pageDimensions.width;\n const scaleY = displayDimensions.height / pageDimensions.height;\n\n const isHovered = ann.id === hoveredAnnotationId;\n const isSelected = ann.id === selectedAnnotationId;\n\n // Get color for this annotation's motivation (not the selected motivation)\n const annMotivation = ann.motivation as SelectionMotivation | null;\n const { stroke: annStroke, fill: annFill } = getMotivationColor(annMotivation);\n\n return (\n <rect\n key={ann.id}\n x={rect.x * scaleX}\n y={rect.y * scaleY}\n width={rect.width * scaleX}\n height={rect.height * scaleY}\n stroke={annStroke}\n strokeWidth={isSelected ? 4 : isHovered ? 3 : 2}\n fill={annFill}\n style={{\n pointerEvents: 'auto',\n cursor: 'pointer',\n opacity: isSelected ? 1 : isHovered ? 0.9 : 0.7\n }}\n onClick={() => eventBus?.emit('annotation:click', { annotationId: ann.id, motivation: ann.motivation })}\n onMouseEnter={() => handleMouseEnter(ann.id)}\n onMouseLeave={handleMouseLeave}\n />\n );\n })}\n\n {/* Render current selection while drawing or awaiting save */}\n {selection && (() => {\n const rectX = Math.min(selection.startX, selection.endX);\n const rectY = Math.min(selection.startY, selection.endY);\n const rectWidth = Math.abs(selection.endX - selection.startX);\n const rectHeight = Math.abs(selection.endY - selection.startY);\n\n // PDF only supports rectangle shapes (FragmentSelector with viewrect)\n // Circle/polygon are disabled in the UI for PDF media types\n return (\n <rect\n x={rectX}\n y={rectY}\n width={rectWidth}\n height={rectHeight}\n stroke={stroke}\n strokeWidth={2}\n strokeDasharray=\"5,5\"\n fill={fill}\n pointerEvents=\"none\"\n />\n );\n })()}\n </svg>\n </div>\n </div>\n )}\n </div>\n\n {/* Page navigation controls */}\n {numPages > 0 && (\n <div className=\"semiont-pdf-annotation-canvas__controls\">\n <button\n disabled={pageNumber <= 1}\n onClick={() => setPageNumber(pageNumber - 1)}\n className=\"semiont-pdf-annotation-canvas__button\"\n >\n Previous\n </button>\n <span className=\"semiont-pdf-annotation-canvas__page-info\">\n Page {pageNumber} of {numPages}\n </span>\n <button\n disabled={pageNumber >= numPages}\n onClick={() => setPageNumber(pageNumber + 1)}\n className=\"semiont-pdf-annotation-canvas__button\"\n >\n Next\n </button>\n </div>\n )}\n </div>\n );\n}\n","/**\n * PDF Coordinate Utilities\n *\n * Handles coordinate transformations between:\n * - Canvas space (pixels, top-left origin, Y increases downward)\n * - PDF space (points, bottom-left origin, Y increases upward)\n *\n * Based on RFC 3778 PDF Fragment Identifiers:\n * https://tools.ietf.org/html/rfc3778\n */\n\nexport interface Rectangle {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface PdfCoordinate {\n page: number;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface CanvasRectangle {\n startX: number;\n startY: number;\n endX: number;\n endY: number;\n}\n\n/**\n * Convert canvas coordinates to PDF coordinates\n *\n * Canvas: Origin at top-left, Y increases downward\n * PDF: Origin at bottom-left, Y increases upward\n *\n * @param canvasRect - Rectangle in canvas pixel coordinates\n * @param page - PDF page number (1-indexed)\n * @param pageWidth - PDF page width in points\n * @param pageHeight - PDF page height in points\n * @param scale - Current canvas scale factor\n */\nexport function canvasToPdfCoordinates(\n canvasRect: CanvasRectangle,\n page: number,\n _pageWidth: number,\n pageHeight: number,\n scale: number = 1\n): PdfCoordinate {\n // Normalize rectangle (handle drag in any direction)\n const x1 = Math.min(canvasRect.startX, canvasRect.endX);\n const y1 = Math.min(canvasRect.startY, canvasRect.endY);\n const x2 = Math.max(canvasRect.startX, canvasRect.endX);\n const y2 = Math.max(canvasRect.startY, canvasRect.endY);\n\n // Convert from canvas pixels to PDF points\n const pdfX = x1 / scale;\n const pdfWidth = (x2 - x1) / scale;\n\n // Flip Y coordinate (canvas top-left to PDF bottom-left)\n const pdfY = pageHeight - (y2 / scale);\n const pdfHeight = (y2 - y1) / scale;\n\n return {\n page,\n x: Math.round(pdfX),\n y: Math.round(pdfY),\n width: Math.round(pdfWidth),\n height: Math.round(pdfHeight)\n };\n}\n\n/**\n * Convert PDF coordinates to canvas coordinates\n *\n * @param pdfCoord - Coordinates in PDF space\n * @param pageHeight - PDF page height in points\n * @param scale - Current canvas scale factor\n */\nexport function pdfToCanvasCoordinates(\n pdfCoord: PdfCoordinate,\n pageHeight: number,\n scale: number = 1\n): Rectangle {\n // Convert from PDF points to canvas pixels\n const canvasX = pdfCoord.x * scale;\n const canvasWidth = pdfCoord.width * scale;\n\n // Flip Y coordinate (PDF bottom-left to canvas top-left)\n const canvasY = (pageHeight - pdfCoord.y - pdfCoord.height) * scale;\n const canvasHeight = pdfCoord.height * scale;\n\n return {\n x: canvasX,\n y: canvasY,\n width: canvasWidth,\n height: canvasHeight\n };\n}\n\n/**\n * Generate RFC 3778 FragmentSelector value\n *\n * Format: page=N&viewrect=left,top,width,height\n * All coordinates in PDF points\n */\nexport function createFragmentSelector(coord: PdfCoordinate): string {\n return `page=${coord.page}&viewrect=${coord.x},${coord.y},${coord.width},${coord.height}`;\n}\n\n/**\n * Parse RFC 3778 FragmentSelector value\n *\n * @param fragment - Fragment string like \"page=5&viewrect=100,200,300,400\"\n * @returns Parsed PDF coordinates or null if invalid\n */\nexport function parseFragmentSelector(fragment: string): PdfCoordinate | null {\n try {\n // Parse page number\n const pageMatch = fragment.match(/page=(\\d+)/);\n if (!pageMatch) return null;\n const page = parseInt(pageMatch[1], 10);\n\n // Parse viewrect coordinates\n const viewrectMatch = fragment.match(/viewrect=([\\d.]+),([\\d.]+),([\\d.]+),([\\d.]+)/);\n if (!viewrectMatch) return null;\n\n return {\n page,\n x: parseFloat(viewrectMatch[1]),\n y: parseFloat(viewrectMatch[2]),\n width: parseFloat(viewrectMatch[3]),\n height: parseFloat(viewrectMatch[4])\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Extract page number from FragmentSelector\n */\nexport function getPageFromFragment(fragment: string): number | null {\n const match = fragment.match(/page=(\\d+)/);\n return match ? parseInt(match[1], 10) : null;\n}\n","/**\n * Browser PDF.js utilities\n *\n * Uses native browser PDF.js when available, falls back to CDN.\n * Zero npm dependencies - no webpack bundling issues.\n */\n\n// Type definitions for PDF.js API\nexport interface PDFDocumentProxy {\n numPages: number;\n getPage(pageNumber: number): Promise<PDFPageProxy>;\n}\n\nexport interface PDFPageProxy {\n getViewport(params: { scale: number; rotation?: number }): PDFViewport;\n render(params: PDFRenderParams): PDFRenderTask;\n getTextContent(): Promise<TextContent>;\n}\n\nexport interface PDFViewport {\n width: number;\n height: number;\n scale: number;\n rotation: number;\n}\n\nexport interface PDFRenderParams {\n canvasContext: CanvasRenderingContext2D;\n viewport: PDFViewport;\n}\n\nexport interface PDFRenderTask {\n promise: Promise<void>;\n cancel(): void;\n}\n\nexport interface PDFLib {\n getDocument(params: { data: ArrayBuffer } | { url: string }): PDFLoadingTask;\n GlobalWorkerOptions: {\n workerSrc: string;\n };\n version: string;\n}\n\nexport interface PDFLoadingTask {\n promise: Promise<PDFDocumentProxy>;\n destroy(): void;\n}\n\n/**\n * Text content types (for Phase 2)\n */\nexport interface TextItem {\n str: string;\n dir: string;\n transform: number[]; // [scaleX, skewX, skewY, scaleY, x, y]\n width: number;\n height: number;\n fontName: string;\n hasEOL: boolean;\n}\n\nexport interface TextContent {\n items: TextItem[];\n styles: Record<string, any>;\n}\n\n/**\n * Ensure PDF.js is available, loading from local public folder if needed\n */\nexport async function ensurePdfJs(): Promise<PDFLib> {\n // Check if already available (browser native or already loaded)\n if (typeof window !== 'undefined' && (window as any).pdfjsLib) {\n return (window as any).pdfjsLib as PDFLib;\n }\n\n // Load from local public folder (staged during build)\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = '/pdfjs/pdf.min.mjs';\n script.type = 'module';\n\n script.onload = () => {\n const pdfjsLib = (window as any).pdfjsLib as PDFLib;\n\n if (!pdfjsLib) {\n reject(new Error('PDF.js loaded but pdfjsLib not available'));\n return;\n }\n\n // Configure worker (also served from local public folder)\n pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs/pdf.worker.min.mjs';\n\n resolve(pdfjsLib);\n };\n\n script.onerror = () => {\n reject(new Error('Failed to load PDF.js from /pdfjs/pdf.min.mjs'));\n };\n\n document.head.appendChild(script);\n });\n}\n\n/**\n * Load PDF document from URL or ArrayBuffer\n *\n * When given a URL string, fetches the PDF as ArrayBuffer with credentials\n * to ensure authentication cookies are included in the request.\n */\nexport async function loadPdfDocument(\n source: string | ArrayBuffer\n): Promise<PDFDocumentProxy> {\n const pdfjsLib = await ensurePdfJs();\n\n if (typeof source === 'string') {\n // Fetch as ArrayBuffer first to include authentication cookies\n const response = await fetch(source, {\n credentials: 'include',\n headers: {\n 'Accept': 'application/pdf',\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch PDF: ${response.status} ${response.statusText}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });\n return loadingTask.promise;\n } else {\n const loadingTask = pdfjsLib.getDocument({ data: source });\n return loadingTask.promise;\n }\n}\n\n/**\n * Render PDF page to canvas and return as data URL\n */\nexport async function renderPdfPageToDataUrl(\n page: PDFPageProxy,\n scale = 1.0\n): Promise<{ dataUrl: string; width: number; height: number }> {\n const viewport = page.getViewport({ scale });\n\n // Create canvas\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) {\n throw new Error('Failed to get 2D context');\n }\n\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n\n // Render PDF page to canvas\n const renderTask = page.render({\n canvasContext: context,\n viewport: viewport\n });\n\n await renderTask.promise;\n\n // Convert to data URL\n return {\n dataUrl: canvas.toDataURL('image/png'),\n width: viewport.width,\n height: viewport.height\n };\n}\n\n/**\n * Render PDF page with text content extraction (Phase 2)\n *\n * This function extracts text in parallel with rendering for future\n * text layer support. Currently the text content is available but not used.\n */\nexport async function renderPdfPageWithText(\n page: PDFPageProxy,\n scale = 1.0\n): Promise<{\n dataUrl: string;\n width: number;\n height: number;\n textContent: TextContent;\n}> {\n const viewport = page.getViewport({ scale });\n\n // Create canvas for rendering\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) {\n throw new Error('Failed to get 2D context');\n }\n\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n\n // Render PDF page to canvas\n const renderTask = page.render({\n canvasContext: context,\n viewport: viewport\n });\n\n // Extract text content in parallel (for future text layer support)\n const [, textContent] = await Promise.all([\n renderTask.promise,\n page.getTextContent()\n ]);\n\n // Convert to data URL\n return {\n dataUrl: canvas.toDataURL('image/png'),\n width: viewport.width,\n height: viewport.height,\n textContent\n };\n}\n"],"mappings":"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","names":["error","pdfCoord","scaleX","scaleY"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/contexts/useEventSubscription.ts","../src/hooks/useAttentionFlow.ts"],"sourcesContent":["import { useEffect, useRef, useMemo } from 'react';\nimport type { EventMap } from './EventBusContext';\nimport { useEventBus } from './EventBusContext';\n\n/**\n * Subscribe to an event bus event with automatic cleanup.\n *\n * This hook solves the \"stale closure\" problem by always using the latest\n * version of the handler without re-subscribing.\n *\n * @example\n * ```tsx\n * useEventSubscription('annotation:created', ({ annotation }) => {\n * // This always uses the latest props/state\n * triggerSparkleAnimation(annotation.id);\n * });\n * ```\n */\nexport function useEventSubscription<K extends keyof EventMap>(\n eventName: K,\n handler: (payload: EventMap[K]) => void\n): void {\n const eventBus = useEventBus();\n\n // Store the latest handler in a ref to avoid stale closures\n const handlerRef = useRef(handler);\n\n // Update ref on every render (no re-subscription needed)\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n // Subscribe once, using a stable wrapper that calls the current handler\n useEffect(() => {\n const stableHandler = (payload: EventMap[K]) => {\n handlerRef.current(payload);\n };\n\n eventBus.on(eventName, stableHandler);\n\n return () => {\n eventBus.off(eventName, stableHandler);\n };\n }, [eventName]); // eventBus is stable, only re-subscribe if event name changes\n}\n\n/**\n * Subscribe to multiple events at once.\n *\n * @example\n * ```tsx\n * useEventSubscriptions({\n * 'annotation:created': ({ annotation }) => setNewAnnotation(annotation),\n * 'annotation:deleted': ({ annotationId }) => removeAnnotation(annotationId),\n * });\n * ```\n */\nexport function useEventSubscriptions(\n subscriptions: {\n [K in keyof EventMap]?: (payload: EventMap[K]) => void;\n }\n): void {\n const eventBus = useEventBus();\n\n // Store the latest handlers in refs\n const handlersRef = useRef(subscriptions);\n\n // Update refs on every render\n useEffect(() => {\n handlersRef.current = subscriptions;\n });\n\n // Get stable list of event names to subscribe to\n const eventNames = useMemo(\n () => Object.keys(subscriptions).sort(),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [Object.keys(subscriptions).sort().join(',')]\n );\n\n // Subscribe once per event - only re-subscribe if event names actually change\n useEffect(() => {\n const stableHandlers = new Map<keyof EventMap, (payload: any) => void>();\n\n // Create stable wrappers for each subscription\n for (const eventName of eventNames) {\n const stableHandler = (payload: any) => {\n const currentHandler = handlersRef.current[eventName as keyof EventMap];\n if (currentHandler) {\n currentHandler(payload);\n } else {\n console.warn('[useEventSubscriptions] No current handler found for:', eventName);\n }\n };\n\n stableHandlers.set(eventName as keyof EventMap, stableHandler);\n eventBus.on(eventName as keyof EventMap, stableHandler);\n }\n\n // Cleanup all subscriptions\n return () => {\n for (const [eventName, stableHandler] of stableHandlers) {\n eventBus.off(eventName, stableHandler);\n }\n };\n }, [eventNames]); // eventBus is stable singleton - never in deps; only re-subscribe if event names change\n}\n","/**\n * useAttentionFlow — Annotation attention / pointer coordination hook\n *\n * Manages which annotation currently has the user's attention:\n * - Hover state (hoveredAnnotationId)\n * - Hover → sparkle relay\n * - Click → focus relay\n *\n * Follows react-rxjs-guide.md Layer 2 pattern: Hook bridge that\n * subscribes to events and pushes values into React state.\n *\n * Note: annotation:sparkle visual effect (triggerSparkleAnimation) is owned by\n * ResourceViewerPage, which subscribes to annotation:sparkle and delegates to\n * ResourceAnnotationsContext. This hook emits the signal; it does not render the effect.\n *\n * @subscribes annotation:hover - Sets hoveredAnnotationId; emits annotation:sparkle\n * @subscribes annotation:click - Emits annotation:focus (attention relay only)\n * @emits annotation:sparkle\n * @emits annotation:focus\n */\n\n/**\n * useHoverEmitter / createHoverHandlers — annotation hover emission utilities\n *\n * Centralises two hover quality-of-life behaviours:\n *\n * 1. currentHover guard — suppresses redundant emissions when the mouse\n * moves within the same annotation element (prevents event bus noise).\n *\n * 2. Debounce delay (HOVER_DELAY_MS) — a short timer before emitting\n * annotation:hover, so that transient pass-through movements (user dragging\n * the mouse across the panel to reach a button elsewhere) do not trigger\n * sparkle animations or cross-highlight effects.\n * The delay is cancelled immediately on mouseLeave, so leaving is always instant.\n *\n * Two forms are provided:\n *\n * useHoverEmitter(annotationId)\n * React hook. Returns { onMouseEnter, onMouseLeave } props for JSX elements.\n * Use in panel entries (HighlightEntry, CommentEntry, …).\n *\n * createHoverHandlers(emit)\n * Plain factory. Returns { handleMouseEnter(id), handleMouseLeave(), cleanup }.\n * Use inside useEffect / imperative setup code where hooks cannot be called\n * (BrowseView, CodeMirrorRenderer, AnnotationOverlay, PdfAnnotationCanvas).\n */\n\nimport { useState, useRef, useCallback } from 'react';\nimport { useEventBus } from '../contexts/EventBusContext';\nimport { useEventSubscriptions } from '../contexts/useEventSubscription';\n\n// ─── useAttentionFlow ─────────────────────────────────────────────────────────\n\nexport interface AttentionFlowState {\n hoveredAnnotationId: string | null;\n}\n\nexport function useAttentionFlow(): AttentionFlowState {\n const eventBus = useEventBus();\n const [hoveredAnnotationId, setHoveredAnnotationId] = useState<string | null>(null);\n\n const handleAnnotationHover = useCallback(({ annotationId }: { annotationId: string | null }) => {\n setHoveredAnnotationId(annotationId);\n if (annotationId) {\n eventBus.emit('annotation:sparkle', { annotationId });\n }\n }, []); // eventBus is stable singleton - never in deps\n\n const handleAnnotationClick = useCallback(({ annotationId }: { annotationId: string }) => {\n eventBus.emit('annotation:focus', { annotationId });\n // Scroll to annotation handled by BrowseView via annotation:focus subscription\n }, []); // eventBus is stable singleton - never in deps\n\n useEventSubscriptions({\n 'annotation:hover': handleAnnotationHover,\n 'annotation:click': handleAnnotationClick,\n });\n\n return { hoveredAnnotationId };\n}\n\n// ─── createHoverHandlers (use inside useEffect / imperative setup) ────────────\n\n/** Milliseconds the mouse must dwell before annotation:hover is emitted. */\nexport const HOVER_DELAY_MS = 150;\n\ntype EmitHover = (annotationId: string | null) => void;\n\nexport interface HoverHandlers {\n /** Call with the annotation ID when the mouse enters an annotation element. */\n handleMouseEnter: (annotationId: string) => void;\n /** Call when the mouse leaves the annotation element. */\n handleMouseLeave: () => void;\n /** Cancel any pending timer — call in the useEffect cleanup. */\n cleanup: () => void;\n}\n\nexport function createHoverHandlers(emit: EmitHover): HoverHandlers {\n let currentHover: string | null = null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const cancelTimer = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const handleMouseEnter = (annotationId: string) => {\n if (currentHover === annotationId) return; // already hovering this one\n cancelTimer();\n timer = setTimeout(() => {\n timer = null;\n currentHover = annotationId;\n emit(annotationId);\n }, HOVER_DELAY_MS);\n };\n\n const handleMouseLeave = () => {\n cancelTimer();\n if (currentHover !== null) {\n currentHover = null;\n emit(null);\n }\n };\n\n return { handleMouseEnter, handleMouseLeave, cleanup: cancelTimer };\n}\n\n// ─── useHoverEmitter (use in JSX onMouseEnter / onMouseLeave props) ───────────\n\nexport interface HoverEmitterProps {\n onMouseEnter: () => void;\n onMouseLeave: () => void;\n}\n\n/**\n * React hook that returns onMouseEnter / onMouseLeave props for a single\n * annotation entry element.\n *\n * @param annotationId - The ID of the annotation this element represents.\n */\nexport function useHoverEmitter(annotationId: string): HoverEmitterProps {\n const eventBus = useEventBus();\n const currentHoverRef = useRef<string | null>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const onMouseEnter = useCallback(() => {\n if (currentHoverRef.current === annotationId) return;\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n }\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n currentHoverRef.current = annotationId;\n eventBus.emit('annotation:hover', { annotationId });\n }, HOVER_DELAY_MS);\n }, [annotationId]); // eventBus is stable singleton - never in deps\n\n const onMouseLeave = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n if (currentHoverRef.current !== null) {\n currentHoverRef.current = null;\n eventBus.emit('annotation:hover', { annotationId: null });\n }\n }, []); // eventBus is stable singleton - never in deps\n\n return { onMouseEnter, onMouseLeave };\n}\n"],"mappings":"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","names":["useRef","useRef"]}
|
package/dist/chunk-XS27QKGP.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
// src/contexts/EventBusContext.tsx
|
|
4
|
-
import { createContext, useContext, useMemo } from "react";
|
|
5
|
-
import mitt from "mitt";
|
|
6
|
-
import { jsx } from "react/jsx-runtime";
|
|
7
|
-
var EventBusContext = createContext(null);
|
|
8
|
-
function generateBusId() {
|
|
9
|
-
return Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
|
|
10
|
-
}
|
|
11
|
-
function createEventBus() {
|
|
12
|
-
const bus = mitt();
|
|
13
|
-
const busId = generateBusId();
|
|
14
|
-
bus.busId = busId;
|
|
15
|
-
const originalEmit = bus.emit.bind(bus);
|
|
16
|
-
bus.emit = (eventName, payload) => {
|
|
17
|
-
console.info(`[EventBus:${busId}] emit:`, eventName, payload);
|
|
18
|
-
return originalEmit(eventName, payload);
|
|
19
|
-
};
|
|
20
|
-
const originalOn = bus.on.bind(bus);
|
|
21
|
-
bus.on = (eventName, handler) => {
|
|
22
|
-
console.debug(`[EventBus:${busId}] subscribe:`, eventName);
|
|
23
|
-
return originalOn(eventName, handler);
|
|
24
|
-
};
|
|
25
|
-
const originalOff = bus.off.bind(bus);
|
|
26
|
-
bus.off = (eventName, handler) => {
|
|
27
|
-
console.debug(`[EventBus:${busId}] unsubscribe:`, eventName);
|
|
28
|
-
return originalOff(eventName, handler);
|
|
29
|
-
};
|
|
30
|
-
return bus;
|
|
31
|
-
}
|
|
32
|
-
var globalEventBus = createEventBus();
|
|
33
|
-
function resetEventBusForTesting() {
|
|
34
|
-
globalEventBus = createEventBus();
|
|
35
|
-
}
|
|
36
|
-
function EventBusProvider({
|
|
37
|
-
children
|
|
38
|
-
}) {
|
|
39
|
-
const eventBus = useMemo(() => globalEventBus, []);
|
|
40
|
-
return /* @__PURE__ */ jsx(EventBusContext.Provider, { value: eventBus, children });
|
|
41
|
-
}
|
|
42
|
-
function useEventBus() {
|
|
43
|
-
const bus = useContext(EventBusContext);
|
|
44
|
-
if (!bus) {
|
|
45
|
-
throw new Error("useEventBus must be used within EventBusProvider");
|
|
46
|
-
}
|
|
47
|
-
return bus;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {
|
|
51
|
-
resetEventBusForTesting,
|
|
52
|
-
EventBusProvider,
|
|
53
|
-
useEventBus
|
|
54
|
-
};
|
|
55
|
-
//# sourceMappingURL=chunk-XS27QKGP.mjs.map
|