diff --git a/components/utilities/files/FileBrowserPanel.tsx b/components/utilities/files/FileBrowserPanel.tsx index 3487187a..7262cdc6 100644 --- a/components/utilities/files/FileBrowserPanel.tsx +++ b/components/utilities/files/FileBrowserPanel.tsx @@ -1,6 +1,13 @@ "use client"; -import React, { useEffect, useMemo, useState, useCallback } from "react"; +import React, { + useEffect, + useMemo, + useState, + useCallback, + useRef, + MutableRefObject, +} from "react"; import { Loader2, Download, @@ -49,6 +56,8 @@ function formatSize(bytes?: number) { return `${v.toFixed(u ? 1 : 0)} ${units[u]}`; } +const LS_KEY = "fs.split.left"; // px + export default function FileBrowserPanel() { const [path, setPath] = useState("/"); const [items, setItems] = useState([]); @@ -56,6 +65,38 @@ export default function FileBrowserPanel() { const [error, setError] = useState(null); const [previewHref, setPreviewHref] = useState(null); + // ───────────────── Splitter state + const containerRef = useRef(null); + const [leftPx, setLeftPx] = useState(() => { + if (typeof window === "undefined") return 640; // SSR default + const v = Number(localStorage.getItem(LS_KEY)); + return Number.isFinite(v) && v > 0 ? v : 720; // good desktop start + }); + + // Enforce bounds on resize / first mount + const clampLeft = useCallback((want: number) => { + const host = containerRef.current; + if (!host) return want; + const total = host.clientWidth || 0; + const MIN_LEFT = 520; // so Name + columns fit + const MIN_RIGHT = 360; // keep preview usable + const maxLeft = Math.max(320, total - MIN_RIGHT); + return Math.min(Math.max(want, MIN_LEFT), maxLeft); + }, []); + + useEffect(() => { + const handle = () => setLeftPx((v) => clampLeft(v)); + window.addEventListener("resize", handle); + return () => window.removeEventListener("resize", handle); + }, [clampLeft]); + + useEffect(() => { + if (typeof window !== "undefined") { + localStorage.setItem(LS_KEY, String(leftPx)); + } + }, [leftPx]); + + // ───────────────── Data const urlList = useMemo(() => { const p = encodeURIComponent(path || "/"); return `${BASE}/api/files/list?path=${p}`; @@ -104,6 +145,7 @@ export default function FileBrowserPanel() { fetchList(); }, [fetchList]); + // ───────────────── Actions const onOpen = (it: FileItem) => { if (it.type === "dir") { setPath((p) => joinPath(p, it.name)); @@ -123,133 +165,194 @@ export default function FileBrowserPanel() { a.remove(); }; - return ( -
- + .split-gutter:hover { background: rgba(255,255,255,0.06); } + @media (max-width: 1023px) { + .split-wrap { display:block !important; } + .split-gutter { display:none; } + } + `} -
- -
- - -
-
+ {/* Toolbar */} +
+ -
{path || "/"}
- -
- {/* TABLE */} -
-
-
- {/* compact column plan: name grows; others stay tidy */} -
-
Name
-
Type
-
Size
-
Modified
-
Actions
-
- -
- {loading ? ( -
- Loading… +
+ +
- ) : error ? ( -
Error: {error}
- ) : items.length === 0 ? ( -
Empty folder.
- ) : ( - items.map((it) => ( -
- -
- {it.type} +
+ +
{path || "/"}
+ + {/* Split container */} +
+ {/* LEFT: table */} +
+
+
+ {/* Header: name flexes; others compact so Actions never truncates */} +
+
Name
+
Type
+
Size
+
Modified
+
Actions
+
+ +
+ {loading ? ( +
+ Loading…
-
{formatSize(it.size)}
-
- {it.mtimeMs ? new Date(it.mtimeMs).toLocaleString() : "—"} -
-
- {it.type === "file" && ( - - )} -
-
- )) - )} -
-
-
-
+
+ {it.type} +
+
{formatSize(it.size)}
+
+ {it.mtimeMs ? new Date(it.mtimeMs).toLocaleString() : "—"} +
+
+ {it.type === "file" && ( + + )} +
+
+ )) + )} +
+
+
+
- {/* PREVIEW */} -
-
Preview
- {previewHref ? ( -
-