/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import { onFieldInputValueChange } from '@formily/core'; import { ISchema, connect, mapProps, useField, useFieldSchema, useForm } from '@formily/react'; import { ActionDesigner, SchemaSettingOpenModeSchemaItems, SchemaSettings, SchemaSettingsItemType, SchemaSettingsLinkageRules, SchemaSettingsModalItem, useCollectionState, useCollection_deprecated, useDesignable, useRecord, useSchemaToolbar, useSyncFromForm, } from '@nocobase/client'; import { Tree as AntdTree } from 'antd'; import { cloneDeep } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; const Tree = connect( AntdTree, mapProps((props, field: any) => { useEffect(() => { field.value = props.defaultCheckedKeys || []; }, []); const [checkedKeys, setCheckedKeys] = useState(props.defaultCheckedKeys || []); const onCheck = (checkedKeys) => { setCheckedKeys(checkedKeys); field.value = checkedKeys; }; field.onCheck = onCheck; const form = useForm(); return { ...props, checkedKeys, onCheck, treeData: props?.treeData.map((v: any) => { if (form.values.duplicateMode === 'quickDulicate') { const children = v?.children?.map((k) => { return { ...k, disabled: false, }; }); return { ...v, disabled: false, children, }; } return v; }), }; }), ); const getAllkeys = (data, result) => { for (let i = 0; i < data?.length; i++) { const { children, ...rest } = data[i]; result.push(rest.key); if (children) { getAllkeys(children, result); } } return result; }; const findFormBlock = (schema) => { const formSchema = schema.reduceProperties((_, s) => { if (s['x-decorator'] === 'FormBlockProvider') { return s; } else { return findFormBlock(s); } }, null); return formSchema; }; function DuplicationMode() { const { dn } = useDesignable(); const { t } = useTranslation(); const field = useField(); const fieldSchema = useFieldSchema(); const { name } = useCollection_deprecated(); const { collectionList, getEnableFieldTree, getOnLoadData, getOnCheck } = useCollectionState(name); const duplicateValues = cloneDeep(fieldSchema['x-component-props']?.duplicateFields || []); const record = useRecord(); const syncCallBack = useCallback((treeData, selectFields, form) => { form.query('duplicateFields').take((f) => { f.componentProps.treeData = treeData; f.componentProps.defaultCheckedKeys = selectFields; f.setInitialValue(selectFields); f?.onCheck(selectFields); form.setValues({ ...form.values, treeData }); }); }, []); const useSelectAllFields = (form) => { return { async run() { form.query('duplicateFields').take((f) => { const selectFields = getAllkeys(f.componentProps.treeData, []); f.componentProps.defaultCheckedKeys = selectFields; f.setInitialValue(selectFields); f?.onCheck?.(selectFields); }); }, }; }; const useUnSelectAllFields = (form) => { return { async run() { form.query('duplicateFields').take((f) => { f.componentProps.defaultCheckedKeys = []; f.setInitialValue([]); f?.onCheck([]); }); }, }; }; return ( 1 ? 'visible' : 'hidden', 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { options: collectionList, }, 'x-reactions': [ { dependencies: ['.duplicateMode'], fulfill: { state: { disabled: `{{ $deps[0]==="quickDulicate" }}`, value: `{{ $deps[0]==="quickDulicate"? currentCollection:collectionName }}`, }, }, }, ], }, syncFromForm: { type: 'void', title: '{{ t("Sync from form fields") }}', 'x-component': 'Action.Link', 'x-component-props': { type: 'primary', style: { float: 'right', position: 'relative', zIndex: 1200 }, useAction: () => { const formSchema = useMemo(() => findFormBlock(fieldSchema), [fieldSchema]); return useSyncFromForm( formSchema, fieldSchema['x-component-props']?.duplicateCollection || record?.__collection || name, syncCallBack, ); }, }, 'x-reactions': [ { dependencies: ['.duplicateMode'], fulfill: { state: { visible: `{{ $deps[0]!=="quickDulicate" }}`, }, }, }, ], }, selectAll: { type: 'void', title: '{{ t("Select all") }}', 'x-component': 'Action.Link', 'x-reactions': [ { dependencies: ['.duplicateMode'], fulfill: { state: { visible: `{{ $deps[0]==="quickDulicate"}}`, }, }, }, ], 'x-component-props': { type: 'primary', style: { float: 'right', position: 'relative', zIndex: 1200 }, useAction: () => { const from = useForm(); return useSelectAllFields(from); }, }, }, unselectAll: { type: 'void', title: '{{ t("UnSelect all") }}', 'x-component': 'Action.Link', 'x-reactions': [ { dependencies: ['.duplicateMode', '.duplicateFields'], fulfill: { state: { visible: `{{ $deps[0]==="quickDulicate"&&$form.getValuesIn('duplicateFields').length>0 }}`, }, }, }, ], 'x-component-props': { type: 'primary', style: { float: 'right', position: 'relative', zIndex: 1200, marginRight: '10px' }, useAction: () => { const from = useForm(); return useUnSelectAllFields(from); }, }, }, duplicateFields: { type: 'array', title: '{{ t("Data fields") }}', required: true, description: t('Only the selected fields will be used as the initialization data for the form'), 'x-decorator': 'FormItem', 'x-component': Tree, 'x-component-props': { defaultCheckedKeys: duplicateValues, treeData: [], checkable: true, checkStrictly: true, selectable: false, loadData: '{{ getOnLoadData($self) }}', onCheck: '{{ getOnCheck($self) }}', rootStyle: { padding: '8px 0', border: '1px solid #d9d9d9', borderRadius: '2px', maxHeight: '30vh', overflow: 'auto', margin: '2px 0', }, }, 'x-reactions': [ { dependencies: ['.collection', '.duplicateMode'], fulfill: { state: { disabled: '{{ !$deps[0] }}', componentProps: { treeData: '{{ getEnableFieldTree($deps[0], $self,treeData) }}', }, }, }, }, ], }, }, } as ISchema } onSubmit={({ duplicateMode, collection, duplicateFields, treeData }) => { const fields = Array.isArray(duplicateFields) ? duplicateFields : duplicateFields.checked || []; field.componentProps.duplicateMode = duplicateMode; field.componentProps.duplicateFields = fields; fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; fieldSchema['x-component-props'].duplicateMode = duplicateMode; fieldSchema['x-component-props'].duplicateFields = fields; fieldSchema['x-component-props'].duplicateCollection = collection; fieldSchema['x-component-props'].treeData = treeData || field.componentProps?.treeData; dn.emit('patch', { schema: { ['x-uid']: fieldSchema['x-uid'], 'x-component-props': { ...fieldSchema['x-component-props'], }, }, }); dn.refresh(); }} /> ); } const schemaSettingsItems: SchemaSettingsItemType[] = [ { name: 'Customize', Component: (props): any => { return props.children; }, children: [ { name: 'editButton', Component: ActionDesigner.ButtonEditor, useComponentProps() { const { buttonEditorProps } = useSchemaToolbar(); return buttonEditorProps; }, }, { name: 'linkageRules', Component: SchemaSettingsLinkageRules, useComponentProps() { const { name } = useCollection_deprecated(); const { linkageRulesProps } = useSchemaToolbar(); return { ...linkageRulesProps, collectionName: name, }; }, }, { name: 'duplicationMode', Component: DuplicationMode, useVisible() { const fieldSchema = useFieldSchema(); const isDuplicateAction = fieldSchema['x-action'] === 'duplicate'; return isDuplicateAction; }, }, { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, useComponentProps() { const { t } = useTranslation(); const modeOptions = useMemo(() => { return [ { label: t('Drawer'), value: 'drawer' }, { label: t('Dialog'), value: 'modal' }, ]; }, [t]); return { openMode: true, openSize: true, modeOptions, }; }, }, { name: 'remove', sort: 100, Component: ActionDesigner.RemoveButton as any, useComponentProps() { const { removeButtonProps } = useSchemaToolbar(); return removeButtonProps; }, }, ], }, ]; /** * @deprecated * 用于兼容之前的 name */ const deprecatedDuplicateActionSettings = new SchemaSettings({ name: 'ActionSettings:duplicate', items: schemaSettingsItems, }); const duplicateActionSettings = new SchemaSettings({ name: 'actionSettings:duplicate', items: schemaSettingsItems, }); export { deprecatedDuplicateActionSettings, duplicateActionSettings };