ycdl/frontends/ycdl_flask/templates/channel.html
Ethan Dalool ff1b043279 Arrange the state filters horizontally instead of vertically.
The "New videos are" dropdown and the sorting options are both
horizontal, so this one being vertical stuck out badly visually.
This is a little better.
2020-06-10 23:09:17 -07:00

492 lines
14 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/api.js"></script>
<script src="/static/js/common.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" onclick="api.channels.refresh_channel(CHANNEL_ID, false, common.refresh)">Refresh new videos</button></span>
<span><button class="refresh_button" onclick="api.channels.refresh_channel(CHANNEL_ID, true, common.refresh)">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}}</a>
{% else %}
<a class="merge_params" href="/videos/{{statename}}">{{statename}}</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>
{% 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 = [];
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);
}
}
function set_automark_hook(event)
{
api.channels.set_automark(CHANNEL_ID, event.target.value);
}
</script>
</html>