feat: improve code (#978)
* feat: 图形化管理数据表 * feat: 图形化管理数据表 * feat: 图形化管理数据表 * feat: 图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat(collection-manager): add foreignKey Field and support relate field record foreignKey info through collection record into collections and foreignKey field record info fields * fix(collection-manager): if has through collection then don't create through collections record * fix(client/route-switch): skip sub routes * feat: 添加graphpostion * feat: 图形化collection新增表时刷新数据 * fix(collection-manager): refactor afterCreateForRelateField * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化样式优化 * feat: styling * feat: 图形化样式优化 * feat: 图形化样式优化 * feat: 图形化数据表多语言完善 * feat: 图形化数据表多语言完善 * feat: improve code * feat: 图形化数据表连线样式修改 * feat: 图形化数据表样式修改 * feat: 图形化数据表样式修改 * feat: 图形化数据表样式修改 * feat: 图形化数据表样式修改 * fix(collection-manager): fix afterCreateForRelateField * feat: 样式优化 * feat: 样式优化 * feat: afterCreateForForeignKeyField * fix: timestamps: false * feat: 连线锚点优化 * fix(collection-manager): when del foreign key field, relate fields will be del too * fix: update package.json * fix: update package.json * feat: 文件名大小写 * feat: 连线锚点优化 * feat: 连线锚点通过计算得到样式优化 * feat: 连线锚点通过计算得到样式优化 * fix: fk * fix: remove index * feat: 连线hover时高亮 * fix: test error * feat: 初始化计算位置 * feat: 初始化时计算坐标位置 * feat: 初始化时计算坐标位置 * feat: improve code (#933) * fix: built in * feat: 没有关系字段时也要连线 * feat: 自关联也要连线 * fix: styling * feat: 滚动条问题 * feat: 拖拽优化 * feat: 画布paddig优化 * feat: 编辑时支持反向关联字段配置 * feat: 画布拖拽滚动优化 * feat: 画布拖拽滚动优化 * fix: reload * feat: 修复数据表新建重叠 * fix: refreshCM & refreshGM * feat: 修复表达式输入框显示异常 * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * fix: 消除代码警告 * fix: 消除代码警告 * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化 * feat: 渲染性能优化 * feat: 外键生成在位置在前面 * feat: 限制表最多显示10个字段其余滚动 * feat: 移动表位置的连线重新计算最优位置 * fix: error * feat: 布局自动换行 * fix: test error * fix: xpipe.eq * fix: upgrade error * fix: upgrade error * feat: 选中表时只显示和目标表关联的表和连线 * fix: maxListenersExceededWarning * feat: remove graph-collection-manager * fix: remove graph-collection-manager * fix: update yarn.lock Co-authored-by: 唐小爱 <tangxiaoai@192.168.0.103> Co-authored-by: lyf-coder <lyf-coder@foxmail.com> Co-authored-by: katherinehhh <katherine_15995@163.com>
This commit is contained in:
parent
274d80d501
commit
f67afba964
@ -27,7 +27,7 @@ export async function move(ctx: Context, next) {
|
||||
await sortAbleCollection.sticky(sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.body = 'ok';
|
||||
await next();
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,7 @@ export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (pr
|
||||
service,
|
||||
interfaces: { ...defaultInterfaces, ...interfaces },
|
||||
collections,
|
||||
refreshCM: async () => {
|
||||
if (refreshCM) {
|
||||
await refreshCM();
|
||||
}
|
||||
},
|
||||
refreshCM,
|
||||
}}
|
||||
>
|
||||
<CollectionManagerSchemaComponentProvider>{props.children}</CollectionManagerSchemaComponentProvider>
|
||||
@ -47,13 +43,18 @@ export const RemoteCollectionManagerProvider = (props: any) => {
|
||||
}
|
||||
return (
|
||||
<CollectionManagerProvider
|
||||
service={{...service, contentLoading, setContentLoading}}
|
||||
service={{ ...service, contentLoading, setContentLoading }}
|
||||
collections={service?.data?.data}
|
||||
refreshCM={async () => {
|
||||
setContentLoading(true);
|
||||
refreshCM={async (opts) => {
|
||||
if (opts?.reload) {
|
||||
setContentLoading(true);
|
||||
}
|
||||
const { data } = await api.request(options);
|
||||
service.mutate(data);
|
||||
setContentLoading(false);
|
||||
if (opts?.reload) {
|
||||
setContentLoading(false);
|
||||
}
|
||||
return data?.data || [];
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { PluginManager } from '../plugin-manager';
|
||||
import { ActionContext, SchemaComponent } from '../schema-component';
|
||||
import { AddFieldAction, ConfigurationTable, EditFieldAction } from './Configuration';
|
||||
import { AddCollectionField, AddFieldAction, ConfigurationTable, EditFieldAction,EditCollectionField } from './Configuration';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
@ -37,7 +37,7 @@ const schema2: ISchema = {
|
||||
export const CollectionManagerPane = () => {
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<SchemaComponent schema={schema2} components={{ ConfigurationTable, AddFieldAction, EditFieldAction }} />
|
||||
<SchemaComponent schema={schema2} components={{ ConfigurationTable, AddFieldAction, AddCollectionField, EditFieldAction,EditCollectionField }} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
@ -7,9 +7,9 @@ import { cloneDeep } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRequest } from '../../api-client';
|
||||
import { useRecord } from '../../record-provider';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useCreateAction } from '../action-hooks';
|
||||
import { useCancelAction, useCreateAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { IField } from '../interfaces/types';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
@ -44,6 +44,9 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
getContainer: '{{ getContainer }}',
|
||||
},
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
useValues(options) {
|
||||
@ -75,7 +78,7 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||
title: '{{ t("Cancel") }}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ cm.useCancelAction }}',
|
||||
useAction: '{{ useCancelAction }}',
|
||||
},
|
||||
},
|
||||
action2: {
|
||||
@ -94,11 +97,25 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useCollectionFieldFormValues = () => {
|
||||
const form = useForm();
|
||||
return {
|
||||
getValues() {
|
||||
const values = cloneDeep(form.values);
|
||||
if (values.autoCreateReverseField) {
|
||||
} else {
|
||||
delete values.reverseField;
|
||||
}
|
||||
delete values.autoCreateReverseField;
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const useCreateCollectionField = () => {
|
||||
const form = useForm();
|
||||
const { run } = useCreateAction();
|
||||
const { refreshCM } = useCollectionManager();
|
||||
const { title } = useRecord();
|
||||
const ctx = useActionContext();
|
||||
const { refresh } = useResourceActionContext();
|
||||
const { resource } = useResourceContext();
|
||||
@ -120,53 +137,71 @@ const useCreateCollectionField = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const AddFieldAction = () => {
|
||||
export const AddCollectionField = (props) => {
|
||||
const record = useRecord();
|
||||
return <AddFieldAction item={record} {...props} />;
|
||||
};
|
||||
|
||||
export const AddFieldAction = (props) => {
|
||||
const { scope, getContainer, item: record, children } = props;
|
||||
const { getInterface } = useCollectionManager();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [schema, setSchema] = useState({});
|
||||
const compile = useCompile();
|
||||
const { t } = useTranslation();
|
||||
const record = useRecord();
|
||||
return (
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu
|
||||
style={{
|
||||
maxHeight: '60vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
onClick={(info) => {
|
||||
const schema = getSchema(getInterface(info.key), record, compile);
|
||||
setSchema(schema);
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{options.map((option) => {
|
||||
return (
|
||||
option.children.length > 0 && (
|
||||
<Menu.ItemGroup key={option.label} title={compile(option.label)}>
|
||||
{option.children
|
||||
.filter((child) => !['o2o', 'subTable'].includes(child.name))
|
||||
.map((child) => {
|
||||
return <Menu.Item key={child.name}>{compile(child.title)}</Menu.Item>;
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button icon={<PlusOutlined />} type={'primary'}>
|
||||
{t('Add field')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<SchemaComponent
|
||||
schema={schema}
|
||||
components={{ ...components, ArrayTable }}
|
||||
scope={{ createOnly: true, useCreateCollectionField, record, showReverseFieldConfig: true }}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
<RecordProvider record={record}>
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
<Dropdown
|
||||
getPopupContainer={getContainer}
|
||||
overlay={
|
||||
<Menu
|
||||
style={{
|
||||
maxHeight: '60vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
onClick={(info) => {
|
||||
const schema = getSchema(getInterface(info.key), record, compile);
|
||||
setSchema(schema);
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{options.map((option) => {
|
||||
return (
|
||||
option.children.length > 0 && (
|
||||
<Menu.ItemGroup key={option.label} title={compile(option.label)}>
|
||||
{option.children
|
||||
.filter((child) => !['o2o', 'subTable'].includes(child.name))
|
||||
.map((child) => {
|
||||
return <Menu.Item key={child.name}>{compile(child.title)}</Menu.Item>;
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
{children || (
|
||||
<Button icon={<PlusOutlined />} type={'primary'}>
|
||||
{t('Add field')}
|
||||
</Button>
|
||||
)}
|
||||
</Dropdown>
|
||||
<SchemaComponent
|
||||
schema={schema}
|
||||
components={{ ...components, ArrayTable }}
|
||||
scope={{
|
||||
getContainer,
|
||||
useCancelAction,
|
||||
createOnly: true,
|
||||
useCreateCollectionField,
|
||||
record,
|
||||
showReverseFieldConfig: true,
|
||||
...scope,
|
||||
}}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
</RecordProvider>
|
||||
);
|
||||
};
|
||||
|
@ -6,15 +6,15 @@ import set from 'lodash/set';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient, useRequest } from '../../api-client';
|
||||
import { useRecord } from '../../record-provider';
|
||||
import { useRecord, RecordProvider } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useUpdateAction } from '../action-hooks';
|
||||
import { useCancelAction, useUpdateAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { IField } from '../interfaces/types';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import * as components from './components';
|
||||
|
||||
const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
@ -64,7 +64,7 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||
title: '{{ t("Cancel") }}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ cm.useCancelAction }}',
|
||||
useAction: '{{ useCancelAction }}',
|
||||
},
|
||||
},
|
||||
action2: {
|
||||
@ -109,8 +109,13 @@ const useUpdateCollectionField = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const EditFieldAction = (props) => {
|
||||
export const EditCollectionField = (props) => {
|
||||
const record = useRecord();
|
||||
return <EditFieldAction item={record} {...props} />;
|
||||
};
|
||||
|
||||
export const EditFieldAction = (props) => {
|
||||
const { scope, getContainer, item: record,children } = props;
|
||||
const { getInterface } = useCollectionManager();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [schema, setSchema] = useState({});
|
||||
@ -118,42 +123,52 @@ export const EditFieldAction = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const compile = useCompile();
|
||||
const [data, setData] = useState<any>({});
|
||||
|
||||
return (
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
<a
|
||||
onClick={async () => {
|
||||
const { data } = await api.resource('collections.fields', record.collectionName).get({
|
||||
filterByTk: record.name,
|
||||
appends: ['uiSchema', 'reverseField'],
|
||||
});
|
||||
setData(data?.data);
|
||||
const interfaceConf = getInterface(record.interface);
|
||||
const defaultValues: any = cloneDeep(data?.data) || {};
|
||||
if (!defaultValues?.reverseField) {
|
||||
defaultValues.autoCreateReverseField = false;
|
||||
defaultValues.reverseField = interfaceConf.default?.reverseField;
|
||||
set(defaultValues.reverseField, 'name', `f_${uid()}`);
|
||||
set(defaultValues.reverseField, 'uiSchema.title', record.__parent.title);
|
||||
}
|
||||
const schema = getSchema(
|
||||
{
|
||||
...interfaceConf,
|
||||
default: defaultValues,
|
||||
},
|
||||
record,
|
||||
compile,
|
||||
);
|
||||
setSchema(schema);
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{t('Edit')}
|
||||
</a>
|
||||
<SchemaComponent
|
||||
schema={schema}
|
||||
components={{ ...components, ArrayTable }}
|
||||
scope={{ useUpdateCollectionField, showReverseFieldConfig: !data?.reverseField }}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
<RecordProvider record={record}>
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
<a
|
||||
onClick={async () => {
|
||||
const { data } = await api.resource('collections.fields', record.collectionName).get({
|
||||
filterByTk: record.name,
|
||||
appends: ['uiSchema', 'reverseField'],
|
||||
});
|
||||
setData(data?.data);
|
||||
const interfaceConf = getInterface(record.interface);
|
||||
const defaultValues: any = cloneDeep(data?.data) || {};
|
||||
if (!defaultValues?.reverseField) {
|
||||
defaultValues.autoCreateReverseField = false;
|
||||
defaultValues.reverseField = interfaceConf.default?.reverseField;
|
||||
set(defaultValues.reverseField, 'name', `f_${uid()}`);
|
||||
set(defaultValues.reverseField, 'uiSchema.title', record.__parent.title);
|
||||
}
|
||||
const schema = getSchema(
|
||||
{
|
||||
...interfaceConf,
|
||||
default: defaultValues,
|
||||
},
|
||||
record,
|
||||
compile,
|
||||
getContainer,
|
||||
);
|
||||
setSchema(schema);
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{children||t('Edit')}
|
||||
</a>
|
||||
<SchemaComponent
|
||||
schema={schema}
|
||||
components={{ ...components, ArrayTable }}
|
||||
scope={{
|
||||
getContainer,
|
||||
useUpdateCollectionField,
|
||||
useCancelAction,
|
||||
showReverseFieldConfig: !data?.reverseField,
|
||||
...scope,
|
||||
}}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
</RecordProvider>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { registerValidateFormats } from '@formily/core';
|
||||
import { exp } from 'mathjs';
|
||||
|
||||
export * from './AddFieldAction';
|
||||
export * from './ConfigurationTable';
|
||||
export * from './EditFieldAction';
|
||||
export * from './interfaces';
|
||||
export * from './components';
|
||||
|
||||
registerValidateFormats({
|
||||
uid: /^[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
||||
|
@ -113,7 +113,7 @@ export const collectionFieldSchema: ISchema = {
|
||||
create: {
|
||||
type: 'void',
|
||||
title: '{{ t("Add new") }}',
|
||||
'x-component': 'AddFieldAction',
|
||||
'x-component': 'AddCollectionField',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
},
|
||||
@ -181,7 +181,7 @@ export const collectionFieldSchema: ISchema = {
|
||||
update: {
|
||||
type: 'void',
|
||||
title: '{{ t("Edit") }}',
|
||||
'x-component': 'EditFieldAction',
|
||||
'x-component': 'EditCollectionField',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
},
|
||||
|
@ -9,4 +9,6 @@ export * from './context';
|
||||
export * from './hooks';
|
||||
export * from './ResourceActionProvider';
|
||||
export * from './types';
|
||||
export * from './Configuration';
|
||||
|
||||
|
||||
|
@ -66,6 +66,10 @@ export const SettingsCenterDropdown = () => {
|
||||
title: t('Workflow'),
|
||||
path: 'workflow/workflows',
|
||||
},
|
||||
{
|
||||
title: t('Graph Collections'),
|
||||
path: 'graph/collections',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
@ -79,6 +83,7 @@ export const SettingsCenterDropdown = () => {
|
||||
onClick={() => {
|
||||
history.push('/admin/settings/' + item.path);
|
||||
}}
|
||||
key={item.path}
|
||||
>
|
||||
{item.title}
|
||||
</Menu.Item>
|
||||
|
@ -178,10 +178,10 @@ const InternalAdminLayout = (props: any) => {
|
||||
className={css`
|
||||
min-height: calc(100vh - 46px);
|
||||
position: relative;
|
||||
padding-bottom: 70px;
|
||||
// padding-bottom: 70px;
|
||||
> div {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
// z-index: 1;
|
||||
}
|
||||
.ant-layout-footer {
|
||||
position: absolute;
|
||||
@ -189,6 +189,7 @@ const InternalAdminLayout = (props: any) => {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
padding: 10px 50px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
|
@ -155,6 +155,8 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
||||
constructor(options: DatabaseOptions) {
|
||||
super();
|
||||
|
||||
// this.setMaxListeners(100);
|
||||
|
||||
this.version = new DatabaseVersion(this);
|
||||
|
||||
const opts = {
|
||||
|
@ -17,6 +17,10 @@ export class BelongsToManyField extends RelationField {
|
||||
);
|
||||
}
|
||||
|
||||
get otherKey() {
|
||||
return this.options.otherKey;
|
||||
}
|
||||
|
||||
bind() {
|
||||
const { database, collection } = this.context;
|
||||
const Target = this.TargetModel;
|
||||
@ -34,6 +38,7 @@ export class BelongsToManyField extends RelationField {
|
||||
} else {
|
||||
Through = database.collection({
|
||||
name: through,
|
||||
// timestamps: false,
|
||||
});
|
||||
|
||||
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
||||
@ -80,6 +85,7 @@ export class BelongsToManyField extends RelationField {
|
||||
|
||||
unbind() {
|
||||
const { database, collection } = this.context;
|
||||
const Through = database.getCollection(this.through);
|
||||
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
||||
database.removePendingField(this);
|
||||
// 删掉 model 的关联字段
|
||||
|
@ -5,12 +5,11 @@ import {
|
||||
ModelIndexesOptions,
|
||||
QueryInterfaceOptions,
|
||||
SyncOptions,
|
||||
Transactionable,
|
||||
Transactionable
|
||||
} from 'sequelize';
|
||||
import { Collection } from '../collection';
|
||||
import { Database } from '../database';
|
||||
import { ModelEventTypes } from '../types';
|
||||
import { checkIdentifier } from '../utils';
|
||||
|
||||
export interface FieldContext {
|
||||
database: Database;
|
||||
@ -84,6 +83,7 @@ export abstract class Field {
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.collection.removeIndex([this.name]);
|
||||
return this.collection.removeField(this.name);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,8 @@
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"semver": "^7.3.7"
|
||||
"semver": "^7.3.7",
|
||||
"xpipe": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/semver": "^7.3.9"
|
||||
|
@ -207,10 +207,15 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
this._events = [];
|
||||
// @ts-ignore
|
||||
this._eventsCount = [];
|
||||
this.removeAllListeners();
|
||||
this.middleware = new Toposort<any>();
|
||||
// this.context = Object.create(context);
|
||||
this.plugins = new Map<string, Plugin>();
|
||||
this._acl = createACL();
|
||||
if (this._db) {
|
||||
// MaxListenersExceededWarning
|
||||
this._db.removeAllListeners();
|
||||
}
|
||||
this._db = this.createDatabase(options);
|
||||
this._resourcer = createResourcer(options);
|
||||
this._cli = new Command('nocobase').usage('[command] [options]');
|
||||
@ -220,9 +225,14 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
this.context.resourcer = this._resourcer;
|
||||
this.context.cache = this._cache;
|
||||
|
||||
this._pm = new PluginManager({
|
||||
app: this,
|
||||
});
|
||||
if (this._pm) {
|
||||
this._pm = this._pm.clone();
|
||||
} else {
|
||||
this._pm = new PluginManager({
|
||||
app: this,
|
||||
plugins: options.plugins,
|
||||
});
|
||||
}
|
||||
|
||||
this._appManager = new AppManager(this);
|
||||
|
||||
@ -236,8 +246,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
registerActions(this);
|
||||
}
|
||||
|
||||
this.loadPluginConfig(options.plugins || []);
|
||||
|
||||
registerCli(this);
|
||||
|
||||
this._version = new ApplicationVersion(this);
|
||||
@ -264,16 +272,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
return this.pm.addStatic(pluginClass, options);
|
||||
}
|
||||
|
||||
loadPluginConfig(pluginsConfigurations: PluginConfiguration[]) {
|
||||
for (let pluginConfiguration of pluginsConfigurations) {
|
||||
if (typeof pluginConfiguration == 'string') {
|
||||
this.plugin(pluginConfiguration);
|
||||
} else {
|
||||
this.plugin(...pluginConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
use<NewStateT = {}, NewContextT = {}>(
|
||||
middleware: Koa.Middleware<StateT & NewStateT, ContextT & NewContextT>,
|
||||
|
@ -4,6 +4,7 @@ import execa from 'execa';
|
||||
import fs from 'fs';
|
||||
import net from 'net';
|
||||
import { resolve } from 'path';
|
||||
import xpipe from 'xpipe';
|
||||
import Application from '../application';
|
||||
import { Plugin } from '../plugin';
|
||||
import collectionOptions from './options/collection';
|
||||
@ -12,6 +13,7 @@ import { PluginManagerRepository } from './PluginManagerRepository';
|
||||
|
||||
export interface PluginManagerOptions {
|
||||
app: Application;
|
||||
plugins?: any[];
|
||||
}
|
||||
|
||||
export interface InstallOptions {
|
||||
@ -27,11 +29,12 @@ export class PluginManager {
|
||||
plugins = new Map<string, Plugin>();
|
||||
server: net.Server;
|
||||
pmSock: string;
|
||||
_tmpPluginArgs = [];
|
||||
|
||||
constructor(options: PluginManagerOptions) {
|
||||
this.app = options.app;
|
||||
const f = resolve(process.cwd(), 'storage', 'pm.sock');
|
||||
this.pmSock = this.app.options.pmSock || f;
|
||||
this.pmSock = xpipe.eq(this.app.options.pmSock || f);
|
||||
this.app.db.registerRepositories({
|
||||
PluginManagerRepository,
|
||||
});
|
||||
@ -71,6 +74,17 @@ export class PluginManager {
|
||||
await this.repository.load();
|
||||
}
|
||||
});
|
||||
this.addStaticMultiple(options.plugins);
|
||||
}
|
||||
|
||||
addStaticMultiple(plugins: any) {
|
||||
for (let plugin of plugins || []) {
|
||||
if (typeof plugin == 'string') {
|
||||
this.addStatic(plugin);
|
||||
} else {
|
||||
this.addStatic(...plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPlugins() {
|
||||
@ -123,7 +137,20 @@ export class PluginManager {
|
||||
await run('yarn', ['install']);
|
||||
}
|
||||
|
||||
clone() {
|
||||
const pm = new PluginManager({
|
||||
app: this.app,
|
||||
});
|
||||
for (const arg of this._tmpPluginArgs) {
|
||||
pm.addStatic(...arg);
|
||||
}
|
||||
return pm;
|
||||
}
|
||||
|
||||
addStatic(plugin?: any, options?: any) {
|
||||
if (!options?.async) {
|
||||
this._tmpPluginArgs.push([plugin, options]);
|
||||
}
|
||||
let name: string;
|
||||
if (typeof plugin === 'string') {
|
||||
name = plugin;
|
||||
@ -154,7 +181,10 @@ export class PluginManager {
|
||||
// console.log(`adding ${plugin} plugin`);
|
||||
const packageName = await PluginManager.findPackage(plugin);
|
||||
const packageJson = require(`${packageName}/package.json`);
|
||||
const instance = this.addStatic(plugin, options);
|
||||
const instance = this.addStatic(plugin, {
|
||||
...options,
|
||||
async: true,
|
||||
});
|
||||
let model = await this.repository.findOne({
|
||||
filter: { name: plugin },
|
||||
});
|
||||
|
@ -56,11 +56,11 @@ interface Resource {
|
||||
|
||||
export class MockServer extends Application {
|
||||
async loadAndInstall(options: any = {}) {
|
||||
await this.load();
|
||||
await this.load({ method: 'install' });
|
||||
await this.install({
|
||||
...options,
|
||||
sync: {
|
||||
force: true,
|
||||
force: false,
|
||||
alter: {
|
||||
drop: false,
|
||||
},
|
||||
|
@ -507,7 +507,7 @@ describe('acl', () => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
|
||||
it('should sync data to acl before app start', async () => {
|
||||
it('should sync data to acl after app reload', async () => {
|
||||
const role = await db.getRepository('roles').create({
|
||||
values: {
|
||||
name: 'new',
|
||||
@ -527,14 +527,14 @@ describe('acl', () => {
|
||||
hooks: false,
|
||||
});
|
||||
|
||||
expect(acl.getRole('new')).toBeUndefined();
|
||||
expect(app.acl.getRole('new')).toBeUndefined();
|
||||
|
||||
await app.start();
|
||||
await app.reload();
|
||||
|
||||
expect(acl.getRole('new')).toBeDefined();
|
||||
expect(app.acl.getRole('new')).toBeDefined();
|
||||
|
||||
expect(
|
||||
acl.can({
|
||||
app.acl.can({
|
||||
role: 'new',
|
||||
resource: 'posts',
|
||||
action: 'view',
|
||||
|
@ -8,12 +8,11 @@ export async function prepareApp() {
|
||||
plugins: ['error-handler', 'users', 'ui-schema-storage', 'collection-manager'],
|
||||
});
|
||||
|
||||
await app.cleanDb();
|
||||
|
||||
app.plugin(PluginACL, {
|
||||
name: 'acl',
|
||||
});
|
||||
await app.loadAndInstall();
|
||||
|
||||
await app.loadAndInstall({ clean: true });
|
||||
|
||||
await app.db.sync();
|
||||
|
||||
|
@ -269,7 +269,7 @@ export class PluginACL extends Plugin {
|
||||
|
||||
// sync database role data to acl
|
||||
this.app.on('afterLoad', async (app, options) => {
|
||||
if (options.method === 'install') {
|
||||
if (options?.method === 'install') {
|
||||
return;
|
||||
}
|
||||
const exists = await this.app.db.collectionExistsInDb('roles');
|
||||
@ -278,6 +278,13 @@ export class PluginACL extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
this.app.on('afterInstall', async (app, options) => {
|
||||
const exists = await this.app.db.collectionExistsInDb('roles');
|
||||
if (exists) {
|
||||
await this.writeRolesToACL();
|
||||
}
|
||||
});
|
||||
|
||||
this.app.on('beforeInstallPlugin', async (plugin) => {
|
||||
if (plugin.constructor.name !== 'UsersPlugin') {
|
||||
return;
|
||||
|
@ -48,7 +48,7 @@ describe('field indexes', () => {
|
||||
|
||||
console.log(response3.body);
|
||||
|
||||
expect(response3.status).toBe(500);
|
||||
expect(response3.status).toBe(400);
|
||||
|
||||
const response4 = await agent.resource(tableName).create({
|
||||
values: { title: 't1' },
|
||||
@ -84,7 +84,7 @@ describe('field indexes', () => {
|
||||
title: 't1',
|
||||
},
|
||||
});
|
||||
expect(response2.status).toBe(500);
|
||||
expect(response2.status).toBe(400);
|
||||
|
||||
// update field to remove unique constraint
|
||||
await agent.resource('fields').update({
|
||||
@ -111,7 +111,7 @@ describe('field indexes', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(response4.status).toBe(500);
|
||||
expect(response4.status).toBe(400);
|
||||
|
||||
// remove a duplicated record
|
||||
await agent.resource(tableName).destroy({
|
||||
|
@ -1,7 +1,6 @@
|
||||
import PluginErrorHandler from '@nocobase/plugin-error-handler';
|
||||
import PluginUiSchema from '@nocobase/plugin-ui-schema-storage';
|
||||
import { mockServer } from '@nocobase/test';
|
||||
import lodash from 'lodash';
|
||||
import Plugin from '../';
|
||||
|
||||
export async function createApp(options = {}) {
|
||||
@ -9,10 +8,6 @@ export async function createApp(options = {}) {
|
||||
acl: false,
|
||||
});
|
||||
|
||||
if (lodash.get(options, 'cleanDB', true)) {
|
||||
await app.cleanDb();
|
||||
}
|
||||
|
||||
app.plugin(PluginErrorHandler, { name: 'error-handler' });
|
||||
app.plugin(Plugin, { name: 'collection-manager' });
|
||||
app.plugin(PluginUiSchema, { name: 'ui-schema-storage' });
|
||||
|
@ -0,0 +1,140 @@
|
||||
import Database from '@nocobase/database';
|
||||
|
||||
export function afterCreateForForeignKeyField(db: Database) {
|
||||
function generateFkOptions(collectionName: string, foreignKey: string) {
|
||||
const M = db.getModel(collectionName);
|
||||
const attr = M.rawAttributes[foreignKey];
|
||||
if (!attr) {
|
||||
throw new Error(`${collectionName}.${foreignKey} does not exists`);
|
||||
}
|
||||
return attribute2field(attr);
|
||||
}
|
||||
|
||||
// Foreign key types are only integer and string
|
||||
function attribute2field(attribute: any) {
|
||||
const type = attribute.type.constructor.name === 'INTEGER' ? 'integer' : 'string';
|
||||
const name = attribute.fieldName;
|
||||
const data = {
|
||||
interface: 'integer',
|
||||
name,
|
||||
type,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: name,
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
};
|
||||
if (type === 'string') {
|
||||
data['interface'] = 'input';
|
||||
data['uiSchema'] = {
|
||||
type: 'string',
|
||||
title: name,
|
||||
'x-component': 'Input',
|
||||
'x-read-pretty': true,
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function createFieldIfNotExists({ values, transaction }) {
|
||||
const { collectionName, name } = values;
|
||||
if (!collectionName || !name) {
|
||||
throw new Error(`field options invalid`);
|
||||
}
|
||||
const r = db.getRepository('fields');
|
||||
const instance = await r.findOne({
|
||||
filter: {
|
||||
collectionName,
|
||||
name,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
if (instance) {
|
||||
if (instance.type !== values.type) {
|
||||
throw new Error(`fk type invalid`);
|
||||
}
|
||||
instance.set('sort',1);
|
||||
instance.set('isForeignKey', true);
|
||||
await instance.save({ transaction });
|
||||
} else {
|
||||
await r.create({
|
||||
values: {
|
||||
isForeignKey: true,
|
||||
sort:1,
|
||||
...values,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return async (model, { transaction, context }) => {
|
||||
// skip if no app context
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
const { type, interface: interfaceType, collectionName, target, through, foreignKey, otherKey } = model.get();
|
||||
// foreign key in target collection
|
||||
if (['oho', 'o2m'].includes(interfaceType)) {
|
||||
const values = generateFkOptions(target, foreignKey);
|
||||
await createFieldIfNotExists({
|
||||
values: {
|
||||
collectionName: target,
|
||||
...values,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
// foreign key in source collection
|
||||
else if (['obo', 'm2o'].includes(interfaceType)) {
|
||||
const values = generateFkOptions(collectionName, foreignKey);
|
||||
await createFieldIfNotExists({
|
||||
values: { collectionName, ...values },
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
// foreign key in through collection
|
||||
else if (['linkTo', 'm2m'].includes(interfaceType)) {
|
||||
if (type !== 'belongsToMany') {
|
||||
return;
|
||||
}
|
||||
const r = db.getRepository('collections');
|
||||
const instance = await r.findOne({
|
||||
filter: {
|
||||
name: through,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
if (!instance) {
|
||||
await r.create({
|
||||
values: {
|
||||
name: through,
|
||||
title: through,
|
||||
timestamps: false,
|
||||
autoGenId: false,
|
||||
autoCreate: true,
|
||||
isThrough: true,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
const opts1 = generateFkOptions(through, foreignKey);
|
||||
const opts2 = generateFkOptions(through, otherKey);
|
||||
await createFieldIfNotExists({
|
||||
values: {
|
||||
collectionName: through,
|
||||
...opts1,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
await createFieldIfNotExists({
|
||||
values: {
|
||||
collectionName: through,
|
||||
...opts2,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import Database, { FindOneOptions, FindOptions, Model } from '@nocobase/database';
|
||||
import { Transaction } from 'sequelize';
|
||||
|
||||
async function destroyFields(db: Database, transaction: Transaction, fieldRecords: Model[]) {
|
||||
const fieldsRepo = db.getRepository('fields');
|
||||
for (const fieldRecord of fieldRecords) {
|
||||
await fieldsRepo.destroy({
|
||||
filter: {
|
||||
name: fieldRecord.get('name'),
|
||||
collectionName: fieldRecord.get('collectionName'),
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function afterDestroyForForeignKeyField(db: Database) {
|
||||
return async (model, opts) => {
|
||||
const { transaction } = opts;
|
||||
const options = model.get('options');
|
||||
if (!options?.isForeignKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionRepo = db.getRepository('collections');
|
||||
const foreignKey = model.get('name');
|
||||
const foreignKeyCollectionName = model.get('collectionName');
|
||||
const collectionRecord = await collectionRepo.findOne({
|
||||
filter: {
|
||||
name: foreignKeyCollectionName,
|
||||
},
|
||||
transaction,
|
||||
} as FindOneOptions);
|
||||
const collectionOptions = collectionRecord.get('options');
|
||||
const fieldsRepo = db.getRepository('fields');
|
||||
|
||||
if (collectionOptions?.isThrough) {
|
||||
// through collection
|
||||
const fieldRecords = await fieldsRepo.find({
|
||||
filter: {
|
||||
options: { through: foreignKeyCollectionName, foreignKey: foreignKey },
|
||||
},
|
||||
transaction,
|
||||
} as FindOptions);
|
||||
await destroyFields(db, transaction, fieldRecords);
|
||||
} else {
|
||||
await destroyFields(
|
||||
db,
|
||||
transaction,
|
||||
await fieldsRepo.find({
|
||||
filter: {
|
||||
collectionName: foreignKeyCollectionName,
|
||||
options: { foreignKey: foreignKey },
|
||||
},
|
||||
transaction,
|
||||
} as FindOptions),
|
||||
);
|
||||
await destroyFields(
|
||||
db,
|
||||
transaction,
|
||||
await fieldsRepo.find({
|
||||
filter: {
|
||||
options: { foreignKey: foreignKey, target: foreignKeyCollectionName },
|
||||
},
|
||||
transaction,
|
||||
} as FindOptions),
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import Database from '@nocobase/database';
|
||||
|
||||
export function beforeDestroyForeignKey(db: Database) {
|
||||
return async (model, opts) => {
|
||||
const { transaction } = opts;
|
||||
const { isForeignKey, collectionName: fkCollectionName, name: fkName } = model.get();
|
||||
|
||||
if (!isForeignKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldKeys = [];
|
||||
|
||||
for (const [sourceName, collection] of db.collections) {
|
||||
for (const [, field] of collection.fields) {
|
||||
const fieldKey = field.options?.key;
|
||||
if (!fieldKey) {
|
||||
continue;
|
||||
}
|
||||
// fk in source collection
|
||||
if (field.type === 'belongsTo') {
|
||||
if (sourceName === fkCollectionName && field.foreignKey === fkName) {
|
||||
fieldKeys.push(fieldKey);
|
||||
}
|
||||
}
|
||||
// fk in target collection
|
||||
else if (field.type === 'hasOne' || field.type === 'hasMany') {
|
||||
if (fkCollectionName === field.target && field.foreignKey === fkName) {
|
||||
fieldKeys.push(fieldKey);
|
||||
}
|
||||
}
|
||||
// fk in through collection
|
||||
else if (field.type === 'belongsToMany' && field.through === fkCollectionName) {
|
||||
console.log(field.foreignKey, field.otherKey);
|
||||
if (field.foreignKey === fkName || field.otherKey === fkName) {
|
||||
fieldKeys.push(fieldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const r = db.getRepository('fields');
|
||||
|
||||
await r.destroy({
|
||||
filter: {
|
||||
'key.$in': fieldKeys,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
};
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
export * from './afterCreateForForeignKeyField';
|
||||
export * from './afterCreateForReverseField';
|
||||
export * from './beforeCreateForChildrenCollection';
|
||||
export * from './beforeCreateForReverseField';
|
||||
export * from './beforeDestroyForeignKey';
|
||||
export * from './beforeInitOptions';
|
||||
|
||||
|
@ -8,9 +8,11 @@ import { Plugin } from '@nocobase/server';
|
||||
|
||||
import { CollectionRepository } from '.';
|
||||
import {
|
||||
afterCreateForForeignKeyField,
|
||||
afterCreateForReverseField,
|
||||
beforeCreateForChildrenCollection,
|
||||
beforeCreateForReverseField,
|
||||
beforeDestroyForeignKey,
|
||||
beforeInitOptions
|
||||
} from './hooks';
|
||||
import { CollectionModel, FieldModel } from './models';
|
||||
@ -81,6 +83,8 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
});
|
||||
}
|
||||
});
|
||||
// after migrate
|
||||
this.app.db.on('fields.afterCreate', afterCreateForForeignKeyField(this.app.db));
|
||||
|
||||
this.app.db.on('fields.afterUpdate', async (model: FieldModel, { context, transaction }) => {
|
||||
const prevOptions = model.previous('options');
|
||||
@ -109,6 +113,8 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
// before field remove
|
||||
this.app.db.on('fields.beforeDestroy', beforeDestroyForeignKey(this.app.db));
|
||||
this.app.db.on('fields.beforeDestroy', async (model, options) => {
|
||||
await model.remove(options);
|
||||
});
|
||||
|
@ -27,6 +27,19 @@ export class PresetNocoBase extends Plugin {
|
||||
}
|
||||
|
||||
afterAdd() {
|
||||
this.app.on('beforeLoad', async (app, options) => {
|
||||
if (options?.method !== 'upgrade') {
|
||||
return;
|
||||
}
|
||||
const result = await this.app.version.satisfies('<0.8.0-alpha.1');
|
||||
if (result) {
|
||||
const r = await this.db.collectionExistsInDb('applicationPlugins');
|
||||
if (r) {
|
||||
await this.db.getRepository('applicationPlugins').destroy({ truncate: true });
|
||||
await this.app.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.app.on('beforeUpgrade', async () => {
|
||||
const result = await this.app.version.satisfies('<0.8.0-alpha.1');
|
||||
if (result) {
|
||||
|
22
yarn.lock
22
yarn.lock
@ -18537,7 +18537,7 @@ rc-cascader@~3.2.1:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
array-tree-filter "^2.1.0"
|
||||
classnames "^2.3.1"
|
||||
rc-select "~14.0.2"
|
||||
rc-select "~14.0.0-alpha.23"
|
||||
rc-tree "~5.4.3"
|
||||
rc-util "^5.6.1"
|
||||
|
||||
@ -18772,6 +18772,19 @@ rc-resize-observer@^1.2.0:
|
||||
rc-util "^5.15.0"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
|
||||
rc-select@~14.0.0-alpha.23, rc-select@~14.0.0-alpha.8:
|
||||
version "14.0.6"
|
||||
resolved "https://registry.npmjs.org/rc-select/-/rc-select-14.0.6.tgz#93be0b185a9d66dc84795e079121f0f65310d8bf"
|
||||
integrity sha512-HMb2BwfTvBxMmIWTR/afP4bcRJLbVKFSBW/VFfL5Z+kdV2XlrYdlliK2uHY7pRRvW16PPGwmOwGfV+eoulPINw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "2.x"
|
||||
rc-motion "^2.0.1"
|
||||
rc-overflow "^1.0.0"
|
||||
rc-trigger "^5.0.4"
|
||||
rc-util "^5.16.1"
|
||||
rc-virtual-list "^3.2.0"
|
||||
|
||||
rc-select@~14.0.2:
|
||||
version "14.0.5"
|
||||
resolved "https://registry.npmjs.org/rc-select/-/rc-select-14.0.5.tgz#145c42e7fd66a7fc6c5c56f6b0cf35d8b50f9e23"
|
||||
@ -18874,7 +18887,7 @@ rc-tree-select@~5.1.1:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "2.x"
|
||||
rc-select "~14.0.2"
|
||||
rc-select "~14.0.0-alpha.8"
|
||||
rc-tree "~5.4.3"
|
||||
rc-util "^5.16.1"
|
||||
|
||||
@ -23296,6 +23309,11 @@ xmldom-sre@^0.1.31:
|
||||
resolved "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4"
|
||||
integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==
|
||||
|
||||
xpipe@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf"
|
||||
integrity sha512-tuqoLk8xPl0o+7ny9iPlEZuzjfy1zC5ZJtAGjDDZWmVTVBK5PJP0arMGVu3Y53zSyeYK+YonMVSUv0DJgGN/ig==
|
||||
|
||||
xregexp@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
|
||||
|
Loading…
Reference in New Issue
Block a user