diff --git a/app/gallery/page.tsx b/app/gallery/page.tsx index 7ebe962..0a772c6 100644 --- a/app/gallery/page.tsx +++ b/app/gallery/page.tsx @@ -7,194 +7,23 @@ import GalleryThumbnail from "@/components/ui/gallery_thumbnail"; import React, { useState, useEffect } from 'react'; import { User } from "@supabase/supabase-js"; import Gallery from "@/components/ui/gallery"; +import Search from "@/components/neroshitron/search"; function PageComponent() { const supabase = createClient(); - const [showNSFW, setShowNSFW] = useState(true); - const [randomIds, setRandomIds] = useState([]); // replace any with your gallery type - const [isOpen, setIsOpen] = useState(false); - const [galleries, setGalleries] = useState([]); // replace any with your gallery type - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - const [selectedGallery, setSelectedGallery] = useState(null); - const [tags, setTags] = useState([]); - const [search, setSearch] = useState(''); - const [galleryColumns, setColumns] = useState(0); - const [selectedTags, setSelectedTags] = useState([]); - const generateRandomString = function (length: number) { - let result = ''; - let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; - } - - const selectGallery = (gallery: string, columns: number) => { - setRandomIds([generateRandomString(3), generateRandomString(3), generateRandomString(3), generateRandomString(3)]); - setSelectedGallery(gallery); - setColumns(columns); - setIsOpen(true); - }; - - const closeGallery = () => { - setSelectedGallery(null); - setColumns(0); - setIsOpen(false); - } - const getData = async () => { - let { data: { user } } = await supabase.auth.getUser(); - const galleriesResponse = await fetch(`/api/galleries?search=` + search + '&nsfw=' + showNSFW, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ tags: selectedTags }) - }); - //console.log(galleriesResponse) - const galleriesData = await galleriesResponse.json(); - const tagsResponse = await fetch(`/api/galleries/tags`); - const tagsData = await tagsResponse.json(); - setGalleries(galleriesData); - setTags(tagsData); - setUser(user); - setLoading(false); } + useEffect(() => { getData(); + }, []); - }, [selectedTags, search, showNSFW]); - - const handleTagClick = (tag: number) => { - if (selectedTags.includes(tag)) { - setSelectedTags(selectedTags.filter((selectedTag) => selectedTag !== tag)); - } else { - setSelectedTags([...selectedTags, tag]); - } - //console.log(selectedTags) - }; return ( -
-
- Background -
- - - {/* - THIS IS THE SEARCH BAR AND TAGS SECTION - THIS IS THE SEARCH BAR AND TAGS SECTION - THIS IS THE SEARCH BAR AND TAGS SECTION - THIS IS THE SEARCH BAR AND TAGS SECTION - THIS IS THE SEARCH BAR AND TAGS SECTION - THIS IS THE SEARCH BAR AND TAGS SECTION - */} -
- {(tags.length > 0) ? ( -
-
- setSearch(e.target.value)} - style={{ - animation: 'expandFromLeft 2s ease-out forwards', - paddingRight: '2rem', // make room for the checkbox - }} - /> - -
- {(tags.length > 0) ? ( - - ) : ( -
-

Loading Tags...

-
- )} -
- ) : ( -
- )} -
- - - - - - {/* - These are the thumbnails for the gallery below the search bar - */} -
- {galleries && galleries.map((gallery, index) => ( -
- -
- ))} -
- {isOpen ? ( - <> - {/* - This is the modal for holding the gallery - */} - - - ) : null} +
+
); } diff --git a/app/gallery/page_old.tsx b/app/gallery/page_old.tsx new file mode 100644 index 0000000..510cdcf --- /dev/null +++ b/app/gallery/page_old.tsx @@ -0,0 +1,131 @@ +"use client"; +import { createClient } from "@/utils/supabase/client"; +import { redirect } from "next/navigation"; +import GalleryThumbnail from "@/components/ui/gallery_thumbnail"; + + +import React, { useState, useEffect } from 'react'; +import { User } from "@supabase/supabase-js"; +import Gallery from "@/components/ui/gallery"; +import Search from "@/components/ui/search"; + +function PageComponent() { + + const supabase = createClient(); + + const [showNSFW, setShowNSFW] = useState(true); + const [randomIds, setRandomIds] = useState([]); // replace any with your gallery type + const [isOpen, setIsOpen] = useState(false); + const [galleries, setGalleries] = useState([]); // replace any with your gallery type + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [selectedGallery, setSelectedGallery] = useState(null); + const [search, setSearch] = useState(''); + const [galleryColumns, setColumns] = useState(0); + const [selectedTags, setSelectedTags] = useState([]); + const generateRandomString = function (length: number) { + let result = ''; + let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + + const selectGallery = (gallery: string, columns: number) => { + setRandomIds([generateRandomString(3), generateRandomString(3), generateRandomString(3), generateRandomString(3)]); + setSelectedGallery(gallery); + setColumns(columns); + setIsOpen(true); + }; + + const closeGallery = () => { + setSelectedGallery(null); + setColumns(0); + setIsOpen(false); + } + + const getData = async () => { + let { data: { user } } = await supabase.auth.getUser(); + const galleriesResponse = await fetch(`/api/galleries?search=` + search + '&nsfw=' + showNSFW, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ tags: selectedTags }) + }); + const galleriesData = await galleriesResponse.json(); + setGalleries(galleriesData); + setUser(user); + setLoading(false); + } + + useEffect(() => { + getData(); + }, [selectedTags, search, showNSFW]); + + + return ( +
+
+ Background +
+ + + + + {/* + These are the thumbnails for the gallery below the search bar + */} +
+ {galleries && galleries.map((gallery, index) => ( +
+ +
+ ))} +
+ {isOpen ? ( + <> + {/* + This is the modal for holding the gallery + */} + + + ) : null} +
+ ); +} + +export default PageComponent; \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 999c7e1..0a4c0b6 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,6 @@ import { GeistSans } from "geist/font/sans"; import "./globals.css"; -import NavigationBar from "@/components/NavigationBar"; +import NavigationBar from "@/components/neroshitron/navigation_bar"; import { SpeedInsights } from "@vercel/speed-insights/next" import { Analytics } from "@vercel/analytics/react" const defaultUrl = process.env.VERCEL_URL diff --git a/components/NavigationBar.tsx b/components/NavigationBar.tsx deleted file mode 100644 index 834b9a2..0000000 --- a/components/NavigationBar.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { createClient } from "@/utils/supabase/server"; -import Link from "next/link"; -import { redirect, useRouter } from "next/navigation"; -import crypto from 'crypto'; -import { headers } from "next/headers"; - - -export default async function AuthButton() { - const supabase = createClient(); - - const { - data: { user }, - } = await supabase.auth.getUser(); - - const signOut = async () => { - "use server"; - - const supabase = createClient(); - await supabase.auth.signOut(); - return redirect("/login"); - }; - - - - // ... - - const heads = headers() - const currentPage = heads.get('x-path') - if(user){ - let email = user.email; - if(email != null){ - const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex'); - const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`; - return( -
- -
) - } - } - else{ - return(
- -
) - } -} diff --git a/components/neroshitron/navigation_bar.tsx b/components/neroshitron/navigation_bar.tsx new file mode 100644 index 0000000..3eee64d --- /dev/null +++ b/components/neroshitron/navigation_bar.tsx @@ -0,0 +1,114 @@ +import { createClient } from "@/utils/supabase/server"; +import Link from "next/link"; +import { redirect, useRouter } from "next/navigation"; +import crypto from 'crypto'; +import { headers } from "next/headers"; + + +export default async function AuthButton() { + const supabase = createClient(); + + const { + data: { user }, + } = await supabase.auth.getUser(); + + const signOut = async () => { + "use server"; + + const supabase = createClient(); + await supabase.auth.signOut(); + return redirect("/login"); + }; + + + + // ... + + const heads = headers() + const currentPage = heads.get('x-path') + + const getGravatarUrl = () => { + if(user==null) + { + return; + } + let email = user.email; + if(email != null){ + const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex'); + return `https://www.gravatar.com/avatar/${emailHash}`; + } + return ""; + } + const url = getGravatarUrl(); + return( +
+ +
) +} diff --git a/components/neroshitron/search.tsx b/components/neroshitron/search.tsx new file mode 100644 index 0000000..07971d7 --- /dev/null +++ b/components/neroshitron/search.tsx @@ -0,0 +1,30 @@ +"use client;" +import React, { useState, useEffect } from 'react'; +import Tag from "@/components/neroshitron/tag_pill"; +import SearchInput from '@/components/neroshitron/search_input'; + +interface SearchProps { } + +const Search = ({ }:SearchProps) => { + const [tags, setTags] = useState([]); + const [selectingTags, setSelectingTags] = useState(false); + + const getData = async () => { + } + + useEffect(() => { + getData(); + }, []); + + return ( + <> +
+
+ +
+
+ + ); +}; + +export default Search; \ No newline at end of file diff --git a/components/neroshitron/search_input.tsx b/components/neroshitron/search_input.tsx new file mode 100644 index 0000000..533f484 --- /dev/null +++ b/components/neroshitron/search_input.tsx @@ -0,0 +1,56 @@ +"use client;" +import React, { useState, useEffect, useRef,forwardRef } from 'react'; +import { on } from 'events'; +import TagSelector from '../neroshitron/tag_selector'; + +interface SearchInputProps { + +} + +const SearchInput = ({ }: SearchInputProps) => { + + const [search, setSearch] = useState(''); + const [nsfw, setNsfw] = useState(false); + const [selectedTags, setSelectedTags] = useState([]); + const [selectingTags, setSelectingTags] = useState(false); + const tagSelectorRef = React.useRef(null); + + const onTagsClosed = (tags:string[]) => { + if(tagSelectorRef.current !== null){ + setSelectedTags((tagSelectorRef.current as any).getSelectedTags()); + } + setSelectingTags(false); + setSelectedTags(tags); + } + + const openTags = () => { + setSelectingTags(true); + if(selectingTags){ + onTagsClosed(selectedTags); + } + } + + return ( + <> +
+
+
+ setSearch(e.target.value)} className="rounded-l-md h-16 bg-gray-100 text-grey-darker py-2 font-normal text-grey-darkest border border-gray-100 font-bold w-full py-1 px-2 outline-none text-lg text-gray-600" type="text" placeholder="Looking for a specific collection?" /> + + + + +
+
+
+ {(selectingTags) && } + + ); +}; + +export default SearchInput; \ No newline at end of file diff --git a/components/neroshitron/tag_pill.tsx b/components/neroshitron/tag_pill.tsx new file mode 100644 index 0000000..025f1a0 --- /dev/null +++ b/components/neroshitron/tag_pill.tsx @@ -0,0 +1,20 @@ +"use client;" +import React, { useState, useEffect } from 'react'; + +interface TagProps { onTagClicked: (tag: string ) => void, selected:boolean, tag:string } + +const Tag = ({ onTagClicked, selected, tag, }:TagProps) => { + + return ( + onTagClicked(tag)} + > + {tag} + + ); +}; + +export default Tag; \ No newline at end of file diff --git a/components/neroshitron/tag_selector.tsx b/components/neroshitron/tag_selector.tsx new file mode 100644 index 0000000..10d51e5 --- /dev/null +++ b/components/neroshitron/tag_selector.tsx @@ -0,0 +1,52 @@ +"use client;" +import React, { useImperativeHandle,forwardRef, useState, useEffect } from 'react'; +import { createClient } from "@/utils/supabase/client"; +import Tag from './tag_pill'; +import Masonry from 'react-masonry-css'; + +interface TagSelectorProps { + + } + +const TagSelector = forwardRef((props, ref) => { + + const [tags, setTags] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); + const supabase = createClient(); + + useImperativeHandle(ref, () => ({ + getSelectedTags: () => { + return tags.map(tag => tag.name); + }, + })); + + const getData = async () => { + const tagsResponse = await fetch(`/api/galleries/tags`); + const tagsData = await tagsResponse.json(); + setTags(tagsData); + } + + const handleTag = (tag: string) => { + if (selectedTags.includes(tag)) { + setSelectedTags(selectedTags.filter(t => t !== tag)); + } else { + setSelectedTags([...selectedTags, tag]); + } + }; + + useEffect(() => { + getData(); + }, []); + console.log(selectedTags) + return ( +
+
+ {tags.map((tag: any) => ( + handleTag(tag)} /> + ))} +
+
+ ); +}); + +export default TagSelector; \ No newline at end of file diff --git a/components/ui/gallery.tsx b/components/ui/gallery.tsx index 2e92548..c2d4650 100644 --- a/components/ui/gallery.tsx +++ b/components/ui/gallery.tsx @@ -12,18 +12,11 @@ interface GalleryProps { const Gallery = ({ id, columns, closeMenu }: GalleryProps) => { - const [isSingle, setIsSingle] = useState(false); - const [loaded, setLoaded] = useState({}) const [selectedImage, setSelectedImage] = useState(null); const [images, setImages] = useState([]); const [galleryId, setGalleryId] = useState(id as string); const [currentIndex, setCurrentIndex] = useState(0); - - const getData = async () => { - const thumbnailResponse = await fetch('/api/galleries/' + String(galleryId) + '/images'); - const thumbnailUrl = await thumbnailResponse.json() as string[]; - setImages(thumbnailUrl); - } + const panZoomRef = useRef(null); const next = () => { if (currentIndex < images.length - 1) { @@ -41,6 +34,76 @@ const Gallery = ({ id, columns, closeMenu }: GalleryProps) => { } } + const handleDownload = (image: string) => { + const link = document.createElement('a'); + link.href = image; + link.download = 'image.jpg'; // or any other filename + link.style.display = 'none'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + const getData = async () => { + const thumbnailResponse = await fetch('/api/galleries/' + String(galleryId) + '/images'); + const thumbnailUrl = await thumbnailResponse.json() as string[]; + setImages(thumbnailUrl); + } + + useEffect(() => { + getData(); + const handleKeyDown = (event: KeyboardEvent) => { + switch (event.key) { + case 'ArrowLeft': + case 'a': + case 'A': + previous(); + break; + case 'ArrowRight': + case 'd': + case 'D': + next(); + break; + case 'Escape': + close(); + break; + default: + break; + } + }; + + setSelectedImage(images[currentIndex]); + window.addEventListener('keydown', handleKeyDown); + + // Clean up the event listener when the component is unmounted + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + + }, [selectedImage, currentIndex]); + + const handleClick = (image: string) => { + setSelectedImage(image); + setCurrentIndex(images.indexOf(image)); + }; + + const resetPanZoom = (event: any) => { + if (panZoomRef.current && event.target.id != "image-container") { + panZoomRef.current.autoCenter(); + } + }; + + const close = () => { + if (selectedImage != null) { + setSelectedImage(null); + setImages([]); + } + else { + closeMenu(); + } + }; + const renderButtons = () => { return (
@@ -85,142 +148,59 @@ const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
); }; - const handleDownload = (image: string) => { - const link = document.createElement('a'); - link.href = image; - link.download = 'image.jpg'; // or any other filename - link.style.display = 'none'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - useEffect(() => { - getData(); - const handleKeyDown = (event: KeyboardEvent) => { - switch (event.key) { - case 'ArrowLeft': - case 'a': - case 'A': - previous(); - break; - case 'ArrowRight': - case 'd': - case 'D': - next(); - break; - case 'Escape': - close(); - break; - default: - break; - } - }; - - setSelectedImage(images[currentIndex]); - window.addEventListener('keydown', handleKeyDown); - - // Clean up the event listener when the component is unmounted - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - - }, [selectedImage,currentIndex]); - - const handleClick = (image: string) => { - setSelectedImage(image); - setCurrentIndex(images.indexOf(image)); - }; - - const open = () => { - if (selectedImage === null) return; - //console.log(selectedImage) - let base64Image = selectedImage.split(';base64,').pop(); - if (!base64Image) return; - let blob = new Blob([Uint8Array.from(atob(base64Image), c => c.charCodeAt(0))], { type: 'image/jpeg' }); // adjust the type as needed - let url = URL.createObjectURL(blob); - window.open(url, '_blank'); - } - - const panZoomRef = useRef(null); - - const resetPanZoom = (event: any) => { - if (panZoomRef.current && event.target.id != "image-container") { - panZoomRef.current.autoCenter(); - } - }; - - const close = () => { - if (selectedImage != null) { - setSelectedImage(null); - setImages([]); - } - else { - closeMenu(); - } - } - - const breakpointColumnsObj = { - default: 3 - }; return (
-
-
- {renderButtons()} +
+
+ {renderButtons()}
- {selectedImage ? ( - <> - -{/* -
resetPanZoom()} className='w-full h-full z-10'> -
*/} -
- - -
-
- - ) : ( -
- - - {images - .filter((img) => img !== selectedImage) - .map((image, index) => ( - handleClick(image)} - className={`animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse hover:scale-105 p-2 cursor-pointer my-2 transition-all opacity-100 duration-500 ease-in-out transform`} - /> - ))} - -
+ {selectedImage ? ( <> + +
+ + +
+
-
- )} -
+ ) : ( +
+ + + {images + .filter((img) => img !== selectedImage) + .map((image, index) => ( + handleClick(image)} + className={`animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse hover:scale-105 p-2 cursor-pointer my-2 transition-all opacity-100 duration-500 ease-in-out transform`} + /> + ))} + +
+
+ )} +
); } diff --git a/components/ui/search.tsx b/components/ui/search.tsx new file mode 100644 index 0000000..d645081 --- /dev/null +++ b/components/ui/search.tsx @@ -0,0 +1,96 @@ +"use client;" +import React, { useState, useEffect } from 'react'; +import TagSelector from './tag_selector'; + +interface SearchProps { } + +const Search = ({ }:SearchProps) => { + const [nsfw, setNsfw] = useState(false); + const [tags, setTags] = useState([]); + const [search, setSearch] = useState(''); + const [selectedTags, setSelectedTagsState] = useState([]); + const [selectingTags, setSelectingTags] = useState(false); + + const getNsfw = () => { + return nsfw; + }; + + const getTags = () => { + return tags; + }; + + const getSearch = () => { + return search; + }; + + const getSelectedTags = () => { + return selectedTags; + }; + + const getData = async () => { + const tagsResponse = await fetch(`/api/galleries/tags`); + const tagsData = await tagsResponse.json(); + setTags(tagsData); + } + + useEffect(() => { + getData(); + }, [selectingTags]); + + return ( + <> +
+ {(tags.length > 0) ? ( +
+
+ setSearch(e.target.value)} + /> +
+ +
+
+ +
+ ) : ( +
+ )} + {(selectingTags) ??( + + )} +
+ + ); +}; + +export default Search; \ No newline at end of file diff --git a/components/ui/tag_selector.tsx b/components/ui/tag_selector.tsx new file mode 100644 index 0000000..c65ece3 --- /dev/null +++ b/components/ui/tag_selector.tsx @@ -0,0 +1,27 @@ +"use client;" +import React, { useState, useEffect } from 'react'; + +interface SearchProps { } + +const TagSelector = ({ }:SearchProps) => { + + + const getData = async () => { + } + + useEffect(() => { + getData(); + }, []); + + return ( + + ); +}; + +export default TagSelector; \ No newline at end of file