From f05f037591ded4ebb14fb090ef272f2813c7df17 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Wed, 27 Nov 2024 15:55:59 +0530 Subject: [PATCH 1/4] fix: added magnification properly and also moving around the zoomed image --- .../components/toolbar/full-screen.tsx | 237 +++++++++++------- 1 file changed, 153 insertions(+), 84 deletions(-) diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx index 38ea23c9925..dca1b9bb000 100644 --- a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx @@ -1,6 +1,5 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { ExternalLink, Maximize, Minus, Plus, X } from "lucide-react"; -// helpers import { cn } from "@/helpers/common"; type Props = { @@ -14,46 +13,67 @@ type Props = { toggleFullScreenMode: (val: boolean) => void; }; -const MAGNIFICATION_VALUES = [0.5, 0.75, 1, 1.5, 1.75, 2]; +const MAGNIFICATION_VALUES = [0.5, 0.75, 1, 1.5, 1.75, 2] as const; export const ImageFullScreenAction: React.FC = (props) => { const { image, isOpen: isFullScreenEnabled, toggleFullScreenMode } = props; const { src, width, aspectRatio } = image; - // states - const [magnification, setMagnification] = useState(1); - // refs + + const [magnification, setMagnification] = useState<(typeof MAGNIFICATION_VALUES)[number]>(1); + const [initialMagnification, setInitialMagnification] = useState(1); + const [isDragging, setIsDragging] = useState(false); + const dragStart = useRef({ x: 0, y: 0 }); + const dragOffset = useRef({ x: 0, y: 0 }); const modalRef = useRef(null); - // derived values + const imgRef = useRef(null); + const widthInNumber = useMemo(() => Number(width?.replace("px", "")), [width]); - // close handler + + const setImageRef = useCallback( + (node: HTMLImageElement | null) => { + if (!node || !isFullScreenEnabled) return; + + imgRef.current = node; + + const viewportWidth = window.innerWidth * 0.9; + const viewportHeight = window.innerHeight * 0.75; + const imageWidth = widthInNumber; + const imageHeight = imageWidth / aspectRatio; + + const widthRatio = viewportWidth / imageWidth; + const heightRatio = viewportHeight / imageHeight; + + setInitialMagnification(Math.min(widthRatio, heightRatio)); + setMagnification(1); + + // Reset image position + node.style.left = "0px"; + node.style.top = "0px"; + }, + [isFullScreenEnabled, widthInNumber, aspectRatio] + ); + const handleClose = useCallback(() => { + if (isDragging) return; toggleFullScreenMode(false); - setTimeout(() => { - setMagnification(1); - }, 200); - }, [toggleFullScreenMode]); - // download handler - const handleOpenInNewTab = () => { - const link = document.createElement("a"); - link.href = src; - link.target = "_blank"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - // magnification decrease handler - const handleDecreaseMagnification = useCallback(() => { - const currentIndex = MAGNIFICATION_VALUES.indexOf(magnification); - if (currentIndex === 0) return; - setMagnification(MAGNIFICATION_VALUES[currentIndex - 1]); - }, [magnification]); - // magnification increase handler - const handleIncreaseMagnification = useCallback(() => { - const currentIndex = MAGNIFICATION_VALUES.indexOf(magnification); - if (currentIndex === MAGNIFICATION_VALUES.length - 1) return; - setMagnification(MAGNIFICATION_VALUES[currentIndex + 1]); - }, [magnification]); - // keydown handler + setMagnification(1); + }, [isDragging, toggleFullScreenMode]); + + const handleOpenInNewTab = () => window.open(src, "_blank"); + + const handleMagnification = useCallback((direction: "increase" | "decrease") => { + setMagnification((prev) => { + const currentIndex = MAGNIFICATION_VALUES.indexOf(prev); + if (direction === "increase" && currentIndex < MAGNIFICATION_VALUES.length - 1) { + return MAGNIFICATION_VALUES[currentIndex + 1]; + } + if (direction === "decrease" && currentIndex > 0) { + return MAGNIFICATION_VALUES[currentIndex - 1]; + } + return prev; + }); + }, []); + const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "Escape" || e.key === "+" || e.key === "=" || e.key === "-") { @@ -61,43 +81,84 @@ export const ImageFullScreenAction: React.FC = (props) => { e.stopPropagation(); if (e.key === "Escape") handleClose(); - if (e.key === "+" || e.key === "=") handleIncreaseMagnification(); - if (e.key === "-") handleDecreaseMagnification(); + if (e.key === "+" || e.key === "=") handleMagnification("increase"); + if (e.key === "-") handleMagnification("decrease"); } }, - [handleClose, handleDecreaseMagnification, handleIncreaseMagnification] + [handleClose, handleMagnification] ); - // click outside handler - const handleClickOutside = useCallback( - (e: React.MouseEvent) => { - if (modalRef.current && e.target === modalRef.current) { - handleClose(); - } + + const handleMouseDown = (e: React.MouseEvent) => { + if (!imgRef.current) return; + + const imgWidth = imgRef.current.offsetWidth * magnification; + const imgHeight = imgRef.current.offsetHeight * magnification; + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + if (imgWidth > viewportWidth || imgHeight > viewportHeight) { + e.preventDefault(); + e.stopPropagation(); + setIsDragging(true); + dragStart.current = { x: e.clientX, y: e.clientY }; + dragOffset.current = { + x: parseInt(imgRef.current.style.left || "0"), + y: parseInt(imgRef.current.style.top || "0"), + }; + } + }; + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isDragging || !imgRef.current) return; + + const dx = e.clientX - dragStart.current.x; + const dy = e.clientY - dragStart.current.y; + + // Apply the scale factor to the drag movement + const scaledDx = dx / magnification; + const scaledDy = dy / magnification; + + imgRef.current.style.left = `${dragOffset.current.x + scaledDx}px`; + imgRef.current.style.top = `${dragOffset.current.y + scaledDy}px`; }, - [handleClose] + [isDragging, magnification] ); - // register keydown listener + + const handleMouseUp = useCallback(() => { + if (!isDragging || !imgRef.current) return; + setIsDragging(false); + }, [isDragging]); + + // Event listeners useEffect(() => { - if (isFullScreenEnabled) { - document.addEventListener("keydown", handleKeyDown); + if (!isFullScreenEnabled) return; - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - } - }, [handleKeyDown, isFullScreenEnabled]); + document.addEventListener("keydown", handleKeyDown); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + }, [isFullScreenEnabled, handleKeyDown, handleMouseMove, handleMouseUp]); return ( <>
-
+
e.target === modalRef.current && handleClose()} + className="relative size-full grid place-items-center overflow-hidden" + > -
-
-
+
+
+ + {(100 * magnification).toFixed(0)}% + +
- {(100 * magnification).toFixed(0)}% -
-
- {(100 * magnification).toFixed(0)}% + {Math.round(100 * magnification)}% From af0cf97c46db7430417c05636fca5b0f11a2d8de Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Mon, 23 Dec 2024 18:52:40 +0530 Subject: [PATCH 3/4] fix: update imports --- .../core/extensions/custom-image/components/image-node.tsx | 2 +- .../custom-image/components/toolbar/full-screen.tsx | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/core/extensions/custom-image/components/image-node.tsx b/packages/editor/src/core/extensions/custom-image/components/image-node.tsx index 58b60b306d6..2bd84fcb310 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-node.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-node.tsx @@ -1,5 +1,5 @@ -import { useEffect, useRef, useState } from "react"; import { Editor, NodeViewProps, NodeViewWrapper } from "@tiptap/react"; +import { useEffect, useRef, useState } from "react"; // extensions import { CustomImageBlock, CustomImageUploader, ImageAttributes } from "@/extensions/custom-image"; diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx index 64944ffaec0..0ff3a2d5cd3 100644 --- a/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx @@ -1,5 +1,5 @@ -import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { ExternalLink, Maximize, Minus, Plus, X } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState, useRef } from "react"; // plane utils import { cn } from "@plane/utils"; @@ -63,8 +63,6 @@ export const ImageFullScreenAction: React.FC = (props) => { setMagnification(1); }, [isDragging, toggleFullScreenMode]); - const handleOpenInNewTab = () => window.open(src, "_blank"); - const handleMagnification = useCallback((direction: "increase" | "decrease") => { setMagnification((prev) => { // Find the appropriate target zoom level based on current magnification @@ -245,7 +243,7 @@ export const ImageFullScreenAction: React.FC = (props) => {