ycdl/frontends/ycdl_flask/templates/channel.html
Ethan Dalool 213278edcc Capitalize state filter links, remove commas from sorter links.
To make these two rows look similar. Rather than lowercasing the
sorters, let's capitalize the state filters. And rather than adding
a ','.join to the state filters, let's just remove commas from the
sorters. Good enough.
2020-08-09 17:54:38 -07:00

522 lines
15 KiB
HTML

<!DOCTYPE html5>
<html>
<head>
{% import "header.html" as header %}
<title>{{channel.name}}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/static/css/common.css">
<script src="/static/js/common.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/spinner.js"></script>
<style>
#content_body
{
display: flex;
flex-direction: column;
min-width: 200px;
width: 100%;
max-width: 1440px;
margin-left: auto;
margin-right: auto;
}
.video_card
{
position: relative;
display: grid;
grid-template:
"thumbnail details toolbox" auto
"embed embed embed" auto
/auto 1fr auto;
grid-gap: 4px;
margin: 8px;
padding: 8px;
border-radius: 4px;
border: 1px solid #000;
}
.video_card:hover
{
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.25);
}
.video_card_pending
{
background-color: #ffffaa;
}
.video_card_ignored
{
background-color: #ffc886;
}
.video_card_selected
{
background-color: #13f4ff !important;
}
.video_card_downloaded
{
background-color: #aaffaa;
}
.video_thumbnail
{
grid-area: thumbnail;
justify-self: center;
}
.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;
}
.action_toolbox
{
grid-area: toolbox;
justify-self: right;
display: inline-flex;
flex-direction: row;
position: relative;
margin-top: auto;
margin-bottom: auto;
}
.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;
}
/*
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;
}
@media screen and (max-width: 600px)
{
.video_card
{
grid-template:
"thumbnail"
"details"
"toolbox"
"embed"
/1fr;
}
}
</style>
</head>
<body>
{{header.make_header()}}
<div id="content_body">
{% if channel is not none %}
<span><button class="refresh_button button_with_spinner" onclick="refresh_channel_form(false)">Refresh new videos</button></span>
<span><button class="refresh_button button_with_spinner" onclick="refresh_channel_form(true)">Refresh everything</button></span>
{% endif %}
<p><!-- spacer --></p>
<span>View
{% if channel is not none %}
<a class="merge_params" href="/channel/{{channel.id}}">All</a>
{% else %}
<a class="merge_params" href="/videos">All</a>
{% endif %}
{% for statename in all_states %}
{% if channel is not none %}
<a class="merge_params" href="/channel/{{channel.id}}/{{statename}}">{{statename.capitalize()}}</a>
{% else %}
<a class="merge_params" href="/videos/{{statename}}">{{statename.capitalize()}}</a>
{% endif %}
{% endfor %}
</span>
{% if channel is not none %}
<p><!-- spacer --></p>
<span>
New videos are:
<select onchange="set_automark_hook(event)">
<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>
</span>
{% endif %}
<p><!-- spacer --></p>
<span>Sort by
<a class="merge_params" href="?orderby=published">Date</a>
<a class="merge_params" href="?orderby=duration">Duration</a>
<a class="merge_params" href="?orderby=views">Views</a>
<a class="merge_params" href="?orderby=random">Random</a>
</span>
<center><input type="text" id="search_filter"/></center>
<center><span id="search_filter_count">{{videos|length}}</span> items</center>
<div id="video_cards">
{% for video in videos %}
<div id="video_card_{{video.id}}"
data-ytid="{{video.id}}"
onclick="onclick_select(event)"
class="video_card video_card_{{video.download}}"
>
<img class="video_thumbnail" src="http://i3.ytimg.com/vi/{{video.id}}/default.jpg" height="100px">
<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>
{% if channel is none %}
<a href="/channel/{{video.author_id}}">({{video.author.name if video.author else video.author_id}})</a>
{% endif %}
</div>
<div class="action_toolbox">
<button
{% if video.download == "pending" %}
class="video_action_pending hidden"
{% else %}
class="video_action_pending"
{% endif %}
onclick="action_button_passthrough(event, api.videos.mark_state, 'pending')"
>Revert to Pending</button>
<button
{% if video.download == "pending" %}
class="video_action_download"
{% else %}
class="video_action_download hidden"
{% endif %}
onclick="action_button_passthrough(event, api.videos.start_download)"
>Download</button>
<button
{% if video.download == "pending" %}
class="video_action_ignore"
{% else %}
class="video_action_ignore hidden"
{% endif %}
onclick="action_button_passthrough(event, api.videos.mark_state, 'ignored')"
>Ignore</button>
</div>
<div class="embed_toolbox">
<button class="show_embed_button" onclick="toggle_embed_video('{{video.id}}');">Embed</button>
<button class="hide_embed_button hidden" onclick="toggle_embed_video('{{video.id}}');">Unembed</button>
</div>
</div>
{% endfor %}
</div>
</div>
</body>
<script type="text/javascript">
{% if channel is not none %}
var CHANNEL_ID = "{{channel.id}}";
{% endif %}
var DOWNLOAD_FILTER = "{{download_filter if download_filter else ""}}";
var video_card_first_selected = null;
var video_card_selections = [];
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)
{
if (response.meta.status == 200)
{
common.refresh();
}
else
{
alert(JSON.stringify(response));
}
}
var search_filter_box = document.getElementById("search_filter");
var search_filter_hook = function(event)
{
filter_video_cards(search_filter_box.value);
}
search_filter_box.addEventListener("keyup", search_filter_hook);
function filter_video_cards(search_term)
{
/*
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.
*/
var count = 0;
video_cards = document.getElementById("video_cards");
video_cards.classList.add("hidden");
search_term = search_term.toLocaleLowerCase();
var cards = video_cards.children;
var download_filter_class = "video_card_" + DOWNLOAD_FILTER;
for (var index = 0; index < video_cards.children.length; index += 1)
{
var video_card = video_cards.children[index];
var title = video_card.getElementsByClassName("video_title")[0].innerText.toLocaleLowerCase();
if (DOWNLOAD_FILTER && !video_card.classList.contains(download_filter_class))
{
video_cards.removeChild(video_card);
index -= 1;
}
else if (search_term !== "" && title.indexOf(search_term) == -1)
{
video_card.classList.add("hidden");
}
else
{
video_card.classList.remove("hidden");
count += 1;
}
}
video_cards.classList.remove("hidden");
document.getElementById("search_filter_count").innerText = count;
}
function toggle_embed_video(video_id)
{
var video_card = document.getElementById("video_card_" + video_id);
var show_button = video_card.getElementsByClassName("show_embed_button")[0];
var hide_button = video_card.getElementsByClassName("hide_embed_button")[0];
var embed_toolbox = video_card.getElementsByClassName("embed_toolbox")[0];
var embeds = video_card.getElementsByClassName("video_iframe_holder");
if (embeds.length == 0)
{
var 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>`
var embed = common.html_to_element(html);
embed_toolbox.appendChild(embed);
show_button.classList.add("hidden");
hide_button.classList.remove("hidden");
}
else
{
embeds[0].parentElement.removeChild(embeds[0]);
show_button.classList.remove("hidden");
hide_button.classList.add("hidden");
}
}
function deselect_all()
{
var video_card_first_selected = null;
for (var index = 0; index < video_card_selections.length; index +=1)
{
video_card_selections[index].classList.remove("video_card_selected");
}
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;
}
var video_cards = Array.from(document.getElementById("video_cards").children);
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)
{
video_card_selections = [];
var start_index = video_cards.indexOf(video_card_first_selected);
var end_index = video_cards.indexOf(event.target);
if (end_index < start_index)
{
var temp = start_index;
start_index = end_index;
end_index = temp;
}
for (var index = start_index; index <= end_index; index += 1)
{
if (video_cards[index].classList.contains("hidden"))
{
continue;
}
video_card_selections.push(video_cards[index]);
}
}
else if (event.ctrlKey === true)
{
var existing_index = video_card_selections.indexOf(event.target)
if (existing_index == -1)
{
video_card_first_selected = event.target;
video_card_selections.push(event.target);
}
else
{
video_card_selections.splice(existing_index, 1);
}
}
for (var index = 0; index < video_cards.length; index += 1)
{
card = video_cards[index];
if (video_card_selections.indexOf(card) > -1)
{
card.classList.add("video_card_selected");
}
else
{
card.classList.remove("video_card_selected");
}
}
document.getSelection().removeAllRanges();
return false;
}
function action_button_passthrough(event, action_function, action_argument)
{
var elements;
var this_card = event.target.parentElement.parentElement;
if (video_card_selections.length > 0 && video_card_selections.indexOf(this_card) > -1)
{
elements = video_card_selections;
}
else
{
// Button -> button toolbox -> video card
elements = [this_card];
}
var video_ids = [];
for (var index = 0; index < elements.length; index += 1)
{
video_ids.push(elements[index].dataset["ytid"]);
}
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);
}
if (! event.shiftKey)
{
deselect_all();
}
}
function give_action_buttons(video_card_div)
{
var toolbox = video_card_div.getElementsByClassName("action_toolbox")[0]
var buttons = Array.from(toolbox.getElementsByTagName("button"));
var is_pending = video_card_div.classList.contains("video_card_pending");
buttons.forEach(function(button)
{
if (is_pending)
{ button.classList.remove("hidden"); }
else
{ button.classList.add("hidden"); }
});
var button_pending = video_card_div.getElementsByClassName("video_action_pending")[0];
if (is_pending)
{ button_pending.classList.add("hidden"); }
else
{ button_pending.classList.remove("hidden"); }
}
function receive_action_response(response)
{
var video_ids = response.data.video_ids;
for (var index = 0; index < video_ids.length; index += 1)
{
var video_id = video_ids[index];
var state = response.data.state;
var card = document.getElementById("video_card_" + video_id);
{% for statename in all_states %}
card.classList.remove("video_card_{{statename}}");
{% endfor %}
card.classList.add("video_card_" + state);
give_action_buttons(card);
}
}
var set_automark_spinner = document.getElementById("set_automark_spinner");
set_automark_spinner = new spinner.Spinner(set_automark_spinner);
function set_automark_hook(event)
{
set_automark_spinner.show();
api.channels.set_automark(CHANNEL_ID, event.target.value, set_automark_callback);
}
function set_automark_callback(response)
{
if (response.meta.status == 200)
{
set_automark_spinner.hide();
}
}
</script>
</html>