@nocobase/flow-engine 3.0.0-alpha.7 → 3.0.0-alpha.9
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/lib/components/FlowContextSelector.js +7 -1
- package/lib/flowContext.d.ts +5 -1
- package/lib/flowContext.js +13 -2
- package/lib/utils/dateVariable.d.ts +22 -0
- package/lib/utils/dateVariable.js +123 -16
- package/lib/utils/index.d.ts +1 -1
- package/lib/utils/index.js +4 -0
- package/lib/utils/params-resolvers.d.ts +1 -0
- package/lib/utils/params-resolvers.js +1 -0
- package/lib/views/createViewMeta.d.ts +1 -0
- package/lib/views/createViewMeta.js +35 -6
- package/package.json +4 -4
- package/src/__tests__/createViewMeta.popup.test.ts +84 -1
- package/src/__tests__/flowContext.test.ts +8 -0
- package/src/__tests__/objectVariable.test.ts +6 -1
- package/src/__tests__/runjsFormSubmit.test.ts +45 -0
- package/src/components/FlowContextSelector.tsx +7 -1
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +35 -0
- package/src/flowContext.ts +23 -3
- package/src/utils/__tests__/dateVariable.test.ts +57 -4
- package/src/utils/dateVariable.ts +145 -18
- package/src/utils/index.ts +6 -0
- package/src/utils/params-resolvers.ts +2 -0
- package/src/views/createViewMeta.ts +33 -0
|
@@ -247,6 +247,7 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
|
|
|
247
247
|
}, [active, cascaderProps.disabled, currentPath]);
|
|
248
248
|
const handleChange = (0, import_react.useCallback)(
|
|
249
249
|
(selectedValues, selectedOptions) => {
|
|
250
|
+
var _a;
|
|
250
251
|
const lastOption = selectedOptions == null ? void 0 : selectedOptions[selectedOptions.length - 1];
|
|
251
252
|
if (!selectedValues || selectedValues.length === 0) {
|
|
252
253
|
onChange == null ? void 0 : onChange("", lastOption == null ? void 0 : lastOption.meta);
|
|
@@ -256,6 +257,7 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
|
|
|
256
257
|
const path = selectedValues.map(String);
|
|
257
258
|
const pathString = path.join(".");
|
|
258
259
|
const isLeaf = lastOption == null ? void 0 : lastOption.isLeaf;
|
|
260
|
+
const isSelectable = ((_a = lastOption == null ? void 0 : lastOption.meta) == null ? void 0 : _a.selectable) !== false;
|
|
259
261
|
const now = Date.now();
|
|
260
262
|
let formattedValue;
|
|
261
263
|
if (customFormatPathToValue) {
|
|
@@ -267,12 +269,16 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
|
|
|
267
269
|
formattedValue = (0, import_utils.formatPathToValue)(lastOption == null ? void 0 : lastOption.meta);
|
|
268
270
|
}
|
|
269
271
|
if (isLeaf) {
|
|
272
|
+
if (!isSelectable) {
|
|
273
|
+
setTempSelectedPath(path);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
270
276
|
onChange == null ? void 0 : onChange(formattedValue, lastOption == null ? void 0 : lastOption.meta);
|
|
271
277
|
setTempSelectedPath([]);
|
|
272
278
|
return;
|
|
273
279
|
}
|
|
274
280
|
const lastSelected = lastSelectedRef.current;
|
|
275
|
-
const isDoubleClick = !onlyLeafSelectable && (lastSelected == null ? void 0 : lastSelected.path) === pathString && now - lastSelected.time < 300;
|
|
281
|
+
const isDoubleClick = isSelectable && !onlyLeafSelectable && (lastSelected == null ? void 0 : lastSelected.path) === pathString && now - lastSelected.time < 300;
|
|
276
282
|
if (isDoubleClick) {
|
|
277
283
|
onChange == null ? void 0 : onChange(formattedValue, lastOption == null ? void 0 : lastOption.meta);
|
|
278
284
|
lastSelectedRef.current = null;
|
package/lib/flowContext.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ import type { RecordRef } from './utils/serverContextParams';
|
|
|
27
27
|
import { FlowView, FlowViewer } from './views/FlowView';
|
|
28
28
|
import { type RunJSVersion } from './runjs-context/registry';
|
|
29
29
|
type Getter<T = any> = (ctx: FlowContext) => T | Promise<T>;
|
|
30
|
+
export type ResolveJsonTemplateOptions = {
|
|
31
|
+
contractModelUid?: string | number | null;
|
|
32
|
+
};
|
|
30
33
|
export type FlowContextDocRef = string | {
|
|
31
34
|
url: string;
|
|
32
35
|
title?: string;
|
|
@@ -77,6 +80,7 @@ export interface MetaTreeNode {
|
|
|
77
80
|
hidden?: boolean | (() => boolean);
|
|
78
81
|
disabled?: boolean | (() => boolean);
|
|
79
82
|
disabledReason?: string | (() => string | undefined);
|
|
83
|
+
selectable?: boolean;
|
|
80
84
|
children?: MetaTreeNode[] | (() => Promise<MetaTreeNode[]>);
|
|
81
85
|
}
|
|
82
86
|
export interface PropertyMeta {
|
|
@@ -343,7 +347,7 @@ declare class BaseFlowEngineContext extends FlowContext {
|
|
|
343
347
|
* @deprecated use `resolveJsonTemplate` instead
|
|
344
348
|
*/
|
|
345
349
|
renderJson: (template: JSONValue) => Promise<any>;
|
|
346
|
-
resolveJsonTemplate: (template: JSONValue) => Promise<any>;
|
|
350
|
+
resolveJsonTemplate: (template: JSONValue, options?: ResolveJsonTemplateOptions) => Promise<any>;
|
|
347
351
|
getVar: (path: string) => Promise<any>;
|
|
348
352
|
request: (options: RequestOptions) => Promise<any>;
|
|
349
353
|
runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
|
package/lib/flowContext.js
CHANGED
|
@@ -2383,7 +2383,7 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
|
|
|
2383
2383
|
this.defineMethod("renderJson", function(template) {
|
|
2384
2384
|
return this.resolveJsonTemplate(template);
|
|
2385
2385
|
});
|
|
2386
|
-
|
|
2386
|
+
const resolveJsonTemplate = /* @__PURE__ */ __name(async function(template, options) {
|
|
2387
2387
|
var _a, _b, _c;
|
|
2388
2388
|
const used = (0, import_utils.extractUsedVariablePaths)(template);
|
|
2389
2389
|
const usedVarNames = Object.keys(used || {});
|
|
@@ -2516,7 +2516,12 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
|
|
|
2516
2516
|
}
|
|
2517
2517
|
if (this.api) {
|
|
2518
2518
|
try {
|
|
2519
|
+
const contractRd = (0, import_params_resolvers.buildFlowModelResolveDescriptor)(
|
|
2520
|
+
this,
|
|
2521
|
+
options == null ? void 0 : options.contractModelUid
|
|
2522
|
+
);
|
|
2519
2523
|
serverResolved = await (0, import_params_resolvers.enqueueVariablesResolve)(this, {
|
|
2524
|
+
...contractRd ? { contractRd } : {},
|
|
2520
2525
|
rd: (0, import_params_resolvers.buildFlowModelResolveDescriptor)(this, (_a = this.model) == null ? void 0 : _a.uid),
|
|
2521
2526
|
template,
|
|
2522
2527
|
contextParams: autoContextParams || {}
|
|
@@ -2528,7 +2533,8 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
|
|
|
2528
2533
|
}
|
|
2529
2534
|
}
|
|
2530
2535
|
return (0, import_utils.resolveExpressions)(serverResolved, this);
|
|
2531
|
-
});
|
|
2536
|
+
}, "resolveJsonTemplate");
|
|
2537
|
+
this.defineMethod("resolveJsonTemplate", resolveJsonTemplate);
|
|
2532
2538
|
this.defineMethod(
|
|
2533
2539
|
"getVar",
|
|
2534
2540
|
async function(varPath) {
|
|
@@ -3505,8 +3511,13 @@ function __mergeRunJSDocMeta(base, patch) {
|
|
|
3505
3511
|
__name(__mergeRunJSDocMeta, "__mergeRunJSDocMeta");
|
|
3506
3512
|
const _FlowRunJSContext = class _FlowRunJSContext extends FlowContext {
|
|
3507
3513
|
constructor(delegate) {
|
|
3514
|
+
var _a, _b;
|
|
3508
3515
|
super();
|
|
3509
3516
|
this.addDelegate(delegate);
|
|
3517
|
+
const submit = (_b = (_a = delegate.blockModel) == null ? void 0 : _a.submitFromRunJs) == null ? void 0 : _b.bind(delegate.blockModel);
|
|
3518
|
+
if (delegate.form && submit) {
|
|
3519
|
+
this.defineProperty("form", { value: { ...delegate.form, submit } });
|
|
3520
|
+
}
|
|
3510
3521
|
this.defineProperty("React", { value: import_react.default });
|
|
3511
3522
|
this.defineProperty("antd", { value: antd });
|
|
3512
3523
|
this.defineProperty("dayjs", {
|
|
@@ -6,11 +6,33 @@
|
|
|
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
|
+
declare const PRESET_KEY_LIST: readonly ["today", "now", "yesterday", "tomorrow", "thisWeek", "lastWeek", "nextWeek", "thisMonth", "lastMonth", "nextMonth", "thisQuarter", "lastQuarter", "nextQuarter", "thisYear", "lastYear", "nextYear"];
|
|
10
|
+
export type CtxDatePreset = (typeof PRESET_KEY_LIST)[number];
|
|
11
|
+
export type CtxDateRelativeDirection = 'next' | 'past';
|
|
12
|
+
export type CtxDateRelativeUnit = 'day' | 'week' | 'month' | 'year';
|
|
13
|
+
export type CtxDateExpressionConfig = {
|
|
14
|
+
kind: 'exact';
|
|
15
|
+
value: string | [string, string];
|
|
16
|
+
format?: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'relative';
|
|
19
|
+
direction: CtxDateRelativeDirection;
|
|
20
|
+
amount: number;
|
|
21
|
+
unit: CtxDateRelativeUnit;
|
|
22
|
+
format?: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'preset';
|
|
25
|
+
preset: CtxDatePreset;
|
|
26
|
+
format?: string;
|
|
27
|
+
};
|
|
9
28
|
export declare function isCtxDatePathPrefix(pathSegments: string[]): boolean;
|
|
10
29
|
export declare function encodeBase64Url(input: string): string;
|
|
11
30
|
export declare function decodeBase64Url(input: string): string | undefined;
|
|
12
31
|
export declare function isCtxDateExpression(value: unknown): value is string;
|
|
13
32
|
export declare function isCompleteCtxDatePath(pathSegments: string[]): boolean;
|
|
14
33
|
export declare function parseCtxDateExpression(value: unknown): any;
|
|
34
|
+
export declare function parseCtxDateExpressionConfig(value: unknown): CtxDateExpressionConfig | undefined;
|
|
35
|
+
export declare function serializeCtxDateExpressionConfig(config: CtxDateExpressionConfig): string | undefined;
|
|
15
36
|
export declare function serializeCtxDateValue(value: unknown): string | undefined;
|
|
16
37
|
export declare function resolveCtxDatePath(pathSegments: string[]): any;
|
|
38
|
+
export {};
|
|
@@ -43,13 +43,15 @@ __export(dateVariable_exports, {
|
|
|
43
43
|
isCtxDateExpression: () => isCtxDateExpression,
|
|
44
44
|
isCtxDatePathPrefix: () => isCtxDatePathPrefix,
|
|
45
45
|
parseCtxDateExpression: () => parseCtxDateExpression,
|
|
46
|
+
parseCtxDateExpressionConfig: () => parseCtxDateExpressionConfig,
|
|
46
47
|
resolveCtxDatePath: () => resolveCtxDatePath,
|
|
48
|
+
serializeCtxDateExpressionConfig: () => serializeCtxDateExpressionConfig,
|
|
47
49
|
serializeCtxDateValue: () => serializeCtxDateValue
|
|
48
50
|
});
|
|
49
51
|
module.exports = __toCommonJS(dateVariable_exports);
|
|
50
52
|
var import_dayjs = __toESM(require("dayjs"));
|
|
51
53
|
const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
|
|
52
|
-
const
|
|
54
|
+
const PRESET_KEY_LIST = [
|
|
53
55
|
"today",
|
|
54
56
|
"now",
|
|
55
57
|
"yesterday",
|
|
@@ -66,9 +68,11 @@ const PRESET_KEYS = /* @__PURE__ */ new Set([
|
|
|
66
68
|
"thisYear",
|
|
67
69
|
"lastYear",
|
|
68
70
|
"nextYear"
|
|
69
|
-
]
|
|
71
|
+
];
|
|
72
|
+
const PRESET_KEYS = new Set(PRESET_KEY_LIST);
|
|
70
73
|
const RELATIVE_DIRECTIONS = /* @__PURE__ */ new Set(["next", "past"]);
|
|
71
74
|
const RELATIVE_UNITS = /* @__PURE__ */ new Set(["day", "week", "month", "year"]);
|
|
75
|
+
const MAX_DATE_FORMAT_LENGTH = 128;
|
|
72
76
|
function parseCtxDateSegments(value) {
|
|
73
77
|
if (typeof value !== "string") return null;
|
|
74
78
|
const trimmed = value.trim();
|
|
@@ -79,8 +83,7 @@ function parseCtxDateSegments(value) {
|
|
|
79
83
|
return rawPath.split(".").map((seg) => seg.trim()).filter(Boolean);
|
|
80
84
|
}
|
|
81
85
|
__name(parseCtxDateSegments, "parseCtxDateSegments");
|
|
82
|
-
function
|
|
83
|
-
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
86
|
+
function isBaseCtxDatePathPrefix(segments) {
|
|
84
87
|
if (segments[0] !== "date") return false;
|
|
85
88
|
if (segments.length === 1) return true;
|
|
86
89
|
if (segments[1] === "preset") {
|
|
@@ -117,6 +120,34 @@ function isCtxDatePathPrefix(pathSegments) {
|
|
|
117
120
|
}
|
|
118
121
|
return false;
|
|
119
122
|
}
|
|
123
|
+
__name(isBaseCtxDatePathPrefix, "isBaseCtxDatePathPrefix");
|
|
124
|
+
function decodeFormatToken(token) {
|
|
125
|
+
const raw = String(token || "");
|
|
126
|
+
if (!raw.startsWith("v")) return void 0;
|
|
127
|
+
const decoded = decodeBase64Url(raw.slice(1));
|
|
128
|
+
if (!decoded || decoded.length > MAX_DATE_FORMAT_LENGTH) return void 0;
|
|
129
|
+
return decoded;
|
|
130
|
+
}
|
|
131
|
+
__name(decodeFormatToken, "decodeFormatToken");
|
|
132
|
+
function splitFormattedDateSegments(segments) {
|
|
133
|
+
if (segments[0] !== "date") return null;
|
|
134
|
+
if (segments[1] !== "format") return { baseSegments: segments };
|
|
135
|
+
if (segments.length < 4) return null;
|
|
136
|
+
const format = decodeFormatToken(segments[2]);
|
|
137
|
+
if (!format) return null;
|
|
138
|
+
return { baseSegments: ["date", ...segments.slice(3)], format };
|
|
139
|
+
}
|
|
140
|
+
__name(splitFormattedDateSegments, "splitFormattedDateSegments");
|
|
141
|
+
function isCtxDatePathPrefix(pathSegments) {
|
|
142
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
143
|
+
if (segments[0] !== "date") return false;
|
|
144
|
+
if (segments.length === 1) return true;
|
|
145
|
+
if (segments[1] !== "format") return isBaseCtxDatePathPrefix(segments);
|
|
146
|
+
if (segments.length === 2) return true;
|
|
147
|
+
if (segments.length === 3) return typeof decodeFormatToken(segments[2]) === "string";
|
|
148
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
149
|
+
return formatted ? isBaseCtxDatePathPrefix(formatted.baseSegments) : false;
|
|
150
|
+
}
|
|
120
151
|
__name(isCtxDatePathPrefix, "isCtxDatePathPrefix");
|
|
121
152
|
function withDatePrefix(pathSegments) {
|
|
122
153
|
if (pathSegments[0] === "date") {
|
|
@@ -225,26 +256,31 @@ __name(isCtxDateExpression, "isCtxDateExpression");
|
|
|
225
256
|
function isCompleteCtxDatePath(pathSegments) {
|
|
226
257
|
if (!isCtxDatePathPrefix(pathSegments)) return false;
|
|
227
258
|
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
259
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
260
|
+
if (!formatted) return false;
|
|
261
|
+
const baseSegments = formatted.baseSegments;
|
|
262
|
+
if (baseSegments[1] === "preset") {
|
|
263
|
+
return baseSegments.length === 3 && PRESET_KEYS.has(baseSegments[2]);
|
|
231
264
|
}
|
|
232
|
-
if (
|
|
233
|
-
if (
|
|
234
|
-
return RELATIVE_DIRECTIONS.has(
|
|
265
|
+
if (baseSegments[1] === "relative") {
|
|
266
|
+
if (baseSegments.length !== 5) return false;
|
|
267
|
+
return RELATIVE_DIRECTIONS.has(baseSegments[2]) && RELATIVE_UNITS.has(baseSegments[3]) && typeof parseNumberToken(baseSegments[4]) === "number";
|
|
235
268
|
}
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
269
|
+
if (baseSegments[1] === "exact" && baseSegments[2] === "single" && baseSegments[3] === "date") {
|
|
270
|
+
return baseSegments.length === 5 && /^v.+/.test(baseSegments[4]);
|
|
238
271
|
}
|
|
239
|
-
if (
|
|
240
|
-
return
|
|
272
|
+
if (baseSegments[1] === "exact" && baseSegments[2] === "range" && baseSegments[3] === "date") {
|
|
273
|
+
return baseSegments.length === 6 && /^v.+/.test(baseSegments[4]) && /^v.+/.test(baseSegments[5]);
|
|
241
274
|
}
|
|
242
275
|
return false;
|
|
243
276
|
}
|
|
244
277
|
__name(isCompleteCtxDatePath, "isCompleteCtxDatePath");
|
|
245
278
|
function parseCtxDateExpression(value) {
|
|
246
279
|
if (!isCtxDateExpression(value)) return void 0;
|
|
247
|
-
const
|
|
280
|
+
const rawSegments = withDatePrefix(parseCtxDateSegments(value) || []);
|
|
281
|
+
const formatted = splitFormattedDateSegments(rawSegments);
|
|
282
|
+
if (!formatted) return void 0;
|
|
283
|
+
const segments = formatted.baseSegments;
|
|
248
284
|
if (segments[1] === "preset" && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
|
|
249
285
|
return { type: segments[2] };
|
|
250
286
|
}
|
|
@@ -272,6 +308,60 @@ function parseCtxDateExpression(value) {
|
|
|
272
308
|
return void 0;
|
|
273
309
|
}
|
|
274
310
|
__name(parseCtxDateExpression, "parseCtxDateExpression");
|
|
311
|
+
function parseCtxDateExpressionConfig(value) {
|
|
312
|
+
if (!isCtxDateExpression(value)) return void 0;
|
|
313
|
+
const segments = withDatePrefix(parseCtxDateSegments(value) || []);
|
|
314
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
315
|
+
if (!formatted) return void 0;
|
|
316
|
+
const parsed = parseCtxDateExpression(value);
|
|
317
|
+
const formatConfig = formatted.format ? { format: formatted.format } : {};
|
|
318
|
+
if (typeof parsed === "string") {
|
|
319
|
+
return { kind: "exact", value: parsed, ...formatConfig };
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(parsed) && parsed.length === 2 && typeof parsed[0] === "string" && typeof parsed[1] === "string") {
|
|
322
|
+
return { kind: "exact", value: [parsed[0], parsed[1]], ...formatConfig };
|
|
323
|
+
}
|
|
324
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
325
|
+
const typed = parsed;
|
|
326
|
+
if (typed.type === "past" || typed.type === "next") {
|
|
327
|
+
if (typeof typed.unit !== "string" || !RELATIVE_UNITS.has(typed.unit) || typeof typed.number !== "number") {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
kind: "relative",
|
|
332
|
+
direction: typed.type,
|
|
333
|
+
amount: typed.number,
|
|
334
|
+
unit: typed.unit,
|
|
335
|
+
...formatConfig
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (typeof typed.type === "string" && PRESET_KEYS.has(typed.type)) {
|
|
339
|
+
return { kind: "preset", preset: typed.type, ...formatConfig };
|
|
340
|
+
}
|
|
341
|
+
return void 0;
|
|
342
|
+
}
|
|
343
|
+
__name(parseCtxDateExpressionConfig, "parseCtxDateExpressionConfig");
|
|
344
|
+
function serializeCtxDateExpressionConfig(config) {
|
|
345
|
+
let legacyValue;
|
|
346
|
+
if (config.kind === "preset") {
|
|
347
|
+
if (!PRESET_KEYS.has(config.preset)) return void 0;
|
|
348
|
+
legacyValue = { type: config.preset };
|
|
349
|
+
} else if (config.kind === "relative") {
|
|
350
|
+
if (!RELATIVE_DIRECTIONS.has(config.direction) || !RELATIVE_UNITS.has(config.unit)) return void 0;
|
|
351
|
+
const amount = Math.floor(Number(config.amount));
|
|
352
|
+
if (!Number.isFinite(amount) || amount <= 0) return void 0;
|
|
353
|
+
legacyValue = { type: config.direction, unit: config.unit, number: amount };
|
|
354
|
+
} else {
|
|
355
|
+
legacyValue = config.value;
|
|
356
|
+
}
|
|
357
|
+
const expression = serializeCtxDateValue(legacyValue);
|
|
358
|
+
if (!expression || !config.format) return expression;
|
|
359
|
+
const format = String(config.format);
|
|
360
|
+
if (!format.trim() || format.length > MAX_DATE_FORMAT_LENGTH) return void 0;
|
|
361
|
+
const segments = withDatePrefix(parseCtxDateSegments(expression) || []);
|
|
362
|
+
return toCtxDateExpression(["date", "format", `v${encodeBase64Url(format)}`, ...segments.slice(1)]);
|
|
363
|
+
}
|
|
364
|
+
__name(serializeCtxDateExpressionConfig, "serializeCtxDateExpressionConfig");
|
|
275
365
|
function serializeCtxDateValue(value) {
|
|
276
366
|
if (isCtxDateExpression(value)) {
|
|
277
367
|
return String(value).trim();
|
|
@@ -317,8 +407,23 @@ function serializeCtxDateValue(value) {
|
|
|
317
407
|
return void 0;
|
|
318
408
|
}
|
|
319
409
|
__name(serializeCtxDateValue, "serializeCtxDateValue");
|
|
410
|
+
function formatResolvedDateValue(value, format) {
|
|
411
|
+
const formatValue = /* @__PURE__ */ __name((item) => {
|
|
412
|
+
if (typeof item !== "string") return item;
|
|
413
|
+
const parsed = (0, import_dayjs.default)(item);
|
|
414
|
+
return parsed.isValid() ? parsed.format(format) : item;
|
|
415
|
+
}, "formatValue");
|
|
416
|
+
return Array.isArray(value) ? value.map(formatValue) : formatValue(value);
|
|
417
|
+
}
|
|
418
|
+
__name(formatResolvedDateValue, "formatResolvedDateValue");
|
|
320
419
|
function resolveCtxDatePath(pathSegments) {
|
|
321
|
-
const
|
|
420
|
+
const rawSegments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
421
|
+
const formatted = splitFormattedDateSegments(rawSegments);
|
|
422
|
+
if (!formatted) return void 0;
|
|
423
|
+
if (formatted.format) {
|
|
424
|
+
return formatResolvedDateValue(resolveCtxDatePath(formatted.baseSegments), formatted.format);
|
|
425
|
+
}
|
|
426
|
+
const segments = formatted.baseSegments;
|
|
322
427
|
if (segments[0] !== "date") return void 0;
|
|
323
428
|
if (segments[1] === "preset" && segments.length === 3) {
|
|
324
429
|
const key = segments[2];
|
|
@@ -375,6 +480,8 @@ __name(resolveCtxDatePath, "resolveCtxDatePath");
|
|
|
375
480
|
isCtxDateExpression,
|
|
376
481
|
isCtxDatePathPrefix,
|
|
377
482
|
parseCtxDateExpression,
|
|
483
|
+
parseCtxDateExpressionConfig,
|
|
378
484
|
resolveCtxDatePath,
|
|
485
|
+
serializeCtxDateExpressionConfig,
|
|
379
486
|
serializeCtxDateValue
|
|
380
487
|
});
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { extractPropertyPath, formatPathToVariable, isVariableExpression } from
|
|
|
21
21
|
export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
|
|
22
22
|
export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
|
|
23
23
|
export { createOpenViewRouteState, decodeOpenViewRouteState, encodeOpenViewRouteState, isOpenViewRouteStateToken, RUNJS_OPEN_VIEW_ROUTE_STATE, type OpenViewRouteMode, type OpenViewRouteSize, type OpenViewRouteState, } from './openViewRouteState';
|
|
24
|
-
export { decodeBase64Url, encodeBase64Url, isCompleteCtxDatePath, isCtxDatePathPrefix, isCtxDateExpression, parseCtxDateExpression, resolveCtxDatePath, serializeCtxDateValue, } from './dateVariable';
|
|
24
|
+
export { decodeBase64Url, encodeBase64Url, isCompleteCtxDatePath, isCtxDatePathPrefix, isCtxDateExpression, parseCtxDateExpression, parseCtxDateExpressionConfig, resolveCtxDatePath, serializeCtxDateExpressionConfig, serializeCtxDateValue, type CtxDateExpressionConfig, type CtxDatePreset, type CtxDateRelativeDirection, type CtxDateRelativeUnit, } from './dateVariable';
|
|
25
25
|
export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
|
|
26
26
|
export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
|
|
27
27
|
export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
|
package/lib/utils/index.js
CHANGED
|
@@ -73,6 +73,7 @@ __export(utils_exports, {
|
|
|
73
73
|
isVariableExpression: () => import_context.isVariableExpression,
|
|
74
74
|
normalizeRunJSValue: () => import_runjsValue.normalizeRunJSValue,
|
|
75
75
|
parseCtxDateExpression: () => import_dateVariable.parseCtxDateExpression,
|
|
76
|
+
parseCtxDateExpressionConfig: () => import_dateVariable.parseCtxDateExpressionConfig,
|
|
76
77
|
parsePathnameToViewParams: () => import_parsePathnameToViewParams.parsePathnameToViewParams,
|
|
77
78
|
prepareRunJsCode: () => import_runjsTemplateCompat.prepareRunJsCode,
|
|
78
79
|
preprocessRunJsTemplates: () => import_runjsTemplateCompat.preprocessRunJsTemplates,
|
|
@@ -87,6 +88,7 @@ __export(utils_exports, {
|
|
|
87
88
|
resolveStepDisabledInSettings: () => import_schema_utils.resolveStepDisabledInSettings,
|
|
88
89
|
resolveStepUiSchema: () => import_schema_utils.resolveStepUiSchema,
|
|
89
90
|
resolveUiMode: () => import_schema_utils.resolveUiMode,
|
|
91
|
+
serializeCtxDateExpressionConfig: () => import_dateVariable.serializeCtxDateExpressionConfig,
|
|
90
92
|
serializeCtxDateValue: () => import_dateVariable.serializeCtxDateValue,
|
|
91
93
|
setAutoFlowError: () => import_autoFlowError.setAutoFlowError,
|
|
92
94
|
setupRuntimeContextSteps: () => import_setupRuntimeContextSteps.setupRuntimeContextSteps,
|
|
@@ -168,6 +170,7 @@ var import_randomId = require("./randomId");
|
|
|
168
170
|
isVariableExpression,
|
|
169
171
|
normalizeRunJSValue,
|
|
170
172
|
parseCtxDateExpression,
|
|
173
|
+
parseCtxDateExpressionConfig,
|
|
171
174
|
parsePathnameToViewParams,
|
|
172
175
|
prepareRunJsCode,
|
|
173
176
|
preprocessRunJsTemplates,
|
|
@@ -182,6 +185,7 @@ var import_randomId = require("./randomId");
|
|
|
182
185
|
resolveStepDisabledInSettings,
|
|
183
186
|
resolveStepUiSchema,
|
|
184
187
|
resolveUiMode,
|
|
188
|
+
serializeCtxDateExpressionConfig,
|
|
185
189
|
serializeCtxDateValue,
|
|
186
190
|
setAutoFlowError,
|
|
187
191
|
setupRuntimeContextSteps,
|
|
@@ -146,6 +146,7 @@ function enqueueVariablesResolve(ctx, payload) {
|
|
|
146
146
|
try {
|
|
147
147
|
const batch = items.map((it) => ({
|
|
148
148
|
id: it.id,
|
|
149
|
+
contractRd: it.payload.contractRd,
|
|
149
150
|
rd: it.payload.rd,
|
|
150
151
|
template: it.payload.template,
|
|
151
152
|
contextParams: it.payload.contextParams || {}
|
|
@@ -28,6 +28,7 @@ interface PopupNodeResource {
|
|
|
28
28
|
interface PopupNode {
|
|
29
29
|
uid?: string;
|
|
30
30
|
resource: PopupNodeResource;
|
|
31
|
+
sourceRecord?: unknown;
|
|
31
32
|
parent?: PopupNode;
|
|
32
33
|
}
|
|
33
34
|
export declare function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode | undefined>;
|
|
@@ -428,10 +428,11 @@ function createPopupMeta(ctx, anchorView) {
|
|
|
428
428
|
}
|
|
429
429
|
__name(createPopupMeta, "createPopupMeta");
|
|
430
430
|
async function buildPopupRuntime(ctx, view) {
|
|
431
|
-
var _a;
|
|
431
|
+
var _a, _b, _c;
|
|
432
432
|
const stack = getViewStack(view);
|
|
433
433
|
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
434
|
-
const
|
|
434
|
+
const sourceRecord = (_b = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.parentItem) == null ? void 0 : _b.value;
|
|
435
|
+
const openerUids = (_c = view == null ? void 0 : view.inputArgs) == null ? void 0 : _c.openerUids;
|
|
435
436
|
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
436
437
|
const hasStackPopup = currentIndex >= 1;
|
|
437
438
|
const isPopup = hasStackPopup || hasOpener;
|
|
@@ -448,16 +449,17 @@ async function buildPopupRuntime(ctx, view) {
|
|
|
448
449
|
associationName: args.associationName,
|
|
449
450
|
filterByTk: args.filterByTk,
|
|
450
451
|
sourceId: args.sourceId
|
|
451
|
-
}
|
|
452
|
+
},
|
|
453
|
+
...typeof sourceRecord !== "undefined" ? { sourceRecord } : {}
|
|
452
454
|
};
|
|
453
455
|
}
|
|
454
456
|
const buildNode = /* @__PURE__ */ __name(async (idx) => {
|
|
455
|
-
var _a2,
|
|
457
|
+
var _a2, _b2, _c2, _d, _e, _f;
|
|
456
458
|
if (idx < 0 || !((_a2 = stack[idx]) == null ? void 0 : _a2.viewUid)) return void 0;
|
|
457
459
|
const viewUid = stack[idx].viewUid;
|
|
458
|
-
let model = (
|
|
460
|
+
let model = (_b2 = ctx.engine) == null ? void 0 : _b2.getModel(viewUid, true);
|
|
459
461
|
if (!model) {
|
|
460
|
-
model = await ((
|
|
462
|
+
model = await ((_c2 = ctx.engine) == null ? void 0 : _c2.loadModel({ uid: viewUid }));
|
|
461
463
|
}
|
|
462
464
|
const p = ((_d = model == null ? void 0 : model.getStepParams) == null ? void 0 : _d.call(model, "popupSettings", "openView")) || {};
|
|
463
465
|
const collectionName = p == null ? void 0 : p.collectionName;
|
|
@@ -477,16 +479,43 @@ async function buildPopupRuntime(ctx, view) {
|
|
|
477
479
|
return node;
|
|
478
480
|
}, "buildNode");
|
|
479
481
|
const currentNode = await buildNode(currentIndex);
|
|
482
|
+
if (currentNode && typeof sourceRecord !== "undefined") {
|
|
483
|
+
currentNode.sourceRecord = sourceRecord;
|
|
484
|
+
}
|
|
480
485
|
return currentNode;
|
|
481
486
|
}
|
|
482
487
|
__name(buildPopupRuntime, "buildPopupRuntime");
|
|
483
488
|
function registerPopupVariable(ctx, view) {
|
|
484
489
|
const POPUP_SERVER_PATH_RE = /^(?:record|sourceRecord)(?:\.|$)|^parent(?:\.parent)*(?:\.(?:record|sourceRecord))(?:\.|$)/;
|
|
490
|
+
const shouldResolveSourceRecordOnServer = /* @__PURE__ */ __name((path) => {
|
|
491
|
+
var _a, _b;
|
|
492
|
+
if (path !== "sourceRecord" && !path.startsWith("sourceRecord.")) return false;
|
|
493
|
+
const parentItem = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.parentItem;
|
|
494
|
+
if (typeof (parentItem == null ? void 0 : parentItem.value) === "undefined") return true;
|
|
495
|
+
const sourcePath = path === "sourceRecord" ? "" : path.slice("sourceRecord.".length);
|
|
496
|
+
if (!sourcePath) return false;
|
|
497
|
+
const parentItemResolver = (_b = view == null ? void 0 : view.inputArgs) == null ? void 0 : _b.parentItemResolver;
|
|
498
|
+
if (typeof parentItemResolver === "function") {
|
|
499
|
+
return parentItemResolver(`value.${sourcePath}`);
|
|
500
|
+
}
|
|
501
|
+
const segments = sourcePath.split(".").filter(Boolean);
|
|
502
|
+
let current = parentItem.value;
|
|
503
|
+
for (const segment of segments) {
|
|
504
|
+
if (current === null || typeof current !== "object" || !(segment in current)) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
current = current[segment];
|
|
508
|
+
}
|
|
509
|
+
return false;
|
|
510
|
+
}, "shouldResolveSourceRecordOnServer");
|
|
485
511
|
ctx.defineProperty("popup", {
|
|
486
512
|
get: /* @__PURE__ */ __name(async () => buildPopupRuntime(ctx, view), "get"),
|
|
487
513
|
meta: createPopupMeta(ctx, view),
|
|
488
514
|
resolveOnServer: /* @__PURE__ */ __name((p) => {
|
|
489
515
|
try {
|
|
516
|
+
if (p === "sourceRecord" || p.startsWith("sourceRecord.")) {
|
|
517
|
+
return shouldResolveSourceRecordOnServer(p);
|
|
518
|
+
}
|
|
490
519
|
return !!p && POPUP_SERVER_PATH_RE.test(p);
|
|
491
520
|
} catch (_) {
|
|
492
521
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "3.0.0-alpha.
|
|
12
|
-
"@nocobase/shared": "3.0.0-alpha.
|
|
11
|
+
"@nocobase/sdk": "3.0.0-alpha.9",
|
|
12
|
+
"@nocobase/shared": "3.0.0-alpha.9",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "82aaad40c5be1120839a11301a48e7fe690f48c3"
|
|
41
41
|
}
|
|
@@ -11,7 +11,7 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
11
11
|
import { FlowContext } from '../flowContext';
|
|
12
12
|
import { FlowEngine } from '../flowEngine';
|
|
13
13
|
import type { FlowView } from '../views/FlowView';
|
|
14
|
-
import { buildPopupRuntime, createPopupMeta } from '../views/createViewMeta';
|
|
14
|
+
import { buildPopupRuntime, createPopupMeta, registerPopupVariable } from '../views/createViewMeta';
|
|
15
15
|
|
|
16
16
|
describe('createPopupMeta - popup variables', () => {
|
|
17
17
|
function makeCtx() {
|
|
@@ -167,6 +167,7 @@ describe('createPopupMeta - popup variables', () => {
|
|
|
167
167
|
it('buildPopupRuntime anchors current popup even when the navigation stack already has a child popup', async () => {
|
|
168
168
|
const { engine, ctx } = makeCtx();
|
|
169
169
|
const parentView = makeNestedPopupView('parent-popup-uid', 13);
|
|
170
|
+
parentView.inputArgs.parentItem = { value: { id: 13, phone: '9999' } };
|
|
170
171
|
mockNestedPopupModels(engine);
|
|
171
172
|
|
|
172
173
|
const popup = await buildPopupRuntime(ctx, parentView);
|
|
@@ -179,6 +180,7 @@ describe('createPopupMeta - popup variables', () => {
|
|
|
179
180
|
filterByTk: 13,
|
|
180
181
|
sourceId: 13,
|
|
181
182
|
});
|
|
183
|
+
expect(popup?.sourceRecord).toEqual({ id: 13, phone: '9999' });
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
it('buildVariablesParams(record) keeps the parent view record when a child popup is open', async () => {
|
|
@@ -314,4 +316,85 @@ describe('createPopupMeta - popup variables', () => {
|
|
|
314
316
|
const props = typeof meta.properties === 'function' ? await (meta.properties as any)() : meta.properties || {};
|
|
315
317
|
expect(props.record).toBeUndefined();
|
|
316
318
|
});
|
|
319
|
+
|
|
320
|
+
it('uses the current parent item value for popup sourceRecord fields', async () => {
|
|
321
|
+
const { ctx } = makeCtx();
|
|
322
|
+
const parentItemResolver = vi.fn(() => false);
|
|
323
|
+
const anchorView: FlowView = {
|
|
324
|
+
type: 'dialog',
|
|
325
|
+
inputArgs: {
|
|
326
|
+
openerUids: ['opener-uid-1'],
|
|
327
|
+
viewUid: 'popup-uid',
|
|
328
|
+
dataSourceKey: 'main',
|
|
329
|
+
collectionName: 'roles',
|
|
330
|
+
associationName: 'users.roles',
|
|
331
|
+
sourceId: 1,
|
|
332
|
+
parentItem: { value: { id: 1, phone: '9999' } },
|
|
333
|
+
parentItemResolver,
|
|
334
|
+
},
|
|
335
|
+
Header: null,
|
|
336
|
+
Footer: null,
|
|
337
|
+
close: () => void 0,
|
|
338
|
+
update: () => void 0,
|
|
339
|
+
} as any;
|
|
340
|
+
|
|
341
|
+
registerPopupVariable(ctx, anchorView);
|
|
342
|
+
|
|
343
|
+
await expect(ctx.popup).resolves.toMatchObject({
|
|
344
|
+
sourceRecord: { id: 1, phone: '9999' },
|
|
345
|
+
});
|
|
346
|
+
expect(ctx.getPropertyOptions('popup')?.resolveOnServer?.('sourceRecord.phone')).toBe(false);
|
|
347
|
+
expect(parentItemResolver).toHaveBeenCalledWith('value.phone');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('resolves a popup sourceRecord field from the current parent item without a server request', async () => {
|
|
351
|
+
const engine = new FlowEngine();
|
|
352
|
+
const ctx = engine.context;
|
|
353
|
+
const anchorView: FlowView = {
|
|
354
|
+
type: 'dialog',
|
|
355
|
+
inputArgs: {
|
|
356
|
+
openerUids: ['opener-uid-1'],
|
|
357
|
+
viewUid: 'popup-uid',
|
|
358
|
+
dataSourceKey: 'main',
|
|
359
|
+
collectionName: 'roles',
|
|
360
|
+
associationName: 'users.roles',
|
|
361
|
+
sourceId: 1,
|
|
362
|
+
parentItem: { value: { id: 1, phone: '9999' } },
|
|
363
|
+
parentItemResolver: () => false,
|
|
364
|
+
},
|
|
365
|
+
Header: null,
|
|
366
|
+
Footer: null,
|
|
367
|
+
close: () => void 0,
|
|
368
|
+
update: () => void 0,
|
|
369
|
+
} as any;
|
|
370
|
+
|
|
371
|
+
registerPopupVariable(ctx, anchorView);
|
|
372
|
+
|
|
373
|
+
await expect(ctx.resolveJsonTemplate('{{ ctx.popup.sourceRecord.phone }}')).resolves.toBe('9999');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('keeps server resolution for popup sourceRecord association subpaths', async () => {
|
|
377
|
+
const { ctx } = makeCtx();
|
|
378
|
+
const anchorView: FlowView = {
|
|
379
|
+
type: 'dialog',
|
|
380
|
+
inputArgs: {
|
|
381
|
+
openerUids: ['opener-uid-1'],
|
|
382
|
+
viewUid: 'popup-uid',
|
|
383
|
+
dataSourceKey: 'main',
|
|
384
|
+
collectionName: 'roles',
|
|
385
|
+
associationName: 'users.roles',
|
|
386
|
+
sourceId: 1,
|
|
387
|
+
parentItem: { value: { id: 1, departmentId: 2 } },
|
|
388
|
+
parentItemResolver: (path: string) => path === 'value.department.title',
|
|
389
|
+
},
|
|
390
|
+
Header: null,
|
|
391
|
+
Footer: null,
|
|
392
|
+
close: () => void 0,
|
|
393
|
+
update: () => void 0,
|
|
394
|
+
} as any;
|
|
395
|
+
|
|
396
|
+
registerPopupVariable(ctx, anchorView);
|
|
397
|
+
|
|
398
|
+
expect(ctx.getPropertyOptions('popup')?.resolveOnServer?.('sourceRecord.department.title')).toBe(true);
|
|
399
|
+
});
|
|
317
400
|
});
|