feat(ImageCropper): add OnCropChangedAsync parameter#460
Conversation
Reviewer's GuideThis PR enhances the ImageCropper component with a new OnCropChangedAsync callback by extending JS interop to capture and forward crop movements, refactoring client-side state management, updating the C# component to register/invoke the callback, introducing a dedicated ImageCropperData model, and applying minor loader attribute and documentation cleanups. Sequence Diagram: OnCropChangedAsync Event FlowsequenceDiagram
actor User
participant CropperJSEvents as "Cropper.js Event Source"
participant ImageCropperJS as "ImageCropper.razor.js"
participant ImageCropperCS as "ImageCropper.razor.cs (C# Component)"
participant DeveloperHandler as "Developer's OnCropChangedAsync Handler"
User->>CropperJSEvents: Modifies crop selection
CropperJSEvents->>ImageCropperJS: Fires 'crop' event (captures cropData)
ImageCropperJS->>ImageCropperJS: Stores cropData
User->>CropperJSEvents: Finishes crop modification
CropperJSEvents->>ImageCropperJS: Fires 'cropend' event
ImageCropperJS->>ImageCropperCS: invokeMethodAsync("TriggerOnCropChangedAsync", cropData)
ImageCropperCS->>ImageCropperCS: Executes TriggerOnCropChangedAsync(cropData)
ImageCropperCS->>DeveloperHandler: Invokes OnCropChangedAsync(cropData)
Sequence Diagram: ImageCropper Initialization for OnCropChangedAsyncsequenceDiagram
participant CSharpComponent as "ImageCropper.razor.cs"
participant BlazorInterop as "Blazor JS Interop"
participant JSModule as "ImageCropper.razor.js"
participant CropperLib as "Cropper.js Library"
participant InternalData as "JS Internal Data Store"
CSharpComponent->>BlazorInterop: InvokeVoidAsync("init", id, dotnetHelper, jsParams)
BlazorInterop->>JSModule: init(id, dotnetHelper, jsParams containing Options & TriggerOnCropEndAsync name)
JSModule->>JSModule: Prepare Cropper.js options based on jsParams
JSModule->>JSModule: If TriggerOnCropEndAsync is present, set 'crop' event handler (stores data)
JSModule->>JSModule: If TriggerOnCropEndAsync is present, set 'cropend' event handler (invokes dotnetHelper.invokeMethodAsync)
JSModule->>CropperLib: new Cropper(imageElement, preparedOptions)
CropperLib-->>JSModule: cropperInstance
JSModule->>InternalData: Store { el, invoke: dotnetHelper, options: jsParams, cropper: cropperInstance }
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey @ArgoZhang - I've reviewed your changes - here's some feedback:
- In your dispose method you destroy the cropper but don’t dispose the stored DotNetObjectReference (invoke), so consider calling invoke.dispose() after removing it from Data to avoid a memory leak.
- The JS destructuring expects a lower-camelCase
triggerOnCropEndAsyncproperty, but your C# anonymous object uses PascalCaseTriggerOnCropEndAsync—either add a JsonPropertyName attribute or switch to camelCase to ensure the JS sees the flag correctly. - Wrap the invoke.invokeMethodAsync call inside a try/catch in JS to prevent any .NET callback errors from bubbling up and breaking the cropper.
Here's what I looked at during the review
- 🟡 General issues: 1 issue found
- 🟢 Security: all looks good
- 🟢 Testing: all looks good
- 🟡 Complexity: 1 issue found
- 🟢 Documentation: all looks good
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
||
| const image = el.querySelector(".bb-cropper-image"); | ||
| const cropper = new Cropper(image, getOptions(options)); | ||
| const { options: op, triggerOnCropEndAsync } = options; |
There was a problem hiding this comment.
suggestion: Avoid shadowing the options identifier
Consider renaming either the parameter or the destructured variable to avoid confusion and improve code clarity.
| } | ||
| } | ||
|
|
||
| export function reset(id) { |
There was a problem hiding this comment.
issue (complexity): Consider introducing a utility function to simplify repeated instance retrieval and null-checks in your API methods.
| export function reset(id) { | |
| // at top of module, add a small utility to collapse the Data.get + null-checks | |
| function getInstance(id) { | |
| return Data.get(id) || {}; | |
| } | |
| // -------------------- refactor your APIs -------------------- | |
| // before: | |
| // export function reset(id) { | |
| // const ic = Data.get(id); | |
| // if (ic != null) { | |
| // const { cropper } = ic; | |
| // if (cropper) { | |
| // cropper.reset(); | |
| // } | |
| // } | |
| // } | |
| // after: | |
| export function reset(id) { | |
| getInstance(id).cropper?.reset(); | |
| } | |
| // before: | |
| // export async function enable(id) { | |
| // const ic = Data.get(id); | |
| // if (ic != null) { | |
| // const { el, cropper } = ic; | |
| // if (cropper) { | |
| // cropper.enable(); | |
| // } | |
| // if (el) { | |
| // el.classList.remove("disabled"); | |
| // } | |
| // } | |
| // } | |
| // after: | |
| export async function enable(id) { | |
| const { el, cropper } = getInstance(id); | |
| cropper?.enable(); | |
| el?.classList.remove("disabled"); | |
| } | |
| // before (crop has more logic and nested destructuring): | |
| // export function crop(id) { | |
| // let ret = null; | |
| // const ic = Data.get(id); | |
| // if (ic != null) { | |
| // const { cropper, options } = ic; | |
| // if (cropper !== null) { | |
| // cropper.crop(); | |
| // let resultData = cropper.getCroppedCanvas(); | |
| // const { isRound } = options.options; | |
| // if (isRound) { | |
| // resultData = getRoundCanvas(resultData); | |
| // } | |
| // ret = resultData.toDataURL(); | |
| // } | |
| // } | |
| // return ret; | |
| // } | |
| // after: | |
| export function crop(id) { | |
| const { cropper, options: { options: opts } = {} } = getInstance(id); | |
| if (!cropper) return null; | |
| cropper.crop(); | |
| let canvas = cropper.getCroppedCanvas(); | |
| if (opts?.isRound) canvas = getRoundCanvas(canvas); | |
| return canvas.toDataURL(); | |
| } |
Link issues
fixes #459
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Add OnCropChangedAsync for live crop events and refactor JS interop storage and initialization to support callback invocations.
New Features:
Enhancements: