Redirect user to requests page after successful media request submission
js->ts
This commit is contained in:
@@ -44,6 +44,11 @@ export default function Player({ user }: PlayerProps) {
|
||||
// Global CSS now contains the paginator / dialog datatable dark rules.
|
||||
|
||||
const [isQueueVisible, setQueueVisible] = useState(false);
|
||||
const lrcContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const lrcWheelHandlerRef = useRef<((e: WheelEvent) => void) | null>(null);
|
||||
const lrcScrollTimeout = useRef<number | null>(null);
|
||||
const lrcScrollRaf = useRef<number | null>(null);
|
||||
const lrcActiveRef = useRef<HTMLElement | null>(null);
|
||||
// Mouse wheel scroll fix for queue modal
|
||||
useEffect(() => {
|
||||
if (!isQueueVisible) return;
|
||||
@@ -249,16 +254,56 @@ export default function Player({ user }: PlayerProps) {
|
||||
|
||||
// Scroll active lyric into view
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
const activeElement = document.querySelector('.lrc-line.active');
|
||||
const lyricsContainer = document.querySelector('.lrc-text');
|
||||
if (activeElement && lyricsContainer) {
|
||||
(lyricsContainer as HTMLElement).style.maxHeight = '220px';
|
||||
(lyricsContainer as HTMLElement).style.overflowY = 'auto';
|
||||
activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
const container = lrcContainerRef.current;
|
||||
if (!container || lyrics.length === 0) return;
|
||||
|
||||
const scheduleScroll = () => {
|
||||
// Read ref/DOM inside the callback so we get the updated element after render
|
||||
const activeElement = lrcActiveRef.current || (container.querySelector('.lrc-line.active') as HTMLElement | null);
|
||||
if (!activeElement) return;
|
||||
|
||||
// Use getBoundingClientRect for accurate positioning
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const activeRect = activeElement.getBoundingClientRect();
|
||||
|
||||
// Calculate where the element is relative to the container's current scroll
|
||||
const elementTopInContainer = activeRect.top - containerRect.top + container.scrollTop;
|
||||
|
||||
// Center the active line in the container
|
||||
const targetScrollTop = elementTopInContainer - (container.clientHeight / 2) + (activeElement.offsetHeight / 2);
|
||||
|
||||
container.scrollTo({ top: Math.max(targetScrollTop, 0), behavior: 'smooth' });
|
||||
};
|
||||
|
||||
// Debounce a tick then align to paint frame so ref is updated after render
|
||||
if (lrcScrollTimeout.current) window.clearTimeout(lrcScrollTimeout.current);
|
||||
lrcScrollTimeout.current = window.setTimeout(() => {
|
||||
if (lrcScrollRaf.current) cancelAnimationFrame(lrcScrollRaf.current);
|
||||
lrcScrollRaf.current = requestAnimationFrame(scheduleScroll);
|
||||
}, 16);
|
||||
|
||||
const wheelHandler = (e: WheelEvent) => {
|
||||
const atTop = container.scrollTop === 0;
|
||||
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight;
|
||||
if ((e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, 0);
|
||||
}, [currentLyricIndex, lyrics]);
|
||||
};
|
||||
|
||||
if (lrcWheelHandlerRef.current) {
|
||||
container.removeEventListener('wheel', lrcWheelHandlerRef.current as EventListener);
|
||||
}
|
||||
lrcWheelHandlerRef.current = wheelHandler;
|
||||
container.addEventListener('wheel', wheelHandler, { passive: false });
|
||||
|
||||
return () => {
|
||||
if (lrcScrollTimeout.current) window.clearTimeout(lrcScrollTimeout.current);
|
||||
if (lrcScrollRaf.current) cancelAnimationFrame(lrcScrollRaf.current);
|
||||
container.removeEventListener('wheel', wheelHandler as EventListener);
|
||||
};
|
||||
}, [currentLyricIndex, lyrics.length]);
|
||||
|
||||
// Handle station changes: reset and start new stream
|
||||
useEffect(() => {
|
||||
@@ -746,10 +791,42 @@ export default function Player({ user }: PlayerProps) {
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className={`lrc-text mt-4 p-4 rounded-lg bg-neutral-100 dark:bg-neutral-800 ${lyrics.length === 0 ? "empty" : ""}`}>
|
||||
<div
|
||||
ref={lrcContainerRef}
|
||||
className={`lrc-text mt-4 p-4 rounded-lg bg-neutral-100 dark:bg-neutral-800 ${lyrics.length === 0 ? "empty" : ""}`}
|
||||
tabIndex={lyrics.length > 0 ? 0 : -1}
|
||||
aria-label="Lyrics"
|
||||
onKeyDown={(e) => {
|
||||
if (!lrcContainerRef.current || lyrics.length === 0) return;
|
||||
const container = lrcContainerRef.current;
|
||||
const step = 24;
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
container.scrollBy({ top: step, behavior: 'smooth' });
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
container.scrollBy({ top: -step, behavior: 'smooth' });
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'Home':
|
||||
container.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'End':
|
||||
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{lyrics.length === 0 && (
|
||||
<p className="lrc-line text-sm text-neutral-400 dark:text-neutral-500">No lyrics available.</p>
|
||||
)}
|
||||
{lyrics.map((lyricObj, index) => (
|
||||
<p
|
||||
key={index}
|
||||
ref={index === currentLyricIndex ? (lrcActiveRef as React.RefObject<HTMLParagraphElement>) : undefined}
|
||||
className={`lrc-line text-sm ${index === currentLyricIndex ? "active font-bold" : ""}`}
|
||||
>
|
||||
{lyricObj.line}
|
||||
|
||||
Reference in New Issue
Block a user