mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 13:38:15 -08:00
feat: add musicbrainz link where possible (#124)
This commit is contained in:
parent
d61e814306
commit
7cf7cd3a10
3 changed files with 195 additions and 80 deletions
23
client/app/components/icons/MbzIcon.tsx
Normal file
23
client/app/components/icons/MbzIcon.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
interface Props {
|
||||
size: number;
|
||||
hover?: boolean;
|
||||
}
|
||||
export default function MbzIcon({ size, hover }: Props) {
|
||||
let classNames = "";
|
||||
if (hover) {
|
||||
classNames += "icon-hover-fill";
|
||||
}
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<svg
|
||||
width={`${size}px`}
|
||||
height={`${size}px`}
|
||||
viewBox="0 0 24 24"
|
||||
fill="var(--color-fg)"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M11.582 0L1.418 5.832v12.336L11.582 24V10.01L7.1 12.668v3.664c.01.111.01.225 0 .336-.103.435-.54.804-1 1.111-.802.537-1.752.509-2.166-.111-.413-.62-.141-1.631.666-2.168.384-.28.863-.399 1.334-.332V6.619c0-.154.134-.252.226-.308L11.582 3zm.836 0v6.162c.574.03 1.14.16 1.668.387a2.225 2.225 0 0 0 1.656-.717 1.02 1.02 0 1 1 1.832-.803l.004.006a1.022 1.022 0 0 1-1.295 1.197c-.34.403-.792.698-1.297.85.34.263.641.576.891.928a1.04 1.04 0 0 1 .777.125c.768.486.568 1.657-.318 1.857-.886.2-1.574-.77-1.09-1.539.02-.03.042-.06.065-.09a3.598 3.598 0 0 0-1.436-1.166 4.142 4.142 0 0 0-1.457-.369v4.01c.855.06 1.256.493 1.555.834.227.256.356.39.578.402.323.018.568.008.806 0a5.44 5.44 0 0 1 .895.022c.94-.017 1.272-.226 1.605-.446a2.533 2.533 0 0 1 1.131-.463 1.027 1.027 0 0 1 .12-.263 1.04 1.04 0 0 1 .105-.137c.023-.025.047-.044.07-.066a4.775 4.775 0 0 1 0-2.405l-.012-.01a1.02 1.02 0 1 1 .692.272h-.057a4.288 4.288 0 0 0 0 1.877h.063a1.02 1.02 0 1 1-.545 1.883l-.047-.033a1 1 0 0 1-.352-.442 1.885 1.885 0 0 0-.814.354 3.03 3.03 0 0 1-.703.365c.757.555 1.772 1.6 2.199 2.299a1.03 1.03 0 0 1 .256-.033 1.02 1.02 0 1 1-.545 1.88l-.047-.03a1.017 1.017 0 0 1-.27-1.376.72.72 0 0 1 .051-.072c-.445-.775-2.026-2.28-2.46-2.387a4.037 4.037 0 0 0-1.31-.117c-.24.008-.513.018-.866 0-.515-.027-.783-.333-1.043-.629-.26-.296-.51-.56-1.055-.611V18.5a1.877 1.877 0 0 0 .426-.135.333.333 0 0 1 .058-.027c.56-.267 1.421-.91 2.096-2.447a1.02 1.02 0 0 1-.27-1.344 1.02 1.02 0 1 1 .915 1.54 6.273 6.273 0 0 1-1.432 2.136 1.785 1.785 0 0 1 .691.306.667.667 0 0 0 .37.168 3.31 3.31 0 0 0 .888-.222 1.02 1.02 0 0 1 1.787-.79v-.005a1.02 1.02 0 0 1-.773 1.683 1.022 1.022 0 0 1-.719-.287 3.935 3.935 0 0 1-1.168.287h-.05a1.313 1.313 0 0 1-.71-.275c-.262-.177-.51-.345-1.402-.12a2.098 2.098 0 0 1-.707.2V24l10.164-5.832V5.832zm4.154 4.904a.352.352 0 0 0-.197.639l.018.01c.163.1.378.053.484-.108v-.002a.352.352 0 0 0-.303-.539zm-4.99 1.928L7.082 9.5v2l4.5-2.668zm8.385.38a.352.352 0 0 0-.295.165v.002a.35.35 0 0 0 .096.473l.013.01a.357.357 0 0 0 .487-.108.352.352 0 0 0-.301-.541zM16.09 8.647a.352.352 0 0 0-.277.163.355.355 0 0 0 .296.54c.482 0 .463-.73-.02-.703zm3.877 2.477a.352.352 0 0 0-.295.164.35.35 0 0 0 .094.475l.015.01a.357.357 0 0 0 .485-.11.352.352 0 0 0-.3-.539zm-4.375 3.594a.352.352 0 0 0-.291.172.35.35 0 0 0-.04.265.352.352 0 1 0 .33-.437zm4.375.789a.352.352 0 0 0-.295.164v.002a.352.352 0 0 0 .094.473l.015.01a.357.357 0 0 0 .485-.108.352.352 0 0 0-.3-.54zm-2.803 2.488v.002a.347.347 0 0 0-.223.084.352.352 0 0 0 .23.62.347.347 0 0 0 .23-.085.348.348 0 0 0 .12-.24.353.353 0 0 0-.35-.38.347.347 0 0 0-.007 0Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue