diff --git a/PlaylistShared.Api/Controllers/AudioController.cs b/PlaylistShared.Api/Controllers/AudioController.cs index d732323..fa2b1d9 100644 --- a/PlaylistShared.Api/Controllers/AudioController.cs +++ b/PlaylistShared.Api/Controllers/AudioController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using PlaylistShared.Api.Entities; +using PlaylistShared.Api.Extensions; using PlaylistShared.Api.Services; using PlaylistShared.Shared; using PlaylistShared.Shared.Yandex; @@ -87,7 +88,13 @@ public class AudioController : ControllerBase { Title = track.Title, CoverUri = track.CoverUri, - Artists = track.Artists.Select(t => t.Name).ToList(), + Artists = track.Artists.Select(a => new YandexArtist + { + Id = a.Id, + Name = a.Name, + CoverUrl = a.Cover.GetUrl(), + Description = a.Description?.Text ?? string.Empty, + }).ToList(), DurationMs = track.DurationMs, })); } diff --git a/PlaylistShared.Api/Controllers/SharedPlaylistController.cs b/PlaylistShared.Api/Controllers/SharedPlaylistController.cs index 0ea2ac6..b027c2a 100644 --- a/PlaylistShared.Api/Controllers/SharedPlaylistController.cs +++ b/PlaylistShared.Api/Controllers/SharedPlaylistController.cs @@ -168,13 +168,19 @@ public class SharedPlaylistController : ControllerBase { return new YandexPlaylistData { - Title = playlist.Title ?? "", - Description = playlist.Description ?? "", - Tracks = playlist.Tracks?.Select(t => new YandexTrack + Title = playlist.Title, + Description = playlist.Description, + Tracks = playlist.Tracks.Select(t => new YandexTrack { - TrackId = t.Track?.Id ?? "", - Title = t.Track?.Title ?? "", - Artists = t.Track?.Artists?.Select(a => a.Name).ToList() ?? new List(), + TrackId = t.Track.Id, + Title = t.Track.Title, + Artists = t.Track.Artists.Select(t => new YandexArtist() + { + Id = t.Id, + Name = t.Name, + CoverUrl = t.Cover.GetUrl(), + Description = t.Description?.Text ?? string.Empty, + }).ToList(), DurationMs = (int)(t.Track?.DurationMs ?? 0), CoverUri = t.Track?.CoverUri ?? "" }).ToList() ?? new List() diff --git a/PlaylistShared.Api/Controllers/YandexSearchController.cs b/PlaylistShared.Api/Controllers/YandexSearchController.cs index a7b3a26..878b725 100644 --- a/PlaylistShared.Api/Controllers/YandexSearchController.cs +++ b/PlaylistShared.Api/Controllers/YandexSearchController.cs @@ -30,7 +30,7 @@ public class YandexSearchController : ControllerBase public async Task>> SearchQuery( [FromQuery] string query, [FromQuery] int limit = 20, - [FromQuery] TrackSearchType? searchType = TrackSearchType.All, + [FromQuery] TrackSearchType searchType = TrackSearchType.All, [FromQuery] bool byId = false, [FromQuery] string? shared_id = null) { @@ -75,7 +75,7 @@ public class YandexSearchController : ControllerBase if (byId) { - results = await _yandexService.SearchTracksByIdAsync(user, query, searchType.Value, limit); + results = await _yandexService.SearchTracksByIdAsync(user, query, searchType, limit); } else { diff --git a/PlaylistShared.Api/PlaylistShared.Api.csproj b/PlaylistShared.Api/PlaylistShared.Api.csproj index f6c7255..3e81a02 100644 --- a/PlaylistShared.Api/PlaylistShared.Api.csproj +++ b/PlaylistShared.Api/PlaylistShared.Api.csproj @@ -27,7 +27,7 @@ - + diff --git a/PlaylistShared.Api/Services/YandexMusicService.cs b/PlaylistShared.Api/Services/YandexMusicService.cs index 4198a6d..b05cc61 100644 --- a/PlaylistShared.Api/Services/YandexMusicService.cs +++ b/PlaylistShared.Api/Services/YandexMusicService.cs @@ -137,7 +137,13 @@ public class YandexMusicService { TrackId = t.Id, Title = t.Title, - Artists = t.Artists?.Select(a => a.Name).ToList() ?? new List(), + Artists = t.Artists.Select(t => new YandexArtist() + { + Id = t.Id, + Name = t.Name, + CoverUrl = t.Cover.GetUrl(), + Description = t.Description?.Text ?? string.Empty, + }).ToList(), CoverUri = t.CoverUri, DurationMs = t.DurationMs, }).ToList(), @@ -149,7 +155,7 @@ public class YandexMusicService OwnerUid = p.Owner?.Uid ?? string.Empty, Title = p.Title, Description = p.Description, - CoverUrl = p.CoverUri, + CoverUrl = string.IsNullOrEmpty(p.CoverUri) ? p.Cover.GetUrl() : p.CoverUri, TrackCount = p.TrackCount, }).ToList(), @@ -185,43 +191,114 @@ public class YandexMusicService int limit = 20 ) { + YandexSearchResult result = new(); + var client = await CreateClientAsync(user); - if (client == null) return new YandexSearchResult(); + if (client == null) return result; - var ySerchType = searchType switch + if (searchType == TrackSearchType.All) { - TrackSearchType.Artist => YandexMusic.API.Models.Common.YSearchType.Artist, - TrackSearchType.Album => YandexMusic.API.Models.Common.YSearchType.Album, - TrackSearchType.Playlist => YandexMusic.API.Models.Common.YSearchType.Playlist, - TrackSearchType.Track => YandexMusic.API.Models.Common.YSearchType.Track, - _ => YandexMusic.API.Models.Common.YSearchType.All - }; - - IEnumerable searchResult = searchType switch - { - TrackSearchType.Playlist => (await client.GetPlaylistAsync(id)).Tracks.Select(t => t.Track), - TrackSearchType.Track => (await client.GetTracksAsync([id])), - TrackSearchType.Album => (await client.GetAlbumAsync(id)).Volumes.SelectMany(t => t), - TrackSearchType.Artist => (await client.GetArtistAsync(id)).Albums.SelectMany(t => t.Volumes.SelectMany(v => v)), - _ => new List() - }; - - if (searchType != TrackSearchType.Track) - { - searchResult = searchResult.Distinct(); - if (limit > 0) searchResult = searchResult.Take(limit); + throw new Exception("Для поиска по ID необходимо указать конкретный тип (трек, альбом, исполнитель или плейлист)."); } - return new YandexSearchResult() + else if (searchType == TrackSearchType.Track) { - Tracks = searchResult.Select(t => new YandexTrack + var track = await client.GetTrackAsync(id); + + if (track != null) { - TrackId = t.Id, - Title = t.Title, - Artists = t.Artists?.Select(a => a.Name).ToList() ?? new List(), - CoverUri = t.CoverUri ?? string.Empty, - DurationMs = t.DurationMs, - }).ToList(), - }; + result.Tracks = new List() + { + new() + { + TrackId = track.Id, + Title = track.Title, + Artists = track.Artists.Select(t => new YandexArtist() + { + Id = t.Id, + Name = t.Name, + CoverUrl = t.Cover.GetUrl(), + Description = t.Description?.Text ?? string.Empty, + }).ToList(), + CoverUri = track.CoverUri ?? string.Empty, + DurationMs = track.DurationMs, + } + }; + } + } + + else if (searchType == TrackSearchType.Album) + { + var album = await client.GetAlbumAsync(id); + if (album != null) + { + result.Tracks = album.Volumes.SelectMany(v => v).Select(t => new YandexTrack + { + TrackId = t.Id, + Title = t.Title, + Artists = t.Artists.Select(t => new YandexArtist() + { + Id = t.Id, + Name = t.Name, + CoverUrl = t.Cover.GetUrl(), + Description = t.Description?.Text ?? string.Empty, + }).ToList(), + CoverUri = t.CoverUri ?? string.Empty, + DurationMs = t.DurationMs, + }).ToList(); + } + } + + else if (searchType == TrackSearchType.Artist) + { + var artist = await client.GetArtistAsync(id); + if (artist != null) + { + result.Albums = artist.Albums.Select(a => new YandexAlbum() + { + Id = a.Id, + Title = a.Title, + Artists = a.Artists.Select(t => new YandexArtist() + { + Id = t.Id, + Name = t.Name, + CoverUrl = t.Cover.GetUrl(), + Description = t.Description?.Text ?? string.Empty, + }).ToList(), + CoverUrl = string.IsNullOrEmpty(a.CoverUri) ? a.Cover.GetUrl() : a.CoverUri, + Description = a.Description, + }).ToList(); + + result.Playlists = artist.Playlists.Select(p => new YandexPlaylist + { + Uuid = p.PlaylistUuid, + Kind = p.Kind, + OwnerUid = p.Owner?.Uid ?? string.Empty, + Title = p.Title, + Description = p.Description, + CoverUrl = p.Cover.GetUrl(), + TrackCount = p.TrackCount, + }).ToList(); + + + + result.Tracks = artist.PopularTracks.Select(t => new YandexTrack + { + TrackId = t.Id, + Title = t.Title, + Artists = t.Artists.Select(a => new YandexArtist() + { + Id = a.Id, + Name = a.Name, + CoverUrl = a.Cover.GetUrl(), + Description = a.Description?.Text ?? string.Empty, + }).ToList(), + CoverUri = t.CoverUri ?? string.Empty, + DurationMs = t.DurationMs, + }).ToList(); + } + } + + return result; } } \ No newline at end of file diff --git a/PlaylistShared.Pwa/Components/Common/TrackCoverWithPlay.razor b/PlaylistShared.Pwa/Components/Common/TrackCoverWithPlay.razor index 5700217..e3fbda8 100644 --- a/PlaylistShared.Pwa/Components/Common/TrackCoverWithPlay.razor +++ b/PlaylistShared.Pwa/Components/Common/TrackCoverWithPlay.razor @@ -12,7 +12,7 @@ @if (CanPlay && (_isHovered || IsCurrentTrackPlaying)) { + style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: opacity 0.2s ease; cursor: pointer;"> @* Секция исполнителей *@ - @if (ShouldShowSection(TrackSearchType.Artist)) + @if (_searchResult?.Artists != null) { - @foreach (var artist in _searchResult.Artists!) + @foreach (var artist in _searchResult.Artists) { - - + + } @@ -55,14 +55,14 @@ } @* Секция альбомов *@ - @if (ShouldShowSection(TrackSearchType.Album)) + @if (_searchResult?.Albums != null) { - @foreach (var album in _searchResult.Albums!) + @foreach (var album in _searchResult.Albums) { - - + + } @@ -70,14 +70,14 @@ } @* Секция плейлистов *@ - @if (ShouldShowSection(TrackSearchType.Playlist)) + @if (_searchResult?.Playlists != null) { - @foreach (var playlist in _searchResult.Playlists!) + @foreach (var playlist in _searchResult.Playlists) { - - + + } @@ -85,7 +85,7 @@ } @* Секция треков *@ - @if (ShouldShowSection(TrackSearchType.Track)) + @if (_searchResult?.Tracks != null) { _searchResult?.Tracks?.Any() == true, - TrackSearchType.Album => _searchResult?.Albums?.Any() == true, - TrackSearchType.Playlist => _searchResult?.Playlists?.Any() == true, - TrackSearchType.Artist => _searchResult?.Artists?.Any() == true, - _ => false - }; - } - private async Task OnSearchQueryChanged() { await SearchTracks(byId: false); @@ -155,7 +142,6 @@ if (string.IsNullOrWhiteSpace(query)) { _searchResult = null; - _isFirstSearch = true; return; } @@ -176,7 +162,6 @@ } } - _isFirstSearch = false; _isSearching = true; _searchResult = null; StateHasChanged(); @@ -212,10 +197,11 @@ } } - private async Task SearchTracksByEntity(string entityId, TrackSearchType entityType) + private async Task SearchTracksByEntity(string entityId, string title, TrackSearchType entityType) { - // Переключаем тип на треки и ищем по ID - _searchType = TrackSearchType.Track; + // Переключаем тип и ищем по ID + _searchType = entityType; + _searchQuery = title; await SearchTracks(byId: true, forcedQuery: entityId); } diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/AlbumCard.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/AlbumCard.razor index 773219c..4c62a67 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/AlbumCard.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/AlbumCard.razor @@ -1,9 +1,11 @@ @using PlaylistShared.Shared.Yandex - + @if (!string.IsNullOrEmpty(Item.CoverUrl)) { - + + + } else { @@ -15,10 +17,18 @@ @string.Join(", ", Item.Artists.Select(a => a.Name)) - + @code { [Parameter] public YandexAlbum Item { get; set; } = null!; [Parameter] public EventCallback OnClick { get; set; } [Parameter] public int Size { get; set; } = 50; + + private async Task HandleClick() + { + if (OnClick.HasDelegate) + { + await OnClick.InvokeAsync(); + } + } } \ No newline at end of file diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/ArtistCard.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/ArtistCard.razor index 3076905..3767df1 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/ArtistCard.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/ArtistCard.razor @@ -1,9 +1,11 @@ @using PlaylistShared.Shared.Yandex - + @if (!string.IsNullOrEmpty(Item.CoverUrl)) { - + + + } else { @@ -12,10 +14,18 @@ } @Item.Name - + @code { [Parameter] public YandexArtist Item { get; set; } = null!; [Parameter] public EventCallback OnClick { get; set; } [Parameter] public int Size { get; set; } = 50; + + private async Task HandleClick() + { + if (OnClick.HasDelegate) + { + await OnClick.InvokeAsync(); + } + } } \ No newline at end of file diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/PlaylistCard.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/PlaylistCard.razor index 24c08b5..b553900 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/PlaylistCard.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/Cards/PlaylistCard.razor @@ -1,9 +1,11 @@ @using PlaylistShared.Shared.Yandex - + @if (!string.IsNullOrEmpty(Item.CoverUrl)) { - + + + } else { @@ -12,10 +14,18 @@ } @Item.Title - + @code { [Parameter] public YandexPlaylist Item { get; set; } = null!; [Parameter] public EventCallback OnClick { get; set; } [Parameter] public int Size { get; set; } = 50; + + private async Task HandleClick() + { + if (OnClick.HasDelegate) + { + await OnClick.InvokeAsync(); + } + } } \ No newline at end of file diff --git a/PlaylistShared.Pwa/wwwroot/css/app.css b/PlaylistShared.Pwa/wwwroot/css/app.css index 46ae5d0..516cfba 100644 --- a/PlaylistShared.Pwa/wwwroot/css/app.css +++ b/PlaylistShared.Pwa/wwwroot/css/app.css @@ -109,41 +109,19 @@ code { text-align: start; } -.track-cover-container { - border-radius: 4px; - overflow: hidden; - transition: transform 0.2s ease; +/* Горизонтальный скролинг */ +.horizontal-scroll { + overflow-x: auto; + scroll-snap-type: x mandatory; + overflow-y: hidden; /* отключаем вертикальный скролл */ + cursor: grab; } - .track-cover-container:hover { - transform: scale(1.05); - } - -.play-overlay { - transition: opacity 0.2s ease; - cursor: pointer; -} - -/* Фиксированный плеер внизу */ -.fixed-player { - position: sticky; - display: flex; - bottom: 0; - width: 100%; - right: 0; - justify-content: center; - background-color: var(--mud-palette-background); - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); -} - -/* Отступ снизу, когда плеер виден */ -.page-with-player { - padding-bottom: 80px; /* Высота плеера (подберите под свою тему) */ -} - -/* На мобильных устройствах можно уменьшить отступ */ -@media (max-width: 600px) { - .page-with-player { - padding-bottom: 100px; /* если плеер выше на мобильных */ + .horizontal-scroll:active { + cursor: grabbing; } +/* Для WebKit (Chrome, Edge, Safari) можно включить горизонтальный скролл мышью */ +.horizontal-scroll { + scrollbar-width: thin; + -webkit-overflow-scrolling: touch; } \ No newline at end of file diff --git a/PlaylistShared.Shared/Yandex/YandexTrack.cs b/PlaylistShared.Shared/Yandex/YandexTrack.cs index 1e8c409..c865c5c 100644 --- a/PlaylistShared.Shared/Yandex/YandexTrack.cs +++ b/PlaylistShared.Shared/Yandex/YandexTrack.cs @@ -12,7 +12,7 @@ public class YandexTrack public string Title { get; set; } = string.Empty; [JsonPropertyName("artists")] - public List Artists { get; set; } = new(); + public List Artists { get; set; } = new(); [JsonPropertyName("coverUri")] public string CoverUri { get; set; } = string.Empty;