feat:gallery

This commit is contained in:
Damien Ostler 2024-05-26 03:46:00 -04:00
parent 1b5e1da64d
commit 30db6f1275
222 changed files with 149 additions and 90 deletions

5
.env
View File

@ -1,2 +1,3 @@
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
NEXT_PUBLIC_

View File

@ -13,14 +13,24 @@ function PageComponent() {
const supabase = createClient();
const [randomIds, setRandomIds] = useState<string[]>([]); // replace any with your gallery type
const [isOpen, setIsOpen] = useState<boolean>(false);
const [galleries, setGalleries] = useState<any[]>([]); // replace any with your gallery type
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [selectedGallery, setSelectedGallery] = useState<number | null>(null);
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:number) => {
setRandomIds([generateRandomString(3), generateRandomString(3), generateRandomString(3), generateRandomString(3)]);
setSelectedGallery(gallery);
setIsOpen(true);
};
@ -35,20 +45,38 @@ function PageComponent() {
}
useEffect(() => {
getData();
}, [selectedGallery,isOpen]);
}, []);
return ( ( user ? (
<div className="w-full h-full flex justify-center">
<div className="flex-1 w-full h-full flex flex-col gap-20">
<div className="absolute w-full h-full overflow-hidden z-2 animate-jump-in animate-ease-out">
{isOpen ? (
<>
<div key={randomIds[0]} className="absolute w-full h-full overflow-hidden z-2 animate-flip-up animate-ease-out animate-reverse">
<img src="gallery_girl.png" className="float-right object-cover h-screen w-3/6" alt="Background" />
</div>
<div key={randomIds[1]} className="absolute items-center w-3/5 h-full ml-10 z-2 overflow-hidden animate-fade animate-ease-out animate-reverse">
<div className="grid grid-cols-3 gap-x-10 h-full overflow-y-auto no-scrollbar pt-36">
{galleries.map((gallery, index) => (
<GalleryThumbnail key={index} id={gallery.id} onSelect={() => selectGallery(gallery.id)}></GalleryThumbnail>
))}
</div>
</div>
</> ) : (
<>
<div key={randomIds[2]} className="absolute w-full h-full overflow-hidden z-2 animate-flip-up animate-ease-out">
<img src="gallery_girl.png" className="float-right object-cover h-screen w-3/6" alt="Background" />
</div>
<div className="absolute items-center w-3/5 h-full ml-10 z-2 overflow-hidden">
<div key={randomIds[3]} className="absolute items-center w-3/5 h-full ml-10 z-2 overflow-hidden nimate-fade animate-ease-out">
<div className="grid grid-cols-3 gap-x-10 h-full overflow-y-auto no-scrollbar pt-36">
{galleries.map((gallery, index) => (
<GalleryThumbnail key={index} id={gallery.id} onSelect={() => selectGallery(gallery.id)}></GalleryThumbnail>
))}
</div>
</div>
</>
)}
</div>
{(isOpen ? (
<>

View File

@ -20,6 +20,15 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
const thumbnailUrl = await thumbnailResponse.json() as string[];
setImages(thumbnailUrl);
}
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 handleDownload = (image: string) => {
const link = document.createElement('a');
link.href = image;
@ -33,7 +42,7 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
useEffect(() => {
getData();
if (images.length === 1) {
setIsSingle(true);
setIsSingle(true);
setSelectedImage(images[0]);
}
}, [selectedImage]);
@ -41,6 +50,16 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
const handleClick = (image: string) => {
setSelectedImage(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 breakpointColumnsObj = {
default: 3
@ -48,7 +67,7 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
return (
<>
<button
className="fixed bg-purple-800 left-10 bottom-5 animate-shake mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
className="fixed bg-purple-800 left-10 bottom-5 animate-ping animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
onClick={()=> closeMenu()}
>
<svg
@ -73,12 +92,27 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
{isSingle ? (
<div className='w-full h-full flex items-center'>
{selectedImage &&
<>
<button
className="fixed bg-neroshi-blue-800 left-40 bottom-5 text-center w-56 animate-ping animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
onClick={() => handleDownload(selectedImage)}
>
Download Current Image
</button><button
className="fixed bg-neroshi-blue-800 left-40 bottom-16 w-56 text-center animate-ping animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
onClick={() => open()}
>
Open Image in New Tab
</button>
<img
src={selectedImage}
style={{ objectFit: 'contain' }}
className="cursor-pointer animate-in w-full h-auto"
onClick={() => setSelectedImage(null)}
/>
</>
}
</div>
) : (
@ -89,20 +123,21 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
<img
src={selectedImage}
style={{ objectFit: 'contain' }}
className="cursor-pointer animate-in w-4/6 pr-20 h-auto"
key={generateRandomString(3)}
className="cursor-pointer animate-in w-4/6 pr-20 h-auto animate-ping animate-once animate-duration-1000 animate-ease-out animate-reverse"
onClick={() => setSelectedImage(null)}
/>
<button
className="fixed bg-neroshi-blue-800 left-40 bottom-5 animate-pulse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
className="fixed bg-neroshi-blue-800 left-40 bottom-5 text-center w-56 animate-ping animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
onClick={() => handleDownload(selectedImage)}
>
<svg className='mr-3' xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download Current Image
</button>
</button><button
className="fixed bg-neroshi-blue-800 left-40 bottom-16 w-56 text-center animate-ping animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
onClick={() => open()}
>
Open Image in New Tab
</button>
</>
}
<Masonry
@ -112,10 +147,10 @@ const Gallery = ({ id, closeMenu }: GalleryProps) => {
>
{images.filter(img => img !== selectedImage).map((image, index) => (
<img
key={index}
key={generateRandomString(3)}
src={image}
onClick={() => handleClick(image)}
className={`animate-in hover:scale-105 p-2 cursor-pointer my-2 transition-all opacity-100 duration-500 ease-in-out transform`}
className={`animate-ping 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`}
/>
))}
</Masonry>

View File

@ -1,5 +1,4 @@
import { use, useState } from 'react';
import { useEffect } from 'react';
import { useState, useEffect } from 'react';
interface GalleryThumbnailProps {
id: number;
@ -9,30 +8,33 @@ interface GalleryThumbnailProps {
const GalleryThumbnail = ({ id, onSelect }: GalleryThumbnailProps) => {
const [galleryId, setGalleryId] = useState(id as number);
const [thumbnailUrl, setThumbnailUrl] = useState('' as string);
const [isLoading, setIsLoading] = useState(true);
const toggleModal = () => {
onSelect(galleryId);
};
const getData = async () => {
setIsLoading(true);
const thumbnailResponse = await fetch('/api/galleries/'+galleryId+'/thumbnail');
const thumbnailUrl = await thumbnailResponse.text();
setThumbnailUrl(thumbnailUrl);
setIsLoading(false);
}
useEffect(() => {
getData();
}, []);
return (
<div className="py-3 sm:max-w-xl sm:mx-auto animate-in flex-3">
<div className="h-48 overflow-visible w-full relative hover:scale-105 shadow-lg">
<img
<div className="py-3 sm:max-w-xl sm:mx-auto flex-3">
<div className="h-48 overflow-visible w-full relative hover:scale-105 shadow-lg bg-gray-400 rounded-3xl">
{!isLoading && <img
className={`aspect-content rounded-3xl`}
src={thumbnailUrl}
alt=""
onClick={toggleModal}
style={{ width: '20rem', height: '20rem', objectFit: 'cover' }}
/>
/>}
</div>
</div>
);

118
package-lock.json generated
View File

@ -25,7 +25,8 @@
"tailwindcss": "3.4.1",
"tailwindcss-animated": "^1.0.1",
"tailwindcss-textshadow": "^2.1.3",
"typescript": "5.3.3"
"typescript": "5.3.3",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/node": "20.11.5",
@ -100,19 +101,6 @@
"node": ">=4"
}
},
"node_modules/@fullhuman/postcss-purgecss/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@fullhuman/postcss-purgecss/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@fullhuman/postcss-purgecss/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -793,6 +781,22 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/chalk/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/chalk/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
@ -858,20 +862,17 @@
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
"color-name": "1.1.3"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/color-string": {
"version": "1.9.1",
@ -882,19 +883,6 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -1782,23 +1770,11 @@
"node": ">=4"
}
},
"node_modules/postcss-functions/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/postcss-functions/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/postcss-functions/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -2052,19 +2028,6 @@
"node": ">=4"
}
},
"node_modules/purgecss/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/purgecss/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/purgecss/node_modules/commander": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
@ -2077,6 +2040,7 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -2799,6 +2763,18 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -2882,6 +2858,22 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/wrap-ansi-cjs/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",

View File

@ -26,7 +26,8 @@
"tailwindcss": "3.4.1",
"tailwindcss-animated": "^1.0.1",
"tailwindcss-textshadow": "^2.1.3",
"typescript": "5.3.3"
"typescript": "5.3.3",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/node": "20.11.5",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More