@page "/media/sources/local/{Id:int}/edit" @page "/media/sources/local/add" @using ErsatzTV.Application.Libraries @implements IDisposable @inject IDialogService Dialog @inject IEntityLocker Locker @inject IMediator Mediator @inject ILogger Logger @inject ISnackbar Snackbar @inject NavigationManager NavigationManager
@(IsEdit ? "Edit Local Library" : "Add Local Library") @foreach (LibraryMediaKind mediaKind in Enum.GetValues()) { @mediaKind } Add Path Save Changes
Library Paths Path @context.Path
@code { private readonly CancellationTokenSource _cts = new(); [Parameter] public int Id { get; set; } private readonly LocalLibraryEditViewModel _model = new(); private readonly LocalLibraryPathEditViewModel _newPath = new(); private EditContext _editContext; private ValidationMessageStore _messageStore; private bool IsEdit => Id != 0; protected override async Task OnParametersSetAsync() { if (IsEdit) { Option maybeLibrary = await Mediator.Send(new GetLocalLibraryById(Id), _cts.Token); await maybeLibrary.Match( async library => { _model.Id = library.Id; _model.Name = library.Name; _model.MediaKind = library.MediaKind; await LoadLibraryPaths(); }, () => { NavigationManager.NavigateTo("404"); return Task.CompletedTask; }); } else { _model.HasChanges = true; _model.Name = "New Local Library"; _model.MediaKind = LibraryMediaKind.Movies; _model.Paths = new List(); } } protected override void OnInitialized() { Locker.OnLibraryChanged += LockChanged; _editContext = new EditContext(_model); _messageStore = new ValidationMessageStore(_editContext); } private void LockChanged(object sender, EventArgs e) => InvokeAsync(StateHasChanged); private async Task LoadLibraryPaths() { _model.HasChanges = false; _model.Paths = await Mediator.Send(new GetLocalLibraryPaths(Id), _cts.Token) .Map( list => list.Map( vm => new LocalLibraryPathEditViewModel { Id = vm.Id, Path = vm.Path }).ToList()); } private async Task MoveLibraryPath(LocalLibraryPathEditViewModel libraryPath) { var parameters = new DialogParameters { { "MediaKind", _model.MediaKind }, { "SourceLibraryId", Id } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; IDialogReference dialog = await Dialog.ShowAsync("Move Local Library Path", parameters, options); DialogResult result = await dialog.Result; if (!result.Canceled && result.Data is LocalLibraryViewModel library) { var request = new MoveLocalLibraryPath(libraryPath.Id, library.Id); Either moveResult = await Mediator.Send(request, _cts.Token); moveResult.Match( _ => NavigationManager.NavigateTo($"media/sources/local/{library.Id}/edit"), error => { Snackbar.Add(error.Value, Severity.Error); Logger.LogError("Unexpected error moving local library path: {Error}", error.Value); }); } } private async Task DeleteLibraryPath(LocalLibraryPathEditViewModel libraryPath) { if (libraryPath.Id == 0) { _model.HasChanges = true; _model.Paths.Remove(libraryPath); } int count = await Mediator.Send(new CountMediaItemsByLibraryPath(libraryPath.Id), _cts.Token); var parameters = new DialogParameters { { "EntityType", "library path" }, { "EntityName", libraryPath.Path }, { "DetailText", $"This library path contains {count} media items." }, { "DetailHighlight", count.ToString() } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; IDialogReference dialog = await Dialog.ShowAsync("Delete Library Path", parameters, options); DialogResult result = await dialog.Result; if (!result.Canceled) { _model.HasChanges = true; _model.Paths.Remove(libraryPath); } } private void AddLibraryPath() { if (!string.IsNullOrWhiteSpace(_newPath.Path) && _model.Paths.All(p => NormalizePath(p.Path) != NormalizePath(_newPath.Path))) { _model.HasChanges = true; _model.Paths.Add( new LocalLibraryPathEditViewModel { Path = _newPath.Path }); } _newPath.Path = null; } private async Task SaveChangesAsync() { _messageStore.Clear(); if (_editContext.Validate()) { Either result = IsEdit ? await Mediator.Send( new UpdateLocalLibrary( _model.Id, _model.Name, _model.Paths.Map(p => new UpdateLocalLibraryPath(p.Id, p.Path)).ToList()), _cts.Token) : await Mediator.Send( new CreateLocalLibrary( _model.Name, _model.MediaKind, _model.Paths.Map(p => p.Path).ToList()), _cts.Token); result.Match( _ => NavigationManager.NavigateTo("media/sources/local"), error => { Snackbar.Add(error.Value, Severity.Error); Logger.LogError("Unexpected error saving local library: {Error}", error.Value); }); } } private string NormalizePath(string path) => Path.GetFullPath(new Uri(path).LocalPath) .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) .ToUpperInvariant(); void IDisposable.Dispose() { Locker.OnLibraryChanged -= LockChanged; _cts.Cancel(); _cts.Dispose(); } }