< Summary

Information
Class: Chronicis.Client.ViewModels.WorldDocumentsViewModel
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/ViewModels/WorldDocumentsViewModel.cs
Line coverage
100%
Covered lines: 53
Uncovered lines: 0
Coverable lines: 53
Total lines: 236
Line coverage: 100%
Branch coverage
100%
Covered branches: 22
Total branches: 22
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Documents()100%11100%
set_Documents(...)100%11100%
get_EditingDocumentId()100%11100%
set_EditingDocumentId(...)100%11100%
get_EditDocumentTitle()100%11100%
set_EditDocumentTitle(...)100%11100%
get_EditDocumentDescription()100%11100%
set_EditDocumentDescription(...)100%11100%
get_IsSavingDocument()100%11100%
set_IsSavingDocument(...)100%11100%
StartEditDocument(...)100%22100%
CancelDocumentEdit()100%11100%
GetDocumentIcon(...)100%1616100%
FormatFileSize(...)100%44100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/ViewModels/WorldDocumentsViewModel.cs

#LineLine coverage
 1using Chronicis.Client.Abstractions;
 2using Chronicis.Client.Components.World;
 3using Chronicis.Client.Services;
 4using Chronicis.Shared.DTOs;
 5using Chronicis.Shared.Extensions;
 6using MudBlazor;
 7
 8namespace Chronicis.Client.ViewModels;
 9
 10/// <summary>
 11/// ViewModel managing document CRUD for a world's Resources tab.
 12/// </summary>
 13public sealed class WorldDocumentsViewModel : ViewModelBase
 14{
 15    private readonly IWorldApiService _worldApi;
 16    private readonly ITreeStateService _treeState;
 17    private readonly IUserNotifier _notifier;
 18    private readonly IDialogService _dialogService;
 19    private readonly IAppNavigator _navigator;
 20    private readonly ILogger<WorldDocumentsViewModel> _logger;
 21
 22    private Guid _worldId;
 3223    private List<WorldDocumentDto> _documents = new();
 24    private Guid? _editingDocumentId;
 3225    private string _editDocumentTitle = string.Empty;
 3226    private string _editDocumentDescription = string.Empty;
 27    private bool _isSavingDocument;
 28
 3229    public WorldDocumentsViewModel(
 3230        IWorldApiService worldApi,
 3231        ITreeStateService treeState,
 3232        IUserNotifier notifier,
 3233        IDialogService dialogService,
 3234        IAppNavigator navigator,
 3235        ILogger<WorldDocumentsViewModel> logger)
 36    {
 3237        _worldApi = worldApi;
 3238        _treeState = treeState;
 3239        _notifier = notifier;
 3240        _dialogService = dialogService;
 3241        _navigator = navigator;
 3242        _logger = logger;
 3243    }
 44
 45    public List<WorldDocumentDto> Documents
 46    {
 747        get => _documents;
 1848        private set => SetField(ref _documents, value);
 49    }
 50
 51    public Guid? EditingDocumentId
 52    {
 1053        get => _editingDocumentId;
 654        private set => SetField(ref _editingDocumentId, value);
 55    }
 56
 57    public string EditDocumentTitle
 58    {
 459        get => _editDocumentTitle;
 760        set => SetField(ref _editDocumentTitle, value);
 61    }
 62
 63    public string EditDocumentDescription
 64    {
 365        get => _editDocumentDescription;
 666        set => SetField(ref _editDocumentDescription, value);
 67    }
 68
 69    public bool IsSavingDocument
 70    {
 171        get => _isSavingDocument;
 472        private set => SetField(ref _isSavingDocument, value);
 73    }
 74
 75    /// <summary>Loads documents for the specified world.</summary>
 76    public async Task LoadAsync(Guid worldId)
 77    {
 78        _worldId = worldId;
 79        Documents = await _worldApi.GetWorldDocumentsAsync(worldId);
 80    }
 81
 82    public async Task OpenUploadDialogAsync(Guid worldId)
 83    {
 84        var parameters = new DialogParameters { { "WorldId", worldId } };
 85        var options = new DialogOptions { MaxWidth = MaxWidth.Small, FullWidth = true };
 86
 87        var dialog = await _dialogService.ShowAsync<WorldDocumentUploadDialog>("Upload Document", parameters, options);
 88        var result = await dialog.Result;
 89
 90        if (result != null && !result.Canceled)
 91        {
 92            Documents = await _worldApi.GetWorldDocumentsAsync(worldId);
 93            await _treeState.RefreshAsync();
 94        }
 95    }
 96
 97    public void StartEditDocument(WorldDocumentDto document)
 98    {
 499        EditingDocumentId = document.Id;
 4100        EditDocumentTitle = document.Title;
 4101        EditDocumentDescription = document.Description ?? string.Empty;
 4102    }
 103
 104    public void CancelDocumentEdit()
 105    {
 1106        EditingDocumentId = null;
 1107        EditDocumentTitle = string.Empty;
 1108        EditDocumentDescription = string.Empty;
 1109    }
 110
 111    public async Task SaveDocumentEditAsync()
 112    {
 113        if (EditingDocumentId == null)
 114            return;
 115
 116        IsSavingDocument = true;
 117
 118        try
 119        {
 120            var updateDto = new WorldDocumentUpdateDto
 121            {
 122                Title = EditDocumentTitle,
 123                Description = EditDocumentDescription
 124            };
 125
 126            var updated = await _worldApi.UpdateDocumentAsync(_worldId, EditingDocumentId.Value, updateDto);
 127
 128            if (updated != null)
 129            {
 130                var doc = _documents.FirstOrDefault(d => d.Id == EditingDocumentId.Value);
 131                if (doc != null)
 132                {
 133                    doc.Title = updated.Title;
 134                    doc.Description = updated.Description;
 135                }
 136
 137                EditingDocumentId = null;
 138                EditDocumentTitle = string.Empty;
 139                EditDocumentDescription = string.Empty;
 140
 141                await _treeState.RefreshAsync();
 142                _notifier.Success("Document updated");
 143            }
 144        }
 145        catch (Exception ex)
 146        {
 147            _logger.LogErrorSanitized(ex, "Error updating document {DocumentId}", EditingDocumentId!.Value);
 148            _notifier.Error($"Failed to update document: {ex.Message}");
 149        }
 150        finally
 151        {
 152            IsSavingDocument = false;
 153        }
 154    }
 155
 156    public async Task DeleteDocumentAsync(Guid documentId)
 157    {
 158        var doc = _documents.FirstOrDefault(d => d.Id == documentId);
 159        if (doc == null)
 160            return;
 161
 162        try
 163        {
 164            var success = await _worldApi.DeleteDocumentAsync(_worldId, documentId);
 165
 166            if (success)
 167            {
 168                var updated = new List<WorldDocumentDto>(_documents);
 169                updated.Remove(doc);
 170                Documents = updated;
 171                await _treeState.RefreshAsync();
 172                _notifier.Success("Document deleted");
 173            }
 174            else
 175            {
 176                _notifier.Error("Failed to delete document");
 177            }
 178        }
 179        catch (Exception ex)
 180        {
 181            _logger.LogErrorSanitized(ex, "Error deleting document {DocumentId}", documentId);
 182            _notifier.Error($"Error deleting document: {ex.Message}");
 183        }
 184    }
 185
 186    public async Task DownloadDocumentAsync(Guid documentId)
 187    {
 188        try
 189        {
 190            var download = await _worldApi.DownloadDocumentAsync(documentId);
 191
 192            if (download == null)
 193            {
 194                _notifier.Error("Failed to get download URL");
 195                return;
 196            }
 197
 198            _navigator.NavigateTo(download.DownloadUrl);
 199            _notifier.Success($"Opening {download.FileName}");
 200        }
 201        catch (Exception ex)
 202        {
 203            _logger.LogErrorSanitized(ex, "Error downloading document {DocumentId}", documentId);
 204            _notifier.Error($"Error downloading document: {ex.Message}");
 205        }
 206    }
 207
 208    /// <summary>Returns a MudBlazor icon string for the given MIME content type.</summary>
 209    public static string GetDocumentIcon(string contentType) =>
 8210        contentType.ToLowerInvariant() switch
 8211        {
 1212            "application/pdf" => Icons.Material.Filled.PictureAsPdf,
 1213            "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => Icons.Material.Filled.Descripti
 1214            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => Icons.Material.Filled.TableChart,
 1215            "application/vnd.openxmlformats-officedocument.presentationml.presentation" => Icons.Material.Filled.Slidesh
 1216            "text/plain" => Icons.Material.Filled.TextSnippet,
 1217            "text/markdown" => Icons.Material.Filled.Article,
 3218            string ct when ct.StartsWith("image/") => Icons.Material.Filled.Image,
 1219            _ => Icons.Material.Filled.InsertDriveFile
 8220        };
 221
 222    /// <summary>Formats a byte count as a human-readable file size string.</summary>
 223    public static string FormatFileSize(long bytes)
 224    {
 5225        string[] sizes = { "B", "KB", "MB", "GB" };
 5226        double len = bytes;
 5227        int order = 0;
 12228        while (len >= 1024 && order < sizes.Length - 1)
 229        {
 7230            order++;
 7231            len /= 1024;
 232        }
 233
 5234        return $"{len:0.##} {sizes[order]}";
 235    }
 236}