Early progress on Vue.js implementation

This commit is contained in:
PrestonN 2018-06-21 16:01:08 -04:00
parent 05a69e95fc
commit 97ade6e392
14 changed files with 13317 additions and 83 deletions

View File

@ -11,17 +11,6 @@
<link rel="stylesheet" href="style/loading.css">
<link rel="stylesheet" href="style/fa-solid.min.css">
<link rel="stylesheet" href="style/fontawesome-all.min.css">
<script src="js/youtubeApi.js"></script>
<script src="js/settings.js"></script>
<script src="js/updates.js"></script>
<script src="js/layout.js"></script>
<script src="js/videos.js"></script>
<script src="js/player.js"></script>
<script src="js/subscriptions.js"></script>
<script src="js/channels.js"></script>
<script src="js/savedVideos.js"></script>
<script src="js/history.js"></script>
<script src="js/events.js"></script>
<title>Freetube Player</title>
</head>
@ -47,15 +36,14 @@
<input id='search' class="search" type="text" placeholder="Search / Go to URL">
<i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
</div>
<img src='icons/iconBlack.png' id='menuIcon'/>
&nbsp;
<img src='icons/textBlack.png' id='menuText'/>
<img src='icons/iconBlack.png' id='menuIcon' /> &nbsp;
<img src='icons/textBlack.png' id='menuText' />
</div>
<div id='sideNavDisabled'></div>
<div id="sideNav">
<div class="sideNavContainer">
<ul>
<li onclick='loadSubscriptions()'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li>
<li v-on:click='subscriptions'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li>
<li onclick='showMostPopular()'><i class="fas fa-users"></i>&nbsp;&nbsp;Most Popular</li>
<li onclick='showSavedVideos()'><i class="fas fa-star"></i>&nbsp;&nbsp;Saved</li>
<li onclick='showHistory()'><i class="fas fa-history"></i>&nbsp;&nbsp;History</li>
@ -63,7 +51,7 @@
<hr />
<ul>
<li onclick='showSettings()'><i class="fas fa-sliders-h"></i>&nbsp;&nbsp;Settings</li>
<li onclick='showAbout()'><i class="fas fa-info-circle"></i>&nbsp;&nbsp;About</li>
<li v-on:click='about' ><i class="fas fa-info-circle"></i>&nbsp;&nbsp;About</li>
</ul>
<hr />
<ul id='subscriptions'>
@ -71,7 +59,24 @@
</div>
</div>
<div id="main">
<div id='mainHeaderView'></div>
<div id='aboutView'></div>
<div id='subscriptionView'></div>
<div id='searchView'></div>
</div>
</body>
<script src="js/youtubeApi.js"></script>
<script src="js/settings.js"></script>
<script src="js/updates.js"></script>
<script src="js/db.js"></script>
<script src="js/layout.js"></script>
<script src="js/templates.js"></script>
<script src="js/videos.js"></script>
<script src="js/player.js"></script>
<script src="js/subscriptions.js"></script>
<script src="js/channels.js"></script>
<script src="js/savedVideos.js"></script>
<script src="js/history.js"></script>
<script src="js/events.js"></script>
</html>

View File

@ -54,7 +54,7 @@ function goToChannel(channelId) {
youtubeAPI('channels', {
part: 'snippet,brandingSettings,statistics',
id: channelId,
}, function (data){
}, (data) => {
const channelData = data.items[0];
const channelViewTemplate = require('./templates/channelView.html');

40
src/js/db.js Normal file
View File

@ -0,0 +1,40 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
const electron = require('electron');
const Datastore = require('nedb'); // database logic
const localDataStorage = electron.remote.app.getPath('userData'); // Grabs the userdata directory based on the user's OS
const subDb = new Datastore({
filename: localDataStorage + '/subscriptions.db',
autoload: true
});
const historyDb = new Datastore({
filename: localDataStorage + '/videohistory.db',
autoload: true
});
const savedVidsDb = new Datastore({
filename: localDataStorage + '/savedvideos.db',
autoload: true
});
const settingsDb = new Datastore({
filename: localDataStorage + '/settings.db',
autoload: true
});

View File

@ -221,3 +221,17 @@ $(document).on('dblclick', '.videoPlayer', fullscreenVideo);
$(document).on('keydown', videoShortcutHandler);
$(document).on('click', '#confirmNo', hideConfirmFunction);
// Open links externally by default
$(document).on('click', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});
// Open links externally on middle click.
$(document).on('auxclick', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});

View File

@ -23,7 +23,6 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
// Add general variables. Please put all require statements here.
const Datastore = require('nedb'); // database logic
window.$ = window.jQuery = require('jquery');
const mustache = require('mustache'); // templating
const dateFormat = require('dateformat'); // formatting dates
@ -32,7 +31,6 @@ const dateFormat = require('dateformat'); // formatting dates
// Used for finding links within text and making them clickable. Used mostly for video descriptions.
const autolinker = require('autolinker');
const electron = require('electron');
const protocol = electron.remote.protocol;
// Used for getting the user's subscriptions. Can probably remove this when that function
@ -41,7 +39,6 @@ const protocol = electron.remote.protocol;
//const youtubedl = require('youtube-dl');
const ytdl = require('ytdl-core');
const shell = electron.shell; // Used to open external links into the user's native browser.
const localDataStorage = electron.remote.app.getPath('userData'); // Grabs the userdata directory based on the user's OS
const clipboard = electron.clipboard;
const getOpml = require('opml-to-json'); // Gets the file type for imported files.
const fs = require('fs'); // Used to read files. Specifically in the settings page.
@ -58,52 +55,17 @@ require.extensions['.html'] = function(module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
};
const subDb = new Datastore({
filename: localDataStorage + '/subscriptions.db',
autoload: true
});
const historyDb = new Datastore({
filename: localDataStorage + '/videohistory.db',
autoload: true
});
const savedVidsDb = new Datastore({
filename: localDataStorage + '/savedvideos.db',
autoload: true
});
const settingsDb = new Datastore({
filename: localDataStorage + '/settings.db',
autoload: true
});
// Grabs the default settings from the settings database file. Makes defaults if
// none are found.
checkDefaultSettings();
require('electron').ipcRenderer.on('ping', function(event, message) {
electron.ipcRenderer.on('ping', function(event, message) {
console.log(message);
let url = message[1].replace('freetube://', '');
parseSearchText(url);
console.log(message);
});
// Open links externally by default
$(document).on('click', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});
// Open links externally on middle click.
$(document).on('auxclick', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});
$(document).ready(() => {
const searchBar = document.getElementById('search');
const jumpToInput = document.getElementById('jumpToInput');
@ -120,7 +82,7 @@ $(document).ready(() => {
// Display subscriptions upon the app opening up. May allow user to specify.
// Home page in the future.
loadSubscriptions();
//loadSubscriptions();
});
/**
@ -188,7 +150,6 @@ function stopLoadingAnimation() {
function createVideoListContainer(headerLabel = '') {
const videoListContainer = document.createElement("div");
videoListContainer.id = 'videoListContainer';
let headerSpacer;
if (headerLabel != '') {
const headerElement = document.createElement("h2");
headerElement.innerHTML = headerLabel;

View File

@ -78,11 +78,10 @@ function removeSubscription(channelId) {
* @return {Void}
*/
function loadSubscriptions() {
clearMainContainer();
showToast('Getting Subscriptions. Please wait...');
const loading = document.getElementById('loading');
startLoadingAnimation()
//startLoadingAnimation()
let videoList = [];
@ -118,7 +117,7 @@ function loadSubscriptions() {
});
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
//createVideoListContainer('Latest Subscriptions:');
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
@ -126,8 +125,9 @@ function loadSubscriptions() {
let grabDuration = getDuration(videoList.slice(0, 49));
grabDuration.then((list) => {
subscriptionView.videoList = [];
list.items.forEach((video) => {
displayVideo(video);
displayVideo(video, 'subscriptions');
});
stopLoadingAnimation();
});
@ -142,8 +142,10 @@ function loadSubscriptions() {
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
console.log(finishedList);
subscriptionView.videoList = [];
finishedList.forEach((video) => {
displayVideo(video);
displayVideo(video, 'subscriptions');
});
stopLoadingAnimation();
});

106
src/js/templates.js Normal file
View File

@ -0,0 +1,106 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
import Vue from './js/vue.js';
const mainHeaderTemplate = require('./templates/mainHeader.html');
const aboutTemplate = require('./templates/about.html');
const videoListTemplate = require('./templates/videoTemplate.html');
const nextPageTemplate = require('./templates/searchNextPage.html');
let sideNavBar = new Vue({
el: '#sideNav',
methods: {
about: (event) => {
hideViews();
aboutView.seen = true;
},
subscriptions: (event) => {
hideViews();
headerView.seen = true;
headerView.title = 'Latest Subscriptions';
subscriptionView.seen = true;
loadSubscriptions();
}
}
});
let headerView = new Vue({
el: '#mainHeaderView',
data: {
seen: true,
title: 'Latest Subscriptions'
},
template: mainHeaderTemplate
});
let subscriptionView = new Vue({
el: '#subscriptionView',
data: {
seen: true,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId)
}
},
template: videoListTemplate
});
let searchView = new Vue({
el: '#searchView',
data: {
seen: false,
isSearch: true,
nextPageToken: '',
videoList: []
},
methods: {
play: (videoId) => {
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId)
},
nextPage: (nextPageToken) => {
console.log(searchView.nextPageToken);
search(searchView.nextPageToken);
}
},
template: videoListTemplate
});
let aboutView = new Vue({
el: '#aboutView',
data: {
seen: false,
versionNumber: electron.remote.app.getVersion()
},
template: aboutTemplate
});
function hideViews(){
subscriptionView.seen = false;
aboutView.seen = false;
headerView.seen = false;
searchView.seen = false;
}

View File

@ -32,8 +32,13 @@ function search(nextPageToken = '') {
}
if (nextPageToken === '') {
clearMainContainer();
startLoadingAnimation();
//clearMainContainer();
//startLoadingAnimation();
hideViews();
headerView.seen = true;
headerView.title = 'Search Results';
searchView.videoList = [];
searchView.seen = true;
} else {
console.log(nextPageToken);
showToast('Fetching results. Please wait...');
@ -81,14 +86,18 @@ function search(nextPageToken = '') {
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
videoList.items.forEach((video) => {
displayVideo(video, 'search');
});
});
if (nextPageToken === '') {
createVideoListContainer('Search results:');
stopLoadingAnimation();
//createVideoListContainer('Search results:');
//stopLoadingAnimation();
}
addNextPage(data.nextPageToken);
searchView.nextPageToken = data.nextPageToken;
//addNextPage(data.nextPageToken);
})
}
@ -137,21 +146,21 @@ function getDuration(data) {
*
* @return {Void}
*/
function displayVideo(video, listType = '') {
const videoSnippet = video.snippet;
function displayVideo(videoData, listType = '') {
let video = {};
const videoDuration = parseVideoDuration(video.contentDetails.duration);
//const videoDuration = '00:00';
const videoSnippet = videoData.snippet;
video.duration = parseVideoDuration(videoData.contentDetails.duration);
// Grab the published date for the video and convert to a user readable state.
const dateString = new Date(videoSnippet.publishedAt);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
video.publishedDate = dateFormat(dateString, "mmm dS, yyyy");
const searchMenu = $('#videoListContainer').html();
const videoId = video.id;
// Include a remove icon in the list if the application is displaying the history list or saved videos.
const deleteHtml = () => {
video.deleteHtml = () => {
switch (listType) {
case 'saved':
return `<li onclick="removeSavedVideo('${videoId}'); showSavedVideos();">Remove Saved Video</li>`;
@ -160,9 +169,29 @@ function displayVideo(video, listType = '') {
}
};
video.id = videoData.id;
video.youtubeUrl = 'https://youtube.com/watch?v=' + video.id;
video.hooktubeUrl = 'https://hooktube.com/watch?v=' + video.id;
// Includes text if the video is live.
const liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
const videoListTemplate = require('./templates/videoList.html');
video.liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
video.thumbnail = videoSnippet.thumbnails.medium.url;
video.title = videoSnippet.title;
video.channelName = videoSnippet.channelTitle;
video.channelId = videoSnippet.channelId;
video.description = videoSnippet.description;
video.isVideo = true;
switch (listType) {
case 'subscriptions':
subscriptionView.videoList = subscriptionView.videoList.concat(video);
break;
case 'search':
searchView.videoList = searchView.videoList.concat(video);
break;
}
/*const videoListTemplate = require('./templates/videoList.html');
mustache.parse(videoListTemplate);
const rendered = mustache.render(videoListTemplate, {
@ -184,7 +213,7 @@ function displayVideo(video, listType = '') {
$('#videoListContainer').append(rendered);
} else {
$(rendered).insertBefore('#getNextPage');
}
}*/
}
function displayChannels(channels) {
@ -211,7 +240,22 @@ function displayChannels(channels) {
console.log(items);
items.forEach((item) => {
mustache.parse(videoListTemplate);
let channelData = {};
channelData.channelId = item.id;
channelData.thumbnail = item.snippet.thumbnails.medium.url;
channelData.channelName = item.snippet.title;
channelData.description = item.snippet.description;
channelData.subscriberCount = item.statistics.subscriberCount;
channelData.videoCount = item.statistics.videoCount;
channelData.isVideo = false;
console.log(searchView.videoList);
console.log(channelData);
searchView.videoList = searchView.videoList.concat(channelData);
/*mustache.parse(videoListTemplate);
let rendered = mustache.render(videoListTemplate, {
channelId: item.id,
channelThumbnail: item.snippet.thumbnails.medium.url,
@ -221,7 +265,7 @@ function displayChannels(channels) {
videoCount: item.statistics.videoCount,
});
$(rendered).insertBefore('#getNextPage');
$(rendered).insertBefore('#getNextPage');*/
});
});
}

10947
src/js/vue.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
<div class='center'>
<div class='center' v-if="seen">
<img src='icons/logoColor.png' width='500px;'/>
<h1>{{versionNumber}} Beta</h1>
<h1>{{ versionNumber }} Beta</h1>
<h3><i class='fas fa-envelope'></i>&nbsp;&nbsp;FreeTubeApp@protonmail.com</h3>
<h2>This software is FOSS and released under the <a href='https://www.gnu.org/licenses/quick-guide-gplv3.html'>GNU Public License v3+</a>.</h2>
<p>Found a bug? Want to suggest a feature? Want to help out? Check out our <a href='https://github.com/FreeTubeApp/FreeTube'>GitHub</a> page. Pull requests are welcome.</p>

View File

@ -0,0 +1,4 @@
<div v-if="seen">
<h2 style='margin-left: 15px;'>{{ title }}</h2>
<hr />
</div>

View File

@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
<div>
<div v-if='seen'>
<div v-for="video in videoList">
<div v-if='video.isVideo'>
<div class='video'>
<div class='videoOptions'>
<i class="fas fa-ellipsis-v" onclick='showVideoOptions(this)'></i>
<ul>
<li onclick='showVideoOptions(this.parentNode.previousSibling);'>Open (New Window)</li>
<li onclick='showVideoOptions(this.parentNode.previousSibling);'>Save Video</li>
<a :href='video.youtubeUrl' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<li>Open in YouTube</li>
</a>
<a :href='video.hooktubeUrl' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<li>Open in HookTube</li>
</a>
{{video.deleteHtml}}
</ul>
</div>
<div class='videoThumbnail'>
<img v-on:click='play(video.id)' :src='video.thumbnail' />
<p v-on:click='play(video.id)' class='videoDuration'>{{video.duration}}</p>
</div>
<p v-on:click='play(video.id)' class='videoTitle'>{{video.title}}</p>
<p v-on:click='channel(video.channelId)' class='channelName'>{{video.channelName}} - {{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>
<hr />
</div>
<!-- Channel View -->
<div v-else>
<div class='video'>
<div class='channelThumbnail'>
<img v-on:click='channel(video.channelId)' :src='video.thumbnail' />
</div>
<p v-on:click='channel(video.channelId)' class='videoTitle'>{{video.channelName}}</p>
<p v-on:click='channel(video.channelId)' class='channelName'>{{video.subscriberCount}} subscribers - {{video.videoCount}} videos</p>
<p v-on:click='channel(video.channelId)' class='videoDescription'>{{video.channelDescription}}</p>
</div>
<hr />
</div>
</div>
<div v-if='isSearch'>
<div v-on:click='nextPage' id='getNextPage'>
<i class="fas fa-search"></i> Fetch more results...
</div>
</div>
</div>
</div>