diff --git a/PlaylistShared.Pwa/Components/ShareButton.razor b/PlaylistShared.Pwa/Components/Common/ShareButton.razor similarity index 62% rename from PlaylistShared.Pwa/Components/ShareButton.razor rename to PlaylistShared.Pwa/Components/Common/ShareButton.razor index 454986f..55edca3 100644 --- a/PlaylistShared.Pwa/Components/ShareButton.razor +++ b/PlaylistShared.Pwa/Components/Common/ShareButton.razor @@ -2,50 +2,43 @@ @inject ISnackbar Snackbar @inject NavigationManager Navigation - - + RelativeWidth="DropdownWidth.Adaptive" + Paper="true"> Ссылка для приглашения:
- - Копировать - + FullWidth="true" /> + +
@code { - private MudIconButton? _buttonRef; private bool _popoverOpen; private string _shareUrl = ""; - + /// /// Ссылка для копирования. Если не указана, используется текущий URL страницы. /// [Parameter] public string ShareUrl { get; set; } = string.Empty; - + protected override void OnInitialized() { if (string.IsNullOrEmpty(ShareUrl)) @@ -58,15 +51,22 @@ _shareUrl = ShareUrl; } - private async Task OpenPopover() + private async Task TogglePopover() { - if (string.IsNullOrEmpty(ShareUrl)) + if (_popoverOpen) { - Snackbar.Add("Ссылка недоступна", Severity.Warning); - return; + _popoverOpen = false; + } + else + { + if (string.IsNullOrEmpty(ShareUrl)) + { + Snackbar.Add("Ссылка недоступна", Severity.Warning); + return; + } + _shareUrl = ShareUrl; + _popoverOpen = true; } - _shareUrl = ShareUrl; - _popoverOpen = true; } private async Task CopyLink() diff --git a/PlaylistShared.Pwa/Components/TrackCoverWithPlay.razor b/PlaylistShared.Pwa/Components/Common/TrackCoverWithPlay.razor similarity index 100% rename from PlaylistShared.Pwa/Components/TrackCoverWithPlay.razor rename to PlaylistShared.Pwa/Components/Common/TrackCoverWithPlay.razor diff --git a/PlaylistShared.Pwa/Components/AudioPlayer.razor b/PlaylistShared.Pwa/Components/Global/AudioPlayer.razor similarity index 100% rename from PlaylistShared.Pwa/Components/AudioPlayer.razor rename to PlaylistShared.Pwa/Components/Global/AudioPlayer.razor diff --git a/PlaylistShared.Pwa/Components/YandexTokenInstructions.razor b/PlaylistShared.Pwa/Components/Profile/YandexTokenInstructions.razor similarity index 100% rename from PlaylistShared.Pwa/Components/YandexTokenInstructions.razor rename to PlaylistShared.Pwa/Components/Profile/YandexTokenInstructions.razor diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor index d65d947..e45aec0 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor @@ -1,4 +1,5 @@ -@using PlaylistShared.Shared.DTO +@using PlaylistShared.Pwa.Components.Common +@using PlaylistShared.Shared.DTO @inject HttpClient Http @inject ISnackbar Snackbar diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor index 90c6760..3f165b4 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor @@ -20,7 +20,7 @@ } else { - Добавить + Добавить } diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/PlaylistHeader.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/PlaylistHeader.razor new file mode 100644 index 0000000..cfc8099 --- /dev/null +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/PlaylistHeader.razor @@ -0,0 +1,153 @@ +@using PlaylistShared.Pwa.Components.Common +@using PlaylistShared.Shared.Shared +@using PlaylistShared.Shared.Enums +@using System.Security.Claims +@inject HttpClient Http +@inject NavigationManager Navigation +@inject AuthenticationStateProvider AuthProvider +@inject ISnackbar Snackbar +@inject IDialogService DialogService + +
+ @if (!string.IsNullOrEmpty(Playlist?.CoverUrl)) + { + + } +
+
+ + @Playlist?.Title + + + + + + + + @if (_isCreator && _isAuthenticated) + { + + } +
+ Владелец: @Playlist?.Creator?.UserName +
+
+ +@code { + [Parameter] public SharedPlaylistDto? Playlist { get; set; } + [Parameter] public EventCallback OnPermissionsChanged { get; set; } + + private bool _isAuthenticated; + private bool _isCreator; + private string? _currentUserId; + private bool _isFavorite; + private bool _favoriteLoading; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthProvider.GetAuthenticationStateAsync(); + _isAuthenticated = authState.User.Identity?.IsAuthenticated == true; + _currentUserId = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + _isCreator = Playlist?.CreatorUserId.ToString() == _currentUserId; + if (_isAuthenticated) + { + await CheckFavoriteStatus(); + } + } + + private async Task CheckFavoriteStatus() + { + if (Playlist == null) return; + try + { + var response = await Http.GetFromJsonAsync>($"/api/favorites/{Playlist.ShareToken}/check"); + if (response?.Success == true) + _isFavorite = response.Data; + } + catch { } + } + + private async Task ToggleFavorite() + { + if (!_isAuthenticated || Playlist == null) return; + _favoriteLoading = true; + try + { + if (_isFavorite) + { + var response = await Http.DeleteAsync($"/api/favorites/{Playlist.ShareToken}"); + if (response.IsSuccessStatusCode) + { + _isFavorite = false; + Snackbar.Add("Плейлист удалён из избранного", Severity.Success); + } + else + { + Snackbar.Add("Ошибка удаления из избранного", Severity.Error); + } + } + else + { + var response = await Http.PostAsync($"/api/favorites/{Playlist.ShareToken}", null); + if (response.IsSuccessStatusCode) + { + _isFavorite = true; + Snackbar.Add("Плейлист добавлен в избранное", Severity.Success); + } + else + { + Snackbar.Add("Ошибка добавления в избранное", Severity.Error); + } + } + } + catch (Exception ex) + { + Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); + } + finally + { + _favoriteLoading = false; + StateHasChanged(); + } + } + + private async Task OpenPermissionsDialog() + { + if (Playlist == null) return; + var initialPermissions = new UpdatePermissionsDto + { + ViewPermission = Playlist.ViewPermission, + PlayPermission = Playlist.PlayPermission, + AddPermission = Playlist.AddPermission, + RemovePermission = Playlist.RemovePermission + }; + var parameters = new DialogParameters + { + { nameof(PermissionsDialog.ShareToken), Playlist.ShareToken }, + { nameof(PermissionsDialog.InitialPermissions), initialPermissions } + }; + var dialog = await DialogService.ShowAsync("Настройки доступа", parameters); + var result = await dialog.Result; + if (!result.Canceled) + { + await OnPermissionsChanged.InvokeAsync(); + } + } + + private string FormatCoverUrl(string? url, string size) + { + if (string.IsNullOrEmpty(url)) return ""; + return "https://" + url.Replace("%%", size); + } +} \ No newline at end of file diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/TracksTable.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/TracksTable.razor new file mode 100644 index 0000000..012f3f0 --- /dev/null +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/TracksTable.razor @@ -0,0 +1,164 @@ +@using PlaylistShared.Pwa.Components.Common +@using PlaylistShared.Shared.DTO +@using PlaylistShared.Shared.Shared +@inject HttpClient Http +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + # + Обложка + Название + Исполнитель + Длительность + @if (CanRemove) + { + + } + + + @context.Index + + @if (!string.IsNullOrEmpty(context.CoverUri)) + { + @if (CanPlay) + { + + } + else + { + + } + } + + + + @context.Title + + + + @string.Join(", ", context.Artists) + @FormatDuration(context.DurationMs) + @if (CanRemove) + { + + + + } + + + +@code { + [Parameter] public string ShareToken { get; set; } = string.Empty; + [Parameter] public bool CanPlay { get; set; } + [Parameter] public bool CanRemove { get; set; } + [Parameter] public string? CurrentPlayingTrackId { get; set; } + [Parameter] public bool IsPlaying { get; set; } + [Parameter] public EventCallback OnPlayTrack { get; set; } + + public async Task Reload() + { + await LoadTracks(); + } + + private List _tracks = new(); + private bool _loading = true; + + protected override async Task OnInitializedAsync() + { + await LoadTracks(); + } + + private async Task LoadTracks() + { + _loading = true; + try + { + var response = await Http.GetFromJsonAsync>($"/api/sharedplaylist/{ShareToken}/tracks"); + if (response?.Success == true && response.Data != null) + { + _tracks = response.Data.Tracks.Select((t, idx) => new TrackDisplay + { + Id = t.Id, + Title = t.Title, + Artists = t.Artists, + DurationMs = t.DurationMs, + CoverUri = t.CoverUri, + Index = idx + 1 + }).ToList(); + } + else + { + Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить треки", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"Ошибка загрузки треков: {ex.Message}", Severity.Error); + } + finally + { + _loading = false; + StateHasChanged(); + } + } + + private async Task RemoveTrack(TrackDisplay track) + { + var confirmed = await DialogService.ShowMessageBoxAsync( + "Подтверждение удаления", + $"Вы уверены, что хотите удалить трек \"{track.Title}\"?", + yesText: "Удалить", cancelText: "Отмена"); + + if (confirmed != true) return; + + try + { + var request = new RemoveTracksRequest { TrackIds = new List { track.Id } }; + var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{ShareToken}/remove-tracks", request); + if (response.IsSuccessStatusCode) + { + Snackbar.Add("Трек удалён", Severity.Success); + await LoadTracks(); + } + else + { + var error = await response.Content.ReadFromJsonAsync>(); + Snackbar.Add(error?.Error?.Message ?? "Ошибка удаления", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); + } + } + + private async Task PlayTrack(string trackId) + { + await OnPlayTrack.InvokeAsync(trackId); + } + + private string FormatCoverUrl(string? url, string size) + { + if (string.IsNullOrEmpty(url)) return ""; + return "https://" + url.Replace("%%", size); + } + + private string FormatDuration(long ms) + { + var seconds = ms / 1000; + var mins = seconds / 60; + var secs = seconds % 60; + return $"{mins}:{secs:D2}"; + } + + private class TrackDisplay : YandexTrack + { + public int Index { get; set; } + } +} \ No newline at end of file diff --git a/PlaylistShared.Pwa/Pages/SharedPlaylistView.razor b/PlaylistShared.Pwa/Pages/SharedPlaylistView.razor index 97dc70f..89c0f76 100644 --- a/PlaylistShared.Pwa/Pages/SharedPlaylistView.razor +++ b/PlaylistShared.Pwa/Pages/SharedPlaylistView.razor @@ -25,38 +25,7 @@ -
- @if (!string.IsNullOrEmpty(_playlist.CoverUrl)) - { - - } -
-
- - @_playlist.Title - - - - - - - - @if (_isCreator && _isAuthenticated) - { - - } -
- Владелец: @_playlist.Creator?.UserName -
-
+
@@ -75,73 +44,21 @@ Треки - - @if (_tracksLoading) - { - - } - else if (_tracks == null || !_tracks.Any()) - { - В плейлисте пока нет треков - } - else - { - - - # - Обложка - Название - Исполнитель - Длительность - @if (_canRemove) - { - - } - - - @context.Index - - @if (!string.IsNullOrEmpty(context.CoverUri)) - { - @if (@_canPlay) - { - - } - else - { - - - } - } - - - - @context.Title - - - - @string.Join(", ", context.Artists) - @FormatDuration(context.DurationMs) - @if (_canRemove) - { - - - - } - - - } + + }
- +
@@ -152,6 +69,7 @@ private int _addTrackTabIndex = 0; // 0 - ссылка, 1 - поиск private AudioPlayer? _audioPlayer; + private TracksTable? _tracksTableRef; private string? _currentTrackId { get; set; } private bool _isPlaying = false; private bool _isPlayerVisible = false; @@ -169,8 +87,6 @@ private bool _isFavorite = false; private bool _favoriteLoading = false; - - private List _tracks = new(); private bool _tracksLoading; private string _trackLink = ""; @@ -184,67 +100,6 @@ await LoadPlaylist(); } - private async Task CheckFavoriteStatus() - { - if (!_isAuthenticated || _playlist == null) return; - try - { - var response = await Http.GetFromJsonAsync>($"/api/favorites/{Token}/check"); - if (response?.Success == true) - _isFavorite = response.Data; - } - catch { } - } - - private async Task ToggleFavorite() - { - if (!_isAuthenticated) - { - Snackbar.Add("Добавление в избранное доступно только авторизованным пользователям", Severity.Warning); - return; - } - - _favoriteLoading = true; - try - { - if (_isFavorite) - { - var response = await Http.DeleteAsync($"/api/favorites/{Token}"); - if (response.IsSuccessStatusCode) - { - _isFavorite = false; - Snackbar.Add("Плейлист удалён из избранного", Severity.Success); - } - else - { - Snackbar.Add("Ошибка удаления из избранного", Severity.Error); - } - } - else - { - var response = await Http.PostAsync($"/api/favorites/{Token}", null); - if (response.IsSuccessStatusCode) - { - _isFavorite = true; - Snackbar.Add("Плейлист добавлен в избранное", Severity.Success); - } - else - { - Snackbar.Add("Ошибка добавления в избранное", Severity.Error); - } - } - } - catch (Exception ex) - { - Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); - } - finally - { - _favoriteLoading = false; - StateHasChanged(); - } - } - private async Task ConfigurePermissions() { if (_playlist is null) @@ -280,7 +135,7 @@ PlayPermission = _playlist.PlayPermission, }; } - + } } @@ -294,8 +149,7 @@ _playlist = response.Data; await ConfigurePermissions(); - await LoadTracks(); - await CheckFavoriteStatus(); + //await LoadTracks(); } else { @@ -316,199 +170,15 @@ private async Task LoadTracks() { if (_playlist == null) return; + if (_tracksTableRef == null) return; + _tracksLoading = true; - try - { - var url = $"/api/sharedplaylist/{Token}/tracks"; - var response = await Http.GetFromJsonAsync>(url); - if (response?.Success == true && response.Data != null) - { - _tracks = response.Data.Tracks.Select((t, idx) => new YandexTrackDisplay - { - Id = t.Id, - Title = t.Title, - Artists = t.Artists, - DurationMs = t.DurationMs, - CoverUri = t.CoverUri, - Index = idx + 1 - }).ToList(); - } - else - { - Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить треки", Severity.Error); - } - } - catch (Exception ex) - { - Snackbar.Add($"Ошибка загрузки треков: {ex.Message}", Severity.Error); - } - finally - { - _tracksLoading = false; - StateHasChanged(); - } - } + StateHasChanged(); - private async Task AddTrackByLink() - { - if (string.IsNullOrWhiteSpace(_trackLink)) - { - Snackbar.Add("Введите ссылку на трек", Severity.Warning); - return; - } + await _tracksTableRef.Reload(); - _addingTrack = true; - try - { - var request = new AddTrackByLinkRequest { Link = _trackLink }; - var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/add-track-by-link", request); - if (response.IsSuccessStatusCode) - { - Snackbar.Add("Трек успешно добавлен", Severity.Success); - _trackLink = ""; - await LoadTracks(); - } - else - { - var error = await response.Content.ReadFromJsonAsync>(); - Snackbar.Add(error?.Error?.Message ?? "Ошибка добавления трека", Severity.Error); - } - } - catch (Exception ex) - { - Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); - } - finally - { - _addingTrack = false; - } - } - - private async Task OnTrackAdded(string trackId) - { - if (!_canAdd) return; - - try - { - var request = new AddTracksRequest { TrackIds = new List { trackId } }; - var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/add-tracks", request); - if (response.IsSuccessStatusCode) - { - Snackbar.Add("Трек успешно добавлен", Severity.Success); - await LoadTracks(); - } - else - { - var error = await response.Content.ReadFromJsonAsync>(); - Snackbar.Add(error?.Error?.Message ?? "Ошибка добавления трека", Severity.Error); - } - } - catch (Exception ex) - { - Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); - } - } - - private async Task AddTrackById(string trackId) - { - if (!_canAdd) - { - Snackbar.Add("У вас нет прав на добавление треков", Severity.Warning); - return; - } - - _addingTrack = true; - try - { - var request = new AddTracksRequest { TrackIds = new List { trackId } }; - var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/add-tracks", request); - if (response.IsSuccessStatusCode) - { - Snackbar.Add("Трек успешно добавлен", Severity.Success); - await LoadTracks(); - } - else - { - var error = await response.Content.ReadFromJsonAsync>(); - Snackbar.Add(error?.Error?.Message ?? "Ошибка добавления трека", Severity.Error); - } - } - catch (Exception ex) - { - Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); - } - finally - { - _addingTrack = false; - } - } - - private async Task RemoveTrack(YandexTrackDisplay track) - { - var confirmed = await DialogService.ShowMessageBoxAsync( - "Подтверждение удаления", - $"Вы уверены, что хотите удалить трек \"{track.Title}\"?", - yesText: "Удалить", cancelText: "Отмена"); - - if (confirmed != true) return; - - try - { - var request = new RemoveTracksRequest { TrackIds = new List { track.Id } }; - var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/remove-tracks", request); - if (response.IsSuccessStatusCode) - { - Snackbar.Add("Трек удалён", Severity.Success); - await LoadTracks(); - } - else - { - var error = await response.Content.ReadFromJsonAsync>(); - Snackbar.Add(error?.Error?.Message ?? "Ошибка удаления", Severity.Error); - } - } - catch (Exception ex) - { - Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error); - } - } - - private async Task OpenPermissionsDialog() - { - var parameters = new DialogParameters - { - { nameof(PermissionsDialog.ShareToken), Token }, - { nameof(PermissionsDialog.InitialPermissions), _editPermissions } - }; - var dialog = await DialogService.ShowAsync("Настройки доступа", parameters); - var result = await dialog.Result; - if (!result.Canceled && result.Data is UpdatePermissionsDto updatedPermissions) - { - // Обновляем локальные права и перезагружаем плейлист - _editPermissions = updatedPermissions; - await LoadPlaylist(); // перезагружаем, чтобы обновить _playlist и права доступа - await LoadTracks(); // возможно, треки тоже нужно перезагрузить - StateHasChanged(); - } - } - - private string FormatDuration(int ms) - { - var seconds = ms / 1000; - var mins = seconds / 60; - var secs = seconds % 60; - return $"{mins}:{secs:D2}"; - } - - private string FormatCoverUrl(string? url, string size = "200x200") - { - if (string.IsNullOrEmpty(url)) return ""; - return "https://" + url.Replace("%%", size); - } - - private class YandexTrackDisplay : YandexTrack - { - public int Index { get; set; } + _tracksLoading = false; + StateHasChanged(); } private async Task PlayTrack(string trackId) diff --git a/PlaylistShared.Shared/DTO/YandexTrack.cs b/PlaylistShared.Shared/DTO/YandexTrack.cs index eed291a..b65a792 100644 --- a/PlaylistShared.Shared/DTO/YandexTrack.cs +++ b/PlaylistShared.Shared/DTO/YandexTrack.cs @@ -5,6 +5,6 @@ public class YandexTrack public string Id { get; set; } = ""; public string Title { get; set; } = ""; public List Artists { get; set; } = new(); - public int DurationMs { get; set; } + public long DurationMs { get; set; } public string CoverUri { get; set; } = ""; } \ No newline at end of file