diff --git a/package.json b/package.json index 615ef24..e8ab6f4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.2", "description": "TypeScript editor + .flowgo helpers for the flowgo mind-map tool. Built as a single self-contained index.html by the public flowgo binary; also consumable as ESM modules by downstream projects.", "type": "module", - "packageManager": "pnpm@10.33.2", + "packageManager": "pnpm@10.34.0+sha512.8736169c56fdb6e26afb5d7e2bd5f4c99cc299620d8bdc288cb354e6dd0936bd4ea62276d1a6b2de4b6e971a00a530e0e08bbeeda63da5d820118d3d7e21ff69", "license": "AGPL-3.0", "repository": { "type": "git", @@ -18,7 +18,8 @@ "exports": { ".": "./src/index.ts", "./editor": "./src/editor/main.ts", - "./editor/*": "./src/editor/*" + "./editor/*": "./src/editor/*", + "./collab": "./src/editor/collab-api.ts" }, "scripts": { "dev": "vite", diff --git a/pkg/flowgo/dist/index.html b/pkg/flowgo/dist/index.html index 0cede78..bef9f7e 100644 --- a/pkg/flowgo/dist/index.html +++ b/pkg/flowgo/dist/index.html @@ -588,8 +588,8 @@ `}let o=a||(e.texts?.length??0)>0;o&&(e.lines?.length??0)&&(r+=` `);for(let t of e.lines??[]){let e=`line ${t.id} ${v(t.x1)} ${v(t.y1)} ${v(t.x2)} ${v(t.y2)}`,n=t.mids??[];if(y(t.palette)||n.length>0){let n=y(t.palette)?t.palette:1;e+=` `+n}for(let[t,r]of n)e+=` ${v(t)} ${v(r)}`;r+=e+` `}for(let t of e.lines??[])typeof t.style==`number`&&t.style>=2&&t.style<=9&&(r+=`linestyle ${t.id} ${t.style}\n`);(o||(e.lines?.length??0)>0)&&(e.strokes?.length??0)&&(r+=` -`);for(let t of e.strokes??[]){if((t.points?.length??0)<2)continue;let e=t.points.map(e=>`${v(e[0])},${v(e[1])}`).join(` `),n=y(t.palette)?` ${t.palette}`:``;r+=`stroke ${t.id}${n} ${e}\n`}}),r},re=()=>{let e=document.getElementById(`helpOverlay`);if(!e)throw Error(`helpOverlay missing from DOM`);return e},ie=e=>{re().classList.toggle(`hidden`,!e)},ae=()=>!re().classList.contains(`hidden`),oe=()=>{let e=document.getElementById(`helpBtn`),t=document.getElementById(`helpClose`);e?.addEventListener(`click`,()=>ie(!0)),t?.addEventListener(`click`,()=>ie(!1)),re().addEventListener(`mousedown`,e=>{e.target===re()&&ie(!1)})},b={x:0,y:0},x=e=>e-b.x,S=e=>e-b.y,se=e=>{let t=document.getElementById(e);if(!t)throw Error(`viewport: missing #${e}`);return t},ce=()=>{let e=b.x,t=b.y;se(`canvas`).style.transform=`translate(${e}px, ${t}px)`;for(let n of[`line-layer`,`stroke-layer`,`edge-layer`])se(n).setAttribute(`transform`,`translate(${e} ${t})`);se(`ghost-line`).setAttribute(`transform`,`translate(${e} ${t})`),se(`bg-layer`).style.backgroundPosition=`${e}px ${t}px`},le=e=>{let t=e.boxes??[],n=t.find(e=>e.anchor)??t.find(e=>e.id===`b1`);if(n&&n.id){let e=document.querySelector(`.box[data-id="${n.id}"]`),t=n.x+(e?e.offsetWidth/2:0),r=n.y+(e?e.offsetHeight/2:0);b.x=window.innerWidth/2-t,b.y=window.innerHeight/2-r,ce();return}let r=[];for(let t of e.boxes??[])r.push([t.x,t.y]);for(let t of e.texts??[])r.push([t.x,t.y]);for(let t of e.lines??[]){r.push([t.x1,t.y1]),r.push([t.x2,t.y2]);for(let[e,n]of t.mids??[])r.push([e,n])}if(r.length===0)b.x=0,b.y=0;else{let e=1/0,t=1/0,n=-1/0,i=-1/0;for(let[a,o]of r)an&&(n=a),o>i&&(i=o);let a=(e+n)/2,o=(t+i)/2;b.x=window.innerWidth/2-a,b.y=window.innerHeight/2-o}ce()},ue=null,de=e=>{ue=e},C=()=>{if(!ue)throw Error(`mutations: wireMutations() not called`);ue.scheduleSave()},fe=()=>C(),pe=()=>C(),me=()=>C(),he=()=>C(),ge=()=>C(),w=()=>C(),_e=()=>C(),ve=null,ye=e=>{ve=e},be=()=>{if(!ve)throw Error(`brush: wireBrush() not called`);return ve},xe=!1,T=null,Se=1,E=()=>xe,Ce=()=>T!==null,we={1:`#333`,2:`#fff`,3:`#b91c1c`,4:`#c2410c`,5:`#a16207`,6:`#15803d`,7:`#1d4ed8`,8:`#6d28d9`,9:`#374151`},Te=e=>`url("data:image/svg+xml;utf8,${``.replace(/#/g,`%23`)}") 2 22, crosshair`,D=null,Ee=()=>{if(!xe||Se===1){D&&(D.textContent=``);return}D||(D=document.createElement(`style`),D.id=`brush-cursor-dynamic`,document.head.appendChild(D));let e=Te(Se);D.textContent=`body.brush-mode,body.brush-mode #bg-layer,body.brush-mode #canvas,body.brush-mode .box,body.brush-mode .text-item { cursor: ${e}; }`},De=e=>{xe!==e&&(xe=e,document.body.classList.toggle(`brush-mode`,xe),Ee(),be().setStatus(xe?`brush mode — drag to paint, V to exit`:`select mode`))},Oe=e=>{e<1||e>9||(Se=e,xe&&Ee())},ke=e=>Math.round(e*100)/100,Ae=e=>e.map(e=>`${e[0]},${e[1]}`).join(` `),je=(e,t)=>{let n=ke(x(e)),r=ke(S(t)),i=be().mintId(),a=`http://www.w3.org/2000/svg`,o=document.createElementNS(a,`g`),s=`stroke-group`+(Se>=2?` palette-${Se}`:``);o.setAttribute(`class`,s),o.dataset.id=i;let c=document.createElementNS(a,`polyline`);c.setAttribute(`class`,`stroke-line`),c.setAttribute(`points`,`${n},${r}`),o.appendChild(c),be().strokeLayer().appendChild(o),T={id:i,palette:Se,points:[[n,r]],polyEl:c}},Me=(e,t)=>{if(!T)return;let n=ke(x(e)),r=ke(S(t)),i=T.points[T.points.length-1];Math.hypot(n-i[0],r-i[1])<2||(T.points.push([n,r]),T.polyEl.setAttribute(`points`,Ae(T.points)))},Ne=()=>{if(!T)return;let e=i(T.points,1.5);if(e.length>=2){let t=be().currentMap(),n={id:T.id,points:e};T.palette>=2&&(n.palette=T.palette),(t.strokes??=[]).push(n),ge()}else{let e=T.polyEl.parentNode;e&&e.parentNode&&e.parentNode.removeChild(e)}T=null,be().afterCommit()},Pe=null,Fe=()=>{if(!Pe)throw Error(`edit: wireEdit() not called`);return Pe},Ie=e=>{Pe=e},Le=null,Re=()=>Le!==null,ze=e=>r(e.innerText??e.textContent??``,{maxLength:500}).label,Be=(e,t)=>{if(Le)return;Le=e,e.contentEditable=`true`,e.textContent=t.label,e.focus();let n=document.createRange();n.selectNodeContents(e);let r=window.getSelection();r?.removeAllRanges(),r?.addRange(n);let i=n=>{e.removeEventListener(`blur`,a),e.removeEventListener(`keydown`,o),e.contentEditable=`false`,Le=null;let r=ze(e);n&&r&&r!==t.label&&(t.label=r,me()),e.textContent=t.label},a=()=>i(!0),o=t=>{t.key===`Enter`&&!t.shiftKey?(t.preventDefault(),e.blur()):t.key===`Escape`&&(t.preventDefault(),i(!1)),t.stopPropagation()};e.addEventListener(`blur`,a),e.addEventListener(`keydown`,o)},O=(e,t,n)=>{if(Le)return;let i=n?.cancelDeletes??!1,a=e.querySelector(`.box-label`);if(!a){Fe().renderAll();let e=Fe().canvas.querySelector(`.box[data-id="${t.id}"]`);e&&O(e,t,n);return}Le=e,e.contentEditable=`true`,a.textContent=t.label,e.focus();let o=document.createRange();o.selectNodeContents(a);let s=window.getSelection();s?.removeAllRanges(),s?.addRange(o);let c=n=>{e.removeEventListener(`blur`,l),e.removeEventListener(`keydown`,u),e.contentEditable=`false`,Le=null;let a=r(e.innerText??e.textContent??``,{maxLength:500});a.truncated&&Fe().setStatus(`label truncated to 500 characters`);let o=a.label;if(!n&&i){let e=Fe(),n=e.getCurrentMap();n.boxes=n.boxes.filter(e=>e.id!==t.id),n.edges=n.edges.filter(e=>e.from!==t.id&&e.to!==t.id);let r=e.getCurrentPath(),i=r===`/`?`/`+t.id:r+`/`+t.id,a=e.getGraph();a.maps=a.maps.filter(e=>e.path!==i&&!e.path.startsWith(i+`/`)),e.setGraph(a),e.setCurrentMap(e.ensureMap(r)),e.selected.delete(t.id),_e(),e.renderAll(),e.setStatus(`cancelled`);return}n&&o&&o!==t.label&&(t.label=o,fe()),Fe().renderAll()},l=()=>c(!0),u=t=>{t.key===`Enter`&&!t.shiftKey?(t.preventDefault(),e.blur()):t.key===`Escape`&&(t.preventDefault(),c(!1)),t.stopPropagation()};e.addEventListener(`blur`,l),e.addEventListener(`keydown`,u)},Ve=(e,t)=>({x:t.x,y:t.y,width:e.offsetWidth,height:e.offsetHeight}),He=(e,t,n)=>d(Ve(e,t),n),Ue=(e,t,n,r)=>f(Ve(t,e),[n,r]),We=(e,t,n,r,i,a)=>{let o=document.elementsFromPoint(i,a);for(let t of o){let n=t;if(n.classList?.contains(`handle`)&&n.parentElement===e){let e=n.dataset.handle;if(e)return e}}return Ue(t,e,n,r)},Ge=(e,t,n,r,i)=>p(Ve(t,e),n,[r,i]),Ke=null,k=null,qe=()=>{if(!Ke)throw Error(`align: wireAlign() not called`);return Ke},Je=e=>{Ke=e},Ye=()=>{let e=qe(),t=e.currentMap(),n=[];for(let r of e.selected){let i=t.boxes.find(e=>e.id===r);if(i){let t=e.canvas.querySelector(`.box[data-id="${r}"]`);t&&n.push({ref:i,width:t.offsetWidth,height:t.offsetHeight});continue}let a=(t.texts??[]).find(e=>e.id===r);if(a){let t=e.canvas.querySelector(`.text-item[data-id="${r}"]`);t&&n.push({ref:a,width:t.offsetWidth,height:t.offsetHeight})}}return n},Xe=e=>{let t=[...e].sort((e,t)=>e.ref.x-t.ref.x);for(let e=1;e{let t=[...e].sort((e,t)=>e.ref.y-t.ref.y);for(let e=1;e{if(e.length<2)return!1;if(t===`horizontal`){let t=e.reduce((e,t)=>e+t.ref.y+t.height/2,0)/e.length;for(let n of e)n.ref.y=Math.round(t-n.height/2);if(Xe(e)){let t=[...e].sort((e,t)=>e.ref.x-t.ref.x||e.ref.y-t.ref.y),n=t[0].ref.x;for(let e of t)e.ref.x=Math.round(n),n=e.ref.x+e.width+20}}else{let t=e.reduce((e,t)=>e+t.ref.x+t.width/2,0)/e.length;for(let n of e)n.ref.x=Math.round(t-n.width/2);if(Ze(e)){let t=[...e].sort((e,t)=>e.ref.y-t.ref.y||e.ref.x-t.ref.x),n=t[0].ref.y;for(let e of t)e.ref.y=Math.round(n),n=e.ref.y+e.height+20}}return!0},$e=e=>{let t=qe();Qe(Ye(),e)&&(t.renderAll(),w())},et=`http://www.w3.org/2000/svg`,tt=(e,t)=>{let n=document.createElementNS(et,`svg`);n.setAttribute(`viewBox`,`0 0 16 16`),n.setAttribute(`width`,`16`),n.setAttribute(`height`,`16`),n.setAttribute(`aria-hidden`,`true`);let r=document.createElementNS(et,`line`);r.setAttribute(`x1`,String(e.x1)),r.setAttribute(`y1`,String(e.y1)),r.setAttribute(`x2`,String(e.x2)),r.setAttribute(`y2`,String(e.y2)),r.setAttribute(`stroke`,`currentColor`),r.setAttribute(`stroke-width`,`1`),r.setAttribute(`stroke-dasharray`,`1.5 1.5`),r.setAttribute(`opacity`,`0.55`),n.appendChild(r);for(let e of t){let t=document.createElementNS(et,`rect`);t.setAttribute(`x`,String(e.x)),t.setAttribute(`y`,String(e.y)),t.setAttribute(`width`,String(e.w)),t.setAttribute(`height`,String(e.h)),t.setAttribute(`fill`,`currentColor`),n.appendChild(t)}return n},nt=()=>tt({x1:0,y1:8,x2:16,y2:8},[{x:2,y:3,w:5,h:10},{x:9,y:5,w:5,h:6}]),rt=()=>tt({x1:8,y1:0,x2:8,y2:16},[{x:3,y:2,w:10,h:5},{x:5,y:9,w:6,h:5}]),it=()=>{let e=qe();k=document.createElement(`div`),k.id=`alignToolbar`,k.style.display=`none`;let t=(e,t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.appendChild(e),r.title=t,r.setAttribute(`aria-label`,t),r.addEventListener(`mousedown`,e=>e.stopPropagation()),r.addEventListener(`touchstart`,e=>e.stopPropagation(),{passive:!0}),r.addEventListener(`click`,e=>{e.stopPropagation(),$e(n)}),r};k.appendChild(t(nt(),`Align on a horizontal line`,`horizontal`)),k.appendChild(t(rt(),`Align on a vertical line`,`vertical`)),e.canvas.appendChild(k)},at=()=>{if(!k||!Ke)return;let e=Ye();if(e.length<2){k.style.display=`none`;return}k.parentNode!==Ke.canvas&&Ke.canvas.appendChild(k);let t=1/0,n=1/0,r=-1/0;for(let i of e)t=Math.min(t,i.ref.x),n=Math.min(n,i.ref.y),r=Math.max(r,i.ref.x+i.width);k.style.display=`flex`,k.style.left=t+(r-t)/2+`px`,k.style.top=n+`px`},ot=e=>{let t=e.mids??[],n=[[e.x1,e.y1],...t,[e.x2,e.y2]],r=e.style??1;if(r===2&&t.length>0){let n=`M ${e.x1} ${e.y1}`;for(let e=0;e=Math.abs(o-i)?e+=` L ${a} ${i} L ${a} ${o}`:e+=` L ${r} ${o} L ${a} ${o}`}return e}let i=`M ${n[0][0]} ${n[0][1]}`;for(let e=1;e{if(!st)throw Error(`render: wireRender() not called`);return st},lt=e=>{st=e},A=`http://www.w3.org/2000/svg`,j=()=>{let n=ct();n.canvas.innerHTML=``;let r=n.currentMap(),i=n.graph(),a=n.currentPath();for(let o of r.boxes){let r=document.createElement(`div`),s=e(o.palette),c=t(o.font);r.className=`box`+(te(i,a,o.id)?` has-submap`:``)+(s===1?``:` palette-`+s)+(c===1?``:` font-`+c),r.dataset.id=o.id,r.style.left=o.x+`px`,r.style.top=o.y+`px`;let u=document.createElement(`span`);u.className=`box-label`,u.textContent=o.label,r.appendChild(u);for(let e of l){let t=document.createElement(`div`);t.className=`handle h-`+e,t.dataset.handle=e,r.appendChild(t)}n.canvas.appendChild(r),n.attachBoxHandlers(r,o)}for(let i of r.texts){let r=document.createElement(`div`),a=e(i.palette),o=t(i.font);r.className=`text-item`+(a===1?``:` palette-`+a)+(o===1?``:` font-`+o),r.dataset.id=i.id,r.style.left=i.x+`px`,r.style.top=i.y+`px`,r.textContent=i.label,n.canvas.appendChild(r),n.attachTextHandlers(r,i)}M(),dt(),ut(),N()},ut=()=>{let t=ct();t.strokeLayer.innerHTML=``;let n=t.currentMap();for(let r of n.strokes??[]){if(!r.points||r.points.length<2)continue;let n=o(r.points),i=document.createElementNS(A,`g`),a=e(r.palette);i.setAttribute(`class`,`stroke-group`+(a===1?``:` palette-`+a)+(t.selected.has(r.id)?` selected`:``)),i.dataset.id=r.id;let s=document.createElementNS(A,`path`);s.setAttribute(`class`,`stroke-hit`),s.setAttribute(`d`,n),s.setAttribute(`fill`,`none`),s.setAttribute(`stroke`,`transparent`),s.setAttribute(`stroke-width`,`12`),i.appendChild(s);let c=document.createElementNS(A,`path`);c.setAttribute(`class`,`stroke-line`),c.setAttribute(`d`,n),c.setAttribute(`fill`,`none`),i.appendChild(c),i.addEventListener(`mousedown`,e=>{t.isBrushMode()||(e.stopPropagation(),e.shiftKey||t.selected.clear(),t.selected.add(r.id),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M(),ut())}),t.strokeLayer.appendChild(i)}},dt=()=>{let t=ct();t.lineLayer.innerHTML=``;let n=t.currentMap();for(let r of n.lines){let n=document.createElementNS(A,`g`),i=e(r.palette);n.setAttribute(`class`,`line-group`+(i===1?``:` palette-`+i)+(t.selected.has(r.id)?` selected`:``)),n.dataset.id=r.id;let a=ot(r),o=document.createElementNS(A,`path`);o.setAttribute(`class`,`line-hit`),o.setAttribute(`d`,a),o.setAttribute(`fill`,`none`),o.setAttribute(`stroke`,`transparent`),o.setAttribute(`stroke-width`,`12`),n.appendChild(o);let s=document.createElementNS(A,`path`);s.setAttribute(`class`,`line-line`),s.setAttribute(`d`,a),s.setAttribute(`fill`,`none`),n.appendChild(s);let c=document.createElementNS(A,`circle`);c.setAttribute(`class`,`line-handle`),c.setAttribute(`cx`,String(r.x1)),c.setAttribute(`cy`,String(r.y1)),c.setAttribute(`r`,`6`),c.dataset.endpoint=`1`,n.appendChild(c);let l=document.createElementNS(A,`circle`);l.setAttribute(`class`,`line-handle`),l.setAttribute(`cx`,String(r.x2)),l.setAttribute(`cy`,String(r.y2)),l.setAttribute(`r`,`6`),l.dataset.endpoint=`2`,n.appendChild(l);let u=[];for(let e=0;e<(r.mids?.length??0);e++){let[t,i]=r.mids[e],a=document.createElementNS(A,`circle`);a.setAttribute(`class`,`line-handle line-handle-mid`),a.setAttribute(`cx`,String(t)),a.setAttribute(`cy`,String(i)),a.setAttribute(`r`,`6`),a.dataset.endpoint=`m`,a.dataset.midIndex=String(e),n.appendChild(a),u.push(a)}t.attachLineHandlers(n,s,o,c,l,u,r),t.lineLayer.appendChild(n)}},M=()=>{let e=ct(),t=e.dropTargetId(),n=e.dropTargetHandle(),r=e.nearTargetId();for(let i of e.canvas.querySelectorAll(`.box`)){let a=i.dataset.id===t;i.classList.toggle(`selected`,e.selected.has(i.dataset.id??``)),i.classList.toggle(`drop-target`,a),i.classList.toggle(`proximity-target`,i.dataset.id===r);for(let e of i.querySelectorAll(`.handle`))e.classList.toggle(`target`,a&&n!==null&&e.dataset.handle===n)}for(let t of e.canvas.querySelectorAll(`.text-item`))t.classList.toggle(`selected`,e.selected.has(t.dataset.id??``));for(let t of e.lineLayer.querySelectorAll(`.line-group`))t.classList.toggle(`selected`,e.selected.has(t.dataset.id??``));for(let t of e.strokeLayer.querySelectorAll(`.stroke-group`))t.classList.toggle(`selected`,e.selected.has(t.dataset.id??``));at()},N=()=>{let t=ct();t.edgeLayer.innerHTML=``;let n=t.currentMap(),r=t.selectedEdge();for(let i of n.edges){let a=n.boxes.find(e=>e.id===i.from),o=n.boxes.find(e=>e.id===i.to);if(!a||!o)continue;let s=t.canvas.querySelector(`.box[data-id="${a.id}"]`),c=t.canvas.querySelector(`.box[data-id="${o.id}"]`);if(!s||!c)continue;let l=a.x+s.offsetWidth/2,u=a.y+s.offsetHeight/2,d=o.x+c.offsetWidth/2,f=o.y+c.offsetHeight/2,[p,m]=Ge(a,s,i.fromHandle,d,f),[h,g]=Ge(o,c,i.toHandle,l,u),ee=document.createElementNS(A,`g`),te=e(i.palette);ee.setAttribute(`class`,`edge-group`+(te===1?``:` palette-`+te)+(i===r?` selected`:``));let _=document.createElementNS(A,`line`);_.setAttribute(`class`,`edge-hit`),_.setAttribute(`x1`,String(p)),_.setAttribute(`y1`,String(m)),_.setAttribute(`x2`,String(h)),_.setAttribute(`y2`,String(g)),_.setAttribute(`stroke`,`transparent`),_.setAttribute(`stroke-width`,`12`),ee.appendChild(_);let v=document.createElementNS(A,`line`);v.setAttribute(`class`,`edge-line`),v.setAttribute(`x1`,String(p)),v.setAttribute(`y1`,String(m)),v.setAttribute(`x2`,String(h)),v.setAttribute(`y2`,String(g)),ee.appendChild(v),ee.addEventListener(`mousedown`,e=>{e.stopPropagation(),t.setSelectedEdge(i),t.selected.clear(),M(),N(),t.setStatus(`edge selected — press Delete to remove`)}),t.edgeLayer.appendChild(ee)}},ft=60,pt=null,mt=()=>{if(!pt)throw Error(`render: wireProximity() not called`);return pt},ht=e=>{pt=e},gt=(e,t)=>{let n=mt(),r=null,i=1/0,a=n.link();for(let o of n.currentMap().boxes){if(a&&o.id===a.fromId)continue;let s=n.canvas.querySelector(`.box[data-id="${o.id}"]`);if(!s)continue;let c=o.x,l=o.y,u=o.x+s.offsetWidth,d=o.y+s.offsetHeight,f=Math.max(c-e,0,e-u),p=Math.max(l-t,0,t-d),m=Math.hypot(f,p);m{let e=mt();e.nearTargetId()!==null&&(e.setNearTargetId(null),M())},vt=null,yt=()=>{if(!vt)throw Error(`factories: wireFactories() not called`);return vt},bt=e=>{vt=e},xt=(e,t,n)=>{let r=yt(),i=r.mintId(),a={id:i,label:`new`,x:e,y:t};r.currentMap().boxes.push(a),j();let o=r.canvas.querySelector(`.box[data-id="${i}"]`);o&&n&&(a.x=n.x-o.offsetWidth/2,a.y=n.y-o.offsetHeight/2,o.style.left=a.x+`px`,o.style.top=a.y+`px`),fe(),o&&(r.selected.clear(),r.selected.add(i),r.selectedEdge()&&(r.clearSelectedEdge(),N()),M(),O(o,a))},St=(e,t)=>{let n=yt(),r=n.mintId(`t`),i={id:r,label:`text`,x:e,y:t};n.currentMap().texts.push(i),j();let a=n.canvas.querySelector(`.text-item[data-id="${r}"]`);a&&(i.x=e-a.offsetWidth/2,i.y=t-a.offsetHeight/2,a.style.left=i.x+`px`,a.style.top=i.y+`px`,n.selected.clear(),n.selected.add(r),n.selectedEdge()&&(n.clearSelectedEdge(),N()),M(),Be(a,i)),me()},Ct=(e,t,n,r)=>{let i=yt(),a=i.mintId(`l`),o={id:a,x1:e,y1:t,x2:n,y2:r};i.currentMap().lines.push(o),i.selected.clear(),i.selected.add(a),i.selectedEdge()&&(i.clearSelectedEdge(),N()),j(),he()},wt=()=>{let e=yt();if(e.selected.size===0){e.setStatus(`nothing selected`);return}let t=e.selected,n=e.currentMap(),r=Array.from(t).filter(e=>n.boxes.some(t=>t.id===e));n.boxes=n.boxes.filter(e=>!t.has(e.id)),n.edges=n.edges.filter(e=>!t.has(e.from)&&!t.has(e.to)),n.texts=n.texts.filter(e=>!t.has(e.id)),n.lines=n.lines.filter(e=>!t.has(e.id)),n.strokes=(n.strokes??[]).filter(e=>!t.has(e.id));let i=e.currentPath(),a=e.graph();for(let e of r){let t=i===`/`?`/`+e:i+`/`+e;a.maps=a.maps.filter(e=>e.path!==t&&!e.path.startsWith(t+`/`))}e.setGraph(a),e.setCurrentMap(e.ensureMap(i)),t.clear(),_e(),j()},Tt=null,Et=()=>{if(!Tt)throw Error(`line: wireLine() not called`);return Tt},Dt=e=>{Tt=e},Ot=!1,P=null,F=null,I=null,L=()=>Ot,kt=()=>P!==null,At=()=>{if(I)return I;let e=document.createElementNS(`http://www.w3.org/2000/svg`,`line`);return e.setAttribute(`class`,`line-preview`),e.setAttribute(`stroke`,`#07f`),e.setAttribute(`stroke-width`,`2`),e.setAttribute(`stroke-dasharray`,`5 4`),e.style.pointerEvents=`none`,Et().lineLayer().appendChild(e),I=e,e},jt=()=>{I&&I.parentNode&&I.parentNode.removeChild(I),I=null},R=e=>Math.round(e*100)/100,Mt=(e,t,n)=>{let r=t-e.x,i=n-e.y,a=Math.hypot(r,i);if(a<.001)return{x:t,y:n};let o=10*Math.PI/180,s=Math.round(Math.atan2(i,r)/o)*o;return{x:R(e.x+Math.cos(s)*a),y:R(e.y+Math.sin(s)*a)}},Nt=e=>{Ot!==e&&(Ot=e,document.body.classList.toggle(`line-mode`,Ot),Ot||(P=null,F=null,jt()),Et().setStatus(Ot?`line mode — click start, click end · L or Escape to exit`:`select mode`))},Pt=()=>{P&&(P=null,F=null,jt())},Ft=(e,t,n=!1)=>{let r=R(x(e)),i=R(S(t));if(!P){P={x:r,y:i},F={x:e,y:t};let n=At();n.setAttribute(`x1`,String(r)),n.setAttribute(`y1`,String(i)),n.setAttribute(`x2`,String(r)),n.setAttribute(`y2`,String(i));return}let a=P,o=n?Mt(a,r,i):{x:r,y:i};P=null,F=null,jt(),!(Math.hypot(o.x-a.x,o.y-a.y)<2)&&Ct(a.x,a.y,o.x,o.y)},It=(e,t,n=!1)=>{if(!P||!F)return;let r=e-F.x,i=t-F.y;if(Math.hypot(r,i)<4)return;let a=R(x(e)),o=R(S(t)),s=P,c=n?Mt(s,a,o):{x:a,y:o};P=null,F=null,jt(),!(Math.hypot(c.x-s.x,c.y-s.y)<2)&&Ct(s.x,s.y,c.x,c.y)},Lt=(e,t,n=!1)=>{if(!P||!I)return;let r=R(x(e)),i=R(S(t)),a=n?Mt(P,r,i):{x:r,y:i};I.setAttribute(`x2`,String(a.x)),I.setAttribute(`y2`,String(a.y))},Rt=null,z=null,zt=e=>{Rt=e},Bt=()=>{if(!Rt)throw Error(`clipboard: wireClipboard() not called`);return Rt},Vt=()=>{let{selected:e,currentMap:t,findTextById:n,findLineById:r}=Bt();if(e.size===0)return!1;let i=t(),a=[],o=[],s=[],c=[],l=new Set;for(let t of e){let e=i.boxes.find(e=>e.id===t);if(e){let t={id:e.id,label:e.label,x:e.x,y:e.y};e.palette&&(t.palette=e.palette),e.font&&(t.font=e.font),a.push(t),l.add(e.id);continue}let c=n(t);if(c){let e={id:c.id,label:c.label,x:c.x,y:c.y};c.palette&&(e.palette=c.palette),c.font&&(e.font=c.font),o.push(e);continue}let u=r(t);if(u){let e={id:u.id,x1:u.x1,y1:u.y1,x2:u.x2,y2:u.y2};u.palette&&(e.palette=u.palette),u.style&&(e.style=u.style),u.mids?.length&&(e.mids=u.mids.map(([e,t])=>[e,t])),s.push(e)}}for(let e of i.edges)l.has(e.from)&&l.has(e.to)&&c.push({from:e.from,fromHandle:e.fromHandle??``,to:e.to,toHandle:e.toHandle??``});if(!a.length&&!o.length&&!s.length)return!1;z={boxes:a,texts:o,lines:s,edges:c,pasteOffset:0};let u=[...a,...o].sort((e,t)=>e.y-t.y||e.x-t.x).map(e=>e.label);return u.length&&typeof navigator<`u`&&navigator.clipboard&&navigator.clipboard.writeText(u.join(` -`)).catch(()=>{}),!0},Ht=()=>{let{selected:e,deleteSelection:t,setStatus:n}=Bt();if(!Vt()){n(`nothing to cut`);return}let r=e.size;t(),n(`cut `+r+` items`)},Ut=()=>{let{selected:e,currentMap:t,mintId:n,renderAll:r,setStatus:i,clearSelectedEdge:a}=Bt();if(!z){i(`clipboard is empty`);return}z.pasteOffset+=20;let o=z.pasteOffset,s=z.pasteOffset,c=new Map;e.clear(),a();let l=t();for(let t of z.boxes){let r=n(`b`);c.set(t.id,r);let i={id:r,label:t.label,x:t.x+o,y:t.y+s};l.boxes.push(i),e.add(r)}for(let t of z.texts){let r=n(`t`);c.set(t.id,r);let i={id:r,label:t.label,x:t.x+o,y:t.y+s};t.palette&&(i.palette=t.palette),t.font&&(i.font=t.font),l.texts.push(i),e.add(r)}for(let t of z.lines){let r=n(`l`);c.set(t.id,r);let i={id:r,x1:t.x1+o,y1:t.y1+s,x2:t.x2+o,y2:t.y2+s};t.palette&&(i.palette=t.palette),t.style&&(i.style=t.style),t.mids?.length&&(i.mids=t.mids.map(([e,t])=>[e+o,t+s])),l.lines.push(i),e.add(r)}for(let e of z.edges){let t=c.get(e.from),n=c.get(e.to);if(!t||!n)continue;let r={from:t,to:n};e.fromHandle&&(r.fromHandle=e.fromHandle),e.toHandle&&(r.toHandle=e.toHandle),l.edges.push(r)}w(),r(),i(`pasted `+e.size+` items`)},Wt=null,Gt=()=>{if(!Wt)throw Error(`navigation: wireNavigation() not called`);return Wt},Kt=e=>{Wt=e},qt=e=>{let t=Gt().getGraph(),n=t.maps.find(t=>t.path===e);return n||(n={path:e,boxes:[],edges:[]},t.maps.push(n)),n.boxes??=[],n.edges??=[],n.texts??=[],n.lines??=[],n.strokes??=[],n},Jt=()=>{let e=location.hash||``;return e.startsWith(`#`)&&(e=e.slice(1)),e?(e.startsWith(`/`)||(e=`/`+e),e):`/`},Yt=(e,t)=>{let n=t?.keepViewport??!1,r=Gt();r.setCurrentPath(e),r.setCurrentMap(qt(e)),r.clearSelected(),r.clearSelectedEdge(),r.renderAll(),Qt(),n||le(qt(e));let i=`#`+e;location.hash!==i&&history.pushState(null,``,i)},Xt=e=>{let t=Gt().getCurrentPath();Yt(t===`/`?`/`+e:t+`/`+e)},Zt=()=>{let e=Gt().getCurrentPath();if(e===`/`)return;let t=e.split(`/`).filter(Boolean);t.pop(),Yt(t.length?`/`+t.join(`/`):`/`)},Qt=()=>{let e=Gt(),t=e.getGraph(),n=e.getCurrentPath(),r=document.getElementById(`path`);if(!r)return;r.innerHTML=``;let i=n===`/`?[]:n.split(`/`).filter(Boolean);r.style.display=i.length===0?`none`:``;let a=document.getElementById(`toolbar`),o=document.body.classList.contains(`snapshot-mode`);if(a&&(a.style.display=i.length===0&&!o?`none`:``),i.length===0){let e=document.getElementById(`upBtn`);e&&(e.style.display=`none`);return}let s=document.createElement(`span`);s.className=`seg`,s.textContent=`/`,s.addEventListener(`click`,()=>Yt(`/`)),r.appendChild(s);let c=``,l=`/`;i.forEach((e,n)=>{if(n>0){let e=document.createElement(`span`);e.className=`sep`,e.textContent=`/`,r.appendChild(e)}c+=`/`+e;let a=c,o=((t.maps||[]).find(e=>e.path===l)?.boxes??[]).find(t=>t.id===e),s=o?.label&&o.label.trim()||e;l=a;let u=document.createElement(`span`);u.className=`seg`,u.textContent=s,u.title=e,nYt(a)):(u.style.fontWeight=`bold`,u.style.cursor=`default`),r.appendChild(u)});let u=document.getElementById(`upBtn`);u&&(u.style.display=n===`/`?`none`:``)},$t=()=>{window.addEventListener(`hashchange`,()=>{let e=Jt();e!==Gt().getCurrentPath()&&Yt(e)})},en=100,tn=200,nn=null,B=()=>{if(!nn)throw Error(`persistence: wirePersistence() not called`);return nn},V=null,H=[],rn=[],an=null,on=location.pathname.match(/^\/m\/([\w-]+)\/?$/),sn=on?on[1]:null,cn=sn!==null,ln=e=>{nn=e},un=async()=>{let e=B(),t=null;if(cn){document.body.classList.add(`snapshot-mode`),document.getElementById(`downloadBtn`)?.style.setProperty(`display`,``),document.getElementById(`reshareBtn`)?.style.setProperty(`display`,``);try{let e=await fetch(`/api/snapshot/`+encodeURIComponent(sn));if(!e.ok)throw Error(`HTTP `+e.status);let n=await e.json();t=n.graph||n}catch(n){let r=n instanceof Error?n.message:String(n);e.setStatus(`snapshot `+sn+` not loaded: `+r),t=null}}else t=await(await fetch(`/state`)).json();(!t||!t.maps||t.maps.length===0)&&(t={maps:[{path:`/`,boxes:[],edges:[]}]}),e.setGraph(t),V=JSON.stringify(t),H=[],rn=[],e.setCurrentPath(e.readPathFromURL()),e.setStatus(cn?`snapshot `+sn+` — local edits only`:`loaded`)},dn=()=>{B().setStatus(`saving…`),an&&clearTimeout(an),an=setTimeout(pn,tn)},fn=async e=>{if(cn){B().setStatus(`local edits only — use Download or Save as new share`);return}await fetch(`/save`,{method:`POST`,headers:{"Content-Type":`application/json`},body:e}),B().setStatus(`saved`)},pn=async()=>{let e=JSON.stringify(B().getGraph());V!==null&&e!==V&&(H.push(V),H.length>en&&H.shift(),rn=[]),V=e,await fn(e)},mn=e=>{let t=B(),n=JSON.parse(e);t.setGraph(n),t.clearSelected(),t.clearSelectedEdge();let r=t.getCurrentPath(),i=n.maps.some(e=>e.path===r)?r:`/`;t.setCurrentPath(i,{keepViewport:i===r})},hn=()=>{let e=B();an&&=(clearTimeout(an),null);let t=JSON.stringify(e.getGraph());if(V!==null&&t!==V&&(H.push(V),H.length>en&&H.shift(),rn=[],V=t),H.length===0){e.setStatus(`nothing to undo`);return}let n=H.pop();V!==null&&rn.push(V),V=n,mn(n),fn(n),e.setStatus(`undo (`+H.length+` left)`)},gn=()=>{let e=B();if(rn.length===0){e.setStatus(`nothing to redo`);return}let t=rn.pop();V!==null&&H.push(V),V=t,mn(t),fn(t),e.setStatus(`redo`)},_n=()=>{let e=B(),t=e.serializeGraph(e.getGraph()),n=new Blob([t],{type:`text/plain;charset=utf-8`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=(sn??`mindmap`)+`.flowgo`,document.body.appendChild(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(r),1e3),e.setStatus(`downloaded`)},vn=async()=>{let e=B();e.setStatus(`re-sharing…`);try{let t=await fetch(`/api/snapshot`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({graph:e.getGraph()})});if(!t.ok)throw Error(`HTTP `+t.status);let n=await t.json();if(!n.url)throw Error(`response missing url`);navigator.clipboard&&navigator.clipboard.writeText(n.url).catch(()=>{}),e.setStatus(`new share: `+n.url+` (copied)`),n.id&&history.pushState(null,``,`/m/`+n.id)}catch(t){let n=t instanceof Error?t.message:String(t);e.setStatus(`re-share failed: `+n)}},yn=null,bn=()=>{if(!yn)throw Error(`clone: wireClone() not called`);return yn},xn=e=>{yn=e},Sn=()=>{let{currentMap:e,selected:t,findTextById:n,findLineById:r,mintId:i}=bn(),a=e(),o=new Map,s=Array.from(t),c=new Set;for(let e of s){let t=a.boxes.find(t=>t.id===e);if(t){let n=i(`b`);o.set(e,n),c.add(n);let r={id:n,label:t.label,x:t.x,y:t.y};t.palette&&(r.palette=t.palette),t.font&&(r.font=t.font),a.boxes.push(r);continue}let s=n(e);if(s){let t=i(`t`);o.set(e,t);let n={id:t,label:s.label,x:s.x,y:s.y};s.palette&&(n.palette=s.palette),s.font&&(n.font=s.font),a.texts.push(n);continue}let l=r(e);if(l){let t=i(`l`);o.set(e,t);let n={id:t,x1:l.x1,y1:l.y1,x2:l.x2,y2:l.y2};l.palette&&(n.palette=l.palette),l.style&&(n.style=l.style),l.mids?.length&&(n.mids=l.mids.map(([e,t])=>[e,t])),a.lines.push(n)}}for(let e of a.edges.slice()){let t=o.get(e.from),n=o.get(e.to);if(t&&n&&c.has(t)&&c.has(n)){let r={from:t,to:n};e.fromHandle&&(r.fromHandle=e.fromHandle),e.toHandle&&(r.toHandle=e.toHandle),a.edges.push(r)}}t.clear();for(let e of o.values())t.add(e);return o},Cn=null,U=()=>{if(!Cn)throw Error(`keys: wireKeys() not called`);return Cn},wn=e=>{Cn=e},Tn=(e,t)=>((e&&e>=1&&e<=9?e:1)-1+t+9)%9+1,En=(e,t)=>t===1?e.palette?(delete e.palette,!0):!1:e.palette===t?!1:(e.palette=t,!0),Dn=e=>{let t=U(),n=t.currentMap();return n.boxes.find(t=>t.id===e)||t.findTextById(e)||n.lines.find(t=>t.id===e)||(n.strokes??[]).find(t=>t.id===e)},On=()=>{let e=U();return e.selected.size>0||e.selectedEdge()!==null},kn=e=>{let t=U();if(!On())return!1;let n=!1;for(let r of t.selected){let t=Dn(r);t&&En(t,Tn(t.palette,e))&&(n=!0)}let r=t.selectedEdge();return r&&En(r,Tn(r.palette,e))&&(n=!0),n},An=e=>{let t=U();if(t.selected.size===0)return!1;let n=t.currentMap(),r=!1;for(let i of t.selected){let a=n.boxes.find(e=>e.id===i)||t.findTextById(i);if(!a)continue;let o=Tn(a.font,e);o===1?a.font&&(delete a.font,r=!0):a.font!==o&&(a.font=o,r=!0)}return r},jn=()=>{let e=U();if(e.selected.size!==1){e.setStatus(`anchor needs exactly one selected box`);return}let t=e.selected.values().next().value,n=e.currentMap(),r=n.boxes.find(e=>e.id===t);if(!r){e.setStatus(`anchor only applies to boxes`);return}let i=!r.anchor;for(let e of n.boxes)e.anchor&&delete e.anchor;i&&(r.anchor=!0),fe(),j(),e.setStatus(i?`anchored `+t:`anchor cleared`)},Mn=e=>{let t=U();if(!On())return!1;let n=!1;for(let r of t.selected){let t=Dn(r);t&&En(t,e)&&(n=!0)}let r=t.selectedEdge();return r&&En(r,e)&&(n=!0),n},Nn=e=>{let t=U();if(t.selected.size===0)return!1;let n=t.currentMap(),r=!1;for(let i of t.selected){let a=n.boxes.find(e=>e.id===i)||t.findTextById(i);a&&(e===1?a.font&&(delete a.font,r=!0):a.font!==e&&(a.font=e,r=!0))}return r},Pn=e=>{let t=U();if(t.selected.size===0)return!1;let n=t.currentMap(),r=!1;for(let i of t.selected){let t=n.lines.find(e=>e.id===i);t&&(e===1?t.style&&(delete t.style,r=!0):t.style!==e&&(t.style=e,r=!0))}return r},Fn=()=>{document.addEventListener(`keydown`,e=>{let t=U();if(e.key===`Escape`&&ae()){ie(!1);return}if(Re())return;let n=e.metaKey||e.ctrlKey;if(n&&!e.altKey&&(e.key===`z`||e.key===`Z`)){e.preventDefault(),e.shiftKey?gn():hn();return}if(n&&!e.altKey&&(e.key===`y`||e.key===`Y`)){e.preventDefault(),gn();return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`a`||e.key===`A`)){e.preventDefault();let n=t.currentMap();t.selected.clear();for(let e of n.boxes)t.selected.add(e.id);for(let e of n.texts??[])t.selected.add(e.id);for(let e of n.lines??[])t.selected.add(e.id);t.selectedEdge()&&(t.setSelectedEdge(null),N()),M(),t.setStatus(`selected `+t.selected.size+` items`);return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`c`||e.key===`C`)){if(window.getSelection&&String(window.getSelection()))return;e.preventDefault(),Vt()?t.setStatus(`copied `+t.selected.size+` items`):t.setStatus(`nothing to copy`);return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`x`||e.key===`X`)){e.preventDefault(),Ht();return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`v`||e.key===`V`)){e.preventDefault(),Ut();return}if(!n&&!e.altKey&&(e.key===`t`||e.key===`T`)){e.preventDefault(),St(x(t.lastCursor.x),S(t.lastCursor.y));return}if(!n&&!e.altKey&&(e.key===`l`||e.key===`L`)){e.preventDefault(),Nt(!L());return}if(!n&&!e.altKey&&(e.key===`b`||e.key===`B`)){e.preventDefault(),De(!0);return}if(!n&&!e.altKey&&(e.key===`v`||e.key===`V`)){e.preventDefault(),De(!1),Nt(!1);return}if(!n&&!e.altKey&&(e.key===`a`||e.key===`A`)){e.preventDefault(),jn();return}if(!n&&!e.altKey&&!e.shiftKey&&/^[1-9]$/.test(e.key)){let t=parseInt(e.key,10);if(E()){e.preventDefault(),Oe(t);return}if(!On())return;Mn(t)&&(e.preventDefault(),w(),j());return}if(!n&&!e.altKey&&e.shiftKey&&/^Digit[1-9]$/.test(e.code)){if(t.selected.size===0)return;let n=parseInt(e.code.slice(5),10),r=Nn(n),i=Pn(n);(r||i)&&(e.preventDefault(),w(),j());return}if(!n&&!e.altKey&&e.shiftKey&&(e.key===`+`||e.key===`*`||e.key===`_`||e.key===`-`)){if(t.selected.size===0)return;An(e.key===`_`||e.key===`-`?-1:1)&&(e.preventDefault(),w(),j());return}if(!n&&!e.altKey&&(e.key===`+`||e.key===`=`||e.key===`-`)){if(!On())return;kn(e.key===`-`?-1:1)&&(e.preventDefault(),w(),j());return}if(!n&&!e.altKey&&!e.shiftKey&&e.key===`Enter`){if(t.selected.size!==1)return;let n=t.selected.values().next().value,r=t.currentMap(),i=r.boxes.find(e=>e.id===n);if(i){let r=t.canvas.querySelector(`.box[data-id="${n}"]`);r&&(e.preventDefault(),O(r,i));return}let a=(r.texts??[]).find(e=>e.id===n);if(a){let r=t.canvas.querySelector(`.text-item[data-id="${n}"]`);r&&(e.preventDefault(),Be(r,a))}return}if(e.key===`Escape`){if(E()){De(!1);return}if(L()){kt()?Pt():Nt(!1);return}let e=t.link();e&&(e.handleEl.classList.remove(`active`),t.ghostLine.style.display=`none`,t.clearLink(),t.setDropTargetId(null),t.setDropTargetHandle(null),M(),t.clearProximity()),t.selected.clear(),t.setSelectedEdge(null),M(),N()}if(e.key===`Delete`||e.key===`Backspace`){let n=t.selectedEdge();if(n){e.preventDefault();let r=t.currentMap(),i=r.edges.indexOf(n);i>=0&&r.edges.splice(i,1),t.setSelectedEdge(null),pe(),N(),t.setStatus(`edge removed`);return}t.selected.size>0&&(e.preventDefault(),wt())}})},In=null,Ln=()=>{if(!In)throw Error(`mouse: wireMouse() not called`);return In},Rn=e=>{In=e},zn=(e,t)=>{let n=Ln(),r=document.elementsFromPoint(e,t);for(let e of r){if(!e||e===n.ghostLine)continue;let t=e.closest?.(`.box`);if(t)return t}return null},Bn=e=>{let t=Ln();if(t.lastCursor.x=e.clientX,t.lastCursor.y=e.clientY,Ce()){Me(e.clientX,e.clientY);return}if(L()){Lt(e.clientX,e.clientY,e.shiftKey);return}let n=t.pan();if(n){b.x=n.startVX+(e.clientX-n.downX),b.y=n.startVY+(e.clientY-n.downY),ce();return}let r=t.drag();if(r){let t=e.clientX-r.downX,n=e.clientY-r.downY;if(!r.active&&Math.hypot(t,n)>4){r.active=!0;for(let e of r.movers)e.el?.classList?.add(`dragging`)}if(r.active){for(let i of r.movers)i.apply(t,n,e);N()}return}let i=t.band();if(i){let t=Math.min(i.startX,e.clientX),n=Math.min(i.startY,e.clientY),r=Math.abs(e.clientX-i.startX),a=Math.abs(e.clientY-i.startY);i.el.style.left=t+`px`,i.el.style.top=n+`px`,i.el.style.width=r+`px`,i.el.style.height=a+`px`;return}let a=t.link();if(a){t.ghostLine.setAttribute(`x2`,String(x(e.clientX))),t.ghostLine.setAttribute(`y2`,String(S(e.clientY)));let n=zn(e.clientX,e.clientY),r=n&&n.dataset.id!==a.fromId?n.dataset.id??null:null,i=null;if(r&&n){let o=t.currentMap().boxes.find(e=>e.id===r);o&&(i=We(n,o,a.startX,a.startY,e.clientX,e.clientY))}let o=r!==t.dropTargetId(),s=i!==t.dropTargetHandle();(o||s)&&(t.setDropTargetId(r),t.setDropTargetHandle(i),M()),gt(x(e.clientX),S(e.clientY));return}gt(x(e.clientX),S(e.clientY))},Vn=e=>{let t=Ln();if(Ce()){Ne();return}if(L()){It(e.clientX,e.clientY,e.shiftKey);return}if(t.pan()){t.setPan(null),document.body.classList.remove(`panning`);return}let n=t.drag();if(n){let e=n.active;for(let e of n.movers)e.el?.classList?.remove(`dragging`);let r=n.primaryId;t.setDrag(null),e?w():(t.selected.clear(),r&&t.selected.add(r),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M());return}let r=t.band();if(r){let n=Math.min(r.startX,e.clientX),i=Math.min(r.startY,e.clientY),a=Math.max(r.startX,e.clientX),o=Math.max(r.startY,e.clientY);if(a-n>2||o-i>2){let e=x(n),r=S(i),s=x(a),c=S(o),l=t.currentMap();for(let n of l.boxes){let i=t.canvas.querySelector(`.box[data-id="${n.id}"]`);if(!i)continue;let a=n.x+i.offsetWidth,o=n.y+i.offsetHeight;n.xe&&n.yr&&t.selected.add(n.id)}for(let n of l.texts){let i=t.canvas.querySelector(`.text-item[data-id="${n.id}"]`);if(!i)continue;let a=n.x+i.offsetWidth,o=n.y+i.offsetHeight;n.xe&&n.yr&&t.selected.add(n.id)}for(let n of l.lines){let i=[n.x1,n.x2],a=[n.y1,n.y2];for(let[e,t]of n.mids??[])i.push(e),a.push(t);let o=Math.min(...i),l=Math.min(...a),u=Math.max(...i),d=Math.max(...a);oe&&lr&&t.selected.add(n.id)}M(),t.selected.size>0&&t.setStatus(t.selected.size+` selected`)}r.el.remove(),t.setBand(null);return}let i=t.link();if(i){i.handleEl.classList.remove(`active`),t.ghostLine.style.display=`none`;let n=zn(e.clientX,e.clientY);if(n&&n.dataset.id!==i.fromId){let r=n.dataset.id,a=t.currentMap(),o=We(n,a.boxes.find(e=>e.id===r),i.startX,i.startY,e.clientX,e.clientY),s={from:i.fromId,to:r};i.fromHandle&&(s.fromHandle=i.fromHandle),o&&(s.toHandle=o),a.edges=h(a.edges,s),pe(),N()}else{let n=t.mintId(),r=x(e.clientX),a=S(e.clientY),o={id:n,label:`new`,x:r,y:a},s=t.currentMap();s.boxes.push(o),j();let c=t.canvas.querySelector(`.box[data-id="${n}"]`);if(c){o.x=r-c.offsetWidth/2,o.y=a-c.offsetHeight/2,c.style.left=o.x+`px`,c.style.top=o.y+`px`;let e=Ue(o,c,i.startX,i.startY),l={from:i.fromId,to:n};i.fromHandle&&(l.fromHandle=i.fromHandle),e&&(l.toHandle=e),s.edges=h(s.edges,l),N(),t.selected.clear(),t.selected.add(n),M(),O(c,o,{cancelDeletes:!0})}w()}t.setLink(null),(t.dropTargetId()||t.dropTargetHandle())&&(t.setDropTargetId(null),t.setDropTargetHandle(null),M()),_t()}},Hn=e=>{e.button===2&&(e.preventDefault(),Ln().setPan({downX:e.clientX,downY:e.clientY,startVX:b.x,startVY:b.y}),document.body.classList.add(`panning`))},Un=e=>{let t=Ln();if(e.button!==0)return;if(E()){e.preventDefault(),e.stopPropagation(),je(e.clientX,e.clientY);return}if(L()){e.preventDefault(),e.stopPropagation(),Ft(e.clientX,e.clientY,e.shiftKey);return}e.shiftKey||t.selected.clear(),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M();let n=document.createElement(`div`);n.className=`selection-band`,n.style.left=e.clientX+`px`,n.style.top=e.clientY+`px`,n.style.width=`0px`,n.style.height=`0px`,document.body.appendChild(n),t.setBand({startX:e.clientX,startY:e.clientY,el:n})},Wn=(e,t,n,r,i,a)=>{let o=i-n,s=a-r,c=o*o+s*s,l=c===0?0:Math.max(0,Math.min(1,((e-n)*o+(t-r)*s)/c)),u=n+l*o,d=r+l*s,f=e-u,p=t-d;return{d2:f*f+p*p,t:l}},Gn=14,Kn=(e,t,n)=>{let r=null,i=0,a=Gn*Gn;for(let o of e.lines){let e=[[o.x1,o.y1],...o.mids??[],[o.x2,o.y2]];for(let s=0;s{let t=Ln();if(E())return;if(L()){let n=x(e.clientX),r=S(e.clientY);Kn(t.currentMap(),n,r)&&(Pt(),he(),j());return}let n=x(e.clientX),r=S(e.clientY);xt(n,r,{x:n,y:r})},Jn=()=>{document.addEventListener(`mousemove`,Bn),document.addEventListener(`mouseup`,Vn),document.addEventListener(`mousedown`,Hn),window.addEventListener(`contextmenu`,e=>e.preventDefault()),window.addEventListener(`auxclick`,e=>{e.button===1&&e.preventDefault()});let e=document.getElementById(`bg-layer`);e&&(e.addEventListener(`mousedown`,Un),e.addEventListener(`dblclick`,qn))},Yn=typeof navigator<`u`&&/Mac|iPhone|iPad|iPod/i.test(navigator.platform||navigator.userAgent||``),Xn=e=>Yn?e.metaKey:e.ctrlKey,W=e=>Math.round(e/20)*20,Zn=e=>{let t=e.mids??[],n=[[e.x1,e.y1],...t,[e.x2,e.y2]],r=e.style??1;if(r===2&&t.length>0){let n=`M ${e.x1} ${e.y1}`;for(let e=0;e=Math.abs(o-i)?e+=` L ${a} ${i} L ${a} ${o}`:e+=` L ${r} ${o} L ${a} ${o}`}return e}let i=`M ${n[0][0]} ${n[0][1]}`;for(let e=1;e{let n=e.x,r=e.y;return{el:t,apply(i,a,o){let s=n+i,c=r+a;o?.shiftKey&&(s=W(s),c=W(c)),e.x=s,e.y=c,t.style.left=e.x+`px`,t.style.top=e.y+`px`}}},$n=(e,t)=>{let n=e.x,r=e.y;return{el:t,apply(i,a,o){let s=n+i,c=r+a;o?.shiftKey&&(s=W(s),c=W(c)),e.x=s,e.y=c,t.style.left=e.x+`px`,t.style.top=e.y+`px`}}},er=(e,t,n,r,i,a,o)=>{let s=e.x1,c=e.y1,l=e.x2,u=e.y2,d=(e.mids??[]).map(([e,t])=>[e,t]);return{el:t,apply(t,f,p){let m=t,h=f;if(p?.shiftKey&&(m=W(s+t)-s,h=W(c+f)-c),e.x1=s+m,e.y1=c+h,e.x2=l+m,e.y2=u+h,d.length>0){e.mids||=[];for(let t=0;t{let r=typeof t==`object`?t.mid:-1,i=t===1?e.x1:t===2?e.x2:e.mids?.[r]?.[0]??0,a=t===1?e.y1:t===2?e.y2:e.mids?.[r]?.[1]??0;return{el:n.g,apply(o,s,c){let l=i+o,u=a+s;c?.shiftKey&&(l=W(l),u=W(u)),t===1?(e.x1=l,e.y1=u):t===2?(e.x2=l,e.y2=u):e.mids&&e.mids[r]&&(e.mids[r]=[l,u]);let d=Zn(e);n.line.setAttribute(`d`,d),n.hit.setAttribute(`d`,d);let f=t===1?n.h1:t===2?n.h2:n.midHandles[r]??null;f&&(f.setAttribute(`cx`,String(l)),f.setAttribute(`cy`,String(u)))}}},nr=(e,t,n,r)=>{let i=e.points.map(([e,t])=>[e,t]);return{el:t,apply(t,a,s){let c=t,l=a;if(s?.shiftKey&&i.length>0){let e=i[0];c=W(e[0]+t)-e[0],l=W(e[1]+a)-e[1]}for(let t=0;t{if(!rr)throw Error(`attach: wireAttach() not called`);return rr},ir=e=>{rr=e},ar=()=>{let e=G(),t=[],n=e.currentMap();for(let r of e.selected){let i=n.boxes.find(e=>e.id===r);if(i){let n=e.canvas.querySelector(`.box[data-id="${r}"]`);n&&t.push(Qn(i,n));continue}let a=e.findTextById(r);if(a){let n=e.canvas.querySelector(`.text-item[data-id="${r}"]`);n&&t.push($n(a,n));continue}let o=e.findLineById(r);if(o){let n=e.lineLayer.querySelector(`.line-group[data-id="${r}"]`);if(n){let e=n.querySelector(`.line-line`),r=n.querySelector(`.line-hit`),i=n.querySelector(`.line-handle[data-endpoint="1"]`),a=n.querySelector(`.line-handle[data-endpoint="2"]`),s=Array.from(n.querySelectorAll(`.line-handle[data-endpoint="m"]`));t.push(er(o,n,e,r,i,a,s))}continue}let s=e.findStrokeById(r);if(s){let n=e.strokeLayer.querySelector(`.stroke-group[data-id="${r}"]`);if(n){let e=n.querySelector(`.stroke-hit`),r=n.querySelector(`.stroke-line`);t.push(nr(s,n,e,r))}}}return t},or=(e,t)=>{e.addEventListener(`mousedown`,n=>{let r=G();if(e.isContentEditable||n.button!==0)return;n.preventDefault(),n.stopPropagation(),r.selected.has(t.id)||(n.shiftKey||r.selected.clear(),r.selected.add(t.id),r.selectedEdge()&&(r.setSelectedEdge(null),N()),M());let i=t.id;if(n.altKey){let e=r.cloneSelection();e.has(t.id)&&(i=e.get(t.id))}r.setDrag({movers:ar(),primaryId:i,downX:n.clientX,downY:n.clientY,active:!1})}),e.addEventListener(`dblclick`,n=>{let r=G();e.isContentEditable||(n.preventDefault(),n.stopPropagation(),r.selected.clear(),r.selected.add(t.id),M(),Be(e,t))})},sr=(e,t,n,r,i,a,o)=>{n.addEventListener(`mousedown`,e=>{let t=G();if(e.button!==0)return;e.preventDefault(),e.stopPropagation(),t.selected.has(o.id)||(e.shiftKey||t.selected.clear(),t.selected.add(o.id),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M());let n=o.id;if(e.altKey){let e=t.cloneSelection();e.has(o.id)&&(n=e.get(o.id))}t.setDrag({movers:ar(),primaryId:n,downX:e.clientX,downY:e.clientY,active:!1})}),n.addEventListener(`dblclick`,e=>{e.preventDefault(),e.stopPropagation();let t=G(),n=x(e.clientX),r=S(e.clientY);o.mids||=[];let i=[[o.x1,o.y1],...o.mids,[o.x2,o.y2]],a=0,s=1/0;for(let e=0;e{let l=G();s.button===0&&(s.preventDefault(),s.stopPropagation(),l.selected.clear(),l.selected.add(o.id),l.selectedEdge()&&(l.setSelectedEdge(null),N()),M(),l.setDrag({movers:[tr(o,c,{g:e,line:t,hit:n,h1:r,h2:i,midHandles:a})],primaryId:o.id,downX:s.clientX,downY:s.clientY,active:!1}))});for(let s=0;s{let c=G();s.button===0&&(s.preventDefault(),s.stopPropagation(),c.selected.clear(),c.selected.add(o.id),c.selectedEdge()&&(c.setSelectedEdge(null),N()),M(),c.setDrag({movers:[tr(o,{mid:l},{g:e,line:t,hit:n,h1:r,h2:i,midHandles:a})],primaryId:o.id,downX:s.clientX,downY:s.clientY,active:!1}))}),c.addEventListener(`dblclick`,e=>{e.preventDefault(),e.stopPropagation(),o.mids&&l{e.addEventListener(`mousedown`,n=>{let r=G();if(e.isContentEditable)return;if(n.button===1||n.button===0&&Xn(n)){n.preventDefault(),n.stopPropagation(),Xt(t.id);return}if(n.button!==0)return;let i=n.target;if(i.classList.contains(`handle`)){n.preventDefault(),n.stopPropagation();let a=i.dataset.handle,o=r.currentMap(),s=null,c=null,l=``;for(let e=o.edges.length-1;e>=0;e--){let n=o.edges[e];if(n.from===t.id&&n.fromHandle===a){s=n,c=n.to,l=n.toHandle??``;break}if(n.to===t.id&&n.toHandle===a){s=n,c=n.from,l=n.fromHandle??``;break}}if(s&&c){let a=o.edges.indexOf(s);a>=0&&o.edges.splice(a,1);let u=o.boxes.find(e=>e.id===c),d=r.canvas.querySelector(`.box[data-id="${c}"]`);if(!u||!d){o.edges.push(s),N();return}let f=t.x+e.offsetWidth/2,p=t.y+e.offsetHeight/2,m=l||Ue(u,d,f,p),[h,g]=He(d,u,m);r.setLink({fromId:c,fromHandle:m,startX:h,startY:g,handleEl:i,rerouting:!0}),i.classList.add(`active`),r.ghostLine.setAttribute(`x1`,String(h)),r.ghostLine.setAttribute(`y1`,String(g)),r.ghostLine.setAttribute(`x2`,String(x(n.clientX))),r.ghostLine.setAttribute(`y2`,String(S(n.clientY))),r.ghostLine.style.display=``,N(),r.setStatus(`re-routing edge — drop on a box, or in empty space`);return}let[u,d]=He(e,t,a);r.setLink({fromId:t.id,fromHandle:a,startX:u,startY:d,handleEl:i}),i.classList.add(`active`),r.ghostLine.setAttribute(`x1`,String(u)),r.ghostLine.setAttribute(`y1`,String(d)),r.ghostLine.setAttribute(`x2`,String(x(n.clientX))),r.ghostLine.setAttribute(`y2`,String(S(n.clientY))),r.ghostLine.style.display=``,r.setStatus(`drop on a box to connect, or release to cancel`);return}n.preventDefault(),n.stopPropagation(),r.selected.has(t.id)||(n.shiftKey||r.selected.clear(),r.selected.add(t.id),r.selectedEdge()&&(r.setSelectedEdge(null),N()),M());let a=t.id;if(n.altKey){let e=r.cloneSelection();e.has(t.id)&&(a=e.get(t.id))}r.setDrag({movers:ar(),primaryId:a,downX:n.clientX,downY:n.clientY,active:!1})}),e.addEventListener(`dblclick`,n=>{let r=G();e.isContentEditable||(n.preventDefault(),n.stopPropagation(),r.selected.clear(),r.selected.add(t.id),r.selectedEdge()&&(r.setSelectedEdge(null),N()),M(),O(e,t))})},lr=(e,t,n)=>e!==null&&e.id===t.id&&t.time-e.time<=n?{kind:`double`,nextLastTap:null}:{kind:`single`,nextLastTap:t},ur=(e,t,n,r,i)=>Math.hypot(n-e,r-t)>i,dr=300,fr=``,pr=500,mr=4,K=null,hr=null,gr=()=>{hr!==null&&(clearTimeout(hr),hr=null)},_r=()=>document.getElementById(`deleteZone`),vr=e=>{let t=_r();return t?e<=t.getBoundingClientRect().bottom:!1},yr=e=>{_r()?.classList.toggle(`armed`,e)},br=null,xr=()=>{if(!br)throw Error(`touch: wireTouch() not called`);return br},Sr=e=>{br=e},Cr=(e,t)=>{if(!(e instanceof Element))return null;let n=e.closest(`.handle`);if(n){let e=n.parentElement?.closest?.(`.box`)??null;if(e&&!e.isContentEditable){let r=e.dataset.id,i=n.dataset.handle;if(r&&i&&t.has(r))return{kind:`handle`,boxEl:e,boxId:r,handleEl:n,code:i}}}let r=e.closest(`.box`);if(r&&!r.isContentEditable){let e=r.dataset.id;if(e)return{kind:`box`,el:r,id:e}}let i=e.closest(`.text-item`);if(i&&!i.isContentEditable){let e=i.dataset.id;if(e)return{kind:`text`,el:i,id:e}}let a=e.closest(`.line-handle`);if(a){let e=a.closest(`.line-group`)?.dataset?.id,n=a.dataset.endpoint;if(e&&t.has(e)&&(n===`1`||n===`2`||n===`m`)){let t;if(n===`1`)t=1;else if(n===`2`)t=2;else{let e=parseInt(a.dataset.midIndex??`0`,10);t={mid:Number.isFinite(e)?e:0}}return{kind:`line-endpoint`,lineId:e,endpoint:t}}}let o=e.closest(`.line-group`);if(o){let e=o.dataset.id;if(e)return{kind:`line`,id:e}}let s=e.closest(`.stroke-group`);if(s){let e=s.dataset.id;if(e)return{kind:`stroke`,id:e}}return e.closest(`#bg-layer`)||e.closest(`#bg-svg`)||e.closest(`#edges`)?{kind:`bg`}:null},wr=()=>{let e=xr();if(gr(),Ce()){Ne();return}if(kt()){Pt();return}e.pan()&&(e.setPan(null),document.body.classList.remove(`panning`));let t=e.drag();if(t){for(let e of t.movers)e.el.classList?.remove(`dragging`);e.setDrag(null),document.body.classList.remove(`dragging`),yr(!1)}let n=e.link();n&&(n.handleEl.classList.remove(`active`),e.ghostLine.style.display=`none`,e.setLink(null),(e.dropTargetId()||e.dropTargetHandle())&&(e.setDropTargetId(null),e.setDropTargetHandle(null),M()),_t())},Tr=e=>{if(e.touches.length!==1){wr();return}if(E()){let t=e.touches[0];e.preventDefault(),je(t.clientX,t.clientY);return}if(L()){let t=e.touches[0];e.preventDefault(),Ft(t.clientX,t.clientY);return}let t=document.querySelector(`[contenteditable="true"]`);t&&!t.contains(e.target)&&t.blur(),document.body.classList.contains(`panning`)||(document.body.classList.remove(`dragging`),yr(!1));let n=xr(),r=e.touches[0],i=Cr(e.target,n.selected);if(i){if(i.kind===`bg`){e.preventDefault(),n.setPan({downX:r.clientX,downY:r.clientY,startVX:b.x,startVY:b.y}),document.body.classList.add(`panning`);return}if(i.kind===`handle`){e.preventDefault();let t=n.currentMap(),a=t.boxes.find(e=>e.id===i.boxId);if(!a)return;let o=null,s=null,c=``;for(let e=t.edges.length-1;e>=0;e--){let n=t.edges[e];if(n.from===i.boxId&&n.fromHandle===i.code){o=n,s=n.to,c=n.toHandle??``;break}if(n.to===i.boxId&&n.toHandle===i.code){o=n,s=n.from,c=n.fromHandle??``;break}}if(o&&s){let e=t.edges.indexOf(o);e>=0&&t.edges.splice(e,1);let l=t.boxes.find(e=>e.id===s),u=n.canvas.querySelector(`.box[data-id="${s}"]`);if(!l||!u){t.edges.push(o),N();return}let d=a.x+i.boxEl.offsetWidth/2,f=a.y+i.boxEl.offsetHeight/2,p=c||Ue(l,u,d,f),[m,h]=He(u,l,p);n.setLink({fromId:s,fromHandle:p,startX:m,startY:h,handleEl:i.handleEl,rerouting:!0}),i.handleEl.classList.add(`active`),n.ghostLine.setAttribute(`x1`,String(m)),n.ghostLine.setAttribute(`y1`,String(h)),n.ghostLine.setAttribute(`x2`,String(x(r.clientX))),n.ghostLine.setAttribute(`y2`,String(S(r.clientY))),n.ghostLine.style.display=``,N();return}let[l,u]=He(i.boxEl,a,i.code),d=i.handleEl.getBoundingClientRect(),f=x(d.left+d.width/2),p=S(d.top+d.height/2);n.setLink({fromId:i.boxId,fromHandle:i.code,startX:l,startY:u,handleEl:i.handleEl}),i.handleEl.classList.add(`active`),n.ghostLine.setAttribute(`x1`,String(f)),n.ghostLine.setAttribute(`y1`,String(p)),n.ghostLine.setAttribute(`x2`,String(x(r.clientX))),n.ghostLine.setAttribute(`y2`,String(S(r.clientY))),n.ghostLine.style.display=``;return}if(i.kind===`line-endpoint`){let t=i.lineId,a=i.endpoint,o=document.querySelector(`.line-group[data-id="${t}"]`),s=n.currentMap().lines.find(e=>e.id===t);if(!o||!s)return;let c=o.querySelector(`.line-line`),l=o.querySelector(`.line-hit`),u=o.querySelector(`.line-handle[data-endpoint="1"]`),d=o.querySelector(`.line-handle[data-endpoint="2"]`),f=Array.from(o.querySelectorAll(`.line-handle[data-endpoint="m"]`));if(!c||!l||!u||!d)return;e.preventDefault(),n.selected.clear(),n.selected.add(t),n.selectedEdge()&&(n.setSelectedEdge(null),N()),M(),n.setDrag({movers:[tr(s,a,{g:o,line:c,hit:l,h1:u,h2:d,midHandles:f})],primaryId:t,downX:r.clientX,downY:r.clientY,active:!1});return}if(e.preventDefault(),n.selected.has(i.id)||(n.selected.clear(),n.selected.add(i.id),n.selectedEdge()&&(n.setSelectedEdge(null),N()),M()),n.setDrag({movers:ar(),primaryId:i.id,downX:r.clientX,downY:r.clientY,active:!1}),i.kind===`box`){let e=i.id;hr=window.setTimeout(()=>{hr=null;let t=n.drag();if(!(!t||t.active)){t.longPressFired=!0;for(let e of t.movers)e.el.classList?.remove(`dragging`);n.setDrag(null),document.body.classList.remove(`dragging`),yr(!1),K=null,Xt(e)}},pr)}}},Er=e=>{let t=xr();if(e.touches.length!==1){wr();return}let n=e.touches[0];if(!n)return;if(Ce()){e.preventDefault(),Me(n.clientX,n.clientY);return}if(L()&&kt()){e.preventDefault(),Lt(n.clientX,n.clientY);return}let r=t.pan();if(r){e.preventDefault(),b.x=r.startVX+(n.clientX-r.downX),b.y=r.startVY+(n.clientY-r.downY),ce();return}let i=t.drag();if(i){e.preventDefault();let t=n.clientX-i.downX,r=n.clientY-i.downY;if(!i.active&&ur(i.downX,i.downY,n.clientX,n.clientY,mr)){i.active=!0,gr(),K=null;for(let e of i.movers)e.el.classList?.add(`dragging`);document.body.classList.add(`dragging`)}if(i.active){for(let e of i.movers)e.apply(t,r,null);N(),yr(vr(n.clientY))}return}let a=t.link();if(a){e.preventDefault();let r=x(n.clientX),i=S(n.clientY);t.ghostLine.setAttribute(`x2`,String(r)),t.ghostLine.setAttribute(`y2`,String(i));let o=zn(n.clientX,n.clientY),s=o&&o.dataset.id!==a.fromId?o.dataset.id??null:null,c=null;if(s&&o){let e=t.currentMap().boxes.find(e=>e.id===s);e&&(c=We(o,e,a.startX,a.startY,n.clientX,n.clientY))}(s!==t.dropTargetId()||c!==t.dropTargetHandle())&&(t.setDropTargetId(s),t.setDropTargetHandle(c),M()),gt(r,i)}},Dr=e=>{let t=xr();if(gr(),Ce()){Ne();return}if(L()&&kt()){let t=e.changedTouches[0];t&&It(t.clientX,t.clientY);return}let n=t.pan();if(n){let r=e.changedTouches[0]??null,i=r!==null&&ur(n.downX,n.downY,r.clientX,r.clientY,mr);if(t.setPan(null),document.body.classList.remove(`panning`),!i&&r){let e=lr(K,{id:fr,time:performance.now()},dr);if(K=e.nextLastTap,e.kind===`double`){let e=x(r.clientX),t=S(r.clientY);xt(e,t,{x:e,y:t})}else (t.selected.size>0||t.selectedEdge())&&(t.selected.clear(),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M())}else K=null;return}let r=t.link();if(r){Or(r,e.changedTouches[0]??null);return}let i=t.drag();if(!i)return;let a=i.active,o=i.longPressFired===!0;for(let e of i.movers)e.el.classList?.remove(`dragging`);let s=i.primaryId;t.setDrag(null),document.body.classList.remove(`dragging`);let c=_r()?.classList.contains(`armed`)??!1;if(yr(!1),o)return;if(a){if(c){wt(),K=null;return}w(),K=null;return}if(!s)return;let l=lr(K,{id:s,time:performance.now()},dr);if(K=l.nextLastTap,l.kind===`double`){let e=t.currentMap().boxes.find(e=>e.id===s);if(e){let n=document.querySelector(`#canvas .box[data-id="${s}"]`);n&&(t.selected.clear(),t.selected.add(s),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M(),O(n,e));return}let n=t.findTextById(s);if(n){let e=document.querySelector(`#canvas .text-item[data-id="${s}"]`);e&&(t.selected.clear(),t.selected.add(s),M(),Be(e,n));return}return}t.selected.clear(),t.selected.add(s),t.selectedEdge()&&(t.setSelectedEdge(null),N()),M()},Or=(e,t)=>{let n=xr();if(e.handleEl.classList.remove(`active`),n.ghostLine.style.display=`none`,!t){n.setLink(null),(n.dropTargetId()||n.dropTargetHandle())&&(n.setDropTargetId(null),n.setDropTargetHandle(null),M()),_t();return}let r=zn(t.clientX,t.clientY);if(r&&r.dataset.id!==e.fromId){let i=r.dataset.id,a=n.currentMap(),o=We(r,a.boxes.find(e=>e.id===i),e.startX,e.startY,t.clientX,t.clientY),s={from:e.fromId,to:i};e.fromHandle&&(s.fromHandle=e.fromHandle),o&&(s.toHandle=o),a.edges=h(a.edges,s),pe(),N()}else{let r=n.mintId(),i=x(t.clientX),a=S(t.clientY),o={id:r,label:`new`,x:i,y:a},s=n.currentMap();s.boxes.push(o),j();let c=n.canvas.querySelector(`.box[data-id="${r}"]`);if(c){o.x=i-c.offsetWidth/2,o.y=a-c.offsetHeight/2,c.style.left=o.x+`px`,c.style.top=o.y+`px`;let t=Ue(o,c,e.startX,e.startY),l={from:e.fromId,to:r};e.fromHandle&&(l.fromHandle=e.fromHandle),t&&(l.toHandle=t),s.edges=h(s.edges,l),N(),n.selected.clear(),n.selected.add(r),M(),O(c,o,{cancelDeletes:!0})}w()}n.setLink(null),(n.dropTargetId()||n.dropTargetHandle())&&(n.setDropTargetId(null),n.setDropTargetHandle(null),M()),_t()},kr=e=>{wr(),K=null},Ar=()=>{document.addEventListener(`touchstart`,Tr,{passive:!1}),document.addEventListener(`touchmove`,Er,{passive:!1}),document.addEventListener(`touchend`,Dr),document.addEventListener(`touchcancel`,kr)},jr=()=>E()?`brush`:L()?`line`:`cursor`,Mr=e=>{De(e===`brush`),Nt(e===`line`)},Nr=`http://www.w3.org/2000/svg`,Pr=(e,t)=>{let n=document.createElementNS(Nr,`svg`);return n.setAttribute(`width`,String(e)),n.setAttribute(`height`,String(e)),n.setAttribute(`viewBox`,`0 0 ${e} ${e}`),n.setAttribute(`fill`,`none`),n.setAttribute(`stroke`,`currentColor`),n.setAttribute(`stroke-width`,`1.6`),n.setAttribute(`stroke-linecap`,`round`),n.setAttribute(`stroke-linejoin`,`round`),t(n),n},Fr=()=>Pr(20,e=>{let t=document.createElementNS(Nr,`path`);t.setAttribute(`d`,`M4 3 L4 16 L8 12 L11 18 L13 17 L10 11 L16 11 Z`),t.setAttribute(`fill`,`currentColor`),t.setAttribute(`stroke`,`currentColor`),e.appendChild(t)}),Ir=()=>Pr(20,e=>{let t=document.createElementNS(Nr,`path`);t.setAttribute(`d`,`M3 17 L5 16 L14 7 L12 5 L3 14 Z`),t.setAttribute(`fill`,`currentColor`),e.appendChild(t);let n=document.createElementNS(Nr,`path`);n.setAttribute(`d`,`M13 6 L16 3 L18 5 L15 8 Z`),n.setAttribute(`fill`,`#a60`),n.setAttribute(`stroke`,`currentColor`),e.appendChild(n)}),Lr=()=>Pr(20,e=>{let t=document.createElementNS(Nr,`line`);t.setAttribute(`x1`,`4`),t.setAttribute(`y1`,`16`),t.setAttribute(`x2`,`16`),t.setAttribute(`y2`,`4`),e.appendChild(t);for(let[t,n]of[[4,16],[16,4]]){let r=document.createElementNS(Nr,`circle`);r.setAttribute(`cx`,String(t)),r.setAttribute(`cy`,String(n)),r.setAttribute(`r`,`2`),r.setAttribute(`fill`,`currentColor`),e.appendChild(r)}}),Rr=()=>{if(document.getElementById(`modeBar`))return;let e=document.createElement(`div`);e.id=`modeBar`;let t=[],n=(n,i,a)=>{let o=document.createElement(`button`);o.type=`button`,o.dataset.mode=n,o.title=i,o.setAttribute(`aria-label`,i),o.appendChild(a),o.addEventListener(`mousedown`,e=>e.stopPropagation()),o.addEventListener(`touchstart`,e=>e.stopPropagation(),{passive:!0});let s=!1,c=e=>{e.stopPropagation(),e.preventDefault(),!s&&(s=!0,setTimeout(()=>{s=!1},0),Mr(n),r())};return o.addEventListener(`pointerup`,c),o.addEventListener(`click`,c),e.appendChild(o),t.push({mode:n,el:o}),o};n(`cursor`,`Cursor`,Fr()),n(`brush`,`Brush`,Ir()),n(`line`,`Line`,Lr());let r=()=>{let e=jr();for(let{mode:n,el:r}of t)r.classList.toggle(`active`,n===e),r.setAttribute(`aria-pressed`,n===e?`true`:`false`)};r(),new MutationObserver(r).observe(document.body,{attributes:!0,attributeFilter:[`class`]}),document.body.appendChild(e)},q={maps:[]},zr=`/`,J={boxes:[],edges:[]},Y=new Set,Br={x:window.innerWidth/2,y:window.innerHeight/2},Vr=null,Hr=null,Ur=null,X=null,Z=null,Wr=null,Gr=null,Kr=null,Q=document.getElementById(`canvas`),qr=document.getElementById(`edge-layer`),Jr=document.getElementById(`line-layer`),Yr=document.getElementById(`stroke-layer`),Xr=document.getElementById(`ghost-line`);function Zr(e){return s(e||`b`,c(J.boxes,J.texts||[],J.lines||[],J.strokes||[]))}var Qr=e=>J.texts.find(t=>t.id===e),$r=e=>J.lines.find(t=>t.id===e),ei=e=>(J.strokes||[]).find(t=>t.id===e),ti=()=>le(J);function $(e){}function ni(){let e=Sn();return j(),M(),w(),e}lt({canvas:Q,lineLayer:Jr,strokeLayer:Yr,edgeLayer:qr,currentMap:()=>J,graph:()=>q,currentPath:()=>zr,selected:Y,selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},dropTargetId:()=>Wr,dropTargetHandle:()=>Gr,nearTargetId:()=>Kr,attachBoxHandlers:cr,attachTextHandlers:or,attachLineHandlers:sr,isBrushMode:()=>E(),setStatus:$}),ht({canvas:Q,currentMap:()=>J,link:()=>X,nearTargetId:()=>Kr,setNearTargetId:e=>{Kr=e}}),Kt({getGraph:()=>q,getCurrentPath:()=>zr,setCurrentPath:e=>{zr=e},setCurrentMap:e=>{J=e},clearSelected:()=>Y.clear(),clearSelectedEdge:()=>{Z=null},renderAll:()=>j()}),$t(),ir({canvas:Q,lineLayer:Jr,strokeLayer:Yr,ghostLine:Xr,currentMap:()=>J,findTextById:Qr,findLineById:$r,findStrokeById:ei,selected:Y,selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},setDrag:e=>{Ur=e},setLink:e=>{X=e},cloneSelection:ni,setStatus:$}),bt({canvas:Q,currentMap:()=>J,setCurrentMap:e=>{J=e},graph:()=>q,setGraph:e=>{q=e},currentPath:()=>zr,ensureMap:qt,selected:Y,selectedEdge:()=>Z,clearSelectedEdge:()=>{Z=null},mintId:Zr,setStatus:$}),Ie({canvas:Q,getCurrentMap:()=>J,setCurrentMap:e=>{J=e},getCurrentPath:()=>zr,getGraph:()=>q,setGraph:e=>{q=e},ensureMap:qt,selected:Y,renderAll:()=>j(),setStatus:$}),ln({getGraph:()=>q,setGraph:e=>{q=e},serializeGraph:ne,setCurrentPath:(e,t)=>Yt(e,t),getCurrentPath:()=>zr,readPathFromURL:Jt,setStatus:$,clearSelected:()=>Y.clear(),clearSelectedEdge:()=>{Z=null}}),de({scheduleSave:()=>dn()}),xn({currentMap:()=>J,selected:Y,findTextById:Qr,findLineById:$r,mintId:Zr}),Je({canvas:Q,currentMap:()=>J,selected:Y,renderAll:()=>j()}),it(),zt({selected:Y,currentMap:()=>J,findTextById:Qr,findLineById:$r,mintId:Zr,renderAll:()=>j(),deleteSelection:()=>wt(),setStatus:$,clearSelectedEdge:()=>{Z=null}}),ye({mintId:()=>Zr(`s`),strokeLayer:()=>Yr,currentMap:()=>J,afterCommit:()=>ut(),setStatus:$}),Dt({lineLayer:()=>Jr,setStatus:$}),Rn({canvas:Q,ghostLine:Xr,currentMap:()=>J,mintId:()=>Zr(),selected:Y,lastCursor:Br,drag:()=>Ur,setDrag:e=>{Ur=e},link:()=>X,setLink:e=>{X=e},pan:()=>Hr,setPan:e=>{Hr=e},band:()=>Vr,setBand:e=>{Vr=e},selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},dropTargetId:()=>Wr,setDropTargetId:e=>{Wr=e},dropTargetHandle:()=>Gr,setDropTargetHandle:e=>{Gr=e},setStatus:$}),Jn(),Sr({canvas:Q,ghostLine:Xr,currentMap:()=>J,findTextById:Qr,mintId:()=>Zr(),selected:Y,drag:()=>Ur,setDrag:e=>{Ur=e},pan:()=>Hr,setPan:e=>{Hr=e},link:()=>X,setLink:e=>{X=e},dropTargetId:()=>Wr,setDropTargetId:e=>{Wr=e},dropTargetHandle:()=>Gr,setDropTargetHandle:e=>{Gr=e},selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e}}),Ar(),wn({canvas:Q,ghostLine:Xr,currentMap:()=>J,findTextById:Qr,selected:Y,selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},link:()=>X,clearLink:()=>{X=null},setDropTargetId:e=>{Wr=e},setDropTargetHandle:e=>{Gr=e},clearProximity:()=>_t(),lastCursor:Br,setStatus:$}),Fn(),oe();var ri=window.matchMedia(`(pointer: coarse)`),ii=()=>document.body.classList.toggle(`touch-input`,ri.matches);ri.addEventListener(`change`,ii),ii(),Rr(),document.getElementById(`upBtn`).addEventListener(`click`,Zt),document.getElementById(`downloadBtn`).addEventListener(`click`,_n),document.getElementById(`reshareBtn`).addEventListener(`click`,vn),window.addEventListener(`mousedown`,e=>{e.button===1&&e.preventDefault()},!0),window.addEventListener(`resize`,()=>ti()),un(),fetch(`/version`).then(e=>e.ok?e.text():``).then(e=>{let t=e.trim();t&&(document.getElementById(`version`).textContent=`flowgo `+t)}).catch(()=>{}); +`);for(let t of e.strokes??[]){if((t.points?.length??0)<2)continue;let e=t.points.map(e=>`${v(e[0])},${v(e[1])}`).join(` `),n=y(t.palette)?` ${t.palette}`:``;r+=`stroke ${t.id}${n} ${e}\n`}}),r},re=()=>{let e=document.getElementById(`helpOverlay`);if(!e)throw Error(`helpOverlay missing from DOM`);return e},ie=e=>{re().classList.toggle(`hidden`,!e)},ae=()=>!re().classList.contains(`hidden`),oe=()=>{let e=document.getElementById(`helpBtn`),t=document.getElementById(`helpClose`);e?.addEventListener(`click`,()=>ie(!0)),t?.addEventListener(`click`,()=>ie(!1)),re().addEventListener(`mousedown`,e=>{e.target===re()&&ie(!1)})},b={x:0,y:0},x=e=>e-b.x,S=e=>e-b.y,se=e=>{let t=document.getElementById(e);if(!t)throw Error(`viewport: missing #${e}`);return t},ce=()=>{let e=b.x,t=b.y;se(`canvas`).style.transform=`translate(${e}px, ${t}px)`;for(let n of[`line-layer`,`stroke-layer`,`edge-layer`])se(n).setAttribute(`transform`,`translate(${e} ${t})`);se(`ghost-line`).setAttribute(`transform`,`translate(${e} ${t})`),se(`bg-layer`).style.backgroundPosition=`${e}px ${t}px`},le=e=>{let t=e.boxes??[],n=t.find(e=>e.anchor)??t.find(e=>e.id===`b1`);if(n&&n.id){let e=document.querySelector(`.box[data-id="${n.id}"]`),t=n.x+(e?e.offsetWidth/2:0),r=n.y+(e?e.offsetHeight/2:0);b.x=window.innerWidth/2-t,b.y=window.innerHeight/2-r,ce();return}let r=[];for(let t of e.boxes??[])r.push([t.x,t.y]);for(let t of e.texts??[])r.push([t.x,t.y]);for(let t of e.lines??[]){r.push([t.x1,t.y1]),r.push([t.x2,t.y2]);for(let[e,n]of t.mids??[])r.push([e,n])}if(r.length===0)b.x=0,b.y=0;else{let e=1/0,t=1/0,n=-1/0,i=-1/0;for(let[a,o]of r)an&&(n=a),o>i&&(i=o);let a=(e+n)/2,o=(t+i)/2;b.x=window.innerWidth/2-a,b.y=window.innerHeight/2-o}ce()},ue=null,de=e=>{ue=e},C=e=>{if(!ue)throw Error(`mutations: wireMutations() not called`);if(ue.scheduleSave(),ue.onMutate){let t=ue.getMapPath?ue.getMapPath():`/`;ue.onMutate({kind:e,mapPath:t})}},fe=()=>C(`box`),pe=()=>C(`edge`),me=()=>C(`text`),he=()=>C(`line`),ge=()=>C(`stroke`),w=()=>C(`currentMap`),_e=()=>C(`doc`),ve=null,ye=e=>{ve=e},be=()=>{if(!ve)throw Error(`brush: wireBrush() not called`);return ve},xe=!1,T=null,Se=1,Ce=()=>xe,we=()=>T!==null,Te={1:`#333`,2:`#fff`,3:`#b91c1c`,4:`#c2410c`,5:`#a16207`,6:`#15803d`,7:`#1d4ed8`,8:`#6d28d9`,9:`#374151`},Ee=e=>`url("data:image/svg+xml;utf8,${``.replace(/#/g,`%23`)}") 2 22, crosshair`,De=null,Oe=()=>{if(!xe||Se===1){De&&(De.textContent=``);return}De||(De=document.createElement(`style`),De.id=`brush-cursor-dynamic`,document.head.appendChild(De));let e=Ee(Se);De.textContent=`body.brush-mode,body.brush-mode #bg-layer,body.brush-mode #canvas,body.brush-mode .box,body.brush-mode .text-item { cursor: ${e}; }`},ke=e=>{xe!==e&&(xe=e,document.body.classList.toggle(`brush-mode`,xe),Oe(),be().setStatus(xe?`brush mode — drag to paint, V to exit`:`select mode`))},Ae=e=>{e<1||e>9||(Se=e,xe&&Oe())},je=e=>Math.round(e*100)/100,Me=e=>e.map(e=>`${e[0]},${e[1]}`).join(` `),Ne=(e,t)=>{let n=je(x(e)),r=je(S(t)),i=be().mintId(),a=`http://www.w3.org/2000/svg`,o=document.createElementNS(a,`g`),s=`stroke-group`+(Se>=2?` palette-${Se}`:``);o.setAttribute(`class`,s),o.dataset.id=i;let c=document.createElementNS(a,`polyline`);c.setAttribute(`class`,`stroke-line`),c.setAttribute(`points`,`${n},${r}`),o.appendChild(c),be().strokeLayer().appendChild(o),T={id:i,palette:Se,points:[[n,r]],polyEl:c}},Pe=(e,t)=>{if(!T)return;let n=je(x(e)),r=je(S(t)),i=T.points[T.points.length-1];Math.hypot(n-i[0],r-i[1])<2||(T.points.push([n,r]),T.polyEl.setAttribute(`points`,Me(T.points)))},Fe=()=>{if(!T)return;let e=i(T.points,1.5);if(e.length>=2){let t=be().currentMap(),n={id:T.id,points:e};T.palette>=2&&(n.palette=T.palette),(t.strokes??=[]).push(n),ge()}else{let e=T.polyEl.parentNode;e&&e.parentNode&&e.parentNode.removeChild(e)}T=null,be().afterCommit()},Ie=null,Le=()=>{if(!Ie)throw Error(`edit: wireEdit() not called`);return Ie},Re=e=>{Ie=e},E=null,ze=()=>E!==null,Be=e=>r(e.innerText??e.textContent??``,{maxLength:500}).label,Ve=(e,t)=>{if(E)return;E=e,e.contentEditable=`true`,e.textContent=t.label,e.focus();let n=document.createRange();n.selectNodeContents(e);let r=window.getSelection();r?.removeAllRanges(),r?.addRange(n);let i=n=>{e.removeEventListener(`blur`,a),e.removeEventListener(`keydown`,o),e.contentEditable=`false`,E=null;let r=Be(e);n&&r&&r!==t.label&&(t.label=r,me()),e.textContent=t.label},a=()=>i(!0),o=t=>{t.key===`Enter`&&!t.shiftKey?(t.preventDefault(),e.blur()):t.key===`Escape`&&(t.preventDefault(),i(!1)),t.stopPropagation()};e.addEventListener(`blur`,a),e.addEventListener(`keydown`,o)},D=(e,t,n)=>{if(E)return;let i=n?.cancelDeletes??!1,a=e.querySelector(`.box-label`);if(!a){Le().renderAll();let e=Le().canvas.querySelector(`.box[data-id="${t.id}"]`);e&&D(e,t,n);return}E=e,e.contentEditable=`true`,a.textContent=t.label,e.focus();let o=document.createRange();o.selectNodeContents(a);let s=window.getSelection();s?.removeAllRanges(),s?.addRange(o);let c=n=>{e.removeEventListener(`blur`,l),e.removeEventListener(`keydown`,u),e.contentEditable=`false`,E=null;let a=r(e.innerText??e.textContent??``,{maxLength:500});a.truncated&&Le().setStatus(`label truncated to 500 characters`);let o=a.label;if(!n&&i){let e=Le(),n=e.getCurrentMap();n.boxes=n.boxes.filter(e=>e.id!==t.id),n.edges=n.edges.filter(e=>e.from!==t.id&&e.to!==t.id);let r=e.getCurrentPath(),i=r===`/`?`/`+t.id:r+`/`+t.id,a=e.getGraph();a.maps=a.maps.filter(e=>e.path!==i&&!e.path.startsWith(i+`/`)),e.setGraph(a),e.setCurrentMap(e.ensureMap(r)),e.selected.delete(t.id),_e(),e.renderAll(),e.setStatus(`cancelled`);return}n&&o&&o!==t.label&&(t.label=o,fe()),Le().renderAll()},l=()=>c(!0),u=t=>{t.key===`Enter`&&!t.shiftKey?(t.preventDefault(),e.blur()):t.key===`Escape`&&(t.preventDefault(),c(!1)),t.stopPropagation()};e.addEventListener(`blur`,l),e.addEventListener(`keydown`,u)},He=(e,t)=>({x:t.x,y:t.y,width:e.offsetWidth,height:e.offsetHeight}),Ue=(e,t,n)=>d(He(e,t),n),We=(e,t,n,r)=>f(He(t,e),[n,r]),Ge=(e,t,n,r,i,a)=>{let o=document.elementsFromPoint(i,a);for(let t of o){let n=t;if(n.classList?.contains(`handle`)&&n.parentElement===e){let e=n.dataset.handle;if(e)return e}}return We(t,e,n,r)},Ke=(e,t,n,r,i)=>p(He(t,e),n,[r,i]),qe=null,O=null,Je=()=>{if(!qe)throw Error(`align: wireAlign() not called`);return qe},Ye=e=>{qe=e},Xe=()=>{let e=Je(),t=e.currentMap(),n=[];for(let r of e.selected){let i=t.boxes.find(e=>e.id===r);if(i){let t=e.canvas.querySelector(`.box[data-id="${r}"]`);t&&n.push({ref:i,width:t.offsetWidth,height:t.offsetHeight});continue}let a=(t.texts??[]).find(e=>e.id===r);if(a){let t=e.canvas.querySelector(`.text-item[data-id="${r}"]`);t&&n.push({ref:a,width:t.offsetWidth,height:t.offsetHeight})}}return n},Ze=e=>{let t=[...e].sort((e,t)=>e.ref.x-t.ref.x);for(let e=1;e{let t=[...e].sort((e,t)=>e.ref.y-t.ref.y);for(let e=1;e{if(e.length<2)return!1;if(t===`horizontal`){let t=e.reduce((e,t)=>e+t.ref.y+t.height/2,0)/e.length;for(let n of e)n.ref.y=Math.round(t-n.height/2);if(Ze(e)){let t=[...e].sort((e,t)=>e.ref.x-t.ref.x||e.ref.y-t.ref.y),n=t[0].ref.x;for(let e of t)e.ref.x=Math.round(n),n=e.ref.x+e.width+20}}else{let t=e.reduce((e,t)=>e+t.ref.x+t.width/2,0)/e.length;for(let n of e)n.ref.x=Math.round(t-n.width/2);if(Qe(e)){let t=[...e].sort((e,t)=>e.ref.y-t.ref.y||e.ref.x-t.ref.x),n=t[0].ref.y;for(let e of t)e.ref.y=Math.round(n),n=e.ref.y+e.height+20}}return!0},et=e=>{let t=Je();$e(Xe(),e)&&(t.renderAll(),w())},tt=`http://www.w3.org/2000/svg`,nt=(e,t)=>{let n=document.createElementNS(tt,`svg`);n.setAttribute(`viewBox`,`0 0 16 16`),n.setAttribute(`width`,`16`),n.setAttribute(`height`,`16`),n.setAttribute(`aria-hidden`,`true`);let r=document.createElementNS(tt,`line`);r.setAttribute(`x1`,String(e.x1)),r.setAttribute(`y1`,String(e.y1)),r.setAttribute(`x2`,String(e.x2)),r.setAttribute(`y2`,String(e.y2)),r.setAttribute(`stroke`,`currentColor`),r.setAttribute(`stroke-width`,`1`),r.setAttribute(`stroke-dasharray`,`1.5 1.5`),r.setAttribute(`opacity`,`0.55`),n.appendChild(r);for(let e of t){let t=document.createElementNS(tt,`rect`);t.setAttribute(`x`,String(e.x)),t.setAttribute(`y`,String(e.y)),t.setAttribute(`width`,String(e.w)),t.setAttribute(`height`,String(e.h)),t.setAttribute(`fill`,`currentColor`),n.appendChild(t)}return n},rt=()=>nt({x1:0,y1:8,x2:16,y2:8},[{x:2,y:3,w:5,h:10},{x:9,y:5,w:5,h:6}]),it=()=>nt({x1:8,y1:0,x2:8,y2:16},[{x:3,y:2,w:10,h:5},{x:5,y:9,w:6,h:5}]),at=()=>{let e=Je();O=document.createElement(`div`),O.id=`alignToolbar`,O.style.display=`none`;let t=(e,t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.appendChild(e),r.title=t,r.setAttribute(`aria-label`,t),r.addEventListener(`mousedown`,e=>e.stopPropagation()),r.addEventListener(`touchstart`,e=>e.stopPropagation(),{passive:!0}),r.addEventListener(`click`,e=>{e.stopPropagation(),et(n)}),r};O.appendChild(t(rt(),`Align on a horizontal line`,`horizontal`)),O.appendChild(t(it(),`Align on a vertical line`,`vertical`)),e.canvas.appendChild(O)},ot=()=>{if(!O||!qe)return;let e=Xe();if(e.length<2){O.style.display=`none`;return}O.parentNode!==qe.canvas&&qe.canvas.appendChild(O);let t=1/0,n=1/0,r=-1/0;for(let i of e)t=Math.min(t,i.ref.x),n=Math.min(n,i.ref.y),r=Math.max(r,i.ref.x+i.width);O.style.display=`flex`,O.style.left=t+(r-t)/2+`px`,O.style.top=n+`px`},st=e=>{let t=e.mids??[],n=[[e.x1,e.y1],...t,[e.x2,e.y2]],r=e.style??1;if(r===2&&t.length>0){let n=`M ${e.x1} ${e.y1}`;for(let e=0;e=Math.abs(o-i)?e+=` L ${a} ${i} L ${a} ${o}`:e+=` L ${r} ${o} L ${a} ${o}`}return e}let i=`M ${n[0][0]} ${n[0][1]}`;for(let e=1;e{if(!ct)throw Error(`render: wireRender() not called`);return ct},ut=e=>{ct=e},k=`http://www.w3.org/2000/svg`,A=()=>{let n=lt();n.canvas.innerHTML=``;let r=n.currentMap(),i=n.graph(),a=n.currentPath();for(let o of r.boxes){let r=document.createElement(`div`),s=e(o.palette),c=t(o.font);r.className=`box`+(te(i,a,o.id)?` has-submap`:``)+(s===1?``:` palette-`+s)+(c===1?``:` font-`+c),r.dataset.id=o.id,r.style.left=o.x+`px`,r.style.top=o.y+`px`;let u=document.createElement(`span`);u.className=`box-label`,u.textContent=o.label,r.appendChild(u);for(let e of l){let t=document.createElement(`div`);t.className=`handle h-`+e,t.dataset.handle=e,r.appendChild(t)}n.canvas.appendChild(r),n.attachBoxHandlers(r,o)}for(let i of r.texts){let r=document.createElement(`div`),a=e(i.palette),o=t(i.font);r.className=`text-item`+(a===1?``:` palette-`+a)+(o===1?``:` font-`+o),r.dataset.id=i.id,r.style.left=i.x+`px`,r.style.top=i.y+`px`,r.textContent=i.label,n.canvas.appendChild(r),n.attachTextHandlers(r,i)}j(),ft(),dt(),M()},dt=()=>{let t=lt();t.strokeLayer.innerHTML=``;let n=t.currentMap();for(let r of n.strokes??[]){if(!r.points||r.points.length<2)continue;let n=o(r.points),i=document.createElementNS(k,`g`),a=e(r.palette);i.setAttribute(`class`,`stroke-group`+(a===1?``:` palette-`+a)+(t.selected.has(r.id)?` selected`:``)),i.dataset.id=r.id;let s=document.createElementNS(k,`path`);s.setAttribute(`class`,`stroke-hit`),s.setAttribute(`d`,n),s.setAttribute(`fill`,`none`),s.setAttribute(`stroke`,`transparent`),s.setAttribute(`stroke-width`,`12`),i.appendChild(s);let c=document.createElementNS(k,`path`);c.setAttribute(`class`,`stroke-line`),c.setAttribute(`d`,n),c.setAttribute(`fill`,`none`),i.appendChild(c),i.addEventListener(`mousedown`,e=>{t.isBrushMode()||(e.stopPropagation(),e.shiftKey||t.selected.clear(),t.selected.add(r.id),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j(),dt())}),t.strokeLayer.appendChild(i)}},ft=()=>{let t=lt();t.lineLayer.innerHTML=``;let n=t.currentMap();for(let r of n.lines){let n=document.createElementNS(k,`g`),i=e(r.palette);n.setAttribute(`class`,`line-group`+(i===1?``:` palette-`+i)+(t.selected.has(r.id)?` selected`:``)),n.dataset.id=r.id;let a=st(r),o=document.createElementNS(k,`path`);o.setAttribute(`class`,`line-hit`),o.setAttribute(`d`,a),o.setAttribute(`fill`,`none`),o.setAttribute(`stroke`,`transparent`),o.setAttribute(`stroke-width`,`12`),n.appendChild(o);let s=document.createElementNS(k,`path`);s.setAttribute(`class`,`line-line`),s.setAttribute(`d`,a),s.setAttribute(`fill`,`none`),n.appendChild(s);let c=document.createElementNS(k,`circle`);c.setAttribute(`class`,`line-handle`),c.setAttribute(`cx`,String(r.x1)),c.setAttribute(`cy`,String(r.y1)),c.setAttribute(`r`,`6`),c.dataset.endpoint=`1`,n.appendChild(c);let l=document.createElementNS(k,`circle`);l.setAttribute(`class`,`line-handle`),l.setAttribute(`cx`,String(r.x2)),l.setAttribute(`cy`,String(r.y2)),l.setAttribute(`r`,`6`),l.dataset.endpoint=`2`,n.appendChild(l);let u=[];for(let e=0;e<(r.mids?.length??0);e++){let[t,i]=r.mids[e],a=document.createElementNS(k,`circle`);a.setAttribute(`class`,`line-handle line-handle-mid`),a.setAttribute(`cx`,String(t)),a.setAttribute(`cy`,String(i)),a.setAttribute(`r`,`6`),a.dataset.endpoint=`m`,a.dataset.midIndex=String(e),n.appendChild(a),u.push(a)}t.attachLineHandlers(n,s,o,c,l,u,r),t.lineLayer.appendChild(n)}},j=()=>{let e=lt(),t=e.dropTargetId(),n=e.dropTargetHandle(),r=e.nearTargetId();for(let i of e.canvas.querySelectorAll(`.box`)){let a=i.dataset.id===t;i.classList.toggle(`selected`,e.selected.has(i.dataset.id??``)),i.classList.toggle(`drop-target`,a),i.classList.toggle(`proximity-target`,i.dataset.id===r);for(let e of i.querySelectorAll(`.handle`))e.classList.toggle(`target`,a&&n!==null&&e.dataset.handle===n)}for(let t of e.canvas.querySelectorAll(`.text-item`))t.classList.toggle(`selected`,e.selected.has(t.dataset.id??``));for(let t of e.lineLayer.querySelectorAll(`.line-group`))t.classList.toggle(`selected`,e.selected.has(t.dataset.id??``));for(let t of e.strokeLayer.querySelectorAll(`.stroke-group`))t.classList.toggle(`selected`,e.selected.has(t.dataset.id??``));ot()},M=()=>{let t=lt();t.edgeLayer.innerHTML=``;let n=t.currentMap(),r=t.selectedEdge();for(let i of n.edges){let a=n.boxes.find(e=>e.id===i.from),o=n.boxes.find(e=>e.id===i.to);if(!a||!o)continue;let s=t.canvas.querySelector(`.box[data-id="${a.id}"]`),c=t.canvas.querySelector(`.box[data-id="${o.id}"]`);if(!s||!c)continue;let l=a.x+s.offsetWidth/2,u=a.y+s.offsetHeight/2,d=o.x+c.offsetWidth/2,f=o.y+c.offsetHeight/2,[p,m]=Ke(a,s,i.fromHandle,d,f),[h,g]=Ke(o,c,i.toHandle,l,u),ee=document.createElementNS(k,`g`),te=e(i.palette);ee.setAttribute(`class`,`edge-group`+(te===1?``:` palette-`+te)+(i===r?` selected`:``));let _=document.createElementNS(k,`line`);_.setAttribute(`class`,`edge-hit`),_.setAttribute(`x1`,String(p)),_.setAttribute(`y1`,String(m)),_.setAttribute(`x2`,String(h)),_.setAttribute(`y2`,String(g)),_.setAttribute(`stroke`,`transparent`),_.setAttribute(`stroke-width`,`12`),ee.appendChild(_);let v=document.createElementNS(k,`line`);v.setAttribute(`class`,`edge-line`),v.setAttribute(`x1`,String(p)),v.setAttribute(`y1`,String(m)),v.setAttribute(`x2`,String(h)),v.setAttribute(`y2`,String(g)),ee.appendChild(v),ee.addEventListener(`mousedown`,e=>{e.stopPropagation(),t.setSelectedEdge(i),t.selected.clear(),j(),M(),t.setStatus(`edge selected — press Delete to remove`)}),t.edgeLayer.appendChild(ee)}},pt=60,mt=null,ht=()=>{if(!mt)throw Error(`render: wireProximity() not called`);return mt},gt=e=>{mt=e},_t=(e,t)=>{let n=ht(),r=null,i=1/0,a=n.link();for(let o of n.currentMap().boxes){if(a&&o.id===a.fromId)continue;let s=n.canvas.querySelector(`.box[data-id="${o.id}"]`);if(!s)continue;let c=o.x,l=o.y,u=o.x+s.offsetWidth,d=o.y+s.offsetHeight,f=Math.max(c-e,0,e-u),p=Math.max(l-t,0,t-d),m=Math.hypot(f,p);m{let e=ht();e.nearTargetId()!==null&&(e.setNearTargetId(null),j())},yt=null,bt=()=>{if(!yt)throw Error(`factories: wireFactories() not called`);return yt},xt=e=>{yt=e},St=(e,t,n)=>{let r=bt(),i=r.mintId(),a={id:i,label:`new`,x:e,y:t};r.currentMap().boxes.push(a),A();let o=r.canvas.querySelector(`.box[data-id="${i}"]`);o&&n&&(a.x=n.x-o.offsetWidth/2,a.y=n.y-o.offsetHeight/2,o.style.left=a.x+`px`,o.style.top=a.y+`px`),fe(),o&&(r.selected.clear(),r.selected.add(i),r.selectedEdge()&&(r.clearSelectedEdge(),M()),j(),D(o,a))},Ct=(e,t)=>{let n=bt(),r=n.mintId(`t`),i={id:r,label:`text`,x:e,y:t};n.currentMap().texts.push(i),A();let a=n.canvas.querySelector(`.text-item[data-id="${r}"]`);a&&(i.x=e-a.offsetWidth/2,i.y=t-a.offsetHeight/2,a.style.left=i.x+`px`,a.style.top=i.y+`px`,n.selected.clear(),n.selected.add(r),n.selectedEdge()&&(n.clearSelectedEdge(),M()),j(),Ve(a,i)),me()},wt=(e,t,n,r)=>{let i=bt(),a=i.mintId(`l`),o={id:a,x1:e,y1:t,x2:n,y2:r};i.currentMap().lines.push(o),i.selected.clear(),i.selected.add(a),i.selectedEdge()&&(i.clearSelectedEdge(),M()),A(),he()},Tt=()=>{let e=bt();if(e.selected.size===0){e.setStatus(`nothing selected`);return}let t=e.selected,n=e.currentMap(),r=Array.from(t).filter(e=>n.boxes.some(t=>t.id===e));n.boxes=n.boxes.filter(e=>!t.has(e.id)),n.edges=n.edges.filter(e=>!t.has(e.from)&&!t.has(e.to)),n.texts=n.texts.filter(e=>!t.has(e.id)),n.lines=n.lines.filter(e=>!t.has(e.id)),n.strokes=(n.strokes??[]).filter(e=>!t.has(e.id));let i=e.currentPath(),a=e.graph();for(let e of r){let t=i===`/`?`/`+e:i+`/`+e;a.maps=a.maps.filter(e=>e.path!==t&&!e.path.startsWith(t+`/`))}e.setGraph(a),e.setCurrentMap(e.ensureMap(i)),t.clear(),_e(),A()},Et=null,Dt=()=>{if(!Et)throw Error(`line: wireLine() not called`);return Et},Ot=e=>{Et=e},kt=!1,N=null,P=null,F=null,I=()=>kt,At=()=>N!==null,jt=()=>{if(F)return F;let e=document.createElementNS(`http://www.w3.org/2000/svg`,`line`);return e.setAttribute(`class`,`line-preview`),e.setAttribute(`stroke`,`#07f`),e.setAttribute(`stroke-width`,`2`),e.setAttribute(`stroke-dasharray`,`5 4`),e.style.pointerEvents=`none`,Dt().lineLayer().appendChild(e),F=e,e},Mt=()=>{F&&F.parentNode&&F.parentNode.removeChild(F),F=null},L=e=>Math.round(e*100)/100,Nt=(e,t,n)=>{let r=t-e.x,i=n-e.y,a=Math.hypot(r,i);if(a<.001)return{x:t,y:n};let o=10*Math.PI/180,s=Math.round(Math.atan2(i,r)/o)*o;return{x:L(e.x+Math.cos(s)*a),y:L(e.y+Math.sin(s)*a)}},Pt=e=>{kt!==e&&(kt=e,document.body.classList.toggle(`line-mode`,kt),kt||(N=null,P=null,Mt()),Dt().setStatus(kt?`line mode — click start, click end · L or Escape to exit`:`select mode`))},Ft=()=>{N&&(N=null,P=null,Mt())},It=(e,t,n=!1)=>{let r=L(x(e)),i=L(S(t));if(!N){N={x:r,y:i},P={x:e,y:t};let n=jt();n.setAttribute(`x1`,String(r)),n.setAttribute(`y1`,String(i)),n.setAttribute(`x2`,String(r)),n.setAttribute(`y2`,String(i));return}let a=N,o=n?Nt(a,r,i):{x:r,y:i};N=null,P=null,Mt(),!(Math.hypot(o.x-a.x,o.y-a.y)<2)&&wt(a.x,a.y,o.x,o.y)},Lt=(e,t,n=!1)=>{if(!N||!P)return;let r=e-P.x,i=t-P.y;if(Math.hypot(r,i)<4)return;let a=L(x(e)),o=L(S(t)),s=N,c=n?Nt(s,a,o):{x:a,y:o};N=null,P=null,Mt(),!(Math.hypot(c.x-s.x,c.y-s.y)<2)&&wt(s.x,s.y,c.x,c.y)},Rt=(e,t,n=!1)=>{if(!N||!F)return;let r=L(x(e)),i=L(S(t)),a=n?Nt(N,r,i):{x:r,y:i};F.setAttribute(`x2`,String(a.x)),F.setAttribute(`y2`,String(a.y))},zt=null,R=null,Bt=e=>{zt=e},Vt=()=>{if(!zt)throw Error(`clipboard: wireClipboard() not called`);return zt},Ht=()=>{let{selected:e,currentMap:t,findTextById:n,findLineById:r}=Vt();if(e.size===0)return!1;let i=t(),a=[],o=[],s=[],c=[],l=new Set;for(let t of e){let e=i.boxes.find(e=>e.id===t);if(e){let t={id:e.id,label:e.label,x:e.x,y:e.y};e.palette&&(t.palette=e.palette),e.font&&(t.font=e.font),a.push(t),l.add(e.id);continue}let c=n(t);if(c){let e={id:c.id,label:c.label,x:c.x,y:c.y};c.palette&&(e.palette=c.palette),c.font&&(e.font=c.font),o.push(e);continue}let u=r(t);if(u){let e={id:u.id,x1:u.x1,y1:u.y1,x2:u.x2,y2:u.y2};u.palette&&(e.palette=u.palette),u.style&&(e.style=u.style),u.mids?.length&&(e.mids=u.mids.map(([e,t])=>[e,t])),s.push(e)}}for(let e of i.edges)l.has(e.from)&&l.has(e.to)&&c.push({from:e.from,fromHandle:e.fromHandle??``,to:e.to,toHandle:e.toHandle??``});if(!a.length&&!o.length&&!s.length)return!1;R={boxes:a,texts:o,lines:s,edges:c,pasteOffset:0};let u=[...a,...o].sort((e,t)=>e.y-t.y||e.x-t.x).map(e=>e.label);return u.length&&typeof navigator<`u`&&navigator.clipboard&&navigator.clipboard.writeText(u.join(` +`)).catch(()=>{}),!0},Ut=()=>{let{selected:e,deleteSelection:t,setStatus:n}=Vt();if(!Ht()){n(`nothing to cut`);return}let r=e.size;t(),n(`cut `+r+` items`)},Wt=()=>{let{selected:e,currentMap:t,mintId:n,renderAll:r,setStatus:i,clearSelectedEdge:a}=Vt();if(!R){i(`clipboard is empty`);return}R.pasteOffset+=20;let o=R.pasteOffset,s=R.pasteOffset,c=new Map;e.clear(),a();let l=t();for(let t of R.boxes){let r=n(`b`);c.set(t.id,r);let i={id:r,label:t.label,x:t.x+o,y:t.y+s};l.boxes.push(i),e.add(r)}for(let t of R.texts){let r=n(`t`);c.set(t.id,r);let i={id:r,label:t.label,x:t.x+o,y:t.y+s};t.palette&&(i.palette=t.palette),t.font&&(i.font=t.font),l.texts.push(i),e.add(r)}for(let t of R.lines){let r=n(`l`);c.set(t.id,r);let i={id:r,x1:t.x1+o,y1:t.y1+s,x2:t.x2+o,y2:t.y2+s};t.palette&&(i.palette=t.palette),t.style&&(i.style=t.style),t.mids?.length&&(i.mids=t.mids.map(([e,t])=>[e+o,t+s])),l.lines.push(i),e.add(r)}for(let e of R.edges){let t=c.get(e.from),n=c.get(e.to);if(!t||!n)continue;let r={from:t,to:n};e.fromHandle&&(r.fromHandle=e.fromHandle),e.toHandle&&(r.toHandle=e.toHandle),l.edges.push(r)}w(),r(),i(`pasted `+e.size+` items`)},Gt=null,Kt=()=>{if(!Gt)throw Error(`navigation: wireNavigation() not called`);return Gt},qt=e=>{Gt=e},Jt=e=>{let t=Kt().getGraph(),n=t.maps.find(t=>t.path===e);return n||(n={path:e,boxes:[],edges:[]},t.maps.push(n)),n.boxes??=[],n.edges??=[],n.texts??=[],n.lines??=[],n.strokes??=[],n},Yt=()=>{let e=location.hash||``;return e.startsWith(`#`)&&(e=e.slice(1)),e?(e.startsWith(`/`)||(e=`/`+e),e):`/`},Xt=(e,t)=>{let n=t?.keepViewport??!1,r=Kt();r.setCurrentPath(e),r.setCurrentMap(Jt(e)),r.clearSelected(),r.clearSelectedEdge(),r.renderAll(),$t(),n||le(Jt(e));let i=`#`+e;location.hash!==i&&history.pushState(null,``,i)},Zt=e=>{let t=Kt().getCurrentPath();Xt(t===`/`?`/`+e:t+`/`+e)},Qt=()=>{let e=Kt().getCurrentPath();if(e===`/`)return;let t=e.split(`/`).filter(Boolean);t.pop(),Xt(t.length?`/`+t.join(`/`):`/`)},$t=()=>{let e=Kt(),t=e.getGraph(),n=e.getCurrentPath(),r=document.getElementById(`path`);if(!r)return;r.innerHTML=``;let i=n===`/`?[]:n.split(`/`).filter(Boolean);r.style.display=i.length===0?`none`:``;let a=document.getElementById(`toolbar`),o=document.body.classList.contains(`snapshot-mode`);if(a&&(a.style.display=i.length===0&&!o?`none`:``),i.length===0){let e=document.getElementById(`upBtn`);e&&(e.style.display=`none`);return}let s=document.createElement(`span`);s.className=`seg`,s.textContent=`/`,s.addEventListener(`click`,()=>Xt(`/`)),r.appendChild(s);let c=``,l=`/`;i.forEach((e,n)=>{if(n>0){let e=document.createElement(`span`);e.className=`sep`,e.textContent=`/`,r.appendChild(e)}c+=`/`+e;let a=c,o=((t.maps||[]).find(e=>e.path===l)?.boxes??[]).find(t=>t.id===e),s=o?.label&&o.label.trim()||e;l=a;let u=document.createElement(`span`);u.className=`seg`,u.textContent=s,u.title=e,nXt(a)):(u.style.fontWeight=`bold`,u.style.cursor=`default`),r.appendChild(u)});let u=document.getElementById(`upBtn`);u&&(u.style.display=n===`/`?`none`:``)},en=()=>{window.addEventListener(`hashchange`,()=>{let e=Yt();e!==Kt().getCurrentPath()&&Xt(e)})},tn=100,nn=200,rn=null,z=()=>{if(!rn)throw Error(`persistence: wirePersistence() not called`);return rn},B=null,V=[],an=[],on=null,sn=location.pathname.match(/^\/m\/([\w-]+)\/?$/),cn=sn?sn[1]:null,ln=cn!==null,un=e=>{rn=e},dn=async()=>{let e=z(),t=null;if(ln){document.body.classList.add(`snapshot-mode`),document.getElementById(`downloadBtn`)?.style.setProperty(`display`,``),document.getElementById(`reshareBtn`)?.style.setProperty(`display`,``);try{let e=await fetch(`/api/snapshot/`+encodeURIComponent(cn));if(!e.ok)throw Error(`HTTP `+e.status);let n=await e.json();t=n.graph||n}catch(n){let r=n instanceof Error?n.message:String(n);e.setStatus(`snapshot `+cn+` not loaded: `+r),t=null}}else t=await(await fetch(`/state`)).json();(!t||!t.maps||t.maps.length===0)&&(t={maps:[{path:`/`,boxes:[],edges:[]}]}),e.setGraph(t),B=JSON.stringify(t),V=[],an=[],e.setCurrentPath(e.readPathFromURL()),e.setStatus(ln?`snapshot `+cn+` — local edits only`:`loaded`)},fn=()=>{z().setStatus(`saving…`),on&&clearTimeout(on),on=setTimeout(mn,nn)},pn=async e=>{if(ln){z().setStatus(`local edits only — use Download or Save as new share`);return}await fetch(`/save`,{method:`POST`,headers:{"Content-Type":`application/json`},body:e}),z().setStatus(`saved`)},mn=async()=>{let e=JSON.stringify(z().getGraph());B!==null&&e!==B&&(V.push(B),V.length>tn&&V.shift(),an=[]),B=e,await pn(e)},hn=e=>{let t=z(),n=JSON.parse(e);t.setGraph(n),t.clearSelected(),t.clearSelectedEdge();let r=t.getCurrentPath(),i=n.maps.some(e=>e.path===r)?r:`/`;t.setCurrentPath(i,{keepViewport:i===r})},gn=()=>{let e=z();on&&=(clearTimeout(on),null);let t=JSON.stringify(e.getGraph());if(B!==null&&t!==B&&(V.push(B),V.length>tn&&V.shift(),an=[],B=t),V.length===0){e.setStatus(`nothing to undo`);return}let n=V.pop();B!==null&&an.push(B),B=n,hn(n),pn(n),e.setStatus(`undo (`+V.length+` left)`)},_n=()=>{let e=z();if(an.length===0){e.setStatus(`nothing to redo`);return}let t=an.pop();B!==null&&V.push(B),B=t,hn(t),pn(t),e.setStatus(`redo`)},vn=()=>{let e=z(),t=e.serializeGraph(e.getGraph()),n=new Blob([t],{type:`text/plain;charset=utf-8`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=(cn??`mindmap`)+`.flowgo`,document.body.appendChild(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(r),1e3),e.setStatus(`downloaded`)},yn=async()=>{let e=z();e.setStatus(`re-sharing…`);try{let t=await fetch(`/api/snapshot`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({graph:e.getGraph()})});if(!t.ok)throw Error(`HTTP `+t.status);let n=await t.json();if(!n.url)throw Error(`response missing url`);navigator.clipboard&&navigator.clipboard.writeText(n.url).catch(()=>{}),e.setStatus(`new share: `+n.url+` (copied)`),n.id&&history.pushState(null,``,`/m/`+n.id)}catch(t){let n=t instanceof Error?t.message:String(t);e.setStatus(`re-share failed: `+n)}},bn=[],xn=e=>{let t=bn.splice(0);for(let n of t)n(e)},Sn=null,Cn=()=>{if(!Sn)throw Error(`clone: wireClone() not called`);return Sn},wn=e=>{Sn=e},Tn=()=>{let{currentMap:e,selected:t,findTextById:n,findLineById:r,mintId:i}=Cn(),a=e(),o=new Map,s=Array.from(t),c=new Set;for(let e of s){let t=a.boxes.find(t=>t.id===e);if(t){let n=i(`b`);o.set(e,n),c.add(n);let r={id:n,label:t.label,x:t.x,y:t.y};t.palette&&(r.palette=t.palette),t.font&&(r.font=t.font),a.boxes.push(r);continue}let s=n(e);if(s){let t=i(`t`);o.set(e,t);let n={id:t,label:s.label,x:s.x,y:s.y};s.palette&&(n.palette=s.palette),s.font&&(n.font=s.font),a.texts.push(n);continue}let l=r(e);if(l){let t=i(`l`);o.set(e,t);let n={id:t,x1:l.x1,y1:l.y1,x2:l.x2,y2:l.y2};l.palette&&(n.palette=l.palette),l.style&&(n.style=l.style),l.mids?.length&&(n.mids=l.mids.map(([e,t])=>[e,t])),a.lines.push(n)}}for(let e of a.edges.slice()){let t=o.get(e.from),n=o.get(e.to);if(t&&n&&c.has(t)&&c.has(n)){let r={from:t,to:n};e.fromHandle&&(r.fromHandle=e.fromHandle),e.toHandle&&(r.toHandle=e.toHandle),a.edges.push(r)}}t.clear();for(let e of o.values())t.add(e);return o},En=null,H=()=>{if(!En)throw Error(`keys: wireKeys() not called`);return En},Dn=e=>{En=e},On=(e,t)=>((e&&e>=1&&e<=9?e:1)-1+t+9)%9+1,kn=(e,t)=>t===1?e.palette?(delete e.palette,!0):!1:e.palette===t?!1:(e.palette=t,!0),An=e=>{let t=H(),n=t.currentMap();return n.boxes.find(t=>t.id===e)||t.findTextById(e)||n.lines.find(t=>t.id===e)||(n.strokes??[]).find(t=>t.id===e)},jn=()=>{let e=H();return e.selected.size>0||e.selectedEdge()!==null},Mn=e=>{let t=H();if(!jn())return!1;let n=!1;for(let r of t.selected){let t=An(r);t&&kn(t,On(t.palette,e))&&(n=!0)}let r=t.selectedEdge();return r&&kn(r,On(r.palette,e))&&(n=!0),n},Nn=e=>{let t=H();if(t.selected.size===0)return!1;let n=t.currentMap(),r=!1;for(let i of t.selected){let a=n.boxes.find(e=>e.id===i)||t.findTextById(i);if(!a)continue;let o=On(a.font,e);o===1?a.font&&(delete a.font,r=!0):a.font!==o&&(a.font=o,r=!0)}return r},Pn=()=>{let e=H();if(e.selected.size!==1){e.setStatus(`anchor needs exactly one selected box`);return}let t=e.selected.values().next().value,n=e.currentMap(),r=n.boxes.find(e=>e.id===t);if(!r){e.setStatus(`anchor only applies to boxes`);return}let i=!r.anchor;for(let e of n.boxes)e.anchor&&delete e.anchor;i&&(r.anchor=!0),fe(),A(),e.setStatus(i?`anchored `+t:`anchor cleared`)},Fn=e=>{let t=H();if(!jn())return!1;let n=!1;for(let r of t.selected){let t=An(r);t&&kn(t,e)&&(n=!0)}let r=t.selectedEdge();return r&&kn(r,e)&&(n=!0),n},In=e=>{let t=H();if(t.selected.size===0)return!1;let n=t.currentMap(),r=!1;for(let i of t.selected){let a=n.boxes.find(e=>e.id===i)||t.findTextById(i);a&&(e===1?a.font&&(delete a.font,r=!0):a.font!==e&&(a.font=e,r=!0))}return r},Ln=e=>{let t=H();if(t.selected.size===0)return!1;let n=t.currentMap(),r=!1;for(let i of t.selected){let t=n.lines.find(e=>e.id===i);t&&(e===1?t.style&&(delete t.style,r=!0):t.style!==e&&(t.style=e,r=!0))}return r},Rn=()=>{document.addEventListener(`keydown`,e=>{let t=H();if(e.key===`Escape`&&ae()){ie(!1);return}if(ze())return;let n=e.metaKey||e.ctrlKey;if(n&&!e.altKey&&(e.key===`z`||e.key===`Z`)){e.preventDefault(),e.shiftKey?_n():gn();return}if(n&&!e.altKey&&(e.key===`y`||e.key===`Y`)){e.preventDefault(),_n();return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`a`||e.key===`A`)){e.preventDefault();let n=t.currentMap();t.selected.clear();for(let e of n.boxes)t.selected.add(e.id);for(let e of n.texts??[])t.selected.add(e.id);for(let e of n.lines??[])t.selected.add(e.id);t.selectedEdge()&&(t.setSelectedEdge(null),M()),j(),t.setStatus(`selected `+t.selected.size+` items`);return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`c`||e.key===`C`)){if(window.getSelection&&String(window.getSelection()))return;e.preventDefault(),Ht()?t.setStatus(`copied `+t.selected.size+` items`):t.setStatus(`nothing to copy`);return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`x`||e.key===`X`)){e.preventDefault(),Ut();return}if(n&&!e.altKey&&!e.shiftKey&&(e.key===`v`||e.key===`V`)){e.preventDefault(),Wt();return}if(!n&&!e.altKey&&(e.key===`t`||e.key===`T`)){e.preventDefault(),Ct(x(t.lastCursor.x),S(t.lastCursor.y));return}if(!n&&!e.altKey&&(e.key===`l`||e.key===`L`)){e.preventDefault(),Pt(!I());return}if(!n&&!e.altKey&&(e.key===`b`||e.key===`B`)){e.preventDefault(),ke(!0);return}if(!n&&!e.altKey&&(e.key===`v`||e.key===`V`)){e.preventDefault(),ke(!1),Pt(!1);return}if(!n&&!e.altKey&&(e.key===`a`||e.key===`A`)){e.preventDefault(),Pn();return}if(!n&&!e.altKey&&!e.shiftKey&&/^[1-9]$/.test(e.key)){let t=parseInt(e.key,10);if(Ce()){e.preventDefault(),Ae(t);return}if(!jn())return;Fn(t)&&(e.preventDefault(),w(),A());return}if(!n&&!e.altKey&&e.shiftKey&&/^Digit[1-9]$/.test(e.code)){if(t.selected.size===0)return;let n=parseInt(e.code.slice(5),10),r=In(n),i=Ln(n);(r||i)&&(e.preventDefault(),w(),A());return}if(!n&&!e.altKey&&e.shiftKey&&(e.key===`+`||e.key===`*`||e.key===`_`||e.key===`-`)){if(t.selected.size===0)return;Nn(e.key===`_`||e.key===`-`?-1:1)&&(e.preventDefault(),w(),A());return}if(!n&&!e.altKey&&(e.key===`+`||e.key===`=`||e.key===`-`)){if(!jn())return;Mn(e.key===`-`?-1:1)&&(e.preventDefault(),w(),A());return}if(!n&&!e.altKey&&!e.shiftKey&&e.key===`Enter`){if(t.selected.size!==1)return;let n=t.selected.values().next().value,r=t.currentMap(),i=r.boxes.find(e=>e.id===n);if(i){let r=t.canvas.querySelector(`.box[data-id="${n}"]`);r&&(e.preventDefault(),D(r,i));return}let a=(r.texts??[]).find(e=>e.id===n);if(a){let r=t.canvas.querySelector(`.text-item[data-id="${n}"]`);r&&(e.preventDefault(),Ve(r,a))}return}if(e.key===`Escape`){if(Ce()){ke(!1);return}if(I()){At()?Ft():Pt(!1);return}let e=t.link();e&&(e.handleEl.classList.remove(`active`),t.ghostLine.style.display=`none`,t.clearLink(),t.setDropTargetId(null),t.setDropTargetHandle(null),j(),t.clearProximity()),t.selected.clear(),t.setSelectedEdge(null),j(),M()}if(e.key===`Delete`||e.key===`Backspace`){let n=t.selectedEdge();if(n){e.preventDefault();let r=t.currentMap(),i=r.edges.indexOf(n);i>=0&&r.edges.splice(i,1),t.setSelectedEdge(null),pe(),M(),t.setStatus(`edge removed`);return}t.selected.size>0&&(e.preventDefault(),Tt())}})},zn=null,Bn=()=>{if(!zn)throw Error(`mouse: wireMouse() not called`);return zn},Vn=e=>{zn=e},Hn=(e,t)=>{let n=Bn(),r=document.elementsFromPoint(e,t);for(let e of r){if(!e||e===n.ghostLine)continue;let t=e.closest?.(`.box`);if(t)return t}return null},Un=e=>{let t=Bn();if(t.lastCursor.x=e.clientX,t.lastCursor.y=e.clientY,we()){Pe(e.clientX,e.clientY);return}if(I()){Rt(e.clientX,e.clientY,e.shiftKey);return}let n=t.pan();if(n){b.x=n.startVX+(e.clientX-n.downX),b.y=n.startVY+(e.clientY-n.downY),ce();return}let r=t.drag();if(r){let t=e.clientX-r.downX,n=e.clientY-r.downY;if(!r.active&&Math.hypot(t,n)>4){r.active=!0;for(let e of r.movers)e.el?.classList?.add(`dragging`)}if(r.active){for(let i of r.movers)i.apply(t,n,e);M()}return}let i=t.band();if(i){let t=Math.min(i.startX,e.clientX),n=Math.min(i.startY,e.clientY),r=Math.abs(e.clientX-i.startX),a=Math.abs(e.clientY-i.startY);i.el.style.left=t+`px`,i.el.style.top=n+`px`,i.el.style.width=r+`px`,i.el.style.height=a+`px`;return}let a=t.link();if(a){t.ghostLine.setAttribute(`x2`,String(x(e.clientX))),t.ghostLine.setAttribute(`y2`,String(S(e.clientY)));let n=Hn(e.clientX,e.clientY),r=n&&n.dataset.id!==a.fromId?n.dataset.id??null:null,i=null;if(r&&n){let o=t.currentMap().boxes.find(e=>e.id===r);o&&(i=Ge(n,o,a.startX,a.startY,e.clientX,e.clientY))}let o=r!==t.dropTargetId(),s=i!==t.dropTargetHandle();(o||s)&&(t.setDropTargetId(r),t.setDropTargetHandle(i),j()),_t(x(e.clientX),S(e.clientY));return}_t(x(e.clientX),S(e.clientY))},Wn=e=>{let t=Bn();if(we()){Fe();return}if(I()){Lt(e.clientX,e.clientY,e.shiftKey);return}if(t.pan()){t.setPan(null),document.body.classList.remove(`panning`);return}let n=t.drag();if(n){let e=n.active;for(let e of n.movers)e.el?.classList?.remove(`dragging`);let r=n.primaryId;t.setDrag(null),e?w():(t.selected.clear(),r&&t.selected.add(r),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j());return}let r=t.band();if(r){let n=Math.min(r.startX,e.clientX),i=Math.min(r.startY,e.clientY),a=Math.max(r.startX,e.clientX),o=Math.max(r.startY,e.clientY);if(a-n>2||o-i>2){let e=x(n),r=S(i),s=x(a),c=S(o),l=t.currentMap();for(let n of l.boxes){let i=t.canvas.querySelector(`.box[data-id="${n.id}"]`);if(!i)continue;let a=n.x+i.offsetWidth,o=n.y+i.offsetHeight;n.xe&&n.yr&&t.selected.add(n.id)}for(let n of l.texts){let i=t.canvas.querySelector(`.text-item[data-id="${n.id}"]`);if(!i)continue;let a=n.x+i.offsetWidth,o=n.y+i.offsetHeight;n.xe&&n.yr&&t.selected.add(n.id)}for(let n of l.lines){let i=[n.x1,n.x2],a=[n.y1,n.y2];for(let[e,t]of n.mids??[])i.push(e),a.push(t);let o=Math.min(...i),l=Math.min(...a),u=Math.max(...i),d=Math.max(...a);oe&&lr&&t.selected.add(n.id)}j(),t.selected.size>0&&t.setStatus(t.selected.size+` selected`)}r.el.remove(),t.setBand(null);return}let i=t.link();if(i){i.handleEl.classList.remove(`active`),t.ghostLine.style.display=`none`;let n=Hn(e.clientX,e.clientY);if(n&&n.dataset.id!==i.fromId){let r=n.dataset.id,a=t.currentMap(),o=Ge(n,a.boxes.find(e=>e.id===r),i.startX,i.startY,e.clientX,e.clientY),s={from:i.fromId,to:r};i.fromHandle&&(s.fromHandle=i.fromHandle),o&&(s.toHandle=o),a.edges=h(a.edges,s),pe(),M()}else{let n=t.mintId(),r=x(e.clientX),a=S(e.clientY),o={id:n,label:`new`,x:r,y:a},s=t.currentMap();s.boxes.push(o),A();let c=t.canvas.querySelector(`.box[data-id="${n}"]`);if(c){o.x=r-c.offsetWidth/2,o.y=a-c.offsetHeight/2,c.style.left=o.x+`px`,c.style.top=o.y+`px`;let e=We(o,c,i.startX,i.startY),l={from:i.fromId,to:n};i.fromHandle&&(l.fromHandle=i.fromHandle),e&&(l.toHandle=e),s.edges=h(s.edges,l),M(),t.selected.clear(),t.selected.add(n),j(),D(c,o,{cancelDeletes:!0})}w()}t.setLink(null),(t.dropTargetId()||t.dropTargetHandle())&&(t.setDropTargetId(null),t.setDropTargetHandle(null),j()),vt()}},Gn=e=>{e.button===2&&(e.preventDefault(),Bn().setPan({downX:e.clientX,downY:e.clientY,startVX:b.x,startVY:b.y}),document.body.classList.add(`panning`))},Kn=e=>{let t=Bn();if(e.button!==0)return;if(Ce()){e.preventDefault(),e.stopPropagation(),Ne(e.clientX,e.clientY);return}if(I()){e.preventDefault(),e.stopPropagation(),It(e.clientX,e.clientY,e.shiftKey);return}e.shiftKey||t.selected.clear(),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j();let n=document.createElement(`div`);n.className=`selection-band`,n.style.left=e.clientX+`px`,n.style.top=e.clientY+`px`,n.style.width=`0px`,n.style.height=`0px`,document.body.appendChild(n),t.setBand({startX:e.clientX,startY:e.clientY,el:n})},qn=(e,t,n,r,i,a)=>{let o=i-n,s=a-r,c=o*o+s*s,l=c===0?0:Math.max(0,Math.min(1,((e-n)*o+(t-r)*s)/c)),u=n+l*o,d=r+l*s,f=e-u,p=t-d;return{d2:f*f+p*p,t:l}},Jn=14,Yn=(e,t,n)=>{let r=null,i=0,a=Jn*Jn;for(let o of e.lines){let e=[[o.x1,o.y1],...o.mids??[],[o.x2,o.y2]];for(let s=0;s{let t=Bn();if(Ce())return;if(I()){let n=x(e.clientX),r=S(e.clientY);Yn(t.currentMap(),n,r)&&(Ft(),he(),A());return}let n=x(e.clientX),r=S(e.clientY);St(n,r,{x:n,y:r})},Zn=()=>{document.addEventListener(`mousemove`,Un),document.addEventListener(`mouseup`,Wn),document.addEventListener(`mousedown`,Gn),window.addEventListener(`contextmenu`,e=>e.preventDefault()),window.addEventListener(`auxclick`,e=>{e.button===1&&e.preventDefault()});let e=document.getElementById(`bg-layer`);e&&(e.addEventListener(`mousedown`,Kn),e.addEventListener(`dblclick`,Xn))},Qn=typeof navigator<`u`&&/Mac|iPhone|iPad|iPod/i.test(navigator.platform||navigator.userAgent||``),$n=e=>Qn?e.metaKey:e.ctrlKey,U=e=>Math.round(e/20)*20,er=e=>{let t=e.mids??[],n=[[e.x1,e.y1],...t,[e.x2,e.y2]],r=e.style??1;if(r===2&&t.length>0){let n=`M ${e.x1} ${e.y1}`;for(let e=0;e=Math.abs(o-i)?e+=` L ${a} ${i} L ${a} ${o}`:e+=` L ${r} ${o} L ${a} ${o}`}return e}let i=`M ${n[0][0]} ${n[0][1]}`;for(let e=1;e{let n=e.x,r=e.y;return{el:t,apply(i,a,o){let s=n+i,c=r+a;o?.shiftKey&&(s=U(s),c=U(c)),e.x=s,e.y=c,t.style.left=e.x+`px`,t.style.top=e.y+`px`}}},nr=(e,t)=>{let n=e.x,r=e.y;return{el:t,apply(i,a,o){let s=n+i,c=r+a;o?.shiftKey&&(s=U(s),c=U(c)),e.x=s,e.y=c,t.style.left=e.x+`px`,t.style.top=e.y+`px`}}},rr=(e,t,n,r,i,a,o)=>{let s=e.x1,c=e.y1,l=e.x2,u=e.y2,d=(e.mids??[]).map(([e,t])=>[e,t]);return{el:t,apply(t,f,p){let m=t,h=f;if(p?.shiftKey&&(m=U(s+t)-s,h=U(c+f)-c),e.x1=s+m,e.y1=c+h,e.x2=l+m,e.y2=u+h,d.length>0){e.mids||=[];for(let t=0;t{let r=typeof t==`object`?t.mid:-1,i=t===1?e.x1:t===2?e.x2:e.mids?.[r]?.[0]??0,a=t===1?e.y1:t===2?e.y2:e.mids?.[r]?.[1]??0;return{el:n.g,apply(o,s,c){let l=i+o,u=a+s;c?.shiftKey&&(l=U(l),u=U(u)),t===1?(e.x1=l,e.y1=u):t===2?(e.x2=l,e.y2=u):e.mids&&e.mids[r]&&(e.mids[r]=[l,u]);let d=er(e);n.line.setAttribute(`d`,d),n.hit.setAttribute(`d`,d);let f=t===1?n.h1:t===2?n.h2:n.midHandles[r]??null;f&&(f.setAttribute(`cx`,String(l)),f.setAttribute(`cy`,String(u)))}}},ar=(e,t,n,r)=>{let i=e.points.map(([e,t])=>[e,t]);return{el:t,apply(t,a,s){let c=t,l=a;if(s?.shiftKey&&i.length>0){let e=i[0];c=U(e[0]+t)-e[0],l=U(e[1]+a)-e[1]}for(let t=0;t{if(!or)throw Error(`attach: wireAttach() not called`);return or},sr=e=>{or=e},cr=()=>{let e=W(),t=[],n=e.currentMap();for(let r of e.selected){let i=n.boxes.find(e=>e.id===r);if(i){let n=e.canvas.querySelector(`.box[data-id="${r}"]`);n&&t.push(tr(i,n));continue}let a=e.findTextById(r);if(a){let n=e.canvas.querySelector(`.text-item[data-id="${r}"]`);n&&t.push(nr(a,n));continue}let o=e.findLineById(r);if(o){let n=e.lineLayer.querySelector(`.line-group[data-id="${r}"]`);if(n){let e=n.querySelector(`.line-line`),r=n.querySelector(`.line-hit`),i=n.querySelector(`.line-handle[data-endpoint="1"]`),a=n.querySelector(`.line-handle[data-endpoint="2"]`),s=Array.from(n.querySelectorAll(`.line-handle[data-endpoint="m"]`));t.push(rr(o,n,e,r,i,a,s))}continue}let s=e.findStrokeById(r);if(s){let n=e.strokeLayer.querySelector(`.stroke-group[data-id="${r}"]`);if(n){let e=n.querySelector(`.stroke-hit`),r=n.querySelector(`.stroke-line`);t.push(ar(s,n,e,r))}}}return t},lr=(e,t)=>{e.addEventListener(`mousedown`,n=>{let r=W();if(e.isContentEditable||n.button!==0)return;n.preventDefault(),n.stopPropagation(),r.selected.has(t.id)||(n.shiftKey||r.selected.clear(),r.selected.add(t.id),r.selectedEdge()&&(r.setSelectedEdge(null),M()),j());let i=t.id;if(n.altKey){let e=r.cloneSelection();e.has(t.id)&&(i=e.get(t.id))}r.setDrag({movers:cr(),primaryId:i,downX:n.clientX,downY:n.clientY,active:!1})}),e.addEventListener(`dblclick`,n=>{let r=W();e.isContentEditable||(n.preventDefault(),n.stopPropagation(),r.selected.clear(),r.selected.add(t.id),j(),Ve(e,t))})},ur=(e,t,n,r,i,a,o)=>{n.addEventListener(`mousedown`,e=>{let t=W();if(e.button!==0)return;e.preventDefault(),e.stopPropagation(),t.selected.has(o.id)||(e.shiftKey||t.selected.clear(),t.selected.add(o.id),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j());let n=o.id;if(e.altKey){let e=t.cloneSelection();e.has(o.id)&&(n=e.get(o.id))}t.setDrag({movers:cr(),primaryId:n,downX:e.clientX,downY:e.clientY,active:!1})}),n.addEventListener(`dblclick`,e=>{e.preventDefault(),e.stopPropagation();let t=W(),n=x(e.clientX),r=S(e.clientY);o.mids||=[];let i=[[o.x1,o.y1],...o.mids,[o.x2,o.y2]],a=0,s=1/0;for(let e=0;e{let l=W();s.button===0&&(s.preventDefault(),s.stopPropagation(),l.selected.clear(),l.selected.add(o.id),l.selectedEdge()&&(l.setSelectedEdge(null),M()),j(),l.setDrag({movers:[ir(o,c,{g:e,line:t,hit:n,h1:r,h2:i,midHandles:a})],primaryId:o.id,downX:s.clientX,downY:s.clientY,active:!1}))});for(let s=0;s{let c=W();s.button===0&&(s.preventDefault(),s.stopPropagation(),c.selected.clear(),c.selected.add(o.id),c.selectedEdge()&&(c.setSelectedEdge(null),M()),j(),c.setDrag({movers:[ir(o,{mid:l},{g:e,line:t,hit:n,h1:r,h2:i,midHandles:a})],primaryId:o.id,downX:s.clientX,downY:s.clientY,active:!1}))}),c.addEventListener(`dblclick`,e=>{e.preventDefault(),e.stopPropagation(),o.mids&&l{e.addEventListener(`mousedown`,n=>{let r=W();if(e.isContentEditable)return;if(n.button===1||n.button===0&&$n(n)){n.preventDefault(),n.stopPropagation(),Zt(t.id);return}if(n.button!==0)return;let i=n.target;if(i.classList.contains(`handle`)){n.preventDefault(),n.stopPropagation();let a=i.dataset.handle,o=r.currentMap(),s=null,c=null,l=``;for(let e=o.edges.length-1;e>=0;e--){let n=o.edges[e];if(n.from===t.id&&n.fromHandle===a){s=n,c=n.to,l=n.toHandle??``;break}if(n.to===t.id&&n.toHandle===a){s=n,c=n.from,l=n.fromHandle??``;break}}if(s&&c){let a=o.edges.indexOf(s);a>=0&&o.edges.splice(a,1);let u=o.boxes.find(e=>e.id===c),d=r.canvas.querySelector(`.box[data-id="${c}"]`);if(!u||!d){o.edges.push(s),M();return}let f=t.x+e.offsetWidth/2,p=t.y+e.offsetHeight/2,m=l||We(u,d,f,p),[h,g]=Ue(d,u,m);r.setLink({fromId:c,fromHandle:m,startX:h,startY:g,handleEl:i,rerouting:!0}),i.classList.add(`active`),r.ghostLine.setAttribute(`x1`,String(h)),r.ghostLine.setAttribute(`y1`,String(g)),r.ghostLine.setAttribute(`x2`,String(x(n.clientX))),r.ghostLine.setAttribute(`y2`,String(S(n.clientY))),r.ghostLine.style.display=``,M(),r.setStatus(`re-routing edge — drop on a box, or in empty space`);return}let[u,d]=Ue(e,t,a);r.setLink({fromId:t.id,fromHandle:a,startX:u,startY:d,handleEl:i}),i.classList.add(`active`),r.ghostLine.setAttribute(`x1`,String(u)),r.ghostLine.setAttribute(`y1`,String(d)),r.ghostLine.setAttribute(`x2`,String(x(n.clientX))),r.ghostLine.setAttribute(`y2`,String(S(n.clientY))),r.ghostLine.style.display=``,r.setStatus(`drop on a box to connect, or release to cancel`);return}n.preventDefault(),n.stopPropagation(),r.selected.has(t.id)||(n.shiftKey||r.selected.clear(),r.selected.add(t.id),r.selectedEdge()&&(r.setSelectedEdge(null),M()),j());let a=t.id;if(n.altKey){let e=r.cloneSelection();e.has(t.id)&&(a=e.get(t.id))}r.setDrag({movers:cr(),primaryId:a,downX:n.clientX,downY:n.clientY,active:!1})}),e.addEventListener(`dblclick`,n=>{let r=W();e.isContentEditable||(n.preventDefault(),n.stopPropagation(),r.selected.clear(),r.selected.add(t.id),r.selectedEdge()&&(r.setSelectedEdge(null),M()),j(),D(e,t))})},fr=(e,t,n)=>e!==null&&e.id===t.id&&t.time-e.time<=n?{kind:`double`,nextLastTap:null}:{kind:`single`,nextLastTap:t},pr=(e,t,n,r,i)=>Math.hypot(n-e,r-t)>i,mr=300,hr=``,gr=500,_r=4,G=null,vr=null,yr=()=>{vr!==null&&(clearTimeout(vr),vr=null)},br=()=>document.getElementById(`deleteZone`),xr=e=>{let t=br();return t?e<=t.getBoundingClientRect().bottom:!1},Sr=e=>{br()?.classList.toggle(`armed`,e)},Cr=null,wr=()=>{if(!Cr)throw Error(`touch: wireTouch() not called`);return Cr},Tr=e=>{Cr=e},Er=(e,t)=>{if(!(e instanceof Element))return null;let n=e.closest(`.handle`);if(n){let e=n.parentElement?.closest?.(`.box`)??null;if(e&&!e.isContentEditable){let r=e.dataset.id,i=n.dataset.handle;if(r&&i&&t.has(r))return{kind:`handle`,boxEl:e,boxId:r,handleEl:n,code:i}}}let r=e.closest(`.box`);if(r&&!r.isContentEditable){let e=r.dataset.id;if(e)return{kind:`box`,el:r,id:e}}let i=e.closest(`.text-item`);if(i&&!i.isContentEditable){let e=i.dataset.id;if(e)return{kind:`text`,el:i,id:e}}let a=e.closest(`.line-handle`);if(a){let e=a.closest(`.line-group`)?.dataset?.id,n=a.dataset.endpoint;if(e&&t.has(e)&&(n===`1`||n===`2`||n===`m`)){let t;if(n===`1`)t=1;else if(n===`2`)t=2;else{let e=parseInt(a.dataset.midIndex??`0`,10);t={mid:Number.isFinite(e)?e:0}}return{kind:`line-endpoint`,lineId:e,endpoint:t}}}let o=e.closest(`.line-group`);if(o){let e=o.dataset.id;if(e)return{kind:`line`,id:e}}let s=e.closest(`.stroke-group`);if(s){let e=s.dataset.id;if(e)return{kind:`stroke`,id:e}}return e.closest(`#bg-layer`)||e.closest(`#bg-svg`)||e.closest(`#edges`)?{kind:`bg`}:null},Dr=()=>{let e=wr();if(yr(),we()){Fe();return}if(At()){Ft();return}e.pan()&&(e.setPan(null),document.body.classList.remove(`panning`));let t=e.drag();if(t){for(let e of t.movers)e.el.classList?.remove(`dragging`);e.setDrag(null),document.body.classList.remove(`dragging`),Sr(!1)}let n=e.link();n&&(n.handleEl.classList.remove(`active`),e.ghostLine.style.display=`none`,e.setLink(null),(e.dropTargetId()||e.dropTargetHandle())&&(e.setDropTargetId(null),e.setDropTargetHandle(null),j()),vt())},Or=e=>{if(e.touches.length!==1){Dr();return}if(Ce()){let t=e.touches[0];e.preventDefault(),Ne(t.clientX,t.clientY);return}if(I()){let t=e.touches[0];e.preventDefault(),It(t.clientX,t.clientY);return}let t=document.querySelector(`[contenteditable="true"]`);t&&!t.contains(e.target)&&t.blur(),document.body.classList.contains(`panning`)||(document.body.classList.remove(`dragging`),Sr(!1));let n=wr(),r=e.touches[0],i=Er(e.target,n.selected);if(i){if(i.kind===`bg`){e.preventDefault(),n.setPan({downX:r.clientX,downY:r.clientY,startVX:b.x,startVY:b.y}),document.body.classList.add(`panning`);return}if(i.kind===`handle`){e.preventDefault();let t=n.currentMap(),a=t.boxes.find(e=>e.id===i.boxId);if(!a)return;let o=null,s=null,c=``;for(let e=t.edges.length-1;e>=0;e--){let n=t.edges[e];if(n.from===i.boxId&&n.fromHandle===i.code){o=n,s=n.to,c=n.toHandle??``;break}if(n.to===i.boxId&&n.toHandle===i.code){o=n,s=n.from,c=n.fromHandle??``;break}}if(o&&s){let e=t.edges.indexOf(o);e>=0&&t.edges.splice(e,1);let l=t.boxes.find(e=>e.id===s),u=n.canvas.querySelector(`.box[data-id="${s}"]`);if(!l||!u){t.edges.push(o),M();return}let d=a.x+i.boxEl.offsetWidth/2,f=a.y+i.boxEl.offsetHeight/2,p=c||We(l,u,d,f),[m,h]=Ue(u,l,p);n.setLink({fromId:s,fromHandle:p,startX:m,startY:h,handleEl:i.handleEl,rerouting:!0}),i.handleEl.classList.add(`active`),n.ghostLine.setAttribute(`x1`,String(m)),n.ghostLine.setAttribute(`y1`,String(h)),n.ghostLine.setAttribute(`x2`,String(x(r.clientX))),n.ghostLine.setAttribute(`y2`,String(S(r.clientY))),n.ghostLine.style.display=``,M();return}let[l,u]=Ue(i.boxEl,a,i.code),d=i.handleEl.getBoundingClientRect(),f=x(d.left+d.width/2),p=S(d.top+d.height/2);n.setLink({fromId:i.boxId,fromHandle:i.code,startX:l,startY:u,handleEl:i.handleEl}),i.handleEl.classList.add(`active`),n.ghostLine.setAttribute(`x1`,String(f)),n.ghostLine.setAttribute(`y1`,String(p)),n.ghostLine.setAttribute(`x2`,String(x(r.clientX))),n.ghostLine.setAttribute(`y2`,String(S(r.clientY))),n.ghostLine.style.display=``;return}if(i.kind===`line-endpoint`){let t=i.lineId,a=i.endpoint,o=document.querySelector(`.line-group[data-id="${t}"]`),s=n.currentMap().lines.find(e=>e.id===t);if(!o||!s)return;let c=o.querySelector(`.line-line`),l=o.querySelector(`.line-hit`),u=o.querySelector(`.line-handle[data-endpoint="1"]`),d=o.querySelector(`.line-handle[data-endpoint="2"]`),f=Array.from(o.querySelectorAll(`.line-handle[data-endpoint="m"]`));if(!c||!l||!u||!d)return;e.preventDefault(),n.selected.clear(),n.selected.add(t),n.selectedEdge()&&(n.setSelectedEdge(null),M()),j(),n.setDrag({movers:[ir(s,a,{g:o,line:c,hit:l,h1:u,h2:d,midHandles:f})],primaryId:t,downX:r.clientX,downY:r.clientY,active:!1});return}if(e.preventDefault(),n.selected.has(i.id)||(n.selected.clear(),n.selected.add(i.id),n.selectedEdge()&&(n.setSelectedEdge(null),M()),j()),n.setDrag({movers:cr(),primaryId:i.id,downX:r.clientX,downY:r.clientY,active:!1}),i.kind===`box`){let e=i.id;vr=window.setTimeout(()=>{vr=null;let t=n.drag();if(!(!t||t.active)){t.longPressFired=!0;for(let e of t.movers)e.el.classList?.remove(`dragging`);n.setDrag(null),document.body.classList.remove(`dragging`),Sr(!1),G=null,Zt(e)}},gr)}}},kr=e=>{let t=wr();if(e.touches.length!==1){Dr();return}let n=e.touches[0];if(!n)return;if(we()){e.preventDefault(),Pe(n.clientX,n.clientY);return}if(I()&&At()){e.preventDefault(),Rt(n.clientX,n.clientY);return}let r=t.pan();if(r){e.preventDefault(),b.x=r.startVX+(n.clientX-r.downX),b.y=r.startVY+(n.clientY-r.downY),ce();return}let i=t.drag();if(i){e.preventDefault();let t=n.clientX-i.downX,r=n.clientY-i.downY;if(!i.active&&pr(i.downX,i.downY,n.clientX,n.clientY,_r)){i.active=!0,yr(),G=null;for(let e of i.movers)e.el.classList?.add(`dragging`);document.body.classList.add(`dragging`)}if(i.active){for(let e of i.movers)e.apply(t,r,null);M(),Sr(xr(n.clientY))}return}let a=t.link();if(a){e.preventDefault();let r=x(n.clientX),i=S(n.clientY);t.ghostLine.setAttribute(`x2`,String(r)),t.ghostLine.setAttribute(`y2`,String(i));let o=Hn(n.clientX,n.clientY),s=o&&o.dataset.id!==a.fromId?o.dataset.id??null:null,c=null;if(s&&o){let e=t.currentMap().boxes.find(e=>e.id===s);e&&(c=Ge(o,e,a.startX,a.startY,n.clientX,n.clientY))}(s!==t.dropTargetId()||c!==t.dropTargetHandle())&&(t.setDropTargetId(s),t.setDropTargetHandle(c),j()),_t(r,i)}},Ar=e=>{let t=wr();if(yr(),we()){Fe();return}if(I()&&At()){let t=e.changedTouches[0];t&&Lt(t.clientX,t.clientY);return}let n=t.pan();if(n){let r=e.changedTouches[0]??null,i=r!==null&&pr(n.downX,n.downY,r.clientX,r.clientY,_r);if(t.setPan(null),document.body.classList.remove(`panning`),!i&&r){let e=fr(G,{id:hr,time:performance.now()},mr);if(G=e.nextLastTap,e.kind===`double`){let e=x(r.clientX),t=S(r.clientY);St(e,t,{x:e,y:t})}else (t.selected.size>0||t.selectedEdge())&&(t.selected.clear(),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j())}else G=null;return}let r=t.link();if(r){jr(r,e.changedTouches[0]??null);return}let i=t.drag();if(!i)return;let a=i.active,o=i.longPressFired===!0;for(let e of i.movers)e.el.classList?.remove(`dragging`);let s=i.primaryId;t.setDrag(null),document.body.classList.remove(`dragging`);let c=br()?.classList.contains(`armed`)??!1;if(Sr(!1),o)return;if(a){if(c){Tt(),G=null;return}w(),G=null;return}if(!s)return;let l=fr(G,{id:s,time:performance.now()},mr);if(G=l.nextLastTap,l.kind===`double`){let e=t.currentMap().boxes.find(e=>e.id===s);if(e){let n=document.querySelector(`#canvas .box[data-id="${s}"]`);n&&(t.selected.clear(),t.selected.add(s),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j(),D(n,e));return}let n=t.findTextById(s);if(n){let e=document.querySelector(`#canvas .text-item[data-id="${s}"]`);e&&(t.selected.clear(),t.selected.add(s),j(),Ve(e,n));return}return}t.selected.clear(),t.selected.add(s),t.selectedEdge()&&(t.setSelectedEdge(null),M()),j()},jr=(e,t)=>{let n=wr();if(e.handleEl.classList.remove(`active`),n.ghostLine.style.display=`none`,!t){n.setLink(null),(n.dropTargetId()||n.dropTargetHandle())&&(n.setDropTargetId(null),n.setDropTargetHandle(null),j()),vt();return}let r=Hn(t.clientX,t.clientY);if(r&&r.dataset.id!==e.fromId){let i=r.dataset.id,a=n.currentMap(),o=Ge(r,a.boxes.find(e=>e.id===i),e.startX,e.startY,t.clientX,t.clientY),s={from:e.fromId,to:i};e.fromHandle&&(s.fromHandle=e.fromHandle),o&&(s.toHandle=o),a.edges=h(a.edges,s),pe(),M()}else{let r=n.mintId(),i=x(t.clientX),a=S(t.clientY),o={id:r,label:`new`,x:i,y:a},s=n.currentMap();s.boxes.push(o),A();let c=n.canvas.querySelector(`.box[data-id="${r}"]`);if(c){o.x=i-c.offsetWidth/2,o.y=a-c.offsetHeight/2,c.style.left=o.x+`px`,c.style.top=o.y+`px`;let t=We(o,c,e.startX,e.startY),l={from:e.fromId,to:r};e.fromHandle&&(l.fromHandle=e.fromHandle),t&&(l.toHandle=t),s.edges=h(s.edges,l),M(),n.selected.clear(),n.selected.add(r),j(),D(c,o,{cancelDeletes:!0})}w()}n.setLink(null),(n.dropTargetId()||n.dropTargetHandle())&&(n.setDropTargetId(null),n.setDropTargetHandle(null),j()),vt()},Mr=e=>{Dr(),G=null},Nr=()=>{document.addEventListener(`touchstart`,Or,{passive:!1}),document.addEventListener(`touchmove`,kr,{passive:!1}),document.addEventListener(`touchend`,Ar),document.addEventListener(`touchcancel`,Mr)},Pr=()=>Ce()?`brush`:I()?`line`:`cursor`,Fr=e=>{ke(e===`brush`),Pt(e===`line`)},Ir=`http://www.w3.org/2000/svg`,Lr=(e,t)=>{let n=document.createElementNS(Ir,`svg`);return n.setAttribute(`width`,String(e)),n.setAttribute(`height`,String(e)),n.setAttribute(`viewBox`,`0 0 ${e} ${e}`),n.setAttribute(`fill`,`none`),n.setAttribute(`stroke`,`currentColor`),n.setAttribute(`stroke-width`,`1.6`),n.setAttribute(`stroke-linecap`,`round`),n.setAttribute(`stroke-linejoin`,`round`),t(n),n},Rr=()=>Lr(20,e=>{let t=document.createElementNS(Ir,`path`);t.setAttribute(`d`,`M4 3 L4 16 L8 12 L11 18 L13 17 L10 11 L16 11 Z`),t.setAttribute(`fill`,`currentColor`),t.setAttribute(`stroke`,`currentColor`),e.appendChild(t)}),zr=()=>Lr(20,e=>{let t=document.createElementNS(Ir,`path`);t.setAttribute(`d`,`M3 17 L5 16 L14 7 L12 5 L3 14 Z`),t.setAttribute(`fill`,`currentColor`),e.appendChild(t);let n=document.createElementNS(Ir,`path`);n.setAttribute(`d`,`M13 6 L16 3 L18 5 L15 8 Z`),n.setAttribute(`fill`,`#a60`),n.setAttribute(`stroke`,`currentColor`),e.appendChild(n)}),Br=()=>Lr(20,e=>{let t=document.createElementNS(Ir,`line`);t.setAttribute(`x1`,`4`),t.setAttribute(`y1`,`16`),t.setAttribute(`x2`,`16`),t.setAttribute(`y2`,`4`),e.appendChild(t);for(let[t,n]of[[4,16],[16,4]]){let r=document.createElementNS(Ir,`circle`);r.setAttribute(`cx`,String(t)),r.setAttribute(`cy`,String(n)),r.setAttribute(`r`,`2`),r.setAttribute(`fill`,`currentColor`),e.appendChild(r)}}),Vr=()=>{if(document.getElementById(`modeBar`))return;let e=document.createElement(`div`);e.id=`modeBar`;let t=[],n=(n,i,a)=>{let o=document.createElement(`button`);o.type=`button`,o.dataset.mode=n,o.title=i,o.setAttribute(`aria-label`,i),o.appendChild(a),o.addEventListener(`mousedown`,e=>e.stopPropagation()),o.addEventListener(`touchstart`,e=>e.stopPropagation(),{passive:!0});let s=!1,c=e=>{e.stopPropagation(),e.preventDefault(),!s&&(s=!0,setTimeout(()=>{s=!1},0),Fr(n),r())};return o.addEventListener(`pointerup`,c),o.addEventListener(`click`,c),e.appendChild(o),t.push({mode:n,el:o}),o};n(`cursor`,`Cursor`,Rr()),n(`brush`,`Brush`,zr()),n(`line`,`Line`,Br());let r=()=>{let e=Pr();for(let{mode:n,el:r}of t)r.classList.toggle(`active`,n===e),r.setAttribute(`aria-pressed`,n===e?`true`:`false`)};r(),new MutationObserver(r).observe(document.body,{attributes:!0,attributeFilter:[`class`]}),document.body.appendChild(e)},K={maps:[]},q=`/`,J={boxes:[],edges:[]},Y=new Set,Hr={x:window.innerWidth/2,y:window.innerHeight/2},Ur=null,Wr=null,Gr=null,X=null,Z=null,Kr=null,qr=null,Jr=null,Q=document.getElementById(`canvas`),Yr=document.getElementById(`edge-layer`),Xr=document.getElementById(`line-layer`),Zr=document.getElementById(`stroke-layer`),Qr=document.getElementById(`ghost-line`);function $r(e){return s(e||`b`,c(J.boxes,J.texts||[],J.lines||[],J.strokes||[]))}var ei=e=>J.texts.find(t=>t.id===e),ti=e=>J.lines.find(t=>t.id===e),ni=e=>(J.strokes||[]).find(t=>t.id===e),ri=()=>le(J);function $(e){}function ii(){let e=Tn();return A(),j(),w(),e}ut({canvas:Q,lineLayer:Xr,strokeLayer:Zr,edgeLayer:Yr,currentMap:()=>J,graph:()=>K,currentPath:()=>q,selected:Y,selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},dropTargetId:()=>Kr,dropTargetHandle:()=>qr,nearTargetId:()=>Jr,attachBoxHandlers:dr,attachTextHandlers:lr,attachLineHandlers:ur,isBrushMode:()=>Ce(),setStatus:$}),gt({canvas:Q,currentMap:()=>J,link:()=>X,nearTargetId:()=>Jr,setNearTargetId:e=>{Jr=e}}),qt({getGraph:()=>K,getCurrentPath:()=>q,setCurrentPath:e=>{q=e},setCurrentMap:e=>{J=e},clearSelected:()=>Y.clear(),clearSelectedEdge:()=>{Z=null},renderAll:()=>A()}),en(),sr({canvas:Q,lineLayer:Xr,strokeLayer:Zr,ghostLine:Qr,currentMap:()=>J,findTextById:ei,findLineById:ti,findStrokeById:ni,selected:Y,selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},setDrag:e=>{Gr=e},setLink:e=>{X=e},cloneSelection:ii,setStatus:$}),xt({canvas:Q,currentMap:()=>J,setCurrentMap:e=>{J=e},graph:()=>K,setGraph:e=>{K=e},currentPath:()=>q,ensureMap:Jt,selected:Y,selectedEdge:()=>Z,clearSelectedEdge:()=>{Z=null},mintId:$r,setStatus:$}),Re({canvas:Q,getCurrentMap:()=>J,setCurrentMap:e=>{J=e},getCurrentPath:()=>q,getGraph:()=>K,setGraph:e=>{K=e},ensureMap:Jt,selected:Y,renderAll:()=>A(),setStatus:$}),un({getGraph:()=>K,setGraph:e=>{K=e},serializeGraph:ne,setCurrentPath:(e,t)=>Xt(e,t),getCurrentPath:()=>q,readPathFromURL:Yt,setStatus:$,clearSelected:()=>Y.clear(),clearSelectedEdge:()=>{Z=null}});var ai=[];de({scheduleSave:()=>fn(),getMapPath:()=>q,onMutate:e=>{for(let t of ai)t(e)}}),wn({currentMap:()=>J,selected:Y,findTextById:ei,findLineById:ti,mintId:$r}),Ye({canvas:Q,currentMap:()=>J,selected:Y,renderAll:()=>A()}),at(),Bt({selected:Y,currentMap:()=>J,findTextById:ei,findLineById:ti,mintId:$r,renderAll:()=>A(),deleteSelection:()=>Tt(),setStatus:$,clearSelectedEdge:()=>{Z=null}}),ye({mintId:()=>$r(`s`),strokeLayer:()=>Zr,currentMap:()=>J,afterCommit:()=>dt(),setStatus:$}),Ot({lineLayer:()=>Xr,setStatus:$}),Vn({canvas:Q,ghostLine:Qr,currentMap:()=>J,mintId:()=>$r(),selected:Y,lastCursor:Hr,drag:()=>Gr,setDrag:e=>{Gr=e},link:()=>X,setLink:e=>{X=e},pan:()=>Wr,setPan:e=>{Wr=e},band:()=>Ur,setBand:e=>{Ur=e},selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},dropTargetId:()=>Kr,setDropTargetId:e=>{Kr=e},dropTargetHandle:()=>qr,setDropTargetHandle:e=>{qr=e},setStatus:$}),Zn(),Tr({canvas:Q,ghostLine:Qr,currentMap:()=>J,findTextById:ei,mintId:()=>$r(),selected:Y,drag:()=>Gr,setDrag:e=>{Gr=e},pan:()=>Wr,setPan:e=>{Wr=e},link:()=>X,setLink:e=>{X=e},dropTargetId:()=>Kr,setDropTargetId:e=>{Kr=e},dropTargetHandle:()=>qr,setDropTargetHandle:e=>{qr=e},selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e}}),Nr(),Dn({canvas:Q,ghostLine:Qr,currentMap:()=>J,findTextById:ei,selected:Y,selectedEdge:()=>Z,setSelectedEdge:e=>{Z=e},link:()=>X,clearLink:()=>{X=null},setDropTargetId:e=>{Kr=e},setDropTargetHandle:e=>{qr=e},clearProximity:()=>vt(),lastCursor:Hr,setStatus:$}),Rn(),oe();var oi=window.matchMedia(`(pointer: coarse)`),si=()=>document.body.classList.toggle(`touch-input`,oi.matches);oi.addEventListener(`change`,si),si(),Vr(),document.getElementById(`upBtn`).addEventListener(`click`,Qt),document.getElementById(`downloadBtn`).addEventListener(`click`,vn),document.getElementById(`reshareBtn`).addEventListener(`click`,yn),window.addEventListener(`mousedown`,e=>{e.button===1&&e.preventDefault()},!0),window.addEventListener(`resize`,()=>ri()),dn(),fetch(`/version`).then(e=>e.ok?e.text():``).then(e=>{let t=e.trim();t&&(document.getElementById(`version`).textContent=`flowgo `+t)}).catch(()=>{}),xn({snapshot:()=>structuredClone(K),applyRemotePatch:e=>{e(K),J=Jt(q),A()},onLocalMutation:e=>(ai.push(e),()=>{let t=ai.indexOf(e);t>=0&&ai.splice(t,1)})});
diff --git a/src/editor/collab-api.ts b/src/editor/collab-api.ts new file mode 100644 index 0000000..543345c --- /dev/null +++ b/src/editor/collab-api.ts @@ -0,0 +1,141 @@ +// Collaboration extension point. +// +// Downstream consumers (e.g. a Yjs collab plugin in a cloud product) +// import `whenCollabReady` from "@flowgo/editor/collab" and pass a +// callback that receives a typed `CollabHandle`. The handle exposes +// the minimum surface needed to mirror the editor's in-memory graph +// into an external store: +// +// 1. snapshot() — read the current graph as a plain object. +// 2. applyRemotePatch(fn) — mutate the live graph + trigger a render +// without losing focus or selection. +// 3. onLocalMutation(cb) — subscribe to local mutation events so the +// plugin can diff and push to its own store. +// +// This module ships in every editor bundle but has zero runtime cost +// until something calls whenCollabReady — no Yjs dep, no subscription +// list traversal, no behaviour change for single-user / CLI users. +// +// Bootstrap order: +// +// The editor's main.ts calls `exposeCollabHandle(...)` at the end +// of its bootstrap, AFTER every wireX() registration has run. A +// plugin that loaded earlier (its whenCollabReady callback queued) +// gets invoked at that moment; a plugin that loads later sees the +// handle immediately. +// +// All types here mirror the on-disk pkg/graph.Graph shape verbatim +// so the cloud plugin can pass snapshots to a Rust/Go sidecar via +// JSON without translation. + +import type { MutationEvent } from "./mutations.ts"; + +// --------------------------------------------------------------- +// Graph shape (mirror of pkg/graph.Graph). +// --------------------------------------------------------------- + +export interface FlowgoBox { + id: string; + label: string; + x: number; + y: number; + palette?: number; + font?: number; + anchor?: boolean; +} + +export interface FlowgoEdge { + from: string; + fromHandle?: string; + to: string; + toHandle?: string; + palette?: number; +} + +export interface FlowgoText { + id: string; + label: string; + x: number; + y: number; + palette?: number; + font?: number; +} + +export interface FlowgoLine { + id: string; + x1: number; + y1: number; + x2: number; + y2: number; + palette?: number; + style?: number; + mids?: number[][]; +} + +export interface FlowgoStroke { + id: string; + points: number[][]; + palette?: number; +} + +export interface FlowgoMap { + path: string; + boxes: FlowgoBox[]; + edges: FlowgoEdge[]; + texts?: FlowgoText[]; + lines?: FlowgoLine[]; + strokes?: FlowgoStroke[]; +} + +export interface FlowgoGraph { + version?: string; + maps: FlowgoMap[]; +} + +// --------------------------------------------------------------- +// Plugin handle. +// --------------------------------------------------------------- + +export interface CollabHandle { + /** Read-only snapshot of the current in-memory graph. The returned + * object is a structured clone — mutating it does NOT mutate the + * editor. Use applyRemotePatch() to write. */ + snapshot(): FlowgoGraph; + + /** Mutate the live graph and trigger a re-render. The callback + * receives a mutable reference to the graph; mutations propagate + * to the next render frame. Selection and focus are preserved. */ + applyRemotePatch(fn: (g: FlowgoGraph) => void): void; + + /** Subscribe to local mutation events. Returns an unsubscribe + * function. The plugin is expected to diff its own store against + * snapshot() inside the callback and forward deltas. */ + onLocalMutation(cb: (e: MutationEvent) => void): () => void; +} + +// --------------------------------------------------------------- +// Bootstrap-order plumbing. +// --------------------------------------------------------------- + +let handle: CollabHandle | null = null; +const awaiting: Array<(h: CollabHandle) => void> = []; + +/** Plugin entry point: register a callback that fires as soon as the + * editor is ready to be wired. Safe to call before OR after the + * editor's main.ts has run. */ +export const whenCollabReady = (cb: (h: CollabHandle) => void): void => { + if (handle) { + cb(handle); + return; + } + awaiting.push(cb); +}; + +/** Editor entry point: install the handle. Called once by main.ts at + * the end of bootstrap. Calling twice replaces the handle (last one + * wins) and re-fires any waiting plugins against the new handle. */ +export const exposeCollabHandle = (h: CollabHandle): void => { + handle = h; + const pending = awaiting.splice(0); + for (const cb of pending) cb(h); +}; diff --git a/src/editor/main.ts b/src/editor/main.ts index 110d0f6..330d55a 100644 --- a/src/editor/main.ts +++ b/src/editor/main.ts @@ -42,6 +42,7 @@ import { wirePersistence, } from "./persistence.ts"; import { mutatedCurrentMap, wireMutations } from "./mutations.ts"; +import { exposeCollabHandle } from "./collab-api.ts"; import { cloneSelection as cloneSelectionPure, wireClone, @@ -236,8 +237,16 @@ wirePersistence({ // Every mutation funnels through mutations.ts. The default wiring // just calls scheduleSave; downstream consumers can swap it without -// touching the 26 call sites. -wireMutations({ scheduleSave: () => scheduleSave() }); +// touching the 26 call sites. onMutate fans events out to any +// collab plugin that called whenCollabReady (see collab-api.ts). +const onLocalMutationCallbacks = []; +wireMutations({ + scheduleSave: () => scheduleSave(), + getMapPath: () => currentPath, + onMutate: (e) => { + for (const cb of onLocalMutationCallbacks) cb(e); + }, +}); wireClone({ currentMap: () => state, @@ -391,3 +400,25 @@ fetch("/version") }) .catch(() => { /* version stamp is best-effort */ }); +// Expose the collab extension point. Plugins import whenCollabReady +// from "@flowgo/editor/collab"; nothing in the CLI / single-user +// bundle imports it so this is a no-op there. +exposeCollabHandle({ + snapshot: () => structuredClone(graph), + applyRemotePatch: (fn) => { + fn(graph); + // The graph mutation may have changed the current map's contents + // (or even removed it). Refresh the local `state` alias so the + // next render reads the right slice. + state = ensureMap(currentPath); + renderAll(); + }, + onLocalMutation: (cb) => { + onLocalMutationCallbacks.push(cb); + return () => { + const i = onLocalMutationCallbacks.indexOf(cb); + if (i >= 0) onLocalMutationCallbacks.splice(i, 1); + }; + }, +}); + diff --git a/src/editor/mutations.ts b/src/editor/mutations.ts index 70e35e8..035fdd5 100644 --- a/src/editor/mutations.ts +++ b/src/editor/mutations.ts @@ -2,13 +2,38 @@ // mutation seam in the editor calls one of the typed mutator // functions below instead of scheduleSave() directly. // -// Today every mutator funnels into the wired scheduleSave — -// behaviour is identical to calling scheduleSave() at the call site. -// The typed surface exists so that downstream wiring can hook on -// the right kind of change without a 30-site audit later. +// Two hooks fan out from each mutator: +// +// scheduleSave — required. Today this funnels into the editor's +// own debounced save path so file persistence (CLI +// mode) or in-memory session save (serve mode) +// happens automatically. +// onMutate — optional. Fires alongside scheduleSave with a +// typed event so a downstream binding (e.g. a Yjs +// collab plugin) can scope its diff to the right +// kind of change without a 30-site audit. + +export type MutationKind = + | "box" + | "edge" + | "text" + | "line" + | "stroke" + | "currentMap" + | "doc"; + +export interface MutationEvent { + readonly kind: MutationKind; + readonly mapPath: string; +} interface MutationBindings { readonly scheduleSave: () => void; + /** Optional. Returns the editor's currently-focused map path + * (e.g. "/", "/b1", "/b1/c2"). Defaults to "/" if not provided. */ + readonly getMapPath?: () => string; + /** Optional. Called after scheduleSave for every mutation. */ + readonly onMutate?: (e: MutationEvent) => void; } let bindings: MutationBindings | null = null; @@ -17,25 +42,29 @@ export const wireMutations = (b: MutationBindings): void => { bindings = b; }; -const fire = (): void => { +const fire = (kind: MutationKind): void => { if (!bindings) throw new Error("mutations: wireMutations() not called"); bindings.scheduleSave(); + if (bindings.onMutate) { + const mapPath = bindings.getMapPath ? bindings.getMapPath() : "/"; + bindings.onMutate({ kind, mapPath }); + } }; // One function per kind on the current map. The function shapes // reserve room for downstream wiring that wants to scope a diff to -// a specific entity; today they all just fire scheduleSave. +// a specific entity; today they all just fire scheduleSave + onMutate. -export const mutatedBox = (): void => fire(); -export const mutatedEdge = (): void => fire(); -export const mutatedText = (): void => fire(); -export const mutatedLine = (): void => fire(); -export const mutatedStroke = (): void => fire(); +export const mutatedBox = (): void => fire("box"); +export const mutatedEdge = (): void => fire("edge"); +export const mutatedText = (): void => fire("text"); +export const mutatedLine = (): void => fire("line"); +export const mutatedStroke = (): void => fire("stroke"); // The current map changed in a way that spans multiple kinds or // touches the whole map (paste, align, multi-select palette change). -export const mutatedCurrentMap = (): void => fire(); +export const mutatedCurrentMap = (): void => fire("currentMap"); // The document structure changed (maps added/removed via box // deletion, or anything that affects more than one map at once). -export const mutatedDoc = (): void => fire(); +export const mutatedDoc = (): void => fire("doc");