This commit is contained in:
2026-05-09 14:51:25 -04:00
parent 97fabc2c17
commit f23a8a2be6
20 changed files with 382 additions and 185 deletions

View File

@@ -1,6 +1,6 @@
import { useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { api, Library } from "../api/client";
import { api, type Library } from "../api/client";
import FileBrowser from "../components/FileBrowser/FileBrowser";
export default function BrowserPage() {
@@ -14,13 +14,13 @@ export default function BrowserPage() {
const library = libraries.find((l) => l.id === id);
if (!library) return <p style={{ padding: "2rem" }}>Library not found.</p>;
if (!library) return <p style={{ padding: "2rem", color: "var(--text)" }}>Library not found.</p>;
return (
<div>
<div style={{ padding: "1rem 1rem 0", borderBottom: "1px solid #eee" }}>
<h2 style={{ margin: 0 }}>{library.name}</h2>
<div style={{ fontSize: 12, color: "#888" }}>{library.path}</div>
<div style={{ padding: "1rem 1rem 0", borderBottom: "1px solid var(--border-subtle)" }}>
<h2 style={{ margin: 0, color: "var(--text)" }}>{library.name}</h2>
<div style={{ fontSize: 12, color: "var(--text-secondary)" }}>{library.path}</div>
</div>
<FileBrowser libraryId={id} />
</div>

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { api, MediaItem, TagsByCategory } from "../api/client";
import { api, type MediaItem, type TagsByCategory } from "../api/client";
export default function SearchPage() {
const [q, setQ] = useState("");
@@ -31,24 +31,23 @@ export default function SearchPage() {
return (
<div style={{ padding: "2rem", maxWidth: 900 }}>
<h1>Search</h1>
<h1 style={{ color: "var(--text)" }}>Search</h1>
<form onSubmit={handleSubmit} style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
<input
placeholder="Search by filename…"
value={q}
onChange={(e) => setQ(e.target.value)}
style={{ flex: 1, minWidth: 200, padding: "6px 10px" }}
style={{ flex: 1, minWidth: 200 }}
/>
<button type="submit">Search</button>
<button type="submit" style={{ background: "var(--accent)", color: "#fff", border: "none" }}>Search</button>
</form>
{/* Tag filter */}
{grouped.length > 0 && (
<div style={{ marginBottom: 24 }}>
<div style={{ fontSize: 13, color: "#666", marginBottom: 8 }}>Filter by tag:</div>
<div style={{ fontSize: 13, color: "var(--text-secondary)", marginBottom: 8 }}>Filter by tag:</div>
{grouped.map((group) => (
<div key={group.category} style={{ marginBottom: 8 }}>
<span style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", color: "#888", marginRight: 8 }}>
<span style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", color: "var(--text-muted)", marginRight: 8 }}>
{group.category}
</span>
{group.tags.map((tag) => (
@@ -61,9 +60,9 @@ export default function SearchPage() {
borderRadius: 12,
border: "1px solid",
cursor: "pointer",
background: selectedTags.includes(tag.id) ? "#3b82f6" : "transparent",
color: selectedTags.includes(tag.id) ? "#fff" : "inherit",
borderColor: selectedTags.includes(tag.id) ? "#3b82f6" : "#ccc",
background: selectedTags.includes(tag.id) ? "var(--accent)" : "transparent",
color: selectedTags.includes(tag.id) ? "#fff" : "var(--text)",
borderColor: selectedTags.includes(tag.id) ? "var(--accent)" : "var(--border)",
fontSize: 13,
}}
>
@@ -75,24 +74,24 @@ export default function SearchPage() {
</div>
)}
{isFetching && <p>Searching</p>}
{isFetching && <p style={{ color: "var(--text-secondary)" }}>Searching</p>}
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))", gap: 12 }}>
{results.map((item) => (
<div key={item.id} style={{ border: "1px solid #ddd", borderRadius: 6, overflow: "hidden" }}>
<div key={item.id} style={{ border: "1px solid var(--border)", borderRadius: 6, overflow: "hidden", background: "var(--bg-card)" }}>
<img
src={api.media.thumbnailUrl(item.id)}
alt={item.filename}
loading="lazy"
style={{ width: "100%", height: 110, objectFit: "cover" }}
/>
<div style={{ padding: "4px 6px", fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
<div style={{ padding: "4px 6px", fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--text)" }}>
{item.filename}
</div>
{item.tags.length > 0 && (
<div style={{ padding: "0 6px 4px", display: "flex", flexWrap: "wrap", gap: 4 }}>
{item.tags.map((t) => (
<span key={t.id} style={{ background: "#e0edff", color: "#3b82f6", borderRadius: 8, padding: "1px 6px", fontSize: 11 }}>
<span key={t.id} style={{ background: "var(--tag-bg)", color: "var(--accent-text)", borderRadius: 8, padding: "1px 6px", fontSize: 11 }}>
{t.name}
</span>
))}
@@ -103,7 +102,7 @@ export default function SearchPage() {
</div>
{submitted && !isFetching && results.length === 0 && (
<p style={{ color: "#888" }}>No results found.</p>
<p style={{ color: "var(--text-secondary)" }}>No results found.</p>
)}
</div>
);

View File

@@ -1,6 +1,37 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api, Library } from "../api/client";
import { api, type Library } from "../api/client";
function LibraryRow({ lib, onRemove }: { lib: Library; onRemove: (id: number) => void }) {
const { data } = useQuery({
queryKey: ["scan-status", lib.id],
queryFn: () => api.libraries.scanStatus(lib.id),
refetchInterval: (query) => (query.state.data?.scanning ? 2000 : false),
});
const scanning = data?.scanning ?? false;
return (
<li style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderBottom: "1px solid var(--border-subtle)" }}>
<div>
<strong style={{ color: "var(--text)" }}>{lib.name}</strong>
{scanning && (
<span style={{ marginLeft: 8, fontSize: 12, color: "var(--accent)" }}>
Scanning
</span>
)}
<div style={{ fontSize: 12, color: "var(--text-secondary)" }}>{lib.path}</div>
</div>
<button
onClick={() => onRemove(lib.id)}
disabled={scanning}
style={{ color: scanning ? "var(--text-muted)" : "var(--danger)", background: "transparent", border: "none" }}
>
Remove
</button>
</li>
);
}
export default function SettingsPage() {
const qc = useQueryClient();
@@ -31,49 +62,24 @@ export default function SettingsPage() {
return (
<div style={{ padding: "2rem", maxWidth: 600 }}>
<h1>Settings</h1>
<h2>Libraries</h2>
<h1 style={{ color: "var(--text)" }}>Settings</h1>
<h2 style={{ color: "var(--text)" }}>Libraries</h2>
<form
onSubmit={(e) => { e.preventDefault(); addMutation.mutate(); }}
style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 24 }}
>
<input
placeholder="Library name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
placeholder="/path/to/directory"
value={path}
onChange={(e) => setPath(e.target.value)}
required
/>
{error && <p style={{ color: "red", margin: 0 }}>{error}</p>}
<button type="submit" disabled={addMutation.isPending}>
<input placeholder="Library name" value={name} onChange={(e) => setName(e.target.value)} required />
<input placeholder="/path/to/directory" value={path} onChange={(e) => setPath(e.target.value)} required />
{error && <p style={{ color: "var(--danger)", margin: 0 }}>{error}</p>}
<button type="submit" disabled={addMutation.isPending} style={{ background: "var(--accent)", color: "#fff", border: "none" }}>
{addMutation.isPending ? "Adding…" : "Add Library"}
</button>
</form>
<ul style={{ listStyle: "none", padding: 0 }}>
{libraries.map((lib) => (
<li
key={lib.id}
style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderBottom: "1px solid #eee" }}
>
<div>
<strong>{lib.name}</strong>
<div style={{ fontSize: 12, color: "#666" }}>{lib.path}</div>
</div>
<button
onClick={() => deleteMutation.mutate(lib.id)}
disabled={deleteMutation.isPending}
style={{ color: "red" }}
>
Remove
</button>
</li>
<LibraryRow key={lib.id} lib={lib} onRemove={(id) => deleteMutation.mutate(id)} />
))}
</ul>
</div>