@using PlaylistShared.Pwa.Components.Common @using PlaylistShared.Pwa.Components.SharedPlaylist.Cards @using PlaylistShared.Shared.DTO @using PlaylistShared.Shared.Enums @using PlaylistShared.Shared.SharedPlaylist @using PlaylistShared.Shared.Yandex @inject HttpClient Http @inject ISnackbar Snackbar @if (_isSearching) { } else if (_searchResult != null) { @* Секция исполнителей *@ @if (_searchResult?.Artists != null) { @foreach (var artist in _searchResult.Artists) { } } @* Секция альбомов *@ @if (_searchResult?.Albums != null) { @foreach (var album in _searchResult.Albums) { } } @* Секция плейлистов *@ @if (_searchResult?.Playlists != null) { @foreach (var playlist in _searchResult.Playlists) { } } @* Секция треков *@ @if (_searchResult?.Tracks != null) { } } @code { [Parameter] public string ShareToken { get; set; } = string.Empty; [Parameter] public EventCallback OnTrackAdded { get; set; } [Parameter] public EventCallback OnTrackRemoved { get; set; } [Parameter] public HashSet ExistingTrackIds { get; set; } = new(); private string _searchQuery = ""; private bool _isSearching = false; private TrackSearchType _searchType = TrackSearchType.All; private YandexSearchResult? _searchResult = null; private async Task OnSearchQueryChanged() { await SearchTracks(byId: false); } private async Task OnSearchTypeChanged() { await SearchTracks(byId: false); } private async Task SearchTracks(bool byId = false, string? forcedQuery = null) { var query = forcedQuery ?? _searchQuery; if (string.IsNullOrWhiteSpace(query)) { _searchResult = null; return; } var type = _searchType; // Распознавание ссылки Яндекс.Музыки if (!byId && Uri.TryCreate(query, UriKind.Absolute, out var uri) && uri.Host == "music.yandex.ru") { try { (type, query) = ParseYandexMusicUrl(uri); byId = true; } catch (Exception ex) { Snackbar.Add($"Ошибка распознавания URL: {ex.Message}", Severity.Error); return; } } _isSearching = true; _searchResult = null; StateHasChanged(); try { var url = $"/api/yandexsearch/search?query={Uri.EscapeDataString(query)}&searchType={Uri.EscapeDataString(type.ToString())}&limit=20"; if (byId) url += "&byId=true"; if (!string.IsNullOrEmpty(ShareToken)) url += $"&shared_id={Uri.EscapeDataString(ShareToken)}"; var response = await Http.GetFromJsonAsync>(url); if (response?.Success == true) { _searchResult = response.Data ?? new YandexSearchResult(); } else { Snackbar.Add(response?.Error?.Message ?? "Ошибка поиска", Severity.Error); _searchResult = null; } } catch (Exception ex) { Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); _searchResult = null; } finally { _isSearching = false; StateHasChanged(); } } private async Task SearchTracksByEntity(string entityId, string title, TrackSearchType entityType) { // Переключаем тип и ищем по ID _searchType = entityType; _searchQuery = title; await SearchTracks(byId: true, forcedQuery: entityId); } private async Task ToggleTrack(YandexTrack track) { if (ExistingTrackIds.Contains(track.TrackId)) await RemoveTrack(track); else await AddTrack(track); } private async Task RemoveTrack(YandexTrack track) { if (!ExistingTrackIds.Remove(track.TrackId)) return; try { await RemoveTrackById(track.TrackId); await OnTrackRemoved.InvokeAsync(); Snackbar.Add($"Трек \"{track.Title}\" удален", Severity.Success, c => c.SnackbarVariant = Variant.Outlined); } catch (Exception ex) { Snackbar.Add($"{ex.Message}", Severity.Error); ExistingTrackIds.Add(track.TrackId); } finally { StateHasChanged(); } } private async Task RemoveTrackById(string trackId) { var request = new UpdateTrackListRequest { TrackIds = new List { trackId } }; var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{ShareToken}/remove-tracks", request); if (response.IsSuccessStatusCode) { await OnTrackAdded.InvokeAsync(); } else { var error = await response.Content.ReadFromJsonAsync>(); throw new Exception(error?.Error?.Message ?? "Ошибка удаления трека"); } } private async Task AddTrack(YandexTrack track) { if (ExistingTrackIds.Contains(track.TrackId)) return; ExistingTrackIds.Add(track.TrackId); try { await AddTrackById(track.TrackId); await OnTrackAdded.InvokeAsync(); Snackbar.Add($"Трек \"{track.Title}\" добавлен", Severity.Success, c => c.SnackbarVariant = Variant.Outlined); } catch (Exception ex) { Snackbar.Add($"{ex.Message}", Severity.Error); ExistingTrackIds.Remove(track.TrackId); } finally { StateHasChanged(); } } private async Task AddTrackById(string trackId) { var request = new UpdateTrackListRequest { TrackIds = new List { trackId } }; var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{ShareToken}/add-tracks", request); if (response.IsSuccessStatusCode) { await OnTrackAdded.InvokeAsync(); } else { var error = await response.Content.ReadFromJsonAsync>(); throw new Exception(error?.Error?.Message ?? "Ошибка добавления трека"); } } private static (TrackSearchType Type, string Id) ParseYandexMusicUrl(Uri uri) { var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); var dataMap = segments .Select((val, idx) => new { val, idx }) .GroupBy(x => x.idx / 2) .ToDictionary( g => g.First().val, g => g.ElementAtOrDefault(1)?.val ); if (dataMap.TryGetValue("track", out var trackId) && !string.IsNullOrEmpty(trackId)) return (TrackSearchType.Track, trackId); if (dataMap.TryGetValue("album", out var albumId) && !string.IsNullOrEmpty(albumId)) return (TrackSearchType.Album, albumId); if (dataMap.TryGetValue("playlist", out var playlistId) && !string.IsNullOrEmpty(playlistId)) return (TrackSearchType.Playlist, playlistId); if (dataMap.TryGetValue("artist", out var artistId) && !string.IsNullOrEmpty(artistId)) return (TrackSearchType.Artist, artistId); throw new ArgumentException("Unsupported URL pattern"); } }