From 320c305949770e481ad6663fd461d4513ff5626a Mon Sep 17 00:00:00 2001 From: Preston Date: Mon, 13 Apr 2020 22:59:25 -0400 Subject: [PATCH] Add Initial PWA Functionality --- _scripts/webpack.web.config.js | 14 +- src/index.ejs | 23 +++ src/renderer/components/ft-input/ft-input.vue | 2 +- src/renderer/components/top-nav/top-nav.js | 13 +- src/renderer/router/index.js | 4 - src/renderer/views/Watch/Watch.js | 21 ++- static/manifest.json | 19 ++ static/pwabuilder-sw.js | 162 ++++++++++++++++++ 8 files changed, 238 insertions(+), 20 deletions(-) create mode 100644 static/manifest.json create mode 100644 static/pwabuilder-sw.js diff --git a/_scripts/webpack.web.config.js b/_scripts/webpack.web.config.js index b7d10ca0..d66f4f7a 100644 --- a/_scripts/webpack.web.config.js +++ b/_scripts/webpack.web.config.js @@ -146,13 +146,17 @@ if (isDevMode) { config.plugins.push( new CopyWebpackPlugin([ { - from: path.join(__dirname, '../static'), - to: path.join(__dirname, '../dist/web/static'), - ignore: ['.*'], + from: path.join(__dirname, '../static/pwabuilder-sw.js'), + to: path.join(__dirname, '../dist/web/pwabuilder-sw.js'), }, { - from: path.join(__dirname, '../__icons'), - to: path.join(__dirname, '../dist/web/icons'), + from: path.join(__dirname, '../static'), + to: path.join(__dirname, '../dist/web/static'), + ignore: ['.*', 'pwabuilder-sw.js'], + }, + { + from: path.join(__dirname, '../_icons'), + to: path.join(__dirname, '../dist/web/_icons'), ignore: ['.*'], }, ]), diff --git a/src/index.ejs b/src/index.ejs index 3b9c321a..ac6ae92d 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -5,6 +5,7 @@ + <% if (htmlWebpackPlugin.options.nodeModules) { %> + + diff --git a/src/renderer/components/ft-input/ft-input.vue b/src/renderer/components/ft-input/ft-input.vue index 35923551..2d3902a6 100644 --- a/src/renderer/components/ft-input/ft-input.vue +++ b/src/renderer/components/ft-input/ft-input.vue @@ -5,10 +5,10 @@ > { return { component: this, + windowWidth: 0, showFilters: false } }, @@ -30,6 +31,13 @@ export default Vue.extend({ } }, mounted: function () { + const appWidth = $(window).width() + + if (appWidth <= 680) { + const searchContainer = $('.searchContainer').get(0) + searchContainer.style.display = 'none' + } + window.addEventListener('resize', function(event) { const width = event.srcElement.innerWidth const searchContainer = $('.searchContainer').get(0) @@ -43,12 +51,11 @@ export default Vue.extend({ }, methods: { goToSearch: function (query) { - console.log(this) - this.showFilters = false const appWidth = $(window).width() if (appWidth <= 680) { const searchContainer = $('.searchContainer').get(0) + searchContainer.blur() searchContainer.style.display = 'none' } @@ -63,6 +70,8 @@ export default Vue.extend({ } } ) + + this.showFilters = false }, toggleSearchContainer: function () { diff --git a/src/renderer/router/index.js b/src/renderer/router/index.js index 4b1f4591..37423b3b 100644 --- a/src/renderer/router/index.js +++ b/src/renderer/router/index.js @@ -111,10 +111,6 @@ const router = new Router({ icon: 'fa-user' }, component: Watch - }, - { - path: '*', - redirect: '/subscriptions' } ], scrollBehavior (to, from, savedPosition) { diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js index 3f32e74e..c9d0ae34 100644 --- a/src/renderer/views/Watch/Watch.js +++ b/src/renderer/views/Watch/Watch.js @@ -30,7 +30,6 @@ export default Vue.extend({ showDashPlayer: true, showLegacyPlayer: false, showYouTubeNoCookieEmbed: false, - proxyVideos: false, hidePlayer: false, activeFormat: 'legacy', videoId: '', @@ -65,6 +64,10 @@ export default Vue.extend({ return this.$store.getters.getInvidiousInstance }, + proxyVideos: function () { + return this.$store.getters.getProxyVideos + }, + defaultVideoFormat: function () { return this.$store.getters.getDefaultVideoFormat }, @@ -82,9 +85,15 @@ export default Vue.extend({ }, dashSrc: function () { + let url = `${this.invidiousInstance}/api/manifest/dash/${this.videoId}.mpd` + + if (this.proxyVideos) { + url = url + '?local=true' + } + return [ { - url: `${this.invidiousInstance}/api/manifest/dash/${this.videoId}.mpd`, + url: url, type: 'application/dash+xml', label: 'Dash', }, @@ -118,10 +127,6 @@ export default Vue.extend({ this.activeFormat = this.defaultVideoFormat - if (this.proxyVideos) { - this.dashSrc = this.dashSrc + '?local=true' - } - switch (this.backendPreference) { case 'local': this.getVideoInformationLocal() @@ -239,9 +244,9 @@ export default Vue.extend({ }) if (this.forceLocalBackendForLegacy) { - this.videoSourceList = result.formatStreams.reverse() - } else { this.getLegacyFormats() + } else { + this.videoSourceList = result.formatStreams.reverse() } this.isLoading = false diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 00000000..7a44f8e8 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,19 @@ +{ + "dir" : "ltr", + "lang" : "de", + "name" : "FreeTube", + "scope" : "/", + "display" : "standalone", + "start_url" : "https://app.freetubeapp.io/", + "short_name" : "FreeTube", + "theme_color" : "transparent", + "description" : "A description", + "orientation" : "any", + "background_color" : "transparent", + "related_applications" : [], + "prefer_related_applications" : false, + "icons" : ["/_icons/logoColor.png"], + "url" : "https://app.freetubeapp.io", + "screenshots" : [], + "generated" : "true" +} diff --git a/static/pwabuilder-sw.js b/static/pwabuilder-sw.js new file mode 100644 index 00000000..dc1e5931 --- /dev/null +++ b/static/pwabuilder-sw.js @@ -0,0 +1,162 @@ +// This is the service worker with the Advanced caching + +const CACHE = 'pwabuilder-adv-cache' +const precacheFiles = [ + /* Add an array of files to precache for your app */ + 'index.html', + 'web.js', + 'web.css', + 'static/*', + '_icons/*', + 'fonts/*' +] + +// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html"; +const offlineFallbackPage = 'index.html' + +const networkFirstPaths = [ + /* Add an array of regex of paths that should go network first */ + // Example: /\/api\/.*/ +] + +const avoidCachingPaths = [ + /* Add an array of regex of paths that shouldn't be cached */ + // Example: /\/api\/.*/ +] + +function pathComparer(requestUrl, pathRegEx) { + return requestUrl.match(new RegExp(pathRegEx)) +} + +function comparePaths(requestUrl, pathsArray) { + if (requestUrl) { + for (let index = 0; index < pathsArray.length; index++) { + const pathRegEx = pathsArray[index] + if (pathComparer(requestUrl, pathRegEx)) { + return true + } + } + } + + return false +} + +self.addEventListener('install', function (event) { + console.log('[PWA Builder] Install Event processing') + + console.log('[PWA Builder] Skip waiting on install') + self.skipWaiting() + + event.waitUntil( + caches.open(CACHE).then(function (cache) { + console.log('[PWA Builder] Caching pages during install') + + return cache.addAll(precacheFiles).then(function () { + if (offlineFallbackPage === 'ToDo-replace-this-name.html') { + return cache.add(new Response('TODO: Update the value of the offlineFallbackPage constant in the serviceworker.')) + } + + return cache.add(offlineFallbackPage) + }) + }) + ) +}) + +// Allow sw to control of current page +self.addEventListener('activate', function (event) { + console.log('[PWA Builder] Claiming clients for current page') + event.waitUntil(self.clients.claim()) +}) + +// If any fetch fails, it will look for the request in the cache and serve it from there first +self.addEventListener('fetch', function (event) { + if (event.request.method !== 'GET') return + + if (comparePaths(event.request.url, networkFirstPaths)) { + networkFirstFetch(event) + } else { + cacheFirstFetch(event) + } +}) + +function cacheFirstFetch(event) { + event.respondWith( + fromCache(event.request).then( + function (response) { + // The response was found in the cache so we responde with it and update the entry + + // This is where we call the server to get the newest version of the + // file to use the next time we show view + event.waitUntil( + fetch(event.request).then(function (response) { + return updateCache(event.request, response) + }) + ) + + return response + }, + function () { + // The response was not found in the cache so we look for it on the server + return fetch(event.request) + .then(function (response) { + // If request was success, add or update it in the cache + event.waitUntil(updateCache(event.request, response.clone())) + + return response + }) + .catch(function (error) { + // The following validates that the request was for a navigation to a new document + if (event.request.destination !== 'document' || event.request.mode !== 'navigate') { + return + } + + console.log('[PWA Builder] Network request failed and no cache.' + error) + // Use the precached offline page as fallback + return caches.open(CACHE).then(function (cache) { + cache.match(offlineFallbackPage) + }) + }) + } + ) + ) +} + +function networkFirstFetch(event) { + event.respondWith( + fetch(event.request) + .then(function (response) { + // If request was success, add or update it in the cache + event.waitUntil(updateCache(event.request, response.clone())) + return response + }) + .catch(function (error) { + console.log('[PWA Builder] Network request Failed. Serving content from cache: ' + error) + return fromCache(event.request) + }) + ) +} + +function fromCache(request) { + // Check to see if you have it in the cache + // Return response + // If not in the cache, then return error page + return caches.open(CACHE).then(function (cache) { + return cache.match(request).then(function (matching) { + if (!matching || matching.status === 404) { + return Promise.reject('no-match') + } + + return matching + }) + }) +} + +function updateCache(request, response) { + if (!comparePaths(request.url, avoidCachingPaths)) { + return caches.open(CACHE).then(function (cache) { + return cache.put(request, response) + }) + } + + return Promise.resolve() +}