Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add video search in user playlist feature #4622

27 changes: 27 additions & 0 deletions src/renderer/components/playlist-info/playlist-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import FtInput from '../ft-input/ft-input.vue'
import FtPrompt from '../ft-prompt/ft-prompt.vue'
import FtButton from '../ft-button/ft-button.vue'
import {
formatNumber,
showToast,
} from '../../helpers/utils'
import debounce from 'lodash.debounce'

export default defineComponent({
name: 'PlaylistInfo',
Expand All @@ -18,6 +20,7 @@ export default defineComponent({
'ft-icon-button': FtIconButton,
'ft-input': FtInput,
'ft-prompt': FtPrompt,
'ft-button': FtButton,
},
props: {
id: {
Expand Down Expand Up @@ -83,6 +86,9 @@ export default defineComponent({
},
data: function () {
return {
searchVideoMode: false,
query: '',
updateQueryDebounce: function() {},
editMode: false,
showDeletePlaylistPrompt: false,
showRemoveVideosOnWatchPrompt: false,
Expand Down Expand Up @@ -232,6 +238,8 @@ export default defineComponent({
created: function () {
this.newTitle = this.title
this.newDescription = this.description

this.updateQueryDebounce = debounce(this.updateQuery, 500)
},
methods: {
toggleCopyVideosPrompt: function (force = false) {
Expand Down Expand Up @@ -373,6 +381,25 @@ export default defineComponent({
showToast(this.$t('User Playlists.SinglePlaylistView.Toast.Quick bookmark disabled'))
},

updateQuery(query) {
this.query = query
this.$emit('search-video-query-change', query)
},
enableVideoSearchMode() {
this.searchVideoMode = true
this.$emit('search-video-mode-on')

nextTick(() => {
// Some elements only present after rendering update
this.$refs.searchInput.focus()
})
},
disableVideoSearchMode() {
this.searchVideoMode = false
this.updateQuery('')
this.$emit('search-video-mode-off')
},

...mapActions([
'showAddToPlaylistPromptForManyVideos',
'updatePlaylist',
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/components/playlist-info/playlist-info.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@
justify-content: flex-end;
}

.searchInputsRow {
margin-block-start: 8px;

display: grid;

/* 2 columns */
grid-template-columns: 1fr auto;
column-gap: 8px;
}

@media only screen and (max-width: 1250px) {
:deep(.sharePlaylistIcon .iconDropdown) {
inset-inline-start: auto;
Expand Down
30 changes: 30 additions & 0 deletions src/renderer/components/playlist-info/playlist-info.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<hr>

<div
v-if="!searchVideoMode"
class="channelShareWrapper"
>
<router-link
Expand Down Expand Up @@ -106,6 +107,13 @@
</div>

<div class="playlistOptions">
<ft-icon-button
v-if="isUserPlaylist && !editMode"
:title="$t('User Playlists.SinglePlaylistView.Search for Videos')"
:icon="['fas', 'search']"
theme="secondary"
@click="enableVideoSearchMode"
/>
<ft-icon-button
v-if="editMode"
:title="$t('User Playlists.Save Changes')"
Expand Down Expand Up @@ -187,6 +195,28 @@
@click="handleRemoveVideosOnWatchPromptAnswer"
/>
</div>

<div
v-if="isUserPlaylist && searchVideoMode"
class="searchInputsRow"
>
<ft-input
ref="searchInput"
class="searchInput"
:placeholder="$t('User Playlists.SinglePlaylistView.Search for Videos')"
:show-clear-text-button="true"
:show-action-button="false"
@input="(input) => updateQueryDebounce(input)"
@clear="updateQueryDebounce('')"
/>
<ft-icon-button
v-if="isUserPlaylist && searchVideoMode"
:title="$t('User Playlists.Cancel')"
:icon="['fas', 'times']"
theme="secondary"
@click="disableVideoSearchMode"
/>
</div>
</div>
</template>

Expand Down
25 changes: 20 additions & 5 deletions src/renderer/views/Playlist/Playlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export default defineComponent({
getPlaylistInfoDebounce: function() {},
playlistInEditMode: false,

playlistInVideoSearchMode: false,
videoSearchQuery: '',

promptOpen: false,
}
},
Expand Down Expand Up @@ -104,7 +107,7 @@ export default defineComponent({

moreVideoDataAvailable() {
if (this.isUserPlaylistRequested) {
return this.userPlaylistVisibleLimit < this.videoCount
return this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length
} else {
return this.continuationData !== null
}
Expand All @@ -123,17 +126,29 @@ export default defineComponent({
return this.selectedUserPlaylist?._id !== this.quickBookmarkPlaylistId
},

sometimesFilteredUserPlaylistItems() {
if (!this.isUserPlaylistRequested) { return this.playlistItems }
if (this.processedVideoSearchQuery === '') { return this.playlistItems }

return this.playlistItems.filter((v) => {
return v.title.toLowerCase().includes(this.processedVideoSearchQuery)
})
},
visiblePlaylistItems: function () {
if (!this.isUserPlaylistRequested) {
// No filtering for non user playlists yet
return this.playlistItems
}

if (this.userPlaylistVisibleLimit < this.videoCount) {
return this.playlistItems.slice(0, this.userPlaylistVisibleLimit)
if (this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length) {
return this.sometimesFilteredUserPlaylistItems.slice(0, this.userPlaylistVisibleLimit)
} else {
return this.playlistItems
return this.sometimesFilteredUserPlaylistItems
}
}
},
processedVideoSearchQuery() {
return this.videoSearchQuery.trim().toLowerCase()
},
},
watch: {
$route () {
Expand Down
90 changes: 52 additions & 38 deletions src/renderer/views/Playlist/Playlist.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
}"
@enter-edit-mode="playlistInEditMode = true"
@exit-edit-mode="playlistInEditMode = false"
@search-video-mode-on="playlistInVideoSearchMode = true"
@search-video-mode-off="playlistInVideoSearchMode = false"
@search-video-query-change="(v) => videoSearchQuery = v"
@prompt-open="promptOpen = true"
@prompt-close="promptOpen = false"
/>
Expand All @@ -39,48 +42,59 @@
<template
v-if="playlistItems.length > 0"
>
<transition-group
name="playlistItem"
tag="span"
<template
v-if="visiblePlaylistItems.length > 0"
>
<ft-list-video-numbered
v-for="(item, index) in visiblePlaylistItems"
:key="`${item.videoId}-${item.playlistItemId || index}`"
class="playlistItem"
:data="item"
:playlist-id="playlistId"
:playlist-type="infoSource"
:playlist-index="index"
:playlist-item-id="item.playlistItemId"
appearance="result"
:always-show-add-to-playlist-button="true"
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
:can-move-video-up="index > 0"
:can-move-video-down="index < visiblePlaylistItems.length - 1"
:can-remove-from-playlist="true"
:video-index="index"
:initial-visible-state="index < 10"
@move-video-up="moveVideoUp(item.videoId, item.playlistItemId)"
@move-video-down="moveVideoDown(item.videoId, item.playlistItemId)"
@remove-from-playlist="removeVideoFromPlaylist(item.videoId, item.playlistItemId)"
/>
</transition-group>
<transition-group
name="playlistItem"
tag="span"
>
<ft-list-video-numbered
v-for="(item, index) in visiblePlaylistItems"
:key="`${item.videoId}-${item.playlistItemId || index}`"
class="playlistItem"
:data="item"
:playlist-id="playlistId"
:playlist-type="infoSource"
:playlist-index="index"
:playlist-item-id="item.playlistItemId"
appearance="result"
:always-show-add-to-playlist-button="true"
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
:can-move-video-up="index > 0 && !playlistInVideoSearchMode"
:can-move-video-down="index < playlistItems.length - 1 && !playlistInVideoSearchMode"
:can-remove-from-playlist="true"
:video-index="index"
:initial-visible-state="index < 10"
@move-video-up="moveVideoUp(item.videoId, item.playlistItemId)"
@move-video-down="moveVideoDown(item.videoId, item.playlistItemId)"
@remove-from-playlist="removeVideoFromPlaylist(item.videoId, item.playlistItemId)"
/>
</transition-group>
<ft-flex-box
v-if="moreVideoDataAvailable && !isLoadingMore"
>
<ft-button
:label="$t('Subscriptions.Load More Videos')"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="getNextPage"
/>
</ft-flex-box>
<div
v-if="isLoadingMore"
class="loadNextPageWrapper"
>
<ft-loader />
</div>
</template>
<ft-flex-box
v-if="moreVideoDataAvailable && !isLoadingMore"
v-else
>
<ft-button
:label="$t('Subscriptions.Load More Videos')"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="getNextPage"
/>
<p class="message">
{{ $t("User Playlists['Empty Search Message']") }}
</p>
</ft-flex-box>
<div
v-if="isLoadingMore"
class="loadNextPageWrapper"
>
<ft-loader />
</div>
</template>
<ft-flex-box
v-else
Expand Down
2 changes: 2 additions & 0 deletions static/locales/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ User Playlists:
EarliestPlayedFirst: 'Earliest Played'

SinglePlaylistView:
Search for Videos: Search for Videos

Toast:
This video cannot be moved up.: This video cannot be moved up.
This video cannot be moved down.: This video cannot be moved down.
Expand Down
Loading