diff --git a/src/components/DrumPadWidget.jsx b/src/components/DrumPadWidget.jsx index 5098b67..ed54575 100644 --- a/src/components/DrumPadWidget.jsx +++ b/src/components/DrumPadWidget.jsx @@ -1,7 +1,5 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { triggerKeyboard } from '../engine/audioEngine.js'; -import { state } from '../engine/state.js'; -import { useIsMobile } from '../hooks/useIsMobile.js'; // 4x4 pad layout — each pad maps to a MIDI note const PAD_NOTES = [ @@ -66,11 +64,8 @@ function FullscreenDrumPad({ moduleId, onClose }) { ); } -export default function DrumPadWidget({ moduleId }) { - const isMobile = useIsMobile(); - const [fullscreen, setFullscreen] = useState(false); +export default function DrumPadWidget({ moduleId, fullscreen, onCloseFullscreen }) { const [activePad, setActivePad] = useState(-1); - const lastTap = useRef(0); const hitPad = useCallback((pad, idx) => { triggerKeyboard(moduleId, midiToFreq(pad.note), true); @@ -81,19 +76,9 @@ export default function DrumPadWidget({ moduleId }) { }, 150); }, [moduleId]); - const handleDoubleTap = useCallback((e) => { - const now = Date.now(); - if (now - lastTap.current < 350) { - e.preventDefault(); - e.stopPropagation(); - setFullscreen(true); - } - lastTap.current = now; - }, []); - return ( <> -
+
{PAD_NOTES.map((pad, i) => (
- {isMobile ? 'Doble-tap: pantalla completa' : 'Tap pads to trigger'} + Tap pads to trigger
{fullscreen && ( - setFullscreen(false)} /> + )} ); diff --git a/src/components/KeyboardWidget.jsx b/src/components/KeyboardWidget.jsx index 99dcbda..8329fd4 100644 --- a/src/components/KeyboardWidget.jsx +++ b/src/components/KeyboardWidget.jsx @@ -1,7 +1,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { triggerKeyboard } from '../engine/audioEngine.js'; import { state } from '../engine/state.js'; -import { useIsMobile } from '../hooks/useIsMobile.js'; const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; @@ -96,13 +95,10 @@ function FullscreenPiano({ moduleId, initialOctave, onClose }) { ); } -export default function KeyboardWidget({ moduleId }) { +export default function KeyboardWidget({ moduleId, fullscreen, onCloseFullscreen }) { const mod = state.modules.find(m => m.id === moduleId); const octave = mod?.params?.octave ?? 4; const activeKeys = useRef(new Set()); - const isMobile = useIsMobile(); - const [fullscreen, setFullscreen] = useState(false); - const lastTap = useRef(0); const playNote = useCallback((semitone) => { const midi = (octave + 1) * 12 + semitone; @@ -138,23 +134,13 @@ export default function KeyboardWidget({ moduleId }) { }; }, [playNote, stopNote]); - const handleDoubleTap = useCallback((e) => { - const now = Date.now(); - if (now - lastTap.current < 350) { - e.preventDefault(); - e.stopPropagation(); - setFullscreen(true); - } - lastTap.current = now; - }, []); - // Mini keyboard (1 octave) const whites = [0, 2, 4, 5, 7, 9, 11]; const blacks = [1, 3, -1, 6, 8, 10]; return ( <> -
+
{whites.map((note, i) => (
- {isMobile ? 'Doble-tap: pantalla completa' : 'Z-M / Q-I keys'} · Oct {octave} + Z-M / Q-I keys · Oct {octave}
@@ -185,7 +171,7 @@ export default function KeyboardWidget({ moduleId }) { setFullscreen(false)} + onClose={onCloseFullscreen} /> )} diff --git a/src/components/ModuleNode.jsx b/src/components/ModuleNode.jsx index 4f006ef..400dcb7 100644 --- a/src/components/ModuleNode.jsx +++ b/src/components/ModuleNode.jsx @@ -46,6 +46,7 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition } if (!def) return null; const isSelected = state.selectedModuleId === mod.id; + const [fullscreen, setFullscreen] = useState(false); // Merge default params const params = { ...Object.fromEntries(Object.entries(def.params).map(([k, v]) => [k, v.default])), ...mod.params }; @@ -177,6 +178,13 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
{def.icon} {def.name} + {(mod.type === 'keyboard' || mod.type === 'drumpad') && ( + + )}
@@ -247,10 +255,10 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition } {mod.type === 'scope' && } {/* Keyboard widget */} - {mod.type === 'keyboard' && } + {mod.type === 'keyboard' && setFullscreen(false)} />} {/* Drum Pad widget */} - {mod.type === 'drumpad' && } + {mod.type === 'drumpad' && setFullscreen(false)} />} {/* Sequencer widget */} {mod.type === 'sequencer' && } diff --git a/src/index.css b/src/index.css index 5e4f1dc..1a18d20 100644 --- a/src/index.css +++ b/src/index.css @@ -35,6 +35,9 @@ html, body, #root { } /* Block native browser zoom gestures globally */ html { touch-action: manipulation; } +/* Prevent text selection globally (except inputs/textareas) */ +* { -webkit-user-select: none; user-select: none; } +input, textarea, [contenteditable] { -webkit-user-select: text; user-select: text; } /* ===== Layout ===== */ .app { display: flex; flex-direction: column; height: 100vh; } @@ -121,6 +124,14 @@ html { touch-action: manipulation; } } .module-header .close-btn:hover { background: var(--red); color: #fff; } +.module-header .expand-btn { + width: 18px; height: 18px; border: none; background: transparent; + color: var(--text2); cursor: pointer; font-size: 13px; border-radius: 3px; + display: flex; align-items: center; justify-content: center; + margin-left: auto; +} +.module-header .expand-btn:hover { background: var(--accent); color: #000; } + .module-body { padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; } /* Ports */