0.13.0 DVD Series

This commit is contained in:
michael committed 2026-04-10 18:44:41 +00:00
1 parent 43702b0138
commit 974b7bfad3
36 files changed
+9666 -739

No files matched your search

+102 -15
View File
@@ -293,9 +293,10 @@ function ExternalStoragePathsEditor({ value, onChange, settingKey }) {
);
}
function DetectedDrivesInfo() {
function DetectedDrivesInfo({ expertModeEnabled = false, onNotify = null }) {
const [drives, setDrives] = useState(null);
const [loading, setLoading] = useState(false);
const [unlocking, setUnlocking] = useState(false);
const load = () => {
setLoading(true);
@@ -307,21 +308,70 @@ function DetectedDrivesInfo() {
useEffect(() => { load(); }, []);
const notify = (severity, summary, detail) => {
if (typeof onNotify === 'function') {
onNotify({ severity, summary, detail });
}
};
const handleUnlockOne = async (devicePath) => {
setUnlocking(true);
try {
await api.forceUnlockDrive({ devicePath });
notify('success', 'Laufwerk freigegeben', `${devicePath} wurde entsperrt.`);
load();
} catch (error) {
notify('error', 'Freigabe fehlgeschlagen', error?.message || 'Unbekannter Fehler');
} finally {
setUnlocking(false);
}
};
const handleUnlockAll = async () => {
setUnlocking(true);
try {
await api.forceUnlockDrive({ all: true });
notify('success', 'Laufwerke freigegeben', 'Alle erkannten Laufwerke wurden entsperrt.');
load();
} catch (error) {
notify('error', 'Freigabe fehlgeschlagen', error?.message || 'Unbekannter Fehler');
} finally {
setUnlocking(false);
}
};
return (
<div className="detected-drives-info">
<div className="detected-drives-header">
<span className="detected-drives-label">Erkannte optische Laufwerke</span>
<Button
icon="pi pi-refresh"
text
rounded
size="small"
loading={loading}
onClick={load}
type="button"
aria-label="Laufwerke neu einlesen"
title="Laufwerke neu einlesen"
/>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.3rem' }}>
{expertModeEnabled ? (
<Button
icon="pi pi-unlock"
text
rounded
size="small"
loading={unlocking}
disabled={loading}
onClick={handleUnlockAll}
type="button"
aria-label="Alle Laufwerke freigeben"
title="Alle Laufwerke freigeben"
/>
) : null}
<Button
icon="pi pi-refresh"
text
rounded
size="small"
loading={loading}
disabled={unlocking}
onClick={load}
type="button"
aria-label="Laufwerke neu einlesen"
title="Laufwerke neu einlesen"
/>
</div>
</div>
{drives === null || loading
? <span className="detected-drives-empty">Wird geladen…</span>
@@ -334,6 +384,7 @@ function DetectedDrivesInfo() {
<th>Gerät</th>
<th>Modell</th>
<th>MakeMKV Index</th>
{expertModeEnabled ? <th>Freigabe</th> : null}
</tr>
</thead>
<tbody>
@@ -342,6 +393,19 @@ function DetectedDrivesInfo() {
<td><code>{d.path}</code></td>
<td>{d.model || '—'}</td>
<td><Tag value={d.discArg ?? '—'} severity="info" /></td>
{expertModeEnabled ? (
<td>
<Button
label="Freigeben"
icon="pi pi-unlock"
size="small"
text
onClick={() => handleUnlockOne(d.path)}
loading={unlocking}
disabled={loading}
/>
</td>
) : null}
</tr>
))}
</tbody>
@@ -438,7 +502,15 @@ function buildToolSections(settings) {
// Path keys per medium — _owner keys are rendered inline
const BLURAY_PATH_KEYS = ['raw_dir_bluray', 'movie_dir_bluray', 'output_template_bluray'];
const DVD_PATH_KEYS = ['raw_dir_dvd', 'movie_dir_dvd', 'output_template_dvd'];
const DVD_PATH_KEYS = [
'raw_dir_dvd',
'raw_dir_dvd_series',
'movie_dir_dvd',
'series_dir_dvd',
'output_template_dvd',
'output_template_dvd_series_episode',
'output_template_dvd_series_multi_episode'
];
const CD_PATH_KEYS = ['raw_dir_cd', 'movie_dir_cd', 'cd_output_template'];
const AUDIOBOOK_PATH_KEYS = ['raw_dir_audiobook', 'movie_dir_audiobook', 'output_template_audiobook', 'output_chapter_template_audiobook', 'audiobook_raw_template'];
const DOWNLOAD_PATH_KEYS = ['download_dir'];
@@ -732,6 +804,7 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
const defaultRaw = effectivePaths?.defaults?.raw || 'data/output/raw';
const defaultMovies = effectivePaths?.defaults?.movies || 'data/output/movies';
const defaultSeries = effectivePaths?.defaults?.series || 'data/output/series';
const defaultCd = effectivePaths?.defaults?.cd || 'data/output/cd';
const defaultAudiobookRaw = effectivePaths?.defaults?.audiobookRaw || 'data/output/audiobook-raw';
const defaultAudiobookMovies = effectivePaths?.defaults?.audiobookMovies || 'data/output/audiobooks';
@@ -745,7 +818,9 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
const blurayRaw = ep.bluray?.raw || defaultRaw;
const blurayMovies = ep.bluray?.movies || defaultMovies;
const dvdRaw = ep.dvd?.raw || defaultRaw;
const dvdSeriesRaw = ep.dvd?.seriesRaw || dvdRaw;
const dvdMovies = ep.dvd?.movies || defaultMovies;
const dvdSeries = ep.dvd?.series || defaultSeries;
const cdRaw = ep.cd?.raw || defaultCd;
const cdMovies = ep.cd?.movies || cdRaw;
const audiobookRaw = ep.audiobook?.raw || defaultAudiobookRaw;
@@ -796,6 +871,17 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
{isDefault(dvdMovies, defaultMovies) && <span className="path-default-badge">Standard</span>}
</td>
</tr>
<tr>
<td><strong>DVD Serie</strong></td>
<td>
<code>{dvdSeriesRaw}</code>
{isDefault(dvdSeriesRaw, dvdRaw) && <span className="path-default-badge">wie DVD RAW</span>}
</td>
<td>
<code>{dvdSeries}</code>
{isDefault(dvdSeries, defaultSeries) && <span className="path-default-badge">Standard</span>}
</td>
</tr>
<tr>
<td><strong>CD / Audio</strong></td>
<td>
@@ -915,7 +1001,8 @@ export default function DynamicSettingsForm({
errors,
dirtyKeys,
onChange,
effectivePaths
effectivePaths,
onNotify
}) {
const safeCategories = Array.isArray(categories) ? categories : [];
const expertModeEnabled = toBoolean(values?.[EXPERT_MODE_SETTING_KEY]);
@@ -1009,7 +1096,7 @@ export default function DynamicSettingsForm({
const isDriveCategory = normalizeText(category?.category) === 'laufwerk';
return (
<div className="settings-sections">
{isDriveCategory && <DetectedDrivesInfo />}
{isDriveCategory && <DetectedDrivesInfo expertModeEnabled={expertModeEnabled} onNotify={onNotify} />}
{sections.map((section) => (
<section
key={`${category?.category || 'category'}-${section.id}`}