enzostvs HF staff commited on
Commit
5033071
1 Parent(s): bf0b625

navigate between images + manage my generations

Browse files
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: true
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 lg:w-1/2 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 on TPU v5e
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 { Button } from "@/components/button";
 
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": !collection.is_visible,
72
  }
73
  )}
74
  />
75
- {!collection.is_visible && (
76
- <div className="flex items-center justify-end px-5 py-6 gap-2">
77
- <p className="text-white/70 font-semibold text-lg lg:text-xl">
78
- Waiting for validation.
79
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, loading: userLoading, token } = useUser();
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
- ...(user?.sub ? { 'Authorization': token } : {})
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
- ...(user?.sub ? { 'Authorization': token } : {})
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-xs": !user?.sub,
80
  "text-white text-sm": user?.sub,
81
  }
82
  )}
@@ -100,7 +100,7 @@ export const Main = () => {
100
  </Link>
101
  </>
102
  ) : (
103
- "to save your generations in your own gallery"
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 } = useCollection(id);
 
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
- <p className="text-xl font-semibold text-white lowercase leading-snug">
91
- {collection?.prompt}
92
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
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 = (id?: string) => {
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
  }