From 7fd0c6dee1e7fc9ca078d2be082f7e81f50bc52b Mon Sep 17 00:00:00 2001 From: Saade Date: Mon, 21 Aug 2023 11:01:55 -0300 Subject: [PATCH] feat: done button --- README.md | 10 +++- resources/dist/filament-autograph.js | 2 +- resources/js/index.js | 52 +++++++++++++++++--- resources/lang/en/filament-autograph.php | 4 ++ resources/lang/pt_BR/filament-autograph.php | 4 ++ resources/views/signature-pad.blade.php | 8 +++ src/Forms/Components/Actions/DoneAction.php | 32 ++++++++++++ src/Forms/Components/Concerns/HasActions.php | 49 ++++++++++++++++++ src/Forms/Components/SignaturePad.php | 1 + 9 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 src/Forms/Components/Actions/DoneAction.php diff --git a/README.md b/README.md index 8dfde80..ca75561 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ SignaturePad::make('signature') ->downloadActionDropdownPlacement('center-end') // Dropdown placement of the download action (defaults to 'bottom-end') ``` -### Disabling clear, download and undo actions. +### Disabling clear, download, undo and done actions. ```php use Saade\FilamentAutograph\Forms\Components\SignaturePad; @@ -76,6 +76,13 @@ SignaturePad::make('signature') ->clearable(false) ->downloadable(false) ->undoable(false) + ->confirmable(false) +``` + +### Requiring confirmation (Done button). +```php +SignaturePad::make('signature') + ->confirmable() // Requires user to click on 'Done' (defaults to false) ``` ### Customizing actions @@ -87,6 +94,7 @@ SignaturePad::make('signature') ->clearAction(fn (Action $action) => $action->button()) ->downloadAction(fn (Action $action) => $action->color('primary')) ->undoAction(fn (Action $action) => $action->icon('heroicon-o-ctrl-z')) + ->confirmAction(fn (Action $action) => $action->iconButton()->icon('heroicon-o-thumbs-up')) ``` ## Changelog diff --git a/resources/dist/filament-autograph.js b/resources/dist/filament-autograph.js index d02d0f2..ff584a0 100644 --- a/resources/dist/filament-autograph.js +++ b/resources/dist/filament-autograph.js @@ -1,4 +1,4 @@ -var w=class{constructor(t,i,e,n){if(isNaN(t)||isNaN(i))throw new Error(`Point is invalid: (${t}, ${i})`);this.x=+t,this.y=+i,this.pressure=e||0,this.time=n||Date.now()}distanceTo(t){return Math.sqrt(Math.pow(this.x-t.x,2)+Math.pow(this.y-t.y,2))}equals(t){return this.x===t.x&&this.y===t.y&&this.pressure===t.pressure&&this.time===t.time}velocityFrom(t){return this.time!==t.time?this.distanceTo(t)/(this.time-t.time):0}},y=class f{constructor(t,i,e,n,s,r){this.startPoint=t,this.control2=i,this.control1=e,this.endPoint=n,this.startWidth=s,this.endWidth=r}static fromPoints(t,i){let e=this.calculateControlPoints(t[0],t[1],t[2]).c2,n=this.calculateControlPoints(t[1],t[2],t[3]).c1;return new f(t[1],e,n,t[2],i.start,i.end)}static calculateControlPoints(t,i,e){let n=t.x-i.x,s=t.y-i.y,r=i.x-e.x,d=i.y-e.y,a={x:(t.x+i.x)/2,y:(t.y+i.y)/2},o={x:(i.x+e.x)/2,y:(i.y+e.y)/2},c=Math.sqrt(n*n+s*s),l=Math.sqrt(r*r+d*d),g=a.x-o.x,v=a.y-o.y,_=l/(c+l),h={x:o.x+g*_,y:o.y+v*_},m=i.x-h.x,u=i.y-h.y;return{c1:new w(a.x+m,a.y+u),c2:new w(o.x+m,o.y+u)}}length(){let i=0,e,n;for(let s=0;s<=10;s+=1){let r=s/10,d=this.point(r,this.startPoint.x,this.control1.x,this.control2.x,this.endPoint.x),a=this.point(r,this.startPoint.y,this.control1.y,this.control2.y,this.endPoint.y);if(s>0){let o=d-e,c=a-n;i+=Math.sqrt(o*o+c*c)}e=d,n=a}return i}point(t,i,e,n,s){return i*(1-t)*(1-t)*(1-t)+3*e*(1-t)*(1-t)*t+3*n*(1-t)*t*t+s*t*t*t}},E=class{constructor(){try{this._et=new EventTarget}catch{this._et=document}}addEventListener(t,i,e){this._et.addEventListener(t,i,e)}dispatchEvent(t){return this._et.dispatchEvent(t)}removeEventListener(t,i,e){this._et.removeEventListener(t,i,e)}};function C(f,t=250){let i=0,e=null,n,s,r,d=()=>{i=Date.now(),e=null,n=f.apply(s,r),e||(s=null,r=[])};return function(...o){let c=Date.now(),l=t-(c-i);return s=this,r=o,l<=0||l>t?(e&&(clearTimeout(e),e=null),i=c,n=f.apply(s,r),e||(s=null,r=[])):e||(e=window.setTimeout(d,l)),n}}var x=class f extends E{constructor(t,i={}){super(),this.canvas=t,this._drawningStroke=!1,this._isEmpty=!0,this._lastPoints=[],this._data=[],this._lastVelocity=0,this._lastWidth=0,this._handleMouseDown=e=>{e.buttons===1&&(this._drawningStroke=!0,this._strokeBegin(e))},this._handleMouseMove=e=>{this._drawningStroke&&this._strokeMoveUpdate(e)},this._handleMouseUp=e=>{e.buttons===1&&this._drawningStroke&&(this._drawningStroke=!1,this._strokeEnd(e))},this._handleTouchStart=e=>{if(e.cancelable&&e.preventDefault(),e.targetTouches.length===1){let n=e.changedTouches[0];this._strokeBegin(n)}},this._handleTouchMove=e=>{e.cancelable&&e.preventDefault();let n=e.targetTouches[0];this._strokeMoveUpdate(n)},this._handleTouchEnd=e=>{if(e.target===this.canvas){e.cancelable&&e.preventDefault();let s=e.changedTouches[0];this._strokeEnd(s)}},this._handlePointerStart=e=>{this._drawningStroke=!0,e.preventDefault(),this._strokeBegin(e)},this._handlePointerMove=e=>{this._drawningStroke&&(e.preventDefault(),this._strokeMoveUpdate(e))},this._handlePointerEnd=e=>{this._drawningStroke&&(e.preventDefault(),this._drawningStroke=!1,this._strokeEnd(e))},this.velocityFilterWeight=i.velocityFilterWeight||.7,this.minWidth=i.minWidth||.5,this.maxWidth=i.maxWidth||2.5,this.throttle="throttle"in i?i.throttle:16,this.minDistance="minDistance"in i?i.minDistance:5,this.dotSize=i.dotSize||0,this.penColor=i.penColor||"black",this.backgroundColor=i.backgroundColor||"rgba(0,0,0,0)",this.compositeOperation=i.compositeOperation||"source-over",this._strokeMoveUpdate=this.throttle?C(f.prototype._strokeUpdate,this.throttle):f.prototype._strokeUpdate,this._ctx=t.getContext("2d"),this.clear(),this.on()}clear(){let{_ctx:t,canvas:i}=this;t.fillStyle=this.backgroundColor,t.clearRect(0,0,i.width,i.height),t.fillRect(0,0,i.width,i.height),this._data=[],this._reset(this._getPointGroupOptions()),this._isEmpty=!0}fromDataURL(t,i={}){return new Promise((e,n)=>{let s=new Image,r=i.ratio||window.devicePixelRatio||1,d=i.width||this.canvas.width/r,a=i.height||this.canvas.height/r,o=i.xOffset||0,c=i.yOffset||0;this._reset(this._getPointGroupOptions()),s.onload=()=>{this._ctx.drawImage(s,o,c,d,a),e()},s.onerror=l=>{n(l)},s.crossOrigin="anonymous",s.src=t,this._isEmpty=!1})}toDataURL(t="image/png",i){switch(t){case"image/svg+xml":return typeof i!="object"&&(i=void 0),`data:image/svg+xml;base64,${btoa(this.toSVG(i))}`;default:return typeof i!="number"&&(i=void 0),this.canvas.toDataURL(t,i)}}on(){this.canvas.style.touchAction="none",this.canvas.style.msTouchAction="none",this.canvas.style.userSelect="none";let t=/Macintosh/.test(navigator.userAgent)&&"ontouchstart"in document;window.PointerEvent&&!t?this._handlePointerEvents():(this._handleMouseEvents(),"ontouchstart"in window&&this._handleTouchEvents())}off(){this.canvas.style.touchAction="auto",this.canvas.style.msTouchAction="auto",this.canvas.style.userSelect="auto",this.canvas.removeEventListener("pointerdown",this._handlePointerStart),this.canvas.removeEventListener("pointermove",this._handlePointerMove),this.canvas.ownerDocument.removeEventListener("pointerup",this._handlePointerEnd),this.canvas.removeEventListener("mousedown",this._handleMouseDown),this.canvas.removeEventListener("mousemove",this._handleMouseMove),this.canvas.ownerDocument.removeEventListener("mouseup",this._handleMouseUp),this.canvas.removeEventListener("touchstart",this._handleTouchStart),this.canvas.removeEventListener("touchmove",this._handleTouchMove),this.canvas.removeEventListener("touchend",this._handleTouchEnd)}isEmpty(){return this._isEmpty}fromData(t,{clear:i=!0}={}){i&&this.clear(),this._fromData(t,this._drawCurve.bind(this),this._drawDot.bind(this)),this._data=this._data.concat(t)}toData(){return this._data}_getPointGroupOptions(t){return{penColor:t&&"penColor"in t?t.penColor:this.penColor,dotSize:t&&"dotSize"in t?t.dotSize:this.dotSize,minWidth:t&&"minWidth"in t?t.minWidth:this.minWidth,maxWidth:t&&"maxWidth"in t?t.maxWidth:this.maxWidth,velocityFilterWeight:t&&"velocityFilterWeight"in t?t.velocityFilterWeight:this.velocityFilterWeight,compositeOperation:t&&"compositeOperation"in t?t.compositeOperation:this.compositeOperation}}_strokeBegin(t){this.dispatchEvent(new CustomEvent("beginStroke",{detail:t}));let i=this._getPointGroupOptions(),e=Object.assign(Object.assign({},i),{points:[]});this._data.push(e),this._reset(i),this._strokeUpdate(t)}_strokeUpdate(t){if(this._data.length===0){this._strokeBegin(t);return}this.dispatchEvent(new CustomEvent("beforeUpdateStroke",{detail:t}));let i=t.clientX,e=t.clientY,n=t.pressure!==void 0?t.pressure:t.force!==void 0?t.force:0,s=this._createPoint(i,e,n),r=this._data[this._data.length-1],d=r.points,a=d.length>0&&d[d.length-1],o=a?s.distanceTo(a)<=this.minDistance:!1,c=this._getPointGroupOptions(r);if(!a||!(a&&o)){let l=this._addPoint(s,c);a?l&&this._drawCurve(l,c):this._drawDot(s,c),d.push({time:s.time,x:s.x,y:s.y,pressure:s.pressure})}this.dispatchEvent(new CustomEvent("afterUpdateStroke",{detail:t}))}_strokeEnd(t){this._strokeUpdate(t),this.dispatchEvent(new CustomEvent("endStroke",{detail:t}))}_handlePointerEvents(){this._drawningStroke=!1,this.canvas.addEventListener("pointerdown",this._handlePointerStart),this.canvas.addEventListener("pointermove",this._handlePointerMove),this.canvas.ownerDocument.addEventListener("pointerup",this._handlePointerEnd)}_handleMouseEvents(){this._drawningStroke=!1,this.canvas.addEventListener("mousedown",this._handleMouseDown),this.canvas.addEventListener("mousemove",this._handleMouseMove),this.canvas.ownerDocument.addEventListener("mouseup",this._handleMouseUp)}_handleTouchEvents(){this.canvas.addEventListener("touchstart",this._handleTouchStart),this.canvas.addEventListener("touchmove",this._handleTouchMove),this.canvas.addEventListener("touchend",this._handleTouchEnd)}_reset(t){this._lastPoints=[],this._lastVelocity=0,this._lastWidth=(t.minWidth+t.maxWidth)/2,this._ctx.fillStyle=t.penColor,this._ctx.globalCompositeOperation=t.compositeOperation}_createPoint(t,i,e){let n=this.canvas.getBoundingClientRect();return new w(t-n.left,i-n.top,e,new Date().getTime())}_addPoint(t,i){let{_lastPoints:e}=this;if(e.push(t),e.length>2){e.length===3&&e.unshift(e[0]);let n=this._calculateCurveWidths(e[1],e[2],i),s=y.fromPoints(e,n);return e.shift(),s}return null}_calculateCurveWidths(t,i,e){let n=e.velocityFilterWeight*i.velocityFrom(t)+(1-e.velocityFilterWeight)*this._lastVelocity,s=this._strokeWidth(n,e),r={end:s,start:this._lastWidth};return this._lastVelocity=n,this._lastWidth=s,r}_strokeWidth(t,i){return Math.max(i.maxWidth/(t+1),i.minWidth)}_drawCurveSegment(t,i,e){let n=this._ctx;n.moveTo(t,i),n.arc(t,i,e,0,2*Math.PI,!1),this._isEmpty=!1}_drawCurve(t,i){let e=this._ctx,n=t.endWidth-t.startWidth,s=Math.ceil(t.length())*2;e.beginPath(),e.fillStyle=i.penColor;for(let r=0;r0?i.dotSize:(i.minWidth+i.maxWidth)/2;e.beginPath(),this._drawCurveSegment(t.x,t.y,n),e.closePath(),e.fillStyle=i.penColor,e.fill()}_fromData(t,i,e){for(let n of t){let{points:s}=n,r=this._getPointGroupOptions(n);if(s.length>1)for(let d=0;d{let l=document.createElement("path");if(!isNaN(o.control1.x)&&!isNaN(o.control1.y)&&!isNaN(o.control2.x)&&!isNaN(o.control2.y)){let g=`M ${o.startPoint.x.toFixed(3)},${o.startPoint.y.toFixed(3)} C ${o.control1.x.toFixed(3)},${o.control1.y.toFixed(3)} ${o.control2.x.toFixed(3)},${o.control2.y.toFixed(3)} ${o.endPoint.x.toFixed(3)},${o.endPoint.y.toFixed(3)}`;l.setAttribute("d",g),l.setAttribute("stroke-width",(o.endWidth*2.25).toFixed(3)),l.setAttribute("stroke",c),l.setAttribute("fill","none"),l.setAttribute("stroke-linecap","round"),a.appendChild(l)}},(o,{penColor:c,dotSize:l,minWidth:g,maxWidth:v})=>{let _=document.createElement("circle"),h=l>0?l:(g+v)/2;_.setAttribute("r",h.toString()),_.setAttribute("cx",o.x.toString()),_.setAttribute("cy",o.y.toString()),_.setAttribute("fill",c),a.appendChild(_)}),a.outerHTML}};var b=({backgroundColor:f,backgroundColorOnDark:t,disabled:i,dotSize:e,exportBackgroundColor:n,exportPenColor:s,filename:r,maxWidth:d,minDistance:a,minWidth:o,penColor:c,penColorOnDark:l,state:g,throttle:v,velocityFilterWeight:_})=>({state:g,previousState:g,signaturePad:null,init(){this.signaturePad=new x(this.$refs.canvas,{backgroundColor:f,dotSize:e,maxWidth:d,minDistance:a,minWidth:o,penColor:c,throttle:v,velocityFilterWeight:_}),i&&this.signaturePad.off(),this.watchState(),this.watchResize(),this.watchTheme(),g.initialValue&&(this.signaturePad.fromDataURL(g.initialValue),this.signaturePad.addEventListener("beginStroke",()=>{this.signaturePad.clear()},{once:!0}))},clear(){this.signaturePad.clear()},undo(){let h=this.signaturePad.toData();h&&(h.pop(),this.signaturePad.fromData(h))},downloadAs(h,m){let{data:u,canvasBackgroundColor:p,canvasPenColor:P}=this.prepareToExport();this.signaturePad.fromData(u),this.download(this.signaturePad.toDataURL(h,{includeBackgroundColor:!0}),`${r}.${m}`);let{data:k}=this.restoreFromExport(u,p,P);this.signaturePad.fromData(k)},watchState(){this.signaturePad.addEventListener("afterUpdateStroke",h=>{let{data:m,canvasBackgroundColor:u,canvasPenColor:p}=this.prepareToExport();this.signaturePad.fromData(m),this.previousState=this.state,this.state=this.signaturePad.toDataURL();let{data:P}=this.restoreFromExport(m,u,p);this.signaturePad.fromData(P)},{once:!1})},watchResize(){window.addEventListener("resize",()=>this.resizeCanvas),this.resizeCanvas()},resizeCanvas(){let h=Math.max(window.devicePixelRatio||1,1);this.$refs.canvas.width=this.$refs.canvas.offsetWidth*h,this.$refs.canvas.height=this.$refs.canvas.offsetHeight*h,this.$refs.canvas.getContext("2d").scale(h,h),this.signaturePad.clear()},watchTheme(){let h;this.$store.hasOwnProperty("theme")?(window.addEventListener("theme-changed",m=>this.onThemeChanged(m.detail)),h=this.$store.theme):(window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",m=>this.onThemeChanged(m.matches?"dark":"light")),h=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),this.onThemeChanged(h)},onThemeChanged(h){if(this.signaturePad.penColor=h==="dark"?l??c:c,this.signaturePad.backgroundColor=h==="dark"?t??f:f,!this.signaturePad.toData().length)return;let m=this.signaturePad.toData();m.map(u=>(u.penColor=h==="dark"?l??c:c,u.backgroundColor=h==="dark"?t??f:f,u)),this.signaturePad.clear(),this.signaturePad.fromData(m)},prepareToExport(){let h=this.signaturePad.toData(),m=this.signaturePad.backgroundColor,u=this.signaturePad.penColor;return this.signaturePad.backgroundColor=n??this.signaturePad.backgroundColor,h.map(p=>p.penColor=s??p.penColor),{data:h,canvasBackgroundColor:m,canvasPenColor:u}},restoreFromExport(h,m,u){return this.signaturePad.backgroundColor=m,h.map(p=>p.penColor=u),{data:h}},download(h,m){let u=document.createElement("a");u.download=m,u.href=h,document.body.appendChild(u),u.click(),document.body.removeChild(u)}});export{b as default}; +var x=class{constructor(t,i,e,n){if(isNaN(t)||isNaN(i))throw new Error(`Point is invalid: (${t}, ${i})`);this.x=+t,this.y=+i,this.pressure=e||0,this.time=n||Date.now()}distanceTo(t){return Math.sqrt(Math.pow(this.x-t.x,2)+Math.pow(this.y-t.y,2))}equals(t){return this.x===t.x&&this.y===t.y&&this.pressure===t.pressure&&this.time===t.time}velocityFrom(t){return this.time!==t.time?this.distanceTo(t)/(this.time-t.time):0}},P=class f{constructor(t,i,e,n,s,h){this.startPoint=t,this.control2=i,this.control1=e,this.endPoint=n,this.startWidth=s,this.endWidth=h}static fromPoints(t,i){let e=this.calculateControlPoints(t[0],t[1],t[2]).c2,n=this.calculateControlPoints(t[1],t[2],t[3]).c1;return new f(t[1],e,n,t[2],i.start,i.end)}static calculateControlPoints(t,i,e){let n=t.x-i.x,s=t.y-i.y,h=i.x-e.x,d=i.y-e.y,r={x:(t.x+i.x)/2,y:(t.y+i.y)/2},o={x:(i.x+e.x)/2,y:(i.y+e.y)/2},l=Math.sqrt(n*n+s*s),c=Math.sqrt(h*h+d*d),v=r.x-o.x,_=r.y-o.y,g=c/(l+c),p={x:o.x+v*g,y:o.y+_*g},a=i.x-p.x,u=i.y-p.y;return{c1:new x(r.x+a,r.y+u),c2:new x(o.x+a,o.y+u)}}length(){let i=0,e,n;for(let s=0;s<=10;s+=1){let h=s/10,d=this.point(h,this.startPoint.x,this.control1.x,this.control2.x,this.endPoint.x),r=this.point(h,this.startPoint.y,this.control1.y,this.control2.y,this.endPoint.y);if(s>0){let o=d-e,l=r-n;i+=Math.sqrt(o*o+l*l)}e=d,n=r}return i}point(t,i,e,n,s){return i*(1-t)*(1-t)*(1-t)+3*e*(1-t)*(1-t)*t+3*n*(1-t)*t*t+s*t*t*t}},E=class{constructor(){try{this._et=new EventTarget}catch{this._et=document}}addEventListener(t,i,e){this._et.addEventListener(t,i,e)}dispatchEvent(t){return this._et.dispatchEvent(t)}removeEventListener(t,i,e){this._et.removeEventListener(t,i,e)}};function S(f,t=250){let i=0,e=null,n,s,h,d=()=>{i=Date.now(),e=null,n=f.apply(s,h),e||(s=null,h=[])};return function(...o){let l=Date.now(),c=t-(l-i);return s=this,h=o,c<=0||c>t?(e&&(clearTimeout(e),e=null),i=l,n=f.apply(s,h),e||(s=null,h=[])):e||(e=window.setTimeout(d,c)),n}}var y=class f extends E{constructor(t,i={}){super(),this.canvas=t,this._drawningStroke=!1,this._isEmpty=!0,this._lastPoints=[],this._data=[],this._lastVelocity=0,this._lastWidth=0,this._handleMouseDown=e=>{e.buttons===1&&(this._drawningStroke=!0,this._strokeBegin(e))},this._handleMouseMove=e=>{this._drawningStroke&&this._strokeMoveUpdate(e)},this._handleMouseUp=e=>{e.buttons===1&&this._drawningStroke&&(this._drawningStroke=!1,this._strokeEnd(e))},this._handleTouchStart=e=>{if(e.cancelable&&e.preventDefault(),e.targetTouches.length===1){let n=e.changedTouches[0];this._strokeBegin(n)}},this._handleTouchMove=e=>{e.cancelable&&e.preventDefault();let n=e.targetTouches[0];this._strokeMoveUpdate(n)},this._handleTouchEnd=e=>{if(e.target===this.canvas){e.cancelable&&e.preventDefault();let s=e.changedTouches[0];this._strokeEnd(s)}},this._handlePointerStart=e=>{this._drawningStroke=!0,e.preventDefault(),this._strokeBegin(e)},this._handlePointerMove=e=>{this._drawningStroke&&(e.preventDefault(),this._strokeMoveUpdate(e))},this._handlePointerEnd=e=>{this._drawningStroke&&(e.preventDefault(),this._drawningStroke=!1,this._strokeEnd(e))},this.velocityFilterWeight=i.velocityFilterWeight||.7,this.minWidth=i.minWidth||.5,this.maxWidth=i.maxWidth||2.5,this.throttle="throttle"in i?i.throttle:16,this.minDistance="minDistance"in i?i.minDistance:5,this.dotSize=i.dotSize||0,this.penColor=i.penColor||"black",this.backgroundColor=i.backgroundColor||"rgba(0,0,0,0)",this.compositeOperation=i.compositeOperation||"source-over",this._strokeMoveUpdate=this.throttle?S(f.prototype._strokeUpdate,this.throttle):f.prototype._strokeUpdate,this._ctx=t.getContext("2d"),this.clear(),this.on()}clear(){let{_ctx:t,canvas:i}=this;t.fillStyle=this.backgroundColor,t.clearRect(0,0,i.width,i.height),t.fillRect(0,0,i.width,i.height),this._data=[],this._reset(this._getPointGroupOptions()),this._isEmpty=!0}fromDataURL(t,i={}){return new Promise((e,n)=>{let s=new Image,h=i.ratio||window.devicePixelRatio||1,d=i.width||this.canvas.width/h,r=i.height||this.canvas.height/h,o=i.xOffset||0,l=i.yOffset||0;this._reset(this._getPointGroupOptions()),s.onload=()=>{this._ctx.drawImage(s,o,l,d,r),e()},s.onerror=c=>{n(c)},s.crossOrigin="anonymous",s.src=t,this._isEmpty=!1})}toDataURL(t="image/png",i){switch(t){case"image/svg+xml":return typeof i!="object"&&(i=void 0),`data:image/svg+xml;base64,${btoa(this.toSVG(i))}`;default:return typeof i!="number"&&(i=void 0),this.canvas.toDataURL(t,i)}}on(){this.canvas.style.touchAction="none",this.canvas.style.msTouchAction="none",this.canvas.style.userSelect="none";let t=/Macintosh/.test(navigator.userAgent)&&"ontouchstart"in document;window.PointerEvent&&!t?this._handlePointerEvents():(this._handleMouseEvents(),"ontouchstart"in window&&this._handleTouchEvents())}off(){this.canvas.style.touchAction="auto",this.canvas.style.msTouchAction="auto",this.canvas.style.userSelect="auto",this.canvas.removeEventListener("pointerdown",this._handlePointerStart),this.canvas.removeEventListener("pointermove",this._handlePointerMove),this.canvas.ownerDocument.removeEventListener("pointerup",this._handlePointerEnd),this.canvas.removeEventListener("mousedown",this._handleMouseDown),this.canvas.removeEventListener("mousemove",this._handleMouseMove),this.canvas.ownerDocument.removeEventListener("mouseup",this._handleMouseUp),this.canvas.removeEventListener("touchstart",this._handleTouchStart),this.canvas.removeEventListener("touchmove",this._handleTouchMove),this.canvas.removeEventListener("touchend",this._handleTouchEnd)}isEmpty(){return this._isEmpty}fromData(t,{clear:i=!0}={}){i&&this.clear(),this._fromData(t,this._drawCurve.bind(this),this._drawDot.bind(this)),this._data=this._data.concat(t)}toData(){return this._data}_getPointGroupOptions(t){return{penColor:t&&"penColor"in t?t.penColor:this.penColor,dotSize:t&&"dotSize"in t?t.dotSize:this.dotSize,minWidth:t&&"minWidth"in t?t.minWidth:this.minWidth,maxWidth:t&&"maxWidth"in t?t.maxWidth:this.maxWidth,velocityFilterWeight:t&&"velocityFilterWeight"in t?t.velocityFilterWeight:this.velocityFilterWeight,compositeOperation:t&&"compositeOperation"in t?t.compositeOperation:this.compositeOperation}}_strokeBegin(t){this.dispatchEvent(new CustomEvent("beginStroke",{detail:t}));let i=this._getPointGroupOptions(),e=Object.assign(Object.assign({},i),{points:[]});this._data.push(e),this._reset(i),this._strokeUpdate(t)}_strokeUpdate(t){if(this._data.length===0){this._strokeBegin(t);return}this.dispatchEvent(new CustomEvent("beforeUpdateStroke",{detail:t}));let i=t.clientX,e=t.clientY,n=t.pressure!==void 0?t.pressure:t.force!==void 0?t.force:0,s=this._createPoint(i,e,n),h=this._data[this._data.length-1],d=h.points,r=d.length>0&&d[d.length-1],o=r?s.distanceTo(r)<=this.minDistance:!1,l=this._getPointGroupOptions(h);if(!r||!(r&&o)){let c=this._addPoint(s,l);r?c&&this._drawCurve(c,l):this._drawDot(s,l),d.push({time:s.time,x:s.x,y:s.y,pressure:s.pressure})}this.dispatchEvent(new CustomEvent("afterUpdateStroke",{detail:t}))}_strokeEnd(t){this._strokeUpdate(t),this.dispatchEvent(new CustomEvent("endStroke",{detail:t}))}_handlePointerEvents(){this._drawningStroke=!1,this.canvas.addEventListener("pointerdown",this._handlePointerStart),this.canvas.addEventListener("pointermove",this._handlePointerMove),this.canvas.ownerDocument.addEventListener("pointerup",this._handlePointerEnd)}_handleMouseEvents(){this._drawningStroke=!1,this.canvas.addEventListener("mousedown",this._handleMouseDown),this.canvas.addEventListener("mousemove",this._handleMouseMove),this.canvas.ownerDocument.addEventListener("mouseup",this._handleMouseUp)}_handleTouchEvents(){this.canvas.addEventListener("touchstart",this._handleTouchStart),this.canvas.addEventListener("touchmove",this._handleTouchMove),this.canvas.addEventListener("touchend",this._handleTouchEnd)}_reset(t){this._lastPoints=[],this._lastVelocity=0,this._lastWidth=(t.minWidth+t.maxWidth)/2,this._ctx.fillStyle=t.penColor,this._ctx.globalCompositeOperation=t.compositeOperation}_createPoint(t,i,e){let n=this.canvas.getBoundingClientRect();return new x(t-n.left,i-n.top,e,new Date().getTime())}_addPoint(t,i){let{_lastPoints:e}=this;if(e.push(t),e.length>2){e.length===3&&e.unshift(e[0]);let n=this._calculateCurveWidths(e[1],e[2],i),s=P.fromPoints(e,n);return e.shift(),s}return null}_calculateCurveWidths(t,i,e){let n=e.velocityFilterWeight*i.velocityFrom(t)+(1-e.velocityFilterWeight)*this._lastVelocity,s=this._strokeWidth(n,e),h={end:s,start:this._lastWidth};return this._lastVelocity=n,this._lastWidth=s,h}_strokeWidth(t,i){return Math.max(i.maxWidth/(t+1),i.minWidth)}_drawCurveSegment(t,i,e){let n=this._ctx;n.moveTo(t,i),n.arc(t,i,e,0,2*Math.PI,!1),this._isEmpty=!1}_drawCurve(t,i){let e=this._ctx,n=t.endWidth-t.startWidth,s=Math.ceil(t.length())*2;e.beginPath(),e.fillStyle=i.penColor;for(let h=0;h0?i.dotSize:(i.minWidth+i.maxWidth)/2;e.beginPath(),this._drawCurveSegment(t.x,t.y,n),e.closePath(),e.fillStyle=i.penColor,e.fill()}_fromData(t,i,e){for(let n of t){let{points:s}=n,h=this._getPointGroupOptions(n);if(s.length>1)for(let d=0;d{let c=document.createElement("path");if(!isNaN(o.control1.x)&&!isNaN(o.control1.y)&&!isNaN(o.control2.x)&&!isNaN(o.control2.y)){let v=`M ${o.startPoint.x.toFixed(3)},${o.startPoint.y.toFixed(3)} C ${o.control1.x.toFixed(3)},${o.control1.y.toFixed(3)} ${o.control2.x.toFixed(3)},${o.control2.y.toFixed(3)} ${o.endPoint.x.toFixed(3)},${o.endPoint.y.toFixed(3)}`;c.setAttribute("d",v),c.setAttribute("stroke-width",(o.endWidth*2.25).toFixed(3)),c.setAttribute("stroke",l),c.setAttribute("fill","none"),c.setAttribute("stroke-linecap","round"),r.appendChild(c)}},(o,{penColor:l,dotSize:c,minWidth:v,maxWidth:_})=>{let g=document.createElement("circle"),p=c>0?c:(v+_)/2;g.setAttribute("r",p.toString()),g.setAttribute("cx",o.x.toString()),g.setAttribute("cy",o.y.toString()),g.setAttribute("fill",l),r.appendChild(g)}),r.outerHTML}};var W=({backgroundColor:f,backgroundColorOnDark:t,confirmable:i,disabled:e,dotSize:n,exportBackgroundColor:s,exportPenColor:h,filename:d,maxWidth:r,minDistance:o,minWidth:l,penColor:c,penColorOnDark:v,state:_,throttle:g,velocityFilterWeight:p})=>({state:_,previousState:_,dirty:!1,confirmed:!1,signaturePad:null,init(){this.signaturePad=new y(this.$refs.canvas,{backgroundColor:f,dotSize:n,maxWidth:r,minDistance:o,minWidth:l,penColor:c,throttle:g,velocityFilterWeight:p}),e&&this.signaturePad.off(),this.watchState(),this.watchResize(),this.watchTheme(),_.initialValue&&(this.signaturePad.fromDataURL(_.initialValue),this.signaturePad.addEventListener("beginStroke",()=>{this.signaturePad.clear()},{once:!0}))},clear(){this.signaturePad.clear(),this.state=null,this.confirmed=!1,this.dirty=!1,this.signaturePad.on()},undo(){let a=this.signaturePad.toData();a.length&&(a.pop(),this.signaturePad.fromData(a)),a.length||(this.state=null),this.confirmed=!1,this.dirty=a.length>0,this.signaturePad.on()},done(){let{data:a,canvasBackgroundColor:u,canvasPenColor:m}=this.prepareToExport();this.signaturePad.fromData(a),this.previousState=this.state,this.state=this.signaturePad.toDataURL(),i&&(this.confirmed=!0,this.signaturePad.off());let{data:w}=this.restoreFromExport(a,u,m);this.signaturePad.fromData(w)},downloadAs(a,u){let{data:m,canvasBackgroundColor:w,canvasPenColor:k}=this.prepareToExport();this.signaturePad.fromData(m),this.download(this.signaturePad.toDataURL(a,{includeBackgroundColor:!0}),`${d}.${u}`);let{data:C}=this.restoreFromExport(m,w,k);this.signaturePad.fromData(C)},watchState(){this.signaturePad.addEventListener("endStroke",a=>{this.dirty=!0,!i&&this.done()},{once:!1}),this.$watch("confirmed",a=>{i&&!a&&(this.state=null)})},watchResize(){window.addEventListener("resize",()=>this.resizeCanvas),this.resizeCanvas()},resizeCanvas(){let a=Math.max(window.devicePixelRatio||1,1);this.$refs.canvas.width=this.$refs.canvas.offsetWidth*a,this.$refs.canvas.height=this.$refs.canvas.offsetHeight*a,this.$refs.canvas.getContext("2d").scale(a,a),this.signaturePad.clear()},watchTheme(){let a;this.$store.hasOwnProperty("theme")?(window.addEventListener("theme-changed",u=>this.onThemeChanged(u.detail)),a=this.$store.theme):(window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",u=>this.onThemeChanged(u.matches?"dark":"light")),a=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),this.onThemeChanged(a)},onThemeChanged(a){if(this.signaturePad.penColor=a==="dark"?v??c:c,this.signaturePad.backgroundColor=a==="dark"?t??f:f,!this.signaturePad.toData().length)return;let u=this.signaturePad.toData();u.map(m=>(m.penColor=a==="dark"?v??c:c,m.backgroundColor=a==="dark"?t??f:f,m)),this.signaturePad.clear(),this.signaturePad.fromData(u)},prepareToExport(){let a=this.signaturePad.toData(),u=this.signaturePad.backgroundColor,m=this.signaturePad.penColor;return this.signaturePad.backgroundColor=s??this.signaturePad.backgroundColor,a.map(w=>w.penColor=h??w.penColor),{data:a,canvasBackgroundColor:u,canvasPenColor:m}},restoreFromExport(a,u,m){return this.signaturePad.backgroundColor=u,a.map(w=>w.penColor=m),{data:a}},download(a,u){let m=document.createElement("a");m.download=u,m.href=a,document.body.appendChild(m),m.click(),document.body.removeChild(m)}});export{W as default}; /*! Bundled license information: signature_pad/dist/signature_pad.js: diff --git a/resources/js/index.js b/resources/js/index.js index 7a7ef90..b7a5b9b 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -3,6 +3,7 @@ import SignaturePad from "signature_pad"; export default ({ backgroundColor, backgroundColorOnDark, + confirmable, disabled, dotSize, exportBackgroundColor, @@ -19,6 +20,8 @@ export default ({ }) => ({ state, previousState: state, + dirty: false, + confirmed: false, /** @type {SignaturePad} */ signaturePad: null, @@ -54,14 +57,42 @@ export default ({ clear() { this.signaturePad.clear(); + this.state = null; + this.confirmed = false; + this.dirty = false; + this.signaturePad.on(); }, undo() { const data = this.signaturePad.toData(); - if (data) { + if (data.length) { data.pop(); this.signaturePad.fromData(data); } + + if (!data.length) { + this.state = null; + } + + this.confirmed = false; + this.dirty = data.length > 0; + this.signaturePad.on(); + }, + + done() { + const { data: exportedData, canvasBackgroundColor, canvasPenColor } = this.prepareToExport() + this.signaturePad.fromData(exportedData) + + this.previousState = this.state; + this.state = this.signaturePad.toDataURL(); + + if (confirmable) { + this.confirmed = true; + this.signaturePad.off(); + } + + const { data: restoredData } = this.restoreFromExport(exportedData, canvasBackgroundColor, canvasPenColor) + this.signaturePad.fromData(restoredData) }, downloadAs(type, extension) { @@ -78,16 +109,21 @@ export default ({ }, watchState() { - this.signaturePad.addEventListener("afterUpdateStroke", (e) => { - const { data: exportedData, canvasBackgroundColor, canvasPenColor } = this.prepareToExport() - this.signaturePad.fromData(exportedData) + this.signaturePad.addEventListener("endStroke", (e) => { + this.dirty = true; - this.previousState = this.state; - this.state = this.signaturePad.toDataURL(); + if (confirmable) { + return; + } - const { data: restoredData } = this.restoreFromExport(exportedData, canvasBackgroundColor, canvasPenColor) - this.signaturePad.fromData(restoredData) + this.done(); }, { once: false }); + + this.$watch("confirmed", (confirmed) => { + if (confirmable && !confirmed) { + this.state = null + } + }) }, watchResize() { diff --git a/resources/lang/en/filament-autograph.php b/resources/lang/en/filament-autograph.php index 32f8c49..dfb99cd 100644 --- a/resources/lang/en/filament-autograph.php +++ b/resources/lang/en/filament-autograph.php @@ -19,5 +19,9 @@ 'svg' => 'SVG', ] ], + + 'done' => [ + 'label' => 'Done', + ], ] ]; diff --git a/resources/lang/pt_BR/filament-autograph.php b/resources/lang/pt_BR/filament-autograph.php index 02b0062..68697d2 100644 --- a/resources/lang/pt_BR/filament-autograph.php +++ b/resources/lang/pt_BR/filament-autograph.php @@ -19,5 +19,9 @@ 'svg' => 'SVG', ] ], + + 'done' => [ + 'label' => 'Concluir', + ], ] ]; diff --git a/resources/views/signature-pad.blade.php b/resources/views/signature-pad.blade.php index 2076d9b..f84eafb 100644 --- a/resources/views/signature-pad.blade.php +++ b/resources/views/signature-pad.blade.php @@ -20,10 +20,12 @@ class="filament-autograph" $downloadableFormats = $getDownloadableFormats(); $downloadActionDropdownPlacement = $getDownloadActionDropdownPlacement() ?? 'bottom-start'; $isUndoable = $isUndoable(); + $isConfirmable = $isConfirmable(); $clearAction = $getAction('clear'); $downloadAction = $getAction('download'); $undoAction = $getAction('undo'); + $doneAction = $getAction('done'); @endphp
@endif + + @if ($isConfirmable) + {{ $doneAction }} + @endif
diff --git a/src/Forms/Components/Actions/DoneAction.php b/src/Forms/Components/Actions/DoneAction.php new file mode 100644 index 0000000..94fd3de --- /dev/null +++ b/src/Forms/Components/Actions/DoneAction.php @@ -0,0 +1,32 @@ +link()->icon('heroicon-o-check')->color('primary'); + + $this->label(fn (): string => __('filament-autograph::filament-autograph.actions.done.label')); + + $this->livewireClickHandlerEnabled(false); + + $this->size(ActionSize::Small); + + $this->visible( + fn (SignaturePad $component): bool => $component->isConfirmable() + ); + } +} diff --git a/src/Forms/Components/Concerns/HasActions.php b/src/Forms/Components/Concerns/HasActions.php index 2e6fca1..325c9ab 100644 --- a/src/Forms/Components/Concerns/HasActions.php +++ b/src/Forms/Components/Concerns/HasActions.php @@ -4,7 +4,9 @@ use Closure; use Filament\Forms\Components\Actions\Action; +use Illuminate\Support\HtmlString; use Saade\FilamentAutograph\Forms\Components\Actions\ClearAction; +use Saade\FilamentAutograph\Forms\Components\Actions\DoneAction; use Saade\FilamentAutograph\Forms\Components\Actions\DownloadAction; use Saade\FilamentAutograph\Forms\Components\Actions\UndoAction; use Saade\FilamentAutograph\Forms\Components\Enums\DownloadableFormat; @@ -25,12 +27,16 @@ trait HasActions protected bool | Closure $isUndoable = true; + protected bool | Closure $isConfirmable = false; + protected ?Closure $modifyClearActionUsing = null; protected ?Closure $modifyDownloadActionUsing = null; protected ?Closure $modifyUndoActionUsing = null; + protected ?Closure $modifyDoneActionUsing = null; + public function getClearAction(): Action { $action = ClearAction::make(); @@ -101,6 +107,33 @@ public function undoAction(?Closure $callback): static return $this; } + public function getDoneAction(): Action + { + $action = DoneAction::make(); + + if ($this->modifyDoneActionUsing) { + $action = $this->evaluate($this->modifyDoneActionUsing, [ + 'action' => $action, + ]) ?? $action; + } + + $action->extraAttributes([ + 'x-on:click' => 'done', + 'x-cloak' => '', + 'x-show' => new HtmlString('dirty && !confirmed'), + ...$action->getExtraAttributes(), + ]); + + return $action; + } + + public function doneAction(?Closure $callback): static + { + $this->modifyDoneActionUsing = $callback; + + return $this; + } + public function clearable(bool | Closure $condition = true): static { $this->isClearable = $condition; @@ -136,6 +169,13 @@ public function undoable(bool | Closure $condition = true): static return $this; } + public function confirmable(bool | Closure $condition = true): static + { + $this->isConfirmable = $condition; + + return $this; + } + public function isClearable(): bool { if ($this->isDisabled()) { @@ -172,4 +212,13 @@ public function isUndoable(): bool return (bool) $this->evaluate($this->isUndoable); } + + public function isConfirmable(): bool + { + if ($this->isDisabled()) { + return false; + } + + return (bool) $this->evaluate($this->isConfirmable); + } } diff --git a/src/Forms/Components/SignaturePad.php b/src/Forms/Components/SignaturePad.php index a496c3a..bcafb17 100644 --- a/src/Forms/Components/SignaturePad.php +++ b/src/Forms/Components/SignaturePad.php @@ -23,6 +23,7 @@ protected function setUp(): void fn (SignaturePad $component): Action => $component->getClearAction(), fn (SignaturePad $component): Action => $component->getDownloadAction(), fn (SignaturePad $component): Action => $component->getUndoAction(), + fn (SignaturePad $component): Action => $component->getDoneAction(), ]); } }