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 { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { api, BrowseResult } from "../../api/client";
import { api, type BrowseResult } from "../../api/client";
import MediaViewer from "../MediaViewer/MediaViewer";
interface Props {
@@ -27,15 +27,15 @@ export default function FileBrowser({ libraryId }: Props) {
<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 }}>
<button onClick={() => navigate("")} style={{ background: "none", border: "none", cursor: "pointer", fontWeight: 600, color: "var(--text)", padding: "2px 4px" }}>
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" }}>
<span style={{ color: "var(--text-muted)" }}>/</span>
<button onClick={() => navigate(partPath)} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--text)", padding: "2px 4px" }}>
{part}
</button>
</span>
@@ -43,7 +43,7 @@ export default function FileBrowser({ libraryId }: Props) {
})}
</nav>
{isLoading && <p>Loading</p>}
{isLoading && <p style={{ color: "var(--text-secondary)" }}>Loading</p>}
{/* Grid */}
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))", gap: 12 }}>
@@ -55,14 +55,15 @@ export default function FileBrowser({ libraryId }: Props) {
else if (entry.media_item_id) setViewingId(entry.media_item_id);
}}
style={{
cursor: "pointer",
border: "1px solid #ddd",
cursor: entry.type === "dir" || entry.media_item_id ? "pointer" : "default",
border: "1px solid var(--border)",
borderRadius: 6,
overflow: "hidden",
background: "#fafafa",
background: "var(--bg-card)",
display: "flex",
flexDirection: "column",
alignItems: "center",
opacity: entry.type !== "dir" && !entry.media_item_id ? 0.5 : 1,
}}
>
{entry.type === "dir" ? (
@@ -76,7 +77,7 @@ export default function FileBrowser({ libraryId }: Props) {
onError={(e) => { (e.target as HTMLImageElement).style.display = "none"; }}
/>
)}
<div style={{ padding: "4px 6px", fontSize: 12, width: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
<div style={{ padding: "4px 6px", fontSize: 12, width: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--text)" }}>
{entry.name}
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { api, BrowseEntry, MediaItem } from "../../api/client";
import { api, type BrowseEntry, type MediaItem } from "../../api/client";
import TagPanel from "../TagPanel/TagPanel";
interface Props {

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api, Tag, TagsByCategory, MediaItem } from "../../api/client";
import { api, type Tag, type TagsByCategory, type MediaItem } from "../../api/client";
interface Props {
item: MediaItem;
@@ -13,7 +13,6 @@ export default function TagPanel({ item }: Props) {
queryFn: api.tags.list,
});
const allTags = grouped.flatMap((g) => g.tags);
const assignedIds = new Set(item.tags.map((t) => t.id));
const [newName, setNewName] = useState("");
@@ -28,8 +27,7 @@ export default function TagPanel({ item }: Props) {
mutationFn: () => api.tags.create(newName.trim(), newCategory.trim()),
onSuccess: (tag: Tag) => {
qc.invalidateQueries({ queryKey: ["tags"] });
const newIds = [...assignedIds, tag.id];
setTagsMutation.mutate(newIds);
setTagsMutation.mutate([...assignedIds, tag.id]);
setNewName("");
setNewCategory("");
},
@@ -44,11 +42,11 @@ export default function TagPanel({ item }: Props) {
return (
<div style={{ minWidth: 220 }}>
<h3 style={{ margin: "0 0 12px" }}>Tags</h3>
<h3 style={{ margin: "0 0 12px", color: "var(--text)" }}>Tags</h3>
{grouped.map((group) => (
<div key={group.category} style={{ marginBottom: 12 }}>
<div style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>
<div style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", color: "var(--text-muted)", marginBottom: 4 }}>
{group.category}
</div>
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
@@ -61,9 +59,9 @@ export default function TagPanel({ item }: Props) {
borderRadius: 12,
border: "1px solid",
cursor: "pointer",
background: assignedIds.has(tag.id) ? "#3b82f6" : "transparent",
color: assignedIds.has(tag.id) ? "#fff" : "inherit",
borderColor: assignedIds.has(tag.id) ? "#3b82f6" : "#ccc",
background: assignedIds.has(tag.id) ? "var(--accent)" : "transparent",
color: assignedIds.has(tag.id) ? "#fff" : "var(--text)",
borderColor: assignedIds.has(tag.id) ? "var(--accent)" : "var(--border)",
fontSize: 13,
}}
>
@@ -75,19 +73,17 @@ export default function TagPanel({ item }: Props) {
))}
<details style={{ marginTop: 16 }}>
<summary style={{ cursor: "pointer", fontSize: 13, color: "#3b82f6" }}>+ New tag</summary>
<summary style={{ cursor: "pointer", fontSize: 13, color: "var(--accent-text)" }}>+ New tag</summary>
<div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 8 }}>
<input
placeholder="Tag name"
value={newName}
onChange={(e) => setNewName(e.target.value)}
style={{ padding: "4px 8px" }}
/>
<input
placeholder="Category"
value={newCategory}
onChange={(e) => setNewCategory(e.target.value)}
style={{ padding: "4px 8px" }}
/>
<button
onClick={() => createTagMutation.mutate()}