@buildcores/render-client 1.2.0 → 1.3.0

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/index.js CHANGED
@@ -179,6 +179,8 @@ const renderBuildExperimental = async (request, config) => {
179
179
  // Include width and height if provided
180
180
  ...(request.width !== undefined ? { width: request.width } : {}),
181
181
  ...(request.height !== undefined ? { height: request.height } : {}),
182
+ // Include profile if provided
183
+ ...(request.profile ? { profile: request.profile } : {}),
182
184
  };
183
185
  const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
184
186
  method: "POST",
@@ -206,6 +208,8 @@ const createRenderBuildJob = async (request, config) => {
206
208
  // Include width and height if provided
207
209
  ...(request.width !== undefined ? { width: request.width } : {}),
208
210
  ...(request.height !== undefined ? { height: request.height } : {}),
211
+ // Include profile if provided
212
+ ...(request.profile ? { profile: request.profile } : {}),
209
213
  };
210
214
  const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD, config), {
211
215
  method: "POST",
@@ -269,6 +273,8 @@ const renderSpriteExperimental = async (request, config) => {
269
273
  // Include width and height if provided
270
274
  ...(request.width !== undefined ? { width: request.width } : {}),
271
275
  ...(request.height !== undefined ? { height: request.height } : {}),
276
+ // Include profile if provided
277
+ ...(request.profile ? { profile: request.profile } : {}),
272
278
  };
273
279
  const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
274
280
  method: "POST",
@@ -570,8 +576,101 @@ const InstructionTooltip = ({ isVisible, progressValue, instructionIcon, }) => {
570
576
  } })) }));
571
577
  };
572
578
 
579
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
580
+ const getTouchDistance = (touches) => {
581
+ const first = touches[0];
582
+ const second = touches[1];
583
+ if (!first || !second)
584
+ return 0;
585
+ return Math.hypot(second.clientX - first.clientX, second.clientY - first.clientY);
586
+ };
587
+ const useZoomPan = ({ displayWidth, displayHeight, minScale = 1, maxScale = 4, } = {}) => {
588
+ const [scale, setScale] = react.useState(1);
589
+ const [isPinching, setIsPinching] = react.useState(false);
590
+ const scaleRef = react.useRef(1);
591
+ const pinchDataRef = react.useRef({
592
+ initialDistance: 0,
593
+ initialScale: 1,
594
+ });
595
+ const setScaleSafe = react.useCallback((next) => {
596
+ const clamped = clamp(next, minScale, maxScale);
597
+ if (clamped === scaleRef.current)
598
+ return;
599
+ scaleRef.current = clamped;
600
+ setScale(clamped);
601
+ }, [minScale, maxScale]);
602
+ const handleWheel = react.useCallback((event) => {
603
+ event.preventDefault();
604
+ event.stopPropagation();
605
+ const deltaY = event.deltaMode === 1
606
+ ? event.deltaY * 16
607
+ : event.deltaMode === 2
608
+ ? event.deltaY * (displayHeight ?? 300)
609
+ : event.deltaY;
610
+ const zoomFactor = Math.exp(-deltaY * 0.0015);
611
+ const nextScale = scaleRef.current * zoomFactor;
612
+ setScaleSafe(nextScale);
613
+ }, [setScaleSafe, displayHeight]);
614
+ const handleTouchStart = react.useCallback((event) => {
615
+ if (event.touches.length < 2) {
616
+ return false;
617
+ }
618
+ const distance = getTouchDistance(event.touches);
619
+ if (!distance) {
620
+ return false;
621
+ }
622
+ pinchDataRef.current = {
623
+ initialDistance: distance,
624
+ initialScale: scaleRef.current,
625
+ };
626
+ setIsPinching(true);
627
+ event.preventDefault();
628
+ return true;
629
+ }, []);
630
+ react.useEffect(() => {
631
+ if (!isPinching)
632
+ return;
633
+ const handleMove = (event) => {
634
+ if (event.touches.length < 2)
635
+ return;
636
+ const distance = getTouchDistance(event.touches);
637
+ if (!distance || pinchDataRef.current.initialDistance === 0)
638
+ return;
639
+ const scaleFactor = distance / pinchDataRef.current.initialDistance;
640
+ const nextScale = pinchDataRef.current.initialScale * scaleFactor;
641
+ setScaleSafe(nextScale);
642
+ event.preventDefault();
643
+ };
644
+ const handleEnd = (event) => {
645
+ if (event.touches.length < 2) {
646
+ setIsPinching(false);
647
+ }
648
+ };
649
+ window.addEventListener("touchmove", handleMove, { passive: false });
650
+ window.addEventListener("touchend", handleEnd);
651
+ window.addEventListener("touchcancel", handleEnd);
652
+ return () => {
653
+ window.removeEventListener("touchmove", handleMove);
654
+ window.removeEventListener("touchend", handleEnd);
655
+ window.removeEventListener("touchcancel", handleEnd);
656
+ };
657
+ }, [isPinching, setScaleSafe]);
658
+ const reset = react.useCallback(() => {
659
+ scaleRef.current = 1;
660
+ setScale(1);
661
+ }, []);
662
+ return {
663
+ scale,
664
+ isPinching,
665
+ handleWheel,
666
+ handleTouchStart,
667
+ reset,
668
+ };
669
+ };
670
+
573
671
  const BuildRender = ({ parts, width, height, size, apiConfig, useSpriteRenderOptions, mouseSensitivity = 0.2, touchSensitivity = 0.2, }) => {
574
672
  const canvasRef = react.useRef(null);
673
+ const containerRef = react.useRef(null);
575
674
  const [img, setImg] = react.useState(null);
576
675
  const [isLoading, setIsLoading] = react.useState(true);
577
676
  const [bouncingAllowed, setBouncingAllowed] = react.useState(false);
@@ -584,6 +683,10 @@ const BuildRender = ({ parts, width, height, size, apiConfig, useSpriteRenderOpt
584
683
  const cols = spriteMetadata ? spriteMetadata.cols : 12;
585
684
  const rows = spriteMetadata ? spriteMetadata.rows : 6;
586
685
  const frameRef = react.useRef(0);
686
+ const { scale, handleWheel: handleZoomWheel, handleTouchStart: handleZoomTouchStart, reset: resetZoom, } = useZoomPan({
687
+ displayWidth: displayW,
688
+ displayHeight: displayH,
689
+ });
587
690
  // Image/frame sizes
588
691
  const frameW = img ? img.width / cols : 0;
589
692
  const frameH = img ? img.height / rows : 0;
@@ -642,8 +745,12 @@ const BuildRender = ({ parts, width, height, size, apiConfig, useSpriteRenderOpt
642
745
  ctx.clearRect(0, 0, targetW, targetH);
643
746
  ctx.imageSmoothingEnabled = true;
644
747
  ctx.imageSmoothingQuality = "high";
645
- ctx.drawImage(img, sx, sy, sw, sh, 0, 0, targetW, targetH);
646
- }, [img, frameW, frameH, displayW, displayH, cols, total]);
748
+ const scaledW = targetW * scale;
749
+ const scaledH = targetH * scale;
750
+ const offsetX = -((scaledW - targetW) / 2);
751
+ const offsetY = -((scaledH - targetH) / 2);
752
+ ctx.drawImage(img, sx, sy, sw, sh, offsetX, offsetY, scaledW, scaledH);
753
+ }, [img, frameW, frameH, displayW, displayH, cols, total, scale]);
647
754
  const { isDragging, handleMouseDown, handleTouchStart, hasDragged } = useSpriteScrubbing(canvasRef, total, {
648
755
  mouseSensitivity,
649
756
  touchSensitivity,
@@ -665,18 +772,66 @@ const BuildRender = ({ parts, width, height, size, apiConfig, useSpriteRenderOpt
665
772
  frameRef.current = frame;
666
773
  draw(frame);
667
774
  }, [progressValue, hasDragged, img, total, draw]);
668
- // Initial draw once image is ready
775
+ // Reset zoom when sprite changes or container size updates
776
+ react.useEffect(() => {
777
+ resetZoom();
778
+ }, [spriteSrc, displayW, displayH, resetZoom]);
779
+ // Add native wheel event listener to prevent scrolling AND handle zoom
780
+ react.useEffect(() => {
781
+ const container = containerRef.current;
782
+ if (!container)
783
+ return;
784
+ const handleNativeWheel = (event) => {
785
+ event.preventDefault();
786
+ event.stopPropagation();
787
+ // Manually trigger zoom since we're preventing the React event
788
+ event.deltaMode === 1
789
+ ? event.deltaY * 16
790
+ : event.deltaMode === 2
791
+ ? event.deltaY * (displayH ?? 300)
792
+ : event.deltaY;
793
+ // We need to call the zoom handler directly
794
+ // Create a synthetic React event-like object
795
+ const syntheticEvent = {
796
+ preventDefault: () => { },
797
+ stopPropagation: () => { },
798
+ deltaY: event.deltaY,
799
+ deltaMode: event.deltaMode,
800
+ currentTarget: container,
801
+ };
802
+ handleZoomWheel(syntheticEvent);
803
+ hasDragged.current = true;
804
+ };
805
+ // Add listener to container to catch all wheel events
806
+ container.addEventListener('wheel', handleNativeWheel, { passive: false });
807
+ return () => {
808
+ container.removeEventListener('wheel', handleNativeWheel);
809
+ };
810
+ }, [handleZoomWheel, scale, displayH]);
811
+ // Initial draw once image is ready or zoom changes
669
812
  react.useEffect(() => {
670
813
  if (img && !isLoading) {
671
814
  draw(frameRef.current);
672
815
  }
673
816
  }, [img, isLoading, draw]);
674
- return (jsxRuntime.jsxs("div", { style: {
817
+ const handleCanvasTouchStart = react.useCallback((event) => {
818
+ if (handleZoomTouchStart(event)) {
819
+ hasDragged.current = true;
820
+ return;
821
+ }
822
+ handleTouchStart(event);
823
+ }, [handleZoomTouchStart, handleTouchStart, hasDragged]);
824
+ react.useCallback((event) => {
825
+ hasDragged.current = true;
826
+ handleZoomWheel(event);
827
+ }, [handleZoomWheel, hasDragged]);
828
+ return (jsxRuntime.jsxs("div", { ref: containerRef, style: {
675
829
  position: "relative",
676
830
  width: displayW,
677
831
  height: displayH,
678
832
  backgroundColor: "black",
679
- }, children: [img && (jsxRuntime.jsx("canvas", { ref: canvasRef, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, style: {
833
+ overflow: "hidden",
834
+ }, children: [img && (jsxRuntime.jsx("canvas", { ref: canvasRef, onMouseDown: handleMouseDown, onTouchStart: handleCanvasTouchStart, style: {
680
835
  width: displayW,
681
836
  height: displayH,
682
837
  cursor: isDragging ? "grabbing" : "grab",