Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 73 additions & 51 deletions app/image-gen/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<UserImage[]>([])
const [loadedImage, setLoadedImage] = useState<UserImage>()
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) => {
Expand All @@ -42,57 +50,67 @@ export default function Home() {
}
});

React.useEffect(()=>{
React.useEffect(() => {
const getUserImages = async (folderName: string | undefined): Promise<void> => {
const folderRef: StorageReference = ref(storage, folderName);
const savedUserImages: UserImage[] = [];

try {
const result: ListResult = await listAll(folderRef);

const urlPromises: Promise<UserImage>[] = 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) {
Expand All @@ -103,49 +121,53 @@ export default function Home() {
}
}

const loadImage = useCallback((image: UserImage) => {
setLoadedImage({ ...image });
setBuildables(() => image.buildables);
}, [setBuildables]);

if (!logIn) {
return (
<p>Access Denied</p>
);
} else {
return (
<div className="flex flex-col h-screen">
<NavBar navLocation="images"/>
<NavBar navLocation="images" />
<div className="flex flex-row h-[calc(100vh-90px)]">
<TabbedSidebar noCard={true} tabs={[
{name: "Image History", content:
<>
<div className="w-full px-4 pt-[30px]">
<p className="text-title-xl font-bold text-white">
{
name: "Image History", content:
<>
<div className="w-full px-4 pt-[30px]">
<p className="text-title-xl font-bold text-white">
Image History
</p>
</div>
<ScrollArea type="auto" className="max-h-[calc(100vh-348px)] w-full px-4">
{userGeneratedImages.map((image,key)=>(
<div className="mt-[30px] h-[92px] w-full border-b border-primary" key={key}>
<p className="text-[15px] text-primary">
{formatISODate(image.date)}
</p>
<div className="flex flex-row py-[18px]">
<p className="flex-1 text-lg text-white">
{image.prompt}
</p>
<Button
onClick={()=>{
setLoadedImage({url:image.url,date:image.date,prompt:image.prompt,buildables:image.buildables});
setBuildables((prev) => image.buildables);
}}
variant="accent" iconPosition="full" className="w-[30px] h-[28px]">
<EditIcon className="w-[16px] h-[16px] text-primary-foreground" />
</Button>
</div>
</div>
))}
</ScrollArea>
</>},
]} defaultValue="Image History" />
<ScrollArea type="auto" className="max-h-[calc(100vh-348px)] w-full px-4">
{userGeneratedImages.map((image, key) => (
<div className="mt-[30px] h-[92px] w-full border-b border-primary" key={key}>
<p className="text-[15px] text-primary">
{formatISODate(image.date)}
</p>
<div className="flex flex-row py-[18px]">
<p className="flex-1 text-lg text-white">
{image.prompt}
</p>
<Button
onClick={() => { loadImage(image); }}
variant="accent" iconPosition="full" className="w-[30px] h-[28px]">
<EditIcon className="w-[16px] h-[16px] text-primary-foreground" />
</Button>
</div>
</div>
))}
</ScrollArea>
</>
},
]} defaultValue="Image History" />
<div className="flex flex-col items-center w-full px-12 py-6">
<ImageGeneration updateUserGeneratedImages={updateUserGeneratedImages} loadedImage={loadedImage}/>
<ImageGeneration updateUserGeneratedImages={updateUserGeneratedImages} loadedImage={loadedImage} />
</div>
</div>
</div>
Expand Down
85 changes: 63 additions & 22 deletions app/image-library/page.tsx
Original file line number Diff line number Diff line change
@@ -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<UserImage[]>([])
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<void> => {
const folderRef: StorageReference = ref(storage, folderName);
const savedUserImages: UserImage[] = [];

try {
const result: ListResult = await listAll(folderRef);

const urlPromises: Promise<UserImage>[] = 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) {
Expand All @@ -27,25 +77,16 @@ export default function Home() {
return (
<div className="flex flex-col h-screen">
<NavBar navLocation="image-library"/>
<div className="flex flex-row w-screen p-5 gap-5">
<Card className="w-full h-max">
<CardHeader>
<CardTitle>Chat History</CardTitle>
<CardDescription>Reopen a chat in chat page or manage them here.</CardDescription>
</CardHeader>
<CardContent>
<ChatHistoryBox/>
</CardContent>
</Card>
<Card className="w-full h-max">
<CardHeader>
<CardTitle>Image History</CardTitle>
<CardDescription>Reopen an image in the image generation page or manage them here.</CardDescription>
</CardHeader>
<CardContent>
<ImageHistoryBox/>
</CardContent>
</Card>
<div className="flex flex-row h-[calc(100vh-90px)]">
<div className="flex flex-col items-center w-full px-12 py-6">
<Card className="h-full w-full xl:w-4/5">
<ScrollArea colorScheme="blue" type="auto" className="h-full w-full px-6 xl:px-[72px]">
<div className="h-12 w-full" />
<ImageHistoryBox />
<div className="h-12 w-full" />
</ScrollArea>
</Card>
</div>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion components/dragable-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ export const DragableInput: FC<DragableInputProps> = ({ onChange, initialValue }

return <div className="flex flex-row items-center bg-secondary text-foreground p-1 rounded-md">
<DragHandleDots2Icon className="cursor-move text-secondary-foreground h-5 w-5" width={size} height={size}/>
<Input onChange={handleChange} type="text" placeholder="Free Input" className="bg-primary enabled:hover:bg-primary" value={initialValue}/>
<Input onChange={handleChange} type="text" placeholder="Free Input" className="bg-primary enabled:hover:bg-primary enabled:hover:border-primary focus-visible:border-border" value={initialValue}/>
</div>
}
13 changes: 12 additions & 1 deletion components/icons/prompt-ed-icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,15 @@ export const NavBarUploadIcon = (props: IconProps) =>
<path d="M7.87305 4.17852L10.8597 1.19186L13.8464 4.17852" stroke="currentColor" stroke-width="1.6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
<path d="M10.8604 11.7619V1.8452" stroke="currentColor" stroke-width="1.6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
<path d="M1.66602 9.42865C1.66602 10.9483 1.66628 13.5839 1.66652 15.6676C1.66672 17.3774 3.05208 18.762 4.76185 18.762C6.93525 18.762 9.68061 18.762 10.9993 18.762C12.3181 18.762 15.0637 18.762 17.2373 18.762C18.9473 18.762 20.3327 17.3772 20.3327 15.6672C20.3327 13.5836 20.3327 10.9482 20.3327 9.42865" stroke="currentColor" stroke-width="1.6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</svg>

export const XIcon = (props: IconProps) =>
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.8214 9.83227C9.57721 10.0766 9.18127 10.0766 8.93699 9.83246C8.93693 9.8324 8.93705 9.83252 8.93699 9.83246L4.9993 5.89477L1.06543 9.82887C0.819867 10.0718 0.423836 10.0697 0.180867 9.82415C-0.0602891 9.58043 -0.0602891 9.18799 0.180867 8.94427L4.11487 5.0103L0.181585 1.07702C-0.0591333 0.829241 -0.0534142 0.433273 0.194367 0.192554C0.437055 -0.0432272 0.823242 -0.0432585 1.06596 0.192491L4.9994 4.12574L8.93612 0.189054C9.17715 -0.0584147 9.57315 -0.0636019 9.82059 0.177429C10.068 0.418461 10.0732 0.814429 9.83221 1.0619C9.82837 1.06584 9.82446 1.06974 9.82049 1.07362L5.88399 5.0103L9.82149 8.9478C10.0657 9.19199 10.0656 9.58805 9.8214 9.83227C9.82146 9.83221 9.82134 9.83234 9.8214 9.83227Z" fill="currentColor" stroke="currentColor" strokeWidth="0.3" strokeLinecap="round" />
</svg>

export const SearchIcon = (props: IconProps) =>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.1767 16.454L22.2482 22.5353M18.7403 10.2579C18.7403 15.1012 14.8141 19.0275 9.97076 19.0275C5.12745 19.0275 1.20117 15.1012 1.20117 10.2579C1.20117 5.41456 5.12745 1.48828 9.97076 1.48828C14.8141 1.48828 18.7403 5.41456 18.7403 10.2579Z" stroke="#00093D" stroke-width="2.33856" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

36 changes: 36 additions & 0 deletions components/image-delete-alert.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<AlertDialog>
<AlertDialogTrigger className="flex flex-none h-[31px] w-[31px] rounded-lg p-[9px] bg-destructive"><XIcon className="w-[13px] h-[13px] text-primary-foreground" /></AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Once you delete this image, it can't be recovered.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={onDeny}>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
Loading