v2: global player, infinite scroll, image picker, threaded comments

This commit is contained in:
khannurien
2026-03-21 13:55:22 +00:00
parent be426eb150
commit 7c098e7c4c
48 changed files with 4346 additions and 711 deletions

View File

@@ -20,13 +20,15 @@ type DumpEditState =
export function DumpEdit() {
const { selectedDump } = useParams();
const navigate = useNavigate();
const { authFetch } = useRequiredAuth();
const { authFetch, token } = useRequiredAuth();
const [state, setState] = useState<DumpEditState>({ status: "loading" });
const [url, setUrl] = useState("");
const [comment, setComment] = useState("");
const [isPrivate, setIsPrivate] = useState(false);
const [newFile, setNewFile] = useState<File | null>(null);
const [confirmDelete, setConfirmDelete] = useState(false);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
if (!selectedDump) return;
@@ -37,6 +39,7 @@ export function DumpEdit() {
try {
const res = await fetch(`${API_URL}/api/dumps/${selectedDump}`, {
cache: "no-store",
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
@@ -46,6 +49,7 @@ export function DumpEdit() {
const dump: Dump = deserializeDump(apiResponse.data);
setUrl(dump.url ?? "");
setComment(dump.comment ?? "");
setIsPrivate(dump.isPrivate);
setState({ status: "loaded", dump });
} else {
setState({ status: "error", error: apiResponse.error.message });
@@ -74,8 +78,8 @@ export function DumpEdit() {
});
} else {
const body: UpdateDumpRequest = state.dump.kind === "url"
? { url: url.trim() || undefined, comment: comment.trim() || undefined }
: { comment: comment.trim() || undefined };
? { url: url.trim() || undefined, comment: comment.trim() || undefined, isPrivate }
: { comment: comment.trim() || undefined, isPrivate };
res = await authFetch(`${API_URL}/api/dumps/${state.dump.id}`, {
method: "PUT",
body: JSON.stringify(body),
@@ -102,6 +106,25 @@ export function DumpEdit() {
navigate(`/dumps/${updatedDump.id}`, { state: { dump: updatedDump } });
};
const handleRefreshMetadata = async () => {
if (state.status !== "loaded" || state.dump.kind !== "url") return;
setRefreshing(true);
try {
const res = await authFetch(
`${API_URL}/api/dumps/${state.dump.id}/refresh-metadata`,
{ method: "POST" },
);
const apiResponse = await res.json();
if (apiResponse.success) {
const updatedDump: Dump = deserializeDump(apiResponse.data);
setState({ status: "loaded", dump: updatedDump });
}
} finally {
setRefreshing(false);
}
};
const handleDelete = async () => {
if (state.status !== "loaded") return;
@@ -176,6 +199,16 @@ export function DumpEdit() {
{dump.url}
</a>
)}
{dump.kind === "url" && (
<button
type="button"
className="btn-secondary dump-edit-refresh"
onClick={handleRefreshMetadata}
disabled={refreshing}
>
{refreshing ? "Refreshing…" : "Refresh metadata"}
</button>
)}
</div>
<form
@@ -230,6 +263,21 @@ export function DumpEdit() {
/>
</div>
<label className="toggle-row">
<span className="toggle-label">Public</span>
<span className="toggle-switch">
<input
type="checkbox"
checked={!isPrivate}
onChange={(e) => setIsPrivate(!e.target.checked)}
/>
<span className="toggle-thumb" />
</span>
{isPrivate && (
<span className="toggle-hint">Only visible to you</span>
)}
</label>
<div className="form-actions">
<button
type="button"