@nocobase/plugin-ui-templates 2.0.2 → 2.0.5

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 (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +99 -0
  3. package/dist/externalVersion.js +7 -7
  4. package/package.json +3 -3
  5. package/LICENSE.txt +0 -172
  6. package/src/client/__tests__/openViewActionExtensions.test.ts +0 -1208
  7. package/src/client/collections/flowModelTemplates.ts +0 -131
  8. package/src/client/components/FlowModelTemplatesPage.tsx +0 -78
  9. package/src/client/components/TemplateSelectOption.tsx +0 -106
  10. package/src/client/constants.ts +0 -10
  11. package/src/client/hooks/useFlowModelTemplateActions.tsx +0 -137
  12. package/src/client/index.ts +0 -52
  13. package/src/client/locale.ts +0 -40
  14. package/src/client/menuExtensions.tsx +0 -1091
  15. package/src/client/models/ReferenceBlockModel.tsx +0 -841
  16. package/src/client/models/ReferenceFormGridModel.tsx +0 -448
  17. package/src/client/models/SubModelTemplateImporterModel.tsx +0 -725
  18. package/src/client/models/__tests__/ReferenceBlockModel.test.tsx +0 -547
  19. package/src/client/models/__tests__/ReferenceFormGridModel.test.tsx +0 -965
  20. package/src/client/models/__tests__/SubModelTemplateImporterModel.test.ts +0 -529
  21. package/src/client/models/referenceShared.tsx +0 -107
  22. package/src/client/openViewActionExtensions.tsx +0 -986
  23. package/src/client/schemas/flowModelTemplates.ts +0 -264
  24. package/src/client/utils/__tests__/templateCopy.test.ts +0 -67
  25. package/src/client/utils/infiniteSelect.ts +0 -150
  26. package/src/client/utils/templateCompatibility.ts +0 -374
  27. package/src/client/utils/templateCopy.ts +0 -59
  28. package/src/client.ts +0 -10
  29. package/src/index.ts +0 -11
  30. package/src/locale/de-DE.json +0 -14
  31. package/src/locale/en-US.json +0 -72
  32. package/src/locale/es-ES.json +0 -14
  33. package/src/locale/fr-FR.json +0 -14
  34. package/src/locale/hu-HU.json +0 -14
  35. package/src/locale/id-ID.json +0 -14
  36. package/src/locale/it-IT.json +0 -14
  37. package/src/locale/ja-JP.json +0 -14
  38. package/src/locale/ko-KR.json +0 -14
  39. package/src/locale/nl-NL.json +0 -14
  40. package/src/locale/pt-BR.json +0 -14
  41. package/src/locale/ru-RU.json +0 -14
  42. package/src/locale/tr-TR.json +0 -14
  43. package/src/locale/uk-UA.json +0 -14
  44. package/src/locale/vi-VN.json +0 -14
  45. package/src/locale/zh-CN.json +0 -71
  46. package/src/locale/zh-TW.json +0 -14
  47. package/src/server/__tests__/template-usage.test.ts +0 -351
  48. package/src/server/collections/flowModelTemplateUsages.ts +0 -51
  49. package/src/server/collections/flowModelTemplates.ts +0 -76
  50. package/src/server/index.ts +0 -10
  51. package/src/server/plugin.ts +0 -236
  52. package/src/server/resources/flowModelTemplateUsages.ts +0 -61
  53. package/src/server/resources/flowModelTemplates.ts +0 -251
@@ -1,448 +0,0 @@
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
-
10
- import React from 'react';
11
- import _ from 'lodash';
12
- import { type CreateModelOptions, FlowContext, FlowEngine, FlowModel } from '@nocobase/flow-engine';
13
- import { REF_HOST_CTX_KEY } from '../constants';
14
- import { NAMESPACE } from '../locale';
15
- import {
16
- ensureBlockScopedEngine,
17
- ReferenceScopedRenderer,
18
- renderReferenceTargetPlaceholder,
19
- ensureScopedEngineView,
20
- unlinkScopedEngine,
21
- } from './referenceShared';
22
-
23
- const SETTINGS_FLOW_KEY = 'referenceSettings';
24
- const SETTINGS_STEP_KEY = 'useTemplate';
25
-
26
- /** 标记已添加 host context bridge,避免重复添加 */
27
- const BRIDGE_MARKER = Symbol.for('nocobase.refGridHostBridge');
28
-
29
- export type ReferenceFormGridTargetSettings = {
30
- /** 模板 uid(flowModelTemplates.uid) */
31
- templateUid: string;
32
- /** 模板名称(用于 UI 展示,可选) */
33
- templateName?: string;
34
- /** 模板根区块 uid(flowModels.uid) */
35
- targetUid: string;
36
- /** 从模板根上取片段的路径,当前仅支持 'subModels.grid' */
37
- targetPath?: string;
38
- };
39
-
40
- export class ReferenceFormGridModel extends FlowModel {
41
- private _scopedEngine?: FlowEngine;
42
- private _targetRoot?: FlowModel;
43
- private _targetGrid?: FlowModel;
44
- private _resolvedTargetUid?: string;
45
- private _invalidTargetUid?: string;
46
-
47
- /**
48
- * 字段模板场景下,模板内 FormItemModel/CollectionFieldModel 的 onInit 会调用 ctx.blockModel.addAppends(fieldPath)。
49
- * 但模板 root 自身也是一个 CollectionBlockModel,会在未桥接宿主上下文时被识别为 blockModel,导致 appends 写到模板 root 的 resource,
50
- * 从而宿主表单(如 ApplyFormModel)在刷新记录时缺少关联 appends(例如 users.roles)。
51
- *
52
- * 这里在目标 grid 解析完成后扫描字段路径,并把需要的 appends 同步到宿主 block(master + forks),确保关系数据可展示。
53
- */
54
- private _syncHostResourceAppends(host: FlowModel, targetGrid: FlowModel) {
55
- if (!host['addAppends']) {
56
- return;
57
- }
58
- const candidates = new Set<string>();
59
- const addCandidate = (val: unknown) => {
60
- const s = typeof val === 'string' ? val.trim() : '';
61
- if (!s) return;
62
- candidates.add(s);
63
- const top = s.split('.')[0]?.trim();
64
- if (top) candidates.add(top);
65
- };
66
-
67
- const visit = (m: FlowModel) => {
68
- const init = m.getStepParams?.('fieldSettings', 'init') as
69
- | { fieldPath?: string; associationPathName?: string }
70
- | undefined;
71
- if (init) {
72
- addCandidate(init.fieldPath);
73
- addCandidate(init.associationPathName);
74
- }
75
- const subs = m.subModels || {};
76
- for (const v of Object.values(subs)) {
77
- if (Array.isArray(v)) {
78
- v.forEach((c) => c instanceof FlowModel && visit(c));
79
- } else if (v instanceof FlowModel) {
80
- visit(v);
81
- }
82
- }
83
- };
84
-
85
- visit(targetGrid);
86
- if (candidates.size === 0) return;
87
- for (const fieldPath of candidates) {
88
- (host as any).addAppends?.(fieldPath);
89
- }
90
- }
91
-
92
- constructor(options: any) {
93
- super(options);
94
-
95
- const forwardedReadMethods = new Set<string>(['mapSubModels', 'filterSubModels', 'findSubModel', 'hasSubModel']);
96
-
97
- const proxy = new Proxy(this as any, {
98
- get(target, prop: string | symbol) {
99
- const t = target._targetGrid as FlowModel | undefined;
100
- if (t) {
101
- if (prop === 'subModels') return t.subModels;
102
- if (typeof prop === 'string' && forwardedReadMethods.has(prop)) {
103
- const val = Reflect.get(t as any, prop, t as any);
104
- return typeof val === 'function' ? val.bind(t) : val;
105
- }
106
- }
107
-
108
- if (prop in target) {
109
- const val = Reflect.get(target, prop, target);
110
- if (typeof val === 'function' && prop !== 'constructor') {
111
- return val.bind(target);
112
- }
113
- return val;
114
- }
115
-
116
- if (t) {
117
- const val = Reflect.get(t as any, prop, t as any);
118
- if (typeof val === 'function' && prop !== 'constructor') {
119
- return val.bind(t);
120
- }
121
- if (val !== undefined) return val;
122
- }
123
-
124
- return undefined;
125
- },
126
- has(target, prop: string | symbol) {
127
- if (prop in target) return true;
128
- const t = target._targetGrid as FlowModel | undefined;
129
- return !!t && prop in t;
130
- },
131
- ownKeys(target) {
132
- const keys = new Set(Reflect.ownKeys(target));
133
- const t = target._targetGrid as FlowModel | undefined;
134
- if (t) {
135
- for (const k of Reflect.ownKeys(t)) keys.add(k);
136
- }
137
- return Array.from(keys);
138
- },
139
- getOwnPropertyDescriptor(target, prop: string | symbol) {
140
- const desc = Reflect.getOwnPropertyDescriptor(target, prop);
141
- if (desc) return desc;
142
- const t = target._targetGrid as FlowModel | undefined;
143
- if (!t) return undefined;
144
- return Object.getOwnPropertyDescriptor(t, prop) || undefined;
145
- },
146
- });
147
-
148
- return proxy as any;
149
- }
150
-
151
- private _ensureScopedEngine(): FlowEngine {
152
- this._scopedEngine = ensureBlockScopedEngine(this.flowEngine, this._scopedEngine);
153
- return this._scopedEngine;
154
- }
155
-
156
- private _getTargetSettings(): ReferenceFormGridTargetSettings | undefined {
157
- const raw = this.getStepParams(SETTINGS_FLOW_KEY, SETTINGS_STEP_KEY);
158
- if (!raw || typeof raw !== 'object') return undefined;
159
- const templateUid = String((raw as any).templateUid || '').trim();
160
- const targetUid = String((raw as any).targetUid || '').trim();
161
- if (!templateUid || !targetUid) return undefined;
162
- const templateName = String((raw as any).templateName || '').trim() || undefined;
163
- const targetPath = String((raw as any).targetPath || '').trim() || undefined;
164
- return { templateUid, templateName, targetUid, targetPath };
165
- }
166
-
167
- private _syncHostExtraTitle(settings?: ReferenceFormGridTargetSettings) {
168
- const host = this.parent as FlowModel | undefined;
169
- if (!host) return;
170
-
171
- const master: any = (host as any)?.isFork ? (host as any).master || host : host;
172
- const targets: FlowModel[] = [host];
173
- if (master && master !== host && master instanceof FlowModel) targets.push(master);
174
- master?.forks?.forEach?.((f: any) => f instanceof FlowModel && targets.push(f));
175
-
176
- // 未配置时清空 extraTitle,回退为原有 title(不修改用户 title)
177
- if (!settings) {
178
- targets.forEach((m) => m.setExtraTitle(''));
179
- return;
180
- }
181
-
182
- const label = host.context.t('Reference template', { ns: [NAMESPACE, 'client'], nsMode: 'fallback' });
183
- const fieldsOnly = host.context.t('(Fields only)', { ns: [NAMESPACE, 'client'], nsMode: 'fallback' });
184
- const name = settings.templateName?.trim() || settings.templateUid?.trim() || '';
185
- const extra = name ? `${label}: ${name} ${fieldsOnly}` : `${label} ${fieldsOnly}`;
186
-
187
- targets.forEach((m) => m.setExtraTitle(extra.trim()));
188
- }
189
-
190
- addSubModel<T extends FlowModel>(subKey: string, options: CreateModelOptions | T) {
191
- if (!this._targetGrid) {
192
- throw new Error('[block-reference] Target grid is not resolved yet.');
193
- }
194
- return this._targetGrid.addSubModel(subKey, options);
195
- }
196
-
197
- setSubModel(subKey: string, options: CreateModelOptions | FlowModel) {
198
- if (!this._targetGrid) {
199
- throw new Error('[block-reference] Target grid is not resolved yet.');
200
- }
201
- return this._targetGrid.setSubModel(subKey, options);
202
- }
203
-
204
- getStepParams(flowKey: string, stepKey: string): any | undefined;
205
- getStepParams(flowKey: string): Record<string, any> | undefined;
206
- getStepParams(): Record<string, any>;
207
- getStepParams(flowKey?: string, stepKey?: string): any {
208
- if (!flowKey || flowKey === SETTINGS_FLOW_KEY) {
209
- return super.getStepParams(flowKey, stepKey);
210
- }
211
-
212
- if (!this._targetGrid) {
213
- // 未解析完成:允许读取本地 stepParams,避免配置对话框/中间态丢值
214
- return super.getStepParams(flowKey, stepKey);
215
- }
216
-
217
- if (stepKey) {
218
- const fromGrid = this._targetGrid.getStepParams(flowKey, stepKey);
219
- if (typeof fromGrid !== 'undefined') return fromGrid;
220
- return this._targetRoot?.getStepParams?.(flowKey, stepKey);
221
- }
222
-
223
- const gridFlow = this._targetGrid.getStepParams(flowKey) as any;
224
- const rootFlow = this._targetRoot?.getStepParams?.(flowKey) as any;
225
- if (rootFlow && typeof rootFlow === 'object') {
226
- return { ...rootFlow, ...(gridFlow || {}) };
227
- }
228
- return gridFlow;
229
- }
230
-
231
- setStepParams(flowKey: string, stepKey: string, params: any): void;
232
- setStepParams(flowKey: string, stepParams: Record<string, any>): void;
233
- setStepParams(allParams: Record<string, any>): void;
234
- setStepParams(flowKeyOrAllParams: any, stepKeyOrStepsParams?: any, params?: any): void {
235
- if (typeof flowKeyOrAllParams === 'string') {
236
- const flowKey = flowKeyOrAllParams;
237
- if (flowKey === SETTINGS_FLOW_KEY || !this._targetGrid) {
238
- super.setStepParams(flowKeyOrAllParams, stepKeyOrStepsParams, params);
239
- return;
240
- }
241
- if (typeof stepKeyOrStepsParams === 'string' && params !== undefined) {
242
- this._targetGrid.setStepParams(flowKey, stepKeyOrStepsParams, params);
243
- return;
244
- }
245
- if (typeof stepKeyOrStepsParams === 'object' && stepKeyOrStepsParams !== null) {
246
- this._targetGrid.setStepParams(flowKey, stepKeyOrStepsParams);
247
- }
248
- return;
249
- }
250
-
251
- if (typeof flowKeyOrAllParams === 'object' && flowKeyOrAllParams !== null) {
252
- const allParams = flowKeyOrAllParams as Record<string, any>;
253
- const localAll: Record<string, any> = {};
254
- const delegatedAll: Record<string, any> = {};
255
- for (const [fk, steps] of Object.entries(allParams)) {
256
- if (fk === SETTINGS_FLOW_KEY || !this._targetGrid) {
257
- localAll[fk] = steps;
258
- } else {
259
- delegatedAll[fk] = steps;
260
- }
261
- }
262
- if (Object.keys(localAll).length > 0) {
263
- super.setStepParams(localAll);
264
- }
265
- if (Object.keys(delegatedAll).length > 0 && this._targetGrid) {
266
- this._targetGrid.setStepParams(delegatedAll);
267
- }
268
- }
269
- }
270
-
271
- async saveStepParams() {
272
- // 如果目标尚未解析,先触发解析
273
- if (!this._targetGrid) {
274
- await this.dispatchEvent('beforeRender');
275
- }
276
- // 将本地非 settings 参数刷新到目标 grid
277
- if (this._targetGrid && this.stepParams) {
278
- for (const [flowKey, steps] of Object.entries(this.stepParams)) {
279
- if (flowKey === SETTINGS_FLOW_KEY || typeof steps !== 'object' || steps === null) continue;
280
- this._targetGrid.setStepParams(flowKey, steps as Record<string, any>);
281
- delete (this.stepParams as Record<string, any>)[flowKey];
282
- }
283
- }
284
- const res = await super.saveStepParams();
285
- if (this._targetGrid) {
286
- await this._targetGrid.saveStepParams();
287
- }
288
- return res;
289
- }
290
-
291
- public async onDispatchEventStart(eventName: string): Promise<void> {
292
- if (eventName !== 'beforeRender') return;
293
-
294
- const settings = this._getTargetSettings();
295
- if (!settings) {
296
- this._syncHostExtraTitle(undefined);
297
- this._targetRoot = undefined;
298
- this._targetGrid = undefined;
299
- this._resolvedTargetUid = undefined;
300
- this._invalidTargetUid = undefined;
301
- return;
302
- }
303
-
304
- this._syncHostExtraTitle(settings);
305
-
306
- const targetPath = settings.targetPath?.trim() || 'subModels.grid';
307
- if (targetPath !== 'subModels.grid') {
308
- throw new Error(
309
- `[block-reference] Only 'subModels.grid' is supported for ReferenceFormGridModel (got '${targetPath}').`,
310
- );
311
- }
312
-
313
- if (this._resolvedTargetUid === settings.targetUid && this._targetGrid) {
314
- // 已成功解析,清理 invalid 标记
315
- this._invalidTargetUid = undefined;
316
- return;
317
- }
318
-
319
- const engine = this._ensureScopedEngine();
320
- const host = this.parent as FlowModel | undefined;
321
- ensureScopedEngineView(engine, (host?.context as any) || (this.context as any));
322
- const targetUid = settings.targetUid;
323
- const prevTargetGrid = this._targetGrid;
324
- const prevResolvedTargetUid = this._resolvedTargetUid;
325
- const prevInvalidTargetUid = this._invalidTargetUid;
326
- // 进入解析流程时,先认为是“resolving”,避免渲染层误判为 invalid
327
- this._invalidTargetUid = undefined;
328
-
329
- // 在“模板引用”切换的中间态(例如模型树刚替换、上下文尚未稳定)下,
330
- // 可能出现首次解析不到目标(短暂返回 null/undefined)。这里做一次轻量重试,
331
- // 避免界面闪现 “Target block is invalid” 占位。
332
- const tryResolveTargetGrid = async (): Promise<{ root: FlowModel; grid: FlowModel } | undefined> => {
333
- const root = await engine.loadModel<FlowModel>({ uid: targetUid });
334
- if (!root) return undefined;
335
-
336
- root.setParent(host);
337
- const hostInfo = {
338
- hostUid: host?.uid,
339
- hostUse: host?.use,
340
- ref: {
341
- templateUid: settings.templateUid,
342
- templateName: settings.templateName,
343
- targetUid,
344
- targetPath,
345
- mountSubKey: 'grid',
346
- mode: 'reference',
347
- },
348
- };
349
- root.context.defineProperty(REF_HOST_CTX_KEY, { value: hostInfo });
350
-
351
- const fragment = root.subModels?.grid;
352
- let gridModel: FlowModel | undefined;
353
- if (fragment instanceof FlowModel) {
354
- gridModel = fragment;
355
- }
356
- // 将宿主区块上下文注入到被引用的 grid:
357
- // - Details 区块字段渲染依赖 ctx.record/resource/blockModel 等(定义在宿主 block context 上);
358
- // - 但同时要保留 scoped engine(ctx.engine)指向,避免丢失实例/缓存隔离。
359
- // 注意:使用 Symbol 标记避免重复添加 delegate(beforeRender 可能多次触发)
360
- const contextWithMarker = gridModel?.context as (FlowContext & { [BRIDGE_MARKER]?: boolean }) | undefined;
361
- if (gridModel && host?.context && !contextWithMarker?.[BRIDGE_MARKER]) {
362
- const bridge = new FlowContext();
363
- bridge.defineProperty('engine', { value: engine });
364
- bridge.addDelegate(host.context);
365
- gridModel.context.addDelegate(bridge);
366
- (gridModel.context as FlowContext & { [BRIDGE_MARKER]?: boolean })[BRIDGE_MARKER] = true;
367
- }
368
- if (!gridModel) return undefined;
369
- // 同步模板字段需要的关联 appends 到宿主 block 的 resource(避免 users.roles 等关系字段为空)
370
- if (host) {
371
- this._syncHostResourceAppends(host, gridModel);
372
- }
373
- return { root, grid: gridModel };
374
- };
375
-
376
- let resolved = await tryResolveTargetGrid();
377
- if (!resolved) {
378
- await new Promise((resolve) => setTimeout(resolve, 50));
379
- const latest = this._getTargetSettings();
380
- if (latest?.targetUid !== targetUid) {
381
- return;
382
- }
383
- resolved = await tryResolveTargetGrid();
384
- }
385
-
386
- if (!resolved) {
387
- this._targetRoot = undefined;
388
- this._targetGrid = undefined;
389
- this._resolvedTargetUid = undefined;
390
- this._invalidTargetUid = targetUid;
391
- if (prevTargetGrid || prevResolvedTargetUid || prevInvalidTargetUid !== targetUid) {
392
- this.rerender();
393
- }
394
- return;
395
- }
396
-
397
- this._targetRoot = resolved.root;
398
- this._targetGrid = resolved.grid;
399
- this._resolvedTargetUid = targetUid;
400
- this._invalidTargetUid = undefined;
401
-
402
- if (prevTargetGrid !== resolved.grid || prevResolvedTargetUid !== targetUid || prevInvalidTargetUid) {
403
- this.rerender();
404
- }
405
- }
406
-
407
- clearForks() {
408
- try {
409
- this._syncHostExtraTitle(undefined);
410
- unlinkScopedEngine(this._scopedEngine);
411
- } finally {
412
- this._scopedEngine = undefined;
413
- }
414
- super.clearForks();
415
- }
416
-
417
- async destroy(): Promise<boolean> {
418
- try {
419
- unlinkScopedEngine(this._scopedEngine);
420
- } finally {
421
- this._scopedEngine = undefined;
422
- }
423
- return await super.destroy();
424
- }
425
-
426
- render() {
427
- const settings = this._getTargetSettings();
428
- if (!settings) {
429
- return renderReferenceTargetPlaceholder(this as any, 'unconfigured');
430
- }
431
-
432
- const target = this._targetGrid;
433
- if (!target) {
434
- if (this._invalidTargetUid === settings.targetUid) {
435
- return renderReferenceTargetPlaceholder(this as any, 'invalid');
436
- }
437
- // 目标尚未解析完成:展示 resolving,占位避免闪现 invalid
438
- return renderReferenceTargetPlaceholder(this as any, 'resolving');
439
- }
440
-
441
- const engine = this._ensureScopedEngine();
442
- return <ReferenceScopedRenderer engine={engine} model={target} />;
443
- }
444
- }
445
-
446
- ReferenceFormGridModel.define({
447
- hide: true,
448
- });