directus-extension-flow-manager 1.0.0 → 1.1.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/README.md +17 -0
- package/dist/index.js +1 -0
- package/package.json +6 -3
- package/screenshoots/image.png +0 -0
- package/src/index.ts +1 -1
- package/src/module.vue +101 -4
package/README.md
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
1
|
# directus-extension-flow-manager
|
|
2
|
+
This directus module extension allows you to manage your flow content from directus.
|
|
3
|
+
|
|
4
|
+
You can install it via ``npm install directus-extension-flow-manager``
|
|
5
|
+
|
|
6
|
+
- [x] Duplicate flow
|
|
7
|
+
- [x] Export and import flow
|
|
8
|
+
- [ ] Add flow validation when Restore
|
|
9
|
+
|
|
10
|
+
Screenshoots
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
Changelogs:
|
|
14
|
+
- 1.0.0: (13 July 2023)
|
|
15
|
+
* Initial release
|
|
16
|
+
- 1.1.0: (21 July 2023)
|
|
17
|
+
* Add Backup and Restore feature
|
|
18
|
+
* Allow user to click the flow row and bring to flow detail page
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useStores as e,useApi as t,defineModule as o}from"@directus/extensions-sdk";import{defineComponent as n,ref as i,unref as a,resolveComponent as l,resolveDirective as r,openBlock as s,createBlock as c,withCtx as u,withDirectives as d,createVNode as p,createCommentVNode as f,normalizeClass as v,createTextVNode as m,toDisplayString as g,createElementVNode as y}from"vue";import{useRouter as w}from"vue-router";var h=n({setup(){const{useFlowsStore:o,useNotificationsStore:n}=e(),l=o(),r=n(),s=t(),c=w(),u=i(l.flows),d=i(null);return{headers:i([{text:"",value:"icon",width:50,sortable:!1},{text:"Status",value:"status"},{text:"Name",value:"name",width:400}]),flows:u,restoredFile:d,duplicate:p,backup:async function(e){const t={name:e.name,icon:e.icon,color:e.color,description:e.description,trigger:e.trigger,options:e.options,operation:e.operation,operations:e.operations.map((e=>({id:e.id,name:e.name,key:e.key,type:e.type,position_x:e.position_x,position_y:e.position_y,options:e.options,resolve:e.resolve,reject:e.reject})))},o=new Blob([JSON.stringify(t,null,2)],{type:"application/json"});var n=window.URL.createObjectURL(o),i=document.createElement("a");i.href=n,i.setAttribute("download",`${function(){const e=new Date,t=e.getFullYear(),o=(e.getMonth()+1).toString().padStart(2,"0"),n=e.getDate().toString().padStart(2,"0"),i=e.getHours().toString().padStart(2,"0"),a=e.getMinutes().toString().padStart(2,"0"),l=e.getSeconds().toString().padStart(2,"0");return`${t}${o}${n}${i}${a}${l}`}()}-${e.name}.json`),document.body.appendChild(i),i.click()},onRestoredFileChanged:function(e){var t;const o=null==(t=null==e?void 0:e.target)?void 0:t.files[0];if(!o)return;const n=new FileReader;n.onload=async e=>{var t;try{const o=null==(t=e.target)?void 0:t.result,n=JSON.parse(o);console.log({parsedResult:n}),await p(n,!1)}catch(e){console.log(e)}},n.readAsText(o)},onRestoreButtonClicked:function(){var e;console.log("restore button clicked"),null==(e=d.value)||e.click()},goToFlow:function({item:e}){console.log("go to flow",e),c.push(`/settings/flows/${e.id}`)}};async function p(e,t=!0){try{let o=function(t){const o={};function i(e){return t.find((t=>t.id===e))}function a(e){if(!e)return null;return{name:e.name,position_x:e.position_x,position_y:e.position_y,key:e.key,type:e.type,options:e.options,flow:n.data.data.id,resolve:a(i(e.resolve)),reject:a(i(e.reject))}}const l=i(e.operation);return o.name=null==l?void 0:l.name,o.position_x=null==l?void 0:l.position_x,o.position_y=null==l?void 0:l.position_y,o.key=null==l?void 0:l.key,o.type=null==l?void 0:l.type,o.options=null==l?void 0:l.options,o.flow=n.data.data.id,o.resolve=a(i((null==l?void 0:l.resolve)||null)),o.reject=a(i((null==l?void 0:l.reject)||null)),o};const n=await s.post("/flows",{name:t?`${e.name} - Copy`:e.name,status:"inactive",icon:e.icon,accountability:e.accountability,description:e.description,trigger:e.trigger,options:e.options}),i=o(e.operations);await s.patch(`/flows/${n.data.data.id}`,{operation:e.operation?i:null}),await l.hydrate(),u.value=a(l.flows),r.add({type:"success",title:t?"Flow Duplicated successfully":`Flow ${e.name} restored successfully`,closeable:!0,persist:!0})}catch(e){console.log(e)}}}});var b=[],k=[];!function(e,t){if(e&&"undefined"!=typeof document){var o,n=!0===t.prepend?"prepend":"append",i=!0===t.singleTag,a="string"==typeof t.container?document.querySelector(t.container):document.getElementsByTagName("head")[0];if(i){var l=b.indexOf(a);-1===l&&(l=b.push(a)-1,k[l]={}),o=k[l]&&k[l][n]?k[l][n]:k[l][n]=r()}else o=r();65279===e.charCodeAt(0)&&(e=e.substring(1)),o.styleSheet?o.styleSheet.cssText+=e:o.appendChild(document.createTextNode(e))}function r(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),t.attributes)for(var o=Object.keys(t.attributes),i=0;i<o.length;i++)e.setAttribute(o[i],t.attributes[o[i]]);var l="prepend"===n?"afterbegin":"beforeend";return a.insertAdjacentElement(l,e),e}}(".active-chip[data-v-b52a34f6] {\n background-color: var(--primary);\n}\n.inactive-chip[data-v-b52a34f6] {\n background-color: var(--foreground-subdued);\n}",{}),h.render=function(e,t,o,n,i,a){const w=l("v-icon"),h=l("v-chip"),b=l("v-list-item-icon"),k=l("v-list-item-content"),_=l("v-list-item"),C=l("v-list"),S=l("v-menu"),x=l("v-table"),F=l("v-button"),j=l("private-view"),$=r("tooltip");return s(),c(j,{title:"Flow Manager"},{actions:u((()=>[d((s(),c(F,{rounded:"",icon:"",onClick:e.onRestoreButtonClicked},{default:u((()=>[p(w,{name:"file_upload"})])),_:1},8,["onClick"])),[[$,"Restore",void 0,{bottom:!0}]])])),default:u((()=>[p(x,{items:e.flows,headers:e.headers,"onUpdate:headers":t[0]||(t[0]=t=>e.headers=t),"onClick:row":e.goToFlow},{"item.icon":u((({item:e})=>[e.icon?(s(),c(w,{key:0,name:e.icon},null,8,["name"])):f("v-if",!0)])),"item.status":u((({item:e})=>[p(h,{rounded:"",class:v("active"===e.status?"active-chip":"inactive-chip")},{default:u((()=>[m(g(e.status),1)])),_:2},1032,["class"])])),"item-append":u((({item:t})=>[p(S,{placement:"bottom-end","show-arrow":"","close-on-content-click":!0},{activator:u((({toggle:e})=>[p(w,{name:"more_vert",class:"ctx-toggle",onClick:e},null,8,["onClick"])])),default:u((()=>[p(C,null,{default:u((()=>[p(_,{clickable:"",onClick:o=>e.duplicate(t)},{default:u((()=>[p(b,null,{default:u((()=>[p(w,{name:"content_copy"})])),_:1}),p(k,null,{default:u((()=>[m(" Duplicate ")])),_:1})])),_:2},1032,["onClick"]),p(_,{clickable:"",onClick:o=>e.backup(t)},{default:u((()=>[p(b,null,{default:u((()=>[p(w,{name:"file_download"})])),_:1}),p(k,null,{default:u((()=>[m(" Backup ")])),_:1})])),_:2},1032,["onClick"])])),_:2},1024)])),_:2},1024)])),_:2},1032,["items","headers","onClick:row"]),y("input",{ref:"restoredFile",type:"file",accept:"application/json",onChange:t[1]||(t[1]=(...t)=>e.onRestoredFileChanged&&e.onRestoredFileChanged(...t)),style:{display:"none"}},null,544)])),_:1})},h.__scopeId="data-v-b52a34f6",h.__file="src/module.vue";var _=o({id:"flow-manager",name:"Flow Manager",icon:"bolt",routes:[{path:"",component:h}]});export{_ as default};
|
package/package.json
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
"name": "directus-extension-flow-manager",
|
|
3
3
|
"description": "This is a custom module for managing Flow",
|
|
4
4
|
"icon": "extension",
|
|
5
|
-
"version": "1.
|
|
6
|
-
|
|
5
|
+
"version": "1.1.0",
|
|
6
|
+
"author": "Bagus Andreanto<andreanto.bagus@gmail.com>",
|
|
7
|
+
"homepage": "https://github.com/baguse/directus-extension-flow-manager",
|
|
8
|
+
"license": "MIT",
|
|
7
9
|
"keywords": [
|
|
8
10
|
"directus",
|
|
9
11
|
"directus-extension",
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
"vue": "^3.3.4"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
|
-
"sass": "^1.63.6"
|
|
31
|
+
"sass": "^1.63.6",
|
|
32
|
+
"vue-router": "^4.2.4"
|
|
30
33
|
}
|
|
31
34
|
}
|
|
Binary file
|
package/src/index.ts
CHANGED
package/src/module.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<private-view title="Flow Manager">
|
|
3
|
-
<v-table :items="flows" v-model:headers="headers">
|
|
3
|
+
<v-table :items="flows" v-model:headers="headers" @click:row="goToFlow">
|
|
4
4
|
<template #[`item.icon`]="{ item }">
|
|
5
5
|
<v-icon v-if="item.icon" :name="item.icon" />
|
|
6
6
|
</template>
|
|
@@ -21,16 +21,31 @@
|
|
|
21
21
|
</v-list-item-icon>
|
|
22
22
|
<v-list-item-content> Duplicate </v-list-item-content>
|
|
23
23
|
</v-list-item>
|
|
24
|
+
<v-list-item clickable @click="backup(item)">
|
|
25
|
+
<v-list-item-icon>
|
|
26
|
+
<v-icon name="file_download" />
|
|
27
|
+
</v-list-item-icon>
|
|
28
|
+
<v-list-item-content> Backup </v-list-item-content>
|
|
29
|
+
</v-list-item>
|
|
24
30
|
</v-list>
|
|
25
31
|
</v-menu>
|
|
26
32
|
</template>
|
|
27
33
|
</v-table>
|
|
34
|
+
|
|
35
|
+
<template #actions>
|
|
36
|
+
<v-button v-tooltip.bottom="'Restore'" rounded icon @click="onRestoreButtonClicked">
|
|
37
|
+
<v-icon name="file_upload" />
|
|
38
|
+
</v-button>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<input ref="restoredFile" type="file" accept="application/json" @change="onRestoredFileChanged" style="display: none" />
|
|
28
42
|
</private-view>
|
|
29
43
|
</template>
|
|
30
44
|
|
|
31
45
|
<script lang="ts">
|
|
32
46
|
import { defineComponent, ref, unref } from "vue";
|
|
33
47
|
import { useStores, useApi } from "@directus/extensions-sdk";
|
|
48
|
+
import {useRouter} from 'vue-router'
|
|
34
49
|
|
|
35
50
|
interface IOperation {
|
|
36
51
|
id: string;
|
|
@@ -78,9 +93,12 @@ export default defineComponent({
|
|
|
78
93
|
const flowsStore = useFlowsStore();
|
|
79
94
|
const notificationsStore = useNotificationsStore();
|
|
80
95
|
const api = useApi();
|
|
96
|
+
const router = useRouter();
|
|
81
97
|
|
|
82
98
|
const flows = ref(flowsStore.flows);
|
|
83
99
|
|
|
100
|
+
const restoredFile = ref(null);
|
|
101
|
+
|
|
84
102
|
const headers = ref([
|
|
85
103
|
{
|
|
86
104
|
text: "",
|
|
@@ -102,13 +120,18 @@ export default defineComponent({
|
|
|
102
120
|
return {
|
|
103
121
|
headers,
|
|
104
122
|
flows,
|
|
123
|
+
restoredFile,
|
|
105
124
|
duplicate,
|
|
125
|
+
backup,
|
|
126
|
+
onRestoredFileChanged,
|
|
127
|
+
onRestoreButtonClicked,
|
|
128
|
+
goToFlow,
|
|
106
129
|
};
|
|
107
130
|
|
|
108
|
-
async function duplicate(item: IFlow) {
|
|
131
|
+
async function duplicate(item: IFlow, isDuplicate = true) {
|
|
109
132
|
try {
|
|
110
133
|
const response = await api.post("/flows", {
|
|
111
|
-
name: `${item.name} - Copy
|
|
134
|
+
name: isDuplicate ? `${item.name} - Copy` : item.name,
|
|
112
135
|
status: "inactive",
|
|
113
136
|
icon: item.icon,
|
|
114
137
|
accountability: item.accountability,
|
|
@@ -169,13 +192,87 @@ export default defineComponent({
|
|
|
169
192
|
|
|
170
193
|
notificationsStore.add({
|
|
171
194
|
type: "success",
|
|
172
|
-
|
|
195
|
+
title: isDuplicate ? "Flow Duplicated successfully" : `Flow ${item.name} restored successfully`,
|
|
173
196
|
closeable: true,
|
|
197
|
+
persist: true,
|
|
174
198
|
});
|
|
175
199
|
} catch (error) {
|
|
176
200
|
console.log(error);
|
|
177
201
|
}
|
|
178
202
|
}
|
|
203
|
+
|
|
204
|
+
function getTimestamp() {
|
|
205
|
+
const date = new Date();
|
|
206
|
+
const year = date.getFullYear();
|
|
207
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
208
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
209
|
+
const hour = date.getHours().toString().padStart(2, "0");
|
|
210
|
+
const minute = date.getMinutes().toString().padStart(2, "0");
|
|
211
|
+
const second = date.getSeconds().toString().padStart(2, "0");
|
|
212
|
+
|
|
213
|
+
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function backup(item: IFlow) {
|
|
217
|
+
interface ISanitizedFlow extends Partial<Omit<IFlow, "operations">> {
|
|
218
|
+
operations: Partial<IOperation>[];
|
|
219
|
+
}
|
|
220
|
+
const sanitizedFlow: ISanitizedFlow = {
|
|
221
|
+
name: item.name,
|
|
222
|
+
icon: item.icon,
|
|
223
|
+
color: item.color,
|
|
224
|
+
description: item.description,
|
|
225
|
+
trigger: item.trigger,
|
|
226
|
+
options: item.options,
|
|
227
|
+
operation: item.operation,
|
|
228
|
+
operations: item.operations.map((operation) => ({
|
|
229
|
+
id: operation.id,
|
|
230
|
+
name: operation.name,
|
|
231
|
+
key: operation.key,
|
|
232
|
+
type: operation.type,
|
|
233
|
+
position_x: operation.position_x,
|
|
234
|
+
position_y: operation.position_y,
|
|
235
|
+
options: operation.options,
|
|
236
|
+
resolve: operation.resolve,
|
|
237
|
+
reject: operation.reject,
|
|
238
|
+
})),
|
|
239
|
+
};
|
|
240
|
+
const blob = new Blob([JSON.stringify(sanitizedFlow, null, 2)], { type: "application/json" });
|
|
241
|
+
var fileObj = window.URL.createObjectURL(blob);
|
|
242
|
+
|
|
243
|
+
var docUrl = document.createElement("a");
|
|
244
|
+
docUrl.href = fileObj;
|
|
245
|
+
docUrl.setAttribute("download", `${getTimestamp()}-${item.name}.json`);
|
|
246
|
+
document.body.appendChild(docUrl);
|
|
247
|
+
docUrl.click();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function onRestoredFileChanged($event: Event) {
|
|
251
|
+
const file: File = $event?.target?.files[0];
|
|
252
|
+
if (!file) return;
|
|
253
|
+
const reader = new FileReader();
|
|
254
|
+
reader.onload = async (e) => {
|
|
255
|
+
try {
|
|
256
|
+
const result = e.target?.result;
|
|
257
|
+
const parsedResult = JSON.parse(result as string);
|
|
258
|
+
console.log({ parsedResult });
|
|
259
|
+
await duplicate(parsedResult, false);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.log(error);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
reader.readAsText(file);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function onRestoreButtonClicked() {
|
|
268
|
+
console.log("restore button clicked");
|
|
269
|
+
restoredFile.value?.click();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function goToFlow({ item }: { item: IFlow }) {
|
|
273
|
+
console.log("go to flow", item);
|
|
274
|
+
router.push(`/settings/flows/${item.id}`);
|
|
275
|
+
}
|
|
179
276
|
},
|
|
180
277
|
});
|
|
181
278
|
</script>
|