@using LANCommander.UI.Services @using Microsoft.AspNetCore.Components.Forms @inject UploadTracker UploadTracker @inject IMessageService MessageService @implements IDisposable @namespace LANCommander.UI.Components
@if (File != null) {
@File.Name ()
} @code { [Parameter] public RenderFragment? Text { get; set; } [Parameter] public RenderFragment? Hint { get; set; } [Parameter] public string? Accept { get; set; } [Parameter] public string? ObjectKey { get; set; } [Parameter] public EventCallback OnUploadCompleted { get; set; } [Parameter] public EventCallback OnUploadError { get; set; } [Parameter] public string? Status { get; set; } [Parameter] public EventCallback StatusChanged { get; set; } [Parameter] public IBrowserFile? File { get; set; } [Parameter] public EventCallback FileChanged { get; set; } [Parameter] public Guid StorageLocationId { get; set; } [Parameter] public EventCallback StorageLocationIdChanged { get; set; } /// /// The type of upload for tracking purposes. /// [Parameter] public UploadType UploadType { get; set; } = UploadType.Archive; readonly Guid _uploaderId = Guid.NewGuid(); InputFile? _fileInput; string? _currentUploadId; int _progress = 0; bool _uploading = false; bool _finished = false; double _speed = 0; ProgressStatus CurrentProgressStatus { get { switch (Status) { case "Complete": return ProgressStatus.Success; case "Uploading": return ProgressStatus.Active; default: return ProgressStatus.Normal; } } } protected override void OnInitialized() { UploadTracker.OnStateChanged += HandleUploadStateChanged; } /// /// Starts the upload. The onCompleted callback is invoked when the upload finishes /// and survives component disposal. /// public async Task Start(Func? onCompleted = null) { _uploading = true; await ChangeStatus("Uploading"); var fileName = File?.Name ?? "Unknown"; _currentUploadId = await UploadTracker.StartUploadAsync( $"ChunkFileInput-{_uploaderId}", StorageLocationId, fileName, UploadType, (!string.IsNullOrWhiteSpace(ObjectKey) && ObjectKey != Guid.Empty.ToString()) ? ObjectKey : null, onCompleted); await InvokeAsync(StateHasChanged); } private void HandleUploadStateChanged() { if (_currentUploadId != null && UploadTracker.ActiveUploads.TryGetValue(_currentUploadId, out var info)) { _progress = info.Percent; _speed = info.Speed; if (info.Status == UploadStatus.Complete) { _uploading = false; _finished = true; _ = ChangeStatus("Complete"); } else if (info.Status == UploadStatus.Error) { _uploading = false; _ = ChangeStatus(""); try { if (OnUploadError.HasDelegate) _ = OnUploadError.InvokeAsync(info.ErrorMessage ?? "Upload failed"); } catch { // Component may be disposed; ignore. } } } try { InvokeAsync(StateHasChanged); } catch { // Component may be disposed; ignore. } } public async Task Clear() { if (_currentUploadId != null) { await UploadTracker.CancelUploadAsync(_currentUploadId); _currentUploadId = null; } File = null; if (FileChanged.HasDelegate) await FileChanged.InvokeAsync(null); Status = ""; if (StatusChanged.HasDelegate) await StatusChanged.InvokeAsync(Status); StateHasChanged(); } async void OnFileSelected(InputFileChangeEventArgs args) { var file = args.File; if (!String.IsNullOrWhiteSpace(Accept)) { var acceptedFileExtensions = Accept.Split(',').Select(x => x.Trim()).ToArray(); if (!acceptedFileExtensions.Any(x => file.Name.ToLower().EndsWith(x.ToLower()))) { MessageService.Error($"Only {String.Join(", ", acceptedFileExtensions)} can be selected!"); return; } } File = file; if (FileChanged.HasDelegate) await FileChanged.InvokeAsync(File); await ChangeStatus(""); } async Task ChangeStatus(string status) { Status = status; if (StatusChanged.HasDelegate) await StatusChanged.InvokeAsync(Status); } public string FileInputId => $"ChunkFileInput-{_uploaderId}"; public bool IsUploading => _uploading; public void Dispose() { UploadTracker.OnStateChanged -= HandleUploadStateChanged; } }