mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
814 lines
20 KiB
TypeScript
814 lines
20 KiB
TypeScript
import { ReactiveStore } from "@luna/core";
|
|
import { LunaSettings, LunaSwitchSetting } from "@luna/ui";
|
|
import React from "react";
|
|
|
|
declare global {
|
|
interface Window {
|
|
applyColoramaLyrics?: () => void;
|
|
}
|
|
}
|
|
|
|
// Define a typed onChange signature for the switch
|
|
type SwitchChangeHandler = (
|
|
event: React.ChangeEvent<HTMLInputElement> | null,
|
|
checked: boolean,
|
|
) => void;
|
|
|
|
export type ColoramaMode =
|
|
| "single"
|
|
| "gradient-experimental"
|
|
| "cover"
|
|
| "cover-gradient";
|
|
|
|
export const settings = await ReactiveStore.getPluginStorage("ColoramaLyrics", {
|
|
enabled: true,
|
|
mode: "single" as ColoramaMode,
|
|
// Store colors as RGB hex (#RRGGBB) and opacity separately (0-100)
|
|
singleColor: "#FFFFFF",
|
|
singleAlpha: 100,
|
|
gradientStart: "#FFFFFF",
|
|
gradientStartAlpha: 100,
|
|
gradientEnd: "#AAFFFF",
|
|
gradientEndAlpha: 100,
|
|
gradientAngle: 0,
|
|
customColors: [] as string[],
|
|
excludeInactive: false,
|
|
});
|
|
|
|
export const Settings = () => {
|
|
// const [enabled, setEnabled] = React.useState(settings.enabled);
|
|
const [mode, setMode] = React.useState<ColoramaMode>(settings.mode);
|
|
const [singleColor, setSingleColor] = React.useState(settings.singleColor);
|
|
const [singleAlpha, setSingleAlpha] = React.useState<number>(
|
|
settings.singleAlpha ?? 100,
|
|
);
|
|
const [gradientStart, setGradientStart] = React.useState(
|
|
settings.gradientStart,
|
|
);
|
|
const [gradientStartAlpha, setGradientStartAlpha] = React.useState<number>(
|
|
settings.gradientStartAlpha ?? 100,
|
|
);
|
|
const [gradientEnd, setGradientEnd] = React.useState(settings.gradientEnd);
|
|
const [gradientEndAlpha, setGradientEndAlpha] = React.useState<number>(
|
|
settings.gradientEndAlpha ?? 100,
|
|
);
|
|
const [gradientAngle, setGradientAngle] = React.useState(
|
|
settings.gradientAngle,
|
|
);
|
|
const [customInput, setCustomInput] = React.useState(settings.singleColor);
|
|
const [customColors, setCustomColors] = React.useState(settings.customColors);
|
|
const [showPicker, setShowPicker] = React.useState(false);
|
|
const [isAnimatingIn, setIsAnimatingIn] = React.useState(false);
|
|
const [shouldRender, setShouldRender] = React.useState(false);
|
|
const [excludeInactive, setExcludeInactive] = React.useState(
|
|
settings.excludeInactive,
|
|
);
|
|
const [activeEndpoint, setActiveEndpoint] = React.useState<
|
|
"single" | "start" | "end"
|
|
>("single");
|
|
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<{
|
|
title: string;
|
|
desc?: string;
|
|
checked: boolean;
|
|
onChange: SwitchChangeHandler;
|
|
}>;
|
|
|
|
// Helper for HEX normalization
|
|
const normalizeToRGB = (
|
|
hex: string,
|
|
fallback: string = "#FFFFFF",
|
|
): string => {
|
|
let v = hex.trim().toLowerCase();
|
|
if (!v.startsWith("#")) v = `#${v}`;
|
|
// #rgb or #rgba -> expand
|
|
if (/^#([0-9a-f]{3,4})$/.test(v)) {
|
|
const m = v.slice(1);
|
|
const r = m[0];
|
|
const g = m[1];
|
|
const b = m[2];
|
|
// ignore alpha if provided (#rgba)
|
|
return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
|
|
}
|
|
// #aarrggbb -> strip alpha
|
|
if (/^#([0-9a-f]{8})$/.test(v)) {
|
|
const rrggbb = v.slice(3);
|
|
return `#${rrggbb}`.toUpperCase();
|
|
}
|
|
// #rrggbb
|
|
if (/^#([0-9a-f]{6})$/.test(v)) return v.toUpperCase();
|
|
return fallback;
|
|
};
|
|
|
|
const colorPresets = [
|
|
"#FFFFFF",
|
|
"#FF0000",
|
|
"#00FF00",
|
|
"#0000FF",
|
|
"#FFFF00",
|
|
"#FF00FF",
|
|
"#00FFFF",
|
|
"#FF8800",
|
|
"#8800FF",
|
|
"#0088FF",
|
|
"#88FF00",
|
|
"#FF0088",
|
|
"#00FF88",
|
|
"#444444",
|
|
"#888888",
|
|
"#CCCCCC",
|
|
"#1DB954",
|
|
"#E22134",
|
|
"#1976D2",
|
|
];
|
|
|
|
const openPicker = (endpoint: "single" | "start" | "end" = "single") => {
|
|
setActiveEndpoint(endpoint);
|
|
setShowPicker(true);
|
|
setShouldRender(true);
|
|
setTimeout(() => setIsAnimatingIn(true), 10);
|
|
};
|
|
const closePicker = () => {
|
|
setIsAnimatingIn(false);
|
|
setTimeout(() => {
|
|
setShowPicker(false);
|
|
setShouldRender(false);
|
|
}, 200);
|
|
};
|
|
|
|
const hexColorRegex = /^#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})$/i;
|
|
|
|
const applyCustomInputColor = (raw: string, updateInput: boolean): void => {
|
|
const trimmed = raw.trim();
|
|
if (!hexColorRegex.test(trimmed)) return;
|
|
if (mode === "single") {
|
|
const next = normalizeToRGB(trimmed);
|
|
settings.singleColor = next;
|
|
setSingleColor(next);
|
|
if (updateInput) setCustomInput(next);
|
|
} else if (mode === "gradient-experimental") {
|
|
const next = normalizeToRGB(trimmed);
|
|
if (activeEndpoint === "end") {
|
|
settings.gradientEnd = next;
|
|
setGradientEnd(next);
|
|
} else {
|
|
settings.gradientStart = next;
|
|
setGradientStart(next);
|
|
}
|
|
if (updateInput) setCustomInput(next);
|
|
}
|
|
requestApply();
|
|
};
|
|
|
|
const addCustomColor = () => {
|
|
const trimmed = customInput.trim();
|
|
if (
|
|
hexColorRegex.test(trimmed) &&
|
|
!colorPresets.includes(trimmed) &&
|
|
!customColors.includes(normalizeToRGB(trimmed))
|
|
) {
|
|
const updated = [...customColors, normalizeToRGB(trimmed)];
|
|
setCustomColors(updated);
|
|
settings.customColors = updated;
|
|
}
|
|
};
|
|
|
|
// const removeCustomColor = (color: string) => {
|
|
// const updated = customColors.filter((c) => c !== color);
|
|
// setCustomColors(updated);
|
|
// settings.customColors = updated;
|
|
// };
|
|
|
|
const allColors = [...colorPresets, ...customColors];
|
|
|
|
const requestApply = () => {
|
|
window.applyColoramaLyrics?.();
|
|
};
|
|
|
|
return (
|
|
<LunaSettings>
|
|
{/* Mode selection via dropdown (aligned right) */}
|
|
<div
|
|
style={{
|
|
padding: "8px 0",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 12,
|
|
}}
|
|
>
|
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
<div style={{ fontWeight: "normal", fontSize: "1.075rem" }}>Mode</div>
|
|
<div style={{ opacity: 0.7, fontSize: 14 }}>
|
|
Choose how lyrics are colored
|
|
</div>
|
|
</div>
|
|
<select
|
|
value={mode}
|
|
onChange={(e) => {
|
|
const next = e.target.value as ColoramaMode;
|
|
settings.mode = next;
|
|
setMode(next);
|
|
requestApply();
|
|
}}
|
|
style={{
|
|
padding: "6px 10px",
|
|
borderRadius: 6,
|
|
border: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.08)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
marginLeft: "auto",
|
|
minWidth: 180,
|
|
}}
|
|
>
|
|
<option value="single" style={{ color: "#000", background: "#fff" }}>
|
|
Single
|
|
</option>
|
|
<option
|
|
value="gradient-experimental"
|
|
style={{ color: "#000", background: "#fff" }}
|
|
>
|
|
Gradient - Experimental
|
|
</option>
|
|
<option value="cover" style={{ color: "#000", background: "#fff" }}>
|
|
Cover - Experimental
|
|
</option>
|
|
<option
|
|
value="cover-gradient"
|
|
style={{ color: "#000", background: "#fff" }}
|
|
>
|
|
Cover (Gradient) - Experimental
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Single color */}
|
|
<div
|
|
style={{
|
|
padding: "8px 0",
|
|
display: mode === "single" ? "flex" : "none",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<div>
|
|
<div
|
|
style={{
|
|
fontWeight: "normal",
|
|
fontSize: "1.075rem",
|
|
marginBottom: 4,
|
|
}}
|
|
>
|
|
Lyrics Color
|
|
</div>
|
|
<div style={{ opacity: 0.7, fontSize: 14 }}>Set lyrics color</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
gap: 8,
|
|
alignItems: "center",
|
|
position: "relative",
|
|
}}
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={() => (showPicker ? closePicker() : openPicker("single"))}
|
|
style={{
|
|
width: 32,
|
|
height: 32,
|
|
border: "1px solid rgba(255,255,255,0.15)",
|
|
borderRadius: 6,
|
|
cursor: "pointer",
|
|
background: normalizeToRGB(singleColor),
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Gradient controls (open picker) */}
|
|
<div
|
|
style={{
|
|
padding: "8px 0",
|
|
display: mode === "gradient-experimental" ? "flex" : "none",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<div>
|
|
<div
|
|
style={{
|
|
fontWeight: "normal",
|
|
fontSize: "1.075rem",
|
|
marginBottom: 4,
|
|
}}
|
|
>
|
|
Gradient (Experimental)
|
|
</div>
|
|
<div style={{ opacity: 0.7, fontSize: 14 }}>Set colors & angle</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setCustomInput(gradientStart);
|
|
openPicker("start");
|
|
}}
|
|
style={{
|
|
padding: "8px 12px",
|
|
borderRadius: 8,
|
|
border: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.08)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
Configure
|
|
</button>
|
|
</div>
|
|
|
|
{/* Cover gradient controls (open picker for angle) */}
|
|
<div
|
|
style={{
|
|
padding: "8px 0",
|
|
display: mode === "cover-gradient" ? "flex" : "none",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<div>
|
|
<div
|
|
style={{
|
|
fontWeight: "normal",
|
|
fontSize: "1.075rem",
|
|
marginBottom: 4,
|
|
}}
|
|
>
|
|
Cover (Gradient) - Experimental
|
|
</div>
|
|
<div style={{ opacity: 0.7, fontSize: 14 }}>Set angle</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => openPicker("start")}
|
|
style={{
|
|
padding: "8px 12px",
|
|
borderRadius: 8,
|
|
border: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.08)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
Configure
|
|
</button>
|
|
</div>
|
|
|
|
{/* Modal for picking and managing colors (reused) */}
|
|
{shouldRender && (
|
|
<>
|
|
<button
|
|
style={{
|
|
position: "fixed",
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
background: "rgba(0,0,0,0.6)",
|
|
zIndex: 1000,
|
|
opacity: isAnimatingIn ? 1 : 0,
|
|
transition: "opacity 0.2s ease",
|
|
}}
|
|
type="button"
|
|
aria-label="Close color picker"
|
|
onClick={closePicker}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" || e.key === "Escape") closePicker();
|
|
}}
|
|
/>
|
|
<div
|
|
style={{
|
|
position: "fixed",
|
|
top: "50%",
|
|
left: "50%",
|
|
background: "rgba(20,20,20,0.98)",
|
|
backdropFilter: "blur(20px)",
|
|
WebkitBackdropFilter: "blur(20px)",
|
|
border: "1px solid rgba(255,255,255,0.15)",
|
|
borderRadius: 16,
|
|
padding: 20,
|
|
minWidth: 320,
|
|
maxWidth: "90vw",
|
|
maxHeight: "90vh",
|
|
zIndex: 1001,
|
|
boxShadow: "0 20px 40px rgba(0,0,0,0.7)",
|
|
opacity: isAnimatingIn ? 1 : 0,
|
|
transform: isAnimatingIn
|
|
? "translate(-50%, -50%) scale(1)"
|
|
: "translate(-50%, -50%) scale(0.9)",
|
|
transition: "all 0.2s ease",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
marginBottom: 12,
|
|
color: "#fff",
|
|
fontWeight: "bold",
|
|
fontSize: 14,
|
|
}}
|
|
>
|
|
{mode === "single" ? "Single Color" : "Gradient Colors"}
|
|
</div>
|
|
{mode === "gradient-experimental" && (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
gap: 8,
|
|
alignItems: "center",
|
|
marginBottom: 12,
|
|
}}
|
|
>
|
|
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: 12 }}>
|
|
Editing
|
|
</div>
|
|
<button
|
|
onClick={() => {
|
|
setActiveEndpoint("start");
|
|
setCustomInput(gradientStart);
|
|
}}
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 8,
|
|
padding: "6px 10px",
|
|
borderRadius: 8,
|
|
border:
|
|
activeEndpoint === "start"
|
|
? "1px solid rgba(255,255,255,0.5)"
|
|
: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.05)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
}}
|
|
type="button"
|
|
>
|
|
<span
|
|
style={{
|
|
width: 14,
|
|
height: 14,
|
|
borderRadius: 3,
|
|
background: normalizeToRGB(gradientStart),
|
|
border: "1px solid rgba(255,255,255,0.3)",
|
|
}}
|
|
/>
|
|
<span style={{ fontSize: 12 }}>Start</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setActiveEndpoint("end");
|
|
setCustomInput(gradientEnd);
|
|
}}
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 8,
|
|
padding: "6px 10px",
|
|
borderRadius: 8,
|
|
border:
|
|
activeEndpoint === "end"
|
|
? "1px solid rgba(255,255,255,0.5)"
|
|
: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.05)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
<span
|
|
style={{
|
|
width: 14,
|
|
height: 14,
|
|
borderRadius: 3,
|
|
background: normalizeToRGB(gradientEnd),
|
|
border: "1px solid rgba(255,255,255,0.3)",
|
|
}}
|
|
/>
|
|
<span style={{ fontSize: 12 }}>End</span>
|
|
</button>
|
|
</div>
|
|
)}
|
|
{mode !== "cover-gradient" && (
|
|
<div
|
|
style={{
|
|
display: "grid",
|
|
gridTemplateColumns: "repeat(7, 1fr)",
|
|
gap: 8,
|
|
marginBottom: 16,
|
|
}}
|
|
>
|
|
{allColors.map((color) => (
|
|
<button
|
|
key={color}
|
|
type="button"
|
|
onClick={() => {
|
|
const next = normalizeToRGB(color);
|
|
if (mode === "single") {
|
|
settings.singleColor = next;
|
|
setSingleColor(next);
|
|
} else if (mode === "gradient-experimental") {
|
|
if (activeEndpoint === "end") {
|
|
settings.gradientEnd = next;
|
|
setGradientEnd(next);
|
|
} else {
|
|
settings.gradientStart = next;
|
|
setGradientStart(next);
|
|
}
|
|
}
|
|
setCustomInput(next);
|
|
requestApply();
|
|
}}
|
|
style={{
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 6,
|
|
border: "1px solid rgba(255,255,255,0.2)",
|
|
background: normalizeToRGB(color),
|
|
cursor: "pointer",
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
{mode !== "cover-gradient" && (
|
|
<div style={{ marginBottom: 12 }}>
|
|
<div
|
|
style={{
|
|
color: "rgba(255,255,255,0.7)",
|
|
fontSize: 12,
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
Custom Hex (#RRGGBB)
|
|
</div>
|
|
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
|
<input
|
|
type="text"
|
|
value={customInput}
|
|
onChange={(e) => setCustomInput(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
applyCustomInputColor(customInput, true);
|
|
addCustomColor();
|
|
}
|
|
}}
|
|
placeholder="#RRGGBB"
|
|
style={{
|
|
flex: 1,
|
|
padding: "8px 12px",
|
|
borderRadius: 6,
|
|
border: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.1)",
|
|
color: "#fff",
|
|
fontSize: 14,
|
|
fontFamily: "monospace",
|
|
boxSizing: "border-box",
|
|
}}
|
|
/>
|
|
<button
|
|
onClick={() => {
|
|
applyCustomInputColor(customInput, false);
|
|
addCustomColor();
|
|
}}
|
|
style={{
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 6,
|
|
border: "1px solid rgba(255,255,255,0.3)",
|
|
background: "rgba(255,255,255,0.15)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
fontSize: 16,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
transition: "all 0.2s ease",
|
|
}}
|
|
type="button"
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{/* Sliders inside picker based on mode */}
|
|
{mode === "single" && (
|
|
<div style={{ marginBottom: 16 }}>
|
|
<div
|
|
style={{
|
|
color: "rgba(255,255,255,0.8)",
|
|
fontSize: 12,
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
Alpha
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min={0}
|
|
max={100}
|
|
step={1}
|
|
value={singleAlpha}
|
|
onChange={(e) => {
|
|
const value = Number(e.target.value);
|
|
settings.singleAlpha = value;
|
|
setSingleAlpha(value);
|
|
requestApply();
|
|
}}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{mode === "gradient-experimental" && (
|
|
<div style={{ marginBottom: 16, display: "grid", gap: 16 }}>
|
|
<div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 8,
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: 3,
|
|
background: normalizeToRGB(gradientStart),
|
|
border: "1px solid rgba(255,255,255,0.3)",
|
|
}}
|
|
/>
|
|
<div
|
|
style={{ color: "rgba(255,255,255,0.8)", fontSize: 12 }}
|
|
>
|
|
Start Alpha
|
|
</div>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min={0}
|
|
max={100}
|
|
step={1}
|
|
value={gradientStartAlpha}
|
|
onChange={(e) => {
|
|
const value = Number(e.target.value);
|
|
settings.gradientStartAlpha = value;
|
|
setGradientStartAlpha(value);
|
|
requestApply();
|
|
}}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 8,
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: 3,
|
|
background: normalizeToRGB(gradientEnd),
|
|
border: "1px solid rgba(255,255,255,0.3)",
|
|
}}
|
|
/>
|
|
<div
|
|
style={{ color: "rgba(255,255,255,0.8)", fontSize: 12 }}
|
|
>
|
|
End Alpha
|
|
</div>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min={0}
|
|
max={100}
|
|
step={1}
|
|
value={gradientEndAlpha}
|
|
onChange={(e) => {
|
|
const value = Number(e.target.value);
|
|
settings.gradientEndAlpha = value;
|
|
setGradientEndAlpha(value);
|
|
requestApply();
|
|
}}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
<div
|
|
style={{ color: "rgba(255,255,255,0.8)", fontSize: 12 }}
|
|
>
|
|
Angle
|
|
</div>
|
|
<div
|
|
style={{ color: "rgba(255,255,255,0.6)", fontSize: 12 }}
|
|
>
|
|
{gradientAngle}°
|
|
</div>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min={0}
|
|
max={360}
|
|
step={1}
|
|
value={gradientAngle}
|
|
onChange={(e) => {
|
|
const value = Number(e.target.value);
|
|
settings.gradientAngle = value;
|
|
setGradientAngle(value);
|
|
requestApply();
|
|
}}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{mode === "cover-gradient" && (
|
|
<div style={{ marginBottom: 16 }}>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
<div style={{ color: "rgba(255,255,255,0.8)", fontSize: 12 }}>
|
|
Angle
|
|
</div>
|
|
<div style={{ color: "rgba(255,255,255,0.6)", fontSize: 12 }}>
|
|
{gradientAngle}°
|
|
</div>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min={0}
|
|
max={360}
|
|
step={1}
|
|
value={gradientAngle}
|
|
onChange={(e) => {
|
|
const value = Number(e.target.value);
|
|
settings.gradientAngle = value;
|
|
setGradientAngle(value);
|
|
requestApply();
|
|
}}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
onClick={closePicker}
|
|
style={{
|
|
width: "100%",
|
|
padding: 8,
|
|
borderRadius: 6,
|
|
border: "1px solid rgba(255,255,255,0.2)",
|
|
background: "rgba(255,255,255,0.1)",
|
|
color: "#fff",
|
|
cursor: "pointer",
|
|
fontSize: 12,
|
|
}}
|
|
type="button"
|
|
>
|
|
Done
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
<AnySwitch
|
|
title="Exclude Inactive"
|
|
desc="Apply color/gradient only to the currently active lyric line"
|
|
checked={excludeInactive}
|
|
onChange={(_event: React.ChangeEvent<HTMLInputElement> | null, checked: boolean) => {
|
|
settings.excludeInactive = checked;
|
|
setExcludeInactive(checked);
|
|
requestApply();
|
|
}}
|
|
/>
|
|
</LunaSettings>
|
|
);
|
|
};
|