Add search playlists with matching videos function (#4537)

* * Update user playlists page to add search playlists with matching videos function

* * Update add videos to playlists prompt to add search playlists with matching videos function

* * Update UI & label text

* * Click on playlist link with search matching video enabled now also search for video when view switched

* * Only auto enable search video mode for playlists with video(s)

* * Make new toggle vertically align center

* * Make new toggle vertically align center
This commit is contained in:
PikachuEXE 2024-03-15 05:16:15 +08:00 committed by GitHub
parent 68c74ea2b6
commit 65a5b0c045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 217 additions and 48 deletions

View File

@ -33,7 +33,12 @@ export default defineComponent({
hideForbiddenTitles: {
type: Boolean,
default: true
}
},
searchQueryText: {
type: String,
required: false,
default: '',
},
},
computed: {
listType: function () {

View File

@ -13,6 +13,7 @@
:show-video-with-last-viewed-playlist="showVideoWithLastViewedPlaylist"
:use-channels-hidden-preference="useChannelsHiddenPreference"
:hide-forbidden-titles="hideForbiddenTitles"
:search-query-text="searchQueryText"
/>
</ft-auto-grid>
</template>

View File

@ -47,6 +47,11 @@ export default defineComponent({
type: Boolean,
default: true
},
searchQueryText: {
type: String,
required: false,
default: '',
},
},
data: function () {
return {

View File

@ -28,6 +28,7 @@
v-else-if="finalDataType === 'playlist'"
:appearance="appearance"
:data="data"
:search-query-text="searchQueryText"
/>
<ft-community-post
v-else-if="finalDataType === 'community'"

View File

@ -15,7 +15,12 @@ export default defineComponent({
appearance: {
type: String,
required: true
}
},
searchQueryText: {
type: String,
required: false,
default: '',
},
},
data: function () {
return {
@ -79,6 +84,7 @@ export default defineComponent({
path: `/playlist/${this.playlistId}`,
query: {
playlistType: this.isUserPlaylist ? 'user' : '',
searchQueryText: this.searchQueryText,
},
}
},

View File

@ -16,6 +16,36 @@
flex-direction: column;
}
.searchInputsRow {
display: grid;
/* 2 columns */
grid-template-columns: 1fr auto;
column-gap: 16px;
}
@media only screen and (max-width: 800px) {
.searchInputsRow {
/* Switch to 2 rows from 2 columns */
grid-template-columns: auto;
grid-template-rows: auto auto;
}
}
.optionsRow {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: 1fr;
align-items: center;
}
@media only screen and (max-width: 800px) {
.optionsRow {
/* Switch to 2 rows from 2 columns */
grid-template-columns: auto;
grid-template-rows: auto auto;
align-items: stretch;
}
}
.sortSelect {
/* Put it on the right */
margin-inline-start: auto;

View File

@ -7,6 +7,7 @@ import FtButton from '../ft-button/ft-button.vue'
import FtPlaylistSelector from '../ft-playlist-selector/ft-playlist-selector.vue'
import FtInput from '../../components/ft-input/ft-input.vue'
import FtSelect from '../../components/ft-select/ft-select.vue'
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
import {
showToast,
} from '../../helpers/utils'
@ -31,12 +32,14 @@ export default defineComponent({
'ft-playlist-selector': FtPlaylistSelector,
'ft-input': FtInput,
'ft-select': FtSelect,
'ft-toggle-switch': FtToggleSwitch,
},
data: function () {
return {
selectedPlaylistIdList: [],
createdSincePromptShownPlaylistIdList: [],
query: '',
doSearchPlaylistsWithMatchingVideos: false,
updateQueryDebounce: function() {},
lastShownAt: Date.now(),
lastActiveElement: null,
@ -115,6 +118,12 @@ export default defineComponent({
return this.allPlaylists.filter((playlist) => {
if (typeof (playlist.playlistName) !== 'string') { return false }
if (this.doSearchPlaylistsWithMatchingVideos) {
if (playlist.videos.some((v) => v.title.toLowerCase().includes(this.processedQuery))) {
return true
}
}
return playlist.playlistName.toLowerCase().includes(this.processedQuery)
})
},

View File

@ -12,23 +12,37 @@
playlistCount: selectedPlaylistCount,
}) }}
</p>
<ft-input
ref="searchBar"
:placeholder="$t('User Playlists.AddVideoPrompt.Search in Playlists')"
:show-clear-text-button="true"
:show-action-button="false"
@input="(input) => updateQueryDebounce(input)"
@clear="updateQueryDebounce('')"
/>
<ft-select
v-if="allPlaylists.length > 1"
class="sortSelect"
:value="sortBy"
:select-names="sortBySelectNames"
:select-values="sortBySelectValues"
:placeholder="$t('User Playlists.Sort By.Sort By')"
@change="sortBy = $event"
/>
<div
class="searchInputsRow"
>
<ft-input
ref="searchBar"
:placeholder="$t('User Playlists.AddVideoPrompt.Search in Playlists')"
:show-clear-text-button="true"
:show-action-button="false"
@input="(input) => updateQueryDebounce(input)"
@clear="updateQueryDebounce('')"
/>
</div>
<div
class="optionsRow"
>
<ft-toggle-switch
:label="$t('User Playlists.Playlists with Matching Videos')"
:compact="true"
:default-value="doSearchPlaylistsWithMatchingVideos"
@change="doSearchPlaylistsWithMatchingVideos = !doSearchPlaylistsWithMatchingVideos"
/>
<ft-select
v-if="allPlaylists.length > 1"
class="sortSelect"
:value="sortBy"
:select-names="sortBySelectNames"
:select-values="sortBySelectValues"
:placeholder="$t('User Playlists.Sort By.Sort By')"
@change="sortBy = $event"
/>
</div>
<div class="playlists-container">
<ft-flex-box>
<div

View File

@ -83,6 +83,18 @@ export default defineComponent({
type: Boolean,
required: true,
},
searchVideoModeAllowed: {
type: Boolean,
required: true,
},
searchVideoModeEnabled: {
type: Boolean,
required: true,
},
searchQueryText: {
type: String,
required: true,
},
},
data: function () {
return {
@ -239,6 +251,12 @@ export default defineComponent({
this.newTitle = this.title
this.newDescription = this.description
if (this.videoCount > 0) {
// Only enable search video mode when viewing non empty playlists
this.searchVideoMode = this.searchVideoModeEnabled
this.query = this.searchQueryText
}
this.updateQueryDebounce = debounce(this.updateQuery, 500)
},
methods: {

View File

@ -108,7 +108,7 @@
<div class="playlistOptions">
<ft-icon-button
v-if="isUserPlaylist && videoCount > 0 && !editMode"
v-if="searchVideoModeAllowed && videoCount > 0 && !editMode"
ref="enableSearchModeButton"
:title="$t('User Playlists.SinglePlaylistView.Search for Videos')"
:icon="['fas', 'search']"
@ -198,7 +198,7 @@
</div>
<div
v-if="isUserPlaylist && searchVideoMode"
v-if="searchVideoModeAllowed && searchVideoMode"
class="searchInputsRow"
>
<ft-input
@ -207,11 +207,11 @@
:placeholder="$t('User Playlists.SinglePlaylistView.Search for Videos')"
:show-clear-text-button="true"
:show-action-button="false"
:value="query"
@input="(input) => updateQueryDebounce(input)"
@clear="updateQueryDebounce('')"
/>
<ft-icon-button
v-if="isUserPlaylist && searchVideoMode"
:title="$t('User Playlists.Cancel')"
:icon="['fas', 'times']"
theme="secondary"

View File

@ -113,6 +113,17 @@ export default defineComponent({
}
},
searchVideoModeAllowed() {
return this.isUserPlaylistRequested
},
searchQueryTextRequested() {
return this.$route.query.searchQueryText
},
searchQueryTextPresent() {
const searchQueryText = this.searchQueryTextRequested
return typeof searchQueryText === 'string' && searchQueryText !== ''
},
isUserPlaylistRequested: function () {
return this.$route.query.playlistType === 'user'
},
@ -181,6 +192,11 @@ export default defineComponent({
},
created: function () {
this.getPlaylistInfoDebounce = debounce(this.getPlaylistInfo, 100)
if (this.searchVideoModeAllowed && this.searchQueryTextPresent) {
this.playlistInVideoSearchMode = true
this.videoSearchQuery = this.searchQueryTextRequested
}
},
mounted: function () {
this.getPlaylistInfoDebounce()

View File

@ -22,6 +22,9 @@
:view-count="viewCount"
:info-source="infoSource"
:more-video-data-available="moreVideoDataAvailable"
:search-video-mode-allowed="searchVideoModeAllowed"
:search-video-mode-enabled="playlistInVideoSearchMode"
:search-query-text="searchQueryTextRequested"
class="playlistInfo"
:class="{
promptOpen,

View File

@ -16,6 +16,36 @@
vertical-align: middle;
}
.searchInputsRow {
display: grid;
/* 2 columns */
grid-template-columns: 1fr auto;
column-gap: 16px;
}
@media only screen and (max-width: 800px) {
.searchInputsRow {
/* Switch to 2 rows from 2 columns */
grid-template-columns: auto;
grid-template-rows: auto auto;
}
}
.optionsRow {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: 1fr;
align-items: center;
}
@media only screen and (max-width: 800px) {
.optionsRow {
/* Switch to 2 rows from 2 columns */
grid-template-columns: auto;
grid-template-rows: auto auto;
align-items: stretch;
}
}
.sortSelect {
/* Put it on the right */
margin-inline-start: auto;

View File

@ -10,6 +10,7 @@ import FtSelect from '../../components/ft-select/ft-select.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtInput from '../../components/ft-input/ft-input.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
const SORT_BY_VALUES = {
NameAscending: 'name_ascending',
@ -37,6 +38,7 @@ export default defineComponent({
'ft-element-list': FtElementList,
'ft-icon-button': FtIconButton,
'ft-input': FtInput,
'ft-toggle-switch': FtToggleSwitch,
},
data: function () {
return {
@ -45,6 +47,7 @@ export default defineComponent({
searchDataLimit: 100,
showLoadMoreButton: false,
query: '',
doSearchPlaylistsWithMatchingVideos: false,
activeData: [],
sortBy: SORT_BY_VALUES.LatestPlayedFirst,
}
@ -165,6 +168,10 @@ export default defineComponent({
this.searchDataLimit = 100
this.filterPlaylistAsync()
},
doSearchPlaylistsWithMatchingVideos() {
this.searchDataLimit = 100
this.filterPlaylistAsync()
},
fullData() {
this.activeData = this.fullData
this.filterPlaylist()
@ -209,15 +216,22 @@ export default defineComponent({
if (this.lowerCaseQuery === '') {
this.activeData = this.fullData
this.showLoadMoreButton = this.allPlaylists.length > this.activeData.length
} else {
const filteredPlaylists = this.allPlaylists.filter((playlist) => {
if (typeof (playlist.playlistName) !== 'string') { return false }
return playlist.playlistName.toLowerCase().includes(this.lowerCaseQuery)
})
this.showLoadMoreButton = filteredPlaylists.length > this.searchDataLimit
this.activeData = filteredPlaylists.length < this.searchDataLimit ? filteredPlaylists : filteredPlaylists.slice(0, this.searchDataLimit)
return
}
const filteredPlaylists = this.allPlaylists.filter((playlist) => {
if (typeof (playlist.playlistName) !== 'string') { return false }
if (this.doSearchPlaylistsWithMatchingVideos) {
if (playlist.videos.some((v) => v.title.toLowerCase().includes(this.lowerCaseQuery))) {
return true
}
}
return playlist.playlistName.toLowerCase().includes(this.lowerCaseQuery)
})
this.showLoadMoreButton = filteredPlaylists.length > this.searchDataLimit
this.activeData = filteredPlaylists.length < this.searchDataLimit ? filteredPlaylists : filteredPlaylists.slice(0, this.searchDataLimit)
},
createNewPlaylist: function () {

View File

@ -19,24 +19,39 @@
class="newPlaylistButton"
@click="createNewPlaylist"
/>
<ft-input
<div
v-if="fullData.length > 1"
ref="searchBar"
:placeholder="$t('User Playlists.Search bar placeholder')"
:show-clear-text-button="true"
:show-action-button="false"
@input="(input) => query = input"
@clear="query = ''"
/>
<ft-select
v-if="fullData.length > 1"
class="sortSelect"
:value="sortBy"
:select-names="sortBySelectNames"
:select-values="sortBySelectValues"
:placeholder="$t('User Playlists.Sort By.Sort By')"
@change="sortBy = $event"
/>
class="searchInputsRow"
>
<ft-input
ref="searchBar"
:placeholder="$t('User Playlists.Search bar placeholder')"
:show-clear-text-button="true"
:show-action-button="false"
@input="(input) => query = input"
@clear="query = ''"
/>
</div>
<div
class="optionsRow"
>
<ft-toggle-switch
v-if="fullData.length > 1"
:label="$t('User Playlists.Playlists with Matching Videos')"
:compact="true"
:default-value="doSearchPlaylistsWithMatchingVideos"
@change="doSearchPlaylistsWithMatchingVideos = !doSearchPlaylistsWithMatchingVideos"
/>
<ft-select
v-if="fullData.length > 1"
class="sortSelect"
:value="sortBy"
:select-names="sortBySelectNames"
:select-values="sortBySelectValues"
:placeholder="$t('User Playlists.Sort By.Sort By')"
@change="sortBy = $event"
/>
</div>
</div>
<ft-flex-box
v-if="fullData.length === 0"
@ -56,6 +71,7 @@
v-else-if="activeData.length > 0 && !isLoading"
:data="activeData"
:data-type="'playlist'"
:search-query-text="doSearchPlaylistsWithMatchingVideos ? lowerCaseQuery : ''"
:use-channels-hidden-preference="false"
:hide-forbidden-titles="false"
/>

View File

@ -143,7 +143,8 @@ User Playlists:
it listed here
You have no playlists. Click on the create new playlist button to create a new one.: You have no playlists. Click on the create new playlist button to create a new one.
Empty Search Message: There are no videos in this playlist that matches your search
Search bar placeholder: Search in Playlist
Search bar placeholder: Search for Playlists
Playlists with Matching Videos: Playlists with Matching Videos
This playlist currently has no videos.: This playlist currently has no videos.