mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-17 19:33:10 +10:00
Flow Refactor
This commit is contained in:
@@ -8,6 +8,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
// Define a typed onChange signature for the switch
|
||||
type SwitchChangeHandler = (
|
||||
event: React.ChangeEvent<HTMLInputElement> | null,
|
||||
checked: boolean,
|
||||
) => void;
|
||||
|
||||
export type ColoramaMode =
|
||||
| "single"
|
||||
| "gradient-experimental"
|
||||
@@ -60,9 +66,12 @@ export const Settings = () => {
|
||||
const [activeEndpoint, setActiveEndpoint] = React.useState<
|
||||
"single" | "start" | "end"
|
||||
>("single");
|
||||
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<{
|
||||
title: string;
|
||||
desc?: string;
|
||||
checked: boolean;
|
||||
onChange: SwitchChangeHandler;
|
||||
}>;
|
||||
|
||||
// Helper for HEX normalization
|
||||
const normalizeToRGB = (
|
||||
@@ -133,19 +142,19 @@ export const Settings = () => {
|
||||
if (!hexColorRegex.test(trimmed)) return;
|
||||
if (mode === "single") {
|
||||
const next = normalizeToRGB(trimmed);
|
||||
settings.singleColor = next;
|
||||
setSingleColor(next);
|
||||
settings.singleColor = next;
|
||||
if (updateInput) setCustomInput(next);
|
||||
} else if (mode === "gradient-experimental") {
|
||||
const norm = normalizeToRGB(trimmed);
|
||||
const next = normalizeToRGB(trimmed);
|
||||
if (activeEndpoint === "end") {
|
||||
settings.gradientEnd = norm;
|
||||
setGradientEnd(norm);
|
||||
setGradientEnd(next);
|
||||
settings.gradientEnd = next;
|
||||
} else {
|
||||
settings.gradientStart = norm;
|
||||
setGradientStart(norm);
|
||||
setGradientStart(next);
|
||||
settings.gradientStart = next;
|
||||
}
|
||||
if (updateInput) setCustomInput(norm);
|
||||
if (updateInput) setCustomInput(next);
|
||||
}
|
||||
requestApply();
|
||||
};
|
||||
@@ -500,21 +509,20 @@ export const Settings = () => {
|
||||
key={color}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const next = normalizeToRGB(color);
|
||||
if (mode === "single") {
|
||||
const next = normalizeToRGB(color);
|
||||
settings.singleColor = next;
|
||||
setSingleColor(next);
|
||||
settings.singleColor = next;
|
||||
} else if (mode === "gradient-experimental") {
|
||||
const next = normalizeToRGB(color);
|
||||
if (activeEndpoint === "end") {
|
||||
settings.gradientEnd = next;
|
||||
setGradientEnd(next);
|
||||
settings.gradientEnd = next;
|
||||
} else {
|
||||
settings.gradientStart = next;
|
||||
setGradientStart(next);
|
||||
settings.gradientStart = next;
|
||||
}
|
||||
}
|
||||
setCustomInput(normalizeToRGB(color));
|
||||
setCustomInput(next);
|
||||
requestApply();
|
||||
}}
|
||||
style={{
|
||||
@@ -610,8 +618,8 @@ export const Settings = () => {
|
||||
value={singleAlpha}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
settings.singleAlpha = value;
|
||||
setSingleAlpha(value);
|
||||
settings.singleAlpha = value;
|
||||
requestApply();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
@@ -653,8 +661,8 @@ export const Settings = () => {
|
||||
value={gradientStartAlpha}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
settings.gradientStartAlpha = value;
|
||||
setGradientStartAlpha(value);
|
||||
settings.gradientStartAlpha = value;
|
||||
requestApply();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
@@ -692,8 +700,8 @@ export const Settings = () => {
|
||||
value={gradientEndAlpha}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
settings.gradientEndAlpha = value;
|
||||
setGradientEndAlpha(value);
|
||||
settings.gradientEndAlpha = value;
|
||||
requestApply();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
@@ -727,8 +735,8 @@ export const Settings = () => {
|
||||
value={gradientAngle}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
settings.gradientAngle = value;
|
||||
setGradientAngle(value);
|
||||
settings.gradientAngle = value;
|
||||
requestApply();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
@@ -762,8 +770,8 @@ export const Settings = () => {
|
||||
value={gradientAngle}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
settings.gradientAngle = value;
|
||||
setGradientAngle(value);
|
||||
settings.gradientAngle = value;
|
||||
requestApply();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
@@ -794,7 +802,7 @@ export const Settings = () => {
|
||||
title="Exclude Inactive"
|
||||
desc="Apply color/gradient only to the currently active lyric line"
|
||||
checked={excludeInactive}
|
||||
onChange={(_: unknown, checked: boolean) => {
|
||||
onChange={(_event: React.ChangeEvent<HTMLInputElement> | null, checked: boolean) => {
|
||||
settings.excludeInactive = checked;
|
||||
setExcludeInactive(checked);
|
||||
requestApply();
|
||||
|
||||
@@ -41,25 +41,39 @@ const onMouseUp = (): void => {
|
||||
if (selection?.toString().length > 0) {
|
||||
const selectedSpans: HTMLSpanElement[] = [];
|
||||
const range = selection.getRangeAt(0);
|
||||
const container = range.commonAncestorContainer;
|
||||
let container: Node | null = range.commonAncestorContainer;
|
||||
|
||||
// If the container is NOT an element and a document, adjust it.
|
||||
// Normalize container: if it's a text node, use its parent element/node
|
||||
if (container && container.nodeType === Node.TEXT_NODE) {
|
||||
container = (container.parentElement ?? container.parentNode) as Node | null;
|
||||
}
|
||||
|
||||
// If parent has data-current, treat as single-line copy case
|
||||
if (
|
||||
container.nodeType !== Node.ELEMENT_NODE &&
|
||||
container.nodeType !== Node.DOCUMENT_NODE
|
||||
container &&
|
||||
container.nodeType === Node.ELEMENT_NODE &&
|
||||
(container as Element).hasAttribute("data-current")
|
||||
) {
|
||||
// Get the parent element if it's a text node
|
||||
const parentElement = container.parentElement;
|
||||
if (parentElement?.hasAttribute("data-current")) {
|
||||
const text_ = selection.toString().trim();
|
||||
SetClipboard(text_);
|
||||
trace.msg.log("Copied to clipboard!");
|
||||
return;
|
||||
}
|
||||
const text_ = selection.toString().trim();
|
||||
SetClipboard(text_);
|
||||
trace.msg.log("Copied to clipboard!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we have an Element or Document before querying
|
||||
if (
|
||||
!container ||
|
||||
(container.nodeType !== Node.ELEMENT_NODE &&
|
||||
container.nodeType !== Node.DOCUMENT_NODE)
|
||||
) {
|
||||
isSelecting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all the spans inside the container.
|
||||
const spans = (container as Element).getElementsByTagName("span");
|
||||
const spans = (container as Element | Document).getElementsByTagName(
|
||||
"span",
|
||||
);
|
||||
for (const span of spans) {
|
||||
if (selection.containsNode(span, true)) {
|
||||
selectedSpans.push(span as HTMLSpanElement);
|
||||
|
||||
@@ -400,8 +400,7 @@ document.addEventListener(
|
||||
const eventX = event.clientX;
|
||||
const eventY = event.clientY;
|
||||
|
||||
// Prevent default immediately if we plan to handle it
|
||||
event.preventDefault();
|
||||
// Allow native context menu by default; we'll show our custom menu only if needed
|
||||
|
||||
// Wait to see if the built-in context menu appears
|
||||
contextMenuTimeout = window.setTimeout(() => {
|
||||
|
||||
@@ -31,11 +31,17 @@ const getQualityColor = (audioQuality: string): string => {
|
||||
return QUALITY_COLORS.MAX;
|
||||
} else if (quality?.includes("LOSSLESS")) {
|
||||
return QUALITY_COLORS.HIGH;
|
||||
} else {
|
||||
} else if (quality?.includes("HIGH")) {
|
||||
return QUALITY_COLORS.HIGH;
|
||||
} else if (quality?.includes("LOW")) {
|
||||
return QUALITY_COLORS.LOW;
|
||||
}
|
||||
return QUALITY_COLORS.LOW;
|
||||
};
|
||||
|
||||
// Interval tracking for quality monitoring
|
||||
let qualityMonitoringIntervalId: number | null = null;
|
||||
|
||||
// Function to Reset Seek Bar Color (if setting gets disabled while playing)
|
||||
const resetSeekBarColor = async (): Promise<void> => {
|
||||
try {
|
||||
@@ -82,17 +88,16 @@ const applyQualityColors = async (): Promise<void> => {
|
||||
|
||||
// Function to monitor track changes using track ID
|
||||
const setupQualityMonitoring = (): void => {
|
||||
if (qualityMonitoringIntervalId != null) return;
|
||||
let lastTrackId: string | null = null;
|
||||
const interval = setInterval(() => {
|
||||
qualityMonitoringIntervalId = window.setInterval(() => {
|
||||
if (!settings.qualityColorMatchedSeekBar) return;
|
||||
const currentTrackId = PlayState.playbackContext?.actualProductId;
|
||||
if (currentTrackId && currentTrackId !== lastTrackId) {
|
||||
//trace.msg.log(`[OLED Theme] Track ID changed: ${lastTrackId} -> ${currentTrackId}`);
|
||||
lastTrackId = currentTrackId;
|
||||
applyQualityColors();
|
||||
}
|
||||
}, 250);
|
||||
unloads.add(() => clearInterval(interval));
|
||||
|
||||
// Initial color application (if a track is already loaded)
|
||||
const currentTrackId = PlayState.playbackContext?.actualProductId;
|
||||
@@ -102,6 +107,13 @@ const setupQualityMonitoring = (): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const stopQualityMonitoring = (): void => {
|
||||
if (qualityMonitoringIntervalId != null) {
|
||||
window.clearInterval(qualityMonitoringIntervalId);
|
||||
qualityMonitoringIntervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to apply theme styles based on current settings
|
||||
const applyThemeStyles = (): void => {
|
||||
// Choose the appropriate CSS file based on settings
|
||||
@@ -126,8 +138,9 @@ const applyThemeStyles = (): void => {
|
||||
);
|
||||
setupQualityMonitoring();
|
||||
} else {
|
||||
// If disabling, reset the seek bar color
|
||||
// If disabling, reset the seek bar color and stop monitoring
|
||||
resetSeekBarColor();
|
||||
stopQualityMonitoring();
|
||||
}
|
||||
|
||||
// Apply the selected theme using StyleTag
|
||||
@@ -145,3 +158,8 @@ window.updateOLEDThemeStyles = applyThemeStyles;
|
||||
|
||||
// Apply the OLED theme initially
|
||||
applyThemeStyles();
|
||||
|
||||
// Ensure interval is cleared on unload
|
||||
unloads.add(() => {
|
||||
stopQualityMonitoring();
|
||||
});
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
[data-test="current-media-imagery"] {
|
||||
border: 0 !important;
|
||||
margin: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[class^="_imageBorder"] {
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
[data-test="current-media-imagery"] {
|
||||
border: 0 !important;
|
||||
margin: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[class^="_imageBorder"] {
|
||||
|
||||
@@ -65,7 +65,7 @@ export const Settings = () => {
|
||||
title="Lyrics Glow Effect"
|
||||
desc="Enable glowing effect for lyrics & Font Stytling Changes"
|
||||
checked={lyricsGlowEnabled}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
setLyricsGlowEnabled((settings.lyricsGlowEnabled = checked));
|
||||
// Update styles immediately when setting changes
|
||||
if ((window as any).updateRadiantLyricsStyles) {
|
||||
@@ -77,7 +77,7 @@ export const Settings = () => {
|
||||
title="Track Title Glow"
|
||||
desc="Apply glow to the track title"
|
||||
checked={trackTitleGlow}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
setTrackTitleGlow((settings.trackTitleGlow = checked));
|
||||
if ((window as any).updateRadiantLyricsStyles) {
|
||||
(window as any).updateRadiantLyricsStyles();
|
||||
@@ -88,7 +88,7 @@ export const Settings = () => {
|
||||
title="Hide UI Feature"
|
||||
desc="Enable hide/unhide UI functionality with toggle buttons"
|
||||
checked={hideUIEnabled}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
setHideUIEnabled((settings.hideUIEnabled = checked));
|
||||
}}
|
||||
/>
|
||||
@@ -96,7 +96,7 @@ export const Settings = () => {
|
||||
title="Player Bar Visibility in Hide UI Mode"
|
||||
desc="Keep player bar visible when UI is hidden"
|
||||
checked={playerBarVisible}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
console.log("Player Bar Visibility:", checked ? "visible" : "hidden");
|
||||
setPlayerBarVisible((settings.playerBarVisible = checked));
|
||||
// Update styles immediately when setting changes
|
||||
@@ -109,7 +109,7 @@ export const Settings = () => {
|
||||
title="Cover Everywhere"
|
||||
desc="Apply the spinning Cover Art background to the entire app, not just the Now Playing view, Heavily Inspired by Cover-Theme by @Inrixia"
|
||||
checked={spinningCoverEverywhere}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
console.log(
|
||||
"Spinning Cover Everywhere:",
|
||||
checked ? "enabled" : "disabled",
|
||||
@@ -127,7 +127,7 @@ export const Settings = () => {
|
||||
title="Performance Mode | Experimental"
|
||||
desc="Performance mode: Reduces blur effects & uses smaller image sizes, to optimize GPU usage"
|
||||
checked={performanceMode}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
console.log("Performance Mode:", checked ? "enabled" : "disabled");
|
||||
setPerformanceMode((settings.performanceMode = checked));
|
||||
// Update background animations immediately when setting changes
|
||||
@@ -143,7 +143,7 @@ export const Settings = () => {
|
||||
title="Background Cover Spin" // Cheers @Max/n0201 for the idea <3
|
||||
desc="Enable the spinning cover art background animation"
|
||||
checked={spinningArtEnabled}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
console.log(
|
||||
"Background Cover Spin:",
|
||||
checked ? "enabled" : "disabled",
|
||||
@@ -262,7 +262,7 @@ export const Settings = () => {
|
||||
title="Settings Affect Now Playing"
|
||||
desc="Apply background settings to Now Playing view"
|
||||
checked={settingsAffectNowPlaying}
|
||||
onChange={(_event: unknown, checked: boolean) => {
|
||||
onChange={(_: void, checked: boolean) => {
|
||||
console.log(
|
||||
"Settings Affect Now Playing:",
|
||||
checked ? "enabled" : "disabled",
|
||||
|
||||
@@ -38,30 +38,30 @@ const updateRadiantLyricsTextGlow = function (): void {
|
||||
// Function to update styles when settings change
|
||||
const updateRadiantLyricsStyles = function (): void {
|
||||
if (isHidden) {
|
||||
// Apply only base styles (button hiding), NOT separated lyrics styles
|
||||
// to avoid affecting lyrics scrolling behavior
|
||||
// Apply only base styles (button hiding) and optional player bar hiding
|
||||
baseStyleTag.css = baseStyles;
|
||||
|
||||
// Apply player bar styles based on setting
|
||||
if (!settings.playerBarVisible) {
|
||||
playerBarStyleTag.css = playerBarHidden;
|
||||
} else {
|
||||
playerBarStyleTag.remove();
|
||||
}
|
||||
// Ensure lyrics glow styles are not applied when hidden
|
||||
lyricsGlowStyleTag.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update lyrics glow based on setting (only if UI is not hidden to avoid interference)
|
||||
// Update lyrics glow based on setting (only when UI is visible)
|
||||
const lyricsContainer = document.querySelector('[class^="_lyricsContainer"]');
|
||||
if (lyricsContainer && !isHidden) {
|
||||
if (lyricsContainer) {
|
||||
if (settings.lyricsGlowEnabled) {
|
||||
lyricsContainer.classList.remove("lyrics-glow-disabled");
|
||||
(lyricsContainer as HTMLElement).classList.remove("lyrics-glow-disabled");
|
||||
lyricsGlowStyleTag.css = lyricsGlow;
|
||||
updateRadiantLyricsTextGlow();
|
||||
} else {
|
||||
lyricsContainer.classList.add("lyrics-glow-disabled");
|
||||
(lyricsContainer as HTMLElement).classList.add("lyrics-glow-disabled");
|
||||
lyricsGlowStyleTag.remove();
|
||||
}
|
||||
} else if (!isHidden) {
|
||||
} else {
|
||||
observePromise<HTMLElement>(unloads, '[class^="_lyricsContainer"]')
|
||||
.then((el) => {
|
||||
if (!el) return;
|
||||
@@ -95,6 +95,16 @@ const updateRadiantLyricsStyles = function (): void {
|
||||
var isHidden = false;
|
||||
let unhideButtonAutoFadeTimeout: number | null = null;
|
||||
|
||||
// Helper to safely create a one-off timeout that clears previous if any
|
||||
const safelySetAutoFadeTimeout = (
|
||||
existingId: number | null,
|
||||
fn: () => void,
|
||||
delay: number,
|
||||
): number => {
|
||||
if (existingId != null) window.clearTimeout(existingId);
|
||||
return window.setTimeout(fn, delay);
|
||||
};
|
||||
|
||||
const updateButtonStates = function (): void {
|
||||
const hideButton = document.querySelector(".hide-ui-button") as HTMLElement;
|
||||
const unhideButton = document.querySelector(
|
||||
@@ -120,7 +130,7 @@ const updateButtonStates = function (): void {
|
||||
}
|
||||
if (unhideButton) {
|
||||
// Clear any existing auto-fade timeout
|
||||
if (unhideButtonAutoFadeTimeout) {
|
||||
if (unhideButtonAutoFadeTimeout != null) {
|
||||
window.clearTimeout(unhideButtonAutoFadeTimeout);
|
||||
unhideButtonAutoFadeTimeout = null;
|
||||
}
|
||||
@@ -137,11 +147,15 @@ const updateButtonStates = function (): void {
|
||||
unhideButton.style.pointerEvents = "auto";
|
||||
|
||||
// Set up auto-fade after 2 seconds
|
||||
unhideButtonAutoFadeTimeout = window.setTimeout(() => {
|
||||
if (isHidden && unhideButton && !unhideButton.matches(":hover")) {
|
||||
unhideButton.classList.add("auto-faded");
|
||||
}
|
||||
}, 2000);
|
||||
unhideButtonAutoFadeTimeout = safelySetAutoFadeTimeout(
|
||||
unhideButtonAutoFadeTimeout,
|
||||
() => {
|
||||
if (isHidden && unhideButton && !unhideButton.matches(":hover")) {
|
||||
unhideButton.classList.add("auto-faded");
|
||||
}
|
||||
},
|
||||
2000,
|
||||
);
|
||||
}, 50);
|
||||
} else {
|
||||
// Smooth fade out for Unhide UI button
|
||||
@@ -771,7 +785,7 @@ unloads.add(() => {
|
||||
cleanUpDynamicArt();
|
||||
|
||||
// Clean up auto-fade timeout
|
||||
if (unhideButtonAutoFadeTimeout) {
|
||||
if (unhideButtonAutoFadeTimeout != null) {
|
||||
window.clearTimeout(unhideButtonAutoFadeTimeout);
|
||||
unhideButtonAutoFadeTimeout = null;
|
||||
}
|
||||
|
||||
@@ -9,22 +9,19 @@
|
||||
@font-face {
|
||||
font-family: "AbyssFont";
|
||||
font-weight: 500;
|
||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2")
|
||||
format("woff2");
|
||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "AbyssFont";
|
||||
font-weight: 600;
|
||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2")
|
||||
format("woff2");
|
||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "AbyssFont";
|
||||
font-weight: 700;
|
||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2")
|
||||
format("woff2");
|
||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
/* Enhanced lyrics styling with glow effects */
|
||||
|
||||
Reference in New Issue
Block a user