import { useState, useEffect } from 'react';
import { Dropdown } from 'primereact/dropdown';
import { Slider } from 'primereact/slider';
import { Button } from 'primereact/button';
import { ProgressBar } from 'primereact/progressbar';
import { Tag } from 'primereact/tag';
import { InputText } from 'primereact/inputtext';
import { InputNumber } from 'primereact/inputnumber';
import { CD_FORMATS, CD_FORMAT_SCHEMAS, getDefaultFormatOptions } from '../config/cdFormatSchemas';
import { api } from '../api/client';
import { getStatusLabel, getStatusSeverity } from '../utils/statusPresentation';
function isFieldVisible(field, values) {
if (!field.showWhen) {
return true;
}
return values[field.showWhen.field] === field.showWhen.value;
}
function FormatField({ field, value, onChange }) {
if (field.type === 'slider') {
return (
{statusText || 'Bereit'}
{isRipping ? (
formatProgressLabel(value)}
/>
{`Fortschritt: ${formatProgressLabel(roundedProgress)} | ${livePhaseLabel} ${completedRipCount}/${selectedTrackRows.length || effectiveTrackRows.length} Rip | ${completedEncodeCount}/${selectedTrackRows.length || effectiveTrackRows.length} Encode`}
{eta ? `ETA ${eta}` : 'ETA unbekannt'}
) : isFinished ? (
) : null}
{isRipping ? (
) : null}
{isTerminalFailure ? (
{isEncodingFailure ? (
handleStart({ forceSkipRip: true })}
loading={busy}
/>
) : (
<>
{jobId ? (
onRetry && onRetry()}
loading={busy}
/>
) : null}
onOpenMetadata && onOpenMetadata()}
loading={busy}
/>
>
)}
{isEncodingFailure ? (
onRestartReview && onRestartReview()}
loading={busy}
/>
) : null}
{jobId ? (
onDeleteJob && onDeleteJob(jobId)}
loading={busy}
/>
) : null}
) : null}
CD-Details
{coverUrl ? (
) : null}
Album: {albumTitle}
Interpret: {albumArtist}
Jahr: {albumYear}
MusicBrainz: {musicBrainzId}
Status: {stateLabel}
Format: {formatLabel}
Rip fertig: {completedRipCount} / {selectedTrackRows.length || effectiveTrackRows.length}
Encode fertig: {completedEncodeCount} / {selectedTrackRows.length || effectiveTrackRows.length}
Aktueller Track: {liveCurrentTrackPosition ? String(liveCurrentTrackPosition).padStart(2, '0') : '-'}
Pre-Skripte: {preScriptNames.length > 0 ? preScriptNames.join(' | ') : '-'}
Pre-Ketten: {preChainNames.length > 0 ? preChainNames.join(' | ') : '-'}
Post-Skripte: {postScriptNames.length > 0 ? postScriptNames.join(' | ') : '-'}
Post-Ketten: {postChainNames.length > 0 ? postChainNames.join(' | ') : '-'}
Auswahl: {selectedTrackNumbers || '-'}
Gesamtdauer: {formatTotalDuration(selectedTrackDurationSec)}
Laufwerk: {devicePath}
Output-Pfad: {outputPath}
{lastState ?
Letzter Pipeline-State: {lastStateLabel}
: null}
{jobId ?
Job-ID: #{jobId}
: null}
{displayTrackRows.length > 0 ? (
Zu rippende Tracks ({displayTrackRows.length})
| Auswahl |
Nr |
Interpret |
Titel |
Länge |
Rip |
Encode |
{displayTrackRows.map((track) => {
const ripMeta = trackStatusTagMeta(track?.ripStatus);
const encodeMeta = trackStatusTagMeta(track?.encodeStatus);
return (
| {track.selected ? 'Ja' : 'Nein'} |
{String(track.position).padStart(2, '0')} |
{track.artist || '-'} |
{track.title || '-'} |
{track.durationLabel} |
|
|
);
})}
) : null}
);
}
return (
CD-Rip Konfiguration
Album-Metadaten
handleMetaFieldChange('title', e.target.value)}
placeholder="Album"
disabled={busy}
/>
handleMetaFieldChange('artist', e.target.value)}
placeholder="Interpret"
disabled={busy}
/>
handleMetaFieldChange('year', normalizeYear(e.value))}
placeholder="Jahr"
useGrouping={false}
min={1900}
max={2100}
disabled={busy}
/>
{/* Format selection */}
setFormat(e.value)}
disabled={busy}
/>
{/* Format-specific options */}
{visibleFields.map((field) => (
))}
{/* Track selection */}
{tracks.length > 0 ? (
Tracks ({selectedCount} / {tracks.length} ausgewählt)
) : null}
Pre-Rip Ausführungen (optional)
{scriptCatalog.length === 0 && chainCatalog.length === 0 ? (
Keine Skripte oder Ketten konfiguriert. In den Settings anlegen.
) : null}
{preRipItems.length === 0 ? (
Keine Pre-Rip Ausführungen ausgewählt.
) : null}
{preRipItems.map((item, rowIndex) => {
const isScript = item?.type === 'script';
const usedScriptIds = new Set(
preRipItems
.filter((entry, index) => entry?.type === 'script' && index !== rowIndex)
.map((entry) => normalizeScriptId(entry?.id))
.filter((id) => id !== null)
.map((id) => String(id))
);
const usedChainIds = new Set(
preRipItems
.filter((entry, index) => entry?.type === 'chain' && index !== rowIndex)
.map((entry) => normalizeChainId(entry?.id))
.filter((id) => id !== null)
.map((id) => String(id))
);
const scriptOptions = scriptCatalog.map((entry) => ({
label: entry?.name || `Skript #${entry?.id}`,
value: normalizeScriptId(entry?.id),
disabled: usedScriptIds.has(String(normalizeScriptId(entry?.id)))
})).filter((entry) => entry.value !== null);
const chainOptions = chainCatalog.map((entry) => ({
label: entry?.name || `Kette #${entry?.id}`,
value: normalizeChainId(entry?.id),
disabled: usedChainIds.has(String(normalizeChainId(entry?.id)))
})).filter((entry) => entry.value !== null);
return (
moveEncodeItem('pre', rowIndex, 'up')}
disabled={busy || rowIndex <= 0}
/>
moveEncodeItem('pre', rowIndex, 'down')}
disabled={busy || rowIndex >= preRipItems.length - 1}
/>
{isScript ? (
changeEncodeItem('pre', rowIndex, 'script', event.value)}
className="full-width"
disabled={busy}
/>
) : (
changeEncodeItem('pre', rowIndex, 'chain', event.value)}
className="full-width"
disabled={busy}
/>
)}
removeEncodeItem('pre', rowIndex)}
disabled={busy}
/>
);
})}
{scriptCatalog.length > preRipItems.filter((entry) => entry?.type === 'script').length ? (
addEncodeItem('pre', 'script')}
disabled={busy}
/>
) : null}
{chainCatalog.length > preRipItems.filter((entry) => entry?.type === 'chain').length ? (
addEncodeItem('pre', 'chain')}
disabled={busy}
/>
) : null}
Ausführung vor dem Rippen, strikt nacheinander. Bei Fehler wird der CD-Rip abgebrochen.
Post-Rip Ausführungen (optional)
{scriptCatalog.length === 0 && chainCatalog.length === 0 ? (
Keine Skripte oder Ketten konfiguriert. In den Settings anlegen.
) : null}
{postRipItems.length === 0 ? (
Keine Post-Rip Ausführungen ausgewählt.
) : null}
{postRipItems.map((item, rowIndex) => {
const isScript = item?.type === 'script';
const usedScriptIds = new Set(
postRipItems
.filter((entry, index) => entry?.type === 'script' && index !== rowIndex)
.map((entry) => normalizeScriptId(entry?.id))
.filter((id) => id !== null)
.map((id) => String(id))
);
const usedChainIds = new Set(
postRipItems
.filter((entry, index) => entry?.type === 'chain' && index !== rowIndex)
.map((entry) => normalizeChainId(entry?.id))
.filter((id) => id !== null)
.map((id) => String(id))
);
const scriptOptions = scriptCatalog.map((entry) => ({
label: entry?.name || `Skript #${entry?.id}`,
value: normalizeScriptId(entry?.id),
disabled: usedScriptIds.has(String(normalizeScriptId(entry?.id)))
})).filter((entry) => entry.value !== null);
const chainOptions = chainCatalog.map((entry) => ({
label: entry?.name || `Kette #${entry?.id}`,
value: normalizeChainId(entry?.id),
disabled: usedChainIds.has(String(normalizeChainId(entry?.id)))
})).filter((entry) => entry.value !== null);
return (
moveEncodeItem('post', rowIndex, 'up')}
disabled={busy || rowIndex <= 0}
/>
moveEncodeItem('post', rowIndex, 'down')}
disabled={busy || rowIndex >= postRipItems.length - 1}
/>
{isScript ? (
changeEncodeItem('post', rowIndex, 'script', event.value)}
className="full-width"
disabled={busy}
/>
) : (
changeEncodeItem('post', rowIndex, 'chain', event.value)}
className="full-width"
disabled={busy}
/>
)}
removeEncodeItem('post', rowIndex)}
disabled={busy}
/>
);
})}
{scriptCatalog.length > postRipItems.filter((entry) => entry?.type === 'script').length ? (
addEncodeItem('post', 'script')}
disabled={busy}
/>
) : null}
{chainCatalog.length > postRipItems.filter((entry) => entry?.type === 'chain').length ? (
addEncodeItem('post', 'chain')}
disabled={busy}
/>
) : null}
Ausführung nach erfolgreichem Rippen/Encodieren, strikt nacheinander.
{/* Actions */}
{jobId ? (
onOpenMetadata && onOpenMetadata()}
disabled={busy}
/>
) : null}
{jobId ? (
onDeleteJob && onDeleteJob(jobId)}
disabled={busy}
/>
) : null}
onCancel && onCancel()}
disabled={busy}
/>
);
}