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" >