diff --git a/app/image-gen/page.tsx b/app/image-gen/page.tsx index 21171a5..e1eb88c 100644 --- a/app/image-gen/page.tsx +++ b/app/image-gen/page.tsx @@ -1,8 +1,8 @@ "use client" import { NavBar } from "@/components/navbar" -import { onAuthStateChanged} from "firebase/auth"; -import { useState } from "react"; +import { onAuthStateChanged } from "firebase/auth"; +import { useCallback, useEffect, useState } from "react"; import React from "react"; import { ImageGeneration } from "@/components/image-generation"; import { auth, storage } from "../firebase"; @@ -12,25 +12,33 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { ref, listAll, getDownloadURL, getMetadata, StorageReference, ListResult, FullMetadata } from "firebase/storage"; import { BuildableState, usePromptEditorState } from "@/store/editorStore"; import { EditIcon } from "@/components/icons/prompt-ed-icons"; +import { useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; -interface UserImage{ - url:string - date:string - prompt:string | undefined - buildables:BuildableState[] +interface UserImage { + url: string + date: string + prompt: string | undefined + buildables: BuildableState[] } export default function Home() { + const router = useRouter(); const [logIn, setLogIn] = useState(false); const [userGeneratedImages, setUserGeneratedImages] = useState([]) const [loadedImage, setLoadedImage] = useState() const [userID, setUserID] = useState(auth.currentUser?.uid); + // This is the Next.js hook, NOT the React hook + const searchParams = useSearchParams(); + + const dateParam = searchParams.get("date"); + const setBuildables = usePromptEditorState((state) => state.setBuildables); - const updateUserGeneratedImages = (image:UserImage) => { - setUserGeneratedImages((oldArray)=>([image,...oldArray ])) + const updateUserGeneratedImages = (image: UserImage) => { + setUserGeneratedImages((oldArray) => ([image, ...oldArray])) } onAuthStateChanged(auth, (user) => { @@ -42,57 +50,67 @@ export default function Home() { } }); - React.useEffect(()=>{ + React.useEffect(() => { const getUserImages = async (folderName: string | undefined): Promise => { const folderRef: StorageReference = ref(storage, folderName); const savedUserImages: UserImage[] = []; - + try { const result: ListResult = await listAll(folderRef); - + const urlPromises: Promise[] = result.items.map(async (itemRef: StorageReference) => { const url = await getDownloadURL(itemRef); const metadata: FullMetadata = await getMetadata(itemRef); - const date = metadata.timeCreated; + const date = metadata.timeCreated; const prompt = metadata.customMetadata?.prompt const buildables = JSON.parse(metadata.customMetadata?.buildables || "{}") as BuildableState[] return { url, date, prompt, buildables }; }); - + const userImages: UserImage[] = await Promise.all(urlPromises); userImages.reverse() savedUserImages.push(...userImages); } catch (error) { console.error("Error getting image URLs:", error); } - + setUserGeneratedImages(savedUserImages); + + if (dateParam) { + savedUserImages.forEach(image => { + if (image.date == dateParam) { + loadImage(image); + } + }); + + router.replace('/image-gen', undefined); + } }; getUserImages(auth.currentUser?.uid) - },[userID]) + }, [userID]) function formatISODate(isoDateString: string): string { const date = new Date(isoDateString); - + const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' }; - + const formattedDate = date.toLocaleDateString('en-US', options); - + const day = date.getDate(); const suffix = getDaySuffix(day); - if(formattedDate.replace(day.toString(), `${day}${suffix}`)==="Invalid Date"){ + if (formattedDate.replace(day.toString(), `${day}${suffix}`) === "Invalid Date") { return isoDateString } return formattedDate.replace(day.toString(), `${day}${suffix}`); } - + function getDaySuffix(day: number): string { if (day > 3 && day < 21) return 'th'; switch (day % 10) { @@ -103,6 +121,11 @@ export default function Home() { } } + const loadImage = useCallback((image: UserImage) => { + setLoadedImage({ ...image }); + setBuildables(() => image.buildables); + }, [setBuildables]); + if (!logIn) { return (

Access Denied

@@ -110,42 +133,41 @@ export default function Home() { } else { return (
- +
-
-

+ { + name: "Image History", content: + <> +

+

Image History -

-
- - {userGeneratedImages.map((image,key)=>( -
-

- {formatISODate(image.date)}

-
-

- {image.prompt} -

- -
- ))} -
- }, - ]} defaultValue="Image History" /> + + {userGeneratedImages.map((image, key) => ( +
+

+ {formatISODate(image.date)} +

+
+

+ {image.prompt} +

+ +
+
+ ))} +
+ + }, + ]} defaultValue="Image History" />
- +
diff --git a/app/image-library/page.tsx b/app/image-library/page.tsx index fc730d6..a48710d 100644 --- a/app/image-library/page.tsx +++ b/app/image-library/page.tsx @@ -1,17 +1,67 @@ "use client" +import * as React from "react"; + import { NavBar } from "@/components/navbar" import { onAuthStateChanged } from "firebase/auth" import { useState } from "react" -import { auth } from "../firebase" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { storage, auth } from "../../app/firebase"; +import { Card } from "@/components/ui/card" +import { ScrollArea } from "@/components/ui/scroll-area" +import { FullMetadata, ListResult, StorageReference, getDownloadURL, getMetadata, listAll, ref } from "firebase/storage"; +import { EditIcon } from "@/components/icons/prompt-ed-icons"; +import { Button } from "@/components/ui/button"; + +interface UserImage { + url:string + date:string + prompt:string | undefined +} import { ChatHistoryBox } from "@/components/chat-history-boxes" import { ImageHistoryBox } from "@/components/image-history-boxes" export default function Home() { + const [logIn, setLogIn] = useState(false); + const [userGeneratedImages, setUserGeneratedImages] = useState([]) + const [userID, setUserID] = useState(auth.currentUser?.uid); + onAuthStateChanged(auth, (user) => { + if (user && !logIn) { + setLogIn(true); + } + if (user && typeof userID === 'undefined') { + setUserID(user.uid); + } + }); + + React.useEffect(()=>{ + const getUserImages = async (folderName: string | undefined): Promise => { + const folderRef: StorageReference = ref(storage, folderName); + const savedUserImages: UserImage[] = []; + + try { + const result: ListResult = await listAll(folderRef); + + const urlPromises: Promise[] = result.items.map(async (itemRef: StorageReference) => { + const url = await getDownloadURL(itemRef); + const metadata: FullMetadata = await getMetadata(itemRef); + const date = metadata.timeCreated; + const prompt = metadata.customMetadata?.prompt + return { url, date, prompt}; + }); + + const userImages: UserImage[] = await Promise.all(urlPromises); + userImages.reverse() + savedUserImages.push(...userImages); + } catch (error) { + console.error("Error getting image URLs:", error); + } + + setUserGeneratedImages(savedUserImages); + }; - const [logIn, setLogIn] = useState(true); + getUserImages(auth.currentUser?.uid) + },[userID]) onAuthStateChanged(auth, (user) => { if (user && !logIn) { @@ -27,25 +77,16 @@ export default function Home() { return (
-
- - - Chat History - Reopen a chat in chat page or manage them here. - - - - - - - - Image History - Reopen an image in the image generation page or manage them here. - - - - - +
+
+ + +
+ +
+ + +
); diff --git a/components/dragable-input.tsx b/components/dragable-input.tsx index e75cdf9..7d43ab4 100644 --- a/components/dragable-input.tsx +++ b/components/dragable-input.tsx @@ -17,6 +17,6 @@ export const DragableInput: FC = ({ onChange, initialValue } return
- +
} \ No newline at end of file diff --git a/components/icons/prompt-ed-icons.tsx b/components/icons/prompt-ed-icons.tsx index f9f1431..0fc6d2f 100644 --- a/components/icons/prompt-ed-icons.tsx +++ b/components/icons/prompt-ed-icons.tsx @@ -130,4 +130,15 @@ export const NavBarUploadIcon = (props: IconProps) => - \ No newline at end of file + + +export const XIcon = (props: IconProps) => + + + + +export const SearchIcon = (props: IconProps) => + + + + diff --git a/components/image-delete-alert.tsx b/components/image-delete-alert.tsx new file mode 100644 index 0000000..8183ff7 --- /dev/null +++ b/components/image-delete-alert.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "./ui/alert-dialog"; +import { XIcon } from "./icons/prompt-ed-icons"; + +export const ImageDeleteAlert: React.FC<{ + onConfirm: () => void; + onDeny: () => void; +}> = ({ onConfirm, onDeny }) => { + return ( + + + + + Are you sure? + + Once you delete this image, it can't be recovered. + + + + Cancel + Confirm + + + + ); +}; diff --git a/components/image-generation/index.tsx b/components/image-generation/index.tsx index 7d29616..213c3d6 100644 --- a/components/image-generation/index.tsx +++ b/components/image-generation/index.tsx @@ -216,14 +216,14 @@ export const ImageGeneration = ({updateUserGeneratedImages, loadedImage}:Props)
-
+
{res}
{saveFirebaseBttn} {saveLocalBttn}
-
+
diff --git a/components/image-history-boxes.tsx b/components/image-history-boxes.tsx index d30fb4d..b64bece 100644 --- a/components/image-history-boxes.tsx +++ b/components/image-history-boxes.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { auth, storage, db } from "@/app/firebase"; import { ImageHistory } from "./image-history"; import { onAuthStateChanged } from "firebase/auth"; @@ -13,10 +13,13 @@ import { ListResult, FullMetadata, } from "firebase/storage"; +import { Input } from "./ui/input"; +import { SearchIcon } from "./icons/prompt-ed-icons"; interface UserImage { url: string; date: string; + formattedDate: string; prompt: string | undefined; objectName: string; } @@ -27,6 +30,8 @@ export const ImageHistoryBox = () => { [] ); + const [searchText, setSearchText] = useState(""); + onAuthStateChanged(auth, (user) => { if (user && typeof userID === "undefined") { setUserID(user.uid); @@ -49,10 +54,11 @@ export const ImageHistoryBox = () => { const url = await getDownloadURL(itemRef); const metadata: FullMetadata = await getMetadata(itemRef); const date = metadata.timeCreated; + const formattedDate = formatDate(date); const prompt = metadata.customMetadata?.prompt; const objectName = itemRef.name; - return { objectName, url, date, prompt }; + return { objectName, url, date, formattedDate, prompt }; } ); @@ -69,20 +75,84 @@ export const ImageHistoryBox = () => { getUserImages(auth.currentUser?.uid); }, [userID]); + + function formatDate(dateString: string): string { + console.log("test"); + const date = new Date(dateString); + + const options: Intl.DateTimeFormatOptions = { + month: "long", + day: "numeric", + year: "numeric", + }; + + const formattedDate = date.toLocaleDateString("en-US", options); + const day = date.getDate(); + const suffix = getDaySuffix(day); + + return formattedDate.replace(day.toString(), `${day}${suffix}`); + } + + function getDaySuffix(day: number): string { + if (day > 3 && day < 21) return "th"; + switch (day % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + default: + return "th"; + } + } + + const shouldDisplay = useCallback((prompt_raw: string, date_raw: string) => { + if (searchText == "") { + return true; + } else { + const prompt = prompt_raw.toLowerCase(); + const date = date_raw.toLowerCase(); + + const parts = searchText.toLowerCase().split(" "); + + for (const part of parts) { + if (prompt.includes(part) || date.includes(part)) { + + } else { + return false; + } + }; + + return true; + } + }, [searchText]); + return ( -
- {userGeneratedImages.map((val, key) => ( -
- +
+
+
+
+ +
- ))} + setSearchText(e.target.value)} inputSize="search" className="bg-transparent border-2 border-secondary pl-[49px]" placeholder="Search Library" /> +
+
+ {userGeneratedImages.map((val, key) => ( + + ))} +
); }; diff --git a/components/image-history.tsx b/components/image-history.tsx index 6a5a81d..1777c50 100644 --- a/components/image-history.tsx +++ b/components/image-history.tsx @@ -3,14 +3,16 @@ import * as React from "react"; import { useState } from "react"; -import { Card, CardHeader, CardTitle } from "./ui/card"; import Link from "next/link"; import { Button } from "./ui/button"; import { storage } from "@/app/firebase"; import { deleteObject, ref } from "firebase/storage"; +import { EditIcon, XIcon } from "./icons/prompt-ed-icons"; +import { ImageDeleteAlert } from "./image-delete-alert"; interface ImageHistoryProps { date: string; + formattedDate: string; prompt: string | undefined; url: string; objectName: string; @@ -19,6 +21,7 @@ interface ImageHistoryProps { export function ImageHistory({ date, + formattedDate, prompt, url, objectName, @@ -33,64 +36,25 @@ export function ImageHistory({ setDeleted(true); }; - function formatDate(dateString: string): string { - console.log(objectName); - const date = new Date(dateString); - - const options: Intl.DateTimeFormatOptions = { - month: "long", - day: "numeric", - year: "numeric", - }; - - const formattedDate = date.toLocaleDateString("en-US", options); - const day = date.getDate(); - const suffix = getDaySuffix(day); - - return formattedDate.replace(day.toString(), `${day}${suffix}`); - } - - function getDaySuffix(day: number): string { - if (day > 3 && day < 21) return "th"; - switch (day % 10) { - case 1: - return "st"; - case 2: - return "nd"; - case 3: - return "rd"; - default: - return "th"; - } - } - return ( <> {deleted ? ( <> ) : ( - - -
- - {prompt} - - {formatDate(date)} -
- -
- -
-
-
+ + {}} /> +
+
)} ); -} +} \ No newline at end of file diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx index 5d235e0..8b20ecf 100644 --- a/components/ui/alert-dialog.tsx +++ b/components/ui/alert-dialog.tsx @@ -18,7 +18,7 @@ const AlertDialogOverlay = React.forwardRef< >(({ className, ...props }, ref) => ( (({ className, ...props }, ref) => ( )) diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 9a9089e..cedfbe2 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -9,7 +9,7 @@ const Card = React.forwardRef<
, VariantProps { diff --git a/tailwind.config.ts b/tailwind.config.ts index d9b7a5d..95dca5b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -83,6 +83,8 @@ module.exports = { }, }, borderRadius: { + card: "calc(var(--radius) + 5px)", + 'card-contents': "calc(var(--radius) + 3px)", xl: "calc(var(--radius) + 2px)", lg: "var(--radius)", md: "calc(var(--radius) - 2px)",