@nocobase/flow-engine 2.0.0-beta.21 → 2.0.0-beta.23

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.
Files changed (51) hide show
  1. package/lib/FlowDefinition.d.ts +2 -0
  2. package/lib/JSRunner.js +23 -1
  3. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +66 -13
  4. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
  5. package/lib/components/variables/VariableInput.js +1 -2
  6. package/lib/components/variables/VariableTag.js +46 -39
  7. package/lib/data-source/index.js +11 -5
  8. package/lib/flowContext.js +5 -1
  9. package/lib/flowI18n.js +6 -4
  10. package/lib/locale/en-US.json +2 -1
  11. package/lib/locale/index.d.ts +2 -0
  12. package/lib/locale/zh-CN.json +1 -0
  13. package/lib/resources/sqlResource.d.ts +3 -3
  14. package/lib/types.d.ts +12 -0
  15. package/lib/utils/associationObjectVariable.d.ts +2 -2
  16. package/lib/utils/index.d.ts +4 -2
  17. package/lib/utils/index.js +16 -0
  18. package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
  19. package/lib/utils/resolveRunJSObjectValues.js +61 -0
  20. package/lib/utils/runjsValue.d.ts +29 -0
  21. package/lib/utils/runjsValue.js +275 -0
  22. package/lib/utils/safeGlobals.d.ts +14 -0
  23. package/lib/utils/safeGlobals.js +37 -2
  24. package/lib/utils/schema-utils.d.ts +10 -0
  25. package/lib/utils/schema-utils.js +61 -0
  26. package/package.json +4 -4
  27. package/src/JSRunner.ts +29 -1
  28. package/src/__tests__/JSRunner.test.ts +64 -0
  29. package/src/__tests__/flowContext.test.ts +78 -0
  30. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +99 -14
  31. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
  32. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +94 -1
  33. package/src/components/variables/VariableInput.tsx +4 -2
  34. package/src/components/variables/VariableTag.tsx +54 -45
  35. package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
  36. package/src/data-source/index.ts +11 -5
  37. package/src/flowContext.ts +6 -2
  38. package/src/flowI18n.ts +7 -5
  39. package/src/locale/en-US.json +2 -1
  40. package/src/locale/zh-CN.json +1 -0
  41. package/src/resources/sqlResource.ts +3 -3
  42. package/src/types.ts +12 -0
  43. package/src/utils/__tests__/runjsValue.test.ts +44 -0
  44. package/src/utils/__tests__/safeGlobals.test.ts +8 -0
  45. package/src/utils/__tests__/utils.test.ts +95 -0
  46. package/src/utils/associationObjectVariable.ts +2 -2
  47. package/src/utils/index.ts +20 -2
  48. package/src/utils/resolveRunJSObjectValues.ts +46 -0
  49. package/src/utils/runjsValue.ts +287 -0
  50. package/src/utils/safeGlobals.ts +55 -1
  51. package/src/utils/schema-utils.ts +79 -0
@@ -419,6 +419,8 @@ export declare class FlowStep {
419
419
  scene?: import("./types").ActionScene | import("./types").ActionScene[];
420
420
  paramsRequired?: boolean;
421
421
  hideInSettings?: boolean | ((ctx: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>) => boolean | Promise<boolean>);
422
+ disabledInSettings?: boolean | ((ctx: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>) => boolean | Promise<boolean>);
423
+ disabledReasonInSettings?: string | ((ctx: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>) => string | Promise<string>);
422
424
  defineProperties?: Record<string, import("./flowContext").PropertyOptions> | ((ctx: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>) => Record<string, import("./flowContext").PropertyOptions> | Promise<Record<string, import("./flowContext").PropertyOptions>>);
423
425
  defineMethods?: Record<string, (this: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>, ...args: any[]) => any> | ((ctx: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>) => Record<string, (this: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>, ...args: any[]) => any> | Promise<Record<string, (this: import("./flowContext").FlowRuntimeContext<import(".").FlowModel<import("./types").DefaultStructure>, any>, ...args: any[]) => any>>);
424
426
  };
package/lib/JSRunner.js CHANGED
@@ -35,6 +35,7 @@ const _JSRunner = class _JSRunner {
35
35
  globals;
36
36
  timeoutMs;
37
37
  constructor(options = {}) {
38
+ var _a, _b;
38
39
  const bindWindowFn = /* @__PURE__ */ __name((key) => {
39
40
  if (typeof window !== "undefined" && typeof window[key] === "function") {
40
41
  return window[key].bind(window);
@@ -42,13 +43,34 @@ const _JSRunner = class _JSRunner {
42
43
  const fn = globalThis[key];
43
44
  return typeof fn === "function" ? fn.bind(globalThis) : fn;
44
45
  }, "bindWindowFn");
46
+ const providedGlobals = options.globals || {};
47
+ const liftedGlobals = {};
48
+ if (!Object.prototype.hasOwnProperty.call(providedGlobals, "Blob")) {
49
+ try {
50
+ const blobCtor = (_a = providedGlobals.window) == null ? void 0 : _a.Blob;
51
+ if (typeof blobCtor !== "undefined") {
52
+ liftedGlobals.Blob = blobCtor;
53
+ }
54
+ } catch {
55
+ }
56
+ }
57
+ if (!Object.prototype.hasOwnProperty.call(providedGlobals, "URL")) {
58
+ try {
59
+ const urlCtor = (_b = providedGlobals.window) == null ? void 0 : _b.URL;
60
+ if (typeof urlCtor !== "undefined") {
61
+ liftedGlobals.URL = urlCtor;
62
+ }
63
+ } catch {
64
+ }
65
+ }
45
66
  this.globals = {
46
67
  console,
47
68
  setTimeout: bindWindowFn("setTimeout"),
48
69
  clearTimeout: bindWindowFn("clearTimeout"),
49
70
  setInterval: bindWindowFn("setInterval"),
50
71
  clearInterval: bindWindowFn("clearInterval"),
51
- ...options.globals || {}
72
+ ...liftedGlobals,
73
+ ...providedGlobals
52
74
  };
53
75
  this.timeoutMs = options.timeoutMs ?? 5e3;
54
76
  }
@@ -135,10 +135,19 @@ const componentMap = {
135
135
  const MenuLabelItem = /* @__PURE__ */ __name(({ title, uiMode, itemProps }) => {
136
136
  const type = (uiMode == null ? void 0 : uiMode.type) || uiMode;
137
137
  const Component = type ? componentMap[type] : null;
138
- if (!Component) {
139
- return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, title);
138
+ const disabled = !!(itemProps == null ? void 0 : itemProps.disabled);
139
+ const disabledReason = itemProps == null ? void 0 : itemProps.disabledReason;
140
+ const disabledIconColor = itemProps == null ? void 0 : itemProps.disabledIconColor;
141
+ const content = (() => {
142
+ if (!Component) {
143
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, title);
144
+ }
145
+ return /* @__PURE__ */ import_react.default.createElement(Component, { title, ...itemProps });
146
+ })();
147
+ if (!disabled) {
148
+ return content;
140
149
  }
141
- return /* @__PURE__ */ import_react.default.createElement(Component, { title, ...itemProps });
150
+ return /* @__PURE__ */ import_react.default.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } }, content, /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: disabledReason, placement: "right", destroyTooltipOnHide: true }, /* @__PURE__ */ import_react.default.createElement(import_icons.QuestionCircleOutlined, { style: { color: disabledIconColor } })));
142
151
  }, "MenuLabelItem");
143
152
  const DefaultSettingsIcon = /* @__PURE__ */ __name(({
144
153
  model,
@@ -150,9 +159,13 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
150
159
  }) => {
151
160
  const { message } = import_antd.App.useApp();
152
161
  const t = (0, import_react.useMemo)(() => (0, import_utils.getT)(model), [model]);
162
+ const { token } = import_antd.theme.useToken();
163
+ const disabledIconColor = (token == null ? void 0 : token.colorTextTertiary) || (token == null ? void 0 : token.colorTextDescription) || (token == null ? void 0 : token.colorTextSecondary);
153
164
  const [visible, setVisible] = (0, import_react.useState)(false);
154
165
  const [refreshTick, setRefreshTick] = (0, import_react.useState)(0);
155
166
  const [extraMenuItems, setExtraMenuItems] = (0, import_react.useState)([]);
167
+ const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = (0, import_react.useState)([]);
168
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
156
169
  const closeDropdown = (0, import_react.useCallback)(() => {
157
170
  setVisible(false);
158
171
  }, []);
@@ -203,7 +216,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
203
216
  return () => {
204
217
  mounted = false;
205
218
  };
206
- }, [model, menuLevels, t, refreshTick, visible, message]);
219
+ }, [model, menuLevels, t, refreshTick, visible]);
207
220
  const copyUidToClipboard = (0, import_react.useCallback)(
208
221
  async (uid) => {
209
222
  var _a;
@@ -309,6 +322,28 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
309
322
  },
310
323
  [closeDropdown, model, t]
311
324
  );
325
+ const isStepMenuItemDisabled = (0, import_react.useCallback)(
326
+ (key) => {
327
+ const cleanKey = key.includes("-") && /^(.+)-\d+$/.test(key) ? key.replace(/-\d+$/, "") : key;
328
+ const keys = cleanKey.split(":");
329
+ let modelKey;
330
+ let flowKey;
331
+ let stepKey;
332
+ if (keys.length === 3) {
333
+ [modelKey, flowKey, stepKey] = keys;
334
+ } else if (keys.length === 2) {
335
+ [flowKey, stepKey] = keys;
336
+ } else {
337
+ return false;
338
+ }
339
+ return configurableFlowsAndSteps.some(({ flow, steps, modelKey: flowModelKey }) => {
340
+ const sameModel = (flowModelKey || void 0) === modelKey;
341
+ if (!sameModel || flow.key !== flowKey) return false;
342
+ return steps.some((stepInfo) => stepInfo.stepKey === stepKey && !!stepInfo.disabled);
343
+ });
344
+ },
345
+ [configurableFlowsAndSteps]
346
+ );
312
347
  const handleMenuClick = (0, import_react.useCallback)(
313
348
  ({ key }) => {
314
349
  const originalKey = key;
@@ -324,6 +359,9 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
324
359
  extra.onClick();
325
360
  return;
326
361
  }
362
+ if (isStepMenuItemDisabled(cleanKey)) {
363
+ return;
364
+ }
327
365
  switch (cleanKey) {
328
366
  case "copy-uid":
329
367
  closeDropdown();
@@ -337,7 +375,15 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
337
375
  break;
338
376
  }
339
377
  },
340
- [closeDropdown, handleCopyUid, handleDelete, handleStepConfiguration, handleCopyPopupUid, extraMenuItems]
378
+ [
379
+ closeDropdown,
380
+ handleCopyUid,
381
+ handleDelete,
382
+ handleStepConfiguration,
383
+ handleCopyPopupUid,
384
+ extraMenuItems,
385
+ isStepMenuItemDisabled
386
+ ]
341
387
  );
342
388
  const getModelConfigurableFlowsAndSteps = (0, import_react.useCallback)(
343
389
  async (targetModel, modelKey) => {
@@ -355,6 +401,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
355
401
  if (await (0, import_utils.shouldHideStepInSettings)(targetModel, flow, actionStep)) {
356
402
  return null;
357
403
  }
404
+ const disabledState = await (0, import_utils.resolveStepDisabledInSettings)(targetModel, flow, actionStep);
358
405
  let uiMode = await (0, import_utils.resolveUiMode)(actionStep.uiMode, targetModel.context);
359
406
  const hasStepUiSchema = actionStep.uiSchema != null;
360
407
  let hasActionUiSchema = false;
@@ -392,7 +439,9 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
392
439
  title: t(stepTitle) || stepKey,
393
440
  modelKey,
394
441
  // 添加模型标识
395
- uiMode
442
+ uiMode,
443
+ disabled: disabledState.disabled,
444
+ disabledReason: disabledState.reason
396
445
  };
397
446
  })
398
447
  ).then((steps) => steps.filter(Boolean));
@@ -423,8 +472,6 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
423
472
  }
424
473
  return result;
425
474
  }, [model, menuLevels, getModelConfigurableFlowsAndSteps]);
426
- const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = (0, import_react.useState)([]);
427
- const [isLoading, setIsLoading] = (0, import_react.useState)(true);
428
475
  (0, import_react.useEffect)(() => {
429
476
  const triggerRebuild = /* @__PURE__ */ __name(() => {
430
477
  setRefreshTick((v) => v + 1);
@@ -517,11 +564,15 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
517
564
  }
518
565
  }, "onChange"),
519
566
  ...(uiMode == null ? void 0 : uiMode.props) || {},
520
- itemKey: uiMode == null ? void 0 : uiMode.key
567
+ itemKey: uiMode == null ? void 0 : uiMode.key,
568
+ disabled: !!stepInfo.disabled,
569
+ disabledReason: stepInfo.disabledReason,
570
+ disabledIconColor
521
571
  };
522
572
  items.push({
523
573
  key: uniqueKey,
524
- label: /* @__PURE__ */ import_react.default.createElement(MenuLabelItem, { title: t(stepInfo.title), uiMode, itemProps })
574
+ label: /* @__PURE__ */ import_react.default.createElement(MenuLabelItem, { title: stepInfo.title, uiMode, itemProps }),
575
+ disabled: !!stepInfo.disabled
525
576
  });
526
577
  });
527
578
  if (flow.options.divider === "bottom") {
@@ -555,7 +606,8 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
555
606
  const uniqueKey = generateUniqueKey(`${flow.key}:${stepInfo.stepKey}`);
556
607
  items.push({
557
608
  key: uniqueKey,
558
- label: t(stepInfo.title)
609
+ label: stepInfo.disabled ? /* @__PURE__ */ import_react.default.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } }, stepInfo.title, /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: stepInfo.disabledReason, placement: "right", destroyTooltipOnHide: true }, /* @__PURE__ */ import_react.default.createElement(import_icons.QuestionCircleOutlined, { style: { color: disabledIconColor } }))) : stepInfo.title,
610
+ disabled: !!stepInfo.disabled
559
611
  });
560
612
  });
561
613
  });
@@ -567,7 +619,8 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
567
619
  const uniqueKey = generateUniqueKey(`${modelKey}:${flow.key}:${stepInfo.stepKey}`);
568
620
  subMenuChildren.push({
569
621
  key: uniqueKey,
570
- label: t(stepInfo.title)
622
+ label: stepInfo.disabled ? /* @__PURE__ */ import_react.default.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } }, stepInfo.title, /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: stepInfo.disabledReason, placement: "right", destroyTooltipOnHide: true }, /* @__PURE__ */ import_react.default.createElement(import_icons.QuestionCircleOutlined, { style: { color: disabledIconColor } }))) : stepInfo.title,
623
+ disabled: !!stepInfo.disabled
571
624
  });
572
625
  });
573
626
  });
@@ -581,7 +634,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
581
634
  }
582
635
  }
583
636
  return items;
584
- }, [configurableFlowsAndSteps, flattenSubMenus, t]);
637
+ }, [configurableFlowsAndSteps, disabledIconColor, flattenSubMenus, t]);
585
638
  const finalMenuItems = (0, import_react.useMemo)(() => {
586
639
  const items = [...menuItems];
587
640
  const commonExtras = extraMenuItems.filter((it) => it.group === "common-actions").sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
@@ -61,6 +61,20 @@ const FlowsContextMenu = /* @__PURE__ */ __name((props) => {
61
61
  const FlowsContextMenuWithModel = (0, import_reactive.observer)(
62
62
  ({ model, children, enabled = true, position = "right", showDeleteButton = true }) => {
63
63
  const t = (0, import_utils.getT)(model);
64
+ const { token } = import_antd.theme.useToken();
65
+ const disabledIconColor = (token == null ? void 0 : token.colorTextTertiary) || (token == null ? void 0 : token.colorTextDescription) || (token == null ? void 0 : token.colorTextSecondary);
66
+ const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = (0, import_react.useState)([]);
67
+ const isStepMenuItemDisabled = (0, import_react.useCallback)(
68
+ (key) => {
69
+ const [flowKey, stepKey] = key.split(":");
70
+ if (!flowKey || !stepKey) return false;
71
+ return configurableFlowsAndSteps.some(({ flow, steps }) => {
72
+ if (flow.key !== flowKey) return false;
73
+ return steps.some((stepInfo) => stepInfo.stepKey === stepKey && !!stepInfo.disabled);
74
+ });
75
+ },
76
+ [configurableFlowsAndSteps]
77
+ );
64
78
  const handleMenuClick = (0, import_react.useCallback)(
65
79
  ({ key }) => {
66
80
  var _a;
@@ -86,6 +100,9 @@ const FlowsContextMenuWithModel = (0, import_reactive.observer)(
86
100
  }
87
101
  });
88
102
  } else {
103
+ if (isStepMenuItemDisabled(key)) {
104
+ return;
105
+ }
89
106
  const [flowKey, stepKey] = key.split(":");
90
107
  try {
91
108
  const flow = model.getFlow(flowKey);
@@ -108,7 +125,7 @@ const FlowsContextMenuWithModel = (0, import_reactive.observer)(
108
125
  }
109
126
  }
110
127
  },
111
- [model]
128
+ [isStepMenuItemDisabled, model]
112
129
  );
113
130
  if (!model) {
114
131
  return /* @__PURE__ */ import_react.default.createElement(import_antd.Alert, { message: "\u63D0\u4F9B\u7684\u6A21\u578B\u65E0\u6548", type: "error" });
@@ -129,6 +146,7 @@ const FlowsContextMenuWithModel = (0, import_reactive.observer)(
129
146
  if (await (0, import_utils.shouldHideStepInSettings)(model, flow, actionStep)) {
130
147
  return null;
131
148
  }
149
+ const disabledState = await (0, import_utils.resolveStepDisabledInSettings)(model, flow, actionStep);
132
150
  const stepUiSchema = actionStep.uiSchema || {};
133
151
  let actionUiSchema = {};
134
152
  if (actionStep.use) {
@@ -152,7 +170,9 @@ const FlowsContextMenuWithModel = (0, import_reactive.observer)(
152
170
  stepKey,
153
171
  step: actionStep,
154
172
  uiSchema: mergedUiSchema,
155
- title: actionStep.title || stepKey
173
+ title: actionStep.title || stepKey,
174
+ disabled: disabledState.disabled,
175
+ disabledReason: disabledState.reason
156
176
  };
157
177
  })
158
178
  ).then((steps) => steps.filter(Boolean));
@@ -165,7 +185,6 @@ const FlowsContextMenuWithModel = (0, import_reactive.observer)(
165
185
  return [];
166
186
  }
167
187
  }, [model]);
168
- const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = (0, import_react.useState)([]);
169
188
  (0, import_react.useEffect)(() => {
170
189
  let mounted = true;
171
190
  (async () => {
@@ -193,7 +212,8 @@ const FlowsContextMenuWithModel = (0, import_reactive.observer)(
193
212
  menuItems.push({
194
213
  key: `${flow.key}:${stepInfo.stepKey}`,
195
214
  icon: /* @__PURE__ */ import_react.default.createElement(import_icons.SettingOutlined, null),
196
- label: stepInfo.title
215
+ label: stepInfo.disabled ? /* @__PURE__ */ import_react.default.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } }, stepInfo.title, /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: stepInfo.disabledReason, placement: "right", destroyTooltipOnHide: true }, /* @__PURE__ */ import_react.default.createElement(import_icons.QuestionCircleOutlined, { style: { color: disabledIconColor } }))) : stepInfo.title,
216
+ disabled: !!stepInfo.disabled
197
217
  });
198
218
  });
199
219
  });
@@ -198,8 +198,7 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
198
198
  (0, import_react.useEffect)(() => {
199
199
  if (!resolvedMetaTreeNode) return;
200
200
  if (!Array.isArray(resolvedMetaTree) || innerValue == null) return;
201
- const finalValue = (resolveValueFromPath == null ? void 0 : resolveValueFromPath(resolvedMetaTreeNode)) || innerValue;
202
- emitChange(finalValue, resolvedMetaTreeNode);
201
+ emitChange(innerValue, resolvedMetaTreeNode);
203
202
  setCurrentMetaTreeNode(resolvedMetaTreeNode);
204
203
  }, [resolvedMetaTreeNode]);
205
204
  const composingRef = (0, import_react.useRef)(false);
@@ -59,53 +59,60 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
59
59
  const ctx = (0, import_FlowContextProvider.useFlowContext)();
60
60
  const { data: displayedValue } = (0, import_ahooks.useRequest)(
61
61
  async () => {
62
- if (metaTreeNode) {
63
- return metaTreeNode.parentTitles ? [...metaTreeNode.parentTitles, metaTreeNode.title].map(ctx.t).join("/") : ctx.t(metaTreeNode.title) || "";
64
- }
65
- if (!value) return String(value);
66
- const rawPath = (0, import_utils.parseValueToPath)(value);
67
- if (!rawPath || !Array.isArray(resolvedMetaTree)) {
68
- return Array.isArray(rawPath) ? rawPath.join("/") : String(value);
69
- }
70
- const topNames = new Set((resolvedMetaTree || []).map((n) => String(n == null ? void 0 : n.name)));
71
- const path = !topNames.has(String(rawPath[0])) ? rawPath.slice(1) : rawPath;
72
- if (!path.length) return "";
73
- let nodes = resolvedMetaTree;
74
- let deepest = null;
75
- let matchedCount = 0;
76
- for (let i = 0; i < path.length; i++) {
77
- if (!nodes) break;
78
- const seg = String(path[i]);
79
- const node = nodes.find((n) => String(n == null ? void 0 : n.name) === seg);
80
- if (!node) break;
81
- deepest = node;
82
- matchedCount = i + 1;
83
- if (i < path.length - 1) {
84
- if (Array.isArray(node.children)) {
85
- nodes = node.children;
86
- } else if (typeof node.children === "function") {
87
- try {
88
- const childNodes = await node.children();
89
- node.children = childNodes;
90
- nodes = childNodes;
91
- } catch {
62
+ const resolveLabelFromPath = /* @__PURE__ */ __name(async (rawPath2) => {
63
+ if (!rawPath2) return null;
64
+ if (!Array.isArray(rawPath2)) return null;
65
+ if (!Array.isArray(resolvedMetaTree)) return null;
66
+ const topNames = new Set((resolvedMetaTree || []).map((n) => String(n == null ? void 0 : n.name)));
67
+ const path = !topNames.has(String(rawPath2[0])) ? rawPath2.slice(1) : rawPath2;
68
+ if (!path.length) return "";
69
+ let nodes = resolvedMetaTree;
70
+ const titleChain = [];
71
+ let matchedCount = 0;
72
+ for (let i = 0; i < path.length; i++) {
73
+ if (!nodes) break;
74
+ const seg = String(path[i]);
75
+ const node = nodes.find((n) => String(n == null ? void 0 : n.name) === seg);
76
+ if (!node) break;
77
+ titleChain.push(String(node.title ?? node.name ?? seg));
78
+ matchedCount = i + 1;
79
+ if (i < path.length - 1) {
80
+ if (Array.isArray(node.children)) {
81
+ nodes = node.children;
82
+ } else if (typeof node.children === "function") {
83
+ try {
84
+ const childNodes = await node.children();
85
+ node.children = childNodes;
86
+ nodes = childNodes;
87
+ } catch {
88
+ nodes = void 0;
89
+ }
90
+ } else {
92
91
  nodes = void 0;
93
92
  }
94
- } else {
95
- nodes = void 0;
96
93
  }
97
94
  }
98
- }
99
- if (deepest) {
100
- const titles = deepest.parentTitles ? [...deepest.parentTitles, deepest.title] : [deepest.title];
101
- let label = titles.map(ctx.t).join("/");
95
+ if (matchedCount === 0) return null;
96
+ let label2 = titleChain.map(ctx.t).join("/");
102
97
  if (matchedCount < path.length) {
103
98
  const tail = path.slice(matchedCount).join("/");
104
- label = tail ? `${label}/${tail}` : label;
99
+ label2 = tail ? `${label2}/${tail}` : label2;
105
100
  }
106
- return label;
101
+ return label2;
102
+ }, "resolveLabelFromPath");
103
+ if (metaTreeNode == null ? void 0 : metaTreeNode.parentTitles) {
104
+ return [...metaTreeNode.parentTitles, metaTreeNode.title].map(ctx.t).join("/");
105
+ }
106
+ if (metaTreeNode) {
107
+ const rawPath2 = (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
108
+ const label2 = await resolveLabelFromPath(rawPath2);
109
+ return label2 ?? ctx.t(metaTreeNode.title) ?? "";
107
110
  }
108
- return path.join("/");
111
+ if (!value) return String(value);
112
+ const rawPath = (0, import_utils.parseValueToPath)(value);
113
+ const label = await resolveLabelFromPath(rawPath);
114
+ if (label != null) return label;
115
+ return Array.isArray(rawPath) ? rawPath.join("/") : String(value);
109
116
  },
110
117
  { refreshDeps: [resolvedMetaTree, value, metaTreeNode] }
111
118
  );
@@ -118,7 +118,7 @@ const _DataSource = class _DataSource {
118
118
  return this.dataSourceManager.flowEngine;
119
119
  }
120
120
  get displayName() {
121
- return this.options.displayName ? this.flowEngine.translate(this.options.displayName) : this.key;
121
+ return this.flowEngine.translate(this.options.displayName, { ns: "lm-collections" }) || this.key;
122
122
  }
123
123
  get key() {
124
124
  return this.options.key;
@@ -457,7 +457,7 @@ const _Collection = class _Collection {
457
457
  return this.options.storage || "local";
458
458
  }
459
459
  get title() {
460
- return this.options.title ? this.flowEngine.translate(this.options.title) : this.name;
460
+ return this.flowEngine.translate(this.options.title, { ns: "lm-collections" }) || this.name;
461
461
  }
462
462
  get titleCollectionField() {
463
463
  const titleFieldName = this.options.titleField || this.filterTargetKey;
@@ -692,8 +692,8 @@ const _CollectionField = class _CollectionField {
692
692
  }
693
693
  get title() {
694
694
  var _a, _b, _c;
695
- const titleValue = ((_b = (_a = this.options) == null ? void 0 : _a.uiSchema) == null ? void 0 : _b.title) || ((_c = this.options) == null ? void 0 : _c.title) || this.options.name;
696
- return this.flowEngine.translate(titleValue);
695
+ const titleValue = ((_b = (_a = this.options) == null ? void 0 : _a.uiSchema) == null ? void 0 : _b.title) || ((_c = this.options) == null ? void 0 : _c.title);
696
+ return this.flowEngine.translate(titleValue, { ns: "lm-collections" }) || this.options.name;
697
697
  }
698
698
  set title(value) {
699
699
  this.options.title = value;
@@ -711,11 +711,17 @@ const _CollectionField = class _CollectionField {
711
711
  }
712
712
  return {
713
713
  ...v,
714
+ label: v.label ? this.flowEngine.translate(v.label, { ns: "lm-collections" }) : v.label,
714
715
  value: Number(v.value)
715
716
  };
716
717
  });
717
718
  }
718
- return options;
719
+ return options.map((v) => {
720
+ return {
721
+ ...v,
722
+ label: this.flowEngine.translate(v.label, { ns: "lm-collections" })
723
+ };
724
+ });
719
725
  }
720
726
  get defaultValue() {
721
727
  return this.options.defaultValue == null ? void 0 : this.options.defaultValue;
@@ -830,7 +830,8 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
830
830
  value: this.engine
831
831
  });
832
832
  this.defineProperty("sql", {
833
- get: /* @__PURE__ */ __name(() => new import_resources.FlowSQLRepository(this), "get")
833
+ get: /* @__PURE__ */ __name((ctx) => new import_resources.FlowSQLRepository(ctx), "get"),
834
+ cache: false
834
835
  });
835
836
  this.defineProperty("dataSourceManager", {
836
837
  value: dataSourceManager
@@ -1120,6 +1121,9 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
1120
1121
  const modelClass = (0, import_registry.getModelClassName)(this);
1121
1122
  const Ctor = import_registry.RunJSContextRegistry.resolve(version, modelClass) || FlowRunJSContext;
1122
1123
  const runCtx = new Ctor(this);
1124
+ runCtx.defineMethod("t", (key, options2) => {
1125
+ return this.t(key, { ns: "runjs", ...options2 });
1126
+ });
1123
1127
  const globals = { ctx: runCtx, ...(options == null ? void 0 : options.globals) || {} };
1124
1128
  const { timeoutMs } = options || {};
1125
1129
  return new import_JSRunner.JSRunner({ globals, timeoutMs });
package/lib/flowI18n.js CHANGED
@@ -56,11 +56,13 @@ const _FlowI18n = class _FlowI18n {
56
56
  if (!keyOrTemplate || typeof keyOrTemplate !== "string") {
57
57
  return keyOrTemplate;
58
58
  }
59
- let result = this.translateKey(keyOrTemplate, options);
60
- if (this.isTemplate(result)) {
61
- result = this.compileTemplate(result);
59
+ if ((options == null ? void 0 : options.compareWith) && keyOrTemplate === options.compareWith) {
60
+ return keyOrTemplate;
61
+ }
62
+ if (this.isTemplate(keyOrTemplate)) {
63
+ return this.compileTemplate(keyOrTemplate);
62
64
  }
63
- return result;
65
+ return this.translateKey(keyOrTemplate, options);
64
66
  }
65
67
  /**
66
68
  * 内部翻译方法
@@ -62,6 +62,7 @@
62
62
  "This is likely a NocoBase internals bug. Please open an issue at": "This is likely a NocoBase internals bug. Please open an issue at",
63
63
  "This step has no configurable parameters": "This step has no configurable parameters",
64
64
  "This variable is not available": "This variable is not available",
65
+ "Use return to output value": "Use return to output value",
65
66
  "Try again": "Try again",
66
67
  "Template created": "Template created",
67
68
  "Template description": "Template description",
@@ -70,4 +71,4 @@
70
71
  "UID copied to clipboard": "UID copied to clipboard",
71
72
  "createModelOptions must specify use property": "createModelOptions must specify \"use\" property",
72
73
  "here": "here"
73
- }
74
+ }
@@ -71,6 +71,7 @@ export declare const locales: {
71
71
  "This is likely a NocoBase internals bug. Please open an issue at": string;
72
72
  "This step has no configurable parameters": string;
73
73
  "This variable is not available": string;
74
+ "Use return to output value": string;
74
75
  "Try again": string;
75
76
  "Template created": string;
76
77
  "Template description": string;
@@ -148,6 +149,7 @@ export declare const locales: {
148
149
  "This is likely a NocoBase internals bug. Please open an issue at": string;
149
150
  "This step has no configurable parameters": string;
150
151
  "This variable is not available": string;
152
+ "Use return to output value": string;
151
153
  "Try again": string;
152
154
  "UID copied to clipboard": string;
153
155
  "createModelOptions must specify use property": string;
@@ -66,6 +66,7 @@
66
66
  "This is likely a NocoBase internals bug. Please open an issue at": "这可能是 NocoBase 内部错误。请在以下地址提交问题",
67
67
  "This step has no configurable parameters": "此步骤没有可配置的参数",
68
68
  "This variable is not available": "此变量不可用",
69
+ "Use return to output value": "使用 return 返回最终值",
69
70
  "Try again": "重试",
70
71
  "UID copied to clipboard": "UID 已复制到剪贴板",
71
72
  "createModelOptions must specify use property": "createModelOptions 必须指定 \"use\" 属性",
@@ -6,7 +6,7 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { FlowEngineContext } from '../flowContext';
9
+ import { FlowContext, FlowEngineContext } from '../flowContext';
10
10
  import { BaseRecordResource } from './baseRecordResource';
11
11
  type SQLRunOptions = {
12
12
  bind?: Record<string, any>;
@@ -20,8 +20,8 @@ type SQLSaveOptions = {
20
20
  dataSourceKey?: string;
21
21
  };
22
22
  export declare class FlowSQLRepository {
23
- protected ctx: FlowEngineContext;
24
- constructor(ctx: FlowEngineContext);
23
+ protected ctx: FlowContext;
24
+ constructor(ctx: FlowContext);
25
25
  run(sql: string, options?: SQLRunOptions): Promise<any>;
26
26
  save(data: SQLSaveOptions): Promise<void>;
27
27
  runById(uid: string, options?: SQLRunOptions): Promise<any>;
package/lib/types.d.ts CHANGED
@@ -147,6 +147,18 @@ export interface ActionDefinition<TModel extends FlowModel = FlowModel, TCtx ext
147
147
  * - StepDefinition.hideInSettings can override the ActionDefinition value.
148
148
  */
149
149
  hideInSettings?: boolean | ((ctx: TCtx) => boolean | Promise<boolean>);
150
+ /**
151
+ * Whether to disable this step/action in settings menus.
152
+ * - Supports static boolean and dynamic decision based on runtime context.
153
+ * - StepDefinition.disabledInSettings can override the ActionDefinition value.
154
+ */
155
+ disabledInSettings?: boolean | ((ctx: TCtx) => boolean | Promise<boolean>);
156
+ /**
157
+ * Optional reason shown when this step/action is disabled in settings menus.
158
+ * - Supports static string and dynamic resolver based on runtime context.
159
+ * - StepDefinition.disabledReasonInSettings can override the ActionDefinition value.
160
+ */
161
+ disabledReasonInSettings?: string | ((ctx: TCtx) => string | Promise<string>);
150
162
  /**
151
163
  * 在执行 Action 前为 ctx 定义临时属性。
152
164
  * - 仅支持 PropertyOptions 形态(例如:{ foo: { value: 5 } });
@@ -9,7 +9,7 @@
9
9
  import type { Collection } from '../data-source';
10
10
  import type { FlowContext, PropertyMetaFactory } from '../flowContext';
11
11
  /**
12
- * 创建一个用于“对象类变量”(如 formValues / currentObject)的 `resolveOnServer` 判定函数。
12
+ * 创建一个用于“对象类变量”(如 formValues / item)的 `resolveOnServer` 判定函数。
13
13
  * 仅当访问路径以“关联字段名”开头(且继续访问其子属性)时,返回 true 交由服务端解析;
14
14
  * 否则在前端解析即可。
15
15
  *
@@ -26,7 +26,7 @@ export declare function createAssociationSubpathResolver(collectionAccessor: ()
26
26
  *
27
27
  * @param collectionAccessor 获取集合对象,用于字段/元信息来源
28
28
  * @param title 变量组标题(用于 UI 展示)
29
- * @param valueAccessor 获取当前对象值(如 ctx.form.getFieldsValue() / ctx.currentObject
29
+ * @param valueAccessor 获取当前对象值(如 ctx.form.getFieldsValue() / ctx.item
30
30
  * @returns PropertyMetaFactory
31
31
  */
32
32
  export declare function createAssociationAwareObjectMetaFactory(collectionAccessor: () => Collection | null, title: string, valueAccessor: (ctx: FlowContext) => any): PropertyMetaFactory;
@@ -12,7 +12,7 @@ export { FlowExitException } from './exceptions';
12
12
  export { defineAction } from './flow-definitions';
13
13
  export { isInheritedFrom } from './inheritance';
14
14
  export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
15
- export { compileUiSchema, resolveStepUiSchema, resolveUiMode, shouldHideStepInSettings } from './schema-utils';
15
+ export { compileUiSchema, resolveStepUiSchema, resolveStepDisabledInSettings, resolveUiMode, shouldHideStepInSettings, } from './schema-utils';
16
16
  export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
17
17
  export { createCollectionContextMeta } from './createCollectionContextMeta';
18
18
  export { createAssociationAwareObjectMetaFactory, createAssociationSubpathResolver } from './associationObjectVariable';
@@ -20,7 +20,9 @@ export { buildRecordMeta, collectContextParamsForTemplate, createCurrentRecordMe
20
20
  export { extractPropertyPath, formatPathToVariable, isVariableExpression } from './context';
21
21
  export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
22
22
  export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
23
- export { createSafeDocument, createSafeWindow, createSafeNavigator } from './safeGlobals';
23
+ export { createSafeDocument, createSafeWindow, createSafeNavigator, createSafeRunJSGlobals, runjsWithSafeGlobals, } from './safeGlobals';
24
+ export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
25
+ export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
24
26
  export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
25
27
  export { createEphemeralContext } from './createEphemeralContext';
26
28
  export { pruneFilter } from './pruneFilter';