video downloads: UX/visual polish on the Downloads tab (review feedback)
- Hidden leftover music sections: the broad hide rule now targets ANY [data-stg='downloads']:not([data-video-only]), not just .settings-group — so the music Quality-Profile tile (#quality-profile-tile) and the Retry-Logic collapsible (which are divs/section-headers, not .settings-group) no longer show as dead, un-openable shells on the video side. - Reordered: Download + Transfer folders now come BEFORE the Download Source section. - slskd connection section is now COLLAPSIBLE (collapsed by default) — it was a lot of fields; uses the same settings-section-header/body pattern as music's Retry Logic. - Hybrid chain now reuses music's .hybrid-source-item markup/CSS for visual PARITY (icon + name + priority + pill toggle + arrows), minus the album/track badge. - Quality profile copy clarified: 'Resolutions to accept' (order = preference) vs 'Preferred source (tie-breaker)', clearer per-control hints. Pure UI (markup/CSS/JS); backend + tests unchanged. JS verdict matches HEAD, HTML divs balanced.
This commit is contained in:
parent
441f0e9ad1
commit
16ba035330
3 changed files with 100 additions and 93 deletions
148
webui/index.html
148
webui/index.html
|
|
@ -6011,9 +6011,23 @@
|
|||
|
||||
<!-- Right Column - Download Settings, Database, Metadata, Logging -->
|
||||
<div class="settings-right-column">
|
||||
<!-- VIDEO Downloads (isolated; shown only on the video side). The
|
||||
music download sections in this tab are hidden on the video side
|
||||
via video-side.css; these data-video-only ones take their place. -->
|
||||
<!-- VIDEO Downloads (isolated; shown only on the video side). EVERY music
|
||||
download element in this tab is hidden via video-side.css; these
|
||||
data-video-only ones replace them. Order: folders → source → slskd → quality. -->
|
||||
<div class="settings-group" data-stg="downloads" data-video-only>
|
||||
<h3>Video Download Folders</h3>
|
||||
<div class="setting-help-text">
|
||||
Where the video side lands downloads and where it moves finished files — SEPARATE from your Music paths.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Download Folder (Input):</label>
|
||||
<input type="text" id="video-download-path" placeholder="./video_downloads">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Transfer Folder (Output — Video Library):</label>
|
||||
<input type="text" id="video-transfer-path" placeholder="./VideoLibrary">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-group" data-stg="downloads" data-video-only>
|
||||
<h3>Download Source</h3>
|
||||
<div class="setting-help-text">
|
||||
|
|
@ -6025,95 +6039,89 @@
|
|||
<option value="soulseek">Soulseek Only</option>
|
||||
<option value="torrent">Torrent Only (via Prowlarr)</option>
|
||||
<option value="usenet">Usenet Only (via Prowlarr)</option>
|
||||
<option value="hybrid">Hybrid (try each in order)</option>
|
||||
<option value="hybrid">Hybrid (Primary + Fallback)</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Hybrid chain — reuses music's hybrid-source-list markup/CSS for
|
||||
visual parity (minus the album/track badge, which is music-only). -->
|
||||
<div id="video-hybrid-container" style="display:none;">
|
||||
<div class="vq-label" style="margin-top:14px;">Hybrid order <span class="vq-hint">best-first; toggle off any you don't want</span></div>
|
||||
<div class="vq-rows" id="video-hybrid-rows"></div>
|
||||
<div class="setting-help-text" style="margin-top:10px;">Use the arrows to order sources. Enable the ones you want — downloads try each enabled source in order.</div>
|
||||
<div class="hybrid-source-list" id="video-hybrid-rows"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- slskd connection — SHARED with Music (one slskd instance). Writes the
|
||||
app-wide soulseek.* config, not video.db. Shown when soulseek is active. -->
|
||||
app-wide soulseek.* config, not video.db. Collapsible; shown when soulseek
|
||||
is active. Inner header/body carry no data-stg so the broad hide rule
|
||||
doesn't catch them (the wrapping group is data-video-only). -->
|
||||
<div class="settings-group" data-stg="downloads" data-video-only id="video-slskd-container" style="display:none;">
|
||||
<h3>Soulseek (slskd) Connection <span class="vq-hint">shared with Music</span></h3>
|
||||
<div class="setting-help-text">
|
||||
These connect to your one slskd instance and are SHARED with the Music side — changing them here changes them for both.
|
||||
<div class="settings-section-header collapsed" onclick="this.classList.toggle('collapsed'); var b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':'';">
|
||||
<span class="settings-section-arrow">▼</span>
|
||||
<h3>Soulseek (slskd) Connection</h3>
|
||||
<span class="settings-section-hint">shared with Music · URL, key, timeouts, peers</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>slskd URL:</label>
|
||||
<input type="text" id="video-slskd-url" placeholder="http://localhost:5030">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="video-slskd-api-key" placeholder="slskd API Key">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Search Timeout (seconds):</label>
|
||||
<input type="number" id="video-slskd-search-timeout" min="15" max="300">
|
||||
<div class="setting-help-text">How long to search for results (15-300 seconds)</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Search Timeout Buffer (seconds):</label>
|
||||
<input type="number" id="video-slskd-search-timeout-buffer" min="5" max="60">
|
||||
<div class="setting-help-text">Extra time to wait for late results (5-60 seconds)</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Delay Between Searches (seconds):</label>
|
||||
<input type="number" id="video-slskd-search-min-delay" min="0" max="60">
|
||||
<div class="setting-help-text">Forces a gap between consecutive searches (0 disables).</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Peer Upload Speed (Mbps):</label>
|
||||
<input type="number" id="video-slskd-min-peer-speed" min="0" max="50">
|
||||
<div class="setting-help-text">Ignore results from peers slower than this</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max Peer Queue Length:</label>
|
||||
<input type="number" id="video-slskd-max-peer-queue" min="0">
|
||||
<div class="setting-help-text">Skip peers with a queue longer than this (0 = no limit)</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Download Timeout (minutes):</label>
|
||||
<input type="number" id="video-slskd-download-timeout" min="2" max="60">
|
||||
<div class="setting-help-text">Abandon stuck downloads after this many minutes</div>
|
||||
</div>
|
||||
<label class="vq-check">
|
||||
<input type="checkbox" id="video-slskd-auto-clear">
|
||||
<span class="vq-check-box"></span>
|
||||
<span>Auto-clear slskd search history</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-group" data-stg="downloads" data-video-only>
|
||||
<h3>Video Download Folders</h3>
|
||||
<div class="setting-help-text">
|
||||
Where the video side lands downloads and where it moves finished files. These are SEPARATE from your Music paths — point them at wherever your video library lives.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Download Folder (Input):</label>
|
||||
<input type="text" id="video-download-path" placeholder="./video_downloads">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Transfer Folder (Output — Video Library):</label>
|
||||
<input type="text" id="video-transfer-path" placeholder="./VideoLibrary">
|
||||
<div class="settings-section-body collapsed" style="display:none;">
|
||||
<div class="setting-help-text">These connect to your one slskd instance and are SHARED with the Music side — changing them here changes them for both.</div>
|
||||
<div class="form-group">
|
||||
<label>slskd URL:</label>
|
||||
<input type="text" id="video-slskd-url" placeholder="http://localhost:5030">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="video-slskd-api-key" placeholder="slskd API Key">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Search Timeout (seconds):</label>
|
||||
<input type="number" id="video-slskd-search-timeout" min="15" max="300">
|
||||
<div class="setting-help-text">How long to search for results (15-300 seconds)</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Search Timeout Buffer (seconds):</label>
|
||||
<input type="number" id="video-slskd-search-timeout-buffer" min="5" max="60">
|
||||
<div class="setting-help-text">Extra time to wait for late results (5-60 seconds)</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Delay Between Searches (seconds):</label>
|
||||
<input type="number" id="video-slskd-search-min-delay" min="0" max="60">
|
||||
<div class="setting-help-text">Forces a gap between consecutive searches (0 disables).</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Peer Upload Speed (Mbps):</label>
|
||||
<input type="number" id="video-slskd-min-peer-speed" min="0" max="50">
|
||||
<div class="setting-help-text">Ignore results from peers slower than this</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max Peer Queue Length:</label>
|
||||
<input type="number" id="video-slskd-max-peer-queue" min="0">
|
||||
<div class="setting-help-text">Skip peers with a queue longer than this (0 = no limit)</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Download Timeout (minutes):</label>
|
||||
<input type="number" id="video-slskd-download-timeout" min="2" max="60">
|
||||
<div class="setting-help-text">Abandon stuck downloads after this many minutes</div>
|
||||
</div>
|
||||
<label class="vq-check">
|
||||
<input type="checkbox" id="video-slskd-auto-clear">
|
||||
<span class="vq-check-box"></span>
|
||||
<span>Auto-clear slskd search history</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- VIDEO quality profile — one unified profile across slskd/torrent/usenet -->
|
||||
<div class="settings-group" data-stg="downloads" data-video-only>
|
||||
<h3>Video Quality Profile</h3>
|
||||
<div class="setting-help-text">
|
||||
One profile across slskd, torrent & usenet — quality is read from the release name / filename. Toggle the tiers you want and order them best-first.
|
||||
One profile for every video source. Quality is read from the release name / filename — choose what you'll accept, then how to break ties.
|
||||
</div>
|
||||
<div class="vq-block">
|
||||
<div class="vq-label">Resolution <span class="vq-hint">order best-first with the arrows</span></div>
|
||||
<div class="vq-label">Resolutions to accept <span class="vq-hint">toggle on what you want · order = preference (best first)</span></div>
|
||||
<div class="vq-rows" id="vq-resolution-rows"></div>
|
||||
</div>
|
||||
<div class="vq-block">
|
||||
<div class="vq-label">Source preference</div>
|
||||
<div class="vq-label">Preferred source <span class="vq-hint">tie-breaker when two releases match the same resolution</span></div>
|
||||
<div class="vq-rows" id="vq-source-rows"></div>
|
||||
</div>
|
||||
<div class="vq-grid">
|
||||
<div class="vq-block">
|
||||
<div class="vq-block" style="margin:0;">
|
||||
<div class="vq-label">Codec</div>
|
||||
<div class="vq-seg" id="vq-codec">
|
||||
<button type="button" data-vq-codec="any">Any</button>
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@
|
|||
// ── Downloads tab: folders + source mode + hybrid chain ──
|
||||
var VIDEO_SOURCES = ['soulseek', 'torrent', 'usenet'];
|
||||
var SRC_DL_LABEL = { soulseek: 'Soulseek', torrent: 'Torrent', usenet: 'Usenet' };
|
||||
var SRC_DL_EMOJI = { soulseek: '🎵', torrent: '🧲', usenet: '📰' };
|
||||
var _videoMode = 'soulseek';
|
||||
var _videoHybrid = ['soulseek'];
|
||||
|
||||
|
|
@ -297,33 +298,29 @@
|
|||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
// Hybrid chain: enabled sources (ordered) + disabled ones appended. No
|
||||
// album-level/track-level distinction — that's a music-only concept.
|
||||
// Hybrid chain — reuses music's .hybrid-source-item markup/CSS for visual
|
||||
// parity. Enabled sources (ordered, numbered) first, disabled ones appended.
|
||||
// No album-level/track-level badge — that's a music-only concept.
|
||||
function renderVideoHybrid() {
|
||||
var host = document.getElementById('video-hybrid-rows');
|
||||
if (!host) return;
|
||||
var enabled = _videoHybrid.filter(function (s) { return VIDEO_SOURCES.indexOf(s) >= 0; });
|
||||
var disabled = VIDEO_SOURCES.filter(function (s) { return enabled.indexOf(s) < 0; });
|
||||
var rows = enabled.map(function (s, i) {
|
||||
return '<div class="vq-row">' +
|
||||
'<span class="vq-arrows">' +
|
||||
'<button type="button" class="vq-arrow" data-vh-move="' + s + '" data-dir="-1"' + (i === 0 ? ' disabled' : '') + '>▲</button>' +
|
||||
'<button type="button" class="vq-arrow" data-vh-move="' + s + '" data-dir="1"' + (i === enabled.length - 1 ? ' disabled' : '') + '>▼</button>' +
|
||||
var visual = enabled.concat(disabled);
|
||||
host.innerHTML = visual.map(function (s) {
|
||||
var on = enabled.indexOf(s) >= 0;
|
||||
var i = enabled.indexOf(s);
|
||||
return '<div class="hybrid-source-item' + (on ? '' : ' disabled') + '">' +
|
||||
'<span class="hybrid-source-arrows">' +
|
||||
'<button type="button" class="hybrid-arrow-btn" data-vh-move="' + s + '" data-dir="-1"' + ((!on || i === 0) ? ' disabled' : '') + ' title="Move up">▲</button>' +
|
||||
'<button type="button" class="hybrid-arrow-btn" data-vh-move="' + s + '" data-dir="1"' + ((!on || i === enabled.length - 1) ? ' disabled' : '') + ' title="Move down">▼</button>' +
|
||||
'</span>' +
|
||||
'<span class="vq-row-name">' + SRC_DL_LABEL[s] + '</span>' +
|
||||
'<span class="vq-row-prio">' + (i + 1) + '</span>' +
|
||||
'<label class="vq-toggle"><input type="checkbox" data-vh-toggle="' + s + '" checked><span class="vq-toggle-track"></span></label>' +
|
||||
'<span class="hybrid-source-icon emoji-icon">' + (SRC_DL_EMOJI[s] || '') + '</span>' +
|
||||
'<span class="hybrid-source-name">' + SRC_DL_LABEL[s] + '</span>' +
|
||||
'<span class="hybrid-source-priority">' + (on ? (i + 1) : '') + '</span>' +
|
||||
'<label class="hybrid-source-toggle"><input type="checkbox" data-vh-toggle="' + s + '"' + (on ? ' checked' : '') + '><span class="toggle-track"></span></label>' +
|
||||
'</div>';
|
||||
});
|
||||
rows = rows.concat(disabled.map(function (s) {
|
||||
return '<div class="vq-row vq-row--off">' +
|
||||
'<span class="vq-arrows"><button type="button" class="vq-arrow" disabled>▲</button><button type="button" class="vq-arrow" disabled>▼</button></span>' +
|
||||
'<span class="vq-row-name">' + SRC_DL_LABEL[s] + '</span>' +
|
||||
'<span class="vq-row-prio"></span>' +
|
||||
'<label class="vq-toggle"><input type="checkbox" data-vh-toggle="' + s + '"><span class="vq-toggle-track"></span></label>' +
|
||||
'</div>';
|
||||
}));
|
||||
host.innerHTML = rows.join('');
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function moveVH(s, dir) {
|
||||
|
|
|
|||
|
|
@ -147,10 +147,12 @@ body[data-side="video"] .settings-group[data-stg="connections"]:not([data-video-
|
|||
display: none !important;
|
||||
}
|
||||
/* The Downloads tab is almost entirely music-specific (streaming sources, music
|
||||
quality profile, retry logic). Hide the music download sections on the video
|
||||
side; the video-only ones (folders, video hybrid, video quality) replace them.
|
||||
quality profile, retry logic). Hide EVERY music download element on the video
|
||||
side — not just .settings-group, but also the quality-profile tile + the
|
||||
collapsible section headers/bodies (#quality-profile-tile, .settings-section-*).
|
||||
The video-only ones (folders, video source/hybrid, video quality) replace them.
|
||||
The shared "Indexers & Downloaders" tab is NOT hidden — it's identical for both. */
|
||||
body[data-side="video"] .settings-group[data-stg="downloads"]:not([data-video-only]) {
|
||||
body[data-side="video"] [data-stg="downloads"]:not([data-video-only]) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue