Reformatted, fixed playlist page grid, added skipping SponsorBlock if such data is unavailable for any reason, fixed errors popping up during logging in

This commit is contained in:
root
2024-01-15 23:25:15 +05:30
parent 289b9b0b8d
commit fa0cc55bfa
3 changed files with 206 additions and 210 deletions
+64 -65
View File
@@ -1,71 +1,71 @@
<template>
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error"/>
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
<v-container fluid v-else-if="playlist">
<v-row>
<v-col md="8" offset-md="2">
<v-card>
<v-img v-if="playlist.avatarUrl" :src="playlist.avatarUrl"/>
<v-card-title class="text-h4">
{{ playlist.name }}
</v-card-title>
<v-card-text>
<router-link v-if="playlist.uploaderUrl" :to="playlist.uploaderUrl" custom
v-slot="{ navigate }">
<div style="justify-items: center; align-items: center; vertical-align: center; display: flex; cursor: pointer" @click="navigate" @keypress.enter="navigate" role="link">
<img :src="playlist.uploaderAvatar" height="48" width="48" class="rounded-circle" />
<div class="text-h5 ml-4">
{{ playlist.uploader }}
</div>
</div>
</router-link>
<h5 class="text-h5 ml-16">{{ $tc('counts.videos', playlist.videos, { n: $store.getters['i18n/fmtFullNumber'](playlist.videos) }) }}</h5>
<br />
<div v-html="renderedDescription" />
</v-card-text>
<v-card-actions>
<v-btn icon x-large link :href="getRssUrl">
<v-icon x-large>{{ mdiRssBox }}</v-icon>
</v-btn>
<v-btn icon x-large link :href="youtubeURL">
<v-icon x-large>{{ mdiYoutube }}</v-icon>
</v-btn>
<v-btn v-if="isAdmin" @click="deleteSelf" outlined>
{{ $t('playlists.delete_self') }}
</v-btn>
<v-btn v-if="isAdmin" @click="renameSelf" outlined>
{{ $t('playlists.rename_self') }}
</v-btn>
</v-card-actions>
</v-card>
<v-row v-for="(chunk, chunkId) in chunkedByFour" :key="chunkId" class="mt-4">
<v-col md="3" v-for="video in chunk" :key="video.url">
<VideoItem :video="video" max-height>
<template v-slot:bottom>
<v-card-actions v-if="isAdmin">
<v-btn
outlined
color="secondary"
@click.prevent="removeVideo(video)"
class="pm-breakable-button"
>
{{ $t('playlists.remove_video')}}
</v-btn>
</v-card-actions>
</template>
</VideoItem>
</v-col>
</v-row>
</v-col>
</v-row>
</v-container>
<v-container fluid v-else-if="playlist">
<v-row>
<v-col md="8" offset-md="2">
<v-card>
<v-img v-if="playlist.avatarUrl" :src="playlist.avatarUrl" />
<v-card-title class="text-h4">
{{ playlist.name }}
</v-card-title>
<v-card-text>
<router-link v-if="playlist.uploaderUrl" :to="playlist.uploaderUrl" custom v-slot="{ navigate }">
<div style="justify-items: center; align-items: center; vertical-align: center; display: flex; cursor: pointer"
@click="navigate" @keypress.enter="navigate" role="link">
<img :src="playlist.uploaderAvatar" height="48" width="48" class="rounded-circle" />
<div class="text-h5 ml-4">
{{ playlist.uploader }}
</div>
</div>
</router-link>
<h5 class="text-h5 ml-16">{{ $tc('counts.videos', playlist.videos, {
n:
$store.getters['i18n/fmtFullNumber'](playlist.videos)
}) }}</h5>
<br />
<div v-html="renderedDescription" />
</v-card-text>
<v-card-actions>
<v-btn icon x-large link :href="getRssUrl">
<v-icon x-large>{{ mdiRssBox }}</v-icon>
</v-btn>
<v-btn icon x-large link :href="youtubeURL">
<v-icon x-large>{{ mdiYoutube }}</v-icon>
</v-btn>
<v-btn v-if="isAdmin" @click="deleteSelf" outlined>
{{ $t('playlists.delete_self') }}
</v-btn>
<v-btn v-if="isAdmin" @click="renameSelf" outlined>
{{ $t('playlists.rename_self') }}
</v-btn>
</v-card-actions>
</v-card>
<v-divider class="my-4" />
<GridRow>
<GridCol v-for="video in playlist.relatedStreams" :key="video.url">
<VideoItem :video="video" max-height>
<template v-slot:bottom>
<v-card-actions v-if="isAdmin">
<v-btn outlined color="secondary" @click.prevent="removeVideo(video)"
class="pm-breakable-button">
{{ $t('playlists.remove_video') }}
</v-btn>
</v-card-actions>
</template>
</VideoItem>
</GridCol>
</GridRow>
</v-col>
</v-row>
</v-container>
</template>
<script>
import { chunk as _chunk } from 'lodash-es'
import { mdiRssBox, mdiYoutube } from '@mdi/js'
import GridRow from '@/components/Grid/GridRow.vue'
import GridCol from '@/components/Grid/GridCol.vue'
import ErrorHandler from '@/components/ErrorHandler.vue'
import VideoItem from '@/components/Video/VideoItem.vue'
@@ -114,9 +114,6 @@ export default {
const url = new URL('https://youtube.com/playlist')
url.searchParams.set('list', this.playlistID)
return url.href
},
chunkedByFour () {
return _chunk(this.playlist.relatedStreams, 4)
}
},
methods: {
@@ -168,7 +165,9 @@ export default {
},
components: {
ErrorHandler,
VideoItem
VideoItem,
GridRow,
GridCol
}
}
</script>
+141 -144
View File
@@ -1,148 +1,136 @@
<template>
<NGErrorHandler :error="error" v-if="error != null" />
<v-progress-linear indeterminate v-else-if="!loaded" />
<div v-else>
<Player
ref="player"
:video="video"
:initial-skip="initialSkip"
:sponsors="sponsors"
:selectedAutoLoop="selectedAutoLoop"
:audio-only="($store.getters['prefs/getPreferenceBoolean']('listen', false) || $route.query.listen === '1') && !video.livestream"
@videoEnded="videoEnded"
@timeupdate="onTimeUpdate"
/>
<NGErrorHandler :error="error" v-if="error != null" />
<v-progress-linear indeterminate v-else-if="!loaded" />
<div v-else>
<Player ref="player" :video="video" :initial-skip="initialSkip" :sponsors="sponsors"
:selectedAutoLoop="selectedAutoLoop"
:audio-only="($store.getters['prefs/getPreferenceBoolean']('listen', false) || $route.query.listen === '1') && !video.livestream"
@videoEnded="videoEnded" @timeupdate="onTimeUpdate" />
<v-dialog max-width="960" v-model="sharingPanelOpen">
<VideoSharingPanel @input="sharingPanelOpen = $event" :current-time="currentTime" :video-id="videoId" />
</v-dialog>
<VideoPlaylistOperationsA
:dialog-open="playlistAddDialogOpen"
@input="playlistAddDialogOpen = $event"
:video-id="videoId"
/>
<!-- <v-dialog max-width="720" v-model="playlistRemoveDialogOpen">
<v-dialog max-width="960" v-model="sharingPanelOpen">
<VideoSharingPanel @input="sharingPanelOpen = $event" :current-time="currentTime" :video-id="videoId" />
</v-dialog>
<VideoPlaylistOperationsA :dialog-open="playlistAddDialogOpen" @input="playlistAddDialogOpen = $event"
:video-id="videoId" />
<!-- <v-dialog max-width="720" v-model="playlistRemoveDialogOpen">
<VideoSharingPanel @input="sharingPanelOpen = $event" :current-time="currentTime" :video-id="videoId" />
</v-dialog> -->
<v-row dense class="mb-2">
<v-col md="10" offset-md="1">
<v-card outlined color="bgTwo">
<v-card-title class="text-h5">{{ video.title }}</v-card-title>
<v-card-subtitle class="text-subtitle-1">
<v-row align="center">
<v-col cols="12" md="6">
<ExpandableNumber :num="video.views" message-key="counts.views" />
{{ $store.getters['i18n/fmtDate'](new Date(video.uploadDate)) }}
<span v-if="lastWatchFound">
{{ $t('misc.lastWatchedTill', { t: lastWatchDurationH }) }}
<ExpandableDate :date="lastWatch.timestamp" />
</span>
<span v-if="video.visibility !== 'public'"> {{ $t('visibilities.' + video.visibility) }}</span>
</v-col>
<v-col cols="12" md="6" :style="$vuetify.breakpoint.mdAndUp ? { textAlign: 'right' } : {}">
<span style="vertical-align: middle">
<v-icon>{{ mdiThumbUp }}</v-icon>
<b class="ml-1">{{ $store.getters['i18n/fmtFullNumber'](video.likes) }}</b>
</span>
<v-btn outlined class="mt-1 ml-1" @click.stop="onShareClick">
<v-icon class="mr-1">
{{ mdiShareVariant }}
</v-icon>
{{ $t('video_sharing_panel.share') }}
</v-btn>
<v-btn outlined class="mt-1 ml-1" @click.stop="playlistAddDialogOpen = true" v-if="$store.getters['auth/isCurrentlyAuthenticated']">
<v-icon class="mr-1">
{{ mdiLinkPlus }}
</v-icon>
{{ $t('playlists.add_to_playlist') }}
</v-btn>
<v-btn class="mt-1 ml-1" link :href="'https://odysee.com/' + video.lbryId"
v-if="video.lbryId" target="_blank" outlined>
LBRY
</v-btn>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="mt-1 ml-1" @click="downloadAccess"
outlined
v-bind="attrs" v-on="on"
>
M3U8/DASH/Stream
</v-btn>
</template>
<span>
With this you can play the video in your own media player.
</span>
</v-tooltip>
</v-col>
</v-row>
</v-card-subtitle>
<v-divider class="my-1" v-if="Array.isArray(video.chapters) && video.chapters.length !== 0" />
<VideoChapters :chapters="video.chapters" @seek="$refs.player.skipToTime($event)" v-if="Array.isArray(video.chapters) && video.chapters.length !== 0" />
<v-divider class="my-1" />
<v-card-text>
<router-link v-if="video.uploaderUrl" :to="video.uploaderUrl" custom v-slot="{ navigate }">
<div
style="justify-items: center; align-items: center; vertical-align: center; display: flex; cursor: pointer"
@click="navigate" @keypress.enter="navigate" role="link"
>
<div>
<v-img :src="video.uploaderAvatar" height="48" width="48" class="rounded-circle" />
</div>
<a :href="video.uploaderUrl" class="text-h5 ml-4 text-decoration-none">
{{ video.uploader }}
<br />
<span class="text-subtitle-1">
<ExpandableNumber :num="video.uploaderSubscriberCount" message-key="counts.subscribers" />
</span>
</a>
<div class="ml-4">
<SubscriptionButton :channel-id="channelId" />
</div>
</div>
</router-link>
<div class="mt-4" dir="auto" v-html="video.description" />
<v-divider class="my-4" />
<div class="mt-4" v-if="showDesc && sponsors && sponsors.segments">
Sponsors Segments: {{ sponsors.segments.length }}
</div>
<div>
<!-- TODO translate -->
<v-checkbox dense :input-value="isAutoplayEnabled" @change="onAutoplayChg"
:label="$t('actions.autoplay_next_video')" hide-details />
<v-checkbox dense
:input-value="$store.getters['prefs/getPreferenceBoolean']('listen', false)"
@change="onListenChg"
:label="$t('preferences.listen')"
hide-details
/>
<v-checkbox dense v-model="selectedAutoLoop" :label="$t('actions.loop_this_video')" hide-details />
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-row dense class="mb-2">
<v-col md="10" offset-md="1">
<v-card outlined color="bgTwo">
<v-card-title class="text-h5">{{ video.title }}</v-card-title>
<v-card-subtitle class="text-subtitle-1">
<v-row align="center">
<v-col cols="12" md="6">
<ExpandableNumber :num="video.views" message-key="counts.views" />
{{ $store.getters['i18n/fmtDate'](new Date(video.uploadDate)) }}
<span v-if="lastWatchFound">
{{ $t('misc.lastWatchedTill', { t: lastWatchDurationH }) }}
<ExpandableDate :date="lastWatch.timestamp" />
</span>
<span v-if="video.visibility !== 'public'"> {{ $t('visibilities.' + video.visibility)
}}</span>
</v-col>
<v-col cols="12" md="6" :style="$vuetify.breakpoint.mdAndUp ? { textAlign: 'right' } : {}">
<span style="vertical-align: middle">
<v-icon>{{ mdiThumbUp }}</v-icon>
<b class="ml-1">{{ $store.getters['i18n/fmtFullNumber'](video.likes) }}</b>
</span>
<v-btn outlined class="mt-1 ml-1" @click.stop="onShareClick">
<v-icon class="mr-1">
{{ mdiShareVariant }}
</v-icon>
{{ $t('video_sharing_panel.share') }}
</v-btn>
<v-btn outlined class="mt-1 ml-1" @click.stop="playlistAddDialogOpen = true"
v-if="$store.getters['auth/isCurrentlyAuthenticated']">
<v-icon class="mr-1">
{{ mdiLinkPlus }}
</v-icon>
{{ $t('playlists.add_to_playlist') }}
</v-btn>
<v-btn class="mt-1 ml-1" link :href="'https://odysee.com/' + video.lbryId"
v-if="video.lbryId" target="_blank" outlined>
LBRY
</v-btn>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn class="mt-1 ml-1" @click="downloadAccess" outlined v-bind="attrs" v-on="on">
M3U8/DASH/Stream
</v-btn>
</template>
<span>
With this you can play the video in your own media player.
</span>
</v-tooltip>
</v-col>
</v-row>
</v-card-subtitle>
<v-divider class="my-1" v-if="Array.isArray(video.chapters) && video.chapters.length !== 0" />
<VideoChapters :chapters="video.chapters" @seek="$refs.player.skipToTime($event)"
v-if="Array.isArray(video.chapters) && video.chapters.length !== 0" />
<v-divider class="my-1" />
<v-card-text>
<router-link v-if="video.uploaderUrl" :to="video.uploaderUrl" custom v-slot="{ navigate }">
<div style="justify-items: center; align-items: center; vertical-align: center; display: flex; cursor: pointer"
@click="navigate" @keypress.enter="navigate" role="link">
<div>
<v-img :src="video.uploaderAvatar" height="48" width="48" class="rounded-circle" />
</div>
<a :href="video.uploaderUrl" class="text-h5 ml-4 text-decoration-none">
{{ video.uploader }}
<br />
<span class="text-subtitle-1">
<ExpandableNumber :num="video.uploaderSubscriberCount"
message-key="counts.subscribers" />
</span>
</a>
<div class="ml-4">
<SubscriptionButton :channel-id="channelId" />
</div>
</div>
</router-link>
<div class="mt-4" dir="auto" v-html="video.description" />
<v-divider class="my-4" />
<div class="mt-4" v-if="showDesc && sponsors && sponsors.segments">
Sponsors Segments: {{ sponsors.segments.length }}
</div>
<div>
<!-- TODO translate -->
<v-checkbox dense :input-value="isAutoplayEnabled" @change="onAutoplayChg"
:label="$t('actions.autoplay_next_video')" hide-details />
<v-checkbox dense :input-value="$store.getters['prefs/getPreferenceBoolean']('listen', false)"
@change="onListenChg" :label="$t('preferences.listen')" hide-details />
<v-checkbox dense v-model="selectedAutoLoop" :label="$t('actions.loop_this_video')"
hide-details />
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="8" offset-md="1" v-if="commentsLoaded">
<h5 class="text-h4 text-center my-4">{{ $t('actions.related_comments') }}</h5>
<VideoComment v-for="comment in comments.comments" :key="comment.commentId" :comment="comment"
:video="video" />
<v-progress-linear indeterminate v-intersect="onCommentsProgressIntersect"
v-if="comments.comments.length !== 0 && comments.nextpage != null" />
</v-col>
<v-col cols="12" md="8" offset-md="1" style="display: flex; justify-content: center" v-else-if="$store.getters['prefs/getPreferenceBoolean']('disableCommentsByDefault')">
<v-btn x-large @click="fetchComments">{{ $t('actions.loadComments') }}</v-btn>
</v-col>
<v-col cols="12" md="2" v-if="video && video.relatedStreams && $store.getters['prefs/getPreferenceBoolean']('showRelatedVideos')">
<h5 class="text-h4 text-center my-4">{{ $t('actions.related_videos') }}</h5>
<VideoItem class="my-4" v-for="related in video.relatedStreams" :video="related" :key="related.url" />
</v-col>
</v-row>
</div>
<v-row>
<v-col cols="12" md="8" offset-md="1" v-if="commentsLoaded">
<h5 class="text-h4 text-center my-4">{{ $t('actions.related_comments') }}</h5>
<VideoComment v-for="comment in comments.comments" :key="comment.commentId" :comment="comment"
:video="video" />
<v-progress-linear indeterminate v-intersect="onCommentsProgressIntersect"
v-if="comments.comments.length !== 0 && comments.nextpage != null" />
</v-col>
<v-col cols="12" md="8" offset-md="1" style="display: flex; justify-content: center"
v-else-if="$store.getters['prefs/getPreferenceBoolean']('disableCommentsByDefault')">
<v-btn x-large @click="fetchComments">{{ $t('actions.loadComments') }}</v-btn>
</v-col>
<v-col cols="12" md="2"
v-if="video && video.relatedStreams && $store.getters['prefs/getPreferenceBoolean']('showRelatedVideos')">
<h5 class="text-h4 text-center my-4">{{ $t('actions.related_videos') }}</h5>
<VideoItem class="my-4" v-for="related in video.relatedStreams" :video="related" :key="related.url" />
</v-col>
</v-row>
</div>
</template>
<script>
@@ -288,12 +276,21 @@ export default {
if (selectedSkip.length === 0) {
return
}
this.sponsors = await this.$store.dispatch('auth/makeRequest', {
path: '/sponsors/' + this.videoId,
params: {
category: JSON.stringify(selectedSkip)
try {
this.sponsors = await this.$store.dispatch('auth/makeRequest', {
path: '/sponsors/' + this.videoId,
params: {
category: JSON.stringify(selectedSkip)
}
})
} catch (e) {
// Just skip if sponsors are not available for some reason
if (e.response?.status === 500) {
this.sponsors = null
} else {
throw e
}
})
}
},
async fetchComments () {
this.comments = await this.$store.dispatch('auth/makeRequest', {
+1 -1
View File
@@ -74,7 +74,7 @@ const AuthenticationStore = {
})
resp = data.data
} catch (e) {
if (e.response.status === 400) {
if (e.response.status === 400 || e.response.status === 401) {
const errorData = e.response.data
if ('error' in errorData) {
throw new AuthenticationError(errorData.error)