@nocobase/plugin-workflow-loop 0.17.0-alpha.4
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 +661 -0
- package/README.md +9 -0
- package/README.zh-CN.md +9 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/LoopInstruction.d.ts +30 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +13 -0
- package/dist/externalVersion.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -0
- package/dist/locale/en-US.json +9 -0
- package/dist/locale/index.d.ts +3 -0
- package/dist/locale/index.js +39 -0
- package/dist/locale/zh-CN.json +9 -0
- package/dist/server/LoopInstruction.d.ts +13 -0
- package/dist/server/LoopInstruction.js +94 -0
- package/dist/server/Plugin.d.ts +6 -0
- package/dist/server/Plugin.js +42 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +33 -0
- package/package.json +24 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/src/client/LoopInstruction.tsx +161 -0
- package/src/client/index.ts +19 -0
- package/src/index.ts +2 -0
- package/src/locale/en-US.json +9 -0
- package/src/locale/index.ts +12 -0
- package/src/locale/zh-CN.json +9 -0
- package/src/server/LoopInstruction.ts +97 -0
- package/src/server/Plugin.ts +14 -0
- package/src/server/__tests__/instruction.test.ts +476 -0
- package/src/server/index.ts +1 -0
package/client.d.ts
ADDED
package/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/client/index.js');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { VariableOption, WorkflowVariableInput, Instruction } from '@nocobase/plugin-workflow/client';
|
|
3
|
+
export default class extends Instruction {
|
|
4
|
+
title: string;
|
|
5
|
+
type: string;
|
|
6
|
+
group: string;
|
|
7
|
+
description: string;
|
|
8
|
+
fieldset: {
|
|
9
|
+
target: {
|
|
10
|
+
type: string;
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
'x-decorator': string;
|
|
14
|
+
'x-component': string;
|
|
15
|
+
'x-component-props': {
|
|
16
|
+
changeOnSelect: boolean;
|
|
17
|
+
useTypedConstant: string[];
|
|
18
|
+
className: string;
|
|
19
|
+
};
|
|
20
|
+
required: boolean;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
components: {
|
|
24
|
+
WorkflowVariableInput: typeof WorkflowVariableInput;
|
|
25
|
+
};
|
|
26
|
+
Component({ data }: {
|
|
27
|
+
data: any;
|
|
28
|
+
}): React.JSX.Element;
|
|
29
|
+
useScopeVariables(node: any, options: any): VariableOption[];
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
(function(n,e){typeof exports=="object"&&typeof module!="undefined"?e(exports,require("@nocobase/client"),require("react/jsx-runtime"),require("@ant-design/icons"),require("@nocobase/plugin-workflow/client"),require("react-i18next")):typeof define=="function"&&define.amd?define(["exports","@nocobase/client","react/jsx-runtime","@ant-design/icons","@nocobase/plugin-workflow/client","react-i18next"],e):(n=typeof globalThis!="undefined"?globalThis:n||self,e(n["@nocobase/plugin-workflow-loop"]={},n["@nocobase/client"],n.jsxRuntime,n["@ant-design/icons"],n["@nocobase/plugin-workflow"],n["react-i18next"]))})(this,function(n,e,o,v,s,k){"use strict";var V=Object.defineProperty,W=Object.defineProperties;var P=Object.getOwnPropertyDescriptors;var O=Object.getOwnPropertySymbols;var F=Object.prototype.hasOwnProperty,M=Object.prototype.propertyIsEnumerable;var I=(n,e,o)=>e in n?V(n,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):n[e]=o,N=(n,e)=>{for(var o in e||(e={}))F.call(e,o)&&I(n,o,e[o]);if(O)for(var o of O(e))M.call(e,o)&&I(n,o,e[o]);return n},T=(n,e)=>W(n,P(e));var g=(n,e,o)=>(I(n,typeof e!="symbol"?e+"":e,o),o);var y=(n,e,o)=>new Promise((v,s)=>{var k=i=>{try{d(o.next(i))}catch(w){s(w)}},u=i=>{try{d(o.throw(i))}catch(w){s(w)}},d=i=>i.done?v(i.value):Promise.resolve(i.value).then(k,u);d((o=o.apply(n,e)).next())});const u="workflow-loop";function d(p,f={}){const{t}=i(f);return t(p)}function i(p){return k.useTranslation(u,p)}function w(p,f){let t=p,h=null;for(let a=0;a<f.length;a++){const c=f[a],r=t.find(m=>m.value===c);if(!r)return null;h=r,!r.isLeaf&&r.loadChildren&&r.loadChildren(r),r.children&&(t=r.children)}return h}class j extends s.Instruction{constructor(){super(...arguments);g(this,"title",`{{t("Loop", { ns: "${u}" })}}`);g(this,"type","loop");g(this,"group","control");g(this,"description",`{{t("By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.", { ns: "${u}" })}}`);g(this,"fieldset",{target:{type:"string",title:`{{t("Loop target", { ns: "${u}" })}}`,description:`{{t("A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.", { ns: "${u}" })}}`,"x-decorator":"FormItem","x-component":"WorkflowVariableInput","x-component-props":{changeOnSelect:!0,useTypedConstant:["string","number","null"],className:e.css`
|
|
2
|
+
width: 100%;
|
|
3
|
+
|
|
4
|
+
.variable {
|
|
5
|
+
flex: 1;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ant-input.null-value {
|
|
9
|
+
width: 100%;
|
|
10
|
+
}
|
|
11
|
+
`},required:!0}});g(this,"components",{WorkflowVariableInput:s.WorkflowVariableInput})}Component({data:t}){var r;const{nodes:h}=s.useFlowContext(),{styles:a}=s.useStyles(),c=h.find(m=>m.upstreamId===t.id&&m.branchIndex!=null);return o.jsx(s.NodeDefaultView,{data:t,children:o.jsx("div",{className:a.nodeSubtreeClass,children:o.jsxs("div",{className:e.cx(a.branchBlockClass,e.css`
|
|
12
|
+
padding-left: 20em;
|
|
13
|
+
`),children:[o.jsx(s.Branch,{from:t,entry:c,branchIndex:(r=c==null?void 0:c.branchIndex)!=null?r:0}),o.jsxs("div",{className:a.branchClass,children:[o.jsx("div",{className:"workflow-branch-lines"}),o.jsx("div",{className:e.cx(a.addButtonClass,a.loopLineClass),children:o.jsx(v.ArrowUpOutlined,{})})]})]})})})}useScopeVariables(t,h){const a=e.useCompile(),c=d("Loop target"),r=d("Loop index"),m=d("Loop length"),{target:x}=t.config;if(!x)return null;const{fieldNames:l=s.defaultFieldNames}=h;let L={key:"item",[l.value]:"item",[l.label]:c};if(typeof x=="string"&&x.startsWith("{{")&&x.endsWith("}}")){const S=x.slice(2,-2).split(".").map(b=>b.trim()),A=[s.scopeOptions,s.nodesOptions,s.triggerOptions].map(b=>{const C=b.useOptions(T(N({},h),{current:t})).filter(Boolean);return{[l.label]:a(b.label),[l.value]:b.value,key:b.value,[l.children]:C,disabled:C&&!C.length}}),B=w(A,S);L=Object.assign({},B,L)}return[L,{key:"index",[l.value]:"index",[l.label]:r},{key:"length",[l.value]:"length",[l.label]:m}]}}class q extends e.Plugin{afterAdd(){return y(this,null,function*(){})}beforeLoad(){return y(this,null,function*(){})}load(){return y(this,null,function*(){const f=this.app.pm.get("workflow"),t=new j;f.instructions.register(t.type,t)})}}n.default=q,Object.defineProperties(n,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var src_exports = {};
|
|
30
|
+
__export(src_exports, {
|
|
31
|
+
default: () => import_server.default
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(src_exports);
|
|
34
|
+
__reExport(src_exports, require("./server"), module.exports);
|
|
35
|
+
var import_server = __toESM(require("./server"));
|
|
36
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
37
|
+
0 && (module.exports = {
|
|
38
|
+
...require("./server")
|
|
39
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Loop": "Loop",
|
|
3
|
+
"Loop target": "Loop target",
|
|
4
|
+
"Loop index": "Loop index",
|
|
5
|
+
"Loop length": "Loop length",
|
|
6
|
+
"By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.": "By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.",
|
|
7
|
+
"Scope variables": "Scope variables",
|
|
8
|
+
"A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.": "A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it."
|
|
9
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var locale_exports = {};
|
|
19
|
+
__export(locale_exports, {
|
|
20
|
+
NAMESPACE: () => NAMESPACE,
|
|
21
|
+
useLang: () => useLang,
|
|
22
|
+
usePluginTranslation: () => usePluginTranslation
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(locale_exports);
|
|
25
|
+
var import_react_i18next = require("react-i18next");
|
|
26
|
+
const NAMESPACE = "workflow-loop";
|
|
27
|
+
function useLang(key, options = {}) {
|
|
28
|
+
const { t } = usePluginTranslation(options);
|
|
29
|
+
return t(key);
|
|
30
|
+
}
|
|
31
|
+
function usePluginTranslation(options) {
|
|
32
|
+
return (0, import_react_i18next.useTranslation)(NAMESPACE, options);
|
|
33
|
+
}
|
|
34
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
35
|
+
0 && (module.exports = {
|
|
36
|
+
NAMESPACE,
|
|
37
|
+
useLang,
|
|
38
|
+
usePluginTranslation
|
|
39
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Loop": "循环",
|
|
3
|
+
"Loop target": "循环对象",
|
|
4
|
+
"Loop index": "当前索引",
|
|
5
|
+
"Loop length": "循环长度",
|
|
6
|
+
"By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.": "使用循环节点可以对多条数据进行同样的操作,多条数据的来源可以是查询节点的多条结果,或者一条数据的多条关系数据。也可以用于一定次数的循环,或者对字符串中每一个字符的循环处理。循环次数过高可能引起性能问题,请谨慎使用。",
|
|
7
|
+
"Scope variables": "局域变量",
|
|
8
|
+
"A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.": "单一数字值将被视为循环次数,单一字符串值将被视为字符数组,其他非数组值将被转换为数组。达到循环次数,或者将数组循环完成后,循环节点结束。你也可以在循环中添加条件节点,以终止循环。"
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Processor, Instruction, FlowNodeModel, JobModel } from '@nocobase/plugin-workflow';
|
|
2
|
+
export default class extends Instruction {
|
|
3
|
+
run(node: FlowNodeModel, prevJob: JobModel, processor: Processor): Promise<{
|
|
4
|
+
status: number;
|
|
5
|
+
result: number;
|
|
6
|
+
}>;
|
|
7
|
+
resume(node: FlowNodeModel, branchJob: any, processor: Processor): Promise<any>;
|
|
8
|
+
getScope(node: any, index: any, processor: any): {
|
|
9
|
+
item: any;
|
|
10
|
+
index: any;
|
|
11
|
+
length: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var LoopInstruction_exports = {};
|
|
19
|
+
__export(LoopInstruction_exports, {
|
|
20
|
+
default: () => LoopInstruction_default
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(LoopInstruction_exports);
|
|
23
|
+
var import_plugin_workflow = require("@nocobase/plugin-workflow");
|
|
24
|
+
function getTargetLength(target) {
|
|
25
|
+
let length = 0;
|
|
26
|
+
if (typeof target === "number") {
|
|
27
|
+
if (target < 0) {
|
|
28
|
+
throw new Error("Loop target in number type must be greater than 0");
|
|
29
|
+
}
|
|
30
|
+
length = Math.floor(target);
|
|
31
|
+
} else {
|
|
32
|
+
const targets = (Array.isArray(target) ? target : [target]).filter((t) => t != null);
|
|
33
|
+
length = targets.length;
|
|
34
|
+
}
|
|
35
|
+
return length;
|
|
36
|
+
}
|
|
37
|
+
class LoopInstruction_default extends import_plugin_workflow.Instruction {
|
|
38
|
+
async run(node, prevJob, processor) {
|
|
39
|
+
const [branch] = processor.getBranches(node);
|
|
40
|
+
const target = processor.getParsedValue(node.config.target, node.id);
|
|
41
|
+
const length = getTargetLength(target);
|
|
42
|
+
if (!branch || !length) {
|
|
43
|
+
return {
|
|
44
|
+
status: import_plugin_workflow.JOB_STATUS.RESOLVED,
|
|
45
|
+
result: 0
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const job = await processor.saveJob({
|
|
49
|
+
status: import_plugin_workflow.JOB_STATUS.PENDING,
|
|
50
|
+
// save loop index
|
|
51
|
+
result: 0,
|
|
52
|
+
nodeId: node.id,
|
|
53
|
+
upstreamId: (prevJob == null ? void 0 : prevJob.id) ?? null
|
|
54
|
+
});
|
|
55
|
+
await processor.run(branch, job);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
async resume(node, branchJob, processor) {
|
|
59
|
+
const job = processor.findBranchParentJob(branchJob, node);
|
|
60
|
+
const loop = processor.nodesMap.get(job.nodeId);
|
|
61
|
+
const [branch] = processor.getBranches(node);
|
|
62
|
+
const { result, status } = job;
|
|
63
|
+
if (status !== import_plugin_workflow.JOB_STATUS.PENDING) {
|
|
64
|
+
return processor.exit();
|
|
65
|
+
}
|
|
66
|
+
const nextIndex = result + 1;
|
|
67
|
+
const target = processor.getParsedValue(loop.config.target, node.id);
|
|
68
|
+
if (branchJob.status > import_plugin_workflow.JOB_STATUS.PENDING) {
|
|
69
|
+
job.set({ result: nextIndex });
|
|
70
|
+
const length = getTargetLength(target);
|
|
71
|
+
if (nextIndex < length) {
|
|
72
|
+
await processor.saveJob(job);
|
|
73
|
+
await processor.run(branch, job);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
job.set({
|
|
78
|
+
status: branchJob.status
|
|
79
|
+
});
|
|
80
|
+
return job;
|
|
81
|
+
}
|
|
82
|
+
getScope(node, index, processor) {
|
|
83
|
+
const target = processor.getParsedValue(node.config.target, node.id);
|
|
84
|
+
const targets = (Array.isArray(target) ? target : [target]).filter((t) => t != null);
|
|
85
|
+
const length = getTargetLength(target);
|
|
86
|
+
const item = typeof target === "number" ? index : targets[index];
|
|
87
|
+
const result = {
|
|
88
|
+
item,
|
|
89
|
+
index,
|
|
90
|
+
length
|
|
91
|
+
};
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var Plugin_exports = {};
|
|
29
|
+
__export(Plugin_exports, {
|
|
30
|
+
default: () => Plugin_default
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(Plugin_exports);
|
|
33
|
+
var import_server = require("@nocobase/server");
|
|
34
|
+
var import_LoopInstruction = __toESM(require("./LoopInstruction"));
|
|
35
|
+
class Plugin_default extends import_server.Plugin {
|
|
36
|
+
workflow;
|
|
37
|
+
async load() {
|
|
38
|
+
const workflowPlugin = this.app.getPlugin("workflow");
|
|
39
|
+
this.workflow = workflowPlugin;
|
|
40
|
+
workflowPlugin.instructions.register("loop", new import_LoopInstruction.default(workflowPlugin));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Plugin';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var server_exports = {};
|
|
29
|
+
__export(server_exports, {
|
|
30
|
+
default: () => import_Plugin.default
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(server_exports);
|
|
33
|
+
var import_Plugin = __toESM(require("./Plugin"));
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nocobase/plugin-workflow-loop",
|
|
3
|
+
"displayName": "Workflow: Loop node",
|
|
4
|
+
"displayName.zh-CN": "工作流:循环节点",
|
|
5
|
+
"description": "Useful plugin for doing dynamic calculation based on expression collection records in workflow.",
|
|
6
|
+
"description.zh-CN": "用于在工作流中进行基于数据行的动态表达式计算。",
|
|
7
|
+
"version": "0.17.0-alpha.4",
|
|
8
|
+
"license": "AGPL-3.0",
|
|
9
|
+
"main": "./dist/server/index.js",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@ant-design/icons": "5.x",
|
|
12
|
+
"react": "18.x",
|
|
13
|
+
"react-i18next": "^11.15.1"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@nocobase/client": "0.x",
|
|
17
|
+
"@nocobase/database": "0.x",
|
|
18
|
+
"@nocobase/plugin-workflow": ">=0.17.0-alpha.3",
|
|
19
|
+
"@nocobase/plugin-workflow-test": ">=0.17.0-alpha.3",
|
|
20
|
+
"@nocobase/server": "0.x",
|
|
21
|
+
"@nocobase/test": "0.x"
|
|
22
|
+
},
|
|
23
|
+
"gitHead": "663b03a3799a70ba1a2bc6a0d686e679331a50ad"
|
|
24
|
+
}
|
package/server.d.ts
ADDED
package/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/server/index.js');
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ArrowUpOutlined } from '@ant-design/icons';
|
|
3
|
+
|
|
4
|
+
import { css, cx, useCompile } from '@nocobase/client';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
NodeDefaultView,
|
|
8
|
+
Branch,
|
|
9
|
+
useFlowContext,
|
|
10
|
+
useStyles,
|
|
11
|
+
VariableOption,
|
|
12
|
+
WorkflowVariableInput,
|
|
13
|
+
defaultFieldNames,
|
|
14
|
+
nodesOptions,
|
|
15
|
+
scopeOptions,
|
|
16
|
+
triggerOptions,
|
|
17
|
+
Instruction,
|
|
18
|
+
} from '@nocobase/plugin-workflow/client';
|
|
19
|
+
import { NAMESPACE, useLang } from '../locale';
|
|
20
|
+
|
|
21
|
+
function findOption(options: VariableOption[], paths: string[]) {
|
|
22
|
+
let opts = options;
|
|
23
|
+
let option = null;
|
|
24
|
+
for (let i = 0; i < paths.length; i++) {
|
|
25
|
+
const path = paths[i];
|
|
26
|
+
const current = opts.find((item) => item.value === path);
|
|
27
|
+
if (!current) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
option = current;
|
|
31
|
+
if (!current.isLeaf && current.loadChildren) {
|
|
32
|
+
current.loadChildren(current);
|
|
33
|
+
}
|
|
34
|
+
if (current.children) {
|
|
35
|
+
opts = current.children;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return option;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default class extends Instruction {
|
|
42
|
+
title = `{{t("Loop", { ns: "${NAMESPACE}" })}}`;
|
|
43
|
+
type = 'loop';
|
|
44
|
+
group = 'control';
|
|
45
|
+
description = `{{t("By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.", { ns: "${NAMESPACE}" })}}`;
|
|
46
|
+
fieldset = {
|
|
47
|
+
target: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
title: `{{t("Loop target", { ns: "${NAMESPACE}" })}}`,
|
|
50
|
+
description: `{{t("A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.", { ns: "${NAMESPACE}" })}}`,
|
|
51
|
+
'x-decorator': 'FormItem',
|
|
52
|
+
'x-component': 'WorkflowVariableInput',
|
|
53
|
+
'x-component-props': {
|
|
54
|
+
changeOnSelect: true,
|
|
55
|
+
useTypedConstant: ['string', 'number', 'null'],
|
|
56
|
+
className: css`
|
|
57
|
+
width: 100%;
|
|
58
|
+
|
|
59
|
+
.variable {
|
|
60
|
+
flex: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ant-input.null-value {
|
|
64
|
+
width: 100%;
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
},
|
|
68
|
+
required: true,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
components = {
|
|
72
|
+
WorkflowVariableInput,
|
|
73
|
+
};
|
|
74
|
+
Component({ data }) {
|
|
75
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
76
|
+
const { nodes } = useFlowContext();
|
|
77
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
78
|
+
const { styles } = useStyles();
|
|
79
|
+
const entry = nodes.find((node) => node.upstreamId === data.id && node.branchIndex != null);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<NodeDefaultView data={data}>
|
|
83
|
+
<div className={styles.nodeSubtreeClass}>
|
|
84
|
+
<div
|
|
85
|
+
className={cx(
|
|
86
|
+
styles.branchBlockClass,
|
|
87
|
+
css`
|
|
88
|
+
padding-left: 20em;
|
|
89
|
+
`,
|
|
90
|
+
)}
|
|
91
|
+
>
|
|
92
|
+
<Branch from={data} entry={entry} branchIndex={entry?.branchIndex ?? 0} />
|
|
93
|
+
|
|
94
|
+
<div className={styles.branchClass}>
|
|
95
|
+
<div className="workflow-branch-lines" />
|
|
96
|
+
<div className={cx(styles.addButtonClass, styles.loopLineClass)}>
|
|
97
|
+
<ArrowUpOutlined />
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</NodeDefaultView>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
useScopeVariables(node, options) {
|
|
106
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
107
|
+
const compile = useCompile();
|
|
108
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
109
|
+
const langLoopTarget = useLang('Loop target');
|
|
110
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
111
|
+
const langLoopIndex = useLang('Loop index');
|
|
112
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
113
|
+
const langLoopLength = useLang('Loop length');
|
|
114
|
+
const { target } = node.config;
|
|
115
|
+
if (!target) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { fieldNames = defaultFieldNames } = options;
|
|
120
|
+
|
|
121
|
+
// const { workflow } = useFlowContext();
|
|
122
|
+
// const current = useNodeContext();
|
|
123
|
+
// const upstreams = useAvailableUpstreams(current);
|
|
124
|
+
// find target data model by path described in `config.target`
|
|
125
|
+
// 1. get options from $context/$jobsMapByNodeKey
|
|
126
|
+
// 2. route to sub-options and use as loop target options
|
|
127
|
+
let targetOption: VariableOption = {
|
|
128
|
+
key: 'item',
|
|
129
|
+
[fieldNames.value]: 'item',
|
|
130
|
+
[fieldNames.label]: langLoopTarget,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (typeof target === 'string' && target.startsWith('{{') && target.endsWith('}}')) {
|
|
134
|
+
const paths = target
|
|
135
|
+
.slice(2, -2)
|
|
136
|
+
.split('.')
|
|
137
|
+
.map((path) => path.trim());
|
|
138
|
+
|
|
139
|
+
const targetOptions = [scopeOptions, nodesOptions, triggerOptions].map((item: any) => {
|
|
140
|
+
const opts = item.useOptions({ ...options, current: node }).filter(Boolean);
|
|
141
|
+
return {
|
|
142
|
+
[fieldNames.label]: compile(item.label),
|
|
143
|
+
[fieldNames.value]: item.value,
|
|
144
|
+
key: item.value,
|
|
145
|
+
[fieldNames.children]: opts,
|
|
146
|
+
disabled: opts && !opts.length,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const found = findOption(targetOptions, paths);
|
|
151
|
+
|
|
152
|
+
targetOption = Object.assign({}, found, targetOption);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return [
|
|
156
|
+
targetOption,
|
|
157
|
+
{ key: 'index', [fieldNames.value]: 'index', [fieldNames.label]: langLoopIndex },
|
|
158
|
+
{ key: 'length', [fieldNames.value]: 'length', [fieldNames.label]: langLoopLength },
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import WorkflowPlugin from '@nocobase/plugin-workflow/client';
|
|
3
|
+
|
|
4
|
+
import LoopInstruction from './LoopInstruction';
|
|
5
|
+
|
|
6
|
+
export default class extends Plugin {
|
|
7
|
+
async afterAdd() {
|
|
8
|
+
// await this.app.pm.add()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async beforeLoad() {}
|
|
12
|
+
|
|
13
|
+
// You can get and modify the app instance here
|
|
14
|
+
async load() {
|
|
15
|
+
const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
|
|
16
|
+
const loopInstruction = new LoopInstruction();
|
|
17
|
+
workflow.instructions.register(loopInstruction.type, loopInstruction);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Loop": "Loop",
|
|
3
|
+
"Loop target": "Loop target",
|
|
4
|
+
"Loop index": "Loop index",
|
|
5
|
+
"Loop length": "Loop length",
|
|
6
|
+
"By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.": "By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.",
|
|
7
|
+
"Scope variables": "Scope variables",
|
|
8
|
+
"A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.": "A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it."
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
|
|
3
|
+
export const NAMESPACE = 'workflow-loop';
|
|
4
|
+
|
|
5
|
+
export function useLang(key: string, options = {}) {
|
|
6
|
+
const { t } = usePluginTranslation(options);
|
|
7
|
+
return t(key);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function usePluginTranslation(options) {
|
|
11
|
+
return useTranslation(NAMESPACE, options);
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Loop": "循环",
|
|
3
|
+
"Loop target": "循环对象",
|
|
4
|
+
"Loop index": "当前索引",
|
|
5
|
+
"Loop length": "循环长度",
|
|
6
|
+
"By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.": "使用循环节点可以对多条数据进行同样的操作,多条数据的来源可以是查询节点的多条结果,或者一条数据的多条关系数据。也可以用于一定次数的循环,或者对字符串中每一个字符的循环处理。循环次数过高可能引起性能问题,请谨慎使用。",
|
|
7
|
+
"Scope variables": "局域变量",
|
|
8
|
+
"A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.": "单一数字值将被视为循环次数,单一字符串值将被视为字符数组,其他非数组值将被转换为数组。达到循环次数,或者将数组循环完成后,循环节点结束。你也可以在循环中添加条件节点,以终止循环。"
|
|
9
|
+
}
|