/**
* 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 { PlusOutlined } from '@ant-design/icons';
import { ArrayTable } from '@formily/antd-v5';
import { useField, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import {
ActionContextProvider,
IField,
RecordProvider,
SchemaComponent,
useActionContext,
useAPIClient,
useCancelAction,
useCollectionManager_deprecated,
useCompile,
useCurrentAppInfo,
useDataSourceManager,
useFieldInterfaceOptions,
useRecord,
useRequest,
useResourceActionContext,
} from '@nocobase/client';
import { Button, Dropdown, MenuProps } from 'antd';
import { cloneDeep } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { ForeignKey } from './components';
const getSchema = (schema: IField, record: any, compile) => {
if (!schema) {
return;
}
const properties = cloneDeep(schema.properties) as any;
if (properties?.foreignKey) {
properties.foreignKey['x-component'] = ForeignKey;
}
if (schema.hasDefaultValue === true) {
properties['defaultValue'] = cloneDeep(schema?.default?.uiSchema);
properties.defaultValue.required = false;
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
properties['defaultValue']['x-decorator'] = 'FormItem';
properties['defaultValue']['x-reactions'] = {
dependencies: [
'uiSchema.x-component-props.gmt',
'uiSchema.x-component-props.showTime',
'uiSchema.x-component-props.dateFormat',
'uiSchema.x-component-props.timeFormat',
],
fulfill: {
state: {
componentProps: {
gmt: '{{$deps[0]}}',
showTime: '{{$deps[1]}}',
dateFormat: '{{$deps[2]}}',
timeFormat: '{{$deps[3]}}',
},
},
},
};
}
const initialValue: any = {
name: `f_${uid()}`,
...cloneDeep(schema.default),
interface: schema.name,
};
if (initialValue.reverseField) {
initialValue.reverseField.name = `f_${uid()}`;
}
// initialValue.uiSchema.title = schema.title;
return {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
getContainer: '{{ getContainer }}',
},
'x-decorator': 'Form',
'x-decorator-props': {
useValues(options) {
return useRequest(
() =>
Promise.resolve({
data: initialValue,
}),
options,
);
},
},
title: `${compile(record.title || record.name)} - ${compile('{{ t("Add field") }}')}`,
properties: {
summary: {
type: 'void',
'x-component': 'FieldSummary',
'x-component-props': {
schemaKey: schema.name,
},
},
// @ts-ignore
...properties,
description: {
type: 'string',
title: '{{t("Description")}}',
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: '{{ t("Cancel") }}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: '{{ t("Submit") }}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateCollectionField }}',
},
},
},
},
},
},
},
};
};
export const useCollectionFieldFormValues = () => {
const form = useForm();
return {
getValues() {
const values = cloneDeep(form.values);
if (values.autoCreateReverseField) {
/* empty */
} else {
delete values.reverseField;
}
delete values.autoCreateReverseField;
return values;
},
};
};
const useCreateCollectionField = () => {
const form = useForm();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const field = useField();
const api = useAPIClient();
const record = useRecord();
const { name: dataSourceKey } = useParams();
const dm = useDataSourceManager();
return {
async run() {
await form.submit();
field.data = field.data || {};
field.data.loading = true;
const values = cloneDeep(form.values);
if (values.autoCreateReverseField) {
/* empty */
} else {
delete values.reverseField;
}
delete values.autoCreateReverseField;
try {
await api.request({
url: `dataSourcesCollections/${dataSourceKey}.${record.name}/fields:create`,
method: 'post',
data: values,
});
ctx.setVisible(false);
dm.getDataSource(dataSourceKey).reload();
await form.reset();
field.data.loading = false;
refresh();
} catch (error) {
field.data.loading = false;
}
},
};
};
export const AddCollectionField = (props) => {
const record = useRecord();
return ;
};
const AddFieldAction = (props) => {
const { scope, getContainer, item: record, children, trigger, align, database } = props;
const { getInterface, getTemplate, collections } = useCollectionManager_deprecated();
const [visible, setVisible] = useState(false);
const [targetScope, setTargetScope] = useState();
const [schema, setSchema] = useState({});
const compile = useCompile();
const { t } = useTranslation();
const {
data: { database: currentDatabase },
} = useCurrentAppInfo() || {
data: { database: {} as any },
};
const isDialect = (dialect: string) => currentDatabase?.dialect === dialect;
const currentCollections = useMemo(() => {
return collections.map((v) => {
return {
label: compile(v.title),
value: v.name,
};
});
}, []);
const fieldOptions = useFieldInterfaceOptions();
const getFieldOptions = useCallback(() => {
const { availableFieldInterfaces } = getTemplate(record.template) || {};
const { exclude, include } = availableFieldInterfaces || {};
const optionArr = [];
fieldOptions.forEach((v) => {
if (v.key === 'relation') {
let children = [];
if (include?.length) {
include.forEach((k) => {
const field = v?.children?.find((h) => [k, k.interface].includes(h.value));
field &&
children.push({
...field,
targetScope: k?.targetScope,
});
});
} else if (exclude?.length) {
children = v?.children?.filter((v) => {
return !exclude.includes(v.value);
});
} else {
children = v?.children;
}
children?.length &&
optionArr.push({
...v,
children,
});
}
});
return optionArr;
}, [getTemplate, record]);
const items = useMemo(() => {
return getFieldOptions()
.map((option) => {
if (option?.children?.length === 0) {
return null;
}
if (record.template === 'view') {
return {
type: 'group' as const,
label: compile(option.label),
title: compile(option.label),
key: option.label,
children: option.children
.filter((child) => ['m2o'].includes(child.name))
.map((child) => {
return {
label: compile(child.title),
title: compile(child.title),
key: child.name,
dataTargetScope: child.targetScope,
};
}),
};
}
return {
type: 'group' as const,
label: compile(option.label),
title: compile(option.label),
key: option.label,
children: option?.children
.filter((child) => !['o2o', 'subTable', 'linkTo'].includes(child.name))
.map((child) => {
return {
label: compile(child.title),
title: compile(child.title),
key: child.name,
dataTargetScope: child.targetScope,
};
}),
};
})
.filter((v) => v?.children?.length);
}, [getFieldOptions]);
const menu = useMemo(() => {
return {
style: {
maxHeight: '60vh',
overflow: 'auto',
},
onClick: (e) => {
//@ts-ignore
const targetScope = e.item.props['data-targetScope'];
targetScope && setTargetScope(targetScope);
const schema = getSchema(getInterface(e.key), record, compile);
if (schema) {
setSchema(schema);
setVisible(true);
}
},
items,
};
}, [getInterface, items, record]);
return (
record.template !== 'sql' && (
{children || (
} type={'primary'}>
{t('Add field')}
)}
)
);
};