initial commit

This commit is contained in:
2026-05-09 12:34:45 -04:00
commit 97fabc2c17
49 changed files with 4856 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { api, BrowseResult } from "../../api/client";
import MediaViewer from "../MediaViewer/MediaViewer";
interface Props {
libraryId: number;
}
export default function FileBrowser({ libraryId }: Props) {
const [currentPath, setCurrentPath] = useState("");
const [viewingId, setViewingId] = useState<number | null>(null);
const { data, isLoading } = useQuery<BrowseResult>({
queryKey: ["browse", libraryId, currentPath],
queryFn: () => api.libraries.browse(libraryId, currentPath),
});
const pathParts = currentPath ? currentPath.split("/").filter(Boolean) : [];
function navigate(relPath: string) {
setCurrentPath(relPath);
setViewingId(null);
}
return (
<div style={{ padding: "1rem" }}>
{/* Breadcrumb */}
<nav style={{ marginBottom: 16, display: "flex", gap: 4, alignItems: "center", flexWrap: "wrap" }}>
<button onClick={() => navigate("")} style={{ background: "none", border: "none", cursor: "pointer", fontWeight: 600 }}>
Root
</button>
{pathParts.map((part, i) => {
const partPath = pathParts.slice(0, i + 1).join("/");
return (
<span key={partPath} style={{ display: "flex", alignItems: "center", gap: 4 }}>
<span style={{ color: "#888" }}>/</span>
<button onClick={() => navigate(partPath)} style={{ background: "none", border: "none", cursor: "pointer" }}>
{part}
</button>
</span>
);
})}
</nav>
{isLoading && <p>Loading</p>}
{/* Grid */}
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))", gap: 12 }}>
{data?.entries.map((entry) => (
<div
key={entry.rel_path}
onClick={() => {
if (entry.type === "dir") navigate(entry.rel_path);
else if (entry.media_item_id) setViewingId(entry.media_item_id);
}}
style={{
cursor: "pointer",
border: "1px solid #ddd",
borderRadius: 6,
overflow: "hidden",
background: "#fafafa",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
{entry.type === "dir" ? (
<div style={{ fontSize: 40, padding: "20px 0" }}>📁</div>
) : (
<img
src={entry.media_item_id ? api.media.thumbnailUrl(entry.media_item_id) : ""}
alt={entry.name}
loading="lazy"
style={{ width: "100%", height: 110, objectFit: "cover" }}
onError={(e) => { (e.target as HTMLImageElement).style.display = "none"; }}
/>
)}
<div style={{ padding: "4px 6px", fontSize: 12, width: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{entry.name}
</div>
</div>
))}
</div>
{viewingId && data && (
<MediaViewer
mediaId={viewingId}
siblings={data.entries}
onClose={() => setViewingId(null)}
onNavigate={setViewingId}
/>
)}
</div>
);
}