diff --git a/frontend/apps/editor3d/app/src/pages/PageDraw/PageDraw.tsx b/frontend/apps/editor3d/app/src/pages/PageDraw/PageDraw.tsx
index 178d43a882..8c453eb19d 100644
--- a/frontend/apps/editor3d/app/src/pages/PageDraw/PageDraw.tsx
+++ b/frontend/apps/editor3d/app/src/pages/PageDraw/PageDraw.tsx
@@ -233,6 +233,35 @@ const PageDraw = () => {
stage.batchDraw();
};
+ // Auto-fit canvas to screen on initial load
+ useEffect(() => {
+ const autoFitCanvas = async () => {
+ let attempts = 0;
+ const maxAttempts = 20; // Increased attempts
+
+ const tryFit = async () => {
+ const stage = stageRef.current;
+ if (stage && stage.container && stage.container().offsetWidth > 0) {
+ // Wait a bit more to ensure everything is rendered
+ await new Promise(resolve => setTimeout(resolve, 50));
+ onFitPressed();
+ return true;
+ }
+
+ attempts++;
+ if (attempts < maxAttempts) {
+ await new Promise(resolve => setTimeout(resolve, 100));
+ return tryFit();
+ }
+ return false;
+ };
+
+ await tryFit();
+ };
+
+ autoFitCanvas();
+ }, []); // Run once on mount
+
return (
<>
(null);
+ const [isPinching, setIsPinching] = useState(false);
+
+ // Helper function to get distance between two touch points
+ const getTouchDistance = (touches: TouchList) => {
+ if (touches.length < 2) return 0;
+ const touch1 = touches[0];
+ const touch2 = touches[1];
+ return Math.sqrt(
+ Math.pow(touch2.clientX - touch1.clientX, 2) +
+ Math.pow(touch2.clientY - touch1.clientY, 2)
+ );
+ };
+
+ // Helper function to get center point between two touches
+ const getTouchCenter = (touches: TouchList) => {
+ if (touches.length < 2) return null;
+ const touch1 = touches[0];
+ const touch2 = touches[1];
+ return {
+ x: (touch1.clientX + touch2.clientX) / 2,
+ y: (touch1.clientY + touch2.clientY) / 2,
+ };
+ };
+
const handleStageWheel = (e: Konva.KonvaEventObject) => {
e.evt.preventDefault();
const stage = e.target.getStage();
@@ -451,17 +502,103 @@ export const PaintSurface = ({
y: (pointer.y - stage.y()) / oldScale,
};
- // Calculate new scale
- const newScale = e.evt.deltaY < 0 ? oldScale * 1.1 : oldScale / 1.1;
+ // Detect if this is a pinch gesture (ctrlKey is set on Mac trackpad pinch)
+ const isPinchGesture = e.evt.ctrlKey;
+ const deltaY = e.evt.deltaY;
+ const absDelta = Math.abs(deltaY);
+
+ // Detect Mac trackpad vs mouse wheel more precisely
+ const isMac = navigator.userAgent.includes('Mac');
- // Calculate new position
- const newPos = {
- x: pointer.x - mousePointTo.x * newScale,
- y: pointer.y - mousePointTo.y * newScale,
- };
+ // Track-pad scroll on Mac: pixel-based (deltaMode===0) and very small deltas (|delta|≤10)
+ const isMacTrackpadScroll =
+ isMac && !isPinchGesture && e.evt.deltaMode === 0 && absDelta <= 10;
+
+ let zoomFactor;
+
+ if (isPinchGesture) {
+ // Mac trackpad pinch gesture – fast & smooth
+ const pinchSensitivity = 0.2;
+ zoomFactor = 1 + deltaY * pinchSensitivity * -0.01; // invert
+ } else if (isMacTrackpadScroll) {
+ // Block two-finger scroll on Mac track-pads – no zoom
+ return;
+ } else {
+ // Mouse wheel on any platform → smooth exponential scale
+ const mouseSensitivity = 0.0005; // reduced sensitivity for slower zoom
+ zoomFactor = Math.exp(-deltaY * mouseSensitivity);
+ }
- stage.scale({ x: newScale, y: newScale });
- stage.position(newPos);
+ // Apply zoom limits
+ const newScale = Math.max(0.1, Math.min(10, oldScale * zoomFactor));
+
+ // Only apply zoom if within limits
+ if (newScale !== oldScale) {
+ // Calculate new position for zoom-to-cursor
+ const newPos = {
+ x: pointer.x - mousePointTo.x * newScale,
+ y: pointer.y - mousePointTo.y * newScale,
+ };
+
+ stage.scale({ x: newScale, y: newScale });
+ stage.position(newPos);
+ }
+ };
+
+ // Handle touch events for proper pinch-to-zoom on mobile/trackpad
+ const handleTouchStart = (e: Konva.KonvaEventObject) => {
+ const touches = e.evt.touches;
+ if (touches.length === 2) {
+ setIsPinching(true);
+ setLastPinchDistance(getTouchDistance(touches));
+ }
+ };
+
+ const handleTouchMove = (e: Konva.KonvaEventObject) => {
+ const touches = e.evt.touches;
+ if (touches.length === 2 && isPinching && lastPinchDistance) {
+ e.evt.preventDefault();
+
+ const stage = e.target.getStage();
+ if (!stage) return;
+
+ const currentDistance = getTouchDistance(touches);
+ const center = getTouchCenter(touches);
+ if (!center) return;
+
+ const oldScale = stage.scaleX();
+ const scaleChange = currentDistance / lastPinchDistance;
+
+ // Apply smooth scaling with limits
+ const newScale = Math.max(0.1, Math.min(10, oldScale * scaleChange));
+
+ if (newScale !== oldScale) {
+ // Get stage coordinates for the center point
+ const stageCenter = {
+ x: (center.x - stage.x()) / oldScale,
+ y: (center.y - stage.y()) / oldScale,
+ };
+
+ // Calculate new position to zoom toward the center of the pinch
+ const newPos = {
+ x: center.x - stageCenter.x * newScale,
+ y: center.y - stageCenter.y * newScale,
+ };
+
+ stage.scale({ x: newScale, y: newScale });
+ stage.position(newPos);
+ }
+
+ setLastPinchDistance(currentDistance);
+ }
+ };
+
+ const handleTouchEnd = (e: Konva.KonvaEventObject) => {
+ const touches = e.evt.touches;
+ if (touches.length < 2) {
+ setIsPinching(false);
+ setLastPinchDistance(null);
+ }
};
// Add click handler for the stage to clear selection
@@ -1065,6 +1202,9 @@ export const PaintSurface = ({
onClick={handleStageClick}
onMouseEnter={handleStageMouseEnter}
onMouseLeave={handleStageMouseLeave}
+ onTouchStart={handleTouchStart}
+ onTouchMove={handleTouchMove}
+ onTouchEnd={handleTouchEnd}
>
{/* Left Panel */}
-
+
)}