diff --git a/tubearchivist/home/templates/home/channel_id.html b/tubearchivist/home/templates/home/channel_id.html index 1cf5fb2..3f0522c 100644 --- a/tubearchivist/home/templates/home/channel_id.html +++ b/tubearchivist/home/templates/home/channel_id.html @@ -106,7 +106,7 @@ {% if results %} {% for video in results %}
- +
video-thumb diff --git a/tubearchivist/home/templates/home/home.html b/tubearchivist/home/templates/home/home.html index 19c830d..6433178 100644 --- a/tubearchivist/home/templates/home/home.html +++ b/tubearchivist/home/templates/home/home.html @@ -45,7 +45,7 @@ {% if results %} {% for video in results %}
- +
video-thumb diff --git a/tubearchivist/home/templates/home/playlist_id.html b/tubearchivist/home/templates/home/playlist_id.html index af453a8..a5cfb0b 100644 --- a/tubearchivist/home/templates/home/playlist_id.html +++ b/tubearchivist/home/templates/home/playlist_id.html @@ -87,7 +87,7 @@ {% if results %} {% for video in results %}
- +
video-thumb diff --git a/tubearchivist/home/templates/home/video.html b/tubearchivist/home/templates/home/video.html index a220fa8..1b2e03d 100644 --- a/tubearchivist/home/templates/home/video.html +++ b/tubearchivist/home/templates/home/video.html @@ -6,7 +6,7 @@
diff --git a/tubearchivist/static/.jshintrc b/tubearchivist/static/.jshintrc new file mode 100644 index 0000000..8ab3485 --- /dev/null +++ b/tubearchivist/static/.jshintrc @@ -0,0 +1,3 @@ +{ + "esversion": 6 +} \ No newline at end of file diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css index 40b845e..53a8882 100644 --- a/tubearchivist/static/css/style.css +++ b/tubearchivist/static/css/style.css @@ -456,6 +456,17 @@ button:hover { text-transform: uppercase; } +.player-stats { + float: right; + display: flex; + align-items: center; + margin-top: 10px; +} + +.player-stats span { + margin: 0 5px; +} + .video-desc-player { margin-bottom: 8px; display: flex; @@ -639,7 +650,7 @@ button:hover { filter: var(--img-filter); } -.thumb-icon.dislike img { +.dislike { transform: rotate(180deg); } diff --git a/tubearchivist/static/img/icon-eye.svg b/tubearchivist/static/img/icon-eye.svg new file mode 100644 index 0000000..cc2d959 --- /dev/null +++ b/tubearchivist/static/img/icon-eye.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/tubearchivist/static/img/icon-thumb.svg b/tubearchivist/static/img/icon-thumb.svg index a3e21f3..55a85ff 100644 --- a/tubearchivist/static/img/icon-thumb.svg +++ b/tubearchivist/static/img/icon-thumb.svg @@ -15,7 +15,7 @@ version="1.1" id="svg1303" inkscape:version="0.92.4 (5da689c313, 2019-01-14)" - sodipodi:docname="Icons_thumbs.svg"> + sodipodi:docname="icon_thumb.svg"> + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true"> + + + @@ -55,8 +68,8 @@ id="layer1" transform="translate(0,-164.70764)"> diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index a7c6b17..854e601 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -9,6 +9,7 @@ function sortChange(sortValue) { } function isWatched(youtube_id) { + // sendVideoProgress(youtube_id, 0); // Reset video progress on watched; var payload = JSON.stringify({'watched': youtube_id}); sendPost(payload); var seenIcon = document.createElement('img'); @@ -33,6 +34,7 @@ function isWatchedButton(button) { } function isUnwatched(youtube_id) { + // sendVideoProgress(youtube_id, 0); // Reset video progress on unwatched; var payload = JSON.stringify({'un_watched': youtube_id}); sendPost(payload); var unseenIcon = document.createElement('img'); @@ -188,7 +190,7 @@ function reEmbed() { function dbBackup() { var payload = JSON.stringify({'db-backup': true}); - sendPost(payload) + sendPost(payload); // clear button var message = document.createElement('p'); message.innerText = 'backing up archive'; @@ -286,161 +288,266 @@ function cancelDelete() { // player function createPlayer(button) { - var mediaUrl = button.getAttribute('data-src'); - var mediaThumb = button.getAttribute('data-thumb'); - var mediaTitle = button.getAttribute('data-title'); - var mediaChannel = button.getAttribute('data-channel'); - var mediaChannelId = button.getAttribute('data-channel-id'); - var dataId = button.getAttribute('data-id'); - // get watched status - var playedStatus = document.createDocumentFragment(); - playedStatus.appendChild(document.getElementById(dataId)); - // create player - removePlayer(); - var playerElement = document.createElement('div'); - playerElement.classList.add("video-player"); - // var playerElement = document.getElementById('player'); - playerElement.setAttribute('data-id', dataId); - // playerElement.innerHTML = ''; - var videoPlayer = document.createElement('video'); - videoPlayer.setAttribute('src', mediaUrl); - videoPlayer.setAttribute('controls', true); - videoPlayer.setAttribute('autoplay', true); - videoPlayer.setAttribute('width', '100%'); - videoPlayer.setAttribute('playsinline', true); - videoPlayer.setAttribute('poster', mediaThumb); - videoPlayer.setAttribute('id', 'video-item'); // Set ID to get URL for casting - playerElement.appendChild(videoPlayer); - // title bar - var titleBar = document.createElement('div'); - titleBar.classList.add('player-title', 'boxed-content'); - // close - var closeButton = document.createElement('img'); - closeButton.className = 'close-button'; - closeButton.setAttribute('src', "/static/img/icon-close.svg"); - closeButton.setAttribute('alt', 'close-icon'); - closeButton.setAttribute('data', dataId); - closeButton.setAttribute('onclick', "removePlayer()"); - closeButton.setAttribute('title', 'Close player'); - titleBar.appendChild(closeButton); - // played - titleBar.appendChild(playedStatus); - // channel title - var channelTitleLink = document.createElement('a'); - channelTitleLink.setAttribute('href', '/channel/' + mediaChannelId + '/'); - var channelTitle = document.createElement('h3'); - channelTitle.innerText = mediaChannel; - channelTitleLink.appendChild(channelTitle); - titleBar.appendChild(channelTitleLink); - // video title - var videoTitleLink = document.createElement('a'); - videoTitleLink.setAttribute('href', '/video/' + dataId + '/'); - var videoTitle = document.createElement('h2'); - videoTitle.setAttribute('id', "video-title"); // Set ID to get title for casting - videoTitle.innerText = mediaTitle; - var castScript = document.getElementById('cast-script'); // Get cast-script - if(typeof(castScript) != 'undefined' && castScript != null) { // Check if cast integration is enabled - var castButton = document.createElement("google-cast-launcher"); // Create cast button - castButton.setAttribute('id', "castbutton"); // Set ID to apply theme - titleBar.appendChild(castButton); // Add cast button to title + var videoId = button.getAttribute('data-id'); + var videoData = getVideoData(videoId); + var videoUrl = videoData.media_url; + var videoThumbUrl = videoData.vid_thumb_url; + var videoName = videoData.title; + + var playlist = ''; + var videoPlaylists = videoData.playlist; // Array of playlists the video is in + if (typeof(videoPlaylists) != 'undefined') { + var subbedPlaylists = getSubbedPlaylists(videoPlaylists); // Array of playlist the video is in that are subscribed + if (subbedPlaylists.length != 0) { + var playlistData = getPlaylistData(subbedPlaylists[0]); // Playlist data for first subscribed playlist + var playlistId = playlistData.playlist_id; + var playlistName = playlistData.playlist_name; + var playlist = `
${playlistName}
`; + } } - videoTitleLink.appendChild(videoTitle); - titleBar.appendChild(videoTitleLink); - // add titlebar - playerElement.appendChild(titleBar); - // add whole - document.getElementById("player").appendChild(playerElement); + + var videoProgress = videoData.player.progress; // Groundwork for saving video position, change once progress variable is added to API + var videoViews = formatNumbers(videoData.stats.view_count); + + var channelId = videoData.channel.channel_id; + var channelName = videoData.channel.channel_name; + + removePlayer(); + document.getElementById(videoId).outerHTML = ''; // Remove watch indicator from video info + + // If cast integration is enabled create cast button + var castButton = ``; + var castScript = document.getElementById('cast-script'); + if (typeof(castScript) != 'undefined' && castScript != null) { + var castButton = ``; + } + + // Watched indicator + if (videoData.player.is_watched) { + var playerState = "seen"; + var watchedFunction = "Unwatched"; + } else { + var playerState = "unseen"; + var watchedFunction = "Watched"; + } + + var playerStats = `
views icon${videoViews}`; + if (videoData.stats.like_count) { + var likes = formatNumbers(videoData.stats.like_count); + playerStats += `|thumbs-up${likes}`; + } + if (videoData.stats.dislike_count) { + var dislikes = formatNumbers(videoData.stats.dislike_count); + playerStats += `|thumbs-down${dislikes}`; + } + playerStats += "
"; + + const markup = ` +
+ +
+ close-icon + ${playerState}-icon + ${castButton} + ${playerStats} +
+

${channelName}

+ ${playlist} +
+

${videoName}

+
+
+ `; + const divPlayer = document.getElementById("player"); + divPlayer.innerHTML = markup; +} + +// Set video progress in seconds +function setVideoProgress(videoProgress) { + if (isNaN(videoProgress)) { + videoProgress = 0; + } + var videoElement = document.getElementById("video-item"); + videoElement.currentTime = videoProgress; +} + +// Runs on video playback, marks video as watched if video gets to 90% or higher, WIP sends position to api +function onVideoProgress(videoId) { + var videoElement = document.getElementById("video-item"); + if (videoElement != null) { + if ((videoElement.currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second + // sendVideoProgress(videoId, videoElement.currentTime); // Groundwork for saving video position + if ((videoElement.currentTime / videoElement.duration) >= 0.90) { + isWatched(videoId); + } + } + } +} + +// Groundwork for saving video position +function sendVideoProgress(videoId, videoProgress) { + var apiEndpoint = "/api/video/"; + if (isNaN(videoProgress)) { + videoProgress = 0; + } + var data = { + "data": [{ + "youtube_id": videoId, + "player": { + "progress": videoProgress + } + }] + }; + videoData = apiRequest(apiEndpoint, "POST", data); +} + +// Format numbers for frontend +function formatNumbers(number) { + var numberUnformatted = parseFloat(number); + if (numberUnformatted > 999999999) { + var numberFormatted = (numberUnformatted / 1000000000).toFixed(1).toString() + "B"; + } else if (numberUnformatted > 999999) { + var numberFormatted = (numberUnformatted / 1000000).toFixed(1).toString() + "M"; + } else if (numberUnformatted > 999) { + var numberFormatted = (numberUnformatted / 1000).toFixed(1).toString() + "K"; + } else { + var numberFormatted = numberUnformatted; + } + return numberFormatted; +} + +// Gets video data in JSON format when passed video ID +function getVideoData(videoId) { + var apiEndpoint = "/api/video/" + videoId + "/"; + videoData = apiRequest(apiEndpoint, "GET"); + return videoData.data; +} + +// Gets channel data in JSON format when passed channel ID +function getChannelData(channelId) { + var apiEndpoint = "/api/channel/" + channelId + "/"; + channelData = apiRequest(apiEndpoint, "GET"); + return channelData.data; +} + +// Gets playlist data in JSON format when passed playlist ID +function getPlaylistData(playlistId) { + var apiEndpoint = "/api/playlist/" + playlistId + "/"; + playlistData = apiRequest(apiEndpoint, "GET"); + return playlistData.data; +} + +// Given an array of playlist ids it returns an array of subbed playlist ids from that list +function getSubbedPlaylists(videoPlaylists) { + var subbedPlaylists = []; + for (var i = 0; i < videoPlaylists.length; i++) { + if(getPlaylistData(videoPlaylists[i]).playlist_subscribed) { + subbedPlaylists.push(videoPlaylists[i]); + } + } + return subbedPlaylists; +} + +// Makes api requests when passed an endpoint and method ("GET" or "POST") +function apiRequest(apiEndpoint, method, data) { + const xhttp = new XMLHttpRequest(); + var sessionToken = getCookie("sessionid"); + xhttp.open(method, apiEndpoint, false); + xhttp.setRequestHeader("Authorization", "Token " + sessionToken); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.send(JSON.stringify(data)); + return JSON.parse(xhttp.responseText); } function removePlayer() { var playerElement = document.getElementById('player'); if (playerElement.hasChildNodes()) { - var youtubeId = playerElement.childNodes[0].getAttribute("data-id"); + var youtubeId = playerElement.childNodes[1].getAttribute("data-id"); var playedStatus = document.createDocumentFragment(); var playedBox = document.getElementById(youtubeId); if (playedBox) { playedStatus.appendChild(playedBox); - }; + } playerElement.innerHTML = ''; // append played status var videoInfo = document.getElementById('video-info-' + youtubeId); videoInfo.insertBefore(playedStatus, videoInfo.firstChild); - }; + } } // multi search form function searchMulti(query) { if (query.length > 1) { - var payload = JSON.stringify({'multi_search': query}) + var payload = JSON.stringify({'multi_search': query}); var http = new XMLHttpRequest(); http.onreadystatechange = function() { if (http.readyState === 4) { - allResults = JSON.parse(http.response)['results']; + allResults = JSON.parse(http.response).results; populateMultiSearchResults(allResults); - }; + } }; http.open("POST", "/process/", true); http.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); http.setRequestHeader("Content-type", "application/json"); http.send(payload); - }; + } } function getViewDefaults(view) { var defaultView = document.getElementById("id_" + view).value; - return defaultView + return defaultView; } function populateMultiSearchResults(allResults) { // videos var defaultVideo = getViewDefaults("home"); - var allVideos = allResults["video_results"]; + var allVideos = allResults.video_results; var videoBox = document.getElementById("video-results"); videoBox.innerHTML = ""; for (let index = 0; index < allVideos.length; index++) { - const video = allVideos[index]["source"]; + const video = allVideos[index].source; const videoDiv = createVideo(video, defaultVideo); videoBox.appendChild(videoDiv); - }; + } // channels var defaultChannel = getViewDefaults("channel"); - var allChannels = allResults["channel_results"]; + var allChannels = allResults.channel_results; var channelBox = document.getElementById("channel-results"); channelBox.innerHTML = ""; for (let index = 0; index < allChannels.length; index++) { - const channel = allChannels[index]["source"]; + const channel = allChannels[index].source; const channelDiv = createChannel(channel, defaultChannel); channelBox.appendChild(channelDiv); - }; + } // playlists var defaultPlaylist = getViewDefaults("playlist"); - var allPlaylists = allResults["playlist_results"]; + var allPlaylists = allResults.playlist_results; var playlistBox = document.getElementById("playlist-results"); playlistBox.innerHTML = ""; for (let index = 0; index < allPlaylists.length; index++) { - const playlist = allPlaylists[index]["source"]; + const playlist = allPlaylists[index].source; const playlistDiv = createPlaylist(playlist, defaultPlaylist); playlistBox.appendChild(playlistDiv); - }; + } } function createVideo(video, viewStyle) { // create video item div from template - const videoId = video["youtube_id"]; - const mediaUrl = video["media_url"]; - const thumbUrl = "/cache/" + video["vid_thumb_url"]; - const videoTitle = video["title"]; - const videoPublished = video["published"]; - const videoDuration = video["player"]["duration_str"]; - if (video["player"]["watched"]) { + const videoId = video.youtube_id; + const mediaUrl = video.media_url; + const thumbUrl = "/cache/" + video.vid_thumb_url; + const videoTitle = video.title; + const videoPublished = video.published; + const videoDuration = video.player.duration_str; + if (video.player.watched) { var playerState = "seen"; } else { var playerState = "unseen"; }; - const channelId = video["channel"]["channel_id"]; - const channelName = video["channel"]["channel_name"]; + const channelId = video.channel.channel_id; + const channelName = video.channel.channel_name; // build markup const markup = ` @@ -463,25 +570,25 @@ function createVideo(video, viewStyle) {

${videoTitle}

- ` + `; const videoDiv = document.createElement("div"); videoDiv.setAttribute("class", "video-item " + viewStyle); - videoDiv.innerHTML = markup - return videoDiv + videoDiv.innerHTML = markup; + return videoDiv; } function createChannel(channel, viewStyle) { // create channel item div from template - const channelId = channel["channel_id"]; - const channelName = channel["channel_name"]; - const channelSubs = channel["channel_subs"]; - const channelLastRefresh = channel["channel_last_refresh"]; - if (channel["channel_subscribed"]) { - var button = `` + const channelId = channel.channel_id; + const channelName = channel.channel_name; + const channelSubs = channel.channel_subs; + const channelLastRefresh = channel.channel_last_refresh; + if (channel.channel_subscribed) { + var button = ``; } else { - var button = `` - }; + var button = ``; + } // build markup const markup = `
@@ -508,25 +615,25 @@ function createChannel(channel, viewStyle) {
- ` + `; const channelDiv = document.createElement("div"); channelDiv.setAttribute("class", "channel-item " + viewStyle); channelDiv.innerHTML = markup; - return channelDiv + return channelDiv; } function createPlaylist(playlist, viewStyle) { // create playlist item div from template - const playlistId = playlist["playlist_id"]; - const playlistName = playlist["playlist_name"]; - const playlistChannelId = playlist["playlist_channel_id"]; - const playlistChannel = playlist["playlist_channel"]; - const playlistLastRefresh = playlist["playlist_last_refresh"]; - if (playlist["playlist_subscribed"]) { - var button = `` + const playlistId = playlist.playlist_id; + const playlistName = playlist.playlist_name; + const playlistChannelId = playlist.playlist_channel_id; + const playlistChannel = playlist.playlist_channel; + const playlistLastRefresh = playlist.playlist_last_refresh; + if (playlist.playlist_subscribed) { + var button = ``; } else { - var button = `` - }; + var button = ``; + } const markup = ` - ` + `; const playlistDiv = document.createElement("div"); playlistDiv.setAttribute("class", "playlist-item " + viewStyle); playlistDiv.innerHTML = markup; - return playlistDiv + return playlistDiv; } @@ -565,8 +672,8 @@ function getCookie(c_name) { c_end = document.cookie.indexOf(";", c_start); if (c_end == -1) c_end = document.cookie.length; return unescape(document.cookie.substring(c_start,c_end)); - }; - }; + } + } return ""; } @@ -582,17 +689,17 @@ function textReveal() { } else { textBox.style.height = 'unset'; button.innerText = 'Hide'; - }; + } } function showForm() { var formElement = document.getElementById('hidden-form'); - var displayStyle = formElement.style.display + var displayStyle = formElement.style.display; if (displayStyle === "") { formElement.style.display = 'block'; } else { formElement.style.display = ""; - }; + } animate('animate-icon', 'pulse-img'); } @@ -602,5 +709,5 @@ function animate(elementId, animationClass) { toAnimate.className = animationClass; } else { toAnimate.classList.remove(animationClass); - }; + } }