Added second album image

This commit is contained in:
2024-10-14 00:07:31 -03:00
parent 48030a035e
commit 19d8322dfc
7 changed files with 204 additions and 95 deletions

View File

@@ -1,7 +1,11 @@
import React from "react"; import { api } from "@/trpc/react";
import React, { useEffect, useState } from "react";
import Tilt from "react-parallax-tilt"; import Tilt from "react-parallax-tilt";
import SadFaceIcon from "./sad-face-icon";
interface AlbumImageProps { interface AlbumImageProps {
number?: number;
entry: { data: any; isLoading: boolean };
showSpookyImage: boolean; showSpookyImage: boolean;
imageSource: string; imageSource: string;
spookyImageSource: string | null; spookyImageSource: string | null;
@@ -9,13 +13,27 @@ interface AlbumImageProps {
generateSpookyImageData: string | null; generateSpookyImageData: string | null;
lastSpookyImageLoaded: number; lastSpookyImageLoaded: number;
place: number; place: number;
spookyImageLoaded: boolean;
onImageLoad: () => void;
loadGeneratedImage: boolean; loadGeneratedImage: boolean;
onQueue: boolean; onQueue: boolean;
saveImage: {
mutate: (arg0: {
entry: {
type: "album" | "artist";
image: string;
name: string;
number?: number;
};
}) => void;
};
setLastSpookyImageLoaded: (
arg0: number | ((state: number) => number),
) => void;
error: boolean;
} }
export function AlbumImage({ export function AlbumImage({
number,
entry,
showSpookyImage, showSpookyImage,
imageSource, imageSource,
spookyImageSource, spookyImageSource,
@@ -23,11 +41,42 @@ export function AlbumImage({
generateSpookyImageData, generateSpookyImageData,
lastSpookyImageLoaded, lastSpookyImageLoaded,
place, place,
spookyImageLoaded,
onImageLoad,
loadGeneratedImage, loadGeneratedImage,
onQueue, onQueue,
saveImage,
setLastSpookyImageLoaded,
error,
}: AlbumImageProps) { }: AlbumImageProps) {
const [spookyImageLoaded, setSpookyImageLoaded] = useState(false);
const handleSaveImage = async () => {
if (!entry.data && !entry.isLoading) {
saveImage.mutate({
entry: {
type: "album",
image: imageSource,
name: album.name,
number,
},
});
}
};
useEffect(() => {
if (spookyImageLoaded) {
handleSaveImage();
}
}, [spookyImageLoaded]);
const onImageLoad = () => {
if (!spookyImageLoaded) {
setSpookyImageLoaded(true);
setLastSpookyImageLoaded((state: number) =>
state > place ? state : place,
);
}
};
return ( return (
<Tilt tiltMaxAngleX={10} tiltMaxAngleY={10} transitionSpeed={200}> <Tilt tiltMaxAngleX={10} tiltMaxAngleY={10} transitionSpeed={200}>
<img <img
@@ -38,7 +87,7 @@ export function AlbumImage({
src={imageSource} src={imageSource}
alt={album.name} alt={album.name}
/> />
{loadGeneratedImage && spookyImageSource && ( {loadGeneratedImage && spookyImageSource && !error && (
<img <img
className="h-36 w-36 cursor-pointer rounded" className="h-36 w-36 cursor-pointer rounded"
style={{ style={{
@@ -52,7 +101,7 @@ export function AlbumImage({
}} }}
/> />
)} )}
{showSpookyImage && !spookyImageLoaded && ( {showSpookyImage && !spookyImageLoaded && !error && (
<div className="flex h-36 w-36 items-center justify-center rounded bg-slate-300 bg-opacity-10"> <div className="flex h-36 w-36 items-center justify-center rounded bg-slate-300 bg-opacity-10">
<div> <div>
{(() => { {(() => {
@@ -91,6 +140,14 @@ export function AlbumImage({
</div> </div>
</div> </div>
)} )}
{error && (
<div className="flex h-36 w-36 items-center justify-center rounded bg-slate-300 bg-opacity-10">
<div>
<SadFaceIcon className="h-16 w-16" color="white" />
<p className="text-center">Error</p>
</div>
</div>
)}
</Tilt> </Tilt>
); );
} }

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { TrackByAlbum } from "./spotify-data"; import { TrackByAlbum } from "./spotify-data";
import { api } from "@/trpc/react"; import { api } from "@/trpc/react";
import { quantum } from "ldrs"; import { quantum } from "ldrs";
@@ -19,12 +19,12 @@ export default function AlbumShowcase({
album, album,
position, position,
tracks, tracks,
place, places,
lastSpookyImageLoaded, lastSpookyImageLoaded,
setLastSpookyImageLoaded, setLastSpookyImageLoaded,
}: TrackByAlbum & { }: TrackByAlbum & {
spookify: boolean; spookify: boolean;
place: number; places: number[];
lastSpookyImageLoaded: number; lastSpookyImageLoaded: number;
setLastSpookyImageLoaded: any; setLastSpookyImageLoaded: any;
}) { }) {
@@ -33,26 +33,34 @@ export default function AlbumShowcase({
: "https://via.placeholder.com/150"; : "https://via.placeholder.com/150";
const [showSpookyImage, setShowSpookyImage] = useState(spookify); const [showSpookyImage, setShowSpookyImage] = useState(spookify);
const [spookyImageLoaded, setSpookyImageLoaded] = useState(false);
useEffect(() => { useEffect(() => {
setShowSpookyImage(spookify); setShowSpookyImage(spookify);
}, [spookify]); }, [spookify]);
const entry = api.entry.get.useQuery({ const entry = api.entry.get.useQuery(
type: "album", {
name: album.name, type: "album",
image: imageSource, name: album.name,
}); image: imageSource,
},
{
refetchInterval: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
},
);
const generateSpookyImage = api.entry.generate.useMutation(); const generateSpookyImage = api.entry.generate.useMutation();
const generateSecondSpookyImage = api.entry.generate.useMutation(); const generateSecondSpookyImage = api.entry.generate.useMutation();
const spookyImageMatch = ( const spookyImageSource = useMemo(() => {
(entry.data?.value || generateSpookyImage.data) as null | string const spookyImageMatch = (
)?.match(/https:\/\/res.cloudinary.com\/[^"]+/); (entry.data?.value || generateSpookyImage.data) as null | string
)?.match(/<img\s+src='([^']+)'[^>]*>/);
const spookyImageSource = spookyImageMatch ? spookyImageMatch[0] : ""; return spookyImageMatch && spookyImageMatch[1] ? spookyImageMatch[1] : "";
}, [entry.data, generateSpookyImage.data]);
const secondEntry = api.entry.get.useQuery( const secondEntry = api.entry.get.useQuery(
{ {
@@ -62,13 +70,12 @@ export default function AlbumShowcase({
number: 2, number: 2,
}, },
{ {
enabled: !!spookyImageSource, refetchInterval: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
}, },
); );
const saveImage = api.entry.save.useMutation();
const saveSecondImage = api.entry.save.useMutation();
const handleGenerateSpookyImage = async () => { const handleGenerateSpookyImage = async () => {
if (!entry.data && !entry.isLoading) { if (!entry.data && !entry.isLoading) {
generateSpookyImage.mutate({ generateSpookyImage.mutate({
@@ -82,61 +89,44 @@ export default function AlbumShowcase({
}; };
const handleSecondGenerateSpookyImage = async () => { const handleSecondGenerateSpookyImage = async () => {
if (!secondEntry.data && !secondEntry.isLoading) { if (!secondEntry.data && !secondEntry.isLoading && spookyImageSource) {
generateSecondSpookyImage.mutate({ generateSecondSpookyImage.mutate({
entry: { entry: {
type: "album", type: "album",
name: album.name, name: album.name,
image: spookyImageSource as string, image: spookyImageSource,
number: 2,
}, },
}); });
} }
}; };
const handleSaveImage = async () => { const saveImage = api.entry.save.useMutation();
if (!entry.data && !entry.isLoading) { const saveSecondImage = api.entry.save.useMutation();
saveImage.mutate({
entry: {
type: "album",
image: imageSource,
name: album.name,
},
});
}
};
const handleSecondSaveImage = async () => {
if (!secondEntry.data && !secondEntry.isLoading) {
saveSecondImage.mutate({
entry: {
type: "album",
image: spookyImageSource as string,
name: album.name,
},
});
}
};
useEffect(() => { useEffect(() => {
handleGenerateSpookyImage(); handleGenerateSpookyImage();
}, [entry.data]); }, [entry.data]);
useEffect(() => { useEffect(() => {
if (spookyImageLoaded) { handleSecondGenerateSpookyImage();
handleSaveImage(); }, [secondEntry.data, entry.data]);
}
}, [spookyImageLoaded]);
//TODO ERROR HANDLING const secondSpookyImageMatch = (
(secondEntry.data?.value || generateSecondSpookyImage?.data || "") as string
).match(/<img\s+src='([^']+)'[^>]*>/);
const secondSpookyImageSource =
secondSpookyImageMatch && secondSpookyImageMatch[1]
? secondSpookyImageMatch[1]
: "";
const onImageLoad = () => { const firstPlace = places[0] || 0;
if (!spookyImageLoaded) { const secondPlace = places[1] || 0;
setSpookyImageLoaded(true);
setLastSpookyImageLoaded((state: number) => const firstImageError = !!(entry.error || generateSpookyImage.error);
state > place ? state : place, const secondImageError = !!(
); secondEntry.error || generateSecondSpookyImage.error
} );
};
return ( return (
<div <div
@@ -148,49 +138,64 @@ export default function AlbumShowcase({
className="cursor-pointer *:select-none" className="cursor-pointer *:select-none"
> >
<Swiper> <Swiper>
<SwiperSlide> <SwiperSlide className="shadow-lg">
<AlbumImage <AlbumImage
entry={entry}
showSpookyImage={showSpookyImage} showSpookyImage={showSpookyImage}
imageSource={imageSource} imageSource={imageSource}
spookyImageSource={spookyImageSource} spookyImageSource={spookyImageSource}
album={album} album={album}
generateSpookyImageData={generateSpookyImage.data as string} generateSpookyImageData={generateSpookyImage.data as string}
lastSpookyImageLoaded={lastSpookyImageLoaded} lastSpookyImageLoaded={lastSpookyImageLoaded}
place={place} place={firstPlace}
spookyImageLoaded={spookyImageLoaded}
onImageLoad={onImageLoad}
loadGeneratedImage={ loadGeneratedImage={
!!( !!(
spookyImageSource && spookyImageSource &&
(generateSpookyImage.data (generateSpookyImage.data
? lastSpookyImageLoaded >= place || ? lastSpookyImageLoaded >= firstPlace ||
lastSpookyImageLoaded + 1 === place lastSpookyImageLoaded + 1 === firstPlace
: true) : true)
) )
} }
onQueue={lastSpookyImageLoaded < place - 1} onQueue={lastSpookyImageLoaded < firstPlace - 1}
saveImage={saveImage}
setLastSpookyImageLoaded={setLastSpookyImageLoaded}
error={firstImageError}
/> />
</SwiperSlide> </SwiperSlide>
{/* <SwiperSlide> <SwiperSlide>
<AlbumImage <AlbumImage
number={2}
entry={secondEntry}
showSpookyImage={showSpookyImage} showSpookyImage={showSpookyImage}
imageSource={imageSource} imageSource={imageSource}
spookyImageSource={spookyImageSource} spookyImageSource={secondSpookyImageSource}
album={album} album={album}
generateSpookyImageData={generateSpookyImage.data as string} generateSpookyImageData={generateSecondSpookyImage.data as string}
lastSpookyImageLoaded={lastSpookyImageLoaded} lastSpookyImageLoaded={lastSpookyImageLoaded}
place={place} place={secondPlace}
spookyImageLoaded={spookyImageLoaded} loadGeneratedImage={
onImageLoad={onImageLoad} !!(
secondSpookyImageSource &&
(generateSecondSpookyImage.data
? lastSpookyImageLoaded >= secondPlace ||
lastSpookyImageLoaded + 1 === secondPlace
: true)
)
}
onQueue={lastSpookyImageLoaded < secondPlace - 1}
saveImage={saveSecondImage}
setLastSpookyImageLoaded={setLastSpookyImageLoaded}
error={secondImageError}
/> />
</SwiperSlide> */} </SwiperSlide>
</Swiper> </Swiper>
</div> </div>
<section className="flex h-36 flex-grow flex-col"> <section className="flex h-36 flex-grow flex-col">
<h4 className="mb-2 text-lg font-semibold leading-none"> <h4 className="mb-2 text-lg font-semibold leading-none">
{album.name} {album.name}
</h4> </h4>
<ul className="overflow-scroll"> <ul className="max-h-[400px] overflow-auto overflow-y-auto [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 [&::-webkit-scrollbar-track]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 [&::-webkit-scrollbar]:w-2">
{tracks.map((track) => ( {tracks.map((track) => (
<li key={track.id}>{track.name}</li> <li key={track.id}>{track.name}</li>
))} ))}

View File

@@ -0,0 +1,38 @@
interface SadFaceIconProps {
color: string;
className?: string;
}
export function SadFaceIcon({ color, className }: SadFaceIconProps) {
return (
<svg
className={className}
fill={color}
version="1.1"
id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
width="800px"
height="800px"
viewBox="0 0 106.059 106.059"
xmlSpace="preserve"
>
<g>
<path
d="M90.546,15.518C69.858-5.172,36.199-5.172,15.515,15.513C-5.173,36.198-5.171,69.858,15.517,90.547
c20.682,20.684,54.341,20.684,75.027-0.004C111.23,69.858,111.229,36.2,90.546,15.518z M84.757,84.758
c-17.494,17.494-45.96,17.496-63.455,0.002c-17.498-17.497-17.496-45.966,0-63.46C38.796,3.807,67.261,3.805,84.759,21.302
C102.253,38.796,102.251,67.265,84.757,84.758z M77.017,74.001c0.658,1.521-0.042,3.286-1.562,3.943
c-1.521,0.66-3.286-0.042-3.944-1.562c-2.893-6.689-9.73-11.012-17.421-11.012c-7.868,0-14.747,4.319-17.522,11.004
c-0.479,1.154-1.596,1.851-2.771,1.851c-0.384,0-0.773-0.074-1.15-0.23c-1.53-0.636-2.255-2.392-1.62-3.921
c3.71-8.932,12.764-14.703,23.063-14.703C64.174,59.371,73.174,65.113,77.017,74.001z M33.24,38.671
c0-3.424,2.777-6.201,6.201-6.201c3.423,0,6.2,2.776,6.2,6.201c0,3.426-2.777,6.202-6.2,6.202
C36.017,44.873,33.24,42.097,33.24,38.671z M61.357,38.671c0-3.424,2.779-6.201,6.203-6.201c3.423,0,6.2,2.776,6.2,6.201
c0,3.426-2.776,6.202-6.2,6.202S61.357,42.097,61.357,38.671z"
/>
</g>
</svg>
);
}
export default SadFaceIcon;

View File

@@ -27,16 +27,18 @@ export function Showcase({
<h3>Tracks by album</h3> <h3>Tracks by album</h3>
{Object.values(tracksByAlbum) {Object.values(tracksByAlbum)
.sort((a, b) => b.position - a.position) .sort((a, b) => b.position - a.position)
.map((album, index) => ( .map((album, index) => {
<AlbumShowcase return (
key={album.id} <AlbumShowcase
place={index} key={album.id}
lastSpookyImageLoaded={lastSpookyImageLoaded} {...album}
setLastSpookyImageLoaded={setLastSpookyImageLoaded} places={[index, albumsQuantity + index]}
spookify={spookify} lastSpookyImageLoaded={lastSpookyImageLoaded}
{...album} setLastSpookyImageLoaded={setLastSpookyImageLoaded}
/> spookify={spookify}
))} />
);
})}
<h3>Artists images</h3> <h3>Artists images</h3>
<div <div
style={{ style={{

View File

@@ -1,2 +1,2 @@
export const FETCH_TRACKS_LIMIT = 20; export const FETCH_TRACKS_LIMIT = 1;
export const FETCH_ARTISTS_LIMIT = 21; export const FETCH_ARTISTS_LIMIT = 1;

View File

@@ -41,6 +41,7 @@ export const entryRouter = createTRPCRouter({
type: z.enum(["artist", "album"]), type: z.enum(["artist", "album"]),
name: z.string().min(1), name: z.string().min(1),
image: z.string().min(1), image: z.string().min(1),
number: z.number().optional(),
}), }),
}), }),
) )
@@ -51,7 +52,7 @@ export const entryRouter = createTRPCRouter({
if (!uploadedImage || typeof uploadedImage !== "string") { if (!uploadedImage || typeof uploadedImage !== "string") {
throw new Error("Failed to upload image"); throw new Error("Failed to upload image");
} }
const spookyImage = makeImageSpooky(uploadedImage); const spookyImage = makeImageSpooky(uploadedImage, input.entry.number);
if (!spookyImage || typeof spookyImage !== "string") { if (!spookyImage || typeof spookyImage !== "string") {
throw new Error("Failed to make image spooky"); throw new Error("Failed to make image spooky");
} }
@@ -79,6 +80,7 @@ export const entryRouter = createTRPCRouter({
type: z.enum(["artist", "album"]), type: z.enum(["artist", "album"]),
name: z.string().min(1), name: z.string().min(1),
image: z.string().min(1), image: z.string().min(1),
number: z.number().optional(),
}), }),
}), }),
) )

View File

@@ -24,11 +24,16 @@ export const uploadImage = async (
} }
}; };
export const makeImageSpooky = (publicId: string) => { export const makeImageSpooky = (publicId: string, number?: number) => {
const options = { const options: Record<string, string> = {};
effect:
"gen_background_replace:prompt_a bizarre and super creepy background acording with main object theme-max creativity", if (number === 2) {
}; options.effect =
"gen_recolor:prompt_main_object_or_motive_on_image;to-color_green";
} else {
options.effect =
"gen_background_replace:prompt_a bizarre and super creepy background acording with main object theme-max creativity";
}
try { try {
const result = cloudinary.v2.image(publicId, { ...options }); const result = cloudinary.v2.image(publicId, { ...options });