@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
}
Ratio (W:H):
:
@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(); } }