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/README.md +9 -1
- package/dist/index.cjs +135 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +135 -63
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +194 -115
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +9 -1
- package/dist/react.d.ts +9 -1
- package/dist/react.js +194 -115
- package/dist/react.js.map +1 -1
- package/dist/realtime.cjs +30 -5
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +30 -5
- package/dist/realtime.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
|
}
|
|
@@ -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
|
|
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
|
|
1077
|
+
async function getHydrationSource(key, preferCachedRasters, loader) {
|
|
1051
1078
|
const cached = preferCachedRasters ? await readCachedHydrationBlob(key) : null;
|
|
1052
|
-
if (cached)
|
|
1053
|
-
|
|
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
|
|
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
|
|
1091
|
+
return source;
|
|
1061
1092
|
})();
|
|
1062
|
-
|
|
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
|
-
|
|
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
|
|
1162
|
+
const source = await getHydrationSource(
|
|
1121
1163
|
buildImageHydrationKey(assetId),
|
|
1122
1164
|
preferCachedRasters,
|
|
1123
|
-
async () =>
|
|
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
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
if (missingPages.length === 0) {
|
|
1159
|
-
continue;
|
|
1205
|
+
if (!hydrationSourcePromises.has(cacheKey)) {
|
|
1206
|
+
pagesToRender.push({ pageNumber, cacheKey });
|
|
1207
|
+
}
|
|
1160
1208
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
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,
|
|
1183
|
-
width:
|
|
1184
|
-
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.
|
|
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
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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
|
}
|