|
44 | 44 | let newFileName = ''; |
45 | 45 | let newFileInput: HTMLInputElement; |
46 | 46 |
|
| 47 | + // ── Navigation history ────────────────────────────────────────────────── |
| 48 | + type NavEntry = { path: string; file: string | null }; |
| 49 | + let navHistory: NavEntry[] = []; |
| 50 | + let navIndex = -1; |
| 51 | + let navigatingHistory = false; |
| 52 | +
|
| 53 | + $: canGoBack = navIndex > 0; |
| 54 | + $: canGoForward = navIndex < navHistory.length - 1; |
| 55 | +
|
| 56 | + const pushNavHistory = (path: string, file: string | null = null) => { |
| 57 | + if (navigatingHistory) return; |
| 58 | + const current = navHistory[navIndex]; |
| 59 | + if (current && current.path === path && current.file === file) return; |
| 60 | + if (navIndex < navHistory.length - 1) { |
| 61 | + navHistory = navHistory.slice(0, navIndex + 1); |
| 62 | + } |
| 63 | + navHistory = [...navHistory, { path, file }]; |
| 64 | + navIndex = navHistory.length - 1; |
| 65 | + }; |
| 66 | +
|
| 67 | + const goBack = async () => { |
| 68 | + if (!canGoBack) return; |
| 69 | + navigatingHistory = true; |
| 70 | + navIndex -= 1; |
| 71 | + const entry = navHistory[navIndex]; |
| 72 | + await loadDir(entry.path); |
| 73 | + if (entry.file) { |
| 74 | + const fileName = entry.file.split('/').pop() ?? ''; |
| 75 | + await openEntry({ name: fileName, type: 'file', size: 0 }); |
| 76 | + } |
| 77 | + navigatingHistory = false; |
| 78 | + }; |
| 79 | +
|
| 80 | + const goForward = async () => { |
| 81 | + if (!canGoForward) return; |
| 82 | + navigatingHistory = true; |
| 83 | + navIndex += 1; |
| 84 | + const entry = navHistory[navIndex]; |
| 85 | + await loadDir(entry.path); |
| 86 | + if (entry.file) { |
| 87 | + const fileName = entry.file.split('/').pop() ?? ''; |
| 88 | + await openEntry({ name: fileName, type: 'file', size: 0 }); |
| 89 | + } |
| 90 | + navigatingHistory = false; |
| 91 | + }; |
| 92 | +
|
47 | 93 | let _reqId = 0; |
48 | 94 |
|
49 | 95 | const IMAGE_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'avif']); |
|
106 | 152 | clearPreview(); |
107 | 153 | currentPath = path.endsWith('/') ? path : path + '/'; |
108 | 154 | savedPyodidePath = currentPath; |
| 155 | + pushNavHistory(currentPath); |
109 | 156 |
|
110 | 157 | try { |
111 | 158 | const res = await sendWorkerMessage({ |
|
130 | 177 | } |
131 | 178 |
|
132 | 179 | const filePath = `${currentPath}${entry.name}`; |
| 180 | + pushNavHistory(currentPath, filePath); |
133 | 181 | selectedFile = filePath; |
134 | 182 | fileLoading = true; |
135 | 183 | clearPreview(); |
|
346 | 394 | {breadcrumbs} |
347 | 395 | {selectedFile} |
348 | 396 | {loading} |
| 397 | + {canGoBack} |
| 398 | + {canGoForward} |
| 399 | + onGoBack={goBack} |
| 400 | + onGoForward={goForward} |
349 | 401 | onNavigate={(path) => loadDir(path)} |
350 | 402 | onRefresh={async () => { |
351 | | - // Sync from IndexedDB first to pick up files written by code execution |
352 | | - try { |
353 | | - await sendWorkerMessage({ type: 'fs:sync' }); |
354 | | - } catch {} |
355 | | - if (selectedFile) { |
356 | | - const name = selectedFile.split('/').pop() ?? ''; |
357 | | - openEntry({ name, type: 'file', size: 0 }); |
358 | | - } else { |
359 | | - loadDir(currentPath); |
360 | | - } |
361 | | - }} |
| 403 | + try { |
| 404 | + await sendWorkerMessage({ type: 'fs:sync' }); |
| 405 | + } catch {} |
| 406 | + if (selectedFile) { |
| 407 | + const name = selectedFile.split('/').pop() ?? ''; |
| 408 | + openEntry({ name, type: 'file', size: 0 }); |
| 409 | + } else { |
| 410 | + loadDir(currentPath); |
| 411 | + } |
| 412 | + }} |
362 | 413 | onNewFolder={startNewFolder} |
363 | 414 | onNewFile={startNewFile} |
364 | 415 | onUploadFiles={uploadFiles} |
|
384 | 435 | /> |
385 | 436 | </svg> |
386 | 437 | </button> |
387 | | - <button |
388 | | - class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400" |
389 | | - on:click={() => { |
390 | | - if (selectedFile) { |
391 | | - confirmDelete(selectedFile, selectedFile.split('/').pop() ?? ''); |
392 | | - } |
393 | | - }} |
394 | | - aria-label={$i18n.t('Delete')} |
395 | | - > |
396 | | - <svg |
397 | | - xmlns="http://www.w3.org/2000/svg" |
398 | | - viewBox="0 0 20 20" |
399 | | - fill="currentColor" |
400 | | - class="size-3.5" |
401 | | - > |
402 | | - <path |
403 | | - fill-rule="evenodd" |
404 | | - d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022 1.005 11.36A2.75 2.75 0 0 0 7.764 20h4.472a2.75 2.75 0 0 0 2.745-2.689l1.005-11.36.149.022a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 1 .7.8l-.3 6a.75.75 0 0 1-1.5-.075l.3-6a.75.75 0 0 1 .8-.725ZM12.2 8.52a.75.75 0 0 0-1.5.075l-.3 6a.75.75 0 0 0 1.5-.075l.3-6Z" |
405 | | - clip-rule="evenodd" |
406 | | - /> |
407 | | - </svg> |
408 | | - </button> |
409 | 438 | </FileNavToolbar> |
410 | 439 |
|
411 | 440 | <!-- Content area --> |
|
0 commit comments