@nocobase/flow-engine 2.2.0-alpha.6 → 2.2.0-alpha.8
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 +2 -2
- package/lib/components/FormItem.js +11 -7
- package/lib/components/MobilePopup.js +28 -10
- package/lib/components/MobilePopup.style.js +11 -1
- package/lib/components/variables/VariableInput.js +16 -7
- package/lib/components/variables/VariableTag.js +43 -32
- package/lib/components/variables/types.d.ts +2 -0
- package/lib/flowEngine.js +6 -0
- package/lib/utils/dirtyAwareApiClient.js +267 -13
- package/lib/utils/loadedPageCache.d.ts +1 -0
- package/lib/utils/loadedPageCache.js +6 -0
- package/package.json +4 -4
- package/src/__tests__/viewScopedFlowEngine.test.ts +72 -6
- package/src/components/FlowContextSelector.tsx +2 -2
- package/src/components/FormItem.tsx +12 -7
- package/src/components/MobilePopup.style.ts +12 -1
- package/src/components/MobilePopup.tsx +30 -10
- package/src/components/__tests__/FormItem.test.tsx +17 -2
- package/src/components/__tests__/MobilePopup.test.tsx +109 -0
- package/src/components/variables/VariableInput.tsx +35 -7
- package/src/components/variables/VariableTag.tsx +47 -34
- package/src/components/variables/__tests__/VariableInput.test.tsx +105 -1
- package/src/components/variables/__tests__/VariableTag.test.tsx +80 -0
- package/src/components/variables/types.ts +2 -0
- package/src/flowEngine.ts +6 -0
- package/src/utils/__tests__/dirtyAwareApiClient.test.ts +321 -0
- package/src/utils/dirtyAwareApiClient.ts +325 -13
- package/src/utils/loadedPageCache.ts +7 -0
|
@@ -31,8 +31,10 @@ __export(dirtyAwareApiClient_exports, {
|
|
|
31
31
|
getDirtyAwareApiClient: () => getDirtyAwareApiClient
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(dirtyAwareApiClient_exports);
|
|
34
|
+
var import_sdk = require("@nocobase/sdk");
|
|
34
35
|
var import_dataSourceDirty = require("./dataSourceDirty");
|
|
35
36
|
const SKIP_DATA_SOURCE_DIRTY = "__nocobaseSkipDataSourceDirty";
|
|
37
|
+
const DIRTY_DISPATCH_TOKEN = Symbol("nocobaseDirtyDispatchToken");
|
|
36
38
|
const dirtyAwareApiClientCache = /* @__PURE__ */ new WeakMap();
|
|
37
39
|
const dirtyAwareApiClientProxies = /* @__PURE__ */ new WeakSet();
|
|
38
40
|
const MUTATING_RESOURCE_ACTIONS = [
|
|
@@ -288,6 +290,43 @@ function resolveDirtyResourceAction(options, context) {
|
|
|
288
290
|
return parseDirtyResourceActionFromUrl(options == null ? void 0 : options.url, context);
|
|
289
291
|
}
|
|
290
292
|
__name(resolveDirtyResourceAction, "resolveDirtyResourceAction");
|
|
293
|
+
function getDirtyResourceActionDispatchKey(dirtyResourceAction, headers) {
|
|
294
|
+
if (!dirtyResourceAction) {
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
return JSON.stringify([
|
|
298
|
+
dirtyResourceAction.dataSourceKey || (0, import_dataSourceDirty.getDataSourceKeyFromHeaders)(headers),
|
|
299
|
+
dirtyResourceAction.resourceName,
|
|
300
|
+
dirtyResourceAction.actionName
|
|
301
|
+
]);
|
|
302
|
+
}
|
|
303
|
+
__name(getDirtyResourceActionDispatchKey, "getDirtyResourceActionDispatchKey");
|
|
304
|
+
function getRequestDispatchKey(options, context) {
|
|
305
|
+
return getDirtyResourceActionDispatchKey(resolveDirtyResourceAction(options, context), options.headers);
|
|
306
|
+
}
|
|
307
|
+
__name(getRequestDispatchKey, "getRequestDispatchKey");
|
|
308
|
+
function getResourceDispatchKey(name, of, headers) {
|
|
309
|
+
const resourceName = String(name ?? "").trim();
|
|
310
|
+
if (!resourceName) {
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
return JSON.stringify([resourceName, String(of ?? ""), (0, import_dataSourceDirty.getDataSourceKeyFromHeaders)(headers)]);
|
|
314
|
+
}
|
|
315
|
+
__name(getResourceDispatchKey, "getResourceDispatchKey");
|
|
316
|
+
function getMatchingRequestToken(token, requestKey) {
|
|
317
|
+
if (!token || !requestKey || token.requestKey && token.requestKey !== requestKey) {
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
return token;
|
|
321
|
+
}
|
|
322
|
+
__name(getMatchingRequestToken, "getMatchingRequestToken");
|
|
323
|
+
function getMatchingResourceToken(token, resourceKey) {
|
|
324
|
+
if (!token || !resourceKey || token.resourceKey && token.resourceKey !== resourceKey) {
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
return token;
|
|
328
|
+
}
|
|
329
|
+
__name(getMatchingResourceToken, "getMatchingResourceToken");
|
|
291
330
|
function markResourceActionDataSourceDirty(context, dirtyResourceAction, headers) {
|
|
292
331
|
(0, import_dataSourceDirty.markDataSourceDirty)({
|
|
293
332
|
engine: context.engine,
|
|
@@ -297,7 +336,19 @@ function markResourceActionDataSourceDirty(context, dirtyResourceAction, headers
|
|
|
297
336
|
});
|
|
298
337
|
}
|
|
299
338
|
__name(markResourceActionDataSourceDirty, "markResourceActionDataSourceDirty");
|
|
300
|
-
function
|
|
339
|
+
function markResourceActionDataSourceDirtyOnce(token, context, dirtyResourceAction, headers) {
|
|
340
|
+
if (token.skip || token.marked || !dirtyResourceAction || !isMutatingResourceAction(dirtyResourceAction.actionName)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
token.marked = true;
|
|
344
|
+
markResourceActionDataSourceDirty(context, dirtyResourceAction, headers);
|
|
345
|
+
}
|
|
346
|
+
__name(markResourceActionDataSourceDirtyOnce, "markResourceActionDataSourceDirtyOnce");
|
|
347
|
+
function isObjectRecord(value) {
|
|
348
|
+
return !!value && typeof value === "object";
|
|
349
|
+
}
|
|
350
|
+
__name(isObjectRecord, "isObjectRecord");
|
|
351
|
+
function createDirtyAwareResource(context, resource, resourceName, resourceOf, headers, requestTokenStack, parentToken) {
|
|
301
352
|
return new Proxy(resource, {
|
|
302
353
|
get(target, prop, receiver) {
|
|
303
354
|
const original = Reflect.get(target, prop, receiver);
|
|
@@ -306,10 +357,38 @@ function createDirtyAwareResource(context, resource, resourceName, resourceOf, h
|
|
|
306
357
|
}
|
|
307
358
|
const action = original;
|
|
308
359
|
return async (...args) => {
|
|
309
|
-
const
|
|
360
|
+
const actionOptions = isObjectRecord(args[1]) ? args[1] : void 0;
|
|
310
361
|
const dirtyResourceAction = resolveDirtyResourceActionFromResource(resourceName, resourceOf, prop, context);
|
|
311
|
-
|
|
312
|
-
|
|
362
|
+
const requestKey = getDirtyResourceActionDispatchKey(dirtyResourceAction, headers);
|
|
363
|
+
const resourceKey = getResourceDispatchKey(resourceName, resourceOf, headers);
|
|
364
|
+
const inheritedToken = getMatchingRequestToken(
|
|
365
|
+
actionOptions == null ? void 0 : actionOptions[DIRTY_DISPATCH_TOKEN],
|
|
366
|
+
requestKey
|
|
367
|
+
) || getMatchingRequestToken(parentToken, requestKey);
|
|
368
|
+
const token = inheritedToken || { marked: false, skip: false };
|
|
369
|
+
const ownsToken = !inheritedToken;
|
|
370
|
+
token.requestKey ||= requestKey;
|
|
371
|
+
token.resourceKey ||= resourceKey;
|
|
372
|
+
if (actionOptions == null ? void 0 : actionOptions[SKIP_DATA_SOURCE_DIRTY]) {
|
|
373
|
+
token.skip = true;
|
|
374
|
+
}
|
|
375
|
+
const forwardedArgs = actionOptions || args[1] == null ? [
|
|
376
|
+
args[0],
|
|
377
|
+
{
|
|
378
|
+
...actionOptions,
|
|
379
|
+
[DIRTY_DISPATCH_TOKEN]: token
|
|
380
|
+
}
|
|
381
|
+
] : args;
|
|
382
|
+
let actionResult;
|
|
383
|
+
requestTokenStack.push({ key: requestKey, token });
|
|
384
|
+
try {
|
|
385
|
+
actionResult = Reflect.apply(action, receiver, forwardedArgs);
|
|
386
|
+
} finally {
|
|
387
|
+
requestTokenStack.pop();
|
|
388
|
+
}
|
|
389
|
+
const result = await actionResult;
|
|
390
|
+
if (ownsToken) {
|
|
391
|
+
markResourceActionDataSourceDirtyOnce(token, context, dirtyResourceAction, headers);
|
|
313
392
|
}
|
|
314
393
|
return result;
|
|
315
394
|
};
|
|
@@ -318,31 +397,206 @@ function createDirtyAwareResource(context, resource, resourceName, resourceOf, h
|
|
|
318
397
|
}
|
|
319
398
|
__name(createDirtyAwareResource, "createDirtyAwareResource");
|
|
320
399
|
function createDirtyAwareApiClient(api, context) {
|
|
400
|
+
const baseResource = api.resource;
|
|
401
|
+
const baseRequest = api.request;
|
|
402
|
+
const shouldUseResourceDispatchReceiver = baseResource === import_sdk.APIClient.prototype.resource;
|
|
403
|
+
const shouldUseRequestDispatchReceiver = baseRequest === import_sdk.APIClient.prototype.request;
|
|
404
|
+
const resourceTokenStack = [];
|
|
405
|
+
const requestTokenStack = [];
|
|
406
|
+
let hasResourceOverride = false;
|
|
407
|
+
let resourceOverride;
|
|
408
|
+
let hasRequestOverride = false;
|
|
409
|
+
let requestOverride;
|
|
410
|
+
const getCurrentResource = /* @__PURE__ */ __name(() => hasResourceOverride ? resourceOverride : resource, "getCurrentResource");
|
|
411
|
+
const getCurrentRequest = /* @__PURE__ */ __name(() => hasRequestOverride ? requestOverride : request, "getCurrentRequest");
|
|
412
|
+
const dispatchResource = /* @__PURE__ */ __name((token, args) => {
|
|
413
|
+
const resourceKey = getResourceDispatchKey(args[0], args[1], args[2]);
|
|
414
|
+
const activeToken = getMatchingResourceToken(token, resourceKey);
|
|
415
|
+
const shouldWrapActions = !!activeToken && hasResourceOverride;
|
|
416
|
+
if (activeToken) {
|
|
417
|
+
activeToken.resourceKey ||= resourceKey;
|
|
418
|
+
resourceTokenStack.push({ key: resourceKey, token: activeToken });
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const resourceInstance = Reflect.apply(getCurrentResource(), proxy, args);
|
|
422
|
+
if (!shouldWrapActions || !activeToken) {
|
|
423
|
+
return resourceInstance;
|
|
424
|
+
}
|
|
425
|
+
return new Proxy(resourceInstance, {
|
|
426
|
+
get(target, prop, receiver) {
|
|
427
|
+
const original = Reflect.get(target, prop, receiver);
|
|
428
|
+
if (typeof prop !== "string" || typeof original !== "function" || !isMutatingResourceAction(prop)) {
|
|
429
|
+
return original;
|
|
430
|
+
}
|
|
431
|
+
const action = original;
|
|
432
|
+
return (...actionArgs) => {
|
|
433
|
+
const actionOptions = isObjectRecord(actionArgs[1]) ? actionArgs[1] : void 0;
|
|
434
|
+
const forwardedArgs = actionOptions || actionArgs[1] == null ? [
|
|
435
|
+
actionArgs[0],
|
|
436
|
+
{
|
|
437
|
+
...actionOptions,
|
|
438
|
+
[DIRTY_DISPATCH_TOKEN]: activeToken
|
|
439
|
+
}
|
|
440
|
+
] : actionArgs;
|
|
441
|
+
resourceTokenStack.push({ key: resourceKey, token: activeToken });
|
|
442
|
+
requestTokenStack.push({ key: activeToken.requestKey, token: activeToken });
|
|
443
|
+
try {
|
|
444
|
+
return Reflect.apply(action, receiver, forwardedArgs);
|
|
445
|
+
} finally {
|
|
446
|
+
requestTokenStack.pop();
|
|
447
|
+
resourceTokenStack.pop();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
if (activeToken) {
|
|
454
|
+
resourceTokenStack.pop();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}, "dispatchResource");
|
|
458
|
+
const createDispatchReceiver = /* @__PURE__ */ __name((token) => {
|
|
459
|
+
const receiver = Object.create(api);
|
|
460
|
+
Object.defineProperties(receiver, {
|
|
461
|
+
request: {
|
|
462
|
+
configurable: true,
|
|
463
|
+
value: /* @__PURE__ */ __name((config) => {
|
|
464
|
+
const options = config;
|
|
465
|
+
const requestKey = getRequestDispatchKey(options, context);
|
|
466
|
+
const stackFrame = requestTokenStack.at(-1);
|
|
467
|
+
const activeToken = getMatchingRequestToken(options == null ? void 0 : options[DIRTY_DISPATCH_TOKEN], requestKey) || (requestKey && (stackFrame == null ? void 0 : stackFrame.key) === requestKey ? stackFrame.token : void 0) || getMatchingRequestToken(token, requestKey);
|
|
468
|
+
const { [DIRTY_DISPATCH_TOKEN]: _dirtyDispatchToken, ...cleanOptions } = options;
|
|
469
|
+
const configWithToken = activeToken ? { ...cleanOptions, [DIRTY_DISPATCH_TOKEN]: activeToken } : cleanOptions;
|
|
470
|
+
if (activeToken) {
|
|
471
|
+
activeToken.requestKey ||= requestKey;
|
|
472
|
+
requestTokenStack.push({ key: requestKey, token: activeToken });
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
return Reflect.apply(getCurrentRequest(), proxy, [configWithToken]);
|
|
476
|
+
} finally {
|
|
477
|
+
if (activeToken) {
|
|
478
|
+
requestTokenStack.pop();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}, "value")
|
|
482
|
+
},
|
|
483
|
+
resource: {
|
|
484
|
+
configurable: true,
|
|
485
|
+
value: /* @__PURE__ */ __name((...args) => {
|
|
486
|
+
const resourceKey = getResourceDispatchKey(args[0], args[1], args[2]);
|
|
487
|
+
const stackFrame = resourceTokenStack.at(-1);
|
|
488
|
+
const activeToken = getMatchingResourceToken(token, resourceKey) || (resourceKey && (stackFrame == null ? void 0 : stackFrame.key) === resourceKey ? stackFrame.token : void 0);
|
|
489
|
+
return dispatchResource(activeToken, args);
|
|
490
|
+
}, "value")
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
return receiver;
|
|
494
|
+
}, "createDispatchReceiver");
|
|
321
495
|
const resource = /* @__PURE__ */ __name((name, of, headers, cancel) => {
|
|
322
|
-
const
|
|
323
|
-
|
|
496
|
+
const resourceKey = getResourceDispatchKey(name, of, headers);
|
|
497
|
+
const stackFrame = resourceTokenStack.at(-1);
|
|
498
|
+
const parentToken = resourceKey && (stackFrame == null ? void 0 : stackFrame.key) === resourceKey ? stackFrame.token : void 0;
|
|
499
|
+
const receiver = createDispatchReceiver(parentToken);
|
|
500
|
+
const resourceInstance = Reflect.apply(baseResource, shouldUseResourceDispatchReceiver ? receiver : api, [
|
|
501
|
+
name,
|
|
502
|
+
of,
|
|
503
|
+
headers,
|
|
504
|
+
cancel
|
|
505
|
+
]);
|
|
506
|
+
return createDirtyAwareResource(context, resourceInstance, name, of, headers, requestTokenStack, parentToken);
|
|
324
507
|
}, "resource");
|
|
325
508
|
const request = /* @__PURE__ */ __name((config) => {
|
|
326
509
|
const options = config;
|
|
510
|
+
const requestKey = getRequestDispatchKey(options, context);
|
|
511
|
+
const stackFrame = requestTokenStack.at(-1);
|
|
512
|
+
const inheritedToken = getMatchingRequestToken(options == null ? void 0 : options[DIRTY_DISPATCH_TOKEN], requestKey) || (requestKey && (stackFrame == null ? void 0 : stackFrame.key) === requestKey ? stackFrame.token : void 0);
|
|
513
|
+
const token = inheritedToken || { marked: false, skip: false };
|
|
514
|
+
const ownsToken = !inheritedToken;
|
|
515
|
+
token.requestKey ||= requestKey;
|
|
516
|
+
if (typeof options.resource === "string") {
|
|
517
|
+
token.resourceKey ||= getResourceDispatchKey(options.resource, options.resourceOf, options.headers);
|
|
518
|
+
}
|
|
327
519
|
const skipDataSourceDirty = options == null ? void 0 : options[SKIP_DATA_SOURCE_DIRTY];
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
520
|
+
if (skipDataSourceDirty) {
|
|
521
|
+
token.skip = true;
|
|
522
|
+
}
|
|
523
|
+
const dirtyResourceAction = resolveDirtyResourceAction(options, context);
|
|
524
|
+
const {
|
|
525
|
+
[DIRTY_DISPATCH_TOKEN]: _dirtyDispatchToken,
|
|
526
|
+
[SKIP_DATA_SOURCE_DIRTY]: _skipDataSourceDirty,
|
|
527
|
+
...cleanConfig
|
|
528
|
+
} = options;
|
|
529
|
+
const receiver = createDispatchReceiver(token);
|
|
530
|
+
return Reflect.apply(baseRequest, shouldUseRequestDispatchReceiver ? receiver : api, [cleanConfig]).then((result) => {
|
|
531
|
+
if (ownsToken) {
|
|
532
|
+
markResourceActionDataSourceDirtyOnce(token, context, dirtyResourceAction, options.headers);
|
|
333
533
|
}
|
|
334
534
|
return result;
|
|
335
535
|
});
|
|
336
536
|
}, "request");
|
|
537
|
+
const isLockedOwnProperty = /* @__PURE__ */ __name((target, prop) => {
|
|
538
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
539
|
+
return !!descriptor && !descriptor.configurable;
|
|
540
|
+
}, "isLockedOwnProperty");
|
|
337
541
|
const proxy = new Proxy(api, {
|
|
338
542
|
get(target, prop, receiver) {
|
|
339
543
|
if (prop === "resource") {
|
|
340
|
-
|
|
544
|
+
if (isLockedOwnProperty(target, prop)) {
|
|
545
|
+
return Reflect.get(target, prop, receiver);
|
|
546
|
+
}
|
|
547
|
+
return hasResourceOverride ? resourceOverride : resource;
|
|
341
548
|
}
|
|
342
549
|
if (prop === "request") {
|
|
343
|
-
|
|
550
|
+
if (isLockedOwnProperty(target, prop)) {
|
|
551
|
+
return Reflect.get(target, prop, receiver);
|
|
552
|
+
}
|
|
553
|
+
return hasRequestOverride ? requestOverride : request;
|
|
344
554
|
}
|
|
345
555
|
return Reflect.get(target, prop, receiver);
|
|
556
|
+
},
|
|
557
|
+
set(target, prop, value, receiver) {
|
|
558
|
+
if (prop === "resource") {
|
|
559
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
560
|
+
if (descriptor && (!descriptor.configurable || !Reflect.isExtensible(target))) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
hasResourceOverride = true;
|
|
564
|
+
resourceOverride = value;
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (prop === "request") {
|
|
568
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
569
|
+
if (descriptor && (!descriptor.configurable || !Reflect.isExtensible(target))) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
hasRequestOverride = true;
|
|
573
|
+
requestOverride = value;
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
return Reflect.set(target, prop, value, receiver);
|
|
577
|
+
},
|
|
578
|
+
deleteProperty(target, prop) {
|
|
579
|
+
if (prop !== "resource" && prop !== "request") {
|
|
580
|
+
return Reflect.deleteProperty(target, prop);
|
|
581
|
+
}
|
|
582
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
583
|
+
if (descriptor && (!descriptor.configurable || !Reflect.isExtensible(target))) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
if (prop === "resource") {
|
|
587
|
+
hasResourceOverride = false;
|
|
588
|
+
resourceOverride = void 0;
|
|
589
|
+
} else {
|
|
590
|
+
hasRequestOverride = false;
|
|
591
|
+
requestOverride = void 0;
|
|
592
|
+
}
|
|
593
|
+
return true;
|
|
594
|
+
},
|
|
595
|
+
defineProperty(target, prop, descriptor) {
|
|
596
|
+
if (prop === "resource" || prop === "request") {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
return Reflect.defineProperty(target, prop, descriptor);
|
|
346
600
|
}
|
|
347
601
|
});
|
|
348
602
|
dirtyAwareApiClientProxies.add(proxy);
|
|
@@ -17,6 +17,7 @@ type DirtyKeyOptions = {
|
|
|
17
17
|
export declare const createLoadedPageCache: () => {
|
|
18
18
|
getDirtyKeyForModel(model?: FlowModel | null, options?: DirtyKeyOptions): string | undefined;
|
|
19
19
|
markDirty(key?: string): void;
|
|
20
|
+
markDirtyForOptions(options?: LoadedPageOptions): void;
|
|
20
21
|
shouldBypass(options?: LoadedPageOptions, isFlowSettingsEnabled?: () => boolean): boolean;
|
|
21
22
|
clear(options?: LoadedPageOptions): void;
|
|
22
23
|
mountModelToParent: <T extends FlowModel<import("..").DefaultStructure> = FlowModel<import("..").DefaultStructure>>(model: T, forceReplace?: boolean) => T;
|
|
@@ -113,6 +113,12 @@ const createLoadedPageCache = /* @__PURE__ */ __name(() => {
|
|
|
113
113
|
dirtyKeys.add(key);
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
|
+
markDirtyForOptions(options) {
|
|
117
|
+
const key = getLoadedPageKey(options);
|
|
118
|
+
if (key) {
|
|
119
|
+
dirtyKeys.add(key);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
116
122
|
shouldBypass(options, isFlowSettingsEnabled) {
|
|
117
123
|
const key = getLoadedPageKey(options);
|
|
118
124
|
if (!key || !dirtyKeys.has(key)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.8",
|
|
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": "2.2.0-alpha.
|
|
12
|
-
"@nocobase/shared": "2.2.0-alpha.
|
|
11
|
+
"@nocobase/sdk": "2.2.0-alpha.8",
|
|
12
|
+
"@nocobase/shared": "2.2.0-alpha.8",
|
|
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": "810c81e5963966bda7ec03b6453a3a80fe60e027"
|
|
41
41
|
}
|
|
@@ -340,9 +340,9 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
340
340
|
const repository = new DirtyPageRepository();
|
|
341
341
|
root.setModelRepository(repository);
|
|
342
342
|
|
|
343
|
-
class ParentModel extends FlowModel {}
|
|
344
|
-
class PageModel extends FlowModel {}
|
|
345
343
|
class BlockModel extends FlowModel {}
|
|
344
|
+
class PageModel extends FlowModel<{ parent?: FlowModel; subModels: { items: BlockModel[] } }> {}
|
|
345
|
+
class ParentModel extends FlowModel<{ parent?: FlowModel; subModels: { page?: PageModel } }> {}
|
|
346
346
|
root.registerModels({ ParentModel, PageModel, BlockModel });
|
|
347
347
|
|
|
348
348
|
const parent = root.createModel<ParentModel>({ use: 'ParentModel', uid: 'popup-action' });
|
|
@@ -357,7 +357,10 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
357
357
|
items: [{ use: 'BlockModel', uid: 'stale-block' }],
|
|
358
358
|
},
|
|
359
359
|
});
|
|
360
|
-
const staleBlock = stalePage.findSubModel('items'
|
|
360
|
+
const staleBlock = stalePage.findSubModel('items', (item) => item.uid === 'stale-block');
|
|
361
|
+
if (!staleBlock) {
|
|
362
|
+
throw new Error('Expected stale block to be loaded');
|
|
363
|
+
}
|
|
361
364
|
parent.setSubModel('page', stalePage);
|
|
362
365
|
oldScoped.unlinkFromStack();
|
|
363
366
|
|
|
@@ -372,7 +375,7 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
372
375
|
},
|
|
373
376
|
};
|
|
374
377
|
|
|
375
|
-
root.flowSettings.enable();
|
|
378
|
+
await root.flowSettings.enable();
|
|
376
379
|
await staleBlock.saveStepParams();
|
|
377
380
|
root.flowSettings.disable();
|
|
378
381
|
repository.findOneCalls = 0;
|
|
@@ -388,8 +391,8 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
388
391
|
|
|
389
392
|
expect(repository.findOneCalls).toBe(1);
|
|
390
393
|
expect(loaded).not.toBe(stalePage);
|
|
391
|
-
expect(
|
|
392
|
-
expect(loaded?.mapSubModels('items'
|
|
394
|
+
expect(parent.subModels.page).toBe(loaded);
|
|
395
|
+
expect(loaded?.mapSubModels('items', (item) => item.uid)).toEqual(['fresh-block']);
|
|
393
396
|
|
|
394
397
|
repository.findOneCalls = 0;
|
|
395
398
|
const nextRuntimeScoped = createViewScopedEngine(root);
|
|
@@ -405,6 +408,69 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
405
408
|
expect(loadedAgain?.uid).toBe('popup-page');
|
|
406
409
|
});
|
|
407
410
|
|
|
411
|
+
it('reloads a page after it was loaded in flow settings mode', async () => {
|
|
412
|
+
const root = new FlowEngine();
|
|
413
|
+
const repository = new DirtyPageRepository();
|
|
414
|
+
root.setModelRepository(repository);
|
|
415
|
+
|
|
416
|
+
class ParentModel extends FlowModel {}
|
|
417
|
+
class PageModel extends FlowModel {}
|
|
418
|
+
class BlockModel extends FlowModel {}
|
|
419
|
+
root.registerModels({ ParentModel, PageModel, BlockModel });
|
|
420
|
+
|
|
421
|
+
const parent = root.createModel<ParentModel>({ use: 'ParentModel', uid: 'settings-popup-action' });
|
|
422
|
+
repository.data = {
|
|
423
|
+
use: 'PageModel',
|
|
424
|
+
uid: 'settings-popup-page',
|
|
425
|
+
parentId: parent.uid,
|
|
426
|
+
subKey: 'page',
|
|
427
|
+
subType: 'object',
|
|
428
|
+
subModels: {
|
|
429
|
+
items: [{ use: 'BlockModel', uid: 'stale-settings-block' }],
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
await root.flowSettings.enable();
|
|
434
|
+
const designScoped = createViewScopedEngine(root);
|
|
435
|
+
const designLoaded = await designScoped.loadOrCreateModel<PageModel>({
|
|
436
|
+
async: true,
|
|
437
|
+
parentId: parent.uid,
|
|
438
|
+
subKey: 'page',
|
|
439
|
+
subType: 'object',
|
|
440
|
+
use: 'PageModel',
|
|
441
|
+
});
|
|
442
|
+
expect(repository.findOneCalls).toBe(1);
|
|
443
|
+
expect(designLoaded?.mapSubModels('items', (item) => item.uid)).toEqual(['stale-settings-block']);
|
|
444
|
+
designScoped.unlinkFromStack();
|
|
445
|
+
|
|
446
|
+
repository.data = {
|
|
447
|
+
use: 'PageModel',
|
|
448
|
+
uid: 'settings-popup-page',
|
|
449
|
+
parentId: parent.uid,
|
|
450
|
+
subKey: 'page',
|
|
451
|
+
subType: 'object',
|
|
452
|
+
subModels: {
|
|
453
|
+
items: [{ use: 'BlockModel', uid: 'fresh-settings-block' }],
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
root.flowSettings.disable();
|
|
457
|
+
repository.findOneCalls = 0;
|
|
458
|
+
|
|
459
|
+
const runtimeScoped = createViewScopedEngine(root);
|
|
460
|
+
const runtimeLoaded = await runtimeScoped.loadOrCreateModel<PageModel>({
|
|
461
|
+
async: true,
|
|
462
|
+
parentId: parent.uid,
|
|
463
|
+
subKey: 'page',
|
|
464
|
+
subType: 'object',
|
|
465
|
+
use: 'PageModel',
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
expect(repository.findOneCalls).toBe(1);
|
|
469
|
+
expect(runtimeLoaded).not.toBe(designLoaded);
|
|
470
|
+
expect(parent.subModels.page).toBe(runtimeLoaded);
|
|
471
|
+
expect(runtimeLoaded?.mapSubModels('items', (item) => item.uid)).toEqual(['fresh-settings-block']);
|
|
472
|
+
});
|
|
473
|
+
|
|
408
474
|
it('does not bypass loaded page cache after a non-config save', async () => {
|
|
409
475
|
const root = new FlowEngine();
|
|
410
476
|
const repository = new DirtyPageRepository();
|
|
@@ -292,11 +292,11 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
292
292
|
const defaultChildren = useMemo(() => {
|
|
293
293
|
const hasSelected = active ?? Boolean(currentPath && currentPath.length > 0);
|
|
294
294
|
return (
|
|
295
|
-
<Button type={hasSelected ? 'primary' : 'default'} style={defaultButtonStyle}>
|
|
295
|
+
<Button type={hasSelected ? 'primary' : 'default'} style={defaultButtonStyle} disabled={cascaderProps.disabled}>
|
|
296
296
|
x
|
|
297
297
|
</Button>
|
|
298
298
|
);
|
|
299
|
-
}, [active, currentPath]);
|
|
299
|
+
}, [active, cascaderProps.disabled, currentPath]);
|
|
300
300
|
|
|
301
301
|
// 处理选择变化事件
|
|
302
302
|
const handleChange = useCallback(
|
|
@@ -54,15 +54,20 @@ const formItemPropKeys: (keyof ExtendedFormItemProps)[] = [
|
|
|
54
54
|
'showLabel',
|
|
55
55
|
];
|
|
56
56
|
|
|
57
|
+
const modelInternalPropKeys = ['globalSort'];
|
|
58
|
+
|
|
57
59
|
export const FormItem = ({
|
|
58
60
|
children,
|
|
59
61
|
showLabel = true,
|
|
60
62
|
labelWidth,
|
|
61
63
|
...rest
|
|
62
64
|
}: ExtendedFormItemProps & ChildExtraProps) => {
|
|
65
|
+
const forwardedRest = Object.fromEntries(
|
|
66
|
+
Object.entries(rest).filter(([key]) => !modelInternalPropKeys.includes(key)),
|
|
67
|
+
) as ExtendedFormItemProps & ChildExtraProps;
|
|
63
68
|
// 过滤掉 Form.Item 专用 props,只保留要传给子组件的
|
|
64
69
|
const childProps = Object.fromEntries(
|
|
65
|
-
Object.entries(
|
|
70
|
+
Object.entries(forwardedRest).filter(([key]) => !formItemPropKeys.includes(key as keyof ExtendedFormItemProps)),
|
|
66
71
|
);
|
|
67
72
|
|
|
68
73
|
const processedChildren =
|
|
@@ -74,7 +79,7 @@ export const FormItem = ({
|
|
|
74
79
|
}
|
|
75
80
|
return child;
|
|
76
81
|
});
|
|
77
|
-
const { label, labelWrap, colon = true, layout } =
|
|
82
|
+
const { label, labelWrap, colon = true, layout } = forwardedRest;
|
|
78
83
|
const effectiveLabelWrap = !layout || layout === 'vertical' ? true : labelWrap;
|
|
79
84
|
const labelColStyle =
|
|
80
85
|
layout === 'vertical' ? { width: labelWidth, ...verticalFormItemLabelStyle } : { width: labelWidth };
|
|
@@ -122,17 +127,17 @@ export const FormItem = ({
|
|
|
122
127
|
};
|
|
123
128
|
return (
|
|
124
129
|
<Form.Item
|
|
125
|
-
{...
|
|
126
|
-
style={{ ...formItemStyle, ...
|
|
130
|
+
{...forwardedRest}
|
|
131
|
+
style={{ ...formItemStyle, ...forwardedRest.style }}
|
|
127
132
|
labelCol={{ style: labelColStyle }}
|
|
128
133
|
layout={layout}
|
|
129
134
|
label={renderLabel()}
|
|
130
135
|
colon={false}
|
|
131
|
-
extra={
|
|
136
|
+
extra={forwardedRest.extra && <span style={{ whiteSpace: 'pre-wrap' }}>{forwardedRest.extra}</span>}
|
|
132
137
|
tooltip={
|
|
133
|
-
|
|
138
|
+
forwardedRest.tooltip &&
|
|
134
139
|
({
|
|
135
|
-
title:
|
|
140
|
+
title: forwardedRest.tooltip,
|
|
136
141
|
overlayInnerStyle: { whiteSpace: 'pre-line' },
|
|
137
142
|
} as TooltipProps)
|
|
138
143
|
}
|
|
@@ -160,7 +160,7 @@ export const useMobileActionDrawerStyle = genStyleHook('nb-mobile-action-drawer'
|
|
|
160
160
|
borderBottom: `1px solid ${token.colorSplit}`,
|
|
161
161
|
position: 'sticky',
|
|
162
162
|
top: 0,
|
|
163
|
-
backgroundColor:
|
|
163
|
+
backgroundColor: token.colorBgContainer,
|
|
164
164
|
zIndex: 1000,
|
|
165
165
|
|
|
166
166
|
// to match the button named 'Add block'
|
|
@@ -172,12 +172,23 @@ export const useMobileActionDrawerStyle = genStyleHook('nb-mobile-action-drawer'
|
|
|
172
172
|
'.nb-mobile-action-drawer-placeholder': {
|
|
173
173
|
display: 'inline-block',
|
|
174
174
|
padding: 12,
|
|
175
|
+
flex: '0 0 auto',
|
|
175
176
|
visibility: 'hidden',
|
|
176
177
|
},
|
|
177
178
|
|
|
179
|
+
'.nb-mobile-action-drawer-title': {
|
|
180
|
+
flex: '1 1 auto',
|
|
181
|
+
minWidth: 0,
|
|
182
|
+
overflow: 'hidden',
|
|
183
|
+
textAlign: 'center',
|
|
184
|
+
textOverflow: 'ellipsis',
|
|
185
|
+
whiteSpace: 'nowrap',
|
|
186
|
+
},
|
|
187
|
+
|
|
178
188
|
'.nb-mobile-action-drawer-close-icon': {
|
|
179
189
|
display: 'inline-block',
|
|
180
190
|
padding: 12,
|
|
191
|
+
flex: '0 0 auto',
|
|
181
192
|
cursor: 'pointer',
|
|
182
193
|
},
|
|
183
194
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { ConfigProvider } from 'antd';
|
|
11
|
-
import React, { FC, ReactNode, useMemo } from 'react';
|
|
11
|
+
import React, { FC, ReactNode, useCallback, useMemo } from 'react';
|
|
12
12
|
import { useMobileActionDrawerStyle } from './MobilePopup.style';
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
14
|
import { lazy } from '../lazy-helper';
|
|
@@ -31,11 +31,33 @@ export const MobilePopup: FC<MobilePopupProps> = (props) => {
|
|
|
31
31
|
const { t } = useTranslation();
|
|
32
32
|
const { componentCls, hashId } = useMobileActionDrawerStyle();
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const bodyStyles = (props as MobilePopupProps & { styles?: { body?: React.CSSProperties } }).styles?.body;
|
|
35
|
+
const popupStyle = useMemo(() => {
|
|
35
36
|
return {
|
|
36
|
-
minHeight,
|
|
37
|
+
minHeight: bodyStyles?.minHeight ?? minHeight,
|
|
38
|
+
height: bodyStyles?.height,
|
|
39
|
+
maxHeight: bodyStyles?.maxHeight,
|
|
37
40
|
};
|
|
38
|
-
}, [minHeight]);
|
|
41
|
+
}, [bodyStyles?.height, bodyStyles?.maxHeight, bodyStyles?.minHeight, minHeight]);
|
|
42
|
+
|
|
43
|
+
const bodyStyle = useMemo(() => {
|
|
44
|
+
return {
|
|
45
|
+
padding: 0,
|
|
46
|
+
...bodyStyles,
|
|
47
|
+
};
|
|
48
|
+
}, [bodyStyles]);
|
|
49
|
+
|
|
50
|
+
const handleCloseKeyDown = useCallback(
|
|
51
|
+
(event: React.KeyboardEvent<HTMLSpanElement>) => {
|
|
52
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
closePopup();
|
|
58
|
+
},
|
|
59
|
+
[closePopup],
|
|
60
|
+
);
|
|
39
61
|
|
|
40
62
|
const theme = useMemo(() => {
|
|
41
63
|
return {
|
|
@@ -57,11 +79,8 @@ export const MobilePopup: FC<MobilePopupProps> = (props) => {
|
|
|
57
79
|
onClose={closePopup}
|
|
58
80
|
onMaskClick={closePopup}
|
|
59
81
|
bodyClassName="nb-mobile-action-drawer-body"
|
|
60
|
-
bodyStyle={
|
|
61
|
-
|
|
62
|
-
}}
|
|
63
|
-
maskStyle={style}
|
|
64
|
-
style={style}
|
|
82
|
+
bodyStyle={bodyStyle}
|
|
83
|
+
style={popupStyle}
|
|
65
84
|
destroyOnClose
|
|
66
85
|
>
|
|
67
86
|
<div className="nb-mobile-action-drawer-header">
|
|
@@ -69,13 +88,14 @@ export const MobilePopup: FC<MobilePopupProps> = (props) => {
|
|
|
69
88
|
<span className="nb-mobile-action-drawer-placeholder">
|
|
70
89
|
<CloseOutline />
|
|
71
90
|
</span>
|
|
72
|
-
<span>{title}</span>
|
|
91
|
+
<span className="nb-mobile-action-drawer-title">{title}</span>
|
|
73
92
|
<span
|
|
74
93
|
className="nb-mobile-action-drawer-close-icon"
|
|
75
94
|
onClick={closePopup}
|
|
76
95
|
role="button"
|
|
77
96
|
tabIndex={0}
|
|
78
97
|
aria-label={t('Close')}
|
|
98
|
+
onKeyDown={handleCloseKeyDown}
|
|
79
99
|
>
|
|
80
100
|
<CloseOutline />
|
|
81
101
|
</span>
|