mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
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:
parent
51cb1d0a71
commit
0fa56d7407
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 = {}>(
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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 });
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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')),
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { layout = 'two-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
|
||||
const InternalActionBar: FC = (props: any) => {
|
||||
const { forceProps = {} } = useActionBarContext();
|
||||
// 新版 UISchema(1.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' },
|
||||
);
|
||||
|
@ -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'}`}>
|
||||
|
@ -155,7 +155,7 @@ const RenderRecord = React.memo(
|
||||
ellipsisWithTooltipRef,
|
||||
enableLink,
|
||||
fieldNames?.label,
|
||||
fieldSchema?.properties,
|
||||
fieldSchema,
|
||||
getLabelUiSchema,
|
||||
insertViewer,
|
||||
isTreeCollection,
|
||||
|
@ -37,7 +37,7 @@ export const useInsertSchema = (component) => {
|
||||
insertAfterBegin(cloneDeep(ss));
|
||||
}
|
||||
},
|
||||
[component],
|
||||
[component, fieldSchema, insertAfterBegin],
|
||||
);
|
||||
return insert;
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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> */}
|
||||
|
@ -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('-')
|
||||
? {
|
||||
|
@ -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>
|
||||
);
|
||||
}}
|
||||
|
@ -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(),
|
||||
|
@ -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}
|
||||
|
@ -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]);
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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],
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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]);
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -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';
|
||||
|
@ -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) => {
|
||||
// 如果字段被删掉,那么在提交的时候不应该提交这个字段
|
||||
|
@ -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>
|
||||
|
@ -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' }}>
|
||||
|
@ -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>
|
||||
|
@ -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')
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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) {
|
||||
|
@ -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' },
|
||||
);
|
||||
|
@ -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();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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={{
|
||||
|
@ -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={{
|
||||
|
@ -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())}
|
||||
|
@ -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} />;
|
||||
};
|
||||
|
@ -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}
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -172,7 +172,7 @@ test.describe('PageHeader', () => {
|
||||
test('Item:add 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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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={{
|
||||
|
Loading…
x
Reference in New Issue
Block a user