@nocobase/client-v2 2.1.0-beta.34 → 2.1.0-beta.36
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/es/BaseApplication.d.ts +7 -1
- package/es/PluginManager.d.ts +2 -0
- package/es/components/PoweredBy.d.ts +18 -0
- package/es/components/SwitchLanguage.d.ts +11 -0
- package/es/components/form/DialogFormLayout.d.ts +75 -0
- package/es/components/form/DrawerFormLayout.d.ts +11 -11
- package/es/components/form/PasswordInput.d.ts +40 -0
- package/es/components/form/RemoteSelect.d.ts +79 -0
- package/es/components/form/index.d.ts +3 -0
- package/es/components/form/table/styles.d.ts +10 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/models/base/ActionModelCore.d.ts +6 -0
- package/es/flow/models/base/GridModel.d.ts +2 -0
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +9 -1
- package/es/flow/utils/dataScopeFormValueClear.d.ts +14 -0
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/hooks/index.d.ts +2 -0
- package/es/hooks/useCurrentAppInfo.d.ts +9 -0
- package/es/index.mjs +117 -105
- package/es/json-logic/globalOperators.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +25 -0
- package/es/utils/appVersionHTML.d.ts +10 -0
- package/es/utils/globalDeps.d.ts +7 -0
- package/es/utils/index.d.ts +1 -0
- package/es/utils/remotePlugins.d.ts +4 -1
- package/lib/index.js +120 -108
- package/package.json +7 -6
- package/src/BaseApplication.tsx +11 -3
- package/src/PluginManager.ts +2 -0
- package/src/PluginSettingsManager.ts +2 -1
- package/src/__tests__/PluginSettingsManager.test.ts +19 -0
- package/src/__tests__/PoweredBy.test.tsx +130 -0
- package/src/__tests__/app.test.tsx +39 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +39 -72
- package/src/__tests__/remotePlugins.test.ts +203 -0
- package/src/__tests__/useCurrentRoles.test.tsx +100 -0
- package/src/components/PoweredBy.tsx +71 -0
- package/src/components/README.md +314 -0
- package/src/components/README.zh-CN.md +312 -0
- package/src/components/SwitchLanguage.tsx +48 -0
- package/src/components/form/DialogFormLayout.tsx +111 -0
- package/src/components/form/DrawerFormLayout.tsx +13 -32
- package/src/components/form/PasswordInput.tsx +211 -0
- package/src/components/form/RemoteSelect.tsx +137 -0
- package/src/components/form/index.tsx +3 -0
- package/src/components/form/table/Table.tsx +2 -1
- package/src/components/form/table/styles.ts +19 -0
- package/src/components/index.ts +2 -0
- package/src/css-variable/CSSVariableProvider.tsx +10 -1
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +96 -0
- package/src/flow/actions/dataScope.tsx +3 -0
- package/src/flow/actions/filterFormDefaultValues.tsx +1 -2
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -4
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +7 -33
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/models/base/ActionModel.tsx +8 -7
- package/src/flow/models/base/ActionModelCore.tsx +15 -7
- package/src/flow/models/base/GridModel.tsx +93 -36
- package/src/flow/models/base/__tests__/GridModel.visibleLayout.test.ts +83 -11
- package/src/flow/models/blocks/details/DetailsItemModel.tsx +2 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +329 -5
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +337 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +5 -3
- package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +108 -0
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +5 -0
- package/src/flow/models/blocks/table/TableColumnModel.tsx +2 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +2 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +278 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCurrentAppInfo.ts +36 -0
- package/src/json-logic/globalOperators.js +731 -0
- package/src/nocobase-buildin-plugin/index.tsx +70 -16
- package/src/utils/appVersionHTML.ts +28 -0
- package/src/utils/globalDeps.ts +47 -31
- package/src/utils/index.tsx +2 -0
- package/src/utils/remotePlugins.ts +119 -13
|
@@ -0,0 +1,278 @@
|
|
|
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 type { FlowContext, FlowModel } from '@nocobase/flow-engine';
|
|
11
|
+
import _ from 'lodash';
|
|
12
|
+
import { namePathToPathKey, pathKeyToNamePath } from '../models/blocks/form/value-runtime/path';
|
|
13
|
+
import { collectStaticDepsFromTemplateValue, type DepCollector } from '../models/blocks/form/value-runtime/deps';
|
|
14
|
+
|
|
15
|
+
type NamePath = Array<string | number>;
|
|
16
|
+
|
|
17
|
+
type DataScopeClearDeps = {
|
|
18
|
+
wildcard: boolean;
|
|
19
|
+
valuePaths: NamePath[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type DataScopeClearBinding = {
|
|
23
|
+
signature: string;
|
|
24
|
+
dispose: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const FORM_VALUES_CHANGE_EVENT = 'formValuesChange';
|
|
28
|
+
const DATA_SCOPE_CLEAR_BINDINGS_KEY = '__formValueDrivenDataScopeClearBindings';
|
|
29
|
+
|
|
30
|
+
function dedupeNamePaths(paths: NamePath[]) {
|
|
31
|
+
const byKey = new Map<string, NamePath>();
|
|
32
|
+
for (const path of paths) {
|
|
33
|
+
if (!path?.length) continue;
|
|
34
|
+
byKey.set(namePathToPathKey(path), path);
|
|
35
|
+
}
|
|
36
|
+
return Array.from(byKey.values());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isNamePathPrefix(prefix: NamePath, path: NamePath) {
|
|
40
|
+
if (prefix.length > path.length) return false;
|
|
41
|
+
return prefix.every((seg, index) => seg === path[index]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function minimizeValueNamePaths(paths: NamePath[]) {
|
|
45
|
+
const deduped = dedupeNamePaths(paths);
|
|
46
|
+
return deduped.filter((path, index) => {
|
|
47
|
+
return !deduped.some((other, otherIndex) => otherIndex !== index && isNamePathPrefix(path, other));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectDataScopeClearDeps(params: any): DataScopeClearDeps {
|
|
52
|
+
const collector: DepCollector = { deps: new Set(), wildcard: false };
|
|
53
|
+
collectStaticDepsFromTemplateValue(params, collector);
|
|
54
|
+
|
|
55
|
+
const valuePaths: NamePath[] = [];
|
|
56
|
+
let wildcard = collector.wildcard;
|
|
57
|
+
|
|
58
|
+
for (const depKey of collector.deps) {
|
|
59
|
+
if (depKey === 'fv:*') {
|
|
60
|
+
wildcard = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!depKey.startsWith('fv:')) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const inner = depKey.slice('fv:'.length);
|
|
67
|
+
if (!inner) {
|
|
68
|
+
wildcard = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
valuePaths.push(pathKeyToNamePath(inner));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
wildcard,
|
|
76
|
+
valuePaths: minimizeValueNamePaths(valuePaths),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasDeps(deps: DataScopeClearDeps) {
|
|
81
|
+
return deps.wildcard || deps.valuePaths.length > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasModelValue(model: any) {
|
|
85
|
+
const current = model?.props?.value;
|
|
86
|
+
if (current == null) return false;
|
|
87
|
+
if (Array.isArray(current)) return current.length > 0;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getChangedPathsFromPayload(payload: any): NamePath[] {
|
|
92
|
+
const rawChangedPaths = Array.isArray(payload?.changedPaths) ? payload.changedPaths : [];
|
|
93
|
+
const out: NamePath[] = [];
|
|
94
|
+
|
|
95
|
+
for (const path of rawChangedPaths) {
|
|
96
|
+
if (Array.isArray(path)) {
|
|
97
|
+
const segs = path.filter((seg) => typeof seg === 'string' || typeof seg === 'number') as NamePath;
|
|
98
|
+
if (segs.length) out.push(segs);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (typeof path === 'string' && path) {
|
|
102
|
+
out.push(pathKeyToNamePath(path));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (out.length) {
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const changedValues = payload?.changedValues;
|
|
111
|
+
if (changedValues && typeof changedValues === 'object' && !Array.isArray(changedValues)) {
|
|
112
|
+
for (const key of Object.keys(changedValues)) {
|
|
113
|
+
const namePath = pathKeyToNamePath(key);
|
|
114
|
+
if (namePath.length) out.push(namePath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function depsMatchPayload(deps: DataScopeClearDeps, payload: any) {
|
|
122
|
+
if (!hasDeps(deps)) return false;
|
|
123
|
+
if (deps.wildcard) return true;
|
|
124
|
+
|
|
125
|
+
const changedPaths = getChangedPathsFromPayload(payload);
|
|
126
|
+
if (!changedPaths.length) return true;
|
|
127
|
+
|
|
128
|
+
for (const changedPath of changedPaths) {
|
|
129
|
+
for (const depPath of deps.valuePaths) {
|
|
130
|
+
if (isNamePathPrefix(depPath, changedPath) || isNamePathPrefix(changedPath, depPath)) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getDepsSignature(deps: DataScopeClearDeps, formBlock: any) {
|
|
139
|
+
const toKeys = (paths: NamePath[]) => paths.map((path) => namePathToPathKey(path)).sort();
|
|
140
|
+
return JSON.stringify({
|
|
141
|
+
formBlockUid: formBlock?.uid,
|
|
142
|
+
wildcard: deps.wildcard,
|
|
143
|
+
valuePaths: toKeys(deps.valuePaths),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getBindings(model: any): Map<string, DataScopeClearBinding> {
|
|
148
|
+
return (model[DATA_SCOPE_CLEAR_BINDINGS_KEY] ||= new Map<string, DataScopeClearBinding>());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isFormBlock(model: any) {
|
|
152
|
+
if (!model || typeof model !== 'object') return false;
|
|
153
|
+
if (!model.emitter || typeof model.emitter.on !== 'function' || typeof model.emitter.off !== 'function') return false;
|
|
154
|
+
return !!model.formValueRuntime || !!model.context?.form || typeof model.context?.setFormValues === 'function';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function findFormBlock(ctx: FlowContext): any | null {
|
|
158
|
+
const candidates: any[] = [];
|
|
159
|
+
const push = (model: any) => {
|
|
160
|
+
if (model && !candidates.includes(model)) candidates.push(model);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
push((ctx.model as any)?.context?.blockModel);
|
|
164
|
+
push(ctx.model);
|
|
165
|
+
|
|
166
|
+
let cursor: any = (ctx.model as any)?.parent;
|
|
167
|
+
while (cursor) {
|
|
168
|
+
push(cursor);
|
|
169
|
+
cursor = cursor?.parent;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return candidates.find(isFormBlock) || null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function clearModelValue(model: any) {
|
|
176
|
+
if (!hasModelValue(model)) return;
|
|
177
|
+
const next = Array.isArray(model?.props?.value) ? [] : null;
|
|
178
|
+
if (typeof model.change === 'function') {
|
|
179
|
+
model.change(next);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (typeof model?.props?.onChange === 'function') {
|
|
183
|
+
model.props.onChange(next);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function shouldBind(model: any) {
|
|
188
|
+
return !!model && typeof model === 'object' && typeof model?.props?.onChange === 'function';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function disposeBinding(model: any, key: string) {
|
|
192
|
+
const bindings = getBindings(model);
|
|
193
|
+
const existing = bindings.get(key);
|
|
194
|
+
if (existing) {
|
|
195
|
+
existing.dispose();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* When a field's dataScope filter references other form values (e.g. `{{ ctx.formValues.school.id }}`),
|
|
201
|
+
* clear current field value after the dependency changes, so users don't keep an invalid stale selection.
|
|
202
|
+
*/
|
|
203
|
+
export function ensureFormValueDrivenDataScopeClear(ctx: FlowContext, params: any) {
|
|
204
|
+
const model: any = ctx.model;
|
|
205
|
+
const flowKey = (ctx as any)?.flowKey;
|
|
206
|
+
if (!shouldBind(model) || !flowKey) return;
|
|
207
|
+
|
|
208
|
+
const stepKey = 'dataScope';
|
|
209
|
+
const bindingKey = `${flowKey}:${stepKey}`;
|
|
210
|
+
const deps = collectDataScopeClearDeps(params);
|
|
211
|
+
if (!hasDeps(deps)) {
|
|
212
|
+
disposeBinding(model, bindingKey);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const formBlock = findFormBlock(ctx);
|
|
217
|
+
if (!formBlock) {
|
|
218
|
+
disposeBinding(model, bindingKey);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const signature = getDepsSignature(deps, formBlock);
|
|
223
|
+
const bindings = getBindings(model);
|
|
224
|
+
const existing = bindings.get(bindingKey);
|
|
225
|
+
if (existing?.signature === signature) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (existing) {
|
|
229
|
+
existing.dispose();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const engineEmitter = model?.flowEngine?.emitter || (ctx as any)?.engine?.emitter || model?.context?.engine?.emitter;
|
|
233
|
+
|
|
234
|
+
const binding: DataScopeClearBinding = {
|
|
235
|
+
signature,
|
|
236
|
+
dispose: () => {},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const dispose = () => {
|
|
240
|
+
formBlock.emitter?.off?.(FORM_VALUES_CHANGE_EVENT, listener);
|
|
241
|
+
engineEmitter?.off?.('model:unmounted', cleanupOnUnmount);
|
|
242
|
+
engineEmitter?.off?.('model:destroyed', cleanupOnDestroyed);
|
|
243
|
+
if (bindings.get(bindingKey) === binding) {
|
|
244
|
+
bindings.delete(bindingKey);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const listener = (payload: any) => {
|
|
249
|
+
if (model.disposed || formBlock.disposed) {
|
|
250
|
+
dispose();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!hasModelValue(model) || !depsMatchPayload(deps, payload)) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
clearModelValue(model);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const cleanupOnUnmount = ({ model: unmountedModel }: { model: FlowModel }) => {
|
|
262
|
+
if (unmountedModel === formBlock || (unmountedModel === model && model.disposed)) {
|
|
263
|
+
dispose();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const cleanupOnDestroyed = ({ model: destroyedModel }: { model: FlowModel }) => {
|
|
268
|
+
if (destroyedModel === model || destroyedModel === formBlock) {
|
|
269
|
+
dispose();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
binding.dispose = dispose;
|
|
274
|
+
bindings.set(bindingKey, binding);
|
|
275
|
+
formBlock.emitter.on(FORM_VALUES_CHANGE_EVENT, listener);
|
|
276
|
+
engineEmitter?.on?.('model:unmounted', cleanupOnUnmount);
|
|
277
|
+
engineEmitter?.on?.('model:destroyed', cleanupOnDestroyed);
|
|
278
|
+
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
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 { useFlowEngineContext } from '@nocobase/flow-engine';
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
|
|
13
|
+
export function useCurrentAppInfo<TAppInfo extends Record<string, any> = Record<string, any>>() {
|
|
14
|
+
const ctx = useFlowEngineContext();
|
|
15
|
+
const [data, setData] = useState<TAppInfo>();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
let active = true;
|
|
19
|
+
|
|
20
|
+
Promise.resolve(ctx.appInfo)
|
|
21
|
+
.then((info) => {
|
|
22
|
+
if (active) {
|
|
23
|
+
setData((info || {}) as TAppInfo);
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.catch((error) => {
|
|
27
|
+
console.error(error);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
active = false;
|
|
32
|
+
};
|
|
33
|
+
}, [ctx]);
|
|
34
|
+
|
|
35
|
+
return data;
|
|
36
|
+
}
|