Compare commits

..

No commits in common. "cb33268dcc3cb916193bae99a863c88c1c0c75d3" and "c7048678b2682c2a797cea4f835354de2fbd610a" have entirely different histories.

4 changed files with 9 additions and 42 deletions

View File

@ -87,7 +87,6 @@
},
"devDependencies": {
"@tailwindcss/vite": "4.1.12",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.160.0",
"@vitejs/plugin-react": "4.7.0",
"tailwindcss": "4.1.12",

View File

@ -1,5 +1,4 @@
import {
Album,
BookOpen,
Braces,
CassetteTape,
@ -13,7 +12,6 @@ import {
Folder,
HardDrive,
Image as ImageIcon,
BookImage,
Layers,
MoreVertical,
Music,
@ -41,7 +39,6 @@ export const DISK_EXTS = new Set(['c64', 'd41', 'd64', 'd67', 'd71', 'd80', '
export const DISC_EXTS = new Set(['iso', 'img', 'cue']);
export const HD_EXTS = new Set(['d1m', 'd2m', 'd4m', 'd90', 'dhd', 'hdd', 'bbt', 'd8b', 'dfi']);
export const ARCHIVE_EXTS = new Set(['zip', '7z', 'tar', 'gz', 'bz2', 'xz', 'rar', 'arj', 'lzh', 'ace', 'z', 'lha', 'cab', 'lbr', 'arc', 'ark', 'lnx']);
export const COMIC_EXTS = new Set(['cbz', 'cbr', 'cb7', 'cbt', 'cbz']);
export const CONFIG_EXTS = new Set(['config']);
// ─── EntryIcon ────────────────────────────────────────────────────────────────
@ -65,7 +62,6 @@ export function EntryIcon({ entry }: { entry: EntryInfo }) {
if (DOC_EXTS.has(ext)) return <FileType className="w-5 h-5 text-blue-400 flex-shrink-0" />;
if (CODE_EXTS.has(ext)) return <Terminal className="w-5 h-5 text-green-600 flex-shrink-0" />;
if (TEXT_EXTS.has(ext)) return <FileText className="w-5 h-5 text-green-600 flex-shrink-0" />;
if (COMIC_EXTS.has(ext)) return <BookImage className="w-5 h-5 text-pink-500 flex-shrink-0" />;
return <File className="w-5 h-5 text-neutral-400 flex-shrink-0" />;
}

View File

@ -1,5 +1,4 @@
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';
import { flushSync } from 'react-dom';
import {
AlignLeft,
ArrowLeft,
@ -460,16 +459,14 @@ export default function MediaManager({ initialPath, rootPath, title, config, set
// ── Directory loading ────────────────────────────────────────────────────
const [folderConfig, setFolderConfig] = useState<Record<string, string> | null>(null);
const [loadedCount, setLoadedCount] = useState<number | null>(null);
const load = useCallback(async (p: string) => {
setLoading(true);
setError(null);
setSelected(new Set());
setFolderConfig(null);
setLoadedCount(null);
try {
const entries = await listDirectory(p, false, bytes => flushSync(() => setLoadedCount(bytes)));
const entries = await listDirectory(p);
setEntries(entries);
try {
const blob = await getFileContents(joinPath(p, '.config'));
@ -1182,8 +1179,7 @@ export default function MediaManager({ initialPath, rootPath, title, config, set
<div className="flex-1 overflow-y-auto">
{loading && (
<div className="p-8 flex flex-col items-center gap-2 text-neutral-500 text-sm">
<Loader2 className="w-6 h-6 animate-spin" />
{loadedCount === null ? 'Loading…' : humanFileSize(loadedCount)}
<Loader2 className="w-6 h-6 animate-spin" /> Loading
</div>
)}

View File

@ -171,44 +171,20 @@ function toEntryInfo(e: WebDAVEntry, baseUrl: string): EntryInfo {
* return all descendants flattened requires the server to support it
* (webdav3.py does after the depth-infinity fix).
*/
export async function listDirectory(
path: string,
recursive = false,
onProgress?: (bytes: number) => void,
): Promise<EntryInfo[]> {
export async function listDirectory(path: string, recursive = false): Promise<EntryInfo[]> {
const manager = getWebDAVClient();
const base = manager.client.baseUrl;
const collectionUrl = pathToUrl(normalizePath(path), base);
const selfPath = normalizePath(path);
// Parse the PROPFIND response directly rather than going through
// parsePropfindListing, which keys entries by display name and would
// silently drop any entries that share the same filename. Parsing
// the XML ourselves also avoids mutating the shared WebDAVManager
// navigation state (navToken, this.files).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const depth: any = recursive ? 'infinity' : 1;
let doc: Document;
if (onProgress) {
const response = await fetch(collectionUrl, {
method: 'PROPFIND',
headers: { 'Depth': String(depth), 'Content-Type': 'text/xml; charset=utf-8' },
body: PROPFIND_LIST_BODY,
});
if (!response.body) throw new Error('No response body');
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
let received = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.byteLength;
onProgress(received);
}
const combined = new Uint8Array(received);
let offset = 0;
for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.byteLength; }
doc = new DOMParser().parseFromString(new TextDecoder().decode(combined), 'text/xml');
} else {
doc = await manager.client.propfind(collectionUrl, PROPFIND_LIST_BODY, depth);
}
const doc = await manager.client.propfind(collectionUrl, PROPFIND_LIST_BODY, depth);
const entries: EntryInfo[] = [];
for (const node of Array.from(doc.querySelectorAll('response'))) {