feat(library): surface metadata enrichment status, errors and covers
The mapper dropped metadata_status and hardcoded availability, so enrichment state was invisible and a just-uploaded track never appeared to change. Map metadata_status/metadata_error/has_cover onto Track; add MetadataStatusBadge (pending spinner / enriched / failed-with-reason / manual) shown in TrackRow, and serve token-bearing track covers via getTrackCoverUrl. UploadPage now polls each uploaded track (stops once enrichment settles) so the resolved title/artist — or a failure reason — appears live. i18n in en + ru. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Badge, Button, Callout, ScrollArea, Spinner } from '@olly/modern-sk';
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
buildUploadFormData,
|
||||
useUploadTrackMutation,
|
||||
} from '../../api/endpoints/upload';
|
||||
import { useGetTrackQuery } from '../../api/endpoints/library';
|
||||
import { MetadataStatusBadge } from '../../components/track/MetadataStatusBadge';
|
||||
|
||||
/** Pure client-side state — this is a transient upload queue, never server data. */
|
||||
type ItemStatus = 'queued' | 'uploading' | 'done' | 'duplicate' | 'error';
|
||||
@@ -273,11 +275,7 @@ function UploadRow({
|
||||
{item.error}
|
||||
</div>
|
||||
)}
|
||||
{done && (
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--color-text-3)' }}>
|
||||
{t('upload.unknownArtist')}
|
||||
</div>
|
||||
)}
|
||||
{done && item.trackId && <EnrichmentStatus trackId={item.trackId} />}
|
||||
</div>
|
||||
|
||||
<StatusBadge status={item.status} />
|
||||
@@ -301,6 +299,59 @@ function UploadRow({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls a just-uploaded track until enrichment settles, then shows the outcome.
|
||||
* Metadata enrichment runs asynchronously in a worker after the upload response
|
||||
* returns, so without polling the row would never reflect the resolved title/
|
||||
* artist or a failure reason. Polling stops (interval → 0) once the status
|
||||
* leaves `pending`.
|
||||
*/
|
||||
function EnrichmentStatus({ trackId }: { trackId: string }) {
|
||||
const { t } = useTranslation();
|
||||
const [pollMs, setPollMs] = useState(2500);
|
||||
const { data } = useGetTrackQuery(trackId, { pollingInterval: pollMs });
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data.metadataStatus !== 'pending') setPollMs(0);
|
||||
}, [data]);
|
||||
|
||||
const status = data?.metadataStatus ?? 'pending';
|
||||
const resolved =
|
||||
data && data.metadataStatus === 'enriched'
|
||||
? `${data.artistName} · ${data.title}`
|
||||
: t(`metadata.statusHint.${status}`);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
marginTop: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<MetadataStatusBadge
|
||||
status={status}
|
||||
error={data?.metadataError}
|
||||
hideWhenEnriched={false}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--color-text-3)',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{status === 'failed' && data?.metadataError
|
||||
? data.metadataError
|
||||
: resolved}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: ItemStatus }) {
|
||||
const { t } = useTranslation();
|
||||
if (status === 'uploading') {
|
||||
|
||||
Reference in New Issue
Block a user