youtube playlists: same settings cog/modal as channels
playlist cards on the watchlist tab now get the same hover cog → the per-channel settings modal (custom show-name + quality override). the modal is kind-aware so it reads 'Playlist settings' + 'the playlist's real name'. no backend change needed: playlist videos are wishlisted with channel_id = playlist_id, so the existing get/set_channel_settings + enqueue override lookup already key off the playlist id. UI-only: cog on playlistCard + data-kind passed to openChannelSettings(id, title, kind). test covers both cogs.
This commit is contained in:
parent
f734905ba1
commit
23c64e000a
3 changed files with 23 additions and 12 deletions
|
|
@ -12,14 +12,19 @@ _WL = (_ROOT / "webui" / "static" / "video" / "video-watchlist.js").read_text(en
|
|||
_CSS = (_ROOT / "webui" / "static" / "video" / "video-side.css").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_channel_card_has_a_settings_cog():
|
||||
assert 'data-vyt-wsettings' in _WL # cog button on the watchlist channel card
|
||||
assert 'vyt-wcard-cog' in _WL and '.vyt-wcard-cog' in _CSS
|
||||
def test_channel_and_playlist_cards_both_have_a_settings_cog():
|
||||
assert 'data-vyt-wsettings' in _WL # cog button on the watchlist cards
|
||||
assert _WL.count('vyt-wcard-cog') >= 2 # channel + playlist cards
|
||||
assert '.vyt-wcard-cog' in _CSS
|
||||
assert 'data-kind="channel"' in _WL and 'data-kind="playlist"' in _WL
|
||||
|
||||
|
||||
def test_cog_click_opens_the_settings_modal():
|
||||
def test_cog_click_opens_the_settings_modal_with_kind():
|
||||
assert 'VideoYoutube.openChannelSettings(' in _WL
|
||||
assert "cog.getAttribute('data-vyt-wsettings')" in _WL
|
||||
assert "cog.getAttribute('data-kind')" in _WL # kind passed through
|
||||
# the modal reads the kind to label channel vs playlist
|
||||
assert "kind === 'playlist' ? 'Playlist' : 'Channel'" in _YT
|
||||
|
||||
|
||||
def test_modal_is_exposed_and_built():
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
return '<div class="vyt-wcard" data-vyt-open-channel="' + esc(ch.youtube_id) + '" title="Open channel">' +
|
||||
'<div class="vyt-wcard-art">' + av + '</div>' +
|
||||
'<button class="vyt-wcard-cog" type="button" data-vyt-wsettings="' + esc(ch.youtube_id) +
|
||||
'" data-title="' + esc(ch.title) + '" title="Channel settings">⚙</button>' +
|
||||
'" data-kind="channel" data-title="' + esc(ch.title) + '" title="Channel settings">⚙</button>' +
|
||||
'<button class="vyt-wcard-unfollow" type="button" data-vyt-wunfollow="' + esc(ch.youtube_id) +
|
||||
'" title="Unfollow">✕</button>' +
|
||||
'<div class="vyt-wcard-info"><span class="vyt-wcard-title" title="' + esc(ch.title) + '">' +
|
||||
|
|
@ -38,6 +38,8 @@
|
|||
? VideoYoutube.avatar({ poster_url: pl.poster_url, title: pl.title }, 'vyt-wcard-avatar') : '';
|
||||
return '<div class="vyt-wcard vyt-wcard--pl" data-vyt-open-playlist="' + esc(pl.playlist_id) + '" title="Open playlist">' +
|
||||
'<div class="vyt-wcard-art">' + av + '<span class="vyt-wcard-pl-ic" aria-hidden="true">▤</span></div>' +
|
||||
'<button class="vyt-wcard-cog" type="button" data-vyt-wsettings="' + esc(pl.playlist_id) +
|
||||
'" data-kind="playlist" data-title="' + esc(pl.title) + '" title="Playlist settings">⚙</button>' +
|
||||
'<button class="vyt-wcard-unfollow" type="button" data-vyt-wunfollow-playlist="' + esc(pl.playlist_id) +
|
||||
'" title="Unfollow">✕</button>' +
|
||||
'<div class="vyt-wcard-info"><span class="vyt-wcard-title" title="' + esc(pl.title) + '">' +
|
||||
|
|
@ -262,7 +264,8 @@
|
|||
var cog = e.target.closest('[data-vyt-wsettings]');
|
||||
if (cog && window.VideoYoutube && VideoYoutube.openChannelSettings) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
VideoYoutube.openChannelSettings(cog.getAttribute('data-vyt-wsettings'), cog.getAttribute('data-title'));
|
||||
VideoYoutube.openChannelSettings(cog.getAttribute('data-vyt-wsettings'),
|
||||
cog.getAttribute('data-title'), cog.getAttribute('data-kind') || 'channel');
|
||||
return;
|
||||
}
|
||||
var unf = e.target.closest('[data-vyt-wunfollow]');
|
||||
|
|
|
|||
|
|
@ -215,15 +215,17 @@
|
|||
}).join('');
|
||||
}
|
||||
|
||||
function _csetForm(title, s, q, dq) {
|
||||
function _csetForm(title, s, q, dq, kind) {
|
||||
var on = !!s.quality; // a stored override → start enabled
|
||||
var b = (q && q.max_resolution) ? q : dq; // seed fields from override, else default
|
||||
b = b || {};
|
||||
var noun = kind === 'playlist' ? 'playlist' : 'channel';
|
||||
return '' +
|
||||
'<label class="vyt-cset-lbl">Show name (folder)</label>' +
|
||||
'<input class="vyt-cset-in" data-cset-name type="text" value="' + esc(s.custom_name || '') +
|
||||
'" placeholder="' + esc(title || '') + '">' +
|
||||
'<div class="vyt-cset-hint">Overrides the <code>$channel</code> folder/show name when this channel’s videos download. Blank = use the channel’s real name.</div>' +
|
||||
'<div class="vyt-cset-hint">Overrides the <code>$channel</code> folder/show name when this ' + noun +
|
||||
'’s videos download. Blank = use the ' + noun + '’s real name.</div>' +
|
||||
'<label class="vyt-cset-toggle"><input type="checkbox" data-cset-qon' + (on ? ' checked' : '') +
|
||||
'> Force a specific quality for this channel</label>' +
|
||||
'<div class="vyt-cset-q" data-cset-q' + (on ? '' : ' hidden') + '>' +
|
||||
|
|
@ -236,13 +238,14 @@
|
|||
'<div class="vyt-cset-hint">Off = use the global YouTube quality from Settings.</div>';
|
||||
}
|
||||
|
||||
function openChannelSettings(channelId, title) {
|
||||
function openChannelSettings(channelId, title, kind) {
|
||||
if (!channelId) return;
|
||||
var heading = (kind === 'playlist' ? 'Playlist' : 'Channel') + ' settings';
|
||||
var prev = document.getElementById('vyt-cset-overlay'); if (prev) prev.remove();
|
||||
var ov = document.createElement('div');
|
||||
ov.id = 'vyt-cset-overlay'; ov.className = 'vyt-cset-overlay';
|
||||
ov.innerHTML = '<div class="vyt-cset" role="dialog" aria-label="Channel settings">' +
|
||||
'<div class="vyt-cset-head"><span class="vyt-cset-h">Channel settings</span>' +
|
||||
ov.innerHTML = '<div class="vyt-cset" role="dialog" aria-label="' + heading + '">' +
|
||||
'<div class="vyt-cset-head"><span class="vyt-cset-h">' + heading + '</span>' +
|
||||
'<button class="vyt-cset-x" type="button" title="Close">✕</button></div>' +
|
||||
'<div class="vyt-cset-body">Loading…</div>' +
|
||||
'<div class="vyt-cset-foot"><button class="vyt-cset-save" type="button" disabled>Save</button></div></div>';
|
||||
|
|
@ -257,7 +260,7 @@
|
|||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
var s = (d && d.settings) || {}, dq = (d && d.default_quality) || {};
|
||||
body.innerHTML = _csetForm(title, s, s.quality || {}, dq);
|
||||
body.innerHTML = _csetForm(title, s, s.quality || {}, dq, kind);
|
||||
saveBtn.disabled = false;
|
||||
var qon = body.querySelector('[data-cset-qon]'), qbox = body.querySelector('[data-cset-q]');
|
||||
if (qon && qbox) qon.addEventListener('change', function () { qbox.hidden = !qon.checked; });
|
||||
|
|
|
|||
Loading…
Reference in a new issue