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