0.13.0 DVD Series

This commit is contained in:
2026-04-10 18:44:41 +00:00
parent 43702b0138
commit 974b7bfad3
36 changed files with 9666 additions and 739 deletions
+269 -51
View File
@@ -21,7 +21,7 @@ import otherIndicatorIcon from '../assets/media-other.svg';
import audiobookIndicatorIcon from '../assets/media-audiobook.svg';
import { getStatusLabel, getStatusSeverity, normalizeStatus } from '../utils/statusPresentation';
import { confirmModal } from '../utils/confirmModal';
import { isConverterJob, resolveMediaType as resolveCentralMediaType } from '../utils/jobTaxonomy';
import { isConverterJob, isSeriesDvdJob, resolveMediaType as resolveCentralMediaType } from '../utils/jobTaxonomy';
const processingStates = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_ANALYZING', 'CD_RIPPING', 'CD_ENCODING'];
const driveActiveStates = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'CD_ANALYZING', 'CD_RIPPING'];
@@ -543,11 +543,18 @@ function resolveMediaType(job) {
function mediaIndicatorMeta(job) {
const mediaType = resolveMediaType(job);
const isSeriesDvd = mediaType === 'dvd' && isSeriesDvdJob(job);
if (mediaType === 'bluray') {
return { mediaType, src: blurayIndicatorIcon, alt: 'Blu-ray', title: 'Blu-ray' };
}
if (mediaType === 'dvd') {
return { mediaType, src: discIndicatorIcon, alt: 'DVD', title: 'DVD' };
return {
mediaType,
src: discIndicatorIcon,
alt: isSeriesDvd ? 'DVD Serie' : 'DVD',
title: isSeriesDvd ? 'DVD Serie' : 'DVD',
isSeriesDvd
};
}
if (mediaType === 'cd') {
return { mediaType, src: otherIndicatorIcon, alt: 'Audio CD', title: 'Audio CD' };
@@ -748,7 +755,16 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
mbId: resolvedCdMbId,
coverUrl: resolvedCdCoverUrl,
imdbId: job?.imdb_id || null,
poster: job?.poster_url || resolvedCdCoverUrl || null
poster: job?.poster_url || resolvedCdCoverUrl || null,
metadataProvider: analyzeContext?.metadataProvider || 'omdb',
providerId: analyzeContext?.selectedMetadata?.providerId || null,
tmdbId: analyzeContext?.selectedMetadata?.tmdbId || null,
metadataKind: analyzeContext?.selectedMetadata?.metadataKind || null,
seasonNumber: analyzeContext?.selectedMetadata?.seasonNumber || analyzeContext?.seriesLookupHint?.seasonNumber || null,
seasonName: analyzeContext?.selectedMetadata?.seasonName || null,
episodeCount: analyzeContext?.selectedMetadata?.episodeCount || 0,
episodes: Array.isArray(analyzeContext?.selectedMetadata?.episodes) ? analyzeContext.selectedMetadata.episodes : [],
discNumber: analyzeContext?.selectedMetadata?.discNumber || analyzeContext?.seriesLookupHint?.discNumber || null
};
const mode = String(encodePlan?.mode || 'rip').trim().toLowerCase();
const isPreRip = mode === 'pre_rip' || Boolean(encodePlan?.preRip);
@@ -836,6 +852,10 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
: [],
selectedPlaylist: analyzeContext.selectedPlaylist || null,
selectedTitleId: analyzeContext.selectedTitleId ?? null,
metadataProvider: analyzeContext.metadataProvider || 'omdb',
metadataCandidates: Array.isArray(analyzeContext.metadataCandidates) ? analyzeContext.metadataCandidates : [],
seriesAnalysis: analyzeContext.seriesAnalysis || null,
seriesLookupHint: analyzeContext.seriesLookupHint || null,
omdbCandidates: [],
canRestartEncodeFromLastSettings,
canRestartReviewFromRaw
@@ -1048,7 +1068,8 @@ export default function RipperPage({
api.getJobs({
statuses: Array.from(ripperStatuses),
limit: 160,
lite: true
lite: true,
includeChildren: true
}),
api.getPipelineQueue()
]);
@@ -1062,6 +1083,9 @@ export default function RipperPage({
if (isConverterJob(job)) {
return false;
}
if (String(job?.job_kind || '').trim().toLowerCase() === 'dvd_series_container') {
return false;
}
const normalizedStatus = String(job?.status || '').trim().toUpperCase();
if (!ripperStatuses.has(normalizedStatus)) {
return false;
@@ -1113,8 +1137,12 @@ export default function RipperPage({
seen.add(String(id));
deduped.push(job);
}
const filteredJobs = deduped.filter((job) => {
const isSeriesBatchChild = Boolean(job?.encodePlan?.seriesBatchChild);
return !isSeriesBatchChild;
});
setRipperJobs(deduped);
setRipperJobs(filteredJobs);
// Prüfen ob Audiobook-Jobs auf Activation Bytes warten
try {
@@ -1314,13 +1342,20 @@ export default function RipperPage({
title: job?.title || job?.detected_title || context?.detectedTitle || '',
year: job?.year || null,
imdbId: job?.imdb_id || null,
poster: job?.poster_url || null
poster: job?.poster_url || null,
metadataProvider: context?.metadataProvider || 'omdb',
seasonNumber: context?.seriesLookupHint?.seasonNumber || null,
discNumber: context?.seriesLookupHint?.discNumber || null
};
return {
...context,
jobId: normalizedJobId,
detectedTitle: context?.detectedTitle || job?.detected_title || selectedMetadata?.title || '',
selectedMetadata,
metadataProvider: context?.metadataProvider || selectedMetadata?.metadataProvider || 'omdb',
metadataCandidates: Array.isArray(context?.metadataCandidates) ? context.metadataCandidates : [],
seriesAnalysis: context?.seriesAnalysis || null,
seriesLookupHint: context?.seriesLookupHint || null,
omdbCandidates: Array.isArray(context?.omdbCandidates) ? context.omdbCandidates : []
};
};
@@ -1342,8 +1377,15 @@ export default function RipperPage({
title: currentContext?.detectedTitle || '',
year: null,
imdbId: null,
poster: null
poster: null,
metadataProvider: currentContext?.metadataProvider || 'omdb',
seasonNumber: currentContext?.seriesLookupHint?.seasonNumber || null,
discNumber: currentContext?.seriesLookupHint?.discNumber || null
},
metadataProvider: currentContext?.metadataProvider || currentContext?.selectedMetadata?.metadataProvider || 'omdb',
metadataCandidates: Array.isArray(currentContext?.metadataCandidates) ? currentContext.metadataCandidates : [],
seriesAnalysis: currentContext?.seriesAnalysis || null,
seriesLookupHint: currentContext?.seriesLookupHint || null,
omdbCandidates: Array.isArray(currentContext?.omdbCandidates) ? currentContext.omdbCandidates : []
};
}
@@ -1409,8 +1451,16 @@ export default function RipperPage({
title: response?.result?.detectedTitle || '',
year: null,
imdbId: null,
poster: null
poster: null,
metadataProvider: response?.result?.metadataProvider || 'omdb',
seasonNumber: response?.result?.seriesLookupHint?.seasonNumber || null
},
metadataProvider: response?.result?.metadataProvider || 'omdb',
metadataCandidates: Array.isArray(response?.result?.metadataCandidates)
? response.result.metadataCandidates
: [],
seriesAnalysis: response?.result?.seriesAnalysis || null,
seriesLookupHint: response?.result?.seriesLookupHint || null,
omdbCandidates: Array.isArray(response?.result?.omdbCandidates)
? response.result.omdbCandidates
: []
@@ -1443,8 +1493,16 @@ export default function RipperPage({
title: response?.result?.detectedTitle || '',
year: null,
imdbId: null,
poster: null
poster: null,
metadataProvider: response?.result?.metadataProvider || 'omdb',
seasonNumber: response?.result?.seriesLookupHint?.seasonNumber || null
},
metadataProvider: response?.result?.metadataProvider || 'omdb',
metadataCandidates: Array.isArray(response?.result?.metadataCandidates)
? response.result.metadataCandidates
: [],
seriesAnalysis: response?.result?.seriesAnalysis || null,
seriesLookupHint: response?.result?.seriesLookupHint || null,
omdbCandidates: Array.isArray(response?.result?.omdbCandidates)
? response.result.omdbCandidates
: []
@@ -1672,7 +1730,9 @@ export default function RipperPage({
if (startOptions.ensureConfirmed) {
const confirmPayload = {
selectedEncodeTitleId: startOptions.selectedEncodeTitleId ?? null,
selectedEncodeTitleIds: startOptions.selectedEncodeTitleIds ?? null,
selectedTrackSelection: startOptions.selectedTrackSelection ?? null,
episodeAssignments: startOptions.episodeAssignments ?? null,
skipPipelineStateUpdate: true
};
if (startOptions.selectedPostEncodeScriptIds !== undefined) {
@@ -1773,16 +1833,26 @@ export default function RipperPage({
selectedTrackSelection = null,
selectedPostEncodeScriptIds = undefined,
selectedUserPresetId = undefined,
selectedHandBrakePreset = undefined
selectedHandBrakePreset = undefined,
selectedEncodeTitleIds = undefined,
episodeAssignments = undefined
) => {
const normalizedJobId = normalizeJobId(jobId);
if (normalizedJobId) setJobBusy(normalizedJobId, true);
try {
const payload = {
selectedEncodeTitleId,
selectedTrackSelection,
selectedPostEncodeScriptIds
selectedTrackSelection
};
if (selectedEncodeTitleIds !== undefined) {
payload.selectedEncodeTitleIds = selectedEncodeTitleIds;
}
if (selectedPostEncodeScriptIds !== undefined) {
payload.selectedPostEncodeScriptIds = selectedPostEncodeScriptIds;
}
if (episodeAssignments !== undefined) {
payload.episodeAssignments = episodeAssignments;
}
if (selectedUserPresetId !== undefined) {
payload.selectedUserPresetId = selectedUserPresetId;
}
@@ -1818,13 +1888,19 @@ export default function RipperPage({
}
};
const handleSelectHandBrakeTitle = async (jobId, selectedHandBrakeTitleId = null) => {
const handleSelectHandBrakeTitle = async (jobId, selectedHandBrakeTitleIds = null) => {
const normalizedJobId = normalizeJobId(jobId);
if (normalizedJobId) setJobBusy(normalizedJobId, true);
try {
const normalizedIds = (Array.isArray(selectedHandBrakeTitleIds) ? selectedHandBrakeTitleIds : [selectedHandBrakeTitleIds])
.map((value) => Number(value))
.filter((value) => Number.isFinite(value) && value > 0)
.map((value) => Math.trunc(value));
const dedupedIds = Array.from(new Set(normalizedIds));
await api.selectMetadata({
jobId,
selectedHandBrakeTitleId: selectedHandBrakeTitleId || null
selectedHandBrakeTitleId: dedupedIds[0] || null,
selectedHandBrakeTitleIds: dedupedIds
});
await refreshPipeline();
await loadRipperJobs();
@@ -1914,9 +1990,12 @@ export default function RipperPage({
}
};
const handleRestartEncodeWithLastSettings = async (jobId) => {
const handleRestartEncodeWithLastSettings = async (jobId, restartOptions = {}) => {
const job = ripperJobs.find((item) => normalizeJobId(item?.id) === normalizeJobId(jobId)) || null;
const title = job?.title || job?.detected_title || `Job #${jobId}`;
const restartMode = String(restartOptions?.restartMode || 'all').trim().toLowerCase() === 'from_abort'
? 'from_abort'
: 'all';
if (job?.encodeSuccess) {
const confirmed = await confirmModal({
header: 'Encode neu starten',
@@ -1934,7 +2013,7 @@ export default function RipperPage({
const normalizedJobId = normalizeJobId(jobId);
if (normalizedJobId) setJobBusy(normalizedJobId, true);
try {
const response = await api.restartEncodeWithLastSettings(jobId);
const response = await api.restartEncodeWithLastSettings(jobId, { restartMode });
const result = getQueueActionResult(response);
const replacementJobId = normalizeJobId(result?.jobId) || normalizedJobId;
await refreshPipeline();
@@ -2115,14 +2194,12 @@ export default function RipperPage({
}
};
const handleOmdbSearch = async (query) => {
try {
const response = await api.searchOmdb(query);
return response.results || [];
} catch (error) {
showError(error);
return [];
}
const handleMetadataSearch = async (query, options = {}) => {
const provider = String(options?.metadataProvider || effectiveMetadataDialogContext?.metadataProvider || 'omdb').trim().toLowerCase() || 'omdb';
const response = provider === 'tmdb'
? await api.searchTmdbSeries(query)
: await api.searchOmdb(query);
return response.results || [];
};
const doSelectMetadata = async (payload) => {
@@ -2772,6 +2849,42 @@ export default function RipperPage({
normalizedStatus === 'READY_TO_ENCODE'
|| (mediaIndicator.mediaType === 'audiobook' && normalizedStatus === 'READY_TO_START')
) && !isCurrentSession;
const analyzeContext = getAnalyzeContext(job);
const selectedMetadata = pipelineForJob?.context?.selectedMetadata && typeof pipelineForJob.context.selectedMetadata === 'object'
? pipelineForJob.context.selectedMetadata
: (analyzeContext?.selectedMetadata && typeof analyzeContext.selectedMetadata === 'object'
? analyzeContext.selectedMetadata
: (job?.makemkvInfo?.selectedMetadata && typeof job.makemkvInfo.selectedMetadata === 'object'
? job.makemkvInfo.selectedMetadata
: {}));
const seriesSeasonNumber = mediaIndicator.isSeriesDvd
? (selectedMetadata?.seasonNumber ?? analyzeContext?.seriesLookupHint?.seasonNumber ?? null)
: null;
const seriesTmdbId = mediaIndicator.isSeriesDvd
? (selectedMetadata?.tmdbId ?? analyzeContext?.tmdbId ?? null)
: null;
const seriesDiscNumber = mediaIndicator.isSeriesDvd
? (selectedMetadata?.discNumber ?? analyzeContext?.seriesLookupHint?.discNumber ?? null)
: null;
const isSeriesContainer = mediaIndicator.isSeriesDvd
&& String(job?.job_kind || '').trim().toLowerCase() === 'dvd_series_container';
const parentContainerId = normalizeJobId(job?.parent_job_id);
const seriesTmdbText = String(seriesTmdbId ?? '').trim();
const seriesSeasonLabel = mediaIndicator.isSeriesDvd && seriesSeasonNumber
? ` Staffel ${seriesSeasonNumber}`
: '';
const seriesDiscLabel = mediaIndicator.isSeriesDvd && seriesDiscNumber
? ` | Disk ${seriesDiscNumber}`
: '';
const seriesContainerLabel = isSeriesContainer
? ' | Container'
: (parentContainerId ? ` | Container #${parentContainerId}` : '');
const seriesTmdbLabel = mediaIndicator.isSeriesDvd && seriesTmdbText && seriesTmdbText !== '0'
? ` | TMDb ${seriesTmdbId}`
: '';
const seriesTitleSuffix = mediaIndicator.isSeriesDvd
? `${seriesSeasonLabel}${seriesDiscLabel}${seriesContainerLabel}`
: '';
const mediaProfile = String(pipelineForJob?.context?.mediaProfile || '').trim().toLowerCase();
const jobState = String(pipelineForJob?.state || normalizedStatus).trim().toUpperCase();
const pipelineStage = String(pipelineForJob?.context?.stage || '').trim().toUpperCase();
@@ -2784,12 +2897,45 @@ export default function RipperPage({
const isAudiobookJob = mediaProfile === 'audiobook'
|| mediaIndicator.mediaType === 'audiobook'
|| String(pipelineForJob?.context?.mode || '').trim().toLowerCase() === 'audiobook';
const rawProgress = Number(pipelineForJob?.progress ?? 0);
const seriesBatchContext = pipelineForJob?.context?.seriesBatch && typeof pipelineForJob.context.seriesBatch === 'object'
? pipelineForJob.context.seriesBatch
: null;
const seriesBatchChildren = Array.isArray(seriesBatchContext?.children) ? seriesBatchContext.children : [];
const hasSeriesBatchProgress = seriesBatchChildren.length > 0 || Number(seriesBatchContext?.totalCount || 0) > 0;
const seriesBatchTotalCountRaw = Number(seriesBatchContext?.totalCount || 0);
const seriesBatchTotalCount = Number.isFinite(seriesBatchTotalCountRaw) && seriesBatchTotalCountRaw > 0
? Math.trunc(seriesBatchTotalCountRaw)
: seriesBatchChildren.length;
const seriesBatchAggregateProgress = hasSeriesBatchProgress && seriesBatchTotalCount > 0
? (
seriesBatchChildren.reduce((sum, child) => {
const status = String(child?.status || '').trim().toUpperCase();
if (status === 'FINISHED') {
return sum + 100;
}
const childProgress = Number(child?.progress || 0);
return sum + (Number.isFinite(childProgress) ? Math.max(0, Math.min(100, childProgress)) : 0);
}, 0) / seriesBatchTotalCount
)
: 0;
const runningSeriesChild = hasSeriesBatchProgress
? (
seriesBatchChildren.find((child) => String(child?.status || '').trim().toUpperCase() === 'RUNNING')
|| null
)
: null;
const rawProgress = hasSeriesBatchProgress
? seriesBatchAggregateProgress
: Number(pipelineForJob?.progress ?? 0);
const clampedProgress = Number.isFinite(rawProgress)
? Math.max(0, Math.min(100, rawProgress))
: 0;
const progressLabel = `${Math.round(clampedProgress)}%`;
const etaLabel = String(pipelineForJob?.eta || '').trim();
const etaLabel = String(
hasSeriesBatchProgress
? (runningSeriesChild?.eta || pipelineForJob?.eta || '')
: (pipelineForJob?.eta || '')
).trim();
const audiobookMeta = pipelineForJob?.context?.selectedMetadata && typeof pipelineForJob.context.selectedMetadata === 'object'
? pipelineForJob.context.selectedMetadata
@@ -2814,8 +2960,12 @@ export default function RipperPage({
/>
<span>#{jobId} | {jobTitle}</span>
</strong>
{mediaIndicator.isSeriesDvd ? (
<small className="ripper-job-subtitle">{seriesTitleSuffix || '-'}</small>
) : null}
<div className="ripper-job-badges">
<Tag value={statusBadgeValue} severity={statusBadgeSeverity} />
{mediaIndicator.isSeriesDvd ? <Tag value="DVD Serie" severity="secondary" /> : null}
{isCurrentSession ? <Tag value="Aktive Session" severity="info" /> : null}
{isResumable ? <Tag value="Fortsetzbar" severity="success" /> : null}
{normalizedStatus === 'READY_TO_ENCODE'
@@ -2934,21 +3084,30 @@ export default function RipperPage({
<span>{jobTitle}</span>
</strong>
<small>
#{jobId}
{isAudiobookJob
? (
`${audiobookMeta?.author ? ` | ${audiobookMeta.author}` : ''}`
+ `${audiobookMeta?.narrator ? ` | ${audiobookMeta.narrator}` : ''}`
+ `${audiobookChapterCount > 0 ? ` | ${audiobookChapterCount} Kapitel` : ''}`
)
{mediaIndicator.isSeriesDvd
? (seriesTitleSuffix || '-')
: (
`${job?.year ? ` | ${job.year}` : ''}`
+ `${job?.imdb_id ? ` | ${job.imdb_id}` : ''}`
`#${jobId}`
+ (isAudiobookJob
? (
`${audiobookMeta?.author ? ` | ${audiobookMeta.author}` : ''}`
+ `${audiobookMeta?.narrator ? ` | ${audiobookMeta.narrator}` : ''}`
+ `${audiobookChapterCount > 0 ? ` | ${audiobookChapterCount} Kapitel` : ''}`
)
: (
`${job?.year ? ` | ${job.year}` : ''}`
+ (mediaIndicator.isSeriesDvd ? seriesSeasonLabel : '')
+ (mediaIndicator.isSeriesDvd ? seriesDiscLabel : '')
+ (mediaIndicator.isSeriesDvd ? seriesContainerLabel : '')
+ (mediaIndicator.isSeriesDvd ? seriesTmdbLabel : '')
+ (mediaIndicator.isSeriesDvd ? '' : `${job?.imdb_id ? ` | ${job.imdb_id}` : ''}`)
))
)}
</small>
</div>
<div className="ripper-job-badges">
<Tag value={statusBadgeValue} severity={statusBadgeSeverity} />
{mediaIndicator.isSeriesDvd ? <Tag value="DVD Serie" severity="secondary" /> : null}
{isCurrentSession ? <Tag value="Aktive Session" severity="info" /> : null}
{isResumable ? <Tag value="Fortsetzbar" severity="success" /> : null}
{normalizedStatus === 'READY_TO_ENCODE'
@@ -2995,7 +3154,35 @@ export default function RipperPage({
const detailKey = buildRunningQueueScriptKey(item?.jobId);
const detailsExpanded = hasScriptSummary && expandedQueueScriptKeys.has(detailKey);
const jobProgressEntry = pipeline?.jobProgress?.[String(item.jobId)] || null;
const rawQueueProgress = Number(jobProgressEntry?.progress ?? 0);
const seriesBatchContext = jobProgressEntry?.context?.seriesBatch && typeof jobProgressEntry.context.seriesBatch === 'object'
? jobProgressEntry.context.seriesBatch
: null;
const seriesBatchChildren = Array.isArray(seriesBatchContext?.children) ? seriesBatchContext.children : [];
const seriesBatchTotalCountRaw = Number(seriesBatchContext?.totalCount || 0);
const seriesBatchTotalCount = Number.isFinite(seriesBatchTotalCountRaw) && seriesBatchTotalCountRaw > 0
? Math.trunc(seriesBatchTotalCountRaw)
: seriesBatchChildren.length;
const seriesBatchOverallProgress = seriesBatchTotalCount > 0
? (
seriesBatchChildren.reduce((sum, child) => {
const status = String(child?.status || '').trim().toUpperCase();
if (status === 'FINISHED') {
return sum + 100;
}
const childProgress = Number(child?.progress || 0);
return sum + (Number.isFinite(childProgress) ? Math.max(0, Math.min(100, childProgress)) : 0);
}, 0) / seriesBatchTotalCount
)
: null;
const runningSeriesChild = seriesBatchChildren.find(
(child) => String(child?.status || '').trim().toUpperCase() === 'RUNNING'
) || null;
const runningChildProgress = Number(runningSeriesChild?.progress ?? NaN);
const rawQueueProgress = Number(
Number.isFinite(runningChildProgress)
? runningChildProgress
: (jobProgressEntry?.progress ?? 0)
);
const clampedQueueProgress = Number.isFinite(rawQueueProgress) ? Math.max(0, Math.min(100, rawQueueProgress)) : 0;
return (
<div key={`running-${item.jobId}`} className="pipeline-queue-entry-wrap">
@@ -3006,6 +3193,7 @@ export default function RipperPage({
{item.hasScripts ? <i className="pi pi-code queue-job-tag" title="Skripte hinterlegt" /> : null}
{item.hasChains ? <i className="pi pi-link queue-job-tag" title="Skriptketten hinterlegt" /> : null}
</strong>
{item.subtitle ? <small>{item.subtitle}</small> : null}
<small>{getStatusLabel(item.status)}</small>
{clampedQueueProgress > 0 && (
<ProgressBar value={clampedQueueProgress} style={{ height: '5px' }} showValue={false} />
@@ -3122,6 +3310,7 @@ export default function RipperPage({
{item.hasScripts ? <i className="pi pi-code queue-job-tag" title="Skripte hinterlegt" /> : null}
{item.hasChains ? <i className="pi pi-link queue-job-tag" title="Skriptketten hinterlegt" /> : null}
</strong>
{item.subtitle ? <small>{item.subtitle}</small> : null}
<small>{item.actionLabel || item.action || '-'} | {getStatusLabel(item.status)}</small>
</>
)}
@@ -3497,7 +3686,7 @@ export default function RipperPage({
setMetadataDialogReassignMode(false);
}}
onSubmit={handleMetadataSubmit}
onSearch={handleOmdbSearch}
onSearch={handleMetadataSearch}
busy={busy}
/>
@@ -3521,31 +3710,60 @@ export default function RipperPage({
style={{ width: '30rem', maxWidth: '96vw' }}
modal
>
{(() => {
const pendingPayload = duplicateJobDialog.pendingPayload || {};
const metadataProvider = String(pendingPayload?.metadataProvider || '').trim().toLowerCase();
const seasonNumber = Number(pendingPayload?.seasonNumber || 0) || 0;
const tmdbId = Number(pendingPayload?.tmdbId || 0) || 0;
const isSeriesContainerAssign = metadataProvider === 'tmdb' && seasonNumber > 0 && tmdbId > 0;
return (
<>
<p>
<strong>{duplicateJobDialog.existingJob?.title || duplicateJobDialog.pendingPayload?.title}</strong> ist bereits als Job #{duplicateJobDialog.existingJob?.id} in der Historie vorhanden.
</p>
<p>Neuen Job anlegen oder mit dem vorhandenen Eintrag weiterarbeiten?</p>
<p>
{isSeriesContainerAssign
? 'Dieser Job kann dem vorhandenen Serien-Container automatisch zugewiesen werden.'
: 'Neuen Job anlegen oder mit dem vorhandenen Eintrag weiterarbeiten?'}
</p>
<div className="dialog-actions">
<Button
label="Vorhandenen Job öffnen"
icon="pi pi-history"
onClick={() => {
label={isSeriesContainerAssign ? 'Vorhandenem Container zuweisen' : 'Vorhandenen Job öffnen'}
icon={isSeriesContainerAssign ? 'pi pi-sitemap' : 'pi pi-history'}
onClick={async () => {
const payload = duplicateJobDialog.pendingPayload;
const jobId = duplicateJobDialog.existingJob?.id;
setDuplicateJobDialog({ visible: false, existingJob: null, pendingPayload: null });
if (isSeriesContainerAssign) {
await doSelectMetadata(payload);
return;
}
navigate(`/history?open=${jobId}`);
}}
/>
<Button
label="Neuen Job anlegen"
severity="secondary"
outlined
onClick={async () => {
const payload = duplicateJobDialog.pendingPayload;
setDuplicateJobDialog({ visible: false, existingJob: null, pendingPayload: null });
await doSelectMetadata(payload);
}}
/>
{isSeriesContainerAssign ? (
<Button
label="Abbrechen"
severity="secondary"
outlined
onClick={() => setDuplicateJobDialog({ visible: false, existingJob: null, pendingPayload: null })}
/>
) : (
<Button
label="Neuen Job anlegen"
severity="secondary"
outlined
onClick={async () => {
const payload = duplicateJobDialog.pendingPayload;
setDuplicateJobDialog({ visible: false, existingJob: null, pendingPayload: null });
await doSelectMetadata(payload);
}}
/>
)}
</div>
</>
);
})()}
</Dialog>
<Dialog