0.12.0 Begin neu Architecture
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import blurayIndicatorIcon from '../assets/media-bluray.svg';
|
||||
import discIndicatorIcon from '../assets/media-disc.svg';
|
||||
import otherIndicatorIcon from '../assets/media-other.svg';
|
||||
import { resolveJobPluginExecution } from '../utils/pluginExecution';
|
||||
import { getStatusLabel } from '../utils/statusPresentation';
|
||||
|
||||
const CD_FORMAT_LABELS = {
|
||||
@@ -465,6 +467,59 @@ function BoolState({ value }) {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveSelectionStateMeta({ selected, outcome }) {
|
||||
if (!selected) {
|
||||
return {
|
||||
label: 'Nicht ausgewählt',
|
||||
icon: 'pi-minus-circle',
|
||||
className: 'track-selection-inline-neutral',
|
||||
title: 'Nicht ausgewählt'
|
||||
};
|
||||
}
|
||||
if (outcome === 'success') {
|
||||
return {
|
||||
label: 'Ausgewählt',
|
||||
icon: 'pi-check-circle',
|
||||
className: 'job-step-inline-ok',
|
||||
title: 'Ausgewählt und erfolgreich'
|
||||
};
|
||||
}
|
||||
if (outcome === 'error') {
|
||||
return {
|
||||
label: 'Ausgewählt',
|
||||
icon: 'pi-times-circle',
|
||||
className: 'job-step-inline-no',
|
||||
title: 'Ausgewählt, aber nicht erfolgreich'
|
||||
};
|
||||
}
|
||||
if (outcome === 'cancelled') {
|
||||
return {
|
||||
label: 'Ausgewählt',
|
||||
icon: 'pi-ban',
|
||||
className: 'job-step-inline-warn',
|
||||
title: 'Ausgewählt, aber abgebrochen'
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: 'Ausgewählt',
|
||||
icon: 'pi-clock',
|
||||
className: 'track-selection-inline-neutral',
|
||||
title: 'Ausgewählt'
|
||||
};
|
||||
}
|
||||
|
||||
function SelectionStateNote({ selected, outcome }) {
|
||||
const meta = resolveSelectionStateMeta({ selected, outcome });
|
||||
return (
|
||||
<small className="track-action-note">
|
||||
<span className={meta.className} title={meta.title}>
|
||||
<span>{meta.label}</span>
|
||||
<i className={`pi ${meta.icon}`} aria-hidden="true" />
|
||||
</span>
|
||||
</small>
|
||||
);
|
||||
}
|
||||
|
||||
function PathField({
|
||||
label,
|
||||
value,
|
||||
@@ -528,17 +583,18 @@ export default function JobDetailDialog({
|
||||
downloadBusyTarget = null
|
||||
}) {
|
||||
const mkDone = Boolean(job?.ripSuccessful) || !job?.makemkvInfo || job?.makemkvInfo?.status === 'SUCCESS';
|
||||
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(job?.status);
|
||||
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_RIPPING', 'CD_ENCODING'].includes(job?.status);
|
||||
const showFinalLog = !running;
|
||||
const canReencode = !!(job?.rawStatus?.exists && job?.rawStatus?.isEmpty !== true && mkDone && !running);
|
||||
const mediaType = resolveMediaType(job);
|
||||
const isCd = mediaType === 'cd';
|
||||
const isAudiobook = mediaType === 'audiobook';
|
||||
const canReencode = !!(job?.rawStatus?.exists && job?.rawStatus?.isEmpty !== true && !running
|
||||
&& (isCd || mkDone));
|
||||
const canResumeReady = Boolean(
|
||||
(String(job?.status || '').trim().toUpperCase() === 'READY_TO_ENCODE' || String(job?.last_state || '').trim().toUpperCase() === 'READY_TO_ENCODE')
|
||||
&& !running
|
||||
&& typeof onResumeReady === 'function'
|
||||
);
|
||||
const mediaType = resolveMediaType(job);
|
||||
const isCd = mediaType === 'cd';
|
||||
const isAudiobook = mediaType === 'audiobook';
|
||||
const hasConfirmedPlan = Boolean(
|
||||
job?.encodePlan
|
||||
&& Array.isArray(job?.encodePlan?.titles)
|
||||
@@ -578,6 +634,7 @@ export default function JobDetailDialog({
|
||||
const mediaTypeAlt = mediaTypeLabel;
|
||||
const statusMeta = statusBadgeMeta(job?.status, queueLocked);
|
||||
const omdbInfo = job?.omdbInfo && typeof job.omdbInfo === 'object' ? job.omdbInfo : {};
|
||||
const pluginExecution = resolveJobPluginExecution(job, null);
|
||||
const configuredSelection = buildConfiguredScriptAndChainSelection(job);
|
||||
const hasConfiguredSelection = configuredSelection.preScriptIds.length > 0
|
||||
|| configuredSelection.postScriptIds.length > 0
|
||||
@@ -776,6 +833,12 @@ export default function JobDetailDialog({
|
||||
<i className={`pi ${statusMeta.icon}`} aria-hidden="true" />
|
||||
</span>
|
||||
</span>
|
||||
{pluginExecution ? (
|
||||
<>
|
||||
<span className="job-infos-sep">|</span>
|
||||
<Tag value={pluginExecution.label} severity="warning" title={pluginExecution.title} />
|
||||
</>
|
||||
) : null}
|
||||
<span className="job-infos-sep">|</span>
|
||||
{isCd ? (
|
||||
<>
|
||||
@@ -949,22 +1012,23 @@ export default function JobDetailDialog({
|
||||
: '-';
|
||||
const selected = selectedSet ? selectedSet.has(pos) : track.selected !== false;
|
||||
const encodeResult = encodeResultMap.get(pos);
|
||||
const encodeLabel = !selected ? 'Nicht übernommen'
|
||||
: encodeResult ? (encodeResult.success ? 'Erfolgreich' : 'Fehler')
|
||||
: (job?.ripSuccessful != null ? (job.ripSuccessful ? 'Erfolgreich' : 'Fehler') : 'Ausgewählt');
|
||||
const encodeClass = !selected ? ''
|
||||
: encodeResult ? (encodeResult.success ? 'tone-ok' : 'tone-no')
|
||||
: (job?.ripSuccessful != null ? (job.ripSuccessful ? 'tone-ok' : 'tone-no') : '');
|
||||
const outcome = !selected
|
||||
? null
|
||||
: encodeResult
|
||||
? (encodeResult.success ? 'success' : 'error')
|
||||
: (job?.ripSuccessful != null ? (job.ripSuccessful ? 'success' : 'error') : null);
|
||||
return (
|
||||
<div key={pos} className="track-item">
|
||||
<span>#{pos} | {label}{artist ? ` | ${artist}` : ''} | {durLabel}</span>
|
||||
<small className={`track-action-note ${encodeClass}`}>Encode: {encodeLabel}</small>
|
||||
<SelectionStateNote selected={selected} outcome={outcome} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})() : (() => {
|
||||
const audiobookFormat = String(job?.handbrakeInfo?.format || job?.encodePlan?.format || '').trim().toLowerCase();
|
||||
const isSplitAudiobook = Boolean(audiobookFormat) && audiobookFormat !== 'm4b';
|
||||
const chapters = Array.isArray(job.handbrakeInfo?.metadata?.chapters) && job.handbrakeInfo.metadata.chapters.length > 0
|
||||
? job.handbrakeInfo.metadata.chapters
|
||||
: (Array.isArray(job.makemkvInfo?.chapters) && job.makemkvInfo.chapters.length > 0
|
||||
@@ -988,6 +1052,15 @@ export default function JobDetailDialog({
|
||||
: '-';
|
||||
const step = stepsByIndex.get(id);
|
||||
const stepStatus = step ? String(step.status || '').toUpperCase() : null;
|
||||
const outcome = stepStatus === 'SUCCESS'
|
||||
? 'success'
|
||||
: stepStatus === 'ERROR'
|
||||
? 'error'
|
||||
: stepStatus === 'CANCELLED'
|
||||
? 'cancelled'
|
||||
: (job?.encodeSuccess != null
|
||||
? (job.encodeSuccess ? 'success' : (String(job?.status || '').trim().toUpperCase() === 'CANCELLED' ? 'cancelled' : 'error'))
|
||||
: null);
|
||||
const encodeLabel = stepStatus === 'SUCCESS' ? 'Erfolgreich'
|
||||
: stepStatus === 'ERROR' ? 'Fehler'
|
||||
: stepStatus === 'CANCELLED' ? 'Abgebrochen'
|
||||
@@ -999,7 +1072,11 @@ export default function JobDetailDialog({
|
||||
return (
|
||||
<div key={id} className="track-item">
|
||||
<span>#{id} | {label} | {durLabel}</span>
|
||||
<small className={`track-action-note ${encodeClass}`}>Encode: {encodeLabel}</small>
|
||||
{isSplitAudiobook ? (
|
||||
<SelectionStateNote selected outcome={outcome} />
|
||||
) : (
|
||||
<small className={`track-action-note ${encodeClass}`}>Encode: {encodeLabel}</small>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -1076,7 +1153,38 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
) : (
|
||||
<div className="actions-section">
|
||||
{!isCd ? (
|
||||
{isCd ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-play-circle" /> Audio CD</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Encode neu starten"
|
||||
icon="pi pi-sync"
|
||||
severity="info"
|
||||
size="small"
|
||||
onClick={() => onReencode?.(job)}
|
||||
loading={reencodeBusy}
|
||||
disabled={!canReencode || typeof onReencode !== 'function'}
|
||||
/>
|
||||
<span className="action-desc">Encodiert die vorhandenen WAV-Rohdaten erneut — ohne die CD neu zu lesen. Nützlich wenn sich Format oder Metadaten geändert haben.</span>
|
||||
</div>
|
||||
{typeof onAssignCdMetadata === 'function' ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="MusicBrainz neu zuweisen"
|
||||
icon="pi pi-search"
|
||||
severity="secondary"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onAssignCdMetadata?.(job)}
|
||||
loading={cdMetadataAssignBusy}
|
||||
disabled={running}
|
||||
/>
|
||||
<span className="action-desc">Öffnet die MusicBrainz-Suche, um Album-Metadaten (Titel, Interpret, Tracks) neu zuzuweisen.</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-play-circle" /> Kodierung</div>
|
||||
{canResumeReady ? (
|
||||
@@ -1141,6 +1249,25 @@ export default function JobDetailDialog({
|
||||
}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isCd && !isAudiobook && typeof onAssignOmdb === 'function' ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-search" /> Metadaten</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="OMDB neu zuweisen"
|
||||
icon="pi pi-search"
|
||||
severity="secondary"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onAssignOmdb?.(job)}
|
||||
loading={omdbAssignBusy}
|
||||
disabled={running}
|
||||
/>
|
||||
<span className="action-desc">Öffnet die OMDB-Suche, um Film-Metadaten (Titel, Jahr, Poster, IMDb-ID) neu zuzuweisen.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="actions-group">
|
||||
|
||||
Reference in New Issue
Block a user