From 501ba51ee3145fd1f32f4a49911b390b4da85267 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 15 Oct 2024 21:07:08 +0530 Subject: [PATCH] fix: image restoration fixed and aspect ratio added to old images to stop updates on load --- .../custom-image/components/image-block.tsx | 29 +++++++++++++++---- .../custom-image/components/image-node.tsx | 15 +++++----- .../components/image-uploader.tsx | 23 +++++---------- .../extensions/custom-image/custom-image.ts | 5 +--- .../src/core/extensions/image/extension.tsx | 26 ++++------------- .../image/image-extension-without-props.tsx | 3 ++ .../core/extensions/image/read-only-image.tsx | 3 ++ 7 files changed, 51 insertions(+), 53 deletions(-) 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 b812a40dd72..7538a97dfbc 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 @@ -59,7 +59,7 @@ export const CustomImageBlock: React.FC = (props) => { src: remoteImageSrc, setEditorContainer, } = props; - const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio } = node.attrs; + const { width: nodeWidth, height: nodeHeight, aspectRatio: nodeAspectRatio } = node.attrs as ImageAttributes; // states const [size, setSize] = useState({ width: ensurePixelString(nodeWidth, "35%"), @@ -72,6 +72,7 @@ export const CustomImageBlock: React.FC = (props) => { const containerRef = useRef(null); const containerRect = useRef(null); const imageRef = useRef(null); + const [onFirstLoadError, setOnFirstLoadError] = useState(false); const updateAttributesSafely = useCallback( (attributes: Partial, errorMessage: string) => { @@ -137,7 +138,7 @@ export const CustomImageBlock: React.FC = (props) => { } } setInitialResizeComplete(true); - }, [nodeWidth, updateAttributes, editorContainer, nodeAspectRatio]); + }, [nodeWidth, updateAttributes, editorContainer, nodeAspectRatio, size.aspectRatio]); // for real time resizing useLayoutEffect(() => { @@ -145,8 +146,9 @@ export const CustomImageBlock: React.FC = (props) => { ...prevSize, width: ensurePixelString(nodeWidth), height: ensurePixelString(nodeHeight), + aspectRatio: nodeAspectRatio, })); - }, [nodeWidth, nodeHeight]); + }, [nodeWidth, nodeHeight, nodeAspectRatio]); const handleResize = useCallback( (e: MouseEvent | TouchEvent) => { @@ -159,7 +161,7 @@ export const CustomImageBlock: React.FC = (props) => { setSize((prevSize) => ({ ...prevSize, width: `${newWidth}px`, height: `${newHeight}px` })); }, - [size] + [size.aspectRatio] ); const handleResizeEnd = useCallback(() => { @@ -182,11 +184,15 @@ export const CustomImageBlock: React.FC = (props) => { window.addEventListener("mousemove", handleResize); window.addEventListener("mouseup", handleResizeEnd); window.addEventListener("mouseleave", handleResizeEnd); + window.addEventListener("touchmove", handleResize, { passive: false }); + window.addEventListener("touchend", handleResizeEnd); return () => { window.removeEventListener("mousemove", handleResize); window.removeEventListener("mouseup", handleResizeEnd); window.removeEventListener("mouseleave", handleResizeEnd); + window.removeEventListener("touchmove", handleResize); + window.removeEventListener("touchend", handleResizeEnd); }; } }, [isResizing, handleResize, handleResizeEnd]); @@ -203,7 +209,7 @@ export const CustomImageBlock: React.FC = (props) => { // show the image loader if the remote image's src or preview image from filesystem is not set yet (while loading the image post upload) (or) // if the initial resize (from 35% width and "auto" height attrs to the actual size in px) is not complete - const showImageLoader = !(remoteImageSrc || imageFromFileSystem) || !initialResizeComplete; + const showImageLoader = !(remoteImageSrc || imageFromFileSystem) || !initialResizeComplete || onFirstLoadError; // show the image utils only if the remote image's (post upload) src is set and the initial resize is complete (but not while we're showing the preview imageFromFileSystem) const showImageUtils = remoteImageSrc && initialResizeComplete; // show the image resizer only if the editor is editable, the remote image's (post upload) src is set and the initial resize is complete (but not while we're showing the preview imageFromFileSystem) @@ -233,12 +239,22 @@ export const CustomImageBlock: React.FC = (props) => { onLoad={handleImageLoad} onError={async (e) => { try { + setOnFirstLoadError(true); + // this is a type error from tiptap, don't remove await until it's fixed await editor?.commands.restoreImage(remoteImageSrc); + console.log( + "imageRef width", + imageRef.current.naturalWidth, + imageRef.current.naturalHeight, + imageRef.current.src.split("/")[10] + ); imageRef.current.src = remoteImageSrc; } catch { setFailedToLoadImage(true); + console.log("Error while loading image", e); + } finally { + setOnFirstLoadError(false); } - console.log("error while loading image", e); }} width={size.width} className={cn("image-component block rounded-md", { @@ -289,6 +305,7 @@ export const CustomImageBlock: React.FC = (props) => { } )} onMouseDown={handleResizeStart} + onTouchStart={handleResizeStart} /> )} 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 f743b0a3c1d..56e67f7214d 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,21 +1,23 @@ import { useEffect, useRef, useState } from "react"; -import { Node as ProsemirrorNode } from "@tiptap/pm/model"; -import { Editor, NodeViewWrapper } from "@tiptap/react"; +import { Editor, NodeViewProps, NodeViewWrapper } from "@tiptap/react"; // extensions import { CustomImageBlock, CustomImageUploader, ImageAttributes } from "@/extensions/custom-image"; -export type CustomImageNodeViewProps = { +export type CustomImageComponentProps = { getPos: () => number; editor: Editor; - node: ProsemirrorNode & { + node: NodeViewProps["node"] & { attrs: ImageAttributes; }; updateAttributes: (attrs: Record) => void; selected: boolean; }; +export type CustomImageNodeViewProps = NodeViewProps & CustomImageComponentProps; + export const CustomImageNode = (props: CustomImageNodeViewProps) => { const { getPos, editor, node, updateAttributes, selected } = props; + const { src: remoteImageSrc } = node.attrs; const [isUploaded, setIsUploaded] = useState(false); const [imageFromFileSystem, setImageFromFileSystem] = useState(undefined); @@ -37,14 +39,13 @@ export const CustomImageNode = (props: CustomImageNodeViewProps) => { // the image is already uploaded if the image-component node has src attribute // and we need to remove the blob from our file system useEffect(() => { - const remoteImageSrc = node.attrs.src; if (remoteImageSrc) { setIsUploaded(true); setImageFromFileSystem(undefined); } else { setIsUploaded(false); } - }, [node.attrs.src]); + }, [remoteImageSrc]); return ( @@ -55,7 +56,7 @@ export const CustomImageNode = (props: CustomImageNodeViewProps) => { editorContainer={editorContainer} editor={editor} // @ts-expect-error function not expected here, but will still work - src={editor?.commands?.getImageSource?.(node.attrs.src)} + src={editor?.commands?.getImageSource?.(remoteImageSrc)} getPos={getPos} node={node} setEditorContainer={setEditorContainer} diff --git a/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx b/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx index 67cb4e32927..36f1361ee80 100644 --- a/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx @@ -1,27 +1,20 @@ import { ChangeEvent, useCallback, useEffect, useMemo, useRef } from "react"; -import { Node as ProsemirrorNode } from "@tiptap/pm/model"; -import { Editor } from "@tiptap/core"; import { ImageIcon } from "lucide-react"; // helpers import { cn } from "@/helpers/common"; // hooks import { useUploader, useDropZone, uploadFirstImageAndInsertRemaining } from "@/hooks/use-file-upload"; // extensions -import { getImageComponentImageFileMap, ImageAttributes } from "@/extensions/custom-image"; +import { type CustomImageComponentProps, getImageComponentImageFileMap } from "@/extensions/custom-image"; -export const CustomImageUploader = (props: { - editor: Editor; - failedToLoadImage: boolean; - getPos: () => number; - loadImageFromFileSystem: (file: string) => void; +type CustomImageUploaderProps = CustomImageComponentProps & { maxFileSize: number; - node: ProsemirrorNode & { - attrs: ImageAttributes; - }; - selected: boolean; + loadImageFromFileSystem: (file: string) => void; + failedToLoadImage: boolean; setIsUploaded: (isUploaded: boolean) => void; - updateAttributes: (attrs: Record) => void; -}) => { +}; + +export const CustomImageUploader = (props: CustomImageUploaderProps) => { const { editor, failedToLoadImage, @@ -36,8 +29,8 @@ export const CustomImageUploader = (props: { // refs const fileInputRef = useRef(null); const hasTriggeredFilePickerRef = useRef(false); + const { id: imageEntityId } = node.attrs; // derived values - const imageEntityId = node.attrs.id; const imageComponentImageFileMap = useMemo(() => getImageComponentImageFileMap(editor), [editor]); const onUpload = useCallback( diff --git a/packages/editor/src/core/extensions/custom-image/custom-image.ts b/packages/editor/src/core/extensions/custom-image/custom-image.ts index 07e718e87d8..8afc5331d40 100644 --- a/packages/editor/src/core/extensions/custom-image/custom-image.ts +++ b/packages/editor/src/core/extensions/custom-image/custom-image.ts @@ -94,10 +94,7 @@ export const CustomImageExtension = (props: TFileHandler) => { }, addProseMirrorPlugins() { - return [ - TrackImageDeletionPlugin(this.editor, deleteImage, this.name), - // TrackImageRestorationPlugin(this.editor, restore, this.name), - ]; + return [TrackImageDeletionPlugin(this.editor, deleteImage, this.name)]; }, addStorage() { diff --git a/packages/editor/src/core/extensions/image/extension.tsx b/packages/editor/src/core/extensions/image/extension.tsx index 83a0cc5798d..6faf96febe1 100644 --- a/packages/editor/src/core/extensions/image/extension.tsx +++ b/packages/editor/src/core/extensions/image/extension.tsx @@ -3,7 +3,7 @@ import { ReactNodeViewRenderer } from "@tiptap/react"; // helpers import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary"; // plugins -import { ImageExtensionStorage, TrackImageDeletionPlugin, TrackImageRestorationPlugin } from "@/plugins/image"; +import { ImageExtensionStorage, TrackImageDeletionPlugin } from "@/plugins/image"; // types import { TFileHandler } from "@/types"; // extensions @@ -25,28 +25,9 @@ export const ImageExtension = (fileHandler: TFileHandler) => { }; }, addProseMirrorPlugins() { - return [ - TrackImageDeletionPlugin(this.editor, deleteImage, this.name), - // TrackImageRestorationPlugin(this.editor, restoreImage, this.name), - ]; + return [TrackImageDeletionPlugin(this.editor, deleteImage, this.name)]; }, - // onCreate(this) { - // const imageSources = new Set(); - // this.editor.state.doc.descendants((node) => { - // if (node.type.name === this.name) { - // imageSources.add(node.attrs.src); - // } - // }); - // imageSources.forEach(async (src) => { - // try { - // await restoreImage(src); - // } catch (error) { - // console.error("Error restoring image: ", error); - // } - // }); - // }, - // storage to keep track of image states Map addStorage() { return { @@ -65,6 +46,9 @@ export const ImageExtension = (fileHandler: TFileHandler) => { height: { default: null, }, + aspectRatio: { + default: null, + }, }; }, diff --git a/packages/editor/src/core/extensions/image/image-extension-without-props.tsx b/packages/editor/src/core/extensions/image/image-extension-without-props.tsx index 0d505000c7e..bb6c5b4ad81 100644 --- a/packages/editor/src/core/extensions/image/image-extension-without-props.tsx +++ b/packages/editor/src/core/extensions/image/image-extension-without-props.tsx @@ -11,6 +11,9 @@ export const ImageExtensionWithoutProps = () => height: { default: null, }, + aspectRatio: { + default: null, + }, }; }, }); diff --git a/packages/editor/src/core/extensions/image/read-only-image.tsx b/packages/editor/src/core/extensions/image/read-only-image.tsx index 7ba961cdb67..c884a43ee72 100644 --- a/packages/editor/src/core/extensions/image/read-only-image.tsx +++ b/packages/editor/src/core/extensions/image/read-only-image.tsx @@ -18,6 +18,9 @@ export const ReadOnlyImageExtension = (props: Pick) height: { default: null, }, + aspectRatio: { + default: null, + }, }; },