Spaces:
Runtime error
Runtime error
navigate between images + manage my generations
Browse files- app/api/collections/route.ts +1 -1
- components/header.tsx +5 -7
- components/main/collections/collection.tsx +46 -8
- components/main/collections/index.tsx +1 -0
- components/main/hooks/useCollections.ts +3 -3
- components/main/index.tsx +2 -2
- components/modal/modal.tsx +27 -4
- components/modal/useCollection.ts +46 -2
app/api/collections/route.ts
CHANGED
@@ -34,7 +34,7 @@ export async function GET(request: Request) {
|
|
34 |
equals: userId
|
35 |
},
|
36 |
is_visible: {
|
37 |
-
equals:
|
38 |
}
|
39 |
}
|
40 |
} else if (is_admin) {
|
|
|
34 |
equals: userId
|
35 |
},
|
36 |
is_visible: {
|
37 |
+
equals: undefined
|
38 |
}
|
39 |
}
|
40 |
} else if (is_admin) {
|
components/header.tsx
CHANGED
@@ -21,21 +21,19 @@ export const Header = () => {
|
|
21 |
>
|
22 |
<div className="relative bg-cover bg-fixed bg-black z-[1]">
|
23 |
<div className="flex items-start px-6 mx-auto max-w-[1722px] relative pt-24 pb-20">
|
24 |
-
<div className="w-full
|
25 |
<h1 className="font-bold text-5xl lg:text-7xl text-white text-center lg:text-left">
|
26 |
-
Fast Stable Diffusion XL
|
27 |
</h1>
|
|
|
|
|
|
|
28 |
|
29 |
<p className="text-base text-white/70 mt-3 text-center lg:text-left">
|
30 |
Generate your own images - all images generated are community
|
31 |
shared.
|
32 |
</p>
|
33 |
</div>
|
34 |
-
{/* <Image
|
35 |
-
src={HeaderImage}
|
36 |
-
alt="Demo generated images"
|
37 |
-
className="absolute h-full top-0 object-contain hidden lg:block right-0 xl:right-44 object-left"
|
38 |
-
/> */}
|
39 |
<div
|
40 |
className="absolute w-full lg:w-1/3 right-0 xl:right-44 -bottom-32 lg:-bottom-32 bg-gradient-to-br from-blue-500 to-pink-500 blur-xl lg:blur-[130px] h-full z-[-1]"
|
41 |
style={{ willChange: "transform" }}
|
|
|
21 |
>
|
22 |
<div className="relative bg-cover bg-fixed bg-black z-[1]">
|
23 |
<div className="flex items-start px-6 mx-auto max-w-[1722px] relative pt-24 pb-20">
|
24 |
+
<div className="w-full relative z-10">
|
25 |
<h1 className="font-bold text-5xl lg:text-7xl text-white text-center lg:text-left">
|
26 |
+
Fast Stable Diffusion XL ⚡
|
27 |
</h1>
|
28 |
+
<p className="text-3xl lg:text-4xl text-transparent bg-gradient-to-tr from-indigo-300 via-blue-500 to-pink-400 bg-clip-text font-bold text-center lg:text-left mt-2 inline-block">
|
29 |
+
on TPU v5e
|
30 |
+
</p>
|
31 |
|
32 |
<p className="text-base text-white/70 mt-3 text-center lg:text-left">
|
33 |
Generate your own images - all images generated are community
|
34 |
shared.
|
35 |
</p>
|
36 |
</div>
|
|
|
|
|
|
|
|
|
|
|
37 |
<div
|
38 |
className="absolute w-full lg:w-1/3 right-0 xl:right-44 -bottom-32 lg:-bottom-32 bg-gradient-to-br from-blue-500 to-pink-500 blur-xl lg:blur-[130px] h-full z-[-1]"
|
39 |
style={{ willChange: "transform" }}
|
components/main/collections/collection.tsx
CHANGED
@@ -1,14 +1,17 @@
|
|
1 |
import { useMemo } from "react";
|
2 |
import { motion } from "framer-motion";
|
3 |
import classNames from "classnames";
|
4 |
-
import { AiFillEyeInvisible } from "react-icons/ai";
|
|
|
5 |
|
6 |
import { Image } from "@/utils/type";
|
7 |
import { useInputGeneration } from "@/components/main/hooks/useInputGeneration";
|
8 |
-
import {
|
|
|
9 |
|
10 |
interface Props {
|
11 |
index: number;
|
|
|
12 |
collection: Image;
|
13 |
className?: string;
|
14 |
onOpen: (id: string) => void;
|
@@ -17,16 +20,23 @@ interface Props {
|
|
17 |
export const Collection: React.FC<Props> = ({
|
18 |
collection,
|
19 |
index,
|
|
|
20 |
className,
|
21 |
onOpen,
|
22 |
}) => {
|
|
|
23 |
const { setPrompt } = useInputGeneration();
|
|
|
24 |
|
25 |
const formatDate = useMemo(() => {
|
26 |
const date = new Date(collection.createdAt);
|
27 |
return date.toLocaleDateString();
|
28 |
}, [collection.createdAt]);
|
29 |
|
|
|
|
|
|
|
|
|
30 |
return (
|
31 |
<div className={`h-[377px] w-full relative ${className}`}>
|
32 |
<motion.div
|
@@ -68,15 +78,43 @@ export const Collection: React.FC<Props> = ({
|
|
68 |
className={classNames(
|
69 |
"rounded-[33px] bg-gray-950 bg-cover absolute top-0 left-0 w-full h-full z-[-1] transition-all duration-200 group-hover:scale-110 bg-center",
|
70 |
{
|
71 |
-
"opacity-40":
|
72 |
}
|
73 |
)}
|
74 |
/>
|
75 |
-
{
|
76 |
-
<div
|
77 |
-
|
78 |
-
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
<AiFillEyeInvisible className="text-white/70 text-2xl" />
|
81 |
</div>
|
82 |
)}
|
|
|
1 |
import { useMemo } from "react";
|
2 |
import { motion } from "framer-motion";
|
3 |
import classNames from "classnames";
|
4 |
+
import { AiFillEyeInvisible, AiFillCheckCircle } from "react-icons/ai";
|
5 |
+
import { BsFillTrashFill } from "react-icons/bs";
|
6 |
|
7 |
import { Image } from "@/utils/type";
|
8 |
import { useInputGeneration } from "@/components/main/hooks/useInputGeneration";
|
9 |
+
import { useUser } from "@/utils/useUser";
|
10 |
+
import { useCollection } from "@/components/modal/useCollection";
|
11 |
|
12 |
interface Props {
|
13 |
index: number;
|
14 |
+
category: string;
|
15 |
collection: Image;
|
16 |
className?: string;
|
17 |
onOpen: (id: string) => void;
|
|
|
20 |
export const Collection: React.FC<Props> = ({
|
21 |
collection,
|
22 |
index,
|
23 |
+
category,
|
24 |
className,
|
25 |
onOpen,
|
26 |
}) => {
|
27 |
+
const { user } = useUser();
|
28 |
const { setPrompt } = useInputGeneration();
|
29 |
+
const { updateVisibility, remove } = useCollection(collection?.id);
|
30 |
|
31 |
const formatDate = useMemo(() => {
|
32 |
const date = new Date(collection.createdAt);
|
33 |
return date.toLocaleDateString();
|
34 |
}, [collection.createdAt]);
|
35 |
|
36 |
+
const isNotVisible = useMemo(() => {
|
37 |
+
return category !== "my-own" && !collection.is_visible;
|
38 |
+
}, [collection.is_visible, category]);
|
39 |
+
|
40 |
return (
|
41 |
<div className={`h-[377px] w-full relative ${className}`}>
|
42 |
<motion.div
|
|
|
78 |
className={classNames(
|
79 |
"rounded-[33px] bg-gray-950 bg-cover absolute top-0 left-0 w-full h-full z-[-1] transition-all duration-200 group-hover:scale-110 bg-center",
|
80 |
{
|
81 |
+
"opacity-40": isNotVisible,
|
82 |
}
|
83 |
)}
|
84 |
/>
|
85 |
+
{isNotVisible && (
|
86 |
+
<div
|
87 |
+
className={classNames("flex items-center gap-2", {
|
88 |
+
"justify-between py-3 pr-5 pl-3": user?.is_admin,
|
89 |
+
"justify-end py-6 px-5": !user?.is_admin,
|
90 |
+
})}
|
91 |
+
>
|
92 |
+
{user?.is_admin ? (
|
93 |
+
<div className="flex items-center justify-start gap-2 p-2 bg-black/20 backdrop-blur rounded-full hover:bg-black/50">
|
94 |
+
<div
|
95 |
+
className="rounded-full bg-white w-8 h-8 flex items-center justify-center p-1 transition-all duration-200 hover:-translate-y-1"
|
96 |
+
onClick={async (e) => {
|
97 |
+
e.stopPropagation();
|
98 |
+
await updateVisibility();
|
99 |
+
}}
|
100 |
+
>
|
101 |
+
<AiFillCheckCircle className="w-full h-full text-gray-800" />
|
102 |
+
</div>
|
103 |
+
<div
|
104 |
+
className="rounded-full bg-red-500 w-8 h-8 flex items-center justify-center p-1.5 transition-all duration-200 hover:-translate-y-1.5"
|
105 |
+
onClick={async (e) => {
|
106 |
+
e.stopPropagation();
|
107 |
+
await remove();
|
108 |
+
}}
|
109 |
+
>
|
110 |
+
<BsFillTrashFill className="w-full h-full text-white" />
|
111 |
+
</div>
|
112 |
+
</div>
|
113 |
+
) : (
|
114 |
+
<p className="text-white/70 font-semibold text-lg lg:text-xl">
|
115 |
+
Waiting for validation.
|
116 |
+
</p>
|
117 |
+
)}
|
118 |
<AiFillEyeInvisible className="text-white/70 text-2xl" />
|
119 |
</div>
|
120 |
)}
|
components/main/collections/index.tsx
CHANGED
@@ -54,6 +54,7 @@ export const Collections: React.FC<{ category: string }> = ({ category }) => {
|
|
54 |
<Collection
|
55 |
key={category + collection.id}
|
56 |
index={i}
|
|
|
57 |
collection={collection}
|
58 |
className={classNames("", {
|
59 |
"!translate-y-12":
|
|
|
54 |
<Collection
|
55 |
key={category + collection.id}
|
56 |
index={i}
|
57 |
+
category={category}
|
58 |
collection={collection}
|
59 |
className={classNames("", {
|
60 |
"!translate-y-12":
|
components/main/hooks/useCollections.ts
CHANGED
@@ -7,7 +7,7 @@ import { useUser } from "@/utils/useUser";
|
|
7 |
|
8 |
export const useCollections = (category: string) => {
|
9 |
const [loading, setLoading] = useState(false);
|
10 |
-
const { user,
|
11 |
|
12 |
const client = useQueryClient();
|
13 |
|
@@ -26,7 +26,7 @@ export const useCollections = (category: string) => {
|
|
26 |
|
27 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
28 |
headers: {
|
29 |
-
...(
|
30 |
},
|
31 |
method: "GET",
|
32 |
})
|
@@ -58,7 +58,7 @@ export const useCollections = (category: string) => {
|
|
58 |
|
59 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
60 |
headers: {
|
61 |
-
...(
|
62 |
},
|
63 |
method: "GET",
|
64 |
})
|
|
|
7 |
|
8 |
export const useCollections = (category: string) => {
|
9 |
const [loading, setLoading] = useState(false);
|
10 |
+
const { user, token } = useUser();
|
11 |
|
12 |
const client = useQueryClient();
|
13 |
|
|
|
26 |
|
27 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
28 |
headers: {
|
29 |
+
...(token ? { 'Authorization': token } : {})
|
30 |
},
|
31 |
method: "GET",
|
32 |
})
|
|
|
58 |
|
59 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
60 |
headers: {
|
61 |
+
...(token ? { 'Authorization': token } : {})
|
62 |
},
|
63 |
method: "GET",
|
64 |
})
|
components/main/index.tsx
CHANGED
@@ -76,7 +76,7 @@ export const Main = () => {
|
|
76 |
className={classNames(
|
77 |
"flex items-center justify-center lg:justify-end text-right gap-1 mt-4 lg:mt-0 pr-2 lg:pr-4",
|
78 |
{
|
79 |
-
"text-gray-300 text-
|
80 |
"text-white text-sm": user?.sub,
|
81 |
}
|
82 |
)}
|
@@ -100,7 +100,7 @@ export const Main = () => {
|
|
100 |
</Link>
|
101 |
</>
|
102 |
) : (
|
103 |
-
"to
|
104 |
)}
|
105 |
</div>
|
106 |
<p
|
|
|
76 |
className={classNames(
|
77 |
"flex items-center justify-center lg:justify-end text-right gap-1 mt-4 lg:mt-0 pr-2 lg:pr-4",
|
78 |
{
|
79 |
+
"text-gray-300 text-sm": !user?.sub,
|
80 |
"text-white text-sm": user?.sub,
|
81 |
}
|
82 |
)}
|
|
|
100 |
</Link>
|
101 |
</>
|
102 |
) : (
|
103 |
+
"to persist your images in your own gallery"
|
104 |
)}
|
105 |
</div>
|
106 |
<p
|
components/modal/modal.tsx
CHANGED
@@ -3,10 +3,15 @@ import { motion } from "framer-motion";
|
|
3 |
import Image from "next/image";
|
4 |
import { BsFillTrashFill } from "react-icons/bs";
|
5 |
import { AiFillCheckCircle } from "react-icons/ai";
|
|
|
|
|
|
|
|
|
6 |
|
7 |
import { useCollection } from "./useCollection";
|
8 |
import { Button } from "../button";
|
9 |
import { useUser } from "@/utils/useUser";
|
|
|
10 |
|
11 |
interface Props {
|
12 |
id: string;
|
@@ -32,10 +37,16 @@ const dropIn = {
|
|
32 |
},
|
33 |
};
|
34 |
|
|
|
|
|
35 |
export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
36 |
-
const { collection, updateVisibility, remove } =
|
|
|
37 |
const { user } = useUser();
|
38 |
|
|
|
|
|
|
|
39 |
const formatDate = useMemo(() => {
|
40 |
if (!collection) return;
|
41 |
const date = new Date(collection?.createdAt);
|
@@ -87,9 +98,21 @@ export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
|
87 |
/>
|
88 |
<div className="text-left w-full px-4 pb-3 pt-2">
|
89 |
<p className="text-sm font-medium text-white/60 mb-1">{formatDate}</p>
|
90 |
-
<
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
</div>
|
94 |
</motion.div>
|
95 |
</motion.div>
|
|
|
3 |
import Image from "next/image";
|
4 |
import { BsFillTrashFill } from "react-icons/bs";
|
5 |
import { AiFillCheckCircle } from "react-icons/ai";
|
6 |
+
import {
|
7 |
+
BsFillArrowLeftSquareFill,
|
8 |
+
BsFillArrowRightSquareFill,
|
9 |
+
} from "react-icons/bs";
|
10 |
|
11 |
import { useCollection } from "./useCollection";
|
12 |
import { Button } from "../button";
|
13 |
import { useUser } from "@/utils/useUser";
|
14 |
+
import { useKeyPressEvent } from "react-use";
|
15 |
|
16 |
interface Props {
|
17 |
id: string;
|
|
|
37 |
},
|
38 |
};
|
39 |
|
40 |
+
const keys = ["ArrowLeft", "ArrowRight"];
|
41 |
+
|
42 |
export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
43 |
+
const { collection, updateVisibility, remove, next, previous } =
|
44 |
+
useCollection(id);
|
45 |
const { user } = useUser();
|
46 |
|
47 |
+
useKeyPressEvent("ArrowLeft", previous);
|
48 |
+
useKeyPressEvent("ArrowRight", next);
|
49 |
+
|
50 |
const formatDate = useMemo(() => {
|
51 |
if (!collection) return;
|
52 |
const date = new Date(collection?.createdAt);
|
|
|
98 |
/>
|
99 |
<div className="text-left w-full px-4 pb-3 pt-2">
|
100 |
<p className="text-sm font-medium text-white/60 mb-1">{formatDate}</p>
|
101 |
+
<div className="flex flex-col lg:flex-row items-start lg:items-end lg:justify-between">
|
102 |
+
<p className="text-xl font-semibold text-white lowercase leading-snug">
|
103 |
+
{collection?.prompt}
|
104 |
+
</p>
|
105 |
+
<div className="flex items-center justify-end gap-2">
|
106 |
+
<BsFillArrowLeftSquareFill
|
107 |
+
className="text-white/60 text-2xl inline-block mr-2 hover:text-white cursor-pointer"
|
108 |
+
onClick={previous}
|
109 |
+
/>
|
110 |
+
<BsFillArrowRightSquareFill
|
111 |
+
className="text-white/60 text-2xl inline-block hover:text-white cursor-pointer"
|
112 |
+
onClick={next}
|
113 |
+
/>
|
114 |
+
</div>
|
115 |
+
</div>
|
116 |
</div>
|
117 |
</motion.div>
|
118 |
</motion.div>
|
components/modal/useCollection.ts
CHANGED
@@ -3,9 +3,11 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"
|
|
3 |
|
4 |
import { Collection, Image } from "@/utils/type"
|
5 |
import { useUser } from "@/utils/useUser"
|
|
|
6 |
|
7 |
-
export const useCollection = (
|
8 |
const { user, token } = useUser()
|
|
|
9 |
const [loading, setLoading] = useState(false)
|
10 |
|
11 |
const { data: open } = useQuery(["modal"], () => {
|
@@ -30,6 +32,8 @@ export const useCollection = (id?: string) => {
|
|
30 |
return collections?.images?.find((collection) => collection.id === id)
|
31 |
}, [id, loading])
|
32 |
|
|
|
|
|
33 |
const updateVisibility = async () => {
|
34 |
setLoading(true)
|
35 |
const response = await fetch(`/api/collections/${collection?.id}/visibility`, {
|
@@ -78,11 +82,51 @@ export const useCollection = (id?: string) => {
|
|
78 |
setLoading(false)
|
79 |
}
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
return {
|
82 |
collection,
|
83 |
open,
|
84 |
setOpen,
|
85 |
updateVisibility,
|
86 |
-
remove
|
|
|
|
|
87 |
}
|
88 |
}
|
|
|
3 |
|
4 |
import { Collection, Image } from "@/utils/type"
|
5 |
import { useUser } from "@/utils/useUser"
|
6 |
+
import { useUpdateEffect } from "react-use"
|
7 |
|
8 |
+
export const useCollection = (initialId ?: string) => {
|
9 |
const { user, token } = useUser()
|
10 |
+
const [id, setId] = useState(initialId)
|
11 |
const [loading, setLoading] = useState(false)
|
12 |
|
13 |
const { data: open } = useQuery(["modal"], () => {
|
|
|
32 |
return collections?.images?.find((collection) => collection.id === id)
|
33 |
}, [id, loading])
|
34 |
|
35 |
+
useUpdateEffect(() => setId(initialId), [initialId])
|
36 |
+
|
37 |
const updateVisibility = async () => {
|
38 |
setLoading(true)
|
39 |
const response = await fetch(`/api/collections/${collection?.id}/visibility`, {
|
|
|
82 |
setLoading(false)
|
83 |
}
|
84 |
|
85 |
+
const next = () => {
|
86 |
+
const collections = client.getQueryData<Collection>(["collections"])
|
87 |
+
if (!collections?.images) {
|
88 |
+
return null
|
89 |
+
}
|
90 |
+
|
91 |
+
const index = collections?.images?.findIndex((collection) => collection.id === id)
|
92 |
+
if (index === -1) {
|
93 |
+
return null
|
94 |
+
}
|
95 |
+
|
96 |
+
const next = collections?.images[index + 1]
|
97 |
+
if (!next) {
|
98 |
+
return null
|
99 |
+
}
|
100 |
+
|
101 |
+
setId(next.id)
|
102 |
+
}
|
103 |
+
|
104 |
+
const previous = () => {
|
105 |
+
const collections = client.getQueryData<Collection>(["collections"])
|
106 |
+
if (!collections?.images) {
|
107 |
+
return null
|
108 |
+
}
|
109 |
+
|
110 |
+
const index = collections?.images?.findIndex((collection) => collection.id === id)
|
111 |
+
if (index === -1) {
|
112 |
+
return null
|
113 |
+
}
|
114 |
+
|
115 |
+
const previous = collections?.images[index - 1]
|
116 |
+
if (!previous) {
|
117 |
+
return null
|
118 |
+
}
|
119 |
+
|
120 |
+
setId(previous.id)
|
121 |
+
}
|
122 |
+
|
123 |
return {
|
124 |
collection,
|
125 |
open,
|
126 |
setOpen,
|
127 |
updateVisibility,
|
128 |
+
remove,
|
129 |
+
next,
|
130 |
+
previous
|
131 |
}
|
132 |
}
|