From 70983739f6cf2f7061333401f5d551a596e38876 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 30 May 2024 14:03:41 +0800 Subject: [PATCH 01/14] $ Simplify boolean assignment, rename session storage key --- src/renderer/views/History/History.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/views/History/History.js b/src/renderer/views/History/History.js index 9432541a141de..03bfee976d47f 100644 --- a/src/renderer/views/History/History.js +++ b/src/renderer/views/History/History.js @@ -74,9 +74,9 @@ export default defineComponent({ this.filterHistory() }, }, - mounted: function () { + created: function () { document.addEventListener('keydown', this.keyboardShortcutHandler) - const limit = sessionStorage.getItem('historyLimit') + const limit = sessionStorage.getItem('History/dataLimit') if (limit !== null) { this.dataLimit = limit @@ -98,7 +98,7 @@ export default defineComponent({ this.filterHistory() } else { this.dataLimit += 100 - sessionStorage.setItem('historyLimit', this.dataLimit) + sessionStorage.setItem('History/dataLimit', this.dataLimit) } }, filterHistoryAsync: function() { From 7c0c69dc42d45ca0f7099b303dd4168234988251 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 30 May 2024 14:29:45 +0800 Subject: [PATCH 02/14] * Make history page remember last query string & search limit only when going back --- src/renderer/views/History/History.js | 61 +++++++++++++++++++++----- src/renderer/views/History/History.vue | 5 ++- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/renderer/views/History/History.js b/src/renderer/views/History/History.js index 03bfee976d47f..1cfd5bfd92359 100644 --- a/src/renderer/views/History/History.js +++ b/src/renderer/views/History/History.js @@ -1,4 +1,5 @@ import { defineComponent } from 'vue' +import { isNavigationFailure, NavigationFailureType } from 'vue-router' import debounce from 'lodash.debounce' import FtLoader from '../../components/ft-loader/ft-loader.vue' import FtCard from '../../components/ft-card/ft-card.vue' @@ -63,10 +64,6 @@ export default defineComponent({ }, }, watch: { - query() { - this.searchDataLimit = 100 - this.filterHistoryAsync() - }, fullData() { this.filterHistory() }, @@ -76,25 +73,42 @@ export default defineComponent({ }, created: function () { document.addEventListener('keydown', this.keyboardShortcutHandler) - const limit = sessionStorage.getItem('History/dataLimit') - if (limit !== null) { - this.dataLimit = limit + const oldDataLimit = sessionStorage.getItem('History/dataLimit') + if (oldDataLimit !== null) { + this.dataLimit = oldDataLimit } - this.activeData = this.fullData - - this.showLoadMoreButton = this.activeData.length < this.historyCacheSorted.length - this.filterHistoryDebounce = debounce(this.filterHistory, 500) + + const oldQuery = this.$route.query.searchQueryText ?? '' + if (oldQuery !== null && oldQuery !== '') { + // `handleQueryChange` must be called after `filterHistoryDebounce` assigned + this.handleQueryChange(oldQuery, this.$route.query.searchDataLimit) + } else { + // Only display unfiltered data when no query used last time + this.filterHistory() + } }, beforeDestroy: function () { document.removeEventListener('keydown', this.keyboardShortcutHandler) }, methods: { + handleQueryChange(val, customLimit = null) { + this.query = val + + const newLimit = customLimit ?? 100 + this.searchDataLimit = newLimit + + this.saveStateInRouter(val, newLimit) + + this.filterHistoryAsync() + }, + increaseLimit: function () { if (this.query !== '') { this.searchDataLimit += 100 + this.saveStateInRouter(this.query, this.searchDataLimit) this.filterHistory() } else { this.dataLimit += 100 @@ -122,6 +136,31 @@ export default defineComponent({ this.activeData = filteredQuery.length < this.searchDataLimit ? filteredQuery : filteredQuery.slice(0, this.searchDataLimit) this.showLoadMoreButton = this.activeData.length > this.searchDataLimit }, + + async saveStateInRouter(query, searchDataLimit) { + if (this.query === '') { + await this.$router.replace({ name: 'history' }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + return + } + + await this.$router.replace({ + name: 'history', + query: { searchQueryText: query, searchDataLimit: searchDataLimit }, + }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + }, + keyboardShortcutHandler: function (event) { ctrlFHandler(event, this.$refs.searchBar) }, diff --git a/src/renderer/views/History/History.vue b/src/renderer/views/History/History.vue index 5167a0ff67d5c..27d2949165142 100644 --- a/src/renderer/views/History/History.vue +++ b/src/renderer/views/History/History.vue @@ -15,8 +15,9 @@ :placeholder="$t('History.Search bar placeholder')" :show-clear-text-button="true" :show-action-button="false" - @input="(input) => query = input" - @clear="query = ''" + :value="query" + @input="(input) => handleQueryChange(input)" + @clear="() => handleQueryChange('')" />
Date: Tue, 20 Aug 2024 09:11:20 +0800 Subject: [PATCH 03/14] ! Fix restoring filtered history having unnecessary delay --- src/renderer/views/History/History.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/views/History/History.js b/src/renderer/views/History/History.js index 1cfd5bfd92359..0737c11e69e0a 100644 --- a/src/renderer/views/History/History.js +++ b/src/renderer/views/History/History.js @@ -84,7 +84,7 @@ export default defineComponent({ const oldQuery = this.$route.query.searchQueryText ?? '' if (oldQuery !== null && oldQuery !== '') { // `handleQueryChange` must be called after `filterHistoryDebounce` assigned - this.handleQueryChange(oldQuery, this.$route.query.searchDataLimit) + this.handleQueryChange(oldQuery, this.$route.query.searchDataLimit, true) } else { // Only display unfiltered data when no query used last time this.filterHistory() @@ -94,7 +94,7 @@ export default defineComponent({ document.removeEventListener('keydown', this.keyboardShortcutHandler) }, methods: { - handleQueryChange(val, customLimit = null) { + handleQueryChange(val, customLimit = null, filterNow = false) { this.query = val const newLimit = customLimit ?? 100 @@ -102,7 +102,7 @@ export default defineComponent({ this.saveStateInRouter(val, newLimit) - this.filterHistoryAsync() + filterNow ? this.filterHistory() : this.filterHistoryAsync() }, increaseLimit: function () { From 66103aee06a444ef2c9eac926a7f6012881cba04 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 20 Aug 2024 09:23:43 +0800 Subject: [PATCH 04/14] * Make subscribed channels page remember last query string only when going back --- .../SubscribedChannels/SubscribedChannels.js | 54 ++++++++++++++++--- .../SubscribedChannels/SubscribedChannels.vue | 5 +- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.js b/src/renderer/views/SubscribedChannels/SubscribedChannels.js index eda06ec8ee14e..6a3a16b62f5fc 100644 --- a/src/renderer/views/SubscribedChannels/SubscribedChannels.js +++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.js @@ -7,6 +7,8 @@ import FtSubscribeButton from '../../components/ft-subscribe-button/ft-subscribe import { invidiousGetChannelInfo, youtubeImageUrlToInvidious, invidiousImageUrlToInvidious } from '../../helpers/api/invidious' import { getLocalChannel, parseLocalChannelHeader } from '../../helpers/api/local' import { ctrlFHandler } from '../../helpers/utils' +import { isNavigationFailure, NavigationFailureType } from 'vue-router' +import debounce from 'lodash.debounce' export default defineComponent({ name: 'SubscribedChannels', @@ -78,9 +80,17 @@ export default defineComponent({ this.filterChannels() } }, - mounted: function () { + created: function () { document.addEventListener('keydown', this.keyboardShortcutHandler) + + this.filterChannelsDebounce = debounce(this.filterChannels, 500) this.getSubscription() + + const oldQuery = this.$route.query.searchQueryText ?? '' + if (oldQuery !== null && oldQuery !== '') { + // `handleQueryChange` must be called after `filterHistoryDebounce` assigned + this.handleQueryChange(oldQuery, true) + } }, beforeDestroy: function () { document.removeEventListener('keydown', this.keyboardShortcutHandler) @@ -92,11 +102,6 @@ export default defineComponent({ }) }, - handleInput: function(input) { - this.query = input - this.filterChannels() - }, - filterChannels: function () { if (this.query === '') { this.filteredChannels = [] @@ -109,6 +114,11 @@ export default defineComponent({ return re.test(channel.name) }) }, + filterChannelsAsync: function() { + // Updating list on every char input could be wasting resources on rendering + // So run it with delay (to be cancelled when more input received within time) + this.filterChannelsDebounce() + }, thumbnailURL: function(originalURL) { if (originalURL == null) { return null } @@ -161,6 +171,38 @@ export default defineComponent({ } }, + handleQueryChange(val, filterNow = false) { + this.query = val + + this.saveStateInRouter(val) + + filterNow ? this.filterChannels() : this.filterChannelsAsync() + }, + + async saveStateInRouter(query) { + if (this.query === '') { + await this.$router.replace({ name: 'subscribedChannels' }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + return + } + + await this.$router.replace({ + name: 'subscribedChannels', + query: { searchQueryText: query }, + }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + }, + keyboardShortcutHandler: function (event) { ctrlFHandler(event, this.$refs.searchBarChannels) }, diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue index 034c414091508..c7b16598d5b72 100644 --- a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue +++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue @@ -6,12 +6,13 @@ v-show="subscribedChannels.length > 0" ref="searchBarChannels" :placeholder="$t('Channels.Search bar placeholder')" + :value="query" :show-clear-text-button="true" :show-action-button="false" :spellcheck="false" :maxlength="255" - @input="handleInput" - @clear="query = ''" + @input="(input) => handleQueryChange(input)" + @clear="() => handleQueryChange('')" /> Date: Tue, 20 Aug 2024 09:37:40 +0800 Subject: [PATCH 05/14] * Make user playlists page remember last query string only when going back --- .../views/UserPlaylists/UserPlaylists.js | 55 ++++++++++++++++--- .../views/UserPlaylists/UserPlaylists.vue | 5 +- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.js b/src/renderer/views/UserPlaylists/UserPlaylists.js index a29495f61c73e..513db8be4d9e3 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.js +++ b/src/renderer/views/UserPlaylists/UserPlaylists.js @@ -13,6 +13,7 @@ import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue' import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue' import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue' import { ctrlFHandler, getIconForSortPreference } from '../../helpers/utils' +import { isNavigationFailure, NavigationFailureType } from 'vue-router' const SORT_BY_VALUES = { NameAscending: 'name_ascending', @@ -183,9 +184,9 @@ export default defineComponent({ sessionStorage.setItem('UserPlaylists/sortBy', this.sortBy) }, }, - mounted: function () { + created: function () { document.addEventListener('keydown', this.keyboardShortcutHandler) - const limit = sessionStorage.getItem('favoritesLimit') + const limit = sessionStorage.getItem('UserPlaylists/dataLimit') if (limit !== null) { this.dataLimit = limit } @@ -195,23 +196,39 @@ export default defineComponent({ this.sortBy = sortBy } - this.activeData = this.fullData - - this.showLoadMoreButton = this.activeData.length < this.allPlaylists.length - this.filterPlaylistDebounce = debounce(this.filterPlaylist, 500) + + const oldQuery = this.$route.query.searchQueryText ?? '' + if (oldQuery !== null && oldQuery !== '') { + // `handleQueryChange` must be called after `filterHistoryDebounce` assigned + this.handleQueryChange(oldQuery, this.$route.query.searchDataLimit, true) + } else { + // Only display unfiltered data when no query used last time + this.filterPlaylist() + } }, beforeDestroy: function () { document.removeEventListener('keydown', this.keyboardShortcutHandler) }, methods: { + handleQueryChange(val, customLimit = null, filterNow = false) { + this.query = val + + const newLimit = customLimit ?? 100 + this.searchDataLimit = newLimit + + this.saveStateInRouter(val, newLimit) + + filterNow ? this.filterPlaylist() : this.filterPlaylistAsync() + }, + increaseLimit: function () { if (this.query !== '') { this.searchDataLimit += 100 this.filterPlaylist() } else { this.dataLimit += 100 - sessionStorage.setItem('favoritesLimit', this.dataLimit) + sessionStorage.setItem('UserPlaylists/dataLimit', this.dataLimit) } }, filterPlaylistAsync: function() { @@ -247,6 +264,30 @@ export default defineComponent({ }) }, + async saveStateInRouter(query, searchDataLimit) { + if (this.query === '') { + await this.$router.replace({ name: 'userPlaylists' }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + return + } + + await this.$router.replace({ + name: 'userPlaylists', + query: { searchQueryText: query, searchDataLimit: searchDataLimit }, + }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + }, + keyboardShortcutHandler: function (event) { ctrlFHandler(event, this.$refs.searchBar) }, diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.vue b/src/renderer/views/UserPlaylists/UserPlaylists.vue index 5f455651d12c5..b5930b207375f 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.vue +++ b/src/renderer/views/UserPlaylists/UserPlaylists.vue @@ -26,11 +26,12 @@
Date: Tue, 20 Aug 2024 09:56:48 +0800 Subject: [PATCH 06/14] * Make playlist page remember last query string only when going back --- src/renderer/views/Playlist/Playlist.js | 29 +++++++++++++++++++++++- src/renderer/views/Playlist/Playlist.vue | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js index fc7c8c798b431..aaf1cd290c0c5 100644 --- a/src/renderer/views/Playlist/Playlist.js +++ b/src/renderer/views/Playlist/Playlist.js @@ -25,6 +25,7 @@ import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../help import { getSortedPlaylistItems, SORT_BY_VALUES } from '../../helpers/playlists' import packageDetails from '../../../../package.json' import { MOBILE_WIDTH_THRESHOLD, PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD } from '../../../constants' +import { isNavigationFailure, NavigationFailureType } from 'vue-router' export default defineComponent({ name: 'Playlist', @@ -257,7 +258,7 @@ export default defineComponent({ this.getPlaylistInfoDebounce = debounce(this.getPlaylistInfo, 100) if (this.isUserPlaylistRequested && this.searchQueryTextPresent) { - this.videoSearchQuery = this.searchQueryTextRequested + this.handleVideoSearchQueryChange(this.searchQueryTextRequested) } }, mounted: function () { @@ -564,6 +565,32 @@ export default defineComponent({ this.forceListView = window.innerWidth <= MOBILE_WIDTH_THRESHOLD || window.innerHeight <= PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD }, + handleVideoSearchQueryChange(val) { + this.videoSearchQuery = val + + this.saveStateInRouter(val) + }, + + async saveStateInRouter(query) { + const routeQuery = { + playlistType: this.$route.query.playlistType, + } + if (query !== '') { + routeQuery.searchQueryText = query + } + + await this.$router.replace({ + path: `/playlist/${this.playlistId}`, + query: routeQuery, + }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + }, + getIconForSortPreference: (s) => getIconForSortPreference(s), ...mapActions([ diff --git a/src/renderer/views/Playlist/Playlist.vue b/src/renderer/views/Playlist/Playlist.vue index c7e6efb8b30af..576f501900d02 100644 --- a/src/renderer/views/Playlist/Playlist.vue +++ b/src/renderer/views/Playlist/Playlist.vue @@ -37,7 +37,7 @@ class="playlistInfo" @enter-edit-mode="playlistInEditMode = true" @exit-edit-mode="playlistInEditMode = false" - @search-video-query-change="(v) => videoSearchQuery = v" + @search-video-query-change="(v) => handleVideoSearchQueryChange(v)" @prompt-open="promptOpen = true" @prompt-close="promptOpen = false" /> From 2f8fa91b32d13e01e808556313329de76a4a1d68 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 20 Aug 2024 10:46:44 +0800 Subject: [PATCH 07/14] * Make channel page remember last query string only when going back --- .../ChannelDetails/ChannelDetails.vue | 7 ++- src/renderer/views/Channel/Channel.js | 62 ++++++++++++++++--- src/renderer/views/Channel/Channel.vue | 3 +- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/ChannelDetails/ChannelDetails.vue b/src/renderer/components/ChannelDetails/ChannelDetails.vue index c6574c40d81c1..5b7897a5d7dde 100644 --- a/src/renderer/components/ChannelDetails/ChannelDetails.vue +++ b/src/renderer/components/ChannelDetails/ChannelDetails.vue @@ -206,6 +206,7 @@ v-if="showSearchBar" ref="searchBar" :placeholder="$t('Channel.Search Channel')" + :value="props.query" :show-clear-text-button="true" class="channelSearch" :maxlength="255" @@ -274,7 +275,11 @@ const props = defineProps({ currentTab: { type: String, default: 'videos' - } + }, + query: { + type: String, + default: '' + }, }) const emit = defineEmits(['change-tab', 'search']) diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 8116dbb705fe6..952d879976cb7 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -41,6 +41,7 @@ import { parseLocalSubscriberCount, getLocalArtistTopicChannelReleasesContinuation } from '../../helpers/api/local' +import { isNavigationFailure, NavigationFailureType } from 'vue-router' export default defineComponent({ name: 'Channel', @@ -57,6 +58,7 @@ export default defineComponent({ }, data: function () { return { + skipRouteChangeWatcherOnce: false, isLoading: true, isElementListLoading: false, currentTab: 'videos', @@ -281,6 +283,10 @@ export default defineComponent({ watch: { $route() { // react to route changes... + if (this.skipRouteChangeWatcherOnce) { + this.skipRouteChangeWatcherOnce = false + return + } this.isLoading = true if (this.$route.query.url) { @@ -337,8 +343,9 @@ export default defineComponent({ // Re-enable auto refresh on sort value change AFTER update done if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') { - this.getChannelInfoInvidious() - this.autoRefreshOnSortByChangeEnabled = true + this.getChannelInfoInvidious().finally(() => { + this.autoRefreshOnSortByChangeEnabled = true + }) } else { this.getChannelLocal().finally(() => { this.autoRefreshOnSortByChangeEnabled = true @@ -415,9 +422,9 @@ export default defineComponent({ } } }, - mounted: function () { + mounted: async function () { if (this.$route.query.url) { - this.resolveChannelUrl(this.$route.query.url, this.$route.params.currentTab) + await this.resolveChannelUrl(this.$route.query.url, this.$route.params.currentTab) return } @@ -433,13 +440,19 @@ export default defineComponent({ // Enable auto refresh on sort value change AFTER initial update done if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') { - this.getChannelInfoInvidious() - this.autoRefreshOnSortByChangeEnabled = true + await this.getChannelInfoInvidious().finally(() => { + this.autoRefreshOnSortByChangeEnabled = true + }) } else { - this.getChannelLocal().finally(() => { + await this.getChannelLocal().finally(() => { this.autoRefreshOnSortByChangeEnabled = true }) } + + const oldQuery = this.$route.query.searchQueryText ?? '' + if (oldQuery !== null && oldQuery !== '') { + this.newSearch(oldQuery) + } }, methods: { resolveChannelUrl: async function (url, tab = undefined) { @@ -963,7 +976,7 @@ export default defineComponent({ this.channelInstance = null const expectedId = this.id - invidiousGetChannelInfo(this.id).then((response) => { + return invidiousGetChannelInfo(this.id).then((response) => { if (expectedId !== this.id) { return } @@ -1850,6 +1863,10 @@ export default defineComponent({ break } }, + newSearchWithStatePersist(query) { + this.saveStateInRouter(query) + this.newSearch(query) + }, searchChannelLocal: async function () { const isNewSearch = this.searchContinuationData === null @@ -1941,6 +1958,35 @@ export default defineComponent({ }) }, + async saveStateInRouter(query) { + this.skipRouteChangeWatcherOnce = true + if (query === '') { + await this.$router.replace({ path: `/channel/${this.id}` }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + return + } + + await this.$router.replace({ + path: `/channel/${this.id}`, + query: { + currentTab: 'search', + searchQueryText: query, + }, + }).catch(failure => { + if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + return + } + + throw failure + }) + this.skipRouteChangeWatcherOnce = false + }, + getIconForSortPreference: (s) => getIconForSortPreference(s), ...mapActions([ diff --git a/src/renderer/views/Channel/Channel.vue b/src/renderer/views/Channel/Channel.vue index c173205a6d50d..44ff15986da11 100644 --- a/src/renderer/views/Channel/Channel.vue +++ b/src/renderer/views/Channel/Channel.vue @@ -17,9 +17,10 @@ :is-subscribed="isSubscribed" :visible-tabs="tabInfoValues" :current-tab="currentTab" + :query="lastSearchQuery" class="card channelDetails" @change-tab="changeTab" - @search="newSearch" + @search="(v) => newSearchWithStatePersist(v)" /> Date: Tue, 1 Oct 2024 11:33:36 +0800 Subject: [PATCH 08/14] * Save more options --- src/renderer/views/History/History.js | 36 ++++++++++++++----- .../views/UserPlaylists/UserPlaylists.js | 34 ++++++++++++++---- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/renderer/views/History/History.js b/src/renderer/views/History/History.js index 0737c11e69e0a..0dc730ce4583d 100644 --- a/src/renderer/views/History/History.js +++ b/src/renderer/views/History/History.js @@ -69,6 +69,7 @@ export default defineComponent({ }, doCaseSensitiveSearch() { this.filterHistory() + this.saveStateInRouter() }, }, created: function () { @@ -84,7 +85,14 @@ export default defineComponent({ const oldQuery = this.$route.query.searchQueryText ?? '' if (oldQuery !== null && oldQuery !== '') { // `handleQueryChange` must be called after `filterHistoryDebounce` assigned - this.handleQueryChange(oldQuery, this.$route.query.searchDataLimit, true) + this.handleQueryChange( + oldQuery, + { + limit: this.$route.query.searchDataLimit, + doCaseSensitiveSearch: this.$route.query.doCaseSensitiveSearch === 'true', + filterNow: true, + }, + ) } else { // Only display unfiltered data when no query used last time this.filterHistory() @@ -94,13 +102,19 @@ export default defineComponent({ document.removeEventListener('keydown', this.keyboardShortcutHandler) }, methods: { - handleQueryChange(val, customLimit = null, filterNow = false) { - this.query = val + handleQueryChange(query, { limit = null, doCaseSensitiveSearch = null, filterNow = false } = {}) { + this.query = query - const newLimit = customLimit ?? 100 + const newLimit = limit ?? 100 this.searchDataLimit = newLimit + const newDoCaseSensitiveSearch = doCaseSensitiveSearch ?? this.doCaseSensitiveSearch + this.doCaseSensitiveSearch = newDoCaseSensitiveSearch - this.saveStateInRouter(val, newLimit) + this.saveStateInRouter({ + query: query, + searchDataLimit: newLimit, + doCaseSensitiveSearch: newDoCaseSensitiveSearch, + }) filterNow ? this.filterHistory() : this.filterHistoryAsync() }, @@ -108,7 +122,6 @@ export default defineComponent({ increaseLimit: function () { if (this.query !== '') { this.searchDataLimit += 100 - this.saveStateInRouter(this.query, this.searchDataLimit) this.filterHistory() } else { this.dataLimit += 100 @@ -137,8 +150,8 @@ export default defineComponent({ this.showLoadMoreButton = this.activeData.length > this.searchDataLimit }, - async saveStateInRouter(query, searchDataLimit) { - if (this.query === '') { + async saveStateInRouter({ query = this.query, searchDataLimit = this.searchDataLimit, doCaseSensitiveSearch = this.doCaseSensitiveSearch } = {}) { + if (query === '') { await this.$router.replace({ name: 'history' }).catch(failure => { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return @@ -149,9 +162,14 @@ export default defineComponent({ return } + const routerQuery = { + searchQueryText: query, + searchDataLimit: searchDataLimit, + } + if (doCaseSensitiveSearch) { routerQuery.doCaseSensitiveSearch = 'true' } await this.$router.replace({ name: 'history', - query: { searchQueryText: query, searchDataLimit: searchDataLimit }, + query: routerQuery, }).catch(failure => { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.js b/src/renderer/views/UserPlaylists/UserPlaylists.js index 513db8be4d9e3..3ecb458382ea0 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.js +++ b/src/renderer/views/UserPlaylists/UserPlaylists.js @@ -175,6 +175,7 @@ export default defineComponent({ doSearchPlaylistsWithMatchingVideos() { this.searchDataLimit = 100 this.filterPlaylistAsync() + this.saveStateInRouter() }, fullData() { this.activeData = this.fullData @@ -201,7 +202,14 @@ export default defineComponent({ const oldQuery = this.$route.query.searchQueryText ?? '' if (oldQuery !== null && oldQuery !== '') { // `handleQueryChange` must be called after `filterHistoryDebounce` assigned - this.handleQueryChange(oldQuery, this.$route.query.searchDataLimit, true) + this.handleQueryChange( + oldQuery, + { + limit: this.$route.query.searchDataLimit, + doSearchPlaylistsWithMatchingVideos: this.$route.query.doSearchPlaylistsWithMatchingVideos === 'true', + filterNow: true, + }, + ) } else { // Only display unfiltered data when no query used last time this.filterPlaylist() @@ -211,13 +219,19 @@ export default defineComponent({ document.removeEventListener('keydown', this.keyboardShortcutHandler) }, methods: { - handleQueryChange(val, customLimit = null, filterNow = false) { - this.query = val + handleQueryChange(query, { limit = null, doSearchPlaylistsWithMatchingVideos = null, filterNow = false } = {}) { + this.query = query - const newLimit = customLimit ?? 100 + const newLimit = limit ?? 100 this.searchDataLimit = newLimit + const newDoSearchPlaylistsWithMatchingVideos = doSearchPlaylistsWithMatchingVideos ?? this.doSearchPlaylistsWithMatchingVideos + this.doSearchPlaylistsWithMatchingVideos = newDoSearchPlaylistsWithMatchingVideos - this.saveStateInRouter(val, newLimit) + this.saveStateInRouter({ + query: query, + searchDataLimit: newLimit, + doSearchPlaylistsWithMatchingVideos: newDoSearchPlaylistsWithMatchingVideos, + }) filterNow ? this.filterPlaylist() : this.filterPlaylistAsync() }, @@ -225,6 +239,7 @@ export default defineComponent({ increaseLimit: function () { if (this.query !== '') { this.searchDataLimit += 100 + this.saveStateInRouter() this.filterPlaylist() } else { this.dataLimit += 100 @@ -264,7 +279,7 @@ export default defineComponent({ }) }, - async saveStateInRouter(query, searchDataLimit) { + async saveStateInRouter({ query = this.query, searchDataLimit = this.searchDataLimit, doSearchPlaylistsWithMatchingVideos = this.doSearchPlaylistsWithMatchingVideos } = {}) { if (this.query === '') { await this.$router.replace({ name: 'userPlaylists' }).catch(failure => { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { @@ -276,9 +291,14 @@ export default defineComponent({ return } + const routerQuery = { + searchQueryText: query, + searchDataLimit: searchDataLimit, + } + if (doSearchPlaylistsWithMatchingVideos) { routerQuery.doSearchPlaylistsWithMatchingVideos = 'true' } await this.$router.replace({ name: 'userPlaylists', - query: { searchQueryText: query, searchDataLimit: searchDataLimit }, + query: routerQuery, }).catch(failure => { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return From 52ad0951e0d52be9eb23c441ea7364b52428b6c7 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 3 Oct 2024 09:45:26 +0800 Subject: [PATCH 09/14] ! Fix strange outline on nav buttons --- src/renderer/views/Channel/Channel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 852ae11804d85..fa6641ae4f826 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -1894,7 +1894,8 @@ export default defineComponent({ const newTabNode = document.getElementById(`${tab}Tab`) this.currentTab = tab newTabNode?.focus() - this.showOutlines() + // Prevents outline shown in strange places + if (newTabNode != null) { this.showOutlines() } }, newSearch: function (query) { From 49cd4490a0d7e326399be40a49afed0533e362d7 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Sun, 13 Oct 2024 08:24:00 +0800 Subject: [PATCH 10/14] * Put `currentTab` value into proper place params instead of query --- src/renderer/views/Channel/Channel.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 4f52b410c0065..f69b7a4481851 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -2051,8 +2051,10 @@ export default defineComponent({ await this.$router.replace({ path: `/channel/${this.id}`, - query: { + params: { currentTab: 'search', + }, + query: { searchQueryText: query, }, }).catch(failure => { From 03f887ecf12b1b378d1735d6908b57d20a0b8c8a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 16 Oct 2024 09:44:51 +0800 Subject: [PATCH 11/14] ! Fix search tab showing "0 results" before search done --- src/renderer/views/Channel/Channel.js | 15 ++++++++++++--- src/renderer/views/Channel/Channel.vue | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index f69b7a4481851..fb8d829a21320 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -64,6 +64,7 @@ export default defineComponent({ skipRouteChangeWatcherOnce: false, isLoading: true, isElementListLoading: false, + isSearchTabLoading: false, currentTab: 'videos', id: '', /** @type {import('youtubei.js').YT.Channel|null} */ @@ -296,6 +297,14 @@ export default defineComponent({ return values }, + + isCurrentTabLoading() { + if (this.currentTab === 'search') { + return this.isSearchTabLoading + } + + return this.isElementListLoading + }, }, watch: { $route() { @@ -1898,7 +1907,7 @@ export default defineComponent({ newSearch: function (query) { this.lastSearchQuery = query this.searchContinuationData = null - this.isElementListLoading = true + this.isSearchTabLoading = true this.searchPage = 1 this.searchResults = [] this.changeTab('search') @@ -1953,7 +1962,7 @@ export default defineComponent({ } this.searchContinuationData = result.has_continuation ? result : null - this.isElementListLoading = false + this.isSearchTabLoading = false } catch (err) { console.error(err) const errorMessage = this.$t('Local API Error (Click to copy)') @@ -1989,7 +1998,7 @@ export default defineComponent({ } else { this.searchResults = this.searchResults.concat(response) } - this.isElementListLoading = false + this.isSearchTabLoading = false this.searchPage++ }).catch((err) => { console.error(err) diff --git a/src/renderer/views/Channel/Channel.vue b/src/renderer/views/Channel/Channel.vue index 1d0f2b634648e..61f1b9ca1a5cd 100644 --- a/src/renderer/views/Channel/Channel.vue +++ b/src/renderer/views/Channel/Channel.vue @@ -81,7 +81,7 @@ />

{{ $t("Channel.Your search results have returned 0 results") }} From 6041e3a0d1058a6d6297f10b69ce6c5f21b8e9d0 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Sun, 24 Nov 2024 09:01:58 +0800 Subject: [PATCH 12/14] - Remove useless file after merging dev --- .../SubscribedChannels/SubscribedChannels.js | 214 ------------------ 1 file changed, 214 deletions(-) delete mode 100644 src/renderer/views/SubscribedChannels/SubscribedChannels.js diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.js b/src/renderer/views/SubscribedChannels/SubscribedChannels.js deleted file mode 100644 index 19ad3572e7a8e..0000000000000 --- a/src/renderer/views/SubscribedChannels/SubscribedChannels.js +++ /dev/null @@ -1,214 +0,0 @@ -import { defineComponent } from 'vue' -import { mapActions } from 'vuex' -import FtCard from '../../components/ft-card/ft-card.vue' -import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' -import FtInput from '../../components/ft-input/ft-input.vue' -import FtSubscribeButton from '../../components/ft-subscribe-button/ft-subscribe-button.vue' -import { invidiousGetChannelInfo, youtubeImageUrlToInvidious, invidiousImageUrlToInvidious } from '../../helpers/api/invidious' -import { getLocalChannel, parseLocalChannelHeader } from '../../helpers/api/local' -import { ctrlFHandler } from '../../helpers/utils' -import { isNavigationFailure, NavigationFailureType } from 'vue-router' -import debounce from 'lodash.debounce' - -export default defineComponent({ - name: 'SubscribedChannels', - components: { - 'ft-card': FtCard, - 'ft-flex-box': FtFlexBox, - 'ft-input': FtInput, - 'ft-subscribe-button': FtSubscribeButton - }, - data: function () { - return { - query: '', - subscribedChannels: [], - filteredChannels: [], - re: { - url: /(.+=\w)\d+(.+)/, - ivToYt: /^.+ggpht\/(.+)/ - }, - thumbnailSize: 176, - ytBaseURL: 'https://yt3.ggpht.com', - errorCount: 0, - } - }, - computed: { - activeProfile: function () { - return this.$store.getters.getActiveProfile - }, - - activeProfileId: function () { - return this.activeProfile._id - }, - - activeSubscriptionList: function () { - return this.activeProfile.subscriptions - }, - - channelList: function () { - if (this.query !== '') { - return this.filteredChannels - } else { - return this.subscribedChannels - } - }, - - hideUnsubscribeButton: function() { - return this.$store.getters.getHideUnsubscribeButton - }, - - locale: function () { - return this.$i18n.locale - }, - - backendPreference: function () { - return this.$store.getters.getBackendPreference - }, - - currentInvidiousInstanceUrl: function () { - return this.$store.getters.getCurrentInvidiousInstanceUrl - } - }, - watch: { - activeProfileId: function() { - this.query = '' - this.getSubscription() - }, - - activeSubscriptionList: function() { - this.getSubscription() - this.filterChannels() - } - }, - created: function () { - document.addEventListener('keydown', this.keyboardShortcutHandler) - - this.filterChannelsDebounce = debounce(this.filterChannels, 500) - this.getSubscription() - - const oldQuery = this.$route.query.searchQueryText ?? '' - if (oldQuery !== null && oldQuery !== '') { - // `handleQueryChange` must be called after `filterHistoryDebounce` assigned - this.handleQueryChange(oldQuery, true) - } - }, - beforeDestroy: function () { - document.removeEventListener('keydown', this.keyboardShortcutHandler) - }, - methods: { - getSubscription: function () { - this.subscribedChannels = this.activeSubscriptionList.slice().sort((a, b) => { - return a.name?.toLowerCase().localeCompare(b.name?.toLowerCase(), this.locale) - }) - }, - - filterChannels: function () { - if (this.query === '') { - this.filteredChannels = [] - return - } - - const escapedQuery = this.query.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&') - const re = new RegExp(escapedQuery, 'i') - this.filteredChannels = this.subscribedChannels.filter(channel => { - return re.test(channel.name) - }) - }, - filterChannelsAsync: function() { - // Updating list on every char input could be wasting resources on rendering - // So run it with delay (to be cancelled when more input received within time) - this.filterChannelsDebounce() - }, - - thumbnailURL: function(originalURL) { - if (originalURL == null) { return null } - let newURL = originalURL - // Sometimes relative protocol URLs are passed in - if (originalURL.startsWith('//')) { - newURL = `https:${originalURL}` - } - const hostname = new URL(newURL).hostname - if (hostname === 'yt3.ggpht.com' || hostname === 'yt3.googleusercontent.com') { - if (this.backendPreference === 'invidious') { // YT to IV - newURL = youtubeImageUrlToInvidious(newURL, this.currentInvidiousInstanceUrl) - } - } else { - if (this.backendPreference === 'local') { // IV to YT - newURL = newURL.replace(this.re.ivToYt, `${this.ytBaseURL}/$1`) - } else { // IV to IV - newURL = invidiousImageUrlToInvidious(newURL, this.currentInvidiousInstanceUrl) - } - } - - return newURL.replace(this.re.url, `$1${this.thumbnailSize}$2`) - }, - - updateThumbnail: function(channel) { - this.errorCount += 1 - if (this.backendPreference === 'local') { - // avoid too many concurrent requests - setTimeout(() => { - getLocalChannel(channel.id).then(response => { - if (!response.alert) { - this.updateSubscriptionDetails({ - channelThumbnailUrl: this.thumbnailURL(parseLocalChannelHeader(response).thumbnailUrl), - channelName: channel.name, - channelId: channel.id - }) - } - }) - }, this.errorCount * 500) - } else { - setTimeout(() => { - invidiousGetChannelInfo(channel.id).then(response => { - this.updateSubscriptionDetails({ - channelThumbnailUrl: this.thumbnailURL(response.authorThumbnails[0].url), - channelName: channel.name, - channelId: channel.id - }) - }) - }, this.errorCount * 500) - } - }, - - handleQueryChange(val, filterNow = false) { - this.query = val - - this.saveStateInRouter(val) - - filterNow ? this.filterChannels() : this.filterChannelsAsync() - }, - - async saveStateInRouter(query) { - if (this.query === '') { - await this.$router.replace({ name: 'subscribedChannels' }).catch(failure => { - if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { - return - } - - throw failure - }) - return - } - - await this.$router.replace({ - name: 'subscribedChannels', - query: { searchQueryText: query }, - }).catch(failure => { - if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { - return - } - - throw failure - }) - }, - - keyboardShortcutHandler: function (event) { - ctrlFHandler(event, this.$refs.searchBarChannels) - }, - - ...mapActions([ - 'updateSubscriptionDetails' - ]) - } -}) From 82c92d33dcfcc8785e27a62fe00393abb714e15d Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Thu, 28 Nov 2024 08:27:59 +0800 Subject: [PATCH 13/14] $ Change code style --- .../ChannelDetails/ChannelDetails.vue | 2 +- src/renderer/views/Channel/Channel.js | 28 +++++++++++-------- src/renderer/views/Channel/Channel.vue | 2 +- src/renderer/views/History/History.js | 18 +++++++----- src/renderer/views/Playlist/Playlist.js | 12 ++++---- .../views/UserPlaylists/UserPlaylists.js | 18 +++++++----- 6 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/renderer/components/ChannelDetails/ChannelDetails.vue b/src/renderer/components/ChannelDetails/ChannelDetails.vue index 33f5f1aaad832..50659bdf5c892 100644 --- a/src/renderer/components/ChannelDetails/ChannelDetails.vue +++ b/src/renderer/components/ChannelDetails/ChannelDetails.vue @@ -207,7 +207,7 @@ v-if="showSearchBar" ref="searchBar" :placeholder="$t('Channel.Search Channel')" - :value="props.query" + :value="query" :show-clear-text-button="true" class="channelSearch" :maxlength="255" diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 2366f25bf9476..cd6d71553554a 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -2039,31 +2039,35 @@ export default defineComponent({ async saveStateInRouter(query) { this.skipRouteChangeWatcherOnce = true if (query === '') { - await this.$router.replace({ path: `/channel/${this.id}` }).catch(failure => { + try { + await this.$router.replace({ path: `/channel/${this.id}` }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } return } - await this.$router.replace({ - path: `/channel/${this.id}`, - params: { - currentTab: 'search', - }, - query: { - searchQueryText: query, - }, - }).catch(failure => { + try { + await this.$router.replace({ + path: `/channel/${this.id}`, + params: { + currentTab: 'search', + }, + query: { + searchQueryText: query, + }, + }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } this.skipRouteChangeWatcherOnce = false }, diff --git a/src/renderer/views/Channel/Channel.vue b/src/renderer/views/Channel/Channel.vue index 61f1b9ca1a5cd..d97d500a05b2f 100644 --- a/src/renderer/views/Channel/Channel.vue +++ b/src/renderer/views/Channel/Channel.vue @@ -20,7 +20,7 @@ :query="lastSearchQuery" class="card channelDetails" @change-tab="changeTab" - @search="(v) => newSearchWithStatePersist(v)" + @search="newSearchWithStatePersist" @subscribed="handleSubscription" /> { + try { + await this.$router.replace({ name: 'history' }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } return } @@ -167,16 +169,18 @@ export default defineComponent({ searchDataLimit: searchDataLimit, } if (doCaseSensitiveSearch) { routerQuery.doCaseSensitiveSearch = 'true' } - await this.$router.replace({ - name: 'history', - query: routerQuery, - }).catch(failure => { + try { + await this.$router.replace({ + name: 'history', + query: routerQuery, + }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } }, keyboardShortcutHandler: function (event) { diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js index 2b3134cb3b0ca..1da0e7aa5d44d 100644 --- a/src/renderer/views/Playlist/Playlist.js +++ b/src/renderer/views/Playlist/Playlist.js @@ -624,16 +624,18 @@ export default defineComponent({ routeQuery.searchQueryText = query } - await this.$router.replace({ - path: `/playlist/${this.playlistId}`, - query: routeQuery, - }).catch(failure => { + try { + await this.$router.replace({ + path: `/playlist/${this.playlistId}`, + query: routeQuery, + }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } }, getIconForSortPreference: (s) => getIconForSortPreference(s), diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.js b/src/renderer/views/UserPlaylists/UserPlaylists.js index 46f9763304621..25deadd856017 100644 --- a/src/renderer/views/UserPlaylists/UserPlaylists.js +++ b/src/renderer/views/UserPlaylists/UserPlaylists.js @@ -281,13 +281,15 @@ export default defineComponent({ async saveStateInRouter({ query = this.query, searchDataLimit = this.searchDataLimit, doSearchPlaylistsWithMatchingVideos = this.doSearchPlaylistsWithMatchingVideos } = {}) { if (this.query === '') { - await this.$router.replace({ name: 'userPlaylists' }).catch(failure => { + try { + await this.$router.replace({ name: 'userPlaylists' }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } return } @@ -296,16 +298,18 @@ export default defineComponent({ searchDataLimit: searchDataLimit, } if (doSearchPlaylistsWithMatchingVideos) { routerQuery.doSearchPlaylistsWithMatchingVideos = 'true' } - await this.$router.replace({ - name: 'userPlaylists', - query: routerQuery, - }).catch(failure => { + try { + await this.$router.replace({ + name: 'userPlaylists', + query: routerQuery, + }) + } catch (failure) { if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { return } throw failure - }) + } }, keyboardShortcutHandler: function (event) { From ad83ebf4ed355e4e65e34206289867f31bb25c0f Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Fri, 29 Nov 2024 20:44:59 +0800 Subject: [PATCH 14/14] * Put event listener back to mounted --- .../SubscribedChannels/SubscribedChannels.vue | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue index 270b1fe20f8d5..742eb469e86ab 100644 --- a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue +++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue @@ -75,7 +75,7 @@