@cesarechazu/directus-extension-run-flow-refresh 1.0.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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/index.js +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cesar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Run Flow + Refresh
|
|
2
|
+
|
|
3
|
+
Run a Directus manual flow for the current item and optionally refresh the current form, reload the page, or navigate back when the flow finishes.
|
|
4
|
+
|
|
5
|
+
This interface is designed for alias fields and works well in collection item views where a user needs a single action button to launch a manual flow and then refresh the UI.
|
|
6
|
+
|
|
7
|
+
## Interface
|
|
8
|
+
|
|
9
|
+
<img src="https://raw.githubusercontent.com/cesarechazu/directus-extension-run-flow-refresh/main/img/interface.png" alt="Run Flow + Refresh interface" width="720">
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Runs a manual Directus flow for the current item
|
|
14
|
+
- Passes the current collection and primary key to the flow trigger
|
|
15
|
+
- Optional confirmation dialog before execution
|
|
16
|
+
- Optional result dialog after completion
|
|
17
|
+
- Configurable refresh delay
|
|
18
|
+
- Supports form refresh, page reload, back navigation, or no reload after execution
|
|
19
|
+
- Works as an `alias` field, so it does not create a database column
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Use this interface on an `alias` field inside an item form when you want to expose a manual action button to the user.
|
|
24
|
+
|
|
25
|
+
Typical flow:
|
|
26
|
+
|
|
27
|
+
- The user opens an existing item.
|
|
28
|
+
- The user clicks the button to trigger a Directus manual flow for that record.
|
|
29
|
+
- The flow receives the current collection and primary key.
|
|
30
|
+
- When the flow finishes, the interface can refresh the current form, reload the page, go back, or do nothing.
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
- `flowId`: manual flow UUID to execute
|
|
35
|
+
- `buttonLabel`: visible button text
|
|
36
|
+
- `buttonIcon`: Directus icon for the action button
|
|
37
|
+
- `refreshType`: controls what happens after the flow finishes
|
|
38
|
+
- `form`: refresh the current item form
|
|
39
|
+
- `full`: reload the page
|
|
40
|
+
- `back`: return to the previous page, with page reload fallback when there is no useful history entry
|
|
41
|
+
- `none`: keep the current page unchanged
|
|
42
|
+
- `refreshDelay`: delay in milliseconds before applying the selected refresh behavior
|
|
43
|
+
- `confirmTitle`: confirmation dialog title
|
|
44
|
+
- `confirmMessage`: confirmation dialog message
|
|
45
|
+
- `confirmCancelLabel`: cancel button label
|
|
46
|
+
- `confirmContinueLabel`: continue button label
|
|
47
|
+
- `resultDialogEnabled`: show a result dialog after success
|
|
48
|
+
- `resultTitle`: result dialog title
|
|
49
|
+
- `resultMessage`: result dialog message
|
|
50
|
+
- `resultCloseLabel`: result dialog close button label
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
|
|
54
|
+
- The selected flow must use the Directus `manual` trigger type.
|
|
55
|
+
- New items must be saved before the flow can run.
|
|
56
|
+
- If no flow completion event is detected, the interface falls back to a delayed completion handling path.
|
|
57
|
+
- `Reload Form` uses the same refresh pattern as the native Directus flow sidebar. If the form refresh context is not available, it falls back to a page reload.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useApi as e,defineInterface as t}from"@directus/extensions-sdk";import{inject as n,ref as a,computed as i,onBeforeUnmount as l,resolveComponent as o,openBlock as r,createElementBlock as s,createElementVNode as c,createVNode as f,withCtx as u,createBlock as d,createCommentVNode as p,toDisplayString as m,createTextVNode as g,withModifiers as w}from"vue";var h=[],y=[];!function(e,t){if(e&&"undefined"!=typeof document){var n,a=!0===t.prepend?"prepend":"append",i=!0===t.singleTag,l="string"==typeof t.container?document.querySelector(t.container):document.getElementsByTagName("head")[0];if(i){var o=h.indexOf(l);-1===o&&(o=h.push(l)-1,y[o]={}),n=y[o]&&y[o][a]?y[o][a]:y[o][a]=r()}else n=r();65279===e.charCodeAt(0)&&(e=e.substring(1)),n.styleSheet?n.styleSheet.cssText+=e:n.appendChild(document.createTextNode(e))}function r(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),t.attributes)for(var n=Object.keys(t.attributes),i=0;i<n.length;i++)e.setAttribute(n[i],t.attributes[n[i]]);var o="prepend"===a?"afterbegin":"beforeend";return l.insertAdjacentElement(o,e),e}}("\n.manual-flow-trigger-interface[data-v-99cf9e51] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.flow-executor[data-v-99cf9e51] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.action-button[data-v-99cf9e51] {\n align-self: flex-start;\n display: inline-flex;\n width: fit-content !important;\n min-width: max-content !important;\n max-width: none !important;\n --v-button-width: auto;\n --v-button-min-width: max-content;\n white-space: nowrap;\n overflow: visible;\n}\n.action-button[data-v-99cf9e51] .v-button__content {\n width: auto !important;\n max-width: none !important;\n overflow: visible;\n white-space: nowrap;\n}\n.button-content[data-v-99cf9e51] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.button-icon[data-v-99cf9e51] {\n flex: 0 0 auto;\n}\n.notice-spacing[data-v-99cf9e51] {\n margin-top: 8px;\n}\n.confirm-backdrop[data-v-99cf9e51] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999;\n padding: 16px;\n}\n.confirm-panel[data-v-99cf9e51] {\n width: min(520px, 100%);\n background: var(--theme--background, #ffffff);\n color: var(--theme--foreground, #1b1b1b);\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2);\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.confirm-title[data-v-99cf9e51] {\n font-size: 16px;\n font-weight: 600;\n}\n.confirm-message[data-v-99cf9e51] {\n opacity: 0.9;\n}\n.confirm-actions[data-v-99cf9e51] {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 4px;\n}\n",{});var v=(e,t)=>{const n=e.__vccOpts||e;for(const[e,a]of t)n[e]=a;return n};const x={emits:["refresh"],props:{value:{type:String,default:null},flowId:{type:String,default:null},buttonLabel:{type:String,default:"Run Flow"},buttonIcon:{type:String,default:"play_arrow"},refreshType:{type:String,default:"full",validator:e=>["form","full","back","none"].includes(e)},refreshDelay:{type:Number,default:1e3},confirmTitle:{type:String,default:"Confirm Action"},confirmMessage:{type:String,default:"Do you want to run this flow?"},confirmCancelLabel:{type:String,default:"Cancel"},confirmContinueLabel:{type:String,default:"Run Flow"},resultDialogEnabled:{type:Boolean,default:!1},resultTitle:{type:String,default:"Done"},resultMessage:{type:String,default:"The flow finished successfully."},resultCloseLabel:{type:String,default:"Close"},collection:{type:String,required:!0},primaryKey:{type:[String,Number],default:"+"}},setup(t,{emit:o,attrs:r}){const s=e(),c=n("notify",null),f=n("refresh",null),u=n("values",null),d=a(!1),p=a(null),m=a(null),g=a(!1),w=a(!1),h=i(()=>Boolean(t.flowId)),y=i(()=>{const e=Number(t.refreshDelay);return!Number.isFinite(e)||e<0?0:Math.round(e)});function v(e){"function"==typeof c&&c(e)}async function x(){const e=t.flowId;if(e)if("+"!==t.primaryKey){d.value=!0,p.value=null;try{const n=(await s.get(`/flows/${e}`)).data.data;if(!n)throw new Error("Flow not found");if("manual"!==n.trigger)throw new Error("Flow is not a manual trigger type");const a={collection:t.collection,keys:[t.primaryKey]};await s.post(`/flows/trigger/${e}`,a),function(){const e=e=>{const n=t.flowId;e.detail?.flow!==n&&e.detail?.flow||(C(),a())};window.addEventListener("directus:flow:complete",e),window.addEventListener("flow:complete",e),m.value=e;const n=setTimeout(()=>{C(),a()},5e3);function a(){window.removeEventListener("directus:flow:complete",e),window.removeEventListener("flow:complete",e),clearTimeout(n),m.value=null}}()}catch(e){console.error("Error executing flow:",e),p.value=e.response?.data?.errors?.[0]?.message||e.message||"Error executing flow",v({title:"Error",text:p.value,type:"error"}),d.value=!1,g.value=!1}}else v({title:"Save Item First",text:"Please save this item before executing the flow",type:"warning"});else p.value="Flow is not configured"}async function b(){if("none"!==t.refreshType){if("form"===t.refreshType){const e=await async function(){if(!t.collection||"+"===t.primaryKey)return!1;const e="function"==typeof r.onSetFieldValue?r.onSetFieldValue:null;try{const n=await s.get(`/items/${t.collection}/${t.primaryKey}`),a=n?.data?.data;if(!a||"object"!=typeof a)return!1;let i=!1;for(const[t,n]of Object.entries(a))e?(e(t,n),i=!0):u&&"object"==typeof u&&(u[t]=n,i=!0);return i}catch(e){return console.warn("Form reload request failed, falling back to page reload.",e),!1}}();if(!e&&"function"==typeof f)try{return void f()}catch(e){console.warn("Injected form refresh failed, falling back to page reload.",e)}try{o("refresh")}catch{}if(e)return}"back"===t.refreshType&&window.history.length>1?window.history.back():window.location.reload()}}function C(){d.value=!1,g.value=!1,setTimeout(async()=>{t.resultDialogEnabled?w.value=!0:"none"!==t.refreshType?await b():v({title:"Success",text:"Flow executed successfully",type:"success"})},y.value)}return l(()=>{m.value&&(window.removeEventListener("directus:flow:complete",m.value),window.removeEventListener("flow:complete",m.value))}),{loading:d,errorMessage:p,executeFlow:x,canExecute:h,confirmOpen:g,resultOpen:w,openConfirm:function(){h.value&&(g.value=!0)},closeConfirm:function(){d.value||(g.value=!1)},confirmAndExecute:async function(){d.value||await x()},closeResult:async function(){w.value=!1,await b()}}}},b={class:"manual-flow-trigger-interface"},C={class:"flow-executor"},k={class:"button-content"},_={class:"confirm-panel"},L={class:"confirm-title"},T={key:0,class:"confirm-message"},S={class:"confirm-actions"},E={class:"confirm-panel"},D={class:"confirm-title"},F={key:0,class:"confirm-message"},R={class:"confirm-actions"};var M=t({id:"run-flow-refresh",name:"Run Flow + Refresh",icon:"play_arrow",description:"Run a manual flow for the current item and optionally refresh the current form or page when it finishes",component:v(x,[["render",function(e,t,n,a,i,l){const h=o("v-icon"),y=o("v-button"),v=o("v-notice");return r(),s("div",b,[c("div",C,[f(y,{loading:a.loading,disabled:a.loading||!a.canExecute,onClick:a.openConfirm,class:"action-button"},{default:u(()=>[c("span",k,[n.buttonIcon?(r(),d(h,{key:0,name:n.buttonIcon,class:"button-icon"},null,8,["name"])):p("v-if",!0),c("span",null,m(n.buttonLabel),1)])]),_:1},8,["loading","disabled","onClick"]),a.errorMessage?(r(),d(v,{key:0,type:"danger",onClose:t[0]||(t[0]=e=>a.errorMessage=null),class:"notice-spacing"},{default:u(()=>[g(m(a.errorMessage),1)]),_:1})):p("v-if",!0)]),a.confirmOpen?(r(),s("div",{key:0,class:"confirm-backdrop",role:"dialog","aria-modal":"true",onClick:t[1]||(t[1]=w((...e)=>a.closeConfirm&&a.closeConfirm(...e),["self"]))},[c("div",_,[c("div",L,m(n.confirmTitle),1),n.confirmMessage?(r(),s("div",T,m(n.confirmMessage),1)):p("v-if",!0),c("div",S,[f(y,{secondary:"",disabled:a.loading,onClick:a.closeConfirm},{default:u(()=>[g(m(n.confirmCancelLabel),1)]),_:1},8,["disabled","onClick"]),f(y,{loading:a.loading,disabled:a.loading,onClick:a.confirmAndExecute},{default:u(()=>[g(m(n.confirmContinueLabel),1)]),_:1},8,["loading","disabled","onClick"])])])])):p("v-if",!0),a.resultOpen?(r(),s("div",{key:1,class:"confirm-backdrop",role:"dialog","aria-modal":"true",onClick:t[2]||(t[2]=w((...e)=>a.closeResult&&a.closeResult(...e),["self"]))},[c("div",E,[c("div",D,m(n.resultTitle),1),n.resultMessage?(r(),s("div",F,m(n.resultMessage),1)):p("v-if",!0),c("div",R,[f(y,{onClick:a.closeResult},{default:u(()=>[g(m(n.resultCloseLabel),1)]),_:1},8,["onClick"])])])])):p("v-if",!0)])}],["__scopeId","data-v-99cf9e51"],["__file","interface.vue"]]),hideLabel:!0,options:[{field:"flowId",name:"Flow",type:"string",meta:{interface:"input",width:"full",required:!0,note:"Enter the manual Flow ID (UUID). You can find it in the URL when editing the flow in Settings > Flows.",options:{placeholder:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}},{field:"buttonLabel",name:"Button Label",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Run Flow"}},{field:"buttonIcon",name:"Button Icon",type:"string",meta:{interface:"select-icon",width:"half"},schema:{default_value:"play_arrow"}},{field:"refreshType",name:"Refresh Mode",type:"string",meta:{interface:"select-dropdown",options:{choices:[{text:"Reload Form",value:"form"},{text:"Reload Page",value:"full"},{text:"Go Back",value:"back"},{text:"No Reload",value:"none"}]},width:"half"},schema:{default_value:"full"}},{field:"refreshDelay",name:"Refresh Delay (ms)",type:"integer",meta:{interface:"input",width:"half",note:"Delay in milliseconds before applying the selected refresh behavior."},schema:{default_value:1e3}},{field:"confirmDivider",name:"Confirmation",type:"alias",meta:{interface:"presentation-divider",options:{title:"Confirmation",icon:"help"},width:"full"}},{field:"confirmTitle",name:"Title",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Confirm Action"}},{field:"confirmMessage",name:"Message",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Do you want to run this flow?"}},{field:"confirmCancelLabel",name:"Cancel Button Label",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Cancel"}},{field:"confirmContinueLabel",name:"Continue Button Label",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Run Flow"}},{field:"resultDivider",name:"Result Dialog",type:"alias",meta:{interface:"presentation-divider",options:{title:"Result Dialog",icon:"check"},width:"full"}},{field:"resultDialogEnabled",name:"Show Dialog",type:"boolean",meta:{interface:"boolean",width:"half"},schema:{default_value:!1}},{field:"resultCloseLabel",name:"Close Button Label",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Close"}},{field:"resultTitle",name:"Title",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"Done"}},{field:"resultMessage",name:"Message",type:"string",meta:{interface:"input",width:"half"},schema:{default_value:"The flow finished successfully."}}],types:["alias"],localTypes:["presentation"],group:"presentation"});export{M as default};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cesarechazu/directus-extension-run-flow-refresh",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interface to run a manual flow for the current item and optionally refresh the current form, reload the page, or navigate back after completion",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "cesar",
|
|
9
|
+
"email": "cesarechazu@gmail.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/cesarechazu/directus-extension-run-flow-refresh.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/cesarechazu/directus-extension-run-flow-refresh/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/cesarechazu/directus-extension-run-flow-refresh#readme",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"directus",
|
|
21
|
+
"directus-extension",
|
|
22
|
+
"directus-extension-interface",
|
|
23
|
+
"flow",
|
|
24
|
+
"manual-flow",
|
|
25
|
+
"refresh"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"directus:extension": {
|
|
33
|
+
"type": "interface",
|
|
34
|
+
"path": "dist/index.js",
|
|
35
|
+
"source": "src/index.js",
|
|
36
|
+
"host": "^11.0.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "directus-extension build",
|
|
40
|
+
"dev": "directus-extension build --watch",
|
|
41
|
+
"validate": "directus-extension validate"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@directus/extensions-sdk": "^11.0.0",
|
|
45
|
+
"vue": "^3.3.0"
|
|
46
|
+
}
|
|
47
|
+
}
|