@using LANCommander.UI.Services
@using Microsoft.AspNetCore.Components.Forms
@inject UploadTracker UploadTracker
@inject IMessageService MessageService
@implements IDisposable
@namespace LANCommander.UI.Components
@if (File != null)
{
}
@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;
}
}