Merge pull request #96 from meowarex/dev

Fixed UI Freeze & Scroll issues
This commit is contained in:
Meow Meow
2026-04-02 23:11:46 +11:00
committed by GitHub
+79 -18
View File
@@ -1515,6 +1515,8 @@ let tickLoopUnload: LunaUnload | null = null;
let isActive = false; let isActive = false;
let savedTidalClasses: string[] | null = null; let savedTidalClasses: string[] | null = null;
let tidalFollowObserver: MutationObserver | null = null; let tidalFollowObserver: MutationObserver | null = null;
let tidalFollowLunaUnload: (() => void) | null = null;
let tidalFollowRebuildPending = false;
let nativeLyricsOverlayInstalled = false; let nativeLyricsOverlayInstalled = false;
let originalReduxGetState: (() => ReturnType<typeof redux.store.getState>) | null = let originalReduxGetState: (() => ReturnType<typeof redux.store.getState>) | null =
null; null;
@@ -1938,6 +1940,7 @@ let scrollSynced = true;
let userScrollListener: (() => void) | null = null; let userScrollListener: (() => void) | null = null;
let syncButtonListener: (() => void) | null = null; let syncButtonListener: (() => void) | null = null;
let syncButtonEl: HTMLElement | null = null; let syncButtonEl: HTMLElement | null = null;
let syncButtonObserverUnload: (() => void) | null = null;
// scroll bounce animation state // scroll bounce animation state
let scrollAnimIsAnimating = false; let scrollAnimIsAnimating = false;
@@ -2962,6 +2965,10 @@ const buildTidalLines = (
}); });
lineDiv.appendChild(lineSpan); lineDiv.appendChild(lineSpan);
lineDiv.addEventListener("click", () => {
tidalSpan.click();
resync();
});
wbwContainer.appendChild(lineDiv); wbwContainer.appendChild(lineDiv);
lines.push({ lines.push({
el: lineDiv, el: lineDiv,
@@ -2979,6 +2986,11 @@ const buildTidalLines = (
}; };
const stopTidalFollowLoop = (): void => { const stopTidalFollowLoop = (): void => {
tidalFollowRebuildPending = false;
if (tidalFollowLunaUnload) {
tidalFollowLunaUnload();
tidalFollowLunaUnload = null;
}
if (tidalFollowObserver) { if (tidalFollowObserver) {
tidalFollowObserver.disconnect(); tidalFollowObserver.disconnect();
tidalFollowObserver = null; tidalFollowObserver = null;
@@ -3213,6 +3225,11 @@ const updateTidalFollowActiveLine = (): void => {
applyInactiveBlurState(activeIndex); applyInactiveBlurState(activeIndex);
// Retry hooking the sync button when desynced (no tick loop in line-tidal)
if (!scrollSynced && !syncButtonEl) {
hookSyncButton();
}
if (activeIndex !== prevPrimary) { if (activeIndex !== prevPrimary) {
const newLine = lines[activeIndex]; const newLine = lines[activeIndex];
const scrollParent = findScroller(newLine.el); const scrollParent = findScroller(newLine.el);
@@ -3244,24 +3261,53 @@ const updateTidalFollowActiveLine = (): void => {
} }
}; };
const startTidalFollowLoop = (): void => { const rebuildTidalSpanAttrObservers = (): void => {
stopTidalFollowLoop(); if (tidalFollowObserver) {
tidalFollowObserver.disconnect();
tidalFollowObserver = null;
}
const lyricsContainer = findLyricsContainer(); const lyricsContainer = findLyricsContainer();
if (!lyricsContainer) return; if (!lyricsContainer) return;
const tidalSpans = lyricsContainer.querySelectorAll(
'span[data-test="lyrics-line"]',
);
if (tidalSpans.length === 0) return;
tidalFollowObserver = new MutationObserver(() => { tidalFollowObserver = new MutationObserver(() => {
updateTidalFollowActiveLine(); updateTidalFollowActiveLine();
}); });
tidalFollowObserver.observe(lyricsContainer, {
subtree: true, for (const span of tidalSpans) {
childList: true, tidalFollowObserver.observe(span, {
attributes: true, attributes: true,
attributeFilter: ["class"], attributeFilter: ["class"],
}); });
}
updateTidalFollowActiveLine(); updateTidalFollowActiveLine();
}; };
const startTidalFollowLoop = (): void => {
stopTidalFollowLoop();
rebuildTidalSpanAttrObservers();
tidalFollowLunaUnload = observe<HTMLElement>(
unloads,
'span[data-test="lyrics-line"]',
() => {
if (tidalFollowRebuildPending) return;
tidalFollowRebuildPending = true;
setTimeout(() => {
tidalFollowRebuildPending = false;
rebuildTidalSpanAttrObservers();
}, 0);
},
);
};
// watch for re-renders // watch for re-renders
const watchForRerender = (): void => { const watchForRerender = (): void => {
unwatchRerender(); unwatchRerender();
@@ -3369,13 +3415,10 @@ const findScroller = (el: HTMLElement): HTMLElement => {
while (parent) { while (parent) {
if (boundary && !boundary.contains(parent)) break; if (boundary && !boundary.contains(parent)) break;
const style = window.getComputedStyle(parent); const style = window.getComputedStyle(parent);
if ( const oy = style.overflowY;
style.overflowY === "auto" || const o = style.overflow;
style.overflowY === "scroll" || const scrollable = oy === "auto" || oy === "scroll" || o === "auto" || o === "scroll";
style.overflow === "auto" || if (scrollable || ((oy === "hidden" || o === "hidden") && parent.scrollHeight > parent.clientHeight + 1)) {
style.overflow === "scroll" ||
parent.scrollHeight > parent.clientHeight + 1
) {
return parent; return parent;
} }
parent = parent.parentElement; parent = parent.parentElement;
@@ -3507,19 +3550,37 @@ const unhookUserScroll = (): void => {
}; };
// Hook lyric scroll sync button // Hook lyric scroll sync button
const hookSyncButton = (): void => { const SYNC_BTN_SELECTOR = 'div[class*="_syncButton"] button';
unhookSyncButton();
const btn = document.querySelector( const attachSyncButtonHandler = (btn: HTMLElement): void => {
'div[class*="_syncButton"] button',
) as HTMLElement;
if (!btn) return;
syncButtonEl = btn; syncButtonEl = btn;
const handler = () => resync(false); const handler = () => resync(false);
btn.addEventListener("click", handler); btn.addEventListener("click", handler);
syncButtonListener = () => btn.removeEventListener("click", handler); syncButtonListener = () => btn.removeEventListener("click", handler);
}; };
const hookSyncButton = (): void => {
unhookSyncButton();
const btn = document.querySelector(SYNC_BTN_SELECTOR) as HTMLElement;
if (btn) {
attachSyncButtonHandler(btn);
return;
}
syncButtonObserverUnload = observe<HTMLElement>(
unloads,
SYNC_BTN_SELECTOR,
(el) => {
if (syncButtonEl) return;
attachSyncButtonHandler(el);
},
);
};
const unhookSyncButton = (): void => { const unhookSyncButton = (): void => {
if (syncButtonObserverUnload) {
syncButtonObserverUnload();
syncButtonObserverUnload = null;
}
if (syncButtonListener) { if (syncButtonListener) {
syncButtonListener(); syncButtonListener();
syncButtonListener = null; syncButtonListener = null;