-
● {block.name||block.type||''}
+
onModelBrowser?.(block.id)}>
+ ● {block.name||block.type||''}
+ {onModelBrowser&&▼}
+
{onFocus&&}
@@ -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}
/>
@@ -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}
/>
):(
@@ -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 (
+ setModelBrowserBlockId(null)}
+ />
+ );
+ })()}
+
>
);
}
diff --git a/src/BlockChain.jsx b/src/BlockChain.jsx
index 887e241..6974f03 100644
--- a/src/BlockChain.jsx
+++ b/src/BlockChain.jsx
@@ -163,17 +163,37 @@ function VertBracket({ height, direction }) {
}
// ── Split Block Tile ─────────────────────────────
-function SplitBlockTile({ block, selected, onSelect, onToggle }) {
+function SplitBlockTile({ block, selected, onSelect, onToggle, onModelBrowser }) {
const splitParams = block.params || {};
const splitType = splitParams.splitType || "y";
const st = SPLIT_TYPES[splitType] || SPLIT_TYPES.y;
const enabled = !block.bypassed;
const color = getBlockColor("split");
+ const longPressRef = useRef(null);
+
+ const handleTouchStart = useCallback(() => {
+ longPressRef.current = setTimeout(() => {
+ longPressRef.current = null;
+ onModelBrowser?.(block.id);
+ }, 500);
+ }, [block.id, onModelBrowser]);
+
+ const handleTouchEnd = useCallback(() => {
+ if (longPressRef.current) {
+ clearTimeout(longPressRef.current);
+ longPressRef.current = null;
+ }
+ }, []);
return (
onSelect(block.id)}
+ onTouchStart={handleTouchStart}
+ onTouchEnd={handleTouchEnd}
+ onTouchMove={handleTouchEnd}
+ onMouseDown={handleTouchEnd}
+ onMouseUp={handleTouchEnd}
style={{
minWidth: 130, width: 130, height: 76, borderRadius: 8,
background: `linear-gradient(135deg, ${T.panel}, #1A1520)`,
@@ -237,14 +257,34 @@ function SplitBlockTile({ block, selected, onSelect, onToggle }) {
}
// ── Merge Block Tile ──────────────────────────────
-function MergeBlockTile({ block, selected, onSelect, onToggle }) {
+function MergeBlockTile({ block, selected, onSelect, onToggle, onModelBrowser }) {
const mergeParams = block.params || {};
const enabled = !block.bypassed;
+ const longPressRef = useRef(null);
+
+ const handleTouchStart = useCallback(() => {
+ longPressRef.current = setTimeout(() => {
+ longPressRef.current = null;
+ onModelBrowser?.(block.id);
+ }, 500);
+ }, [block.id, onModelBrowser]);
+
+ const handleTouchEnd = useCallback(() => {
+ if (longPressRef.current) {
+ clearTimeout(longPressRef.current);
+ longPressRef.current = null;
+ }
+ }, []);
return (
onSelect(block.id)}
+ onTouchStart={handleTouchStart}
+ onTouchEnd={handleTouchEnd}
+ onTouchMove={handleTouchEnd}
+ onMouseDown={handleTouchEnd}
+ onMouseUp={handleTouchEnd}
style={{
minWidth: 130, width: 130, height: 76, borderRadius: 8,
background: `linear-gradient(135deg, #15201A, ${T.panel})`,
@@ -304,14 +344,34 @@ function MergeBlockTile({ block, selected, onSelect, onToggle }) {
}
// ── Regular Block Tile ────────────────────────────
-function BlockTile({ block, selected, onSelect, onToggle, pathLabel }) {
+function BlockTile({ block, selected, onSelect, onToggle, pathLabel, onModelBrowser }) {
const color = getBlockColor(block.type);
const enabled = !block.bypassed;
+ const longPressRef = useRef(null);
+
+ const handleTouchStart = useCallback(() => {
+ longPressRef.current = setTimeout(() => {
+ longPressRef.current = null;
+ onModelBrowser?.(block.id);
+ }, 500);
+ }, [block.id, onModelBrowser]);
+
+ const handleTouchEnd = useCallback(() => {
+ if (longPressRef.current) {
+ clearTimeout(longPressRef.current);
+ longPressRef.current = null;
+ }
+ }, []);
return (
onSelect(block.id)}
+ onTouchStart={handleTouchStart}
+ onTouchEnd={handleTouchEnd}
+ onTouchMove={handleTouchEnd}
+ onMouseDown={handleTouchEnd}
+ onMouseUp={handleTouchEnd}
style={{
minWidth: 120, width: 120, height: 76, borderRadius: 8,
background: pathLabel === "B" ? "#1A1820" : T.panel,
@@ -435,7 +495,7 @@ function buildChainModel(blocks) {
// ── Block Chain (exported) ─────────────────────────
export default function BlockChain({
blocks, selectedBlockId, onSelectBlock, onToggleBlock,
- onReorder, onAddBlock, onRemoveBlock,
+ onReorder, onAddBlock, onRemoveBlock, onModelBrowser,
}) {
const scrollRef = useRef(null);
const scrollBRef = useRef(null);
@@ -528,6 +588,7 @@ export default function BlockChain({
index={idx}
onSelect={onSelectBlock}
onToggle={onToggleBlock}
+ onModelBrowser={onModelBrowser}
/>
@@ -585,6 +646,7 @@ export default function BlockChain({
index={idx}
onSelect={onSelectBlock}
onToggle={onToggleBlock}
+ onModelBrowser={onModelBrowser}
/>
@@ -597,6 +659,7 @@ export default function BlockChain({
selected={splitBlock.id === selectedBlockId}
onSelect={onSelectBlock}
onToggle={onToggleBlock}
+ onModelBrowser={onModelBrowser}
/>
@@ -611,6 +674,7 @@ export default function BlockChain({
onSelect={onSelectBlock}
onToggle={onToggleBlock}
pathLabel="A"
+ onModelBrowser={onModelBrowser}
/>
@@ -626,6 +690,7 @@ export default function BlockChain({
selected={mergeBlock.id === selectedBlockId}
onSelect={onSelectBlock}
onToggle={onToggleBlock}
+ onModelBrowser={onModelBrowser}
/>