< Summary

Information
Class: Chronicis.Client.Components.World.WorldDocumentUploadDialog
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/World/WorldDocumentUploadDialog.razor
Line coverage
0%
Covered lines: 0
Uncovered lines: 94
Coverable lines: 94
Total lines: 229
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 34
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_MudDialog()100%210%
get_WorldId()100%210%
.ctor()100%210%
.cctor()100%210%
OnFileSelected()0%7280%
Upload()0%210140%
Cancel()100%210%
FormatFileSize(...)0%2040%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/World/WorldDocumentUploadDialog.razor

#LineLine coverage
 1@using Chronicis.Shared.DTOs
 2@using Microsoft.AspNetCore.Components.Forms
 3@inject IWorldApiService WorldApi
 4@inject ISnackbar Snackbar
 5@inject IJSRuntime JSRuntime
 6
 7<MudDialog>
 8    <DialogContent>
 9        <MudStack Spacing="3">
 010            @if (_isUploading)
 11            {
 12                <MudProgressLinear Color="Color.Primary" Indeterminate="true" />
 13                <MudText Typo="Typo.body2" Align="Align.Center">
 014                    @_uploadStatus
 15                </MudText>
 16            }
 17            else
 18            {
 19                <MudPaper Outlined="true" Class="pa-4" Style="border-style: dashed;">
 20                    <MudStack AlignItems="AlignItems.Center" Spacing="2">
 21                        <MudIcon Icon="@Icons.Material.Filled.CloudUpload" Size="Size.Large" Color="Color.Primary" />
 22                        <InputFile id="fileInput"
 23                                   OnChange="OnFileSelected"
 24                                   accept=".pdf,.docx,.xlsx,.pptx,.txt,.md,.png,.jpg,.jpeg,.gif,.webp"
 25                                   style="display: none;" />
 26                        <label for="fileInput" style="cursor: pointer; width: 100%;">
 27                            <MudText Typo="Typo.h6" Align="Align.Center">
 028                                @(_selectedFile == null ? "Click to select file" : _selectedFile.Name)
 29                            </MudText>
 30                        </label>
 31                        <MudText Typo="Typo.body2" Color="Color.Secondary">
 32                            Maximum file size: 200 MB
 33                        </MudText>
 34                        <MudText Typo="Typo.body2" Color="Color.Secondary">
 35                            Supported: PDF, Word, Excel, PowerPoint, Text, Markdown, Images
 36                        </MudText>
 37                    </MudStack>
 38                </MudPaper>
 39
 040                @if (_selectedFile != null)
 41                {
 42                    <MudTextField @bind-Value="_title"
 43                                  Label="Title"
 44                                  Variant="Variant.Outlined"
 45                                  HelperText="Optional: Customize the display name (defaults to filename)" />
 46
 47                    <MudTextField @bind-Value="_description"
 48                                  Label="Description"
 49                                  Variant="Variant.Outlined"
 50                                  Lines="3"
 51                                  HelperText="Optional: Add a description for this document" />
 52
 053                    @if (!string.IsNullOrEmpty(_validationError))
 54                    {
 055                        <MudAlert Severity="Severity.Error">@_validationError</MudAlert>
 56                    }
 57                }
 58            }
 59        </MudStack>
 60    </DialogContent>
 61    <DialogActions>
 62        <MudButton OnClick="Cancel" Disabled="_isUploading">Cancel</MudButton>
 63        <MudButton Color="Color.Primary"
 64                   OnClick="Upload"
 65                   Disabled="_selectedFile == null || _isUploading">
 66            Upload
 67        </MudButton>
 68    </DialogActions>
 69</MudDialog>
 70
 71@code {
 072    [CascadingParameter] MudDialogInstance MudDialog { get; set; } = null!;
 073    [Parameter] public Guid WorldId { get; set; }
 74
 75    private IBrowserFile? _selectedFile;
 76    private byte[]? _fileData;
 077    private string _title = string.Empty;
 078    private string _description = string.Empty;
 079    private string _validationError = string.Empty;
 80    private bool _isUploading = false;
 081    private string _uploadStatus = string.Empty;
 82
 83    private const long MaxFileSize = 200 * 1024 * 1024; // 200 MB
 084    private static readonly HashSet<string> AllowedExtensions = new()
 085    {
 086        ".pdf", ".docx", ".xlsx", ".pptx", ".txt", ".md",
 087        ".png", ".jpg", ".jpeg", ".gif", ".webp"
 088    };
 89
 90    private async Task OnFileSelected(InputFileChangeEventArgs e)
 91    {
 092        _selectedFile = e.File;
 093        _fileData = null;
 094        _validationError = string.Empty;
 95
 096        if (_selectedFile != null)
 97        {
 98            // Default title to filename without extension
 099            if (string.IsNullOrWhiteSpace(_title))
 100            {
 0101                _title = Path.GetFileNameWithoutExtension(_selectedFile.Name);
 102            }
 103
 104            // Validate file
 0105            if (_selectedFile.Size > MaxFileSize)
 106            {
 0107                _validationError = $"File size ({FormatFileSize(_selectedFile.Size)}) exceeds maximum of 200 MB";
 0108                _selectedFile = null;
 0109                return;
 110            }
 111
 0112            var extension = Path.GetExtension(_selectedFile.Name).ToLowerInvariant();
 0113            if (!AllowedExtensions.Contains(extension))
 114            {
 0115                _validationError = $"File type {extension} is not supported";
 0116                _selectedFile = null;
 0117                return;
 118            }
 119
 120            // Read file data immediately to avoid disposal issues
 121            try
 122            {
 0123                using var stream = _selectedFile.OpenReadStream(MaxFileSize);
 0124                using var memoryStream = new MemoryStream();
 0125                await stream.CopyToAsync(memoryStream);
 0126                _fileData = memoryStream.ToArray();
 0127            }
 0128            catch (Exception ex)
 129            {
 0130                _validationError = $"Failed to read file: {ex.Message}";
 0131                _selectedFile = null;
 0132                _fileData = null;
 0133            }
 134        }
 135
 0136        StateHasChanged();
 0137    }
 138
 139    private async Task Upload()
 140    {
 0141        if (_selectedFile == null || _fileData == null) return;
 142
 0143        _isUploading = true;
 0144        _uploadStatus = "Preparing upload...";
 0145        StateHasChanged();
 146
 147        try
 148        {
 149            // Step 1: Request upload (get SAS URL)
 0150            var request = new WorldDocumentUploadRequestDto
 0151            {
 0152                FileName = _selectedFile.Name,
 0153                ContentType = _selectedFile.ContentType,
 0154                FileSizeBytes = _selectedFile.Size,
 0155                Description = _description
 0156            };
 157
 0158            var uploadResponse = await WorldApi.RequestDocumentUploadAsync(WorldId, request);
 159
 0160            if (uploadResponse == null)
 161            {
 0162                throw new Exception("Failed to get upload URL");
 163            }
 164
 165            // Use the server-generated title (may have been renamed for uniqueness)
 0166            var finalTitle = uploadResponse.Title;
 167
 0168            _uploadStatus = "Uploading file...";
 0169            StateHasChanged();
 170
 171            // Step 2: Upload file directly to blob storage using SAS URL (from byte array)
 0172            using var content = new ByteArrayContent(_fileData);
 0173            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(_selectedFile.ContentType);
 174
 175            // Add required blob headers for Azure Storage
 0176            content.Headers.Add("x-ms-blob-type", "BlockBlob");
 177
 0178            using var httpClient = new HttpClient();
 0179            httpClient.Timeout = TimeSpan.FromMinutes(5); // Allow time for large uploads
 180
 0181            var uploadResult = await httpClient.PutAsync(uploadResponse.UploadUrl, content);
 182
 0183            if (!uploadResult.IsSuccessStatusCode)
 184            {
 0185                var errorBody = await uploadResult.Content.ReadAsStringAsync();
 0186                throw new Exception($"Blob upload failed ({uploadResult.StatusCode}): {errorBody}");
 187            }
 188
 0189            _uploadStatus = "Finalizing...";
 0190            StateHasChanged();
 191
 192            // Step 3: Confirm upload
 0193            var document = await WorldApi.ConfirmDocumentUploadAsync(WorldId, uploadResponse.DocumentId);
 194
 0195            if (document == null)
 196            {
 0197                throw new Exception("Failed to confirm upload");
 198            }
 199
 0200            Snackbar.Add($"Document '{document.Title}' uploaded successfully", Severity.Success);
 0201            MudDialog.Close(DialogResult.Ok(document));
 0202        }
 0203        catch (Exception ex)
 204        {
 0205            Snackbar.Add($"Upload failed: {ex.Message}", Severity.Error);
 0206            _isUploading = false;
 0207            _uploadStatus = string.Empty;
 0208            StateHasChanged();
 0209        }
 0210    }
 211
 212    private void Cancel()
 213    {
 0214        MudDialog.Cancel();
 0215    }
 216
 217    private static string FormatFileSize(long bytes)
 218    {
 0219        string[] sizes = { "B", "KB", "MB", "GB" };
 0220        double len = bytes;
 0221        int order = 0;
 0222        while (len >= 1024 && order < sizes.Length - 1)
 223        {
 0224            order++;
 0225            len /= 1024;
 226        }
 0227        return $"{len:0.##} {sizes[order]}";
 228    }
 229}