511 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			511 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="refresh_channel('{{channel.id}}', false, common.refresh)">Refresh new videos</button></span>
 | |
|     <span><button class="refresh_button" onclick="refresh_channel('{{channel.id}}', true, common.refresh)">Refresh everything</button></span>
 | |
| 
 | |
|     <span><a class="merge_params" href="/channel/{{channel.id}}">All</a></span>
 | |
|     {% else %}
 | |
|     <span><a class="merge_params" href="/videos">All</a></span>
 | |
|     {% endif %}
 | |
| 
 | |
|     {% for statename in all_states %}
 | |
|     {% if channel is not none %}
 | |
|     <span><a class="merge_params" href="/channel/{{channel.id}}/{{statename}}">{{statename}}</a></span>
 | |
|     {% else %}
 | |
|     <span><a class="merge_params" href="/videos/{{statename}}">{{statename}}</a></span>
 | |
|     {% endif %}
 | |
|     {% endfor %}
 | |
| 
 | |
|     <p><!-- spacer --></p>
 | |
| 
 | |
|     {% if channel is not none %}
 | |
|     <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, mark_video_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, 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, mark_video_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">
 | |
| 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 refresh_channel(channel_id, force, callback)
 | |
| {
 | |
|     var url = `/channel/${channel_id}/refresh`;
 | |
|     data = new FormData();
 | |
|     data.append("force", force)
 | |
|     return common.post(url, data, callback);
 | |
| }
 | |
| 
 | |
| function set_automark_hook(event)
 | |
| {
 | |
|     var url = "/channel/{{channel.id}}/set_automark";
 | |
|     data = new FormData();
 | |
|     data.append("state", event.target.value);
 | |
|     return common.post(url, data);
 | |
| }
 | |
| 
 | |
| function mark_video_state(video_ids, state, callback)
 | |
| {
 | |
|     var url = "/mark_video_state";
 | |
|     data = new FormData();
 | |
|     data.append("video_ids", video_ids);
 | |
|     data.append("state", state);
 | |
|     return common.post(url, data, callback);
 | |
| }
 | |
| 
 | |
| function start_download(video_ids, callback)
 | |
| {
 | |
|     var url = "/start_download";
 | |
|     data = new FormData();
 | |
|     data.append("video_ids", video_ids);
 | |
|     return common.post(url, data, callback);
 | |
| }
 | |
| </script>
 | |
| </html>
 |