@nocobase/client-v2 2.2.0-beta.9 → 2.3.0-alpha.1

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 (112) hide show
  1. package/es/APIClient.d.ts +1 -3
  2. package/es/BaseApplication.d.ts +1 -0
  3. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  4. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  5. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  6. package/es/components/category-tabs/index.d.ts +9 -0
  7. package/es/components/form/ScanInput/useCodeScanner.d.ts +2 -1
  8. package/es/components/form/TypedVariableInput.d.ts +31 -5
  9. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  10. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  11. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  12. package/es/components/form/filter/index.d.ts +2 -0
  13. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  14. package/es/components/index.d.ts +1 -0
  15. package/es/flow/actions/index.d.ts +1 -1
  16. package/es/flow/actions/linkageRules.d.ts +2 -0
  17. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  18. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  19. package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
  20. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  21. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  22. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  23. package/es/flow/index.d.ts +1 -0
  24. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  25. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  26. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  27. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  28. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  29. package/es/flow-compat/routeTypes.d.ts +1 -0
  30. package/es/index.d.ts +4 -0
  31. package/es/index.mjs +153 -118
  32. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  33. package/es/utils/index.d.ts +1 -0
  34. package/lib/index.js +162 -127
  35. package/lib/locale/languageCodes.js +2 -1
  36. package/package.json +8 -7
  37. package/src/APIClient.ts +1 -10
  38. package/src/Application.tsx +4 -0
  39. package/src/BaseApplication.tsx +11 -6
  40. package/src/__tests__/app.test.tsx +73 -0
  41. package/src/__tests__/browserChecker.test.ts +61 -0
  42. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  43. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +28 -0
  44. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  45. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  46. package/src/collection-manager/field-validation.ts +1 -1
  47. package/src/components/README.md +7 -1
  48. package/src/components/README.zh-CN.md +6 -1
  49. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  50. package/src/components/category-tabs/index.ts +10 -0
  51. package/src/components/form/JsonTextArea.tsx +4 -0
  52. package/src/components/form/ScanInput/CodeScanner.tsx +23 -6
  53. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +180 -3
  54. package/src/components/form/ScanInput/useCodeScanner.ts +170 -5
  55. package/src/components/form/TypedVariableInput.tsx +452 -101
  56. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  57. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  58. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  59. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  60. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  61. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  62. package/src/components/form/filter/index.ts +2 -0
  63. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  64. package/src/components/index.ts +1 -0
  65. package/src/flow/FlowPage.tsx +9 -1
  66. package/src/flow/__tests__/FlowPage.test.tsx +50 -3
  67. package/src/flow/__tests__/FlowRoute.test.tsx +2 -2
  68. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  69. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  70. package/src/flow/actions/index.ts +2 -0
  71. package/src/flow/actions/linkageRules.tsx +86 -11
  72. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  73. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +0 -1
  74. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  75. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  76. package/src/flow/components/FieldAssignValueInput.tsx +91 -20
  77. package/src/flow/components/FlowRoute.tsx +7 -3
  78. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  79. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  80. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  81. package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
  82. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  83. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  84. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
  85. package/src/flow/index.ts +1 -0
  86. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  87. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  88. package/src/flow/models/base/PageModel/PageTabModel.tsx +184 -3
  89. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  90. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  91. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  92. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +572 -1
  93. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -0
  94. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  95. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  96. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +34 -0
  97. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  98. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  99. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  100. package/src/flow/models/blocks/table/TableBlockModel.tsx +1 -1
  101. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
  102. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  103. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  104. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  105. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  106. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  107. package/src/flow-compat/routeTypes.ts +1 -0
  108. package/src/index.ts +4 -0
  109. package/src/locale/languageCodes.ts +2 -1
  110. package/src/nocobase-buildin-plugin/index.tsx +12 -0
  111. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  112. package/src/utils/index.tsx +1 -0
@@ -7,9 +7,10 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { FlowModel, FlowModelRenderer, observable, tExpr } from '@nocobase/flow-engine';
10
+ import { FlowModel, FlowModelRenderer, observable, type ParamObject, tExpr } from '@nocobase/flow-engine';
11
11
  import { Icon, type NocoBaseDesktopRoute } from '../../../../flow-compat';
12
12
  import { useRequest } from 'ahooks';
13
+ import _ from 'lodash';
13
14
  import React from 'react';
14
15
  import { SkeletonFallback } from '../../../components/SkeletonFallback';
15
16
  import { TextAreaWithContextSelector } from '../../../components/TextAreaWithContextSelector';
@@ -52,6 +53,24 @@ function normalizePersistedRoute(payload: unknown): Partial<NocoBaseDesktopRoute
52
53
  return undefined;
53
54
  }
54
55
 
56
+ type PersistedFlowModelAnchor = Record<string, unknown> & {
57
+ uid?: unknown;
58
+ stepParams?: unknown;
59
+ subModels?: unknown;
60
+ };
61
+
62
+ type LinkageRulesStepParams = ParamObject & {
63
+ value?: unknown;
64
+ };
65
+
66
+ function isRecord(value: unknown): value is Record<string, unknown> {
67
+ return !!value && typeof value === 'object' && !Array.isArray(value);
68
+ }
69
+
70
+ function isLinkageRulesStepParams(value: unknown): value is LinkageRulesStepParams {
71
+ return isRecord(value);
72
+ }
73
+
55
74
  export class BasePageTabModel extends FlowModel<{
56
75
  subModels: {
57
76
  grid: BlockGridModel;
@@ -139,13 +158,100 @@ BasePageTabModel.registerFlow({
139
158
  ctx.model.setProps('title', translate(params.title, { ns: 'lm-desktop-routes' }));
140
159
  ctx.model.setProps('icon', params.icon);
141
160
  const pageModel = ctx.engine.getModel(ctx.model.parentId) as { updateDocumentTitle?: () => Promise<void> };
142
- void pageModel?.updateDocumentTitle?.();
161
+ await pageModel?.updateDocumentTitle?.();
143
162
  },
144
163
  },
164
+ linkageRules: {
165
+ use: 'tabLinkageRules',
166
+ },
145
167
  },
146
168
  });
147
169
 
148
170
  export class RootPageTabModel extends BasePageTabModel {
171
+ private persistedLinkageRulesHydrated = false;
172
+ private persistedLinkageRulesHydrating?: Promise<void>;
173
+
174
+ onInit(options) {
175
+ super.onInit(options);
176
+ if (this.shouldHydratePersistedLinkageRules()) {
177
+ this.hydratePersistedLinkageRules().catch((error) => {
178
+ console.warn('[RootPageTabModel] Failed to hydrate tab linkage rules', error);
179
+ });
180
+ }
181
+ }
182
+
183
+ private hasExplicitLinkageRulesStep() {
184
+ const pageTabSettings = this.stepParams?.pageTabSettings;
185
+ return isRecord(pageTabSettings) && Object.prototype.hasOwnProperty.call(pageTabSettings, 'linkageRules');
186
+ }
187
+
188
+ private shouldHydratePersistedLinkageRules() {
189
+ return !!this.context.flowSettingsEnabled || this.props.route?.options?.hasPersistedPageTabFlowModel === true;
190
+ }
191
+
192
+ private async fetchPersistedAnchor(): Promise<PersistedFlowModelAnchor | undefined> {
193
+ const response = await this.context.api.request({
194
+ url: 'flowModels:findOne',
195
+ params: { uid: this.uid },
196
+ });
197
+ const anchor = response?.data?.data;
198
+ return isRecord(anchor) ? anchor : undefined;
199
+ }
200
+
201
+ private async performPersistedLinkageRulesHydrate() {
202
+ if (this.hasExplicitLinkageRulesStep()) {
203
+ return;
204
+ }
205
+
206
+ const anchor = await this.fetchPersistedAnchor();
207
+ if (this.hasExplicitLinkageRulesStep()) {
208
+ return;
209
+ }
210
+
211
+ const stepParams = isRecord(anchor?.stepParams) ? anchor.stepParams : undefined;
212
+ const pageTabSettings = isRecord(stepParams?.pageTabSettings) ? stepParams.pageTabSettings : undefined;
213
+ const linkageRules = pageTabSettings?.linkageRules;
214
+ if (!isLinkageRulesStepParams(linkageRules)) {
215
+ return;
216
+ }
217
+
218
+ this.setStepParams('pageTabSettings', 'linkageRules', _.cloneDeep(linkageRules));
219
+ this.invalidateFlowCache('beforeRender', true);
220
+ await this.rerender();
221
+ }
222
+
223
+ private hydratePersistedLinkageRules(): Promise<void> {
224
+ if (this.persistedLinkageRulesHydrated) {
225
+ return Promise.resolve();
226
+ }
227
+ if (this.persistedLinkageRulesHydrating) {
228
+ return this.persistedLinkageRulesHydrating;
229
+ }
230
+
231
+ const hydration = this.performPersistedLinkageRulesHydrate();
232
+ this.persistedLinkageRulesHydrating = hydration;
233
+ hydration
234
+ .then(() => {
235
+ if (this.persistedLinkageRulesHydrating === hydration) {
236
+ this.persistedLinkageRulesHydrated = true;
237
+ this.persistedLinkageRulesHydrating = undefined;
238
+ }
239
+ })
240
+ .catch(() => {
241
+ if (this.persistedLinkageRulesHydrating === hydration) {
242
+ this.persistedLinkageRulesHydrating = undefined;
243
+ }
244
+ });
245
+ return hydration;
246
+ }
247
+
248
+ async openFlowSettings(options?: Parameters<FlowModel['openFlowSettings']>[0]) {
249
+ if (options?.flowKey === 'pageTabSettings' && options?.stepKey === 'linkageRules') {
250
+ await this.hydratePersistedLinkageRules();
251
+ }
252
+ return super.openFlowSettings(options);
253
+ }
254
+
149
255
  renderChildren() {
150
256
  return (
151
257
  <PageTabChildrenRenderer
@@ -162,7 +268,15 @@ export class RootPageTabModel extends BasePageTabModel {
162
268
  }
163
269
 
164
270
  async saveStepParams() {
165
- return this.save();
271
+ const hasExplicitLinkageRulesStep = this.hasExplicitLinkageRulesStep();
272
+ const linkageRules = hasExplicitLinkageRulesStep
273
+ ? (_.cloneDeep(this.stepParams.pageTabSettings.linkageRules) as LinkageRulesStepParams)
274
+ : undefined;
275
+
276
+ await this.save();
277
+ if (hasExplicitLinkageRulesStep && linkageRules) {
278
+ await this.persistLinkageRulesToAnchor(linkageRules);
279
+ }
166
280
  }
167
281
 
168
282
  async save() {
@@ -179,6 +293,7 @@ export class RootPageTabModel extends BasePageTabModel {
179
293
  title: this.getTabTitle(''),
180
294
  icon: this.getTabIcon(),
181
295
  options: {
296
+ ...(this.props.route?.options || {}),
182
297
  flowRegistry: json.flowRegistry,
183
298
  documentTitle,
184
299
  },
@@ -199,6 +314,72 @@ export class RootPageTabModel extends BasePageTabModel {
199
314
  }
200
315
  }
201
316
 
317
+ private buildAnchorPayload(latestAnchor: PersistedFlowModelAnchor | undefined, linkageRules: LinkageRulesStepParams) {
318
+ const hasPersistedAnchor = typeof latestAnchor?.uid === 'string' && latestAnchor.uid.length > 0;
319
+ const anchorRoot: PersistedFlowModelAnchor = hasPersistedAnchor
320
+ ? _.cloneDeep(latestAnchor)
321
+ : { uid: this.uid, use: 'RouteModel' };
322
+ delete anchorRoot.subModels;
323
+
324
+ const latestStepParams = isRecord(anchorRoot.stepParams) ? anchorRoot.stepParams : {};
325
+ const latestPageTabSettings = isRecord(latestStepParams.pageTabSettings) ? latestStepParams.pageTabSettings : {};
326
+
327
+ return {
328
+ ...anchorRoot,
329
+ stepParams: {
330
+ ...latestStepParams,
331
+ pageTabSettings: {
332
+ ...latestPageTabSettings,
333
+ linkageRules: _.cloneDeep(linkageRules),
334
+ },
335
+ },
336
+ };
337
+ }
338
+
339
+ private async persistLinkageRulesToAnchor(linkageRules: LinkageRulesStepParams) {
340
+ const latestAnchor = await this.fetchPersistedAnchor();
341
+ const anchorPayload = this.buildAnchorPayload(latestAnchor, linkageRules);
342
+ await this.context.api.request({
343
+ method: 'post',
344
+ url: 'flowModels:save',
345
+ data: anchorPayload,
346
+ });
347
+
348
+ const hasRules = Array.isArray(linkageRules.value) && linkageRules.value.length > 0;
349
+ await this.syncPersistedPageTabFlowModelMarker(hasRules);
350
+ this.persistedLinkageRulesHydrated = true;
351
+ }
352
+
353
+ private async syncPersistedPageTabFlowModelMarker(hasRules: boolean) {
354
+ const route = this.props.route;
355
+ if (route?.id == null) {
356
+ throw new Error('Cannot persist page tab FlowModel marker before the desktop route is saved.');
357
+ }
358
+ if (typeof this.context.routeRepository?.updateRoute !== 'function') {
359
+ throw new Error('Route repository is unavailable while persisting the page tab FlowModel marker.');
360
+ }
361
+
362
+ const nextOptions = {
363
+ ...(route.options || {}),
364
+ };
365
+ if (hasRules) {
366
+ nextOptions.hasPersistedPageTabFlowModel = true;
367
+ } else {
368
+ delete nextOptions.hasPersistedPageTabFlowModel;
369
+ }
370
+ const persistedOptions = Object.keys(nextOptions).length > 0 ? nextOptions : undefined;
371
+
372
+ await this.context.routeRepository.updateRoute(
373
+ route.id,
374
+ { options: persistedOptions },
375
+ { refreshAfterMutation: false },
376
+ );
377
+ this.setProps('route', {
378
+ ...route,
379
+ options: persistedOptions,
380
+ });
381
+ }
382
+
202
383
  async destroy() {
203
384
  this.observerDispose();
204
385
  this.invalidateFlowCache('beforeRender', true);