easy_flow 0.32.1 → 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/concerns/easy_flow/manage/draws_canvas.rb +5 -2
- data/app/controllers/easy_flow/flows_controller.rb +3 -0
- data/app/controllers/easy_flow/manage/canvas_controller.rb +26 -12
- data/app/controllers/easy_flow/manage/flows_controller.rb +1 -0
- data/app/javascript/easy_flow/canvas/Canvas.jsx +15 -8
- data/app/javascript/easy_flow/canvas/Connector.jsx +5 -5
- data/app/javascript/easy_flow/canvas/ConnectorLayer.jsx +3 -3
- data/app/javascript/easy_flow/canvas/Panel.jsx +19 -4
- data/app/javascript/easy_flow/canvas/StepCard.jsx +15 -4
- data/app/javascript/easy_flow/canvas/TypePicker.jsx +2 -2
- 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/canvas.rb +23 -13
- data/app/models/easy_flow/change.rb +11 -1
- data/app/models/easy_flow/data_step.rb +33 -0
- data/app/models/easy_flow/definition.rb +5 -0
- data/app/models/easy_flow/document.rb +11 -0
- data/app/models/easy_flow/inputs.rb +24 -0
- data/app/models/easy_flow/progress/kept.rb +4 -0
- data/app/models/easy_flow/progress/loose.rb +4 -0
- data/app/models/easy_flow/read_data.rb +11 -0
- data/app/models/easy_flow/runner.rb +9 -1
- data/app/models/easy_flow/step_type/declaration.rb +5 -1
- data/app/models/easy_flow/step_type.rb +3 -2
- data/app/models/easy_flow/unconnected_steps.rb +44 -0
- data/app/models/easy_flow/write_data.rb +11 -0
- data/app/views/easy_flow/flows/refused.html.erb +3 -0
- data/app/views/easy_flow/manage/flows/show.html.erb +5 -1
- data/lib/easy_flow/engine.rb +2 -0
- data/lib/easy_flow/host.rb +17 -3
- data/lib/easy_flow/version.rb +1 -1
- data/lib/easy_flow.rb +1 -1
- data/the_local/agents/easy_flow-develop.md +30 -3
- data/the_local/agents/easy_flow-info.md +11 -1
- data/the_local/agents/easy_flow-install.md +23 -5
- data/the_local/interface.yml +6 -0
- metadata +7 -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 Lt=globalThis.ReactDOM;var{createPortal:Be,createRoot:Q,flushSync:Te,hydrateRoot:Ee,preconnect:Pe,prefetchDNS:De,preinit:$e,preinitModule:Ae,preload:_e,preloadModule:Le,requestFormReset:Ie,unstable_batchedUpdates:Me,useFormState:Oe,useFormStatus:He,version:Fe}=Lt;var R=globalThis.React,e=R,{Children:We,Component:Ve,Fragment:Ue,PureComponent:Ye,StrictMode:Xe,Suspense:Ge,cloneElement:qe,createContext:Je,createElement:Ke,createRef:Ze,forwardRef:Qe,isValidElement:Re,lazy:to,memo:eo,startTransition:oo,useCallback:ro,useContext:no,useDebugValue:so,useDeferredValue:ao,useEffect:D,useId:io,useImperativeHandle:lo,useLayoutEffect:tt,useMemo:$,useReducer:co,useRef:U,useState:b,useSyncExternalStore:po,useTransition:uo}=R;var et=new Map,M=new Map,ot=(t,o)=>et.set(t,o),rt=()=>{M.forEach(t=>t.unmount()),M.clear()},nt=(t,o)=>t.querySelectorAll("[data-react-ui]").forEach(n=>{if(M.has(n))return;let r=et.get(n.dataset.reactUi),a=o(n);M.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 N=(...t)=>t.filter(Boolean).join(" ");var It={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"},Ot=({variant:t="primary",size:o="md",className:n,children:r,...a})=>{let l=a.href?"a":"button";return e.createElement(l,{...a,className:N("ks-button",It[t],Mt[o],n)},r)},x=Ot;var Ht="border border-amber-500 bg-white text-amber-700 dark:bg-zinc-900 dark:text-amber-400",Ft="border border-gray-300 bg-white text-gray-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-gray-400",jt="border border-accent-600 bg-accent-600 text-white",Wt=({name:t,connected:o,armed:n,onArm:r,connecting:a})=>e.createElement("button",{onClick:l=>{a||(l.stopPropagation(),r(l))},title:n?"Now choose a step to connect to":"Connect this branch",className:n?jt:o?Ft:Ht,style:{padding:"1px 8px",marginRight:4,borderRadius:999,fontSize:11,cursor:"pointer"}},t||"next"),at=Wt;var Vt={width:240,boxSizing:"border-box",borderRadius:8},Ut={padding:"8px 14px",textAlign:"center",fontWeight:600},Yt={fontWeight:600,lineHeight:1.3},Xt=(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"},Gt=({node:t,selected:o,armed:n,connecting:r,onSelect:a,onOpen:l,onArm:i,onDragEnd:d,onDragStart:m})=>{let c=t.begins_here||t.ends_here,g=t.begins_here,u=t.ends_here?[]:t.ports,y=r&&!g;return e.createElement("div",{ref:t.ref,"data-step":t.id,draggable:!g,onDragStart:p=>{p.dataTransfer.effectAllowed="move",p.dataTransfer.setData("text/plain",t.id),m()},onDragEnd:d,onClick:t.opens&&!r?()=>l(t.opens):a,className:`bg-white border-2 dark:bg-zinc-900 ${Xt(y,t.violations.length>0,o)}`,style:{...Vt,padding:c?0:"12px 14px",cursor:g?"default":r?"crosshair":"grab"}},c&&e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:Ut},t.label),!c&&e.createElement("div",{style:Yt},t.label),!c&&e.createElement("div",{className:"text-gray-500 dark:text-gray-400",style:{fontSize:11,marginTop:2}},t.type),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(x,{variant:"secondary",size:"sm",className:"mt-2",onClick:p=>{p.stopPropagation(),a()}},"Settings"),t.violations.map(p=>e.createElement("div",{key:p.problem+p.detail,className:"text-red-600 dark:text-red-400",style:{fontSize:11,padding:"0 14px 6px"}},p.problem.replace(/_/g," "),p.detail?`: ${p.detail}`:"")),u.length>0&&e.createElement("div",{style:{padding:t.begins_here?"0 14px 8px":"10px 0 0"}},u.map(p=>e.createElement(at,{key:p,name:p,connected:t.connected.includes(p),connecting:r,armed:n===p,onArm:v=>i(p,v)}))))},Y=Gt;var X=(t,o)=>t.includes(o)?t.filter(n=>n!==o):[...t,o],it=(t,o)=>({...o?.choices||{},...t?.choices||{}});var qt=({as:t="input",disabled:o,className:n,...r})=>e.createElement(t,{...r,disabled:o,className:N("ks-input",o&&"ks-input-disabled",n)}),T=qt;var Jt=t=>e.createElement(T,{...t,as:"select"}),lt=Jt;var Kt=({className:t,...o})=>e.createElement("input",{type:"checkbox",...o,className:N("ks-checkbox",t)}),O=Kt;var dt=t=>(t||[]).map(o=>typeof o=="object"?o:{value:o,label:o}),Zt=({type:t,value:o,choices:n,onChange:r,onSettle:a,onPick:l,pictureUrl:i})=>{if(t==="picture")return e.createElement("div",{className:"mb-3"},o&&i&&e.createElement("img",{src:i,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:d=>d.target.files[0]&&l(d.target.files[0])}));if(t==="boolean")return e.createElement(O,{checked:!!o,onChange:d=>a(d.target.checked)});if(t==="select"||t==="previous_step"||t==="from_step"||t==="flow")return e.createElement(lt,{className:"mb-3",value:o??"",onChange:d=>a(d.target.value)},e.createElement("option",{value:""}),dt(n).map(d=>e.createElement("option",{key:d.value,value:d.value},d.label)));if(t==="multi_select"){let d=Array.isArray(o)?o:[],m=c=>a(X(d,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(O,{checked:d.includes(c),onChange:()=>m(c)}),e.createElement("span",null,c))))}if(t==="flows"){let d=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(O,{checked:d.includes(m.value),onChange:()=>a(X(d,m.value))}),e.createElement("span",null,m.label))))}return t==="integer"||t==="float"?e.createElement(T,{className:"mb-3",type:"number",step:t==="integer"?"1":"any",value:o??"",onChange:d=>r(d.target.value),onBlur:d=>a(d.target.value)}):e.createElement(T,{className:"mb-3",type:"text",value:o??"",onChange:d=>r(d.target.value),onBlur:d=>a(d.target.value)})},H=Zt;var ct=(t,o,n,r)=>t.map((a,l)=>l===o?{...a,[n]:r}:a);var Qt=(t,o,n,r)=>{let a=(t||{})[n],l=(o||{})[n];return l?(a||{})[r[l]]:a},Rt=({holds:t,labels:o,rows:n,choices:r,outputsOf:a,onChange:l,onSettle:i})=>{let d=Array.isArray(n)?n:[],m=(c,g,u,y)=>{let p=ct(d,c,g,u);y?i(p):l(p)};return e.createElement("div",{style:{marginBottom:12}},d.map((c,g)=>e.createElement("div",{key:g,className:"rounded-md border border-gray-200 dark:border-zinc-700",style:{padding:"8px 8px 2px",marginBottom:6}},Object.entries(t).map(([u,y])=>e.createElement("label",{key:u,style:{display:"block"}},e.createElement("span",{className:"text-gray-500 dark:text-gray-400",style:{display:"block",marginBottom:2,fontSize:11}},(o||{})[u]||u),e.createElement(H,{type:y,value:c[u],choices:Qt(r,a,u,c),onChange:p=>m(g,u,p,!1),onSettle:p=>m(g,u,p,!0)}))),e.createElement(x,{variant:"secondary",size:"sm",className:"mb-1.5",onClick:()=>i(d.filter((u,y)=>y!==g))},"Remove"))),e.createElement(x,{variant:"secondary",size:"sm",className:"w-full",onClick:()=>i([...d,{}])},"Add"))},pt=Rt;var te=({as:t="div",className:o,children:n,...r})=>e.createElement(t,{...r,className:N("ks-panel",o)},n),_=te;var F=(t,o)=>({className:n,children:r,...a})=>e.createElement(t,{...a,className:N(o,n)},r),I=F("label","ks-label"),rr=F("p","ks-hint"),nr=F("p","ks-error"),sr=F("span","ks-required");var ee={width:280,padding:20,overflowY:"auto"},oe=({node:t,fields:o,holds:n,labels:r,recordLabels:a,recordChoices:l,recordOutputsOf:i,choices:d,onSave:m,onDelete:c,onClose:g,onPickPicture:u,refusal:y})=>{let[p,v]=b(t.config),[k,B]=b(t.picture_url);D(()=>B(t.picture_url),[t.id,t.picture_url]),D(()=>v(t.config),[t.id,t.config]);let S=h=>{v(h),JSON.stringify(h)!==JSON.stringify(t.config)&&m(h)};return e.createElement(_,{as:"aside",style:ee,"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:g,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),y&&e.createElement("p",{className:"text-red-800 dark:text-red-400",style:{fontWeight:600,marginBottom:12},"data-refusal":!0},y),Object.entries(o).map(([h,z])=>e.createElement(I,{key:h},e.createElement("span",{style:{display:"block",marginBottom:3}},r[h]||h),z==="list"?e.createElement(pt,{holds:n[h]||{},labels:a[h]||{},rows:p[h],choices:{...(l||{})[h],...(t.record_choices||{})[h]},outputsOf:(i||{})[h],onChange:w=>v({...p,[h]:w}),onSettle:w=>S({...p,[h]:w})}):e.createElement(H,{type:z,value:p[h],choices:d[h],pictureUrl:k,onChange:w=>v({...p,[h]:w}),onSettle:w=>S({...p,[h]:w}),onPick:async w=>{let L=await u(w);L&&(B(L.url),S({...p,[h]:L.reference}))}}))),e.createElement(x,{variant:"danger",size:"sm",className:"w-full",onClick:c},"Delete step"))},ut=oe;var re={width:240,boxSizing:"border-box",padding:"10px 14px",borderRadius:8,textAlign:"center",fontSize:12,cursor:"pointer"},ne="border-2 border-dashed border-amber-500 bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300",se="border-2 border-dashed border-accent-600 bg-accent-50 text-accent-800 dark:bg-accent-950 dark:text-accent-200",ae=({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?se:ne,style:re},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=ae;var ie=({entries:t,at:o,onPick:n,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(x,{key:l.type,variant:"secondary",size:"sm",className:"w-full mb-1.5 flex-col",onClick:()=>n(l)},l.label,e.createElement("span",{className:"opacity-75",style:{display:"block",fontSize:11}},l.type))),r&&e.createElement(x,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:r},"Connect to a step already here"),e.createElement(x,{variant:"secondary",size:"sm",className:"w-full",onClick:a},"Cancel")),ft=ie;var le={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},gt=({label:t,title:o,idle:n,enabled:r,onUse:a})=>e.createElement(x,{variant:"secondary",size:"sm",title:r?o:n,disabled:!r,onClick:a,style:{opacity:r?1:.4,cursor:r?"pointer":"default"}},t),de=({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(x,{variant:"secondary",className:"rounded-full",style:le,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=de;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 ce={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})},pe=({flow:t,changes:o,problems:n,refusal:r,notice:a,onCreate:l,onPublish:i,onSaveDetails:d,onClose:m})=>e.createElement(_,{as:"aside",style:ce,"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:m,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("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(c=>e.createElement("p",{key:`${c.node}-${c.problem}`,className:"text-amber-700 dark:text-amber-400",style:j,"data-problem":!0},"\u26A0 ",ht(c))),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((c,g)=>e.createElement("p",{key:g,style:j,"data-change":!0},c)),e.createElement("h2",{style:G},"Details"),e.createElement(I,null,e.createElement("span",{style:bt},"Title"),e.createElement(T,{className:"mb-3",defaultValue:t.title??"",onBlur:vt(t,"title",d),"data-flow-title":!0})),e.createElement(I,null,e.createElement("span",{style:bt},"Start label"),e.createElement(T,{className:"mb-3",defaultValue:t.start_label??"",onBlur:vt(t,"start_label",d),"data-flow-start-label":!0})),e.createElement("div",{style:{marginTop:16}},e.createElement(x,{variant:"secondary",size:"sm",className:"w-full mb-1.5",onClick:l,"data-create-version":!0},"Create version"),e.createElement(x,{size:"sm",className:"w-full mb-1.5",onClick:i,"data-publish":!0},"Publish"),e.createElement(x,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:t.definition_url,"data-definition":!0},"Definition"),e.createElement(x,{variant:"secondary",size:"sm",className:"w-full mb-1.5",href:t.details_url,"data-details":!0},"Edit details"))),kt=pe;var wt={width:24,height:24,padding:0,fontSize:15,lineHeight:"15px"},ue=({link:t,onInsert:o,onRemove:n,onDrop:r,dragging:a})=>{let[l,i]=b(!1),d=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:m=>{m.preventDefault(),m.dataTransfer.dropEffect="move"},onDrop:m=>{m.preventDefault(),i(!1),r()}},e.createElement("div",{style:{display:"flex",gap:4,justifyContent:"center",alignItems:"center",height:"100%",opacity:d?1:0,transition:"opacity .12s"}},e.createElement(x,{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},"+"),!a&&e.createElement(x,{variant:"danger",title:"Remove this connection",onClick:n,className:"rounded-full",style:wt},"\xD7")))},Ct=ue;var me=({links:t,extent:o,dragging:n,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(Ct,{key:i.id,link:i,dragging:!!n,onInsert:()=>r(i),onRemove:()=>a(i),onDrop:()=>l(i)}))),St=me;var Nt="Your change was not saved because the server could not be reached.",zt=({base:t,token:o,fetch:n,onFlow:r,onError:a,onNotice:l})=>async(i,d,m)=>{let c=await n(t+i,{method:d,headers:{"Content-Type":"application/json","X-CSRF-Token":o},body:m&&JSON.stringify(m)}).catch(()=>null);if(!c)return l(null),a(Nt);let g=await c.json().catch(()=>({}));if(!c.ok)return l(null),a(g.error||"That change was refused");a(null),l(g.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 l=new FormData;l.append("picture",a);let i=await n(t+"/pictures",{method:"POST",headers:{"X-CSRF-Token":o},body:l}).catch(()=>null);if(!i)return r(Nt),null;let d=await i.json().catch(()=>({}));return i.ok?(r(null),d):(r(d.error||"That picture was refused"),null)};var fe=(t,o,n)=>{let[r,a]=b(n),[l,i]=b(null),[d,m]=b(null),c=$(()=>zt({base:t,token:o,fetch:(...u)=>window.fetch(...u),onFlow:a,onError:i,onNotice:m}),[t,o]),g=$(()=>Bt({base:t,token:o,fetch:(...u)=>window.fetch(...u),onError:i}),[t,o]);return{flow:r,error:l,notice:d,send:c,pickPicture:g}},Tt=fe;var W=(t,o,n,r)=>{let a=t.left-n.left+r.scrollLeft,l=t.top-n.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}},ge=(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},ye=(t,o,n,r,a)=>{let l={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:l,midX:t.route==="detour"?a:(o.x+n.x)/2,midY:t.route==="lane"?r:(o.y+n.y)/2}},he=(t,o,n,r)=>{let[a,l]=b([]),[i,d]=b({width:"100%",height:"100%"});return tt(()=>{let m=()=>{let g=o.current?.getBoundingClientRect();g&&(d({width:o.current.scrollWidth,height:o.current.scrollHeight}),l(t.edges.flatMap(u=>{let y=n.current[u.source]?.getBoundingClientRect(),p=n.current[u.target]?.getBoundingClientRect();if(!y||!p)return[];let v=W(y,u.leaves,g,o.current),k=ge(W(p,u.enters,g,o.current),u.enters),B=(W(y,"bottom",g,o.current).y+W(p,"top",g,o.current).y)/2,S=u.leaves==="left"?Math.min(v.x,k.x)-28:Math.max(v.x,k.x)+28;return[ye(u,v,k,B,S)]})))};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:i}},Et=he;var Pt=(t,o)=>{let n=new Set(o),r=t,a=2;for(;n.has(r);)r=`${t}_${a++}`;return r};var ve={display:"flex",height:"100%",minHeight:0,fontSize:13},ke={flex:1,overflow:"auto",position:"relative"},we={position:"absolute",top:56,left:16,zIndex:5,width:264,padding:12,borderRadius:8},Ce={display:"grid",rowGap:64,columnGap:0,padding:40,justifyContent:"center",position:"relative"},Se={position:"sticky",zIndex:8,top:8,margin:"8px auto 0",width:"fit-content",padding:"6px 12px",borderRadius:6,fontSize:12},Ne=({port:t,onCancel:o})=>e.createElement("div",{className:"bg-accent-100 text-accent-800 dark:bg-accent-900 dark:text-accent-100",style:Se},"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")),ze=({base:t,token:o,initial:n})=>{let{flow:r,error:a,notice:l,send:i,pickPicture:d}=Tt(t,o,n),[m,c]=b(null),[g,u]=b(null),[y,p]=b(null),[v,k]=b(null),[B,S]=b(!1),h=U({}),z=U(null);D(()=>{let s=()=>{c(null),S(!0)};return document.addEventListener("easy_flow:open-flow",s),()=>document.removeEventListener("easy_flow:open-flow",s)},[]),D(()=>{if(!B)return;let s=f=>{f.target.closest("[data-builder-panel], [data-open-panel]")||S(!1)};return document.addEventListener("click",s),()=>document.removeEventListener("click",s)},[B]),D(()=>{document.dispatchEvent(new CustomEvent("easy_flow:flow-named",{detail:r.flow?.title||r.flow?.slug}))},[r.flow?.title,r.flow?.slug]);let w=$(()=>Object.fromEntries(r.nodes.map(s=>[s.id,s])),[r.nodes]),{links:L,extent:$t}=Et(r,z,h,[r,m,w]),q=$(()=>{let s={};return r.violations.forEach(f=>{var C;return(s[C=f.node]||(s[C]=[])).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,_t=Math.max(0,...V.map(s=>s.column))+1,K=s=>{if(!y)return;let[f,C]=y;p(null),f!==s&&i("/edges","POST",{from:f,to:s,on:C||null})},E=r.nodes.find(s=>s.id===m),P=r.palette.find(s=>s.type===E?.type);return e.createElement("div",{className:"text-gray-900 dark:text-gray-100",style:ve,onMouseUp:()=>k(null),onKeyDown:s=>{s.key==="Escape"&&(c(null),p(null),u(null))},tabIndex:-1},e.createElement("div",{ref:z,className:"bg-surface-50 dark:bg-surface-950",style:ke,onClick:s=>{s.target===z.current&&(c(null),p(null),S(!1))}},y&&e.createElement(Ne,{port:y[1],onCancel:()=>p(null)}),e.createElement(yt,{undoable:r.undoable,redoable:r.redoable,onAdd:()=>u({at:{x:16,y:52}}),onUndo:()=>{c(null),i("/undo","POST")},onRedo:()=>{c(null),i("/redo","POST")}}),e.createElement(St,{links:L,extent:$t,dragging:v,onInsert:s=>u({from:s.source,to:s.target,at:{x:s.midX+30,y:s.midY}}),onRemove:s=>{c(null),i("/edges","DELETE",{from:s.source,to:s.target})},onDrop:s=>{let f=v;k(null),i("/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:we},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=>{h.current[s.id]=f},violations:q[s.id]||[],connected:[]},selected:m===s.id,armed:y&&y[0]===s.id?y[1]:null,connecting:!!y,onSelect:()=>y?K(s.id):c(s.id),onOpen:f=>window.location.assign(f),onArm:(f,C)=>{C.stopPropagation(),p([s.id,f])},onDragStart:()=>k(s.id),onDragEnd:()=>k(null)})))),e.createElement("div",{style:{...Ce,gridTemplateColumns:`repeat(${_t+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=>{h.current[s.id]=f}},dragging:!!v,onFill:()=>u({from:s.from,on:s.on,at:{x:16,y:52}}),onDrop:()=>{let f=v;k(null),f&&i("/edges","POST",{from:s.from,to:f,on:s.on})}}),!s.placeholder&&e.createElement(Y,{node:{...s,ref:f=>{h.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:y&&y[0]===s.id?y[1]:null,connecting:!!y&&y[0]!==s.id,onSelect:()=>y?K(s.id):c(s.id),onOpen:f=>window.location.assign(f),onArm:(f,C)=>{let Z=z.current.getBoundingClientRect();u({from:s.id,on:f,at:{x:C.clientX-Z.left+z.current.scrollLeft+8,y:C.clientY-Z.top+z.current.scrollTop+8}})},onDragEnd:()=>k(null),onDragStart:()=>k(s.id)})))),g&&e.createElement(ft,{entries:r.palette,at:g.at,onDismiss:()=>u(null),onConnect:g.on!==void 0?()=>{p([g.from,g.on]),u(null)}:null,onPick:s=>{let f=g;u(null),i("/steps","POST",{id:Pt(s.type,r.nodes.map(C=>C.id)),type:s.type,from:f.from,to:f.to,on:f.on})}})),B&&!E&&e.createElement(kt,{flow:r.flow||{},changes:r.changes||[],problems:r.violations,refusal:a,notice:l,onClose:()=>S(!1),onCreate:()=>i("/versions","POST"),onPublish:()=>i("/publish","POST"),onSaveDetails:s=>i("/details","PATCH",{flow:s})}),E&&e.createElement(ut,{node:E,fields:P?.fields||{},holds:P?.records||{},labels:P?.labels||{},recordLabels:P?.record_labels||{},recordChoices:P?.record_choices||{},recordOutputsOf:P?.record_outputs_of||{},choices:it(E,P),onClose:()=>c(null),onSave:s=>i(`/steps/${E.id}`,"PATCH",{config:s}),onPickPicture:d,refusal:a,onDelete:()=>{c(null),i(`/steps/${E.id}`,"DELETE")}}))},Dt=ze;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);})();
|
|
@@ -8,7 +8,10 @@ module EasyFlow
|
|
|
8
8
|
def canvas_payload(flow)
|
|
9
9
|
Canvas.new(canvas_document(flow), host: flow_host, flows: hosted_flows.order(:title), flow_path: ->(id) { manage_flow_path(hosted_flows.find_by(id: id) || id, from: flow) }, flow: flow).to_h
|
|
10
10
|
.merge("undoable" => flow.edit_history.undoable?, "redoable" => flow.edit_history.redoable?,
|
|
11
|
-
"changes" => listed_changes(flow), "flow" => flow_details(flow)
|
|
11
|
+
"changes" => listed_changes(flow), "flow" => flow_details(flow),
|
|
12
|
+
"always_show_insert_buttons" => flow_host.always_show_insert_buttons?,
|
|
13
|
+
"hide_step_type_codes" => flow_host.hide_step_type_codes?,
|
|
14
|
+
"publish_note" => flow_host.publish_note)
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def canvas_document(flow)
|
|
@@ -16,7 +19,7 @@ module EasyFlow
|
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def listed_changes(flow)
|
|
19
|
-
flow.changes_since_version.to_a
|
|
22
|
+
Change.listed(flow.changes_since_version.to_a)
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def flow_details(flow)
|
|
@@ -17,6 +17,9 @@ module EasyFlow
|
|
|
17
17
|
return redirect_to run_location(run.waiting_on) if run&.waiting_on
|
|
18
18
|
|
|
19
19
|
@answers = @progress.recorded
|
|
20
|
+
@unsaved_refusal = @guide.unsaved_refusal(@answers, times: @progress.completed_at) unless @progress.saved?
|
|
21
|
+
return render :refused if @unsaved_refusal
|
|
22
|
+
|
|
20
23
|
@question = @guide.next_step(@answers, run: run, times: @progress.completed_at)
|
|
21
24
|
@drawing = @guide.drawing_at(@answers, times: @progress.completed_at)
|
|
22
25
|
flash.now[:alert] = @refused if @refused
|
|
@@ -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)
|
|
@@ -78,7 +81,7 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
const selectedNode = flow.nodes.find((node) => node.id === selected)
|
|
81
|
-
const entryFor = flow.palette.find((entry) => entry.type === selectedNode?.type)
|
|
84
|
+
const entryFor = [ ...flow.palette, ...(flow.unoffered || []) ].find((entry) => entry.type === selectedNode?.type)
|
|
82
85
|
|
|
83
86
|
return (
|
|
84
87
|
<div className="text-gray-900 dark:text-gray-100" style={page} onMouseUp={() => setDragging(null)}
|
|
@@ -94,7 +97,7 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
94
97
|
onUndo={() => { setSelected(null); send("/undo", "POST") }}
|
|
95
98
|
onRedo={() => { setSelected(null); send("/redo", "POST") }} />
|
|
96
99
|
|
|
97
|
-
<ConnectorLayer links={links} extent={extent} dragging={dragging}
|
|
100
|
+
<ConnectorLayer links={links} extent={extent} dragging={dragging} alwaysShowInsertButtons={flow.always_show_insert_buttons}
|
|
98
101
|
onInsert={(link) => setAdding({ from: link.source, to: link.target, at: { x: link.midX + 30, y: link.midY } })}
|
|
99
102
|
onRemove={(link) => { setSelected(null); send("/edges", "DELETE", { from: link.source, to: link.target }) }}
|
|
100
103
|
onDrop={(link) => { const held = dragging; setDragging(null); send("/steps/" + held + "/move", "PATCH", { from: link.source, to: link.target }) }} />
|
|
@@ -118,7 +121,8 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
118
121
|
onOpen={(path) => window.location.assign(path)}
|
|
119
122
|
onArm={(port, event) => { event.stopPropagation(); setArmed([ node.id, port ]) }}
|
|
120
123
|
onDragStart={() => setDragging(node.id)}
|
|
121
|
-
onDragEnd={() => setDragging(null)}
|
|
124
|
+
onDragEnd={() => setDragging(null)}
|
|
125
|
+
hideStepTypeCodes={flow.hide_step_type_codes} />
|
|
122
126
|
</div>
|
|
123
127
|
))}
|
|
124
128
|
</div>
|
|
@@ -159,13 +163,14 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
159
163
|
}}
|
|
160
164
|
onDragEnd={() => setDragging(null)}
|
|
161
165
|
onDragStart={() => setDragging(node.id)}
|
|
166
|
+
hideStepTypeCodes={flow.hide_step_type_codes}
|
|
162
167
|
/>}
|
|
163
168
|
</div>
|
|
164
169
|
))}
|
|
165
170
|
</div>
|
|
166
171
|
|
|
167
172
|
{adding && (
|
|
168
|
-
<TypePicker entries={flow.palette} at={adding.at} onDismiss={() => setAdding(null)}
|
|
173
|
+
<TypePicker entries={flow.palette} at={adding.at} onDismiss={() => setAdding(null)} hideStepTypeCodes={flow.hide_step_type_codes}
|
|
169
174
|
onConnect={adding.on !== undefined ? () => { setArmed([ adding.from, adding.on ]); setAdding(null) } : null}
|
|
170
175
|
onPick={(entry) => {
|
|
171
176
|
const where = adding
|
|
@@ -176,10 +181,12 @@ const Canvas = ({ base, token, initial }) => {
|
|
|
176
181
|
</div>
|
|
177
182
|
|
|
178
183
|
{showing && !selectedNode && (
|
|
179
|
-
<Panel flow={flow.flow || {}} changes={flow.changes || []} problems={flow.violations} refusal={error} notice={notice}
|
|
184
|
+
<Panel flow={flow.flow || {}} changes={flow.changes || []} problems={flow.violations} refusal={error} notice={notice} publishNote={flow.publish_note} warning={warning}
|
|
180
185
|
onClose={() => setShowing(false)}
|
|
181
186
|
onCreate={() => send("/versions", "POST")}
|
|
182
187
|
onPublish={() => send("/publish", "POST")}
|
|
188
|
+
onAccept={() => send(warning.path, "POST", { remove_unconnected: warning.steps.map((step) => step.id) })}
|
|
189
|
+
onDecline={() => setWarning(null)}
|
|
183
190
|
onSaveDetails={(details) => send("/details", "PATCH", { flow: details })} />
|
|
184
191
|
)}
|
|
185
192
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
2
|
import Button from "keystone_ui-react/src/Button.jsx"
|
|
3
3
|
const round = { width: 24, height: 24, padding: 0, fontSize: 15, lineHeight: "15px" }
|
|
4
|
+
const shownWhen = (shown) => ({ opacity: shown ? 1 : 0, transition: "opacity .12s" })
|
|
4
5
|
|
|
5
|
-
const Connector = ({ link, onInsert, onRemove, onDrop, dragging }) => {
|
|
6
|
+
const Connector = ({ link, onInsert, onRemove, onDrop, dragging, alwaysShowInsertButtons }) => {
|
|
6
7
|
const [ over, setOver ] = useState(false)
|
|
7
8
|
const showing = over || dragging
|
|
8
9
|
|
|
@@ -13,14 +14,13 @@ const Connector = ({ link, onInsert, onRemove, onDrop, dragging }) => {
|
|
|
13
14
|
onDragEnter={() => setOver(true)} onDragLeave={() => setOver(false)}
|
|
14
15
|
onDragOver={(event) => { event.preventDefault(); event.dataTransfer.dropEffect = "move" }}
|
|
15
16
|
onDrop={(event) => { event.preventDefault(); setOver(false); onDrop() }}>
|
|
16
|
-
<div style={{ display: "flex", gap: 4, justifyContent: "center", alignItems: "center", height: "100%"
|
|
17
|
-
opacity: showing ? 1 : 0, transition: "opacity .12s" }}>
|
|
17
|
+
<div style={{ display: "flex", gap: 4, justifyContent: "center", alignItems: "center", height: "100%" }}>
|
|
18
18
|
<Button variant="secondary" title={dragging ? "Move the step here" : "Insert a step here"} onClick={onInsert}
|
|
19
19
|
className="rounded-full data-[over=true]:ring-2 data-[over=true]:ring-accent-600"
|
|
20
|
-
data-over={Boolean(dragging && over)} style={round}>+</Button>
|
|
20
|
+
data-over={Boolean(dragging && over)} style={{ ...round, ...shownWhen(showing || alwaysShowInsertButtons) }}>+</Button>
|
|
21
21
|
{!dragging && (
|
|
22
22
|
<Button variant="danger" title="Remove this connection" onClick={onRemove}
|
|
23
|
-
className="rounded-full" style={round}>×</Button>
|
|
23
|
+
className="rounded-full" style={{ ...round, ...shownWhen(showing) }}>×</Button>
|
|
24
24
|
)}
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import Connector from "./Connector"
|
|
3
3
|
|
|
4
|
-
const ConnectorLayer = ({ links, extent, dragging, onInsert, onRemove, onDrop }) => (
|
|
4
|
+
const ConnectorLayer = ({ links, extent, dragging, onInsert, onRemove, onDrop, alwaysShowInsertButtons }) => (
|
|
5
5
|
<>
|
|
6
6
|
<svg width={extent.width} height={extent.height}
|
|
7
7
|
style={{ position: "absolute", top: 0, left: 0, pointerEvents: "none", zIndex: 1 }}>
|
|
@@ -17,12 +17,12 @@ const ConnectorLayer = ({ links, extent, dragging, onInsert, onRemove, onDrop })
|
|
|
17
17
|
|
|
18
18
|
{links.filter((link) => link.label).map((link) => (
|
|
19
19
|
<div key={`${link.id}-label`} data-link-label={link.id} className="bg-surface-50 text-gray-500 dark:bg-surface-950 dark:text-gray-400"
|
|
20
|
-
style={{ position: "absolute", left: link.midX -
|
|
20
|
+
style={{ position: "absolute", left: link.midX + (alwaysShowInsertButtons ? 2 : -10), top: link.midY - 18, zIndex: 3,
|
|
21
21
|
fontSize: 11, padding: "0 3px" }}>{link.label}</div>
|
|
22
22
|
))}
|
|
23
23
|
|
|
24
24
|
{links.filter((link) => !link.placeholder).map((link) => (
|
|
25
|
-
<Connector key={link.id} link={link} dragging={Boolean(dragging)}
|
|
25
|
+
<Connector key={link.id} link={link} dragging={Boolean(dragging)} alwaysShowInsertButtons={alwaysShowInsertButtons}
|
|
26
26
|
onInsert={() => onInsert(link)} onRemove={() => onRemove(link)} onDrop={() => onDrop(link)} />
|
|
27
27
|
))}
|
|
28
28
|
</>
|
|
@@ -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, 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,6 +25,22 @@ const Panel = ({ flow, changes, problems, refusal, notice, onCreate, onPublish,
|
|
|
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
|
|
|
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
|
+
)}
|
|
43
|
+
|
|
27
44
|
<h2 style={heading}>Problems</h2>
|
|
28
45
|
{refusal && <p className="text-red-800 dark:text-red-400" style={{ ...item, fontWeight: 600 }} data-refusal>{refusal}</p>}
|
|
29
46
|
{notice && !refusal && <p className="text-emerald-800 dark:text-emerald-400" style={{ ...item, fontWeight: 600 }} data-notice>{notice}</p>}
|
|
@@ -50,8 +67,6 @@ const Panel = ({ flow, changes, problems, refusal, notice, onCreate, onPublish,
|
|
|
50
67
|
</Label>
|
|
51
68
|
|
|
52
69
|
<div style={{ marginTop: 16 }}>
|
|
53
|
-
<Button variant="secondary" size="sm" className="w-full mb-1.5" onClick={onCreate} data-create-version>Create version</Button>
|
|
54
|
-
<Button size="sm" className="w-full mb-1.5" onClick={onPublish} data-publish>Publish</Button>
|
|
55
70
|
<Button variant="secondary" size="sm" className="w-full mb-1.5" href={flow.definition_url} data-definition>Definition</Button>
|
|
56
71
|
<Button variant="secondary" size="sm" className="w-full mb-1.5" href={flow.details_url} data-details>Edit details</Button>
|
|
57
72
|
</div>
|
|
@@ -16,11 +16,21 @@ const edge = (target, troubled, selected) => {
|
|
|
16
16
|
return "border-gray-300 shadow-sm dark:border-zinc-700"
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const headingOf = (node, hideStepTypeCodes) =>
|
|
20
|
+
hideStepTypeCodes && node.label === node.id ? node.type_name || node.label : node.label
|
|
21
|
+
|
|
22
|
+
const typeLineOf = (node, heading, hideStepTypeCodes) => {
|
|
23
|
+
if (!hideStepTypeCodes) return node.type
|
|
24
|
+
return heading === node.type_name ? null : node.type_name
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const StepCard = ({ node, selected, armed, connecting, onSelect, onOpen, onArm, onDragEnd, onDragStart, hideStepTypeCodes }) => {
|
|
20
28
|
const bookend = node.begins_here || node.ends_here
|
|
21
29
|
const pinned = node.begins_here
|
|
22
30
|
const ports = node.ends_here ? [] : node.ports
|
|
23
31
|
const target = connecting && !pinned
|
|
32
|
+
const heading = headingOf(node, hideStepTypeCodes)
|
|
33
|
+
const typeLine = typeLineOf(node, heading, hideStepTypeCodes)
|
|
24
34
|
|
|
25
35
|
return (
|
|
26
36
|
<div ref={node.ref}
|
|
@@ -35,9 +45,10 @@ const StepCard = ({ node, selected, armed, connecting, onSelect, onOpen, onArm,
|
|
|
35
45
|
padding: bookend ? 0 : "12px 14px",
|
|
36
46
|
cursor: pinned ? "default" : connecting ? "crosshair" : "grab"
|
|
37
47
|
}}>
|
|
38
|
-
{bookend && <div className="text-gray-500 dark:text-gray-400" style={bookendCard}>{
|
|
39
|
-
{!bookend && <div style={named}>{
|
|
40
|
-
{!bookend && <div className="text-gray-500 dark:text-gray-400" style={{ fontSize: 11, marginTop: 2 }}>{
|
|
48
|
+
{bookend && <div className="text-gray-500 dark:text-gray-400" style={bookendCard}>{heading}</div>}
|
|
49
|
+
{!bookend && <div style={named}>{heading}</div>}
|
|
50
|
+
{!bookend && typeLine && <div className="text-gray-500 dark:text-gray-400" style={{ fontSize: 11, marginTop: 2 }}>{typeLine}</div>}
|
|
51
|
+
{node.picture_url && <div className="text-accent-600 dark:text-accent-400" style={{ fontSize: 11, marginTop: 2 }}>Has a picture</div>}
|
|
41
52
|
{node.starts_a_flow && <div className="text-accent-600 dark:text-accent-400" style={{ fontSize: 11, marginTop: 2 }}>Runs a flow</div>}
|
|
42
53
|
{node.opens && <Button variant="secondary" size="sm" className="mt-2" onClick={(event) => { event.stopPropagation(); onSelect() }}>Settings</Button>}
|
|
43
54
|
{node.violations.map((violation) => (
|
|
@@ -2,13 +2,13 @@ import React from "react"
|
|
|
2
2
|
import Panel from "keystone_ui-react/src/Panel.jsx"
|
|
3
3
|
import Button from "keystone_ui-react/src/Button.jsx"
|
|
4
4
|
|
|
5
|
-
const TypePicker = ({ entries, at, onPick, onConnect, onDismiss }) => (
|
|
5
|
+
const TypePicker = ({ entries, at, onPick, onConnect, onDismiss, hideStepTypeCodes }) => (
|
|
6
6
|
<Panel className="shadow-lg" style={{ position: "absolute", zIndex: 9, top: at.y, left: at.x, width: 210, padding: 8, borderRadius: 8 }}>
|
|
7
7
|
<p className="text-gray-500 dark:text-gray-400" style={{ margin: "0 0 6px", fontSize: 11 }}>Add a step</p>
|
|
8
8
|
{entries.map((entry) => (
|
|
9
9
|
<Button key={entry.type} variant="secondary" size="sm" className="w-full mb-1.5 flex-col" onClick={() => onPick(entry)}>
|
|
10
10
|
{entry.label}
|
|
11
|
-
<span className="opacity-75" style={{ display: "block", fontSize: 11 }}>{entry.type}</span>
|
|
11
|
+
{!hideStepTypeCodes && <span className="opacity-75" style={{ display: "block", fontSize: 11 }}>{entry.type}</span>}
|
|
12
12
|
</Button>
|
|
13
13
|
))}
|
|
14
14
|
{onConnect && (
|
|
@@ -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
|
|
@@ -10,7 +10,7 @@ module EasyFlow
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def to_h
|
|
13
|
-
{ "nodes" => nodes, "edges" => edges, "palette" => palette, "violations" => violations }
|
|
13
|
+
{ "nodes" => nodes, "edges" => edges, "palette" => palette, "unoffered" => unoffered, "violations" => violations }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
private
|
|
@@ -20,7 +20,7 @@ module EasyFlow
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def drawn_step(node)
|
|
23
|
-
{ "id" => node.id, "type" => node.type, "label" => label_for(node),
|
|
23
|
+
{ "id" => node.id, "type" => node.type, "type_name" => step_type_for(node)&.step_name, "label" => label_for(node),
|
|
24
24
|
"config" => node.config.merge(kept_values(node)), "ports" => [], "ends_here" => ends_here?(node), "begins_here" => begins_here?(node),
|
|
25
25
|
"starts_a_flow" => starts_a_flow?(node), "opens" => opens(node), "loose" => loose?(node), "choices" => choices_for(node), "record_choices" => record_choices_for(node), "placeholder" => false, "picture_url" => picture_url(node), **placed[node.id] }
|
|
26
26
|
end
|
|
@@ -89,17 +89,27 @@ module EasyFlow
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def palette
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
offered_step_types.map { |step_type| palette_entry(step_type) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def unoffered
|
|
96
|
+
(@document.nodes.filter_map { |node| step_type_for(node) }.uniq.reject(&:begins_here?) - offered_step_types).map { |step_type| palette_entry(step_type) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def offered_step_types
|
|
100
|
+
@registry.step_types.reject(&:begins_here?).select { |step_type| @host.nil? || step_type.ends_here? || @host.offers?(step_type.id) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def palette_entry(step_type)
|
|
104
|
+
{ "type" => step_type.id.to_s, "label" => step_type.step_name,
|
|
105
|
+
"fields" => step_type.settings.fields.transform_keys(&:to_s).transform_values(&:to_s),
|
|
106
|
+
"labels" => step_type.settings.labels.transform_keys(&:to_s),
|
|
107
|
+
"choices" => step_type.settings.choices.transform_keys(&:to_s),
|
|
108
|
+
"records" => holdings_of(step_type),
|
|
109
|
+
"record_choices" => step_type.settings.record_choices.to_h { |name, offered| [ name.to_s, offered.transform_keys(&:to_s) ] },
|
|
110
|
+
"record_outputs_of" => step_type.settings.record_outputs_of.to_h { |name, sources| [ name.to_s, sources.to_h { |field, source| [ field.to_s, source.to_s ] } ] },
|
|
111
|
+
"record_labels" => step_type.settings.record_labels.to_h { |name, held| [ name.to_s, held.transform_keys(&:to_s) ] },
|
|
112
|
+
"awaits_input" => step_type.awaits_input? }
|
|
103
113
|
end
|
|
104
114
|
|
|
105
115
|
def holdings_of(step_type)
|