From 57e3881e9ac459f9db1c136a69fb776f48b60fc1 Mon Sep 17 00:00:00 2001 From: Carel vd Merwe Date: Sun, 10 Sep 2023 16:14:45 +0200 Subject: [PATCH 1/3] Added an initial implementation for a signature pad --- .../Components/SignaturePad/LineCapTypes.cs | 8 + .../Components/SignaturePad/LineJoinTypes.cs | 8 + .../SignaturePad/MudSignaturePad.razor | 56 ++++ .../SignaturePad/MudSignaturePad.razor.cs | 144 ++++++++++ .../SignaturePad/SignaturePadOptions.cs | 11 + .../TScripts/MudExtensions.js | 249 +++++++++++++++++- .../wwwroot/eraser.cur | Bin 0 -> 4286 bytes .../wwwroot/pencil.cur | Bin 0 -> 326 bytes .../Pages/Components/SignaturePad.razor | 12 + .../Pages/Examples/SignaturePadExample1.razor | 61 +++++ ComponentViewer.Docs/Pages/Index.razor | 4 +- 11 files changed, 549 insertions(+), 4 deletions(-) create mode 100644 CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineCapTypes.cs create mode 100644 CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineJoinTypes.cs create mode 100644 CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor create mode 100644 CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs create mode 100644 CodeBeam.MudBlazor.Extensions/Components/SignaturePad/SignaturePadOptions.cs create mode 100644 CodeBeam.MudBlazor.Extensions/wwwroot/eraser.cur create mode 100644 CodeBeam.MudBlazor.Extensions/wwwroot/pencil.cur create mode 100644 ComponentViewer.Docs/Pages/Components/SignaturePad.razor create mode 100644 ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineCapTypes.cs b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineCapTypes.cs new file mode 100644 index 00000000..faa190e2 --- /dev/null +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineCapTypes.cs @@ -0,0 +1,8 @@ +namespace MudExtensions; + +public enum LineCapTypes +{ + Round, + Butt, + Square, +} \ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineJoinTypes.cs b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineJoinTypes.cs new file mode 100644 index 00000000..1c5e5105 --- /dev/null +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/LineJoinTypes.cs @@ -0,0 +1,8 @@ +namespace MudExtensions; + +public enum LineJoinTypes +{ + Round, + Bevel, + Miter +} \ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor new file mode 100644 index 00000000..cd09203c --- /dev/null +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor @@ -0,0 +1,56 @@ +@using Microsoft.JSInterop +@using MudBlazor.Charts +@using MudBlazor.Utilities +@using System.Text +@namespace MudExtensions +@inject IJSRuntime JsRuntime +@inherits ComponentBase + + +
+ +
+ + + @if (ShowLineWidth) + { + + } + @if (ShowStrokeStyle) + { + + } + @if (ShowLineJoinStyle) + { + + @foreach (var value in Enum.GetValues()) + { + + } + + } + @if (ShowLineCapStyle) + { + + @foreach (var value in Enum.GetValues()) + { + + } + + } + + + @DrawEraseChipText + + @if (ShowDownload) + { + Download + } + @if (ShowClear) + { + Clear + } + + +
\ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs new file mode 100644 index 00000000..0159809e --- /dev/null +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs @@ -0,0 +1,144 @@ +using System.Text; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using MudBlazor; +using MudBlazor.Utilities; + +namespace MudExtensions; + +public partial class MudSignaturePad : IAsyncDisposable +{ + public MudSignaturePad() + { + _dotnetObjectRef = DotNetObjectReference.Create(this); + } + + private DotNetObjectReference _dotnetObjectRef; + ElementReference _reference; + bool _isErasing = true; + int _lineWidth = 3; + private byte[] _value = Array.Empty(); + readonly string _id = Guid.NewGuid().ToString(); + string DrawEraseChipText => _isErasing ? "Eraser" : "Pen"; + string DrawEraseChipIcon => _isErasing ? @Icons.Material.Filled.DeleteSweep : @Icons.Material.Filled.Edit; + + private object JsOptionsStruct => new + { + lineWidth = Options.LineWidth, + lineCap = Options.LineCapStyle.ToString().ToLower(), + lineJoin = Options.LineJoinStyle.ToString().ToLower(), + strokeStyle = Options.StrokeStyle.Value + }; + + [Parameter] + public byte[] Value + { + get => _value; + set + { + if (value == _value) return; + + _value = value; + } + } + + [Parameter] public EventCallback ValueChanged { get; set; } + + [Parameter] public SignaturePadOptions Options { get; set; } = new SignaturePadOptions(); + + [Parameter] public string ToolbarStyle { get; set; } = string.Empty; + + [Parameter] public string CanvasContainerClass { get; set; } = "border-solid border-2 mud-border-primary"; + [Parameter] public string CanvasContainerStyle { get; set; } = "height: 100%;width: 100%;"; + [Parameter] public bool ShowClear { get; set; } = true; + [Parameter] public bool ShowLineWidth { get; set; } = true; + [Parameter] public bool ShowStrokeStyle { get; set; } = true; + [Parameter] public bool ShowDownload { get; set; } = true; + [Parameter] public bool ShowLineJoinStyle { get; set; } = true; + [Parameter] public bool ShowLineCapStyle { get; set; } = true; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.addPad", _dotnetObjectRef, _reference, JsOptionsStruct); + if (Value.Length > 0) + { + await PushImageUpdateToJsRuntime(); + } + } + + await base.OnAfterRenderAsync(firstRender); + } + + private async Task IsEditToggled() + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.togglePadEraser", _reference); + _isErasing = !_isErasing; + } + + async Task ClearPad() + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.clearPad", _reference); + } + + async Task PushImageUpdateToJsRuntime() + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.updatePadImage", _reference, Convert.ToBase64String(Value)); + } + + async Task UpdateOptions() + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.updatePadOptions", _reference, JsOptionsStruct); + } + + async Task Download() + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.downloadPadImage", _reference); + } + + private async Task LineWidthUpdated(decimal obj) + { + Options.LineWidth = obj; + await UpdateOptions(); + } + + private async Task StrokeStyleUpdated(MudColor obj) + { + Options.StrokeStyle = obj; + await UpdateOptions(); + } + + private async Task LineJoinTypeUpdated(LineJoinTypes obj) + { + Options.LineJoinStyle = obj; + await UpdateOptions(); + } + + private async Task LineCapTypeUpdated(LineCapTypes obj) + { + Options.LineCapStyle = obj; + await UpdateOptions(); + } + + public async ValueTask DisposeAsync() + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.disposePad", _reference); + } + + [JSInvokable] + public async Task SignatureDataChangedAsync() + { + var base64Data = await JsRuntime.InvokeAsync("mudSignaturePad.getBase64", _reference); + try + { + Value = Convert.FromBase64String(base64Data.Replace("data:image/png;base64,", "")); + } + catch (Exception) + { + Value = Array.Empty(); + } + + await ValueChanged.InvokeAsync(Value); + } +} \ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/SignaturePadOptions.cs b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/SignaturePadOptions.cs new file mode 100644 index 00000000..92657e14 --- /dev/null +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/SignaturePadOptions.cs @@ -0,0 +1,11 @@ +using MudBlazor.Utilities; + +namespace MudExtensions; + +public class SignaturePadOptions +{ + public LineCapTypes LineCapStyle { get; set; } = LineCapTypes.Round; + public LineJoinTypes LineJoinStyle { get; set; } = LineJoinTypes.Round; + public MudColor StrokeStyle { get; set; } = new MudColor("#000000"); + public decimal LineWidth { get; set; } = 4; +} \ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/TScripts/MudExtensions.js b/CodeBeam.MudBlazor.Extensions/TScripts/MudExtensions.js index 3fd735cf..04c39e3d 100644 --- a/CodeBeam.MudBlazor.Extensions/TScripts/MudExtensions.js +++ b/CodeBeam.MudBlazor.Extensions/TScripts/MudExtensions.js @@ -1,5 +1,4 @@ - -class MudScrollManagerExtended { +class MudScrollManagerExtended { scrollToMiddle(parentId, childId) { @@ -44,4 +43,248 @@ window.mudTeleport = { removeFromDOM: (el) => { if (el && el.__internalId !== null) el.remove(); }, -}; \ No newline at end of file +}; + +class MudSignaturePadManager { + constructor() { + this.pads = []; + } + + addPad(dotnetRef, canvasRef, canvasOption) { + const signaturePad = new MudSignaturePad(dotnetRef, canvasRef, canvasOption); + signaturePad.init(); + this.pads.push(signaturePad); + } + + togglePadEraser(canvasRef) { + const pad = this.getPad(canvasRef); + if (pad) { + pad.toggleEraser(); + } + } + + disposePad(canvasRef) { + const pad = this.getPad(canvasRef); + if (pad) { + pad.dispose(); + } + } + + clearPad(canvasRef) { + const pad = this.getPad(canvasRef); + if (pad) { + pad.clear(true); + } + } + + downloadPadImage(canvasRef) { + const pad = this.getPad(canvasRef); + if (pad) { + pad.download(); + } + } + + getBase64(canvasRef) { + const pad = this.getPad(canvasRef); + if (pad) { + return pad.getBase64(); + } + } + + updatePadOptions(canvasRef, options) { + const pad = this.getPad(canvasRef); + if (pad) { + pad.setOptions(options); + } + } + + updatePadImage(canvasRef, base64Src) { + const pad = this.getPad(canvasRef); + if (pad) { + if (base64Src.startsWith("data:image/png;base64,")) { + pad.updateImage(base64Src); + return; + } + pad.updateImage(`data:image/png;base64,${base64Src}`); + } + } + + getPad(canvasRef) { + const padIndex = this.pads.findIndex(x => x.canvas.id === canvasRef.id); + if (padIndex >= 0) { + return this.pads[padIndex]; + } + return null; + } +} + +class MudSignaturePad { + constructor(dotnetRef, canvasRef, canvasOption) { + this.canvas = canvasRef; + this.options = canvasOption; + this.isMouseDown = false; + this.isErasing = false; + this.memCanvas = document.createElement('canvas'); + this.points = []; + this.dotnetRef = dotnetRef; + } + + get ctx() { + return this.canvas.getContext('2d'); + } + + get memCtx() { + return this.memCanvas.getContext('2d'); + } + + getBase64() { + return this.canvas.toDataURL(); + } + + init() { + this.setCanvasSize(); + this.setOptions(this.options); + this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e)); + this.canvas.addEventListener('mousemove', (e) => this.drawLine(e)); + this.canvas.addEventListener('mouseup', () => this.stopDrawing()); + this.canvas.addEventListener('mouseout', () => this.stopDrawing()); + this.canvas.addEventListener("touchstart", (e) => this.startDrawing(e)); + this.canvas.addEventListener("touchend", () => this.stopDrawing()); + this.canvas.addEventListener("touchmove", (e) => this.drawLine(e)); + this.setPencilCursor(); + }; + + download() { + const link = document.createElement('a'); + link.download = 'signature.png'; + link.href = this.canvas.toDataURL(); + link.click(); + link.remove(); + }; + + updateImage(base64) { + this.clear(true); + const image = new Image(); + const ctx = this.ctx; + const memCtx = this.memCtx; + image.onload = function () { + ctx.drawImage(image, 0, 0); + memCtx.drawImage(image, 0, 0); + image.remove(); + }; + image.src = base64; + } + + setCanvasSize() { + const parent = this.canvas.parentElement; + const parentRect = parent.getBoundingClientRect(); + this.canvas.width = parentRect.width; + this.canvas.height = parentRect.height; + this.memCanvas.height = parentRect.height; + this.memCanvas.width = parentRect.width; + } + + dispose() { + this.canvas.removeEventListener('mousedown'); + this.canvas.removeEventListener('mousemove'); + this.canvas.removeEventListener('mouseup'); + this.canvas.removeEventListener('mouseout'); + this.canvas.removeEventListener("touchstart"); + this.canvas.removeEventListener("touchend"); + this.canvas.removeEventListener("touchmove"); + } + + clear(both) { + if (both === true) { + this.memCtx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + + stopDrawing() { + this.isMouseDown = false; + this.memCtx.clearRect(0, 0, this.memCanvas.width, this.memCanvas.height); + this.memCtx.drawImage(this.canvas, 0, 0); + this.points = []; + } + + startDrawing(event) { + this.isMouseDown = true; + this.points.push({ + x: event.offsetX, + y: event.offsetY + }); + } + + setOptions(options) { + this.ctx.lineWidth = options.lineWidth; + this.ctx.lineJoin = options.lineJoin; + this.ctx.lineCap = options.lineCap; + this.ctx.strokeStyle = options.strokeStyle; + this.options = options; + } + + toggleEraser() { + this.isErasing = !this.isErasing; + if (this.isErasing) { + this.setEraserCursor(); + return; + } + this.setPencilCursor(); + } + + setPencilCursor() { + this.canvas.setAttribute('style', 'cursor:url(\'_content/CodeBeam.MudBlazor.Extensions/pencil.cur\'), auto;'); + } + + setEraserCursor() { + this.canvas.setAttribute('style', 'cursor:url(\'_content/CodeBeam.MudBlazor.Extensions/eraser.cur\'), auto;'); + } + + drawLine(event) { + if (this.isMouseDown) { + if (this.isErasing === false) { + this.clear(); + this.ctx.drawImage(this.memCanvas, 0, 0); + this.points.push({ + x: event.offsetX, + y: event.offsetY + }); + this.drawPoints(this.ctx, this.points); + } else { + this.ctx.clearRect(event.offsetX, event.offsetY, 23, 23); + } + } + } + + drawPoints(ctx, points) { + if (points.length < 6) return; + if (points.length < 6) { + const b = points[0]; + ctx.beginPath(); + ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0); + ctx.closePath(); + ctx.fill(); + this.pushUpdateToBlazorComponent(); + return; + } + ctx.beginPath(); + ctx.moveTo(points[0].x, points[0].y); + let lastPoint; + for (let i = 1; i < points.length - 2; i++) { + const c = (points[i].x + points[i + 1].x) / 2, + d = (points[i].y + points[i + 1].y) / 2; + ctx.quadraticCurveTo(points[i].x, points[i].y, c, d); + lastPoint = i; + } + ctx.quadraticCurveTo(points[lastPoint].x, points[lastPoint].y, points[lastPoint + 1].x, points[lastPoint + 1].y); + ctx.stroke() + this.pushUpdateToBlazorComponent(); + } + + pushUpdateToBlazorComponent() { + this.dotnetRef.invokeMethodAsync('SignatureDataChangedAsync'); + } +} + +window.mudSignaturePad = new MudSignaturePadManager(); \ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/wwwroot/eraser.cur b/CodeBeam.MudBlazor.Extensions/wwwroot/eraser.cur new file mode 100644 index 0000000000000000000000000000000000000000..521d6d368669285184cbf98d347bfe86e5bca986 GIT binary patch literal 4286 zcmeH`y$!-J5JoRhP(nqE%WMIA&>%4g8HX_#g%MJw0FLh>S)44pihqQ3C%xE-ojmKa zL$sofoTIt!JEA>$J#gRzFp4V*V^)@cC143y0+xU!UP_J{78{*+su$H=+=DYrgzx-au{`OW?Nw_{oO+;e6wdl$OGd+(Jtw>$LL yc58nNxA(VlYrm`^hRi8hI9JPSz$b|L172VPBY1*G$2_btya8Kq07q~Gc|CU@Qd literal 0 HcmV?d00001 diff --git a/CodeBeam.MudBlazor.Extensions/wwwroot/pencil.cur b/CodeBeam.MudBlazor.Extensions/wwwroot/pencil.cur new file mode 100644 index 0000000000000000000000000000000000000000..ca2f27488b8c6f66f0b9493e43ee3df4566c9b73 GIT binary patch literal 326 zcmYk0F-ikb5Qe|^Tr}92fLPe%iGr3E_E{{%1Bh5!YL^>i3qc#v8))G{_6Qzfk6@W1 zWc*@W$UMGb=6(DykV%xX>Y4Kd2f&#WsU`VV)%^8!%Nuh4*ojzf xMJ!}4KhjC-f3xSg<5)6@1+$oMtNrT6>WO-$Hl6;{CVJheTXh=|&+;LC{Q^VTbtnJ; literal 0 HcmV?d00001 diff --git a/ComponentViewer.Docs/Pages/Components/SignaturePad.razor b/ComponentViewer.Docs/Pages/Components/SignaturePad.razor new file mode 100644 index 00000000..4938a1aa --- /dev/null +++ b/ComponentViewer.Docs/Pages/Components/SignaturePad.razor @@ -0,0 +1,12 @@ +@page "/mudsignaturepad" +@using ComponentViewer.Docs.Pages.Examples + + + + + + + +@code { + +} \ No newline at end of file diff --git a/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor b/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor new file mode 100644 index 00000000..6bb4b261 --- /dev/null +++ b/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor @@ -0,0 +1,61 @@ +@using MudBlazor.Utilities +@using System.Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@code { + bool _showDownload = true; + bool _showClear = true; + bool _showLineWidth = true; + bool _showStrokeStyle = true; + bool _showLineCapStyle = true; + bool _showLineJoinStyle = true; + + SignaturePadOptions _options = new SignaturePadOptions() + { + LineWidth = 4, + LineCapStyle = LineCapTypes.Round, + LineJoinStyle = LineJoinTypes.Round, + StrokeStyle = new MudColor("#000000") + }; + + byte[] _value = Convert.FromBase64String(existingSignature); + + void BytesChanged(byte[] bytes) + { + _value = bytes; + } + + static string existingSignature = "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"; +} \ No newline at end of file diff --git a/ComponentViewer.Docs/Pages/Index.razor b/ComponentViewer.Docs/Pages/Index.razor index b30b4d2e..9210990a 100644 --- a/ComponentViewer.Docs/Pages/Index.razor +++ b/ComponentViewer.Docs/Pages/Index.razor @@ -234,7 +234,8 @@ new("MudPopup", "A mobile friendly popup content for several situations."), new("MudRangeSlider", "A slider with range capabilities, set upper and lower values."), new("MudScrollbar", "Handle all or selected scrollbar styles with a Mud component."), - new("MudSpeedDial", "An expandable fab component."), + new("MudWheel", "Smoothly changes values in a wheel within defined ItemCollection."), + new("MudSignaturePad", "A signature pad."), new("MudSplitter", "A resizeable content splitter."), new("MudStepper", "A wizard-like steps to control the flow with rich options."), new("MudSwitchM3", "Material 3 switch component that has all MudBlazor features."), @@ -243,5 +244,6 @@ new("MudTransferList", "A component that has 2 lists that transfer items to another."), new("MudWatch", "A performance optimized watch to show current time or show stopwatch or countdown."), new("MudWheel", "Smoothly changes values in a wheel within defined ItemCollection.") + }; } From b7c239f6a5f9dc34a64e09b00207b8ae37a52160 Mon Sep 17 00:00:00 2001 From: Carel vd Merwe Date: Sun, 10 Sep 2023 17:07:30 +0200 Subject: [PATCH 2/3] Add Localized String & Outer Class to paper --- .../SignaturePad/MudSignaturePad.razor | 16 +++++++++------- .../SignaturePad/MudSignaturePad.razor.cs | 8 +++++--- .../Utilities/SignaturePadLocalizedStrings.cs | 13 +++++++++++++ .../Pages/Examples/SignaturePadExample1.razor | 6 +++++- 4 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 CodeBeam.MudBlazor.Extensions/Utilities/SignaturePadLocalizedStrings.cs diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor index cd09203c..7ec0a72f 100644 --- a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor @@ -6,7 +6,7 @@ @inject IJSRuntime JsRuntime @inherits ComponentBase - +
@@ -14,15 +14,16 @@ @if (ShowLineWidth) { - + } @if (ShowStrokeStyle) { - + } @if (ShowLineJoinStyle) { - + @foreach (var value in Enum.GetValues()) { @@ -31,7 +32,8 @@ } @if (ShowLineCapStyle) { - + @foreach (var value in Enum.GetValues()) { @@ -45,11 +47,11 @@ @if (ShowDownload) { - Download + @LocalizedStrings.Download } @if (ShowClear) { - Clear + @LocalizedStrings.Clear } diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs index 0159809e..e2614780 100644 --- a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs @@ -3,6 +3,7 @@ using Microsoft.JSInterop; using MudBlazor; using MudBlazor.Utilities; +using MudExtensions.Utilities; namespace MudExtensions; @@ -19,7 +20,7 @@ public MudSignaturePad() int _lineWidth = 3; private byte[] _value = Array.Empty(); readonly string _id = Guid.NewGuid().ToString(); - string DrawEraseChipText => _isErasing ? "Eraser" : "Pen"; + string DrawEraseChipText => _isErasing ? LocalizedStrings.Eraser : LocalizedStrings.Pen; string DrawEraseChipIcon => _isErasing ? @Icons.Material.Filled.DeleteSweep : @Icons.Material.Filled.Edit; private object JsOptionsStruct => new @@ -43,11 +44,12 @@ public byte[] Value } [Parameter] public EventCallback ValueChanged { get; set; } - + [Parameter] public SignaturePadLocalizedStrings LocalizedStrings { get; set; } = new(); [Parameter] public SignaturePadOptions Options { get; set; } = new SignaturePadOptions(); [Parameter] public string ToolbarStyle { get; set; } = string.Empty; - + [Parameter] public string OuterClass { get; set; } = "pa-4"; + [Parameter] public int Elevation { get; set; } = 4; [Parameter] public string CanvasContainerClass { get; set; } = "border-solid border-2 mud-border-primary"; [Parameter] public string CanvasContainerStyle { get; set; } = "height: 100%;width: 100%;"; [Parameter] public bool ShowClear { get; set; } = true; diff --git a/CodeBeam.MudBlazor.Extensions/Utilities/SignaturePadLocalizedStrings.cs b/CodeBeam.MudBlazor.Extensions/Utilities/SignaturePadLocalizedStrings.cs new file mode 100644 index 00000000..1efff35b --- /dev/null +++ b/CodeBeam.MudBlazor.Extensions/Utilities/SignaturePadLocalizedStrings.cs @@ -0,0 +1,13 @@ +namespace MudExtensions.Utilities; + +public class SignaturePadLocalizedStrings +{ + public string Download { get; set; } = "Download"; + public string Clear { get; set; } = "Clear"; + public string LineWidth { get; set; } = "Line Width"; + public string StrokeColor { get; set; } = "Stroke Color"; + public string LineCapStyle { get; set; } = "Line Cap Style"; + public string LineJoinStyle { get; set; } = "Line Join Style"; + public string Eraser { get; set; } = "Eraser"; + public string Pen { get; set; } = "Pen"; +} \ No newline at end of file diff --git a/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor b/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor index 6bb4b261..b0378b57 100644 --- a/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor +++ b/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor @@ -1,5 +1,6 @@ @using MudBlazor.Utilities @using System.Text +@using MudExtensions.Utilities + ShowLineJoinStyle="_showLineJoinStyle" + LocalizedStrings="_localizedStrings"> @@ -42,6 +44,8 @@ bool _showLineCapStyle = true; bool _showLineJoinStyle = true; + SignaturePadLocalizedStrings _localizedStrings = new SignaturePadLocalizedStrings(); + SignaturePadOptions _options = new SignaturePadOptions() { LineWidth = 4, From 9413b6d008aaacfab9322b2b372d7ddaada84e12 Mon Sep 17 00:00:00 2001 From: mckaragoz <78308169+mckaragoz@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:36:17 +0300 Subject: [PATCH 3/3] Makeup --- .../SignaturePad/MudSignaturePad.razor | 91 +++++++++---------- .../SignaturePad/MudSignaturePad.razor.cs | 36 ++++++-- .../wwwroot/MudExtensions.min.js | 2 +- .../Pages/Components/ApiPage.razor | 21 +++++ .../Pages/Components/SignaturePad.razor | 6 +- .../Pages/Examples/SignaturePadExample1.razor | 63 ++++++------- ComponentViewer.Docs/Pages/Index.razor | 2 +- ComponentViewer.Docs/Shared/MainLayout.razor | 1 + 8 files changed, 131 insertions(+), 91 deletions(-) diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor index 7ec0a72f..42be1b02 100644 --- a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor @@ -7,52 +7,51 @@ @inherits ComponentBase -
+
+ + @if (ShowClear) + { + + } + @if (ShowDownload) + { + + } + @ToolbarContent +
+
- - - @if (ShowLineWidth) - { - - } - @if (ShowStrokeStyle) - { - - } - @if (ShowLineJoinStyle) - { - - @foreach (var value in Enum.GetValues()) - { - - } - - } - @if (ShowLineCapStyle) - { - - @foreach (var value in Enum.GetValues()) - { - - } - - } - - - @DrawEraseChipText - - @if (ShowDownload) - { - @LocalizedStrings.Download - } - @if (ShowClear) - { - @LocalizedStrings.Clear - } - - +
+ @if (ShowLineWidth) + { + + } + @if (ShowStrokeStyle) + { + + } + @if (ShowLineJoinStyle) + { + + @foreach (var value in Enum.GetValues()) + { + + } + + } + @if (ShowLineCapStyle) + { + + @foreach (var value in Enum.GetValues()) + { + + } + + } +
\ No newline at end of file diff --git a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs index e2614780..a1f84a34 100644 --- a/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs +++ b/CodeBeam.MudBlazor.Extensions/Components/SignaturePad/MudSignaturePad.razor.cs @@ -14,6 +14,16 @@ public MudSignaturePad() _dotnetObjectRef = DotNetObjectReference.Create(this); } + protected string CanvasContainerClassname => new CssBuilder() + //.AddClass($"border-solid border-2 mud-border-{Color.ToDescriptionString()}") + .AddClass(CanvasContainerClass) + .Build(); + + protected string ToolbarClassname => new CssBuilder() + .AddClass("pa-2 d-flex flex-wrap gap-2") + .AddClass(ToolbarClass) + .Build(); + private DotNetObjectReference _dotnetObjectRef; ElementReference _reference; bool _isErasing = true; @@ -21,7 +31,7 @@ public MudSignaturePad() private byte[] _value = Array.Empty(); readonly string _id = Guid.NewGuid().ToString(); string DrawEraseChipText => _isErasing ? LocalizedStrings.Eraser : LocalizedStrings.Pen; - string DrawEraseChipIcon => _isErasing ? @Icons.Material.Filled.DeleteSweep : @Icons.Material.Filled.Edit; + string DrawEraseChipIcon => _isErasing ? Icons.Material.Filled.Edit : Icons.Material.Filled.EditOff; private object JsOptionsStruct => new { @@ -47,17 +57,22 @@ public byte[] Value [Parameter] public SignaturePadLocalizedStrings LocalizedStrings { get; set; } = new(); [Parameter] public SignaturePadOptions Options { get; set; } = new SignaturePadOptions(); - [Parameter] public string ToolbarStyle { get; set; } = string.Empty; - [Parameter] public string OuterClass { get; set; } = "pa-4"; + [Parameter] public string ToolbarClass { get; set; } + [Parameter] public string ToolbarStyle { get; set; } + [Parameter] public string OuterClass { get; set; } [Parameter] public int Elevation { get; set; } = 4; - [Parameter] public string CanvasContainerClass { get; set; } = "border-solid border-2 mud-border-primary"; - [Parameter] public string CanvasContainerStyle { get; set; } = "height: 100%;width: 100%;"; + [Parameter] public string CanvasContainerClass { get; set; } + [Parameter] public string CanvasContainerStyle { get; set; } = "height: 100%;width: 100%; box-shadow: rgb(204, 219, 232) 3px 3px 6px 0px inset, rgba(255, 255, 255, 0.5) -3px -3px 6px 1px inset;"; [Parameter] public bool ShowClear { get; set; } = true; [Parameter] public bool ShowLineWidth { get; set; } = true; [Parameter] public bool ShowStrokeStyle { get; set; } = true; [Parameter] public bool ShowDownload { get; set; } = true; [Parameter] public bool ShowLineJoinStyle { get; set; } = true; [Parameter] public bool ShowLineCapStyle { get; set; } = true; + [Parameter] public bool Dense { get; set; } + [Parameter] public Variant Variant { get; set; } + [Parameter] public Color Color { get; set; } + [Parameter] public RenderFragment ToolbarContent { get; set; } protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -125,7 +140,14 @@ private async Task LineCapTypeUpdated(LineCapTypes obj) public async ValueTask DisposeAsync() { - await JsRuntime.InvokeVoidAsync("mudSignaturePad.disposePad", _reference); + try + { + await JsRuntime.InvokeVoidAsync("mudSignaturePad.disposePad", _reference); + } + catch + { + //ignore + } } [JSInvokable] @@ -143,4 +165,4 @@ public async Task SignatureDataChangedAsync() await ValueChanged.InvokeAsync(Value); } -} \ No newline at end of file +} diff --git a/CodeBeam.MudBlazor.Extensions/wwwroot/MudExtensions.min.js b/CodeBeam.MudBlazor.Extensions/wwwroot/MudExtensions.min.js index d5055167..9d95c0ff 100644 --- a/CodeBeam.MudBlazor.Extensions/wwwroot/MudExtensions.min.js +++ b/CodeBeam.MudBlazor.Extensions/wwwroot/MudExtensions.min.js @@ -1 +1 @@ -function auto_size(n){n.style.height="5px";n.style.height=n.scrollHeight+4+"px"}function getcss(n,t){const i=document.querySelector(n);return i.style.getPropertyValue(t)}function setcss(n,t,i){const r=document.querySelectorAll(n);for(let n=0;n{const i=document.querySelector(t);return i?(i.appendChild(n),"ok"):"not found"},removeFromDOM:n=>{n&&n.__internalId!==null&&n.remove()}}; \ No newline at end of file +function auto_size(n){n.style.height="5px";n.style.height=n.scrollHeight+4+"px"}function getcss(n,t){const i=document.querySelector(n);return i.style.getPropertyValue(t)}function setcss(n,t,i){const r=document.querySelectorAll(n);for(let n=0;n{const i=document.querySelector(t);return i?(i.appendChild(n),"ok"):"not found"},removeFromDOM:n=>{n&&n.__internalId!==null&&n.remove()}};class MudSignaturePadManager{constructor(){this.pads=[]}addPad(n,t,i){const r=new MudSignaturePad(n,t,i);r.init();this.pads.push(r)}togglePadEraser(n){const t=this.getPad(n);t&&t.toggleEraser()}disposePad(n){const t=this.getPad(n);t&&t.dispose()}clearPad(n){const t=this.getPad(n);t&&t.clear(!0)}downloadPadImage(n){const t=this.getPad(n);t&&t.download()}getBase64(n){const t=this.getPad(n);if(t)return t.getBase64()}updatePadOptions(n,t){const i=this.getPad(n);i&&i.setOptions(t)}updatePadImage(n,t){const i=this.getPad(n);if(i){if(t.startsWith("data:image/png;base64,")){i.updateImage(t);return}i.updateImage(`data:image/png;base64,${t}`)}}getPad(n){const t=this.pads.findIndex(t=>t.canvas.id===n.id);return t>=0?this.pads[t]:null}}class MudSignaturePad{constructor(n,t,i){this.canvas=t;this.options=i;this.isMouseDown=!1;this.isErasing=!1;this.memCanvas=document.createElement("canvas");this.points=[];this.dotnetRef=n}get ctx(){return this.canvas.getContext("2d")}get memCtx(){return this.memCanvas.getContext("2d")}getBase64(){return this.canvas.toDataURL()}init(){this.setCanvasSize();this.setOptions(this.options);this.canvas.addEventListener("mousedown",n=>this.startDrawing(n));this.canvas.addEventListener("mousemove",n=>this.drawLine(n));this.canvas.addEventListener("mouseup",()=>this.stopDrawing());this.canvas.addEventListener("mouseout",()=>this.stopDrawing());this.canvas.addEventListener("touchstart",n=>this.startDrawing(n));this.canvas.addEventListener("touchend",()=>this.stopDrawing());this.canvas.addEventListener("touchmove",n=>this.drawLine(n));this.setPencilCursor()}download(){const n=document.createElement("a");n.download="signature.png";n.href=this.canvas.toDataURL();n.click();n.remove()}updateImage(n){this.clear(!0);const t=new Image,i=this.ctx,r=this.memCtx;t.onload=function(){i.drawImage(t,0,0);r.drawImage(t,0,0);t.remove()};t.src=n}setCanvasSize(){const t=this.canvas.parentElement,n=t.getBoundingClientRect();this.canvas.width=n.width;this.canvas.height=n.height;this.memCanvas.height=n.height;this.memCanvas.width=n.width}dispose(){this.canvas.removeEventListener("mousedown");this.canvas.removeEventListener("mousemove");this.canvas.removeEventListener("mouseup");this.canvas.removeEventListener("mouseout");this.canvas.removeEventListener("touchstart");this.canvas.removeEventListener("touchend");this.canvas.removeEventListener("touchmove")}clear(n){n===!0&&this.memCtx.clearRect(0,0,this.canvas.width,this.canvas.height);this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}stopDrawing(){this.isMouseDown=!1;this.memCtx.clearRect(0,0,this.memCanvas.width,this.memCanvas.height);this.memCtx.drawImage(this.canvas,0,0);this.points=[]}startDrawing(n){this.isMouseDown=!0;this.points.push({x:n.offsetX,y:n.offsetY})}setOptions(n){this.ctx.lineWidth=n.lineWidth;this.ctx.lineJoin=n.lineJoin;this.ctx.lineCap=n.lineCap;this.ctx.strokeStyle=n.strokeStyle;this.options=n}toggleEraser(){if(this.isErasing=!this.isErasing,this.isErasing){this.setEraserCursor();return}this.setPencilCursor()}setPencilCursor(){this.canvas.setAttribute("style","cursor:url('_content/CodeBeam.MudBlazor.Extensions/pencil.cur'), auto;")}setEraserCursor(){this.canvas.setAttribute("style","cursor:url('_content/CodeBeam.MudBlazor.Extensions/eraser.cur'), auto;")}drawLine(n){this.isMouseDown&&(this.isErasing===!1?(this.clear(),this.ctx.drawImage(this.memCanvas,0,0),this.points.push({x:n.offsetX,y:n.offsetY}),this.drawPoints(this.ctx,this.points)):this.ctx.clearRect(n.offsetX,n.offsetY,23,23))}drawPoints(n,t){if(!(t.length<6)){if(t.length<6){const i=t[0];n.beginPath();n.arc(i.x,i.y,n.lineWidth/2,0,Math.PI*2,!0);n.closePath();n.fill();this.pushUpdateToBlazorComponent();return}n.beginPath();n.moveTo(t[0].x,t[0].y);let i;for(let r=1;r + + x.Name).ToList())"> + + Name + Type + Default + + + @context.Name + @context.PropertyType.Name + + @if (true) + { + MudSignaturePad instance = new(); + @(context.GetValue(instance)?.ToString() ?? "null") + } + + + + + x.Name).ToList())"> diff --git a/ComponentViewer.Docs/Pages/Components/SignaturePad.razor b/ComponentViewer.Docs/Pages/Components/SignaturePad.razor index 4938a1aa..18b22bc5 100644 --- a/ComponentViewer.Docs/Pages/Components/SignaturePad.razor +++ b/ComponentViewer.Docs/Pages/Components/SignaturePad.razor @@ -1,12 +1,8 @@ @page "/mudsignaturepad" @using ComponentViewer.Docs.Pages.Examples + - - -@code { - -} \ No newline at end of file diff --git a/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor b/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor index b0378b57..2893e373 100644 --- a/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor +++ b/ComponentViewer.Docs/Pages/Examples/SignaturePadExample1.razor @@ -1,8 +1,8 @@ @using MudBlazor.Utilities @using System.Text @using MudExtensions.Utilities - - + + + LocalizedStrings="_localizedStrings" + Variant="@_variant" + Color="@_color" + Dense="@_dense" + Elevation="@_elevation"> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @code { bool _showDownload = true; @@ -43,16 +40,20 @@ bool _showStrokeStyle = true; bool _showLineCapStyle = true; bool _showLineJoinStyle = true; + bool _dense; + Variant _variant; + Color _color; + int _elevation = 4; SignaturePadLocalizedStrings _localizedStrings = new SignaturePadLocalizedStrings(); SignaturePadOptions _options = new SignaturePadOptions() - { - LineWidth = 4, - LineCapStyle = LineCapTypes.Round, - LineJoinStyle = LineJoinTypes.Round, - StrokeStyle = new MudColor("#000000") - }; + { + LineWidth = 4, + LineCapStyle = LineCapTypes.Round, + LineJoinStyle = LineJoinTypes.Round, + StrokeStyle = new MudColor("#000000") + }; byte[] _value = Convert.FromBase64String(existingSignature); diff --git a/ComponentViewer.Docs/Pages/Index.razor b/ComponentViewer.Docs/Pages/Index.razor index 9210990a..f192c42e 100644 --- a/ComponentViewer.Docs/Pages/Index.razor +++ b/ComponentViewer.Docs/Pages/Index.razor @@ -234,8 +234,8 @@ new("MudPopup", "A mobile friendly popup content for several situations."), new("MudRangeSlider", "A slider with range capabilities, set upper and lower values."), new("MudScrollbar", "Handle all or selected scrollbar styles with a Mud component."), - new("MudWheel", "Smoothly changes values in a wheel within defined ItemCollection."), new("MudSignaturePad", "A signature pad."), + new("MudSpeedDial", "Stacked buttons in a menu content."), new("MudSplitter", "A resizeable content splitter."), new("MudStepper", "A wizard-like steps to control the flow with rich options."), new("MudSwitchM3", "Material 3 switch component that has all MudBlazor features."), diff --git a/ComponentViewer.Docs/Shared/MainLayout.razor b/ComponentViewer.Docs/Shared/MainLayout.razor index 83c98781..7e745b4b 100644 --- a/ComponentViewer.Docs/Shared/MainLayout.razor +++ b/ComponentViewer.Docs/Shared/MainLayout.razor @@ -57,6 +57,7 @@ Range Slider Scrollbar SelectExtended + SignaturePad SpeedDial Splitter Stepper