fix: My Computer auto-scan không mở picker, playhead chạy tới cuối canvas
This commit is contained in:
+24
-28
@@ -9353,31 +9353,8 @@ const MediaExplorerPanel = ({ height }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
if (window.showDirectoryPicker) {
|
// Auto-scan ổ đĩa/thư mục hệ thống (server API = máy local), không mở hộp thoại picker
|
||||||
try {
|
|
||||||
const handle = await window.showDirectoryPicker({ mode: 'read' });
|
|
||||||
setComputerMode('client');
|
|
||||||
setClientRoot(handle);
|
|
||||||
const rootEntry = { name: handle.name, path: 'root', is_dir: true, handle };
|
|
||||||
setComputerRoots([rootEntry]);
|
|
||||||
setComputerPath('root');
|
|
||||||
setComputerFiles([]);
|
|
||||||
setComputerTree({ root: { handle, parent: null, dirs: [], expanded: true } });
|
|
||||||
const browsed = await browseComputerDir(rootEntry);
|
|
||||||
// Tự động load cấu trúc: expand từng thư mục con cấp 1 để hiển thị cây
|
|
||||||
if (browsed && browsed.dirs) {
|
|
||||||
browsed.dirs.slice(0, 10).forEach(d => browseComputerDir(d));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
if (e && e.name === 'AbortError') return;
|
|
||||||
window.showToast && window.showToast('Không thể mở thư mục: ' + e.message, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fallback: server API (trình duyệt không hỗ trợ File System Access API)
|
|
||||||
setComputerMode('server');
|
setComputerMode('server');
|
||||||
if (computerRoots) return;
|
|
||||||
setComputerRoots(null);
|
setComputerRoots(null);
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${API_BASE_URL}/api/v1/media/computer`);
|
const resp = await fetch(`${API_BASE_URL}/api/v1/media/computer`);
|
||||||
@@ -9386,6 +9363,23 @@ const MediaExplorerPanel = ({ height }) => {
|
|||||||
const roots = data.roots || [];
|
const roots = data.roots || [];
|
||||||
setComputerRoots(roots.length ? roots : [{ path: '/', name: 'Root (/)', is_dir: true }]);
|
setComputerRoots(roots.length ? roots : [{ path: '/', name: 'Root (/)', is_dir: true }]);
|
||||||
if (!roots.length) window.showToast && window.showToast('Không tìm thấy ổ đĩa nào', 'warning');
|
if (!roots.length) window.showToast && window.showToast('Không tìm thấy ổ đĩa nào', 'warning');
|
||||||
|
// Tự động quét: expand từng ổ đĩa để hiển thị cây thư mục
|
||||||
|
const rootList = roots.length ? roots : [{ path: '/', name: 'Root (/)', is_dir: true }];
|
||||||
|
rootList.slice(0, 10).forEach(async (root) => {
|
||||||
|
const res = await browseComputerDir(root);
|
||||||
|
if (res && res.dirs) {
|
||||||
|
res.dirs.slice(0, 8).forEach(d => browseComputerDir(d));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Nếu có root handle đã lưu (File System Access API), cũng tự động quét client tree
|
||||||
|
const savedHandle = await loadClientRootHandle();
|
||||||
|
if (savedHandle && savedHandle.kind === 'directory' && !computerRoots) {
|
||||||
|
setClientRoot(savedHandle);
|
||||||
|
setComputerRoots([{ name: savedHandle.name, path: 'root', is_dir: true, handle: savedHandle }]);
|
||||||
|
setComputerMode('client');
|
||||||
|
const browsed = await browseComputerDir({ name: savedHandle.name, path: 'root', is_dir: true, handle: savedHandle });
|
||||||
|
if (browsed && browsed.dirs) browsed.dirs.slice(0, 10).forEach(d => browseComputerDir(d));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setComputerRoots([{ path: '/', name: 'Root (/)', is_dir: true }]);
|
setComputerRoots([{ path: '/', name: 'Root (/)', is_dir: true }]);
|
||||||
window.showToast && window.showToast('Không thể truy cập My Computer: ' + e.message, 'error');
|
window.showToast && window.showToast('Không thể truy cập My Computer: ' + e.message, 'error');
|
||||||
@@ -9810,8 +9804,9 @@ const MediaExplorerPanel = ({ height }) => {
|
|||||||
ctx.fillRect(nx, ny, Math.max(8, (i % 5 + 1) * 10), 3);
|
ctx.fillRect(nx, ny, Math.max(8, (i % 5 + 1) * 10), 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// playhead (drawn over MIDI): moves to frame center, then stops there & content scrolls
|
// playhead (drawn over MIDI): moves to frame center, then stops there & content scrolls.
|
||||||
const midiPlayheadX = contentW > w ? Math.min(w / 2, t * pxPerSec) : Math.min(w, t * pxPerSec);
|
// Khi content không scroll được nữa (offset đạt max) thì playhead tiếp tục chạy tới cuối canvas.
|
||||||
|
const midiPlayheadX = contentW > w ? Math.max(0, Math.min(w, t * pxPerSec - offset)) : Math.min(w, t * pxPerSec);
|
||||||
if (playing && dur > 0) {
|
if (playing && dur > 0) {
|
||||||
ctx.fillStyle = '#ef4444';
|
ctx.fillStyle = '#ef4444';
|
||||||
ctx.fillRect(midiPlayheadX - 1, 0, 2, h - 14);
|
ctx.fillRect(midiPlayheadX - 1, 0, 2, h - 14);
|
||||||
@@ -9837,8 +9832,9 @@ const MediaExplorerPanel = ({ height }) => {
|
|||||||
const ph = Math.max(2, pk[i] * (h / 2 - 4));
|
const ph = Math.max(2, pk[i] * (h / 2 - 4));
|
||||||
ctx.fillRect(x, mid - ph, barW, ph * 2);
|
ctx.fillRect(x, mid - ph, barW, ph * 2);
|
||||||
}
|
}
|
||||||
// Playhead theo tỉ lệ tempo; khi content rộng hơn frame thì khóa giữa + scroll
|
// Playhead theo tỉ lệ tempo; khi content rộng hơn frame thì khóa giữa + scroll.
|
||||||
const audioPlayheadX = contentW > w ? Math.min(w / 2, t * pxPerSec) : (t * pxPerSec);
|
// Khi content không scroll được nữa (offset đạt max) thì playhead tiếp tục chạy tới cuối canvas.
|
||||||
|
const audioPlayheadX = contentW > w ? Math.max(0, Math.min(w, t * pxPerSec - offset)) : (t * pxPerSec);
|
||||||
if (playing && dur > 0) {
|
if (playing && dur > 0) {
|
||||||
ctx.fillStyle = '#ef4444';
|
ctx.fillStyle = '#ef4444';
|
||||||
ctx.fillRect(audioPlayheadX - 1, 0, 2, h - 14);
|
ctx.fillRect(audioPlayheadX - 1, 0, 2, h - 14);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608021956" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608021965" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1149,3 +1149,8 @@
|
|||||||
- **Tóm tắt thay đổi:** (1) Khi mở My Computer client-side (pick xong root), tự động browse + expand tối đa 10 thư mục con cấp 1 (`browseComputerDir`/`browseClientDir` giờ trả về `{dirs, files}` để auto-expand) → cây hiển thị cấu trúc ổ đĩa/thư mục ngay. (2) **Favorites**: node "Favorites" ngay dưới My Computer (hiện khi có favorite, icon `fa-star`); chuột phải folder trong cây (hoặc trong Favorites) → context menu "Thêm vào Favorites"/"Gỡ khỏi Favorites" + "Mở thư mục"; lưu vào `localStorage['studio_media_explorer_favorites_v1']` `[{path, name}]`; click favorite mở nhanh folder (tìm handle trong `computerTree`, hoặc walk lại từ `clientRoot` qua `getDirectoryHandle` theo segments path, fallback `browseComputerDir`); node folder có favorite hiện icon star.
|
- **Tóm tắt thay đổi:** (1) Khi mở My Computer client-side (pick xong root), tự động browse + expand tối đa 10 thư mục con cấp 1 (`browseComputerDir`/`browseClientDir` giờ trả về `{dirs, files}` để auto-expand) → cây hiển thị cấu trúc ổ đĩa/thư mục ngay. (2) **Favorites**: node "Favorites" ngay dưới My Computer (hiện khi có favorite, icon `fa-star`); chuột phải folder trong cây (hoặc trong Favorites) → context menu "Thêm vào Favorites"/"Gỡ khỏi Favorites" + "Mở thư mục"; lưu vào `localStorage['studio_media_explorer_favorites_v1']` `[{path, name}]`; click favorite mở nhanh folder (tìm handle trong `computerTree`, hoặc walk lại từ `clientRoot` qua `getDirectoryHandle` theo segments path, fallback `browseComputerDir`); node folder có favorite hiện icon star.
|
||||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js`, `app/templates/index.html`
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js`, `app/templates/index.html`
|
||||||
- **Ghi chú/Test (nếu có):** Smoke jsdom: pick root → C: + Users + Program Files auto-load; right-click Users → menu → favorite saved localStorage + node Favorites hiện; click favorite → mở `root/Users`. Hard reload.
|
- **Ghi chú/Test (nếu có):** Smoke jsdom: pick root → C: + Users + Program Files auto-load; right-click Users → menu → favorite saved localStorage + node Favorites hiện; click favorite → mở `root/Users`. Hard reload.
|
||||||
|
|
||||||
|
### [2026-08-02 19:65] Task: My Computer auto-scan + playhead chạy tới cuối khi hết scroll
|
||||||
|
- **Tóm tắt thay đổi:** (1) `openMyComputer` bỏ `showDirectoryPicker` khi click node My Computer — giờ tự động quét server API (`/api/v1/media/computer` liệt kê ổ đĩa máy local + `/browse` expand ổ đĩa → expand thư mục con cấp 1), cây hiển thị ngay không cần hộp thoại; vẫn restore session/handle IndexedDB nếu có. (2) Playhead trong `drawCanvas` (MIDI + audio): công thức `max(0, min(w, t*pxPerSec - offset))` — khi content rộng hơn frame, playhead chạy tới giữa rồi khóa ở giữa trong khi content scroll trái; khi offset đạt max (contentW-w) thì playhead tiếp tục chạy với tốc độ play tới cuối canvas.
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js`, `app/templates/index.html`
|
||||||
|
- **Ghi chú/Test (nếu có):** Smoke: click My Computer → picker NOT called, DATA/HOME + Music/user auto-load; playhead math: big clip t=1→84, t=3→150 (center), t=5→150 locked, t=10→300 (end). Hard reload.
|
||||||
|
|||||||
Reference in New Issue
Block a user