feat: initial Reaktor modular synth app
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>
This commit is contained in:
72
src/components/PresetModal.jsx
Normal file
72
src/components/PresetModal.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user