Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.0.2</Version>
<Version>9.0.3</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@namespace BootstrapBlazor.Components
@inherits BootstrapModuleComponentBase
@attribute [JSModuleAutoLoader("./_content/BootstrapBlazor.ImageCropper/ImageCropper.razor.js")]
@attribute [JSModuleAutoLoader("./_content/BootstrapBlazor.ImageCropper/ImageCropper.razor.js", JSObjectReference = true)]

<div @attributes="@AdditionalAttributes" class="@ClassString" id="@Id">
<img src="@Url" class="bb-cropper-image" />
Expand Down
28 changes: 26 additions & 2 deletions src/components/BootstrapBlazor.ImageCropper/ImageCropper.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Website: https://www.blazor.zone or https://argozhang.github.io/

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BootstrapBlazor.Components;

Expand All @@ -29,6 +30,12 @@ public partial class ImageCropper
[Parameter]
public Func<ImageCropperResult, Task>? OnCropAsync { get; set; }

/// <summary>
/// 获得/设置 剪裁框调整大小位置回调方法
/// </summary>
[Parameter]
public Func<ImageCropperData, Task>? OnCropChangedAsync { get; set; }

/// <summary>
/// 获取/设置 裁剪选项
/// </summary>
Expand Down Expand Up @@ -81,10 +88,14 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Options ?? new());
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new
{
Options = Options ?? new(),
TriggerOnCropEndAsync = OnCropChangedAsync != null ? nameof(TriggerOnCropChangedAsync) : null,
});

/// <summary>
/// 剪裁方法 自动触发 <see cref="OnCropAsync"/> 回调方法
/// 剪裁方法 触发 <see cref="OnCropAsync"/> 回调方法
/// </summary>
public async Task<string?> Crop()
{
Expand Down Expand Up @@ -153,4 +164,17 @@ public Task Disable()
/// <param name="angle">旋转角度</param>
/// <returns></returns>
public async Task Rotate(int angle) => await InvokeVoidAsync("rotate", Id, angle);

/// <summary>
///
/// </summary>
/// <returns></returns>
[JSInvokable]
public async Task TriggerOnCropChangedAsync(ImageCropperData data)
{
if (OnCropChangedAsync != null)
{
await OnCropChangedAsync(data);
}
}
}
127 changes: 81 additions & 46 deletions src/components/BootstrapBlazor.ImageCropper/ImageCropper.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import Data from '../BootstrapBlazor/modules/data.js'
import { addLink } from '../BootstrapBlazor/modules/utility.js'

export async function init(id, options) {
export async function init(id, invoke, options) {
await addLink("./_content/BootstrapBlazor.ImageCropper/cropper.bundle.css");

const el = document.getElementById(id);
Expand All @@ -11,9 +11,20 @@ export async function init(id, options) {
}

const image = el.querySelector(".bb-cropper-image");
const cropper = new Cropper(image, getOptions(options));
const { options: op, triggerOnCropEndAsync } = options;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Avoid shadowing the options identifier

Consider renaming either the parameter or the destructured variable to avoid confusion and improve code clarity.

if (triggerOnCropEndAsync) {
let cropData = null;
op.cropend = () => {
invoke.invokeMethodAsync(triggerOnCropEndAsync, cropData);
cropData = null;
}
op.crop = e => {
cropData = e.detail;
}
}
const cropper = new Cropper(image, getOptions(op));

Data.set(id, cropper);
Data.set(id, { el, invoke, options, cropper });
}

const getOptions = op => {
Expand All @@ -27,86 +38,110 @@ const getOptions = op => {
}

export function dispose(id) {
const cropper = Data.get(id);
const ic = Data.get(id);
Data.remove(id);

if (cropper != null) {
cropper.destroy();
if (ic != null) {
const { cropper } = ic;
if (cropper) {
cropper.destroy();
}
}
}

export function crop(id) {
let ret = null;
const cropper = Data.get(id);
if (cropper != null) {
const { isRound } = cropper.options;
cropper.crop();
let resultData = cropper.getCroppedCanvas();
if (isRound) {
resultData = getRoundCanvas(resultData);
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();
resultData = null;
}
ret = resultData.toDataURL();
resultData = null;
}
return ret;
}

export function replace(id, url) {
const cropper = Data.get(id);
if (cropper != null) {
cropper.replace(url);
const ic = Data.get(id);
if (ic != null) {
const { cropper } = ic;
if (cropper) {
cropper.replace(url);
}
}
}

export function reset(id) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider introducing a utility function to simplify repeated instance retrieval and null-checks in your API methods.

Suggested change
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();
}

const cropper = Data.get(id);
if (cropper != null) {
cropper.reset();
const ic = Data.get(id);
if (ic != null) {
const { cropper } = ic;
if (cropper) {
cropper.reset();
}
}
}

export function setDragMode(id, mode) {
const cropper = Data.get(id);
if (cropper != null) {
cropper.setDragMode(mode);
const ic = Data.get(id);
if (ic != null) {
const { cropper } = ic;
if (cropper) {
cropper.setDragMode(mode);
}
}
}

export function rotate(id, angle) {
const cropper = Data.get(id);
if (cropper != null) {
cropper.rotate(angle);
const ic = Data.get(id);
if (ic != null) {
const { cropper } = ic;
if (cropper) {
cropper.rotate(angle);
}
}
}

export function clear(id) {
const cropper = Data.get(id);
if (cropper != null) {
cropper.clear();
const ic = Data.get(id);
if (ic != null) {
const { cropper } = ic;
if (cropper) {
cropper.clear();
}
}
}

export async function enable(id) {
const cropper = Data.get(id);
if (cropper != null) {
cropper.enable();
}

const el = document.getElementById(id);
if (el) {
el.classList.remove("disabled");
const ic = Data.get(id);
if (ic != null) {
const { el, cropper } = ic;
if (cropper) {
cropper.enable();
}
if (el) {
el.classList.remove("disabled");
}
}
}

export async function disable(id) {
const cropper = Data.get(id);
if (cropper != null) {
cropper.disable();
}

const el = document.getElementById(id);
if (el) {
el.classList.add("disabled");
const ic = Data.get(id);
if (ic != null) {
const { el, cropper } = ic;
if (cropper) {
cropper.disable();
}
if (el) {
el.classList.add("disabled");
}
}
}

Expand Down
46 changes: 46 additions & 0 deletions src/components/BootstrapBlazor.ImageCropper/ImageCropperData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components;

/// <summary>
/// 裁切数据实体类
/// </summary>
public struct ImageCropperData
{
/// <summary>
/// 获得/设置 裁剪框高度值
/// </summary>
public float Height { get; set; }

/// <summary>
/// 获得/设置 裁剪框宽度值
/// </summary>
public float Width { get; set; }

/// <summary>
/// 获得/设置 裁剪框 X 值
/// </summary>
public float X { get; set; }

/// <summary>
/// 获得/设置 裁剪框 Y 值
/// </summary>
public float Y { get; set; }

/// <summary>
/// 获得/设置 裁剪框旋转角度值
/// </summary>
public float Rotate { get; set; }

/// <summary>
/// 获得/设置 裁剪框 X 轴缩放值
/// </summary>
public float ScaleX { get; set; }

/// <summary>
/// 获得/设置 裁剪框 Y 轴缩放值
/// </summary>
public float ScaleY { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ namespace BootstrapBlazor.Components;
/// <summary>
/// 裁切结果实体类
/// </summary>
/// <remarks>
/// 构造函数
/// </remarks>
/// <param name="data"></param>
public class ImageCropperResult(string data)
{
Expand Down