[Feature] Trending and more settings

This commit is contained in:
PrestonN 2018-09-21 10:49:23 -04:00
parent a5f23e7af4
commit 0ad8213f78
12 changed files with 425 additions and 157 deletions

View File

@ -44,6 +44,7 @@
<div class="sideNavContainer">
<ul>
<li v-on:click='subscriptions'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li>
<li v-on:click='trending'><i class="fas fa-fire"></i>&nbsp;&nbsp;Trending</li>
<li v-on:click='popular'><i class="fas fa-users"></i>&nbsp;&nbsp;Most Popular</li>
<li v-on:click='saved'><i class="fas fa-star"></i>&nbsp;&nbsp;Favorites</li>
<li v-on:click='history'><i class="fas fa-history"></i>&nbsp;&nbsp;History</li>
@ -73,6 +74,7 @@
<div id='searchView'></div>
<div id='subscriptionView'></div>
<div id='popularView'></div>
<div id='trendingView'></div>
<div id='savedView'></div>
<div id='historyView'></div>
<div id='aboutView'></div>

View File

@ -30,6 +30,9 @@
*/
function goToChannel(channelId) {
channelView.channelId = channelId;
channelView.page = 2;
headerView.title = 'Latest Uploads';
hideViews();
loadingView.seen = true;
@ -65,3 +68,17 @@ function goToChannel(channelId) {
});
});
}
function channelNextPage() {
showToast('Fetching results, please wait...');
console.log(channelView.page);
invidiousAPI('channels/videos', channelView.channelId, {'page': channelView.page}, (data) => {
console.log(data);
data.forEach((video) => {
displayVideo(video, 'channel');
});
});
channelView.page = channelView.page + 1;
}

View File

@ -79,51 +79,83 @@ let init = function () {
label: 'File',
submenu: [{
label: 'Open New Window',
click () { init() }
},
{role: 'quit'}
click() {
init()
}
},
{
role: 'quit'
}
]
},
{
label: 'Open New Window',
click () { init() }
},
{role: 'quit'}
]
},
{
label: 'Edit',
submenu: [
{role: 'cut'},
{role: 'copy', accelerator: "CmdOrCtrl+C", selector: "copy:" },
{role: 'paste', accelerator: "CmdOrCtrl+V", selector: "paste:" },
{role: 'pasteandmatchstyle'},
{role: 'delete'},
{role: 'selectall'}
]
},
{
label: 'View',
submenu: [
{role: 'reload'},
{role: 'forcereload'},
{role: 'toggledevtools'},
{type: 'separator'},
{role: 'resetzoom'},
{role: 'zoomin'},
{role: 'zoomout'},
{type: 'separator'},
{role: 'togglefullscreen'}
]
},
{
role: 'window',
submenu: [
{role: 'minimize'},
{role: 'close'}
]
}
];
{
label: 'Edit',
submenu: [{
role: 'cut'
},
{
role: 'copy',
accelerator: "CmdOrCtrl+C",
selector: "copy:"
},
{
role: 'paste',
accelerator: "CmdOrCtrl+V",
selector: "paste:"
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
},
{
label: 'View',
submenu: [{
role: 'reload'
},
{
role: 'forcereload'
},
{
role: 'toggledevtools'
},
{
type: 'separator'
},
{
role: 'resetzoom'
},
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
}
]
},
{
role: 'window',
submenu: [{
role: 'minimize'
},
{
role: 'close'
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

View File

@ -40,9 +40,15 @@ const getOpml = require('opml-to-json'); // Gets the file type for imported file
const fs = require('fs'); // Used to read files. Specifically in the settings page.
const tor = require('tor-request');
// User Defaults
let currentTheme = '';
let useTor = false;
let rememberHistory = true;
let autoplay = true;
let enableSubtitles = false;
let checkForUpdates = true;
let currentVolume = 1;
let dialog = electron.remote.dialog; // Used for opening file browser to export / import subscriptions.
let toastTimeout; // Timeout for toast notifications.
let mouseTimeout; // Timeout for hiding the mouse cursor on video playback

View File

@ -54,6 +54,8 @@ function playVideo(videoId) {
//"kpkXPy_jXmU"
invidiousAPI('videos', videoId, {}, function (data) {
console.log(data);
// Figure out the width for the like/dislike bar.
playerView.videoLikes = data.likeCount;
playerView.videoDislikes = data.dislikeCount;
@ -122,7 +124,7 @@ function playVideo(videoId) {
if (!useEmbedPlayer) {
data.captions.forEach((caption) => {
let subtitleUrl = 'https://www.youtube.com/api/timedtext?lang=' + caption.languageCode + '&fmt=vtt&name=&v=' + videoId;
let subtitleUrl = 'https://www.invidio.us/api/v1/captions/' + videoId + '?label=' + caption.label;
videoHtml = videoHtml + '<track kind="subtitles" src="' + subtitleUrl + '" srclang="' + caption.languageCode + '" label="' + caption.label + '">';
});
@ -173,7 +175,7 @@ function playVideo(videoId) {
data.id = video.id;
data.title = video.title;
data.channelName = video.author;
data.thumbnail = video.videoThumbnails[3].url;
data.thumbnail = video.videoThumbnails[4].url;
//data.publishedDate = dateFormat(snippet.publishedAt, "mmm dS, yyyy");
data.viewCount = video.viewCountText;
@ -201,6 +203,7 @@ function playVideo(videoId) {
}*/
window.setTimeout(checkVideoUrls, 5000, playerView.video480p, playerView.video720p);
});
}
@ -246,6 +249,17 @@ function openMiniPlayer() {
});
}
function checkVideoSettings() {
let player = document.getElementById('videoPlayer');
if (autoplay) {
console.log(player);
player.play();
}
player.volume = currentVolume;
}
/**
* Change the quality of the current video.
*
@ -360,3 +374,8 @@ function changeDurationByPercentage(percentage) {
const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.duration * percentage;
}
function updateVolume(){
let player = document.getElementById('videoPlayer');
currentVolume = player.volume
}

View File

@ -64,6 +64,24 @@ function updateSettingsView() {
} else {
settingsView.history = false;
}
if (autoplay) {
settingsView.autoplay = true;
} else {
settingsView.autoplay = false;
}
/*if (subtitles) {
settingsView.subtitles = true;
} else {
settingsView.subtitles = false;
}*/
if (checkForUpdates) {
settingsView.updates = true;
} else {
settingsView.updates = false;
}
});
}
@ -83,7 +101,9 @@ function checkDefaultSettings() {
'apiKey': settingsView.apiKey,
'useTor': false,
'history': true,
'quality': '720'
'autoplay': true,
'subtitles': false,
'updates': true,
};
console.log(settingDefaults);
@ -121,6 +141,24 @@ function checkDefaultSettings() {
case 'history':
rememberHistory = docs[0]['value'];
break;
case 'autoplay':
autoplay = docs[0]['value'];
break;
case 'subtitles':
enableSubtitles = docs[0]['value'];
break;
case 'updates':
checkForUpdates = docs[0]['value'];
if (checkForUpdates) {
updateChecker(options, function (error, update) { // callback function
if (error) throw error;
if (update) { // print some update info if an update is available
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);
}
});
}
break;
default:
break;
}
@ -138,11 +176,17 @@ function updateSettings() {
let themeSwitch = document.getElementById('themeSwitch').checked;
let torSwitch = document.getElementById('torSwitch').checked;
let historySwitch = document.getElementById('historySwitch').checked;
let autoplaySwitch = document.getElementById('autoplaySwitch').checked;
let subtitlesSwitch = document.getElementById('subtitlesSwitch').checked;
let updatesSwitch = document.getElementById('updatesSwitch').checked;
let key = document.getElementById('api-key').value;
let theme = 'light';
settingsView.useTor = torSwitch;
settingsView.history = historySwitch;
settingsView.autoplay = autoplaySwitch;
settingsView.subtitles = subtitlesSwitch;
settingsView.updates = updatesSwitch;
rememberHistory = historySwitch;
console.log(historySwitch);
@ -183,15 +227,48 @@ function updateSettings() {
useTor = torSwitch;
});
// Update tor usage.
// Update history
settingsDb.update({
_id: 'history'
}, {
value: torSwitch
value: historySwitch
}, {}, function(err, numReplaced) {
console.log(err);
console.log(numReplaced);
useTor = torSwitch;
rememberHistory = historySwitch;
});
// Update autoplay.
settingsDb.update({
_id: 'autoplay'
}, {
value: autoplaySwitch
}, {}, function(err, numReplaced) {
console.log(err);
console.log(numReplaced);
autoplay = autoplaySwitch;
});
// Update subtitles.
settingsDb.update({
_id: 'subtitles'
}, {
value: subtitlesSwitch
}, {}, function(err, numReplaced) {
console.log(err);
console.log(numReplaced);
enableSubtitles = subtitlesSwitch;
});
// Update checkForUpdates.
settingsDb.update({
_id: 'updates'
}, {
value: updatesSwitch
}, {}, function(err, numReplaced) {
console.log(err);
console.log(numReplaced);
checkForUpdates = updatesSwitch;
});
// To any third party devs that fork the project, please be ethical and change the API key.

View File

@ -82,6 +82,19 @@ let sideNavBar = new Vue({
popularView.seen = true;
showMostPopular();
},
trending: (event) => {
hideViews();
if (loadingView.seen !== false){
loadingView.seen = false;
}
if(trendingView.videoList.length === 0){
loadingView.seen = true;
}
headerView.seen = true;
headerView.title = 'Trending';
trendingView.seen = true;
showTrending();
},
saved: (event) => {
hideViews();
if (loadingView.seen !== false){
@ -189,6 +202,33 @@ let popularView = new Vue({
template: videoListTemplate
});
let trendingView = new Vue({
el: '#trendingView',
data: {
seen: false,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
},
template: videoListTemplate
});
let savedView = new Vue({
el: '#savedView',
data: {
@ -260,6 +300,9 @@ let settingsView = new Vue({
useTor: false,
apiKey: '',
history: true,
autoplay: true,
subtitles: false,
updates: true,
},
template: settingsTemplate
});
@ -288,7 +331,7 @@ let searchView = new Vue({
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
},
nextPage: (page) => {
nextPage: () => {
console.log(searchView.page);
search(searchView.page);
}
@ -320,7 +363,9 @@ let channelVideosView = new Vue({
el: '#channelVideosView',
data: {
seen: false,
isSearch: false,
channelId: '',
isSearch: true,
page: 2,
videoList: []
},
methods: {
@ -334,6 +379,9 @@ let channelVideosView = new Vue({
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
nextPage: () => {
channelNextPage();
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
@ -411,6 +459,18 @@ let playerView = new Vue({
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
loop: () => {
let player = document.getElementById('videoPlayer');
if (player.loop === false) {
player.loop = true;
showToast('Video loop has been turned on.');
}
else{
player.loop = false;
showToast('Video loop has been turned off.')
}
}
},
template: playerTemplate
@ -424,6 +484,7 @@ function hideViews(){
searchView.seen = false;
settingsView.seen = false;
popularView.seen = false;
trendingView.seen = false;
savedView.seen = false;
historyView.seen = false;
playerView.seen = false;

View File

@ -34,25 +34,3 @@ const updateChecker = require('github-version-checker');
const openReleasePage = function () {
shell.openExternal('https://github.com/FreeTubeApp/FreeTube/releases');
}
/*function checkForUpdates() {
updateChecker(options, function(error, update) { // callback function
if (error){
showToast('There was a problem with checking for updates');
freeTubeLog(error);
}
if (update) { // print some update info if an update is available
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);
}
else{
showToast('No update is currently available.');
}
});
}*/
updateChecker(options, function (error, update) { // callback function
if (error) throw error;
if (update) { // print some update info if an update is available
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);
}
});

View File

@ -15,6 +15,11 @@
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
let popularTimer;
let checkPopular = true;
let trendingTimer;
let checkTrending = true;
/**
* Perform a search using the YouTube API. The search query is grabbed from the #search element.
*
@ -116,85 +121,104 @@ function displayVideo(videoData, listType = '') {
} else {
video.watched = true;
}
});
let time = videoData.lengthSeconds;
let hours = 0;
video.views = videoData.viewCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
if (time >= 3600) {
hours = Math.floor(time / 3600);
time = time - hours * 3600;
}
let minutes = Math.floor(time / 60);
let seconds = time - minutes * 60;
if (seconds < 10) {
seconds = '0' + seconds;
}
if (hours > 0) {
video.duration = hours + ":" + minutes + ":" + seconds;
} else {
video.duration = minutes + ":" + seconds;
}
// Grab the published date for the video and convert to a user readable state.
//const dateString = new Date(videoSnippet.publishedAt);
//video.publishedDate = dateFormat(dateString, "mmm dS, yyyy");
video.publishedDate = videoData.publishedText;
//const searchMenu = $('#videoListContainer').html();
// Include a remove icon in the list if the application is displaying the history list or saved videos.
video.deleteHtml = () => {
switch (listType) {
case 'saved':
return `<li onclick="removeSavedVideo('${video.id}'); showSavedVideos();">Remove Saved Video</li>`;
case 'history':
return `<li onclick="removeFromHistory('${video.id}'); showHistory();">Remove From History</li>`;
if (videoData.liveNow === true){
video.liveText = (videoData.liveNow === true) ? 'LIVE NOW' : '';
video.duration = '';
video.publishedDate = '';
video.viewText = 'watching';
}
};
else{
video.liveText = '';
video.youtubeUrl = 'https://youtube.com/watch?v=' + video.id;
video.invidiousUrl = 'https://invidio.us/watch?v=' + video.id;
// Includes text if the video is live.
//video.liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
video.liveText = '';
video.thumbnail = videoData.videoThumbnails[3].url;
video.title = videoData.title;
video.channelName = videoData.author;
video.channelId = videoData.authorId;
video.views = videoData.viewCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");;
video.description = videoData.description;
video.isVideo = true;
if (video.views <= 1) {
video.viewText = 'view';
}
else{
video.viewText = 'views';
}
switch (listType) {
case 'subscriptions':
subscriptionView.videoList = subscriptionView.videoList.concat(video);
video.removeFromSave = true;
break;
case 'search':
searchView.videoList = searchView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'popular':
popularView.videoList = popularView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'saved':
savedView.videoList = savedView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'history':
historyView.videoList = historyView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'channel':
channelVideosView.videoList = channelVideosView.videoList.concat(video);
video.removeFromSave = false;
break;
}
let time = videoData.lengthSeconds;
let hours = 0;
if (time >= 3600) {
hours = Math.floor(time / 3600);
time = time - hours * 3600;
}
let minutes = Math.floor(time / 60);
let seconds = time - minutes * 60;
if (seconds < 10) {
seconds = '0' + seconds;
}
if (hours > 0) {
video.duration = hours + ":" + minutes + ":" + seconds;
} else {
video.duration = minutes + ":" + seconds;
}
// Grab the published date for the video and convert to a user readable state.
//const dateString = new Date(videoSnippet.publishedAt);
//video.publishedDate = dateFormat(dateString, "mmm dS, yyyy");
video.publishedDate = videoData.publishedText;
}
//const searchMenu = $('#videoListContainer').html();
// Include a remove icon in the list if the application is displaying the history list or saved videos.
video.deleteHtml = () => {
switch (listType) {
case 'saved':
return `<li onclick="removeSavedVideo('${video.id}'); showSavedVideos();">Remove Saved Video</li>`;
case 'history':
return `<li onclick="removeFromHistory('${video.id}'); showHistory();">Remove From History</li>`;
}
};
video.youtubeUrl = 'https://youtube.com/watch?v=' + video.id;
video.invidiousUrl = 'https://invidio.us/watch?v=' + video.id;
video.thumbnail = videoData.videoThumbnails[4].url;
video.title = videoData.title;
video.channelName = videoData.author;
video.channelId = videoData.authorId;
video.description = videoData.description;
video.isVideo = true;
switch (listType) {
case 'subscriptions':
subscriptionView.videoList = subscriptionView.videoList.concat(video);
video.removeFromSave = true;
break;
case 'search':
searchView.videoList = searchView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'popular':
popularView.videoList = popularView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'trending':
trendingView.videoList = trendingView.videoList.concat(video);
video.removeFromSave = false
break;
case 'saved':
savedView.videoList = savedView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'history':
historyView.videoList = historyView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'channel':
channelVideosView.videoList = channelVideosView.videoList.concat(video);
video.removeFromSave = false;
break;
}
});
}
function displayChannels(channels) {
@ -335,16 +359,58 @@ function parseSearchText(url = '') {
* @return {Void}
*/
function showMostPopular() {
invidiousAPI('top', '', {}, function (data) {
console.log(data);
popularView.videoList = [];
if (checkPopular === false && popularView.videoList.length > 0) {
console.log('Will not load popular. Timer still on.');
loadingView.seen = false;
return;
} else {
checkPopular = false;
}
data.forEach((video) => {
loadingView.seen = false;
console.log(video);
displayVideo(video, 'popular');
});
invidiousAPI('top', '', {}, function (data) {
console.log(data);
popularView.videoList = [];
data.forEach((video) => {
loadingView.seen = false;
console.log(video);
displayVideo(video, 'popular');
});
});
popularTimer = window.setTimeout(() => {
checkPopular = true;
}, 60000);
}
/**
* Grab trending videos over the last couple of days and display them.
*
* @return {Void}
*/
function showTrending() {
if (checkTrending === false && trendingView.videoList.length > 0) {
console.log('Will not load trending. Timer still on.');
loadingView.seen = false;
return;
} else {
checkTrending = false;
}
invidiousAPI('trending', '', {}, function (data) {
console.log(data);
popularView.videoList = [];
data.forEach((video) => {
loadingView.seen = false;
console.log(video);
displayVideo(video, 'trending');
});
});
trendingTimer = window.setTimeout(() => {
checkTrending = true;
}, 60000);
}
/**

View File

@ -1,6 +1,6 @@
<div v-if='seen'>
<div v-if='playerSeen'>
<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml" autoplay>
<video class="videoPlayer" id='videoPlayer' type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" onloadstart='checkVideoSettings()' onvolumechange='updateVolume()' controls="" :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml">
</video>
</div>
<div v-else>
@ -35,6 +35,9 @@
</ul>
</div>
</div>
<div class='smallButton' v-on:click='loop'>
<i id='loopIcon' :class='savedIconType' class="fas fa-sync-alt"></i> LOOP
</div>
<div class='smallButton' v-on:click='save(videoId)'>
<i id='saveIcon' :class='savedIconType' class="fa-star"></i> <span id='savedText'>{{savedText}}</span>
</div>

View File

@ -9,8 +9,15 @@
<label for="themeSwitch" class="switch-label">Use Dark Theme</label>
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" :checked='useTor'>
<label for="torSwitch" class="switch-label">Use Tor for API calls</label>
<input type="checkbox" id="updatesSwitch" name="set-name" class="switch-input" :checked='updates'>
<label for="updatesSwitch" class="switch-label">Check for Updates</label>
<br />
<input type="checkbox" id="historySwitch" name="set-name" class="switch-input" :checked='history'>
<label for="historySwitch" class="switch-label">Remember History</label>
<input type="checkbox" id="autoplaySwitch" name="set-name" class="switch-input" :checked='autoplay'>
<label for="autoplaySwitch" class="switch-label">Autoplay Videos</label>
<input type="checkbox" id="subtitlesSwitch" name="set-name" class="switch-input" :checked='subtitles'>
<label for="subtitlesSwitch" class="switch-label">Turn on Subtitles by Default</label>
</div>
<div class='center'>
<div onclick='importSubscriptions()' class='settingsButton'>

View File

@ -26,7 +26,7 @@
</div>
<p v-on:click='play(video.id)' class='videoTitle'>{{video.title}}</p>
<p v-on:click='channel(video.channelId)' class='channelName'>{{video.channelName}}</p>
<p v-on:click='play(video.id)' class='videoViews'>{{video.views}} views - {{video.publishedDate}}</p>
<p v-on:click='play(video.id)' class='videoViews'>{{video.views}} {{video.viewText}} - {{video.publishedDate}}</p>
<p v-on:click='play(video.id)' class='videoDescription'>{{video.description}}</p>
<p v-on:click='play(video.id)' class='live'>{{video.liveText}}</p>
</div>