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")
|
_CSS = (_ROOT / "webui" / "static" / "video" / "video-side.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def test_channel_card_has_a_settings_cog():
|
def test_channel_and_playlist_cards_both_have_a_settings_cog():
|
||||||
assert 'data-vyt-wsettings' in _WL # cog button on the watchlist channel card
|
assert 'data-vyt-wsettings' in _WL # cog button on the watchlist cards
|
||||||
assert 'vyt-wcard-cog' in _WL and '.vyt-wcard-cog' in _CSS
|
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 'VideoYoutube.openChannelSettings(' in _WL
|
||||||
assert "cog.getAttribute('data-vyt-wsettings')" 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():
|
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">' +
|
return '<div class="vyt-wcard" data-vyt-open-channel="' + esc(ch.youtube_id) + '" title="Open channel">' +
|
||||||
'<div class="vyt-wcard-art">' + av + '</div>' +
|
'<div class="vyt-wcard-art">' + av + '</div>' +
|
||||||
'<button class="vyt-wcard-cog" type="button" data-vyt-wsettings="' + esc(ch.youtube_id) +
|
'<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) +
|
'<button class="vyt-wcard-unfollow" type="button" data-vyt-wunfollow="' + esc(ch.youtube_id) +
|
||||||
'" title="Unfollow">✕</button>' +
|
'" title="Unfollow">✕</button>' +
|
||||||
'<div class="vyt-wcard-info"><span class="vyt-wcard-title" title="' + esc(ch.title) + '">' +
|
'<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') : '';
|
? 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">' +
|
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>' +
|
'<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) +
|
'<button class="vyt-wcard-unfollow" type="button" data-vyt-wunfollow-playlist="' + esc(pl.playlist_id) +
|
||||||
'" title="Unfollow">✕</button>' +
|
'" title="Unfollow">✕</button>' +
|
||||||
'<div class="vyt-wcard-info"><span class="vyt-wcard-title" title="' + esc(pl.title) + '">' +
|
'<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]');
|
var cog = e.target.closest('[data-vyt-wsettings]');
|
||||||
if (cog && window.VideoYoutube && VideoYoutube.openChannelSettings) {
|
if (cog && window.VideoYoutube && VideoYoutube.openChannelSettings) {
|
||||||
e.preventDefault(); e.stopPropagation();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
var unf = e.target.closest('[data-vyt-wunfollow]');
|
var unf = e.target.closest('[data-vyt-wunfollow]');
|
||||||
|
|
|
||||||
|
|
@ -215,15 +215,17 @@
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function _csetForm(title, s, q, dq) {
|
function _csetForm(title, s, q, dq, kind) {
|
||||||
var on = !!s.quality; // a stored override → start enabled
|
var on = !!s.quality; // a stored override → start enabled
|
||||||
var b = (q && q.max_resolution) ? q : dq; // seed fields from override, else default
|
var b = (q && q.max_resolution) ? q : dq; // seed fields from override, else default
|
||||||
b = b || {};
|
b = b || {};
|
||||||
|
var noun = kind === 'playlist' ? 'playlist' : 'channel';
|
||||||
return '' +
|
return '' +
|
||||||
'<label class="vyt-cset-lbl">Show name (folder)</label>' +
|
'<label class="vyt-cset-lbl">Show name (folder)</label>' +
|
||||||
'<input class="vyt-cset-in" data-cset-name type="text" value="' + esc(s.custom_name || '') +
|
'<input class="vyt-cset-in" data-cset-name type="text" value="' + esc(s.custom_name || '') +
|
||||||
'" placeholder="' + esc(title || '') + '">' +
|
'" 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' : '') +
|
'<label class="vyt-cset-toggle"><input type="checkbox" data-cset-qon' + (on ? ' checked' : '') +
|
||||||
'> Force a specific quality for this channel</label>' +
|
'> Force a specific quality for this channel</label>' +
|
||||||
'<div class="vyt-cset-q" data-cset-q' + (on ? '' : ' hidden') + '>' +
|
'<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>';
|
'<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;
|
if (!channelId) return;
|
||||||
|
var heading = (kind === 'playlist' ? 'Playlist' : 'Channel') + ' settings';
|
||||||
var prev = document.getElementById('vyt-cset-overlay'); if (prev) prev.remove();
|
var prev = document.getElementById('vyt-cset-overlay'); if (prev) prev.remove();
|
||||||
var ov = document.createElement('div');
|
var ov = document.createElement('div');
|
||||||
ov.id = 'vyt-cset-overlay'; ov.className = 'vyt-cset-overlay';
|
ov.id = 'vyt-cset-overlay'; ov.className = 'vyt-cset-overlay';
|
||||||
ov.innerHTML = '<div class="vyt-cset" role="dialog" aria-label="Channel settings">' +
|
ov.innerHTML = '<div class="vyt-cset" role="dialog" aria-label="' + heading + '">' +
|
||||||
'<div class="vyt-cset-head"><span class="vyt-cset-h">Channel settings</span>' +
|
'<div class="vyt-cset-head"><span class="vyt-cset-h">' + heading + '</span>' +
|
||||||
'<button class="vyt-cset-x" type="button" title="Close">✕</button></div>' +
|
'<button class="vyt-cset-x" type="button" title="Close">✕</button></div>' +
|
||||||
'<div class="vyt-cset-body">Loading…</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>';
|
'<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 (r) { return r.json(); })
|
||||||
.then(function (d) {
|
.then(function (d) {
|
||||||
var s = (d && d.settings) || {}, dq = (d && d.default_quality) || {};
|
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;
|
saveBtn.disabled = false;
|
||||||
var qon = body.querySelector('[data-cset-qon]'), qbox = body.querySelector('[data-cset-q]');
|
var qon = body.querySelector('[data-cset-qon]'), qbox = body.querySelector('[data-cset-q]');
|
||||||
if (qon && qbox) qon.addEventListener('change', function () { qbox.hidden = !qon.checked; });
|
if (qon && qbox) qon.addEventListener('change', function () { qbox.hidden = !qon.checked; });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue