v3: added localization, use global player for uploaded audio/video files
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router";
|
||||
import { t } from "@lingui/core/macro"
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { API_URL } from "../config/api.ts";
|
||||
import type {
|
||||
PlaylistWithDumps,
|
||||
@@ -108,7 +116,7 @@ export function PlaylistDetail() {
|
||||
|
||||
const fetchAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const fetchPlaylist = () => {
|
||||
const fetchPlaylist = useCallback(() => {
|
||||
if (!playlistId) return;
|
||||
fetchAbortRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
@@ -152,12 +160,12 @@ export function PlaylistDetail() {
|
||||
error: friendlyFetchError(err),
|
||||
});
|
||||
});
|
||||
};
|
||||
}, [playlistId, token]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlaylist();
|
||||
return () => fetchAbortRef.current?.abort();
|
||||
}, [playlistId]);
|
||||
}, [fetchPlaylist]);
|
||||
|
||||
// Start the cooldown→dismissing→gone sequence for a dump being removed.
|
||||
// After the sequence completes, the dump is removed from state.playlist.dumps.
|
||||
@@ -337,7 +345,7 @@ export function PlaylistDetail() {
|
||||
} else if (ev.type === "deleted") {
|
||||
navigate("/");
|
||||
}
|
||||
}, [lastPlaylistEvent, playlistUUID]);
|
||||
}, [lastPlaylistEvent, playlistUUID, navigate, token]);
|
||||
|
||||
// Filter out globally deleted dumps (dump was deleted entirely, not just removed from playlist)
|
||||
useEffect(() => {
|
||||
@@ -579,7 +587,7 @@ export function PlaylistDetail() {
|
||||
if (state.status === "loading") {
|
||||
return (
|
||||
<PageShell>
|
||||
<p className="page-loading">Loading playlist…</p>
|
||||
<p className="page-loading"><Trans>Loading playlist…</Trans></p>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
@@ -594,7 +602,7 @@ export function PlaylistDetail() {
|
||||
type="button"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
← Back
|
||||
<Trans>← Back</Trans>
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
@@ -654,21 +662,21 @@ export function PlaylistDetail() {
|
||||
disabled={editSaving}
|
||||
onClick={handleEditSave}
|
||||
>
|
||||
{editSaving ? "Saving…" : "Save"}
|
||||
{editSaving ? <Trans>Saving…</Trans> : <Trans>Save</Trans>}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="form-cancel"
|
||||
onClick={() => setEditOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-danger"
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
>
|
||||
Delete
|
||||
<Trans>Delete</Trans>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
@@ -687,7 +695,7 @@ export function PlaylistDetail() {
|
||||
className="playlist-edit-btn"
|
||||
onClick={openEdit}
|
||||
>
|
||||
Edit
|
||||
<Trans>Edit</Trans>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -699,7 +707,7 @@ export function PlaylistDetail() {
|
||||
className="playlist-edit-textarea"
|
||||
value={editDescription}
|
||||
onChange={setEditDescription}
|
||||
placeholder="Description (optional)"
|
||||
placeholder={t`Description (optional)`}
|
||||
autoResize
|
||||
rows={1}
|
||||
/>
|
||||
@@ -719,14 +727,14 @@ export function PlaylistDetail() {
|
||||
className={editIsPublic ? "active" : ""}
|
||||
onClick={() => setEditIsPublic(true)}
|
||||
>
|
||||
Public
|
||||
<Trans>Public</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={!editIsPublic ? "active" : ""}
|
||||
onClick={() => setEditIsPublic(false)}
|
||||
>
|
||||
Private
|
||||
<Trans>Private</Trans>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
@@ -737,7 +745,7 @@ export function PlaylistDetail() {
|
||||
playlist.isPublic ? "" : " playlist-badge--private"
|
||||
}`}
|
||||
>
|
||||
{playlist.isPublic ? "public" : "private"}
|
||||
{playlist.isPublic ? <Trans>public</Trans> : <Trans>private</Trans>}
|
||||
</span>
|
||||
{playlist.ownerUsername && (
|
||||
<Link
|
||||
@@ -754,10 +762,10 @@ export function PlaylistDetail() {
|
||||
</Tooltip>
|
||||
{playlist.updatedAt && (
|
||||
<Tooltip
|
||||
text={`Edited ${playlist.updatedAt.toLocaleString()}`}
|
||||
text={t`Edited ${playlist.updatedAt.toLocaleString()}`}
|
||||
>
|
||||
<span className="playlist-edited-label">
|
||||
edited {relativeTime(playlist.updatedAt)}
|
||||
<Trans>edited {relativeTime(playlist.updatedAt)}</Trans>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -765,14 +773,14 @@ export function PlaylistDetail() {
|
||||
)}
|
||||
</div>
|
||||
{editError && (
|
||||
<ErrorCard title="Failed to save" message={editError} />
|
||||
<ErrorCard title={t`Failed to save`} message={editError} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleDumps.length === 0
|
||||
? <p className="empty-state">No dumps in this playlist yet.</p>
|
||||
? <p className="empty-state"><Trans>No dumps in this playlist yet.</Trans></p>
|
||||
: (
|
||||
<div
|
||||
className="playlist-dump-list"
|
||||
@@ -827,7 +835,7 @@ export function PlaylistDetail() {
|
||||
type="button"
|
||||
className="playlist-remove-btn"
|
||||
onClick={() => handleRemoveDump(dump.id)}
|
||||
aria-label="Remove from playlist"
|
||||
aria-label={t`Remove from playlist`}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
@@ -837,9 +845,9 @@ export function PlaylistDetail() {
|
||||
type="button"
|
||||
className="playlist-cancel-btn"
|
||||
onClick={() => handleCancelRemove(dump.id)}
|
||||
aria-label="Cancel removal"
|
||||
aria-label={t`Cancel removal`}
|
||||
>
|
||||
Undo
|
||||
<Trans>Undo</Trans>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -849,8 +857,8 @@ export function PlaylistDetail() {
|
||||
)}
|
||||
{confirmDelete && (
|
||||
<ConfirmModal
|
||||
message="Delete this playlist? This cannot be undone."
|
||||
confirmLabel="Delete playlist"
|
||||
message={t`Delete this playlist? This cannot be undone.`}
|
||||
confirmLabel={t`Delete playlist`}
|
||||
onConfirm={handleDelete}
|
||||
onCancel={() => setConfirmDelete(false)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user