enhancement(plugin):make playlist detail reactive to upload upgrades from plugins

This commit is contained in:
petitminion 2025-06-02 15:46:59 +00:00
parent 5f0414138b
commit ad9a829af6
8 changed files with 30 additions and 12 deletions

View File

@ -307,7 +307,7 @@ class PlaylistTrackQuerySet(models.QuerySet, common_models.LocalFromFidQuerySet)
return self.annotate(is_playable_by_actor=subquery) return self.annotate(is_playable_by_actor=subquery)
def playable_by(self, actor, include=True): def playable_by(self, actor, include=True):
tracks = music_models.Track.objects.playable_by(actor, include) tracks = music_models.Track.objects.playable_by(actor)
if include: if include:
return self.filter(track__pk__in=tracks).distinct() return self.filter(track__pk__in=tracks).distinct()
else: else:

View File

@ -67,8 +67,10 @@ class PlaylistSerializer(serializers.ModelSerializer):
@extend_schema_field(OpenApiTypes.BOOL) @extend_schema_field(OpenApiTypes.BOOL)
def get_library_followed(self, obj): def get_library_followed(self, obj):
if self.context.get("request", False) and hasattr( if (
self.context["request"], "user" self.context.get("request", False)
and hasattr(self.context["request"], "user")
and hasattr(self.context["request"].user, "actor")
): ):
actor = self.context["request"].user.actor actor = self.context["request"].user.actor
lib_qs = obj.library.received_follows.filter(actor=actor) lib_qs = obj.library.received_follows.filter(actor=actor)

View File

@ -130,14 +130,15 @@ class PlaylistViewSet(
@action(methods=["get"], detail=True) @action(methods=["get"], detail=True)
def tracks(self, request, *args, **kwargs): def tracks(self, request, *args, **kwargs):
playlist = self.get_object() playlist = self.get_object()
plts = playlist.playlist_tracks.all().for_nested_serialization( actor = music_utils.get_actor_from_request(request)
music_utils.get_actor_from_request(request) plts = playlist.playlist_tracks.all().for_nested_serialization(actor)
) for plt in plts.playable_by(actor, include=False)[
plts_without_upload = plts.filter(track__uploads__isnull=True) : settings.THIRD_PARTY_UPLOAD_MAX_UPLOADS
for plt in plts_without_upload[: settings.THIRD_PARTY_UPLOAD_MAX_UPLOADS]: ]:
plugins.trigger_hook( plugins.trigger_hook(
plugins.TRIGGER_THIRD_PARTY_UPLOAD, plugins.TRIGGER_THIRD_PARTY_UPLOAD,
track=plt.track, track=plt.track,
actor=actor,
) )
# Apply pagination # Apply pagination

View File

@ -0,0 +1 @@
Make playlist detail page reactive to plugin upload updates (#2464)

View File

@ -250,7 +250,7 @@ const playlistLibraryFollowInfo = computed(() => {
<hr v-if="filterableArtist || Object.keys(getReportableObjects({ track, album, artist, playlist, account, channel })).length > 0"> <hr v-if="filterableArtist || Object.keys(getReportableObjects({ track, album, artist, playlist, account, channel })).length > 0">
<PopoverItem <PopoverItem
v-if="filterableArtist" v-if="filterableArtist && !props.playlist"
:disabled="!filterableArtist" :disabled="!filterableArtist"
:title="labels.hideArtist" :title="labels.hideArtist"
icon="bi-eye-slash" icon="bi-eye-slash"

View File

@ -1,4 +1,4 @@
import type { Notification } from '~/types' import type { Notification, Track } from '~/types'
import store from '~/store' import store from '~/store'
import { tryOnScopeDispose } from '@vueuse/core' import { tryOnScopeDispose } from '@vueuse/core'
@ -51,6 +51,7 @@ function useWebSocketHandler (eventName: 'mutation.updated', handler: (event: Pe
function useWebSocketHandler (eventName: 'import.status_updated', handler: (event: ImportStatusWS) => void): stopFn function useWebSocketHandler (eventName: 'import.status_updated', handler: (event: ImportStatusWS) => void): stopFn
function useWebSocketHandler (eventName: 'user_request.created', handler: (event: PendingReviewRequests) => void): stopFn function useWebSocketHandler (eventName: 'user_request.created', handler: (event: PendingReviewRequests) => void): stopFn
function useWebSocketHandler (eventName: 'Listen', handler: (event: unknown) => void): stopFn function useWebSocketHandler (eventName: 'Listen', handler: (event: unknown) => void): stopFn
function useWebSocketHandler (eventName: 'playlist.track_updated', handler: (event: {track: Track}) => void): stopFn
function useWebSocketHandler (eventName: string, handler: (event: any) => void): stopFn { function useWebSocketHandler (eventName: string, handler: (event: any) => void): stopFn {
const id = `${+new Date() + Math.random()}` const id = `${+new Date() + Math.random()}`

View File

@ -12,7 +12,7 @@ import useLogger from '~/composables/useLogger'
type SupportedExtension = 'flac' | 'ogg' | 'mp3' | 'opus' | 'aac' | 'm4a' | 'aiff' | 'aif' type SupportedExtension = 'flac' | 'ogg' | 'mp3' | 'opus' | 'aac' | 'm4a' | 'aiff' | 'aif'
export type WebSocketEventName = 'inbox.item_added' | 'import.status_updated' | 'mutation.created' | 'mutation.updated' export type WebSocketEventName = 'inbox.item_added' | 'import.status_updated' | 'mutation.created' | 'mutation.updated'
| 'report.created' | 'user_request.created' | 'Listen' | 'report.created' | 'user_request.created' | 'Listen' | 'playlist.track_updated'
export type OrderingField = 'creation_date' | 'title' | 'album__title' | 'artist__name' | 'release_date' | 'name' export type OrderingField = 'creation_date' | 'title' | 'album__title' | 'artist__name' | 'release_date' | 'name'
| 'applied_date' | 'followers_count' | 'uploads_count' | 'length' | 'items_count' | 'modification_date' | 'size' | 'applied_date' | 'followers_count' | 'uploads_count' | 'length' | 'items_count' | 'modification_date' | 'size'
@ -91,7 +91,8 @@ const store: Module<State, RootState> = {
'mutation.updated': {}, 'mutation.updated': {},
'report.created': {}, 'report.created': {},
'user_request.created': {}, 'user_request.created': {},
Listen: {} Listen: {},
'playlist.track_updated': {}
}, },
pageTitle: null, pageTitle: null,
modalsOpen: new Set([]), modalsOpen: new Set([]),
@ -273,6 +274,7 @@ const store: Module<State, RootState> = {
const handlers = state.websocketEventsHandlers[event.type] const handlers = state.websocketEventsHandlers[event.type]
logger.log('Dispatching websocket event', event, handlers) logger.log('Dispatching websocket event', event, handlers)
if (!handlers) { if (!handlers) {
logger.log('No websocket handlers for this event', event, handlers)
return return
} }

View File

@ -24,6 +24,7 @@ import Alert from '~/components/ui/Alert.vue'
import PlaylistDropdown from '~/components/playlists/PlaylistDropdown.vue' import PlaylistDropdown from '~/components/playlists/PlaylistDropdown.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useWebSocketHandler from '~/composables/useWebSocketHandler'
// TODO: Is this event ever caught somewhere? // TODO: Is this event ever caught somewhere?
// interface Events { // interface Events {
@ -51,6 +52,15 @@ const fullPlaylistTracks = ref<FullPlaylistTrack[]>([])
const tracks = computed(() => fullPlaylistTracks.value.map(({ track }, index) => ({ ...track as Track, position: index + 1 }))) const tracks = computed(() => fullPlaylistTracks.value.map(({ track }, index) => ({ ...track as Track, position: index + 1 })))
const updateTrack = (updatedTrack: Track) => {
fullPlaylistTracks.value = fullPlaylistTracks.value.map((item) =>
item.track.id === updatedTrack.id ? { ...item, track: updatedTrack } : item
);
};
useWebSocketHandler('playlist.track_updated', async (event) => {
updateTrack(event.track);
});
const { t } = useI18n() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
playlist: t('views.playlists.Detail.title') playlist: t('views.playlists.Detail.title')
@ -203,6 +213,7 @@ const shuffle = () => {}
low-height low-height
:is-playable="true" :is-playable="true"
:tracks="tracks" :tracks="tracks"
:playlist="playlist"
> >
{{ t('views.playlists.Detail.button.playAll') }} {{ t('views.playlists.Detail.button.playAll') }}
</PlayButton> </PlayButton>