diff --git a/src/constants.js b/src/constants.js index ba55e79b..71febf58 100644 --- a/src/constants.js +++ b/src/constants.js @@ -36,7 +36,8 @@ const DBActions = { }, HISTORY: { - UPDATE_WATCH_PROGRESS: 'db-action-history-update-watch-progress' + UPDATE_WATCH_PROGRESS: 'db-action-history-update-watch-progress', + SEARCH: 'db-action-history-search' }, PLAYLISTS: { diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js index 90b9d183..a968bebe 100644 --- a/src/datastores/handlers/base.js +++ b/src/datastores/handlers/base.js @@ -38,6 +38,11 @@ class History { return db.history.find({}).sort({ timeWatched: -1 }) } + static search(query) { + const re = new RegExp(query, 'i') + return db.history.find({ $or: [{ author: { $regex: re } }, { title: { $regex: re } }] }).sort({ timeWatched: -1 }) + } + static upsert(record) { return db.history.update({ videoId: record.videoId }, record, { upsert: true }) } diff --git a/src/datastores/handlers/electron.js b/src/datastores/handlers/electron.js index a054d7b5..96abc990 100644 --- a/src/datastores/handlers/electron.js +++ b/src/datastores/handlers/electron.js @@ -25,6 +25,13 @@ class History { ) } + static search(query) { + return ipcRenderer.invoke( + IpcChannels.DB_HISTORY, + { action: DBActions.HISTORY.SEARCH, data: query } + ) + } + static upsert(record) { return ipcRenderer.invoke( IpcChannels.DB_HISTORY, diff --git a/src/datastores/handlers/web.js b/src/datastores/handlers/web.js index bf7ee41f..3ec50213 100644 --- a/src/datastores/handlers/web.js +++ b/src/datastores/handlers/web.js @@ -25,6 +25,10 @@ class History { return baseHandlers.history.find() } + static search(query) { + return baseHandlers.history.search(query) + } + static upsert(record) { return baseHandlers.history.upsert(record) } diff --git a/src/datastores/index.js b/src/datastores/index.js index 713a0ea3..879ec199 100644 --- a/src/datastores/index.js +++ b/src/datastores/index.js @@ -18,4 +18,7 @@ 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 }) +db.history.ensureIndex({ fieldName: 'author' }) +db.history.ensureIndex({ fieldName: 'title' }) +db.history.ensureIndex({ fieldName: 'videoId' }) export default db diff --git a/src/main/index.js b/src/main/index.js index c594cfbd..e48a2d70 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -445,6 +445,9 @@ function runApp() { ) return null + case DBActions.HISTORY.SEARCH: + return await baseHandlers.history.search(data) + case DBActions.GENERAL.DELETE: await baseHandlers.history.delete(data) syncOtherWindows( 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 b114947d..b6f47767 100644 --- a/src/renderer/components/ft-list-video/ft-list-video.js +++ b/src/renderer/components/ft-list-video/ft-list-video.js @@ -208,6 +208,12 @@ export default Vue.extend({ return this.$store.getters.getSaveWatchedProgress } }, + watch: { + data: function () { + this.parseVideoData() + this.checkIfWatched() + } + }, mounted: function () { this.parseVideoData() this.checkIfWatched() diff --git a/src/renderer/store/datastores.js b/src/renderer/store/datastores.js new file mode 100644 index 00000000..3f1bd234 --- /dev/null +++ b/src/renderer/store/datastores.js @@ -0,0 +1,51 @@ +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 +}) + +historyDb.ensureIndex({ fieldName: 'author' }) +historyDb.ensureIndex({ fieldName: 'title' }) +historyDb.ensureIndex({ fieldName: 'videoId' }) + +export { + settingsDb, + profilesDb, + playlistsDb, + historyDb +} diff --git a/src/renderer/store/modules/history.js b/src/renderer/store/modules/history.js index cb36636c..e6184dd4 100644 --- a/src/renderer/store/modules/history.js +++ b/src/renderer/store/modules/history.js @@ -1,12 +1,16 @@ import { DBHistoryHandlers } from '../../../datastores/handlers/index' const state = { - historyCache: [] + historyCache: [], + searchHistoryCache: [] } const getters = { getHistoryCache: () => { return state.historyCache + }, + getSearchHistoryCache: () => { + return state.searchHistoryCache } } @@ -47,6 +51,15 @@ const actions = { } }, + async searchHistory({ commit }, query) { + try { + const results = await DBHistoryHandlers.search(query) + commit('setSearchHistoryCache', results) + } catch (errMessage) { + console.error(errMessage) + } + }, + async updateWatchProgress({ commit }, { videoId, watchProgress }) { try { await DBHistoryHandlers.updateWatchProgress(videoId, watchProgress) @@ -66,6 +79,10 @@ const mutations = { state.historyCache = historyCache }, + setSearchHistoryCache(state, result) { + state.searchHistoryCache = result + }, + hoistEntryToTopOfHistoryCache(state, { currentIndex, updatedEntry }) { state.historyCache.splice(currentIndex, 1) state.historyCache.unshift(updatedEntry) diff --git a/src/renderer/store/modules/playlists.js b/src/renderer/store/modules/playlists.js index 8bd9de51..984f352c 100644 --- a/src/renderer/store/modules/playlists.js +++ b/src/renderer/store/modules/playlists.js @@ -13,14 +13,20 @@ const state = { removeOnWatched: true, videos: [] } - ] + ], + searchPlaylistCache: { + videos: [] + } } const getters = { getAllPlaylists: () => state.playlists, getFavorites: () => state.playlists[0], getPlaylist: (playlistId) => state.playlists.find(playlist => playlist._id === playlistId), - getWatchLater: () => state.playlists[1] + getWatchLater: () => state.playlists[1], + getSearchPlaylistCache: () => { + return state.searchPlaylistCache + } } const actions = { @@ -130,10 +136,25 @@ const actions = { } catch (errMessage) { console.error(errMessage) } + }, + async searchFavoritePlaylist({ commit }, query) { + const re = new RegExp(query, 'i') + // filtering in the frontend because the documents are the playlists and not the videos + const results = state.playlists[0].videos.slice() + .filter((video) => { + return video.author.match(re) || + video.title.match(re) + }) + commit('setPlaylistCache', results) } } const mutations = { + setPlaylistCache(state, result) { + state.searchPlaylistCache = { + videos: result + } + }, addPlaylist(state, payload) { state.playlists.push(payload) }, diff --git a/src/renderer/views/History/History.js b/src/renderer/views/History/History.js index 90414add..a7e6834a 100644 --- a/src/renderer/views/History/History.js +++ b/src/renderer/views/History/History.js @@ -4,6 +4,7 @@ import FtCard from '../../components/ft-card/ft-card.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtElementList from '../../components/ft-element-list/ft-element-list.vue' import FtButton from '../../components/ft-button/ft-button.vue' +import FtInput from '../../components/ft-input/ft-input.vue' export default Vue.extend({ name: 'History', @@ -12,17 +13,23 @@ export default Vue.extend({ 'ft-card': FtCard, 'ft-flex-box': FtFlexBox, 'ft-element-list': FtElementList, - 'ft-button': FtButton + 'ft-button': FtButton, + 'ft-input': FtInput }, data: function () { return { isLoading: false, - dataLimit: 100 + dataLimit: 100, + hasQuery: false } }, computed: { historyCache: function () { - return this.$store.getters.getHistoryCache + if (!this.hasQuery) { + return this.$store.getters.getHistoryCache + } else { + return this.$store.getters.getSearchHistoryCache + } }, activeData: function () { @@ -33,14 +40,7 @@ export default Vue.extend({ } } }, - watch: { - historyCache() { - this.isLoading = true - setTimeout(() => { - this.isLoading = false - }, 100) - } - }, + mounted: function () { console.log(this.historyCache) @@ -54,6 +54,16 @@ export default Vue.extend({ increaseLimit: function () { this.dataLimit += 100 sessionStorage.setItem('historyLimit', this.dataLimit) + }, + filterHistory: function(query) { + this.hasQuery = query !== '' + this.$store.dispatch('searchHistory', query) + }, + load: function() { + this.isLoading = true + setTimeout(() => { + this.isLoading = false + }, 100) } } }) diff --git a/src/renderer/views/History/History.vue b/src/renderer/views/History/History.vue index 2507f326..8cd99ad7 100644 --- a/src/renderer/views/History/History.vue +++ b/src/renderer/views/History/History.vue @@ -9,6 +9,13 @@ class="card" >

{{ $t("History.History") }}

+ diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.js b/src/renderer/views/UserPlaylists/UserPlaylists.js index eefe53f7..efd252a2 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.js +++ b/src/renderer/views/UserPlaylists/UserPlaylists.js @@ -5,6 +5,7 @@ import FtTooltip from '../../components/ft-tooltip/ft-tooltip.vue' import FtLoader from '../../components/ft-loader/ft-loader.vue' import FtButton from '../../components/ft-button/ft-button.vue' import FtElementList from '../../components/ft-element-list/ft-element-list.vue' +import FtInput from '../../components/ft-input/ft-input.vue' export default Vue.extend({ name: 'UserPlaylists', @@ -14,17 +15,23 @@ export default Vue.extend({ 'ft-tooltip': FtTooltip, 'ft-loader': FtLoader, 'ft-button': FtButton, - 'ft-element-list': FtElementList + 'ft-element-list': FtElementList, + 'ft-input': FtInput }, data: function () { return { isLoading: false, - dataLimit: 100 + dataLimit: 100, + hasQuery: false } }, computed: { favoritesPlaylist: function () { - return this.$store.getters.getFavorites + if (!this.hasQuery) { + return this.$store.getters.getFavorites + } else { + return this.$store.getters.getSearchPlaylistCache + } }, activeData: function () { @@ -59,6 +66,10 @@ export default Vue.extend({ increaseLimit: function () { this.dataLimit += 100 sessionStorage.setItem('favoritesLimit', this.dataLimit) + }, + filterPlaylist: function(query) { + this.hasQuery = query !== '' + this.$store.dispatch('searchFavoritePlaylist', query) } } }) diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.vue b/src/renderer/views/UserPlaylists/UserPlaylists.vue index 6cae1139..ce4f23d7 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.vue +++ b/src/renderer/views/UserPlaylists/UserPlaylists.vue @@ -16,6 +16,13 @@ :tooltip="$t('User Playlists.Playlist Message')" /> + diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml index 324f214f..b2c12430 100644 --- a/static/locales/en-US.yaml +++ b/static/locales/en-US.yaml @@ -103,11 +103,13 @@ User Playlists: 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 + Search bar placeholder: Search in Playlist History: # On History Page History: History Watch History: Watch History Your history list is currently empty.: Your history list is currently empty. + Search bar placeholder: "Search in History" Settings: # On Settings Page Settings: Settings diff --git a/static/locales/fr-FR.yaml b/static/locales/fr-FR.yaml index 9d304b52..3e91f8ef 100644 --- a/static/locales/fr-FR.yaml +++ b/static/locales/fr-FR.yaml @@ -98,11 +98,13 @@ User Playlists: Elle ne répertorie que les vidéos que vous avez enregistrées ou mises en favoris. Une fois le travail terminé, toutes les vidéos actuellement présentes ici seront migrées vers une liste de lecture « Favoris ». + Search bar placeholder: Recherche dans la liste de lecture History: # On History Page History: 'Historique' Watch History: 'Historique de visionnage' Your history list is currently empty.: 'Votre historique est vide.' + Search bar placeholder: "Recherche dans l'historique" Settings: # On Settings Page Settings: 'Paramètres'