easy_flow 0.1.0 → 0.2.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_flow/canvas.js +1 -0
  3. data/app/controllers/concerns/easy_flow/manage/authenticates_admin.rb +19 -0
  4. data/app/controllers/concerns/easy_flow/manage/draws_canvas.rb +34 -0
  5. data/app/controllers/easy_flow/application_controller.rb +32 -0
  6. data/app/controllers/easy_flow/flows_controller.rb +103 -0
  7. data/app/controllers/easy_flow/manage/base_controller.rb +14 -0
  8. data/app/controllers/easy_flow/manage/canvas_controller.rb +183 -0
  9. data/app/controllers/easy_flow/manage/definitions_controller.rb +15 -0
  10. data/app/controllers/easy_flow/manage/flows_controller.rb +55 -0
  11. data/app/controllers/easy_flow/manage/previews_controller.rb +38 -0
  12. data/app/controllers/easy_flow/manage/versions_controller.rb +22 -0
  13. data/app/javascript/easy_flow/canvas/Canvas.jsx +197 -0
  14. data/app/javascript/easy_flow/canvas/Connector.jsx +30 -0
  15. data/app/javascript/easy_flow/canvas/ConnectorLayer.jsx +31 -0
  16. data/app/javascript/easy_flow/canvas/Control.jsx +42 -0
  17. data/app/javascript/easy_flow/canvas/Inspector.jsx +44 -0
  18. data/app/javascript/easy_flow/canvas/Panel.jsx +61 -0
  19. data/app/javascript/easy_flow/canvas/Placeholder.jsx +24 -0
  20. data/app/javascript/easy_flow/canvas/Port.jsx +16 -0
  21. data/app/javascript/easy_flow/canvas/Records.jsx +34 -0
  22. data/app/javascript/easy_flow/canvas/StepCard.jsx +57 -0
  23. data/app/javascript/easy_flow/canvas/Toolbar.jsx +19 -0
  24. data/app/javascript/easy_flow/canvas/TypePicker.jsx +21 -0
  25. data/app/javascript/easy_flow/canvas/changes.js +5 -0
  26. data/app/javascript/easy_flow/canvas/choices.js +4 -0
  27. data/app/javascript/easy_flow/canvas/flow.js +7 -0
  28. data/app/javascript/easy_flow/canvas/flowSender.js +22 -0
  29. data/app/javascript/easy_flow/canvas/ids.js +7 -0
  30. data/app/javascript/easy_flow/canvas/rows.js +2 -0
  31. data/app/javascript/easy_flow/canvas/styles.js +3 -0
  32. data/app/javascript/easy_flow/canvas/useConnectors.js +75 -0
  33. data/app/javascript/easy_flow/canvas/useFlow.js +17 -0
  34. data/app/javascript/easy_flow/canvas.jsx +13 -0
  35. data/app/views/easy_flow/flows/complete.html.erb +22 -0
  36. data/app/views/easy_flow/flows/show.html.erb +17 -0
  37. data/app/views/easy_flow/flows/step.html.erb +27 -0
  38. data/app/views/easy_flow/manage/definitions/edit.html.erb +13 -0
  39. data/app/views/easy_flow/manage/flows/edit.html.erb +26 -0
  40. data/app/views/easy_flow/manage/flows/index.html.erb +32 -0
  41. data/app/views/easy_flow/manage/flows/show.html.erb +26 -0
  42. data/app/views/easy_flow/manage/versions/index.html.erb +41 -0
  43. data/app/views/easy_flow/steps/_choosing.html.erb +9 -0
  44. data/app/views/layouts/easy_flow/application.html.erb +15 -0
  45. data/config/routes.rb +32 -0
  46. data/lib/easy_flow/engine.rb +11 -0
  47. data/lib/easy_flow/version.rb +1 -1
  48. data/lib/easy_flow.rb +15 -0
  49. metadata +58 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e3c51d7ce51a8a5b5d9f1209d8b621a21353082b1815cd3d5b567c7da7d2acc
4
- data.tar.gz: 36fb74833ec6d3ee98e20c2d9fb5328a14cd160727f0854f3384fb6dee4e0559
3
+ metadata.gz: d7beeb603b60c113d63219761c29a84858b1d9bc5c870c877ea628cd6cde8cb3
4
+ data.tar.gz: 2fac26b851b947941aa562c785e78f037f2647e8cce611f1230026b4a5f2b063
5
5
  SHA512:
6
- metadata.gz: 6c8720e19f2fa889ccaa3c2ddc8915d16951edf4620165009c5a32912d3d30199c0ec516cea28cca106028484b76cf8f648866dc30ea3c6258d1c0e36927738d
7
- data.tar.gz: 5c12ab8931cd2ec76567c9ae5667849b88a64454664dd0dfb40d97ab21c583502edf3e2d9010011836c8de363eaef6d94236d52dd60fa078961c53420701b67a
6
+ metadata.gz: 73e4f10cba25f80dabc9ca201b09ae971d1369ddacfbe97e511649d007dd1d55e873573b096ef5c34c00b8a6b5e6134e9384374d348893d1d6031c466fb3000c
7
+ data.tar.gz: 85320b6c006c40e196f62ac1511e73ce87ac9edc0484a35e2d02e42b89d574f7b8e716c0a4d4ada5c35f21b63be4fc857aec4a055c655bfa2fefc3403ab62879
@@ -0,0 +1 @@
1
+ (()=>{var Dt=globalThis.ReactDOM;var{createPortal:Se,createRoot:q,flushSync:Ne,hydrateRoot:ze,preconnect:Be,prefetchDNS:Ee,preinit:Te,preinitModule:$e,preload:De,preloadModule:Ae,requestFormReset:Pe,unstable_batchedUpdates:Le,useFormState:_e,useFormStatus:Ie,version:Me}=Dt;var J=globalThis.React,e=J,{Children:He,Component:je,Fragment:Fe,PureComponent:Ve,StrictMode:We,Suspense:Ye,cloneElement:Ue,createContext:Xe,createElement:Ge,createRef:qe,forwardRef:Je,isValidElement:Ke,lazy:Ze,memo:Qe,startTransition:Re,useCallback:to,useContext:eo,useDebugValue:oo,useDeferredValue:ro,useEffect:E,useId:no,useImperativeHandle:so,useLayoutEffect:K,useMemo:A,useReducer:ao,useRef:j,useState:x,useSyncExternalStore:lo,useTransition:io}=J;var Z=new Map,L=new Map,Q=(t,o)=>Z.set(t,o),R=()=>{L.forEach(t=>t.unmount()),L.clear()},tt=(t,o)=>t.querySelectorAll("[data-react-ui]").forEach(s=>{if(L.has(s))return;let r=Z.get(s.dataset.reactUi),a=o(s);L.set(s,a),a.render(e.createElement(r,JSON.parse(s.dataset.props)))});var et=(t,o)=>{let s=()=>tt(t,o);t.addEventListener("turbo:load",s),t.addEventListener("turbo:frame-load",s),t.addEventListener("turbo:before-cache",R),t.readyState==="loading"?t.addEventListener("DOMContentLoaded",s):s()};var At="border border-amber-500 bg-white text-amber-700 dark:bg-zinc-900 dark:text-amber-400",Pt="border border-gray-300 bg-white text-gray-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-gray-400",Lt="border border-accent-600 bg-accent-600 text-white",_t=({name:t,connected:o,armed:s,onArm:r,connecting:a})=>e.createElement("button",{onClick:l=>{a||(l.stopPropagation(),r(l))},title:s?"Now choose a step to connect to":"Connect this branch",className:s?Lt:o?Pt:At,style:{padding:"1px 8px",marginRight:4,borderRadius:999,fontSize:11,cursor:"pointer"}},t||"next"),ot=_t;var It={width:240,boxSizing:"border-box",borderRadius:8},Mt={padding:"8px 14px",textAlign:"center",fontWeight:600},Ot={fontWeight:600,lineHeight:1.3},Ht=(t,o,s)=>{let r=s?"ring-4 ring-accent-600/15":"shadow-sm";return t||s&&!o?`border-accent-600 ${r}`:o?`border-red-600 ${r}`:"border-gray-300 shadow-sm dark:border-zinc-700"},jt=({node:t,selected:o,armed:s,connecting:r,onSelect:a,onArm:l,onDragEnd:i,onDragStart:f})=>{let p=t.begins_here||t.ends_here,d=t.begins_here,m=t.ends_here?[]:t.ports,u=r&&!d;return e.createElement("div",{ref:t.ref,"data-step":t.id,draggable:!d,onDragStart:c=>{c.dataTransfer.effectAllowed="move",c.dataTransfer.setData("text/plain",t.id),f()},onDragEnd:i,onClick:a,className:`bg-white border-2 dark:bg-zinc-900 ${Ht(u,t.violations.length>0,o)}`,style:{...It,padding:p?0:"12px 14px",cursor:d?"default":r?"crosshair":"grab"}},p&&e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:Mt},t.label),!p&&e.createElement("div",{style:Ot},t.label),!p&&e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginTop:2}},t.type),t.violations.map(c=>e.createElement("div",{key:c.problem+c.detail,className:"text-red-600 dark:text-red-400",style:{fontSize:11,padding:"0 14px 6px"}},c.problem.replace(/_/g," "),c.detail?`: ${c.detail}`:"")),m.length>0&&e.createElement("div",{style:{padding:t.begins_here?"0 14px 8px":"10px 0 0"}},m.map(c=>e.createElement(ot,{key:c,name:c,connected:t.connected.includes(c),connecting:r,armed:s===c,onArm:b=>l(c,b)}))))},F=jt;var rt=(t,o)=>t.includes(o)?t.filter(s=>s!==o):[...t,o],nt=(t,o)=>({...o?.choices||{},...t?.choices||{}});var k=(...t)=>t.filter(Boolean).join(" ");var Ft=({as:t="input",disabled:o,className:s,...r})=>e.createElement(t,{...r,disabled:o,className:k("ks-input",o&&"ks-input-disabled",s)}),w=Ft;var Vt=t=>e.createElement(w,{...t,as:"select"}),st=Vt;var Wt=({className:t,...o})=>e.createElement("input",{type:"checkbox",...o,className:k("ks-checkbox",t)}),V=Wt;var Yt=t=>(t||[]).map(o=>typeof o=="object"?o:{value:o,label:o}),Ut=({type:t,value:o,choices:s,onChange:r,onSettle:a})=>{if(t==="boolean")return e.createElement(V,{checked:!!o,onChange:l=>a(l.target.checked)});if(t==="select"||t==="previous_step"||t==="from_step")return e.createElement(st,{className:"mb-3",value:o??"",onChange:l=>a(l.target.value)},e.createElement("option",{value:""}),Yt(s).map(l=>e.createElement("option",{key:l.value,value:l.value},l.label)));if(t==="multi_select"){let l=Array.isArray(o)?o:[],i=f=>a(rt(l,f));return e.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:8}},(s||[]).map(f=>e.createElement("label",{key:f,style:{display:"inline-flex",alignItems:"center",gap:4}},e.createElement(V,{checked:l.includes(f),onChange:()=>i(f)}),e.createElement("span",null,f))))}return t==="integer"||t==="float"?e.createElement(w,{className:"mb-3",type:"number",step:t==="integer"?"1":"any",value:o??"",onChange:l=>r(l.target.value),onBlur:l=>a(l.target.value)}):e.createElement(w,{className:"mb-3",type:"text",value:o??"",onChange:l=>r(l.target.value),onBlur:l=>a(l.target.value)})},_=Ut;var at=(t,o,s,r)=>t.map((a,l)=>l===o?{...a,[s]:r}:a);var Xt={primary:"ks-button-primary",secondary:"ks-button-secondary",danger:"ks-button-danger"},Gt={sm:"ks-button-sm",md:"ks-button-md",lg:"ks-button-lg"},qt=({variant:t="primary",size:o="md",className:s,children:r,...a})=>{let l=a.href?"a":"button";return e.createElement(l,{...a,className:k("ks-button",Xt[t],Gt[o],s)},r)},y=qt;var Jt=({holds:t,labels:o,rows:s,onChange:r,onSettle:a})=>{let l=Array.isArray(s)?s:[],i=(f,p,d,m)=>{let u=at(l,f,p,d);m?a(u):r(u)};return e.createElement("div",{style:{marginBottom:12}},l.map((f,p)=>e.createElement("div",{key:p,className:"rounded-md border border-gray-200 dark:border-zinc-700",style:{padding:"8px 8px 2px",marginBottom:6}},Object.entries(t).map(([d,m])=>e.createElement("label",{key:d,style:{display:"block"}},e.createElement("span",{className:"text-gray-500 dark:text-gray-400",style:{display:"block",marginBottom:2,fontSize:11}},(o||{})[d]||d),e.createElement(_,{type:m,value:f[d],onChange:u=>i(p,d,u,!1),onSettle:u=>i(p,d,u,!0)}))),e.createElement(y,{variant:"secondary",size:"sm",className:"mb-1.5",onClick:()=>a(l.filter((d,m)=>m!==p))},"Remove"))),e.createElement(y,{variant:"secondary",size:"sm",className:"w-full",onClick:()=>a([...l,{}])},"Add"))},lt=Jt;var Kt=({as:t="div",className:o,children:s,...r})=>e.createElement(t,{...r,className:k("ks-panel",o)},s),$=Kt;var I=(t,o)=>({className:s,children:r,...a})=>e.createElement(t,{...a,className:k(o,s)},r),P=I("label","ks-label"),Ro=I("p","ks-hint"),tr=I("p","ks-error"),er=I("span","ks-required");var Zt={width:280,padding:20,overflowY:"auto"},Qt=({node:t,fields:o,holds:s,labels:r,recordLabels:a,choices:l,onSave:i,onDelete:f,onClose:p})=>{let[d,m]=x(t.config);E(()=>m(t.config),[t.id,t.config]);let u=c=>{m(c),JSON.stringify(c)!==JSON.stringify(t.config)&&i(c)};return e.createElement($,{as:"aside",style:Zt,"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:p,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),Object.entries(o).map(([c,b])=>e.createElement(P,{key:c},e.createElement("span",{style:{display:"block",marginBottom:3}},r[c]||c),b==="list"?e.createElement(lt,{holds:s[c]||{},labels:a[c]||{},rows:d[c],onChange:h=>m({...d,[c]:h}),onSettle:h=>u({...d,[c]:h})}):e.createElement(_,{type:b,value:d[c],choices:l[c],onChange:h=>m({...d,[c]:h}),onSettle:h=>u({...d,[c]:h})}))),e.createElement(y,{variant:"danger",size:"sm",className:"w-full",onClick:f},"Delete step"))},it=Qt;var Rt={width:240,boxSizing:"border-box",padding:"10px 14px",borderRadius:8,textAlign:"center",fontSize:12,cursor:"pointer"},te="border-2 border-dashed border-amber-500 bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300",ee="border-2 border-dashed border-accent-600 bg-accent-50 text-accent-800 dark:bg-accent-950 dark:text-accent-200",oe=({node:t,dragging:o,onFill:s,onDrop:r})=>e.createElement("div",{ref:t.ref,"data-placeholder":t.id,title:`Choose the step \u201C${t.label}\u201D should lead to`,onClick:s,onDragOver:a=>{o&&(a.preventDefault(),a.dataTransfer.dropEffect="move")},onDrop:a=>{a.preventDefault(),r()},className:o?ee:te,style:Rt},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")),dt=oe;var re=({entries:t,at:o,onPick:s,onConnect:r,onDismiss:a})=>e.createElement($,{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(y,{key:l.type,variant:"secondary",size:"sm",className:"w-full mb-1.5 flex-col",onClick:()=>s(l)},l.label,e.createElement("span",{className:"opacity-75",style:{display:"block",fontSize:11}},l.type))),r&&e.createElement(y,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:r},"Connect to a step already here"),e.createElement(y,{variant:"secondary",size:"sm",className:"w-full",onClick:a},"Cancel")),ct=re;var ne={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},pt=({label:t,title:o,idle:s,enabled:r,onUse:a})=>e.createElement(y,{variant:"secondary",size:"sm",title:r?o:s,disabled:!r,onClick:a,style:{opacity:r?1:.4,cursor:r?"pointer":"default"}},t),se=({undoable:t,redoable:o,onAdd:s,onUndo:r,onRedo:a})=>e.createElement("div",{style:{position:"absolute",top:16,left:16,zIndex:5,display:"flex",gap:8}},e.createElement(y,{variant:"secondary",className:"rounded-full",style:ne,title:"Add a step",onClick:s},"+"),e.createElement(pt,{label:"\u21B6 Undo",title:"Undo the last change",idle:"Nothing to undo",enabled:t,onUse:r}),e.createElement(pt,{label:"\u21B7 Redo",title:"Redo the change you undid",idle:"Nothing to redo",enabled:o,onUse:a})),mt=se;var ut=t=>{let o=t.problem.replace(/_/g," ");return t.node?`\u201C${t.node}\u201D ${o}`:o};var ft=({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 ae={width:280,padding:20,overflowY:"auto"},W={fontWeight:600,marginBottom:6,marginTop:14},M={fontSize:12,marginBottom:4,lineHeight:1.4},gt={display:"block",marginBottom:3},yt=(t,o,s)=>r=>{r.target.value!==(t[o]??"")&&s({[o]:r.target.value})},le=({flow:t,changes:o,problems:s,refusal:r,notice:a,onCreate:l,onPublish:i,onSaveDetails:f,onClose:p})=>e.createElement($,{as:"aside",style:ae,"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:p,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},ft(t)),e.createElement("h2",{style:W},"Problems"),r&&e.createElement("p",{className:"text-red-800 dark:text-red-400",style:{...M,fontWeight:600},"data-refusal":!0},r),a&&!r&&e.createElement("p",{className:"text-emerald-800 dark:text-emerald-400",style:{...M,fontWeight:600},"data-notice":!0},a),s.length===0&&!r?e.createElement("p",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:12}},"Nothing wrong with this flow."):s.map(d=>e.createElement("p",{key:`${d.node}-${d.problem}`,className:"text-amber-700 dark:text-amber-400",style:M,"data-problem":!0},"\u26A0 ",ut(d))),e.createElement("h2",{style:W},"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((d,m)=>e.createElement("p",{key:m,style:M,"data-change":!0},d)),e.createElement("h2",{style:W},"Details"),e.createElement(P,null,e.createElement("span",{style:gt},"Title"),e.createElement(w,{className:"mb-3",defaultValue:t.title??"",onBlur:yt(t,"title",f),"data-flow-title":!0})),e.createElement(P,null,e.createElement("span",{style:gt},"Start label"),e.createElement(w,{className:"mb-3",defaultValue:t.start_label??"",onBlur:yt(t,"start_label",f),"data-flow-start-label":!0})),e.createElement("div",{style:{marginTop:16}},e.createElement(y,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:l,"data-create-version":!0},"Create version"),e.createElement(y,{size:"sm",className:"w-full mb-1.5",onClick:i,"data-publish":!0},"Publish"),e.createElement(y,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:t.definition_url,"data-definition":!0},"Definition"),e.createElement(y,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:t.details_url,"data-details":!0},"Edit details"))),ht=le;var xt={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},ie=({link:t,onInsert:o,onRemove:s,onDrop:r,dragging:a})=>{let[l,i]=x(!1),f=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:p=>{p.preventDefault(),p.dataTransfer.dropEffect="move"},onDrop:p=>{p.preventDefault(),i(!1),r()}},e.createElement("div",{style:{display:"flex",gap:4,justifyContent:"center",alignItems:"center",height:"100%",opacity:f?1:0,transition:"opacity .12s"}},e.createElement(y,{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:xt},"+"),!a&&e.createElement(y,{variant:"danger",title:"Remove this connection",onClick:s,className:"rounded-full",style:xt},"\xD7")))},bt=ie;var de=({links:t,extent:o,dragging:s,onInsert:r,onRemove:a,onDrop: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-10,top:i.midY-18,zIndex:3,fontSize:11,padding:"0 3px"}},i.label)),t.filter(i=>!i.placeholder).map(i=>e.createElement(bt,{key:i.id,link:i,dragging:!!s,onInsert:()=>r(i),onRemove:()=>a(i),onDrop:()=>l(i)}))),vt=de;var ce="Your change was not saved because the server could not be reached.",kt=({base:t,token:o,fetch:s,onFlow:r,onError:a,onNotice:l})=>async(i,f,p)=>{let d=await s(t+i,{method:f,headers:{"Content-Type":"application/json","X-CSRF-Token":o},body:p&&JSON.stringify(p)}).catch(()=>null);if(!d)return l(null),a(ce);let m=await d.json().catch(()=>({}));if(!d.ok)return l(null),a(m.error||"That change was refused");a(null),l(m.notice||null),r(await(await s(t+".json",{headers:{Accept:"application/json"}})).json())};var pe=(t,o,s)=>{let[r,a]=x(s),[l,i]=x(null),[f,p]=x(null),d=A(()=>kt({base:t,token:o,fetch:(...m)=>window.fetch(...m),onFlow:a,onError:i,onNotice:p}),[t,o]);return{flow:r,error:l,notice:f,send:d}},wt=pe;var O=(t,o,s,r)=>{let a=t.left-s.left+r.scrollLeft,l=t.top-s.top+r.scrollTop;return o==="left"?{x:a,y:l+t.height/2}:o==="right"?{x:a+t.width,y:l+t.height/2}:o==="top"?{x:a+t.width/2,y:l}:{x:a+t.width/2,y:l+t.height}},me=(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},ue=(t,o,s,r,a)=>{let l={straight:`M ${o.x} ${o.y} L ${s.x} ${s.y}`,turn:`M ${o.x} ${o.y} H ${s.x} V ${s.y}`,lane:`M ${o.x} ${o.y} V ${r} H ${s.x} V ${s.y}`,detour:`M ${o.x} ${o.y} H ${a} V ${s.y} H ${s.x}`}[t.route];return{...t,path:l,midX:t.route==="detour"?a:(o.x+s.x)/2,midY:t.route==="lane"?r:(o.y+s.y)/2}},fe=(t,o,s,r)=>{let[a,l]=x([]),[i,f]=x({width:"100%",height:"100%"});return K(()=>{let p=()=>{let m=o.current?.getBoundingClientRect();m&&(f({width:o.current.scrollWidth,height:o.current.scrollHeight}),l(t.edges.flatMap(u=>{let c=s.current[u.source]?.getBoundingClientRect(),b=s.current[u.target]?.getBoundingClientRect();if(!c||!b)return[];let h=O(c,u.leaves,m,o.current),C=me(O(b,u.enters,m,o.current),u.enters),N=(O(c,"bottom",m,o.current).y+O(b,"top",m,o.current).y)/2,z=u.leaves==="left"?Math.min(h.x,C.x)-28:Math.max(h.x,C.x)+28;return[ue(u,h,C,N,z)]})))};p(),window.addEventListener("resize",p);let d=new ResizeObserver(p);return o.current&&d.observe(o.current),()=>{window.removeEventListener("resize",p),d.disconnect()}},r),{links:a,extent:i}},Ct=fe;var St=(t,o)=>{let s=new Set(o),r=t,a=2;for(;s.has(r);)r=`${t}_${a++}`;return r};var he={display:"flex",height:"100%",minHeight:0,fontSize:13},xe={flex:1,overflow:"auto",position:"relative"},be={position:"absolute",top:56,left:16,zIndex:5,width:264,padding:12,borderRadius:8},ve={display:"grid",rowGap:64,columnGap:0,padding:40,justifyContent:"center",position:"relative"},ke={position:"sticky",zIndex:8,top:8,margin:"8px auto 0",width:"fit-content",padding:"6px 12px",borderRadius:6,fontSize:12},we=({port:t,onCancel:o})=>e.createElement("div",{className:"bg-accent-100 text-accent-800 dark:bg-accent-900 dark:text-accent-100",style:ke},"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")),Ce=({base:t,token:o,initial:s})=>{let{flow:r,error:a,notice:l,send:i}=wt(t,o,s),[f,p]=x(null),[d,m]=x(null),[u,c]=x(null),[b,h]=x(null),[C,N]=x(!1),z=j({}),B=j(null);E(()=>{let n=()=>{p(null),N(!0)};return document.addEventListener("easy_flow:open-flow",n),()=>document.removeEventListener("easy_flow:open-flow",n)},[]),E(()=>{if(!C)return;let n=g=>{g.target.closest("[data-builder-panel], [data-open-panel]")||N(!1)};return document.addEventListener("click",n),()=>document.removeEventListener("click",n)},[C]),E(()=>{document.dispatchEvent(new CustomEvent("easy_flow:flow-named",{detail:r.flow?.title||r.flow?.slug}))},[r.flow?.title,r.flow?.slug]);let zt=A(()=>Object.fromEntries(r.nodes.map(n=>[n.id,n])),[r.nodes]),{links:Bt,extent:Et}=Ct(r,B,z,[r,f,zt]),Y=A(()=>{let n={};return r.violations.forEach(g=>{var v;return(n[v=g.node]||(n[v]=[])).push(g)}),n},[r.violations]),H=r.nodes.filter(n=>!n.loose),U=r.nodes.filter(n=>n.loose),Tt=Math.max(0,...H.map(n=>n.row))+1,$t=Math.max(0,...H.map(n=>n.column))+1,X=n=>{if(!u)return;let[g,v]=u;c(null),g!==n&&i("/edges","POST",{from:g,to:n,on:v||null})},S=r.nodes.find(n=>n.id===f),D=r.palette.find(n=>n.type===S?.type);return e.createElement("div",{className:"text-gray-900 dark:text-gray-100",style:he,onMouseUp:()=>h(null),onKeyDown:n=>{n.key==="Escape"&&(p(null),c(null),m(null))},tabIndex:-1},e.createElement("div",{ref:B,className:"bg-surface-50 dark:bg-surface-950",style:xe,onClick:n=>{n.target===B.current&&(p(null),c(null),N(!1))}},u&&e.createElement(we,{port:u[1],onCancel:()=>c(null)}),e.createElement(mt,{undoable:r.undoable,redoable:r.redoable,onAdd:()=>m({at:{x:16,y:52}}),onUndo:()=>{p(null),i("/undo","POST")},onRedo:()=>{p(null),i("/redo","POST")}}),e.createElement(vt,{links:Bt,extent:Et,dragging:b,onInsert:n=>m({from:n.source,to:n.target,at:{x:n.midX+30,y:n.midY}}),onRemove:n=>{p(null),i("/edges","DELETE",{from:n.source,to:n.target})},onDrop:n=>{let g=b;h(null),i("/steps/"+g+"/move","PATCH",{from:n.source,to:n.target})}}),U.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:be},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"),U.map(n=>e.createElement("div",{key:n.id,style:{marginBottom:8}},e.createElement(F,{node:{...n,ref:g=>{z.current[n.id]=g},violations:Y[n.id]||[],connected:[]},selected:f===n.id,armed:u&&u[0]===n.id?u[1]:null,connecting:!!u,onSelect:()=>u?X(n.id):p(n.id),onArm:(g,v)=>{v.stopPropagation(),c([n.id,g])},onDragStart:()=>h(n.id),onDragEnd:()=>h(null)})))),e.createElement("div",{style:{...ve,gridTemplateColumns:`repeat(${$t+1}, ${280/2}px)`,gridTemplateRows:`repeat(${Tt}, auto)`}},H.map(n=>e.createElement("div",{key:n.id,style:{gridRow:n.row+1,gridColumn:`${n.column+1} / span 2`,zIndex:2}},n.placeholder&&e.createElement(dt,{node:{...n,ref:g=>{z.current[n.id]=g}},dragging:!!b,onFill:()=>m({from:n.from,on:n.on,at:{x:16,y:52}}),onDrop:()=>{let g=b;h(null),g&&i("/edges","POST",{from:n.from,to:g,on:n.on})}}),!n.placeholder&&e.createElement(F,{node:{...n,ref:g=>{z.current[n.id]=g},violations:Y[n.id]||[],connected:r.edges.filter(g=>g.source===n.id).map(g=>g.label||null)},selected:n.id===f,armed:u&&u[0]===n.id?u[1]:null,connecting:!!u&&u[0]!==n.id,onSelect:()=>u?X(n.id):p(n.id),onArm:(g,v)=>{let G=B.current.getBoundingClientRect();m({from:n.id,on:g,at:{x:v.clientX-G.left+B.current.scrollLeft+8,y:v.clientY-G.top+B.current.scrollTop+8}})},onDragEnd:()=>h(null),onDragStart:()=>h(n.id)})))),d&&e.createElement(ct,{entries:r.palette,at:d.at,onDismiss:()=>m(null),onConnect:d.on!==void 0?()=>{c([d.from,d.on]),m(null)}:null,onPick:n=>{let g=d;m(null),i("/steps","POST",{id:St(n.type,r.nodes.map(v=>v.id)),type:n.type,from:g.from,to:g.to,on:g.on})}})),C&&!S&&e.createElement(ht,{flow:r.flow||{},changes:r.changes||[],problems:r.violations,refusal:a,notice:l,onClose:()=>N(!1),onCreate:()=>i("/versions","POST"),onPublish:()=>i("/publish","POST"),onSaveDetails:n=>i("/details","PATCH",{flow:n})}),S&&e.createElement(it,{node:S,fields:D?.fields||{},holds:D?.records||{},labels:D?.labels||{},recordLabels:D?.record_labels||{},choices:nt(S,D),onClose:()=>p(null),onSave:n=>i(`/steps/${S.id}`,"PATCH",{config:n}),onDelete:()=>{p(null),i(`/steps/${S.id}`,"DELETE")}}))},Nt=Ce;Q("easy_flow/flow-editor",Nt);document.addEventListener("easy_flow:flow-named",t=>{let o=document.querySelector("[data-flow-heading]");o&&(o.textContent=t.detail)});et(document,q);})();
@@ -0,0 +1,19 @@
1
+ module EasyFlow
2
+ module Manage
3
+ module AuthenticatesAdmin
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :authenticate_admin
8
+ end
9
+
10
+ private
11
+
12
+ def authenticate_admin
13
+ return unless EasyFlow.admin_authentication_method
14
+
15
+ send(EasyFlow.admin_authentication_method)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ module EasyFlow
2
+ module Manage
3
+ module DrawsCanvas
4
+ extend ActiveSupport::Concern
5
+
6
+ private
7
+
8
+ def canvas_payload(flow)
9
+ Canvas.new(canvas_document(flow)).to_h
10
+ .merge("undoable" => flow.edit_history.undoable?, "redoable" => flow.edit_history.redoable?,
11
+ "changes" => listed_changes(flow), "flow" => flow_details(flow))
12
+ end
13
+
14
+ def canvas_document(flow)
15
+ Document.new(flow.document || flow.definition || {})
16
+ end
17
+
18
+ def listed_changes(flow)
19
+ flow.changes_since_version.to_a.map { |change| Change.phrase(change) }
20
+ end
21
+
22
+ def flow_details(flow)
23
+ { "title" => flow.title,
24
+ "slug" => flow.slug,
25
+ "start_label" => flow.start_label,
26
+ "version" => flow.current_definition_version&.number,
27
+ "published" => flow.live_version&.number,
28
+ "definition_url" => edit_manage_flow_definition_path(flow),
29
+ "details_url" => edit_manage_flow_path(flow),
30
+ "history_url" => manage_flow_versions_path(flow) }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module EasyFlow
2
+ class ApplicationController < EasyFlow.base_controller.constantize
3
+ layout -> { EasyFlow.layout }
4
+ helper KeystoneUiHelper
5
+
6
+ rescue_from NotPublished, NotPermitted, Withdrawn, with: :refuse
7
+
8
+ helper_method :flow_start_path
9
+
10
+ private
11
+
12
+ def flow_start_path(slug)
13
+ easy_flow.flow_path(slug)
14
+ end
15
+
16
+ def admit(flow)
17
+ Admission.of(flow, permitted: permitted?(flow))
18
+ end
19
+
20
+ def permitted?(flow)
21
+ return false unless EasyFlow.visitor_authorization_method
22
+
23
+ send(EasyFlow.visitor_authorization_method, flow)
24
+ end
25
+
26
+ def refuse(refusal)
27
+ return head :not_found unless EasyFlow.refusal_method
28
+
29
+ send(EasyFlow.refusal_method, refusal)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,103 @@
1
+ module EasyFlow
2
+ class FlowsController < ApplicationController
3
+ helper_method :flow_start_path, :flow_step_path, :previewing?, :step_form, :carries_answers?
4
+
5
+ def show
6
+ @flow = flow
7
+ end
8
+
9
+ def start
10
+ redirect_to easy_flow.run_path(Run.start(flow))
11
+ end
12
+
13
+ def step
14
+ @guide = QuestionRunner.new(running_definition)
15
+ @progress = progress
16
+ @guide.run(@progress)
17
+ @answers = @progress.recorded
18
+ @question = @guide.next_step(@answers)
19
+ @drawing = @guide.drawing_at(@answers)
20
+ return render :step if @question
21
+
22
+ render_completion
23
+ end
24
+
25
+ def update
26
+ params[:back] ? progress.discard_last : record_submitted
27
+ redirect_to easy_flow.run_path(run)
28
+ end
29
+
30
+ private
31
+
32
+ def flow_start_path(slug)
33
+ easy_flow.flow_path(slug)
34
+ end
35
+
36
+ def flow_step_path(slug)
37
+ easy_flow.flow_step_path(slug)
38
+ end
39
+
40
+ def previewing?
41
+ false
42
+ end
43
+
44
+ def step_form
45
+ return { url: easy_flow.run_path(run), method: :patch } if run
46
+
47
+ { url: flow_step_path(@guide.slug), method: :get }
48
+ end
49
+
50
+ def carries_answers?
51
+ run.nil?
52
+ end
53
+
54
+ def render_completion
55
+ @answered = @guide.state_on_path(@answers)
56
+ @progress.finish(@answered)
57
+ render :complete
58
+ end
59
+
60
+ def progress
61
+ return @progress ||= Progress.for(run.flow, run: run) if run
62
+
63
+ @progress ||= Progress.for(flow, answers: submitted_answers, definition: running_definition)
64
+ end
65
+
66
+ def running_definition
67
+ @running_definition ||= run ? run.pinned_definition : flowing_definition(flow)
68
+ end
69
+
70
+ def run
71
+ return unless params[:id]
72
+
73
+ @run ||= admitted_run
74
+ end
75
+
76
+ def admitted_run
77
+ found = Run.find(params[:id])
78
+
79
+ Admission.of_run(found, permitted: permitted?(found.flow))
80
+ end
81
+
82
+ def flow
83
+ @stored_flow ||= admit(Definition.find_by(slug: params[:slug]))
84
+ end
85
+
86
+ def flowing_definition(flow)
87
+ flow.live_definition
88
+ end
89
+
90
+ def submitted_answers
91
+ params.fetch(:answers, {}).permit(*@guide.steps.map(&:id)).to_h.symbolize_keys
92
+ end
93
+
94
+ def record_submitted
95
+ id, value = params.fetch(:answers, {}).permit(*asked).to_h.first
96
+ progress.record(id, value)
97
+ end
98
+
99
+ def asked
100
+ QuestionRunner.new(run.pinned_definition).steps.map(&:id)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,14 @@
1
+ require "keystone_ui/react/mount_helper"
2
+
3
+ module EasyFlow
4
+ module Manage
5
+ class BaseController < EasyFlow.base_controller.constantize
6
+ include AuthenticatesAdmin
7
+
8
+ layout -> { EasyFlow.admin_layout }
9
+
10
+ helper KeystoneUiHelper
11
+ helper KeystoneUi::React::MountHelper
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,183 @@
1
+ module EasyFlow
2
+ module Manage
3
+ class CanvasController < BaseController
4
+ include DrawsCanvas
5
+ def show
6
+ render json: canvas_payload(flow)
7
+ end
8
+
9
+ def create
10
+ objections = Validator.new(document).violations
11
+ return render json: { error: refusal(objections) }, status: :unprocessable_entity if objections.any?
12
+
13
+ stood_at = flow.current_definition_version&.number
14
+ flow.create_version
15
+
16
+ render json: { notice: created(stood_at) }
17
+ end
18
+
19
+ def publish
20
+ objections = Validator.new(document).violations
21
+ return render json: { error: refusal(objections) }, status: :unprocessable_entity if objections.any?
22
+
23
+ ran = flow.live_version&.number
24
+ flow.publish
25
+
26
+ render json: { notice: published(ran) }
27
+ end
28
+
29
+ def details
30
+ flow.update!(details_params)
31
+ render json: { notice: "Saved the flow's details." }
32
+ end
33
+
34
+ def undo
35
+ flow.edit_history.undo_change
36
+ head :no_content
37
+ end
38
+
39
+ def redo
40
+ flow.edit_history.redo_change
41
+ head :no_content
42
+ end
43
+
44
+ def add_step
45
+ apply(:added, params[:id]) do |edited|
46
+ next edited.insert(new_step, on: edge_endpoints, leaving: first_port) if placed_on_edge?
47
+ next edited.add(new_step).connect(from: params[:from], to: params[:id], on: params[:on].presence) if branched_from?
48
+
49
+ edited.add(new_step)
50
+ end
51
+ end
52
+
53
+ def configure_step
54
+ apply(:updated, params[:step]) { |edited| edited.configure(params[:step], coerced(edited, params[:step])) }
55
+ end
56
+
57
+ def move_step
58
+ apply(:moved, params[:step]) { |edited| edited.move(params[:step], on: edge_endpoints, leaving: port_for(params[:step])) }
59
+ end
60
+
61
+ def remove_step
62
+ return render json: { error: refusal_to_remove }, status: :unprocessable_entity if begins_the_flow?(params[:step])
63
+
64
+ apply(:removed, params[:step]) { |edited| edited.remove(params[:step]) }
65
+ end
66
+
67
+ def connect
68
+ apply(:connected, params[:from], params[:to]) { |edited| edited.connect(from: params[:from], to: params[:to], on: params[:on].presence) }
69
+ end
70
+
71
+ def disconnect
72
+ apply(:disconnected, params[:from], params[:to]) { |edited| edited.disconnect(from: params[:from], to: params[:to]) }
73
+ end
74
+
75
+ private
76
+
77
+ def apply(action = nil, *steps)
78
+ before = document.to_h
79
+ flow.edit_history.record(yield(document), action, steps, before)
80
+ head :no_content
81
+ rescue InvalidEdit => invalid
82
+ render json: { error: invalid.message }, status: :unprocessable_entity
83
+ end
84
+
85
+ def flow
86
+ @flow ||= Definition.find(params[:flow_id])
87
+ end
88
+
89
+ def document
90
+ Document.new(flow.document || flow.definition || {})
91
+ end
92
+
93
+ def details_params
94
+ params.require(:flow).permit(:title, :start_label, :persists)
95
+ end
96
+
97
+ def new_step
98
+ { "id" => params.require(:id), "type" => params.require(:type) }
99
+ end
100
+
101
+ def first_port
102
+ first_value_of(Node.new(id: nil, type: params[:type], config: {}))
103
+ end
104
+
105
+ def port_for(id)
106
+ first_value_of(document.node(id))
107
+ end
108
+
109
+ def first_value_of(node)
110
+ return unless node && EasyFlow.registry.registered?(node.type)
111
+
112
+ step_type = EasyFlow.registry.fetch(node.type)
113
+ return unless step_type.routes?
114
+
115
+ step_type.outputs.flat_map { |output| output.values_for(node) }.first&.fetch("value", nil)&.to_s
116
+ end
117
+
118
+ def begins_the_flow?(id)
119
+ node = document.node(id)
120
+
121
+ node.present? && document.begins_here?(node)
122
+ end
123
+
124
+ def placed_on_edge?
125
+ params[:from].present? && params[:to].present?
126
+ end
127
+
128
+ def branched_from?
129
+ params[:from].present?
130
+ end
131
+
132
+ def edge_endpoints
133
+ [ params[:from], params[:to] ]
134
+ end
135
+
136
+ def configuration
137
+ params.fetch(:config, {}).permit!.to_h
138
+ end
139
+
140
+ def created(stood_at)
141
+ now_at = flow.reload.current_definition_version&.number
142
+ return "Nothing has changed since version #{stood_at}." if now_at == stood_at
143
+
144
+ "Created version #{now_at}."
145
+ end
146
+
147
+ def published(ran)
148
+ now_at = flow.reload.live_version&.number
149
+ return "Visitors already run version #{now_at}." if now_at == ran
150
+
151
+ "Published version #{now_at}. Visitors run it now."
152
+ end
153
+
154
+ def refusal_to_remove
155
+ "Cannot remove where a flow begins."
156
+ end
157
+
158
+ def refusal(objections)
159
+ "Cannot publish: #{objections.map { |problem| worded(problem) }.join(', ')}."
160
+ end
161
+
162
+ def worded(problem)
163
+ trouble = problem.problem.to_s.humanize(capitalize: false)
164
+
165
+ problem.node ? "“#{problem.node}” is #{trouble}" : trouble
166
+ end
167
+
168
+ def coerced(edited, id)
169
+ step_type = Digest.new(edited).step(id)&.type
170
+ return configuration unless step_type && EasyFlow.registry.registered?(step_type)
171
+
172
+ settled(EasyFlow.registry.fetch(step_type))
173
+ end
174
+
175
+ def settled(step_type)
176
+ step_type.settings.coerce(configuration).tap do |values|
177
+ objections = step_type.settings.objections(values)
178
+ raise InvalidEdit, objections.join(", ") if objections.any?
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,15 @@
1
+ module EasyFlow
2
+ module Manage
3
+ class DefinitionsController < BaseController
4
+ def edit
5
+ @flow = Definition.find(params[:flow_id])
6
+ end
7
+
8
+ def update
9
+ @flow = Definition.find(params[:flow_id])
10
+ @flow.edit_history.edit_document(JSON.parse(params.require(:definition)))
11
+ redirect_to manage_flow_path(@flow), notice: "Definition saved."
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module EasyFlow
2
+ module Manage
3
+ class FlowsController < BaseController
4
+ include DrawsCanvas
5
+ def index
6
+ @flows = ordered_flows
7
+ end
8
+
9
+ def create
10
+ @flow = Definition.new(create_params)
11
+
12
+ if @flow.save
13
+ redirect_to manage_flow_path(@flow), notice: "Flow created."
14
+ else
15
+ @flows = ordered_flows
16
+ render :index, status: :unprocessable_entity
17
+ end
18
+ end
19
+
20
+ def show
21
+ @flow = Definition.find(params[:id])
22
+ @canvas = canvas_payload(@flow)
23
+ end
24
+
25
+ def edit
26
+ @flow = Definition.find(params[:id])
27
+ end
28
+
29
+ def update
30
+ @flow = Definition.find(params[:id])
31
+ @flow.update!(flow_params)
32
+ redirect_to manage_flow_path(@flow), notice: "Saved."
33
+ end
34
+
35
+ def destroy
36
+ Definition.find(params[:id]).destroy!
37
+ redirect_to manage_flows_path, notice: "Flow removed."
38
+ end
39
+
40
+ private
41
+
42
+ def ordered_flows
43
+ Definition.order(:slug)
44
+ end
45
+
46
+ def create_params
47
+ params.require(:flow).permit(:slug, :kind)
48
+ end
49
+
50
+ def flow_params
51
+ params.require(:flow).permit(:title, :start_label, :persists)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,38 @@
1
+ module EasyFlow
2
+ module Manage
3
+ class PreviewsController < ::EasyFlow::FlowsController
4
+ include AuthenticatesAdmin
5
+
6
+ def show
7
+ @flow = previewed
8
+ render template: "easy_flow/flows/show"
9
+ end
10
+
11
+ private
12
+
13
+ def flow_start_path(_slug)
14
+ easy_flow.manage_flow_preview_path(previewed)
15
+ end
16
+
17
+ def flow_step_path(_slug)
18
+ easy_flow.step_manage_flow_preview_path(previewed)
19
+ end
20
+
21
+ def previewing?
22
+ true
23
+ end
24
+
25
+ def flowing_definition(flow)
26
+ flow.document
27
+ end
28
+
29
+ def previewed
30
+ @previewed ||= Definition.find(params[:flow_id])
31
+ end
32
+
33
+ def admit(_flow)
34
+ previewed
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ module EasyFlow
2
+ module Manage
3
+ class VersionsController < BaseController
4
+ def index
5
+ @flow = flow
6
+ @versions = @flow.definition_versions.order(number: :desc)
7
+ end
8
+
9
+ def return
10
+ flow.return_to(flow.definition_versions.find(params[:id]))
11
+
12
+ redirect_to manage_flow_path(flow), notice: "The flow is back to that version."
13
+ end
14
+
15
+ private
16
+
17
+ def flow
18
+ @flow ||= Definition.find(params[:flow_id])
19
+ end
20
+ end
21
+ end
22
+ end