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