fps-flow
Version:
An easy and simple flow ui created by First Penny
1 lines • 11.1 kB
JavaScript
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.FpFlow=e():t.FpFlow=e()}(this,(()=>(()=>{"use strict";var t={d:(e,o)=>{for(var n in o)t.o(o,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:o[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});class o{constructor(t,e,o,n){var i;this.id=t,this.hostContainer=e,this.flowContainer=o,this.savedNode=n,this.data={},this.isDragging=!1,this.nodeColor="turquoise",this.text=(null===(i=this.savedNode)||void 0===i?void 0:i.text)||`Node ${this.id}`,this.node=this.createNode(),this.setPosition(),this.watchMovement()}addChild(t){this.nodeBody.appendChild(t)}handleItemsMovementCB(t){this.nodeEdgeMovementHandler=t}createNode(){const t=document.createElement("div");t.classList.add("fp-flow-node"),t.style.backgroundColor=this.nodeColor;const e=document.createElement("div");e.classList.add("header");const o=document.createElement("div");o.classList.add("name"),o.innerText=this.text,o.addEventListener("click",(t=>{t.stopPropagation(),o.contentEditable="true",o.focus();const e=document.createRange(),n=window.getSelection();e.selectNodeContents(o),e.collapse(!1),n.removeAllRanges(),n.addRange(e),o.addEventListener("blur",(()=>{o.contentEditable="false",this.text=o.innerText,this.updateConnections()}))}));const n=document.createElement("div");n.classList.add("controls");const i=document.createElement("div");i.classList.add("edit","control"),i.innerHTML='\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">\n <path d="M3 21h3.75L17.81 9.94l-3.75-3.75L3 17.25V21zm18.71-13.04a.996.996 0 0 0 0-1.41L18.46 2.29a.996.996 0 0 0-1.41 0L15 4.34l3.75 3.75 2.96-2.96z"/>\n </svg>\n ',n.appendChild(i),i.addEventListener("click",(()=>{o.contentEditable="true",o.focus();const t=document.createRange(),e=window.getSelection();t.selectNodeContents(o),t.collapse(!1),e.removeAllRanges(),e.addRange(t),o.addEventListener("blur",(()=>{o.contentEditable="false",this.text=o.innerText,this.updateConnections()}))}));const s=document.createElement("div");s.classList.add("connection","control"),s.innerHTML='\n <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n \x3c!-- Diagonal Connection Line --\x3e\n <line x1="4" y1="20" x2="20" y2="4"></line>\n \n \x3c!-- Start Circle --\x3e\n <circle cx="4" cy="20" r="2" fill="black"></circle>\n \n \x3c!-- End Circle --\x3e\n <circle cx="20" cy="4" r="2" fill="black"></circle>\n </svg>\n ',n.appendChild(s),s.addEventListener("click",(t=>{t.stopPropagation(),this.handleConnection(this.id)?s.classList.add("active"):s.classList.remove("active")}));const d=document.createElement("div");return d.classList.add("delete","control"),d.innerHTML='\n <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n <polyline points="3 6 5 6 21 6"></polyline>\n <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>\n <path d="M10 11v6"></path>\n <path d="M14 11v6"></path>\n <path d="M9 6V3h6v3"></path>\n </svg>\n ',n.appendChild(d),d.addEventListener("click",(t=>{t.stopPropagation(),this.removeNode(this.id),this.node.remove()})),e.appendChild(o),e.appendChild(n),t.appendChild(e),this.nodeBody=document.createElement("div"),this.nodeBody.classList.add("fp-flow-node-body"),t.appendChild(this.nodeBody),t}setPosition(){const{scrollLeft:t,scrollTop:e,offsetWidth:o,offsetHeight:n}=this.hostContainer;this.node.style.display="none",setTimeout((()=>{var i,s;let d,h;d=(null===(i=this.savedNode)||void 0===i?void 0:i.position.x)?this.savedNode.position.x*this.hostContainer.offsetWidth:t+o/2-this.node.offsetWidth/2,h=(null===(s=this.savedNode)||void 0===s?void 0:s.position.y)?this.savedNode.position.y*this.hostContainer.offsetHeight:e+n/2-this.node.offsetHeight/2,this.node.style.left=`${d}px`,this.node.style.top=`${h}px`,this.node.style.display="block"}))}watchMovement(){let t,e,o,n;this.node.addEventListener("mousedown",(o=>{this.isDragging=!0,t=o.clientX-this.node.offsetLeft,e=o.clientY-this.node.offsetTop,setTimeout((()=>{this.isDragging&&this.node.classList.add("dragging")}),300)})),window.addEventListener("mouseup",(()=>{this.isDragging=!1,this.node.classList.remove("dragging"),clearInterval(null)})),window.addEventListener("mousemove",(i=>{if(this.isDragging){clearInterval(null);const{clientX:s,clientY:d}=i;let h=s-t,a=d-e;h<0&&(h=0),a<0&&(a=0),h+this.node.offsetWidth>this.flowContainer.offsetWidth&&(h=this.flowContainer.offsetWidth-this.node.offsetWidth),a+this.node.offsetHeight>this.flowContainer.offsetHeight&&(a=this.flowContainer.offsetHeight-this.node.offsetHeight),this.node.style.left=`${h}px`,this.node.style.top=`${a}px`,this.updateConnections(),s>o?requestAnimationFrame((()=>this.handleMovement("right"))):s<o&&requestAnimationFrame((()=>this.handleMovement("left"))),o=s,d>n?requestAnimationFrame((()=>this.handleMovement("down"))):d<n&&requestAnimationFrame((()=>this.handleMovement("top"))),n=d,this.updateConnections()}}))}handleMovement(t){({top:()=>{const t=this.hostContainer.scrollTop-this.node.offsetTop;t>0&&(this.hostContainer.scrollTop-=t)},right:()=>{const t=this.node.offsetLeft+this.node.offsetWidth+8,e=this.hostContainer.scrollLeft+this.hostContainer.offsetWidth,o=t-e;if(o>0){if(e>=this.hostContainer.scrollWidth){const t=this.flowContainer.offsetWidth+o;this.flowContainer.style.width=`${t}px`,this.canvas.width=t}this.hostContainer.scrollLeft+=o}},down:()=>{const t=this.node.offsetTop+this.node.offsetHeight+6-this.hostContainer.offsetHeight-this.hostContainer.scrollTop,e=this.hostContainer.scrollTop+this.hostContainer.offsetHeight;if(t>0){if(e>=this.hostContainer.scrollWidth){const e=this.flowContainer.offsetHeight+t;this.flowContainer.style.height=`${e}px`,this.canvas.height=e}this.hostContainer.scrollTop+=t}},left:()=>{const t=this.hostContainer.scrollLeft-this.node.offsetLeft;t>0&&(this.hostContainer.scrollLeft-=t)}})[t]()}}function n(t,e,o,n){n.beginPath(),n.moveTo(t,e),n.lineTo(t-10*Math.cos(o-Math.PI/6),e-10*Math.sin(o-Math.PI/6)),n.moveTo(t,e),n.lineTo(t-10*Math.cos(o+Math.PI/6),e-10*Math.sin(o+Math.PI/6)),n.stroke()}class i{constructor(t,e){this.hostContainer=t,this.savedFlow=e,this.nodes=[],this.flowContainerScale=1.5,this.currentScale=1,this.connections=[],console.log("FpFlow initialized"),t.classList.add("fp-flow-host-container"),this.flowContainer=this.createFlowContainer(),this.watchZoom(),this.stopConnection(),console.log(e),e&&this.renderSavedFLow()}addNode(t){const e=new o((null==t?void 0:t.id)||this.newNodeId,this.hostContainer,this.flowContainer,t);return e.canvas=this.canvas,e.handleItemsMovementCB(this.handleNodeEdgeMovement.bind(this)),e.getScale=()=>this.currentScale,e.handleConnection=this.handleConnection.bind(this),e.updateConnections=this.drawConnections.bind(this),e.removeNode=this.removeNode.bind(this),this.nodes.push(e),this.flowContainer.appendChild(e.node),e}addChild(t,e){const o=this.nodes.find((e=>e.id===t));o?o.addChild(e):console.error(`No node found with id ${t}`)}getFlowInfo(){return{nodes:this.nodes.map((t=>({id:t.id,data:t.data,text:t.text,position:{x:t.node.offsetLeft/this.hostContainer.offsetWidth,y:t.node.offsetTop/this.hostContainer.offsetHeight}}))),connections:this.connections}}renderSavedFLow(){this.savedFlow?(this.savedFlow.nodes.forEach((t=>{const e=this.addNode(t);t.data&&(e.data=t.data)})),this.connections=this.savedFlow.connections,setTimeout((()=>{this.drawConnections()}))):console.error("No saved flow provided")}stopConnection(){this.flowContainer.addEventListener("click",(()=>{this.removeConnectionHighlight()}))}handleConnection(t){if(void 0===this.connectionFrom)return this.connectionFrom=t,!0;if(this.connectionFrom!==t){if(!this.connections.find((e=>e.from===this.connectionFrom&&e.to===t))){const e=this.nodes.find((t=>t.id===this.connectionFrom)),o=this.nodes.find((e=>e.id===t));this.connections.push({from:this.connectionFrom,to:t}),this.drawConnection(e,o)}this.removeConnectionHighlight()}else this.connectionFrom=void 0}drawConnections(){requestAnimationFrame((()=>{this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.connections.forEach((({from:t,to:e})=>{const o=this.nodes.find((e=>e.id===t)),n=this.nodes.find((t=>t.id===e));this.drawConnection(o,n)}))}))}drawConnection(t,e){const o=t.node.offsetLeft+t.node.offsetWidth/2,i=t.node.offsetTop+t.node.offsetHeight/2,s=e.node.offsetLeft+e.node.offsetWidth/2,d=e.node.offsetTop+e.node.offsetHeight/2,h=Math.abs(i-d),a=Math.sign(i-d);if(this.ctx.beginPath(),this.ctx.moveTo(o,i),h>=150){let t,e,h,c;-1===a?(t=o,e=i+50,h=s,c=d-50):(t=o,e=i-50,h=s,c=d+50),this.ctx.bezierCurveTo(t,e,h,c,s,d),this.ctx.stroke();const r=function(t,e,o,n,i,s,d,h,a){return{x:Math.pow(.5,3)*e+3*Math.pow(.5,2)*t*n+1.5*Math.pow(t,2)*s+Math.pow(t,3)*h,y:Math.pow(.5,3)*o+3*Math.pow(.5,2)*t*i+1.5*Math.pow(t,2)*d+Math.pow(t,3)*a}}(.5,o,i,t,e,h,c,s,d),l=function(t,e,o,n,i,s,d,h,a){return{x:3*Math.pow(.5,2)*(n-e)+1.5*(s-n)+3*Math.pow(t,2)*(h-s),y:3*Math.pow(.5,2)*(i-o)+1.5*(d-i)+3*Math.pow(t,2)*(a-d)}}(.5,o,i,t,e,h,c,s,d),f=Math.atan2(l.y,l.x);n(r.x,r.y,f,this.ctx)}else this.ctx.lineTo(s,d),this.ctx.stroke(),n((o+s)/2,(i+d)/2,Math.atan2(d-i,s-o),this.ctx)}removeConnectionHighlight(){this.connectionFrom=void 0;for(const t of this.nodes){const e=t.node.querySelector(".header").querySelector(".controls").querySelector(".active");if(e){e.classList.remove("active");break}}}createFlowContainer(){const t=document.createElement("div"),e=this.hostContainer.offsetWidth*this.flowContainerScale,o=this.hostContainer.offsetHeight*this.flowContainerScale;return t.classList.add("fp-flow-container"),t.style.width=e+"px",t.style.height=o+"px",this.hostContainer.appendChild(t),this.canvas=document.createElement("canvas"),this.canvas.classList.add("fp-flow-container-canvas"),this.canvas.width=e,this.canvas.height=o,t.appendChild(this.canvas),this.ctx=this.canvas.getContext("2d"),this.ctx.strokeStyle="#666666",this.ctx.lineWidth=1.5,this.ctx.lineJoin="round",this.ctx.lineCap="round",t}handleNodeEdgeMovement(t,e,o){this.nodes.forEach((({node:o,id:n})=>{"left"===t?o.style.left=`${o.offsetLeft+e}px`:o.style.top=`${o.offsetTop+e}px`}))}watchZoom(){document.addEventListener("wheel",(t=>{t.ctrlKey&&t.preventDefault()}),{passive:!1})}get newNodeId(){return this.nodes.length?Math.max(...this.nodes.map((t=>t.id)))+1:0}removeNode(t){this.connections=this.connections.filter((e=>e.from!==t&&e.to!==t)),this.nodes=this.nodes.filter((e=>e.id!==t)),this.drawConnections()}}return window.FpFlow=i,e.default})()));