init: inital commit
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { Flipper, Flipped } from "react-flip-toolkit";
|
||||
|
||||
import { RiMusic2Line, RiUser3Line, RiAlbumLine } from "@remixicon/react";
|
||||
|
||||
import type NodeCG from "@nodecg/types";
|
||||
import type {
|
||||
CoverArtReplicant,
|
||||
MusicConfigReplicant,
|
||||
MusicReplicant,
|
||||
} from "../types";
|
||||
|
||||
const ensureArrayBufferLike = (data: any): ArrayBufferLike => {
|
||||
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
||||
return data.slice();
|
||||
} else if (Array.isArray(data)) {
|
||||
return new Uint8Array(data);
|
||||
} else if (typeof data === "object" && data !== null) {
|
||||
return new Uint8Array(Object.values(data));
|
||||
}
|
||||
throw new Error("Unsupported data format");
|
||||
};
|
||||
|
||||
const musicReplicant = nodecg.Replicant(
|
||||
"musicReplicant",
|
||||
) as unknown as NodeCG.ServerReplicant<MusicReplicant>;
|
||||
|
||||
const musicConfigReplicant = nodecg.Replicant(
|
||||
"musicConfigReplicant",
|
||||
) as unknown as NodeCG.ServerReplicant<MusicConfigReplicant>;
|
||||
|
||||
const coverArtReplicant = nodecg.Replicant(
|
||||
"coverArtReplicant",
|
||||
) as unknown as NodeCG.ServerReplicant<CoverArtReplicant>;
|
||||
const useReplicant = <T = any,>(
|
||||
replicant: NodeCG.ServerReplicant<T>,
|
||||
fn: (newValue: T | undefined, oldValue: T | undefined) => void | (() => void),
|
||||
deps: any[] = [],
|
||||
) => {
|
||||
useEffect(() => {
|
||||
let cleanup: (() => void) | void;
|
||||
|
||||
const handler = (newValue: T | undefined, oldValue: T | undefined) => {
|
||||
if (cleanup) cleanup(); // Run previous cleanup before setting new one
|
||||
cleanup = fn(newValue, oldValue); // Store new cleanup function (if provided)
|
||||
};
|
||||
|
||||
replicant.on("change", handler);
|
||||
|
||||
return () => {
|
||||
replicant.off("change", handler);
|
||||
if (cleanup) cleanup();
|
||||
};
|
||||
}, [...deps]);
|
||||
};
|
||||
|
||||
export function Index() {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [songInfo, setSongInfo] = useState(
|
||||
{} as Omit<MusicReplicant, "playing">,
|
||||
);
|
||||
|
||||
useReplicant(musicReplicant, (newValue?: MusicReplicant) => {
|
||||
setIsPlaying(newValue?.playing || false);
|
||||
setSongInfo(newValue || {});
|
||||
});
|
||||
|
||||
const [imageUrl, setImageUrl] = useState(null as null | string);
|
||||
|
||||
useReplicant(
|
||||
coverArtReplicant,
|
||||
(newValue?: CoverArtReplicant) => {
|
||||
if (newValue?.data && newValue?.format) {
|
||||
const data = ensureArrayBufferLike(newValue.data);
|
||||
|
||||
// Convert to Blob
|
||||
const blob = new Blob([data], {
|
||||
type: newValue.format,
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
setImageUrl(url);
|
||||
|
||||
// Free old image
|
||||
return () => {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const [songAlbumToggle, setSongAlbumToggle] = useState(true);
|
||||
const [showLabelNames, setShowLabelNames] = useState(true);
|
||||
|
||||
const mainTickerClasses =
|
||||
" bg-black text-white line-clamp-3 min-h-[calc(var(--height)_*_1/3)] py-8 text-8xl ";
|
||||
const subTickerClasses =
|
||||
" min-h-[calc(var(--height)_*_1/5)] bg-[#fdc700] line-clamp-2 py-4 text-5xl ";
|
||||
|
||||
const topTickerClasses = songAlbumToggle
|
||||
? mainTickerClasses
|
||||
: subTickerClasses;
|
||||
const bottomTickerClasses = !songAlbumToggle
|
||||
? mainTickerClasses
|
||||
: subTickerClasses;
|
||||
|
||||
const [tickerSwapInterval, setTickerSwapInterval] = useState(25000);
|
||||
|
||||
useReplicant(
|
||||
musicConfigReplicant,
|
||||
(newValue) => {
|
||||
setTickerSwapInterval(newValue!.tickerSwapInterval * 1000);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let value = songAlbumToggle;
|
||||
|
||||
let timeout1 = undefined as undefined | NodeJS.Timeout;
|
||||
let timeout2 = undefined as undefined | NodeJS.Timeout;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
value = !value;
|
||||
setSongAlbumToggle(value);
|
||||
|
||||
timeout1 = setTimeout(() => {
|
||||
setShowLabelNames(true);
|
||||
timeout2 = setTimeout(
|
||||
() => {
|
||||
setShowLabelNames(false);
|
||||
},
|
||||
Math.min(tickerSwapInterval * 0.5, 10000),
|
||||
);
|
||||
}, tickerSwapInterval / 5);
|
||||
}, tickerSwapInterval);
|
||||
|
||||
return () => {
|
||||
setSongAlbumToggle(true);
|
||||
setShowLabelNames(false);
|
||||
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout1);
|
||||
clearTimeout(timeout2);
|
||||
};
|
||||
}, [tickerSwapInterval, songInfo]);
|
||||
|
||||
const springOpts = "stiff";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="p-16 overflow-hidden drop-shadow-2xl"
|
||||
style={{
|
||||
display: "flex",
|
||||
...{ "--height": "26vw" },
|
||||
}}
|
||||
>
|
||||
<div className={"flex flex-col relative "}>
|
||||
{isPlaying ? (
|
||||
<div
|
||||
className={
|
||||
"flex bg-[#00a63e] text-3xl p-3 text-white relative transition "
|
||||
}
|
||||
style={{
|
||||
transform: `translateY(${songAlbumToggle ? 100 : 0}%)`,
|
||||
}}
|
||||
>
|
||||
<div className="">Godina izdavanja:</div>
|
||||
<div className="grow" />
|
||||
<div className="font-bold">{songInfo.year}</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{imageUrl && isPlaying ? (
|
||||
<img
|
||||
className={"z-10"}
|
||||
src={imageUrl}
|
||||
alt="Cover art"
|
||||
style={{
|
||||
width: "var(--height)",
|
||||
height: "var(--height)",
|
||||
minWidth: "var(--height)",
|
||||
minHeight: "var(--height)",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
{isPlaying ? (
|
||||
<div className="block relative flex flex-col">
|
||||
<div className="grow" />
|
||||
|
||||
<Flipper
|
||||
flipKey={
|
||||
songAlbumToggle + "," + showLabelNames + "," + songInfo.name
|
||||
}
|
||||
spring={springOpts}
|
||||
>
|
||||
<Flipped flipId="tickers">
|
||||
<div
|
||||
className={
|
||||
"inline-flex relative z-30 overflow-visible shrink-0 transition-colors duration-100 flex-col justify-center min-w-(--height) px-8 font-bold " +
|
||||
topTickerClasses +
|
||||
"" ||
|
||||
(showLabelNames && !songAlbumToggle ? " pb-12 pt-8 " : "")
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"absolute flex items-center justify-center transition origin-left overflow-hidden min-h-[calc(var(--height)_/_8)] min-w-[calc(var(--height)_/_8)] text-2xl top-[calc(100%_-_var(--height)_/_10.66)] right-[calc(var(--height)_/_-10.66)] font-bold bg-[#00a63e] h-[var(--height)_/_10.66] text-white top-4 z-20 ml-4 max-w-fit "
|
||||
}
|
||||
style={{
|
||||
transform: showLabelNames ? " scaleX(1) " : " scaleX(0) ",
|
||||
}}
|
||||
>
|
||||
{songAlbumToggle ? (
|
||||
<RiMusic2Line color="white" size={"2.5rem"} />
|
||||
) : (
|
||||
<RiUser3Line color="white" size={"2.5rem"} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{songAlbumToggle ? songInfo.name : songInfo.artist}
|
||||
</div>
|
||||
</Flipped>
|
||||
</Flipper>
|
||||
<Flipper flipKey={songAlbumToggle} spring={springOpts}>
|
||||
<Flipped flipId="tickers">
|
||||
<div
|
||||
className={
|
||||
"inline-flex overflow-visible relative shrink-0 transition-colors duration-100 flex-col justify-center shrink-0 min-w-(--height) px-8 font-bold z-20 " +
|
||||
bottomTickerClasses
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"absolute flex items-center justify-center transition origin-left overflow-hidden min-h-[calc(var(--height)_/_8)] min-w-[calc(var(--height)_/_8)] text-2xl top-[calc(100%_-_var(--height)_/_10.66)] right-[calc(var(--height)_/_-10.66)] font-bold bg-[#00a63e] h-[var(--height)_/_10.66] text-white top-4 ml-4 max-w-fit "
|
||||
}
|
||||
style={{
|
||||
transform: showLabelNames ? " scaleX(1) " : " scaleX(0) ",
|
||||
}}
|
||||
>
|
||||
{songAlbumToggle ? (
|
||||
<RiUser3Line color="white" size={"2.5rem"} />
|
||||
) : (
|
||||
<RiAlbumLine color="white" size={"2.5rem"} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{songAlbumToggle ? songInfo.artist : songInfo.album}
|
||||
</div>
|
||||
</Flipped>
|
||||
</Flipper>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user