fix(MediaManager): display loading progress in MediaManager while fetching directory contents
fix(webdav): enhance listDirectory to report progress during directory listing fix(package): add @types/react-dom dependency for improved TypeScript support
This commit is contained in:
parent
c7048678b2
commit
421a667548
|
|
@ -87,6 +87,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "4.1.12",
|
"@tailwindcss/vite": "4.1.12",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.160.0",
|
||||||
"@vitejs/plugin-react": "4.7.0",
|
"@vitejs/plugin-react": "4.7.0",
|
||||||
"tailwindcss": "4.1.12",
|
"tailwindcss": "4.1.12",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import {
|
import {
|
||||||
AlignLeft,
|
AlignLeft,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
|
@ -459,14 +460,16 @@ export default function MediaManager({ initialPath, rootPath, title, config, set
|
||||||
// ── Directory loading ────────────────────────────────────────────────────
|
// ── Directory loading ────────────────────────────────────────────────────
|
||||||
|
|
||||||
const [folderConfig, setFolderConfig] = useState<Record<string, string> | null>(null);
|
const [folderConfig, setFolderConfig] = useState<Record<string, string> | null>(null);
|
||||||
|
const [loadedCount, setLoadedCount] = useState<number | null>(null);
|
||||||
|
|
||||||
const load = useCallback(async (p: string) => {
|
const load = useCallback(async (p: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setSelected(new Set());
|
setSelected(new Set());
|
||||||
setFolderConfig(null);
|
setFolderConfig(null);
|
||||||
|
setLoadedCount(null);
|
||||||
try {
|
try {
|
||||||
const entries = await listDirectory(p);
|
const entries = await listDirectory(p, false, bytes => flushSync(() => setLoadedCount(bytes)));
|
||||||
setEntries(entries);
|
setEntries(entries);
|
||||||
try {
|
try {
|
||||||
const blob = await getFileContents(joinPath(p, '.config'));
|
const blob = await getFileContents(joinPath(p, '.config'));
|
||||||
|
|
@ -1179,7 +1182,8 @@ export default function MediaManager({ initialPath, rootPath, title, config, set
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="p-8 flex flex-col items-center gap-2 text-neutral-500 text-sm">
|
<div className="p-8 flex flex-col items-center gap-2 text-neutral-500 text-sm">
|
||||||
<Loader2 className="w-6 h-6 animate-spin" /> Loading…
|
<Loader2 className="w-6 h-6 animate-spin" />
|
||||||
|
{loadedCount === null ? 'Loading…' : humanFileSize(loadedCount)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,20 +171,44 @@ function toEntryInfo(e: WebDAVEntry, baseUrl: string): EntryInfo {
|
||||||
* return all descendants flattened — requires the server to support it
|
* return all descendants flattened — requires the server to support it
|
||||||
* (webdav3.py does after the depth-infinity fix).
|
* (webdav3.py does after the depth-infinity fix).
|
||||||
*/
|
*/
|
||||||
export async function listDirectory(path: string, recursive = false): Promise<EntryInfo[]> {
|
export async function listDirectory(
|
||||||
|
path: string,
|
||||||
|
recursive = false,
|
||||||
|
onProgress?: (bytes: number) => void,
|
||||||
|
): Promise<EntryInfo[]> {
|
||||||
const manager = getWebDAVClient();
|
const manager = getWebDAVClient();
|
||||||
const base = manager.client.baseUrl;
|
const base = manager.client.baseUrl;
|
||||||
const collectionUrl = pathToUrl(normalizePath(path), base);
|
const collectionUrl = pathToUrl(normalizePath(path), base);
|
||||||
const selfPath = normalizePath(path);
|
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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const depth: any = recursive ? 'infinity' : 1;
|
const depth: any = recursive ? 'infinity' : 1;
|
||||||
const doc = await manager.client.propfind(collectionUrl, PROPFIND_LIST_BODY, depth);
|
|
||||||
|
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 entries: EntryInfo[] = [];
|
const entries: EntryInfo[] = [];
|
||||||
|
|
||||||
for (const node of Array.from(doc.querySelectorAll('response'))) {
|
for (const node of Array.from(doc.querySelectorAll('response'))) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user