easy_flow 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/easy_flow/canvas.js +1 -1
- data/app/controllers/easy_flow/manage/canvas_controller.rb +26 -12
- data/app/javascript/easy_flow/canvas/Canvas.jsx +9 -4
- data/app/javascript/easy_flow/canvas/Panel.jsx +18 -7
- data/app/javascript/easy_flow/canvas/changes.js +6 -0
- data/app/javascript/easy_flow/canvas/flowSender.js +10 -1
- data/app/javascript/easy_flow/canvas/useFlow.js +3 -2
- data/app/models/easy_flow/change.rb +3 -1
- data/app/models/easy_flow/definition.rb +5 -0
- data/app/models/easy_flow/document.rb +11 -0
- data/app/models/easy_flow/unconnected_steps.rb +44 -0
- data/lib/easy_flow/version.rb +1 -1
- data/the_local/agents/easy_flow-develop.md +1 -0
- data/the_local/agents/easy_flow-info.md +1 -0
- data/the_local/agents/easy_flow-install.md +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 230776aa4cde3ff6b14e622b6556b8fc27c7cc200689fe1760f79c9517419186
|
|
4
|
+
data.tar.gz: 7617ecac9e8bffd31608e407ffb685f21df70311a7693ffb20d923b8abeb7996
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbe9e03688e1a7f6bbe0ec3c1b11112342da52a4a346be1711125530eab22a25c70a52c1b036c42c3dd03c583c324ef44799f713691e2161eaa4d5d64d97a58b
|
|
7
|
+
data.tar.gz: 46ae29e0296da8586207576e9db24fa84e02bab9a1ccd63d3fc5d0e7aca769bbcca126096a31cdadfc869372c4feeb543f96707c1914ab045a516f681f621e99
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var It=globalThis.ReactDOM;var{createPortal:Ee,createRoot:Q,flushSync:Pe,hydrateRoot:De,preconnect:$e,prefetchDNS:Ae,preinit:Le,preinitModule:Ie,preload:Oe,preloadModule:Me,requestFormReset:He,unstable_batchedUpdates:Fe,useFormState:je,useFormStatus:We,version:Ve}=It;var R=globalThis.React,e=R,{Children:Ye,Component:Xe,Fragment:Ge,PureComponent:qe,StrictMode:Je,Suspense:Ke,cloneElement:Ze,createContext:Qe,createElement:Re,createRef:to,forwardRef:eo,isValidElement:oo,lazy:ro,memo:no,startTransition:so,useCallback:ao,useContext:io,useDebugValue:lo,useDeferredValue:co,useEffect:P,useId:po,useImperativeHandle:uo,useLayoutEffect:tt,useMemo:D,useReducer:mo,useRef:U,useState:v,useSyncExternalStore:fo,useTransition:go}=R;var et=new Map,O=new Map,ot=(t,o)=>et.set(t,o),rt=()=>{O.forEach(t=>t.unmount()),O.clear()},nt=(t,o)=>t.querySelectorAll("[data-react-ui]").forEach(n=>{if(O.has(n))return;let r=et.get(n.dataset.reactUi),a=o(n);O.set(n,a),a.render(e.createElement(r,JSON.parse(n.dataset.props)))});var st=(t,o)=>{let n=()=>nt(t,o);t.addEventListener("turbo:load",n),t.addEventListener("turbo:frame-load",n),t.addEventListener("turbo:before-cache",rt),t.readyState==="loading"?t.addEventListener("DOMContentLoaded",n):n()};var z=(...t)=>t.filter(Boolean).join(" ");var Ot={primary:"ks-button-primary",secondary:"ks-button-secondary",danger:"ks-button-danger"},Mt={sm:"ks-button-sm",md:"ks-button-md",lg:"ks-button-lg"},Ht=({variant:t="primary",size:o="md",className:n,children:r,...a})=>{let d=a.href?"a":"button";return e.createElement(d,{...a,className:z("ks-button",Ot[t],Mt[o],n)},r)},b=Ht;var Ft="border border-amber-500 bg-white text-amber-700 dark:bg-zinc-900 dark:text-amber-400",jt="border border-gray-300 bg-white text-gray-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-gray-400",Wt="border border-accent-600 bg-accent-600 text-white",Vt=({name:t,connected:o,armed:n,onArm:r,connecting:a})=>e.createElement("button",{onClick:d=>{a||(d.stopPropagation(),r(d))},title:n?"Now choose a step to connect to":"Connect this branch",className:n?Wt:o?jt:Ft,style:{padding:"1px 8px",marginRight:4,borderRadius:999,fontSize:11,cursor:"pointer"}},t||"next"),at=Vt;var Ut={width:240,boxSizing:"border-box",borderRadius:8},Yt={padding:"8px 14px",textAlign:"center",fontWeight:600},Xt={fontWeight:600,lineHeight:1.3},Gt=(t,o,n)=>{let r=n?"ring-4 ring-accent-600/15":"shadow-sm";return t||n&&!o?`border-accent-600 ${r}`:o?`border-red-600 ${r}`:"border-gray-300 shadow-sm dark:border-zinc-700"},qt=(t,o)=>o&&t.label===t.id&&t.type_name||t.label,Jt=(t,o,n)=>n?o===t.type_name?null:t.type_name:t.type,Kt=({node:t,selected:o,armed:n,connecting:r,onSelect:a,onOpen:d,onArm:l,onDragEnd:i,onDragStart:m,hideStepTypeCodes:c})=>{let u=t.begins_here||t.ends_here,p=t.begins_here,g=t.ends_here?[]:t.ports,y=r&&!p,k=qt(t,c),w=Jt(t,k,c);return e.createElement("div",{ref:t.ref,"data-step":t.id,draggable:!p,onDragStart:h=>{h.dataTransfer.effectAllowed="move",h.dataTransfer.setData("text/plain",t.id),m()},onDragEnd:i,onClick:t.opens&&!r?()=>d(t.opens):a,className:`bg-white border-2 dark:bg-zinc-900 ${Gt(y,t.violations.length>0,o)}`,style:{...Ut,padding:u?0:"12px 14px",cursor:p?"default":r?"crosshair":"grab"}},u&&e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:Yt},k),!u&&e.createElement("div",{style:Xt},k),!u&&w&&e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginTop:2}},w),t.picture_url&&e.createElement("div",{className:"text-accent-600 dark:text-accent-400",style:{fontSize:11,marginTop:2}},"Has a picture"),t.starts_a_flow&&e.createElement("div",{className:"text-accent-600 dark:text-accent-400",style:{fontSize:11,marginTop:2}},"Runs a flow"),t.opens&&e.createElement(b,{variant:"secondary",size:"sm",className:"mt-2",onClick:h=>{h.stopPropagation(),a()}},"Settings"),t.violations.map(h=>e.createElement("div",{key:h.problem+h.detail,className:"text-red-600 dark:text-red-400",style:{fontSize:11,padding:"0 14px 6px"}},h.problem.replace(/_/g," "),h.detail?`: ${h.detail}`:"")),g.length>0&&e.createElement("div",{style:{padding:t.begins_here?"0 14px 8px":"10px 0 0"}},g.map(h=>e.createElement(at,{key:h,name:h,connected:t.connected.includes(h),connecting:r,armed:n===h,onArm:C=>l(h,C)}))))},Y=Kt;var X=(t,o)=>t.includes(o)?t.filter(n=>n!==o):[...t,o],it=(t,o)=>({...o?.choices||{},...t?.choices||{}});var Zt=({as:t="input",disabled:o,className:n,...r})=>e.createElement(t,{...r,disabled:o,className:z("ks-input",o&&"ks-input-disabled",n)}),B=Zt;var Qt=t=>e.createElement(B,{...t,as:"select"}),lt=Qt;var Rt=({className:t,...o})=>e.createElement("input",{type:"checkbox",...o,className:z("ks-checkbox",t)}),M=Rt;var dt=t=>(t||[]).map(o=>typeof o=="object"?o:{value:o,label:o}),te=({type:t,value:o,choices:n,onChange:r,onSettle:a,onPick:d,pictureUrl:l})=>{if(t==="picture")return e.createElement("div",{className:"mb-3"},o&&l&&e.createElement("img",{src:l,alt:"The step's picture",style:{maxWidth:"100%"}}),o&&e.createElement("button",{type:"button","data-remove-picture":!0,className:"ks-button ks-button-secondary ks-button-sm",onClick:()=>a("")},"Remove picture"),e.createElement("input",{type:"file",accept:"image/*",onChange:i=>i.target.files[0]&&d(i.target.files[0])}));if(t==="boolean")return e.createElement(M,{checked:!!o,onChange:i=>a(i.target.checked)});if(t==="select"||t==="previous_step"||t==="from_step"||t==="flow")return e.createElement(lt,{className:"mb-3",value:o??"",onChange:i=>a(i.target.value)},e.createElement("option",{value:""}),dt(n).map(i=>e.createElement("option",{key:i.value,value:i.value},i.label)));if(t==="multi_select"){let i=Array.isArray(o)?o:[],m=c=>a(X(i,c));return e.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:8}},(n||[]).map(c=>e.createElement("label",{key:c,style:{display:"inline-flex",alignItems:"center",gap:4}},e.createElement(M,{checked:i.includes(c),onChange:()=>m(c)}),e.createElement("span",null,c))))}if(t==="flows"){let i=Array.isArray(o)?o:[];return e.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4},className:"mb-3"},dt(n).map(m=>e.createElement("label",{key:m.value,style:{display:"inline-flex",alignItems:"center",gap:4}},e.createElement(M,{checked:i.includes(m.value),onChange:()=>a(X(i,m.value))}),e.createElement("span",null,m.label))))}return t==="integer"||t==="float"?e.createElement(B,{className:"mb-3",type:"number",step:t==="integer"?"1":"any",value:o??"",onChange:i=>r(i.target.value),onBlur:i=>a(i.target.value)}):e.createElement(B,{className:"mb-3",type:"text",value:o??"",onChange:i=>r(i.target.value),onBlur:i=>a(i.target.value)})},H=te;var ct=(t,o,n,r)=>t.map((a,d)=>d===o?{...a,[n]:r}:a);var ee=(t,o,n,r)=>{let a=(t||{})[n],d=(o||{})[n];return d?(a||{})[r[d]]:a},oe=({holds:t,labels:o,rows:n,choices:r,outputsOf:a,onChange:d,onSettle:l})=>{let i=Array.isArray(n)?n:[],m=(c,u,p,g)=>{let y=ct(i,c,u,p);g?l(y):d(y)};return e.createElement("div",{style:{marginBottom:12}},i.map((c,u)=>e.createElement("div",{key:u,className:"rounded-md border border-gray-200 dark:border-zinc-700",style:{padding:"8px 8px 2px",marginBottom:6}},Object.entries(t).map(([p,g])=>e.createElement("label",{key:p,style:{display:"block"}},e.createElement("span",{className:"text-gray-500 dark:text-gray-400",style:{display:"block",marginBottom:2,fontSize:11}},(o||{})[p]||p),e.createElement(H,{type:g,value:c[p],choices:ee(r,a,p,c),onChange:y=>m(u,p,y,!1),onSettle:y=>m(u,p,y,!0)}))),e.createElement(b,{variant:"secondary",size:"sm",className:"mb-1.5",onClick:()=>l(i.filter((p,g)=>g!==u))},"Remove"))),e.createElement(b,{variant:"secondary",size:"sm",className:"w-full",onClick:()=>l([...i,{}])},"Add"))},pt=oe;var re=({as:t="div",className:o,children:n,...r})=>e.createElement(t,{...r,className:z("ks-panel",o)},n),A=re;var F=(t,o)=>({className:n,children:r,...a})=>e.createElement(t,{...a,className:z(o,n)},r),I=F("label","ks-label"),ar=F("p","ks-hint"),ir=F("p","ks-error"),lr=F("span","ks-required");var ne={width:280,padding:20,overflowY:"auto"},se=({node:t,fields:o,holds:n,labels:r,recordLabels:a,recordChoices:d,recordOutputsOf:l,choices:i,onSave:m,onDelete:c,onClose:u,onPickPicture:p,refusal:g})=>{let[y,k]=v(t.config),[w,h]=v(t.picture_url);P(()=>h(t.picture_url),[t.id,t.picture_url]),P(()=>k(t.config),[t.id,t.config]);let C=x=>{k(x),JSON.stringify(x)!==JSON.stringify(t.config)&&m(x)};return e.createElement(A,{as:"aside",style:ne,"data-inspector":!0},e.createElement("div",{style:{display:"flex",alignItems:"start",justifyContent:"space-between",gap:8}},e.createElement("h2",{style:{fontWeight:600,marginBottom:2}},t.label),e.createElement("button",{title:"Close",onClick:u,className:"text-gray-500 dark:text-gray-400",style:{border:"none",background:"none",cursor:"pointer",fontSize:18,lineHeight:1}},"\xD7")),e.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginBottom:16}},t.id," \xB7 ",t.type),g&&e.createElement("p",{className:"text-red-800 dark:text-red-400",style:{fontWeight:600,marginBottom:12},"data-refusal":!0},g),Object.entries(o).map(([x,_])=>e.createElement(I,{key:x},e.createElement("span",{style:{display:"block",marginBottom:3}},r[x]||x),_==="list"?e.createElement(pt,{holds:n[x]||{},labels:a[x]||{},rows:y[x],choices:{...(d||{})[x],...(t.record_choices||{})[x]},outputsOf:(l||{})[x],onChange:S=>k({...y,[x]:S}),onSettle:S=>C({...y,[x]:S})}):e.createElement(H,{type:_,value:y[x],choices:i[x],pictureUrl:w,onChange:S=>k({...y,[x]:S}),onSettle:S=>C({...y,[x]:S}),onPick:async S=>{let L=await p(S);L&&(h(L.url),C({...y,[x]:L.reference}))}}))),e.createElement(b,{variant:"danger",size:"sm",className:"w-full",onClick:c},"Delete step"))},ut=se;var ae={width:240,boxSizing:"border-box",padding:"10px 14px",borderRadius:8,textAlign:"center",fontSize:12,cursor:"pointer"},ie="border-2 border-dashed border-amber-500 bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300",le="border-2 border-dashed border-accent-600 bg-accent-50 text-accent-800 dark:bg-accent-950 dark:text-accent-200",de=({node:t,dragging:o,onFill:n,onDrop:r})=>e.createElement("div",{ref:t.ref,"data-placeholder":t.id,title:`Choose the step \u201C${t.label}\u201D should lead to`,onClick:n,onDragOver:a=>{o&&(a.preventDefault(),a.dataTransfer.dropEffect="move")},onDrop:a=>{a.preventDefault(),r()},className:o?le:ie,style:ae},e.createElement("div",{style:{fontWeight:600}},t.label),e.createElement("div",{style:{fontSize:11}},o?"drop a step here":"leads nowhere yet \u2014 click to choose")),mt=de;var ce=({entries:t,at:o,onPick:n,onConnect:r,onDismiss:a,hideStepTypeCodes:d})=>e.createElement(A,{className:"shadow-lg",style:{position:"absolute",zIndex:9,top:o.y,left:o.x,width:210,padding:8,borderRadius:8}},e.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{margin:"0 0 6px",fontSize:11}},"Add a step"),t.map(l=>e.createElement(b,{key:l.type,variant:"secondary",size:"sm",className:"w-full mb-1.5 flex-col",onClick:()=>n(l)},l.label,!d&&e.createElement("span",{className:"opacity-75",style:{display:"block",fontSize:11}},l.type))),r&&e.createElement(b,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:r},"Connect to a step already here"),e.createElement(b,{variant:"secondary",size:"sm",className:"w-full",onClick:a},"Cancel")),ft=ce;var pe={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},gt=({label:t,title:o,idle:n,enabled:r,onUse:a})=>e.createElement(b,{variant:"secondary",size:"sm",title:r?o:n,disabled:!r,onClick:a,style:{opacity:r?1:.4,cursor:r?"pointer":"default"}},t),ue=({undoable:t,redoable:o,onAdd:n,onUndo:r,onRedo:a})=>e.createElement("div",{style:{position:"absolute",top:16,left:16,zIndex:5,display:"flex",gap:8}},e.createElement(b,{variant:"secondary",className:"rounded-full",style:pe,title:"Add a step",onClick:n},"+"),e.createElement(gt,{label:"\u21B6 Undo",title:"Undo the last change",idle:"Nothing to undo",enabled:t,onUse:r}),e.createElement(gt,{label:"\u21B7 Redo",title:"Redo the change you undid",idle:"Nothing to redo",enabled:o,onUse:a})),yt=ue;var ht=t=>{let o=t.problem.replace(/_/g," ");return t.node?`\u201C${t.node}\u201D ${o}`:o};var xt=({version:t,published:o})=>t?o?o===t?`Version ${t} \xB7 live`:`Version ${t} \xB7 visitors still on ${o}`:`Version ${t} \xB7 never published`:"No version yet";var me={width:280,padding:20,overflowY:"auto"},G={fontWeight:600,marginBottom:6,marginTop:14},j={fontSize:12,marginBottom:4,lineHeight:1.4},bt={display:"block",marginBottom:3},vt=(t,o,n)=>r=>{r.target.value!==(t[o]??"")&&n({[o]:r.target.value})},fe=({flow:t,changes:o,problems:n,refusal:r,notice:a,publishNote:d,onCreate:l,onPublish:i,onSaveDetails:m,onClose:c})=>e.createElement(A,{as:"aside",style:me,"data-builder-panel":!0},e.createElement("div",{style:{display:"flex",alignItems:"start",justifyContent:"space-between",gap:8}},e.createElement("h2",{style:{fontWeight:600},"data-flow-name":!0},t.title||t.slug),e.createElement("button",{title:"Close",onClick:c,className:"text-gray-500 dark:text-gray-400",style:{border:"none",background:"none",cursor:"pointer",fontSize:18,lineHeight:1}},"\xD7")),e.createElement("a",{className:"text-accent-600 dark:text-accent-400",style:{fontSize:12,display:"block"},href:t.history_url,"data-history":!0},xt(t)),e.createElement("div",{style:{marginTop:14}},e.createElement(b,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:l,"data-create-version":!0},"Create version"),e.createElement(b,{size:"sm",className:"w-full mb-1.5",onClick:i,"data-publish":!0},"Publish"),d&&e.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12,lineHeight:1.4},"data-publish-note":!0},d)),e.createElement("h2",{style:G},"Problems"),r&&e.createElement("p",{className:"text-red-800 dark:text-red-400",style:{...j,fontWeight:600},"data-refusal":!0},r),a&&!r&&e.createElement("p",{className:"text-emerald-800 dark:text-emerald-400",style:{...j,fontWeight:600},"data-notice":!0},a),n.length===0&&!r?e.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12}},"Nothing wrong with this flow."):n.map(u=>e.createElement("p",{key:`${u.node}-${u.problem}`,className:"text-amber-700 dark:text-amber-400",style:j,"data-problem":!0},"\u26A0 ",ht(u))),e.createElement("h2",{style:G},"Changes since the last version"),o.length===0?e.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12}},"Nothing has changed."):o.map((u,p)=>e.createElement("p",{key:p,style:j,"data-change":!0},u)),e.createElement("h2",{style:G},"Details"),e.createElement(I,null,e.createElement("span",{style:bt},"Title"),e.createElement(B,{className:"mb-3",defaultValue:t.title??"",onBlur:vt(t,"title",m),"data-flow-title":!0})),e.createElement(I,null,e.createElement("span",{style:bt},"Start label"),e.createElement(B,{className:"mb-3",defaultValue:t.start_label??"",onBlur:vt(t,"start_label",m),"data-flow-start-label":!0})),e.createElement("div",{style:{marginTop:16}},e.createElement(b,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:t.definition_url,"data-definition":!0},"Definition"),e.createElement(b,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:t.details_url,"data-details":!0},"Edit details"))),kt=fe;var wt={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},Ct=t=>({opacity:t?1:0,transition:"opacity .12s"}),ge=({link:t,onInsert:o,onRemove:n,onDrop:r,dragging:a,alwaysShowInsertButtons:d})=>{let[l,i]=v(!1),m=l||a;return e.createElement("div",{"data-connector":`${t.source}-${t.target}`,style:{position:"absolute",left:t.midX-34,top:t.midY-20,width:68,height:40,zIndex:4},onMouseEnter:()=>i(!0),onMouseLeave:()=>i(!1),onDragEnter:()=>i(!0),onDragLeave:()=>i(!1),onDragOver:c=>{c.preventDefault(),c.dataTransfer.dropEffect="move"},onDrop:c=>{c.preventDefault(),i(!1),r()}},e.createElement("div",{style:{display:"flex",gap:4,justifyContent:"center",alignItems:"center",height:"100%"}},e.createElement(b,{variant:"secondary",title:a?"Move the step here":"Insert a step here",onClick:o,className:"rounded-full data-[over=true]:ring-2 data-[over=true]:ring-accent-600","data-over":!!(a&&l),style:{...wt,...Ct(m||d)}},"+"),!a&&e.createElement(b,{variant:"danger",title:"Remove this connection",onClick:n,className:"rounded-full",style:{...wt,...Ct(m)}},"\xD7")))},St=ge;var ye=({links:t,extent:o,dragging:n,onInsert:r,onRemove:a,onDrop:d,alwaysShowInsertButtons:l})=>e.createElement(e.Fragment,null,e.createElement("svg",{width:o.width,height:o.height,style:{position:"absolute",top:0,left:0,pointerEvents:"none",zIndex:1}},e.createElement("defs",null,e.createElement("marker",{id:"easy_flow-arrow",viewBox:"0 0 10 10",refX:"8",refY:"5",markerWidth:"5",markerHeight:"5",orient:"auto"},e.createElement("path",{d:"M 0 0 L 10 5 L 0 10 z",className:"fill-gray-400 dark:fill-zinc-500"}))),t.map(i=>e.createElement("path",{key:i.id,"data-link":i.id,className:"stroke-gray-400 dark:stroke-zinc-500",fill:"none",strokeWidth:"1.5",markerEnd:"url(#easy_flow-arrow)",d:i.path}))),t.filter(i=>i.label).map(i=>e.createElement("div",{key:`${i.id}-label`,"data-link-label":i.id,className:"bg-surface-50 text-gray-500 dark:bg-surface-950 dark:text-gray-400",style:{position:"absolute",left:i.midX+(l?2:-10),top:i.midY-18,zIndex:3,fontSize:11,padding:"0 3px"}},i.label)),t.filter(i=>!i.placeholder).map(i=>e.createElement(St,{key:i.id,link:i,dragging:!!n,alwaysShowInsertButtons:l,onInsert:()=>r(i),onRemove:()=>a(i),onDrop:()=>d(i)}))),Nt=ye;var zt="Your change was not saved because the server could not be reached.",_t=({base:t,token:o,fetch:n,onFlow:r,onError:a,onNotice:d})=>async(l,i,m)=>{let c=await n(t+l,{method:i,headers:{"Content-Type":"application/json","X-CSRF-Token":o},body:m&&JSON.stringify(m)}).catch(()=>null);if(!c)return d(null),a(zt);let u=await c.json().catch(()=>({}));if(!c.ok)return d(null),a(u.error||"That change was refused");a(null),d(u.notice||null),r(await(await n(t+".json",{headers:{Accept:"application/json"}})).json())},Bt=({base:t,token:o,fetch:n,onError:r})=>async a=>{let d=new FormData;d.append("picture",a);let l=await n(t+"/pictures",{method:"POST",headers:{"X-CSRF-Token":o},body:d}).catch(()=>null);if(!l)return r(zt),null;let i=await l.json().catch(()=>({}));return l.ok?(r(null),i):(r(i.error||"That picture was refused"),null)};var he=(t,o,n)=>{let[r,a]=v(n),[d,l]=v(null),[i,m]=v(null),c=D(()=>_t({base:t,token:o,fetch:(...p)=>window.fetch(...p),onFlow:a,onError:l,onNotice:m}),[t,o]),u=D(()=>Bt({base:t,token:o,fetch:(...p)=>window.fetch(...p),onError:l}),[t,o]);return{flow:r,error:d,notice:i,send:c,pickPicture:u}},Tt=he;var W=(t,o,n,r)=>{let a=t.left-n.left+r.scrollLeft,d=t.top-n.top+r.scrollTop;return o==="left"?{x:a,y:d+t.height/2}:o==="right"?{x:a+t.width,y:d+t.height/2}:o==="top"?{x:a+t.width/2,y:d}:{x:a+t.width/2,y:d+t.height}},xe=(t,o)=>o==="top"?{x:t.x,y:t.y-3}:o==="left"?{x:t.x-3,y:t.y}:o==="right"?{x:t.x+3,y:t.y}:{x:t.x,y:t.y+3},be=(t,o,n,r,a)=>{let d={straight:`M ${o.x} ${o.y} L ${n.x} ${n.y}`,turn:`M ${o.x} ${o.y} H ${n.x} V ${n.y}`,lane:`M ${o.x} ${o.y} V ${r} H ${n.x} V ${n.y}`,detour:`M ${o.x} ${o.y} H ${a} V ${n.y} H ${n.x}`}[t.route];return{...t,path:d,midX:t.route==="detour"?a:(o.x+n.x)/2,midY:t.route==="lane"?r:(o.y+n.y)/2}},ve=(t,o,n,r)=>{let[a,d]=v([]),[l,i]=v({width:"100%",height:"100%"});return tt(()=>{let m=()=>{let u=o.current?.getBoundingClientRect();u&&(i({width:o.current.scrollWidth,height:o.current.scrollHeight}),d(t.edges.flatMap(p=>{let g=n.current[p.source]?.getBoundingClientRect(),y=n.current[p.target]?.getBoundingClientRect();if(!g||!y)return[];let k=W(g,p.leaves,u,o.current),w=xe(W(y,p.enters,u,o.current),p.enters),h=(W(g,"bottom",u,o.current).y+W(y,"top",u,o.current).y)/2,C=p.leaves==="left"?Math.min(k.x,w.x)-28:Math.max(k.x,w.x)+28;return[be(p,k,w,h,C)]})))};m(),window.addEventListener("resize",m);let c=new ResizeObserver(m);return o.current&&c.observe(o.current),()=>{window.removeEventListener("resize",m),c.disconnect()}},r),{links:a,extent:l}},Et=ve;var Pt=(t,o)=>{let n=new Set(o),r=t,a=2;for(;n.has(r);)r=`${t}_${a++}`;return r};var Ce={display:"flex",height:"100%",minHeight:0,fontSize:13},Se={flex:1,overflow:"auto",position:"relative"},Ne={position:"absolute",top:56,left:16,zIndex:5,width:264,padding:12,borderRadius:8},ze={display:"grid",rowGap:64,columnGap:0,padding:40,justifyContent:"center",position:"relative"},_e={position:"sticky",zIndex:8,top:8,margin:"8px auto 0",width:"fit-content",padding:"6px 12px",borderRadius:6,fontSize:12},Be=({port:t,onCancel:o})=>e.createElement("div",{className:"bg-accent-100 text-accent-800 dark:bg-accent-900 dark:text-accent-100",style:_e},"Choose the step \u201C",t||"next","\u201D should lead to \u2014 ",e.createElement("button",{onClick:o,className:"text-accent-800 underline dark:text-accent-100",style:{border:"none",background:"none",cursor:"pointer",fontSize:12,padding:0}},"cancel")),Te=({base:t,token:o,initial:n})=>{let{flow:r,error:a,notice:d,send:l,pickPicture:i}=Tt(t,o,n),[m,c]=v(null),[u,p]=v(null),[g,y]=v(null),[k,w]=v(null),[h,C]=v(!1),x=U({}),_=U(null);P(()=>{let s=()=>{c(null),C(!0)};return document.addEventListener("easy_flow:open-flow",s),()=>document.removeEventListener("easy_flow:open-flow",s)},[]),P(()=>{if(!h)return;let s=f=>{f.target.closest("[data-builder-panel], [data-open-panel]")||C(!1)};return document.addEventListener("click",s),()=>document.removeEventListener("click",s)},[h]),P(()=>{document.dispatchEvent(new CustomEvent("easy_flow:flow-named",{detail:r.flow?.title||r.flow?.slug}))},[r.flow?.title,r.flow?.slug]);let S=D(()=>Object.fromEntries(r.nodes.map(s=>[s.id,s])),[r.nodes]),{links:L,extent:$t}=Et(r,_,x,[r,m,S]),q=D(()=>{let s={};return r.violations.forEach(f=>{var N;return(s[N=f.node]||(s[N]=[])).push(f)}),s},[r.violations]),V=r.nodes.filter(s=>!s.loose),J=r.nodes.filter(s=>s.loose),At=Math.max(0,...V.map(s=>s.row))+1,Lt=Math.max(0,...V.map(s=>s.column))+1,K=s=>{if(!g)return;let[f,N]=g;y(null),f!==s&&l("/edges","POST",{from:f,to:s,on:N||null})},T=r.nodes.find(s=>s.id===m),E=[...r.palette,...r.unoffered||[]].find(s=>s.type===T?.type);return e.createElement("div",{className:"text-gray-900 dark:text-gray-100",style:Ce,onMouseUp:()=>w(null),onKeyDown:s=>{s.key==="Escape"&&(c(null),y(null),p(null))},tabIndex:-1},e.createElement("div",{ref:_,className:"bg-surface-50 dark:bg-surface-950",style:Se,onClick:s=>{s.target===_.current&&(c(null),y(null),C(!1))}},g&&e.createElement(Be,{port:g[1],onCancel:()=>y(null)}),e.createElement(yt,{undoable:r.undoable,redoable:r.redoable,onAdd:()=>p({at:{x:16,y:52}}),onUndo:()=>{c(null),l("/undo","POST")},onRedo:()=>{c(null),l("/redo","POST")}}),e.createElement(Nt,{links:L,extent:$t,dragging:k,alwaysShowInsertButtons:r.always_show_insert_buttons,onInsert:s=>p({from:s.source,to:s.target,at:{x:s.midX+30,y:s.midY}}),onRemove:s=>{c(null),l("/edges","DELETE",{from:s.source,to:s.target})},onDrop:s=>{let f=k;w(null),l("/steps/"+f+"/move","PATCH",{from:s.source,to:s.target})}}),J.length>0&&e.createElement("div",{"data-loose":!0,className:"border border-dashed border-gray-300 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-900",style:Ne},e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginBottom:6}},"Not in the flow yet \u2014 drag one onto a connection"),J.map(s=>e.createElement("div",{key:s.id,style:{marginBottom:8}},e.createElement(Y,{node:{...s,ref:f=>{x.current[s.id]=f},violations:q[s.id]||[],connected:[]},selected:m===s.id,armed:g&&g[0]===s.id?g[1]:null,connecting:!!g,onSelect:()=>g?K(s.id):c(s.id),onOpen:f=>window.location.assign(f),onArm:(f,N)=>{N.stopPropagation(),y([s.id,f])},onDragStart:()=>w(s.id),onDragEnd:()=>w(null),hideStepTypeCodes:r.hide_step_type_codes})))),e.createElement("div",{style:{...ze,gridTemplateColumns:`repeat(${Lt+1}, ${280/2}px)`,gridTemplateRows:`repeat(${At}, auto)`}},V.map(s=>e.createElement("div",{key:s.id,style:{gridRow:s.row+1,gridColumn:`${s.column+1} / span 2`,zIndex:2}},s.placeholder&&e.createElement(mt,{node:{...s,ref:f=>{x.current[s.id]=f}},dragging:!!k,onFill:()=>p({from:s.from,on:s.on,at:{x:16,y:52}}),onDrop:()=>{let f=k;w(null),f&&l("/edges","POST",{from:s.from,to:f,on:s.on})}}),!s.placeholder&&e.createElement(Y,{node:{...s,ref:f=>{x.current[s.id]=f},violations:q[s.id]||[],connected:r.edges.filter(f=>f.source===s.id).map(f=>f.label||null)},selected:s.id===m,armed:g&&g[0]===s.id?g[1]:null,connecting:!!g&&g[0]!==s.id,onSelect:()=>g?K(s.id):c(s.id),onOpen:f=>window.location.assign(f),onArm:(f,N)=>{let Z=_.current.getBoundingClientRect();p({from:s.id,on:f,at:{x:N.clientX-Z.left+_.current.scrollLeft+8,y:N.clientY-Z.top+_.current.scrollTop+8}})},onDragEnd:()=>w(null),onDragStart:()=>w(s.id),hideStepTypeCodes:r.hide_step_type_codes})))),u&&e.createElement(ft,{entries:r.palette,at:u.at,onDismiss:()=>p(null),hideStepTypeCodes:r.hide_step_type_codes,onConnect:u.on!==void 0?()=>{y([u.from,u.on]),p(null)}:null,onPick:s=>{let f=u;p(null),l("/steps","POST",{id:Pt(s.type,r.nodes.map(N=>N.id)),type:s.type,from:f.from,to:f.to,on:f.on})}})),h&&!T&&e.createElement(kt,{flow:r.flow||{},changes:r.changes||[],problems:r.violations,refusal:a,notice:d,publishNote:r.publish_note,onClose:()=>C(!1),onCreate:()=>l("/versions","POST"),onPublish:()=>l("/publish","POST"),onSaveDetails:s=>l("/details","PATCH",{flow:s})}),T&&e.createElement(ut,{node:T,fields:E?.fields||{},holds:E?.records||{},labels:E?.labels||{},recordLabels:E?.record_labels||{},recordChoices:E?.record_choices||{},recordOutputsOf:E?.record_outputs_of||{},choices:it(T,E),onClose:()=>c(null),onSave:s=>l(`/steps/${T.id}`,"PATCH",{config:s}),onPickPicture:i,refusal:a,onDelete:()=>{c(null),l(`/steps/${T.id}`,"DELETE")}}))},Dt=Te;ot("easy_flow/flow-editor",Dt);document.addEventListener("easy_flow:flow-named",t=>{let o=document.querySelector("[data-flow-heading]");o&&(o.textContent=t.detail)});st(document,Q);})();
|
|
1
|
+
(()=>{var je=globalThis.ReactDOM;var{createPortal:Lt,createRoot:Q,flushSync:It,hydrateRoot:Ot,preconnect:Mt,prefetchDNS:Ht,preinit:jt,preinitModule:Ft,preload:Wt,preloadModule:Vt,requestFormReset:Ut,unstable_batchedUpdates:Yt,useFormState:Xt,useFormStatus:Gt,version:qt}=je;var R=globalThis.React,t=R,{Children:Kt,Component:Zt,Fragment:Qt,PureComponent:Rt,StrictMode:eo,Suspense:to,cloneElement:oo,createContext:ro,createElement:no,createRef:so,forwardRef:ao,isValidElement:lo,lazy:io,memo:co,startTransition:po,useCallback:uo,useContext:mo,useDebugValue:fo,useDeferredValue:go,useEffect:$,useId:yo,useImperativeHandle:ho,useLayoutEffect:ee,useMemo:A,useReducer:bo,useRef:U,useState:k,useSyncExternalStore:xo,useTransition:vo}=R;var te=new Map,O=new Map,oe=(e,o)=>te.set(e,o),re=()=>{O.forEach(e=>e.unmount()),O.clear()},ne=(e,o)=>e.querySelectorAll("[data-react-ui]").forEach(n=>{if(O.has(n))return;let r=te.get(n.dataset.reactUi),a=o(n);O.set(n,a),a.render(t.createElement(r,JSON.parse(n.dataset.props)))});var se=(e,o)=>{let n=()=>ne(e,o);e.addEventListener("turbo:load",n),e.addEventListener("turbo:frame-load",n),e.addEventListener("turbo:before-cache",re),e.readyState==="loading"?e.addEventListener("DOMContentLoaded",n):n()};var S=(...e)=>e.filter(Boolean).join(" ");var Fe={primary:"ks-button-primary",secondary:"ks-button-secondary",danger:"ks-button-danger"},We={sm:"ks-button-sm",md:"ks-button-md",lg:"ks-button-lg"},Ve=({variant:e="primary",size:o="md",className:n,children:r,...a})=>{let i=a.href?"a":"button";return t.createElement(i,{...a,className:S("ks-button",Fe[e],We[o],n)},r)},v=Ve;var Ue="border border-amber-500 bg-white text-amber-700 dark:bg-zinc-900 dark:text-amber-400",Ye="border border-gray-300 bg-white text-gray-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-gray-400",Xe="border border-accent-600 bg-accent-600 text-white",Ge=({name:e,connected:o,armed:n,onArm:r,connecting:a})=>t.createElement("button",{onClick:i=>{a||(i.stopPropagation(),r(i))},title:n?"Now choose a step to connect to":"Connect this branch",className:n?Xe:o?Ye:Ue,style:{padding:"1px 8px",marginRight:4,borderRadius:999,fontSize:11,cursor:"pointer"}},e||"next"),ae=Ge;var qe={width:240,boxSizing:"border-box",borderRadius:8},Je={padding:"8px 14px",textAlign:"center",fontWeight:600},Ke={fontWeight:600,lineHeight:1.3},Ze=(e,o,n)=>{let r=n?"ring-4 ring-accent-600/15":"shadow-sm";return e||n&&!o?`border-accent-600 ${r}`:o?`border-red-600 ${r}`:"border-gray-300 shadow-sm dark:border-zinc-700"},Qe=(e,o)=>o&&e.label===e.id&&e.type_name||e.label,Re=(e,o,n)=>n?o===e.type_name?null:e.type_name:e.type,et=({node:e,selected:o,armed:n,connecting:r,onSelect:a,onOpen:i,onArm:c,onDragEnd:l,onDragStart:p,hideStepTypeCodes:f})=>{let g=e.begins_here||e.ends_here,d=e.begins_here,h=e.ends_here?[]:e.ports,u=r&&!d,y=Qe(e,f),w=Re(e,y,f);return t.createElement("div",{ref:e.ref,"data-step":e.id,draggable:!d,onDragStart:b=>{b.dataTransfer.effectAllowed="move",b.dataTransfer.setData("text/plain",e.id),p()},onDragEnd:l,onClick:e.opens&&!r?()=>i(e.opens):a,className:`bg-white border-2 dark:bg-zinc-900 ${Ze(u,e.violations.length>0,o)}`,style:{...qe,padding:g?0:"12px 14px",cursor:d?"default":r?"crosshair":"grab"}},g&&t.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:Je},y),!g&&t.createElement("div",{style:Ke},y),!g&&w&&t.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginTop:2}},w),e.picture_url&&t.createElement("div",{className:"text-accent-600 dark:text-accent-400",style:{fontSize:11,marginTop:2}},"Has a picture"),e.starts_a_flow&&t.createElement("div",{className:"text-accent-600 dark:text-accent-400",style:{fontSize:11,marginTop:2}},"Runs a flow"),e.opens&&t.createElement(v,{variant:"secondary",size:"sm",className:"mt-2",onClick:b=>{b.stopPropagation(),a()}},"Settings"),e.violations.map(b=>t.createElement("div",{key:b.problem+b.detail,className:"text-red-600 dark:text-red-400",style:{fontSize:11,padding:"0 14px 6px"}},b.problem.replace(/_/g," "),b.detail?`: ${b.detail}`:"")),h.length>0&&t.createElement("div",{style:{padding:e.begins_here?"0 14px 8px":"10px 0 0"}},h.map(b=>t.createElement(ae,{key:b,name:b,connected:e.connected.includes(b),connecting:r,armed:n===b,onArm:C=>c(b,C)}))))},Y=et;var X=(e,o)=>e.includes(o)?e.filter(n=>n!==o):[...e,o],le=(e,o)=>({...o?.choices||{},...e?.choices||{}});var tt=({as:e="input",disabled:o,className:n,...r})=>t.createElement(e,{...r,disabled:o,className:S("ks-input",o&&"ks-input-disabled",n)}),_=tt;var ot=e=>t.createElement(_,{...e,as:"select"}),ie=ot;var rt=({className:e,...o})=>t.createElement("input",{type:"checkbox",...o,className:S("ks-checkbox",e)}),M=rt;var de=e=>(e||[]).map(o=>typeof o=="object"?o:{value:o,label:o}),nt=({type:e,value:o,choices:n,onChange:r,onSettle:a,onPick:i,pictureUrl:c})=>{if(e==="picture")return t.createElement("div",{className:"mb-3"},o&&c&&t.createElement("img",{src:c,alt:"The step's picture",style:{maxWidth:"100%"}}),o&&t.createElement("button",{type:"button","data-remove-picture":!0,className:"ks-button ks-button-secondary ks-button-sm",onClick:()=>a("")},"Remove picture"),t.createElement("input",{type:"file",accept:"image/*",onChange:l=>l.target.files[0]&&i(l.target.files[0])}));if(e==="boolean")return t.createElement(M,{checked:!!o,onChange:l=>a(l.target.checked)});if(e==="select"||e==="previous_step"||e==="from_step"||e==="flow")return t.createElement(ie,{className:"mb-3",value:o??"",onChange:l=>a(l.target.value)},t.createElement("option",{value:""}),de(n).map(l=>t.createElement("option",{key:l.value,value:l.value},l.label)));if(e==="multi_select"){let l=Array.isArray(o)?o:[],p=f=>a(X(l,f));return t.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:8}},(n||[]).map(f=>t.createElement("label",{key:f,style:{display:"inline-flex",alignItems:"center",gap:4}},t.createElement(M,{checked:l.includes(f),onChange:()=>p(f)}),t.createElement("span",null,f))))}if(e==="flows"){let l=Array.isArray(o)?o:[];return t.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4},className:"mb-3"},de(n).map(p=>t.createElement("label",{key:p.value,style:{display:"inline-flex",alignItems:"center",gap:4}},t.createElement(M,{checked:l.includes(p.value),onChange:()=>a(X(l,p.value))}),t.createElement("span",null,p.label))))}return e==="integer"||e==="float"?t.createElement(_,{className:"mb-3",type:"number",step:e==="integer"?"1":"any",value:o??"",onChange:l=>r(l.target.value),onBlur:l=>a(l.target.value)}):t.createElement(_,{className:"mb-3",type:"text",value:o??"",onChange:l=>r(l.target.value),onBlur:l=>a(l.target.value)})},H=nt;var ce=(e,o,n,r)=>e.map((a,i)=>i===o?{...a,[n]:r}:a);var st=(e,o,n,r)=>{let a=(e||{})[n],i=(o||{})[n];return i?(a||{})[r[i]]:a},at=({holds:e,labels:o,rows:n,choices:r,outputsOf:a,onChange:i,onSettle:c})=>{let l=Array.isArray(n)?n:[],p=(f,g,d,h)=>{let u=ce(l,f,g,d);h?c(u):i(u)};return t.createElement("div",{style:{marginBottom:12}},l.map((f,g)=>t.createElement("div",{key:g,className:"rounded-md border border-gray-200 dark:border-zinc-700",style:{padding:"8px 8px 2px",marginBottom:6}},Object.entries(e).map(([d,h])=>t.createElement("label",{key:d,style:{display:"block"}},t.createElement("span",{className:"text-gray-500 dark:text-gray-400",style:{display:"block",marginBottom:2,fontSize:11}},(o||{})[d]||d),t.createElement(H,{type:h,value:f[d],choices:st(r,a,d,f),onChange:u=>p(g,d,u,!1),onSettle:u=>p(g,d,u,!0)}))),t.createElement(v,{variant:"secondary",size:"sm",className:"mb-1.5",onClick:()=>c(l.filter((d,h)=>h!==g))},"Remove"))),t.createElement(v,{variant:"secondary",size:"sm",className:"w-full",onClick:()=>c([...l,{}])},"Add"))},pe=at;var lt=({as:e="div",className:o,children:n,...r})=>t.createElement(e,{...r,className:S("ks-panel",o)},n),L=lt;var j=(e,o)=>({className:n,children:r,...a})=>t.createElement(e,{...a,className:S(o,n)},r),I=j("label","ks-label"),pr=j("p","ks-hint"),ur=j("p","ks-error"),mr=j("span","ks-required");var it={width:280,padding:20,overflowY:"auto"},dt=({node:e,fields:o,holds:n,labels:r,recordLabels:a,recordChoices:i,recordOutputsOf:c,choices:l,onSave:p,onDelete:f,onClose:g,onPickPicture:d,refusal:h})=>{let[u,y]=k(e.config),[w,b]=k(e.picture_url);$(()=>b(e.picture_url),[e.id,e.picture_url]),$(()=>y(e.config),[e.id,e.config]);let C=x=>{y(x),JSON.stringify(x)!==JSON.stringify(e.config)&&p(x)};return t.createElement(L,{as:"aside",style:it,"data-inspector":!0},t.createElement("div",{style:{display:"flex",alignItems:"start",justifyContent:"space-between",gap:8}},t.createElement("h2",{style:{fontWeight:600,marginBottom:2}},e.label),t.createElement("button",{title:"Close",onClick:g,className:"text-gray-500 dark:text-gray-400",style:{border:"none",background:"none",cursor:"pointer",fontSize:18,lineHeight:1}},"\xD7")),t.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginBottom:16}},e.id," \xB7 ",e.type),h&&t.createElement("p",{className:"text-red-800 dark:text-red-400",style:{fontWeight:600,marginBottom:12},"data-refusal":!0},h),Object.entries(o).map(([x,T])=>t.createElement(I,{key:x},t.createElement("span",{style:{display:"block",marginBottom:3}},r[x]||x),T==="list"?t.createElement(pe,{holds:n[x]||{},labels:a[x]||{},rows:u[x],choices:{...(i||{})[x],...(e.record_choices||{})[x]},outputsOf:(c||{})[x],onChange:N=>y({...u,[x]:N}),onSettle:N=>C({...u,[x]:N})}):t.createElement(H,{type:T,value:u[x],choices:l[x],pictureUrl:w,onChange:N=>y({...u,[x]:N}),onSettle:N=>C({...u,[x]:N}),onPick:async N=>{let B=await d(N);B&&(b(B.url),C({...u,[x]:B.reference}))}}))),t.createElement(v,{variant:"danger",size:"sm",className:"w-full",onClick:f},"Delete step"))},ue=dt;var ct={width:240,boxSizing:"border-box",padding:"10px 14px",borderRadius:8,textAlign:"center",fontSize:12,cursor:"pointer"},pt="border-2 border-dashed border-amber-500 bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300",ut="border-2 border-dashed border-accent-600 bg-accent-50 text-accent-800 dark:bg-accent-950 dark:text-accent-200",mt=({node:e,dragging:o,onFill:n,onDrop:r})=>t.createElement("div",{ref:e.ref,"data-placeholder":e.id,title:`Choose the step \u201C${e.label}\u201D should lead to`,onClick:n,onDragOver:a=>{o&&(a.preventDefault(),a.dataTransfer.dropEffect="move")},onDrop:a=>{a.preventDefault(),r()},className:o?ut:pt,style:ct},t.createElement("div",{style:{fontWeight:600}},e.label),t.createElement("div",{style:{fontSize:11}},o?"drop a step here":"leads nowhere yet \u2014 click to choose")),me=mt;var ft=({entries:e,at:o,onPick:n,onConnect:r,onDismiss:a,hideStepTypeCodes:i})=>t.createElement(L,{className:"shadow-lg",style:{position:"absolute",zIndex:9,top:o.y,left:o.x,width:210,padding:8,borderRadius:8}},t.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{margin:"0 0 6px",fontSize:11}},"Add a step"),e.map(c=>t.createElement(v,{key:c.type,variant:"secondary",size:"sm",className:"w-full mb-1.5 flex-col",onClick:()=>n(c)},c.label,!i&&t.createElement("span",{className:"opacity-75",style:{display:"block",fontSize:11}},c.type))),r&&t.createElement(v,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:r},"Connect to a step already here"),t.createElement(v,{variant:"secondary",size:"sm",className:"w-full",onClick:a},"Cancel")),fe=ft;var gt={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},ge=({label:e,title:o,idle:n,enabled:r,onUse:a})=>t.createElement(v,{variant:"secondary",size:"sm",title:r?o:n,disabled:!r,onClick:a,style:{opacity:r?1:.4,cursor:r?"pointer":"default"}},e),yt=({undoable:e,redoable:o,onAdd:n,onUndo:r,onRedo:a})=>t.createElement("div",{style:{position:"absolute",top:16,left:16,zIndex:5,display:"flex",gap:8}},t.createElement(v,{variant:"secondary",className:"rounded-full",style:gt,title:"Add a step",onClick:n},"+"),t.createElement(ge,{label:"\u21B6 Undo",title:"Undo the last change",idle:"Nothing to undo",enabled:e,onUse:r}),t.createElement(ge,{label:"\u21B7 Redo",title:"Redo the change you undid",idle:"Nothing to redo",enabled:o,onUse:a})),ye=yt;var ht=({type:e="info",title:o,message:n,onDismiss:r,className:a})=>t.createElement("div",{className:S("ks-alert",`ks-alert-${e}`,a),role:"alert"},t.createElement("div",{className:"ks-alert-body"},t.createElement("div",{className:"ks-alert-content"},o&&t.createElement("p",{className:"ks-alert-title"},o),t.createElement("p",{className:o?"ks-alert-message-titled":"ks-alert-message"},n)),r&&t.createElement("button",{type:"button",className:"ks-alert-dismiss","aria-label":"Dismiss",onClick:r},"\xD7"))),he=ht;var be=e=>{let o=e.problem.replace(/_/g," ");return e.node?`\u201C${e.node}\u201D ${o}`:o},xe=e=>`${e.map(n=>`\u201C${n.label}\u201D`).join(", ")} ${e.length===1?"is":"are"} not connected to the flow and will be removed.`;var ve=({version:e,published:o})=>e?o?o===e?`Version ${e} \xB7 live`:`Version ${e} \xB7 visitors still on ${o}`:`Version ${e} \xB7 never published`:"No version yet";var bt={width:280,padding:20,overflowY:"auto"},G={fontWeight:600,marginBottom:6,marginTop:14},F={fontSize:12,marginBottom:4,lineHeight:1.4},ke={display:"block",marginBottom:3},we=(e,o,n)=>r=>{r.target.value!==(e[o]??"")&&n({[o]:r.target.value})},xt=({flow:e,changes:o,problems:n,refusal:r,notice:a,publishNote:i,warning:c,onCreate:l,onPublish:p,onAccept:f,onDecline:g,onSaveDetails:d,onClose:h})=>t.createElement(L,{as:"aside",style:bt,"data-builder-panel":!0},t.createElement("div",{style:{display:"flex",alignItems:"start",justifyContent:"space-between",gap:8}},t.createElement("h2",{style:{fontWeight:600},"data-flow-name":!0},e.title||e.slug),t.createElement("button",{title:"Close",onClick:h,className:"text-gray-500 dark:text-gray-400",style:{border:"none",background:"none",cursor:"pointer",fontSize:18,lineHeight:1}},"\xD7")),t.createElement("a",{className:"text-accent-600 dark:text-accent-400",style:{fontSize:12,display:"block"},href:e.history_url,"data-history":!0},ve(e)),c?t.createElement("div",{style:{marginTop:14},"data-unconnected-warning":!0},t.createElement(he,{type:"warning",className:"mb-1.5",message:xe(c.steps)}),t.createElement(v,{size:"sm",className:"w-full mb-1.5",onClick:f,"data-accept-removal":!0},c.path==="/publish"?"Remove and publish":"Remove and create version"),t.createElement(v,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:g,"data-decline-removal":!0},"Cancel")):t.createElement("div",{style:{marginTop:14}},t.createElement(v,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:l,"data-create-version":!0},"Create version"),t.createElement(v,{size:"sm",className:"w-full mb-1.5",onClick:p,"data-publish":!0},"Publish"),i&&t.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12,lineHeight:1.4},"data-publish-note":!0},i)),t.createElement("h2",{style:G},"Problems"),r&&t.createElement("p",{className:"text-red-800 dark:text-red-400",style:{...F,fontWeight:600},"data-refusal":!0},r),a&&!r&&t.createElement("p",{className:"text-emerald-800 dark:text-emerald-400",style:{...F,fontWeight:600},"data-notice":!0},a),n.length===0&&!r?t.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12}},"Nothing wrong with this flow."):n.map(u=>t.createElement("p",{key:`${u.node}-${u.problem}`,className:"text-amber-700 dark:text-amber-400",style:F,"data-problem":!0},"\u26A0 ",be(u))),t.createElement("h2",{style:G},"Changes since the last version"),o.length===0?t.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12}},"Nothing has changed."):o.map((u,y)=>t.createElement("p",{key:y,style:F,"data-change":!0},u)),t.createElement("h2",{style:G},"Details"),t.createElement(I,null,t.createElement("span",{style:ke},"Title"),t.createElement(_,{className:"mb-3",defaultValue:e.title??"",onBlur:we(e,"title",d),"data-flow-title":!0})),t.createElement(I,null,t.createElement("span",{style:ke},"Start label"),t.createElement(_,{className:"mb-3",defaultValue:e.start_label??"",onBlur:we(e,"start_label",d),"data-flow-start-label":!0})),t.createElement("div",{style:{marginTop:16}},t.createElement(v,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:e.definition_url,"data-definition":!0},"Definition"),t.createElement(v,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:e.details_url,"data-details":!0},"Edit details"))),Ce=xt;var Ne={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},Se=e=>({opacity:e?1:0,transition:"opacity .12s"}),vt=({link:e,onInsert:o,onRemove:n,onDrop:r,dragging:a,alwaysShowInsertButtons:i})=>{let[c,l]=k(!1),p=c||a;return t.createElement("div",{"data-connector":`${e.source}-${e.target}`,style:{position:"absolute",left:e.midX-34,top:e.midY-20,width:68,height:40,zIndex:4},onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),onDragEnter:()=>l(!0),onDragLeave:()=>l(!1),onDragOver:f=>{f.preventDefault(),f.dataTransfer.dropEffect="move"},onDrop:f=>{f.preventDefault(),l(!1),r()}},t.createElement("div",{style:{display:"flex",gap:4,justifyContent:"center",alignItems:"center",height:"100%"}},t.createElement(v,{variant:"secondary",title:a?"Move the step here":"Insert a step here",onClick:o,className:"rounded-full data-[over=true]:ring-2 data-[over=true]:ring-accent-600","data-over":!!(a&&c),style:{...Ne,...Se(p||i)}},"+"),!a&&t.createElement(v,{variant:"danger",title:"Remove this connection",onClick:n,className:"rounded-full",style:{...Ne,...Se(p)}},"\xD7")))},ze=vt;var kt=({links:e,extent:o,dragging:n,onInsert:r,onRemove:a,onDrop:i,alwaysShowInsertButtons:c})=>t.createElement(t.Fragment,null,t.createElement("svg",{width:o.width,height:o.height,style:{position:"absolute",top:0,left:0,pointerEvents:"none",zIndex:1}},t.createElement("defs",null,t.createElement("marker",{id:"easy_flow-arrow",viewBox:"0 0 10 10",refX:"8",refY:"5",markerWidth:"5",markerHeight:"5",orient:"auto"},t.createElement("path",{d:"M 0 0 L 10 5 L 0 10 z",className:"fill-gray-400 dark:fill-zinc-500"}))),e.map(l=>t.createElement("path",{key:l.id,"data-link":l.id,className:"stroke-gray-400 dark:stroke-zinc-500",fill:"none",strokeWidth:"1.5",markerEnd:"url(#easy_flow-arrow)",d:l.path}))),e.filter(l=>l.label).map(l=>t.createElement("div",{key:`${l.id}-label`,"data-link-label":l.id,className:"bg-surface-50 text-gray-500 dark:bg-surface-950 dark:text-gray-400",style:{position:"absolute",left:l.midX+(c?2:-10),top:l.midY-18,zIndex:3,fontSize:11,padding:"0 3px"}},l.label)),e.filter(l=>!l.placeholder).map(l=>t.createElement(ze,{key:l.id,link:l,dragging:!!n,alwaysShowInsertButtons:c,onInsert:()=>r(l),onRemove:()=>a(l),onDrop:()=>i(l)}))),Be=kt;var _e="Your change was not saved because the server could not be reached.",Te=({base:e,token:o,fetch:n,onFlow:r,onError:a,onNotice:i,onWarning:c=()=>{}})=>async(l,p,f)=>{let g=await n(e+l,{method:p,headers:{"Content-Type":"application/json","X-CSRF-Token":o},body:f&&JSON.stringify(f)}).catch(()=>null);if(!g)return i(null),a(_e);let d=await g.json().catch(()=>({}));if(d.unconnected)return i(null),a(null),c({path:l,steps:d.unconnected}),r(await(await n(e+".json",{headers:{Accept:"application/json"}})).json());if(c(null),!g.ok)return i(null),a(d.error||"That change was refused");a(null),i(d.notice||null),r(await(await n(e+".json",{headers:{Accept:"application/json"}})).json())},Pe=({base:e,token:o,fetch:n,onError:r})=>async a=>{let i=new FormData;i.append("picture",a);let c=await n(e+"/pictures",{method:"POST",headers:{"X-CSRF-Token":o},body:i}).catch(()=>null);if(!c)return r(_e),null;let l=await c.json().catch(()=>({}));return c.ok?(r(null),l):(r(l.error||"That picture was refused"),null)};var wt=(e,o,n)=>{let[r,a]=k(n),[i,c]=k(null),[l,p]=k(null),[f,g]=k(null),d=A(()=>Te({base:e,token:o,fetch:(...u)=>window.fetch(...u),onFlow:a,onError:c,onNotice:p,onWarning:g}),[e,o]),h=A(()=>Pe({base:e,token:o,fetch:(...u)=>window.fetch(...u),onError:c}),[e,o]);return{flow:r,error:i,notice:l,warning:f,setWarning:g,send:d,pickPicture:h}},Ee=wt;var W=(e,o,n,r)=>{let a=e.left-n.left+r.scrollLeft,i=e.top-n.top+r.scrollTop;return o==="left"?{x:a,y:i+e.height/2}:o==="right"?{x:a+e.width,y:i+e.height/2}:o==="top"?{x:a+e.width/2,y:i}:{x:a+e.width/2,y:i+e.height}},Ct=(e,o)=>o==="top"?{x:e.x,y:e.y-3}:o==="left"?{x:e.x-3,y:e.y}:o==="right"?{x:e.x+3,y:e.y}:{x:e.x,y:e.y+3},Nt=(e,o,n,r,a)=>{let i={straight:`M ${o.x} ${o.y} L ${n.x} ${n.y}`,turn:`M ${o.x} ${o.y} H ${n.x} V ${n.y}`,lane:`M ${o.x} ${o.y} V ${r} H ${n.x} V ${n.y}`,detour:`M ${o.x} ${o.y} H ${a} V ${n.y} H ${n.x}`}[e.route];return{...e,path:i,midX:e.route==="detour"?a:(o.x+n.x)/2,midY:e.route==="lane"?r:(o.y+n.y)/2}},St=(e,o,n,r)=>{let[a,i]=k([]),[c,l]=k({width:"100%",height:"100%"});return ee(()=>{let p=()=>{let g=o.current?.getBoundingClientRect();g&&(l({width:o.current.scrollWidth,height:o.current.scrollHeight}),i(e.edges.flatMap(d=>{let h=n.current[d.source]?.getBoundingClientRect(),u=n.current[d.target]?.getBoundingClientRect();if(!h||!u)return[];let y=W(h,d.leaves,g,o.current),w=Ct(W(u,d.enters,g,o.current),d.enters),b=(W(h,"bottom",g,o.current).y+W(u,"top",g,o.current).y)/2,C=d.leaves==="left"?Math.min(y.x,w.x)-28:Math.max(y.x,w.x)+28;return[Nt(d,y,w,b,C)]})))};p(),window.addEventListener("resize",p);let f=new ResizeObserver(p);return o.current&&f.observe(o.current),()=>{window.removeEventListener("resize",p),f.disconnect()}},r),{links:a,extent:c}},$e=St;var Ae=(e,o)=>{let n=new Set(o),r=e,a=2;for(;n.has(r);)r=`${e}_${a++}`;return r};var _t={display:"flex",height:"100%",minHeight:0,fontSize:13},Tt={flex:1,overflow:"auto",position:"relative"},Pt={position:"absolute",top:56,left:16,zIndex:5,width:264,padding:12,borderRadius:8},Et={display:"grid",rowGap:64,columnGap:0,padding:40,justifyContent:"center",position:"relative"},$t={position:"sticky",zIndex:8,top:8,margin:"8px auto 0",width:"fit-content",padding:"6px 12px",borderRadius:6,fontSize:12},At=({port:e,onCancel:o})=>t.createElement("div",{className:"bg-accent-100 text-accent-800 dark:bg-accent-900 dark:text-accent-100",style:$t},"Choose the step \u201C",e||"next","\u201D should lead to \u2014 ",t.createElement("button",{onClick:o,className:"text-accent-800 underline dark:text-accent-100",style:{border:"none",background:"none",cursor:"pointer",fontSize:12,padding:0}},"cancel")),Dt=({base:e,token:o,initial:n})=>{let{flow:r,error:a,notice:i,warning:c,setWarning:l,send:p,pickPicture:f}=Ee(e,o,n),[g,d]=k(null),[h,u]=k(null),[y,w]=k(null),[b,C]=k(null),[x,T]=k(!1),N=U({}),B=U(null);$(()=>{let s=()=>{d(null),T(!0)};return document.addEventListener("easy_flow:open-flow",s),()=>document.removeEventListener("easy_flow:open-flow",s)},[]),$(()=>{if(!x){l(null);return}let s=m=>{m.target.isConnected&&!m.target.closest("[data-builder-panel], [data-open-panel]")&&T(!1)};return document.addEventListener("click",s),()=>document.removeEventListener("click",s)},[x]),$(()=>{document.dispatchEvent(new CustomEvent("easy_flow:flow-named",{detail:r.flow?.title||r.flow?.slug}))},[r.flow?.title,r.flow?.slug]);let Le=A(()=>Object.fromEntries(r.nodes.map(s=>[s.id,s])),[r.nodes]),{links:Ie,extent:Oe}=$e(r,B,N,[r,g,Le]),q=A(()=>{let s={};return r.violations.forEach(m=>{var z;return(s[z=m.node]||(s[z]=[])).push(m)}),s},[r.violations]),V=r.nodes.filter(s=>!s.loose),J=r.nodes.filter(s=>s.loose),Me=Math.max(0,...V.map(s=>s.row))+1,He=Math.max(0,...V.map(s=>s.column))+1,K=s=>{if(!y)return;let[m,z]=y;w(null),m!==s&&p("/edges","POST",{from:m,to:s,on:z||null})},P=r.nodes.find(s=>s.id===g),E=[...r.palette,...r.unoffered||[]].find(s=>s.type===P?.type);return t.createElement("div",{className:"text-gray-900 dark:text-gray-100",style:_t,onMouseUp:()=>C(null),onKeyDown:s=>{s.key==="Escape"&&(d(null),w(null),u(null))},tabIndex:-1},t.createElement("div",{ref:B,className:"bg-surface-50 dark:bg-surface-950",style:Tt,onClick:s=>{s.target===B.current&&(d(null),w(null),T(!1))}},y&&t.createElement(At,{port:y[1],onCancel:()=>w(null)}),t.createElement(ye,{undoable:r.undoable,redoable:r.redoable,onAdd:()=>u({at:{x:16,y:52}}),onUndo:()=>{d(null),p("/undo","POST")},onRedo:()=>{d(null),p("/redo","POST")}}),t.createElement(Be,{links:Ie,extent:Oe,dragging:b,alwaysShowInsertButtons:r.always_show_insert_buttons,onInsert:s=>u({from:s.source,to:s.target,at:{x:s.midX+30,y:s.midY}}),onRemove:s=>{d(null),p("/edges","DELETE",{from:s.source,to:s.target})},onDrop:s=>{let m=b;C(null),p("/steps/"+m+"/move","PATCH",{from:s.source,to:s.target})}}),J.length>0&&t.createElement("div",{"data-loose":!0,className:"border border-dashed border-gray-300 bg-gray-50 dark:border-zinc-700 dark:bg-zinc-900",style:Pt},t.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginBottom:6}},"Not in the flow yet \u2014 drag one onto a connection"),J.map(s=>t.createElement("div",{key:s.id,style:{marginBottom:8}},t.createElement(Y,{node:{...s,ref:m=>{N.current[s.id]=m},violations:q[s.id]||[],connected:[]},selected:g===s.id,armed:y&&y[0]===s.id?y[1]:null,connecting:!!y,onSelect:()=>y?K(s.id):d(s.id),onOpen:m=>window.location.assign(m),onArm:(m,z)=>{z.stopPropagation(),w([s.id,m])},onDragStart:()=>C(s.id),onDragEnd:()=>C(null),hideStepTypeCodes:r.hide_step_type_codes})))),t.createElement("div",{style:{...Et,gridTemplateColumns:`repeat(${He+1}, ${280/2}px)`,gridTemplateRows:`repeat(${Me}, auto)`}},V.map(s=>t.createElement("div",{key:s.id,style:{gridRow:s.row+1,gridColumn:`${s.column+1} / span 2`,zIndex:2}},s.placeholder&&t.createElement(me,{node:{...s,ref:m=>{N.current[s.id]=m}},dragging:!!b,onFill:()=>u({from:s.from,on:s.on,at:{x:16,y:52}}),onDrop:()=>{let m=b;C(null),m&&p("/edges","POST",{from:s.from,to:m,on:s.on})}}),!s.placeholder&&t.createElement(Y,{node:{...s,ref:m=>{N.current[s.id]=m},violations:q[s.id]||[],connected:r.edges.filter(m=>m.source===s.id).map(m=>m.label||null)},selected:s.id===g,armed:y&&y[0]===s.id?y[1]:null,connecting:!!y&&y[0]!==s.id,onSelect:()=>y?K(s.id):d(s.id),onOpen:m=>window.location.assign(m),onArm:(m,z)=>{let Z=B.current.getBoundingClientRect();u({from:s.id,on:m,at:{x:z.clientX-Z.left+B.current.scrollLeft+8,y:z.clientY-Z.top+B.current.scrollTop+8}})},onDragEnd:()=>C(null),onDragStart:()=>C(s.id),hideStepTypeCodes:r.hide_step_type_codes})))),h&&t.createElement(fe,{entries:r.palette,at:h.at,onDismiss:()=>u(null),hideStepTypeCodes:r.hide_step_type_codes,onConnect:h.on!==void 0?()=>{w([h.from,h.on]),u(null)}:null,onPick:s=>{let m=h;u(null),p("/steps","POST",{id:Ae(s.type,r.nodes.map(z=>z.id)),type:s.type,from:m.from,to:m.to,on:m.on})}})),x&&!P&&t.createElement(Ce,{flow:r.flow||{},changes:r.changes||[],problems:r.violations,refusal:a,notice:i,publishNote:r.publish_note,warning:c,onClose:()=>T(!1),onCreate:()=>p("/versions","POST"),onPublish:()=>p("/publish","POST"),onAccept:()=>p(c.path,"POST",{remove_unconnected:c.steps.map(s=>s.id)}),onDecline:()=>l(null),onSaveDetails:s=>p("/details","PATCH",{flow:s})}),P&&t.createElement(ue,{node:P,fields:E?.fields||{},holds:E?.records||{},labels:E?.labels||{},recordLabels:E?.record_labels||{},recordChoices:E?.record_choices||{},recordOutputsOf:E?.record_outputs_of||{},choices:le(P,E),onClose:()=>d(null),onSave:s=>p(`/steps/${P.id}`,"PATCH",{config:s}),onPickPicture:f,refusal:a,onDelete:()=>{d(null),p(`/steps/${P.id}`,"DELETE")}}))},De=Dt;oe("easy_flow/flow-editor",De);document.addEventListener("easy_flow:flow-named",e=>{let o=document.querySelector("[data-flow-heading]");o&&(o.textContent=e.detail)});se(document,Q);})();
|
|
@@ -7,23 +7,21 @@ module EasyFlow
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def create
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
with_unconnected_settled do
|
|
11
|
+
stood_at = flow.current_definition_version&.number
|
|
12
|
+
flow.create_version
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
render json: { notice: created(stood_at) }
|
|
14
|
+
render json: { notice: created(stood_at) }
|
|
15
|
+
end
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def publish
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
with_unconnected_settled do
|
|
20
|
+
ran = flow.live_version&.number
|
|
21
|
+
flow.publish
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
render json: { notice: published(ran) }
|
|
23
|
+
render json: { notice: published(ran) }
|
|
24
|
+
end
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def details
|
|
@@ -83,6 +81,22 @@ module EasyFlow
|
|
|
83
81
|
|
|
84
82
|
private
|
|
85
83
|
|
|
84
|
+
def with_unconnected_settled
|
|
85
|
+
flow.with_lock do
|
|
86
|
+
unconnected = UnconnectedSteps.new(document, flow: flow)
|
|
87
|
+
objections = unconnected.objections
|
|
88
|
+
|
|
89
|
+
if objections.any?
|
|
90
|
+
render json: { error: refusal(objections) }, status: :unprocessable_entity
|
|
91
|
+
elsif unconnected.ids.any? && !unconnected.accepted?(params[:remove_unconnected])
|
|
92
|
+
render json: { error: refusal(Validator.new(document, flow: flow).violations), unconnected: unconnected.named }, status: :unprocessable_entity
|
|
93
|
+
else
|
|
94
|
+
flow.remove_unconnected(unconnected.ids) if unconnected.ids.any?
|
|
95
|
+
yield
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
86
100
|
def apply(action = nil, *steps)
|
|
87
101
|
before = document.to_h
|
|
88
102
|
flow.edit_history.record(yield(document), action, steps, before)
|
|
@@ -26,7 +26,7 @@ export const Choosing = ({ port, onCancel }) => (
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
const Canvas = ({ base, token, initial }) => {
|
|
29
|
-
const { flow, error, notice, send, pickPicture } = useFlow(base, token, initial)
|
|
29
|
+
const { flow, error, notice, warning, setWarning, send, pickPicture } = useFlow(base, token, initial)
|
|
30
30
|
const [ selected, setSelected ] = useState(null)
|
|
31
31
|
const [ adding, setAdding ] = useState(null)
|
|
32
32
|
const [ armed, setArmed ] = useState(null)
|
|
@@ -42,10 +42,13 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
42
42
|
}, [])
|
|
43
43
|
|
|
44
44
|
useEffect(() => {
|
|
45
|
-
if (!showing)
|
|
45
|
+
if (!showing) {
|
|
46
|
+
setWarning(null)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
46
49
|
|
|
47
50
|
const away = (event) => {
|
|
48
|
-
if (!event.target.closest("[data-builder-panel], [data-open-panel]")) setShowing(false)
|
|
51
|
+
if (event.target.isConnected && !event.target.closest("[data-builder-panel], [data-open-panel]")) setShowing(false)
|
|
49
52
|
}
|
|
50
53
|
document.addEventListener("click", away)
|
|
51
54
|
return () => document.removeEventListener("click", away)
|
|
@@ -178,10 +181,12 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
178
181
|
</div>
|
|
179
182
|
|
|
180
183
|
{showing && !selectedNode && (
|
|
181
|
-
<Panel flow={flow.flow || {}} changes={flow.changes || []} problems={flow.violations} refusal={error} notice={notice} publishNote={flow.publish_note}
|
|
184
|
+
<Panel flow={flow.flow || {}} changes={flow.changes || []} problems={flow.violations} refusal={error} notice={notice} publishNote={flow.publish_note} warning={warning}
|
|
182
185
|
onClose={() => setShowing(false)}
|
|
183
186
|
onCreate={() => send("/versions", "POST")}
|
|
184
187
|
onPublish={() => send("/publish", "POST")}
|
|
188
|
+
onAccept={() => send(warning.path, "POST", { remove_unconnected: warning.steps.map((step) => step.id) })}
|
|
189
|
+
onDecline={() => setWarning(null)}
|
|
185
190
|
onSaveDetails={(details) => send("/details", "PATCH", { flow: details })} />
|
|
186
191
|
)}
|
|
187
192
|
|
|
@@ -2,8 +2,9 @@ import React from "react"
|
|
|
2
2
|
import KeystonePanel from "keystone_ui-react/src/Panel.jsx"
|
|
3
3
|
import Button from "keystone_ui-react/src/Button.jsx"
|
|
4
4
|
import Input from "keystone_ui-react/src/Input.jsx"
|
|
5
|
+
import Alert from "keystone_ui-react/src/Alert.jsx"
|
|
5
6
|
import { Label } from "keystone_ui-react/src/FieldText.jsx"
|
|
6
|
-
import { worded } from "./changes"
|
|
7
|
+
import { warned, worded } from "./changes"
|
|
7
8
|
import { standing } from "./flow"
|
|
8
9
|
|
|
9
10
|
const sheet = { width: 280, padding: 20, overflowY: "auto" }
|
|
@@ -15,7 +16,7 @@ const settling = (flow, name, onSaveDetails) => (event) => {
|
|
|
15
16
|
if (event.target.value !== (flow[name] ?? "")) onSaveDetails({ [name]: event.target.value })
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const Panel = ({ flow, changes, problems, refusal, notice, publishNote, onCreate, onPublish, onSaveDetails, onClose }) => (
|
|
19
|
+
const Panel = ({ flow, changes, problems, refusal, notice, publishNote, warning, onCreate, onPublish, onAccept, onDecline, onSaveDetails, onClose }) => (
|
|
19
20
|
<KeystonePanel as="aside" style={sheet} data-builder-panel>
|
|
20
21
|
<div style={{ display: "flex", alignItems: "start", justifyContent: "space-between", gap: 8 }}>
|
|
21
22
|
<h2 style={{ fontWeight: 600 }} data-flow-name>{flow.title || flow.slug}</h2>
|
|
@@ -24,11 +25,21 @@ const Panel = ({ flow, changes, problems, refusal, notice, publishNote, onCreate
|
|
|
24
25
|
</div>
|
|
25
26
|
<a className="text-accent-600 dark:text-accent-400" style={{ fontSize: 12, display: "block" }} href={flow.history_url} data-history>{standing(flow)}</a>
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
{warning
|
|
29
|
+
? (
|
|
30
|
+
<div style={{ marginTop: 14 }} data-unconnected-warning>
|
|
31
|
+
<Alert type="warning" className="mb-1.5" message={warned(warning.steps)} />
|
|
32
|
+
<Button size="sm" className="w-full mb-1.5" onClick={onAccept} data-accept-removal>{warning.path === "/publish" ? "Remove and publish" : "Remove and create version"}</Button>
|
|
33
|
+
<Button variant="secondary" size="sm" className="w-full mb-1.5" onClick={onDecline} data-decline-removal>Cancel</Button>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
: (
|
|
37
|
+
<div style={{ marginTop: 14 }}>
|
|
38
|
+
<Button variant="secondary" size="sm" className="w-full mb-1.5" onClick={onCreate} data-create-version>Create version</Button>
|
|
39
|
+
<Button size="sm" className="w-full mb-1.5" onClick={onPublish} data-publish>Publish</Button>
|
|
40
|
+
{publishNote && <p className="text-gray-500 dark:text-gray-400" style={{ fontSize: 12, lineHeight: 1.4 }} data-publish-note>{publishNote}</p>}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
32
43
|
|
|
33
44
|
<h2 style={heading}>Problems</h2>
|
|
34
45
|
{refusal && <p className="text-red-800 dark:text-red-400" style={{ ...item, fontWeight: 600 }} data-refusal>{refusal}</p>}
|
|
@@ -3,3 +3,9 @@ export const worded = (violation) => {
|
|
|
3
3
|
|
|
4
4
|
return violation.node ? `“${violation.node}” ${problem}` : problem
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
export const warned = (steps) => {
|
|
8
|
+
const named = steps.map((step) => `“${step.label}”`).join(", ")
|
|
9
|
+
|
|
10
|
+
return `${named} ${steps.length === 1 ? "is" : "are"} not connected to the flow and will be removed.`
|
|
11
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const UNREACHABLE = "Your change was not saved because the server could not be reached."
|
|
2
2
|
|
|
3
|
-
export const createFlowSender = ({ base, token, fetch, onFlow, onError, onNotice }) => async (path, method, body) => {
|
|
3
|
+
export const createFlowSender = ({ base, token, fetch, onFlow, onError, onNotice, onWarning = () => {} }) => async (path, method, body) => {
|
|
4
4
|
const response = await fetch(base + path, {
|
|
5
5
|
method, headers: { "Content-Type": "application/json", "X-CSRF-Token": token }, body: body && JSON.stringify(body)
|
|
6
6
|
}).catch(() => null)
|
|
@@ -11,6 +11,15 @@ export const createFlowSender = ({ base, token, fetch, onFlow, onError, onNotice
|
|
|
11
11
|
|
|
12
12
|
const answered = await response.json().catch(() => ({}))
|
|
13
13
|
|
|
14
|
+
if (answered.unconnected) {
|
|
15
|
+
onNotice(null)
|
|
16
|
+
onError(null)
|
|
17
|
+
onWarning({ path, steps: answered.unconnected })
|
|
18
|
+
return onFlow(await (await fetch(base + ".json", { headers: { Accept: "application/json" } })).json())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onWarning(null)
|
|
22
|
+
|
|
14
23
|
if (!response.ok) {
|
|
15
24
|
onNotice(null)
|
|
16
25
|
return onError(answered.error || "That change was refused")
|
|
@@ -5,9 +5,10 @@ const useFlow = (base, token, initial) => {
|
|
|
5
5
|
const [ flow, setFlow ] = useState(initial)
|
|
6
6
|
const [ error, setError ] = useState(null)
|
|
7
7
|
const [ notice, setNotice ] = useState(null)
|
|
8
|
+
const [ warning, setWarning ] = useState(null)
|
|
8
9
|
|
|
9
10
|
const send = useMemo(
|
|
10
|
-
() => createFlowSender({ base, token, fetch: (...request) => window.fetch(...request), onFlow: setFlow, onError: setError, onNotice: setNotice }),
|
|
11
|
+
() => createFlowSender({ base, token, fetch: (...request) => window.fetch(...request), onFlow: setFlow, onError: setError, onNotice: setNotice, onWarning: setWarning }),
|
|
11
12
|
[ base, token ]
|
|
12
13
|
)
|
|
13
14
|
|
|
@@ -16,7 +17,7 @@ const useFlow = (base, token, initial) => {
|
|
|
16
17
|
[ base, token ]
|
|
17
18
|
)
|
|
18
19
|
|
|
19
|
-
return { flow, error, notice, send, pickPicture }
|
|
20
|
+
return { flow, error, notice, warning, setWarning, send, pickPicture }
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export default useFlow
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module EasyFlow
|
|
2
2
|
module Change
|
|
3
3
|
PHRASING = { "added" => "Added", "updated" => "Updated", "removed" => "Removed",
|
|
4
|
-
"moved" => "Moved", "connected" => "Connected", "disconnected" => "Disconnected", "returned" => "Returned to"
|
|
4
|
+
"moved" => "Moved", "connected" => "Connected", "disconnected" => "Disconnected", "returned" => "Returned to",
|
|
5
|
+
"removed_unconnected" => "Removed unconnected" }.freeze
|
|
5
6
|
|
|
6
7
|
def self.listed(changes)
|
|
7
8
|
changes.chunk_while { |earlier, later| saves_to_one_step?(earlier, later) }.map { |saves| phrase(saves.last) }
|
|
@@ -15,6 +16,7 @@ module EasyFlow
|
|
|
15
16
|
named = Array(change["named"]).compact_blank.map { |name| "“#{name}”" }
|
|
16
17
|
verb = PHRASING.fetch(change["action"], change["action"])
|
|
17
18
|
|
|
19
|
+
return "#{verb} #{named.join(', ')}" if named.any? && change["action"] == "removed_unconnected"
|
|
18
20
|
return "#{verb} #{named.join(' → ')}" if named.any?
|
|
19
21
|
return "#{verb} #{change['detail']}" if change["detail"].present?
|
|
20
22
|
|
|
@@ -61,6 +61,11 @@ module EasyFlow
|
|
|
61
61
|
update!(undo_history: edit_history.undoable, changes_since_version: [], undone_changes: [])
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def remove_unconnected(steps)
|
|
65
|
+
create_version
|
|
66
|
+
edit_history.record(Document.new(document).without(steps), :removed_unconnected, steps, document)
|
|
67
|
+
end
|
|
68
|
+
|
|
64
69
|
def returning_to(version)
|
|
65
70
|
{ "action" => "returned", "steps" => [], "named" => [],
|
|
66
71
|
"detail" => "version #{version.number}", "before" => document }
|
|
@@ -47,6 +47,12 @@ module EasyFlow
|
|
|
47
47
|
found
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def unconnected
|
|
51
|
+
return [] unless entry
|
|
52
|
+
|
|
53
|
+
nodes.map(&:id).uniq - reachable - beginnings
|
|
54
|
+
end
|
|
55
|
+
|
|
50
56
|
def to_h
|
|
51
57
|
@document
|
|
52
58
|
end
|
|
@@ -103,6 +109,11 @@ module EasyFlow
|
|
|
103
109
|
edges: (raw_edges - incoming - outgoing) + bridges(incoming, outgoing))
|
|
104
110
|
end
|
|
105
111
|
|
|
112
|
+
def without(ids)
|
|
113
|
+
Document.new(@document.merge("nodes" => raw_nodes.reject { |node| ids.include?(node["id"]) },
|
|
114
|
+
"edges" => raw_edges.reject { |edge| ids.include?(edge["from"]) || ids.include?(edge["to"]) }), registry: @registry)
|
|
115
|
+
end
|
|
116
|
+
|
|
106
117
|
private
|
|
107
118
|
|
|
108
119
|
def bridges(incoming, outgoing)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module EasyFlow
|
|
2
|
+
class UnconnectedSteps
|
|
3
|
+
def initialize(document, flow: nil, registry: EasyFlow.registry)
|
|
4
|
+
@document = document
|
|
5
|
+
@flow = flow
|
|
6
|
+
@registry = registry
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def ids
|
|
10
|
+
@document.unconnected
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def named
|
|
14
|
+
ids.map { |id| { "id" => id, "label" => Name.of(@document.node(id), @registry) } }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def accepted?(named_steps)
|
|
18
|
+
Array(named_steps).map(&:to_s).sort == ids.map(&:to_s).sort
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def objections
|
|
22
|
+
Validator.new(staying, registry: @registry, flow: @flow).violations + readers
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def readers
|
|
28
|
+
staying.nodes.select { |node| entries_naming(node).intersect?(ids) }
|
|
29
|
+
.map { |node| Violation.new(node: node.id, problem: :reading_an_unconnected_step) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def entries_naming(node)
|
|
33
|
+
return [] unless @registry.registered?(node.type)
|
|
34
|
+
|
|
35
|
+
@registry.fetch(node.type).settings.record_fields.flat_map do |list, holds|
|
|
36
|
+
Array(node.config[list.to_s]).flat_map { |entry| entry.to_h.values_at(*holds.select { |_name, type| type == :previous_step }.keys.map(&:to_s)) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def staying
|
|
41
|
+
@document.without(ids)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/easy_flow/version.rb
CHANGED
|
@@ -581,6 +581,7 @@ Use this when a visitor's run should stop until a set time has passed, such as a
|
|
|
581
581
|
- A flow holding a `starts_a_flow` step whose `flow` names no existing flow, or whose `version` names no live or replaced version of it, or whose `flow` leads back to the flow the step sits in directly or through other flows, cannot be saved as a version or published, and these checks cannot be turned off.
|
|
582
582
|
- On the canvas, clicking a step of a `starts_a_flow` type whose `flow` setting names a flow goes to that flow's page, which has a `Back to` button to the flow the step is in, and the step's settings open from its `Settings` button.
|
|
583
583
|
- A `:previous_step` setting is required unless declared `optional: true`, and an `outputs_of:` setting must be filled once the step it reads is picked, or the flow cannot be saved as a version or published.
|
|
584
|
+
- Only the canvas's Create version and Publish requests remove unconnected steps, and only when the request's `remove_unconnected` names exactly the steps unconnected at that moment. `Definition#publish`, `Definition#create_version` and `Validator#violations` never remove one, and `violations` lists each as `unreachable`, so app code that checks `violations` before publishing goes on refusing such a flow. A request that names none is answered with status 422, the refusal in `error`, and the steps in `unconnected`.
|
|
584
585
|
- A type that routes declares the values its output takes, or the canvas offers no connections to label.
|
|
585
586
|
- An output named by `outputs_named_by` takes its name from an entry's `name`, lists no values, and is never a connection leaving the step.
|
|
586
587
|
- A run starts only on a version its own flow has published and not retired or withdrawn, and any other version raises `ActiveRecord::RecordInvalid`.
|
|
@@ -45,6 +45,7 @@ easy_flow declares no commands of its own for this local. Its surface is split b
|
|
|
45
45
|
- **Flow** — one guided path, found by its slug within its host. It holds a working document the admin edits and a list of versions.
|
|
46
46
|
- **Document** — the flow's content: its steps (nodes), the connections between them (edges), a headline and a slug.
|
|
47
47
|
- **Version** — a numbered snapshot of the document. A version is a draft until it is published, and a flow has at most one live version at a time. Publishing another version makes the one that was live superseded. A version can also be retired or withdrawn, and a retired or withdrawn version cannot be published again.
|
|
48
|
+
- **Unconnected step** — a step on the canvas the flow does not reach from its start, left to the side until an admin connects it. It stays in the working document through every edit. Create version and Publish warn with each such step's name, and only an admin accepting that warning removes them. Accepting saves a draft version with the steps in it first, which an admin can return to from the history, then creates or publishes the next version without them. A second start step is not counted, and a problem on a step that stays still blocks.
|
|
48
49
|
- **Published version** — a version that is live or superseded, which is any version that has been published and not taken out of service.
|
|
49
50
|
- **Removing a flow** — an admin deleting a flow from the admin's list of flows. It deletes the flow's versions and its runs with it. A run of another flow that one of those runs had started is kept, and no longer records a run that started it. A flow is not removed while a run of it is the inner run that a run of another flow is stopped at a Flow step waiting on. The admin is then told "This flow cannot be removed while" followed by the waiting flow's title, or its slug when it has no title, and "waits on one of its runs".
|
|
50
51
|
- **Canvas** — the admin screen where steps are added, configured, moved, removed and connected, with undo and redo. Publishing happens there.
|
|
@@ -124,6 +124,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
124
124
|
## Conventions
|
|
125
125
|
|
|
126
126
|
- After install, check that `<mount path>/manage/flows` shows the flow list and that a new flow opens on the canvas. If the canvas is blank, check that the admin layout calls `yield :head`.
|
|
127
|
+
- After install, add a step to a flow without connecting it and press Publish. Check that the flow panel names the step, that Cancel changes nothing, and that accepting publishes the flow without it and the history shows `Removed unconnected` with its name.
|
|
127
128
|
- After publishing a flow, check that `<mount path>/<slug>` shows it to a visitor who passes the host's visitor authorization method.
|
|
128
129
|
- After publishing a flow with a question or a checklist whose answer has info text, check that the visitor's page shows an info button on that answer. If it does not, compare the app's `keystone_ui` version with the one in step 2.
|
|
129
130
|
- After install, check that an End step selected on the canvas shows an output setting. An admin fills it in on each End step, and it needs no migration and no setting in the initializer.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: easy_flow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.34.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
@@ -146,6 +146,7 @@ files:
|
|
|
146
146
|
- app/models/easy_flow/steps/question.rb
|
|
147
147
|
- app/models/easy_flow/switch.rb
|
|
148
148
|
- app/models/easy_flow/terminal.rb
|
|
149
|
+
- app/models/easy_flow/unconnected_steps.rb
|
|
149
150
|
- app/models/easy_flow/validator.rb
|
|
150
151
|
- app/models/easy_flow/version.rb
|
|
151
152
|
- app/models/easy_flow/violation.rb
|