@nocobase/flow-engine 2.2.0-beta.1 → 2.2.0-beta.10

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 (85) hide show
  1. package/lib/JSRunner.d.ts +1 -0
  2. package/lib/JSRunner.js +110 -20
  3. package/lib/components/FieldModelRenderer.js +44 -31
  4. package/lib/components/MobilePopup.js +28 -10
  5. package/lib/components/MobilePopup.style.js +27 -6
  6. package/lib/components/dnd/index.js +9 -2
  7. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +86 -32
  8. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +20 -0
  9. package/lib/flowContext.d.ts +1 -1
  10. package/lib/flowContext.js +97 -42
  11. package/lib/flowEngine.js +6 -0
  12. package/lib/flowSettings.d.ts +5 -1
  13. package/lib/flowSettings.js +70 -0
  14. package/lib/locale/en-US.json +1 -0
  15. package/lib/locale/index.d.ts +2 -0
  16. package/lib/locale/zh-CN.json +1 -0
  17. package/lib/resources/apiResource.js +2 -1
  18. package/lib/resources/baseRecordResource.js +6 -17
  19. package/lib/resources/multiRecordResource.js +13 -3
  20. package/lib/resources/singleRecordResource.js +7 -2
  21. package/lib/runjs-context/helpers.js +12 -5
  22. package/lib/types.d.ts +12 -0
  23. package/lib/utils/dataSourceDirty.d.ts +20 -0
  24. package/lib/utils/dataSourceDirty.js +139 -0
  25. package/lib/utils/dirtyAwareApiClient.d.ts +11 -0
  26. package/lib/utils/dirtyAwareApiClient.js +378 -0
  27. package/lib/utils/index.d.ts +1 -1
  28. package/lib/utils/index.js +11 -11
  29. package/lib/utils/loadedPageCache.d.ts +1 -0
  30. package/lib/utils/loadedPageCache.js +6 -0
  31. package/lib/utils/openViewRouteState.d.ts +28 -0
  32. package/lib/utils/openViewRouteState.js +125 -0
  33. package/lib/utils/parsePathnameToViewParams.d.ts +3 -0
  34. package/lib/utils/parsePathnameToViewParams.js +18 -1
  35. package/lib/utils/resolveRunJSObjectValues.js +3 -2
  36. package/lib/utils/runjsModuleLoader.js +0 -30
  37. package/lib/views/ViewNavigation.js +5 -0
  38. package/package.json +4 -4
  39. package/src/JSRunner.ts +112 -25
  40. package/src/__tests__/JSRunner.test.ts +4 -5
  41. package/src/__tests__/flowContext.test.ts +154 -0
  42. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +51 -0
  43. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  44. package/src/__tests__/flowSettings.test.ts +72 -0
  45. package/src/__tests__/runjsRuntimeFeatures.test.ts +15 -2
  46. package/src/__tests__/viewScopedFlowEngine.test.ts +72 -6
  47. package/src/components/FieldModelRenderer.tsx +50 -36
  48. package/src/components/MobilePopup.style.ts +34 -7
  49. package/src/components/MobilePopup.tsx +30 -10
  50. package/src/components/__tests__/FieldModelRenderer.test.tsx +165 -0
  51. package/src/components/__tests__/MobilePopup.style.test.tsx +103 -0
  52. package/src/components/__tests__/MobilePopup.test.tsx +109 -0
  53. package/src/components/dnd/index.tsx +11 -2
  54. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +105 -35
  55. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +381 -12
  56. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +28 -0
  57. package/src/flowContext.ts +104 -36
  58. package/src/flowEngine.ts +6 -0
  59. package/src/flowSettings.ts +85 -1
  60. package/src/locale/en-US.json +1 -0
  61. package/src/locale/zh-CN.json +1 -0
  62. package/src/resources/apiResource.ts +2 -1
  63. package/src/resources/baseRecordResource.ts +6 -23
  64. package/src/resources/multiRecordResource.ts +13 -3
  65. package/src/resources/singleRecordResource.ts +6 -1
  66. package/src/runjs-context/helpers.ts +12 -6
  67. package/src/types.ts +14 -0
  68. package/src/utils/__tests__/dirtyAwareApiClient.test.ts +392 -0
  69. package/src/utils/__tests__/openViewRouteState.test.ts +40 -0
  70. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +36 -0
  71. package/src/utils/dataSourceDirty.ts +126 -0
  72. package/src/utils/dirtyAwareApiClient.ts +430 -0
  73. package/src/utils/index.ts +10 -9
  74. package/src/utils/loadedPageCache.ts +7 -0
  75. package/src/utils/openViewRouteState.ts +107 -0
  76. package/src/utils/parsePathnameToViewParams.ts +23 -1
  77. package/src/utils/resolveRunJSObjectValues.ts +5 -2
  78. package/src/utils/runjsModuleLoader.ts +0 -32
  79. package/src/views/ViewNavigation.ts +6 -1
  80. package/src/views/__tests__/ViewNavigation.test.ts +15 -0
  81. package/lib/utils/safeGlobals.d.ts +0 -28
  82. package/lib/utils/safeGlobals.js +0 -367
  83. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +0 -38
  84. package/src/utils/__tests__/safeGlobals.test.ts +0 -106
  85. package/src/utils/safeGlobals.ts +0 -406
@@ -75,6 +75,7 @@ var import_utils = require("./utils");
75
75
  var import_exceptions = require("./utils/exceptions");
76
76
  var import_params_resolvers = require("./utils/params-resolvers");
77
77
  var import_serverContextParams = require("./utils/serverContextParams");
78
+ var import_dirtyAwareApiClient = require("./utils/dirtyAwareApiClient");
78
79
  var import_variablesParams = require("./utils/variablesParams");
79
80
  var import_registry = require("./runjs-context/registry");
80
81
  var import_createEphemeralContext = require("./utils/createEphemeralContext");
@@ -1899,15 +1900,16 @@ const _FlowContext = class _FlowContext {
1899
1900
  const options = this._props[key];
1900
1901
  if (!options) return void 0;
1901
1902
  if ("value" in options) {
1902
- return options.value;
1903
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(options.value, currentContext) : options.value;
1903
1904
  }
1904
1905
  if (options.get) {
1905
1906
  if (options.cache === false) {
1906
- return options.get(currentContext);
1907
+ const value = options.get(currentContext);
1908
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(value, currentContext) : value;
1907
1909
  }
1908
1910
  const cacheKey = options.observable ? "_observableCache" : "_cache";
1909
1911
  if (key in this[cacheKey]) {
1910
- return this[cacheKey][key];
1912
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(this[cacheKey][key], currentContext) : this[cacheKey][key];
1911
1913
  }
1912
1914
  if (this._pending[key]) return this._pending[key];
1913
1915
  const result = options.get(this.createProxy());
@@ -1917,7 +1919,7 @@ const _FlowContext = class _FlowContext {
1917
1919
  (v) => {
1918
1920
  this[cacheKey][key] = v;
1919
1921
  delete this._pending[key];
1920
- return v;
1922
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(v, currentContext) : v;
1921
1923
  },
1922
1924
  (err) => {
1923
1925
  delete this._pending[key];
@@ -1927,7 +1929,7 @@ const _FlowContext = class _FlowContext {
1927
1929
  return this._pending[key];
1928
1930
  }
1929
1931
  this[cacheKey][key] = result;
1930
- return result;
1932
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(result, currentContext) : result;
1931
1933
  }
1932
1934
  return void 0;
1933
1935
  }
@@ -2251,7 +2253,7 @@ const _BaseFlowEngineContext = class _BaseFlowEngineContext extends FlowContext
2251
2253
  this.defineMethod("getModel", (modelName, searchInPreviousEngines) => {
2252
2254
  return this.engine.getModel(modelName, searchInPreviousEngines);
2253
2255
  });
2254
- this.defineMethod("request", (options) => {
2256
+ this.defineMethod("request", function(options) {
2255
2257
  const app = this.app;
2256
2258
  if (typeof (options == null ? void 0 : options.url) === "string" && shouldBypassApiClient(options.url, app)) {
2257
2259
  return import_axios.default.request(options);
@@ -2294,6 +2296,36 @@ const _BaseFlowModelContext = class _BaseFlowModelContext extends BaseFlowEngine
2294
2296
  };
2295
2297
  __name(_BaseFlowModelContext, "BaseFlowModelContext");
2296
2298
  let BaseFlowModelContext = _BaseFlowModelContext;
2299
+ const OPEN_VIEW_INHERITED_INPUT_ARG_KEYS = [
2300
+ "dataSourceKey",
2301
+ "collectionName",
2302
+ "associationName",
2303
+ "filterByTk",
2304
+ "sourceId",
2305
+ "tabUid"
2306
+ ];
2307
+ function pickDefinedKeys(source, keys) {
2308
+ const res = {};
2309
+ for (const key of keys) {
2310
+ if (typeof (source == null ? void 0 : source[key]) !== "undefined") {
2311
+ res[key] = source[key];
2312
+ }
2313
+ }
2314
+ return res;
2315
+ }
2316
+ __name(pickDefinedKeys, "pickDefinedKeys");
2317
+ function pickDefinedOpenViewInputArgs(source) {
2318
+ return pickDefinedKeys(source, OPEN_VIEW_INHERITED_INPUT_ARG_KEYS);
2319
+ }
2320
+ __name(pickDefinedOpenViewInputArgs, "pickDefinedOpenViewInputArgs");
2321
+ function applyDefinedDefaults(target, defaults) {
2322
+ for (const [key, value] of Object.entries(defaults)) {
2323
+ if (typeof target[key] === "undefined") {
2324
+ target[key] = value;
2325
+ }
2326
+ }
2327
+ }
2328
+ __name(applyDefinedDefaults, "applyDefinedDefaults");
2297
2329
  const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContext {
2298
2330
  // public dataSourceManager: DataSourceManager;
2299
2331
  constructor(engine) {
@@ -2525,8 +2557,16 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2525
2557
  });
2526
2558
  this.defineProperty("role", {
2527
2559
  get: /* @__PURE__ */ __name(() => {
2528
- var _a, _b;
2529
- return (_b = (_a = this.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.role;
2560
+ var _a, _b, _c;
2561
+ const currentRole = (_b = (_a = this.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.role;
2562
+ if (currentRole !== "__union__") {
2563
+ return currentRole;
2564
+ }
2565
+ const roles = (_c = this.user) == null ? void 0 : _c.roles;
2566
+ if (!Array.isArray(roles)) {
2567
+ return [];
2568
+ }
2569
+ return roles.map((role) => role == null ? void 0 : role.name).filter((name) => !!name);
2530
2570
  }, "get"),
2531
2571
  cache: false,
2532
2572
  // 注意:使用惰性 meta 工厂,避免在 i18n 尚未注入时提前求值导致无法翻译
@@ -2586,12 +2626,16 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2586
2626
  }, "get")
2587
2627
  });
2588
2628
  this.defineProperty("auth", {
2589
- get: /* @__PURE__ */ __name(() => ({
2590
- roleName: this.api.auth.role,
2591
- locale: this.api.auth.locale,
2592
- token: this.api.auth.token,
2593
- user: this.user
2594
- }), "get")
2629
+ get: /* @__PURE__ */ __name(() => {
2630
+ var _a, _b, _c, _d, _e, _f;
2631
+ return {
2632
+ roleName: (_b = (_a = this.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.role,
2633
+ locale: (_d = (_c = this.api) == null ? void 0 : _c.auth) == null ? void 0 : _d.locale,
2634
+ token: (_f = (_e = this.api) == null ? void 0 : _e.auth) == null ? void 0 : _f.token,
2635
+ user: this.user
2636
+ };
2637
+ }, "get"),
2638
+ cache: false
2595
2639
  });
2596
2640
  this.defineProperty("date", {
2597
2641
  get: /* @__PURE__ */ __name(() => {
@@ -2680,7 +2724,17 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2680
2724
  doc = {};
2681
2725
  }
2682
2726
  const deprecatedCtx = createRunJSDeprecationProxy(runCtx, { doc });
2683
- const globals = { ctx: deprecatedCtx, ...(options == null ? void 0 : options.globals) || {} };
2727
+ const browserGlobals = {};
2728
+ if (typeof window !== "undefined") {
2729
+ browserGlobals.window = window;
2730
+ if (typeof navigator !== "undefined") {
2731
+ browserGlobals.navigator = navigator;
2732
+ }
2733
+ }
2734
+ if (typeof document !== "undefined") {
2735
+ browserGlobals.document = document;
2736
+ }
2737
+ const globals = { ctx: deprecatedCtx, ...browserGlobals, ...(options == null ? void 0 : options.globals) || {} };
2684
2738
  const { timeoutMs } = options || {};
2685
2739
  return new import_JSRunner.JSRunner({ globals, timeoutMs });
2686
2740
  });
@@ -2791,23 +2845,19 @@ const _FlowModelContext = class _FlowModelContext extends BaseFlowModelContext {
2791
2845
  }
2792
2846
  });
2793
2847
  this.defineMethod("openView", async function(uid, options) {
2794
- var _a, _b, _c, _d, _e;
2795
- const opts = { ...options };
2848
+ var _a, _b, _c, _d;
2849
+ const inheritedInputArgs = {
2850
+ ...typeof ((_a = this.model) == null ? void 0 : _a["getInputArgs"]) === "function" ? pickDefinedOpenViewInputArgs(this.model["getInputArgs"]()) : {},
2851
+ ...pickDefinedOpenViewInputArgs(this.inputArgs)
2852
+ };
2853
+ const opts = { ...options || {} };
2854
+ applyDefinedDefaults(opts, inheritedInputArgs);
2796
2855
  if (opts.defineProperties || opts.defineMethods) {
2797
2856
  opts.navigation = false;
2798
2857
  }
2799
2858
  let model2 = null;
2800
2859
  model2 = await this.engine.loadModel({ uid });
2801
2860
  if (!model2) {
2802
- const pickDefined = /* @__PURE__ */ __name((src, keys) => {
2803
- const res = {};
2804
- for (const k of keys) {
2805
- if (typeof (src == null ? void 0 : src[k]) !== "undefined") {
2806
- res[k] = src[k];
2807
- }
2808
- }
2809
- return res;
2810
- }, "pickDefined");
2811
2861
  model2 = this.engine.createModel({
2812
2862
  uid,
2813
2863
  // 注意: 新建的 model 应该使用 ${parentModel.uid}-xxx 形式的 uid
@@ -2819,7 +2869,7 @@ const _FlowModelContext = class _FlowModelContext extends BaseFlowModelContext {
2819
2869
  popupSettings: {
2820
2870
  openView: {
2821
2871
  // 仅在创建时持久化一份默认配置;运行时以本次 opts 为准,避免多个 opener 互相覆盖。
2822
- ...pickDefined(opts, ["dataSourceKey", "collectionName", "associationName", "mode", "size"])
2872
+ ...pickDefinedKeys(opts, ["dataSourceKey", "collectionName", "associationName", "mode", "size"])
2823
2873
  }
2824
2874
  }
2825
2875
  }
@@ -2827,12 +2877,10 @@ const _FlowModelContext = class _FlowModelContext extends BaseFlowModelContext {
2827
2877
  await model2.save();
2828
2878
  }
2829
2879
  model2.setParent(this.model);
2830
- const viewUid = (opts == null ? void 0 : opts.routeViewUid) ?? (opts == null ? void 0 : opts.viewUid) ?? (((_b = (_a = model2.stepParams) == null ? void 0 : _a.popupSettings) == null ? void 0 : _b.openView) ? model2.uid : this.model.uid);
2880
+ const viewUid = (opts == null ? void 0 : opts.routeViewUid) ?? (opts == null ? void 0 : opts.viewUid) ?? (((_c = (_b = model2.stepParams) == null ? void 0 : _b.popupSettings) == null ? void 0 : _c.openView) ? model2.uid : this.model.uid);
2831
2881
  const parentView = this.view;
2832
2882
  const pendingType = (opts == null ? void 0 : opts.isMobileLayout) ? "embed" : (opts == null ? void 0 : opts.mode) || "drawer";
2833
2883
  const pendingInputArgs = { ...opts, viewUid, navigation: opts.navigation };
2834
- pendingInputArgs.filterByTk = pendingInputArgs.filterByTk || ((_c = this.inputArgs) == null ? void 0 : _c.filterByTk);
2835
- pendingInputArgs.sourceId = pendingInputArgs.sourceId || ((_d = this.inputArgs) == null ? void 0 : _d.sourceId);
2836
2884
  const pendingView = {
2837
2885
  type: pendingType,
2838
2886
  inputArgs: pendingInputArgs,
@@ -2841,7 +2889,7 @@ const _FlowModelContext = class _FlowModelContext extends BaseFlowModelContext {
2841
2889
  engineCtx: this.engine.context
2842
2890
  };
2843
2891
  model2.context.defineProperty("view", { value: pendingView });
2844
- const popupFlow = (_e = model2.getFlow) == null ? void 0 : _e.call(model2, "popupSettings");
2892
+ const popupFlow = (_d = model2.getFlow) == null ? void 0 : _d.call(model2, "popupSettings");
2845
2893
  const on = popupFlow == null ? void 0 : popupFlow.on;
2846
2894
  let openEventName = "click";
2847
2895
  if (typeof on === "string" && on) {
@@ -2849,17 +2897,10 @@ const _FlowModelContext = class _FlowModelContext extends BaseFlowModelContext {
2849
2897
  } else if (on && typeof on === "object" && typeof on.eventName === "string" && on.eventName) {
2850
2898
  openEventName = on.eventName;
2851
2899
  }
2852
- await model2.dispatchEvent(
2853
- openEventName,
2854
- {
2855
- // navigation: false, // TODO: 路由模式有bug,不支持多层同样viewId的弹窗,因此这里默认先用false
2856
- // ...this.model?.['getInputArgs']?.(), // 避免部分关系字段信息丢失, 仿照 ClickableCollectionField 做法
2857
- ...opts
2858
- },
2859
- {
2860
- debounce: true
2861
- }
2862
- );
2900
+ await model2.dispatchEvent(openEventName, {
2901
+ // navigation: false, // TODO: 路由模式有bug,不支持多层同样viewId的弹窗,因此这里默认先用false
2902
+ ...opts
2903
+ });
2863
2904
  });
2864
2905
  this.defineMethod("getEvents", function() {
2865
2906
  return this.model.getEvents();
@@ -3475,6 +3516,20 @@ const _FlowRunJSContext = class _FlowRunJSContext extends FlowContext {
3475
3516
  ReactDOMShim.__nbRunjsInternalShim = true;
3476
3517
  this.defineProperty("ReactDOM", { value: ReactDOMShim });
3477
3518
  (0, import_runjsLibs.setupRunJSLibs)(this);
3519
+ this.defineMethod("openView", async function(uid, options) {
3520
+ const delegateOpenView = delegate.openView;
3521
+ if (typeof delegateOpenView !== "function") {
3522
+ throw new Error("ctx.openView is not available in current context.");
3523
+ }
3524
+ const routeState = (0, import_utils.createOpenViewRouteState)(options);
3525
+ if (!routeState) {
3526
+ return delegateOpenView(uid, options);
3527
+ }
3528
+ return delegateOpenView(uid, {
3529
+ ...options || {},
3530
+ [import_utils.RUNJS_OPEN_VIEW_ROUTE_STATE]: routeState
3531
+ });
3532
+ });
3478
3533
  this.defineMethod(
3479
3534
  "render",
3480
3535
  function(vnode, container) {
package/lib/flowEngine.js CHANGED
@@ -1141,6 +1141,9 @@ const _FlowEngine = class _FlowEngine {
1141
1141
  if (!this.ensureModelRepository()) return;
1142
1142
  const refresh = !!(options == null ? void 0 : options.refresh);
1143
1143
  const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled);
1144
+ if (this.context.flowSettingsEnabled) {
1145
+ this._loadedPageCache.markDirtyForOptions(options);
1146
+ }
1144
1147
  if (!refresh && !bypassLoadedPageCache) {
1145
1148
  const model2 = this.findModelByParentId(options.parentId, options.subKey);
1146
1149
  if (model2) {
@@ -1201,6 +1204,9 @@ const _FlowEngine = class _FlowEngine {
1201
1204
  if (!this.ensureModelRepository()) return;
1202
1205
  const { uid, parentId, subKey } = options;
1203
1206
  const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled);
1207
+ if (this.context.flowSettingsEnabled) {
1208
+ this._loadedPageCache.markDirtyForOptions(options);
1209
+ }
1204
1210
  if (uid && !bypassLoadedPageCache && this._modelInstances.has(uid)) {
1205
1211
  return this._modelInstances.get(uid);
1206
1212
  }
@@ -9,7 +9,7 @@
9
9
  import React from 'react';
10
10
  import { FlowEngine } from '.';
11
11
  import type { FlowModel } from './models';
12
- import { StepSettingsDialogProps, ToolbarItemConfig } from './types';
12
+ import { DynamicFlowSource, DynamicFlowSourceProvider, StepSettingsDialogProps, ToolbarItemConfig } from './types';
13
13
  /**
14
14
  * 打开流程设置的参数接口
15
15
  */
@@ -93,6 +93,7 @@ export declare class FlowSettings {
93
93
  enabled: boolean;
94
94
  private engine;
95
95
  toolbarItems: ToolbarItemConfig[];
96
+ private dynamicFlowSourceProviders;
96
97
  constructor(engine: FlowEngine);
97
98
  on(event: 'beforeOpen', callback: (...args: any[]) => void): void;
98
99
  off(event: 'beforeOpen', callback: (...args: any[]) => void): void;
@@ -206,6 +207,9 @@ export declare class FlowSettings {
206
207
  * @returns {ToolbarItemConfig[]} 所有项目配置
207
208
  */
208
209
  getToolbarItems(): ToolbarItemConfig[];
210
+ registerDynamicFlowSourceProvider(provider: DynamicFlowSourceProvider): () => void;
211
+ hasDynamicFlowSourceProvider(model: FlowModel): boolean;
212
+ getDynamicFlowSources(model: FlowModel): Promise<DynamicFlowSource[]>;
209
213
  /**
210
214
  * 清空所有工具栏项目
211
215
  * @example
@@ -78,6 +78,7 @@ const _FlowSettings = class _FlowSettings {
78
78
  __privateAdd(this, _forceEnabled, false);
79
79
  // 强制启用状态,主要用于设计模式下的强制启用
80
80
  __publicField(this, "toolbarItems", []);
81
+ __publicField(this, "dynamicFlowSourceProviders", []);
81
82
  __privateAdd(this, _emitter, new import_emitter.Emitter());
82
83
  this.engine = engine;
83
84
  this.enabled = false;
@@ -369,6 +370,75 @@ const _FlowSettings = class _FlowSettings {
369
370
  getToolbarItems() {
370
371
  return [...this.toolbarItems];
371
372
  }
373
+ registerDynamicFlowSourceProvider(provider) {
374
+ const existingIndex = this.dynamicFlowSourceProviders.findIndex((item) => item.key === provider.key);
375
+ if (existingIndex !== -1) {
376
+ console.warn(
377
+ `FlowSettings: Dynamic flow source provider with key '${provider.key}' already exists and will be replaced.`
378
+ );
379
+ this.dynamicFlowSourceProviders[existingIndex] = provider;
380
+ } else {
381
+ this.dynamicFlowSourceProviders.push(provider);
382
+ }
383
+ this.dynamicFlowSourceProviders.sort((a, b) => (a.sort || 0) - (b.sort || 0));
384
+ return () => {
385
+ const index = this.dynamicFlowSourceProviders.indexOf(provider);
386
+ if (index !== -1) {
387
+ this.dynamicFlowSourceProviders.splice(index, 1);
388
+ }
389
+ };
390
+ }
391
+ hasDynamicFlowSourceProvider(model) {
392
+ return this.dynamicFlowSourceProviders.some((provider) => {
393
+ try {
394
+ return provider.visible ? provider.visible(model) : true;
395
+ } catch (error) {
396
+ console.warn(`FlowSettings: Dynamic flow source provider '${provider.key}' visibility check failed.`, error);
397
+ return false;
398
+ }
399
+ });
400
+ }
401
+ async getDynamicFlowSources(model) {
402
+ const t = (0, import_utils.getT)(model);
403
+ const selfSource = {
404
+ key: "self",
405
+ label: t("Current block"),
406
+ model,
407
+ sort: -1e3
408
+ };
409
+ const sources = [];
410
+ const seenKeys = /* @__PURE__ */ new Set(["self"]);
411
+ const seenModelUids = /* @__PURE__ */ new Set([model.uid]);
412
+ for (const provider of this.dynamicFlowSourceProviders) {
413
+ try {
414
+ if (provider.visible && !provider.visible(model)) {
415
+ continue;
416
+ }
417
+ const providerSources = await provider.getSources(model);
418
+ for (const source of providerSources || []) {
419
+ if (!(source == null ? void 0 : source.key) || !source.model) {
420
+ continue;
421
+ }
422
+ const key = String(source.key);
423
+ const uid = source.model.uid;
424
+ if (seenKeys.has(key) || seenModelUids.has(uid)) {
425
+ continue;
426
+ }
427
+ seenKeys.add(key);
428
+ seenModelUids.add(uid);
429
+ sources.push({
430
+ ...source,
431
+ key,
432
+ label: source.label || key,
433
+ sort: source.sort || 0
434
+ });
435
+ }
436
+ } catch (error) {
437
+ console.warn(`FlowSettings: Dynamic flow source provider '${provider.key}' failed.`, error);
438
+ }
439
+ }
440
+ return [selfSource, ...sources.sort((a, b) => (a.sort || 0) - (b.sort || 0))];
441
+ }
372
442
  /**
373
443
  * 清空所有工具栏项目
374
444
  * @example
@@ -56,6 +56,7 @@
56
56
  "Replace current block with template?": "Replace current block with template?",
57
57
  "Replaced with template block": "Replaced with template block",
58
58
  "Render failed": "Render failed",
59
+ "Response record": "Response record",
59
60
  "Step configuration": "Step configuration",
60
61
  "Step parameter configuration": "Step parameter configuration",
61
62
  "Step with key {{stepKey}} not found": "Step with key {{stepKey}} not found",
@@ -65,6 +65,7 @@ export declare const locales: {
65
65
  "Replace current block with template?": string;
66
66
  "Replaced with template block": string;
67
67
  "Render failed": string;
68
+ "Response record": string;
68
69
  "Step configuration": string;
69
70
  "Step parameter configuration": string;
70
71
  "Step with key {{stepKey}} not found": string;
@@ -150,6 +151,7 @@ export declare const locales: {
150
151
  "Other blocks": string;
151
152
  "Previous step": string;
152
153
  "Render failed": string;
154
+ "Response record": string;
153
155
  "Step configuration": string;
154
156
  "Step parameter configuration": string;
155
157
  "Step with key {{stepKey}} not found": string;
@@ -60,6 +60,7 @@
60
60
  "Other blocks": "其他区块",
61
61
  "Previous step": "上一步",
62
62
  "Render failed": "渲染失败",
63
+ "Response record": "响应结果记录",
63
64
  "Step configuration": "步骤配置",
64
65
  "Step parameter configuration": "步骤参数配置",
65
66
  "Step with key {{stepKey}} not found": "未找到key为 {{stepKey}} 的步骤",
@@ -30,6 +30,7 @@ __export(apiResource_exports, {
30
30
  APIResource: () => APIResource
31
31
  });
32
32
  module.exports = __toCommonJS(apiResource_exports);
33
+ var import_dirtyAwareApiClient = require("../utils/dirtyAwareApiClient");
33
34
  var import_flowResource = require("./flowResource");
34
35
  const _APIResource = class _APIResource extends import_flowResource.FlowResource {
35
36
  // 请求配置
@@ -49,7 +50,7 @@ const _APIResource = class _APIResource extends import_flowResource.FlowResource
49
50
  }
50
51
  }
51
52
  setAPIClient(api) {
52
- this.api = api;
53
+ this.api = (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(api, this.context);
53
54
  return this;
54
55
  }
55
56
  getURL() {
@@ -44,7 +44,7 @@ var import_lodash = __toESM(require("lodash"));
44
44
  var import_apiResource = require("./apiResource");
45
45
  var import_filterItem = require("./filterItem");
46
46
  var import_flowResource = require("./flowResource");
47
- var import_viewEvents = require("../views/viewEvents");
47
+ var import_dataSourceDirty = require("../utils/dataSourceDirty");
48
48
  const _BaseRecordResource = class _BaseRecordResource extends import_apiResource.APIResource {
49
49
  resourceName;
50
50
  sourceId = null;
@@ -151,22 +151,11 @@ const _BaseRecordResource = class _BaseRecordResource extends import_apiResource
151
151
  * Used to coordinate "refresh on active" across view stacks.
152
152
  */
153
153
  markDataSourceDirty(resourceName) {
154
- var _a, _b;
155
- const engine = this.context.engine;
156
- if (!engine) return;
157
- const dataSourceKey = this.getDataSourceKey() || "main";
158
- const resName = resourceName || this.getResourceName();
159
- if (!resName) return;
160
- const affectedResourceNames = /* @__PURE__ */ new Set([String(resName)]);
161
- if (typeof resName === "string" && resName.includes(".")) {
162
- affectedResourceNames.add(resName.split(".")[0]);
163
- }
164
- for (const name of affectedResourceNames) {
165
- engine.markDataSourceDirty(dataSourceKey, name);
166
- }
167
- (_b = (_a = engine.emitter) == null ? void 0 : _a.emit) == null ? void 0 : _b.call(_a, import_viewEvents.DATA_SOURCE_DIRTY_EVENT, {
168
- dataSourceKey,
169
- resourceNames: Array.from(affectedResourceNames)
154
+ (0, import_dataSourceDirty.markDataSourceDirty)({
155
+ engine: this.context.engine,
156
+ dataSourceKey: this.getDataSourceKey(),
157
+ resourceName: resourceName || this.getResourceName(),
158
+ includePreviousEngines: true
170
159
  });
171
160
  }
172
161
  setSourceId(sourceId) {
@@ -42,6 +42,7 @@ __export(multiRecordResource_exports, {
42
42
  module.exports = __toCommonJS(multiRecordResource_exports);
43
43
  var import_reactive = require("@formily/reactive");
44
44
  var import_lodash = __toESM(require("lodash"));
45
+ var import_dirtyAwareApiClient = require("../utils/dirtyAwareApiClient");
45
46
  var import_baseRecordResource = require("./baseRecordResource");
46
47
  const _MultiRecordResource = class _MultiRecordResource extends import_baseRecordResource.BaseRecordResource {
47
48
  _data = import_reactive.observable.ref([]);
@@ -130,7 +131,10 @@ const _MultiRecordResource = class _MultiRecordResource extends import_baseRecor
130
131
  }
131
132
  async create(data, options) {
132
133
  const config = this.mergeRequestConfig({ data }, this.createActionOptions, options);
133
- const res = await this.runAction("create", config);
134
+ const res = await this.runAction("create", {
135
+ ...config,
136
+ [import_dirtyAwareApiClient.SKIP_DATA_SOURCE_DIRTY]: true
137
+ });
134
138
  this.markDataSourceDirty();
135
139
  this.emit("saved", data);
136
140
  if ((options == null ? void 0 : options.refresh) !== false) {
@@ -161,7 +165,10 @@ const _MultiRecordResource = class _MultiRecordResource extends import_baseRecor
161
165
  this.updateActionOptions,
162
166
  options
163
167
  );
164
- await this.runAction("update", config);
168
+ await this.runAction("update", {
169
+ ...config,
170
+ [import_dirtyAwareApiClient.SKIP_DATA_SOURCE_DIRTY]: true
171
+ });
165
172
  this.markDataSourceDirty();
166
173
  this.emit("saved", data);
167
174
  await this.refresh();
@@ -182,7 +189,10 @@ const _MultiRecordResource = class _MultiRecordResource extends import_baseRecor
182
189
  },
183
190
  options
184
191
  );
185
- await this.runAction("destroy", config);
192
+ await this.runAction("destroy", {
193
+ ...config,
194
+ [import_dirtyAwareApiClient.SKIP_DATA_SOURCE_DIRTY]: true
195
+ });
186
196
  this.markDataSourceDirty();
187
197
  const currentPage = this.getPage();
188
198
  const lastPage = Math.ceil((this.getCount() - import_lodash.default.castArray(filterByTk).length) / this.getPageSize());
@@ -41,6 +41,7 @@ __export(singleRecordResource_exports, {
41
41
  });
42
42
  module.exports = __toCommonJS(singleRecordResource_exports);
43
43
  var import_lodash = __toESM(require("lodash"));
44
+ var import_dirtyAwareApiClient = require("../utils/dirtyAwareApiClient");
44
45
  var import_baseRecordResource = require("./baseRecordResource");
45
46
  const _SingleRecordResource = class _SingleRecordResource extends import_baseRecordResource.BaseRecordResource {
46
47
  isNewRecord = false;
@@ -68,7 +69,8 @@ const _SingleRecordResource = class _SingleRecordResource extends import_baseRec
68
69
  }
69
70
  const res = await this.runAction(actionName, {
70
71
  ...config,
71
- data: result
72
+ data: result,
73
+ [import_dirtyAwareApiClient.SKIP_DATA_SOURCE_DIRTY]: true
72
74
  });
73
75
  this.markDataSourceDirty();
74
76
  this.emit("saved", data);
@@ -86,7 +88,10 @@ const _SingleRecordResource = class _SingleRecordResource extends import_baseRec
86
88
  },
87
89
  options
88
90
  );
89
- await this.runAction("destroy", config);
91
+ await this.runAction("destroy", {
92
+ ...config,
93
+ [import_dirtyAwareApiClient.SKIP_DATA_SOURCE_DIRTY]: true
94
+ });
90
95
  this.markDataSourceDirty();
91
96
  this.setData(null);
92
97
  }
@@ -71,18 +71,25 @@ function createJSRunnerWithVersion(options) {
71
71
  doc = {};
72
72
  }
73
73
  const deprecatedCtx = (0, import_flowContext.createRunJSDeprecationProxy)(runCtx, { doc });
74
- const globals = { ctx: deprecatedCtx, ...(options == null ? void 0 : options.globals) || {} };
75
- if (modelClass === "JSFieldModel" || modelClass === "JSBlockModel") {
76
- if (typeof window !== "undefined") globals.window = window;
77
- if (typeof document !== "undefined") globals.document = document;
74
+ const browserGlobals = {};
75
+ if (typeof window !== "undefined") {
76
+ browserGlobals.window = window;
77
+ if (typeof navigator !== "undefined") {
78
+ browserGlobals.navigator = navigator;
79
+ }
78
80
  }
81
+ if (typeof document !== "undefined") {
82
+ browserGlobals.document = document;
83
+ }
84
+ const globals = { ctx: deprecatedCtx, ...browserGlobals, ...(options == null ? void 0 : options.globals) || {} };
79
85
  const { timeoutMs } = options || {};
80
86
  return new import_JSRunner.JSRunner({ globals, timeoutMs });
81
87
  }
82
88
  __name(createJSRunnerWithVersion, "createJSRunnerWithVersion");
83
89
  function getRunJSScenesForModel(modelClass, version = "v1") {
84
90
  const meta = import_registry.RunJSContextRegistry.getMeta(version, modelClass);
85
- return Array.isArray(meta == null ? void 0 : meta.scenes) ? [...meta.scenes] : [];
91
+ const scenes = meta == null ? void 0 : meta.scenes;
92
+ return Array.isArray(scenes) ? [...scenes] : [];
86
93
  }
87
94
  __name(getRunJSScenesForModel, "getRunJSScenesForModel");
88
95
  function getRunJSScenesForContext(ctx, { version = "v1" } = {}) {
package/lib/types.d.ts CHANGED
@@ -500,6 +500,18 @@ export interface ToolbarItemConfig {
500
500
  /** 排序权重,数字越小越靠右(先添加的在右边) */
501
501
  sort?: number;
502
502
  }
503
+ export interface DynamicFlowSource {
504
+ key: string;
505
+ label: React.ReactNode;
506
+ model: FlowModel;
507
+ sort?: number;
508
+ }
509
+ export interface DynamicFlowSourceProvider {
510
+ key: string;
511
+ sort?: number;
512
+ visible?: (model: FlowModel) => boolean;
513
+ getSources: (model: FlowModel) => DynamicFlowSource[] | Promise<DynamicFlowSource[]>;
514
+ }
503
515
  export interface ApplyFlowCacheEntry {
504
516
  status: 'pending' | 'resolved' | 'rejected';
505
517
  promise: Promise<any>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import type { FlowEngine } from '../flowEngine';
10
+ type MarkDataSourceDirtyOptions = {
11
+ engine?: FlowEngine;
12
+ dataSourceKey?: unknown;
13
+ resourceName?: unknown;
14
+ includePreviousEngines?: boolean;
15
+ };
16
+ export declare function getHeaderValue(headers: unknown, name: string): unknown;
17
+ export declare function getDataSourceKeyFromHeaders(headers: unknown): string;
18
+ export declare function getAffectedResourceNames(resourceName: unknown): string[];
19
+ export declare function markDataSourceDirty(options: MarkDataSourceDirtyOptions): string[];
20
+ export {};