0.12.0-17 Misc fixes
Deploy Docs to GitHub Pages / Build Documentation (push) Has been cancelled
Deploy Docs to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled

This commit is contained in:
michael committed 2026-03-30 19:15:23 +00:00
1 parent 5284dd2a43
commit c67ac593de
29 files changed
+1596 -117

No files matched your search

+105 -1
View File
@@ -48,6 +48,7 @@ const NOTIFICATION_EVENT_TOGGLE_KEYS = new Set([
const PUSHOVER_ENABLED_SETTING_KEY = 'pushover_enabled';
const EXPERT_MODE_SETTING_KEY = 'ui_expert_mode';
const EXTERNAL_STORAGE_PATHS_KEY = 'external_storage_paths';
const ALWAYS_HIDDEN_SETTING_KEYS = new Set([
'drive_device',
'makemkv_source_index', // auto-derived from drive path — shown as read-only in drive info
@@ -201,6 +202,96 @@ function DriveDevicesEditor({ value, onChange, settingKey }) {
);
}
function ExternalStoragePathsEditor({ value, onChange, settingKey }) {
const parsePaths = (rawValue) => {
try {
const parsed = JSON.parse(rawValue || '[]');
if (!Array.isArray(parsed)) return [];
const normalized = [];
for (const entry of parsed) {
if (typeof entry === 'string') {
normalized.push({
name: '',
path: String(entry || '')
});
continue;
}
if (entry && typeof entry === 'object') {
normalized.push({
name: String(entry.name || entry.label || ''),
path: String(entry.path || '')
});
}
}
return normalized;
} catch (_error) {
return [];
}
};
const entries = parsePaths(value);
const updatePaths = (nextPaths) => {
const normalized = (Array.isArray(nextPaths) ? nextPaths : [])
.map((entry) => ({
name: String(entry?.name || ''),
path: String(entry?.path || '')
}));
onChange?.(settingKey, JSON.stringify(normalized));
};
return (
<div className="external-storage-editor">
{entries.length === 0 && (
<p className="external-storage-empty">Keine externen Speicherpfade konfiguriert.</p>
)}
{entries.map((entry, idx) => (
<div key={`external-storage-${idx}`} className="external-storage-row">
<InputText
value={entry.name}
placeholder="Name (z.B. USB HDD)"
onChange={(event) => {
const next = [...entries];
next[idx] = { ...next[idx], name: event.target.value };
updatePaths(next);
}}
className="external-storage-name-input"
/>
<InputText
value={entry.path}
placeholder="/mnt/external-disk"
onChange={(event) => {
const next = [...entries];
next[idx] = { ...next[idx], path: event.target.value };
updatePaths(next);
}}
className="external-storage-input"
/>
<Button
icon="pi pi-trash"
severity="danger"
text
rounded
size="small"
onClick={() => updatePaths(entries.filter((_, entryIndex) => entryIndex !== idx))}
type="button"
aria-label="Externen Speicherpfad entfernen"
/>
</div>
))}
<Button
icon="pi pi-plus"
label="Pfad hinzufügen"
severity="secondary"
text
size="small"
onClick={() => updatePaths([...entries, { name: '', path: '' }])}
type="button"
/>
</div>
);
}
function DetectedDrivesInfo() {
const [drives, setDrives] = useState(null);
const [loading, setLoading] = useState(false);
@@ -351,6 +442,7 @@ 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'];
const LOG_PATH_KEYS = ['log_dir'];
const EXTERNAL_STORAGE_PATH_KEYS = [EXTERNAL_STORAGE_PATHS_KEY];
const CONVERTER_PATH_KEYS = ['converter_raw_dir', 'converter_movie_dir', 'converter_audio_dir', 'converter_output_template_video', 'converter_output_template_audio'];
function buildSectionsForCategory(categoryName, settings) {
@@ -432,7 +524,9 @@ function SettingField({
</label>
)}
{(setting.type === 'string' || setting.type === 'path') && setting.key !== 'drive_devices' ? (
{(setting.type === 'string' || setting.type === 'path')
&& setting.key !== 'drive_devices'
&& setting.key !== EXTERNAL_STORAGE_PATHS_KEY ? (
<InputText
id={setting.key}
value={value ?? ''}
@@ -448,6 +542,14 @@ function SettingField({
/>
) : null}
{setting.key === EXTERNAL_STORAGE_PATHS_KEY ? (
<ExternalStoragePathsEditor
value={value}
onChange={onChange}
settingKey={setting.key}
/>
) : null}
{setting.type === 'number' ? (
<InputNumber
id={setting.key}
@@ -540,6 +642,7 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
const audiobookSettings = list.filter((s) => AUDIOBOOK_PATH_KEYS.includes(s.key) || (s.key.endsWith('_owner') && AUDIOBOOK_PATH_KEYS.includes(s.key.replace('_owner', ''))));
const downloadSettings = list.filter((s) => DOWNLOAD_PATH_KEYS.includes(s.key) || (s.key.endsWith('_owner') && DOWNLOAD_PATH_KEYS.includes(s.key.replace('_owner', ''))));
const logSettings = list.filter((s) => LOG_PATH_KEYS.includes(s.key));
const externalStorageSettings = list.filter((s) => EXTERNAL_STORAGE_PATH_KEYS.includes(s.key));
const converterSettings = list.filter((s) => CONVERTER_PATH_KEYS.includes(s.key));
const defaultRaw = effectivePaths?.defaults?.raw || 'data/output/raw';
@@ -675,6 +778,7 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
{ title: 'Audiobook', pathSettings: audiobookSettings },
{ title: 'Converter', pathSettings: converterSettings },
{ title: 'Downloads', pathSettings: downloadSettings },
...(externalStorageSettings.length > 0 ? [{ title: 'Externe Speicher', pathSettings: externalStorageSettings }] : []),
...(logSettings.length > 0 ? [{ title: 'Logs', pathSettings: logSettings }] : [])
]
.filter(({ pathSettings }) =>