2016-11-29 04:16:16 +00:00
<!DOCTYPE html5>
< html >
< head >
{% import "header.html" as header %}
2020-04-04 22:13:01 +00:00
< title > {{channel.name}}< / title >
2016-11-29 04:16:16 +00:00
< meta charset = "UTF-8" >
2020-01-07 05:55:01 +00:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
2020-05-22 02:43:45 +00:00
< link rel = "stylesheet" href = "/static/css/common.css" >
2020-09-03 18:52:51 +00:00
< link rel = "stylesheet" href = "/static/css/ycdl.css" >
2020-05-22 02:43:45 +00:00
< script src = "/static/js/common.js" > < / script >
2020-06-17 21:13:33 +00:00
< script src = "/static/js/api.js" > < / script >
2020-06-17 21:15:50 +00:00
< script src = "/static/js/spinner.js" > < / script >
2016-11-29 04:16:16 +00:00
< style >
2020-09-03 18:54:51 +00:00
.tabbed_container .tab
2016-11-29 04:16:16 +00:00
{
2020-09-05 16:31:37 +00:00
display: grid;
grid-auto-flow: row;
grid-gap: 8px;
2020-09-03 18:54:51 +00:00
padding: 8px;
2020-08-11 01:29:50 +00:00
}
#video_cards
{
}
2017-05-21 20:58:59 +00:00
.video_card
2016-11-29 04:16:16 +00:00
{
position: relative;
2020-04-04 22:13:01 +00:00
display: grid;
grid-template:
"thumbnail details toolbox" auto
"embed embed embed" auto
/auto 1fr auto;
grid-gap: 4px;
2020-09-03 18:54:51 +00:00
margin: 8px 0;
2020-04-04 22:13:01 +00:00
padding: 8px;
2016-11-29 04:16:16 +00:00
border-radius: 4px;
border: 1px solid #000;
}
2017-05-21 20:58:59 +00:00
.video_card:hover
{
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.25);
}
2016-11-29 04:16:16 +00:00
.video_card_pending
{
background-color: #ffffaa;
}
.video_card_ignored
{
background-color: #ffc886;
}
2017-05-21 20:58:59 +00:00
.video_card_selected
{
background-color: #13f4ff !important;
}
2016-11-29 04:16:16 +00:00
.video_card_downloaded
{
background-color: #aaffaa;
}
2020-04-04 22:13:01 +00:00
.video_thumbnail
{
grid-area: thumbnail;
justify-self: center;
}
2020-09-03 18:54:51 +00:00
.video_title
{
word-break: break-word;
}
2020-04-04 22:13:01 +00:00
.video_details
{
grid-area: details;
align-self: center;
/*
margin-right prevents the empty space of the < a > tag from swallowing
click events meant for the video card.
*/
margin-right: auto;
}
.embed_toolbox
{
grid-area: embed;
/*
disabling pointer events on the toolbox prevents it from swallowing click
events meant for the video card. Then we must re-enable them for child
elements so the embed button is still clickable.
This one uses pointer-events instead of margin because margin makes the
whole embed too small.
*/
pointer-events: none;
}
.embed_toolbox *
{
pointer-events: auto;
}
2016-11-29 04:16:16 +00:00
.action_toolbox
{
2020-04-04 22:13:01 +00:00
grid-area: toolbox;
justify-self: right;
2016-11-29 04:16:16 +00:00
display: inline-flex;
flex-direction: row;
position: relative;
2020-04-04 22:13:01 +00:00
margin-top: auto;
margin-bottom: auto;
2016-11-29 04:16:16 +00:00
}
2020-04-04 22:13:01 +00:00
2016-11-29 04:16:16 +00:00
.video_action_dropdown
{
z-index: 1;
background-color: #fff;
padding: 4px;
border: 1px solid #000;
position: absolute;
top: 100%;
right: 0;
display: none;
flex-direction: column;
}
2020-04-03 15:43:47 +00:00
/*
Thank you SimonSimCity
https://stackoverflow.com/a/35153397
*/
.video_iframe_holder
{
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
.video_iframe_holder iframe
{
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
2020-04-04 22:13:01 +00:00
@media screen and (max-width: 600px)
{
.video_card
{
grid-template:
2020-09-03 18:54:51 +00:00
"thumbnail details"
"toolbox toolbox"
"embed embed"
/auto 1fr;
2020-04-04 22:13:01 +00:00
}
}
2016-11-29 04:16:16 +00:00
< / style >
< / head >
< body >
{{header.make_header()}}
< div id = "content_body" >
2017-05-21 20:58:59 +00:00
{% if channel is not none %}
2020-09-03 18:54:51 +00:00
< h2 > {{channel.name}}< / h2 >
{% endif %}
2020-09-05 16:31:13 +00:00
{% if channel is not none %}
2020-09-03 18:54:51 +00:00
< div class = "tabbed_container" >
< div class = "tab" data-tab-title = "Videos" >
2020-09-04 21:48:50 +00:00
< div > < button class = "refresh_button button_with_spinner" onclick = "return refresh_channel_form(false);" > Refresh new videos< / button > < / div >
< div > < button class = "refresh_button button_with_spinner" onclick = "return refresh_channel_form(true);" > Refresh everything< / button > < / div >
2020-06-11 06:09:17 +00:00
2020-08-16 05:28:27 +00:00
{% endif %}
2020-04-04 22:13:01 +00:00
2020-09-03 18:54:51 +00:00
< div > View
2020-06-11 06:09:17 +00:00
{% if channel is not none %}
2020-09-06 00:49:04 +00:00
< a class = "merge_params {{" bold " if not state else " " } } " href = "/channel/{{channel.id}}" > All< / a >
2016-11-29 04:16:16 +00:00
{% else %}
2020-09-06 00:49:04 +00:00
< a class = "merge_params {{" bold " if not state else " " } } " href = "/videos" > All< / a >
2016-11-29 04:16:16 +00:00
{% endif %}
2020-09-03 18:54:51 +00:00
2020-03-11 21:30:12 +00:00
{% for statename in all_states %}
{% if channel is not none %}
2020-09-06 00:49:04 +00:00
< a class = "merge_params {{" bold " if state = = statename else " " } } " href = "/channel/{{channel.id}}/{{statename}}" > {{statename.capitalize()}}< / a >
2020-03-11 21:30:12 +00:00
{% else %}
2020-09-06 00:49:04 +00:00
< a class = "merge_params {{" bold " if state = = statename else " " } } " href = "/videos/{{statename}}" > {{statename.capitalize()}}< / a >
2020-03-11 21:30:12 +00:00
{% endif %}
{% endfor %}
2020-09-03 18:54:51 +00:00
< / div >
2020-05-22 00:28:34 +00:00
2020-09-03 18:54:51 +00:00
< div > Sort by
2020-09-06 00:49:04 +00:00
< a class = "merge_params {{" bold " if orderby = = " published " or not orderby else " " } } " href = "?orderby=published" > Date< / a >
< a class = "merge_params {{" bold " if orderby = = " duration " else " " } } " href = "?orderby=duration" > Duration< / a >
< a class = "merge_params {{" bold " if orderby = = " views " else " " } } " href = "?orderby=views" > Views< / a >
< a class = "merge_params {{" bold " if orderby = = " random " else " " } } " href = "?orderby=random" > Random< / a >
2020-08-11 01:29:50 +00:00
< / div >
2017-05-21 20:58:59 +00:00
< div id = "video_cards" >
2020-08-28 23:21:42 +00:00
< center > < input disabled class = "enable_on_pageload" type = "text" id = "search_filter" / > < / center >
2020-08-11 01:29:50 +00:00
< center > < span id = "search_filter_count" > {{videos|length}}< / span > items< / center >
2017-05-21 20:58:59 +00:00
{% for video in videos %}
2020-04-04 22:13:01 +00:00
< div id = "video_card_{{video.id}}"
data-ytid="{{video.id}}"
2020-09-04 17:37:49 +00:00
onclick="return onclick_select(event);"
2020-09-04 22:55:48 +00:00
class="video_card video_card_{{video.state}}"
2017-05-21 20:58:59 +00:00
>
2020-08-16 05:28:53 +00:00
< img class = "video_thumbnail" loading = "lazy" src = "http://i3.ytimg.com/vi/{{video.id}}/default.jpg" height = "100px" >
2020-04-04 22:13:01 +00:00
< div class = "video_details" >
< a class = "video_title" href = "https://www.youtube.com/watch?v={{video.id}}" > {{video._published_str}} - {{video.title}}< / a >
< span > ({{video.duration | seconds_to_hms}})< / span >
< span > ({{video.views}})< / span >
2017-05-21 20:58:59 +00:00
{% if channel is none %}
2020-04-04 22:13:01 +00:00
< a href = "/channel/{{video.author_id}}" > ({{video.author.name if video.author else video.author_id}})< / a >
2016-11-29 04:16:16 +00:00
{% endif %}
2020-04-04 22:13:01 +00:00
< / div >
2017-05-21 20:58:59 +00:00
< div class = "action_toolbox" >
< button
2020-09-04 22:55:48 +00:00
{% if video.state == "pending" %}
2017-05-21 20:58:59 +00:00
class="video_action_pending hidden"
{% else %}
class="video_action_pending"
{% endif %}
2020-09-04 17:37:49 +00:00
onclick="return action_button_passthrough(event, api.videos.mark_state, 'pending');"
2017-05-21 20:58:59 +00:00
>Revert to Pending< / button >
< button
2020-09-04 22:55:48 +00:00
{% if video.state == "pending" %}
2017-05-21 20:58:59 +00:00
class="video_action_download"
{% else %}
class="video_action_download hidden"
{% endif %}
2020-09-04 17:37:49 +00:00
onclick="return action_button_passthrough(event, api.videos.start_download);"
2017-05-21 20:58:59 +00:00
>Download< / button >
< button
2020-09-04 22:55:48 +00:00
{% if video.state == "pending" %}
2017-05-21 20:58:59 +00:00
class="video_action_ignore"
{% else %}
class="video_action_ignore hidden"
{% endif %}
2020-09-04 17:37:49 +00:00
onclick="return action_button_passthrough(event, api.videos.mark_state, 'ignored');"
2017-05-21 20:58:59 +00:00
>Ignore< / button >
< / div >
2020-04-04 22:13:01 +00:00
< div class = "embed_toolbox" >
2020-09-04 17:37:49 +00:00
< button class = "show_embed_button" onclick = "return toggle_embed_video('{{video.id}}');" > Embed< / button >
< button class = "hide_embed_button hidden" onclick = "return toggle_embed_video('{{video.id}}');" > Unembed< / button >
2020-04-04 22:13:01 +00:00
< / div >
2016-11-29 04:16:16 +00:00
< / div >
2017-05-21 20:58:59 +00:00
{% endfor %}
2020-09-03 18:54:51 +00:00
< / div > <!-- video_cards -->
{% if channel is not none %}
2020-09-05 16:31:13 +00:00
< / div > <!-- tab - videos -->
2020-09-03 18:54:51 +00:00
< div class = "tab" data-tab-title = "Settings" >
< div >
New videos are:
2020-09-04 17:37:49 +00:00
< select onchange = "set_automark_hook(event);" >
2020-09-03 18:54:51 +00:00
< option value = "pending" { { " selected " if channel . automark = = " pending " else " " } } > pending< / option >
< option value = "downloaded" { { " selected " if channel . automark = = " downloaded " else " " } } > downloaded< / option >
< option value = "ignored" { { " selected " if channel . automark = = " ignored " else " " } } > ignored< / option >
< / select >
< span id = "set_automark_spinner" class = "hidden" > Working...< / span >
2016-11-29 04:16:16 +00:00
< / div >
2017-05-21 20:58:59 +00:00
2020-09-03 18:54:51 +00:00
< button class = "red_button button_with_confirm"
data-prompt="Delete channel and all videos?"
2020-09-04 17:37:49 +00:00
data-onclick="return delete_channel_form();"
2020-09-03 18:54:51 +00:00
>Delete Channel< / button >
< / div > <!-- tab - settings -->
< / div > <!-- tabbed_container -->
2020-09-05 16:31:13 +00:00
{% endif %}
2016-11-29 04:16:16 +00:00
< / div >
< / body >
< script type = "text/javascript" >
2020-06-03 19:58:33 +00:00
{% if channel is not none %}
var CHANNEL_ID = "{{channel.id}}";
{% endif %}
2020-09-04 22:55:48 +00:00
var STATE = "{{state if state else ""}}";
2017-05-21 20:58:59 +00:00
var video_card_first_selected = null;
var video_card_selections = [];
2019-01-24 05:22:09 +00:00
2020-08-11 01:30:56 +00:00
function delete_channel_form()
{
api.channels.delete_channel(CHANNEL_ID, api.channels.callback_go_to_channels);
}
2020-06-17 21:15:50 +00:00
function refresh_channel_form(force)
{
console.log(`Refreshing channel ${CHANNEL_ID}, force=${force}.`);
api.channels.refresh_channel(CHANNEL_ID, force, refresh_channel_callback)
}
function refresh_channel_callback(response)
{
2020-06-28 23:57:32 +00:00
if (response.meta.status == 200)
2020-06-17 21:15:50 +00:00
{
common.refresh();
}
else
{
alert(JSON.stringify(response));
}
}
2019-01-24 05:22:09 +00:00
var search_filter_box = document.getElementById("search_filter");
2020-08-28 00:11:52 +00:00
var search_filter_wait_for_typing;
2019-01-24 05:22:09 +00:00
var search_filter_hook = function(event)
{
2020-08-28 00:11:52 +00:00
clearTimeout(search_filter_wait_for_typing);
search_filter_wait_for_typing = setTimeout(function()
{
filter_video_cards(search_filter_box.value);
}, 200);
2019-01-24 05:22:09 +00:00
}
search_filter_box.addEventListener("keyup", search_filter_hook);
function filter_video_cards(search_term)
{
2019-01-25 23:54:31 +00:00
/*
Apply the current download filter (pending, ignored, downloaded) by removing
mismatched cards from the dom.
Apply the search filter textbox by hiding the mismatched cards.
*/
2020-09-03 22:44:58 +00:00
let count = 0;
2020-08-16 05:27:47 +00:00
video_cards = document.getElementById("video_cards");
2020-08-27 23:53:51 +00:00
video_cards.classList.add("hidden");
2019-01-24 05:22:09 +00:00
search_term = search_term.toLocaleLowerCase();
2020-09-04 22:55:48 +00:00
let state_class = "video_card_" + STATE;
2020-08-27 23:53:51 +00:00
Array.from(video_cards.getElementsByClassName("video_card")).forEach(function(video_card)
2019-01-24 05:22:09 +00:00
{
2020-09-03 22:44:58 +00:00
let title = video_card.getElementsByClassName("video_title")[0].innerText.toLocaleLowerCase();
2020-09-04 22:55:48 +00:00
if (STATE & & !video_card.classList.contains(state_class))
2019-01-25 23:54:31 +00:00
{
video_cards.removeChild(video_card);
}
else if (search_term !== "" & & title.indexOf(search_term) == -1)
2019-01-24 05:22:09 +00:00
{
video_card.classList.add("hidden");
}
else
{
video_card.classList.remove("hidden");
count += 1;
}
2020-08-27 23:53:51 +00:00
});
video_cards.classList.remove("hidden");
2019-01-24 05:22:09 +00:00
document.getElementById("search_filter_count").innerText = count;
}
2017-05-21 20:58:59 +00:00
2018-12-18 03:17:53 +00:00
function toggle_embed_video(video_id)
{
2020-09-03 22:44:58 +00:00
let video_card = document.getElementById("video_card_" + video_id);
let show_button = video_card.getElementsByClassName("show_embed_button")[0];
let hide_button = video_card.getElementsByClassName("hide_embed_button")[0];
let embed_toolbox = video_card.getElementsByClassName("embed_toolbox")[0];
let embeds = video_card.getElementsByClassName("video_iframe_holder");
2018-12-18 03:17:53 +00:00
if (embeds.length == 0)
{
2020-09-03 22:44:58 +00:00
let html = `< div class = "video_iframe_holder" > < iframe width = "711" height = "400" src = "https://www.youtube.com/embed/${video_id}" frameborder = "0" allow = "encrypted-media" allowfullscreen > < / iframe > < / div > `
let embed = common.html_to_element(html);
2020-04-04 22:13:01 +00:00
embed_toolbox.appendChild(embed);
2018-12-18 03:17:53 +00:00
show_button.classList.add("hidden");
hide_button.classList.remove("hidden");
}
else
{
2020-04-04 22:13:01 +00:00
embeds[0].parentElement.removeChild(embeds[0]);
2018-12-18 03:17:53 +00:00
show_button.classList.remove("hidden");
hide_button.classList.add("hidden");
}
}
2017-05-21 20:58:59 +00:00
function deselect_all()
2016-11-29 04:16:16 +00:00
{
2020-09-03 22:44:58 +00:00
let video_card_first_selected = null;
for (const video_card_selection of video_card_selections)
2016-11-29 04:16:16 +00:00
{
2020-09-03 22:44:58 +00:00
video_card_selection.classList.remove("video_card_selected");
2016-11-29 04:16:16 +00:00
}
2017-05-21 20:58:59 +00:00
video_card_selections = [];
}
function onclick_select(event)
{
if (!event.target.classList.contains("video_card"))
{
return;
}
if (video_card_first_selected === null)
{
video_card_first_selected = event.target;
}
2020-09-03 22:44:58 +00:00
let video_cards = Array.from(document.getElementById("video_cards").children);
2019-01-24 05:22:09 +00:00
2017-05-21 20:58:59 +00:00
if (event.shiftKey === false & & event.ctrlKey === false)
{
video_card_selections = [];
video_card_selections.push(event.target);
video_card_first_selected = event.target;
}
else if (event.shiftKey === true)
2016-11-29 04:16:16 +00:00
{
2017-05-21 20:58:59 +00:00
video_card_selections = [];
2020-09-03 22:44:58 +00:00
let start_index = video_cards.indexOf(video_card_first_selected);
let end_index = video_cards.indexOf(event.target);
2017-05-21 20:58:59 +00:00
if (end_index < start_index )
{
2020-09-03 22:44:58 +00:00
let temp = start_index;
2017-05-21 20:58:59 +00:00
start_index = end_index;
end_index = temp;
2016-11-29 04:16:16 +00:00
}
2020-09-03 22:44:58 +00:00
for (let index = start_index; index < = end_index; index += 1)
2017-05-21 20:58:59 +00:00
{
2019-01-24 05:22:09 +00:00
if (video_cards[index].classList.contains("hidden"))
{
continue;
}
2017-05-21 20:58:59 +00:00
video_card_selections.push(video_cards[index]);
2016-11-29 04:16:16 +00:00
}
2017-05-21 20:58:59 +00:00
}
else if (event.ctrlKey === true)
{
2020-09-03 22:44:58 +00:00
let existing_index = video_card_selections.indexOf(event.target)
2017-05-21 20:58:59 +00:00
if (existing_index == -1)
{
video_card_first_selected = event.target;
video_card_selections.push(event.target);
}
else
{
video_card_selections.splice(existing_index, 1);
}
}
2020-09-03 22:44:58 +00:00
for (const video_card of video_cards)
2017-05-21 20:58:59 +00:00
{
2020-09-03 22:44:58 +00:00
if (video_card_selections.indexOf(video_card) > -1)
2017-05-21 20:58:59 +00:00
{
2020-09-03 22:44:58 +00:00
video_card.classList.add("video_card_selected");
2017-05-21 20:58:59 +00:00
}
else
{
2020-09-03 22:44:58 +00:00
video_card.classList.remove("video_card_selected");
2017-05-21 20:58:59 +00:00
}
}
2020-03-11 20:01:55 +00:00
document.getSelection().removeAllRanges();
2017-05-21 20:58:59 +00:00
return false;
}
function action_button_passthrough(event, action_function, action_argument)
{
2020-09-03 22:44:58 +00:00
let elements;
let this_card = event.target.parentElement.parentElement;
2017-05-21 20:58:59 +00:00
if (video_card_selections.length > 0 & & video_card_selections.indexOf(this_card) > -1)
{
elements = video_card_selections;
2016-11-29 04:16:16 +00:00
}
else
{
2017-05-21 20:58:59 +00:00
// Button -> button toolbox -> video card
elements = [this_card];
}
2020-09-03 22:44:58 +00:00
let video_ids = [];
for (const element of elements)
2017-05-21 20:58:59 +00:00
{
2020-09-03 22:44:58 +00:00
video_ids.push(element.dataset["ytid"]);
2019-01-24 05:22:09 +00:00
}
video_ids = video_ids.join(",");
if (action_argument === undefined)
{
action_function(video_ids, receive_action_response);
}
else
{
action_function(video_ids, action_argument, receive_action_response);
2016-11-29 04:16:16 +00:00
}
2020-03-11 21:30:56 +00:00
if (! event.shiftKey)
{
deselect_all();
}
2016-11-29 04:16:16 +00:00
}
2017-05-21 20:58:59 +00:00
function give_action_buttons(video_card_div)
2016-11-29 04:16:16 +00:00
{
2020-09-03 22:44:58 +00:00
let toolbox = video_card_div.getElementsByClassName("action_toolbox")[0]
let buttons = Array.from(toolbox.getElementsByTagName("button"));
let is_pending = video_card_div.classList.contains("video_card_pending");
2020-03-11 21:30:12 +00:00
buttons.forEach(function(button)
2017-05-21 20:58:59 +00:00
{
2020-03-11 21:30:12 +00:00
if (is_pending)
{ button.classList.remove("hidden"); }
else
{ button.classList.add("hidden"); }
});
2020-09-03 22:44:58 +00:00
let button_pending = video_card_div.getElementsByClassName("video_action_pending")[0];
2020-03-11 21:30:12 +00:00
if (is_pending)
{ button_pending.classList.add("hidden"); }
2017-05-21 20:58:59 +00:00
else
2020-03-11 21:30:12 +00:00
{ button_pending.classList.remove("hidden"); }
2016-11-29 04:16:16 +00:00
}
function receive_action_response(response)
{
2020-09-03 22:44:58 +00:00
let video_ids = response.data.video_ids;
let state = response.data.state;
let state_class = "video_card_" + state;
for (const video_id of video_ids)
2016-11-29 04:16:16 +00:00
{
2020-09-03 22:44:58 +00:00
let card = document.getElementById("video_card_" + video_id);
2020-03-11 21:30:12 +00:00
{% for statename in all_states %}
card.classList.remove("video_card_{{statename}}");
{% endfor %}
2020-09-03 22:44:58 +00:00
card.classList.add(state_class);
2019-01-24 05:22:09 +00:00
give_action_buttons(card);
2016-11-29 04:16:16 +00:00
}
}
2020-06-27 03:59:55 +00:00
var set_automark_spinner = document.getElementById("set_automark_spinner");
set_automark_spinner = new spinner.Spinner(set_automark_spinner);
2020-05-22 00:28:34 +00:00
function set_automark_hook(event)
{
2020-06-27 03:59:55 +00:00
set_automark_spinner.show();
api.channels.set_automark(CHANNEL_ID, event.target.value, set_automark_callback);
}
function set_automark_callback(response)
{
2020-06-28 23:57:32 +00:00
if (response.meta.status == 200)
2020-06-27 03:59:55 +00:00
{
set_automark_spinner.hide();
}
2020-05-22 00:28:34 +00:00
}
2016-11-29 04:16:16 +00:00
< / script >
< / html >