canvu-react 0.3.13 → 0.3.16
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 +14 -1
- package/dist/index.cjs +41 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +41 -15
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +114 -102
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +10 -1
- package/dist/react.d.ts +10 -1
- package/dist/react.js +114 -102
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.d.cts
CHANGED
|
@@ -68,6 +68,7 @@ type IngestAssetFileError = {
|
|
|
68
68
|
*/
|
|
69
69
|
type IngestAssetFilesToSceneItemsOptions = {
|
|
70
70
|
files: readonly File[];
|
|
71
|
+
existingItems?: readonly VectorSceneItem[];
|
|
71
72
|
worldCenter: {
|
|
72
73
|
x: number;
|
|
73
74
|
y: number;
|
|
@@ -107,10 +108,18 @@ type IngestAssetFilesToSceneItemsOptions = {
|
|
|
107
108
|
/**
|
|
108
109
|
* PDF rasterization scale forwarded to `pdfjs-dist`.
|
|
109
110
|
*
|
|
110
|
-
* Defaults to `1.
|
|
111
|
+
* Defaults to `1.15` to balance first paint latency with zoom quality.
|
|
111
112
|
*/
|
|
112
113
|
pdfScale?: number;
|
|
113
114
|
pdfPageConcurrency?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Called as soon as canvu finishes converting one or more items from the
|
|
117
|
+
* source files.
|
|
118
|
+
*/
|
|
119
|
+
onItemsReady?: (items: VectorSceneItem[], context: {
|
|
120
|
+
file: File;
|
|
121
|
+
kind: VectorViewportAssetKind;
|
|
122
|
+
}) => void;
|
|
114
123
|
/**
|
|
115
124
|
* Final hook to customize each created item after canvu attaches its own
|
|
116
125
|
* image metadata and any `assetStore.upload(...)` `pluginData`.
|
package/dist/react.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ type IngestAssetFileError = {
|
|
|
68
68
|
*/
|
|
69
69
|
type IngestAssetFilesToSceneItemsOptions = {
|
|
70
70
|
files: readonly File[];
|
|
71
|
+
existingItems?: readonly VectorSceneItem[];
|
|
71
72
|
worldCenter: {
|
|
72
73
|
x: number;
|
|
73
74
|
y: number;
|
|
@@ -107,10 +108,18 @@ type IngestAssetFilesToSceneItemsOptions = {
|
|
|
107
108
|
/**
|
|
108
109
|
* PDF rasterization scale forwarded to `pdfjs-dist`.
|
|
109
110
|
*
|
|
110
|
-
* Defaults to `1.
|
|
111
|
+
* Defaults to `1.15` to balance first paint latency with zoom quality.
|
|
111
112
|
*/
|
|
112
113
|
pdfScale?: number;
|
|
113
114
|
pdfPageConcurrency?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Called as soon as canvu finishes converting one or more items from the
|
|
117
|
+
* source files.
|
|
118
|
+
*/
|
|
119
|
+
onItemsReady?: (items: VectorSceneItem[], context: {
|
|
120
|
+
file: File;
|
|
121
|
+
kind: VectorViewportAssetKind;
|
|
122
|
+
}) => void;
|
|
114
123
|
/**
|
|
115
124
|
* Final hook to customize each created item after canvu attaches its own
|
|
116
125
|
* image metadata and any `assetStore.upload(...)` `pluginData`.
|
package/dist/react.js
CHANGED
|
@@ -949,10 +949,32 @@ async function runWithConcurrency(items, concurrency, execute) {
|
|
|
949
949
|
async function loadPdfToStore(file, store, options) {
|
|
950
950
|
const scale = options?.scale ?? 1.5;
|
|
951
951
|
const pageConcurrency = options?.pageConcurrency ?? 2;
|
|
952
|
+
const storeThumbnails = options?.storeThumbnails ?? false;
|
|
952
953
|
const pdfjs = await getPdfJs();
|
|
953
954
|
const arrayBuffer = await file.arrayBuffer();
|
|
954
955
|
const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
|
|
955
956
|
const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
|
|
957
|
+
const bufferedResults = /* @__PURE__ */ new Map();
|
|
958
|
+
let nextEmitIndex = 0;
|
|
959
|
+
let emitChain = Promise.resolve();
|
|
960
|
+
const queuePageEmission = async (pageResult) => {
|
|
961
|
+
bufferedResults.set(pageResult.pageNumber, pageResult);
|
|
962
|
+
const run = async () => {
|
|
963
|
+
while (nextEmitIndex < pageNumbers.length) {
|
|
964
|
+
const nextPageNumber = pageNumbers[nextEmitIndex];
|
|
965
|
+
if (nextPageNumber == null) break;
|
|
966
|
+
const bufferedResult = bufferedResults.get(nextPageNumber);
|
|
967
|
+
if (!bufferedResult) break;
|
|
968
|
+
bufferedResults.delete(nextPageNumber);
|
|
969
|
+
nextEmitIndex += 1;
|
|
970
|
+
await options?.onPageStored?.(bufferedResult);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
const nextChain = emitChain.then(run, run);
|
|
974
|
+
emitChain = nextChain.catch(() => {
|
|
975
|
+
});
|
|
976
|
+
await nextChain;
|
|
977
|
+
};
|
|
956
978
|
return await runWithConcurrency(
|
|
957
979
|
pageNumbers,
|
|
958
980
|
pageConcurrency,
|
|
@@ -962,27 +984,31 @@ async function loadPdfToStore(file, store, options) {
|
|
|
962
984
|
const mime = "image/png";
|
|
963
985
|
const pageBlob = await canvasToBlob2(canvas, mime);
|
|
964
986
|
const blobId = await store.storeOriginal(pageBlob);
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
tCtx
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
987
|
+
const thumbnailBlobId = storeThumbnails ? await (async () => {
|
|
988
|
+
const thumbScale = Math.min(1, 256 / Math.max(width, height));
|
|
989
|
+
const tw = Math.max(1, Math.round(width * thumbScale));
|
|
990
|
+
const th = Math.max(1, Math.round(height * thumbScale));
|
|
991
|
+
const thumbCanvas = document.createElement("canvas");
|
|
992
|
+
thumbCanvas.width = tw;
|
|
993
|
+
thumbCanvas.height = th;
|
|
994
|
+
const tCtx = thumbCanvas.getContext("2d");
|
|
995
|
+
if (tCtx) {
|
|
996
|
+
tCtx.imageSmoothingEnabled = true;
|
|
997
|
+
tCtx.imageSmoothingQuality = "high";
|
|
998
|
+
tCtx.drawImage(canvas, 0, 0, tw, th);
|
|
999
|
+
}
|
|
1000
|
+
const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
|
|
1001
|
+
return await store.storeThumbnail(thumbBlob);
|
|
1002
|
+
})() : "";
|
|
1003
|
+
const pageResult = {
|
|
980
1004
|
blobId,
|
|
981
1005
|
thumbnailBlobId,
|
|
982
1006
|
width,
|
|
983
1007
|
height,
|
|
984
1008
|
pageNumber
|
|
985
1009
|
};
|
|
1010
|
+
await queuePageEmission(pageResult);
|
|
1011
|
+
return pageResult;
|
|
986
1012
|
}
|
|
987
1013
|
);
|
|
988
1014
|
}
|
|
@@ -1332,23 +1358,21 @@ async function uploadAssetIfNeeded(assetStore, file, kind) {
|
|
|
1332
1358
|
async function ingestAssetFilesToSceneItems(options) {
|
|
1333
1359
|
const {
|
|
1334
1360
|
files,
|
|
1361
|
+
existingItems = [],
|
|
1335
1362
|
worldCenter,
|
|
1336
1363
|
assetStore,
|
|
1337
1364
|
imageStore = new IndexedDbImageStore(),
|
|
1338
1365
|
createId = createShapeId,
|
|
1339
1366
|
gapWorld = 16,
|
|
1340
|
-
|
|
1341
|
-
pdfScale = 1.5,
|
|
1367
|
+
pdfScale = 1.15,
|
|
1342
1368
|
pdfPageConcurrency = 2,
|
|
1343
1369
|
decorateItem,
|
|
1370
|
+
onItemsReady,
|
|
1344
1371
|
onError
|
|
1345
1372
|
} = options;
|
|
1346
1373
|
const items = [];
|
|
1347
1374
|
const errors = [];
|
|
1348
|
-
let
|
|
1349
|
-
let occupiedBottomY = null;
|
|
1350
|
-
let imageYOffsetAdjustment = 0;
|
|
1351
|
-
let hasImagePlacementBase = false;
|
|
1375
|
+
let occupiedBottomY = existingItems.length > 0 ? Math.max(...existingItems.map((item) => item.bounds.y + item.bounds.height)) : null;
|
|
1352
1376
|
for (const file of files) {
|
|
1353
1377
|
const kind = getAssetKindForFile(file);
|
|
1354
1378
|
if (!kind) {
|
|
@@ -1362,58 +1386,56 @@ async function ingestAssetFilesToSceneItems(options) {
|
|
|
1362
1386
|
}
|
|
1363
1387
|
try {
|
|
1364
1388
|
if (kind === "pdf") {
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1389
|
+
const uploadResultPromise = uploadAssetIfNeeded(assetStore, file, kind);
|
|
1390
|
+
await loadPdfToStore(file, imageStore, {
|
|
1391
|
+
scale: pdfScale,
|
|
1392
|
+
pageConcurrency: pdfPageConcurrency,
|
|
1393
|
+
storeThumbnails: false,
|
|
1394
|
+
onPageStored: async (page) => {
|
|
1395
|
+
const uploadResult2 = await uploadResultPromise;
|
|
1396
|
+
const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
|
|
1397
|
+
const naturalTopY2 = worldCenter.y - page.height / 2;
|
|
1398
|
+
const stackedTopY2 = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
|
|
1399
|
+
const bounds2 = {
|
|
1400
|
+
x: worldCenter.x - page.width / 2,
|
|
1401
|
+
y: Math.max(naturalTopY2, stackedTopY2),
|
|
1402
|
+
width: page.width,
|
|
1403
|
+
height: page.height
|
|
1404
|
+
};
|
|
1405
|
+
const itemContext2 = {
|
|
1406
|
+
file,
|
|
1407
|
+
kind,
|
|
1408
|
+
itemIndex: items.length,
|
|
1409
|
+
pageNumber: page.pageNumber
|
|
1410
|
+
};
|
|
1411
|
+
const item2 = finalizeIngestedItem(
|
|
1412
|
+
{
|
|
1413
|
+
id: createId(),
|
|
1414
|
+
x: bounds2.x,
|
|
1415
|
+
y: bounds2.y,
|
|
1416
|
+
bounds: { ...bounds2 },
|
|
1417
|
+
toolKind: "image",
|
|
1418
|
+
imageBlobId: page.blobId,
|
|
1419
|
+
imageRasterHref: fullUrl2 ?? void 0,
|
|
1420
|
+
imageIntrinsicSize: {
|
|
1421
|
+
width: page.width,
|
|
1422
|
+
height: page.height
|
|
1423
|
+
},
|
|
1424
|
+
childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
|
|
1425
|
+
fullUrl2,
|
|
1426
|
+
{ width: page.width, height: page.height },
|
|
1427
|
+
bounds2
|
|
1428
|
+
) : ""
|
|
1400
1429
|
},
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
);
|
|
1411
|
-
items.push(item2);
|
|
1412
|
-
occupiedBottomY = bounds2.y + page.height;
|
|
1413
|
-
}
|
|
1414
|
-
hasImagePlacementBase = false;
|
|
1415
|
-
imagePlacementIndex = 0;
|
|
1416
|
-
imageYOffsetAdjustment = 0;
|
|
1430
|
+
itemContext2,
|
|
1431
|
+
uploadResult2,
|
|
1432
|
+
decorateItem
|
|
1433
|
+
);
|
|
1434
|
+
items.push(item2);
|
|
1435
|
+
occupiedBottomY = bounds2.y + page.height;
|
|
1436
|
+
onItemsReady?.([item2], { file, kind });
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1417
1439
|
continue;
|
|
1418
1440
|
}
|
|
1419
1441
|
const [uploadResult, storedImage] = await Promise.all([
|
|
@@ -1424,17 +1446,11 @@ async function ingestAssetFilesToSceneItems(options) {
|
|
|
1424
1446
|
const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
|
|
1425
1447
|
const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
|
|
1426
1448
|
const thumbnailHref = thumbBlob ? URL.createObjectURL(thumbBlob) : null;
|
|
1427
|
-
const
|
|
1428
|
-
const
|
|
1429
|
-
const naturalTopY = worldCenter.y - height / 2 + oy;
|
|
1430
|
-
if (!hasImagePlacementBase) {
|
|
1431
|
-
const minimumTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
|
|
1432
|
-
imageYOffsetAdjustment = Math.max(0, minimumTopY - naturalTopY);
|
|
1433
|
-
hasImagePlacementBase = true;
|
|
1434
|
-
}
|
|
1449
|
+
const naturalTopY = worldCenter.y - height / 2;
|
|
1450
|
+
const stackedTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
|
|
1435
1451
|
const bounds = {
|
|
1436
|
-
x: worldCenter.x - width / 2
|
|
1437
|
-
y: naturalTopY
|
|
1452
|
+
x: worldCenter.x - width / 2,
|
|
1453
|
+
y: Math.max(naturalTopY, stackedTopY),
|
|
1438
1454
|
width,
|
|
1439
1455
|
height
|
|
1440
1456
|
};
|
|
@@ -1463,7 +1479,7 @@ async function ingestAssetFilesToSceneItems(options) {
|
|
|
1463
1479
|
decorateItem
|
|
1464
1480
|
);
|
|
1465
1481
|
items.push(item);
|
|
1466
|
-
|
|
1482
|
+
onItemsReady?.([item], { file, kind });
|
|
1467
1483
|
occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
|
|
1468
1484
|
} catch (error) {
|
|
1469
1485
|
const fileError = {
|
|
@@ -5974,7 +5990,7 @@ body[data-canvu-pen-active="true"] [data-slot="shape-context-menu"] * {
|
|
|
5974
5990
|
}
|
|
5975
5991
|
`;
|
|
5976
5992
|
function debugApplePencilPointer(phase, detail) {
|
|
5977
|
-
|
|
5993
|
+
return;
|
|
5978
5994
|
}
|
|
5979
5995
|
var MARKER_TOOL_STYLE = {
|
|
5980
5996
|
stroke: "#fde047",
|
|
@@ -6574,7 +6590,6 @@ var VectorViewport = forwardRef(
|
|
|
6574
6590
|
const captureInteractionPointer = useCallback(
|
|
6575
6591
|
(target, pointerId, options) => {
|
|
6576
6592
|
const captureNativePointer = options?.captureNativePointer ?? true;
|
|
6577
|
-
debugApplePencilPointer("capture", { pointerId, captureNativePointer });
|
|
6578
6593
|
activeInteractionPointerIdRef.current = pointerId;
|
|
6579
6594
|
activeInteractionPointerTargetRef.current = captureNativePointer ? target : null;
|
|
6580
6595
|
if (captureNativePointer) {
|
|
@@ -6586,7 +6601,6 @@ var VectorViewport = forwardRef(
|
|
|
6586
6601
|
const releaseInteractionPointer = useCallback(() => {
|
|
6587
6602
|
const pointerId = activeInteractionPointerIdRef.current;
|
|
6588
6603
|
const target = activeInteractionPointerTargetRef.current;
|
|
6589
|
-
debugApplePencilPointer("release", { pointerId });
|
|
6590
6604
|
if (pointerId != null && target) {
|
|
6591
6605
|
try {
|
|
6592
6606
|
if (target.hasPointerCapture(pointerId)) {
|
|
@@ -6619,8 +6633,6 @@ var VectorViewport = forwardRef(
|
|
|
6619
6633
|
const itemId = strokeState.itemId;
|
|
6620
6634
|
const style = { ...strokeStyleRef.current };
|
|
6621
6635
|
debugApplePencilPointer("finalize-stroke", {
|
|
6622
|
-
pointerType,
|
|
6623
|
-
tool,
|
|
6624
6636
|
points: pts.length
|
|
6625
6637
|
});
|
|
6626
6638
|
dragStateRef.current = { kind: "idle" };
|
|
@@ -7370,12 +7382,19 @@ var VectorViewport = forwardRef(
|
|
|
7370
7382
|
}
|
|
7371
7383
|
const result = await ingestAssetFilesToSceneItems({
|
|
7372
7384
|
files,
|
|
7385
|
+
existingItems: itemsRef.current,
|
|
7373
7386
|
worldCenter: {
|
|
7374
7387
|
x: worldX,
|
|
7375
7388
|
y: worldY
|
|
7376
7389
|
},
|
|
7377
7390
|
imageStore: store,
|
|
7378
|
-
assetStore: assetStoreRef.current ?? void 0
|
|
7391
|
+
assetStore: assetStoreRef.current ?? void 0,
|
|
7392
|
+
onItemsReady: (nextItems) => {
|
|
7393
|
+
if (nextItems.length === 0) return;
|
|
7394
|
+
setLoadingSkeletons([]);
|
|
7395
|
+
change([...itemsRef.current, ...nextItems]);
|
|
7396
|
+
setEffectiveSelectedIdsRef.current(nextItems.map((item) => item.id));
|
|
7397
|
+
}
|
|
7379
7398
|
});
|
|
7380
7399
|
if (result.errors.length > 0) {
|
|
7381
7400
|
for (const error of result.errors) {
|
|
@@ -7383,8 +7402,6 @@ var VectorViewport = forwardRef(
|
|
|
7383
7402
|
}
|
|
7384
7403
|
}
|
|
7385
7404
|
if (result.items.length === 0) return;
|
|
7386
|
-
change([...itemsRef.current, ...result.items]);
|
|
7387
|
-
setEffectiveSelectedIdsRef.current(result.items.map((item) => item.id));
|
|
7388
7405
|
} finally {
|
|
7389
7406
|
setLoadingSkeletons([]);
|
|
7390
7407
|
}
|
|
@@ -7393,12 +7410,12 @@ var VectorViewport = forwardRef(
|
|
|
7393
7410
|
);
|
|
7394
7411
|
const handleImageFileChange = useCallback(
|
|
7395
7412
|
async (e) => {
|
|
7396
|
-
const
|
|
7413
|
+
const files = e.target.files ? Array.from(e.target.files) : [];
|
|
7397
7414
|
e.target.value = "";
|
|
7398
7415
|
const pending = pendingImagePlacementRef.current;
|
|
7399
7416
|
pendingImagePlacementRef.current = null;
|
|
7400
|
-
if (
|
|
7401
|
-
await placeImageFilesAtWorld(
|
|
7417
|
+
if (files.length === 0 || !pending) return;
|
|
7418
|
+
await placeImageFilesAtWorld(files, pending.worldX, pending.worldY);
|
|
7402
7419
|
},
|
|
7403
7420
|
[placeImageFilesAtWorld]
|
|
7404
7421
|
);
|
|
@@ -7494,14 +7511,12 @@ var VectorViewport = forwardRef(
|
|
|
7494
7511
|
pointerType: e.pointerType,
|
|
7495
7512
|
pointerId: e.pointerId,
|
|
7496
7513
|
pressure: e.pointerType === "pen" ? e.pressure : void 0,
|
|
7497
|
-
activePointerId,
|
|
7498
7514
|
dragKind: currentDragState.kind
|
|
7499
7515
|
});
|
|
7500
7516
|
}
|
|
7501
7517
|
if (e.pointerType === "pen" && currentDragState.kind === "stroke" && currentDragState.pointerType === "pen") {
|
|
7502
7518
|
debugApplePencilPointer("pen-reentry", {
|
|
7503
7519
|
pointerId: e.pointerId,
|
|
7504
|
-
previousPointerId: activePointerId,
|
|
7505
7520
|
previousPoints: currentDragState.points.length
|
|
7506
7521
|
});
|
|
7507
7522
|
finalizeStrokeDragState(currentDragState);
|
|
@@ -7847,9 +7862,7 @@ var VectorViewport = forwardRef(
|
|
|
7847
7862
|
debugApplePencilPointer("native-pointerdown", {
|
|
7848
7863
|
pointerType: e.pointerType,
|
|
7849
7864
|
pointerId: e.pointerId,
|
|
7850
|
-
pressure: e.pressure
|
|
7851
|
-
tool
|
|
7852
|
-
});
|
|
7865
|
+
pressure: e.pressure});
|
|
7853
7866
|
e.preventDefault();
|
|
7854
7867
|
e.stopImmediatePropagation();
|
|
7855
7868
|
};
|
|
@@ -7949,9 +7962,7 @@ var VectorViewport = forwardRef(
|
|
|
7949
7962
|
debugApplePencilPointer("touchstart-stroke", {
|
|
7950
7963
|
touchId: touch.identifier,
|
|
7951
7964
|
touchType: touchKind(touch),
|
|
7952
|
-
force: touchPressure(touch)
|
|
7953
|
-
tool
|
|
7954
|
-
});
|
|
7965
|
+
force: touchPressure(touch)});
|
|
7955
7966
|
stopTouchEvent(ev);
|
|
7956
7967
|
};
|
|
7957
7968
|
const onTouchMove = (ev) => {
|
|
@@ -8673,6 +8684,7 @@ var VectorViewport = forwardRef(
|
|
|
8673
8684
|
{
|
|
8674
8685
|
ref: imageInputRef,
|
|
8675
8686
|
type: "file",
|
|
8687
|
+
multiple: true,
|
|
8676
8688
|
accept: "image/*,application/pdf",
|
|
8677
8689
|
style: { display: "none" },
|
|
8678
8690
|
"aria-label": "Select image or PDF to place on canvas",
|