feat: add musicbrainz link where possible (#124)

This commit is contained in:
Gabe Farrell 2026-01-11 01:39:56 -05:00 committed by GitHub
parent d61e814306
commit 7cf7cd3a10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 195 additions and 80 deletions

View file

@ -10,97 +10,189 @@ import DeleteModal from "~/components/modals/DeleteModal";
import RenameModal from "~/components/modals/EditModal/EditModal";
import EditModal from "~/components/modals/EditModal/EditModal";
import AddListenModal from "~/components/modals/AddListenModal";
import MbzIcon from "~/components/icons/MbzIcon";
import { Link } from "react-router";
export type MergeFunc = (from: number, to: number, replaceImage: boolean) => Promise<Response>
export type MergeSearchCleanerFunc = (r: SearchResponse, id: number) => SearchResponse
export type MergeFunc = (
from: number,
to: number,
replaceImage: boolean
) => Promise<Response>;
export type MergeSearchCleanerFunc = (
r: SearchResponse,
id: number
) => SearchResponse;
interface Props {
type: "Track" | "Album" | "Artist"
title: string
img: string
id: number
musicbrainzId: string
imgItemId: number
mergeFunc: MergeFunc
mergeCleanerFunc: MergeSearchCleanerFunc
children: React.ReactNode
subContent: React.ReactNode
type: "Track" | "Album" | "Artist";
title: string;
img: string;
id: number;
musicbrainzId: string;
imgItemId: number;
mergeFunc: MergeFunc;
mergeCleanerFunc: MergeSearchCleanerFunc;
children: React.ReactNode;
subContent: React.ReactNode;
}
export default function MediaLayout(props: Props) {
const [bgColor, setBgColor] = useState<string>("(--color-bg)");
const [mergeModalOpen, setMergeModalOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [imageModalOpen, setImageModalOpen] = useState(false);
const [renameModalOpen, setRenameModalOpen] = useState(false);
const [addListenModalOpen, setAddListenModalOpen] = useState(false);
const { user } = useAppContext();
const [bgColor, setBgColor] = useState<string>("(--color-bg)");
const [mergeModalOpen, setMergeModalOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [imageModalOpen, setImageModalOpen] = useState(false);
const [renameModalOpen, setRenameModalOpen] = useState(false);
const [addListenModalOpen, setAddListenModalOpen] = useState(false);
const { user } = useAppContext();
useEffect(() => {
average(imageUrl(props.img, 'small'), { amount: 1 }).then((color) => {
setBgColor(`rgba(${color[0]},${color[1]},${color[2]},0.4)`);
});
}, [props.img]);
useEffect(() => {
average(imageUrl(props.img, "small"), { amount: 1 }).then((color) => {
setBgColor(`rgba(${color[0]},${color[1]},${color[2]},0.4)`);
});
}, [props.img]);
const replaceImageCallback = () => {
window.location.reload()
}
const replaceImageCallback = () => {
window.location.reload();
};
const title = `${props.title} - Koito`
const title = `${props.title} - Koito`;
const mobileIconSize = 22
const normalIconSize = 30
const mobileIconSize = 22;
const normalIconSize = 30;
let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
let vw = Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0
);
let iconSize = vw > 768 ? normalIconSize : mobileIconSize
let iconSize = vw > 768 ? normalIconSize : mobileIconSize;
return (
<main
className="w-full flex flex-col flex-grow"
style={{
background: `linear-gradient(to bottom, ${bgColor}, var(--color-bg) 700px)`,
transition: '1000',
}}
>
<ImageDropHandler itemType={props.type.toLowerCase() === 'artist' ? 'artist' : 'album'} onComplete={replaceImageCallback} />
<title>{title}</title>
<meta property="og:title" content={title} />
<meta
name="description"
content={title}
/>
<div className="w-19/20 mx-auto pt-12">
<div className="flex gap-8 flex-wrap md:flex-nowrap relative">
<div className="flex flex-col justify-around">
<img style={{zIndex: 5}} src={imageUrl(props.img, "large")} alt={props.title} className="md:min-w-[385px] w-[220px] h-auto shadow-(--color-shadow) shadow-lg" />
</div>
<div className="flex flex-col items-start">
<h3>{props.type}</h3>
<h1>{props.title}</h1>
{props.subContent}
</div>
{ user &&
<div className="absolute left-1 sm:right-1 sm:left-auto -top-9 sm:top-1 flex gap-3 items-center">
{ props.type === "Track" &&
<>
<button title="Add Listen" className="hover:cursor-pointer" onClick={() => setAddListenModalOpen(true)}><Plus size={iconSize} /></button>
<AddListenModal open={addListenModalOpen} setOpen={setAddListenModalOpen} trackid={props.id} />
</>
}
<button title="Edit Item" className="hover:cursor-pointer" onClick={() => setRenameModalOpen(true)}><Edit size={iconSize} /></button>
<button title="Replace Image" className="hover:cursor-pointer" onClick={() => setImageModalOpen(true)}><ImageIcon size={iconSize} /></button>
<button title="Merge Items" className="hover:cursor-pointer" onClick={() => setMergeModalOpen(true)}><Merge size={iconSize} /></button>
<button title="Delete Item" className="hover:cursor-pointer" onClick={() => setDeleteModalOpen(true)}><Trash size={iconSize} /></button>
<EditModal open={renameModalOpen} setOpen={setRenameModalOpen} type={props.type.toLowerCase()} id={props.id}/>
<ImageReplaceModal open={imageModalOpen} setOpen={setImageModalOpen} id={props.imgItemId} musicbrainzId={props.musicbrainzId} type={props.type === "Track" ? "Album" : props.type} />
<MergeModal currentTitle={props.title} mergeFunc={props.mergeFunc} mergeCleanerFunc={props.mergeCleanerFunc} type={props.type} currentId={props.id} open={mergeModalOpen} setOpen={setMergeModalOpen} />
<DeleteModal open={deleteModalOpen} setOpen={setDeleteModalOpen} title={props.title} id={props.id} type={props.type} />
</div>
}
</div>
{props.children}
console.log("MBZ:", props.musicbrainzId);
return (
<main
className="w-full flex flex-col flex-grow"
style={{
background: `linear-gradient(to bottom, ${bgColor}, var(--color-bg) 700px)`,
transition: "1000",
}}
>
<ImageDropHandler
itemType={props.type.toLowerCase() === "artist" ? "artist" : "album"}
onComplete={replaceImageCallback}
/>
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="description" content={title} />
<div className="w-19/20 mx-auto pt-12">
<div className="flex gap-8 flex-wrap md:flex-nowrap relative">
<div className="flex flex-col justify-around">
<img
style={{ zIndex: 5 }}
src={imageUrl(props.img, "large")}
alt={props.title}
className="md:min-w-[385px] w-[220px] h-auto shadow-(--color-shadow) shadow-lg"
/>
</div>
<div className="flex flex-col items-start">
<h3>{props.type}</h3>
<h1>{props.title}</h1>
{props.subContent}
</div>
{user && (
<div className="absolute left-1 sm:right-1 sm:left-auto -top-9 sm:top-1 flex gap-3 items-center">
{props.musicbrainzId && (
<Link
title="View on MusicBrainz"
target="_blank"
to={`https://musicbrainz.org/${props.type.toLowerCase()}/${
props.musicbrainzId
}`}
>
<MbzIcon size={iconSize} hover />
</Link>
)}
{props.type === "Track" && (
<>
<button
title="Add Listen"
className="hover:cursor-pointer"
onClick={() => setAddListenModalOpen(true)}
>
<Plus size={iconSize} />
</button>
<AddListenModal
open={addListenModalOpen}
setOpen={setAddListenModalOpen}
trackid={props.id}
/>
</>
)}
<button
title="Edit Item"
className="hover:cursor-pointer"
onClick={() => setRenameModalOpen(true)}
>
<Edit size={iconSize} />
</button>
{props.type !== "Track" && (
<button
title="Replace Image"
className="hover:cursor-pointer"
onClick={() => setImageModalOpen(true)}
>
<ImageIcon size={iconSize} />
</button>
)}
<button
title="Merge Items"
className="hover:cursor-pointer"
onClick={() => setMergeModalOpen(true)}
>
<Merge size={iconSize} />
</button>
<button
title="Delete Item"
className="hover:cursor-pointer"
onClick={() => setDeleteModalOpen(true)}
>
<Trash size={iconSize} />
</button>
<EditModal
open={renameModalOpen}
setOpen={setRenameModalOpen}
type={props.type.toLowerCase()}
id={props.id}
/>
<ImageReplaceModal
open={imageModalOpen}
setOpen={setImageModalOpen}
id={props.imgItemId}
musicbrainzId={props.musicbrainzId}
type={props.type === "Track" ? "Album" : props.type}
/>
<MergeModal
currentTitle={props.title}
mergeFunc={props.mergeFunc}
mergeCleanerFunc={props.mergeCleanerFunc}
type={props.type}
currentId={props.id}
open={mergeModalOpen}
setOpen={setMergeModalOpen}
/>
<DeleteModal
open={deleteModalOpen}
setOpen={setDeleteModalOpen}
title={props.title}
id={props.id}
type={props.type}
/>
</div>
</main>
);
)}
</div>
{props.children}
</div>
</main>
);
}

View file

@ -33,7 +33,7 @@ export default function Track() {
title={track.title}
img={track.image}
id={track.id}
musicbrainzId={album.musicbrainz_id}
musicbrainzId={track.musicbrainz_id}
imgItemId={track.album_id}
mergeFunc={mergeTracks}
mergeCleanerFunc={(r, id) => {