mirror of https://github.com/FreeTubeApp/FreeTube
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:
parent
68c74ea2b6
commit
65a5b0c045
|
@ -33,7 +33,12 @@ export default defineComponent({
|
|||
hideForbiddenTitles: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
searchQueryText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
listType: function () {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -47,6 +47,11 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
searchQueryText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
|
|
|
@ -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'"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue