Flow Refactor

This commit is contained in:
2025-09-09 19:01:12 +10:00
parent 0d9b378e43
commit 11d08b6403
9 changed files with 124 additions and 74 deletions
+30 -22
View File
@@ -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();
+22 -8
View File
@@ -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;
}
// 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);
+1 -2
View File
@@ -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(() => {
+23 -5
View File
@@ -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();
});
+1 -1
View File
@@ -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"] {
+8 -8
View File
@@ -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",
+27 -13
View File
@@ -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(() => {
unhideButtonAutoFadeTimeout = safelySetAutoFadeTimeout(
unhideButtonAutoFadeTimeout,
() => {
if (isHidden && unhideButton && !unhideButton.matches(":hover")) {
unhideButton.classList.add("auto-faded");
}
}, 2000);
},
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 */