mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
Merge pull request #83 from meowarex/dev
Fixed Context Menus & other things
This commit is contained in:
@@ -1319,10 +1319,12 @@ const handleStickyLyricsTrackChange = (): void => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// MARKER: Injected API Lyrics (for non tidal lyric tracks)
|
// MARKER: Injected API Lyrics (for non tidal lyric tracks)
|
||||||
|
|
||||||
let injectedTablistClickCleanup: (() => void) | null = null;
|
let injectedTablistClickCleanup: (() => void) | null = null;
|
||||||
|
let isTrackChangeRunning = false;
|
||||||
|
let trackChangeRunSeq = 0;
|
||||||
|
const hiddenPanelsByInjected = new Set<HTMLElement>();
|
||||||
|
|
||||||
const getTabsRoot = (): HTMLElement | null => {
|
const getTabsRoot = (): HTMLElement | null => {
|
||||||
const roots = Array.from(
|
const roots = Array.from(
|
||||||
@@ -1338,12 +1340,16 @@ const hideInjectedLyricsTab = (): void => {
|
|||||||
if (!injectedTabEl || !injectedPanelEl) return;
|
if (!injectedTabEl || !injectedPanelEl) return;
|
||||||
const root = getTabsRoot();
|
const root = getTabsRoot();
|
||||||
if (root) {
|
if (root) {
|
||||||
|
for (const panel of hiddenPanelsByInjected) {
|
||||||
|
panel.style.removeProperty("display");
|
||||||
|
}
|
||||||
|
hiddenPanelsByInjected.clear();
|
||||||
|
|
||||||
const nativePanels = Array.from(
|
const nativePanels = Array.from(
|
||||||
root.querySelectorAll('div[role="tabpanel"]'),
|
root.querySelectorAll('div[role="tabpanel"]'),
|
||||||
) as HTMLElement[];
|
) as HTMLElement[];
|
||||||
for (const panel of nativePanels) {
|
for (const panel of nativePanels) {
|
||||||
if (panel === injectedPanelEl) continue;
|
if (panel === injectedPanelEl) continue;
|
||||||
panel.removeAttribute("aria-hidden");
|
|
||||||
panel.style.removeProperty("display");
|
panel.style.removeProperty("display");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1402,14 +1408,21 @@ const showInjectedLyricsTab = (): void => {
|
|||||||
const nativePanels = Array.from(
|
const nativePanels = Array.from(
|
||||||
root.querySelectorAll('div[role="tabpanel"]'),
|
root.querySelectorAll('div[role="tabpanel"]'),
|
||||||
) as HTMLElement[];
|
) as HTMLElement[];
|
||||||
|
for (const panel of hiddenPanelsByInjected) {
|
||||||
|
panel.style.removeProperty("display");
|
||||||
|
}
|
||||||
|
hiddenPanelsByInjected.clear();
|
||||||
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
if (tab === injectedTabEl) continue;
|
if (tab === injectedTabEl) continue;
|
||||||
if (activeTabClass) tab.classList.remove(activeTabClass);
|
if (activeTabClass) tab.classList.remove(activeTabClass);
|
||||||
}
|
}
|
||||||
for (const panel of nativePanels) {
|
for (const panel of nativePanels) {
|
||||||
if (panel === injectedPanelEl) continue;
|
if (panel === injectedPanelEl) continue;
|
||||||
panel.setAttribute("aria-hidden", "true");
|
if (panel.classList.contains("react-tabs__tab-panel--selected")) {
|
||||||
panel.style.display = "none";
|
panel.style.display = "none";
|
||||||
|
hiddenPanelsByInjected.add(panel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
injectedTabEl.setAttribute("aria-selected", "true");
|
injectedTabEl.setAttribute("aria-selected", "true");
|
||||||
@@ -1626,6 +1639,12 @@ function setupStickyLyricsObserver(): void {
|
|||||||
|
|
||||||
// Apply word lyrics when lyrics container appears or reappears
|
// Apply word lyrics when lyrics container appears or reappears
|
||||||
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
|
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
|
||||||
|
if (isTrackChangeRunning) return;
|
||||||
|
const lyricsLines = document.querySelector(
|
||||||
|
'[data-test="lyrics-lines"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
if (lyricsLines?.querySelector(".rl-wbw-container")) return;
|
||||||
|
|
||||||
if (lyricsMode === "line-tidal") {
|
if (lyricsMode === "line-tidal") {
|
||||||
void reapplyTidalLines();
|
void reapplyTidalLines();
|
||||||
} else if (lyricsData) {
|
} else if (lyricsData) {
|
||||||
@@ -2805,6 +2824,23 @@ const stopTidalFollowLoop = (): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// smthn GPT 5.3 Codex did
|
||||||
|
const setTidalFallbackLineWordState = (
|
||||||
|
lineEl: HTMLElement,
|
||||||
|
active: boolean,
|
||||||
|
): void => {
|
||||||
|
const words = lineEl.querySelectorAll(".rl-wbw-word");
|
||||||
|
for (const word of words) {
|
||||||
|
if (active) {
|
||||||
|
word.classList.add("rl-wbw-active");
|
||||||
|
word.classList.remove("rl-wbw-finished");
|
||||||
|
} else {
|
||||||
|
word.classList.remove("rl-wbw-active");
|
||||||
|
word.classList.add("rl-wbw-finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateTidalFollowActiveLine = (): void => {
|
const updateTidalFollowActiveLine = (): void => {
|
||||||
if (!isActive || lyricsMode !== "line-tidal" || lines.length === 0) return;
|
if (!isActive || lyricsMode !== "line-tidal" || lines.length === 0) return;
|
||||||
|
|
||||||
@@ -2824,6 +2860,7 @@ const updateTidalFollowActiveLine = (): void => {
|
|||||||
if (!newActiveSet.has(idx) && idx < lines.length) {
|
if (!newActiveSet.has(idx) && idx < lines.length) {
|
||||||
lines[idx].el.classList.remove("rl-wbw-line-active");
|
lines[idx].el.classList.remove("rl-wbw-line-active");
|
||||||
lines[idx].el.removeAttribute("data-current");
|
lines[idx].el.removeAttribute("data-current");
|
||||||
|
setTidalFallbackLineWordState(lines[idx].el, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2832,6 +2869,7 @@ const updateTidalFollowActiveLine = (): void => {
|
|||||||
lines[activeIndex].el.classList.remove("rl-pos-1", "rl-pos-2", "rl-pos-3");
|
lines[activeIndex].el.classList.remove("rl-pos-1", "rl-pos-2", "rl-pos-3");
|
||||||
lines[activeIndex].el.setAttribute("data-current", "true");
|
lines[activeIndex].el.setAttribute("data-current", "true");
|
||||||
}
|
}
|
||||||
|
setTidalFallbackLineWordState(lines[activeIndex].el, true);
|
||||||
|
|
||||||
const prevPrimary = primaryLineIdx;
|
const prevPrimary = primaryLineIdx;
|
||||||
primaryLineIdx = activeIndex;
|
primaryLineIdx = activeIndex;
|
||||||
@@ -3192,7 +3230,7 @@ const startTickLoop = (): void => {
|
|||||||
span.removeAttribute("data-current");
|
span.removeAttribute("data-current");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nowMs - lastLogTime >= 1000) {
|
if (!isLineStyle && nowMs - lastLogTime >= 1000) {
|
||||||
lastLogTime = nowMs;
|
lastLogTime = nowMs;
|
||||||
sylLog(`[RL-Syllable] Playback | ${nowMs.toFixed(0)} ms`);
|
sylLog(`[RL-Syllable] Playback | ${nowMs.toFixed(0)} ms`);
|
||||||
}
|
}
|
||||||
@@ -3430,9 +3468,11 @@ const startTickLoop = (): void => {
|
|||||||
word.el.style.animation = wipe + sylAnim;
|
word.el.style.animation = wipe + sylAnim;
|
||||||
}
|
}
|
||||||
activeWordEls.set(lineIdx, word.el);
|
activeWordEls.set(lineIdx, word.el);
|
||||||
sylLog(
|
if (!isLineStyle) {
|
||||||
`[RL-Syllable] Word/Syllable "${word.el.textContent}" | ${word.start} ms - ${word.end} ms [${nowMs.toFixed(0)} ms]`,
|
sylLog(
|
||||||
);
|
`[RL-Syllable] Word/Syllable "${word.el.textContent}" | ${word.start} ms - ${word.end} ms [${nowMs.toFixed(0)} ms]`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
word.el.classList.remove(CLS_ACTIVE);
|
word.el.classList.remove(CLS_ACTIVE);
|
||||||
@@ -3510,97 +3550,106 @@ const startTickLoop = (): void => {
|
|||||||
const onTrackChange = async (): Promise<void> => {
|
const onTrackChange = async (): Promise<void> => {
|
||||||
teardown();
|
teardown();
|
||||||
|
|
||||||
const token = ++trackChangeToken;
|
const runId = ++trackChangeRunSeq;
|
||||||
|
isTrackChangeRunning = true;
|
||||||
const trackInfo = await getTrackInfo();
|
const token = ++trackChangeToken;s
|
||||||
if (token !== trackChangeToken) return;
|
try {
|
||||||
if (!trackInfo) {
|
const trackInfo = await getTrackInfo();
|
||||||
trace.log("could not get track info from playback state");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sylTrace(
|
|
||||||
`RL API: looking up "${trackInfo.title}" by "${trackInfo.artist}"${trackInfo.isrc ? ` (ISRC: ${trackInfo.isrc})` : ""}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await fetchLyrics(
|
|
||||||
trackInfo.title,
|
|
||||||
trackInfo.artist,
|
|
||||||
trackInfo.isrc,
|
|
||||||
);
|
|
||||||
if (token !== trackChangeToken) return;
|
|
||||||
if (!response) {
|
|
||||||
trace.log("RL API: no API lyrics available, falling back to TIDAL lines");
|
|
||||||
const tidalTexts = getTidalLines();
|
|
||||||
const romanized = settings.romanizeLyrics
|
|
||||||
? await romanizeLines(tidalTexts)
|
|
||||||
: null;
|
|
||||||
if (token !== trackChangeToken) return;
|
if (token !== trackChangeToken) return;
|
||||||
cachedTidalRomanizedLines = romanized;
|
if (!trackInfo) {
|
||||||
cachedTidalRomanizeKey = settings.romanizeLyrics
|
trace.log("could not get track info from playback state");
|
||||||
? `${tidalTexts.join("\n")}\0r`
|
return;
|
||||||
: null;
|
}
|
||||||
isActive = true;
|
|
||||||
lyricsMode = "line-tidal";
|
sylTrace(
|
||||||
hideTidalLyrics();
|
`RL API: looking up "${trackInfo.title}" by "${trackInfo.artist}"${trackInfo.isrc ? ` (ISRC: ${trackInfo.isrc})` : ""}`,
|
||||||
const tidalResult = buildTidalLines(romanized);
|
);
|
||||||
lines = tidalResult.lines;
|
|
||||||
if (lines.length === 0) {
|
const response = await fetchLyrics(
|
||||||
trace.log("No TIDAL lines available yet");
|
trackInfo.title,
|
||||||
|
trackInfo.artist,
|
||||||
|
trackInfo.isrc,
|
||||||
|
);
|
||||||
|
if (token !== trackChangeToken) return;
|
||||||
|
if (!response) {
|
||||||
|
trace.log("RL API: no API lyrics available, falling back to TIDAL lines");
|
||||||
|
const tidalTexts = getTidalLines();
|
||||||
|
const romanized = settings.romanizeLyrics
|
||||||
|
? await romanizeLines(tidalTexts)
|
||||||
|
: null;
|
||||||
|
if (token !== trackChangeToken) return;
|
||||||
|
cachedTidalRomanizedLines = romanized;
|
||||||
|
cachedTidalRomanizeKey = settings.romanizeLyrics
|
||||||
|
? `${tidalTexts.join("\n")}\0r`
|
||||||
|
: null;
|
||||||
|
isActive = true;
|
||||||
|
lyricsMode = "line-tidal";
|
||||||
|
hideTidalLyrics();
|
||||||
|
const tidalResult = buildTidalLines(romanized);
|
||||||
|
lines = tidalResult.lines;
|
||||||
|
if (lines.length === 0) {
|
||||||
|
trace.log("No TIDAL lines available yet");
|
||||||
|
teardown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
watchForRerender();
|
||||||
|
startTidalFollowLoop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sylTrace(
|
||||||
|
`RL API: loaded ${response.data.length} lines (source: ${response.metadata.source})`,
|
||||||
|
);
|
||||||
|
sylLog(
|
||||||
|
`[RL-Syllable] Loaded "${trackInfo.title}" by "${trackInfo.artist}" — ${response.data.length} lines`,
|
||||||
|
);
|
||||||
|
|
||||||
|
lyricsMode = response.type === "Word" ? "word" : "line-api";
|
||||||
|
if (!ensureLyricsTab()) {
|
||||||
|
trace.log("Could not create/find lyrics tab container");
|
||||||
teardown();
|
teardown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (injectedTabEl && settings.stickyLyrics) {
|
||||||
|
showInjectedLyricsTab();
|
||||||
|
safeTimeout(
|
||||||
|
unloads,
|
||||||
|
() => {
|
||||||
|
if (!settings.stickyLyrics || token !== trackChangeToken) return;
|
||||||
|
showInjectedLyricsTab();
|
||||||
|
},
|
||||||
|
180,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lyricsData =
|
||||||
|
response.type === "Word"
|
||||||
|
? response.data
|
||||||
|
: normalizeLineData(response.data);
|
||||||
|
lyricsResponse = response;
|
||||||
|
isActive = true;
|
||||||
|
if (!lyricsData || lyricsData.length === 0) {
|
||||||
|
trace.log("Lyrics payload had no usable lines");
|
||||||
|
teardown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Tidal classes
|
||||||
|
hideTidalLyrics();
|
||||||
|
|
||||||
|
// Build word spans and line entries
|
||||||
|
const result = buildWordSpans();
|
||||||
|
lines = result.lines;
|
||||||
|
|
||||||
|
// Watch React re-renders
|
||||||
watchForRerender();
|
watchForRerender();
|
||||||
startTidalFollowLoop();
|
|
||||||
return;
|
// Start the highlight loop
|
||||||
|
startTickLoop();
|
||||||
|
} finally {
|
||||||
|
if (runId === trackChangeRunSeq) {
|
||||||
|
isTrackChangeRunning = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sylTrace(
|
|
||||||
`RL API: loaded ${response.data.length} lines (source: ${response.metadata.source})`,
|
|
||||||
);
|
|
||||||
sylLog(
|
|
||||||
`[RL-Syllable] Loaded "${trackInfo.title}" by "${trackInfo.artist}" — ${response.data.length} lines`,
|
|
||||||
);
|
|
||||||
|
|
||||||
lyricsMode = response.type === "Word" ? "word" : "line-api";
|
|
||||||
if (!ensureLyricsTab()) {
|
|
||||||
trace.log("Could not create/find lyrics tab container");
|
|
||||||
teardown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (injectedTabEl && settings.stickyLyrics) {
|
|
||||||
showInjectedLyricsTab();
|
|
||||||
safeTimeout(
|
|
||||||
unloads,
|
|
||||||
() => {
|
|
||||||
if (!settings.stickyLyrics || token !== trackChangeToken) return;
|
|
||||||
showInjectedLyricsTab();
|
|
||||||
},
|
|
||||||
180,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
lyricsData =
|
|
||||||
response.type === "Word" ? response.data : normalizeLineData(response.data);
|
|
||||||
lyricsResponse = response;
|
|
||||||
isActive = true;
|
|
||||||
if (!lyricsData || lyricsData.length === 0) {
|
|
||||||
trace.log("Lyrics payload had no usable lines");
|
|
||||||
teardown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove Tidal classes
|
|
||||||
hideTidalLyrics();
|
|
||||||
|
|
||||||
// Build word spans and line entries
|
|
||||||
const result = buildWordSpans();
|
|
||||||
lines = result.lines;
|
|
||||||
|
|
||||||
// Watch React re-renders
|
|
||||||
watchForRerender();
|
|
||||||
|
|
||||||
// Start the highlight loop
|
|
||||||
startTickLoop();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reapply word lyrics (for tab switch back)
|
// Reapply word lyrics (for tab switch back)
|
||||||
|
|||||||
Reference in New Issue
Block a user