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

425 lines
12 KiB
JavaScript

import { defineComponent, nextTick } from 'vue'
import { mapActions } from 'vuex'
import FtShareButton from '../ft-share-button/ft-share-button.vue'
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 {
ctrlFHandler,
formatNumber,
showToast,
} from '../../helpers/utils'
import debounce from 'lodash.debounce'
export default defineComponent({
name: 'PlaylistInfo',
components: {
'ft-share-button': FtShareButton,
'ft-flex-box': FtFlexBox,
'ft-icon-button': FtIconButton,
'ft-input': FtInput,
'ft-prompt': FtPrompt,
'ft-button': FtButton,
},
props: {
id: {
type: String,
required: true,
},
firstVideoId: {
type: String,
required: true,
},
firstVideoPlaylistItemId: {
type: String,
required: true,
},
playlistThumbnail: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
channelThumbnail: {
type: String,
required: true,
},
channelName: {
type: String,
required: true,
},
channelId: {
type: String,
default: null,
},
videoCount: {
type: Number,
required: true,
},
videos: {
type: Array,
required: true
},
viewCount: {
type: Number,
required: true,
},
lastUpdated: {
type: String,
default: undefined,
},
description: {
type: String,
required: true,
},
infoSource: {
type: String,
required: true,
},
moreVideoDataAvailable: {
type: Boolean,
required: true,
},
searchVideoModeAllowed: {
type: Boolean,
required: true,
},
searchVideoModeEnabled: {
type: Boolean,
required: true,
},
searchQueryText: {
type: String,
required: true,
},
},
data: function () {
return {
searchVideoMode: false,
query: '',
updateQueryDebounce: function() {},
editMode: false,
showDeletePlaylistPrompt: false,
showRemoveVideosOnWatchPrompt: false,
newTitle: '',
newDescription: '',
deletePlaylistPromptValues: [
'yes',
'no'
],
}
},
computed: {
hideSharingActions: function () {
return this.$store.getters.getHideSharingActions
},
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance
},
historyCacheById: function () {
return this.$store.getters.getHistoryCacheById
},
thumbnailPreference: function () {
return this.$store.getters.getThumbnailPreference
},
blurThumbnails: function () {
return this.$store.getters.getBlurThumbnails
},
blurThumbnailsStyle: function () {
return this.blurThumbnails ? 'blur(20px)' : null
},
backendPreference: function () {
return this.$store.getters.getBackendPreference
},
hideViews: function () {
return this.$store.getters.getHideVideoViews
},
showPlaylists: function () {
return !this.$store.getters.getHidePlaylists
},
selectedUserPlaylist: function () {
return this.$store.getters.getPlaylist(this.id)
},
deletePlaylistPromptNames: function () {
return [
this.$t('Yes'),
this.$t('No')
]
},
firstVideoIdExists() {
return this.firstVideoId !== ''
},
parsedViewCount() {
return formatNumber(this.viewCount)
},
parsedVideoCount() {
return formatNumber(this.videoCount)
},
thumbnail: function () {
if (this.thumbnailPreference === 'hidden' || !this.firstVideoIdExists) {
return require('../../assets/img/thumbnail_placeholder.svg')
}
let baseUrl = 'https://i.ytimg.com'
if (this.backendPreference === 'invidious') {
baseUrl = this.currentInvidiousInstance
} else if (typeof this.playlistThumbnail === 'string' && this.playlistThumbnail.length > 0) {
// Use playlist thumbnail provided by YT when available
return this.playlistThumbnail
}
switch (this.thumbnailPreference) {
case 'start':
return `${baseUrl}/vi/${this.firstVideoId}/mq1.jpg`
case 'middle':
return `${baseUrl}/vi/${this.firstVideoId}/mq2.jpg`
case 'end':
return `${baseUrl}/vi/${this.firstVideoId}/mq3.jpg`
default:
return `${baseUrl}/vi/${this.firstVideoId}/mqdefault.jpg`
}
},
isUserPlaylist() {
return this.infoSource === 'user'
},
videoPlaylistType() {
return this.isUserPlaylist ? 'user' : ''
},
deletePlaylistButtonVisible: function() {
if (!this.isUserPlaylist) { return false }
// Cannot delete during edit
if (this.editMode) { return false }
// Cannot delete protected playlist
return !this.selectedUserPlaylist.protected
},
sharePlaylistButtonVisible: function() {
// Only online playlists can be shared
if (this.isUserPlaylist) { return false }
// Cannot delete protected playlist
return !this.hideSharingActions
},
quickBookmarkPlaylistId() {
return this.$store.getters.getQuickBookmarkTargetPlaylistId
},
quickBookmarkPlaylist() {
return this.$store.getters.getPlaylist(this.quickBookmarkPlaylistId)
},
quickBookmarkEnabled() {
return this.quickBookmarkPlaylist != null
},
markedAsQuickBookmarkTarget() {
// Only user playlists can be target
if (this.selectedUserPlaylist == null) { return false }
if (this.quickBookmarkPlaylist == null) { return false }
return this.quickBookmarkPlaylist._id === this.selectedUserPlaylist._id
},
},
watch: {
showDeletePlaylistPrompt(shown) {
this.$emit(shown ? 'prompt-open' : 'prompt-close')
},
showRemoveVideosOnWatchPrompt(shown) {
this.$emit(shown ? 'prompt-open' : 'prompt-close')
},
},
created: function () {
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)
},
mounted: function () {
document.addEventListener('keydown', this.keyboardShortcutHandler)
},
beforeDestroy: function () {
document.removeEventListener('keydown', this.keyboardShortcutHandler)
},
methods: {
toggleCopyVideosPrompt: function (force = false) {
if (this.moreVideoDataAvailable && !this.isUserPlaylist && !force) {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["Some videos in the playlist are not loaded yet. Click here to copy anyway."]'), 5000, () => {
this.toggleCopyVideosPrompt(true)
})
return
}
this.showAddToPlaylistPromptForManyVideos({
videos: this.videos,
newPlaylistDefaultProperties: { title: this.title },
})
},
savePlaylistInfo: function () {
if (this.newTitle === '') {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["Playlist name cannot be empty. Please input a name."]'))
return
}
const playlist = {
playlistName: this.newTitle,
protected: this.selectedUserPlaylist.protected,
description: this.newDescription,
videos: this.selectedUserPlaylist.videos,
_id: this.id,
}
try {
this.updatePlaylist(playlist)
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["Playlist has been updated."]'))
} catch (e) {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["There was an issue with updating this playlist."]'))
console.error(e)
} finally {
this.exitEditMode()
}
},
enterEditMode: function () {
this.newTitle = this.title
this.newDescription = this.description
this.editMode = true
this.$emit('enter-edit-mode')
nextTick(() => {
// Some elements only present after rendering update
this.$refs.playlistTitleInput.focus()
})
},
exitEditMode: function () {
this.editMode = false
this.$emit('exit-edit-mode')
},
handleRemoveVideosOnWatchPromptAnswer: function (option) {
if (option === 'yes') {
const videosToWatch = this.selectedUserPlaylist.videos.filter((video) => {
return this.historyCacheById[video.videoId] == null
})
const removedVideosCount = this.selectedUserPlaylist.videos.length - videosToWatch.length
if (removedVideosCount === 0) {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["There were no videos to remove."]'))
this.showRemoveVideosOnWatchPrompt = false
return
}
const playlist = {
playlistName: this.title,
protected: this.selectedUserPlaylist.protected,
description: this.description,
videos: videosToWatch,
_id: this.id
}
try {
this.updatePlaylist(playlist)
showToast(this.$tc('User Playlists.SinglePlaylistView.Toast.{videoCount} video(s) have been removed', removedVideosCount, {
videoCount: removedVideosCount,
}))
} catch (e) {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["There was an issue with updating this playlist."]'))
console.error(e)
}
}
this.showRemoveVideosOnWatchPrompt = false
},
handleDeletePlaylistPromptAnswer: function (option) {
if (option === 'yes') {
if (this.selectedUserPlaylist.protected) {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["This playlist is protected and cannot be removed."]'))
} else {
this.removePlaylist(this.id)
this.$router.push(
{
path: '/userPlaylists'
}
)
showToast(this.$t('User Playlists.SinglePlaylistView.Toast["Playlist {playlistName} has been deleted."]', {
playlistName: this.title,
}))
}
}
this.showDeletePlaylistPrompt = false
},
enableQuickBookmarkForThisPlaylist() {
const currentQuickBookmarkTargetPlaylist = this.quickBookmarkPlaylist
this.updateQuickBookmarkTargetPlaylistId(this.id)
if (currentQuickBookmarkTargetPlaylist != null) {
showToast(
this.$t('User Playlists.SinglePlaylistView.Toast["This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo"]', {
oldPlaylistName: currentQuickBookmarkTargetPlaylist.playlistName,
}),
5000,
() => {
this.updateQuickBookmarkTargetPlaylistId(currentQuickBookmarkTargetPlaylist._id)
showToast(
this.$t('User Playlists.SinglePlaylistView.Toast["Reverted to use {oldPlaylistName} for quick bookmark"]', {
oldPlaylistName: currentQuickBookmarkTargetPlaylist.playlistName,
}),
5000,
)
},
)
} else {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist is now used for quick bookmark'))
}
},
disableQuickBookmark() {
this.updateQuickBookmarkTargetPlaylistId(null)
showToast(this.$t('User Playlists.SinglePlaylistView.Toast.Quick bookmark disabled'))
},
updateQuery(query) {
this.query = query
this.$emit('search-video-query-change', query)
},
keyboardShortcutHandler: function (event) {
ctrlFHandler(event, this.$refs.searchInput)
},
...mapActions([
'showAddToPlaylistPromptForManyVideos',
'updatePlaylist',
'removePlaylist',
'updateQuickBookmarkTargetPlaylistId',
]),
},
})