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 |
}
|