@page "/shared/{token}" @_playlist?.Title - Playlist Share @using PlaylistShared.Pwa.Components.Common @using PlaylistShared.Pwa.Components.SharedPlaylist @using PlaylistShared.Pwa.Components.SharedPlaylist.Cards @using PlaylistShared.Shared.DTO @using PlaylistShared.Shared.Enums @using PlaylistShared.Pwa.Services @using PlaylistShared.Shared.SharedPlaylist @using PlaylistShared.Shared.Yandex @inject HttpClient Http @inject ISnackbar Snackbar @inject NavigationManager Navigation @inject AuthenticationStateProvider AuthProvider @inject IDialogService DialogService @if (_loading) { } else if (_playlist == null) { Плейлист не найден или у вас нет доступа } else { @* --- ВЕРСИЯ ДЛЯ ПК (сетка) --- *@ @PlaylistCardContent @if (_canAdd) { @AddTrackCardContent } @* --- ВЕРСИЯ ДЛЯ МОБИЛОК (вкладки внизу) --- *@
@* Область контента: оба компонента здесь всегда *@
@PlaylistCardContent
@AddTrackCardContent
@* Кастомная панель навигации внизу *@ @if (_canAdd) { }
}
@code { /// Токен расшаренного плейлиста [Parameter] public string Token { get; set; } private RenderFragment PlaylistCardContent => __builder => { @if (_canRemove) { } }; private RenderFragment AddTrackCardContent => __builder => { Добавление треков @if (_isSearching) { } else if (_searchResult != null) { @* Секция исполнителей *@ @if (_searchResult?.Artists?.Any() == true) { Исполнители
@foreach (var artist in _searchResult.Artists) {
}
} @* Секция альбомов *@ @if (_searchResult?.Albums?.Any() == true) { Альбомы
@foreach (var album in _searchResult.Albums) {
}
} @* Секция плейлистов *@ @if (_searchResult?.Playlists?.Any() == true) { Плейлисты
@foreach (var playlist in _searchResult.Playlists) {
}
} @* Секция треков *@ @if (_searchResult?.Tracks != null) { Треки @if (_canAdd) { } } }
}; /// Активная вкладка для моб.версии (0 = плейлист, 1 = добавление треков) private int _activeMobileTab = 0; /// /// Список ID треков, уже добавленных в плейлист. /// Используется для быстрого определения, добавлен трек или нет, при отображении результатов поиска. /// private HashSet _existingTrackIds = new(); /// /// Список добавленных в плейлист треков. /// private List _tracks = new(); /// Свойства плейлиста. private SharedPlaylistDto? _playlist; /// Пользователь является создателем плейлиста. private bool _isCreator; /// Пользователь имеет право воспроизведения треков. private bool _canPlay; /// Пользователь имеет право добавления треков в плейлист. private bool _canAdd; /// Пользователь имеет право удаления треков из плейлиста. private bool _canRemove; /// Пользователь авторизован из под учетной записи. private bool _isAuthenticated; /// Текущий ID пользователя, если авторизован. private string? _currentUserId; /// Состояние: Происходит загрузка плейлиста. private bool _loading = true; /// Состояние: Происходит загрузка треков плейлиста. private bool _tracksLoading; /******************************** * Вкладка добавления треков *********************************/ /// Строка запроса поиска. private string _searchQuery = ""; /// Тип поиска. private TrackSearchType _searchType = TrackSearchType.All; /// Состояние: Происходит поиск. private bool _isSearching = false; /// Результат поиска. private YandexSearchResult? _searchResult = null; protected override async Task OnInitializedAsync() { var authState = await AuthProvider.GetAuthenticationStateAsync(); _isAuthenticated = authState.User.Identity?.IsAuthenticated == true; _currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; await LoadPlaylist(); await LoadTracks(); } /// Установка разрешений. private async Task ConfigurePermissions() { if (_playlist is null) { _isCreator = false; _canAdd = false; _canRemove = false; _canPlay = false; } else { _isCreator = _playlist.CreatorUserId.ToString() == _currentUserId; _canAdd = _isCreator || _playlist.AddPermission == EditPermission.Everyone || (_playlist.AddPermission == EditPermission.AuthorizedOnly && _isAuthenticated); _canRemove = _isCreator || _playlist.RemovePermission == EditPermission.Everyone || (_playlist.RemovePermission == EditPermission.AuthorizedOnly && _isAuthenticated); _canPlay = _isCreator || _playlist.PlayPermission == ViewPermission.Everyone || (_playlist.PlayPermission == ViewPermission.AuthorizedOnly && _isAuthenticated); } } /// Загрузка плейлиста. private async Task LoadPlaylist() { try { var response = await Http.GetFromJsonAsync>($"/api/sharedplaylist/{Token}"); if (response?.Success == true) { _playlist = response.Data; _activeMobileTab = 0; await ConfigurePermissions(); } else { Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить плейлист", Severity.Error); } } catch (Exception ex) { Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); } finally { _loading = false; StateHasChanged(); } } /// Загрузка треков. private async Task LoadTracks() { if (_playlist == null) return; _tracksLoading = true; try { var response = await Http.GetFromJsonAsync>($"/api/sharedplaylist/{Token}/tracks"); if (response?.Success == true && response.Data != null) { _tracks = response.Data.Tracks; await GenerateUniqTrackIds(); } else { Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить треки", Severity.Error); } } catch (Exception ex) { Snackbar.Add($"Ошибка загрузки треков: {ex.Message}", Severity.Error); } finally { _tracksLoading = false; StateHasChanged(); } } private async Task GenerateUniqTrackIds() { _existingTrackIds = _tracks.Select(t => t.TrackId).ToHashSet(); } /// Удаление трека из списка плейлиста private async Task OnRemoveTrack(YandexTrack track) { var confirmed = await DialogService.ShowMessageBoxAsync( "Подтверждение удаления", $"Вы уверены, что хотите удалить трек \"{track.Title}\"?", yesText: "Удалить", cancelText: "Отмена"); if (confirmed != true) return; await RemoveTrack(track); } #region Добавление/удаление трека /// Добавление трека. private async Task AddTrack(YandexTrack track) { if (_existingTrackIds.Contains(track.TrackId)) return; try { var request = new UpdateTrackListRequest { TrackIds = new List { track.TrackId } }; var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/add-tracks", request); if (response.IsSuccessStatusCode) { Snackbar.Add($"Трек \"{track.Title}\" добавлен", Severity.Success, c => c.SnackbarVariant = Variant.Outlined); _tracks.Add(track); await GenerateUniqTrackIds(); } else { var error = await response.Content.ReadFromJsonAsync>(); Snackbar.Add(error?.Error?.Message ?? "Ошибка добавления трека", Severity.Error); } } catch (Exception ex) { Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); } StateHasChanged(); } /// Удаление трека. private async Task RemoveTrack(YandexTrack track) { if (!_existingTrackIds.Contains(track.TrackId)) return; try { var request = new UpdateTrackListRequest { TrackIds = new List { track.TrackId } }; var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/remove-tracks", request); if (response.IsSuccessStatusCode) { Snackbar.Add("Трек удалён", Severity.Success); _tracks.Remove(track); await GenerateUniqTrackIds(); } else { var error = await response.Content.ReadFromJsonAsync>(); Snackbar.Add(error?.Error?.Message ?? "Ошибка удаления", Severity.Error); } } catch (Exception ex) { Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); } StateHasChanged(); } #endregion #region Вкладка Добавление треков private async Task OnSearchQueryChanged() { await SearchTracks(byId: false); } private async Task OnSearchTypeChanged() { await SearchTracks(byId: false); } /// /// Поиск трека /// /// По ID /// Принудительный запрос /// 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(Token)) url += $"&shared_id={Uri.EscapeDataString(Token)}"; 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 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"); } #endregion }