From daecf944fb0c69eb50a3078f82fdfaea4f2d0ced Mon Sep 17 00:00:00 2001 From: Svallinn <41585298+Svallinn@users.noreply.github.com> Date: Wed, 15 Dec 2021 18:42:24 +0000 Subject: [PATCH] Store Revamp / Full database synchronization across windows (#1833) * History: Refactor history module * Profiles: Refactor profiles module * IPC: Move channel ids to their own file and make them constants * IPC: Replace single sync channel for one channel per sync type * Everywhere: Replace default profile id magic strings with constant ref * Profiles: Refactor `activeProfile` property from store This commit makes it so that `activeProfile`'s getter returns the entire profile, while the related update function only needs the profile id (instead of the previously used array index) to change the currently active profile. This change was made due to inconsistency regarding the active profile when creating new profiles. If a new profile coincidentally landed in the current active profile's array index after sorting, the app would mistakenly change to it without any action from the user apart from the profile's creation. Turning the profile id into the selector instead solves this issue. * Revert "Store: Implement history synchronization between windows" This reverts commit 99b61e617873412eb393d8f4dfccd8f8c172021f. This is necessary for an upcoming improved implementation of the history synchronization. * History: Remove unused mutation * Everywhere: Create abstract database handlers The project now utilizes abstract handlers to fetch, modify or otherwise manipulate data from the database. This facilitates 3 aspects of the app, in addition of making them future proof: - Switching database libraries is now trivial Since most of the app utilizes the abstract handlers, it's incredibly easily to change to a different DB library. Hypothetically, all that would need to be done is to simply replace the the file containing the base handlers, while the rest of the app would go unchanged. - Syncing logic between Electron and web is now properly separated There are now two distinct DB handling APIs: the Electron one and the web one. The app doesn't need to manually choose the API, because it's detected which platform is being utilized on import. - All Electron windows now share the same database instance This provides a single source of truth, improving consistency regarding data manipulation and windows synchronization. As a sidenote, syncing implementation has been left as is (web unimplemented; Electron only syncs settings, remaining datastore syncing will be implemented in the upcoming commits). * Electron/History: Implement history synchronization * Profiles: Implement suplementary profile creation logic * ft-profile-edit: Small fix on profile name missing display * Electron/Profiles: Implement profile synchronization * Electron/Playlists: Implement playlist synchronization --- src/constants.js | 77 +++++ src/datastores/handlers/base.js | 153 +++++++++ src/datastores/handlers/electron.js | 198 ++++++++++++ src/datastores/handlers/index.js | 19 ++ src/datastores/handlers/web.js | 120 +++++++ src/datastores/index.js | 21 ++ src/main/index.js | 301 +++++++++++++++--- src/renderer/App.js | 7 +- .../components/data-settings/data-settings.js | 3 +- .../ft-profile-edit/ft-profile-edit.js | 27 +- .../ft-profile-edit/ft-profile-edit.vue | 2 +- .../ft-profile-selector.js | 26 +- .../ft-profile-selector.vue | 4 +- .../ft-video-player/ft-video-player.js | 10 +- .../privacy-settings/privacy-settings.js | 7 +- .../proxy-settings/proxy-settings.js | 6 +- src/renderer/components/side-nav/side-nav.js | 2 +- src/renderer/components/top-nav/top-nav.js | 4 +- .../watch-video-info/watch-video-info.js | 11 +- src/renderer/store/datastores.js | 47 --- src/renderer/store/modules/history.js | 135 ++++---- src/renderer/store/modules/playlists.js | 130 ++++---- src/renderer/store/modules/profiles.js | 168 ++++++---- src/renderer/store/modules/settings.js | 126 +++++--- src/renderer/store/modules/subscriptions.js | 4 +- src/renderer/store/modules/utils.js | 18 +- src/renderer/views/Channel/Channel.js | 11 +- src/renderer/views/ProfileEdit/ProfileEdit.js | 60 ++-- .../views/Subscriptions/Subscriptions.js | 11 +- 29 files changed, 1285 insertions(+), 423 deletions(-) create mode 100644 src/constants.js create mode 100644 src/datastores/handlers/base.js create mode 100644 src/datastores/handlers/electron.js create mode 100644 src/datastores/handlers/index.js create mode 100644 src/datastores/handlers/web.js create mode 100644 src/datastores/index.js delete mode 100644 src/renderer/store/datastores.js diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 00000000..ba55e79b --- /dev/null +++ b/src/constants.js @@ -0,0 +1,77 @@ +// IPC Channels +const IpcChannels = { + ENABLE_PROXY: 'enable-proxy', + DISABLE_PROXY: 'disable-proxy', + OPEN_EXTERNAL_LINK: 'open-external-link', + GET_SYSTEM_LOCALE: 'get-system-locale', + GET_USER_DATA_PATH: 'get-user-data-path', + GET_USER_DATA_PATH_SYNC: 'get-user-data-path-sync', + SHOW_OPEN_DIALOG: 'show-open-dialog', + SHOW_SAVE_DIALOG: 'show-save-dialog', + STOP_POWER_SAVE_BLOCKER: 'stop-power-save-blocker', + START_POWER_SAVE_BLOCKER: 'start-power-save-blocker', + CREATE_NEW_WINDOW: 'create-new-window', + OPEN_IN_EXTERNAL_PLAYER: 'open-in-external-player', + + DB_SETTINGS: 'db-settings', + DB_HISTORY: 'db-history', + DB_PROFILES: 'db-profiles', + DB_PLAYLISTS: 'db-playlists', + + SYNC_SETTINGS: 'sync-settings', + SYNC_HISTORY: 'sync-history', + SYNC_PROFILES: 'sync-profiles', + SYNC_PLAYLISTS: 'sync-playlists' +} + +const DBActions = { + GENERAL: { + CREATE: 'db-action-create', + FIND: 'db-action-find', + UPSERT: 'db-action-upsert', + DELETE: 'db-action-delete', + DELETE_MULTIPLE: 'db-action-delete-multiple', + DELETE_ALL: 'db-action-delete-all', + PERSIST: 'db-action-persist' + }, + + HISTORY: { + UPDATE_WATCH_PROGRESS: 'db-action-history-update-watch-progress' + }, + + PLAYLISTS: { + UPSERT_VIDEO: 'db-action-playlists-upsert-video-by-playlist-name', + UPSERT_VIDEO_IDS: 'db-action-playlists-upsert-video-ids-by-playlist-id', + DELETE_VIDEO_ID: 'db-action-playlists-delete-video-by-playlist-name', + DELETE_VIDEO_IDS: 'db-action-playlists-delete-video-ids', + DELETE_ALL_VIDEOS: 'db-action-playlists-delete-all-videos' + } +} + +const SyncEvents = { + GENERAL: { + CREATE: 'sync-create', + UPSERT: 'sync-upsert', + DELETE: 'sync-delete', + DELETE_ALL: 'sync-delete-all' + }, + + HISTORY: { + UPDATE_WATCH_PROGRESS: 'sync-history-update-watch-progress' + }, + + PLAYLISTS: { + UPSERT_VIDEO: 'sync-playlists-upsert-video', + DELETE_VIDEO: 'sync-playlists-delete-video' + } +} + +// Utils +const MAIN_PROFILE_ID = 'allChannels' + +export { + IpcChannels, + DBActions, + SyncEvents, + MAIN_PROFILE_ID +} diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js new file mode 100644 index 00000000..90b9d183 --- /dev/null +++ b/src/datastores/handlers/base.js @@ -0,0 +1,153 @@ +import db from '../index' + +class Settings { + static find() { + return db.settings.find({ _id: { $ne: 'bounds' } }) + } + + static upsert(_id, value) { + return db.settings.update({ _id }, { _id, value }, { upsert: true }) + } + + // ******************** // + // Unique Electron main process handlers + static _findAppReadyRelatedSettings() { + return db.settings.find({ + $or: [ + { _id: 'disableSmoothScrolling' }, + { _id: 'useProxy' }, + { _id: 'proxyProtocol' }, + { _id: 'proxyHostname' }, + { _id: 'proxyPort' } + ] + }) + } + + static _findBounds() { + return db.settings.findOne({ _id: 'bounds' }) + } + + static _updateBounds(value) { + return db.settings.update({ _id: 'bounds' }, { _id: 'bounds', value }, { upsert: true }) + } + // ******************** // +} + +class History { + static find() { + return db.history.find({}).sort({ timeWatched: -1 }) + } + + static upsert(record) { + return db.history.update({ videoId: record.videoId }, record, { upsert: true }) + } + + static updateWatchProgress(videoId, watchProgress) { + return db.history.update({ videoId }, { $set: { watchProgress } }, { upsert: true }) + } + + static delete(videoId) { + return db.history.remove({ videoId }) + } + + static deleteAll() { + return db.history.remove({}, { multi: true }) + } + + static persist() { + db.history.persistence.compactDatafile() + } +} + +class Profiles { + static create(profile) { + return db.profiles.insert(profile) + } + + static find() { + return db.profiles.find({}) + } + + static upsert(profile) { + return db.profiles.update({ _id: profile._id }, profile, { upsert: true }) + } + + static delete(id) { + return db.profiles.remove({ _id: id }) + } + + static persist() { + db.profiles.persistence.compactDatafile() + } +} + +class Playlists { + static create(playlists) { + return db.playlists.insert(playlists) + } + + static find() { + return db.playlists.find({}) + } + + static upsertVideoByPlaylistName(playlistName, videoData) { + return db.playlists.update( + { playlistName }, + { $push: { videos: videoData } }, + { upsert: true } + ) + } + + static upsertVideoIdsByPlaylistId(_id, videoIds) { + return db.playlists.update( + { _id }, + { $push: { videos: { $each: videoIds } } }, + { upsert: true } + ) + } + + static delete(_id) { + return db.playlists.remove({ _id, protected: { $ne: true } }) + } + + static deleteVideoIdByPlaylistName(playlistName, videoId) { + return db.playlists.update( + { playlistName }, + { $pull: { videos: { videoId } } }, + { upsert: true } + ) + } + + static deleteVideoIdsByPlaylistName(playlistName, videoIds) { + return db.playlists.update( + { playlistName }, + { $pull: { videos: { $in: videoIds } } }, + { upsert: true } + ) + } + + static deleteAllVideosByPlaylistName(playlistName) { + return db.playlists.update( + { playlistName }, + { $set: { videos: [] } }, + { upsert: true } + ) + } + + static deleteMultiple(ids) { + return db.playlists.remove({ _id: { $in: ids }, protected: { $ne: true } }) + } + + static deleteAll() { + return db.playlists.remove({ protected: { $ne: true } }) + } +} + +const baseHandlers = { + settings: Settings, + history: History, + profiles: Profiles, + playlists: Playlists +} + +export default baseHandlers diff --git a/src/datastores/handlers/electron.js b/src/datastores/handlers/electron.js new file mode 100644 index 00000000..a054d7b5 --- /dev/null +++ b/src/datastores/handlers/electron.js @@ -0,0 +1,198 @@ +import { ipcRenderer } from 'electron' +import { IpcChannels, DBActions } from '../../constants' + +class Settings { + static find() { + return ipcRenderer.invoke( + IpcChannels.DB_SETTINGS, + { action: DBActions.GENERAL.FIND } + ) + } + + static upsert(_id, value) { + return ipcRenderer.invoke( + IpcChannels.DB_SETTINGS, + { action: DBActions.GENERAL.UPSERT, data: { _id, value } } + ) + } +} + +class History { + static find() { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { action: DBActions.GENERAL.FIND } + ) + } + + static upsert(record) { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { action: DBActions.GENERAL.UPSERT, data: record } + ) + } + + static updateWatchProgress(videoId, watchProgress) { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { + action: DBActions.HISTORY.UPDATE_WATCH_PROGRESS, + data: { videoId, watchProgress } + } + ) + } + + static delete(videoId) { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { action: DBActions.GENERAL.DELETE, data: videoId } + ) + } + + static deleteAll() { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { action: DBActions.GENERAL.DELETE_ALL } + ) + } + + static persist() { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { action: DBActions.GENERAL.PERSIST } + ) + } +} + +class Profiles { + static create(profile) { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { action: DBActions.GENERAL.CREATE, data: profile } + ) + } + + static find() { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { action: DBActions.GENERAL.FIND } + ) + } + + static upsert(profile) { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { action: DBActions.GENERAL.UPSERT, data: profile } + ) + } + + static delete(id) { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { action: DBActions.GENERAL.DELETE, data: id } + ) + } + + static persist() { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { action: DBActions.GENERAL.PERSIST } + ) + } +} + +class Playlists { + static create(playlists) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { action: DBActions.GENERAL.CREATE, data: playlists } + ) + } + + static find() { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { action: DBActions.GENERAL.FIND } + ) + } + + static upsertVideoByPlaylistName(playlistName, videoData) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { + action: DBActions.PLAYLISTS.UPSERT_VIDEO, + data: { playlistName, videoData } + } + ) + } + + static upsertVideoIdsByPlaylistId(_id, videoIds) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { + action: DBActions.PLAYLISTS.UPSERT_VIDEO_IDS, + data: { _id, videoIds } + } + ) + } + + static delete(_id) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { action: DBActions.GENERAL.DELETE, data: _id } + ) + } + + static deleteVideoIdByPlaylistName(playlistName, videoId) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { + action: DBActions.PLAYLISTS.DELETE_VIDEO_ID, + data: { playlistName, videoId } + } + ) + } + + static deleteVideoIdsByPlaylistName(playlistName, videoIds) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { + action: DBActions.PLAYLISTS.DELETE_VIDEO_IDS, + data: { playlistName, videoIds } + } + ) + } + + static deleteAllVideosByPlaylistName(playlistName) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { + action: DBActions.PLAYLISTS.DELETE_ALL_VIDEOS, + data: playlistName + } + ) + } + + static deleteMultiple(ids) { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { action: DBActions.GENERAL.DELETE_MULTIPLE, data: ids } + ) + } + + static deleteAll() { + return ipcRenderer.invoke( + IpcChannels.DB_PLAYLISTS, + { action: DBActions.GENERAL.DELETE_ALL } + ) + } +} + +const handlers = { + settings: Settings, + history: History, + profiles: Profiles, + playlists: Playlists +} + +export default handlers diff --git a/src/datastores/handlers/index.js b/src/datastores/handlers/index.js new file mode 100644 index 00000000..0b1dc938 --- /dev/null +++ b/src/datastores/handlers/index.js @@ -0,0 +1,19 @@ +let handlers +const usingElectron = window?.process?.type === 'renderer' +if (usingElectron) { + handlers = require('./electron').default +} else { + handlers = require('./web').default +} + +const DBSettingHandlers = handlers.settings +const DBHistoryHandlers = handlers.history +const DBProfileHandlers = handlers.profiles +const DBPlaylistHandlers = handlers.playlists + +export { + DBSettingHandlers, + DBHistoryHandlers, + DBProfileHandlers, + DBPlaylistHandlers +} diff --git a/src/datastores/handlers/web.js b/src/datastores/handlers/web.js new file mode 100644 index 00000000..bf7ee41f --- /dev/null +++ b/src/datastores/handlers/web.js @@ -0,0 +1,120 @@ +import baseHandlers from './base' + +// TODO: Syncing +// Syncing on the web would involve a different implementation +// to the electron one (obviously) +// One idea would be to use a watcher-like mechanism on +// localStorage or IndexedDB to inform other tabs on the changes +// that have occurred in other tabs +// +// NOTE: NeDB uses `localForage` on the browser +// https://www.npmjs.com/package/localforage + +class Settings { + static find() { + return baseHandlers.settings.find() + } + + static upsert(_id, value) { + return baseHandlers.settings.upsert(_id, value) + } +} + +class History { + static find() { + return baseHandlers.history.find() + } + + static upsert(record) { + return baseHandlers.history.upsert(record) + } + + static updateWatchProgress(videoId, watchProgress) { + return baseHandlers.history.updateWatchProgress(videoId, watchProgress) + } + + static delete(videoId) { + return baseHandlers.history.delete(videoId) + } + + static deleteAll() { + return baseHandlers.history.deleteAll() + } + + static persist() { + baseHandlers.history.persist() + } +} + +class Profiles { + static create(profile) { + return baseHandlers.profiles.create(profile) + } + + static find() { + return baseHandlers.profiles.find() + } + + static upsert(profile) { + return baseHandlers.profiles.upsert(profile) + } + + static delete(id) { + return baseHandlers.profiles.delete(id) + } + + static persist() { + baseHandlers.profiles.persist() + } +} + +class Playlists { + static create(playlists) { + return baseHandlers.playlists.create(playlists) + } + + static find() { + return baseHandlers.playlists.find() + } + + static upsertVideoByPlaylistName(playlistName, videoData) { + return baseHandlers.playlists.upsertVideoByPlaylistName(playlistName, videoData) + } + + static upsertVideoIdsByPlaylistId(_id, videoIds) { + return baseHandlers.playlists.upsertVideoIdsByPlaylistId(_id, videoIds) + } + + static delete(_id) { + return baseHandlers.playlists.delete(_id) + } + + static deleteVideoIdByPlaylistName(playlistName, videoId) { + return baseHandlers.playlists.deleteVideoIdByPlaylistName(playlistName, videoId) + } + + static deleteVideoIdsByPlaylistName(playlistName, videoIds) { + return baseHandlers.playlists.deleteVideoIdsByPlaylistName(playlistName, videoIds) + } + + static deleteAllVideosByPlaylistName(playlistName) { + return baseHandlers.playlists.deleteAllVideosByPlaylistName(playlistName) + } + + static deleteMultiple(ids) { + return baseHandlers.playlists.deleteMultiple(ids) + } + + static deleteAll() { + return baseHandlers.playlists.deleteAll() + } +} + +const handlers = { + settings: Settings, + history: History, + profiles: Profiles, + playlists: Playlists +} + +export default handlers diff --git a/src/datastores/index.js b/src/datastores/index.js new file mode 100644 index 00000000..713a0ea3 --- /dev/null +++ b/src/datastores/index.js @@ -0,0 +1,21 @@ +import Datastore from 'nedb-promises' + +let dbPath = null + +const isElectronMain = !!process?.versions?.electron +if (isElectronMain) { + const { app } = require('electron') + const { join } = require('path') + const userDataPath = app.getPath('userData') // This is based on the user's OS + dbPath = (dbName) => join(userDataPath, `${dbName}.db`) +} else { + dbPath = (dbName) => `${dbName}.db` +} + +const db = {} +db.settings = Datastore.create({ filename: dbPath('settings'), autoload: true }) +db.profiles = Datastore.create({ filename: dbPath('profiles'), autoload: true }) +db.playlists = Datastore.create({ filename: dbPath('playlists'), autoload: true }) +db.history = Datastore.create({ filename: dbPath('history'), autoload: true }) + +export default db diff --git a/src/main/index.js b/src/main/index.js index 66c3ade0..c594cfbd 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -2,10 +2,12 @@ import { app, BrowserWindow, dialog, Menu, ipcMain, powerSaveBlocker, screen, session, shell } from 'electron' -import Datastore from 'nedb-promises' import path from 'path' import cp from 'child_process' +import { IpcChannels, DBActions, SyncEvents } from '../constants' +import baseHandlers from '../datastores/handlers/base' + if (process.argv.includes('--version')) { console.log(`v${app.getVersion()}`) app.exit() @@ -29,13 +31,6 @@ function runApp() { ] }) - const localDataStorage = app.getPath('userData') // Grabs the userdata directory based on the user's OS - - const settingsDb = Datastore.create({ - filename: localDataStorage + '/settings.db', - autoload: true - }) - // disable electron warning process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' const isDev = process.env.NODE_ENV === 'development' @@ -93,15 +88,7 @@ function runApp() { app.on('ready', async (_, __) => { let docArray try { - docArray = await settingsDb.find({ - $or: [ - { _id: 'disableSmoothScrolling' }, - { _id: 'useProxy' }, - { _id: 'proxyProtocol' }, - { _id: 'proxyHostname' }, - { _id: 'proxyPort' } - ] - }) + docArray = await baseHandlers.settings._findAppReadyRelatedSettings() } catch (err) { console.error(err) app.exit() @@ -241,7 +228,7 @@ function runApp() { height: 800 }) - const boundsDoc = await settingsDb.findOne({ _id: 'bounds' }) + const boundsDoc = await baseHandlers.settings._findBounds() if (typeof boundsDoc?.value === 'object') { const { maximized, ...bounds } = boundsDoc.value const allDisplaysSummaryWidth = screen @@ -296,11 +283,7 @@ function runApp() { maximized: newWindow.isMaximized() } - await settingsDb.update( - { _id: 'bounds' }, - { _id: 'bounds', value }, - { upsert: true } - ) + await baseHandlers.settings._updateBounds(value) }) newWindow.once('closed', () => { @@ -354,70 +337,292 @@ function runApp() { app.quit() }) - ipcMain.on('enableProxy', (_, url) => { + ipcMain.on(IpcChannels.ENABLE_PROXY, (_, url) => { console.log(url) session.defaultSession.setProxy({ proxyRules: url }) }) - ipcMain.on('disableProxy', () => { + ipcMain.on(IpcChannels.DISABLE_PROXY, () => { session.defaultSession.setProxy({}) }) - ipcMain.on('openExternalLink', (_, url) => { + ipcMain.on(IpcChannels.OPEN_EXTERNAL_LINK, (_, url) => { if (typeof url === 'string') shell.openExternal(url) }) - ipcMain.handle('getSystemLocale', () => { + ipcMain.handle(IpcChannels.GET_SYSTEM_LOCALE, () => { return app.getLocale() }) - ipcMain.handle('getUserDataPath', () => { + ipcMain.handle(IpcChannels.GET_USER_DATA_PATH, () => { return app.getPath('userData') }) - ipcMain.on('getUserDataPathSync', (event) => { + ipcMain.on(IpcChannels.GET_USER_DATA_PATH_SYNC, (event) => { event.returnValue = app.getPath('userData') }) - ipcMain.handle('showOpenDialog', async (_, options) => { + ipcMain.handle(IpcChannels.SHOW_OPEN_DIALOG, async (_, options) => { return await dialog.showOpenDialog(options) }) - ipcMain.handle('showSaveDialog', async (_, options) => { + ipcMain.handle(IpcChannels.SHOW_SAVE_DIALOG, async (_, options) => { return await dialog.showSaveDialog(options) }) - ipcMain.on('stopPowerSaveBlocker', (_, id) => { + ipcMain.on(IpcChannels.STOP_POWER_SAVE_BLOCKER, (_, id) => { powerSaveBlocker.stop(id) }) - ipcMain.handle('startPowerSaveBlocker', (_, type) => { - return powerSaveBlocker.start(type) + ipcMain.handle(IpcChannels.START_POWER_SAVE_BLOCKER, (_) => { + return powerSaveBlocker.start('prevent-display-sleep') }) - ipcMain.on('createNewWindow', () => { + ipcMain.on(IpcChannels.CREATE_NEW_WINDOW, () => { createWindow(false) }) - ipcMain.on('syncWindows', (event, payload) => { - const otherWindows = BrowserWindow.getAllWindows().filter( - (window) => { - return window.webContents.id !== event.sender.id - } - ) - - for (const window of otherWindows) { - window.webContents.send('syncWindows', payload) - } - }) - - ipcMain.on('openInExternalPlayer', (_, payload) => { + ipcMain.on(IpcChannels.OPEN_IN_EXTERNAL_PLAYER, (_, payload) => { const child = cp.spawn(payload.executable, payload.args, { detached: true, stdio: 'ignore' }) child.unref() }) + // ************************************************* // + // DB related IPC calls + // *********** // + + // Settings + ipcMain.handle(IpcChannels.DB_SETTINGS, async (event, { action, data }) => { + try { + switch (action) { + case DBActions.GENERAL.FIND: + return await baseHandlers.settings.find() + + case DBActions.GENERAL.UPSERT: + await baseHandlers.settings.upsert(data._id, data.value) + syncOtherWindows( + IpcChannels.SYNC_SETTINGS, + event, + { event: SyncEvents.GENERAL.UPSERT, data } + ) + return null + + default: + // eslint-disable-next-line no-throw-literal + throw 'invalid settings db action' + } + } catch (err) { + if (typeof err === 'string') throw err + else throw err.toString() + } + }) + + // *********** // + // History + ipcMain.handle(IpcChannels.DB_HISTORY, async (event, { action, data }) => { + try { + switch (action) { + case DBActions.GENERAL.FIND: + return await baseHandlers.history.find() + + case DBActions.GENERAL.UPSERT: + await baseHandlers.history.upsert(data) + syncOtherWindows( + IpcChannels.SYNC_HISTORY, + event, + { event: SyncEvents.GENERAL.UPSERT, data } + ) + return null + + case DBActions.HISTORY.UPDATE_WATCH_PROGRESS: + await baseHandlers.history.updateWatchProgress(data.videoId, data.watchProgress) + syncOtherWindows( + IpcChannels.SYNC_HISTORY, + event, + { event: SyncEvents.HISTORY.UPDATE_WATCH_PROGRESS, data } + ) + return null + + case DBActions.GENERAL.DELETE: + await baseHandlers.history.delete(data) + syncOtherWindows( + IpcChannels.SYNC_HISTORY, + event, + { event: SyncEvents.GENERAL.DELETE, data } + ) + return null + + case DBActions.GENERAL.DELETE_ALL: + await baseHandlers.history.deleteAll() + syncOtherWindows( + IpcChannels.SYNC_HISTORY, + event, + { event: SyncEvents.GENERAL.DELETE_ALL } + ) + return null + + case DBActions.GENERAL.PERSIST: + baseHandlers.history.persist() + return null + + default: + // eslint-disable-next-line no-throw-literal + throw 'invalid history db action' + } + } catch (err) { + if (typeof err === 'string') throw err + else throw err.toString() + } + }) + + // *********** // + // Profiles + ipcMain.handle(IpcChannels.DB_PROFILES, async (event, { action, data }) => { + try { + switch (action) { + case DBActions.GENERAL.CREATE: { + const newProfile = await baseHandlers.profiles.create(data) + syncOtherWindows( + IpcChannels.SYNC_PROFILES, + event, + { event: SyncEvents.GENERAL.CREATE, data: newProfile } + ) + return newProfile + } + + case DBActions.GENERAL.FIND: + return await baseHandlers.profiles.find() + + case DBActions.GENERAL.UPSERT: + await baseHandlers.profiles.upsert(data) + syncOtherWindows( + IpcChannels.SYNC_PROFILES, + event, + { event: SyncEvents.GENERAL.UPSERT, data } + ) + return null + + case DBActions.GENERAL.DELETE: + await baseHandlers.profiles.delete(data) + syncOtherWindows( + IpcChannels.SYNC_PROFILES, + event, + { event: SyncEvents.GENERAL.DELETE, data } + ) + return null + + case DBActions.GENERAL.PERSIST: + baseHandlers.profiles.persist() + return null + + default: + // eslint-disable-next-line no-throw-literal + throw 'invalid profile db action' + } + } catch (err) { + if (typeof err === 'string') throw err + else throw err.toString() + } + }) + + // *********** // + // Playlists + // ! NOTE: A lot of these actions are currently not used for anything + // As such, only the currently used actions have synchronization implemented + // The remaining should have it implemented only when playlists + // get fully implemented into the app + ipcMain.handle(IpcChannels.DB_PLAYLISTS, async (event, { action, data }) => { + try { + switch (action) { + case DBActions.GENERAL.CREATE: + await baseHandlers.playlists.create(data) + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + case DBActions.GENERAL.FIND: + return await baseHandlers.playlists.find() + + case DBActions.PLAYLISTS.UPSERT_VIDEO: + await baseHandlers.playlists.upsertVideoByPlaylistName(data.playlistName, data.videoData) + syncOtherWindows( + IpcChannels.SYNC_PLAYLISTS, + event, + { event: SyncEvents.PLAYLISTS.UPSERT_VIDEO, data } + ) + return null + + case DBActions.PLAYLISTS.UPSERT_VIDEO_IDS: + await baseHandlers.playlists.upsertVideoIdsByPlaylistId(data._id, data.videoIds) + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + case DBActions.GENERAL.DELETE: + await baseHandlers.playlists.delete(data) + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + case DBActions.PLAYLISTS.DELETE_VIDEO_ID: + await baseHandlers.playlists.deleteVideoIdByPlaylistName(data.playlistName, data.videoId) + syncOtherWindows( + IpcChannels.SYNC_PLAYLISTS, + event, + { event: SyncEvents.PLAYLISTS.DELETE_VIDEO, data } + ) + return null + + case DBActions.PLAYLISTS.DELETE_VIDEO_IDS: + await baseHandlers.playlists.deleteVideoIdsByPlaylistName(data.playlistName, data.videoIds) + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + case DBActions.PLAYLISTS.DELETE_ALL_VIDEOS: + await baseHandlers.playlists.deleteAllVideosByPlaylistName(data) + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + case DBActions.GENERAL.DELETE_MULTIPLE: + await baseHandlers.playlists.deleteMultiple(data) + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + case DBActions.GENERAL.DELETE_ALL: + await baseHandlers.playlists.deleteAll() + // TODO: Syncing (implement only when it starts being used) + // syncOtherWindows(IpcChannels.SYNC_PLAYLISTS, event, { event: '_', data }) + return null + + default: + // eslint-disable-next-line no-throw-literal + throw 'invalid playlist db action' + } + } catch (err) { + if (typeof err === 'string') throw err + else throw err.toString() + } + }) + + // *********** // + + function syncOtherWindows(channel, event, payload) { + const otherWindows = BrowserWindow.getAllWindows().filter((window) => { + return window.webContents.id !== event.sender.id + }) + + for (const window of otherWindows) { + window.webContents.send(channel, payload) + } + } + + // ************************************************* // + app.once('window-all-closed', () => { // Clear cache and storage if it's the last window session.defaultSession.clearCache() diff --git a/src/renderer/App.js b/src/renderer/App.js index 74975d91..ec2ebd13 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -92,9 +92,6 @@ export default Vue.extend({ return null } }, - activeProfile: function () { - return this.$store.getters.getActiveProfile - }, defaultProfile: function () { return this.$store.getters.getDefaultProfile }, @@ -142,7 +139,7 @@ export default Vue.extend({ if (this.usingElectron) { console.log('User is using Electron') ipcRenderer = require('electron').ipcRenderer - this.setupListenerToSyncWindows() + this.setupListenersToSyncWindows() this.activateKeyboardShortcuts() this.openAllLinksExternally() this.enableOpenUrl() @@ -468,7 +465,7 @@ export default Vue.extend({ 'getExternalPlayerCmdArgumentsData', 'fetchInvidiousInstances', 'setRandomCurrentInvidiousInstance', - 'setupListenerToSyncWindows' + 'setupListenersToSyncWindows' ]) } }) diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js index 5001c581..7963873a 100644 --- a/src/renderer/components/data-settings/data-settings.js +++ b/src/renderer/components/data-settings/data-settings.js @@ -5,6 +5,7 @@ import FtButton from '../ft-button/ft-button.vue' import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtPrompt from '../ft-prompt/ft-prompt.vue' +import { MAIN_PROFILE_ID } from '../../../constants' import fs from 'fs' import { opmlToJSON } from 'opml-to-json' @@ -165,7 +166,7 @@ export default Vue.extend({ message: message }) } else { - if (profileObject.name === 'All Channels' || profileObject._id === 'allChannels') { + if (profileObject.name === 'All Channels' || profileObject._id === MAIN_PROFILE_ID) { primaryProfile.subscriptions = primaryProfile.subscriptions.concat(profileObject.subscriptions) primaryProfile.subscriptions = primaryProfile.subscriptions.filter((sub, index) => { const profileIndex = primaryProfile.subscriptions.findIndex((x) => { diff --git a/src/renderer/components/ft-profile-edit/ft-profile-edit.js b/src/renderer/components/ft-profile-edit/ft-profile-edit.js index 3918120b..bc740fae 100644 --- a/src/renderer/components/ft-profile-edit/ft-profile-edit.js +++ b/src/renderer/components/ft-profile-edit/ft-profile-edit.js @@ -5,6 +5,7 @@ import FtPrompt from '../../components/ft-prompt/ft-prompt.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtInput from '../../components/ft-input/ft-input.vue' import FtButton from '../../components/ft-button/ft-button.vue' +import { MAIN_PROFILE_ID } from '../../../constants' export default Vue.extend({ name: 'FtProfileEdit', @@ -40,6 +41,9 @@ export default Vue.extend({ } }, computed: { + isMainProfile: function () { + return this.profileId === MAIN_PROFILE_ID + }, colorValues: function () { return this.$store.getters.getColorValues }, @@ -70,7 +74,7 @@ export default Vue.extend({ this.profileTextColor = await this.calculateColorLuminance(val) } }, - mounted: async function () { + created: function () { this.profileId = this.$route.params.id this.profileName = this.profile.name this.profileBgColor = this.profile.bgColor @@ -109,9 +113,8 @@ export default Vue.extend({ console.log(profile) - this.updateProfile(profile) - if (this.isNew) { + this.createProfile(profile) this.showToast({ message: this.$t('Profile.Profile has been created') }) @@ -119,6 +122,7 @@ export default Vue.extend({ path: '/settings/profile/' }) } else { + this.updateProfile(profile) this.showToast({ message: this.$t('Profile.Profile has been updated') }) @@ -134,20 +138,22 @@ export default Vue.extend({ }, deleteProfile: function () { + if (this.activeProfile._id === this.profileId) { + this.updateActiveProfile(MAIN_PROFILE_ID) + } + this.removeProfile(this.profileId) + const message = this.$t('Profile.Removed $ from your profiles').replace('$', this.profileName) - this.showToast({ - message: message - }) + this.showToast({ message }) + if (this.defaultProfile === this.profileId) { - this.updateDefaultProfile('allChannels') + this.updateDefaultProfile(MAIN_PROFILE_ID) this.showToast({ message: this.$t('Profile.Your default profile has been changed to your primary profile') }) } - if (this.profileList[this.activeProfile]._id === this.profileId) { - this.updateActiveProfile(0) - } + this.$router.push({ path: '/settings/profile/' }) @@ -155,6 +161,7 @@ export default Vue.extend({ ...mapActions([ 'showToast', + 'createProfile', 'updateProfile', 'removeProfile', 'updateDefaultProfile', diff --git a/src/renderer/components/ft-profile-edit/ft-profile-edit.vue b/src/renderer/components/ft-profile-edit/ft-profile-edit.vue index e6c07270..700d54c3 100644 --- a/src/renderer/components/ft-profile-edit/ft-profile-edit.vue +++ b/src/renderer/components/ft-profile-edit/ft-profile-edit.vue @@ -77,7 +77,7 @@ @click="setDefaultProfile" /> { - return x._id === profile._id - }) + if (this.activeProfile._id !== profile._id) { + const targetProfile = this.profileList.find((x) => { + return x._id === profile._id + }) - if (index === -1) { - return + if (targetProfile) { + this.updateActiveProfile(targetProfile._id) + + const message = this.$t('Profile.$ is now the active profile').replace('$', profile.name) + this.showToast({ message }) + } } - this.updateActiveProfile(index) - const message = this.$t('Profile.$ is now the active profile').replace('$', profile.name) - this.showToast({ - message: message - }) - $('#profileList').focusout() + + $('#profileList').trigger('focusout') }, ...mapActions([ diff --git a/src/renderer/components/ft-profile-selector/ft-profile-selector.vue b/src/renderer/components/ft-profile-selector/ft-profile-selector.vue index e2160877..4e8ccde1 100644 --- a/src/renderer/components/ft-profile-selector/ft-profile-selector.vue +++ b/src/renderer/components/ft-profile-selector/ft-profile-selector.vue @@ -2,13 +2,13 @@
- {{ profileInitials[activeProfile] }} + {{ activeProfileInitial }}
{ - if (profile._id === 'allChannels') { + if (profile._id === MAIN_PROFILE_ID) { const newProfile = { - _id: 'allChannels', + _id: MAIN_PROFILE_ID, name: profile.name, bgColor: profile.bgColor, textColor: profile.textColor, diff --git a/src/renderer/components/proxy-settings/proxy-settings.js b/src/renderer/components/proxy-settings/proxy-settings.js index 7635b486..1f536e17 100644 --- a/src/renderer/components/proxy-settings/proxy-settings.js +++ b/src/renderer/components/proxy-settings/proxy-settings.js @@ -14,6 +14,8 @@ import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import { ipcRenderer } from 'electron' import debounce from 'lodash.debounce' +import { IpcChannels } from '../../../constants' + export default Vue.extend({ name: 'ProxySettings', components: { @@ -111,11 +113,11 @@ export default Vue.extend({ }, enableProxy: function () { - ipcRenderer.send('enableProxy', this.proxyUrl) + ipcRenderer.send(IpcChannels.ENABLE_PROXY, this.proxyUrl) }, disableProxy: function () { - ipcRenderer.send('disableProxy') + ipcRenderer.send(IpcChannels.DISABLE_PROXY) }, testProxy: function () { diff --git a/src/renderer/components/side-nav/side-nav.js b/src/renderer/components/side-nav/side-nav.js index 329f474f..60b81295 100644 --- a/src/renderer/components/side-nav/side-nav.js +++ b/src/renderer/components/side-nav/side-nav.js @@ -25,7 +25,7 @@ export default Vue.extend({ return this.$store.getters.getActiveProfile }, activeSubscriptions: function () { - const profile = JSON.parse(JSON.stringify(this.profileList[this.activeProfile])) + const profile = JSON.parse(JSON.stringify(this.activeProfile)) return profile.subscriptions.sort((a, b) => { const nameA = a.name.toLowerCase() const nameB = b.name.toLowerCase() diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js index e806fd5b..d3c2f4dd 100644 --- a/src/renderer/components/top-nav/top-nav.js +++ b/src/renderer/components/top-nav/top-nav.js @@ -7,6 +7,8 @@ import $ from 'jquery' import debounce from 'lodash.debounce' import ytSuggest from 'youtube-suggest' +import { IpcChannels } from '../../../constants' + export default Vue.extend({ name: 'TopNav', components: { @@ -303,7 +305,7 @@ export default Vue.extend({ createNewWindow: function () { if (this.usingElectron) { const { ipcRenderer } = require('electron') - ipcRenderer.send('createNewWindow') + ipcRenderer.send(IpcChannels.CREATE_NEW_WINDOW) } else { // Web placeholder } diff --git a/src/renderer/components/watch-video-info/watch-video-info.js b/src/renderer/components/watch-video-info/watch-video-info.js index 088bd006..ea0bdcb7 100644 --- a/src/renderer/components/watch-video-info/watch-video-info.js +++ b/src/renderer/components/watch-video-info/watch-video-info.js @@ -6,6 +6,7 @@ import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtIconButton from '../ft-icon-button/ft-icon-button.vue' import FtShareButton from '../ft-share-button/ft-share-button.vue' +import { MAIN_PROFILE_ID } from '../../../constants' export default Vue.extend({ name: 'WatchVideoInfo', @@ -228,7 +229,7 @@ export default Vue.extend({ }, isSubscribed: function () { - const subIndex = this.profileList[this.activeProfile].subscriptions.findIndex((channel) => { + const subIndex = this.activeProfile.subscriptions.findIndex((channel) => { return channel.id === this.channelId }) @@ -322,7 +323,7 @@ export default Vue.extend({ return } - const currentProfile = JSON.parse(JSON.stringify(this.profileList[this.activeProfile])) + const currentProfile = JSON.parse(JSON.stringify(this.activeProfile)) const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0])) if (this.isSubscribed) { @@ -335,13 +336,13 @@ export default Vue.extend({ message: this.$t('Channel.Channel has been removed from your subscriptions') }) - if (this.activeProfile === 0) { + if (this.activeProfile._id === MAIN_PROFILE_ID) { // Check if a subscription exists in a different profile. // Remove from there as well. let duplicateSubscriptions = 0 this.profileList.forEach((profile) => { - if (profile._id === 'allChannels') { + if (profile._id === MAIN_PROFILE_ID) { return } const parsedProfile = JSON.parse(JSON.stringify(profile)) @@ -380,7 +381,7 @@ export default Vue.extend({ message: this.$t('Channel.Added channel to your subscriptions') }) - if (this.activeProfile !== 0) { + if (this.activeProfile._id !== MAIN_PROFILE_ID) { const index = primaryProfile.subscriptions.findIndex((channel) => { return channel.id === this.channelId }) diff --git a/src/renderer/store/datastores.js b/src/renderer/store/datastores.js deleted file mode 100644 index 5a236538..00000000 --- a/src/renderer/store/datastores.js +++ /dev/null @@ -1,47 +0,0 @@ -import Datastore from 'nedb-promises' - -// Initialize all datastores and export their references -// Current dbs: -// `settings.db` -// `profiles.db` -// `playlists.db` -// `history.db` - -let buildFileName = null - -// Check if using Electron -const usingElectron = window?.process?.type === 'renderer' -if (usingElectron) { - const { ipcRenderer } = require('electron') - const userDataPath = ipcRenderer.sendSync('getUserDataPathSync') - buildFileName = (dbName) => userDataPath + '/' + dbName + '.db' -} else { - buildFileName = (dbName) => dbName + '.db' -} - -const settingsDb = Datastore.create({ - filename: buildFileName('settings'), - autoload: true -}) - -const playlistsDb = Datastore.create({ - filename: buildFileName('playlists'), - autoload: true -}) - -const profilesDb = Datastore.create({ - filename: buildFileName('profiles'), - autoload: true -}) - -const historyDb = Datastore.create({ - filename: buildFileName('history'), - autoload: true -}) - -export { - settingsDb, - profilesDb, - playlistsDb, - historyDb -} diff --git a/src/renderer/store/modules/history.js b/src/renderer/store/modules/history.js index ee5ab1f8..cb36636c 100644 --- a/src/renderer/store/modules/history.js +++ b/src/renderer/store/modules/history.js @@ -1,4 +1,4 @@ -import { historyDb } from '../datastores' +import { DBHistoryHandlers } from '../../../datastores/handlers/index' const state = { historyCache: [] @@ -12,80 +12,52 @@ const getters = { const actions = { async grabHistory({ commit }) { - const results = await historyDb.find({}).sort({ timeWatched: -1 }) - commit('setHistoryCache', results) + try { + const results = await DBHistoryHandlers.find() + commit('setHistoryCache', results) + } catch (errMessage) { + console.error(errMessage) + } }, - async updateHistory({ commit, dispatch, state }, entry) { - await historyDb.update( - { videoId: entry.videoId }, - entry, - { upsert: true } - ) - - const entryIndex = state.historyCache.findIndex((currentEntry) => { - return entry.videoId === currentEntry.videoId - }) - - entryIndex === -1 - ? commit('insertNewEntryToHistoryCache', entry) - : commit('hoistEntryToTopOfHistoryCache', { - currentIndex: entryIndex, - updatedEntry: entry - }) - - dispatch('propagateHistory') + async updateHistory({ commit }, record) { + try { + await DBHistoryHandlers.upsert(record) + commit('upsertToHistoryCache', record) + } catch (errMessage) { + console.error(errMessage) + } }, - async removeFromHistory({ commit, dispatch }, videoId) { - await historyDb.remove({ videoId: videoId }) - - const updatedCache = state.historyCache.filter((entry) => { - return entry.videoId !== videoId - }) - - commit('setHistoryCache', updatedCache) - - dispatch('propagateHistory') + async removeFromHistory({ commit }, videoId) { + try { + await DBHistoryHandlers.delete(videoId) + commit('removeFromHistoryCacheById', videoId) + } catch (errMessage) { + console.error(errMessage) + } }, - async removeAllHistory({ commit, dispatch }) { - await historyDb.remove({}, { multi: true }) - commit('setHistoryCache', []) - dispatch('propagateHistory') + async removeAllHistory({ commit }) { + try { + await DBHistoryHandlers.deleteAll() + commit('setHistoryCache', []) + } catch (errMessage) { + console.error(errMessage) + } }, - async updateWatchProgress({ commit, dispatch }, entry) { - await historyDb.update( - { videoId: entry.videoId }, - { $set: { watchProgress: entry.watchProgress } }, - { upsert: true } - ) - - const entryIndex = state.historyCache.findIndex((currentEntry) => { - return entry.videoId === currentEntry.videoId - }) - - commit('updateEntryWatchProgressInHistoryCache', { - index: entryIndex, - value: entry.watchProgress - }) - - dispatch('propagateHistory') - }, - - propagateHistory({ getters: { getUsingElectron: usingElectron } }) { - if (usingElectron) { - const { ipcRenderer } = require('electron') - ipcRenderer.send('syncWindows', { - type: 'history', - data: state.historyCache - }) + async updateWatchProgress({ commit }, { videoId, watchProgress }) { + try { + await DBHistoryHandlers.updateWatchProgress(videoId, watchProgress) + commit('updateRecordWatchProgressInHistoryCache', { videoId, watchProgress }) + } catch (errMessage) { + console.error(errMessage) } }, compactHistory(_) { - historyDb.persistence.compactDatafile() + DBHistoryHandlers.persist() } } @@ -94,17 +66,42 @@ const mutations = { state.historyCache = historyCache }, - insertNewEntryToHistoryCache(state, entry) { - state.historyCache.unshift(entry) - }, - hoistEntryToTopOfHistoryCache(state, { currentIndex, updatedEntry }) { state.historyCache.splice(currentIndex, 1) state.historyCache.unshift(updatedEntry) }, - updateEntryWatchProgressInHistoryCache(state, { index, value }) { - state.historyCache[index].watchProgress = value + upsertToHistoryCache(state, record) { + const i = state.historyCache.findIndex((currentRecord) => { + return record.videoId === currentRecord.videoId + }) + + if (i !== -1) { + // Already in cache + // Must be hoisted to top, remove it and then unshift it + state.historyCache.splice(i, 1) + } + + state.historyCache.unshift(record) + }, + + updateRecordWatchProgressInHistoryCache(state, { videoId, watchProgress }) { + const i = state.historyCache.findIndex((currentRecord) => { + return currentRecord.videoId === videoId + }) + + const targetRecord = Object.assign({}, state.historyCache[i]) + targetRecord.watchProgress = watchProgress + state.historyCache.splice(i, 1, targetRecord) + }, + + removeFromHistoryCacheById(state, videoId) { + for (let i = 0; i < state.historyCache.length; i++) { + if (state.historyCache[i].videoId === videoId) { + state.historyCache.splice(i, 1) + break + } + } } } diff --git a/src/renderer/store/modules/playlists.js b/src/renderer/store/modules/playlists.js index 2e587906..8bd9de51 100644 --- a/src/renderer/store/modules/playlists.js +++ b/src/renderer/store/modules/playlists.js @@ -1,4 +1,4 @@ -import { playlistsDb } from '../datastores' +import { DBPlaylistHandlers } from '../../../datastores/handlers/index' const state = { playlists: [ @@ -25,89 +25,111 @@ const getters = { const actions = { async addPlaylist({ commit }, payload) { - await playlistsDb.insert(payload) - commit('addPlaylist', payload) + try { + await DBPlaylistHandlers.create(payload) + commit('addPlaylist', payload) + } catch (errMessage) { + console.error(errMessage) + } }, async addPlaylists({ commit }, payload) { - await playlistsDb.insert(payload) - commit('addPlaylists', payload) + try { + await DBPlaylistHandlers.create(payload) + commit('addPlaylists', payload) + } catch (errMessage) { + console.error(errMessage) + } }, async addVideo({ commit }, payload) { - await playlistsDb.update( - { playlistName: payload.playlistName }, - { $push: { videos: payload.videoData } }, - { upsert: true } - ) - commit('addVideo', payload) + try { + const { playlistName, videoData } = payload + await DBPlaylistHandlers.upsertVideoByPlaylistName(playlistName, videoData) + commit('addVideo', payload) + } catch (errMessage) { + console.error(errMessage) + } }, async addVideos({ commit }, payload) { - await playlistsDb.update( - { _id: payload.playlistId }, - { $push: { videos: { $each: payload.videosIds } } }, - { upsert: true } - ) - commit('addVideos', payload) + try { + const { playlistId, videoIds } = payload + await DBPlaylistHandlers.upsertVideoIdsByPlaylistId(playlistId, videoIds) + commit('addVideos', payload) + } catch (errMessage) { + console.error(errMessage) + } }, - async grabAllPlaylists({ commit, dispatch }) { - const payload = await playlistsDb.find({}) - if (payload.length === 0) { - commit('setAllPlaylists', state.playlists) - dispatch('addPlaylists', payload) - } else { - commit('setAllPlaylists', payload) + async grabAllPlaylists({ commit, dispatch, state }) { + try { + const payload = await DBPlaylistHandlers.find() + if (payload.length === 0) { + commit('setAllPlaylists', state.playlists) + dispatch('addPlaylists', payload) + } else { + commit('setAllPlaylists', payload) + } + } catch (errMessage) { + console.error(errMessage) } }, async removeAllPlaylists({ commit }) { - await playlistsDb.remove({ protected: { $ne: true } }) - commit('removeAllPlaylists') + try { + await DBPlaylistHandlers.deleteAll() + commit('removeAllPlaylists') + } catch (errMessage) { + console.error(errMessage) + } }, async removeAllVideos({ commit }, playlistName) { - await playlistsDb.update( - { playlistName: playlistName }, - { $set: { videos: [] } }, - { upsert: true } - ) - commit('removeAllVideos', playlistName) + try { + await DBPlaylistHandlers.deleteAllVideosByPlaylistName(playlistName) + commit('removeAllVideos', playlistName) + } catch (errMessage) { + console.error(errMessage) + } }, async removePlaylist({ commit }, playlistId) { - await playlistsDb.remove({ - _id: playlistId, - protected: { $ne: true } - }) - commit('removePlaylist', playlistId) + try { + await DBPlaylistHandlers.delete(playlistId) + commit('removePlaylist', playlistId) + } catch (errMessage) { + console.error(errMessage) + } }, async removePlaylists({ commit }, playlistIds) { - await playlistsDb.remove({ - _id: { $in: playlistIds }, - protected: { $ne: true } - }) - commit('removePlaylists', playlistIds) + try { + await DBPlaylistHandlers.deleteMultiple(playlistIds) + commit('removePlaylists', playlistIds) + } catch (errMessage) { + console.error(errMessage) + } }, async removeVideo({ commit }, payload) { - await playlistsDb.update( - { playlistName: payload.playlistName }, - { $pull: { videos: { videoId: payload.videoId } } }, - { upsert: true } - ) - commit('removeVideo', payload) + try { + const { playlistName, videoId } = payload + await DBPlaylistHandlers.deleteVideoIdByPlaylistName(playlistName, videoId) + commit('removeVideo', payload) + } catch (errMessage) { + console.error(errMessage) + } }, async removeVideos({ commit }, payload) { - await playlistsDb.update( - { _id: payload.playlistName }, - { $pull: { videos: { $in: payload.videoId } } }, - { upsert: true } - ) - commit('removeVideos', payload) + try { + const { playlistName, videoIds } = payload + await DBPlaylistHandlers.deleteVideoIdsByPlaylistName(playlistName, videoIds) + commit('removeVideos', payload) + } catch (errMessage) { + console.error(errMessage) + } } } diff --git a/src/renderer/store/modules/profiles.js b/src/renderer/store/modules/profiles.js index 8b467d8d..ed69e53c 100644 --- a/src/renderer/store/modules/profiles.js +++ b/src/renderer/store/modules/profiles.js @@ -1,14 +1,15 @@ -import { profilesDb } from '../datastores' +import { MAIN_PROFILE_ID } from '../../../constants' +import { DBProfileHandlers } from '../../../datastores/handlers/index' const state = { profileList: [{ - _id: 'allChannels', + _id: MAIN_PROFILE_ID, name: 'All Channels', bgColor: '#000000', textColor: '#FFFFFF', subscriptions: [] }], - activeProfile: 0 + activeProfile: MAIN_PROFILE_ID } const getters = { @@ -16,94 +17,111 @@ const getters = { return state.profileList }, - getActiveProfile: () => { - return state.activeProfile + getActiveProfile: (state) => { + const activeProfileId = state.activeProfile + return state.profileList.find((profile) => { + return profile._id === activeProfileId + }) + }, + + profileById: (state) => (id) => { + const profile = state.profileList.find(p => p._id === id) + return profile } } +function profileSort(a, b) { + if (a._id === MAIN_PROFILE_ID) return -1 + if (b._id === MAIN_PROFILE_ID) return 1 + if (a.name < b.name) return -1 + if (a.name > b.name) return 1 + return 0 +} + const actions = { async grabAllProfiles({ rootState, dispatch, commit }, defaultName = null) { - let profiles = await profilesDb.find({}) - if (profiles.length === 0) { - dispatch('createDefaultProfile', defaultName) + let profiles + try { + profiles = await DBProfileHandlers.find() + } catch (errMessage) { + console.error(errMessage) return } + + if (!Array.isArray(profiles)) return + + if (profiles.length === 0) { + // Create a default profile and persist it + const randomColor = await dispatch('getRandomColor') + const textColor = await dispatch('calculateColorLuminance', randomColor) + const defaultProfile = { + _id: MAIN_PROFILE_ID, + name: defaultName, + bgColor: randomColor, + textColor: textColor, + subscriptions: [] + } + + try { + await DBProfileHandlers.create(defaultProfile) + commit('setProfileList', [defaultProfile]) + } catch (errMessage) { + console.error(errMessage) + } + + return + } + // We want the primary profile to always be first // So sort with that then sort alphabetically by profile name - profiles = profiles.sort((a, b) => { - if (a._id === 'allChannels') { - return -1 - } - - if (b._id === 'allChannels') { - return 1 - } - - return b.name - a.name - }) + profiles = profiles.sort(profileSort) if (state.profileList.length < profiles.length) { - const profileIndex = profiles.findIndex((profile) => { + const profile = profiles.find((profile) => { return profile._id === rootState.settings.defaultProfile }) - if (profileIndex !== -1) { - commit('setActiveProfile', profileIndex) + if (profile) { + commit('setActiveProfile', profile._id) } } commit('setProfileList', profiles) }, - async grabProfileInfo(_, profileId) { - console.log(profileId) - return await profilesDb.findOne({ _id: profileId }) - }, - - async createDefaultProfile({ dispatch }, defaultName) { - const randomColor = await dispatch('getRandomColor') - const textColor = await dispatch('calculateColorLuminance', randomColor) - const defaultProfile = { - _id: 'allChannels', - name: defaultName, - bgColor: randomColor, - textColor: textColor, - subscriptions: [] + async createProfile({ commit }, profile) { + try { + const newProfile = await DBProfileHandlers.create(profile) + commit('addProfileToList', newProfile) + } catch (errMessage) { + console.error(errMessage) } - - await profilesDb.update( - { _id: 'allChannels' }, - defaultProfile, - { upsert: true } - ) - dispatch('grabAllProfiles') }, - async updateProfile({ dispatch }, profile) { - await profilesDb.update( - { _id: profile._id }, - profile, - { upsert: true } - ) - dispatch('grabAllProfiles') + async updateProfile({ commit }, profile) { + try { + await DBProfileHandlers.upsert(profile) + commit('upsertProfileToList', profile) + } catch (errMessage) { + console.error(errMessage) + } }, - async insertProfile({ dispatch }, profile) { - await profilesDb.insert(profile) - dispatch('grabAllProfiles') - }, - - async removeProfile({ dispatch }, profileId) { - await profilesDb.remove({ _id: profileId }) - dispatch('grabAllProfiles') + async removeProfile({ commit }, profileId) { + try { + await DBProfileHandlers.delete(profileId) + commit('removeProfileFromList', profileId) + } catch (errMessage) { + console.error(errMessage) + } }, compactProfiles(_) { - profilesDb.persistence.compactDatafile() + DBProfileHandlers.persist() }, - updateActiveProfile({ commit }, index) { - commit('setActiveProfile', index) + updateActiveProfile({ commit }, id) { + commit('setActiveProfile', id) } } @@ -111,8 +129,36 @@ const mutations = { setProfileList(state, profileList) { state.profileList = profileList }, + setActiveProfile(state, activeProfile) { state.activeProfile = activeProfile + }, + + addProfileToList(state, profile) { + state.profileList.push(profile) + state.profileList.sort(profileSort) + }, + + upsertProfileToList(state, updatedProfile) { + const i = state.profileList.findIndex((p) => { + return p._id === updatedProfile._id + }) + + if (i === -1) { + state.profileList.push(updatedProfile) + } else { + state.profileList.splice(i, 1, updatedProfile) + } + + state.profileList.sort(profileSort) + }, + + removeProfileFromList(state, profileId) { + const i = state.profileList.findIndex((profile) => { + return profile._id === profileId + }) + + state.profileList.splice(i, 1) } } diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js index ab26b1ad..6b9140a3 100644 --- a/src/renderer/store/modules/settings.js +++ b/src/renderer/store/modules/settings.js @@ -1,5 +1,6 @@ -import { settingsDb } from '../datastores' import i18n from '../../i18n/index' +import { MAIN_PROFILE_ID, IpcChannels, SyncEvents } from '../../../constants' +import { DBSettingHandlers } from '../../../datastores/handlers/index' /* * Due to the complexity of the settings module in FreeTube, a more @@ -170,7 +171,7 @@ const state = { defaultCaptionSettings: '{}', defaultInterval: 5, defaultPlayback: 1, - defaultProfile: 'allChannels', + defaultProfile: MAIN_PROFILE_ID, defaultQuality: '720', defaultSkipInterval: 5, defaultTheatreMode: false, @@ -312,29 +313,29 @@ Object.assign(customGetters, { const customActions = { grabUserSettings: async ({ commit, dispatch, getters }) => { - const userSettings = await settingsDb.find({ - _id: { $ne: 'bounds' } - }) + try { + const userSettings = await DBSettingHandlers.find() + for (const setting of userSettings) { + const { _id, value } = setting + if (getters.settingHasSideEffects(_id)) { + dispatch(defaultSideEffectsTriggerId(_id), value) + } - for (const setting of userSettings) { - const { _id, value } = setting - if (getters.settingHasSideEffects(_id)) { - dispatch(defaultSideEffectsTriggerId(_id), value) + commit(defaultMutationId(_id), value) } - - commit(defaultMutationId(_id), value) + } catch (errMessage) { + console.error(errMessage) } }, // Should be a root action, but we'll tolerate - setupListenerToSyncWindows: ({ commit, dispatch, getters }) => { + setupListenersToSyncWindows: ({ commit, dispatch, getters }) => { // Already known to be Electron, no need to check const { ipcRenderer } = require('electron') - ipcRenderer.on('syncWindows', (_, payload) => { - const { type, data } = payload - switch (type) { - case 'setting': - // `data` is a single setting => { _id, value } + + ipcRenderer.on(IpcChannels.SYNC_SETTINGS, (_, { event, data }) => { + switch (event) { + case SyncEvents.GENERAL.UPSERT: if (getters.settingHasSideEffects(data._id)) { dispatch(defaultSideEffectsTriggerId(data._id), data.value) } @@ -342,18 +343,65 @@ const customActions = { commit(defaultMutationId(data._id), data.value) break - case 'history': - // `data` is the whole history => Array of history entries - commit('setHistoryCache', data) + default: + console.error('settings: invalid sync event received') + } + }) + + ipcRenderer.on(IpcChannels.SYNC_HISTORY, (_, { event, data }) => { + switch (event) { + case SyncEvents.GENERAL.UPSERT: + commit('upsertToHistoryCache', data) break - case 'playlist': - // TODO: Not implemented + case SyncEvents.HISTORY.UPDATE_WATCH_PROGRESS: + commit('updateRecordWatchProgressInHistoryCache', data) break - case 'profile': - // TODO: Not implemented + case SyncEvents.GENERAL.DELETE: + commit('removeFromHistoryCacheById', data) break + + case SyncEvents.GENERAL.DELETE_ALL: + commit('setHistoryCache', []) + break + + default: + console.error('history: invalid sync event received') + } + }) + + ipcRenderer.on(IpcChannels.SYNC_PROFILES, (_, { event, data }) => { + switch (event) { + case SyncEvents.GENERAL.CREATE: + commit('addProfileToList', data) + break + + case SyncEvents.GENERAL.UPSERT: + commit('upsertProfileToList', data) + break + + case SyncEvents.GENERAL.DELETE: + commit('removeProfileFromList', data) + break + + default: + console.error('profiles: invalid sync event received') + } + }) + + ipcRenderer.on(IpcChannels.SYNC_PLAYLISTS, (_, { event, data }) => { + switch (event) { + case SyncEvents.PLAYLISTS.UPSERT_VIDEO: + commit('addVideo', data) + break + + case SyncEvents.PLAYLISTS.DELETE_VIDEO: + commit('removeVideo', data) + break + + default: + console.error('playlists: invalid sync event received') } }) } @@ -398,30 +446,16 @@ for (const settingId of Object.keys(state)) { } actions[updaterId] = async ({ commit, dispatch, getters }, value) => { - await settingsDb.update( - { _id: settingId }, - { _id: settingId, value: value }, - { upsert: true } - ) + try { + await DBSettingHandlers.upsert(settingId, value) - const { - getUsingElectron: usingElectron, - settingHasSideEffects - } = getters + if (getters.settingHasSideEffects(settingId)) { + dispatch(triggerId, value) + } - if (settingHasSideEffects(settingId)) { - dispatch(triggerId, value) - } - commit(mutationId, value) - - if (usingElectron) { - const { ipcRenderer } = require('electron') - - // Propagate settings to all other existing windows - ipcRenderer.send('syncWindows', { - type: 'setting', - data: { _id: settingId, value: value } - }) + commit(mutationId, value) + } catch (errMessage) { + console.error(errMessage) } } } diff --git a/src/renderer/store/modules/subscriptions.js b/src/renderer/store/modules/subscriptions.js index 6bbf944c..486a2e10 100644 --- a/src/renderer/store/modules/subscriptions.js +++ b/src/renderer/store/modules/subscriptions.js @@ -1,7 +1,9 @@ +import { MAIN_PROFILE_ID } from '../../../constants' + const state = { allSubscriptionsList: [], profileSubscriptions: { - activeProfile: 0, + activeProfile: MAIN_PROFILE_ID, videoList: [] } } diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js index 2be086a4..df01e80e 100644 --- a/src/renderer/store/modules/utils.js +++ b/src/renderer/store/modules/utils.js @@ -1,6 +1,9 @@ import IsEqual from 'lodash.isequal' import FtToastEvents from '../../components/ft-toast/ft-toast-events' import fs from 'fs' + +import { IpcChannels } from '../../../constants' + const state = { isSideNavOpen: false, sessionSearchHistory: [], @@ -166,7 +169,7 @@ const actions = { const usingElectron = rootState.settings.usingElectron if (usingElectron) { const ipcRenderer = require('electron').ipcRenderer - ipcRenderer.send('openExternalLink', url) + ipcRenderer.send(IpcChannels.OPEN_EXTERNAL_LINK, url) } else { // Web placeholder } @@ -179,25 +182,25 @@ const actions = { } } - return (await invokeIRC(context, 'getSystemLocale', webCbk)) || 'en-US' + return (await invokeIRC(context, IpcChannels.GET_SYSTEM_LOCALE, webCbk)) || 'en-US' }, async showOpenDialog (context, options) { // TODO: implement showOpenDialog web compatible callback const webCbk = () => null - return await invokeIRC(context, 'showOpenDialog', webCbk, options) + return await invokeIRC(context, IpcChannels.SHOW_OPEN_DIALOG, webCbk, options) }, async showSaveDialog (context, options) { // TODO: implement showSaveDialog web compatible callback const webCbk = () => null - return await invokeIRC(context, 'showSaveDialog', webCbk, options) + return await invokeIRC(context, IpcChannels.SHOW_SAVE_DIALOG, webCbk, options) }, async getUserDataPath (context) { // TODO: implement getUserDataPath web compatible callback const webCbk = () => null - return await invokeIRC(context, 'getUserDataPath', webCbk) + return await invokeIRC(context, IpcChannels.GET_USER_DATA_PATH, webCbk) }, updateShowProgressBar ({ commit }, value) { @@ -853,10 +856,7 @@ const actions = { console.log(executable, args) const { ipcRenderer } = require('electron') - ipcRenderer.send('openInExternalPlayer', { - executable, - args - }) + ipcRenderer.send(IpcChannels.OPEN_IN_EXTERNAL_PLAYER, { executable, args }) } } diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 9ae2f556..58131c7f 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -11,6 +11,7 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue' import ytch from 'yt-channel-info' import autolinker from 'autolinker' +import { MAIN_PROFILE_ID } from '../../../constants' export default Vue.extend({ name: 'Search', @@ -90,7 +91,7 @@ export default Vue.extend({ }, isSubscribed: function () { - const subIndex = this.profileList[this.activeProfile].subscriptions.findIndex((channel) => { + const subIndex = this.activeProfile.subscriptions.findIndex((channel) => { return channel.id === this.id }) @@ -508,7 +509,7 @@ export default Vue.extend({ }, handleSubscription: function () { - const currentProfile = JSON.parse(JSON.stringify(this.profileList[this.activeProfile])) + const currentProfile = JSON.parse(JSON.stringify(this.activeProfile)) const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0])) if (this.isSubscribed) { @@ -521,13 +522,13 @@ export default Vue.extend({ message: this.$t('Channel.Channel has been removed from your subscriptions') }) - if (this.activeProfile === 0) { + if (this.activeProfile === MAIN_PROFILE_ID) { // Check if a subscription exists in a different profile. // Remove from there as well. let duplicateSubscriptions = 0 this.profileList.forEach((profile) => { - if (profile._id === 'allChannels') { + if (profile._id === MAIN_PROFILE_ID) { return } const parsedProfile = JSON.parse(JSON.stringify(profile)) @@ -566,7 +567,7 @@ export default Vue.extend({ message: this.$t('Channel.Added channel to your subscriptions') }) - if (this.activeProfile !== 0) { + if (this.activeProfile !== MAIN_PROFILE_ID) { const index = primaryProfile.subscriptions.findIndex((channel) => { return channel.id === this.id }) diff --git a/src/renderer/views/ProfileEdit/ProfileEdit.js b/src/renderer/views/ProfileEdit/ProfileEdit.js index 7b6bccb0..1ffb531b 100644 --- a/src/renderer/views/ProfileEdit/ProfileEdit.js +++ b/src/renderer/views/ProfileEdit/ProfileEdit.js @@ -1,9 +1,10 @@ import Vue from 'vue' -import { mapActions } from 'vuex' +import { mapActions, mapGetters } from 'vuex' import FtLoader from '../../components/ft-loader/ft-loader.vue' import FtProfileEdit from '../../components/ft-profile-edit/ft-profile-edit.vue' import FtProfileChannelList from '../../components/ft-profile-channel-list/ft-profile-channel-list.vue' import FtProfileFilterChannelsList from '../../components/ft-profile-filter-channels-list/ft-profile-filter-channels-list.vue' +import { MAIN_PROFILE_ID } from '../../../constants' export default Vue.extend({ name: 'ProfileEdit', @@ -15,40 +16,43 @@ export default Vue.extend({ }, data: function () { return { - isLoading: false, + isLoading: true, isNew: false, profileId: '', profile: {} } }, computed: { + ...mapGetters([ + 'profileById' + ]), + profileList: function () { return this.$store.getters.getProfileList }, + isMainProfile: function () { - return this.profileId === 'allChannels' + return this.profileId === MAIN_PROFILE_ID } }, watch: { profileList: { handler: function () { - this.grabProfileInfo(this.profileId).then((profile) => { - if (profile === null) { - this.showToast({ - message: this.$t('Profile.Profile could not be found') - }) - this.$router.push({ - path: '/settings/profile/' - }) - } - this.profile = profile - }) + const profile = this.profileById(this.profileId) + if (!profile) { + this.showToast({ + message: this.$t('Profile.Profile could not be found') + }) + this.$router.push({ + path: '/settings/profile/' + }) + } + this.profile = profile }, deep: true } }, mounted: async function () { - this.isLoading = true const profileType = this.$route.name this.deletePromptLabel = `${this.$t('Profile.Are you sure you want to delete this profile?')} ${this.$t('Profile["All subscriptions will also be deleted."]')}` @@ -63,29 +67,27 @@ export default Vue.extend({ textColor: textColor, subscriptions: [] } - this.isLoading = false } else { this.isNew = false this.profileId = this.$route.params.id - this.grabProfileInfo(this.profileId).then((profile) => { - if (profile === null) { - this.showToast({ - message: this.$t('Profile.Profile could not be found') - }) - this.$router.push({ - path: '/settings/profile/' - }) - } - this.profile = profile - this.isLoading = false - }) + const profile = this.profileById(this.profileId) + if (!profile) { + this.showToast({ + message: this.$t('Profile.Profile could not be found') + }) + this.$router.push({ + path: '/settings/profile/' + }) + } + this.profile = profile } + + this.isLoading = false }, methods: { ...mapActions([ 'showToast', - 'grabProfileInfo', 'getRandomColor', 'calculateColorLuminance' ]) diff --git a/src/renderer/views/Subscriptions/Subscriptions.js b/src/renderer/views/Subscriptions/Subscriptions.js index 619107cc..660fd0e8 100644 --- a/src/renderer/views/Subscriptions/Subscriptions.js +++ b/src/renderer/views/Subscriptions/Subscriptions.js @@ -9,6 +9,7 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue' import ytch from 'yt-channel-info' import Parser from 'rss-parser' +import { MAIN_PROFILE_ID } from '../../../constants' export default Vue.extend({ name: 'Subscriptions', @@ -81,11 +82,11 @@ export default Vue.extend({ }, activeSubscriptionList: function () { - return this.profileList[this.activeProfile].subscriptions + return this.activeProfile.subscriptions } }, watch: { - activeProfile: async function (val) { + activeProfile: async function (_) { this.getProfileSubscriptions() } }, @@ -97,7 +98,7 @@ export default Vue.extend({ } if (this.profileSubscriptions.videoList.length !== 0) { - if (this.profileSubscriptions.activeProfile === this.activeProfile) { + if (this.profileSubscriptions.activeProfile === this.activeProfile._id) { const subscriptionList = JSON.parse(JSON.stringify(this.profileSubscriptions)) if (this.hideWatchedSubs) { this.videoList = await Promise.all(subscriptionList.videoList.filter((video) => { @@ -172,7 +173,7 @@ export default Vue.extend({ })) const profileSubscriptions = { - activeProfile: this.activeProfile, + activeProfile: this.activeProfile._id, videoList: videoList } @@ -191,7 +192,7 @@ export default Vue.extend({ this.isLoading = false this.updateShowProgressBar(false) - if (this.activeProfile === 0) { + if (this.activeProfile === MAIN_PROFILE_ID) { this.updateAllSubscriptionsList(profileSubscriptions.videoList) } }