diff --git a/src/components/BootstrapBlazor.SignaturePad/BootstrapBlazor.SignaturePad.csproj b/src/components/BootstrapBlazor.SignaturePad/BootstrapBlazor.SignaturePad.csproj index 0e8399c5..f36e12bc 100644 --- a/src/components/BootstrapBlazor.SignaturePad/BootstrapBlazor.SignaturePad.csproj +++ b/src/components/BootstrapBlazor.SignaturePad/BootstrapBlazor.SignaturePad.csproj @@ -1,15 +1,17 @@ + + 11.0.0-rc.2.1 + + Bootstrap Blazor WebAssembly wasm UI Components SignaturePad Bootstrap UI components extensions of Plyr - - - - + + diff --git a/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.cs b/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.cs index c25ab917..8cf8ec05 100644 --- a/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.cs +++ b/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.cs @@ -1,173 +1,205 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// 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/ using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Localization; -using Microsoft.JSInterop; namespace BootstrapBlazor.Components; /// -/// SignaturePad 签名 +/// 手写签名组件 +/// Signature pad component /// public partial class SignaturePad { + private const string StylePath = "_content/BootstrapBlazor.SignaturePad/SignaturePad.css"; + +#if NET9_0_OR_GREATER + private string StyleUrl => Assets[StylePath]; +#else + private static string StyleUrl => StylePath; +#endif + /// - /// 手写签名结果回调/SignaturePad result callback method + /// 手写签名结果回调 + /// Callback invoked with the signature result /// [Parameter] public Func? OnResult { get; set; } /// - /// 手写签名警告信息回调/SignaturePad alert callback method + /// 手写签名警告信息回调 + /// Callback invoked when a signature alert is raised /// [Parameter] public Func? OnAlert { get; set; } /// - /// 手写签名关闭信息回调/SignaturePad close callback method + /// 手写签名关闭回调 + /// Callback invoked when the signature pad is closed /// [Parameter] public Func? OnClose { get; set; } /// - /// 获得/设置 错误回调方法 + /// 获得或设置错误回调 + /// Gets or sets the error callback /// [Parameter] public Func? OnError { get; set; } /// - /// 在框内签名标签文本/Sign above label + /// 获得或设置签名区域上方的提示文本 + /// Gets or sets the label displayed above the signing area /// [Parameter] [NotNull] public string? SignAboveLabel { get; set; } /// - /// 清除按钮文本/Clear button title + /// 获得或设置清除按钮文本 + /// Gets or sets the clear button label /// [Parameter] [NotNull] public string? ClearBtnTitle { get; set; } /// - /// 请先签名提示文本/'Please provide a signature first' alert text + /// 获得或设置尚未签名时显示的提示文本 + /// Gets or sets the alert text shown when no signature is present /// [Parameter] [NotNull] public string? SignatureAlertText { get; set; } /// - /// 换颜色按钮文本/Change color button title + /// 获得或设置更改颜色按钮文本 + /// Gets or sets the change color button label /// [Parameter] [NotNull] public string? ChangeColorBtnTitle { get; set; } /// - /// 撤消按钮文本/Undo button title + /// 获得或设置撤销按钮文本 + /// Gets or sets the undo button label /// [Parameter] [NotNull] public string? UndoBtnTitle { get; set; } /// - /// 关闭按钮文本/Close button title + /// 获得或设置关闭按钮文本 + /// Gets or sets the close button label /// [Parameter] [NotNull] public string? CloseBtnTitle { get; set; } /// - /// 保存为 base64 按钮文本 Save as Base64 button title + /// 获得或设置保存为 Base64 按钮文本 + /// Gets or sets the save as Base64 button label /// [Parameter] [NotNull] public string? SaveBase64BtnTitle { get; set; } /// - /// 保存为PNG按钮文本/Save as PNG button title + /// 获得或设置保存为 PNG 按钮文本 + /// Gets or sets the save as PNG button label /// [Parameter] [NotNull] public string? SavePNGBtnTitle { get; set; } /// - /// 保存为JPG按钮文本/Save as JPG button title + /// 获得或设置保存为 JPG 按钮文本 + /// Gets or sets the save as JPG button label /// [Parameter] [NotNull] public string? SaveJPGBtnTitle { get; set; } /// - /// 保存为SVG按钮文本/Save as SVG button title + /// 获得或设置保存为 SVG 按钮文本 + /// Gets or sets the save as SVG button label /// [Parameter] [NotNull] public string? SaveSVGBtnTitle { get; set; } /// - /// 启用换颜色按钮/Enable change color button + /// 获得或设置是否启用更改颜色按钮 + /// Gets or sets whether the change color button is enabled /// [Parameter] [NotNull] public bool EnableChangeColorBtn { get; set; } = true; /// - /// 启用撤消按钮/Enable undo button + /// 获得或设置是否启用撤销按钮 + /// Gets or sets whether the undo button is enabled /// [Parameter] [NotNull] public bool EnableUndoBtn { get; set; } = true; /// - /// 启用JS错误弹窗/Enable Alert from JS + /// 获得或设置是否启用 JavaScript 提示框 + /// Gets or sets whether JavaScript alerts are enabled /// [Parameter] [NotNull] public bool EnableAlertJS { get; set; } = true; /// - /// 启用保存为 base64 按钮 Enable save as Base64 button + /// 获得或设置是否启用保存为 Base64 按钮 + /// Gets or sets whether the save as Base64 button is enabled /// [Parameter] [NotNull] public bool EnableSaveBase64Btn { get; set; } = true; /// - /// 启用保存为PNG按钮文本/Enable save as PNG button + /// 获得或设置是否启用保存为 PNG 按钮 + /// Gets or sets whether the save as PNG button is enabled /// [Parameter] [NotNull] public bool EnableSavePNGBtn { get; set; } /// - /// 启用保存为JPG按钮文本/Enable save as JPG button + /// 获得或设置是否启用保存为 JPG 按钮 + /// Gets or sets whether the save as JPG button is enabled /// [Parameter] [NotNull] public bool EnableSaveJPGBtn { get; set; } /// - /// 启用保存为SVG按钮文本/Enable save as SVG button + /// 获得或设置是否启用保存为 SVG 按钮 + /// Gets or sets whether the save as SVG button is enabled /// [Parameter] public bool EnableSaveSVGBtn { get; set; } /// - /// 按钮CSS式样/Button css style + /// 获得或设置按钮 CSS 类 + /// Gets or sets the CSS classes for buttons /// [Parameter] public string BtnCssClass { get; set; } = "btn btn-light"; /// - /// 按钮 "保存 "的css样式/Button "Save" css style + /// 获得或设置保存按钮的 CSS 类 + /// Gets or sets the CSS classes for save buttons /// [Parameter] public string BtnSaveCssClass { get; set; } = "btn btn-light"; /// - /// 组件CSS式样/Components css style + /// 获得或设置组件 CSS 类(已过期,请直接使用 class) + /// Gets or sets the component CSS class (obsolete; use class instead) /// [Parameter] [NotNull] @@ -175,14 +207,16 @@ public partial class SignaturePad public string CssClass { get; set; } = "signature-pad-body"; /// - /// 响应式css界面,为所有用户设计最佳体验/Enable Responsive css + /// 获得或设置是否启用响应式布局 + /// Gets or sets whether responsive layout is enabled /// [Parameter] [NotNull] public bool Responsive { get; set; } /// - /// 组件背景 backgroundColor + /// 获得或设置签名画布的背景颜色 + /// Gets or sets the signature canvas background color /// [Parameter] public string? BackgroundColor { get; set; } @@ -219,13 +253,13 @@ protected override void OnParametersSet() /// /// /// - protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, EnableAlertJS ? SignatureAlertText : null, BackgroundColor); + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, EnableAlertJS ? SignatureAlertText : null, BackgroundColor, StyleUrl); /// - /// 签名完成回调方法 + /// 调用签名结果回调 + /// Invokes the signature result callback /// /// - /// [JSInvokable] public async Task SignatureResult(string? val) { @@ -233,9 +267,9 @@ public async Task SignatureResult(string? val) } /// - /// 告警回调方法 + /// 调用签名提示回调 + /// Invokes the signature alert callback /// - /// [JSInvokable] public async Task SignatureAlert() { @@ -246,9 +280,9 @@ public async Task SignatureAlert() } /// - /// 关闭回调方法 + /// 调用签名板关闭回调 + /// Invokes the signature pad close callback /// - /// [JSInvokable] public async Task Close() { diff --git a/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.js b/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.js index 832764cb..a4655b8e 100644 --- a/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.js +++ b/src/components/BootstrapBlazor.SignaturePad/SignaturePad.razor.js @@ -1,11 +1,10 @@ -import './lib/signature_pad.umd.js'; +import './lib/signature_pad.umd.js'; import Data from '../BootstrapBlazor/modules/data.js' import { addLink } from '../BootstrapBlazor/modules/utility.js' -export async function init(id, invoke, alertText, backgroundColor) { - await addLink("./_content/BootstrapBlazor.SignaturePad/SignaturePad.css"); +export async function init(id, invoke, alertText, backgroundColor, styleUrl) { + await addLink(styleUrl) - //Code modify from https://github.com/szimek/signature_pad var element = document.getElementById(id); var clearButton = element.querySelector("[data-action=clear]"); var changeColorButton = element.querySelector("[data-action=change-color]"); @@ -17,35 +16,42 @@ export async function init(id, invoke, alertText, backgroundColor) { var closeButton = element.querySelector("[data-action=close]"); var canvas = element.querySelector("canvas"); var signaturePad = new SignaturePad(canvas, { - // It's Necessary to use an opaque color when saving image as JPEG; - // this option can be omitted if only saving as PNG or SVG backgroundColor: backgroundColor || 'rgb(255, 255, 255)' }); - // Adjust canvas coordinate space taking into account pixel ratio, - // to make it look crisp on mobile devices. - // This also causes canvas to be cleared. + var previousWidth = canvas.parentElement.offsetWidth; + var previousHeight = canvas.parentElement.offsetHeight; + function resizeCanvas() { - // When zoomed out to less than 100%, for some very strange reason, - // some browsers report devicePixelRatio as less than 1 - // and only part of the canvas is cleared then. var ratio = devicePixelRatio; + var width = canvas.parentElement.offsetWidth; + var height = canvas.parentElement.offsetHeight; + var data = signaturePad.toData(); + var scaleX = previousWidth > 0 ? width / previousWidth : 1; + var scaleY = previousHeight > 0 ? height / previousHeight : 1; - // This part causes the canvas to be cleared - canvas.width = canvas.parentElement.offsetWidth * ratio; - canvas.height = canvas.parentElement.offsetHeight * ratio; + canvas.width = width * ratio; + canvas.height = height * ratio; canvas.getContext("2d").scale(ratio, ratio); - // This library does not listen for canvas changes, so after the canvas is automatically - // cleared by the browser, SignaturePad#isEmpty might still return false, even though the - // canvas looks empty, because the internal data of this library wasn't cleared. To make sure - // that the state of this library is consistent with visual state of the canvas, you - // have to clear it manually. - signaturePad.clear(); + previousWidth = width; + previousHeight = height; + + if (data.length > 0) { + signaturePad.fromData(data.map(group => ({ + ...group, + points: group.points.map(point => ({ + ...point, + x: point.x * scaleX, + y: point.y * scaleY + })) + }))); + } + else { + signaturePad.clear(); + } } - // On mobile devices it might make more sense to listen to orientation change, - // rather than window resize events. const observer = new ResizeObserver(resizeCanvas); observer.observe(element); @@ -65,14 +71,14 @@ export async function init(id, invoke, alertText, backgroundColor) { document.body.appendChild(a); a.click(); - window.URL.revokeObjectURL(url); + setTimeout(() => { + window.URL.revokeObjectURL(url); + a.remove(); + }, 1000); } } - // One could simply use Canvas#toBlob method instead, but it's just to show - // that it can be done using result of SignaturePad#toDataURL. function dataURLToBlob(dataURL) { - // Code taken from https://github.com/ebidel/filer.js var parts = dataURL.split(';base64,'); var contentType = parts[0].split(":")[1]; var raw = window.atob(parts[1]); @@ -88,7 +94,6 @@ export async function init(id, invoke, alertText, backgroundColor) { if (clearButton) clearButton.addEventListener("click", function (event) { signaturePad.clear(); - //return wrapperc.invokeMethodAsync("SignatureResult", null); }); if (undoButton) undoButton.addEventListener("click", function (event) { @@ -114,7 +119,6 @@ export async function init(id, invoke, alertText, backgroundColor) { alertMessage(); } else { var imgBase64 = signaturePad.toDataURL(); - //console.log(imgBase64); return invoke.invokeMethodAsync("SignatureResult", imgBase64); } }); @@ -158,12 +162,7 @@ export async function init(id, invoke, alertText, backgroundColor) { } export function dispose(id) { - const element = document.getElementById(id); - if (element) { - const observer = Data.get(id); - - observer.unobserve(element); - observer.disconnect(); - } + const observer = Data.get(id)?.observer; + observer?.disconnect(); Data.remove(id); }