Files
pi-multifx-pedal-ui/src/MainMenu.jsx
T
shawn c7f148341d feat: Undo/Redo + Main Menu + Global Settings + Global EQ
- Undo/Redo in header bar: snapshot-based tracking for block adds, removes,
  reorders, parameter changes, and model swaps. 50-level undo stack with
  debounced param snapshots (400ms cooldown).
- Main Menu: slide-out sidebar from left with ☰ button in status bar.
  Items: Save Preset, Bypass/Control, Global Settings, Global EQ, Tuner,
  Wi-Fi / Bluetooth.
- Global Settings: full-screen overlay with grouped settings — Audio (Master
  Volume, Input Pad, Impedance), MIDI (Channel, Clock, Thru), Display
  (Brightness, Auto Dim, Screen Saver), Tempo (Tap Tempo, Division, Mute).
- Global EQ: 3-band shelving equalizer (Low 80Hz, Mid 800Hz, High 6.4kHz)
  with large knobs, canvas-based frequency response graph, and reset button.
- New files: src/MainMenu.jsx, src/GlobalSettings.jsx, src/GlobalEQ.jsx
2026-06-12 19:54:53 -04:00

141 lines
4.9 KiB
React
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 { useRef, useEffect } from "react";
const T = {
bg: "#0A0A0C", panel: "#141418", surface: "#1C1C22", border: "#2A2A32",
amber: "#E8A030", amberDim: "#7A5218", blue: "#3A7BA8", blueDim: "#1E4060",
green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458",
};
const MENU_ITEMS = [
{ id: "save", label: "Save Preset", icon: "💾", desc: "Save current preset to bank" },
{ id: "bypass", label: "Bypass/Control", icon: "🔌", desc: "Footswitch & pedal assignment" },
{ id: "settings", label: "Global Settings",icon: "⚙", desc: "Master volume, input, MIDI, display" },
{ id: "eq", label: "Global EQ", icon: "📊", desc: "Low, Mid, High shelving EQ" },
{ id: "tuner", label: "Tuner", icon: "🎵", desc: "Instrument tuner" },
{ id: "wifi", label: "Wi-Fi / Bluetooth",icon: "📶", desc: "Wireless & network settings" },
];
export default function MainMenu({ onClose, onItemSelect }) {
const overlayRef = useRef(null);
useEffect(() => {
const el = overlayRef.current;
if (!el) return;
// Trigger enter animation
requestAnimationFrame(() => { el.style.transform = "translateX(0)"; });
}, []);
const handleBackdropClick = (e) => {
if (e.target === e.currentTarget) onClose?.();
};
return (
<div
ref={overlayRef}
onClick={handleBackdropClick}
style={{
position: "fixed", inset: 0, zIndex: 999,
display: "flex",
background: "rgba(0,0,0,0.55)",
transition: "background .2s ease",
}}
>
{/* Sidebar */}
<div
style={{
width: 260, maxWidth: "75vw", height: "100%",
background: T.panel,
borderRight: `1px solid ${T.border}`,
display: "flex", flexDirection: "column",
boxShadow: "4px 0 24px rgba(0,0,0,0.5)",
transform: "translateX(-100%)",
transition: "transform .22s cubic-bezier(.22,.61,.36,1)",
}}
>
{/* Header */}
<div style={{
padding: "14px 16px",
borderBottom: `1px solid ${T.border}`,
display: "flex", alignItems: "center", justifyContent: "space-between",
flexShrink: 0,
}}>
<div style={{ fontSize: 14, fontWeight: 600, color: T.textPrimary }}>
Menu
</div>
<button
onClick={onClose}
style={{
width: 28, height: 28, borderRadius: 6,
display: "flex", alignItems: "center", justifyContent: "center",
background: T.surface, border: `1px solid ${T.border}`,
color: T.textSec, fontSize: 13, cursor: "pointer",
}}
>
</button>
</div>
{/* Menu items */}
<div style={{
flex: 1, overflowY: "auto", padding: "8px 0",
}}>
{MENU_ITEMS.map((item) => (
<button
key={item.id}
onClick={() => onItemSelect?.(item.id)}
style={{
width: "100%", display: "flex", alignItems: "center", gap: 12,
padding: "12px 16px",
background: "none", border: "none", cursor: "pointer",
textAlign: "left", color: T.textPrimary,
transition: "background .1s",
borderBottom: `1px solid ${T.border}33`,
}}
onMouseEnter={(e) => { e.currentTarget.style.background = T.surface; }}
onMouseLeave={(e) => { e.currentTarget.style.background = "none"; }}
>
<div style={{
width: 34, height: 34, borderRadius: 8,
display: "flex", alignItems: "center", justifyContent: "center",
background: T.surface, border: `1px solid ${T.border}`,
fontSize: 16, flexShrink: 0,
}}>
{item.icon}
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 13, fontWeight: 600,
color: T.textPrimary,
}}>
{item.label}
</div>
<div style={{
fontSize: 10, color: T.textDim, marginTop: 1,
whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
}}>
{item.desc}
</div>
</div>
<span style={{ color: T.textDim, fontSize: 11 }}></span>
</button>
))}
</div>
{/* Footer */}
<div style={{
padding: "12px 16px",
borderTop: `1px solid ${T.border}`,
flexShrink: 0,
}}>
<div style={{
fontSize: 9, color: T.textDim, textAlign: "center",
letterSpacing: ".06em",
}}>
Helix Stadium v0.1
</div>
</div>
</div>
</div>
);
}