From b7136bbdaf2c2a2a2248c61c80780cbf883adf81 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 20 Sep 2024 14:28:49 +0530 Subject: [PATCH 1/4] feat: editor image full screen mode --- .../custom-image/components/image-block.tsx | 10 +- .../custom-image/components/index.ts | 1 + .../components/toolbar/full-screen.tsx | 150 ++++++++++++++++++ .../custom-image/components/toolbar/index.ts | 1 + .../custom-image/components/toolbar/root.tsx | 39 +++++ 5 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx create mode 100644 packages/editor/src/core/extensions/custom-image/components/toolbar/index.ts create mode 100644 packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx diff --git a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx index c951c3bf2cb..89ace94a57f 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-block.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-block.tsx @@ -1,7 +1,7 @@ import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from "react"; import { NodeSelection } from "@tiptap/pm/state"; // extensions -import { CustomImageNodeViewProps } from "@/extensions/custom-image"; +import { CustomImageNodeViewProps, ImageToolbarRoot } from "@/extensions/custom-image"; // helpers import { cn } from "@/helpers/common"; @@ -154,6 +154,14 @@ export const CustomImageBlock: React.FC = (props) => { height: size.height, }} /> + {editor.isEditable && selected &&
} {editor.isEditable && ( <> diff --git a/packages/editor/src/core/extensions/custom-image/components/index.ts b/packages/editor/src/core/extensions/custom-image/components/index.ts index d16be13c869..9d12c3ecf19 100644 --- a/packages/editor/src/core/extensions/custom-image/components/index.ts +++ b/packages/editor/src/core/extensions/custom-image/components/index.ts @@ -1,3 +1,4 @@ +export * from "./toolbar"; export * from "./image-block"; export * from "./image-node"; export * from "./image-uploader"; 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 new file mode 100644 index 00000000000..fc99bb867c6 --- /dev/null +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx @@ -0,0 +1,150 @@ +import { useCallback, useEffect, useState } from "react"; +import { ExternalLink, Maximize, Minus, Plus, X } from "lucide-react"; +// helpers +import { cn } from "@/helpers/common"; + +type Props = { + image: { + src: string; + height: string; + width: string; + }; + isOpen: boolean; + toggleFullScreenMode: (val: boolean) => void; +}; + +const MAGNIFICATION_VALUES = [0.5, 0.75, 1, 1.5, 1.75, 2]; + +export const ImageFullScreenAction: React.FC = (props) => { + const { image, isOpen: isFullScreenEnabled, toggleFullScreenMode } = props; + const { height, src, width } = image; + // states + const [magnification, setMagnification] = useState(1); + // derived values + const widthInNumber = Number(width.replace("px", "")); + const heightInNumber = Number(height.replace("px", "")); + const aspectRatio = widthInNumber / heightInNumber; + // close handler + const handleClose = useCallback(() => { + 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 + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.key === "Escape") handleClose(); + if (e.key === "+" || e.key === "=") handleIncreaseMagnification(); + if (e.key === "-") handleDecreaseMagnification(); + }, + [handleClose, handleDecreaseMagnification, handleIncreaseMagnification] + ); + // register keydown listener + useEffect(() => { + document.addEventListener("keydown", handleKeyDown); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, [handleKeyDown]); + // remove keydown listener + useEffect(() => { + if (!isFullScreenEnabled) { + document.removeEventListener("keydown", handleKeyDown); + } + }, [handleKeyDown]); + + return ( + <> +
+
+ + +
+
+
+ + {(100 * magnification).toFixed(0)}% + +
+ +
+
+ + + ); +}; diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/index.ts b/packages/editor/src/core/extensions/custom-image/components/toolbar/index.ts new file mode 100644 index 00000000000..1efe34c51ec --- /dev/null +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx new file mode 100644 index 00000000000..fdab05e7369 --- /dev/null +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx @@ -0,0 +1,39 @@ +import { useState } from "react"; +// helpers +import { cn } from "@/helpers/common"; +// components +import { ImageFullScreenAction } from "./full-screen"; + +type Props = { + containerClassName?: string; + image: { + src: string; + height: string; + width: string; + }; +}; + +export const ImageToolbarRoot: React.FC = (props) => { + const { containerClassName, image } = props; + // state + const [isFullScreenEnabled, setIsFullScreenEnabled] = useState(false); + + return ( + <> +
+ setIsFullScreenEnabled(val)} + /> +
+ + ); +}; From 6a0019297323eb5584cd5db4d5fbc4369d8f82fb Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 20 Sep 2024 14:35:10 +0530 Subject: [PATCH 2/4] fix: full screen modal visibility --- .../extensions/custom-image/components/toolbar/root.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx b/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx index fdab05e7369..3cfc844be59 100644 --- a/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx @@ -21,12 +21,9 @@ export const ImageToolbarRoot: React.FC = (props) => { return ( <>
Date: Fri, 20 Sep 2024 15:22:24 +0530 Subject: [PATCH 3/4] refactor: memoize calculations --- .../custom-image/components/toolbar/full-screen.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 fc99bb867c6..22e681c2c75 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,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { ExternalLink, Maximize, Minus, Plus, X } from "lucide-react"; // helpers import { cn } from "@/helpers/common"; @@ -21,9 +21,9 @@ export const ImageFullScreenAction: React.FC = (props) => { // states const [magnification, setMagnification] = useState(1); // derived values - const widthInNumber = Number(width.replace("px", "")); - const heightInNumber = Number(height.replace("px", "")); - const aspectRatio = widthInNumber / heightInNumber; + const widthInNumber = useMemo(() => Number(width.replace("px", "")), [width]); + const heightInNumber = useMemo(() => Number(height.replace("px", "")), [height]); + const aspectRatio = useMemo(() => widthInNumber / heightInNumber, [heightInNumber, widthInNumber]); // close handler const handleClose = useCallback(() => { toggleFullScreenMode(false); From 35451bd230d7bc3907aace45af8924fdae297e7d Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 20 Sep 2024 15:36:12 +0530 Subject: [PATCH 4/4] chore: update useEffect dependencies --- .../custom-image/components/toolbar/full-screen.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 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 22e681c2c75..9333fb21c58 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 @@ -67,16 +67,14 @@ export const ImageFullScreenAction: React.FC = (props) => { useEffect(() => { document.addEventListener("keydown", handleKeyDown); - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - }, [handleKeyDown]); - // remove keydown listener - useEffect(() => { if (!isFullScreenEnabled) { document.removeEventListener("keydown", handleKeyDown); } - }, [handleKeyDown]); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, [handleKeyDown, isFullScreenEnabled]); return ( <>