canvu-react 0.3.24 → 0.3.25

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
@@ -718,6 +718,47 @@ var init_shape_builders = __esm({
718
718
  }
719
719
  });
720
720
 
721
+ // src/image/canvas-encode.ts
722
+ var DEFAULT_FALLBACK_MIME_TYPES = ["image/png"];
723
+ var tryCanvasToBlob = (canvas, mimeType, quality) => new Promise((resolve) => {
724
+ canvas.toBlob((blob) => resolve(blob), mimeType, quality);
725
+ });
726
+ var blobFromDataUrl = async (dataUrl) => {
727
+ const response = await fetch(dataUrl);
728
+ const blob = await response.blob();
729
+ if (blob.size === 0) {
730
+ throw new Error("Failed to encode canvas to blob");
731
+ }
732
+ return blob;
733
+ };
734
+ async function encodeCanvasToBlob(canvas, options) {
735
+ const primaryMimeType = options?.mimeType ?? "image/png";
736
+ const quality = options?.quality;
737
+ const mimeTypes = [
738
+ primaryMimeType,
739
+ ...options?.fallbackMimeTypes ?? DEFAULT_FALLBACK_MIME_TYPES
740
+ ].filter(
741
+ (mimeType, index, mimeTypeList) => mimeTypeList.indexOf(mimeType) === index
742
+ );
743
+ for (const mimeType of mimeTypes) {
744
+ const blob = await tryCanvasToBlob(
745
+ canvas,
746
+ mimeType,
747
+ mimeType === primaryMimeType ? quality : void 0
748
+ );
749
+ if (blob) {
750
+ return blob;
751
+ }
752
+ }
753
+ for (const mimeType of mimeTypes) {
754
+ const dataUrl = canvas.toDataURL(mimeType, quality);
755
+ if (dataUrl && dataUrl !== "data:,") {
756
+ return blobFromDataUrl(dataUrl);
757
+ }
758
+ }
759
+ throw new Error("Failed to encode canvas to blob");
760
+ }
761
+
721
762
  // src/image/indexed-db-image-store.ts
722
763
  var DB_NAME = "canvu-image-store";
723
764
  var DB_VERSION = 1;
@@ -836,19 +877,7 @@ function decodeImageToCanvas(blob, maxDimension) {
836
877
  });
837
878
  }
838
879
  function canvasToBlob(canvas, mime, quality) {
839
- return new Promise((resolve, reject) => {
840
- canvas.toBlob(
841
- (blob) => {
842
- if (!blob) {
843
- reject(new Error("Could not encode blob"));
844
- return;
845
- }
846
- resolve(blob);
847
- },
848
- mime,
849
- quality
850
- );
851
- });
880
+ return encodeCanvasToBlob(canvas, { mimeType: mime, quality });
852
881
  }
853
882
  async function loadImageToStore(file, store) {
854
883
  const originalBlob = file;
@@ -916,21 +945,6 @@ async function renderPageToCanvas(page, scale) {
916
945
  await page.render({ canvas, viewport }).promise;
917
946
  return { canvas, width: w, height: h };
918
947
  }
919
- function canvasToBlob2(canvas, mime, quality) {
920
- return new Promise((resolve, reject) => {
921
- canvas.toBlob(
922
- (blob) => {
923
- if (!blob) {
924
- reject(new Error("Could not encode blob"));
925
- return;
926
- }
927
- resolve(blob);
928
- },
929
- mime,
930
- quality
931
- );
932
- });
933
- }
934
948
  function normalizePdfPageNumbers(pageNumbers, pageCount) {
935
949
  if (!pageNumbers || pageNumbers.length === 0) {
936
950
  return Array.from({ length: pageCount }, (_, index) => index + 1);
@@ -992,7 +1006,7 @@ async function loadPdfToStore(file, store, options) {
992
1006
  const page = await pdf.getPage(pageNumber);
993
1007
  const { canvas, width, height } = await renderPageToCanvas(page, scale);
994
1008
  const mime = "image/png";
995
- const pageBlob = await canvasToBlob2(canvas, mime);
1009
+ const pageBlob = await encodeCanvasToBlob(canvas, { mimeType: mime });
996
1010
  const blobId = await store.storeOriginal(pageBlob);
997
1011
  const thumbnailBlobId = storeThumbnails ? await (async () => {
998
1012
  const thumbScale = Math.min(1, 256 / Math.max(width, height));
@@ -1007,7 +1021,9 @@ async function loadPdfToStore(file, store, options) {
1007
1021
  tCtx.imageSmoothingQuality = "high";
1008
1022
  tCtx.drawImage(canvas, 0, 0, tw, th);
1009
1023
  }
1010
- const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
1024
+ const thumbBlob = await encodeCanvasToBlob(thumbCanvas, {
1025
+ mimeType: mime
1026
+ });
1011
1027
  return await store.storeThumbnail(thumbBlob);
1012
1028
  })() : "";
1013
1029
  const pageResult = {
@@ -7405,6 +7421,40 @@ var VectorViewport = react.forwardRef(
7405
7421
  onWorldPointerLeaveRef.current = onWorldPointerLeave;
7406
7422
  const onPlacementPreviewChangeRef = react.useRef(onPlacementPreviewChange);
7407
7423
  onPlacementPreviewChangeRef.current = onPlacementPreviewChange;
7424
+ const directRemoteStrokePreviewRef = react.useRef(false);
7425
+ const remoteStrokePreviewFrameRef = react.useRef(null);
7426
+ const pendingRemoteStrokePreviewRef = react.useRef(null);
7427
+ const flushRemoteStrokePreview = react.useCallback(() => {
7428
+ remoteStrokePreviewFrameRef.current = null;
7429
+ const pending = pendingRemoteStrokePreviewRef.current;
7430
+ if (!pending) return;
7431
+ onPlacementPreviewChangeRef.current?.({
7432
+ kind: "stroke",
7433
+ tool: pending.tool,
7434
+ points: pending.points
7435
+ });
7436
+ }, []);
7437
+ const emitRemoteStrokePreview = react.useCallback(
7438
+ (tool, points) => {
7439
+ if (tool === "laser") return;
7440
+ directRemoteStrokePreviewRef.current = true;
7441
+ pendingRemoteStrokePreviewRef.current = { tool, points };
7442
+ if (remoteStrokePreviewFrameRef.current != null) return;
7443
+ remoteStrokePreviewFrameRef.current = requestAnimationFrame(
7444
+ flushRemoteStrokePreview
7445
+ );
7446
+ },
7447
+ [flushRemoteStrokePreview]
7448
+ );
7449
+ const emitRemoteStrokePreviewClear = react.useCallback(() => {
7450
+ if (remoteStrokePreviewFrameRef.current != null) {
7451
+ cancelAnimationFrame(remoteStrokePreviewFrameRef.current);
7452
+ remoteStrokePreviewFrameRef.current = null;
7453
+ }
7454
+ pendingRemoteStrokePreviewRef.current = null;
7455
+ directRemoteStrokePreviewRef.current = false;
7456
+ onPlacementPreviewChangeRef.current?.(null);
7457
+ }, []);
7408
7458
  const overlayCameraTickRef = react.useRef(false);
7409
7459
  overlayCameraTickRef.current = interactive || remotePresence != null && remotePresence.length > 0 || presenceOverlay != null;
7410
7460
  const pruneEraserTrail = react.useCallback(
@@ -7523,6 +7573,7 @@ var VectorViewport = react.forwardRef(
7523
7573
  if (itemId) {
7524
7574
  renderSceneWithLivePenStroke(null);
7525
7575
  }
7576
+ emitRemoteStrokePreviewClear();
7526
7577
  setPlacementPreview(null);
7527
7578
  commitCompletedStroke({
7528
7579
  tool,
@@ -7534,6 +7585,7 @@ var VectorViewport = react.forwardRef(
7534
7585
  },
7535
7586
  [
7536
7587
  commitCompletedStroke,
7588
+ emitRemoteStrokePreviewClear,
7537
7589
  releaseInteractionPointer,
7538
7590
  renderSceneWithLivePenStroke
7539
7591
  ]
@@ -7645,8 +7697,18 @@ var VectorViewport = react.forwardRef(
7645
7697
  };
7646
7698
  }, [onWorldPointerMove]);
7647
7699
  react.useEffect(() => {
7700
+ if (directRemoteStrokePreviewRef.current && placementPreview === null) {
7701
+ return;
7702
+ }
7648
7703
  onPlacementPreviewChangeRef.current?.(placementPreview);
7649
7704
  }, [placementPreview]);
7705
+ react.useEffect(() => {
7706
+ return () => {
7707
+ if (remoteStrokePreviewFrameRef.current != null) {
7708
+ cancelAnimationFrame(remoteStrokePreviewFrameRef.current);
7709
+ }
7710
+ };
7711
+ }, []);
7650
7712
  react.useEffect(() => {
7651
7713
  const scene = sceneRef.current;
7652
7714
  if (scene) {
@@ -8640,6 +8702,7 @@ var VectorViewport = react.forwardRef(
8640
8702
  setPlacementPreview(null);
8641
8703
  } else if (directPenStroke) {
8642
8704
  setPlacementPreview(null);
8705
+ emitRemoteStrokePreview(tool, [startPoint]);
8643
8706
  } else {
8644
8707
  setPlacementPreview({
8645
8708
  kind: "stroke",
@@ -8681,6 +8744,7 @@ var VectorViewport = react.forwardRef(
8681
8744
  [
8682
8745
  applePencilNav,
8683
8746
  captureInteractionPointer,
8747
+ emitRemoteStrokePreview,
8684
8748
  finalizeStrokeDragState,
8685
8749
  renderSceneWithLivePenStroke,
8686
8750
  screenToWorld
@@ -8739,6 +8803,7 @@ var VectorViewport = react.forwardRef(
8739
8803
  activeInteractionPointerIdRef.current = e.pointerId;
8740
8804
  activeInteractionPointerTargetRef.current = null;
8741
8805
  setPlacementPreview(null);
8806
+ emitRemoteStrokePreview(tool, [startPoint]);
8742
8807
  debugApplePencilPointer("native-pointerdown", {
8743
8808
  pointerType: e.pointerType,
8744
8809
  pointerId: e.pointerId,
@@ -8756,6 +8821,7 @@ var VectorViewport = react.forwardRef(
8756
8821
  };
8757
8822
  }, [
8758
8823
  applePencilNav,
8824
+ emitRemoteStrokePreview,
8759
8825
  finalizeStrokeDragState,
8760
8826
  interactive,
8761
8827
  renderSceneWithLivePenStroke,
@@ -8839,6 +8905,7 @@ var VectorViewport = react.forwardRef(
8839
8905
  activeInteractionPointerTargetRef.current = null;
8840
8906
  activeInteractionTouchIdRef.current = touch.identifier;
8841
8907
  setPlacementPreview(null);
8908
+ emitRemoteStrokePreview(tool, [startPoint]);
8842
8909
  debugApplePencilPointer("touchstart-stroke", {
8843
8910
  touchId: touch.identifier,
8844
8911
  touchType: touchKind(touch),
@@ -8870,6 +8937,7 @@ var VectorViewport = react.forwardRef(
8870
8937
  renderSceneWithLivePenStroke(item);
8871
8938
  }
8872
8939
  setPlacementPreview(null);
8940
+ emitRemoteStrokePreview(st.tool, interpolated);
8873
8941
  }
8874
8942
  debugApplePencilPointer("touchmove-stroke", {
8875
8943
  touchId: touch.identifier,
@@ -8932,6 +9000,7 @@ var VectorViewport = react.forwardRef(
8932
9000
  };
8933
9001
  }, [
8934
9002
  applePencilNav,
9003
+ emitRemoteStrokePreview,
8935
9004
  finalizeStrokeDragState,
8936
9005
  interactive,
8937
9006
  renderSceneWithLivePenStroke,
@@ -8986,6 +9055,7 @@ var VectorViewport = react.forwardRef(
8986
9055
  renderSceneWithLivePenStroke(item);
8987
9056
  }
8988
9057
  setPlacementPreview(null);
9058
+ emitRemoteStrokePreview(st.tool, interpolated);
8989
9059
  return;
8990
9060
  }
8991
9061
  if (st.tool === "laser") {
@@ -9127,6 +9197,9 @@ var VectorViewport = react.forwardRef(
9127
9197
  }
9128
9198
  const cam = cameraRef.current;
9129
9199
  if (!cam) {
9200
+ if (st.kind === "stroke") {
9201
+ emitRemoteStrokePreviewClear();
9202
+ }
9130
9203
  if (st.kind === "erase") {
9131
9204
  eraserPreviewIdsRef.current.clear();
9132
9205
  setEraserPreviewIds([]);
@@ -9358,6 +9431,8 @@ var VectorViewport = react.forwardRef(
9358
9431
  document.removeEventListener("pointercancel", onUp);
9359
9432
  };
9360
9433
  }, [
9434
+ emitRemoteStrokePreview,
9435
+ emitRemoteStrokePreviewClear,
9361
9436
  interactive,
9362
9437
  pruneEraserTrail,
9363
9438
  pruneLaserTrail,