canvu-react 0.3.12 → 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.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 thumbScale = Math.min(1, 256 / Math.max(width, height));
973
- const tw = Math.max(1, Math.round(width * thumbScale));
974
- const th = Math.max(1, Math.round(height * thumbScale));
975
- const thumbCanvas = document.createElement("canvas");
976
- thumbCanvas.width = tw;
977
- thumbCanvas.height = th;
978
- const tCtx = thumbCanvas.getContext("2d");
979
- if (tCtx) {
980
- tCtx.imageSmoothingEnabled = true;
981
- tCtx.imageSmoothingQuality = "high";
982
- tCtx.drawImage(canvas, 0, 0, tw, th);
983
- }
984
- const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
985
- const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
986
- return {
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
  }
@@ -999,7 +1025,8 @@ init_shape_builders();
999
1025
  var HYDRATION_CACHE_NAME = "canvu-asset-hydration-v1";
1000
1026
  var DEFAULT_PDF_SCALE = 1.15;
1001
1027
  var DEFAULT_PDF_PAGE_CONCURRENCY = 2;
1002
- var hydrationBlobPromises = /* @__PURE__ */ new Map();
1028
+ var hydrationSourcePromises = /* @__PURE__ */ new Map();
1029
+ var pdfSourceBlobPromises = /* @__PURE__ */ new Map();
1003
1030
  function buildImageHydrationKey(assetId) {
1004
1031
  return `image:${assetId}`;
1005
1032
  }
@@ -1047,23 +1074,38 @@ async function fetchBlob(url) {
1047
1074
  return null;
1048
1075
  }
1049
1076
  }
1050
- async function getHydrationBlob(key, preferCachedRasters, loader) {
1077
+ async function getHydrationSource(key, preferCachedRasters, loader) {
1051
1078
  const cached = preferCachedRasters ? await readCachedHydrationBlob(key) : null;
1052
- if (cached) return cached;
1053
- const inFlight = hydrationBlobPromises.get(key);
1079
+ if (cached) {
1080
+ return {
1081
+ blob: cached
1082
+ };
1083
+ }
1084
+ const inFlight = hydrationSourcePromises.get(key);
1054
1085
  if (inFlight) return await inFlight;
1055
1086
  const nextPromise = (async () => {
1056
- const blob = await loader();
1057
- if (blob) {
1058
- await writeCachedHydrationBlob(key, blob);
1087
+ const source = await loader();
1088
+ if (source?.blob) {
1089
+ await writeCachedHydrationBlob(key, source.blob);
1059
1090
  }
1060
- return blob;
1091
+ return source;
1061
1092
  })();
1062
- hydrationBlobPromises.set(key, nextPromise);
1093
+ hydrationSourcePromises.set(key, nextPromise);
1094
+ try {
1095
+ return await nextPromise;
1096
+ } finally {
1097
+ hydrationSourcePromises.delete(key);
1098
+ }
1099
+ }
1100
+ async function getPdfSourceBlob(url) {
1101
+ const inFlight = pdfSourceBlobPromises.get(url);
1102
+ if (inFlight) return await inFlight;
1103
+ const nextPromise = fetchBlob(url);
1104
+ pdfSourceBlobPromises.set(url, nextPromise);
1063
1105
  try {
1064
1106
  return await nextPromise;
1065
1107
  } finally {
1066
- hydrationBlobPromises.delete(key);
1108
+ pdfSourceBlobPromises.delete(url);
1067
1109
  }
1068
1110
  }
1069
1111
  function registerObjectUrl(objectUrls, blob) {
@@ -1117,18 +1159,22 @@ async function hydrateImageAssets(requests, resolvedAssetUrls, objectUrls, prefe
1117
1159
  if (!resolvedAsset?.url) {
1118
1160
  return [assetId, null];
1119
1161
  }
1120
- const blob = await getHydrationBlob(
1162
+ const source = await getHydrationSource(
1121
1163
  buildImageHydrationKey(assetId),
1122
1164
  preferCachedRasters,
1123
- async () => await fetchBlob(resolvedAsset.url)
1165
+ async () => {
1166
+ const blob = await fetchBlob(resolvedAsset.url);
1167
+ if (!blob) return null;
1168
+ return { blob };
1169
+ }
1124
1170
  );
1125
- if (!blob) {
1171
+ if (!source?.blob) {
1126
1172
  return [assetId, null];
1127
1173
  }
1128
1174
  return [
1129
1175
  assetId,
1130
1176
  {
1131
- href: registerObjectUrl(objectUrls, blob)
1177
+ href: registerObjectUrl(objectUrls, source.blob)
1132
1178
  }
1133
1179
  ];
1134
1180
  })
@@ -1143,45 +1189,69 @@ async function hydratePdfAssets(requests, resolvedAssetUrls, objectUrls, options
1143
1189
  if (!resolvedAsset?.url) {
1144
1190
  continue;
1145
1191
  }
1146
- const missingPages = [];
1147
- for (const pageNumber of group.pageNumbers) {
1148
- const cacheKey = buildPdfHydrationKey(group.assetId, pageNumber, group.scale);
1149
- const cachedBlob = await readCachedHydrationBlob(cacheKey);
1150
- if (!cachedBlob) {
1151
- missingPages.push(pageNumber);
1192
+ const pageKeys = group.pageNumbers.map((pageNumber) => ({
1193
+ pageNumber,
1194
+ cacheKey: buildPdfHydrationKey(group.assetId, pageNumber, group.scale)
1195
+ }));
1196
+ const pagesToRender = [];
1197
+ for (const { pageNumber, cacheKey } of pageKeys) {
1198
+ const cachedBlob = options.preferCachedRasters ? await readCachedHydrationBlob(cacheKey) : null;
1199
+ if (cachedBlob) {
1200
+ hydratedPages.set(cacheKey, {
1201
+ href: registerObjectUrl(objectUrls, cachedBlob)
1202
+ });
1152
1203
  continue;
1153
1204
  }
1154
- hydratedPages.set(cacheKey, {
1155
- href: registerObjectUrl(objectUrls, cachedBlob)
1156
- });
1157
- }
1158
- if (missingPages.length === 0) {
1159
- continue;
1205
+ if (!hydrationSourcePromises.has(cacheKey)) {
1206
+ pagesToRender.push({ pageNumber, cacheKey });
1207
+ }
1160
1208
  }
1161
- const pdfBlob = await fetchBlob(resolvedAsset.url);
1162
- if (!pdfBlob) {
1163
- continue;
1209
+ if (pagesToRender.length > 0) {
1210
+ const renderPromise = (async () => {
1211
+ const pdfBlob = await getPdfSourceBlob(resolvedAsset.url);
1212
+ if (!pdfBlob) {
1213
+ return /* @__PURE__ */ new Map();
1214
+ }
1215
+ const renderedPages = await loadPdfToStore(pdfBlob, options.imageStore, {
1216
+ scale: group.scale,
1217
+ pageNumbers: pagesToRender.map(({ pageNumber }) => pageNumber),
1218
+ pageConcurrency: options.pdfPageConcurrency
1219
+ });
1220
+ const renderedByPage = /* @__PURE__ */ new Map();
1221
+ for (const renderedPage of renderedPages) {
1222
+ const pageBlob = await options.imageStore.getOriginal(renderedPage.blobId);
1223
+ renderedByPage.set(
1224
+ renderedPage.pageNumber,
1225
+ pageBlob ? {
1226
+ blob: pageBlob,
1227
+ width: renderedPage.width,
1228
+ height: renderedPage.height
1229
+ } : null
1230
+ );
1231
+ }
1232
+ return renderedByPage;
1233
+ })();
1234
+ for (const { pageNumber, cacheKey } of pagesToRender) {
1235
+ const pagePromise = getHydrationSource(
1236
+ cacheKey,
1237
+ options.preferCachedRasters,
1238
+ async () => (await renderPromise).get(pageNumber) ?? null
1239
+ );
1240
+ hydrationSourcePromises.set(cacheKey, pagePromise);
1241
+ }
1164
1242
  }
1165
- const renderedPages = await loadPdfToStore(pdfBlob, options.imageStore, {
1166
- scale: group.scale,
1167
- pageNumbers: missingPages,
1168
- pageConcurrency: options.pdfPageConcurrency
1169
- });
1170
- for (const renderedPage of renderedPages) {
1171
- const cacheKey = buildPdfHydrationKey(
1172
- group.assetId,
1173
- renderedPage.pageNumber,
1174
- group.scale
1243
+ for (const { cacheKey } of pageKeys) {
1244
+ if (hydratedPages.has(cacheKey)) continue;
1245
+ const source = await getHydrationSource(
1246
+ cacheKey,
1247
+ options.preferCachedRasters,
1248
+ async () => null
1175
1249
  );
1176
- const pageBlob = await options.imageStore.getOriginal(renderedPage.blobId);
1177
- if (!pageBlob) {
1178
- continue;
1179
- }
1180
- await writeCachedHydrationBlob(cacheKey, pageBlob);
1250
+ if (!source?.blob) continue;
1181
1251
  hydratedPages.set(cacheKey, {
1182
- href: registerObjectUrl(objectUrls, pageBlob),
1183
- width: renderedPage.width,
1184
- height: renderedPage.height
1252
+ href: registerObjectUrl(objectUrls, source.blob),
1253
+ width: source.width,
1254
+ height: source.height
1185
1255
  });
1186
1256
  }
1187
1257
  }
@@ -1222,7 +1292,9 @@ async function hydrateSceneItemsWithAssets(items, assetStore, options = {}) {
1222
1292
  objectUrls,
1223
1293
  {
1224
1294
  imageStore,
1225
- pdfPageConcurrency}
1295
+ pdfPageConcurrency,
1296
+ preferCachedRasters
1297
+ }
1226
1298
  );
1227
1299
  return {
1228
1300
  items: items.map((item) => {
@@ -1299,9 +1371,10 @@ async function ingestAssetFilesToSceneItems(options) {
1299
1371
  createId = createShapeId,
1300
1372
  gapWorld = 16,
1301
1373
  stepWorld = 48,
1302
- pdfScale = 1.5,
1374
+ pdfScale = 1.15,
1303
1375
  pdfPageConcurrency = 2,
1304
1376
  decorateItem,
1377
+ onItemsReady,
1305
1378
  onError
1306
1379
  } = options;
1307
1380
  const items = [];
@@ -1323,55 +1396,56 @@ async function ingestAssetFilesToSceneItems(options) {
1323
1396
  }
1324
1397
  try {
1325
1398
  if (kind === "pdf") {
1326
- const [uploadResult2, pages] = await Promise.all([
1327
- uploadAssetIfNeeded(assetStore, file, kind),
1328
- loadPdfToStore(file, imageStore, {
1329
- scale: pdfScale,
1330
- pageConcurrency: pdfPageConcurrency
1331
- })
1332
- ]);
1333
- for (const page of pages) {
1334
- const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1335
- const naturalTopY2 = worldCenter.y - page.height / 2;
1336
- const stackedTopY = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1337
- const bounds2 = {
1338
- x: worldCenter.x - page.width / 2,
1339
- y: Math.max(naturalTopY2, stackedTopY),
1340
- width: page.width,
1341
- height: page.height
1342
- };
1343
- const itemContext2 = {
1344
- file,
1345
- kind,
1346
- itemIndex: items.length,
1347
- pageNumber: page.pageNumber
1348
- };
1349
- const item2 = finalizeIngestedItem(
1350
- {
1351
- id: createId(),
1352
- x: bounds2.x,
1353
- y: bounds2.y,
1354
- bounds: { ...bounds2 },
1355
- toolKind: "image",
1356
- imageBlobId: page.blobId,
1357
- imageRasterHref: fullUrl2 ?? void 0,
1358
- imageIntrinsicSize: {
1359
- width: page.width,
1360
- height: page.height
1399
+ const uploadResultPromise = uploadAssetIfNeeded(assetStore, file, kind);
1400
+ await loadPdfToStore(file, imageStore, {
1401
+ scale: pdfScale,
1402
+ pageConcurrency: pdfPageConcurrency,
1403
+ storeThumbnails: false,
1404
+ onPageStored: async (page) => {
1405
+ const uploadResult2 = await uploadResultPromise;
1406
+ const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1407
+ const naturalTopY2 = worldCenter.y - page.height / 2;
1408
+ const stackedTopY = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1409
+ const bounds2 = {
1410
+ x: worldCenter.x - page.width / 2,
1411
+ y: Math.max(naturalTopY2, stackedTopY),
1412
+ width: page.width,
1413
+ height: page.height
1414
+ };
1415
+ const itemContext2 = {
1416
+ file,
1417
+ kind,
1418
+ itemIndex: items.length,
1419
+ pageNumber: page.pageNumber
1420
+ };
1421
+ const item2 = finalizeIngestedItem(
1422
+ {
1423
+ id: createId(),
1424
+ x: bounds2.x,
1425
+ y: bounds2.y,
1426
+ bounds: { ...bounds2 },
1427
+ toolKind: "image",
1428
+ imageBlobId: page.blobId,
1429
+ imageRasterHref: fullUrl2 ?? void 0,
1430
+ imageIntrinsicSize: {
1431
+ width: page.width,
1432
+ height: page.height
1433
+ },
1434
+ childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
1435
+ fullUrl2,
1436
+ { width: page.width, height: page.height },
1437
+ bounds2
1438
+ ) : ""
1361
1439
  },
1362
- childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
1363
- fullUrl2,
1364
- { width: page.width, height: page.height },
1365
- bounds2
1366
- ) : ""
1367
- },
1368
- itemContext2,
1369
- uploadResult2,
1370
- decorateItem
1371
- );
1372
- items.push(item2);
1373
- occupiedBottomY = bounds2.y + page.height;
1374
- }
1440
+ itemContext2,
1441
+ uploadResult2,
1442
+ decorateItem
1443
+ );
1444
+ items.push(item2);
1445
+ occupiedBottomY = bounds2.y + page.height;
1446
+ onItemsReady?.([item2], { file, kind });
1447
+ }
1448
+ });
1375
1449
  hasImagePlacementBase = false;
1376
1450
  imagePlacementIndex = 0;
1377
1451
  imageYOffsetAdjustment = 0;
@@ -1424,6 +1498,7 @@ async function ingestAssetFilesToSceneItems(options) {
1424
1498
  decorateItem
1425
1499
  );
1426
1500
  items.push(item);
1501
+ onItemsReady?.([item], { file, kind });
1427
1502
  imagePlacementIndex++;
1428
1503
  occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
1429
1504
  } catch (error) {
@@ -7336,7 +7411,13 @@ var VectorViewport = react.forwardRef(
7336
7411
  y: worldY
7337
7412
  },
7338
7413
  imageStore: store,
7339
- assetStore: assetStoreRef.current ?? void 0
7414
+ assetStore: assetStoreRef.current ?? void 0,
7415
+ onItemsReady: (nextItems) => {
7416
+ if (nextItems.length === 0) return;
7417
+ setLoadingSkeletons([]);
7418
+ change([...itemsRef.current, ...nextItems]);
7419
+ setEffectiveSelectedIdsRef.current(nextItems.map((item) => item.id));
7420
+ }
7340
7421
  });
7341
7422
  if (result.errors.length > 0) {
7342
7423
  for (const error of result.errors) {
@@ -7344,8 +7425,6 @@ var VectorViewport = react.forwardRef(
7344
7425
  }
7345
7426
  }
7346
7427
  if (result.items.length === 0) return;
7347
- change([...itemsRef.current, ...result.items]);
7348
- setEffectiveSelectedIdsRef.current(result.items.map((item) => item.id));
7349
7428
  } finally {
7350
7429
  setLoadingSkeletons([]);
7351
7430
  }