@inject Microsoft.JSInterop.IJSRuntime JSRuntime
@using Microsoft.JSInterop
@implements IDisposable
@if (_showSuccessMessage)
{
Image Cropped Successfully!
}
@if (_imageDataUrl != null)
{

}
else
{
Select an image to view or crop
}
@if (_isCropping)
{
}
else
{
}
@code {
[Parameter] public MediaItemModel? Item { get; set; }
[Parameter] public EventCallback OnCropSaved { get; set; }
[Parameter] public EventCallback OnCancel { get; set; } // May act as clearing selection
[Parameter] public double? AspectRatio { get; set; }
[Parameter] public int? TargetWidth { get; set; }
[Parameter] public int? TargetHeight { get; set; }
[Parameter] public string? AiApiKey { get; set; }
private string _uniqueId = "cropper-" + Guid.NewGuid().ToString("N");
// Removed _isOpen as it's always visible
private bool _isCropping = false;
private string? _imageDataUrl;
private MediaJsInterop _interop = default!;
private DotNetObjectReference? _objRef;
private double? _ratioW = 16;
private double? _ratioH = 9;
private bool _showSuccessMessage = false;
private bool _isDisabled => Item == null;
protected override void OnInitialized()
{
_interop = new MediaJsInterop(JSRuntime);
_objRef = DotNetObjectReference.Create(this);
}
public async Task OpenAsync(MediaItemModel item)
{
// Close existing if open
try { await _interop.DestroyCropperAsync(); } catch { }
Item = item;
_isCropping = false;
_imageDataUrl = null;
_imageLoaded = false;
StateHasChanged();
if (item.IsLocal && item.File != null && string.IsNullOrEmpty(item.PreviewUrl))
{
// Try to load preview if not ready
await LoadPreviewInternal(item);
}
_imageDataUrl = !string.IsNullOrEmpty(item.PreviewUrl) ? item.PreviewUrl : item.Url;
StateHasChanged();
}
private async Task LoadPreviewInternal(MediaItemModel item)
{
try
{
var maxFileSize = 10L * 1024 * 1024;
var buffer = new byte[item.File.Size];
await item.File.OpenReadStream(maxAllowedSize: maxFileSize).ReadAsync(buffer);
var base64 = Convert.ToBase64String(buffer);
item.PreviewUrl = $"data:{item.File.ContentType};base64,{base64}";
}
catch (Exception ex)
{
Console.WriteLine($"Error reading file: {ex.Message}");
}
}
private bool _imageLoaded = false;
private async Task OnImageLoaded()
{
_imageLoaded = true;
// Optional: Auto start crop? User said "select image ... viewed ... then buttons enabled to start cropping IF you want"
// So we just stay in view mode.
}
private async Task StartCropping()
{
if (_isCropping || Item == null) return;
_isCropping = true;
StateHasChanged();
if (!string.IsNullOrEmpty(_imageDataUrl) && _imageLoaded)
{
await InitCropperInternal();
}
}
private async Task InitCropperInternal()
{
await Task.Delay(50);
// Use input ratio if set, else Free (NaN)
double ratio = double.NaN;
if (_ratioW > 0 && _ratioH > 0)
{
ratio = _ratioW.Value / _ratioH.Value;
}
await _interop.InitCropperAsync(_uniqueId, ratio, _objRef!);
}
private async Task OnRatioInput()
{
if (_ratioW.HasValue && _ratioW < 1) _ratioW = 1;
if (_ratioH.HasValue && _ratioH < 1) _ratioH = 1;
if (_ratioW > 0 && _ratioH > 0)
{
await _interop.SetAspectRatioAsync(_ratioW.Value / _ratioH.Value);
}
else
{
await _interop.SetAspectRatioAsync(double.NaN);
}
}
private async Task ClearRatio()
{
_ratioW = null;
_ratioH = null;
await _interop.SetAspectRatioAsync(double.NaN);
}
private async Task SetFullWidth()
{
await _interop.SetFullWidthAsync();
}
private async Task SetFullHeight()
{
await _interop.SetFullHeightAsync();
}
private async Task Save()
{
var croppedBase64 = await _interop.GetCroppedImageAsync();
if (!string.IsNullOrEmpty(croppedBase64) && Item != null)
{
Item.PreviewUrl = croppedBase64;
// If we want to replace the image in the view immediately:
_imageDataUrl = croppedBase64;
}
await _interop.DestroyCropperAsync();
_isCropping = false;
// Success Feedback
_showSuccessMessage = true;
StateHasChanged();
await OnCropSaved.InvokeAsync(Item);
// Hide message after delay
await Task.Delay(2000);
_showSuccessMessage = false;
StateHasChanged();
}
private async Task CancelCropMode()
{
try
{
await _interop.DestroyCropperAsync();
}
catch { }
_isCropping = false;
StateHasChanged();
}
public void Dispose()
{
_objRef?.Dispose();
}
}