FIX: Media Explorer panel chọn tập tin ở thư mục nào thì di chuyển đến thư mục đó
This commit is contained in:
+101
-5
@@ -9037,6 +9037,9 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
});
|
||||
|
||||
const [zoom, setZoom] = React.useState(1.0);
|
||||
const [scrollOffset, setScrollOffset] = React.useState(0);
|
||||
const scrollOffsetRef = React.useRef(0);
|
||||
scrollOffsetRef.current = scrollOffset;
|
||||
const [selStart, setSelStart] = React.useState(null);
|
||||
const [selEnd, setSelEnd] = React.useState(null);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
@@ -9072,6 +9075,16 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
tempoRef.current = tempo;
|
||||
currentTimeRef.current = currentTime;
|
||||
|
||||
React.useEffect(() => {
|
||||
setScrollOffset(0);
|
||||
}, [selected]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
window.mediaExplorerActive = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getCanvasLayout = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return { w: 300, h: 100, pxPerBeat: 42, pxPerSec: 84, offset: 0, dur: 0, contentW: 0 };
|
||||
@@ -9093,9 +9106,11 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
contentW = Math.max(1, dur * pxPerSec);
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
let offset = scrollOffset;
|
||||
if (isPlayingRef.current && contentW > w && dur > 0) {
|
||||
offset = Math.max(0, Math.min(contentW - w, currentTimeRef.current * pxPerSec - w / 2));
|
||||
} else {
|
||||
offset = Math.max(0, Math.min(contentW - w, scrollOffset));
|
||||
}
|
||||
return { w, h, pxPerBeat, pxPerSec, offset, dur, contentW };
|
||||
};
|
||||
@@ -9119,6 +9134,16 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
const handleCanvasWheel = (e) => {
|
||||
if (!selected) return;
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
const scrollSpeed = 45;
|
||||
const direction = e.deltaY > 0 ? 1 : -1;
|
||||
setScrollOffset(prev => {
|
||||
const { contentW, w } = getCanvasLayout();
|
||||
const maxScroll = Math.max(0, contentW - w);
|
||||
return Math.max(0, Math.min(maxScroll, prev + direction * scrollSpeed));
|
||||
});
|
||||
return;
|
||||
}
|
||||
const zoomFactor = 1.15;
|
||||
if (e.deltaY < 0) {
|
||||
setZoom(prev => Math.min(10.0, prev * zoomFactor));
|
||||
@@ -9278,6 +9303,34 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
return () => window.removeEventListener('click', handleGlobalClick);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === ' ' || e.code === 'Space') {
|
||||
if (window.mediaExplorerActive) {
|
||||
const target = e.target;
|
||||
if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (isPlayingRef.current) {
|
||||
stopMediaPlayback();
|
||||
} else {
|
||||
const cur = selectedRef.current;
|
||||
if (cur) {
|
||||
selectTokenRef.current++;
|
||||
playSelected(cur, selectTokenRef.current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown, { capture: true });
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown, { capture: true });
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [computerRoots, setComputerRoots] = React.useState(null);
|
||||
const [computerTree, setComputerTree] = React.useState({});
|
||||
const [computerPath, setComputerPath] = React.useState(null);
|
||||
@@ -10081,9 +10134,12 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
const beats = Math.max(totalBeats, 4);
|
||||
const contentW = Math.max(w, beats * pxPerBeat);
|
||||
|
||||
let offset = 0;
|
||||
const offsetVal = scrollOffsetRef.current;
|
||||
let offset = offsetVal;
|
||||
if (playing && contentW > w && dur > 0) {
|
||||
offset = Math.max(0, Math.min(contentW - w, t * pxPerSec - w / 2));
|
||||
} else {
|
||||
offset = Math.max(0, Math.min(contentW - w, offsetVal));
|
||||
}
|
||||
|
||||
// bar lines (every 4 beats)
|
||||
@@ -10138,9 +10194,12 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
const pk = curPeaks && curPeaks.length > 0 ? curPeaks : null;
|
||||
if (pk) {
|
||||
const contentW = Math.max(1, dur * pxPerSec);
|
||||
let offset = 0;
|
||||
const offsetVal = scrollOffsetRef.current;
|
||||
let offset = offsetVal;
|
||||
if (playing && contentW > w && dur > 0) {
|
||||
offset = Math.max(0, Math.min(contentW - w, t * pxPerSec - w / 2));
|
||||
} else {
|
||||
offset = Math.max(0, Math.min(contentW - w, offsetVal));
|
||||
}
|
||||
const mid = h / 2;
|
||||
ctx.fillStyle = '#22c55e';
|
||||
@@ -10193,11 +10252,46 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => { drawCanvas(currentTime); }, [peaks, audioBuffer, audioDuration, midiNotes, selected, folder, isPlaying, zoom, selStart, selEnd]);
|
||||
React.useEffect(() => { drawCanvas(currentTime); }, [peaks, audioBuffer, audioDuration, midiNotes, selected, folder, isPlaying, zoom, selStart, selEnd, scrollOffset]);
|
||||
React.useEffect(() => () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }, []);
|
||||
|
||||
const handleSelect = (f) => {
|
||||
if (!f || f.is_dir) return;
|
||||
|
||||
// Find parent path of selected file and scroll it into view in Tree pane
|
||||
if (f.path) {
|
||||
const lastSlash = f.path.lastIndexOf('/');
|
||||
if (lastSlash > 0) {
|
||||
const parentPath = f.path.substring(0, lastSlash);
|
||||
setComputerPath(parentPath);
|
||||
|
||||
// Expand all parent nodes in computerTree
|
||||
setComputerTree(prev => {
|
||||
const next = { ...prev };
|
||||
let current = parentPath;
|
||||
while (current) {
|
||||
if (!next[current]) {
|
||||
next[current] = { dirs: [], expanded: true };
|
||||
} else {
|
||||
next[current] = { ...next[current], expanded: true };
|
||||
}
|
||||
const idx = current.lastIndexOf('/');
|
||||
if (idx <= 0) break;
|
||||
current = current.substring(0, idx);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
|
||||
// Scroll the parent tree element into view
|
||||
setTimeout(() => {
|
||||
const treeNodeEl = document.querySelector(`[data-tree-path="${parentPath}"]`);
|
||||
if (treeNodeEl) {
|
||||
treeNodeEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
selectTokenRef.current++;
|
||||
const token = selectTokenRef.current;
|
||||
setSelected(f);
|
||||
@@ -10224,7 +10318,7 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
const fav = isFavorite(entry);
|
||||
return (
|
||||
<React.Fragment key={nodePath}>
|
||||
<div className={`flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm ${computerPath === nodePath ? 'bg-slate-300 text-slate-900' : 'hover:bg-slate-200 text-slate-800'}`}
|
||||
<div data-tree-path={nodePath} className={`flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm ${computerPath === nodePath ? 'bg-slate-300 text-slate-900' : 'hover:bg-slate-200 text-slate-800'}`}
|
||||
style={{ paddingLeft: pad }}
|
||||
onClick={() => browseComputerDir(entry)}
|
||||
onDoubleClick={e => { e.stopPropagation(); toggleComputerDir(entry); }}
|
||||
@@ -10315,6 +10409,7 @@ const MediaExplorerPanel = ({ height, clipboardRef }) => {
|
||||
<div className="pl-3 space-y-0.5">
|
||||
{favorites.map((fav, fi) => (
|
||||
<div key={fav.path + fi}
|
||||
data-tree-path={fav.path}
|
||||
className="flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm hover:bg-amber-100 text-slate-800"
|
||||
style={{ paddingLeft: 20 }}
|
||||
onClick={() => openFavorite(fav)}
|
||||
@@ -12666,6 +12761,7 @@ const App = () => {
|
||||
|
||||
// Global space play/pause shortcut for transport
|
||||
if (e.key === ' ' || e.code === 'Space') {
|
||||
if (window.mediaExplorerActive) return;
|
||||
e.preventDefault();
|
||||
if (recordingStateRef.current === 'RECORDING' || recordingStateRef.current === 'COUNT_IN') {
|
||||
handleRecordClickRef.current();
|
||||
|
||||
Reference in New Issue
Block a user