Files
TidaLuna-Plugins/plugins/audio-visualizer-luna/src/Settings.tsx
T
2025-06-11 02:57:18 +10:00

355 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ReactiveStore } from "@luna/core";
import { LunaSettings, LunaNumberSetting, LunaSwitchSetting, LunaTextSetting } from "@luna/ui";
import React from "react";
export const settings = await ReactiveStore.getPluginStorage("AudioVisualizer", {
barCount: 32,
barColor: "#ffffff",
barRounding: true,
customColors: [] as string[]
});
export const Settings = () => {
const [barCount, setBarCount] = React.useState(settings.barCount);
const [barColor, setBarColor] = React.useState(settings.barColor);
const [barRounding, setBarRounding] = React.useState(settings.barRounding);
const [showColorPicker, setShowColorPicker] = React.useState(false);
const [isAnimatingIn, setIsAnimatingIn] = React.useState(false);
const [shouldRender, setShouldRender] = React.useState(false);
const [customInput, setCustomInput] = React.useState(settings.barColor);
const [customColors, setCustomColors] = React.useState(settings.customColors);
const [hoveredColorIndex, setHoveredColorIndex] = React.useState<number | null>(null);
const closeColorPicker = () => {
setIsAnimatingIn(false);
setTimeout(() => {
setShowColorPicker(false);
setShouldRender(false);
}, 200); // Wait for animation to complete because i need to
};
const openColorPicker = () => {
setShowColorPicker(true);
setShouldRender(true);
setTimeout(() => setIsAnimatingIn(true), 10);
};
React.useEffect(() => {
if (showColorPicker) {
setShouldRender(true);
setTimeout(() => setIsAnimatingIn(true), 10);
}
}, [showColorPicker]);
// Common color presets for cool points :D
const colorPresets = [
"#ffffff", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff",
"#ff8800", "#8800ff", "#0088ff", "#88ff00", "#ff0088", "#00ff88",
"#444444", "#888888", "#cccccc", "#1db954", "#e22134", "#1976d2"
];
const updateColor = (color: string) => {
setBarColor(color);
setCustomInput(color);
settings.barColor = color;
(window as any).updateAudioVisualizer?.();
};
const addCustomColor = () => {
if (customInput) {
// Trim whitespace and convert to lowercase
const trimmedInput = customInput.trim().toLowerCase();
// Validate hex color format
const hexColorRegex = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
if (hexColorRegex.test(trimmedInput) &&
!colorPresets.includes(trimmedInput) &&
!customColors.includes(trimmedInput)) {
const newCustomColors = [...customColors, trimmedInput];
setCustomColors(newCustomColors);
settings.customColors = newCustomColors;
}
}
};
const removeCustomColor = (colorToRemove: string) => {
const newCustomColors = customColors.filter(color => color !== colorToRemove);
setCustomColors(newCustomColors);
settings.customColors = newCustomColors;
// If the removed color was the selected color (reset to white)
if (barColor === colorToRemove) {
updateColor("#ffffff");
}
};
const allColors = [...colorPresets, ...customColors];
return (
<LunaSettings>
<LunaSwitchSetting
title="Bar Roundness"
desc="Enable rounded corners on visualizer bars"
checked={barRounding}
onChange={(_, checked) => {
setBarRounding(checked);
settings.barRounding = checked;
(window as any).updateAudioVisualizer?.();
}}
/>
<LunaNumberSetting
title="Bar Count"
desc="Number of frequency bars to display"
min={8}
max={64}
step={1}
value={barCount}
onNumber={(value: number) => {
setBarCount(value);
settings.barCount = value;
(window as any).updateAudioVisualizer?.();
}}
/>
{/* YUP YOUR EYES WORK... we do be using React code in the settings..*/}
{/* I'm not sure if this is a good idea, but it works & looks amazing */}
{/* Sorry @Inrixia <3 */}
<div style={{
padding: "16px 0",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}>
<div>
<div style={{ fontWeight: "normal", fontSize: "1.075rem", marginBottom: "4px" }}>Bar Color</div>
<div style={{ opacity: 0.7, fontSize: "14px" }}>Color of the visualizer bars</div>
</div>
<div style={{ display: "flex", gap: "8px", alignItems: "center", position: "relative" }}>
<button
onClick={() => showColorPicker ? closeColorPicker() : openColorPicker()}
style={{
width: "32px",
height: "32px",
border: "1px solid rgba(255,255,255,0.15)",
borderRadius: "6px",
cursor: "pointer",
background: barColor,
backdropFilter: "blur(10px)",
WebkitBackdropFilter: "blur(10px)",
position: "relative",
overflow: "hidden"
}}
>
<div style={{
position: "absolute",
inset: 0,
background: "rgba(0,0,0,0.1)",
backdropFilter: "blur(2px)"
}} />
</button>
{/* Custom Color Picker Modal */}
{shouldRender && (
<>
{/* Backdrop */}
<div
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"
}}
onClick={closeColorPicker}
/>
{/* Color Picker Panel */}
<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: "16px",
padding: "20px",
minWidth: "320px",
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: "12px", color: "#fff", fontWeight: "bold", fontSize: "14px" }}>
Choose Color
</div>
{/* Color Grid */}
<div style={{
display: "grid",
gridTemplateColumns: "repeat(7, 1fr)",
gap: "8px",
marginBottom: "16px"
}}>
{allColors.map((color, index) => {
const isCustomColor = customColors.includes(color);
const isHovered = hoveredColorIndex === index;
return (
<div
key={index}
style={{
position: "relative",
width: "32px",
height: "32px",
cursor: "pointer"
}}
className="color-item"
onMouseEnter={() => setHoveredColorIndex(index)}
onMouseLeave={() => setHoveredColorIndex(null)}
>
<button
onClick={() => {
updateColor(color);
closeColorPicker();
}}
style={{
width: "100%",
height: "100%",
borderRadius: "6px",
border: barColor === color ? "2px solid #fff" : "1px solid rgba(255,255,255,0.2)",
background: color,
cursor: "pointer",
transition: "all 0.2s ease"
}}
/>
{isCustomColor && (
<button
onClick={(e) => {
e.stopPropagation();
removeCustomColor(color);
}}
style={{
position: "absolute",
top: "-4px",
right: "-4px",
width: "16px",
height: "16px",
borderRadius: "50%",
border: "1px solid rgba(255,255,255,0.8)",
background: "rgba(0,0,0,0.8)",
color: "#fff",
cursor: "pointer",
fontSize: "10px",
display: "flex",
alignItems: "center",
justifyContent: "center",
opacity: isHovered ? 1 : 0,
transition: "opacity 0.2s ease",
zIndex: 10
}}
className="remove-button"
>
×
</button>
)}
</div>
);
})}
</div>
{/* Custom Hex Input */}
<div style={{ marginBottom: "12px" }}>
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: "12px", marginBottom: "6px" }}>
Add Custom Color
</div>
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
<input
type="text"
value={customInput}
onChange={(e) => setCustomInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
updateColor(customInput);
addCustomColor();
}
}}
placeholder="#ffffff"
style={{
flex: 1,
padding: "8px 12px",
borderRadius: "6px",
border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(255,255,255,0.1)",
color: "#fff",
fontSize: "14px",
fontFamily: "monospace",
boxSizing: "border-box"
}}
/>
<button
onClick={() => {
updateColor(customInput);
addCustomColor();
}}
style={{
width: "32px",
height: "32px",
borderRadius: "6px",
border: "1px solid rgba(255,255,255,0.3)",
background: "rgba(255,255,255,0.15)",
color: "#fff",
cursor: "pointer",
fontSize: "16px",
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "all 0.2s ease"
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "rgba(255,255,255,0.25)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "rgba(255,255,255,0.15)";
}}
>
+
</button>
</div>
</div>
{/* Close Button (Done) - Also runs when color chosen*/}
<button
onClick={closeColorPicker}
style={{
width: "100%",
padding: "8px",
borderRadius: "6px",
border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(255,255,255,0.1)",
color: "#fff",
cursor: "pointer",
fontSize: "12px"
}}
>
Done
</button>
</div>
</>
)}
</div>
</div>
</LunaSettings>
);
};