From 659415edc324e5a554e53143252de4a97c19c302 Mon Sep 17 00:00:00 2001 From: Preston Date: Thu, 14 Jan 2021 18:03:23 -0500 Subject: [PATCH] Initial Playlist implementation. Functions like basic favorites list --- src/renderer/App.js | 1 + .../ft-icon-button/ft-icon-button.sass | 3 + .../components/ft-list-video/ft-list-video.js | 70 +++++++++++++++++-- .../ft-list-video/ft-list-video.vue | 6 +- .../watch-video-info/watch-video-info.js | 69 +++++++++++++++++- .../watch-video-info/watch-video-info.vue | 8 +++ src/renderer/store/modules/playlist.js | 64 ++++++++++------- src/renderer/themes.css | 3 + .../views/UserPlaylists/UserPlaylists.js | 42 +++++++++++ .../views/UserPlaylists/UserPlaylists.vue | 38 ++++++++-- src/renderer/views/Watch/Watch.vue | 1 + static/locales/en-US.yaml | 5 ++ 12 files changed, 270 insertions(+), 40 deletions(-) diff --git a/src/renderer/App.js b/src/renderer/App.js index 95c7aad0..2795d8c4 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -88,6 +88,7 @@ export default Vue.extend({ this.$store.dispatch('grabUserSettings') this.$store.dispatch('grabHistory') this.$store.dispatch('grabAllProfiles', this.$t('Profile.All Channels')) + this.$store.dispatch('grabAllPlaylists') this.$store.commit('setUsingElectron', useElectron) this.checkThemeSettings() this.checkLocale() diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.sass b/src/renderer/components/ft-icon-button/ft-icon-button.sass index 032d8a55..8099b01d 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.sass +++ b/src/renderer/components/ft-icon-button/ft-icon-button.sass @@ -52,6 +52,9 @@ &:active background-color: var(--accent-color-active) + &.favorite + color: var(--favorite-icon-color) + .iconDropdown display: none position: absolute diff --git a/src/renderer/components/ft-list-video/ft-list-video.js b/src/renderer/components/ft-list-video/ft-list-video.js index ecf8d5ac..50c4c2de 100644 --- a/src/renderer/components/ft-list-video/ft-list-video.js +++ b/src/renderer/components/ft-list-video/ft-list-video.js @@ -165,6 +165,22 @@ export default Vue.extend({ addWatchedStyle: function () { return this.watched && !this.inHistory + }, + + favoritesPlaylist: function () { + return this.$store.getters.getFavorites + }, + + inFavoritesPlaylist: function () { + const index = this.favoritesPlaylist.videos.findIndex((video) => { + return video.videoId === this.id + }) + + return index !== -1 + }, + + favoriteIconTheme: function () { + return this.inFavoritesPlaylist ? 'base favorite' : 'base' } }, mounted: function () { @@ -173,10 +189,11 @@ export default Vue.extend({ }, methods: { toggleSave: function () { - console.log('TODO: ft-list-video method toggleSave') - this.showToast({ - message: this.$t('Saving videos are currently not available. Please wait for a future update') - }) + if (this.inFavoritesPlaylist) { + this.removeFromPlaylist() + } else { + this.addToPlaylist() + } }, handleOptionsClick: function (option) { @@ -396,11 +413,54 @@ export default Vue.extend({ this.watched = false }, + addToPlaylist: function () { + const videoData = { + videoId: this.id, + title: this.title, + author: this.channelName, + authorId: this.channelId, + published: '', + description: this.description, + viewCount: this.viewCount, + lengthSeconds: this.data.lengthSeconds, + timeAdded: new Date().getTime(), + isLive: false, + paid: false, + type: 'video' + } + + const payload = { + playlistName: 'Favorites', + videoData: videoData + } + + this.addVideo(payload) + + this.showToast({ + message: this.$t('Video.Video has been saved') + }) + }, + + removeFromPlaylist: function () { + const payload = { + playlistName: 'Favorites', + videoId: this.id + } + + this.removeVideo(payload) + + this.showToast({ + message: this.$t('Video.Video has been removed from your saved list') + }) + }, + ...mapActions([ 'showToast', 'toLocalePublicationString', 'updateHistory', - 'removeFromHistory' + 'removeFromHistory', + 'addVideo', + 'removeVideo' ]) } }) diff --git a/src/renderer/components/ft-list-video/ft-list-video.vue b/src/renderer/components/ft-list-video/ft-list-video.vue index abf66448..2a79d601 100644 --- a/src/renderer/components/ft-list-video/ft-list-video.vue +++ b/src/renderer/components/ft-list-video/ft-list-video.vue @@ -32,13 +32,13 @@
{ + return video.videoId === this.id + }) + + return index !== -1 + }, + + favoriteIconTheme: function () { + return this.inFavoritesPlaylist ? 'base favorite' : 'base' + }, + downloadLinkNames: function () { return this.downloadLinks.map((download) => { return download.label @@ -208,6 +228,14 @@ export default Vue.extend({ this.$router.push({ path: `/channel/${this.channelId}` }) }, + toggleSave: function () { + if (this.inFavoritesPlaylist) { + this.removeFromPlaylist() + } else { + this.addToPlaylist() + } + }, + handleSubscription: function () { if (this.channelId === '') { return @@ -303,9 +331,48 @@ export default Vue.extend({ shell.openExternal(url) }, + addToPlaylist: function () { + const videoData = { + videoId: this.id, + title: this.title, + author: this.channelName, + authorId: this.channelId, + published: '', + description: this.description, + viewCount: this.viewCount, + lengthSeconds: this.lengthSeconds, + timeAdded: new Date().getTime(), + isLive: false, + paid: false, + type: 'video' + } + + const payload = { + playlistName: 'Favorites', + videoData: videoData + } + + this.addVideo(payload) + + this.showToast({ + message: this.$t('Video.Video has been marked as watched') + }) + }, + + removeFromPlaylist: function () { + const payload = { + playlistName: 'Favorites', + videoId: this.id + } + + this.removeVideo(payload) + }, + ...mapActions([ 'showToast', - 'updateProfile' + 'updateProfile', + 'addVideo', + 'removeVideo' ]) } }) diff --git a/src/renderer/components/watch-video-info/watch-video-info.vue b/src/renderer/components/watch-video-info/watch-video-info.vue index 05e7ec76..67c22a87 100644 --- a/src/renderer/components/watch-video-info/watch-video-info.vue +++ b/src/renderer/components/watch-video-info/watch-video-info.vue @@ -62,6 +62,14 @@
+ { + playlistDb.update({ playlistName: payload.playlistName }, { $push: { videos: payload.videoData } }, { upsert: true }, err => { if (err) { console.error(err) } else { @@ -81,12 +86,17 @@ const actions = { } }) }, - grabAllPlaylists({ commit }) { - playlistDb.getAllData((err, payload) => { + grabAllPlaylists({ commit, dispatch }) { + playlistDb.find({}, (err, payload) => { if (err) { console.error(err) } else { - commit('setAllPlaylists', payload) + if (payload.length === 0) { + commit('setAllPlaylists', state.playlists) + dispatch('addPlaylists', payload) + } else { + commit('setAllPlaylists', payload) + } } }) }, @@ -99,12 +109,12 @@ const actions = { } }) }, - removeAllVideos ({ commit }, playlistId) { - playlistDb.update({ _id: playlistId }, { $set: { videos: [] } }, { upsert: true }, err => { + removeAllVideos ({ commit }, playlistName) { + playlistDb.update({ playlistName: playlistName }, { $set: { videos: [] } }, { upsert: true }, err => { if (err) { console.error(err) } else { - commit('removeAllVideos', playlistId) + commit('removeAllVideos', playlistName) } }) }, @@ -127,7 +137,7 @@ const actions = { }) }, removeVideo ({ commit }, payload) { - playlistDb.update({ _id: payload.playlistId }, { $pull: { videos: payload.videoId } }, { upsert: true }, err => { + playlistDb.update({ playlistName: payload.playlistName }, { $pull: { videos: { videoId: payload.videoId } } }, { upsert: true }, (err, numRemoved) => { if (err) { console.error(err) } else { @@ -136,7 +146,7 @@ const actions = { }) }, removeVideos ({ commit }, payload) { - playlistDb.update({ _id: payload.playlistId }, { $pull: { videos: { $in: payload.videoIds } } }, { upsert: true }, err => { + playlistDb.update({ _id: payload.playlistName }, { $pull: { videos: { $in: payload.videoId } } }, { upsert: true }, err => { if (err) { console.error(err) } else { @@ -154,9 +164,9 @@ const mutations = { state.playlists = state.playlists.concat(payload) }, addVideo (state, payload) { - const playlist = state.playlists.find(playlist => playlist._id === payload.playlistId) + const playlist = state.playlists.find(playlist => playlist.playlistName === payload.playlistName) if (playlist) { - playlist.videos.push(payload.videoId) + playlist.videos.push(payload.videoData) } }, addVideos (state, payload) { @@ -168,22 +178,22 @@ const mutations = { removeAllPlaylists (state) { state.playlists = state.playlists.filter(playlist => playlist.protected !== true) }, - removeAllVideos (state, playlistId) { - const playlist = state.playlists.find(playlist => playlist._id === playlistId) + removeAllVideos (state, playlistName) { + const playlist = state.playlists.find(playlist => playlist.playlistName === playlistName) if (playlist) { playlist.videos = [] } }, removeVideo (state, payload) { - const playlist = state.playlists.find(playlist => playlist._id === payload.playlistId) - if (playlist) { - playlist.videos = playlist.videos.filter(video => video !== payload.videoId) + const playlist = state.playlists.findIndex(playlist => playlist.playlistName === payload.playlistName) + if (playlist !== -1) { + state.playlists[playlist].videos = state.playlists[playlist].videos.filter(video => video.videoId !== payload.videoId) } }, removeVideos (state, payload) { - const playlist = state.playlists.find(playlist => playlist._id === payload.playlistId) - if (playlist) { - playlist.videos = playlist.videos.filter(video => payload.videoIds.indexOf(video) === -1) + const playlist = state.playlists.findIndex(playlist => playlist._id === payload.playlistId) + if (playlist !== -1) { + playlist.videos = playlist.videos.filter(video => payload.videoId.indexOf(video) === -1) } }, removePlaylist (state, playlistId) { diff --git a/src/renderer/themes.css b/src/renderer/themes.css index d2346125..787de982 100644 --- a/src/renderer/themes.css +++ b/src/renderer/themes.css @@ -8,6 +8,7 @@ --bg-color: #f1f1f1; --link-color: var(--accent-color); --link-visited-color: var(--accent-color-visited); + --favorite-icon-color: #FFD600; --card-bg-color: #FFFFFF; --secondary-card-bg-color: #eeeeee; --scrollbar-color: #CCCCCC; @@ -32,6 +33,7 @@ --bg-color: #212121; --link-color: var(--accent-color); --link-visited-color: var(--accent-color-visited); + --favorite-icon-color: #FFEA00; --card-bg-color: #303030; --secondary-card-bg-color: rgba(0, 0, 0, 0.75); --scrollbar-color: #414141; @@ -55,6 +57,7 @@ --bg-color: #000000; --link-color: var(--accent-color); --link-visited-color: var(--accent-color-visited); + --favorite-icon-color: #FFEA00; --card-bg-color: #000000; --secondary-card-bg-color: rgba(0, 0, 0, 0.75); --scrollbar-color: #515151; diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.js b/src/renderer/views/UserPlaylists/UserPlaylists.js index a216cfe4..8e6068cc 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.js +++ b/src/renderer/views/UserPlaylists/UserPlaylists.js @@ -1,6 +1,8 @@ import Vue from 'vue' import FtCard from '../../components/ft-card/ft-card.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' +import FtTooltip from '../../components/ft-tooltip/ft-tooltip.vue' +import FtLoader from '../../components/ft-loader/ft-loader.vue' import FtElementList from '../../components/ft-element-list/ft-element-list.vue' export default Vue.extend({ @@ -8,8 +10,48 @@ export default Vue.extend({ components: { 'ft-card': FtCard, 'ft-flex-box': FtFlexBox, + 'ft-tooltip': FtTooltip, + 'ft-loader': FtLoader, 'ft-element-list': FtElementList }, + data: function () { + return { + isLoading: false, + dataLimit: 100 + } + }, + computed: { + favoritesPlaylist: function () { + return this.$store.getters.getFavorites + }, + + activeData: function () { + if (this.favoritesPlaylist.videos.length < this.dataLimit) { + return this.favoritesPlaylist.videos + } else { + return this.favoritesPlaylist.videos.slice(0, this.dataLimit) + } + } + }, + watch: { + activeData() { + this.isLoading = true + setTimeout(() => { + this.isLoading = false + }, 100) + } + }, mounted: function () { + const limit = sessionStorage.getItem('favoritesLimit') + + if (limit !== null) { + this.dataLimit = limit + } + }, + methods: { + increaseLimit: function () { + this.dataLimit += 100 + sessionStorage.setItem('favoritesLimit', this.dataLimit) + } } }) diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.vue b/src/renderer/views/UserPlaylists/UserPlaylists.vue index 77fe35b1..6cae1139 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.vue +++ b/src/renderer/views/UserPlaylists/UserPlaylists.vue @@ -1,12 +1,42 @@ diff --git a/src/renderer/views/Watch/Watch.vue b/src/renderer/views/Watch/Watch.vue index b2247a34..8202edde 100644 --- a/src/renderer/views/Watch/Watch.vue +++ b/src/renderer/views/Watch/Watch.vue @@ -81,6 +81,7 @@ :download-links="downloadLinks" :watching-playlist="watchingPlaylist" :theatre-possible="theatrePossible" + :length-seconds="videoLengthSeconds" class="watchVideo" :class="{ theatreWatchVideo: useTheatreMode }" @theatre-mode="toggleTheatreMode" diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml index ce61e623..14a916d3 100644 --- a/static/locales/en-US.yaml +++ b/static/locales/en-US.yaml @@ -86,6 +86,8 @@ Most Popular: Most Popular Playlists: Playlists User Playlists: Your Playlists: Your Playlists + Playlist Message: This page is not reflective of fully working playlists. It only lists videos that you have saved or favorited. When the work has finished, all videos currently here will be migrated to a 'Favorites' playlist. + Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: Your saved videos are empty. Click on the save button on the corner of a video to have it listed here History: # On History Page History: History @@ -368,6 +370,9 @@ Video: Remove From History: Remove From History Video has been marked as watched: Video has been marked as watched Video has been removed from your history: Video has been removed from your history + Save Video: Save Video + Video has been saved: Video has been saved + Video has been removed from your saved list: Video has been removed from your saved list Open in YouTube: Open in YouTube Copy YouTube Link: Copy YouTube Link Open YouTube Embedded Player: Open YouTube Embedded Player