@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/hooks/useZoomPan.d.ts +16 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.esm.js +160 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +160 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +24 -0
- package/package.json +1 -1
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
|
-
|
|
646
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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",
|