web-annotation-renderer 0.1.0 → 0.1.2
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 +631 -19
- package/dist/index14.cjs +1 -1
- package/dist/index14.cjs.map +1 -1
- package/dist/index14.js +68 -58
- package/dist/index14.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# web-annotation-renderer
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/web-annotation-renderer)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
6
|
A framework-agnostic PDF annotation renderer with timeline synchronization for educational content, interactive presentations, and annotated documents.
|
|
@@ -21,7 +21,7 @@ This library renders structured annotation data (highlights, text boxes, drawing
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install
|
|
24
|
+
npm install web-annotation-renderer
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
**Requirements:**
|
|
@@ -47,7 +47,7 @@ This must be done once at application startup, before using any PDF functionalit
|
|
|
47
47
|
## Quick Start - Vanilla JavaScript
|
|
48
48
|
|
|
49
49
|
```javascript
|
|
50
|
-
import { AnnotationRenderer } from "
|
|
50
|
+
import { AnnotationRenderer } from "web-annotation-renderer";
|
|
51
51
|
import * as pdfjsLib from "pdfjs-dist";
|
|
52
52
|
|
|
53
53
|
// Configure PDF.js worker (call once at app startup)
|
|
@@ -63,7 +63,10 @@ const renderer = new AnnotationRenderer({
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
// Load PDF
|
|
66
|
-
await renderer.loadPDF("/path/to/document.pdf");
|
|
66
|
+
const result = await renderer.loadPDF("/path/to/document.pdf");
|
|
67
|
+
if (result.success) {
|
|
68
|
+
console.log(`PDF loaded with ${result.pageCount} pages`);
|
|
69
|
+
}
|
|
67
70
|
|
|
68
71
|
// Set annotations
|
|
69
72
|
renderer.setAnnotations([
|
|
@@ -73,19 +76,25 @@ renderer.setAnnotations([
|
|
|
73
76
|
page: 1,
|
|
74
77
|
start: 0,
|
|
75
78
|
end: 5,
|
|
79
|
+
mode: "quads",
|
|
76
80
|
quads: [{ x: 0.1, y: 0.2, w: 0.3, h: 0.05 }],
|
|
77
81
|
style: { color: "rgba(255, 255, 0, 0.3)" },
|
|
78
82
|
},
|
|
79
83
|
]);
|
|
80
84
|
|
|
85
|
+
// Set initial page and scale
|
|
86
|
+
await renderer.setPage(1);
|
|
87
|
+
await renderer.setScale(1.0);
|
|
88
|
+
|
|
81
89
|
// Update timeline position
|
|
82
|
-
renderer.
|
|
90
|
+
renderer.setTime(2.5); // seconds
|
|
83
91
|
```
|
|
84
92
|
|
|
85
93
|
## Quick Start - React
|
|
86
94
|
|
|
87
95
|
```javascript
|
|
88
|
-
import {
|
|
96
|
+
import { useState } from "react";
|
|
97
|
+
import { AnnotPdf } from "web-annotation-renderer";
|
|
89
98
|
import * as pdfjsLib from "pdfjs-dist";
|
|
90
99
|
|
|
91
100
|
// Configure PDF.js worker (call once at app startup)
|
|
@@ -96,6 +105,8 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
|
|
96
105
|
|
|
97
106
|
function App() {
|
|
98
107
|
const [currentTime, setCurrentTime] = useState(0);
|
|
108
|
+
const [page, setPage] = useState(1);
|
|
109
|
+
const [totalPages, setTotalPages] = useState(1);
|
|
99
110
|
|
|
100
111
|
const annotations = [
|
|
101
112
|
{
|
|
@@ -104,30 +115,550 @@ function App() {
|
|
|
104
115
|
page: 1,
|
|
105
116
|
start: 0,
|
|
106
117
|
end: 5,
|
|
118
|
+
mode: "quads",
|
|
107
119
|
quads: [{ x: 0.1, y: 0.2, w: 0.3, h: 0.05 }],
|
|
108
120
|
style: { color: "rgba(255, 255, 0, 0.3)" },
|
|
109
121
|
},
|
|
110
122
|
];
|
|
111
123
|
|
|
124
|
+
const handleLoad = (pdfDocument) => {
|
|
125
|
+
setTotalPages(pdfDocument.pageCount);
|
|
126
|
+
console.log(`PDF loaded with ${pdfDocument.pageCount} pages`);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const handleError = (error) => {
|
|
130
|
+
console.error("PDF Error:", error);
|
|
131
|
+
};
|
|
132
|
+
|
|
112
133
|
return (
|
|
113
134
|
<AnnotPdf
|
|
114
135
|
pdfUrl="/path/to/document.pdf"
|
|
115
136
|
annotations={annotations}
|
|
116
137
|
currentTime={currentTime}
|
|
117
|
-
page={
|
|
118
|
-
scale={1.
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
page={page}
|
|
139
|
+
scale={1.5}
|
|
140
|
+
onLoad={handleLoad}
|
|
141
|
+
onError={handleError}
|
|
142
|
+
onPageChange={setPage}
|
|
121
143
|
/>
|
|
122
144
|
);
|
|
123
145
|
}
|
|
124
146
|
```
|
|
125
147
|
|
|
126
|
-
##
|
|
148
|
+
## API Reference
|
|
149
|
+
|
|
150
|
+
### AnnotationRenderer Class
|
|
151
|
+
|
|
152
|
+
#### Constructor
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
const renderer = new AnnotationRenderer(config);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Configuration Options:**
|
|
159
|
+
|
|
160
|
+
| Property | Type | Required | Default | Description |
|
|
161
|
+
| --------------- | ----------------- | -------- | ------- | -------------------------------------- |
|
|
162
|
+
| `container` | HTMLElement | ✅ Yes | - | DOM element for annotation layers |
|
|
163
|
+
| `canvasElement` | HTMLCanvasElement | ✅ Yes | - | Canvas element for PDF rendering |
|
|
164
|
+
| `pdfUrl` | string | No | `null` | PDF URL to auto-load on initialization |
|
|
165
|
+
| `initialPage` | number | No | `1` | Initial page number to display |
|
|
166
|
+
| `initialScale` | number | No | `1.0` | Initial zoom/scale factor |
|
|
167
|
+
| `annotations` | Array | No | `[]` | Initial annotation data |
|
|
168
|
+
|
|
169
|
+
**Example:**
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
const renderer = new AnnotationRenderer({
|
|
173
|
+
container: document.getElementById("annotation-container"),
|
|
174
|
+
canvasElement: document.getElementById("pdf-canvas"),
|
|
175
|
+
pdfUrl: "/document.pdf", // Optional: auto-load
|
|
176
|
+
initialPage: 1,
|
|
177
|
+
initialScale: 1.5,
|
|
178
|
+
annotations: [],
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Methods
|
|
183
|
+
|
|
184
|
+
##### `loadPDF(url)`
|
|
185
|
+
|
|
186
|
+
Load a PDF document from URL.
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
const result = await renderer.loadPDF("/path/to/document.pdf");
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Parameters:**
|
|
193
|
+
|
|
194
|
+
- `url` (string): URL or path to PDF file
|
|
195
|
+
|
|
196
|
+
**Returns:** `Promise<Object>`
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
{
|
|
200
|
+
success: boolean, // Whether loading succeeded
|
|
201
|
+
pageCount?: number, // Number of pages (if successful)
|
|
202
|
+
error?: string // Error message (if failed)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
##### `setPage(pageNum)`
|
|
207
|
+
|
|
208
|
+
Navigate to a specific page.
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
const result = await renderer.setPage(2);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
|
|
216
|
+
- `pageNum` (number): Page number (1-indexed)
|
|
217
|
+
|
|
218
|
+
**Returns:** `Promise<Object>`
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
{
|
|
222
|
+
success: boolean,
|
|
223
|
+
viewport?: Object, // Viewport dimensions (if successful)
|
|
224
|
+
error?: string // Error message (if failed)
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
##### `setScale(scale)`
|
|
229
|
+
|
|
230
|
+
Change the zoom level.
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
const result = await renderer.setScale(1.5);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Parameters:**
|
|
237
|
+
|
|
238
|
+
- `scale` (number): Scale factor (e.g., 0.5, 1.0, 1.5, 2.0)
|
|
239
|
+
|
|
240
|
+
**Returns:** `Promise<Object>` - Same structure as `setPage()`
|
|
241
|
+
|
|
242
|
+
##### `setAnnotations(annotations)`
|
|
243
|
+
|
|
244
|
+
Update the annotation data.
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
renderer.setAnnotations(annotationsArray);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Parameters:**
|
|
251
|
+
|
|
252
|
+
- `annotations` (Array): Array of annotation objects
|
|
253
|
+
|
|
254
|
+
**Returns:** `void`
|
|
255
|
+
|
|
256
|
+
##### `setTime(timestamp)`
|
|
257
|
+
|
|
258
|
+
Update the timeline position for animation synchronization.
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
renderer.setTime(5.2); // 5.2 seconds
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Parameters:**
|
|
265
|
+
|
|
266
|
+
- `timestamp` (number): Current timeline position in seconds
|
|
267
|
+
|
|
268
|
+
**Returns:** `void`
|
|
269
|
+
|
|
270
|
+
##### `getState()`
|
|
271
|
+
|
|
272
|
+
Get the current renderer state.
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
const state = renderer.getState();
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Returns:** `Object`
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
{
|
|
282
|
+
page: number, // Current page number
|
|
283
|
+
scale: number, // Current scale factor
|
|
284
|
+
annotations: Array, // Current annotations array
|
|
285
|
+
pageCount: number, // Total page count
|
|
286
|
+
time: number, // Current timeline position
|
|
287
|
+
viewport: Object|null, // Current viewport dimensions
|
|
288
|
+
pdfUrl: string|null // Current PDF URL
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
##### `destroy()`
|
|
293
|
+
|
|
294
|
+
Clean up all resources and subsystems.
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
renderer.destroy();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Returns:** `void`
|
|
301
|
+
|
|
302
|
+
**Important:** Call this before removing the renderer instance to prevent memory leaks.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### AnnotPdf Component (React)
|
|
307
|
+
|
|
308
|
+
#### Props
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
<AnnotPdf
|
|
312
|
+
pdfUrl={string}
|
|
313
|
+
page={number}
|
|
314
|
+
scale={number}
|
|
315
|
+
annotations={array}
|
|
316
|
+
currentTime={number}
|
|
317
|
+
onLoad={function}
|
|
318
|
+
onError={function}
|
|
319
|
+
onPageChange={function}
|
|
320
|
+
className={string}
|
|
321
|
+
style={object}
|
|
322
|
+
canvasStyle={object}
|
|
323
|
+
/>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Prop Reference:**
|
|
327
|
+
|
|
328
|
+
| Prop | Type | Required | Default | Description |
|
|
329
|
+
| -------------- | -------- | -------- | ------- | -------------------------------------------- |
|
|
330
|
+
| `pdfUrl` | string | ✅ Yes | - | URL or path to PDF file |
|
|
331
|
+
| `page` | number | No | `1` | Current page number (1-indexed) |
|
|
332
|
+
| `scale` | number | No | `1.5` | Zoom level / scale factor |
|
|
333
|
+
| `annotations` | Array | No | `[]` | Array of annotation objects |
|
|
334
|
+
| `currentTime` | number | No | `0` | Current timeline position in seconds |
|
|
335
|
+
| `onLoad` | function | No | - | Callback when PDF loads: `(doc) => void` |
|
|
336
|
+
| `onError` | function | No | - | Callback on error: `(error) => void` |
|
|
337
|
+
| `onPageChange` | function | No | - | Callback on page change: `(pageNum) => void` |
|
|
338
|
+
| `className` | string | No | - | CSS class for container div |
|
|
339
|
+
| `style` | object | No | `{}` | Inline styles for container div |
|
|
340
|
+
| `canvasStyle` | object | No | `{}` | Inline styles for canvas element |
|
|
341
|
+
|
|
342
|
+
**Note:** The component auto-sizes based on PDF dimensions and scale. There are no `width` or `height` props.
|
|
343
|
+
|
|
344
|
+
**Example with all props:**
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
<AnnotPdf
|
|
348
|
+
pdfUrl="/document.pdf"
|
|
349
|
+
page={currentPage}
|
|
350
|
+
scale={1.5}
|
|
351
|
+
annotations={annotations}
|
|
352
|
+
currentTime={audioTime}
|
|
353
|
+
onLoad={(doc) => setTotalPages(doc.pageCount)}
|
|
354
|
+
onError={(err) => console.error(err)}
|
|
355
|
+
onPageChange={(num) => setCurrentPage(num)}
|
|
356
|
+
className="pdf-viewer"
|
|
357
|
+
style={{ border: "1px solid #ccc" }}
|
|
358
|
+
canvasStyle={{ boxShadow: "0 2px 8px rgba(0,0,0,0.1)" }}
|
|
359
|
+
/>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Annotation Data Format
|
|
363
|
+
|
|
364
|
+
All annotations use **normalized coordinates** (0-1 range) for positioning, making them resolution-independent.
|
|
365
|
+
|
|
366
|
+
### Common Fields
|
|
367
|
+
|
|
368
|
+
All annotation types share these base fields:
|
|
369
|
+
|
|
370
|
+
| Field | Type | Required | Description |
|
|
371
|
+
| ------- | ------ | -------- | ---------------------------------------------------- |
|
|
372
|
+
| `id` | string | ✅ Yes | Unique identifier for the annotation |
|
|
373
|
+
| `type` | string | ✅ Yes | Annotation type: `"highlight"`, `"text"`, or `"ink"` |
|
|
374
|
+
| `page` | number | ✅ Yes | Page number (1-indexed) |
|
|
375
|
+
| `start` | number | ✅ Yes | Timeline start time in seconds |
|
|
376
|
+
| `end` | number | ✅ Yes | Timeline end time in seconds |
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### Highlight Annotations
|
|
381
|
+
|
|
382
|
+
Highlights rectangular regions on the PDF with progressive reveal animation.
|
|
383
|
+
|
|
384
|
+
**Type:** `"highlight"`
|
|
385
|
+
|
|
386
|
+
**Structure:**
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
{
|
|
390
|
+
id: "highlight-1",
|
|
391
|
+
type: "highlight",
|
|
392
|
+
page: 1,
|
|
393
|
+
start: 0,
|
|
394
|
+
end: 5,
|
|
395
|
+
mode: "quads", // ✅ REQUIRED - must be "quads"
|
|
396
|
+
quads: [
|
|
397
|
+
{ x: 0.1, y: 0.2, w: 0.3, h: 0.05 }, // First quad
|
|
398
|
+
{ x: 0.1, y: 0.25, w: 0.35, h: 0.05 } // Second quad (optional)
|
|
399
|
+
],
|
|
400
|
+
style: {
|
|
401
|
+
color: "rgba(255, 255, 0, 0.3)" // Highlight color
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Fields:**
|
|
127
407
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
408
|
+
| Field | Type | Required | Description |
|
|
409
|
+
| ------------- | ------ | -------- | -------------------------------------------------- |
|
|
410
|
+
| `mode` | string | ✅ Yes | Must be `"quads"` |
|
|
411
|
+
| `quads` | Array | ✅ Yes | Array of quad objects defining highlighted regions |
|
|
412
|
+
| `quads[].x` | number | ✅ Yes | Left position (0-1, normalized) |
|
|
413
|
+
| `quads[].y` | number | ✅ Yes | Top position (0-1, normalized) |
|
|
414
|
+
| `quads[].w` | number | ✅ Yes | Width (0-1, normalized) |
|
|
415
|
+
| `quads[].h` | number | ✅ Yes | Height (0-1, normalized) |
|
|
416
|
+
| `style.color` | string | ✅ Yes | CSS color for highlight |
|
|
417
|
+
|
|
418
|
+
**Animation:** Highlights reveal progressively from left to right across all quads during the `start` to `end` timeline.
|
|
419
|
+
|
|
420
|
+
**Example:**
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
{
|
|
424
|
+
id: "hl-1",
|
|
425
|
+
type: "highlight",
|
|
426
|
+
page: 1,
|
|
427
|
+
start: 2.0,
|
|
428
|
+
end: 7.0,
|
|
429
|
+
mode: "quads",
|
|
430
|
+
quads: [
|
|
431
|
+
{ x: 0.1, y: 0.3, w: 0.4, h: 0.05 }
|
|
432
|
+
],
|
|
433
|
+
style: {
|
|
434
|
+
color: "rgba(255, 255, 0, 0.4)"
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### Text Annotations
|
|
442
|
+
|
|
443
|
+
Display text boxes with progressive typing animation.
|
|
444
|
+
|
|
445
|
+
**Type:** `"text"`
|
|
446
|
+
|
|
447
|
+
**Structure:**
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
{
|
|
451
|
+
id: "text-1",
|
|
452
|
+
type: "text",
|
|
453
|
+
page: 1,
|
|
454
|
+
start: 3,
|
|
455
|
+
end: 8,
|
|
456
|
+
content: "This is the annotation text content",
|
|
457
|
+
x: 0.5, // Left position (normalized)
|
|
458
|
+
y: 0.2, // Top position (normalized)
|
|
459
|
+
w: 0.3, // Width (normalized)
|
|
460
|
+
h: 0.1, // Height (normalized)
|
|
461
|
+
style: {
|
|
462
|
+
bg: "rgba(255, 255, 255, 0.95)", // Background color
|
|
463
|
+
color: "#1f2937" // Text color
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Fields:**
|
|
469
|
+
|
|
470
|
+
| Field | Type | Required | Default | Description |
|
|
471
|
+
| ------------- | ------ | -------- | ------------------------- | ------------------------------- |
|
|
472
|
+
| `content` | string | ✅ Yes | - | Text to display |
|
|
473
|
+
| `x` | number | ✅ Yes | - | Left position (0-1, normalized) |
|
|
474
|
+
| `y` | number | ✅ Yes | - | Top position (0-1, normalized) |
|
|
475
|
+
| `w` | number | ✅ Yes | - | Width (0-1, normalized) |
|
|
476
|
+
| `h` | number | ✅ Yes | - | Height (0-1, normalized) |
|
|
477
|
+
| `style.bg` | string | No | `"rgba(255,255,255,0.9)"` | Background color |
|
|
478
|
+
| `style.color` | string | No | `"#1f2937"` | Text color |
|
|
479
|
+
|
|
480
|
+
**Animation:** Text appears word-by-word with a typing effect during the `start` to `end` timeline.
|
|
481
|
+
|
|
482
|
+
**Example:**
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
{
|
|
486
|
+
id: "txt-1",
|
|
487
|
+
type: "text",
|
|
488
|
+
page: 1,
|
|
489
|
+
start: 5.0,
|
|
490
|
+
end: 12.0,
|
|
491
|
+
content: "Important concept to remember",
|
|
492
|
+
x: 0.6,
|
|
493
|
+
y: 0.4,
|
|
494
|
+
w: 0.25,
|
|
495
|
+
h: 0.08,
|
|
496
|
+
style: {
|
|
497
|
+
bg: "rgba(255, 255, 200, 0.95)",
|
|
498
|
+
color: "#0066cc"
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
### Ink Annotations
|
|
506
|
+
|
|
507
|
+
Draw strokes/paths with progressive reveal animation.
|
|
508
|
+
|
|
509
|
+
**Type:** `"ink"`
|
|
510
|
+
|
|
511
|
+
**Structure:**
|
|
512
|
+
|
|
513
|
+
```javascript
|
|
514
|
+
{
|
|
515
|
+
id: "ink-1",
|
|
516
|
+
type: "ink",
|
|
517
|
+
page: 1,
|
|
518
|
+
start: 10,
|
|
519
|
+
end: 15,
|
|
520
|
+
strokes: [
|
|
521
|
+
{
|
|
522
|
+
color: "rgb(255, 0, 0)",
|
|
523
|
+
size: 3,
|
|
524
|
+
points: [
|
|
525
|
+
{ t: 0.0, x: 0.2, y: 0.5 },
|
|
526
|
+
{ t: 0.5, x: 0.3, y: 0.45 },
|
|
527
|
+
{ t: 1.0, x: 0.4, y: 0.5 }
|
|
528
|
+
]
|
|
529
|
+
}
|
|
530
|
+
]
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Fields:**
|
|
535
|
+
|
|
536
|
+
| Field | Type | Required | Default | Description |
|
|
537
|
+
| ------------------ | ------ | -------- | ----------- | ------------------------------------ |
|
|
538
|
+
| `strokes` | Array | ✅ Yes | - | Array of stroke objects |
|
|
539
|
+
| `strokes[].color` | string | No | `"#1f2937"` | CSS color for stroke |
|
|
540
|
+
| `strokes[].size` | number | No | `3` | Line width in pixels |
|
|
541
|
+
| `strokes[].points` | Array | ✅ Yes | - | Array of point objects |
|
|
542
|
+
| `points[].t` | number | ✅ Yes | - | Time within stroke (0-1, normalized) |
|
|
543
|
+
| `points[].x` | number | ✅ Yes | - | X position (0-1, normalized) |
|
|
544
|
+
| `points[].y` | number | ✅ Yes | - | Y position (0-1, normalized) |
|
|
545
|
+
|
|
546
|
+
**Important:**
|
|
547
|
+
|
|
548
|
+
- Each point **must have a `t` parameter** representing its position in time within the stroke (0-1 range)
|
|
549
|
+
- The `t` values are used for progressive drawing animation
|
|
550
|
+
- Points should be ordered by increasing `t` values
|
|
551
|
+
|
|
552
|
+
**Animation:** Strokes are drawn progressively based on the `t` parameter of each point during the `start` to `end` timeline.
|
|
553
|
+
|
|
554
|
+
**Example:**
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
{
|
|
558
|
+
id: "ink-1",
|
|
559
|
+
type: "ink",
|
|
560
|
+
page: 1,
|
|
561
|
+
start: 8.0,
|
|
562
|
+
end: 13.0,
|
|
563
|
+
strokes: [
|
|
564
|
+
{
|
|
565
|
+
color: "rgb(255, 0, 0)",
|
|
566
|
+
size: 2,
|
|
567
|
+
points: [
|
|
568
|
+
{ t: 0.0, x: 0.2, y: 0.4 },
|
|
569
|
+
{ t: 0.25, x: 0.25, y: 0.38 },
|
|
570
|
+
{ t: 0.5, x: 0.3, y: 0.4 },
|
|
571
|
+
{ t: 0.75, x: 0.35, y: 0.42 },
|
|
572
|
+
{ t: 1.0, x: 0.4, y: 0.4 }
|
|
573
|
+
]
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
color: "rgb(255, 0, 0)",
|
|
577
|
+
size: 2,
|
|
578
|
+
points: [
|
|
579
|
+
{ t: 0.0, x: 0.22, y: 0.45 },
|
|
580
|
+
{ t: 1.0, x: 0.38, y: 0.45 }
|
|
581
|
+
]
|
|
582
|
+
}
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
### Coordinate System
|
|
590
|
+
|
|
591
|
+
All position and size values use **normalized coordinates** (0-1 range):
|
|
592
|
+
|
|
593
|
+
- `0` = left edge / top edge
|
|
594
|
+
- `1` = right edge / bottom edge
|
|
595
|
+
- `0.5` = center
|
|
596
|
+
|
|
597
|
+
**Example:** `x: 0.1, y: 0.2, w: 0.3, h: 0.05` means:
|
|
598
|
+
|
|
599
|
+
- Starts at 10% from left, 20% from top
|
|
600
|
+
- Width is 30% of page width
|
|
601
|
+
- Height is 5% of page height
|
|
602
|
+
|
|
603
|
+
This makes annotations resolution-independent and responsive to different screen sizes.
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
### Complete Example
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
const annotations = [
|
|
611
|
+
// Highlight
|
|
612
|
+
{
|
|
613
|
+
id: "h1",
|
|
614
|
+
type: "highlight",
|
|
615
|
+
page: 1,
|
|
616
|
+
start: 0,
|
|
617
|
+
end: 5,
|
|
618
|
+
mode: "quads",
|
|
619
|
+
quads: [{ x: 0.1, y: 0.2, w: 0.3, h: 0.05 }],
|
|
620
|
+
style: { color: "rgba(255, 255, 0, 0.4)" },
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
// Text
|
|
624
|
+
{
|
|
625
|
+
id: "t1",
|
|
626
|
+
type: "text",
|
|
627
|
+
page: 1,
|
|
628
|
+
start: 3,
|
|
629
|
+
end: 8,
|
|
630
|
+
content: "Key concept here",
|
|
631
|
+
x: 0.6,
|
|
632
|
+
y: 0.2,
|
|
633
|
+
w: 0.25,
|
|
634
|
+
h: 0.08,
|
|
635
|
+
style: {
|
|
636
|
+
bg: "rgba(255, 255, 255, 0.95)",
|
|
637
|
+
color: "#0066cc",
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
|
|
641
|
+
// Ink
|
|
642
|
+
{
|
|
643
|
+
id: "i1",
|
|
644
|
+
type: "ink",
|
|
645
|
+
page: 1,
|
|
646
|
+
start: 6,
|
|
647
|
+
end: 10,
|
|
648
|
+
strokes: [
|
|
649
|
+
{
|
|
650
|
+
color: "rgb(255, 0, 0)",
|
|
651
|
+
size: 3,
|
|
652
|
+
points: [
|
|
653
|
+
{ t: 0.0, x: 0.2, y: 0.5 },
|
|
654
|
+
{ t: 0.5, x: 0.3, y: 0.45 },
|
|
655
|
+
{ t: 1.0, x: 0.4, y: 0.5 },
|
|
656
|
+
],
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
},
|
|
660
|
+
];
|
|
661
|
+
```
|
|
131
662
|
|
|
132
663
|
## Browser Compatibility
|
|
133
664
|
|
|
@@ -143,13 +674,94 @@ Requires:
|
|
|
143
674
|
- Canvas API
|
|
144
675
|
- Web Workers
|
|
145
676
|
|
|
677
|
+
## Troubleshooting
|
|
678
|
+
|
|
679
|
+
### Annotations not appearing
|
|
680
|
+
|
|
681
|
+
**Symptoms:** PDF renders but annotations don't show when moving timeline
|
|
682
|
+
|
|
683
|
+
**Solutions:**
|
|
684
|
+
|
|
685
|
+
1. Check that highlight annotations include `mode: "quads"` field
|
|
686
|
+
2. Verify text annotations use `type: "text"` (not `"textBox"`)
|
|
687
|
+
3. Ensure ink annotation points have `t` parameter
|
|
688
|
+
4. Check browser console for validation warnings
|
|
689
|
+
5. Verify timeline position is within annotation `start`/`end` range
|
|
690
|
+
|
|
691
|
+
### Worker errors
|
|
692
|
+
|
|
693
|
+
**Symptoms:** "Setting up fake worker" or worker-related errors
|
|
694
|
+
|
|
695
|
+
**Solution:** Ensure PDF.js worker is configured before using the library:
|
|
696
|
+
|
|
697
|
+
```javascript
|
|
698
|
+
import * as pdfjsLib from "pdfjs-dist";
|
|
699
|
+
|
|
700
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
|
701
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
702
|
+
import.meta.url
|
|
703
|
+
).toString();
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Type errors with React
|
|
707
|
+
|
|
708
|
+
**Symptoms:** TypeScript errors about missing props
|
|
709
|
+
|
|
710
|
+
**Solution:** Install React type definitions:
|
|
711
|
+
|
|
712
|
+
```bash
|
|
713
|
+
npm install --save-dev @types/react @types/react-dom
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Canvas rendering issues in React StrictMode
|
|
717
|
+
|
|
718
|
+
**Symptoms:** Double rendering or canvas errors in development
|
|
719
|
+
|
|
720
|
+
**Solution:** Consider removing `<React.StrictMode>` wrapper during development (it's safe to keep in production).
|
|
721
|
+
|
|
722
|
+
## Migration Guide
|
|
723
|
+
|
|
724
|
+
If you're upgrading from documentation examples that used the old format:
|
|
725
|
+
|
|
726
|
+
### Breaking Changes
|
|
727
|
+
|
|
728
|
+
1. **Package name:** `@ai-annotator/renderer` → `web-annotation-renderer`
|
|
729
|
+
2. **Method name:** `renderer.updateTimeline()` → `renderer.setTime()`
|
|
730
|
+
3. **Highlight annotations:** Must include `mode: "quads"` field
|
|
731
|
+
4. **Text annotations:** Use `type: "text"` (not `"textBox"`), `content` (not `text`), flat coordinates, and `style.bg`/`style.color`
|
|
732
|
+
5. **Ink annotations:** Use `strokes` (not `paths`), `size` (not `width`), and points must have `t` parameter
|
|
733
|
+
|
|
734
|
+
### Quick Migration
|
|
735
|
+
|
|
736
|
+
```javascript
|
|
737
|
+
// OLD (won't work)
|
|
738
|
+
import { AnnotationRenderer } from "@ai-annotator/renderer";
|
|
739
|
+
renderer.updateTimeline(5.0);
|
|
740
|
+
|
|
741
|
+
// NEW (correct)
|
|
742
|
+
import { AnnotationRenderer } from "web-annotation-renderer";
|
|
743
|
+
renderer.setTime(5.0);
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## Additional Resources
|
|
747
|
+
|
|
748
|
+
- [GitHub Repository](https://github.com/jhl72e/pdfAutoAnnotator)
|
|
749
|
+
- [Issue Tracker](https://github.com/jhl72e/pdfAutoAnnotator/issues)
|
|
750
|
+
- [Changelog](CHANGELOG.md)
|
|
751
|
+
|
|
146
752
|
## Examples
|
|
147
753
|
|
|
148
|
-
Check out
|
|
754
|
+
Check out working examples in the test projects:
|
|
755
|
+
|
|
756
|
+
- **Vanilla JavaScript:** See `test-vanilla/` for a complete implementation
|
|
757
|
+
- **React:** See `test-react/` for React component usage
|
|
758
|
+
|
|
759
|
+
Both examples include:
|
|
149
760
|
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
761
|
+
- PDF loading and rendering
|
|
762
|
+
- Page navigation and zoom controls
|
|
763
|
+
- Timeline slider with annotation synchronization
|
|
764
|
+
- All three annotation types (highlight, text, ink)
|
|
153
765
|
|
|
154
766
|
## License
|
|
155
767
|
|
package/dist/index14.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const P=require("react/jsx-runtime"),n=require("react"),j=require("./index2.cjs");function m({pdfUrl:l,page:u=1,scale:i=1.5,annotations:A=[],currentTime:o=0,onLoad:f,onError:t,onPageChange:a,className:F,style:R,canvasStyle:p}){const d=n.useRef(null),y=n.useRef(null),r=n.useRef(null),v=n.useRef(Promise.resolve()),c=n.useCallback(e=>{v.current=v.current.then(e).catch(h=>{console.error("AnnotPdf: Queued operation failed:",h)})},[]);n.useEffect(()=>{if(!(!d.current||!y.current)){try{r.current=new j.AnnotationRenderer({canvasElement:d.current,container:y.current})}catch(e){console.error("AnnotPdf: Failed to initialize renderer:",e),t&&t(e)}return()=>{r.current&&(r.current.destroy(),r.current=null)}}},[]),n.useEffect(()=>{if(!r.current||!l)return;let e=!1;return c(async()=>{try{const s=await r.current.loadPDF(l);if(e)return;if(!s.success){console.error("AnnotPdf: Failed to load PDF:",s.error),t&&t(new Error(s.error));return}f&&f({pageCount:s.pageCount})}catch(s){if(e)return;console.error("AnnotPdf: Failed to load PDF:",s),t&&t(s)}}),()=>{e=!0}},[l,f,t,c]),n.useEffect(()=>{!r.current||!u||typeof u!="number"||c(async()=>{try{const e=await r.current.setPage(u);if(!e.success){console.error("AnnotPdf: Failed to set page:",e.error),t&&t(new Error(e.error));return}a&&a(u)}catch(e){console.error("AnnotPdf: Failed to set page:",e),t&&t(e)}})},[u,a,t,c]),n.useEffect(()=>{!r.current||!i||typeof i!="number"||c(async()=>{try{const e=await r.current.setScale(i);e.success||(console.error("AnnotPdf: Failed to set scale:",e.error),t&&t(new Error(e.error)))}catch(e){console.error("AnnotPdf: Failed to set scale:",e),t&&t(e)}})},[i,t,c]),n.useEffect(()=>{if(r.current)try{r.current.setAnnotations(A||[])}catch(e){console.error("AnnotPdf: Failed to set annotations:",e),t&&t(e)}},[A,t]),n.useEffect(()=>{if(!(!r.current||o===void 0||o===null))try{r.current.setTime(o)}catch(e){console.error("AnnotPdf: Failed to set time:",e),t&&t(e)}},[o,t]);const w={position:"relative",display:"inline-block",lineHeight:0,...R},b={position:"absolute",top:0,left:0,width:"100%",height:"100%",pointerEvents:"none",overflow:"hidden"},S={display:"block",...p};return P.jsxs("div",{className:F,style:w,children:[P.jsx("canvas",{ref:d,style:S}),P.jsx("div",{ref:y,style:b})]})}exports.default=m;
|
|
2
2
|
//# sourceMappingURL=index14.cjs.map
|
package/dist/index14.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index14.cjs","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current\n });\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n */\n useEffect(() => {\n // Guard: Engine must exist and pdfUrl must be valid\n if (!engineRef.current || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n loadPdf();\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, onLoad, onError]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n */\n useEffect(() => {\n // Guard: Engine must exist and page must be valid\n if (!engineRef.current || !page || typeof page !== 'number') {\n return;\n }\n\n // Sync page to engine\n engineRef.current.setPage(page)\n .then((result) => {\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n })\n .catch((error) => {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n });\n }, [page, onPageChange, onError]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n */\n useEffect(() => {\n // Guard: Engine must exist and scale must be valid\n if (!engineRef.current || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Sync scale to engine (async method)\n engineRef.current.setScale(scale)\n .then((result) => {\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n })\n .catch((error) => {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n });\n }, [scale, onError]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations, onError]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n */\n useEffect(() => {\n // Guard: Engine must exist and currentTime must be defined\n if (!engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime, onError]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","useEffect","AnnotationRenderer","error","cancelled","result","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index14.cjs","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n /**\n * Reference to the render operation queue\n * Ensures sequential execution of async canvas operations\n * Prevents PDF.js race condition: \"Cannot use the same canvas during multiple render() operations\"\n * @type {React.RefObject<Promise<void>>}\n */\n const renderQueue = useRef(Promise.resolve());\n\n // ==========================================================================\n // SECTION 4.5: RENDER QUEUE HELPER\n // ==========================================================================\n\n /**\n * Queue a render operation to execute sequentially\n *\n * This helper ensures that async canvas operations (loadPDF, setPage, setScale)\n * execute one at a time, preventing concurrent access to the PDF.js canvas.\n * Uses Promise chaining to maintain operation order.\n *\n * @param {Function} operation - Async function returning a Promise\n * @returns {void}\n */\n const queueOperation = useCallback((operation) => {\n renderQueue.current = renderQueue.current\n .then(operation)\n .catch(error => {\n // Log errors but don't break the queue\n console.error('AnnotPdf: Queued operation failed:', error);\n });\n }, []);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current\n });\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and pdfUrl must be valid\n if (!engineRef.current || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n // Queue the PDF loading operation to prevent race conditions\n queueOperation(loadPdf);\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, onLoad, onError, queueOperation]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and page must be valid\n if (!engineRef.current || !page || typeof page !== 'number') {\n return;\n }\n\n // Queue the page change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setPage(page);\n\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [page, onPageChange, onError, queueOperation]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and scale must be valid\n if (!engineRef.current || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Queue the scale change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setScale(scale);\n\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [scale, onError, queueOperation]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations, onError]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n */\n useEffect(() => {\n // Guard: Engine must exist and currentTime must be defined\n if (!engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime, onError]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","renderQueue","queueOperation","useCallback","operation","error","useEffect","AnnotationRenderer","cancelled","result","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
|
package/dist/index14.js
CHANGED
|
@@ -1,97 +1,107 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import { AnnotationRenderer as
|
|
4
|
-
function
|
|
1
|
+
import { jsxs as S, jsx as F } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as u, useCallback as k, useEffect as i } from "react";
|
|
3
|
+
import { AnnotationRenderer as x } from "./index2.js";
|
|
4
|
+
function q({
|
|
5
5
|
// Required props
|
|
6
|
-
pdfUrl:
|
|
6
|
+
pdfUrl: a,
|
|
7
7
|
// Optional props with defaults
|
|
8
|
-
page:
|
|
9
|
-
scale:
|
|
10
|
-
annotations:
|
|
11
|
-
currentTime:
|
|
8
|
+
page: s = 1,
|
|
9
|
+
scale: o = 1.5,
|
|
10
|
+
annotations: A = [],
|
|
11
|
+
currentTime: l = 0,
|
|
12
12
|
// Callbacks
|
|
13
|
-
onLoad:
|
|
13
|
+
onLoad: f,
|
|
14
14
|
onError: t,
|
|
15
|
-
onPageChange:
|
|
15
|
+
onPageChange: d,
|
|
16
16
|
// Styling
|
|
17
|
-
className:
|
|
18
|
-
style:
|
|
19
|
-
canvasStyle:
|
|
17
|
+
className: v,
|
|
18
|
+
style: m,
|
|
19
|
+
canvasStyle: w
|
|
20
20
|
}) {
|
|
21
|
-
const
|
|
21
|
+
const y = u(null), P = u(null), r = u(null), h = u(Promise.resolve()), c = k((e) => {
|
|
22
|
+
h.current = h.current.then(e).catch((p) => {
|
|
23
|
+
console.error("AnnotPdf: Queued operation failed:", p);
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
22
26
|
i(() => {
|
|
23
|
-
if (!(!
|
|
27
|
+
if (!(!y.current || !P.current)) {
|
|
24
28
|
try {
|
|
25
|
-
|
|
26
|
-
canvasElement:
|
|
27
|
-
container:
|
|
29
|
+
r.current = new x({
|
|
30
|
+
canvasElement: y.current,
|
|
31
|
+
container: P.current
|
|
28
32
|
});
|
|
29
33
|
} catch (e) {
|
|
30
34
|
console.error("AnnotPdf: Failed to initialize renderer:", e), t && t(e);
|
|
31
35
|
}
|
|
32
36
|
return () => {
|
|
33
|
-
|
|
37
|
+
r.current && (r.current.destroy(), r.current = null);
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
}, []), i(() => {
|
|
37
|
-
if (!
|
|
41
|
+
if (!r.current || !a)
|
|
38
42
|
return;
|
|
39
43
|
let e = !1;
|
|
40
|
-
return (async () => {
|
|
44
|
+
return c(async () => {
|
|
41
45
|
try {
|
|
42
|
-
const
|
|
46
|
+
const n = await r.current.loadPDF(a);
|
|
43
47
|
if (e) return;
|
|
44
|
-
if (!
|
|
45
|
-
console.error("AnnotPdf: Failed to load PDF:",
|
|
48
|
+
if (!n.success) {
|
|
49
|
+
console.error("AnnotPdf: Failed to load PDF:", n.error), t && t(new Error(n.error));
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
} catch (
|
|
52
|
+
f && f({ pageCount: n.pageCount });
|
|
53
|
+
} catch (n) {
|
|
50
54
|
if (e) return;
|
|
51
|
-
console.error("AnnotPdf: Failed to load PDF:",
|
|
55
|
+
console.error("AnnotPdf: Failed to load PDF:", n), t && t(n);
|
|
52
56
|
}
|
|
53
|
-
})
|
|
57
|
+
}), () => {
|
|
54
58
|
e = !0;
|
|
55
59
|
};
|
|
56
|
-
}, [f,
|
|
57
|
-
!
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
}, [a, f, t, c]), i(() => {
|
|
61
|
+
!r.current || !s || typeof s != "number" || c(async () => {
|
|
62
|
+
try {
|
|
63
|
+
const e = await r.current.setPage(s);
|
|
64
|
+
if (!e.success) {
|
|
65
|
+
console.error("AnnotPdf: Failed to set page:", e.error), t && t(new Error(e.error));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
d && d(s);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error("AnnotPdf: Failed to set page:", e), t && t(e);
|
|
61
71
|
}
|
|
62
|
-
u && u(c);
|
|
63
|
-
}).catch((e) => {
|
|
64
|
-
console.error("AnnotPdf: Failed to set page:", e), t && t(e);
|
|
65
72
|
});
|
|
66
|
-
}, [
|
|
67
|
-
!
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
}, [s, d, t, c]), i(() => {
|
|
74
|
+
!r.current || !o || typeof o != "number" || c(async () => {
|
|
75
|
+
try {
|
|
76
|
+
const e = await r.current.setScale(o);
|
|
77
|
+
e.success || (console.error("AnnotPdf: Failed to set scale:", e.error), t && t(new Error(e.error)));
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error("AnnotPdf: Failed to set scale:", e), t && t(e);
|
|
80
|
+
}
|
|
71
81
|
});
|
|
72
|
-
}, [
|
|
73
|
-
if (
|
|
82
|
+
}, [o, t, c]), i(() => {
|
|
83
|
+
if (r.current)
|
|
74
84
|
try {
|
|
75
|
-
|
|
85
|
+
r.current.setAnnotations(A || []);
|
|
76
86
|
} catch (e) {
|
|
77
87
|
console.error("AnnotPdf: Failed to set annotations:", e), t && t(e);
|
|
78
88
|
}
|
|
79
|
-
}, [
|
|
80
|
-
if (!(!
|
|
89
|
+
}, [A, t]), i(() => {
|
|
90
|
+
if (!(!r.current || l === void 0 || l === null))
|
|
81
91
|
try {
|
|
82
|
-
|
|
92
|
+
r.current.setTime(l);
|
|
83
93
|
} catch (e) {
|
|
84
94
|
console.error("AnnotPdf: Failed to set time:", e), t && t(e);
|
|
85
95
|
}
|
|
86
|
-
}, [
|
|
87
|
-
const
|
|
96
|
+
}, [l, t]);
|
|
97
|
+
const b = {
|
|
88
98
|
position: "relative",
|
|
89
99
|
display: "inline-block",
|
|
90
100
|
lineHeight: 0,
|
|
91
101
|
// Remove extra space below canvas
|
|
92
|
-
...
|
|
102
|
+
...m
|
|
93
103
|
// User styles override defaults
|
|
94
|
-
},
|
|
104
|
+
}, R = {
|
|
95
105
|
position: "absolute",
|
|
96
106
|
top: 0,
|
|
97
107
|
left: 0,
|
|
@@ -100,17 +110,17 @@ function j({
|
|
|
100
110
|
pointerEvents: "none",
|
|
101
111
|
// Allow clicks to pass through to canvas
|
|
102
112
|
overflow: "hidden"
|
|
103
|
-
},
|
|
113
|
+
}, C = {
|
|
104
114
|
display: "block",
|
|
105
|
-
...
|
|
115
|
+
...w
|
|
106
116
|
// User styles override defaults
|
|
107
117
|
};
|
|
108
|
-
return /* @__PURE__ */
|
|
109
|
-
/* @__PURE__ */
|
|
110
|
-
/* @__PURE__ */
|
|
118
|
+
return /* @__PURE__ */ S("div", { className: v, style: b, children: [
|
|
119
|
+
/* @__PURE__ */ F("canvas", { ref: y, style: C }),
|
|
120
|
+
/* @__PURE__ */ F("div", { ref: P, style: R })
|
|
111
121
|
] });
|
|
112
122
|
}
|
|
113
123
|
export {
|
|
114
|
-
|
|
124
|
+
q as default
|
|
115
125
|
};
|
|
116
126
|
//# sourceMappingURL=index14.js.map
|
package/dist/index14.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index14.js","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current\n });\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n */\n useEffect(() => {\n // Guard: Engine must exist and pdfUrl must be valid\n if (!engineRef.current || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n loadPdf();\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, onLoad, onError]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n */\n useEffect(() => {\n // Guard: Engine must exist and page must be valid\n if (!engineRef.current || !page || typeof page !== 'number') {\n return;\n }\n\n // Sync page to engine\n engineRef.current.setPage(page)\n .then((result) => {\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n })\n .catch((error) => {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n });\n }, [page, onPageChange, onError]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n */\n useEffect(() => {\n // Guard: Engine must exist and scale must be valid\n if (!engineRef.current || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Sync scale to engine (async method)\n engineRef.current.setScale(scale)\n .then((result) => {\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n })\n .catch((error) => {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n });\n }, [scale, onError]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations, onError]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n */\n useEffect(() => {\n // Guard: Engine must exist and currentTime must be defined\n if (!engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime, onError]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","useEffect","AnnotationRenderer","error","cancelled","result","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index14.js","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n /**\n * Reference to the render operation queue\n * Ensures sequential execution of async canvas operations\n * Prevents PDF.js race condition: \"Cannot use the same canvas during multiple render() operations\"\n * @type {React.RefObject<Promise<void>>}\n */\n const renderQueue = useRef(Promise.resolve());\n\n // ==========================================================================\n // SECTION 4.5: RENDER QUEUE HELPER\n // ==========================================================================\n\n /**\n * Queue a render operation to execute sequentially\n *\n * This helper ensures that async canvas operations (loadPDF, setPage, setScale)\n * execute one at a time, preventing concurrent access to the PDF.js canvas.\n * Uses Promise chaining to maintain operation order.\n *\n * @param {Function} operation - Async function returning a Promise\n * @returns {void}\n */\n const queueOperation = useCallback((operation) => {\n renderQueue.current = renderQueue.current\n .then(operation)\n .catch(error => {\n // Log errors but don't break the queue\n console.error('AnnotPdf: Queued operation failed:', error);\n });\n }, []);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current\n });\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and pdfUrl must be valid\n if (!engineRef.current || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n // Queue the PDF loading operation to prevent race conditions\n queueOperation(loadPdf);\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, onLoad, onError, queueOperation]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and page must be valid\n if (!engineRef.current || !page || typeof page !== 'number') {\n return;\n }\n\n // Queue the page change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setPage(page);\n\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [page, onPageChange, onError, queueOperation]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and scale must be valid\n if (!engineRef.current || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Queue the scale change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setScale(scale);\n\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [scale, onError, queueOperation]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations, onError]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n */\n useEffect(() => {\n // Guard: Engine must exist and currentTime must be defined\n if (!engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime, onError]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","renderQueue","queueOperation","useCallback","operation","error","useEffect","AnnotationRenderer","cancelled","result","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":";;;AA4EA,SAASA,EAAS;AAAA;AAAA,EAEhB,QAAAC;AAAA;AAAA,EAGA,MAAAC,IAAO;AAAA,EACP,OAAAC,IAAQ;AAAA,EACR,aAAAC,IAAc,CAAA;AAAA,EACd,aAAAC,IAAc;AAAA;AAAA,EAGd,QAAAC;AAAA,EACA,SAAAC;AAAA,EACA,cAAAC;AAAA;AAAA,EAGA,WAAAC;AAAA,EACA,OAAAC;AAAA,EACA,aAAAC;AACF,GAAG;AAUD,QAAMC,IAAYC,EAAO,IAAI,GAMvBC,IAAoBD,EAAO,IAAI,GAO/BE,IAAYF,EAAO,IAAI,GAQvBG,IAAcH,EAAO,QAAQ,QAAA,CAAS,GAgBtCI,IAAiBC,EAAY,CAACC,MAAc;AAChD,IAAAH,EAAY,UAAUA,EAAY,QAC/B,KAAKG,CAAS,EACd,MAAM,CAAAC,MAAS;AAEd,cAAQ,MAAM,sCAAsCA,CAAK;AAAA,IAC3D,CAAC;AAAA,EACL,GAAG,CAAA,CAAE;AAUL,EAAAC,EAAU,MAAM;AAEd,QAAI,GAACT,EAAU,WAAW,CAACE,EAAkB,UAK7C;AAAA,UAAI;AACF,QAAAC,EAAU,UAAU,IAAIO,EAAmB;AAAA,UACzC,eAAeV,EAAU;AAAA,UACzB,WAAWE,EAAkB;AAAA,QAAA,CAC9B;AAAA,MACH,SAASM,GAAO;AACd,gBAAQ,MAAM,4CAA4CA,CAAK,GAC3Db,KACFA,EAAQa,CAAK;AAAA,MAEjB;AAGA,aAAO,MAAM;AACX,QAAIL,EAAU,YACZA,EAAU,QAAQ,QAAA,GAClBA,EAAU,UAAU;AAAA,MAExB;AAAA;AAAA,EACF,GAAG,CAAA,CAAE,GAWLM,EAAU,MAAM;AAEd,QAAI,CAACN,EAAU,WAAW,CAACd;AACzB;AAGF,QAAIsB,IAAY;AAiChB,WAAAN,EA/BgB,YAAY;AAC1B,UAAI;AACF,cAAMO,IAAS,MAAMT,EAAU,QAAQ,QAAQd,CAAM;AAGrD,YAAIsB,EAAW;AAGf,YAAI,CAACC,EAAO,SAAS;AACnB,kBAAQ,MAAM,iCAAiCA,EAAO,KAAK,GACvDjB,KACFA,EAAQ,IAAI,MAAMiB,EAAO,KAAK,CAAC;AAEjC;AAAA,QACF;AAGA,QAAIlB,KACFA,EAAO,EAAE,WAAWkB,EAAO,UAAA,CAAW;AAAA,MAE1C,SAASJ,GAAO;AACd,YAAIG,EAAW;AAEf,gBAAQ,MAAM,iCAAiCH,CAAK,GAChDb,KACFA,EAAQa,CAAK;AAAA,MAEjB;AAAA,IACF,CAGsB,GAGf,MAAM;AACX,MAAAG,IAAY;AAAA,IACd;AAAA,EACF,GAAG,CAACtB,GAAQK,GAAQC,GAASU,CAAc,CAAC,GAU5CI,EAAU,MAAM;AAEd,IAAI,CAACN,EAAU,WAAW,CAACb,KAAQ,OAAOA,KAAS,YAKnDe,EAAe,YAAY;AACzB,UAAI;AACF,cAAMO,IAAS,MAAMT,EAAU,QAAQ,QAAQb,CAAI;AAGnD,YAAI,CAACsB,EAAO,SAAS;AACnB,kBAAQ,MAAM,iCAAiCA,EAAO,KAAK,GACvDjB,KACFA,EAAQ,IAAI,MAAMiB,EAAO,KAAK,CAAC;AAEjC;AAAA,QACF;AAGA,QAAIhB,KACFA,EAAaN,CAAI;AAAA,MAErB,SAASkB,GAAO;AACd,gBAAQ,MAAM,iCAAiCA,CAAK,GAChDb,KACFA,EAAQa,CAAK;AAAA,MAEjB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAClB,GAAMM,GAAcD,GAASU,CAAc,CAAC,GAUhDI,EAAU,MAAM;AAEd,IAAI,CAACN,EAAU,WAAW,CAACZ,KAAS,OAAOA,KAAU,YAKrDc,EAAe,YAAY;AACzB,UAAI;AACF,cAAMO,IAAS,MAAMT,EAAU,QAAQ,SAASZ,CAAK;AAGrD,QAAKqB,EAAO,YACV,QAAQ,MAAM,kCAAkCA,EAAO,KAAK,GACxDjB,KACFA,EAAQ,IAAI,MAAMiB,EAAO,KAAK,CAAC;AAAA,MAGrC,SAASJ,GAAO;AACd,gBAAQ,MAAM,kCAAkCA,CAAK,GACjDb,KACFA,EAAQa,CAAK;AAAA,MAEjB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAACjB,GAAOI,GAASU,CAAc,CAAC,GASnCI,EAAU,MAAM;AAEd,QAAKN,EAAU;AAKf,UAAI;AACF,QAAAA,EAAU,QAAQ,eAAeX,KAAe,CAAA,CAAE;AAAA,MACpD,SAASgB,GAAO;AACd,gBAAQ,MAAM,wCAAwCA,CAAK,GACvDb,KACFA,EAAQa,CAAK;AAAA,MAEjB;AAAA,EACF,GAAG,CAAChB,GAAaG,CAAO,CAAC,GASzBc,EAAU,MAAM;AAEd,QAAI,GAACN,EAAU,WAAWV,MAAgB,UAAaA,MAAgB;AAKvE,UAAI;AACF,QAAAU,EAAU,QAAQ,QAAQV,CAAW;AAAA,MACvC,SAASe,GAAO;AACd,gBAAQ,MAAM,iCAAiCA,CAAK,GAChDb,KACFA,EAAQa,CAAK;AAAA,MAEjB;AAAA,EACF,GAAG,CAACf,GAAaE,CAAO,CAAC;AAUzB,QAAMkB,IAAwB;AAAA,IAC5B,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY;AAAA;AAAA,IACZ,GAAGf;AAAA;AAAA,EAAA,GAOCgB,IAAoB;AAAA,IACxB,UAAU;AAAA,IACV,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,eAAe;AAAA;AAAA,IACf,UAAU;AAAA,EAAA,GAONC,IAAqB;AAAA,IACzB,SAAS;AAAA,IACT,GAAGhB;AAAA;AAAA,EAAA;AAOL,SACE,gBAAAiB,EAAC,OAAA,EAAI,WAAAnB,GAAsB,OAAOgB,GAChC,UAAA;AAAA,IAAA,gBAAAI,EAAC,UAAA,EAAO,KAAKjB,GAAW,OAAOe,GAAoB;AAAA,IACnD,gBAAAE,EAAC,OAAA,EAAI,KAAKf,GAAmB,OAAOY,EAAA,CAAmB;AAAA,EAAA,GACzD;AAEJ;"}
|
package/package.json
CHANGED