canvu-react 0.3.24 → 0.3.26

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 = {
@@ -6499,7 +6515,12 @@ function PresenceRemoteLayer({
6499
6515
  const markup = peer.markupStroke;
6500
6516
  let strokeNode = null;
6501
6517
  if (markup && markup.points.length > 0) {
6502
- const paint = strokePaint(markup.tool, color);
6518
+ const fallbackPaint = strokePaint(markup.tool, color);
6519
+ const paint = {
6520
+ stroke: markup.stroke ?? fallbackPaint.stroke,
6521
+ strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
6522
+ widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
6523
+ };
6503
6524
  const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
6504
6525
  if (d) {
6505
6526
  strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
@@ -7405,6 +7426,41 @@ var VectorViewport = react.forwardRef(
7405
7426
  onWorldPointerLeaveRef.current = onWorldPointerLeave;
7406
7427
  const onPlacementPreviewChangeRef = react.useRef(onPlacementPreviewChange);
7407
7428
  onPlacementPreviewChangeRef.current = onPlacementPreviewChange;
7429
+ const directRemoteStrokePreviewRef = react.useRef(false);
7430
+ const remoteStrokePreviewFrameRef = react.useRef(null);
7431
+ const pendingRemoteStrokePreviewRef = react.useRef(null);
7432
+ const flushRemoteStrokePreviewWithStyle = react.useCallback(() => {
7433
+ remoteStrokePreviewFrameRef.current = null;
7434
+ const pending = pendingRemoteStrokePreviewRef.current;
7435
+ if (!pending) return;
7436
+ onPlacementPreviewChangeRef.current?.({
7437
+ kind: "stroke",
7438
+ tool: pending.tool,
7439
+ points: pending.points,
7440
+ style: { ...strokeStyleRef.current }
7441
+ });
7442
+ }, []);
7443
+ const emitRemoteStrokePreview = react.useCallback(
7444
+ (tool, points) => {
7445
+ if (tool === "laser") return;
7446
+ directRemoteStrokePreviewRef.current = true;
7447
+ pendingRemoteStrokePreviewRef.current = { tool, points };
7448
+ if (remoteStrokePreviewFrameRef.current != null) return;
7449
+ remoteStrokePreviewFrameRef.current = requestAnimationFrame(
7450
+ flushRemoteStrokePreviewWithStyle
7451
+ );
7452
+ },
7453
+ [flushRemoteStrokePreviewWithStyle]
7454
+ );
7455
+ const emitRemoteStrokePreviewClear = react.useCallback(() => {
7456
+ if (remoteStrokePreviewFrameRef.current != null) {
7457
+ cancelAnimationFrame(remoteStrokePreviewFrameRef.current);
7458
+ remoteStrokePreviewFrameRef.current = null;
7459
+ }
7460
+ pendingRemoteStrokePreviewRef.current = null;
7461
+ directRemoteStrokePreviewRef.current = false;
7462
+ onPlacementPreviewChangeRef.current?.(null);
7463
+ }, []);
7408
7464
  const overlayCameraTickRef = react.useRef(false);
7409
7465
  overlayCameraTickRef.current = interactive || remotePresence != null && remotePresence.length > 0 || presenceOverlay != null;
7410
7466
  const pruneEraserTrail = react.useCallback(
@@ -7523,6 +7579,7 @@ var VectorViewport = react.forwardRef(
7523
7579
  if (itemId) {
7524
7580
  renderSceneWithLivePenStroke(null);
7525
7581
  }
7582
+ emitRemoteStrokePreviewClear();
7526
7583
  setPlacementPreview(null);
7527
7584
  commitCompletedStroke({
7528
7585
  tool,
@@ -7534,6 +7591,7 @@ var VectorViewport = react.forwardRef(
7534
7591
  },
7535
7592
  [
7536
7593
  commitCompletedStroke,
7594
+ emitRemoteStrokePreviewClear,
7537
7595
  releaseInteractionPointer,
7538
7596
  renderSceneWithLivePenStroke
7539
7597
  ]
@@ -7645,8 +7703,18 @@ var VectorViewport = react.forwardRef(
7645
7703
  };
7646
7704
  }, [onWorldPointerMove]);
7647
7705
  react.useEffect(() => {
7706
+ if (directRemoteStrokePreviewRef.current && placementPreview === null) {
7707
+ return;
7708
+ }
7648
7709
  onPlacementPreviewChangeRef.current?.(placementPreview);
7649
7710
  }, [placementPreview]);
7711
+ react.useEffect(() => {
7712
+ return () => {
7713
+ if (remoteStrokePreviewFrameRef.current != null) {
7714
+ cancelAnimationFrame(remoteStrokePreviewFrameRef.current);
7715
+ }
7716
+ };
7717
+ }, []);
7650
7718
  react.useEffect(() => {
7651
7719
  const scene = sceneRef.current;
7652
7720
  if (scene) {
@@ -8640,11 +8708,13 @@ var VectorViewport = react.forwardRef(
8640
8708
  setPlacementPreview(null);
8641
8709
  } else if (directPenStroke) {
8642
8710
  setPlacementPreview(null);
8711
+ emitRemoteStrokePreview(tool, [startPoint]);
8643
8712
  } else {
8644
8713
  setPlacementPreview({
8645
8714
  kind: "stroke",
8646
8715
  tool,
8647
- points: [startPoint]
8716
+ points: [startPoint],
8717
+ style: { ...strokeStyleRef.current }
8648
8718
  });
8649
8719
  }
8650
8720
  captureInteractionPointer(e.currentTarget, e.pointerId);
@@ -8681,6 +8751,7 @@ var VectorViewport = react.forwardRef(
8681
8751
  [
8682
8752
  applePencilNav,
8683
8753
  captureInteractionPointer,
8754
+ emitRemoteStrokePreview,
8684
8755
  finalizeStrokeDragState,
8685
8756
  renderSceneWithLivePenStroke,
8686
8757
  screenToWorld
@@ -8739,6 +8810,7 @@ var VectorViewport = react.forwardRef(
8739
8810
  activeInteractionPointerIdRef.current = e.pointerId;
8740
8811
  activeInteractionPointerTargetRef.current = null;
8741
8812
  setPlacementPreview(null);
8813
+ emitRemoteStrokePreview(tool, [startPoint]);
8742
8814
  debugApplePencilPointer("native-pointerdown", {
8743
8815
  pointerType: e.pointerType,
8744
8816
  pointerId: e.pointerId,
@@ -8756,6 +8828,7 @@ var VectorViewport = react.forwardRef(
8756
8828
  };
8757
8829
  }, [
8758
8830
  applePencilNav,
8831
+ emitRemoteStrokePreview,
8759
8832
  finalizeStrokeDragState,
8760
8833
  interactive,
8761
8834
  renderSceneWithLivePenStroke,
@@ -8839,6 +8912,7 @@ var VectorViewport = react.forwardRef(
8839
8912
  activeInteractionPointerTargetRef.current = null;
8840
8913
  activeInteractionTouchIdRef.current = touch.identifier;
8841
8914
  setPlacementPreview(null);
8915
+ emitRemoteStrokePreview(tool, [startPoint]);
8842
8916
  debugApplePencilPointer("touchstart-stroke", {
8843
8917
  touchId: touch.identifier,
8844
8918
  touchType: touchKind(touch),
@@ -8870,6 +8944,7 @@ var VectorViewport = react.forwardRef(
8870
8944
  renderSceneWithLivePenStroke(item);
8871
8945
  }
8872
8946
  setPlacementPreview(null);
8947
+ emitRemoteStrokePreview(st.tool, interpolated);
8873
8948
  }
8874
8949
  debugApplePencilPointer("touchmove-stroke", {
8875
8950
  touchId: touch.identifier,
@@ -8932,6 +9007,7 @@ var VectorViewport = react.forwardRef(
8932
9007
  };
8933
9008
  }, [
8934
9009
  applePencilNav,
9010
+ emitRemoteStrokePreview,
8935
9011
  finalizeStrokeDragState,
8936
9012
  interactive,
8937
9013
  renderSceneWithLivePenStroke,
@@ -8986,6 +9062,7 @@ var VectorViewport = react.forwardRef(
8986
9062
  renderSceneWithLivePenStroke(item);
8987
9063
  }
8988
9064
  setPlacementPreview(null);
9065
+ emitRemoteStrokePreview(st.tool, interpolated);
8989
9066
  return;
8990
9067
  }
8991
9068
  if (st.tool === "laser") {
@@ -9004,7 +9081,8 @@ var VectorViewport = react.forwardRef(
9004
9081
  setPlacementPreview({
9005
9082
  kind: "stroke",
9006
9083
  tool: st.tool,
9007
- points: interpolated
9084
+ points: interpolated,
9085
+ style: { ...strokeStyleRef.current }
9008
9086
  });
9009
9087
  }
9010
9088
  return;
@@ -9127,6 +9205,9 @@ var VectorViewport = react.forwardRef(
9127
9205
  }
9128
9206
  const cam = cameraRef.current;
9129
9207
  if (!cam) {
9208
+ if (st.kind === "stroke") {
9209
+ emitRemoteStrokePreviewClear();
9210
+ }
9130
9211
  if (st.kind === "erase") {
9131
9212
  eraserPreviewIdsRef.current.clear();
9132
9213
  setEraserPreviewIds([]);
@@ -9358,6 +9439,8 @@ var VectorViewport = react.forwardRef(
9358
9439
  document.removeEventListener("pointercancel", onUp);
9359
9440
  };
9360
9441
  }, [
9442
+ emitRemoteStrokePreview,
9443
+ emitRemoteStrokePreviewClear,
9361
9444
  interactive,
9362
9445
  pruneEraserTrail,
9363
9446
  pruneLaserTrail,