perf(Table): remove Formily components to improve performance (#5738)

* perf(Table): remove Formily components to improve performance

* refactor: rename and modify comments

* fix(UIEditor): fix hover style

* fix(Table): fix style configuration option not showing

* fix(ellipsis): fix refresh issue

* fix: need to update field value

* perf(Table): improve performance of configuring top buttons

* refactor: skip style-related code when no style rules are set

* chore(e2e): fix e2e errors

* fix(e2e): fix e2e errors

* fix(workflow): fix refresh issue

* fix(workflow): fix refresh issue

* fix(Action.Container): fix refresh issue

* fix(drawer): fix refresh issue

* fix(Table): fix refresh issue

* fix(AssociationField): fix refresh issues

* fix(NocoBaseRecursionField): fix refresh code

* refactor(SchemaComponentContext): remove useless code

* refactor: rename RefreshContext to RefreshFieldSchemaContext

* refactor(useDataBlockRequestGetter): optimize comment

* refactor: rename and new refresh context

* fix(Table): fix some refresh issues

* fix(AssociationField): fix refresh issue

* refactor(Table): make code better

* fix: replace RecursionField with NocoBaseRecursionField

* refactor: remove useless code

* fix(Menu): fix draging issue

* fix: refresh entire page after drag and drop operation

* fix(Page): fix draging issue

* chore: fix build error

* fix(test): make unit tests pass

* chore(test): fix unit test

* chore(e2e): fix e2e errors

* chore(e2e): update e2e to make it pass

* chore(e2e): update e2e to make it pass

* chore(e2e): fix e2e errors

* fix(e2e): fix some e2e errors

* fix(SchemaComponent): fix onChange issue

* chore(e2e): make e2e more stable
This commit is contained in:
Zeke Zhang 2024-12-04 21:05:50 +08:00 committed by GitHub
parent 51cb1d0a71
commit 0fa56d7407
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
84 changed files with 805 additions and 653 deletions

View File

@ -11,7 +11,7 @@ import {
SchemaSettingsModalItem,
createDesignable,
useAPIClient,
useSchemaComponentContext,
useRefreshFieldSchema,
useSchemaSettings,
useSchemaSettingsRender,
} from '@nocobase/client';
@ -82,16 +82,16 @@ const Demo = () => {
const fieldSchema = useFieldSchema();
const field = useField();
const api = useAPIClient();
const { refresh } = useSchemaComponentContext();
const refreshFieldSchema = useRefreshFieldSchema();
const dn = useMemo(
() =>
createDesignable({
current: fieldSchema.parent,
model: field.parent,
api,
refresh,
refresh: () => refreshFieldSchema({ refreshParentSchema: true }),
}),
[],
[api, field.parent, fieldSchema.parent, refreshFieldSchema],
);
const { render, exists } = useSchemaSettingsRender(fieldSchema['x-settings'], {
fieldSchema: dn.current,

View File

@ -11,7 +11,7 @@ import {
SchemaSettingsModalItem,
createDesignable,
useAPIClient,
useSchemaComponentContext,
useRefreshFieldSchema,
useSchemaSettings,
useSchemaSettingsRender,
} from '@nocobase/client';
@ -82,16 +82,16 @@ const Demo = () => {
const fieldSchema = useFieldSchema();
const field = useField();
const api = useAPIClient();
const { refresh } = useSchemaComponentContext();
const refreshFieldSchema = useRefreshFieldSchema();
const dn = useMemo(
() =>
createDesignable({
current: fieldSchema.parent,
model: field.parent,
api,
refresh,
refresh: () => refreshFieldSchema({ refreshParentSchema: true }),
}),
[],
[api, field.parent, fieldSchema.parent, refreshFieldSchema],
);
const { render, exists } = useSchemaSettingsRender(fieldSchema['x-settings'], {
fieldSchema: dn.current,

View File

@ -17,7 +17,7 @@ import { SchemaInitializer } from '../SchemaInitializer';
import { SchemaInitializerOptions } from '../types';
import { withInitializer } from '../withInitializer';
const InitializerComponent: FC<SchemaInitializerOptions<any, any>> = React.memo((options) => {
const InitializerComponent: FC<SchemaInitializerOptions<any, any>> = (options) => {
const Component: any = options.Component || SchemaInitializerButton;
const ItemsComponent: any = options.ItemsComponent || SchemaInitializerItems;
@ -31,7 +31,8 @@ const InitializerComponent: FC<SchemaInitializerOptions<any, any>> = React.memo(
const C = useMemo(() => withInitializer(Component), [Component]);
return React.createElement(C, options, React.createElement(ItemsComponent, itemsComponentProps));
});
};
InitializerComponent.displayName = 'InitializerComponent';
export function useSchemaInitializerRender<P1 = ButtonProps, P2 = {}>(

View File

@ -54,7 +54,7 @@ export function withInitializer<T>(C: ComponentType<T>) {
insertAdjacent(insertPosition, wrapCallback(schema, { isInSubTable }), { onSuccess });
}
},
[insertCallback, wrapCallback, insertAdjacent, insertPosition, onSuccess],
[insertCallback, wrapCallback, isInSubTable, insertAdjacent, insertPosition, onSuccess],
);
const { wrapSSR, hashId, componentCls } = useSchemaInitializerStyles();

View File

@ -10,7 +10,7 @@
import { Field, GeneralField } from '@formily/core';
import { RecursionField, useField, useFieldSchema } from '@formily/react';
import { Col, Row } from 'antd';
import { isArray } from 'lodash';
import _, { isArray } from 'lodash';
import template from 'lodash/template';
import React, { createContext, useContext, useMemo } from 'react';
import { Link } from 'react-router-dom';
@ -33,10 +33,12 @@ import {
useCollectionManager_deprecated,
useCollection_deprecated,
} from '../collection-manager';
import { RefreshComponentProvider } from '../formily/NocoBaseRecursionField';
import { useSourceId } from '../modules/blocks/useSourceId';
import { RecordProvider, useRecordIndex } from '../record-provider';
import { useAssociationNames } from './hooks';
import { useDataBlockParentRecord } from './hooks/useDataBlockParentRecord';
import { useUpdate } from 'ahooks';
/**
* @deprecated
@ -243,6 +245,7 @@ export const BlockProvider = (props: {
}) => {
const { name, dataSource, useParams, parentRecord } = props;
const parentRecordFromHook = useCompatDataBlockParentRecord(props);
const refresh = useUpdate();
// 新版1.0)已弃用 useParams这里之所以继续保留是为了兼容旧版的 UISchema
const paramsFromHook = useParams?.();
@ -261,7 +264,7 @@ export const BlockProvider = (props: {
<BlockContext.Provider value={blockValue}>
<DataBlockProvider {...(props as any)} params={params} parentRecord={parentRecord || parentRecordFromHook}>
<BlockRequestProvider_deprecated {...props} updateAssociationValues={updateAssociationValues} params={params}>
{props.children}
<RefreshComponentProvider refresh={refresh}>{props.children}</RefreshComponentProvider>
</BlockRequestProvider_deprecated>
</DataBlockProvider>
</BlockContext.Provider>

View File

@ -1590,7 +1590,7 @@ export const useAssociationNames = (dataSource?: string) => {
const { getCollectionJoinField, getCollection } = useCollectionManager_deprecated(dataSource);
const fieldSchema = useFieldSchema();
const getAssociationAppends = () => {
const getAssociationAppends = useCallback(() => {
const updateAssociationValues = new Set([]);
let appends = new Set([]);
@ -1606,7 +1606,7 @@ export const useAssociationNames = (dataSource?: string) => {
appends = fillParentFields(appends);
return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] };
};
}, [dataSource, fieldSchema, getCollection, getCollectionJoinField]);
return { getAssociationAppends };
};

View File

@ -16,7 +16,6 @@ import { useDataSourceManager } from '../data-source';
import { DEFAULT_DATA_SOURCE_KEY } from '../../data-source/data-source/DataSourceManager';
import { useCollection } from '../collection';
import { BlockItemCard } from '../../schema-component/antd/block-item/BlockItemCard';
import { AnyKindOfDictionary } from 'lodash';
export interface CollectionDeletedPlaceholderProps {
type: 'Collection' | 'Field' | 'Data Source' | 'Block template';
@ -99,6 +98,7 @@ export const CollectionDeletedPlaceholder: FC<CollectionDeletedPlaceholderProps>
...confirm,
onOk() {
dn.remove(null, { removeParentsIfNoChildren: true, breakRemoveOn: { 'x-component': 'Grid' } });
dn.refresh({ refreshParentSchema: true });
},
})
}

View File

@ -225,10 +225,27 @@ export const useDataBlockRequest = <T extends {}>(): UseRequestResult<{ data: T
};
/**
* Compared to `useDataBlockRequest`, the advantage of this hook is that it prevents unnecessary re-renders.
* For example, if you only need to use methods like `refresh` or `run`, it's recommended to use this hook,
* as it avoids component re-rendering when re-triggering requests.
* @returns
* Compared to `useDataBlockRequest`, this Hook helps prevent unnecessary re-renders.
*
* This Hook returns a stable function reference that won't change between renders. When you only need
* methods like `refresh` or `run`, using this Hook is recommended because:
*
* 1. It returns a memoized object containing only the getter function
* 2. The getter function accesses the latest request data through a ref, avoiding re-renders
* 3. Unlike useDataBlockRequest which returns request state directly, this Hook provides indirect access
* through a getter, breaking the reactive dependency chain
*
* For example:
* ```ts
* // This will re-render when request state changes
* const { refresh } = useDataBlockRequest();
*
* // This won't re-render when request state changes
* const { getDataBlockRequest } = useDataBlockRequestGetter();
* const refresh = getDataBlockRequest().refresh;
* ```
*
* @returns An object containing the getDataBlockRequest method that provides access to the request instance
*/
export const useDataBlockRequestGetter = () => {
const contextRef = useContext(BlockRequestRefContext);

View File

@ -7,23 +7,32 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { FieldContext, IFieldProps, JSXComponent, Schema, useField, useForm } from '@formily/react';
import React from 'react';
import { FieldContext, IFieldProps, JSXComponent, Schema, useFieldSchema } from '@formily/react';
import React, { useMemo } from 'react';
import { useCompile } from '../schema-component/hooks/useCompile';
import { NocoBaseReactiveField } from './NocoBaseReactiveField';
import { createNocoBaseField } from './createNocoBaseField';
/**
* To maintain high performance of Table, this component removes Formily-related components
* @param props component props
* @returns
*/
export const NocoBaseField = <D extends JSXComponent, C extends JSXComponent>(
props: IFieldProps<D, C> & { schema: Schema },
) => {
const compile = useCompile();
const form = useForm();
const parent = useField();
const field = createNocoBaseField.call(form, { basePath: parent?.address, compile, ...props });
const fieldSchema = useFieldSchema();
// eslint-disable-next-line react-hooks/exhaustive-deps
const field = useMemo(() => createNocoBaseField({ ...props, compile }), []);
// update componentProps to rerender field component
Object.assign(field.componentProps, fieldSchema['x-component-props']);
field.value = props.value;
return (
<FieldContext.Provider value={field}>
<NocoBaseReactiveField field={field}>{props.children}</NocoBaseReactiveField>
<FieldContext.Provider value={field as any}>
<NocoBaseReactiveField field={field as any}>{props.children}</NocoBaseReactiveField>
</FieldContext.Provider>
);
};

View File

@ -7,9 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Form, GeneralField, isVoidField } from '@formily/core';
import { Form, GeneralField } from '@formily/core';
import { RenderPropsChildren, SchemaComponentsContext } from '@formily/react';
import { observer } from '@formily/reactive-react';
import { FormPath, isFn } from '@formily/shared';
import React, { Fragment, useContext } from 'react';
interface IReactiveFieldProps {
@ -34,19 +33,16 @@ const renderChildren = (children: RenderPropsChildren<GeneralField>, field?: Gen
isFn(children) ? children(field, form) : children;
/**
* Based on @formily/react v2.3.2 ReactiveInternal component
* Modified to better adapt to NocoBase's needs
* To maintain high performance of Table, this component removes Formily-related code
*/
const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
const NocoBaseReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
const components = useContext(SchemaComponentsContext);
if (!props.field) {
return <Fragment>{renderChildren(props.children)}</Fragment>;
}
const field = props.field;
const field: any = props.field;
const content = mergeChildren(
renderChildren(props.children, field, field.form),
field.content ?? field.componentProps.children,
);
if (field.display !== 'visible') return null;
const getComponent = (target: any) => {
@ -63,27 +59,8 @@ const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
const renderComponent = () => {
if (!field.componentType) return content;
const value = !isVoidField(field) ? field.value : undefined;
const onChange = !isVoidField(field)
? (...args: any[]) => {
field.onInput(...args);
field.componentProps?.onChange?.(...args);
}
: field.componentProps?.onChange;
const onFocus = !isVoidField(field)
? (...args: any[]) => {
field.onFocus(...args);
field.componentProps?.onFocus?.(...args);
}
: field.componentProps?.onFocus;
const onBlur = !isVoidField(field)
? (...args: any[]) => {
field.onBlur(...args);
field.componentProps?.onBlur?.(...args);
}
: field.componentProps?.onBlur;
const disabled = !isVoidField(field) ? field.pattern === 'disabled' || field.pattern === 'readPretty' : undefined;
const readOnly = !isVoidField(field) ? field.pattern === 'readOnly' : undefined;
const disabled = true;
const readOnly = true;
return React.createElement(
getComponent(field.componentType),
@ -91,10 +68,7 @@ const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
disabled,
readOnly,
...field.componentProps,
value,
onChange,
onFocus,
onBlur,
value: field.value,
},
content,
);
@ -103,14 +77,12 @@ const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
return renderDecorator(renderComponent());
};
ReactiveInternal.displayName = 'NocoBaseReactiveInternal';
NocoBaseReactiveInternal.displayName = 'NocoBaseReactiveInternal';
/**
* Based on @formily/react v2.3.2 NocoBaseReactiveField component
* Modified to better adapt to NocoBase's needs
*/
export const NocoBaseReactiveField = observer(ReactiveInternal, {
forwardRef: true,
});
export const NocoBaseReactiveField = NocoBaseReactiveInternal;
NocoBaseReactiveField.displayName = 'NocoBaseReactiveField';

View File

@ -24,10 +24,11 @@ import {
import { isBool, isFn, isValid, merge } from '@formily/shared';
import { useUpdate } from 'ahooks';
import _ from 'lodash';
import React, { FC, Fragment, useCallback, useMemo } from 'react';
import React, { FC, Fragment, useCallback, useContext, useMemo, useRef } from 'react';
import { CollectionFieldOptions } from '../data-source/collection/Collection';
import { useCollectionManager } from '../data-source/collection/CollectionManagerProvider';
import { useCollection } from '../data-source/collection/CollectionProvider';
import { SchemaComponentOnChangeContext } from '../schema-component/core/SchemaComponent';
import { EMPTY_OBJECT } from '../variables';
import { NocoBaseField } from './NocoBaseField';
@ -51,22 +52,36 @@ interface INocoBaseRecursionFieldProps extends IRecursionFieldProps {
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
const RefreshContext = React.createContext<(options?: { refreshParent?: boolean }) => void>(_.noop);
const RefreshFieldSchemaContext = React.createContext<(options?: { refreshParentSchema?: boolean }) => void>(_.noop);
const RefreshProvider: FC<{ refresh: (options?: { refreshParent?: boolean }) => void }> = ({ children, refresh }) => {
const RefreshFieldSchemaProvider: FC<{ refresh: (options?: { refreshParentSchema?: boolean }) => void }> = ({
children,
refresh,
}) => {
const refreshParent = useRefreshFieldSchema();
const value = useCallback(
(options?: { refreshParent?: boolean }) => {
if (options?.refreshParent) {
(options?: { refreshParentSchema?: boolean }) => {
refresh();
if (options?.refreshParentSchema) {
refreshParent?.();
}
refresh();
},
[refreshParent, refresh],
);
return <RefreshContext.Provider value={value}>{children}</RefreshContext.Provider>;
return <RefreshFieldSchemaContext.Provider value={value}>{children}</RefreshFieldSchemaContext.Provider>;
};
const RefreshComponentContext = React.createContext<() => void>(_.noop);
export const RefreshComponentProvider: FC<{ refresh: () => void }> = ({ children, refresh }) => {
return <RefreshComponentContext.Provider value={refresh}>{children}</RefreshComponentContext.Provider>;
};
export const useRefreshComponent = () => {
return React.useContext(RefreshComponentContext);
};
/**
@ -74,7 +89,7 @@ const RefreshProvider: FC<{ refresh: (options?: { refreshParent?: boolean }) =>
* @returns
*/
export const useRefreshFieldSchema = () => {
return React.useContext(RefreshContext);
return React.useContext(RefreshFieldSchemaContext);
};
/**
@ -121,15 +136,11 @@ const useFieldProps = (schema: Schema) => {
const useBasePath = (props: IRecursionFieldProps) => {
const parent = useField();
if (props.onlyRenderProperties) {
return props.basePath || parent?.address.concat(props.name);
return props.basePath || parent?.address?.concat(props.name);
}
return props.basePath || parent?.address;
};
const createSchemaInstance = _.memoize((schema: ISchema): Schema => {
return new Schema(schema);
});
const createMergedSchemaInstance = (schema: Schema, uiSchema: ISchema, onlyRenderProperties: boolean) => {
const clonedSchema = schema.toJSON();
@ -257,14 +268,32 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
uiSchema,
} = props;
const basePath = useBasePath(props);
const fieldSchema = createSchemaInstance(schema);
const newFieldSchemaRef = useRef(null);
const oldFieldSchema = useMemo(() => {
newFieldSchemaRef.current = null;
return new Schema(schema);
}, [schema]);
const { uiSchema: collectionFiledUiSchema, defaultValue } = useCollectionFieldUISchema();
const update = useUpdate();
const { onChange: onChangeFromContext } = useContext(SchemaComponentOnChangeContext);
const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema;
const refresh = useCallback(() => {
createSchemaInstance.cache.delete(schema);
const parent = fieldSchema.parent;
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);
if (parent?.properties) {
Object.keys(parent.properties).forEach((key) => {
if (key === fieldSchema.name) {
parent.properties[key] = newFieldSchemaRef.current;
}
});
}
update();
}, [schema, update]);
onChangeFromContext?.();
}, [fieldSchema, onChangeFromContext, update]);
// Merge default Schema of collection fields
const mergedFieldSchema = useMemo(() => {
@ -330,7 +359,7 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
// some default schema values would also be saved in fieldSchema.
return (
<SchemaContext.Provider value={fieldSchema}>
<RefreshProvider refresh={refresh}>{render()}</RefreshProvider>
<RefreshFieldSchemaProvider refresh={refresh}>{render()}</RefreshFieldSchemaProvider>
</SchemaContext.Provider>
);
});

View File

@ -7,29 +7,12 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { FormPath, FormPathPattern, IFieldFactoryProps, IFieldProps, LifeCycleTypes } from '@formily/core';
import { Field } from '@formily/core/esm/models/Field';
import { locateNode } from '@formily/core/esm/shared/internals';
import { IFieldProps } from '@formily/core';
import { JSXComponent, Schema } from '@formily/react';
import { batch, define, observable, raw } from '@formily/reactive';
import { toArr } from '@formily/shared';
export function createNocoBaseField<Decorator extends JSXComponent, Component extends JSXComponent>(
props: IFieldFactoryProps<Decorator, Component> & { compile: (source: any) => any },
): Field<Decorator, Component> {
const address = FormPath.parse(props.basePath).concat(props.name);
const identifier = address.toString();
if (!identifier) return;
if (!this.fields[identifier]) {
batch(() => {
new NocoBaseField(address, props, this, this.props.designable);
});
this.notify(LifeCycleTypes.ON_FORM_GRAPH_CHANGE);
}
this.fields[identifier].value = props.value;
return this.fields[identifier] as any;
export function createNocoBaseField<Decorator extends JSXComponent, Component extends JSXComponent>(props: any) {
return new NocoBaseField(props);
}
/**
@ -40,35 +23,79 @@ class NocoBaseField<
Component extends JSXComponent = any,
TextType = any,
ValueType = any,
> extends Field {
declare props: IFieldProps<Decorator, Component, TextType, ValueType> & {
> {
props: IFieldProps<Decorator, Component, TextType, ValueType> & {
schema: Schema;
compile: (source: any) => any;
};
initialized: boolean;
loading: boolean;
validating: boolean;
submitting: boolean;
selfModified: boolean;
active: boolean;
visited: boolean;
mounted: boolean;
unmounted: boolean;
inputValues: any[];
inputValue: any;
feedbacks: any[];
title: string;
description: string;
display: string;
pattern: string;
editable: boolean;
disabled: boolean;
readOnly: boolean;
readPretty: boolean;
visible: boolean;
hidden: boolean;
dataSource: any[];
validator: any;
required: boolean;
content: string;
initialValue: any;
value: any;
data: any;
decorator: any[];
component: any[];
decoratorProps: any;
componentProps: any;
decoratorType: any;
componentType: any;
path: any;
form: any;
constructor(props: any) {
this.props = props;
this.initialize();
// this.makeObservable();
}
protected initialize() {
const compile = this.props.compile;
this.initialized = false;
this.pattern = 'readPretty';
this.readPretty = true;
this.initialized = true;
this.loading = false;
this.validating = false;
this.submitting = false;
this.selfModified = false;
this.active = false;
this.visited = false;
this.mounted = false;
this.visited = true;
this.mounted = true;
this.unmounted = false;
this.inputValues = [];
this.inputValue = null;
this.feedbacks = [];
this.title = compile(this.props.title || this.props.schema?.title);
this.description = compile(this.props.description || this.props.schema?.['description']);
this.display = this.props.display || this.props.schema?.['x-display'];
this.pattern = this.props.pattern || this.props.schema?.['x-pattern'];
this.display = 'visible';
this.editable = this.props.editable || this.props.schema?.['x-editable'];
this.disabled = this.props.disabled || this.props.schema?.['x-disabled'];
this.readOnly = this.props.readOnly || this.props.schema?.['x-read-only'];
this.readPretty = this.props.readPretty || this.props.schema?.['x-read-pretty'];
this.visible = this.props.visible || this.props.schema?.['x-visible'];
this.hidden = this.props.hidden || this.props.schema?.['x-hidden'];
this.dataSource = compile(this.props.dataSource || (this.props.schema?.enum as any));
@ -84,19 +111,18 @@ class NocoBaseField<
this.component = this.props.component
? toArr(this.props.component)
: [this.props.schema?.['x-component'], this.props.schema?.['x-component-props']];
this.decoratorProps = this.props.schema?.['x-decorator-props'] || {};
this.componentProps = this.props.schema?.['x-component-props'] || {};
this.decoratorType = this.props.schema?.['x-decorator'];
this.componentType = this.props.schema?.['x-component'];
this.path = {};
this.form = {};
}
locate(address: FormPathPattern) {
raw(this.form.fields)[address.toString()] = this as any;
locateNode(this as any, address);
}
protected makeObservable() {
define(this, {
componentProps: observable,
});
}
// Set as an empty function to prevent parent class from executing this method
protected makeReactive() {}
// protected makeObservable() {
// define(this, {
// componentProps: observable,
// });
// }
}

View File

@ -41,6 +41,7 @@ export * from './global-theme';
export * from './hooks';
export * from './i18n';
export * from './icon';
export * from './lazy-helper';
export { default as locale } from './locale';
export * from './nocobase-buildin-plugin';
export * from './plugin-manager';
@ -58,7 +59,6 @@ export * from './system-settings';
export * from './testUtils';
export * from './user';
export * from './variables';
export * from './lazy-helper';
export { withDynamicSchemaProps } from './hoc/withDynamicSchemaProps';
export { withSkeletonComponent } from './hoc/withSkeletonComponent';
@ -83,7 +83,8 @@ export { languageCodes } from './locale';
// Override Formily API
export {
NocoBaseRecursionField,
CollectionFieldUISchemaProvider,
IsInNocoBaseRecursionFieldContext,
NocoBaseRecursionField,
useRefreshFieldSchema,
} from './formily/NocoBaseRecursionField';

View File

@ -17,7 +17,7 @@ test.describe('Add new: inherit', () => {
// 1. click the "Add new" button, and then create a block, the block's collection should be "parent"
await page.getByRole('button', { name: 'plus Add new' }).click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Current collection' }).click();
await page.getByLabel('block-item-CardItem-parent-form').hover();
await expect(page.getByLabel('block-item-CardItem-parent-form').getByText('parent')).toBeVisible();
@ -28,7 +28,7 @@ test.describe('Add new: inherit', () => {
await page.getByRole('button', { name: 'down' }).hover();
await page.getByRole('menuitem', { name: 'child1' }).click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Current collection' }).click();
await page.getByLabel('block-item-CardItem-child1-').hover();
await expect(page.getByLabel('block-item-CardItem-child1-').getByText('child1')).toBeVisible();
@ -39,7 +39,7 @@ test.describe('Add new: inherit', () => {
await page.getByRole('button', { name: 'down' }).hover();
await page.getByRole('menuitem', { name: 'child2' }).click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Current collection' }).click();
await page.getByLabel('block-item-CardItem-child2-').hover();
await expect(page.getByLabel('block-item-CardItem-child2-').getByText('child2')).toBeVisible();

View File

@ -17,7 +17,7 @@ test('basic', async ({ page, mockPage, mockRecord }) => {
// 1. 打开弹窗,并创建一个 Table 关系区块
await page.getByLabel('action-Action.Link-Edit record-update-collection1-table-0').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
await page.getByRole('menuitem', { name: 'manyToMany' }).click();

View File

@ -337,7 +337,7 @@ test.describe('set default value', () => {
// 3. Table 数据选择器中使用 `Current popup record`
// 创建 Table 区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
@ -465,7 +465,7 @@ test.describe('set default value', () => {
// 3. Table 数据选择器中使用 `Parent popup record`
// 创建 Table 区块
await page.getByLabel('schema-initializer-Grid-popup').nth(1).hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
@ -595,7 +595,7 @@ test.describe('set default value', () => {
// 3. Table 数据选择器中使用 `Parent popup record`
// 创建 Table 区块
await page.getByLabel('schema-initializer-Grid-popup').nth(1).hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
@ -733,7 +733,7 @@ test.describe('set default value', () => {
// 3. Table 数据选择器中使用 `Parent popup record`
// 创建 Table 区块
await page.getByLabel('schema-initializer-Grid-popup').nth(1).hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);

View File

@ -143,7 +143,7 @@ test.describe('creation form block schema settings', () => {
// 创建区块的时候,可以选择刚才保存的模板 --------------------------------------------------
await page.getByLabel('schema-initializer-Grid-page:addBlock').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).first().hover();
await page.getByRole('menuitem', { name: 'Form right' }).first().hover();
await page.getByRole('menuitem', { name: 'General right' }).hover();
// Duplicate template
@ -152,7 +152,7 @@ test.describe('creation form block schema settings', () => {
// Reference template
await page.getByLabel('schema-initializer-Grid-page:addBlock').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).first().hover();
await page.getByRole('menuitem', { name: 'Form right' }).first().hover();
await page.getByRole('menuitem', { name: 'General right' }).hover();
await page.getByRole('menuitem', { name: 'General right' }).click();
await page.getByRole('menuitem', { name: 'Reference template right' }).click();

View File

@ -32,7 +32,7 @@ test.describe('where list block can be added', () => {
// 1. 打开弹窗,通过 Associated records 创建一个列表区块
await page.getByLabel('action-Action.Link-View').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'ordered-list List right' }).hover();
await page.getByRole('menuitem', { name: 'List right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page.mouse.move(300, 0);
@ -46,7 +46,7 @@ test.describe('where list block can be added', () => {
// 2. 通过 Other records 创建一个列表区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'ordered-list List right' }).hover();
await page.getByRole('menuitem', { name: 'List right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);

View File

@ -137,12 +137,15 @@ test.describe('table data selector schema settings', () => {
await expect(
page.getByLabel('block-item-CardItem-table-selector-data-scope-variable-table-selector').getByRole('row'),
).toHaveCount(2); // 这里之所以是 2是因为表头也是一个 row
await expect(
page
.getByLabel('block-item-CardItem-table-selector-data-scope-variable-table-selector')
.getByRole('row')
.getByText(record['manyToMany'][0]['singleLineText']),
).toBeVisible();
).toBeVisible({
timeout: 3000,
});
});
});

View File

@ -30,7 +30,7 @@ test.describe('block template', () => {
// The template saved above cannot be used to create a association block.
await page.getByLabel('action-Action.Link-View-view-').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
// The saved template should not be displayed (no arrow should be shown)
@ -79,7 +79,7 @@ test.describe('block template', () => {
// The template saved above cannot be used to create a non-association block.
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Roles right' }).hover();
await page.getByRole('menuitem', { name: 'Duplicate template right' }).hover();
await expect(page.getByRole('menuitem', { name: 'Roles_Table' })).toBeVisible();

View File

@ -23,7 +23,7 @@ test.describe('pagination', () => {
// 1. 创建一个 Table
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'collectionName' }).click();
// 显示出 ID
await page.getByLabel('schema-initializer-TableV2-').hover();

View File

@ -36,7 +36,7 @@ test.describe('where table block can be added', () => {
// 添加当前表关系区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
await page.getByRole('menuitem', { name: 'childAssociationField' }).click();
await page
@ -47,7 +47,7 @@ test.describe('where table block can be added', () => {
// 添加父表关系区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
await page.getByRole('menuitem', { name: 'parentAssociationField' }).click();
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-parentTargetCollection').hover();
@ -69,7 +69,7 @@ test.describe('where table block can be added', () => {
// 通过 Other records 创建一个表格区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
@ -99,7 +99,7 @@ test.describe('where table block can be added', () => {
await page.getByLabel('action-Action.Link-View-view-').first().click();
//表格关系区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).click();
await page.getByRole('menuitem', { name: 'Table right' }).click();
await page.getByText('Associated records').hover();
const [request] = await Promise.all([
page.waitForRequest((request) => request.url().includes('uiSchemas:insertAdjacent')),

View File

@ -104,7 +104,7 @@ test.describe('actions schema settings', () => {
// 配置出一个表单
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Current collection' }).click();
await page.getByLabel('schema-initializer-Grid-form:').hover();

View File

@ -14,7 +14,7 @@ test.describe('save as template', () => {
// 1. 创建一个区块,然后保存为模板
await mockPage().goto();
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.getByLabel('block-item-CardItem-users-').hover();
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:table-users').hover();

View File

@ -19,6 +19,7 @@ import { useCollectionManager_deprecated } from '../../../../collection-manager'
import { useFieldComponentName } from '../../../../common/useFieldComponentName';
import { useCollection } from '../../../../data-source';
import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem';
import { useFlag } from '../../../../flag-provider/hooks/useFlag';
import { useDesignable } from '../../../../schema-component';
import { useAssociationFieldContext } from '../../../../schema-component/antd/association-field/hooks';
import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator';
@ -89,7 +90,13 @@ export const tableColumnSettings = new SchemaSettings({
},
useVisible() {
const { fieldSchema } = useColumnSchema();
const { isInSubTable } = useFlag();
const field: any = useField();
if (!isInSubTable) {
return true;
}
const path = field.path?.splice(field.path?.length - 1, 1);
if (fieldSchema) {
const isReadPretty = field.form.query(`${path.concat(`*.` + fieldSchema.name)}`).get('readPretty');

View File

@ -18,7 +18,7 @@ test.describe('where filter block can be added', () => {
// 1. 页面中创建一个 filter form一个 filter collapse
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).nth(1).hover();
await page.getByRole('menuitem', { name: 'Form right' }).nth(1).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'Collapse right' }).hover();
@ -99,7 +99,7 @@ test.describe('where filter block can be added', () => {
await page.getByLabel('action-Action.Link-View record-view-users-table-1').click();
await page.waitForTimeout(1000);
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page.getByLabel('schema-initializer-Grid-filterForm:configureFields-roles').hover();
await page.getByRole('menuitem', { name: 'Role UID' }).click();
@ -151,7 +151,7 @@ test.describe('where filter block can be added', () => {
// 2. 测试用表单筛选其它区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.getByLabel('schema-initializer-Grid-filterForm:configureFields-users').hover();
await page.getByRole('menuitem', { name: 'Nickname' }).click();

View File

@ -42,6 +42,15 @@ export const ellipsisSettingsItem: SchemaSettingsItemType = {
checked: !!schema['x-component-props']?.ellipsis,
hidden,
onChange: async (checked) => {
if (tableFieldSchema && tableFieldInstanceList) {
tableFieldInstanceList.forEach((fieldInstance) => {
fieldInstance.componentProps.ellipsis = checked;
});
schema['x-component-props']['ellipsis'] = checked;
} else {
formField.componentProps.ellipsis = checked;
}
await dn.emit('patch', {
schema: {
'x-uid': schema['x-uid'],
@ -51,19 +60,6 @@ export const ellipsisSettingsItem: SchemaSettingsItemType = {
},
},
});
if (tableFieldSchema && tableFieldInstanceList) {
tableFieldInstanceList.forEach((fieldInstance) => {
fieldInstance.componentProps.ellipsis = checked;
});
schema['x-component-props']['ellipsis'] = checked;
const path = formField.path?.splice(formField.path?.length - 1, 1);
formField.form.query(`${path.concat(`*.` + fieldSchema.name)}`).forEach((f) => {
f.componentProps.ellipsis = checked;
});
} else {
formField.componentProps.ellipsis = checked;
}
},
};
},

View File

@ -66,8 +66,6 @@ test.describe('group page menus schema settings', () => {
await page.locator('.ant-select-dropdown').getByText('anchor page').click();
await page.getByLabel('Inner').click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// 当前页面菜单会消失
await expect(page.getByLabel('group page', { exact: true })).not.toBeVisible();
// 跳转到 anchor page 页面,会有一个名为 group page 的子页面菜单
await page.getByLabel('anchor page').click();
await expect(page.locator('.ant-layout-sider').getByLabel('group page')).toBeVisible();

View File

@ -13,7 +13,7 @@ test.describe('page:addBlock', () => {
test('当搜索不到数据时显示空状态', async ({ page, mockPage }) => {
await mockPage().goto();
await page.getByLabel('schema-initializer-Grid-').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('textbox', { name: 'Search and select collection' }).fill('no match');
await expect(page.getByRole('menuitem', { name: 'No data' })).toBeVisible();

View File

@ -86,7 +86,7 @@ test.describe('add blocks to the popup', () => {
// 通过 Association records 创建关系区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
await page.mouse.move(-300, 0);
@ -134,7 +134,7 @@ test.describe('add blocks to the popup', () => {
// 通过 Association records 创建一个关系区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Table right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page

View File

@ -13,6 +13,7 @@ import classNames from 'classnames';
// @ts-ignore
import React, { FC, startTransition, useCallback, useEffect, useMemo, useState } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { ErrorFallback } from '../error-fallback';
import { useCurrentPopupContext } from '../page/PagePopups';
import { TabsContextProvider, useTabsContext } from '../tabs/context';
@ -63,7 +64,7 @@ const ActionDrawerContent: FC<{ footerNodeName: string; field: any; schema: any
}
return (
<MemoizeRecursionField
<NocoBaseRecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
@ -77,7 +78,7 @@ ActionDrawerContent.displayName = 'ActionDrawerContent';
export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
(props) => {
const { footerNodeName = 'Action.Drawer.Footer', zIndex: _zIndex, ...others } = props;
const { footerNodeName = 'Action.Drawer.Footer', zIndex: _zIndex, onClose: onCloseFromProps, ...others } = props;
const { visible, setVisible, openSize = 'middle', drawerProps } = useActionContext();
const schema = useFieldSchema();
const field = useField();
@ -105,7 +106,13 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
const zIndex = _zIndex || parentZIndex + (props.level || 0);
const onClose = useCallback(() => setVisible(false, true), [setVisible]);
const onClose = useCallback(
(e) => {
setVisible(false, true);
onCloseFromProps?.(e);
},
[setVisible, onCloseFromProps],
);
const keepFooterNode = useCallback(
(s) => {
return s['x-component'] === footerNodeName;

View File

@ -8,9 +8,9 @@
*/
import { cx } from '@emotion/css';
import { observer, useFieldSchema } from '@formily/react';
import { useFieldSchema } from '@formily/react';
import { Space, SpaceProps } from 'antd';
import React, { CSSProperties, useContext } from 'react';
import React, { CSSProperties, FC, useContext } from 'react';
import { createPortal } from 'react-dom';
import { useSchemaInitializerRender } from '../../../application';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
@ -58,92 +58,96 @@ const Portal: React.FC = (props) => {
);
};
export const ActionBar = withDynamicSchemaProps(
observer((props: any) => {
const { forceProps = {} } = useActionBarContext();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { layout = 'two-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
const InternalActionBar: FC = (props: any) => {
const { forceProps = {} } = useActionBarContext();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { layout = 'two-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
const { designable } = useDesignable();
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
const { designable } = useDesignable();
if (layout === 'one-column') {
return (
<Portal>
<DndContext>
<div
style={{ display: 'flex', alignItems: 'center', gap: 8, ...style, marginTop: 0 }}
{...others}
className={cx(others.className, 'nb-action-bar')}
>
{props.children && (
<div>
<Space {...spaceProps} style={{ flexWrap: 'wrap', ...(spaceProps?.style || {}) }}>
{fieldSchema.mapProperties((schema, key) => {
return <NocoBaseRecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</div>
)}
{render({ style: { margin: '0 !important' } })}
</div>
</DndContext>
</Portal>
);
}
const hasActions = Object.keys(fieldSchema.properties ?? {}).length > 0;
if (layout === 'one-column') {
return (
<div
style={
!designable && !hasActions
? undefined
: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
overflowX: 'auto',
flexShrink: 0,
gap: '8px',
...style,
}
}
{...others}
className={cx(others.className, 'nb-action-bar')}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
overflow: 'hidden',
flexWrap: 'wrap',
gap: '8px',
}}
>
<DndContext>
<Space {...spaceProps} style={{ flexWrap: 'wrap' }}>
{fieldSchema.mapProperties((schema, key) => {
if (schema['x-align'] !== 'left') {
return null;
}
return <NocoBaseRecursionField key={key} name={key} schema={schema} />;
})}
</Space>
<Space {...spaceProps} style={{ flexWrap: 'wrap', ...(spaceProps?.style || {}) }}>
{fieldSchema.mapProperties((schema, key) => {
if (schema['x-align'] === 'left') {
return null;
}
return <NocoBaseRecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</DndContext>
</div>
{render()}
</div>
<Portal>
<DndContext>
<div
style={{ display: 'flex', alignItems: 'center', gap: 8, ...style, marginTop: 0 }}
{...others}
className={cx(others.className, 'nb-action-bar')}
>
{
<div>
<Space {...spaceProps} style={{ flexWrap: 'wrap', ...(spaceProps?.style || {}) }}>
{fieldSchema.mapProperties((schema, key) => {
return <NocoBaseRecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</div>
}
{render({ style: { margin: '0 !important' } })}
</div>
</DndContext>
</Portal>
);
}),
}
const hasActions = Object.keys(fieldSchema.properties ?? {}).length > 0;
return (
<div
style={
!designable && !hasActions
? undefined
: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
overflowX: 'auto',
flexShrink: 0,
gap: '8px',
...style,
}
}
{...others}
className={cx(others.className, 'nb-action-bar')}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
overflow: 'hidden',
flexWrap: 'wrap',
gap: '8px',
}}
>
<DndContext>
<Space {...spaceProps} style={{ flexWrap: 'wrap' }}>
{fieldSchema.mapProperties((schema, key) => {
if (schema['x-align'] !== 'left') {
return null;
}
return <NocoBaseRecursionField key={key} name={key} schema={schema} />;
})}
</Space>
<Space {...spaceProps} style={{ flexWrap: 'wrap', ...(spaceProps?.style || {}) }}>
{fieldSchema.mapProperties((schema, key) => {
if (schema['x-align'] === 'left') {
return null;
}
return <NocoBaseRecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</DndContext>
</div>
{render()}
</div>
);
};
export const ActionBar = withDynamicSchemaProps(
(props: any) => {
return <InternalActionBar {...props} />;
},
{ displayName: 'ActionBar' },
);

View File

@ -54,9 +54,11 @@ export const InternalNester = observer(
labelWidth = 120,
labelWrap = true,
} = fieldSchema?.['x-component-props'] || {};
useEffect(() => {
insertNester(schema.Nester);
}, []);
return (
<CollectionProvider_deprecated name={collectionField.target}>
<ACLCollectionProvider actionPath={`${collectionField.target}:${actionName || 'view'}`}>

View File

@ -155,7 +155,7 @@ const RenderRecord = React.memo(
ellipsisWithTooltipRef,
enableLink,
fieldNames?.label,
fieldSchema?.properties,
fieldSchema,
getLabelUiSchema,
insertViewer,
isTreeCollection,

View File

@ -37,7 +37,7 @@ export const useInsertSchema = (component) => {
insertAfterBegin(cloneDeep(ss));
}
},
[component],
[component, fieldSchema, insertAfterBegin],
);
return insert;
};

View File

@ -8,12 +8,13 @@
*/
import { Schema, useFieldSchema } from '@formily/react';
import React, { useContext } from 'react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaComponentContext, createDesignable } from '../..';
import { createDesignable } from '../..';
import { useAPIClient } from '../../../api-client';
import { useBlockRequestContext } from '../../../block-provider';
import { mergeFilter } from '../../../filter-provider/utils';
import { useRefreshFieldSchema } from '../../../formily/NocoBaseRecursionField';
import { ActionInitializerItem } from '../../../schema-initializer/items/ActionInitializerItem';
/**
@ -22,11 +23,11 @@ import { ActionInitializerItem } from '../../../schema-initializer/items/ActionI
* @returns
*/
export const ActionBarAssociationFilterAction = (props) => {
const { refresh } = useContext(SchemaComponentContext);
const refreshFieldSchema = useRefreshFieldSchema();
const fieldSchema = useFieldSchema();
const api = useAPIClient();
const { t } = useTranslation();
const dn = createDesignable({ t, api, refresh, current: fieldSchema });
const dn = createDesignable({ t, api, refresh: refreshFieldSchema, current: fieldSchema });
const { service, props: blockProps } = useBlockRequestContext();
dn.loadAPIClientEvents();

View File

@ -9,13 +9,14 @@
import { FormLayout } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { FieldContext, FormContext, observer, useField, useFieldSchema } from '@formily/react';
import { Options, Result } from 'ahooks/es/useRequest/src/types';
import { ConfigProvider, Spin } from 'antd';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { useAttach, useComponent } from '../..';
import { useRequest } from '../../../api-client';
import { useCollection_deprecated } from '../../../collection-manager';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { GeneralSchemaDesigner, SchemaSettingsDivider, SchemaSettingsRemove } from '../../../schema-settings';
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
import { useSchemaTemplate } from '../../../schema-templates';
@ -39,7 +40,7 @@ const FormComponent: React.FC<FormProps> = (props) => {
<FieldContext.Provider value={undefined}>
<FormContext.Provider value={form}>
<FormLayout layout={'vertical'} {...others}>
<RecursionField basePath={f.address} schema={fieldSchema} onlyRenderProperties />
<NocoBaseRecursionField basePath={f.address} schema={fieldSchema} onlyRenderProperties />
</FormLayout>
</FormContext.Provider>
</FieldContext.Provider>
@ -65,7 +66,7 @@ const FormDecorator: React.FC<FormProps> = (props) => {
<FormLayout layout={'vertical'} {...others}>
<FieldContext.Provider value={f}>
<Component {...field.componentProps}>
<RecursionField basePath={f.address} schema={fieldSchema} onlyRenderProperties />
<NocoBaseRecursionField basePath={f.address} schema={fieldSchema} onlyRenderProperties />
</Component>
</FieldContext.Provider>
{/* <FieldContext.Provider value={f}>{children}</FieldContext.Provider> */}

View File

@ -52,33 +52,6 @@ export const GridCardDesigner = () => {
const defaultResource =
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
const columnCountSchema = useMemo(() => {
return {
'x-component': 'Slider',
'x-decorator': 'FormItem',
'x-component-props': {
min: 1,
max: 24,
marks: columnCountMarks,
tooltip: {
formatter: (value) => `${value}${t('Column')}`,
},
step: null,
},
};
}, [t]);
const columnCountProperties = useMemo(() => {
return gridSizes.reduce((o, k) => {
o[k] = {
...columnCountSchema,
title: t(screenSizeTitleMaps[k]),
description: `${t('Screen size')} ${screenSizeMaps[k]} ${t('pixels')}`,
};
return o;
}, {});
}, [columnCountSchema, t]);
const sort = defaultSort?.map((item: string) => {
return item.startsWith('-')
? {

View File

@ -10,10 +10,11 @@
import { css, cx } from '@emotion/css';
import { FormLayout } from '@formily/antd-v5';
import { ArrayField } from '@formily/core';
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { Schema, useField, useFieldSchema } from '@formily/react';
import { List as AntdList, Col, PaginationProps } from 'antd';
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import { getCardItemSchema } from '../../../block-provider';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
import { withSkeletonComponent } from '../../../hoc/withSkeletonComponent';
import { SortableItem } from '../../common';
@ -127,25 +128,18 @@ const InternalGridCard = withSkeletonComponent(
const field = useField<ArrayField>();
const Designer = useDesigner();
const height = useGridCardBodyHeight();
const [schemaMap] = useState(new Map());
const getSchema = useCallback(
(key) => {
if (!schemaMap.has(key)) {
schemaMap.set(
key,
new Schema({
type: 'object',
properties: {
[key]: {
...fieldSchema.properties['item'],
},
},
}),
);
}
return schemaMap.get(key);
return new Schema({
type: 'object',
properties: {
[key]: {
...fieldSchema.properties['item'],
},
},
});
},
[fieldSchema.properties, schemaMap],
[fieldSchema.properties],
);
const onPaginationChange: PaginationProps['onChange'] = useCallback(
@ -218,13 +212,13 @@ const InternalGridCard = withSkeletonComponent(
renderItem={(item, index) => {
return (
<Col style={{ height: '100%' }} className="nb-card-item-warper">
<RecursionField
<NocoBaseRecursionField
key={index}
basePath={field.address}
name={index}
onlyRenderProperties
schema={getSchema(index)}
></RecursionField>
></NocoBaseRecursionField>
</Col>
);
}}

View File

@ -297,7 +297,7 @@ const useRowProperties = () => {
}
return buf;
}, []);
}, [Object.keys(fieldSchema.properties || {}).join(',')]);
}, [fieldSchema]);
};
const useColProperties = () => {
@ -309,7 +309,7 @@ const useColProperties = () => {
}
return buf;
}, []);
}, [Object.keys(fieldSchema.properties || {}).join(',')]);
}, [fieldSchema]);
};
const DndWrapper = (props) => {
@ -361,7 +361,7 @@ export const Grid: any = observer(
useEffect(() => {
gridRef.current && setPrintContent?.(gridRef.current);
}, [gridRef.current]);
}, [setPrintContent]);
const gridContextValue = useMemo(() => {
return {
@ -498,7 +498,7 @@ Grid.Col = observer(
width = `calc(${w}% - ${token.marginBlock}px * ${(showDivider ? cols.length + 1 : 0) / cols.length})`;
}
return { width };
}, [cols?.length, schema?.['x-component-props']?.['width'], token.marginBlock]);
}, [cols.length, schema, showDivider, token.marginBlock]);
const { isOver, setNodeRef } = useDroppable({
id: field.address.toString(),

View File

@ -10,10 +10,11 @@
import { css, cx } from '@emotion/css';
import { FormLayout } from '@formily/antd-v5';
import { ArrayField } from '@formily/core';
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { Schema, useField, useFieldSchema } from '@formily/react';
import { List as AntdList, PaginationProps, theme } from 'antd';
import React, { useCallback, useState } from 'react';
import { getCardItemSchema } from '../../../block-provider';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
import { withSkeletonComponent } from '../../../hoc/withSkeletonComponent';
import { SortableItem } from '../../common';
@ -159,13 +160,13 @@ const InternalList = withSkeletonComponent(
{field.value?.length
? field.value.map((item, index) => {
return (
<RecursionField
<NocoBaseRecursionField
basePath={field.address}
key={index}
name={index}
onlyRenderProperties
schema={getSchema(index)}
></RecursionField>
></NocoBaseRecursionField>
);
})
: null}

View File

@ -11,7 +11,6 @@ import { css } from '@emotion/css';
import {
FieldContext,
observer,
RecursionField,
SchemaContext,
SchemaExpressionScopeContext,
useField,
@ -23,14 +22,22 @@ import { Menu as AntdMenu, MenuProps } from 'antd';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { createDesignable, DndContext, SchemaComponentContext, SortableItem, useDesignable, useDesigner } from '../..';
import { Icon, useAPIClient, useParseURLAndParams, useSchemaInitializerRender } from '../../../';
import {
Icon,
NocoBaseRecursionField,
useAPIClient,
useParseURLAndParams,
useSchemaInitializerRender,
} from '../../../';
import { useCollectMenuItems, useMenuItem } from '../../../hooks/useMenuItem';
import { useProps } from '../../hooks/useProps';
import { useMenuTranslation } from './locale';
import { MenuDesigner } from './Menu.Designer';
import { findKeysByUid, findMenuItem } from './util';
import { useUpdate } from 'ahooks';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useRefreshComponent, useRefreshFieldSchema } from '../../../formily/NocoBaseRecursionField';
const subMenuDesignerCss = css`
position: relative;
@ -210,7 +217,6 @@ const HeaderMenu = React.memo<{
onChange: any;
onFocus: any;
theme: any;
refreshId: number;
}>(
({
schema,
@ -228,10 +234,6 @@ const HeaderMenu = React.memo<{
onChange,
onFocus,
theme,
/**
* Used to refresh the component
*/
refreshId,
}) => {
const { Component, getMenuItems } = useMenuItem();
const items = useMemo(() => {
@ -255,7 +257,7 @@ const HeaderMenu = React.memo<{
return result;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children, designable, refreshId]);
}, [children, designable]);
const handleSelect = useCallback(
(info: { item; key; keyPath; domEvent }) => {
@ -327,17 +329,24 @@ const SideMenu = React.memo<any>(
t,
api,
designable,
refreshId,
refresh,
}) => {
// Used to refresh component
refreshId;
const { Component, getMenuItems } = useMenuItem();
// 使用 ref 用来防止闭包问题
const sideMenuSchemaRef = useRef(sideMenuSchema);
sideMenuSchemaRef.current = sideMenuSchema;
const update = useUpdate();
const refreshFieldSchema = useRefreshFieldSchema();
const refreshComponent = useRefreshComponent();
const refresh = useCallback(
(options?: { refreshParentSchema?: boolean }) => {
console.log('refresh');
// refresh current component
update();
// refresh fieldSchema context value
refreshFieldSchema?.(options);
// refresh component context value
refreshComponent?.();
},
[update, refreshFieldSchema, refreshComponent],
);
const handleSelect = useCallback(
(info) => {
@ -348,7 +357,7 @@ const SideMenu = React.memo<any>(
const items = useMemo(() => {
const result = getMenuItems(() => {
return <RecursionField key={uid()} schema={sideMenuSchema} onlyRenderProperties />;
return <NocoBaseRecursionField key={uid()} schema={sideMenuSchema} onlyRenderProperties />;
});
if (designable) {
@ -362,7 +371,7 @@ const SideMenu = React.memo<any>(
t,
api,
refresh: refresh,
current: sideMenuSchemaRef.current,
current: sideMenuSchema,
});
dn.loadAPIClientEvents();
dn.insertAdjacent('beforeEnd', s);
@ -374,8 +383,7 @@ const SideMenu = React.memo<any>(
}
return result;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, designable, getMenuItems, refresh, render, sideMenuSchema, t, refreshId]);
}, [api, designable, getMenuItems, refresh, render, sideMenuSchema, t]);
return (
mode === 'mix' &&
@ -497,57 +505,44 @@ export const Menu: ComposedMenu = React.memo((props) => {
}, [defaultSelectedKeys]);
const ctx = useContext(SchemaComponentContext);
const refreshIdRef = useRef(0);
const ctxRefresh = ctx.refresh;
const refresh = useCallback(() => {
refreshIdRef.current += 1;
ctxRefresh?.();
}, [ctxRefresh]);
const newCtx = useMemo(() => ({ ...ctx, refresh }), [ctx, refresh]);
return (
<DndContext>
<SchemaComponentContext.Provider value={newCtx}>
<MenuItemDesignerContext.Provider value={Designer}>
<MenuModeContext.Provider value={mode}>
<HeaderMenu
disabled={disabled}
onBlur={onBlur}
onChange={onChange}
onFocus={onFocus}
theme={theme}
schema={schema}
mode={mode}
onSelect={onSelect}
setDefaultSelectedKeys={setDefaultSelectedKeys}
defaultSelectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultOpenKeys}
selectedKeys={selectedKeys}
designable={ctx.designable}
render={render}
refreshId={refreshIdRef.current}
>
{children}
</HeaderMenu>
<SideMenu
mode={mode}
sideMenuSchema={sideMenuSchema}
sideMenuRef={sideMenuRef}
openKeys={defaultOpenKeys}
setOpenKeys={setDefaultOpenKeys}
selectedKeys={selectedKeys}
onSelect={onSelect}
render={render}
t={t}
api={api}
designable={ctx.designable}
refreshId={refreshIdRef.current}
refresh={refresh}
/>
</MenuModeContext.Provider>
</MenuItemDesignerContext.Provider>
</SchemaComponentContext.Provider>
<MenuItemDesignerContext.Provider value={Designer}>
<MenuModeContext.Provider value={mode}>
<HeaderMenu
disabled={disabled}
onBlur={onBlur}
onChange={onChange}
onFocus={onFocus}
theme={theme}
schema={schema}
mode={mode}
onSelect={onSelect}
setDefaultSelectedKeys={setDefaultSelectedKeys}
defaultSelectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultOpenKeys}
selectedKeys={selectedKeys}
designable={ctx.designable}
render={render}
>
{children}
</HeaderMenu>
<SideMenu
mode={mode}
sideMenuSchema={sideMenuSchema}
sideMenuRef={sideMenuRef}
openKeys={defaultOpenKeys}
setOpenKeys={setDefaultOpenKeys}
selectedKeys={selectedKeys}
onSelect={onSelect}
render={render}
t={t}
api={api}
designable={ctx.designable}
/>
</MenuModeContext.Provider>
</MenuItemDesignerContext.Provider>
</DndContext>
);
});
@ -728,7 +723,7 @@ Menu.SubMenu = observer(
</SchemaContext.Provider>
),
children: getMenuItems(() => {
return <RecursionField schema={schema} onlyRenderProperties />;
return <NocoBaseRecursionField schema={schema} onlyRenderProperties />;
}),
};
}, [field.title, icon, schema, children, Designer]);

View File

@ -35,7 +35,6 @@ import { KeepAliveProvider, useKeepAlive } from '../../../route-switch/antd/admi
import { useGetAriaLabelOfSchemaInitializer } from '../../../schema-initializer/hooks/useGetAriaLabelOfSchemaInitializer';
import { DndContext } from '../../common';
import { SortableItem } from '../../common/sortable-item';
import { SchemaComponentContext, useNewRefreshContext } from '../../context';
import { SchemaComponent, SchemaComponentOptions } from '../../core';
import { useDesignable } from '../../hooks';
import { useToken } from '../__builtins__';
@ -348,7 +347,6 @@ const NocoBasePageHeader = React.memo(({ activeKey, className }: { activeKey: st
const { setTitle: setDocumentTitle } = useDocumentTitle();
const { t } = useTranslation();
const [pageTitle, setPageTitle] = useState(() => t(fieldSchema.title));
const newRefreshCtx = useNewRefreshContext();
const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
const enablePageTabs = fieldSchema['x-component-props']?.enablePageTabs;
@ -376,7 +374,7 @@ const NocoBasePageHeader = React.memo(({ activeKey, className }: { activeKey: st
);
return (
<SchemaComponentContext.Provider value={newRefreshCtx}>
<>
<PageDesigner title={pageTitle} />
{!disablePageHeader && (
<AntdPageHeader
@ -387,7 +385,7 @@ const NocoBasePageHeader = React.memo(({ activeKey, className }: { activeKey: st
footer={<NocoBasePageHeaderTabs className={className} activeKey={activeKey} />}
/>
)}
</SchemaComponentContext.Provider>
</>
);
});

View File

@ -8,7 +8,7 @@
*/
import { ArrayField } from '@formily/core';
import { RecursionField, useField, useFieldSchema } from '@formily/react';
import { useField, useFieldSchema } from '@formily/react';
import { toArr } from '@formily/shared';
import { Select } from 'antd';
import { differenceBy, unionBy } from 'lodash';
@ -18,6 +18,7 @@ import {
useTableSelectorProps as useTsp,
} from '../../../block-provider/TableSelectorProvider';
import { CollectionProvider_deprecated, useCollection_deprecated } from '../../../collection-manager';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { FormProvider, SchemaComponentOptions } from '../../core';
import { useCompile } from '../../hooks';
import { ActionContextProvider, useActionContext } from '../action';
@ -277,7 +278,7 @@ const Drawer: React.FunctionComponent<{
<FormProvider>
<TableSelectorParamsProvider params={{ filter: getFilter() }}>
<SchemaComponentOptions scope={{ useTableSelectorProps, usePickActionProps }}>
<RecursionField
<NocoBaseRecursionField
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observer, RecursionField, useFieldSchema } from '@formily/react';
import { observer, useFieldSchema } from '@formily/react';
import { toArr } from '@formily/shared';
import React, { Fragment, useRef, useState } from 'react';
import { WithoutTableFieldResource } from '../../../block-provider';
@ -16,6 +16,7 @@ import { BlockAssociationContext } from '../../../block-provider/BlockProvider';
import { CollectionProvider_deprecated } from '../../../collection-manager';
import { useCollectionManager } from '../../../data-source/collection/CollectionManagerProvider';
import { useCollection } from '../../../data-source/collection/CollectionProvider';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { RecordProvider, useRecord } from '../../../record-provider';
import { FormProvider } from '../../core';
import { useCompile } from '../../hooks';
@ -94,7 +95,7 @@ export const ReadPrettyRecordPicker: React.FC = observer(
const renderWithoutTableFieldResourceProvider = () => (
<WithoutTableFieldResource.Provider value={true}>
<FormProvider>
<RecursionField
<NocoBaseRecursionField
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {

View File

@ -13,11 +13,11 @@ import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sor
import { css, cx } from '@emotion/css';
import { ArrayField } from '@formily/core';
import { spliceArrayState } from '@formily/core/esm/shared/internals';
import { Schema, SchemaOptionsContext, observer, useField, useFieldSchema } from '@formily/react';
import { observer, Schema, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
import { action } from '@formily/reactive';
import { uid } from '@formily/shared';
import { isPortalInBody } from '@nocobase/utils/client';
import { useCreation, useDeepCompareEffect, useMemoizedFn } from 'ahooks';
import { useDeepCompareEffect, useMemoizedFn } from 'ahooks';
import { Table as AntdTable, TableColumnProps } from 'antd';
import { default as classNames, default as cls } from 'classnames';
import _, { omit } from 'lodash';
@ -29,11 +29,13 @@ import {
BlockRequestLoadingContext,
RecordIndexProvider,
RecordProvider,
useAssociationNames,
useCollection,
useCollectionParentRecordData,
useDataBlockProps,
useDataBlockRequest,
useDataBlockRequestData,
useDataBlockRequestGetter,
useFlag,
useSchemaInitializerRender,
useTableSelectorContext,
@ -41,10 +43,15 @@ import {
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
import { useTableBlockContext } from '../../../block-provider/TableBlockProvider';
import { isNewRecord } from '../../../data-source/collection-record/isNewRecord';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import {
NocoBaseRecursionField,
RefreshComponentProvider,
useRefreshFieldSchema,
} from '../../../formily/NocoBaseRecursionField';
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
import { withSkeletonComponent } from '../../../hoc/withSkeletonComponent';
import { useSatisfiedActionValues } from '../../../schema-settings/LinkageRules/useActionValues';
import { LinkageRuleDataKeyMap } from '../../../schema-settings/LinkageRules/type';
import { GetStyleRules } from '../../../schema-settings/LinkageRules/useActionValues';
import { HighPerformanceSpin } from '../../common/high-performance-spin/HighPerformanceSpin';
import { useToken } from '../__builtins__';
import { useAssociationFieldContext } from '../association-field/hooks';
@ -99,17 +106,6 @@ function adjustColumnOrder(columns) {
return [...leftFixedColumns, ...normalColumns, ...rightFixedColumns];
}
const useColumnsDeepMemoized = (columns: any[]) => {
const columnsJSON = getSchemaArrJSON(columns);
const oldObj = useCreation(() => ({ value: _.cloneDeep(columnsJSON) }), []);
if (!_.isEqual(columnsJSON, oldObj.value)) {
oldObj.value = _.cloneDeep(columnsJSON);
}
return oldObj.value;
};
const TableCellRender: FC<{
record: any;
columnSchema: Schema;
@ -118,7 +114,7 @@ const TableCellRender: FC<{
schemaToolbarBigger: string;
field: ArrayField;
index: number;
}> = React.memo(({ record, columnSchema, uiSchema, filterProperties, schemaToolbarBigger, field, index }) => {
}> = ({ record, columnSchema, uiSchema, filterProperties, schemaToolbarBigger, field, index }) => {
const basePath = field.address.concat(record.__index || index);
return (
@ -135,9 +131,29 @@ const TableCellRender: FC<{
/>
</span>
);
});
};
TableCellRender.displayName = 'TableCellRender';
const useRefreshTableColumns = () => {
const { params: blockParams, dataSource } = useDataBlockProps() || {};
const { getDataBlockRequest } = useDataBlockRequestGetter();
const { getAssociationAppends } = useAssociationNames(dataSource);
const prevParamsRef = useRef(blockParams);
const refreshFieldSchema = useRefreshFieldSchema();
const refresh = useCallback(() => {
const { appends } = getAssociationAppends();
const service = getDataBlockRequest();
if (!_.isEqual(prevParamsRef.current.appends, appends)) {
prevParamsRef.current = { ...blockParams, appends };
service.run(prevParamsRef.current);
}
refreshFieldSchema?.();
}, [blockParams, getAssociationAppends, getDataBlockRequest, refreshFieldSchema]);
return { refresh };
};
const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginationProps) => {
const { token } = useToken();
@ -146,15 +162,17 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
const { schemaInWhitelist } = useACLFieldWhitelist();
const { designable } = useDesignable();
const { exists, render } = useSchemaInitializerRender(schema['x-initializer'], schema['x-initializer-props']);
const columnsSchemas = schema.reduceProperties((buf, s) => {
if (isColumnComponent(s) && schemaInWhitelist(Object.values(s.properties || {}).pop())) {
return buf.concat([s]);
}
return buf;
}, []);
const columnsSchemas = useMemo(() => {
return schema.reduceProperties((buf, s) => {
if (isColumnComponent(s) && schemaInWhitelist(Object.values(s.properties || {}).pop())) {
return buf.concat([s]);
}
return buf;
}, []);
}, [schema, schemaInWhitelist]);
const { current, pageSize } = paginationProps;
const hasChangedColumns = useColumnsDeepMemoized(columnsSchemas);
const { isPopupVisibleControlledByURL } = usePopupSettings();
const { refresh } = useRefreshTableColumns();
const filterProperties = useCallback(
(schema) =>
@ -191,12 +209,14 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
return {
title: (
<NocoBaseRecursionField
name={columnSchema.name}
schema={columnSchema}
onlyRenderSelf
isUseFormilyField={false}
/>
<RefreshComponentProvider refresh={refresh}>
<NocoBaseRecursionField
name={columnSchema.name}
schema={columnSchema}
onlyRenderSelf
isUseFormilyField={false}
/>
</RefreshComponentProvider>
),
dataIndex,
key: columnSchema.name,
@ -222,7 +242,6 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
record,
schema: columnSchema,
rowIndex,
isSubTable: props.isSubTable,
columnHidden,
};
},
@ -234,9 +253,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
} as TableColumnProps<any>;
}),
// 这里不能把 columnsSchema 作为依赖,因为其每次都会变化,这里使用 hasChangedColumns 作为依赖
// eslint-disable-next-line react-hooks/exhaustive-deps
[hasChangedColumns, field.address, collection, schemaToolbarBigger, designable, filterProperties],
[columnsSchemas, collection, refresh, designable, filterProperties, schemaToolbarBigger, field],
);
const tableColumns = useMemo(() => {
@ -246,7 +263,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
const res = [
...columns,
{
title: render(),
title: <RefreshComponentProvider refresh={refresh}>{render()}</RefreshComponentProvider>,
dataIndex: 'TABLE_COLUMN_INITIALIZER',
key: 'TABLE_COLUMN_INITIALIZER',
render: designable
@ -602,19 +619,27 @@ const InternalBodyCellComponent = React.memo<BodyCellComponentProps>((props) =>
const inView = useContext(InViewContext);
const isIndex = props.className?.includes('selection-column');
const { record, schema, rowIndex, isSubTable, ...others } = props;
const { valueMap } = useSatisfiedActionValues({ formValues: record, category: 'style', schema });
const style = useMemo(() => Object.assign({ ...props.style }, valueMap), [props.style, valueMap]);
const skeletonStyle = {
height: '1em',
backgroundColor: token.colorFillSecondary,
borderRadius: `${token.borderRadiusSM}px`,
};
const styleRules = schema?.[LinkageRuleDataKeyMap['style']];
const [dynamicStyle, setDynamicStyle] = useState({});
const style = useMemo(() => ({ ...props.style, ...dynamicStyle }), [props.style, dynamicStyle]);
const skeletonStyle = useMemo(
() => ({
height: '1em',
backgroundColor: token.colorFillSecondary,
borderRadius: `${token.borderRadiusSM}px`,
}),
[token.borderRadiusSM, token.colorFillSecondary],
);
return (
<td {...others} className={classNames(props.className, cellClass)} style={style}>
{/* Lazy rendering cannot be used in sub-tables. */}
{isSubTable || inView || isIndex ? props.children : <div style={skeletonStyle} />}
</td>
<>
{/* To improve rendering performance, do not render GetStyleRules component when no style rules are set */}
{!_.isEmpty(styleRules) && <GetStyleRules record={record} schema={schema} onStyleChange={setDynamicStyle} />}
<td {...others} className={classNames(props.className, cellClass)} style={style}>
{/* Lazy rendering cannot be used in sub-tables. */}
{isSubTable || inView || isIndex ? props.children : <div style={skeletonStyle} />}
</td>
</>
);
});

View File

@ -47,7 +47,7 @@ const useDragEnd = (onDragEnd) => {
const dn = createDesignable({
t,
api,
refresh,
refresh: ({ refreshParentSchema = true } = {}) => refresh({ refreshParentSchema }),
current: overSchema,
});
@ -70,7 +70,7 @@ const useDragEnd = (onDragEnd) => {
return;
}
},
[onDragEnd],
[api, onDragEnd, refresh, t],
);
};

View File

@ -78,7 +78,7 @@ const useSortableItemId = (props) => {
if (props.id) {
return props.id;
}
return field.address.toString();
return field.address?.toString();
};
interface SortableItemProps extends HTMLAttributes<HTMLDivElement> {

View File

@ -7,39 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useUpdate } from 'ahooks';
import { createContext, useCallback, useContext, useMemo } from 'react';
import { useRefreshFieldSchema } from '../formily/NocoBaseRecursionField';
import { createContext } from 'react';
import { ISchemaComponentContext } from './types';
export const SchemaComponentContext = createContext<ISchemaComponentContext>({});
SchemaComponentContext.displayName = 'SchemaComponentContext.Provider';
/**
* Get a new refresh context, used to refresh the block that uses this hook
* @returns
*/
export const useNewRefreshContext = (refresh?: () => void) => {
const oldCtx = useContext(SchemaComponentContext);
const newCtx = useMemo(() => ({ ...oldCtx }), [oldCtx]);
const refreshFieldSchema = useRefreshFieldSchema();
const update = useUpdate();
const _refresh = useCallback(
(options?: { refreshParent?: boolean }) => {
// refresh fieldSchema
refreshFieldSchema(options);
// refresh current component
update();
refresh?.();
},
[refreshFieldSchema, update, refresh],
);
if (oldCtx) {
Object.assign(newCtx, oldCtx);
newCtx.refresh = _refresh;
}
return newCtx;
};

View File

@ -15,16 +15,19 @@ import { useTranslation } from 'react-i18next';
import { useDesignable } from '..';
import { useToken } from '../../style';
const designableStyle = {
backgroundColor: 'var(--colorSettings) !important',
};
const unDesignableStyle = {
backgroundColor: 'transparent',
};
export const DesignableSwitch = () => {
const { designable, setDesignable } = useDesignable();
const { t } = useTranslation();
const { token } = useToken();
const style = {};
if (designable) {
style['backgroundColor'] = 'var(--colorSettings)';
} else {
style['backgroundColor'] = 'transparent';
}
const style = designable ? designableStyle : unDesignableStyle;
// 快捷键切换编辑状态
useHotkeys('Ctrl+Shift+U', () => setDesignable(!designable), [designable]);

View File

@ -8,14 +8,14 @@
*/
import { IRecursionFieldProps, ISchemaFieldProps, Schema } from '@formily/react';
import { useUpdate } from 'ahooks';
import React, { memo, useContext, useMemo } from 'react';
import _ from 'lodash';
import React, { createContext, memo, useContext, useMemo } from 'react';
import { NocoBaseRecursionField } from '../../formily/NocoBaseRecursionField';
import { SchemaComponentContext } from '../context';
import { SchemaComponentOptions } from './SchemaComponentOptions';
type SchemaComponentOnChange = {
onChange?: (s: Schema) => void;
onChange?: (s?: Schema) => void;
};
function toSchema(schema?: any) {
@ -45,32 +45,49 @@ interface DistributedProps {
distributed?: boolean;
}
/**
* Used to pass the onChange callback function.
*
* The onChange callback will be triggered whenever a descendant Schema changes.
*/
export const SchemaComponentOnChangeContext = createContext<SchemaComponentOnChange>({ onChange: _.noop });
const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => {
const { components, scope, schema: _schema, distributed, onChange, ...others } = props;
const { components, scope, schema: _schema, distributed, onChange: _onChange, ...others } = props;
const ctx = useContext(SchemaComponentContext);
const schema = useMemo(() => toSchema(_schema), [_schema]);
const refresh = useUpdate();
const value = useMemo(
() => ({
...ctx,
distributed: ctx.distributed == false ? false : distributed,
refresh: () => {
refresh();
if (ctx.distributed === false || distributed === false) {
ctx.refresh?.();
}
onChange?.(schema);
/**
* @deprecated
*/
refresh: _.noop,
}),
[ctx, distributed],
);
const { onChange: onChangeFromContext } = useContext(SchemaComponentOnChangeContext);
const onChangeValue = useMemo(
() => ({
onChange: () => {
_onChange?.(schema);
onChangeFromContext?.();
},
}),
[ctx, distributed, onChange, refresh, schema],
[_onChange, onChangeFromContext, schema],
);
return (
<SchemaComponentContext.Provider value={value}>
<SchemaComponentOptions inherit components={components} scope={scope}>
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField />
</SchemaComponentOptions>
</SchemaComponentContext.Provider>
<SchemaComponentOnChangeContext.Provider value={onChangeValue}>
<SchemaComponentContext.Provider value={value}>
<SchemaComponentOptions inherit components={components} scope={scope}>
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField />
</SchemaComponentOptions>
</SchemaComponentContext.Provider>
</SchemaComponentOnChangeContext.Provider>
);
});

View File

@ -10,7 +10,7 @@
import { createForm } from '@formily/core';
import { FormProvider, Schema } from '@formily/react';
import { uid } from '@formily/shared';
import { useUpdate } from 'ahooks';
import _ from 'lodash';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaComponentContext } from '../context';
@ -56,7 +56,6 @@ export const SchemaComponentProvider: React.FC<ISchemaComponentProvider> = (prop
const { designable, onDesignableChange, components, children } = props;
const ctx = useContext(SchemaComponentContext);
const ctxOptions = useSchemaOptionsContext();
const refresh = useUpdate();
const [formId, setFormId] = useState(() => uid());
const form = useMemo(() => props.form || createForm(), [formId]);
const { t } = useTranslation();
@ -89,11 +88,14 @@ export const SchemaComponentProvider: React.FC<ISchemaComponentProvider> = (prop
scope,
components,
reset,
refresh,
/**
* @deprecated
*/
refresh: _.noop,
designable: designableValue,
setDesignable,
}),
[components, designableValue, refresh, reset, scope, setDesignable],
[components, designableValue, reset, scope, setDesignable],
);
return (

View File

@ -10,6 +10,7 @@
import { GeneralField, Query } from '@formily/core';
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { useUpdate } from 'ahooks';
import { message } from 'antd';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
@ -17,6 +18,7 @@ import set from 'lodash/set';
import React, { ComponentType, useCallback, useContext, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { APIClient, useAPIClient } from '../../api-client';
import { useRefreshComponent, useRefreshFieldSchema } from '../../formily/NocoBaseRecursionField';
import { LAZY_COMPONENT_KEY } from '../../lazy-helper';
import { SchemaComponentContext } from '../context';
import { addAppVersion } from './addAppVersion';
@ -29,7 +31,7 @@ interface CreateDesignableProps {
model?: GeneralField;
query?: Query;
api?: APIClient;
refresh?: (options?: { refreshParent?: boolean }) => void;
refresh?: (options?: { refreshParentSchema?: boolean }) => void;
onSuccess?: any;
t?: any;
/**
@ -219,7 +221,6 @@ export class Designable {
message.success(t('Saved successfully'), 0.2);
});
this.on('initializeActionContext', async ({ schema }) => {
this.refresh();
if (!schema?.['x-uid']) {
return;
}
@ -316,7 +317,7 @@ export class Designable {
return false;
}
refresh(options?: { refreshParent?: boolean }) {
refresh(options?: { refreshParentSchema?: boolean }) {
const { refresh } = this.options;
return refresh?.(options);
}
@ -743,7 +744,7 @@ export function useFindComponent() {
// TODO
export function useDesignable() {
const { designable, setDesignable, refresh, reset } = useContext(SchemaComponentContext);
const { designable, setDesignable, refresh: refreshFromContext, reset } = useContext(SchemaComponentContext);
const schemaOptions = useContext(SchemaOptionsContext);
const components = useMemo(() => schemaOptions?.components || {}, [schemaOptions]);
const DesignableBar = useMemo(
@ -752,6 +753,21 @@ export function useDesignable() {
},
[],
);
const update = useUpdate();
const refreshFieldSchema = useRefreshFieldSchema();
const refreshComponent = useRefreshComponent();
const refresh = useCallback(
(options?: { refreshParentSchema?: boolean }) => {
refreshFromContext?.();
// refresh current component
update();
// refresh fieldSchema context value
refreshFieldSchema?.(options);
// refresh component context value
refreshComponent?.();
},
[refreshFromContext, update, refreshFieldSchema, refreshComponent],
);
const field = useField();
const fieldSchema = useFieldSchema();
const api = useAPIClient();

View File

@ -14,7 +14,7 @@ import React from 'react';
export interface ISchemaComponentContext {
scope?: any;
components?: SchemaReactComponents;
refresh?: (options?: { refreshParent?: boolean }) => void;
refresh?: (options?: { refreshParentSchema?: boolean }) => void;
reset?: () => void;
designable?: boolean;
setDesignable?: (value: boolean) => void;

View File

@ -10,6 +10,7 @@
import { merge } from '@formily/shared';
import React from 'react';
import { useUpdate } from 'ahooks';
import { SchemaInitializerSwitch, useSchemaInitializer } from '../../application';
import { useCurrentSchema } from '../utils';
@ -23,6 +24,7 @@ export const InitializerWithSwitch = (props) => {
schema?.name || item?.schema?.name,
);
const { insert } = useSchemaInitializer();
const update = useUpdate();
return (
<SchemaInitializerSwitch
checked={exists}
@ -33,11 +35,13 @@ export const InitializerWithSwitch = (props) => {
return;
}
if (exists) {
return remove();
remove();
return update();
}
const s = merge(schema || {}, item.schema || {});
item?.schemaInitialize?.(s);
insert(s);
update();
}}
/>
);

View File

@ -7,14 +7,15 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useState, useEffect, useCallback } from 'react';
import { uid } from '@formily/shared';
import { Form, onFormValuesChange } from '@formily/core';
import { useVariables, useLocalVariables } from '../../variables';
import { useFieldSchema } from '@formily/react';
import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './type';
import { getSatisfiedValueMap } from './compute-rules';
import { Schema, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import { useLocalVariables, useVariables } from '../../variables';
import { getSatisfiedValueMap } from './compute-rules';
import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './type';
export function useSatisfiedActionValues({
formValues,
category = 'default',
@ -33,10 +34,11 @@ export function useSatisfiedActionValues({
const variables = useVariables();
const localVariables = useLocalVariables({ currentForm: { values: formValues } as any });
const localSchema = schema ?? fieldSchema;
const linkageRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]];
const styleRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]];
const compute = useCallback(() => {
if (linkageRules && formValues) {
getSatisfiedValueMap({ rules: linkageRules, variables, localVariables })
if (styleRules && formValues) {
getSatisfiedValueMap({ rules: styleRules, variables, localVariables })
.then((valueMap) => {
if (!isEmpty(valueMap)) {
setValueMap(valueMap);
@ -46,11 +48,11 @@ export function useSatisfiedActionValues({
throw new Error(err.message);
});
}
}, [variables, localVariables, linkageRules, formValues]);
}, [variables, localVariables, styleRules, formValues]);
useEffect(() => {
compute();
}, [compute]);
useEffect(() => {
if (form) {
const id = uid();
form.addEffects(id, () => {
@ -63,5 +65,22 @@ export function useSatisfiedActionValues({
};
}
}, [form, compute]);
return { valueMap };
}
export const GetStyleRules: React.FC<{
record: Record<string, any>;
schema: Schema;
onStyleChange?: (value: Record<string, any>) => void;
}> = React.memo(({ record, schema, onStyleChange }) => {
const { valueMap } = useSatisfiedActionValues({ formValues: record, category: 'style', schema });
useEffect(() => {
onStyleChange(valueMap);
}, [onStyleChange, valueMap]);
return null;
});
GetStyleRules.displayName = 'GetStyleRules';

View File

@ -96,7 +96,7 @@ import { useRecord } from '../record-provider';
import { ActionContextProvider } from '../schema-component/antd/action/context';
import { SubFormProvider, useSubFormValue } from '../schema-component/antd/association-field/hooks';
import { FormDialog } from '../schema-component/antd/form-dialog';
import { SchemaComponentContext, useNewRefreshContext } from '../schema-component/context';
import { SchemaComponentContext } from '../schema-component/context';
import { FormProvider } from '../schema-component/core/FormProvider';
import { RemoteSchemaComponent } from '../schema-component/core/RemoteSchemaComponent';
import { SchemaComponent } from '../schema-component/core/SchemaComponent';
@ -174,14 +174,6 @@ export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = React.memo(
// 单测中需要在首次就把菜单渲染出来,否则不会触发菜单的渲染进而报错。原因未知。
const [openDropdown, setOpenDropdown] = useState(process.env.__TEST__ ? true : false);
const toolbarVisible = useContext(SchemaToolbarVisibleContext);
const refreshCtx = useContext(SchemaComponentContext);
const newRefreshCtx = useNewRefreshContext(refreshCtx.refresh);
const newDn: any = useMemo(() => {
const result = Object.create(dn);
result.refresh = newRefreshCtx.refresh;
return result;
}, [dn, newRefreshCtx.refresh]);
useEffect(() => {
if (toolbarVisible) {
@ -214,30 +206,28 @@ export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = React.memo(
const items = getMenuItems(() => props.children);
return (
<SchemaComponentContext.Provider value={newRefreshCtx}>
<SchemaSettingsProvider visible={visible} setVisible={setVisible} dn={newDn} {...others}>
<Component />
<Dropdown
open={visible}
onOpenChange={changeMenu}
overlayClassName={css`
.ant-dropdown-menu-item-group-list {
max-height: 300px;
overflow-y: auto;
}
`}
menu={
{
items,
'data-testid': 'schema-settings-menu',
style: { maxHeight: dropdownMaxHeight, overflowY: 'auto' },
} as any
<SchemaSettingsProvider visible={visible} setVisible={setVisible} dn={dn} {...others}>
<Component />
<Dropdown
open={visible}
onOpenChange={changeMenu}
overlayClassName={css`
.ant-dropdown-menu-item-group-list {
max-height: 300px;
overflow-y: auto;
}
>
<div data-testid={props['data-testid']}>{typeof title === 'string' ? <span>{title}</span> : title}</div>
</Dropdown>
</SchemaSettingsProvider>
</SchemaComponentContext.Provider>
`}
menu={
{
items,
'data-testid': 'schema-settings-menu',
style: { maxHeight: dropdownMaxHeight, overflowY: 'auto' },
} as any
}
>
<div data-testid={props['data-testid']}>{typeof title === 'string' ? <span>{title}</span> : title}</div>
</Dropdown>
</SchemaSettingsProvider>
);
});
@ -300,7 +290,6 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
const sdn = createDesignable({
t,
api,
refresh: dn.refresh.bind(dn),
current: templateSchema.parent,
});
sdn.loadAPIClientEvents();
@ -371,7 +360,6 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
const sdn = createDesignable({
t,
api,
refresh: dn.refresh.bind(dn),
current: gridSchema.parent,
});
sdn.loadAPIClientEvents();
@ -400,6 +388,7 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
'x-template-key': key,
},
});
dn.refresh();
}}
>
{t('Save as block template')}
@ -537,6 +526,7 @@ export const SchemaSettingsRemove: FC<SchemaSettingsRemoveProps> = (props) => {
}
await confirm?.onOk?.();
delete form.values[fieldSchema.name];
dn.refresh({ refreshParentSchema: true });
removeActiveFieldName?.(fieldSchema.name as string);
form?.query(new RegExp(`${fieldSchema.parent.name}.${fieldSchema.name}$`)).forEach((field: Field) => {
// 如果字段被删掉,那么在提交的时候不应该提交这个字段

View File

@ -9,7 +9,7 @@
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
import { Input, Spin } from 'antd';
import React, { useContext, useState } from 'react';
import React, { useContext, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useAPIClient, useRequest, useSchemaTemplateManager } from '..';
import { useNavigateNoUpdate } from '../application/CustomRouterContextProvider';
@ -73,6 +73,8 @@ export const BlockTemplateDetails = () => {
const params = useParams<any>();
const key = params?.key;
const value = useContext(SchemaComponentContext);
const schemaComponentContext = useMemo(() => ({ ...value, designable: true }), [value]);
const { data, loading } = useRequest<{
data: any;
}>({
@ -82,9 +84,11 @@ export const BlockTemplateDetails = () => {
filterByTk: key,
},
});
if (loading) {
return <Spin />;
}
return (
<div>
<AntdPageHeader
@ -96,7 +100,7 @@ export const BlockTemplateDetails = () => {
title={<EditableTitle filterByTk={key} title={data?.data?.name} />}
/>
<div style={{ margin: 'var(--nb-spacing)' }}>
<SchemaComponentContext.Provider value={{ ...value, designable: true }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<RemoteSchemaComponent uid={data?.data?.uid} />
</SchemaComponentContext.Provider>
</div>

View File

@ -7,23 +7,22 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useState } from 'react';
import { Card, Row, Col, Tabs, Divider } from 'antd';
import { ISchema, Schema } from '@formily/react';
import {
CollectionProvider,
CollectionProvider_deprecated,
ResourceActionProvider,
SchemaComponentContext,
usePlugin,
useSchemaComponentContext,
} from '@nocobase/client';
import { ISchema, Schema } from '@formily/react';
import { Card, Col, Divider, Row, Tabs } from 'antd';
import React, { useMemo, useState } from 'react';
import ACLPlugin from '.';
import { NewRole } from './NewRole';
import { RolesManagerContext } from './RolesManagerProvider';
import { RolesMenu } from './RolesMenu';
import { useACLTranslation } from './locale';
import ACLPlugin from '.';
import { RolesManagerContext } from './RolesManagerProvider';
import { Permissions } from './permissions/Permissions';
import { NewRole } from './NewRole';
const collection = {
name: 'roles',
@ -75,9 +74,10 @@ export const RolesManagement: React.FC = () => {
}));
const [role, setRole] = useState(null);
const scCtx = useSchemaComponentContext();
const schemaComponentContext = useMemo(() => ({ ...scCtx, designable: false }), [scCtx]);
return (
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<RolesManagerContext.Provider value={{ role, setRole }}>
<Card>
<Row gutter={24} style={{ flexWrap: 'nowrap' }}>

View File

@ -8,11 +8,12 @@
*/
import { css, cx } from '@emotion/css';
import { RecursionField, observer, useField, useFieldSchema } from '@formily/react';
import { observer, useField, useFieldSchema } from '@formily/react';
import {
ActionContextProvider,
CollectionProvider_deprecated,
FormBlockContext,
NocoBaseRecursionField,
PopupSettingsProvider,
RecordProvider,
TabsContextProvider,
@ -208,7 +209,7 @@ export const DuplicateAction = observer(
<RecordProvider record={{ ...parentRecordData, __collection: duplicateCollection || __collection }}>
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
<PopupSettingsProvider enableURL={false}>
<RecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
<NocoBaseRecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
</PopupSettingsProvider>
</ActionContextProvider>
</RecordProvider>

View File

@ -124,7 +124,7 @@ test.describe('direct duplicate & copy into the form and continue to fill in', (
.getByTestId('drawer-Action.Container-general2-Duplicate')
.getByLabel('schema-initializer-Grid-popup')
.hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Form right' }).hover();
await page.getByRole('menuitem', { name: 'Current collection' }).click();
await page
.getByTestId('drawer-Action.Container-general2-Duplicate')

View File

@ -7,20 +7,21 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, observer, useFieldSchema } from '@formily/react';
import { css } from '@emotion/css';
import { observer, useFieldSchema } from '@formily/react';
import {
CollectionContext,
DataSourceContext,
DndContext,
Icon,
NocoBaseRecursionField,
useBlockHeight,
useDesignable,
useSchemaInitializerRender,
withDynamicSchemaProps,
Icon,
useBlockHeight,
} from '@nocobase/client';
import { css } from '@emotion/css';
import { Space, List, Avatar, theme } from 'antd';
import React, { createContext, useState, useEffect } from 'react';
import { Avatar, List, Space, theme } from 'antd';
import React, { createContext, useEffect, useState } from 'react';
import { WorkbenchLayout } from './workbenchBlockSettings';
const ConfigureActionsButton = observer(
@ -72,7 +73,7 @@ const InternalIcons = () => {
{layout === WorkbenchLayout.Grid ? (
<Space wrap size={gap}>
{fieldSchema.mapProperties((s, key) => (
<RecursionField name={key} schema={s} key={key} />
<NocoBaseRecursionField name={key} schema={s} key={key} />
))}
</Space>
) : (
@ -103,7 +104,7 @@ const InternalIcons = () => {
>
<List.Item.Meta
avatar={<Avatar style={{ backgroundColor }} icon={<Icon type={icon} />} />}
title={<RecursionField name={key} schema={s} key={key} />}
title={<NocoBaseRecursionField name={key} schema={s} key={key} />}
></List.Item.Meta>
</List.Item>
);

View File

@ -8,44 +8,44 @@
*/
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { RecursionField, Schema, observer, useFieldSchema, useField } from '@formily/react';
import { Schema, useField, useFieldSchema } from '@formily/react';
import {
ActionContextProvider,
CollectionProvider,
NocoBaseRecursionField,
PopupContextProvider,
RecordProvider,
SchemaComponentOptions,
getLabelFormatValue,
handleDateChangeOnForm,
useACLRoleContext,
useActionContext,
useCollection,
useCollectionParentRecordData,
useDesignable,
useFormBlockContext,
useLazy,
usePopupUtils,
useProps,
useToken,
withDynamicSchemaProps,
useDesignable,
ActionContextProvider,
useActionContext,
CollectionProvider,
SchemaComponentOptions,
useFormBlockContext,
handleDateChangeOnForm,
withSkeletonComponent,
} from '@nocobase/client';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { get, cloneDeep } from 'lodash';
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import { Calendar as BigCalendar, View, dayjsLocalizer } from 'react-big-calendar';
import * as dates from 'react-big-calendar/lib/utils/dates';
import { cloneDeep, get } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { View } from 'react-big-calendar';
import { i18nt, useTranslation } from '../../locale';
import { CalendarRecordViewer, findEventSchema } from './CalendarRecordViewer';
import Header from './components/Header';
import { CalendarToolbarContext } from './context';
import GlobalStyle from './global.style';
import { useCalenderHeight } from './hook';
import { addNew } from './schema';
import useStyle from './style';
import type { ToolbarProps } from './types';
import { formatDate } from './utils';
import { addNew } from './schema';
interface Event {
id: string;
colorFieldValue: string;
@ -93,7 +93,7 @@ function Toolbar(props: ToolbarProps) {
);
return (
<CalendarToolbarContext.Provider value={props}>
<RecursionField name={toolBarSchema.name} schema={toolBarSchema} />
<NocoBaseRecursionField name={toolBarSchema.name} schema={toolBarSchema} />
</CalendarToolbarContext.Provider>
);
}
@ -464,7 +464,7 @@ export const Calendar: any = withDynamicSchemaProps(
<ActionContextProvider value={{ ...ctx, visible: visibleAddNewer, setVisible: setVisibleAddNewer }}>
<CollectionProvider name={collection.name}>
<SchemaComponentOptions scope={{ useCreateFormBlockProps }}>
<RecursionField
<NocoBaseRecursionField
onlyRenderProperties
basePath={field?.address}
schema={fieldSchema}

View File

@ -7,7 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, Schema, useFieldSchema } from '@formily/react';
import { Schema, useFieldSchema } from '@formily/react';
import { NocoBaseRecursionField } from '@nocobase/client';
import React, { FC, useMemo } from 'react';
export const CalendarRecordViewer: FC = (props) => {
@ -18,7 +19,7 @@ export const CalendarRecordViewer: FC = (props) => {
return null;
}
return <RecursionField schema={eventSchema} name={eventSchema.name} />;
return <NocoBaseRecursionField schema={eventSchema} name={eventSchema.name} />;
};
export function findEventSchema(schema: Schema) {

View File

@ -9,11 +9,12 @@
import { FormLayout } from '@formily/antd-v5';
import { Field } from '@formily/core';
import { RecursionField, Schema, SchemaOptionsContext, observer, useField, useForm } from '@formily/react';
import { Schema, SchemaOptionsContext, observer, useField, useForm } from '@formily/react';
import {
APIClientProvider,
FormDialog,
FormProvider,
NocoBaseRecursionField,
SchemaComponent,
SchemaComponentOptions,
css,
@ -49,7 +50,7 @@ export const Options = observer(
setSchema(new Schema(template.configurableProperties || {}));
}
}, [form.values.type]);
return <RecursionField schema={s} />;
return <NocoBaseRecursionField schema={s} />;
},
{ displayName: 'Options' },
);

View File

@ -74,10 +74,26 @@ test.describe('form item & view form', () => {
},
supportedOptions: ['oneToMany', 'manyToOne', 'manyToMany', 'oneToOneBelongsTo', 'oneToOneHasOne'],
expectValue: async () => {
await expect(page.getByText(record.oneToMany.map((item: any) => item.id).join(','))).toBeVisible();
await expect(page.getByText(record.manyToOne.id)).toBeVisible();
await expect(page.getByText(record.manyToMany.map((item: any) => item.id).join(','))).toBeVisible();
await expect(page.getByText(record.oneToOneBelongsTo.id)).toBeVisible();
await expect(
page
.getByLabel('block-item-CollectionField-general-form-general.oneToMany-oneToMany')
.getByText(record.oneToMany.map((item: any) => item.id).join(',')),
).toBeVisible();
await expect(
page
.getByLabel('block-item-CollectionField-general-form-general.manyToOne-manyToOne')
.getByText(record.manyToOne.id),
).toBeVisible();
await expect(
page
.getByLabel('block-item-CollectionField-general-form-general.manyToMany-manyToMany')
.getByText(record.manyToMany.map((item: any) => item.id).join(',')),
).toBeVisible();
await expect(
page
.getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-')
.getByText(record.oneToOneBelongsTo.id),
).toBeVisible();
},
});
});

View File

@ -15,7 +15,7 @@ test.describe('form item & filter form', () => {
// 在页面上创建一个筛选表单,并在表单中添加一个字段
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'form Form right' }).nth(1).click();
await page.getByRole('menuitem', { name: 'Form right' }).nth(1).click();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.getByLabel('schema-initializer-Grid-filterForm:configureFields-users').hover();
await page.getByRole('menuitem', { name: 'Nickname' }).click();

View File

@ -186,8 +186,10 @@ export const ConfigurationTable = () => {
});
});
};
const schemaComponentContext = useMemo(() => ({ ...ctx, designable: false, dataSourceData }), [ctx, dataSourceData]);
return (
<SchemaComponentContext.Provider value={{ ...ctx, designable: false, dataSourceData }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<SchemaComponent
schema={collectionSchema}
components={{

View File

@ -28,7 +28,7 @@ import {
useRecord,
} from '@nocobase/client';
import { getPickerFormat } from '@nocobase/utils/client';
import React, { useContext, useState } from 'react';
import React, { useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CollectionFields } from './CollectionFields';
import { collectionSchema } from './schemas/collections';
@ -212,8 +212,10 @@ export const ConfigurationTable = () => {
};
const ctx = useContext(SchemaComponentContext);
const schemaComponentContext = useMemo(() => ({ ...ctx, designable: false }), [ctx]);
return (
<SchemaComponentContext.Provider value={{ ...ctx, designable: false }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<SchemaComponent
schema={collectionSchema}
components={{

View File

@ -41,12 +41,13 @@ export const useAvailableActions = () => {
return useContext(AvailableActionsContext);
};
const schemaComponentContext = { designable: false };
export const DataSourceTable = () => {
const record = useRecord();
const plugin = usePlugin(PluginDataSourceManagerClient);
return (
<div>
<SchemaComponentContext.Provider value={{ designable: false }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<AvailableActionsProver>
<SchemaComponent
schema={dataSourceSchema(plugin.getExtendedTabs())}

View File

@ -7,7 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, useFieldSchema } from '@formily/react';
import { useFieldSchema } from '@formily/react';
import { NocoBaseRecursionField } from '@nocobase/client';
import { Schema } from '@nocobase/utils';
import React, { FC } from 'react';
@ -19,5 +20,5 @@ export const GanttRecordViewer: FC = (props) => {
return null;
}
return <RecursionField schema={eventSchema} name={eventSchema.name} />;
return <NocoBaseRecursionField schema={eventSchema} name={eventSchema.name} />;
};

View File

@ -8,8 +8,9 @@
*/
import { css, cx } from '@emotion/css';
import { RecursionField, useFieldSchema } from '@formily/react';
import { useFieldSchema } from '@formily/react';
import {
NocoBaseRecursionField,
PopupContextProvider,
RecordProvider,
useAPIClient,
@ -520,8 +521,8 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
<RecordProvider record={record} parent={parentRecordData}>
<GanttRecordViewer />
</RecordProvider>
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
<RecursionField name={'table'} schema={fieldSchema.properties.table} />
<NocoBaseRecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
<NocoBaseRecursionField name={'table'} schema={fieldSchema.properties.table} />
<div className={styles.wrapper} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}>
<TaskGantt
gridProps={gridProps}

View File

@ -18,7 +18,7 @@ test.describe('where map block can be added', () => {
// 1. 在页面中添加地图区块,因为没有配置 Access key 等信息,所以会显示错误提示
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'table Map right' }).hover();
await page.getByRole('menuitem', { name: 'Map right' }).hover();
await page.getByRole('menuitem', { name: 'map', exact: true }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(

View File

@ -7,8 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, useFieldSchema } from '@formily/react';
import { useCollection, useCollectionRecordData, VariablePopupRecordProvider } from '@nocobase/client';
import { useFieldSchema } from '@formily/react';
import {
NocoBaseRecursionField,
useCollection,
useCollectionRecordData,
VariablePopupRecordProvider,
} from '@nocobase/client';
import React, { FC, useMemo } from 'react';
export const MapBlockDrawer: FC = (props) => {
@ -32,7 +37,7 @@ export const MapBlockDrawer: FC = (props) => {
return (
<VariablePopupRecordProvider recordData={recordData} collection={collection}>
<RecursionField schema={schema} name={schema.name} />
<NocoBaseRecursionField schema={schema} name={schema.name} />
</VariablePopupRecordProvider>
);
};

View File

@ -7,8 +7,16 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, useField, useFieldSchema } from '@formily/react';
import { ActionBarProvider, SortableItem, TabsContextProvider, css, cx, useDesigner } from '@nocobase/client';
import { useField, useFieldSchema } from '@formily/react';
import {
ActionBarProvider,
NocoBaseRecursionField,
SortableItem,
TabsContextProvider,
css,
cx,
useDesigner,
} from '@nocobase/client';
import { TabsProps } from 'antd';
import React, { useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
@ -106,34 +114,34 @@ const InternalPage: React.FC = (props) => {
})}
>
{isHeaderEnabled && (
<RecursionField
<NocoBaseRecursionField
schema={fieldSchema}
filterProperties={(s) => {
return 'MHeader' === s['x-component'];
}}
></RecursionField>
></NocoBaseRecursionField>
)}
<TabsContextProvider
PaneRoot={GlobalActionProvider}
activeKey={searchParams.get('tab')}
onChange={onTabsChange}
>
<RecursionField
<NocoBaseRecursionField
schema={isTabsEnabled ? fieldSchema : pageSchema}
filterProperties={(s) => {
return 'Tabs' === s['x-component'] || 'Grid' === s['x-component'] || 'Grid.Row' === s['x-component'];
}}
></RecursionField>
></NocoBaseRecursionField>
</TabsContextProvider>
</div>
<GlobalActionProvider>
{!tabsSchema && (
<RecursionField
<NocoBaseRecursionField
schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] !== 'MHeader';
}}
></RecursionField>
></NocoBaseRecursionField>
)}
</GlobalActionProvider>
</SortableItem>

View File

@ -172,7 +172,7 @@ test.describe('PageHeader', () => {
test('Itemadd and remove', async ({ page }) => {
// 添加页面内容
await page.getByLabel('schema-initializer-Grid-mobile:addBlock').hover();
await page.getByRole('menuitem', { name: 'form Markdown' }).click();
await page.getByRole('menuitem', { name: 'Markdown' }).click();
await expect(page.getByLabel('block-item-Markdown.Void-')).toBeVisible();
await page.getByLabel('action-Action-undefined').click();

View File

@ -7,8 +7,15 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { Action, SchemaComponent, useActionContext, useZIndexContext, zIndexContext } from '@nocobase/client';
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
import {
Action,
NocoBaseRecursionField,
SchemaComponent,
useActionContext,
useZIndexContext,
zIndexContext,
} from '@nocobase/client';
import { ConfigProvider } from 'antd';
import { Popup } from 'antd-mobile';
import { CloseOutline } from 'antd-mobile-icons';
@ -109,7 +116,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
)}
{footerSchema ? (
<div className="nb-mobile-action-drawer-footer" style={isSpecialSchema ? specialStyle : null}>
<RecursionField
<NocoBaseRecursionField
basePath={field.address}
schema={fieldSchema}
onlyRenderProperties

View File

@ -7,9 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, useField, useFieldSchema } from '@formily/react';
import { useField, useFieldSchema } from '@formily/react';
import {
BackButtonUsedInSubPage,
NocoBaseRecursionField,
SchemaComponent,
TabsContextProvider,
useActionContext,
@ -67,7 +68,7 @@ export const MobileActionPage = ({ level, footerNodeName }) => {
</TabsContextProvider>
{footerSchema && (
<div className="nb-mobile-action-page-footer" style={zIndexStyle}>
<RecursionField
<NocoBaseRecursionField
basePath={field.address}
schema={fieldSchema}
onlyRenderProperties

View File

@ -7,11 +7,12 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { observer, useField, useFieldSchema } from '@formily/react';
import {
css,
DndContext,
Icon,
NocoBaseRecursionField,
SchemaComponent,
SortableItem,
Tabs as TabsOfPC,
@ -54,7 +55,9 @@ export const MobileTabsForMobileActionPage: any = observer(
const items = useMemo(() => {
const result = fieldSchema.mapProperties((schema, key) => {
return <Tabs.Tab title={<RecursionField name={key} schema={schema} onlyRenderSelf />} key={key}></Tabs.Tab>;
return (
<Tabs.Tab title={<NocoBaseRecursionField name={key} schema={schema} onlyRenderSelf />} key={key}></Tabs.Tab>
);
});
return result;

View File

@ -7,8 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { RecursionField, useFieldSchema } from '@formily/react';
import { cx, SchemaToolbarProvider } from '@nocobase/client';
import { useFieldSchema } from '@formily/react';
import { cx, NocoBaseRecursionField, SchemaToolbarProvider } from '@nocobase/client';
import { NavBar } from 'antd-mobile';
import React, { FC } from 'react';
@ -31,12 +31,12 @@ export const MobilePageNavigationBar: FC = () => {
back={null}
left={
<SchemaToolbarProvider position="left">
<RecursionField name="actionBarLeft" schema={fieldSchema} onlyRenderProperties />
<NocoBaseRecursionField name="actionBarLeft" schema={fieldSchema} onlyRenderProperties />
</SchemaToolbarProvider>
}
right={
<SchemaToolbarProvider position="right">
<RecursionField name="actionBarRight" schema={fieldSchema} onlyRenderProperties />
<NocoBaseRecursionField name="actionBarRight" schema={fieldSchema} onlyRenderProperties />
</SchemaToolbarProvider>
}
>
@ -44,7 +44,7 @@ export const MobilePageNavigationBar: FC = () => {
</NavBar>
<SchemaToolbarProvider position="bottom">
<RecursionField name="actionBarBottom" schema={fieldSchema} onlyRenderProperties />
<NocoBaseRecursionField name="actionBarBottom" schema={fieldSchema} onlyRenderProperties />
</SchemaToolbarProvider>
</div>
);

View File

@ -8,16 +8,17 @@
*/
import { cx } from '@emotion/css';
import { SpaceProps } from 'antd';
import React, { CSSProperties, useContext } from 'react';
import { ISchema, RecursionField, observer, useFieldSchema } from '@formily/react';
import { ISchema, observer, useFieldSchema } from '@formily/react';
import {
DndContext,
NocoBaseRecursionField,
useProps,
useSchemaInitializerRender,
useSchemaToolbar,
withDynamicSchemaProps,
} from '@nocobase/client';
import { SpaceProps } from 'antd';
import React, { CSSProperties, useContext } from 'react';
export interface ActionBarProps {
style?: CSSProperties;
@ -81,7 +82,7 @@ export const MobileNavigationActionBar = withDynamicSchemaProps(
{position === 'left' && render({})}
{props.children && (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<RecursionField
<NocoBaseRecursionField
onlyRenderProperties
schema={fieldSchema}
filterProperties={(schema) => schema['x-position'] === position}
@ -91,7 +92,7 @@ export const MobileNavigationActionBar = withDynamicSchemaProps(
{position === 'right' && render({})}
</div>
) : (
<RecursionField
<NocoBaseRecursionField
onlyRenderProperties
schema={fieldSchema}
filterProperties={(schema) => schema['x-position'] === position}

View File

@ -18,8 +18,8 @@ import {
useAsyncData,
useSchemaComponentContext,
} from '@nocobase/client';
import { Button, Dropdown, Empty, Card } from 'antd';
import React, { useState } from 'react';
import { Button, Card, Dropdown, Empty } from 'antd';
import React, { useMemo, useState } from 'react';
import channelCollection from '../../../../collections/channel';
import messageLogCollection from '../../../../collections/messageLog';
import { useNotificationTranslation } from '../../../locale';
@ -125,10 +125,11 @@ export const ChannelManager = () => {
const { t } = useNotificationTranslation();
const notificationTypes = useNotificationTypes();
const scCtx = useSchemaComponentContext();
const schemaComponentContext = useMemo(() => ({ ...scCtx, designable: false }), [scCtx]);
return (
<ExtendCollectionsProvider collections={[channelCollection, messageLogCollection]}>
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<NotificationTypesContext.Provider value={{ channelTypes: notificationTypes }}>
<Card bordered={false}>
<SchemaComponent

View File

@ -7,23 +7,29 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React from 'react';
import {
ExtendCollectionsProvider,
SchemaComponent,
SchemaComponentContext,
useSchemaComponentContext,
} from '@nocobase/client';
import { Card } from 'antd';
import { messageLogsManagerSchema } from '../schemas';
import { SchemaComponent, SchemaComponentContext, useSchemaComponentContext } from '@nocobase/client';
import { ExtendCollectionsProvider } from '@nocobase/client';
import { useNotificationTranslation } from '../../../locale';
import messageLogCollection from '../../../../collections/messageLog';
import React, { useMemo } from 'react';
import channelCollection from '../../../../collections/channel';
import messageLogCollection from '../../../../collections/messageLog';
import { useNotificationTranslation } from '../../../locale';
import { useEditFormProps, useNotificationTypes } from '../../channel/hooks';
import { messageLogsManagerSchema } from '../schemas';
export const LogManager = () => {
const { t } = useNotificationTranslation();
const scCtx = useSchemaComponentContext();
const notificationTypes = useNotificationTypes();
const schemaComponentContext = useMemo(() => ({ ...scCtx, designable: false }), [scCtx]);
return (
<ExtendCollectionsProvider collections={[messageLogCollection, channelCollection]}>
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
<SchemaComponentContext.Provider value={schemaComponentContext}>
<Card bordered={false}>
<SchemaComponent
schema={messageLogsManagerSchema}

View File

@ -11,7 +11,7 @@ import { FormLayout } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormProvider, ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
import { Alert, Button, Modal, Space, message } from 'antd';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -39,7 +39,6 @@ import {
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaOptionsContext,
useVariableScope,
} from '@nocobase/client';
import WorkflowPlugin, {
DetailsBlockProvider,
@ -430,6 +429,7 @@ export function SchemaConfig({ value, onChange }) {
const nodes = useAvailableUpstreams(node);
const form = useForm();
const { workflow } = useFlowContext();
const refreshRef = useRef(() => {});
const nodeComponents = {};
nodes.forEach((item) => {
@ -452,6 +452,8 @@ export function SchemaConfig({ value, onChange }) {
background: var(--nb-box-bg);
}
`,
// Using ref to call refresh ensures accessing the latest refresh function
onClose: () => refreshRef.current(),
},
properties: {
tabs: {
@ -501,6 +503,8 @@ export function SchemaConfig({ value, onChange }) {
[form, onChange, schema],
);
refreshRef.current = refresh;
return (
<SchemaComponentContext.Provider
value={{