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.cjs
CHANGED
|
@@ -956,10 +956,32 @@ async function runWithConcurrency(items, concurrency, execute) {
|
|
|
956
956
|
async function loadPdfToStore(file, store, options) {
|
|
957
957
|
const scale = options?.scale ?? 1.5;
|
|
958
958
|
const pageConcurrency = options?.pageConcurrency ?? 2;
|
|
959
|
+
const storeThumbnails = options?.storeThumbnails ?? false;
|
|
959
960
|
const pdfjs = await getPdfJs();
|
|
960
961
|
const arrayBuffer = await file.arrayBuffer();
|
|
961
962
|
const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
|
|
962
963
|
const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
|
|
964
|
+
const bufferedResults = /* @__PURE__ */ new Map();
|
|
965
|
+
let nextEmitIndex = 0;
|
|
966
|
+
let emitChain = Promise.resolve();
|
|
967
|
+
const queuePageEmission = async (pageResult) => {
|
|
968
|
+
bufferedResults.set(pageResult.pageNumber, pageResult);
|
|
969
|
+
const run = async () => {
|
|
970
|
+
while (nextEmitIndex < pageNumbers.length) {
|
|
971
|
+
const nextPageNumber = pageNumbers[nextEmitIndex];
|
|
972
|
+
if (nextPageNumber == null) break;
|
|
973
|
+
const bufferedResult = bufferedResults.get(nextPageNumber);
|
|
974
|
+
if (!bufferedResult) break;
|
|
975
|
+
bufferedResults.delete(nextPageNumber);
|
|
976
|
+
nextEmitIndex += 1;
|
|
977
|
+
await options?.onPageStored?.(bufferedResult);
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
const nextChain = emitChain.then(run, run);
|
|
981
|
+
emitChain = nextChain.catch(() => {
|
|
982
|
+
});
|
|
983
|
+
await nextChain;
|
|
984
|
+
};
|
|
963
985
|
return await runWithConcurrency(
|
|
964
986
|
pageNumbers,
|
|
965
987
|
pageConcurrency,
|
|
@@ -969,27 +991,31 @@ async function loadPdfToStore(file, store, options) {
|
|
|
969
991
|
const mime = "image/png";
|
|
970
992
|
const pageBlob = await canvasToBlob2(canvas, mime);
|
|
971
993
|
const blobId = await store.storeOriginal(pageBlob);
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
tCtx
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
994
|
+
const thumbnailBlobId = storeThumbnails ? await (async () => {
|
|
995
|
+
const thumbScale = Math.min(1, 256 / Math.max(width, height));
|
|
996
|
+
const tw = Math.max(1, Math.round(width * thumbScale));
|
|
997
|
+
const th = Math.max(1, Math.round(height * thumbScale));
|
|
998
|
+
const thumbCanvas = document.createElement("canvas");
|
|
999
|
+
thumbCanvas.width = tw;
|
|
1000
|
+
thumbCanvas.height = th;
|
|
1001
|
+
const tCtx = thumbCanvas.getContext("2d");
|
|
1002
|
+
if (tCtx) {
|
|
1003
|
+
tCtx.imageSmoothingEnabled = true;
|
|
1004
|
+
tCtx.imageSmoothingQuality = "high";
|
|
1005
|
+
tCtx.drawImage(canvas, 0, 0, tw, th);
|
|
1006
|
+
}
|
|
1007
|
+
const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
|
|
1008
|
+
return await store.storeThumbnail(thumbBlob);
|
|
1009
|
+
})() : "";
|
|
1010
|
+
const pageResult = {
|
|
987
1011
|
blobId,
|
|
988
1012
|
thumbnailBlobId,
|
|
989
1013
|
width,
|
|
990
1014
|
height,
|
|
991
1015
|
pageNumber
|
|
992
1016
|
};
|
|
1017
|
+
await queuePageEmission(pageResult);
|
|
1018
|
+
return pageResult;
|
|
993
1019
|
}
|
|
994
1020
|
);
|
|
995
1021
|
}
|
|
@@ -1339,23 +1365,21 @@ async function uploadAssetIfNeeded(assetStore, file, kind) {
|
|
|
1339
1365
|
async function ingestAssetFilesToSceneItems(options) {
|
|
1340
1366
|
const {
|
|
1341
1367
|
files,
|
|
1368
|
+
existingItems = [],
|
|
1342
1369
|
worldCenter,
|
|
1343
1370
|
assetStore,
|
|
1344
1371
|
imageStore = new IndexedDbImageStore(),
|
|
1345
1372
|
createId = createShapeId,
|
|
1346
1373
|
gapWorld = 16,
|
|
1347
|
-
|
|
1348
|
-
pdfScale = 1.5,
|
|
1374
|
+
pdfScale = 1.15,
|
|
1349
1375
|
pdfPageConcurrency = 2,
|
|
1350
1376
|
decorateItem,
|
|
1377
|
+
onItemsReady,
|
|
1351
1378
|
onError
|
|
1352
1379
|
} = options;
|
|
1353
1380
|
const items = [];
|
|
1354
1381
|
const errors = [];
|
|
1355
|
-
let
|
|
1356
|
-
let occupiedBottomY = null;
|
|
1357
|
-
let imageYOffsetAdjustment = 0;
|
|
1358
|
-
let hasImagePlacementBase = false;
|
|
1382
|
+
let occupiedBottomY = existingItems.length > 0 ? Math.max(...existingItems.map((item) => item.bounds.y + item.bounds.height)) : null;
|
|
1359
1383
|
for (const file of files) {
|
|
1360
1384
|
const kind = getAssetKindForFile(file);
|
|
1361
1385
|
if (!kind) {
|
|
@@ -1369,58 +1393,56 @@ async function ingestAssetFilesToSceneItems(options) {
|
|
|
1369
1393
|
}
|
|
1370
1394
|
try {
|
|
1371
1395
|
if (kind === "pdf") {
|
|
1372
|
-
const
|
|
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
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1396
|
+
const uploadResultPromise = uploadAssetIfNeeded(assetStore, file, kind);
|
|
1397
|
+
await loadPdfToStore(file, imageStore, {
|
|
1398
|
+
scale: pdfScale,
|
|
1399
|
+
pageConcurrency: pdfPageConcurrency,
|
|
1400
|
+
storeThumbnails: false,
|
|
1401
|
+
onPageStored: async (page) => {
|
|
1402
|
+
const uploadResult2 = await uploadResultPromise;
|
|
1403
|
+
const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
|
|
1404
|
+
const naturalTopY2 = worldCenter.y - page.height / 2;
|
|
1405
|
+
const stackedTopY2 = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
|
|
1406
|
+
const bounds2 = {
|
|
1407
|
+
x: worldCenter.x - page.width / 2,
|
|
1408
|
+
y: Math.max(naturalTopY2, stackedTopY2),
|
|
1409
|
+
width: page.width,
|
|
1410
|
+
height: page.height
|
|
1411
|
+
};
|
|
1412
|
+
const itemContext2 = {
|
|
1413
|
+
file,
|
|
1414
|
+
kind,
|
|
1415
|
+
itemIndex: items.length,
|
|
1416
|
+
pageNumber: page.pageNumber
|
|
1417
|
+
};
|
|
1418
|
+
const item2 = finalizeIngestedItem(
|
|
1419
|
+
{
|
|
1420
|
+
id: createId(),
|
|
1421
|
+
x: bounds2.x,
|
|
1422
|
+
y: bounds2.y,
|
|
1423
|
+
bounds: { ...bounds2 },
|
|
1424
|
+
toolKind: "image",
|
|
1425
|
+
imageBlobId: page.blobId,
|
|
1426
|
+
imageRasterHref: fullUrl2 ?? void 0,
|
|
1427
|
+
imageIntrinsicSize: {
|
|
1428
|
+
width: page.width,
|
|
1429
|
+
height: page.height
|
|
1430
|
+
},
|
|
1431
|
+
childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
|
|
1432
|
+
fullUrl2,
|
|
1433
|
+
{ width: page.width, height: page.height },
|
|
1434
|
+
bounds2
|
|
1435
|
+
) : ""
|
|
1407
1436
|
},
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
);
|
|
1418
|
-
items.push(item2);
|
|
1419
|
-
occupiedBottomY = bounds2.y + page.height;
|
|
1420
|
-
}
|
|
1421
|
-
hasImagePlacementBase = false;
|
|
1422
|
-
imagePlacementIndex = 0;
|
|
1423
|
-
imageYOffsetAdjustment = 0;
|
|
1437
|
+
itemContext2,
|
|
1438
|
+
uploadResult2,
|
|
1439
|
+
decorateItem
|
|
1440
|
+
);
|
|
1441
|
+
items.push(item2);
|
|
1442
|
+
occupiedBottomY = bounds2.y + page.height;
|
|
1443
|
+
onItemsReady?.([item2], { file, kind });
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1424
1446
|
continue;
|
|
1425
1447
|
}
|
|
1426
1448
|
const [uploadResult, storedImage] = await Promise.all([
|
|
@@ -1431,17 +1453,11 @@ async function ingestAssetFilesToSceneItems(options) {
|
|
|
1431
1453
|
const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
|
|
1432
1454
|
const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
|
|
1433
1455
|
const thumbnailHref = thumbBlob ? URL.createObjectURL(thumbBlob) : null;
|
|
1434
|
-
const
|
|
1435
|
-
const
|
|
1436
|
-
const naturalTopY = worldCenter.y - height / 2 + oy;
|
|
1437
|
-
if (!hasImagePlacementBase) {
|
|
1438
|
-
const minimumTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
|
|
1439
|
-
imageYOffsetAdjustment = Math.max(0, minimumTopY - naturalTopY);
|
|
1440
|
-
hasImagePlacementBase = true;
|
|
1441
|
-
}
|
|
1456
|
+
const naturalTopY = worldCenter.y - height / 2;
|
|
1457
|
+
const stackedTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
|
|
1442
1458
|
const bounds = {
|
|
1443
|
-
x: worldCenter.x - width / 2
|
|
1444
|
-
y: naturalTopY
|
|
1459
|
+
x: worldCenter.x - width / 2,
|
|
1460
|
+
y: Math.max(naturalTopY, stackedTopY),
|
|
1445
1461
|
width,
|
|
1446
1462
|
height
|
|
1447
1463
|
};
|
|
@@ -1470,7 +1486,7 @@ async function ingestAssetFilesToSceneItems(options) {
|
|
|
1470
1486
|
decorateItem
|
|
1471
1487
|
);
|
|
1472
1488
|
items.push(item);
|
|
1473
|
-
|
|
1489
|
+
onItemsReady?.([item], { file, kind });
|
|
1474
1490
|
occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
|
|
1475
1491
|
} catch (error) {
|
|
1476
1492
|
const fileError = {
|
|
@@ -5981,7 +5997,7 @@ body[data-canvu-pen-active="true"] [data-slot="shape-context-menu"] * {
|
|
|
5981
5997
|
}
|
|
5982
5998
|
`;
|
|
5983
5999
|
function debugApplePencilPointer(phase, detail) {
|
|
5984
|
-
|
|
6000
|
+
return;
|
|
5985
6001
|
}
|
|
5986
6002
|
var MARKER_TOOL_STYLE = {
|
|
5987
6003
|
stroke: "#fde047",
|
|
@@ -6581,7 +6597,6 @@ var VectorViewport = react.forwardRef(
|
|
|
6581
6597
|
const captureInteractionPointer = react.useCallback(
|
|
6582
6598
|
(target, pointerId, options) => {
|
|
6583
6599
|
const captureNativePointer = options?.captureNativePointer ?? true;
|
|
6584
|
-
debugApplePencilPointer("capture", { pointerId, captureNativePointer });
|
|
6585
6600
|
activeInteractionPointerIdRef.current = pointerId;
|
|
6586
6601
|
activeInteractionPointerTargetRef.current = captureNativePointer ? target : null;
|
|
6587
6602
|
if (captureNativePointer) {
|
|
@@ -6593,7 +6608,6 @@ var VectorViewport = react.forwardRef(
|
|
|
6593
6608
|
const releaseInteractionPointer = react.useCallback(() => {
|
|
6594
6609
|
const pointerId = activeInteractionPointerIdRef.current;
|
|
6595
6610
|
const target = activeInteractionPointerTargetRef.current;
|
|
6596
|
-
debugApplePencilPointer("release", { pointerId });
|
|
6597
6611
|
if (pointerId != null && target) {
|
|
6598
6612
|
try {
|
|
6599
6613
|
if (target.hasPointerCapture(pointerId)) {
|
|
@@ -6626,8 +6640,6 @@ var VectorViewport = react.forwardRef(
|
|
|
6626
6640
|
const itemId = strokeState.itemId;
|
|
6627
6641
|
const style = { ...strokeStyleRef.current };
|
|
6628
6642
|
debugApplePencilPointer("finalize-stroke", {
|
|
6629
|
-
pointerType,
|
|
6630
|
-
tool,
|
|
6631
6643
|
points: pts.length
|
|
6632
6644
|
});
|
|
6633
6645
|
dragStateRef.current = { kind: "idle" };
|
|
@@ -7377,12 +7389,19 @@ var VectorViewport = react.forwardRef(
|
|
|
7377
7389
|
}
|
|
7378
7390
|
const result = await ingestAssetFilesToSceneItems({
|
|
7379
7391
|
files,
|
|
7392
|
+
existingItems: itemsRef.current,
|
|
7380
7393
|
worldCenter: {
|
|
7381
7394
|
x: worldX,
|
|
7382
7395
|
y: worldY
|
|
7383
7396
|
},
|
|
7384
7397
|
imageStore: store,
|
|
7385
|
-
assetStore: assetStoreRef.current ?? void 0
|
|
7398
|
+
assetStore: assetStoreRef.current ?? void 0,
|
|
7399
|
+
onItemsReady: (nextItems) => {
|
|
7400
|
+
if (nextItems.length === 0) return;
|
|
7401
|
+
setLoadingSkeletons([]);
|
|
7402
|
+
change([...itemsRef.current, ...nextItems]);
|
|
7403
|
+
setEffectiveSelectedIdsRef.current(nextItems.map((item) => item.id));
|
|
7404
|
+
}
|
|
7386
7405
|
});
|
|
7387
7406
|
if (result.errors.length > 0) {
|
|
7388
7407
|
for (const error of result.errors) {
|
|
@@ -7390,8 +7409,6 @@ var VectorViewport = react.forwardRef(
|
|
|
7390
7409
|
}
|
|
7391
7410
|
}
|
|
7392
7411
|
if (result.items.length === 0) return;
|
|
7393
|
-
change([...itemsRef.current, ...result.items]);
|
|
7394
|
-
setEffectiveSelectedIdsRef.current(result.items.map((item) => item.id));
|
|
7395
7412
|
} finally {
|
|
7396
7413
|
setLoadingSkeletons([]);
|
|
7397
7414
|
}
|
|
@@ -7400,12 +7417,12 @@ var VectorViewport = react.forwardRef(
|
|
|
7400
7417
|
);
|
|
7401
7418
|
const handleImageFileChange = react.useCallback(
|
|
7402
7419
|
async (e) => {
|
|
7403
|
-
const
|
|
7420
|
+
const files = e.target.files ? Array.from(e.target.files) : [];
|
|
7404
7421
|
e.target.value = "";
|
|
7405
7422
|
const pending = pendingImagePlacementRef.current;
|
|
7406
7423
|
pendingImagePlacementRef.current = null;
|
|
7407
|
-
if (
|
|
7408
|
-
await placeImageFilesAtWorld(
|
|
7424
|
+
if (files.length === 0 || !pending) return;
|
|
7425
|
+
await placeImageFilesAtWorld(files, pending.worldX, pending.worldY);
|
|
7409
7426
|
},
|
|
7410
7427
|
[placeImageFilesAtWorld]
|
|
7411
7428
|
);
|
|
@@ -7501,14 +7518,12 @@ var VectorViewport = react.forwardRef(
|
|
|
7501
7518
|
pointerType: e.pointerType,
|
|
7502
7519
|
pointerId: e.pointerId,
|
|
7503
7520
|
pressure: e.pointerType === "pen" ? e.pressure : void 0,
|
|
7504
|
-
activePointerId,
|
|
7505
7521
|
dragKind: currentDragState.kind
|
|
7506
7522
|
});
|
|
7507
7523
|
}
|
|
7508
7524
|
if (e.pointerType === "pen" && currentDragState.kind === "stroke" && currentDragState.pointerType === "pen") {
|
|
7509
7525
|
debugApplePencilPointer("pen-reentry", {
|
|
7510
7526
|
pointerId: e.pointerId,
|
|
7511
|
-
previousPointerId: activePointerId,
|
|
7512
7527
|
previousPoints: currentDragState.points.length
|
|
7513
7528
|
});
|
|
7514
7529
|
finalizeStrokeDragState(currentDragState);
|
|
@@ -7854,9 +7869,7 @@ var VectorViewport = react.forwardRef(
|
|
|
7854
7869
|
debugApplePencilPointer("native-pointerdown", {
|
|
7855
7870
|
pointerType: e.pointerType,
|
|
7856
7871
|
pointerId: e.pointerId,
|
|
7857
|
-
pressure: e.pressure
|
|
7858
|
-
tool
|
|
7859
|
-
});
|
|
7872
|
+
pressure: e.pressure});
|
|
7860
7873
|
e.preventDefault();
|
|
7861
7874
|
e.stopImmediatePropagation();
|
|
7862
7875
|
};
|
|
@@ -7956,9 +7969,7 @@ var VectorViewport = react.forwardRef(
|
|
|
7956
7969
|
debugApplePencilPointer("touchstart-stroke", {
|
|
7957
7970
|
touchId: touch.identifier,
|
|
7958
7971
|
touchType: touchKind(touch),
|
|
7959
|
-
force: touchPressure(touch)
|
|
7960
|
-
tool
|
|
7961
|
-
});
|
|
7972
|
+
force: touchPressure(touch)});
|
|
7962
7973
|
stopTouchEvent(ev);
|
|
7963
7974
|
};
|
|
7964
7975
|
const onTouchMove = (ev) => {
|
|
@@ -8680,6 +8691,7 @@ var VectorViewport = react.forwardRef(
|
|
|
8680
8691
|
{
|
|
8681
8692
|
ref: imageInputRef,
|
|
8682
8693
|
type: "file",
|
|
8694
|
+
multiple: true,
|
|
8683
8695
|
accept: "image/*,application/pdf",
|
|
8684
8696
|
style: { display: "none" },
|
|
8685
8697
|
"aria-label": "Select image or PDF to place on canvas",
|