diff --git a/src/constants.js b/src/constants.js index 030b7a2da926f..f6a85d351ebc7 100644 --- a/src/constants.js +++ b/src/constants.js @@ -67,6 +67,8 @@ const DBActions = { HISTORY: { UPDATE_WATCH_PROGRESS: 20, UPDATE_PLAYLIST: 21, + UNSET_PLAYLIST_FOR_VIDEOS: 22, + UNSET_PLAYLISTS: 23 }, PROFILES: { diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js index 24f7765cb2c50..397e2811ca32a 100644 --- a/src/datastores/handlers/base.js +++ b/src/datastores/handlers/base.js @@ -124,6 +124,27 @@ class History { return db.history.updateAsync({ videoId }, { $set: { lastViewedPlaylistId, lastViewedPlaylistType, lastViewedPlaylistItemId } }, { upsert: true }) } + static unsetLastViewedPlaylistForVideos(videoIds, lastViewedPlaylistId) { + return db.history.updateAsync( + { + videoId: { $in: videoIds }, + lastViewedPlaylistId: lastViewedPlaylistId + }, + { $unset: { lastViewedPlaylistId: '', lastViewedPlaylistType: '', lastViewedPlaylistItemId: '' } }, + { multi: true } + ) + } + + static unsetLastViewedPlaylists(lastViewedPlaylistIds) { + return db.history.updateAsync( + { + lastViewedPlaylistId: { $in: lastViewedPlaylistIds } + }, + { $unset: { lastViewedPlaylistId: '', lastViewedPlaylistType: '', lastViewedPlaylistItemId: '' } }, + { multi: true } + ) + } + static delete(videoId) { return db.history.removeAsync({ videoId }) } diff --git a/src/datastores/handlers/electron.js b/src/datastores/handlers/electron.js index 5e4357c04dde4..b7d0610223dbc 100644 --- a/src/datastores/handlers/electron.js +++ b/src/datastores/handlers/electron.js @@ -37,6 +37,14 @@ class History { ) } + static unsetLastViewedPlaylistForVideos(videoIds, lastViewedPlaylistId) { + return window.ftElectron.dbHistory(DBActions.HISTORY.UNSET_PLAYLIST_FOR_VIDEOS, { videoIds, lastViewedPlaylistId }) + } + + static unsetLastViewedPlaylists(lastViewedPlaylistIds) { + return window.ftElectron.dbHistory(DBActions.HISTORY.UNSET_PLAYLISTS, lastViewedPlaylistIds) + } + static delete(videoId) { return window.ftElectron.dbHistory(DBActions.GENERAL.DELETE, videoId) } diff --git a/src/main/index.js b/src/main/index.js index 228bc2d65adfc..40cdb7b8d76e0 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1736,6 +1736,24 @@ function runApp() { ) return null + case DBActions.HISTORY.UNSET_PLAYLIST_FOR_VIDEOS: + await baseHandlers.history.unsetLastViewedPlaylistForVideos(data.videoIds, data.lastViewedPlaylistId) + syncOtherWindows( + IpcChannels.SYNC_HISTORY, + event, + { event: SyncEvents.HISTORY.UNSET_PLAYLIST_FOR_VIDEOS, data } + ) + return null + + case DBActions.HISTORY.UNSET_PLAYLISTS: + await baseHandlers.history.unsetLastViewedPlaylists(data) + syncOtherWindows( + IpcChannels.SYNC_HISTORY, + event, + { event: SyncEvents.HISTORY.UNSET_PLAYLISTS, data } + ) + return null + case DBActions.GENERAL.DELETE: await baseHandlers.history.delete(data) syncOtherWindows( diff --git a/src/renderer/store/modules/history.js b/src/renderer/store/modules/history.js index dfd7c6831d0a1..f358b900019e4 100644 --- a/src/renderer/store/modules/history.js +++ b/src/renderer/store/modules/history.js @@ -101,6 +101,24 @@ const actions = { console.error(errMessage) } }, + + async unsetLastViewedPlaylistForVideos({ commit }, { videoIds, lastViewedPlaylistId }) { + try { + await DBHistoryHandlers.unsetLastViewedPlaylistForVideos(videoIds, lastViewedPlaylistId) + commit('unsetRecordsLastViewedPlaylistIdInHistoryCache', { videoIds, lastViewedPlaylistId }) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async unsetLastViewedPlaylists({ commit }, lastViewedPlaylistIds) { + try { + await DBHistoryHandlers.unsetLastViewedPlaylists(lastViewedPlaylistIds) + commit('unsetRecordsLastViewedPlaylistIdsInHistoryCache', lastViewedPlaylistIds) + } catch (errMessage) { + console.error(errMessage) + } + }, } const mutations = { @@ -153,6 +171,35 @@ const mutations = { } }, + unsetRecordsLastViewedPlaylistIdInHistoryCache(state, { videoIds, lastViewedPlaylistId }) { + for (const videoId of videoIds) { + // historyCacheById and historyCacheSorted reference the same object instances, + // so modifying an existing object in one of them will update both. + + const record = state.historyCacheById[videoId] + + // Don't unset if the item was removed from the watch history or if the last viewed playlist does not match + if (record && record.lastViewedPlaylistId === lastViewedPlaylistId) { + delete record.lastViewedPlaylistId + delete record.lastViewedPlaylistType + delete record.lastViewedPlaylistItemId + } + } + }, + + unsetRecordsLastViewedPlaylistIdsInHistoryCache(state, playlistIds) { + const playlistIdSet = new Set(playlistIds) + + for (const record of state.historyCacheSorted) { + // Don't unset if the item was removed from the watch history or if the last viewed playlist does not match + if (record && playlistIdSet.has(record.lastViewedPlaylistId)) { + delete record.lastViewedPlaylistId + delete record.lastViewedPlaylistType + delete record.lastViewedPlaylistItemId + } + } + }, + removeFromHistoryCacheById(state, videoId) { for (let i = 0; i < state.historyCacheSorted.length; i++) { if (state.historyCacheSorted[i].videoId === videoId) { diff --git a/src/renderer/store/modules/playlists.js b/src/renderer/store/modules/playlists.js index 04926de6104e2..d482b970c4d7f 100644 --- a/src/renderer/store/modules/playlists.js +++ b/src/renderer/store/modules/playlists.js @@ -369,8 +369,11 @@ const actions = { } }, - async removeAllPlaylists({ commit }) { + async removeAllPlaylists({ commit, dispatch, getters }) { try { + const playlistIds = getters.getAllPlaylists.map(playlist => playlist._id) + await dispatch('unsetLastViewedPlaylists', playlistIds) + await DBPlaylistHandlers.deleteAll() commit('removeAllPlaylists') } catch (errMessage) { @@ -378,8 +381,10 @@ const actions = { } }, - async removeAllVideos({ commit }, _id) { + async removeAllVideos({ commit, dispatch }, _id) { try { + await dispatch('unsetLastViewedPlaylists', [_id]) + await DBPlaylistHandlers.deleteAllVideosByPlaylistId(_id) commit('removeAllVideos', _id) } catch (errMessage) { @@ -387,8 +392,10 @@ const actions = { } }, - async removePlaylist({ commit }, playlistId) { + async removePlaylist({ commit, dispatch }, playlistId) { try { + await dispatch('unsetLastViewedPlaylists', [playlistId]) + await DBPlaylistHandlers.delete(playlistId) commit('removePlaylist', playlistId) } catch (errMessage) { @@ -396,8 +403,10 @@ const actions = { } }, - async removePlaylists({ commit }, playlistIds) { + async removePlaylists({ commit, dispatch }, playlistIds) { try { + await dispatch('unsetLastViewedPlaylists', playlistIds) + await DBPlaylistHandlers.deleteMultiple(playlistIds) commit('removePlaylists', playlistIds) } catch (errMessage) { @@ -405,10 +414,12 @@ const actions = { } }, - async removeVideo({ commit }, payload) { + async removeVideo({ commit, dispatch }, payload) { try { const { _id, videoId, playlistItemId } = payload + await dispatch('unsetLastViewedPlaylistForVideos', { videoIds: [videoId], lastViewedPlaylistId: _id }) + const lastUpdatedAt = Date.now() await DBPlaylistHandlers.deleteVideoIdByPlaylistId(_id, lastUpdatedAt, videoId, playlistItemId) @@ -421,9 +432,11 @@ const actions = { } }, - async removeVideos({ commit }, payload) { + async removeVideos({ commit, dispatch }, payload) { try { - const { _id, playlistItemIds } = payload + const { _id, playlistItemIds, videoIds } = payload + + await dispatch('unsetLastViewedPlaylistForVideos', { videoIds, lastViewedPlaylistId: _id }) const lastUpdatedAt = Date.now() diff --git a/src/renderer/views/Playlist/Playlist.vue b/src/renderer/views/Playlist/Playlist.vue index aaae9b1899b0f..72c014220cb0b 100644 --- a/src/renderer/views/Playlist/Playlist.vue +++ b/src/renderer/views/Playlist/Playlist.vue @@ -243,6 +243,8 @@ const videoSearchQuery = ref('') const promptOpen = ref(false) /** @type {import('vue').Ref} */ const toBeDeletedPlaylistItemIds = ref([]) +/** @type {import('vue').Ref} */ +const videosWithPlaylistToUnset = ref([]) /** @type {AbortController | null} */ let undoToastAbortController = null @@ -852,6 +854,7 @@ function removeVideoFromPlaylist(videoId, playlistItemId) { if (foundVideo) { toBeDeletedPlaylistItemIds.value.push(playlistItemId) + videosWithPlaylistToUnset.value.push(videoId) // Only show toast when no existing toast shown if (undoToastAbortController == null) { @@ -868,6 +871,7 @@ function removeVideoFromPlaylist(videoId, playlistItemId) { () => { clearTimeout(actualRemoveVideosTimeout) toBeDeletedPlaylistItemIds.value = [] + videosWithPlaylistToUnset.value = [] undoToastAbortController = null }, undoToastAbortController.signal, @@ -886,11 +890,13 @@ async function removeToBeDeletedVideosSometimes() { if (toBeDeletedPlaylistItemIds.value.length > 0) { await store.dispatch('removeVideos', { _id: playlistId.value, - // Create a new non-reactive array to avoid Electron erroring about Proxy objects not being clonable + // Create new non-reactive arrays to avoid Electron erroring about Proxy objects not being clonable playlistItemIds: [...toBeDeletedPlaylistItemIds.value], + videoIds: [...videosWithPlaylistToUnset.value], }) toBeDeletedPlaylistItemIds.value = [] + videosWithPlaylistToUnset.value = [] undoToastAbortController?.abort() undoToastAbortController = null }