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.
- checksums.yaml +4 -4
- data/app/assets/builds/easy_flow/canvas.js +1 -0
- data/app/controllers/concerns/easy_flow/manage/authenticates_admin.rb +19 -0
- data/app/controllers/concerns/easy_flow/manage/draws_canvas.rb +34 -0
- data/app/controllers/easy_flow/application_controller.rb +32 -0
- data/app/controllers/easy_flow/flows_controller.rb +103 -0
- data/app/controllers/easy_flow/manage/base_controller.rb +14 -0
- data/app/controllers/easy_flow/manage/canvas_controller.rb +183 -0
- data/app/controllers/easy_flow/manage/definitions_controller.rb +15 -0
- data/app/controllers/easy_flow/manage/flows_controller.rb +55 -0
- data/app/controllers/easy_flow/manage/previews_controller.rb +38 -0
- data/app/controllers/easy_flow/manage/versions_controller.rb +22 -0
- data/app/javascript/easy_flow/canvas/Canvas.jsx +197 -0
- data/app/javascript/easy_flow/canvas/Connector.jsx +30 -0
- data/app/javascript/easy_flow/canvas/ConnectorLayer.jsx +31 -0
- data/app/javascript/easy_flow/canvas/Control.jsx +42 -0
- data/app/javascript/easy_flow/canvas/Inspector.jsx +44 -0
- data/app/javascript/easy_flow/canvas/Panel.jsx +61 -0
- data/app/javascript/easy_flow/canvas/Placeholder.jsx +24 -0
- data/app/javascript/easy_flow/canvas/Port.jsx +16 -0
- data/app/javascript/easy_flow/canvas/Records.jsx +34 -0
- data/app/javascript/easy_flow/canvas/StepCard.jsx +57 -0
- data/app/javascript/easy_flow/canvas/Toolbar.jsx +19 -0
- data/app/javascript/easy_flow/canvas/TypePicker.jsx +21 -0
- data/app/javascript/easy_flow/canvas/changes.js +5 -0
- data/app/javascript/easy_flow/canvas/choices.js +4 -0
- data/app/javascript/easy_flow/canvas/flow.js +7 -0
- data/app/javascript/easy_flow/canvas/flowSender.js +22 -0
- data/app/javascript/easy_flow/canvas/ids.js +7 -0
- data/app/javascript/easy_flow/canvas/rows.js +2 -0
- data/app/javascript/easy_flow/canvas/styles.js +3 -0
- data/app/javascript/easy_flow/canvas/useConnectors.js +75 -0
- data/app/javascript/easy_flow/canvas/useFlow.js +17 -0
- data/app/javascript/easy_flow/canvas.jsx +13 -0
- data/app/views/easy_flow/flows/complete.html.erb +22 -0
- data/app/views/easy_flow/flows/show.html.erb +17 -0
- data/app/views/easy_flow/flows/step.html.erb +27 -0
- data/app/views/easy_flow/manage/definitions/edit.html.erb +13 -0
- data/app/views/easy_flow/manage/flows/edit.html.erb +26 -0
- data/app/views/easy_flow/manage/flows/index.html.erb +32 -0
- data/app/views/easy_flow/manage/flows/show.html.erb +26 -0
- data/app/views/easy_flow/manage/versions/index.html.erb +41 -0
- data/app/views/easy_flow/steps/_choosing.html.erb +9 -0
- data/app/views/layouts/easy_flow/application.html.erb +15 -0
- data/config/routes.rb +32 -0
- data/lib/easy_flow/engine.rb +11 -0
- data/lib/easy_flow/version.rb +1 -1
- data/lib/easy_flow.rb +15 -0
- metadata +58 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7beeb603b60c113d63219761c29a84858b1d9bc5c870c877ea628cd6cde8cb3
|
|
4
|
+
data.tar.gz: 2fac26b851b947941aa562c785e78f037f2647e8cce611f1230026b4a5f2b063
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|