Merge branch '2456-alpha1-followup' into 'develop'

alha release followup

Closes #2456

See merge request funkwhale/funkwhale!2921
This commit is contained in:
petitminion 2025-05-31 08:57:26 +00:00
commit 9fe379d55b
9 changed files with 62 additions and 31 deletions

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: Funkwhale API title: Funkwhale API
version: 1.4.0 version: 2.0.0a1
description: | description: |
# Funkwhale API # Funkwhale API

View File

@ -56,12 +56,8 @@ class Migration(migrations.Migration):
model_name="trackfavorite", model_name="trackfavorite",
name="fid", name="fid",
field=models.URLField( field=models.URLField(
db_index=True,
default="https://default.fid",
max_length=500, max_length=500,
unique=True,
), ),
preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name="trackfavorite", model_name="trackfavorite",
@ -90,6 +86,15 @@ class Migration(migrations.Migration):
related_name="track_favorites", related_name="track_favorites",
to="federation.actor", to="federation.actor",
), ), ), ),
migrations.AlterField(
model_name="trackfavorite",
name="fid",
field=models.URLField(
db_index=True,
max_length=500,
unique=True,
),
),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="trackfavorite", name="trackfavorite",
unique_together={("track", "actor")}, unique_together={("track", "actor")},

View File

@ -20,6 +20,7 @@ from funkwhale_api.music import utils as music_utils
from funkwhale_api.users.oauth import permissions as oauth_permissions from funkwhale_api.users.oauth import permissions as oauth_permissions
from . import filters, models, parsers, renderers, serializers from . import filters, models, parsers, renderers, serializers
from rest_framework.pagination import PageNumberPagination
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -138,9 +139,15 @@ class PlaylistViewSet(
plugins.TRIGGER_THIRD_PARTY_UPLOAD, plugins.TRIGGER_THIRD_PARTY_UPLOAD,
track=plt.track, track=plt.track,
) )
serializer = serializers.PlaylistTrackSerializer(plts, many=True)
data = {"count": len(plts), "results": serializer.data} # Apply pagination
return Response(data, status=200) paginator = PageNumberPagination()
paginator.page_size = 100 # Set the page size (number of items per page)
paginated_plts = paginator.paginate_queryset(plts, request)
# Serialize the paginated data
serializer = serializers.PlaylistTrackSerializer(paginated_plts, many=True)
return paginator.get_paginated_response(serializer.data)
@extend_schema( @extend_schema(
operation_id="add_to_playlist", request=serializers.PlaylistAddManySerializer operation_id="add_to_playlist", request=serializers.PlaylistAddManySerializer

View File

@ -25,7 +25,6 @@ interface Props extends PlayOptionsProps {
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
isPlayable?: boolean isPlayable?: boolean
tracks?: Track[]
artist?: Artist | null artist?: Artist | null
album?: Album | null album?: Album | null
playlist?: Playlist | null playlist?: Playlist | null
@ -39,7 +38,6 @@ const props = withDefaults(defineProps<Props>(), {
isArtist: false, isArtist: false,
isAlbum: false, isAlbum: false,
tracks: () => [],
artist: null, artist: null,
album: null, album: null,
playlist: null, playlist: null,

View File

@ -245,7 +245,7 @@ const updatePage = (page: number) => {
<Loader v-if="isLoading" /> <Loader v-if="isLoading" />
<!-- For each item, build a row --> <!-- For each item, build a row -->
<!--
<track-mobile-row <track-mobile-row
v-for="(track, index) in allTracks" v-for="(track, index) in allTracks"
:key="track.id" :key="track.id"
@ -258,7 +258,7 @@ const updatePage = (page: number) => {
:is-artist="isArtist" :is-artist="isArtist"
:is-album="isAlbum" :is-album="isAlbum"
:is-podcast="isPodcast" :is-podcast="isPodcast"
/> /> -->
<Pagination <Pagination
v-if="paginateResults" v-if="paginateResults"
:pages="paginateBy" :pages="paginateBy"

View File

@ -176,7 +176,6 @@ const isOpen = useModal('artist-description').isOpen
class="description" class="description"
:content="{ ...object.description, text: object.description.text ?? undefined }" :content="{ ...object.description, text: object.description.text ?? undefined }"
:truncate-length="100" :truncate-length="100"
:more-link="false"
/> />
<Spacer grow /> <Spacer grow />
<Link <Link

View File

@ -4597,7 +4597,8 @@
"edit": "Edit", "edit": "Edit",
"embed": "Embed", "embed": "Embed",
"playAll": "Play all", "playAll": "Play all",
"stopEdit": "Stop Editing" "stopEdit": "Stop Editing",
"loadMoreTracks": "Load more tracks"
}, },
"empty": { "empty": {
"noTracks": "There are no tracks in this playlist yet" "noTracks": "There are no tracks in this playlist yet"
@ -4800,4 +4801,4 @@
} }
} }
} }
} }

View File

@ -190,7 +190,6 @@ const isOpen = useModal('artist-description').isOpen
class="description" class="description"
:content="{ html: object?.summary.html || '' }" :content="{ html: object?.summary.html || '' }"
:truncate-length="100" :truncate-length="100"
:more-link="false"
/> />
<Spacer grow /> <Spacer grow />
<Link <Link

View File

@ -31,7 +31,7 @@ import useErrorHandler from '~/composables/useErrorHandler'
// } // }
interface Props { interface Props {
id: number id: string
defaultEdit?: boolean defaultEdit?: boolean
} }
@ -40,7 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
}) })
const store = useStore() const store = useStore()
const isLoadingMoreTracks = ref(false)
const edit = ref(props.defaultEdit) const edit = ref(props.defaultEdit)
const playlist = ref<Playlist | null>(null) const playlist = ref<Playlist | null>(null)
const playlistTracks = ref<PlaylistTrack[]>([]) const playlistTracks = ref<PlaylistTrack[]>([])
@ -57,17 +57,24 @@ const labels = computed(() => ({
})) }))
const isLoading = ref(false) const isLoading = ref(false)
const nextPage = ref<string | null>(null) // Tracks the next page URL
const previousPage = ref<string | null>(null) // Tracks the previous page URL
const totalTracks = ref<number>(0) // Total number of tracks
const fetchData = async () => { const fetchData = async () => {
isLoading.value = true isLoading.value = true
try { try {
const [playlistResponse, tracksResponse] = await Promise.all([ const [playlistResponse, tracksResponse] = await Promise.all([
axios.get(`playlists/${props.id}/`), axios.get(`playlists/${props.id}/`),
axios.get(`playlists/${props.id}/tracks/`) axios.get(`playlists/${props.id}/tracks?page=1`)
]) ])
playlist.value = playlistResponse.data playlist.value = playlistResponse.data
fullPlaylistTracks.value = tracksResponse.data.results fullPlaylistTracks.value = tracksResponse.data.results
nextPage.value = tracksResponse.data.next
previousPage.value = tracksResponse.data.previous
totalTracks.value = tracksResponse.data.count
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
} }
@ -75,6 +82,25 @@ const fetchData = async () => {
isLoading.value = false isLoading.value = false
} }
const loadMoreTracks = async () => {
if (nextPage.value) {
isLoadingMoreTracks.value = true; // Set loading state for the button
try {
const response = await axios.get(nextPage.value);
// Append new tracks to the existing list
fullPlaylistTracks.value = [...fullPlaylistTracks.value, ...response.data.results];
// Update pagination metadata
nextPage.value = response.data.next;
} catch (error) {
useErrorHandler(error as Error)
} finally {
isLoadingMoreTracks.value = false; // Reset loading state
}
}
};
fetchData() fetchData()
const images = computed(() => { const images = computed(() => {
@ -113,17 +139,6 @@ const randomizedColors = computed(() => shuffleArray(bgcolors.value))
// return t('components.audio.ChannelCard.title', { date }) // return t('components.audio.ChannelCard.title', { date })
// }) // })
// TODO: Check if this function is still needed
// const deletePlaylist = async () => {
// try {
// await axios.delete(`playlists/${props.id}/`)
// store.dispatch('playlists/fetchOwn')
// return router.push({ path: '/library' })
// } catch (error) {
// useErrorHandler(error as Error)
// }
// }
// TODO: Implement shuffle // TODO: Implement shuffle
const shuffle = () => {} const shuffle = () => {}
</script> </script>
@ -178,7 +193,6 @@ const shuffle = () => {}
<RenderedDescription <RenderedDescription
:content="{ html: playlist.description }" :content="{ html: playlist.description }"
:truncate-length="100" :truncate-length="100"
:show-more="true"
/> />
<Layout <Layout
flex flex
@ -240,6 +254,14 @@ const shuffle = () => {}
:tracks="tracks" :tracks="tracks"
:unique="false" :unique="false"
/> />
<Button
v-if="nextPage"
primary
:is-loading="isLoadingMoreTracks"
@click="loadMoreTracks"
>
{{ t('views.playlists.Detail.button.loadMoreTracks') }}
</Button>
</template> </template>
<Alert <Alert
v-else-if="!isLoading" v-else-if="!isLoading"