feat: Model Browser — block library with category filter tabs, search, favorites, NAM/IR models
This commit is contained in:
+57
-7
@@ -3,6 +3,7 @@ import BlockChain from "./BlockChain.jsx";
|
||||
import FootswitchBar from "./FootswitchBar.jsx";
|
||||
import TunerScreen from "./Tuner.jsx";
|
||||
import FocusView from "./FocusView.jsx";
|
||||
import ModelBrowser from "./ModelBrowser.jsx";
|
||||
|
||||
// ── API Layer ──────────────────────────────────────────────────────
|
||||
const API_BASE = "";
|
||||
@@ -343,13 +344,17 @@ function SelectParam({ def, value, color, onChange }) {
|
||||
}
|
||||
|
||||
// ── Parameter Panel ──────────────────────────────────────────
|
||||
function ParameterPanel({ block,params,onParamChange,onParamChangeEnd,viewMode,onToggleView,onFocus }) {
|
||||
function ParameterPanel({ block,params,onParamChange,onParamChangeEnd,viewMode,onToggleView,onFocus,onModelBrowser }) {
|
||||
const defs=getBlockParams(block.type);const color=getBlockColor(block.type);
|
||||
const visibleDefs = defs.filter(d => !d.conditional || d.conditional(params));
|
||||
return (
|
||||
<div className="param-panel">
|
||||
<div className="param-panel-header">
|
||||
<span className="param-panel-title"><span style={{color}}>●</span> {block.name||block.type||''}</span>
|
||||
<span className="param-panel-title" style={{cursor:onModelBrowser?'pointer':'default'}}
|
||||
onClick={()=>onModelBrowser?.(block.id)}>
|
||||
<span style={{color}}>●</span> {block.name||block.type||''}
|
||||
{onModelBrowser&&<span style={{marginLeft:4,fontSize:8,color:T.textDim}}>▼</span>}
|
||||
</span>
|
||||
<div style={{display:'flex',gap:4,alignItems:'center'}}>
|
||||
{onFocus&&<button className="btn" style={{padding:'4px 8px',fontSize:9,fontWeight:600,background:`${T.amber}22`,border:`1px solid ${T.amber}44`,color:T.amber,borderRadius:5,letterSpacing:'.04em',cursor:'pointer'}}
|
||||
onClick={onFocus}>🔍 Focus</button>}
|
||||
@@ -450,6 +455,7 @@ export default function App(){
|
||||
const[globalBypass,setGlobalBypass]=useState(false);
|
||||
const[loadingPreset,setLoadingPreset]=useState(false);
|
||||
const[focusBlockId,setFocusBlockId]=useState(null);
|
||||
const[modelBrowserBlockId,setModelBrowserBlockId]=useState(null);
|
||||
const[footswitchMode,setFootswitchMode]=useState("stomp");
|
||||
|
||||
// ── Poll pedal state from API ──
|
||||
@@ -604,10 +610,37 @@ export default function App(){
|
||||
setTimeout(()=>{userChangedBlock.current=false;},3000);
|
||||
},[focusBlockId]);
|
||||
|
||||
const handleOpenFocusModelBrowser=useCallback(()=>{
|
||||
setView("captures");
|
||||
setFocusBlockId(null);
|
||||
},[]);
|
||||
const handleOpenModelBrowser=useCallback((blockId)=>{
|
||||
setModelBrowserBlockId(blockId);
|
||||
if(focusBlockId) setFocusBlockId(null);
|
||||
},[focusBlockId]);
|
||||
|
||||
const handleSelectModel=useCallback(({type, name, modelPath, isBuiltin})=>{
|
||||
const id=modelBrowserBlockId;
|
||||
if(!id)return;
|
||||
userChangedBlock.current=true;
|
||||
setBlocks(prev=>prev.map(b=>{
|
||||
if(b.id!==id)return b;
|
||||
const defs=getBlockParams(type);
|
||||
const init={};
|
||||
defs.forEach(p=>{init[p.key]=p.def;});
|
||||
return {
|
||||
...b,
|
||||
type,
|
||||
name: name || getBlockDisplayName({type}),
|
||||
params: init,
|
||||
nam_model_path: (!isBuiltin && modelPath && type==='nam') ? modelPath : undefined,
|
||||
ir_file_path: (!isBuiltin && modelPath && type==='ir') ? modelPath : undefined,
|
||||
};
|
||||
}));
|
||||
setModelBrowserBlockId(null);
|
||||
setBlockParams(prev=>{
|
||||
const next={...prev};
|
||||
delete next[id];
|
||||
return next;
|
||||
});
|
||||
setTimeout(()=>{userChangedBlock.current=false;},3000);
|
||||
},[modelBrowserBlockId]);
|
||||
|
||||
// ── Render ──
|
||||
return(
|
||||
@@ -720,6 +753,7 @@ export default function App(){
|
||||
onReorder={handleReorder}
|
||||
onAddBlock={handleAddBlock}
|
||||
onRemoveBlock={handleRemoveBlock}
|
||||
onModelBrowser={handleOpenModelBrowser}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -733,6 +767,7 @@ export default function App(){
|
||||
onParamChange={(key,value)=>handleParamChange(selectedBlockId,key,value)}
|
||||
onParamChangeEnd={(key,value)=>handleParamChangeEnd(selectedBlockId,key,value)}
|
||||
onFocus={()=>setFocusBlockId(selectedBlockId)}
|
||||
onModelBrowser={handleOpenModelBrowser}
|
||||
/>
|
||||
):(
|
||||
<div style={{flex:1,display:"flex",alignItems:"center",justifyContent:"center",background:T.bg,color:T.textDim,fontSize:11}}>
|
||||
@@ -774,7 +809,7 @@ export default function App(){
|
||||
onParamChangeEnd={(key,value)=>handleParamChangeEnd(focusBlockId,key,value)}
|
||||
onToggleView={()=>setParamView(p=>p==='slider'?'knob':'slider')}
|
||||
onToggleBypass={handleFocusViewBypass}
|
||||
onOpenModelBrowser={handleOpenFocusModelBrowser}
|
||||
onOpenModelBrowser={()=>handleOpenModelBrowser(focusBlockId)}
|
||||
/>
|
||||
)}
|
||||
{/* ── Overlays ── */}
|
||||
@@ -809,6 +844,21 @@ export default function App(){
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── Model Browser ── */}
|
||||
{modelBrowserBlockId&&(()=>{
|
||||
const block=blocks.find(b=>b.id===modelBrowserBlockId);
|
||||
if(!block)return null;
|
||||
return (
|
||||
<ModelBrowser
|
||||
currentBlockType={block.type}
|
||||
currentBlockName={block.name}
|
||||
currentModelPath={block.nam_model_path||block.ir_file_path}
|
||||
onSelectModel={handleSelectModel}
|
||||
onClose={()=>setModelBrowserBlockId(null)}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
</div></>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user