From b1254b7376e70bdf25f27fdadda0c222dfd82baf Mon Sep 17 00:00:00 2001 From: Fergus Lai Date: Mon, 17 Apr 2023 23:17:23 +0100 Subject: [PATCH] feat: implemented local playlist (#611) --- NUXT/.eslintrc.js | 1 + NUXT/components/Player/index.vue | 5 + NUXT/components/Player/seekbar.vue | 5 + NUXT/components/Playlist/playlistAlert.vue | 41 +++++++ .../{ => Playlist}/playlistCard.vue | 56 ++++++++-- .../components/Playlist/playlistVideoCard.vue | 101 ++++++++++++++++++ NUXT/components/topNavigation.vue | 6 +- NUXT/package.json | 1 + NUXT/pages/channel/playlists.vue | 11 +- NUXT/pages/library.vue | 78 ++++++++++++-- NUXT/pages/playlist.vue | 30 ++++++ NUXT/pages/watch.vue | 69 +++++++++++- NUXT/store/playlist/index.js | 51 +++++++++ ios/App/Podfile | 20 ++-- 14 files changed, 433 insertions(+), 42 deletions(-) create mode 100644 NUXT/components/Playlist/playlistAlert.vue rename NUXT/components/{ => Playlist}/playlistCard.vue (60%) create mode 100644 NUXT/components/Playlist/playlistVideoCard.vue create mode 100644 NUXT/pages/playlist.vue create mode 100644 NUXT/store/playlist/index.js diff --git a/NUXT/.eslintrc.js b/NUXT/.eslintrc.js index e7109c8..92aaeff 100644 --- a/NUXT/.eslintrc.js +++ b/NUXT/.eslintrc.js @@ -18,6 +18,7 @@ module.exports = { "vue/multi-word-component-names": 0, "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + "prettier/prettier": ["error", {endOfLine: "auto"}] // 'prettier/prettier': ['error', { semi: false }], // semi: [2, 'never'], }, diff --git a/NUXT/components/Player/index.vue b/NUXT/components/Player/index.vue index 041e405..832a902 100644 --- a/NUXT/components/Player/index.vue +++ b/NUXT/components/Player/index.vue @@ -312,6 +312,7 @@ :controls="controls" :sources="sources" :seeking="seeking" + :disabled="disabled" @seeking="seeking = !seeking" @scrub=" ($refs.player.currentTime = $event), ($refs.audio.currentTime = $event) @@ -384,6 +385,10 @@ export default { return []; }, }, + disabled: { + type: Boolean, + default: false, + }, }, data() { return { diff --git a/NUXT/components/Player/seekbar.vue b/NUXT/components/Player/seekbar.vue index 22aa621..962593b 100644 --- a/NUXT/components/Player/seekbar.vue +++ b/NUXT/components/Player/seekbar.vue @@ -13,6 +13,7 @@ hide-details height="2" dense + :disabled="disabled" color="transparent" thumb-color="primary" track-color="transparent" @@ -85,6 +86,10 @@ export default { type: Boolean, required: true, }, + disabled: { + type: Boolean, + default: false, + }, }, emits: ["scrub", "seeking"], data: () => ({ diff --git a/NUXT/components/Playlist/playlistAlert.vue b/NUXT/components/Playlist/playlistAlert.vue new file mode 100644 index 0000000..843af4b --- /dev/null +++ b/NUXT/components/Playlist/playlistAlert.vue @@ -0,0 +1,41 @@ + + + diff --git a/NUXT/components/playlistCard.vue b/NUXT/components/Playlist/playlistCard.vue similarity index 60% rename from NUXT/components/playlistCard.vue rename to NUXT/components/Playlist/playlistCard.vue index 95f3b83..0cb253e 100644 --- a/NUXT/components/playlistCard.vue +++ b/NUXT/components/Playlist/playlistCard.vue @@ -1,7 +1,9 @@ + + + + diff --git a/NUXT/components/Playlist/playlistVideoCard.vue b/NUXT/components/Playlist/playlistVideoCard.vue new file mode 100644 index 0000000..c2f29e5 --- /dev/null +++ b/NUXT/components/Playlist/playlistVideoCard.vue @@ -0,0 +1,101 @@ + + + diff --git a/NUXT/components/topNavigation.vue b/NUXT/components/topNavigation.vue index 5e7cc13..5ed4592 100644 --- a/NUXT/components/topNavigation.vue +++ b/NUXT/components/topNavigation.vue @@ -6,7 +6,11 @@ v-show="!search" class="my-auto ml-4" v-text=" - $route.path.includes('channel') ? $store.state.channel.title : page + $route.path.includes('channel') + ? $store.state.channel.title + : $route.path.includes('playlist') + ? $store.state.playlist.currentPlaylist.name + : page " /> diff --git a/NUXT/package.json b/NUXT/package.json index 4e73a2d..4603996 100644 --- a/NUXT/package.json +++ b/NUXT/package.json @@ -14,6 +14,7 @@ "@capacitor/status-bar": "^1.0.8", "core-js": "^3.25.0", "nuxt": "^2.15.8", + "uuid": "^9.0.0", "vue": "^2.7.10", "vue-server-renderer": "^2.7.10", "vue-template-compiler": "^2.7.10", diff --git a/NUXT/pages/channel/playlists.vue b/NUXT/pages/channel/playlists.vue index 8af7935..4c5332a 100644 --- a/NUXT/pages/channel/playlists.vue +++ b/NUXT/pages/channel/playlists.vue @@ -1,14 +1,7 @@ diff --git a/NUXT/pages/library.vue b/NUXT/pages/library.vue index 64c6ac1..8029b01 100644 --- a/NUXT/pages/library.vue +++ b/NUXT/pages/library.vue @@ -1,12 +1,25 @@ diff --git a/NUXT/pages/playlist.vue b/NUXT/pages/playlist.vue new file mode 100644 index 0000000..6029d99 --- /dev/null +++ b/NUXT/pages/playlist.vue @@ -0,0 +1,30 @@ + + + diff --git a/NUXT/pages/watch.vue b/NUXT/pages/watch.vue index c16b833..9529437 100644 --- a/NUXT/pages/watch.vue +++ b/NUXT/pages/watch.vue @@ -9,6 +9,7 @@ :video="video" :sources="sources" :recommends="recommends" + :disabled="saveDialog" /> @@ -273,6 +274,30 @@ }" /> + + + Save To Playlist + + + + + + Done + + @@ -304,6 +329,12 @@ export default { data: function () { return this.initializeState(); }, + + computed: { + playlists() { + return this.$store.state.playlist.playlists; + }, + }, watch: { // Watch for change in the route query string (in this case, ?v=xxxxxxxx to ?v=yyyyyyyy) $route: { @@ -356,6 +387,14 @@ export default { title: this.video.title, channel: this.video.channelName, }); + + this.playlistsCheckbox = this.playlists.map( + (playlist) => + playlist.videos.findIndex( + (playlistVideo) => playlistVideo.id === this.video.id + ) !== -1 + ); + //--- API WatchTime call ---// if (this.$store.state.watchTelemetry) { this.playbackTracking = result.playbackTracking; @@ -459,8 +498,9 @@ export default { { name: "Save", icon: "mdi-plus-box-multiple-outline", - actionName: "enqueue", - disabled: true, + // action: this.save() + actionName: "save", + disabled: false, }, // { // name: "Quality", @@ -484,13 +524,14 @@ export default { interval: null, video: null, backHierarchy: [], + saveDialog: false, + playlistsCheckbox: [], }; }, mountedInit() { this.startTime = Math.floor(Date.now() / 1000); this.getVideo(); - // Reset vertical scrolling const scrollableList = document.querySelectorAll(".overflow-y-auto"); scrollableList.forEach((scrollable) => { @@ -514,6 +555,28 @@ export default { this.$vuetube.addBackAction(dismissComment); } }, + + save() { + this.saveDialog = true; + }, + + updatePlaylist(event, index) { + if (event) { + this.$store.commit("playlist/addToPlaylist", { + video: { + id: this.video.id, + title: this.video.title, + channel: this.video.channelName, + }, + index, + }); + } else { + this.$store.commit("playlist/removeFromPlaylist", { + video: this.video, + playlistIndex: index, + }); + } + }, }, }; diff --git a/NUXT/store/playlist/index.js b/NUXT/store/playlist/index.js new file mode 100644 index 0000000..1ea846b --- /dev/null +++ b/NUXT/store/playlist/index.js @@ -0,0 +1,51 @@ +export const state = () => ({ + playlists: [], + currentPlaylist: null, +}); + +// Shape of playlists +// [playlist, playlist] + +// Shape of playlist +// {name: string, videos: []} + +// Shape of currentPlaylist +// {index: number, name: string, videos: []} + +export const mutations = { + initPlaylists(state) { + if (process.client) { + // read local storage and parse the list of objects + state.playlists = JSON.parse(localStorage.getItem("playlists")); + } + }, + createPlaylist(state, name) { + state.playlists.push({ name, videos: [] }); + localStorage.setItem("playlists", JSON.stringify(state.playlists)); + }, + removePlaylist(state, index) { + state.playlists.splice(index, 1); + localStorage.setItem("playlists", JSON.stringify(state.playlists)); + }, + addToPlaylist(state, { index, video }) { + state.playlists[index].videos.unshift(video); + localStorage.setItem("playlists", JSON.stringify(state.playlists)); + }, + removeFromPlaylist(state, { playlistIndex, video }) { + const videoIndex = state.playlists[playlistIndex].videos.findIndex( + (playlistVideo) => playlistVideo.id === video.id + ); + if (videoIndex === -1) throw new Error("Unable To Find Video"); + state.playlists[playlistIndex].videos.splice(videoIndex, 1); + localStorage.setItem("playlists", JSON.stringify(state.playlists)); + }, + changeToPlaylist(state, videoIndex) { + state.currentPlaylist = { + index: videoIndex, + ...state.playlists[videoIndex], + }; + }, + exitPlaylist(state) { + state.currentPlaylist = null; + }, +}; diff --git a/ios/App/Podfile b/ios/App/Podfile index 00328d2..6639c10 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -9,16 +9,16 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' - pod 'CapacitorCommunityHttp', :path => '../../node_modules/@capacitor-community/http' - pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' - pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device' - pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem' - pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' - pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share' - pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen' - pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar' - pod 'CapacitorToast', :path => '../../node_modules/@capacitor/toast' - pod 'HugotomaziCapacitorNavigationBar', :path => '../../node_modules/@hugotomazi/capacitor-navigation-bar' + pod 'CapacitorCommunityHttp', :path => '..\..\node_modules\@capacitor-community\http' + pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app' + pod 'CapacitorDevice', :path => '..\..\node_modules\@capacitor\device' + pod 'CapacitorFilesystem', :path => '..\..\node_modules\@capacitor\filesystem' + pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics' + pod 'CapacitorShare', :path => '..\..\node_modules\@capacitor\share' + pod 'CapacitorSplashScreen', :path => '..\..\node_modules\@capacitor\splash-screen' + pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar' + pod 'CapacitorToast', :path => '..\..\node_modules\@capacitor\toast' + pod 'HugotomaziCapacitorNavigationBar', :path => '..\..\node_modules\@hugotomazi\capacitor-navigation-bar' pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins' end