React + Tone.js modular synthesizer with visual node editor. Includes: Oscillator, Filter, Envelope, LFO, VCA, Delay, Reverb, Distortion, Mixer, Scope, Output, and Keyboard modules. SVG wire connections, knob controls, preset save/load system. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
2.3 KiB
JavaScript
73 lines
2.3 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { getPresets, savePreset, loadPreset, deletePreset } from '../engine/presets.js';
|
|
|
|
export default function PresetModal({ mode, onClose }) {
|
|
const [name, setName] = useState('');
|
|
const presets = getPresets();
|
|
|
|
const handleSave = () => {
|
|
if (!name.trim()) return;
|
|
savePreset(name.trim());
|
|
onClose();
|
|
};
|
|
|
|
const handleLoad = (presetName) => {
|
|
loadPreset(presetName);
|
|
onClose();
|
|
};
|
|
|
|
const handleDelete = (e, presetName) => {
|
|
e.stopPropagation();
|
|
deletePreset(presetName);
|
|
// Force re-render
|
|
setName(n => n + '');
|
|
};
|
|
|
|
return (
|
|
<div className="modal-overlay" onClick={onClose}>
|
|
<div className="modal" onClick={e => e.stopPropagation()}>
|
|
<h2>{mode === 'save' ? 'Save Preset' : 'Load Preset'}</h2>
|
|
|
|
{mode === 'save' && (
|
|
<>
|
|
<input
|
|
autoFocus
|
|
placeholder="Preset name..."
|
|
value={name}
|
|
onChange={e => setName(e.target.value)}
|
|
onKeyDown={e => e.key === 'Enter' && handleSave()}
|
|
/>
|
|
<div className="modal-actions">
|
|
<button onClick={onClose}>Cancel</button>
|
|
<button className="primary" onClick={handleSave}>Save</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{mode === 'load' && (
|
|
<>
|
|
<div className="preset-list">
|
|
{presets.length === 0 && (
|
|
<div style={{ color: 'var(--text2)', padding: 12, textAlign: 'center' }}>No presets saved yet</div>
|
|
)}
|
|
{presets.map(p => (
|
|
<div key={p.name} className="preset-item" onClick={() => handleLoad(p.name)}>
|
|
<span>{p.name}</span>
|
|
<span className="preset-date">{p.modules?.length || 0} modules</span>
|
|
<button
|
|
style={{ background: 'none', border: 'none', color: 'var(--red)', cursor: 'pointer', marginLeft: 8, fontSize: 12 }}
|
|
onClick={e => handleDelete(e, p.name)}
|
|
>✕</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="modal-actions">
|
|
<button onClick={onClose}>Close</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|