Skip to content

Commit 65a5b0c

Browse files
authored
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
1 parent 68c74ea commit 65a5b0c

16 files changed

+217
-48
lines changed

src/renderer/components/ft-element-list/ft-element-list.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ export default defineComponent({
3333
hideForbiddenTitles: {
3434
type: Boolean,
3535
default: true
36-
}
36+
},
37+
searchQueryText: {
38+
type: String,
39+
required: false,
40+
default: '',
41+
},
3742
},
3843
computed: {
3944
listType: function () {

src/renderer/components/ft-element-list/ft-element-list.vue

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:show-video-with-last-viewed-playlist="showVideoWithLastViewedPlaylist"
1414
:use-channels-hidden-preference="useChannelsHiddenPreference"
1515
:hide-forbidden-titles="hideForbiddenTitles"
16+
:search-query-text="searchQueryText"
1617
/>
1718
</ft-auto-grid>
1819
</template>

src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.js

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export default defineComponent({
4747
type: Boolean,
4848
default: true
4949
},
50+
searchQueryText: {
51+
type: String,
52+
required: false,
53+
default: '',
54+
},
5055
},
5156
data: function () {
5257
return {

src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
v-else-if="finalDataType === 'playlist'"
2929
:appearance="appearance"
3030
:data="data"
31+
:search-query-text="searchQueryText"
3132
/>
3233
<ft-community-post
3334
v-else-if="finalDataType === 'community'"

src/renderer/components/ft-list-playlist/ft-list-playlist.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ export default defineComponent({
1515
appearance: {
1616
type: String,
1717
required: true
18-
}
18+
},
19+
searchQueryText: {
20+
type: String,
21+
required: false,
22+
default: '',
23+
},
1924
},
2025
data: function () {
2126
return {
@@ -79,6 +84,7 @@ export default defineComponent({
7984
path: `/playlist/${this.playlistId}`,
8085
query: {
8186
playlistType: this.isUserPlaylist ? 'user' : '',
87+
searchQueryText: this.searchQueryText,
8288
},
8389
}
8490
},

src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.css

+30
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,36 @@
1616
flex-direction: column;
1717
}
1818

19+
.searchInputsRow {
20+
display: grid;
21+
22+
/* 2 columns */
23+
grid-template-columns: 1fr auto;
24+
column-gap: 16px;
25+
}
26+
@media only screen and (max-width: 800px) {
27+
.searchInputsRow {
28+
/* Switch to 2 rows from 2 columns */
29+
grid-template-columns: auto;
30+
grid-template-rows: auto auto;
31+
}
32+
}
33+
34+
.optionsRow {
35+
display: grid;
36+
grid-template-columns: repeat(2, 1fr);
37+
grid-template-rows: 1fr;
38+
align-items: center;
39+
}
40+
@media only screen and (max-width: 800px) {
41+
.optionsRow {
42+
/* Switch to 2 rows from 2 columns */
43+
grid-template-columns: auto;
44+
grid-template-rows: auto auto;
45+
align-items: stretch;
46+
}
47+
}
48+
1949
.sortSelect {
2050
/* Put it on the right */
2151
margin-inline-start: auto;

src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import FtButton from '../ft-button/ft-button.vue'
77
import FtPlaylistSelector from '../ft-playlist-selector/ft-playlist-selector.vue'
88
import FtInput from '../../components/ft-input/ft-input.vue'
99
import FtSelect from '../../components/ft-select/ft-select.vue'
10+
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
1011
import {
1112
showToast,
1213
} from '../../helpers/utils'
@@ -31,12 +32,14 @@ export default defineComponent({
3132
'ft-playlist-selector': FtPlaylistSelector,
3233
'ft-input': FtInput,
3334
'ft-select': FtSelect,
35+
'ft-toggle-switch': FtToggleSwitch,
3436
},
3537
data: function () {
3638
return {
3739
selectedPlaylistIdList: [],
3840
createdSincePromptShownPlaylistIdList: [],
3941
query: '',
42+
doSearchPlaylistsWithMatchingVideos: false,
4043
updateQueryDebounce: function() {},
4144
lastShownAt: Date.now(),
4245
lastActiveElement: null,
@@ -115,6 +118,12 @@ export default defineComponent({
115118
return this.allPlaylists.filter((playlist) => {
116119
if (typeof (playlist.playlistName) !== 'string') { return false }
117120

121+
if (this.doSearchPlaylistsWithMatchingVideos) {
122+
if (playlist.videos.some((v) => v.title.toLowerCase().includes(this.processedQuery))) {
123+
return true
124+
}
125+
}
126+
118127
return playlist.playlistName.toLowerCase().includes(this.processedQuery)
119128
})
120129
},

src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.vue

+31-17
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,37 @@
1212
playlistCount: selectedPlaylistCount,
1313
}) }}
1414
</p>
15-
<ft-input
16-
ref="searchBar"
17-
:placeholder="$t('User Playlists.AddVideoPrompt.Search in Playlists')"
18-
:show-clear-text-button="true"
19-
:show-action-button="false"
20-
@input="(input) => updateQueryDebounce(input)"
21-
@clear="updateQueryDebounce('')"
22-
/>
23-
<ft-select
24-
v-if="allPlaylists.length > 1"
25-
class="sortSelect"
26-
:value="sortBy"
27-
:select-names="sortBySelectNames"
28-
:select-values="sortBySelectValues"
29-
:placeholder="$t('User Playlists.Sort By.Sort By')"
30-
@change="sortBy = $event"
31-
/>
15+
<div
16+
class="searchInputsRow"
17+
>
18+
<ft-input
19+
ref="searchBar"
20+
:placeholder="$t('User Playlists.AddVideoPrompt.Search in Playlists')"
21+
:show-clear-text-button="true"
22+
:show-action-button="false"
23+
@input="(input) => updateQueryDebounce(input)"
24+
@clear="updateQueryDebounce('')"
25+
/>
26+
</div>
27+
<div
28+
class="optionsRow"
29+
>
30+
<ft-toggle-switch
31+
:label="$t('User Playlists.Playlists with Matching Videos')"
32+
:compact="true"
33+
:default-value="doSearchPlaylistsWithMatchingVideos"
34+
@change="doSearchPlaylistsWithMatchingVideos = !doSearchPlaylistsWithMatchingVideos"
35+
/>
36+
<ft-select
37+
v-if="allPlaylists.length > 1"
38+
class="sortSelect"
39+
:value="sortBy"
40+
:select-names="sortBySelectNames"
41+
:select-values="sortBySelectValues"
42+
:placeholder="$t('User Playlists.Sort By.Sort By')"
43+
@change="sortBy = $event"
44+
/>
45+
</div>
3246
<div class="playlists-container">
3347
<ft-flex-box>
3448
<div

src/renderer/components/playlist-info/playlist-info.js

+18
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ export default defineComponent({
8383
type: Boolean,
8484
required: true,
8585
},
86+
searchVideoModeAllowed: {
87+
type: Boolean,
88+
required: true,
89+
},
90+
searchVideoModeEnabled: {
91+
type: Boolean,
92+
required: true,
93+
},
94+
searchQueryText: {
95+
type: String,
96+
required: true,
97+
},
8698
},
8799
data: function () {
88100
return {
@@ -239,6 +251,12 @@ export default defineComponent({
239251
this.newTitle = this.title
240252
this.newDescription = this.description
241253

254+
if (this.videoCount > 0) {
255+
// Only enable search video mode when viewing non empty playlists
256+
this.searchVideoMode = this.searchVideoModeEnabled
257+
this.query = this.searchQueryText
258+
}
259+
242260
this.updateQueryDebounce = debounce(this.updateQuery, 500)
243261
},
244262
methods: {

src/renderer/components/playlist-info/playlist-info.vue

+3-3
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108

109109
<div class="playlistOptions">
110110
<ft-icon-button
111-
v-if="isUserPlaylist && videoCount > 0 && !editMode"
111+
v-if="searchVideoModeAllowed && videoCount > 0 && !editMode"
112112
ref="enableSearchModeButton"
113113
:title="$t('User Playlists.SinglePlaylistView.Search for Videos')"
114114
:icon="['fas', 'search']"
@@ -198,7 +198,7 @@
198198
</div>
199199

200200
<div
201-
v-if="isUserPlaylist && searchVideoMode"
201+
v-if="searchVideoModeAllowed && searchVideoMode"
202202
class="searchInputsRow"
203203
>
204204
<ft-input
@@ -207,11 +207,11 @@
207207
:placeholder="$t('User Playlists.SinglePlaylistView.Search for Videos')"
208208
:show-clear-text-button="true"
209209
:show-action-button="false"
210+
:value="query"
210211
@input="(input) => updateQueryDebounce(input)"
211212
@clear="updateQueryDebounce('')"
212213
/>
213214
<ft-icon-button
214-
v-if="isUserPlaylist && searchVideoMode"
215215
:title="$t('User Playlists.Cancel')"
216216
:icon="['fas', 'times']"
217217
theme="secondary"

src/renderer/views/Playlist/Playlist.js

+16
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ export default defineComponent({
113113
}
114114
},
115115

116+
searchVideoModeAllowed() {
117+
return this.isUserPlaylistRequested
118+
},
119+
searchQueryTextRequested() {
120+
return this.$route.query.searchQueryText
121+
},
122+
searchQueryTextPresent() {
123+
const searchQueryText = this.searchQueryTextRequested
124+
return typeof searchQueryText === 'string' && searchQueryText !== ''
125+
},
126+
116127
isUserPlaylistRequested: function () {
117128
return this.$route.query.playlistType === 'user'
118129
},
@@ -181,6 +192,11 @@ export default defineComponent({
181192
},
182193
created: function () {
183194
this.getPlaylistInfoDebounce = debounce(this.getPlaylistInfo, 100)
195+
196+
if (this.searchVideoModeAllowed && this.searchQueryTextPresent) {
197+
this.playlistInVideoSearchMode = true
198+
this.videoSearchQuery = this.searchQueryTextRequested
199+
}
184200
},
185201
mounted: function () {
186202
this.getPlaylistInfoDebounce()

src/renderer/views/Playlist/Playlist.vue

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
:view-count="viewCount"
2323
:info-source="infoSource"
2424
:more-video-data-available="moreVideoDataAvailable"
25+
:search-video-mode-allowed="searchVideoModeAllowed"
26+
:search-video-mode-enabled="playlistInVideoSearchMode"
27+
:search-query-text="searchQueryTextRequested"
2528
class="playlistInfo"
2629
:class="{
2730
promptOpen,

src/renderer/views/UserPlaylists/UserPlaylists.css

+30
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,36 @@
1616
vertical-align: middle;
1717
}
1818

19+
.searchInputsRow {
20+
display: grid;
21+
22+
/* 2 columns */
23+
grid-template-columns: 1fr auto;
24+
column-gap: 16px;
25+
}
26+
@media only screen and (max-width: 800px) {
27+
.searchInputsRow {
28+
/* Switch to 2 rows from 2 columns */
29+
grid-template-columns: auto;
30+
grid-template-rows: auto auto;
31+
}
32+
}
33+
34+
.optionsRow {
35+
display: grid;
36+
grid-template-columns: repeat(2, 1fr);
37+
grid-template-rows: 1fr;
38+
align-items: center;
39+
}
40+
@media only screen and (max-width: 800px) {
41+
.optionsRow {
42+
/* Switch to 2 rows from 2 columns */
43+
grid-template-columns: auto;
44+
grid-template-rows: auto auto;
45+
align-items: stretch;
46+
}
47+
}
48+
1949
.sortSelect {
2050
/* Put it on the right */
2151
margin-inline-start: auto;

src/renderer/views/UserPlaylists/UserPlaylists.js

+22-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import FtSelect from '../../components/ft-select/ft-select.vue'
1010
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
1111
import FtInput from '../../components/ft-input/ft-input.vue'
1212
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
13+
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
1314

1415
const SORT_BY_VALUES = {
1516
NameAscending: 'name_ascending',
@@ -37,6 +38,7 @@ export default defineComponent({
3738
'ft-element-list': FtElementList,
3839
'ft-icon-button': FtIconButton,
3940
'ft-input': FtInput,
41+
'ft-toggle-switch': FtToggleSwitch,
4042
},
4143
data: function () {
4244
return {
@@ -45,6 +47,7 @@ export default defineComponent({
4547
searchDataLimit: 100,
4648
showLoadMoreButton: false,
4749
query: '',
50+
doSearchPlaylistsWithMatchingVideos: false,
4851
activeData: [],
4952
sortBy: SORT_BY_VALUES.LatestPlayedFirst,
5053
}
@@ -165,6 +168,10 @@ export default defineComponent({
165168
this.searchDataLimit = 100
166169
this.filterPlaylistAsync()
167170
},
171+
doSearchPlaylistsWithMatchingVideos() {
172+
this.searchDataLimit = 100
173+
this.filterPlaylistAsync()
174+
},
168175
fullData() {
169176
this.activeData = this.fullData
170177
this.filterPlaylist()
@@ -209,15 +216,22 @@ export default defineComponent({
209216
if (this.lowerCaseQuery === '') {
210217
this.activeData = this.fullData
211218
this.showLoadMoreButton = this.allPlaylists.length > this.activeData.length
212-
} else {
213-
const filteredPlaylists = this.allPlaylists.filter((playlist) => {
214-
if (typeof (playlist.playlistName) !== 'string') { return false }
215-
216-
return playlist.playlistName.toLowerCase().includes(this.lowerCaseQuery)
217-
})
218-
this.showLoadMoreButton = filteredPlaylists.length > this.searchDataLimit
219-
this.activeData = filteredPlaylists.length < this.searchDataLimit ? filteredPlaylists : filteredPlaylists.slice(0, this.searchDataLimit)
219+
return
220220
}
221+
222+
const filteredPlaylists = this.allPlaylists.filter((playlist) => {
223+
if (typeof (playlist.playlistName) !== 'string') { return false }
224+
225+
if (this.doSearchPlaylistsWithMatchingVideos) {
226+
if (playlist.videos.some((v) => v.title.toLowerCase().includes(this.lowerCaseQuery))) {
227+
return true
228+
}
229+
}
230+
231+
return playlist.playlistName.toLowerCase().includes(this.lowerCaseQuery)
232+
})
233+
this.showLoadMoreButton = filteredPlaylists.length > this.searchDataLimit
234+
this.activeData = filteredPlaylists.length < this.searchDataLimit ? filteredPlaylists : filteredPlaylists.slice(0, this.searchDataLimit)
221235
},
222236

223237
createNewPlaylist: function () {

0 commit comments

Comments
 (0)