From e8f1f4fb0db32c056851a6b6950ff16d49b3ea2a Mon Sep 17 00:00:00 2001 From: Mateus Abelli Date: Sun, 21 May 2023 12:36:31 -0300 Subject: [PATCH 1/3] Add history panel preliminary feature --- src/app/page.tsx | 60 +++++++++++++++++++------------ src/components/ClipboardImage.tsx | 14 +++++++- src/components/PreviewPanel.tsx | 49 +++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 23 deletions(-) create mode 100644 src/components/PreviewPanel.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index e17ca85..6659b43 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { ClipboardImage } from "@components/ClipboardImage"; import { ActionPanel } from "@components/ActionsPanel"; +import { PreviewPanel } from "@components/PreviewPanel"; import { defaultSettings } from "@config/defaults"; import { cn } from "@utils/cn"; import { useSettings } from "@hooks/useSettings"; @@ -18,6 +19,8 @@ import Link from "next/link"; export default function Home() { const clipboardRef = React.useRef(null); + const [images, setImages] = React.useState([]); + const [selectImage, setSelectImage] = React.useState("") const { settings, setAspectRatio, @@ -32,30 +35,43 @@ export default function Home() {
-
-
-
img]:rounded-none", - settings.aspectRatio, - settings.aspectRatio === "aspect-[3/4]" && "h-fit", - settings.aspectRatio === "aspect-video" && "w-full", - settings.backgroundColor - )}`} - > - + {/* Editing box container */} +
+ {/* Editing panel */} +
+
+
img]:rounded-none", + settings.aspectRatio, + settings.aspectRatio === "aspect-[3/4]" && "h-fit", + settings.aspectRatio === "aspect-video" && "w-full", + settings.backgroundColor + )}`} + > + +
+ {/* Preview panel */} +
diff --git a/src/components/ClipboardImage.tsx b/src/components/ClipboardImage.tsx index 7572850..b3ecad5 100644 --- a/src/components/ClipboardImage.tsx +++ b/src/components/ClipboardImage.tsx @@ -12,11 +12,17 @@ export const ClipboardImage = ({ insetPadding, setInsetColor, setInsetPadding, + setImage, + images, + selectImage }: { insetColor: string; insetPadding: number; setInsetColor: (input: string) => void; setInsetPadding: (input: number) => void; + setImage: React.Dispatch> + images: string[] + selectImage: string }) => { const { toast } = useToast(); const imageRef = React.useRef(null); @@ -38,11 +44,17 @@ export const ClipboardImage = ({ }, [setInsetColor, setInsetPadding]); React.useEffect(() => { + if (imageRef.current && selectImage) { + const currentImage = imageRef.current; + currentImage.src = selectImage; + } if (imageRef.current) { const currentImage = imageRef.current; currentImage.onclick = async () => { const result = await pasteImage(currentImage); if (result === "SUCCESS") { + // Adds the images to the preview panel + images.length < 10 && setImage((prev) => [currentImage.src, ...prev]) toast({ title: ( @@ -64,7 +76,7 @@ export const ClipboardImage = ({ }); }; } - }, [toast]); + }, [toast, setImage, selectImage, images.length]); return ( > + setImages: React.Dispatch> +}) => { + + const handleClick = (src: string) => { + setSelectImage(src); + } + + const handleDelete = (src: string) => { + console.log(src) + setImages((prev) => prev.filter((prevSrc) => prevSrc !== src)) + } + + return ( +
+
+ {images.length > 0 ? ( + images.map((imgSrc, index) => { + return ( +
+ + Image handleClick(imgSrc)} + className="rounded-lg object-cover" + /> +
+ ) + }) + ) : ( +

Clipboard images history (empty)

+ )} +
+
+ ) +} \ No newline at end of file From 1f0d82129b22944213a8b6a25fb196d0719db7cb Mon Sep 17 00:00:00 2001 From: Mateus Abelli Date: Sun, 21 May 2023 18:45:18 -0300 Subject: [PATCH 2/3] Refactor history panel and add comments --- src/app/page.tsx | 38 ++++++++++++------- src/components/ClipboardImage.tsx | 26 +++++++------ src/components/HistoryPanel.tsx | 63 +++++++++++++++++++++++++++++++ src/components/PreviewPanel.tsx | 49 ------------------------ 4 files changed, 103 insertions(+), 73 deletions(-) create mode 100644 src/components/HistoryPanel.tsx delete mode 100644 src/components/PreviewPanel.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index 6659b43..833aff6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { ClipboardImage } from "@components/ClipboardImage"; import { ActionPanel } from "@components/ActionsPanel"; -import { PreviewPanel } from "@components/PreviewPanel"; +import { HistoryPanel } from "@components/HistoryPanel"; import { defaultSettings } from "@config/defaults"; import { cn } from "@utils/cn"; import { useSettings } from "@hooks/useSettings"; @@ -19,8 +19,6 @@ import Link from "next/link"; export default function Home() { const clipboardRef = React.useRef(null); - const [images, setImages] = React.useState([]); - const [selectImage, setSelectImage] = React.useState("") const { settings, setAspectRatio, @@ -30,14 +28,27 @@ export default function Home() { setBackgroundColor, } = useSettings(defaultSettings); + /** History Panel Shared State Setup ** + * + * The "setImagesHistoryUrl" useState hook holds a blob URL array to + * manage the history of clipboard pastes. + * Sample data: ["blob:http://localhost:3000/73c51e5e...", "..."] + * + * The "setSelectedImageUrl" useState hook holds the blob URL of the + * current selected image from the history panel + * Sample data: "blob:http://localhost:3000/73c51e5e..." + */ + const [imagesHistoryUrl, setImagesHistoryUrl] = React.useState([]); + const [selectedImageUrl, setSelectedImageUrl] = React.useState("") + return (
- {/* Editing box container */} + {/* Editing container */}
- {/* Editing panel */} + {/* Clipboard image panel */}
- {/* Preview panel */} -
+ {/* Sidebar container */}
diff --git a/src/components/ClipboardImage.tsx b/src/components/ClipboardImage.tsx index b3ecad5..09fae04 100644 --- a/src/components/ClipboardImage.tsx +++ b/src/components/ClipboardImage.tsx @@ -12,17 +12,17 @@ export const ClipboardImage = ({ insetPadding, setInsetColor, setInsetPadding, - setImage, - images, - selectImage + setImagesHistoryUrl, + imagesHistoryUrl, + selectedImageUrl, }: { insetColor: string; insetPadding: number; setInsetColor: (input: string) => void; setInsetPadding: (input: number) => void; - setImage: React.Dispatch> - images: string[] - selectImage: string + setImagesHistoryUrl: React.Dispatch> + imagesHistoryUrl: string[] + selectedImageUrl: string }) => { const { toast } = useToast(); const imageRef = React.useRef(null); @@ -44,17 +44,21 @@ export const ClipboardImage = ({ }, [setInsetColor, setInsetPadding]); React.useEffect(() => { - if (imageRef.current && selectImage) { + // Checks if there are selected images from the shared state + if (imageRef.current && selectedImageUrl) { + // If there are, set the "src" of the ref to the selected image const currentImage = imageRef.current; - currentImage.src = selectImage; + currentImage.src = selectedImageUrl; } if (imageRef.current) { const currentImage = imageRef.current; currentImage.onclick = async () => { const result = await pasteImage(currentImage); if (result === "SUCCESS") { - // Adds the images to the preview panel - images.length < 10 && setImage((prev) => [currentImage.src, ...prev]) + // Adds the images to the shared history state, only if there are less than 10. + imagesHistoryUrl.length < 10 && ( + setImagesHistoryUrl((prevUrl) => [currentImage.src, ...prevUrl]) + ); toast({ title: ( @@ -76,7 +80,7 @@ export const ClipboardImage = ({ }); }; } - }, [toast, setImage, selectImage, images.length]); + }, [toast, setImagesHistoryUrl, selectedImageUrl, imagesHistoryUrl.length]); return ( > + setImagesHistoryUrl: React.Dispatch> +}) => { + + const handleSelectFromHistory = (url: string) => { + // Updates the state with the current selected image from the panel + setSelectedImageUrl(url); + } + + const handleDeleteFromHistory = (url: string) => { + // Filter out the URL to delete the image selected from the panel + setImagesHistoryUrl((prevUrl) => prevUrl.filter((value) => value !== url)) + } + + return ( + /* History panel container */ +
+
+ {imagesHistoryUrl.length > 0 ? ( + imagesHistoryUrl.map((url, index) => { + return ( + /* History image container */ +
+ {/* History image delete button */} + + {/* History image */} + Image handleSelectFromHistory(url)} + className="rounded-lg object-cover" + /> +
+ ) + }) + ) : ( + /* No history placeholder text */ +

Clipboard images history (empty)

+ )} +
+
+ ) +} \ No newline at end of file diff --git a/src/components/PreviewPanel.tsx b/src/components/PreviewPanel.tsx deleted file mode 100644 index c2603d7..0000000 --- a/src/components/PreviewPanel.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import Image from "next/image"; -import React from "react"; - -export const PreviewPanel = ({ - images, - setSelectImage, - setImages -}: { - images: string[], - setSelectImage: React.Dispatch> - setImages: React.Dispatch> -}) => { - - const handleClick = (src: string) => { - setSelectImage(src); - } - - const handleDelete = (src: string) => { - console.log(src) - setImages((prev) => prev.filter((prevSrc) => prevSrc !== src)) - } - - return ( -
-
- {images.length > 0 ? ( - images.map((imgSrc, index) => { - return ( -
- - Image handleClick(imgSrc)} - className="rounded-lg object-cover" - /> -
- ) - }) - ) : ( -

Clipboard images history (empty)

- )} -
-
- ) -} \ No newline at end of file From 95383b37324df5265f8298a7eb906f97569c2c9c Mon Sep 17 00:00:00 2001 From: Mateus Abelli Date: Wed, 24 May 2023 22:00:20 -0300 Subject: [PATCH 3/3] Fix state when deleting from history --- src/app/page.tsx | 14 ++++---------- src/components/ClipboardImage.tsx | 25 ++++++++++++++++--------- src/components/HistoryPanel.tsx | 31 ++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index b247f13..b811ed2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -31,16 +31,9 @@ export default function Home() { setBackgroundColor, } = useSettings(defaultSettings); - /** History Panel Shared State Setup ** - * - * The "setImagesHistoryUrl" useState hook holds a blob URL array to - * manage the history of clipboard pastes. - * Sample data: ["blob:http://localhost:3000/73c51e5e...", "..."] - * - * The "setSelectedImageUrl" useState hook holds the blob URL of the - * current selected image from the history panel - * Sample data: "blob:http://localhost:3000/73c51e5e..." - */ + // TODO: Move this to a custom hook + // imagesHistoryUrl = ["blob:http://localhost:3000/73c51e5e...", "..."] + // selectedImageUrl = "blob:http://localhost:3000/73c51e5e..." const [imagesHistoryUrl, setImagesHistoryUrl] = React.useState([]); const [selectedImageUrl, setSelectedImageUrl] = React.useState("") @@ -76,6 +69,7 @@ export default function Home() { selectedImageUrl={selectedImageUrl} imagesHistoryUrl={imagesHistoryUrl} setImagesHistoryUrl={setImagesHistoryUrl} + setSelectedImageUrl={setSelectedImageUrl} />
diff --git a/src/components/ClipboardImage.tsx b/src/components/ClipboardImage.tsx index 11b6d3b..5464653 100644 --- a/src/components/ClipboardImage.tsx +++ b/src/components/ClipboardImage.tsx @@ -18,6 +18,7 @@ export const ClipboardImage = ({ setImagesHistoryUrl, imagesHistoryUrl, selectedImageUrl, + setSelectedImageUrl, }: { insetColor: string; scale: number, @@ -29,6 +30,7 @@ export const ClipboardImage = ({ setImagesHistoryUrl: React.Dispatch> imagesHistoryUrl: string[] selectedImageUrl: string + setSelectedImageUrl: React.Dispatch> }) => { const { toast } = useToast(); const imageRef = React.useRef(null); @@ -50,21 +52,20 @@ export const ClipboardImage = ({ }, [setInsetColor, setInsetPadding]); React.useEffect(() => { - // Checks if there are selected images from the shared state - if (imageRef.current && selectedImageUrl) { - // If there are, set the "src" of the ref to the selected image - const currentImage = imageRef.current; - currentImage.src = selectedImageUrl; - } if (imageRef.current) { const currentImage = imageRef.current; currentImage.onclick = async () => { const result = await pasteImage(currentImage); if (result === "SUCCESS") { + // TODO: Extract this to a custom hook // Adds the images to the shared history state, only if there are less than 10. - imagesHistoryUrl.length < 10 && ( + if (imagesHistoryUrl.length < 10) { setImagesHistoryUrl((prevUrl) => [currentImage.src, ...prevUrl]) - ); + // TODO: Fix redundant code, pasteImage and setSelectedImage are + // both setting the current image to be displayed + setSelectedImageUrl(currentImage.src) + } + toast({ title: ( @@ -86,7 +87,13 @@ export const ClipboardImage = ({ }); }; } - }, [toast, setImagesHistoryUrl, selectedImageUrl, imagesHistoryUrl.length]); + // Checks if there are selected images from the shared state + if (imageRef.current && selectedImageUrl) { + // If there are, set the "src" of the ref to the selected image + const currentImage = imageRef.current; + currentImage.src = selectedImageUrl; + } + }, [toast, setImagesHistoryUrl, selectedImageUrl, imagesHistoryUrl.length, setSelectedImageUrl]); return ( { // Filter out the URL to delete the image selected from the panel - setImagesHistoryUrl((prevUrl) => prevUrl.filter((value) => value !== url)) + setImagesHistoryUrl((prevUrl) => { + // Length has to be subtracted by one to stay accurate + // because the last operation is going to remove one item + if ((prevUrl.length - 1) === 0) { + // If the length is zero, then no images are left. Set the default placeholder. + setSelectedImageUrl(placeholder.src); + return [] + } + + return ( + prevUrl.filter((value, index) => { + + // If we match the current image being deleted with the one being displayed + // And there are more than one images remaining in history then... + if (value === url && (prevUrl.length - 1) > 0) { + // Check if we have entries on the positive side + prevUrl[index + 1] ? ( + // If there are, change the current image to the next positive index + setSelectedImageUrl((prevUrl[index + 1])) + ) : ( + // Else, change the current image to the next negative index + setSelectedImageUrl((prevUrl[index - 1])) + ); + } + + return (value !== url) + }) + ); + }); } return (