From 2385ec38e8055d88d4f4b0329194a38ad1035918 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Fri, 24 Jul 2026 18:54:31 +0200 Subject: [PATCH] Changes --- .../Albums/GetRecentlyListenedAlbums.cs | 8 +- .../Configuration/PlayListConfiguration.cs | 18 ++++ .../PlayLists/CreatePlayList.cs | 58 ++++++++---- .../PlayLists/UpdatePlayList.cs | 10 ++- backend/Musify.Domain/Entities/PlayList.cs | 3 +- .../ValueObjects/PlayListPictures.cs | 14 ++- .../Logs/UpdatePlayListPictureLog.cs | 8 +- web-player/package-lock.json | 11 +++ web-player/package.json | 1 + web-player/src/lib/api/schema.d.ts | 2 +- .../src/lib/components/ui/ArtistAvatar.svelte | 2 +- .../src/lib/components/ui/MediaCard.svelte | 2 +- .../src/lib/components/ui/MixBentoTile.svelte | 48 ++++++---- .../lib/components/ui/PlayPauseOverlay.svelte | 3 +- .../lib/components/ui/SectionHeading.svelte | 10 ++- web-player/src/lib/config.ts | 1 - web-player/src/lib/server/topArtists.ts | 42 --------- web-player/src/lib/theme/theme.css | 4 +- .../src/routes/(app)/explore/+page.svelte | 19 ++-- web-player/src/routes/+layout.svelte | 1 + web-player/src/routes/+page.server.ts | 3 - web-player/src/routes/+page.svelte | 89 +++++++------------ web-player/src/routes/layout.css | 5 +- 23 files changed, 187 insertions(+), 175 deletions(-) delete mode 100644 web-player/src/lib/server/topArtists.ts diff --git a/backend/Musify.Application/Albums/GetRecentlyListenedAlbums.cs b/backend/Musify.Application/Albums/GetRecentlyListenedAlbums.cs index 4db0e34..a4399f2 100644 --- a/backend/Musify.Application/Albums/GetRecentlyListenedAlbums.cs +++ b/backend/Musify.Application/Albums/GetRecentlyListenedAlbums.cs @@ -7,7 +7,7 @@ using Musify.Application.Contracts; namespace Musify.Application.Albums { public record GetRecentlyListenedAlbumsQuery(long UserId, int Limit) - : IQuery>>; + : IQuery >>; public class GetRecentlyListenedAlbumsQueryHandler(IDatabase database) : IQueryHandler>> @@ -24,9 +24,9 @@ namespace Musify.Application.Albums listenedTrackIds, albumTrack => albumTrack.TrackId, history => history.TrackId, - (albumTrack, history) => new { albumTrack.AlbumId, history.ListenedAt }) + (albumTrack, history) => new { albumTrack.AlbumId, history.ListenedAt } ) .GroupBy(entry => entry.AlbumId) - .Select(group => new { AlbumId = group.Key, LastListenedAt = group.Max(entry => entry.ListenedAt) }) + .Select(group => new { AlbumId = group.Key, LastListenedAt = group.Max(entry => entry.ListenedAt) } ) .OrderByDescending(entry => entry.LastListenedAt) .Take(request.Limit) .ToListAsync(cancellationToken); @@ -48,7 +48,7 @@ namespace Musify.Application.Albums .Take(AlbumApplicationResponse.CoverTrackCount) .Select(albumTrack => albumTrack.TrackId) .ToList() - }) + } ) .ToDictionaryAsync(entry => entry.Album.Id, cancellationToken); return ranked diff --git a/backend/Musify.Application/Configuration/PlayListConfiguration.cs b/backend/Musify.Application/Configuration/PlayListConfiguration.cs index cb1ea32..87d74b1 100644 --- a/backend/Musify.Application/Configuration/PlayListConfiguration.cs +++ b/backend/Musify.Application/Configuration/PlayListConfiguration.cs @@ -34,6 +34,18 @@ namespace Musify.Application.Configuration [Required] public string LargePicturesFolder { get; set; } = "LargePictures"; + [Required] + public string PresetOriginalPicture { get; set; } = "PresetOriginalPicture.webp"; + + [Required] + public string PresetSmallPicture { get; set; } = "PresetSmallPicture.webp"; + + [Required] + public string PresetMediumPicture { get; set; } = "PresetMediumPicture.webp"; + + [Required] + public string PresetLargePicture { get; set; } = "PresetLargePicture.webp"; + public string SmallPicturesPath => StorageKey.Combine(ParentFolders, SmallPicturesFolder); public string MediumPicturesPath => StorageKey.Combine(ParentFolders, MediumPicturesFolder); @@ -42,6 +54,12 @@ namespace Musify.Application.Configuration public string OriginalPicturesPath => StorageKey.Combine(ParentFolders, OriginalPicturesFolder); + public string PresetSmallPicturePath => StorageKey.Combine(ParentFolders, SmallPicturesFolder, PresetSmallPicture); + + public string PresetMediumPicturePath => StorageKey.Combine(ParentFolders, MediumPicturesFolder, PresetMediumPicture); + + public string PresetLargePicturePath => StorageKey.Combine(ParentFolders, LargePicturesFolder, PresetLargePicture); + public string BuildOriginalPicturePath(string pictureName) => StorageKey.Combine(ParentFolders, OriginalPicturesFolder, pictureName); public string BuildOriginalPicturePath(long userId, string pictureName) => StorageKey.Combine(UploadsFolder, userId.ToString(), ParentFolders, OriginalPicturesFolder, pictureName); diff --git a/backend/Musify.Application/PlayLists/CreatePlayList.cs b/backend/Musify.Application/PlayLists/CreatePlayList.cs index a9efde6..e50a91c 100644 --- a/backend/Musify.Application/PlayLists/CreatePlayList.cs +++ b/backend/Musify.Application/PlayLists/CreatePlayList.cs @@ -30,6 +30,8 @@ namespace Musify.Application.PlayLists UploadIntentConfiguration uploadIntentConfiguration) : ICommandHandler> { + private sealed record ResolvedPlayListPictures(PlayListPictures Pictures, UploadIntent? Intent); + public async ValueTask> Handle(CreatePlayListCommand request, CancellationToken cancellationToken) { var userExists = await database.Users @@ -41,35 +43,26 @@ namespace Musify.Application.PlayLists return Error.NotFound(description: $"User {request.UserId} not found"); } - UploadIntent? pictureIntent = null; - if (request.PictureIntentId.HasValue) - { - var validation = await uploadIntentValidator.ValidateAndLoadAsync( - uploadIntentConfiguration, - request.PictureIntentId.Value, request.UserId, cancellationToken); - if (validation.IsError) - return validation.Errors; + var picturesResult = await ResolvePicturesAsync(request, cancellationToken); + if (picturesResult.IsError) + return picturesResult.Errors; - pictureIntent = validation.Value; - } + var resolvedPictures = picturesResult.Value; var playList = new PlayList { UserId = request.UserId, Name = request.Name, NormalizedName = request.Name.Trim().ToUpperInvariant(), - Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(), - Pictures = pictureIntent == null ? null - : new PlayListPictures { OriginalName = pictureIntent.ObjectName } + Description = string.IsNullOrWhiteSpace(request.Description)? null: request.Description.Trim(), + Pictures = resolvedPictures.Pictures }; - await database.PlayLists.AddAsync(playList, cancellationToken); - if (pictureIntent != null) + if (resolvedPictures.Intent != null) { - var finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName); - var publishResult = await PublishCreatePlayListEventAsync( - playList.Id, pictureIntent, finalPictureKey, cancellationToken); + var finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, resolvedPictures.Pictures.OriginalName); + var publishResult = await PublishCreatePlayListEventAsync(playList.Id, resolvedPictures.Intent, finalPictureKey, cancellationToken); if (publishResult.IsError) return publishResult.Errors; } @@ -88,6 +81,35 @@ namespace Musify.Application.PlayLists return PlayListApplicationResponse.FromEntity(playList); } + private async ValueTask> ResolvePicturesAsync( + CreatePlayListCommand request, CancellationToken cancellationToken) + { + if (!request.PictureIntentId.HasValue) + { + return new ResolvedPlayListPictures(new PlayListPictures + { + OriginalName = playListConfiguration.Routes.PresetOriginalPicture, + SmallName = playListConfiguration.Routes.PresetSmallPicture, + MediumName = playListConfiguration.Routes.PresetMediumPicture, + LargeName = playListConfiguration.Routes.PresetLargePicture + }, null); + } + + var validation = await uploadIntentValidator.ValidateAndLoadAsync( + uploadIntentConfiguration, request.PictureIntentId.Value, request.UserId, cancellationToken); + + if (validation.IsError) + return validation.Errors; + + return new ResolvedPlayListPictures(new PlayListPictures + { + OriginalName = validation.Value.ObjectName, + SmallName = playListConfiguration.Routes.PresetSmallPicture, + MediumName = playListConfiguration.Routes.PresetMediumPicture, + LargeName = playListConfiguration.Routes.PresetLargePicture + }, validation.Value); + } + async Task> PublishCreatePlayListEventAsync( Guid playListId, UploadIntent pictureIntent, diff --git a/backend/Musify.Application/PlayLists/UpdatePlayList.cs b/backend/Musify.Application/PlayLists/UpdatePlayList.cs index a231b99..1d54615 100644 --- a/backend/Musify.Application/PlayLists/UpdatePlayList.cs +++ b/backend/Musify.Application/PlayLists/UpdatePlayList.cs @@ -69,13 +69,19 @@ namespace Musify.Application.PlayLists return validation.Errors; pictureIntent = validation.Value; - playListEntity.Pictures = new PlayListPictures { OriginalName = pictureIntent.ObjectName }; + playListEntity.Pictures = new PlayListPictures + { + OriginalName = pictureIntent.ObjectName, + SmallName = playListConfiguration.Routes.PresetSmallPicture, + MediumName = playListConfiguration.Routes.PresetMediumPicture, + LargeName = playListConfiguration.Routes.PresetLargePicture + }; finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName); } database.PlayLists.Update(playListEntity); - if (pictureIntent is not null && finalPictureKey is not null) + if (pictureIntent != null&& finalPictureKey != null) { var publishResult = await PublishUpdatePlayListPictureSourceEventAsync( playListEntity.Id, pictureIntent, finalPictureKey, cancellationToken); diff --git a/backend/Musify.Domain/Entities/PlayList.cs b/backend/Musify.Domain/Entities/PlayList.cs index befa179..1922ef8 100644 --- a/backend/Musify.Domain/Entities/PlayList.cs +++ b/backend/Musify.Domain/Entities/PlayList.cs @@ -27,7 +27,8 @@ namespace Musify.Domain.Entities [MaxLength(256)] public string? Description { get; set; } - public PlayListPictures? Pictures { get; set; } + [Required] + public required PlayListPictures Pictures { get; set; } [Required] public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active; diff --git a/backend/Musify.Domain/ValueObjects/PlayListPictures.cs b/backend/Musify.Domain/ValueObjects/PlayListPictures.cs index 26aa96a..7c701a9 100644 --- a/backend/Musify.Domain/ValueObjects/PlayListPictures.cs +++ b/backend/Musify.Domain/ValueObjects/PlayListPictures.cs @@ -1,13 +1,19 @@ +using System.ComponentModel.DataAnnotations; + namespace Musify.Domain.ValueObjects { public class PlayListPictures { - public string? OriginalName { get; set; } + [Required] + public required string OriginalName { get; set; } - public string? SmallName { get; set; } + [Required] + public required string SmallName { get; set; } - public string? MediumName { get; set; } + [Required] + public required string MediumName { get; set; } - public string? LargeName { get; set; } + [Required] + public required string LargeName { get; set; } } } diff --git a/backend/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs b/backend/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs index 8b8f2dd..8135ec8 100644 --- a/backend/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs +++ b/backend/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs @@ -2,8 +2,8 @@ namespace Musify.Infrastructure.MassTransit.Logs { public record UpdatePlayListPictureLog( Guid PlayListId, - string? PreviousOriginalPictureKey, - string? PreviousSmallPictureKey, - string? PreviousMediumPictureKey, - string? PreviousLargePictureKey); + string PreviousOriginalPictureKey, + string PreviousSmallPictureKey, + string PreviousMediumPictureKey, + string PreviousLargePictureKey); } diff --git a/web-player/package-lock.json b/web-player/package-lock.json index 69ab3e3..46cc1df 100644 --- a/web-player/package-lock.json +++ b/web-player/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/sora": "^5.3.0", "@lucide/svelte": "^1.23.0", "@sveltejs/adapter-node": "^5.5.7", "jose": "^6.2.3", @@ -183,6 +184,15 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource-variable/sora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/sora/-/sora-5.3.0.tgz", + "integrity": "sha512-h8kHta4Z8SkfUGeI82TUy1n3rbrKpBRGwuk4t4qCwto5oEHxlbOFD2yGP+Df5fbmTXwKGn+o667vYp0E4K8Whw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1639,6 +1649,7 @@ "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, diff --git a/web-player/package.json b/web-player/package.json index ec7010f..35117fb 100644 --- a/web-player/package.json +++ b/web-player/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/sora": "^5.3.0", "@lucide/svelte": "^1.23.0", "@sveltejs/adapter-node": "^5.5.7", "jose": "^6.2.3", diff --git a/web-player/src/lib/api/schema.d.ts b/web-player/src/lib/api/schema.d.ts index 585a1df..96f1764 100644 --- a/web-player/src/lib/api/schema.d.ts +++ b/web-player/src/lib/api/schema.d.ts @@ -591,7 +591,7 @@ export interface components { }; CreatePlayListRequest: { name: string; - description: string; + description: null | string; /** Format: uuid */ pictureIntentId: null | string; }; diff --git a/web-player/src/lib/components/ui/ArtistAvatar.svelte b/web-player/src/lib/components/ui/ArtistAvatar.svelte index 1e1786c..53482b7 100644 --- a/web-player/src/lib/components/ui/ArtistAvatar.svelte +++ b/web-player/src/lib/components/ui/ArtistAvatar.svelte @@ -21,7 +21,7 @@
export const mediaCardArt = - 'aspect-square w-full rounded-art shadow-art transition duration-300 ease-out group-hover:-translate-y-1 group-hover:shadow-art-lg'; + 'aspect-square w-full rounded-art shadow-art transition duration-300 ease-out group-hover:shadow-art-lg'; - +{#if tiles.length > 0}
+ class="absolute inset-0 flex transition duration-300 ease-out group-hover/tile:scale-[1.01]" + > + {#each tiles as item, i (i)} + + {/each} +
+{/if} +
{mix.title}
{#if mix.subtitle} -
{mix.subtitle}
+
{mix.subtitle}
{/if}
diff --git a/web-player/src/lib/components/ui/PlayPauseOverlay.svelte b/web-player/src/lib/components/ui/PlayPauseOverlay.svelte index 1bf0eb5..87e23d8 100644 --- a/web-player/src/lib/components/ui/PlayPauseOverlay.svelte +++ b/web-player/src/lib/components/ui/PlayPauseOverlay.svelte @@ -11,9 +11,8 @@ {#if active && playing} diff --git a/web-player/src/lib/components/ui/SectionHeading.svelte b/web-player/src/lib/components/ui/SectionHeading.svelte index 7bd098f..9f56e58 100644 --- a/web-player/src/lib/components/ui/SectionHeading.svelte +++ b/web-player/src/lib/components/ui/SectionHeading.svelte @@ -11,12 +11,16 @@ {#if actions} -
-

{title}

+
+

+ {title} +

{@render actions()}
{:else} -

+

{title}

{/if} diff --git a/web-player/src/lib/config.ts b/web-player/src/lib/config.ts index 7a5f8c6..7136a92 100644 --- a/web-player/src/lib/config.ts +++ b/web-player/src/lib/config.ts @@ -10,7 +10,6 @@ export const EXPLORE_ALBUMS_PAGE_SIZE = 10; export const HOME_LATEST_PAGE_SIZE = 10; export const HOME_ALBUMS_LIMIT = 12; export const HOME_MIXES_BENTO = 5; -export const HOME_ARTISTS_LIMIT = 10; export const HOME_SHELF_LIMIT = 6; export const YOUTUBE_FILLER_LIMIT = 27; export const PLAYLIST_PICKER_PAGE_SIZE = 50; diff --git a/web-player/src/lib/server/topArtists.ts b/web-player/src/lib/server/topArtists.ts deleted file mode 100644 index faee9ae..0000000 --- a/web-player/src/lib/server/topArtists.ts +++ /dev/null @@ -1,42 +0,0 @@ -export interface HomeArtist { - id: string; - name: string; -} - -const FILLER_ARTISTS = [ - 'Nova Reyes', - 'The Paper Lanterns', - 'Kairo', - 'Lucía Marés', - 'Midnight Transit', - 'Sundown Kids', - 'Aria Volkov', - 'El Bosque Rojo', - 'Halcyon', - 'Marea Baja' -]; - -export function pickTopArtists(tracks: { artist?: string | null }[], limit: number): HomeArtist[] { - const seen = new Set(); - const artists: HomeArtist[] = []; - - for (const track of tracks) { - for (const name of (track.artist ?? '').split(',').map((part) => part.trim())) { - const key = name.toLowerCase(); - if (!name || seen.has(key)) continue; - seen.add(key); - artists.push({ id: name, name }); - if (artists.length >= limit) return artists; - } - } - - for (const name of FILLER_ARTISTS) { - const key = name.toLowerCase(); - if (seen.has(key)) continue; - seen.add(key); - artists.push({ id: name, name }); - if (artists.length >= limit) break; - } - - return artists; -} diff --git a/web-player/src/lib/theme/theme.css b/web-player/src/lib/theme/theme.css index 3db89b9..66b32ed 100644 --- a/web-player/src/lib/theme/theme.css +++ b/web-player/src/lib/theme/theme.css @@ -5,9 +5,9 @@ --mf-text: #c7c6c5; --mf-text-2: #a5a4a3; --mf-text-3: #8c8b8a; - --mf-text-4: #7f7e7d; + --mf-text-4: rgb(120, 120, 120); - --mf-surface: rgb(18, 18, 18); + --mf-surface: rgb(15, 15, 15); --mf-surface-2: rgba(255, 255, 255, 0.06); --mf-surface-hover: rgba(255, 255, 255, 0.09); diff --git a/web-player/src/routes/(app)/explore/+page.svelte b/web-player/src/routes/(app)/explore/+page.svelte index e562b9b..1e9e376 100644 --- a/web-player/src/routes/(app)/explore/+page.svelte +++ b/web-player/src/routes/(app)/explore/+page.svelte @@ -14,7 +14,6 @@ import InfiniteScroll from '$lib/components/ui/InfiniteScroll.svelte'; import Cover from '$lib/components/ui/Cover.svelte'; import PlaylistArt from '$lib/components/ui/PlaylistArt.svelte'; - import CoverBadge from '$lib/components/ui/CoverBadge.svelte'; import ArtistAvatar from '$lib/components/ui/ArtistAvatar.svelte'; import Rail from '$lib/components/ui/Rail.svelte'; import NowPlaying from '$lib/components/ui/NowPlaying.svelte'; @@ -223,7 +222,7 @@ -
+
- +
{#each albumEntries as entry, i (entry.kind === 'local' ? entry.album.id : entry.album.albumId)}
  • @@ -264,9 +262,8 @@ -
  • -
    +
    {#each users as u, i (u.id)} @@ -328,8 +324,7 @@
    {/if} -
    - +
    {#if songRows.length > 0}
      {#each songRows as { target: item, seconds }, i (targetId(item))} @@ -363,7 +358,7 @@
    {#if targetArtist(item)} -
    {targetArtist(item)}
    +
    Canción · {targetArtist(item)}
    {/if}
    {fmtTime(seconds)} diff --git a/web-player/src/routes/+layout.svelte b/web-player/src/routes/+layout.svelte index b3941ce..586be4b 100644 --- a/web-player/src/routes/+layout.svelte +++ b/web-player/src/routes/+layout.svelte @@ -1,5 +1,6 @@