canvu-react 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.cts CHANGED
@@ -107,10 +107,18 @@ type IngestAssetFilesToSceneItemsOptions = {
107
107
  /**
108
108
  * PDF rasterization scale forwarded to `pdfjs-dist`.
109
109
  *
110
- * Defaults to `1.5` for crisp zoom while keeping payloads reasonable.
110
+ * Defaults to `1.15` to balance first paint latency with zoom quality.
111
111
  */
112
112
  pdfScale?: number;
113
113
  pdfPageConcurrency?: number;
114
+ /**
115
+ * Called as soon as canvu finishes converting one or more items from the
116
+ * source files.
117
+ */
118
+ onItemsReady?: (items: VectorSceneItem[], context: {
119
+ file: File;
120
+ kind: VectorViewportAssetKind;
121
+ }) => void;
114
122
  /**
115
123
  * Final hook to customize each created item after canvu attaches its own
116
124
  * image metadata and any `assetStore.upload(...)` `pluginData`.
package/dist/react.d.ts CHANGED
@@ -107,10 +107,18 @@ type IngestAssetFilesToSceneItemsOptions = {
107
107
  /**
108
108
  * PDF rasterization scale forwarded to `pdfjs-dist`.
109
109
  *
110
- * Defaults to `1.5` for crisp zoom while keeping payloads reasonable.
110
+ * Defaults to `1.15` to balance first paint latency with zoom quality.
111
111
  */
112
112
  pdfScale?: number;
113
113
  pdfPageConcurrency?: number;
114
+ /**
115
+ * Called as soon as canvu finishes converting one or more items from the
116
+ * source files.
117
+ */
118
+ onItemsReady?: (items: VectorSceneItem[], context: {
119
+ file: File;
120
+ kind: VectorViewportAssetKind;
121
+ }) => void;
114
122
  /**
115
123
  * Final hook to customize each created item after canvu attaches its own
116
124
  * 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 thumbScale = Math.min(1, 256 / Math.max(width, height));
966
- const tw = Math.max(1, Math.round(width * thumbScale));
967
- const th = Math.max(1, Math.round(height * thumbScale));
968
- const thumbCanvas = document.createElement("canvas");
969
- thumbCanvas.width = tw;
970
- thumbCanvas.height = th;
971
- const tCtx = thumbCanvas.getContext("2d");
972
- if (tCtx) {
973
- tCtx.imageSmoothingEnabled = true;
974
- tCtx.imageSmoothingQuality = "high";
975
- tCtx.drawImage(canvas, 0, 0, tw, th);
976
- }
977
- const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
978
- const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
979
- return {
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
  }
@@ -1338,9 +1364,10 @@ async function ingestAssetFilesToSceneItems(options) {
1338
1364
  createId = createShapeId,
1339
1365
  gapWorld = 16,
1340
1366
  stepWorld = 48,
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 = [];
@@ -1362,55 +1389,56 @@ async function ingestAssetFilesToSceneItems(options) {
1362
1389
  }
1363
1390
  try {
1364
1391
  if (kind === "pdf") {
1365
- const [uploadResult2, pages] = await Promise.all([
1366
- uploadAssetIfNeeded(assetStore, file, kind),
1367
- loadPdfToStore(file, imageStore, {
1368
- scale: pdfScale,
1369
- pageConcurrency: pdfPageConcurrency
1370
- })
1371
- ]);
1372
- for (const page of pages) {
1373
- const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1374
- const naturalTopY2 = worldCenter.y - page.height / 2;
1375
- const stackedTopY = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1376
- const bounds2 = {
1377
- x: worldCenter.x - page.width / 2,
1378
- y: Math.max(naturalTopY2, stackedTopY),
1379
- width: page.width,
1380
- height: page.height
1381
- };
1382
- const itemContext2 = {
1383
- file,
1384
- kind,
1385
- itemIndex: items.length,
1386
- pageNumber: page.pageNumber
1387
- };
1388
- const item2 = finalizeIngestedItem(
1389
- {
1390
- id: createId(),
1391
- x: bounds2.x,
1392
- y: bounds2.y,
1393
- bounds: { ...bounds2 },
1394
- toolKind: "image",
1395
- imageBlobId: page.blobId,
1396
- imageRasterHref: fullUrl2 ?? void 0,
1397
- imageIntrinsicSize: {
1398
- width: page.width,
1399
- height: page.height
1392
+ const uploadResultPromise = uploadAssetIfNeeded(assetStore, file, kind);
1393
+ await loadPdfToStore(file, imageStore, {
1394
+ scale: pdfScale,
1395
+ pageConcurrency: pdfPageConcurrency,
1396
+ storeThumbnails: false,
1397
+ onPageStored: async (page) => {
1398
+ const uploadResult2 = await uploadResultPromise;
1399
+ const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1400
+ const naturalTopY2 = worldCenter.y - page.height / 2;
1401
+ const stackedTopY = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1402
+ const bounds2 = {
1403
+ x: worldCenter.x - page.width / 2,
1404
+ y: Math.max(naturalTopY2, stackedTopY),
1405
+ width: page.width,
1406
+ height: page.height
1407
+ };
1408
+ const itemContext2 = {
1409
+ file,
1410
+ kind,
1411
+ itemIndex: items.length,
1412
+ pageNumber: page.pageNumber
1413
+ };
1414
+ const item2 = finalizeIngestedItem(
1415
+ {
1416
+ id: createId(),
1417
+ x: bounds2.x,
1418
+ y: bounds2.y,
1419
+ bounds: { ...bounds2 },
1420
+ toolKind: "image",
1421
+ imageBlobId: page.blobId,
1422
+ imageRasterHref: fullUrl2 ?? void 0,
1423
+ imageIntrinsicSize: {
1424
+ width: page.width,
1425
+ height: page.height
1426
+ },
1427
+ childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
1428
+ fullUrl2,
1429
+ { width: page.width, height: page.height },
1430
+ bounds2
1431
+ ) : ""
1400
1432
  },
1401
- childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
1402
- fullUrl2,
1403
- { width: page.width, height: page.height },
1404
- bounds2
1405
- ) : ""
1406
- },
1407
- itemContext2,
1408
- uploadResult2,
1409
- decorateItem
1410
- );
1411
- items.push(item2);
1412
- occupiedBottomY = bounds2.y + page.height;
1413
- }
1433
+ itemContext2,
1434
+ uploadResult2,
1435
+ decorateItem
1436
+ );
1437
+ items.push(item2);
1438
+ occupiedBottomY = bounds2.y + page.height;
1439
+ onItemsReady?.([item2], { file, kind });
1440
+ }
1441
+ });
1414
1442
  hasImagePlacementBase = false;
1415
1443
  imagePlacementIndex = 0;
1416
1444
  imageYOffsetAdjustment = 0;
@@ -1463,6 +1491,7 @@ async function ingestAssetFilesToSceneItems(options) {
1463
1491
  decorateItem
1464
1492
  );
1465
1493
  items.push(item);
1494
+ onItemsReady?.([item], { file, kind });
1466
1495
  imagePlacementIndex++;
1467
1496
  occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
1468
1497
  } catch (error) {
@@ -7375,7 +7404,13 @@ var VectorViewport = forwardRef(
7375
7404
  y: worldY
7376
7405
  },
7377
7406
  imageStore: store,
7378
- assetStore: assetStoreRef.current ?? void 0
7407
+ assetStore: assetStoreRef.current ?? void 0,
7408
+ onItemsReady: (nextItems) => {
7409
+ if (nextItems.length === 0) return;
7410
+ setLoadingSkeletons([]);
7411
+ change([...itemsRef.current, ...nextItems]);
7412
+ setEffectiveSelectedIdsRef.current(nextItems.map((item) => item.id));
7413
+ }
7379
7414
  });
7380
7415
  if (result.errors.length > 0) {
7381
7416
  for (const error of result.errors) {
@@ -7383,8 +7418,6 @@ var VectorViewport = forwardRef(
7383
7418
  }
7384
7419
  }
7385
7420
  if (result.items.length === 0) return;
7386
- change([...itemsRef.current, ...result.items]);
7387
- setEffectiveSelectedIdsRef.current(result.items.map((item) => item.id));
7388
7421
  } finally {
7389
7422
  setLoadingSkeletons([]);
7390
7423
  }