410 lines
10 KiB
TypeScript
410 lines
10 KiB
TypeScript
import React, {
|
|
Children,
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
} from 'react';
|
|
import {
|
|
connect,
|
|
observer,
|
|
mapProps,
|
|
mapReadPretty,
|
|
useField,
|
|
useFieldSchema,
|
|
RecursionField,
|
|
Schema,
|
|
SchemaOptionsContext,
|
|
FormProvider,
|
|
useForm,
|
|
} from '@formily/react';
|
|
import {
|
|
Menu as AntdMenu,
|
|
MenuProps,
|
|
MenuItemProps,
|
|
SubMenuProps,
|
|
DividerProps,
|
|
Dropdown,
|
|
Modal,
|
|
Button,
|
|
} from 'antd';
|
|
import get from 'lodash/get';
|
|
import { uid } from '@formily/shared';
|
|
import {
|
|
MenuOutlined,
|
|
GroupOutlined,
|
|
PlusOutlined,
|
|
LinkOutlined,
|
|
AppstoreAddOutlined,
|
|
EditOutlined,
|
|
DeleteOutlined,
|
|
ArrowRightOutlined,
|
|
SettingOutlined,
|
|
ArrowUpOutlined,
|
|
ArrowDownOutlined,
|
|
} from '@ant-design/icons';
|
|
import './style.less';
|
|
import { Icon } from '../icon-picker';
|
|
import { useHistory } from 'react-router-dom';
|
|
import cls from 'classnames';
|
|
import ReactDOM from 'react-dom';
|
|
import { useMount } from 'ahooks';
|
|
import { useDesignable, SchemaRenderer } from '..';
|
|
import { Router } from "react-router";
|
|
|
|
import { useLifecycle } from 'beautiful-react-hooks';
|
|
import { useDefaultAction } from '../action';
|
|
|
|
export type MenuType = React.FC<MenuProps & { hideSubMenu?: boolean }> & {
|
|
Item?: React.FC<MenuItemProps>;
|
|
SubMenu?: React.FC<SubMenuProps>;
|
|
Divider?: React.FC<DividerProps>;
|
|
DesignableBar?: React.FC<any>;
|
|
AddNew?: React.FC<any>;
|
|
Link?: React.FC<MenuItemProps>;
|
|
Url?: React.FC<MenuItemProps & { url: string }>;
|
|
};
|
|
|
|
export const MenuContainerContext = createContext({
|
|
sideMenuRef: null,
|
|
});
|
|
|
|
export const MenuContext = createContext({
|
|
mode: null,
|
|
designableBar: null,
|
|
});
|
|
|
|
function Blank() {
|
|
return null;
|
|
}
|
|
|
|
function useDesignableBar() {
|
|
const { designableBar } = useContext(MenuContext);
|
|
|
|
const options = useContext(SchemaOptionsContext);
|
|
const DesignableBar = designableBar
|
|
? get(options.components, designableBar)
|
|
: null;
|
|
|
|
return {
|
|
DesignableBar: DesignableBar || Blank,
|
|
};
|
|
}
|
|
|
|
export const Menu: MenuType = observer((props) => {
|
|
const { onSelect, mode, defaultSelectedKeys, ...others } = props;
|
|
const { sideMenuRef } = useContext(MenuContainerContext);
|
|
const schema = useFieldSchema();
|
|
const { schema: designableSchema, refresh } = useDesignable();
|
|
const designableBar = schema['x-designable-bar'];
|
|
const history = useHistory();
|
|
const renderSideMenu = (selectedKey) => {
|
|
if (!selectedKey) {
|
|
return;
|
|
}
|
|
if ((mode as any) !== 'mix') {
|
|
return;
|
|
}
|
|
if (!sideMenuRef || !sideMenuRef.current) {
|
|
return;
|
|
}
|
|
const properties = schema.properties[selectedKey].properties;
|
|
console.log({ selectedKey, properties });
|
|
sideMenuRef.current.style.display = properties ? 'block' : 'none';
|
|
const newProps = {};
|
|
Object.keys(properties || {}).forEach((name) => {
|
|
newProps[name] = properties[name].toJSON();
|
|
});
|
|
ReactDOM.render(
|
|
properties ? (
|
|
<Router history={history}>
|
|
<SchemaRenderer
|
|
key={`${Math.random()}`}
|
|
onRefresh={(subSchema: Schema) => {
|
|
const selected = designableSchema.properties[selectedKey];
|
|
const diff = subSchema.properties[`${schema.name}.${selectedKey}`];
|
|
Object.keys(selected.properties).forEach((name) => {
|
|
selected.properties[name].parent.removeProperty(name);
|
|
});
|
|
Object.keys(diff.properties).forEach((name) => {
|
|
if (name.endsWith('-add-new')) {
|
|
return;
|
|
}
|
|
console.log('diff', name)
|
|
const current = diff.properties[name];
|
|
selected.addProperty(current.name, current.toJSON());
|
|
});
|
|
console.log({selected })
|
|
refresh();
|
|
}}
|
|
schema={{
|
|
type: 'void',
|
|
name: `${schema.name}.${selectedKey}`,
|
|
'x-component': 'Menu',
|
|
'x-designable-bar': designableBar,
|
|
'x-component-props': {
|
|
mode: 'inline',
|
|
onSelect,
|
|
},
|
|
properties: {
|
|
...newProps,
|
|
[`${uid()}-add-new`]: {
|
|
type: 'void',
|
|
'x-component': 'Menu.AddNew',
|
|
},
|
|
},
|
|
}}
|
|
/></Router>
|
|
) : null,
|
|
sideMenuRef.current,
|
|
);
|
|
};
|
|
useMount(() => {
|
|
const defaultSelectedKey = defaultSelectedKeys
|
|
? defaultSelectedKeys[0]
|
|
: null;
|
|
renderSideMenu(defaultSelectedKey);
|
|
});
|
|
return (
|
|
<MenuContext.Provider
|
|
value={{
|
|
mode,
|
|
designableBar,
|
|
}}
|
|
>
|
|
<AntdMenu
|
|
defaultSelectedKeys={defaultSelectedKeys}
|
|
{...others}
|
|
onSelect={(info) => {
|
|
console.log('info.key', info.key);
|
|
renderSideMenu(info.key);
|
|
onSelect && onSelect(info);
|
|
}}
|
|
mode={(mode as any) === 'mix' ? 'horizontal' : mode}
|
|
/>
|
|
</MenuContext.Provider>
|
|
);
|
|
});
|
|
|
|
const AddNewAction = () => {
|
|
const { insertBefore } = useDesignable();
|
|
return (
|
|
<Dropdown
|
|
overlayStyle={{
|
|
minWidth: 150,
|
|
}}
|
|
trigger={['click']}
|
|
overlay={
|
|
<AntdMenu>
|
|
<AntdMenu.Item onClick={() => {
|
|
insertBefore({
|
|
type: 'void',
|
|
title: uid(),
|
|
"x-component": 'Menu.Item',
|
|
})
|
|
}} style={{ minWidth: 150 }}>
|
|
<MenuOutlined /> 新建菜单
|
|
</AntdMenu.Item>
|
|
<AntdMenu.Item>
|
|
<GroupOutlined /> 新建分组
|
|
</AntdMenu.Item>
|
|
<AntdMenu.Item>
|
|
<LinkOutlined /> 添加链接
|
|
</AntdMenu.Item>
|
|
</AntdMenu>
|
|
}
|
|
>
|
|
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
|
|
</Dropdown>
|
|
);
|
|
};
|
|
|
|
Menu.AddNew = observer((props) => {
|
|
const { designableBar } = useContext(MenuContext);
|
|
return designableBar ? (
|
|
<AntdMenu.ItemGroup
|
|
key={'add-menu-item'}
|
|
className={'menu-add'}
|
|
title={<AddNewAction />}
|
|
></AntdMenu.ItemGroup>
|
|
) : null;
|
|
});
|
|
|
|
Menu.Url = observer((props) => {
|
|
const field = useField();
|
|
const schema = useFieldSchema();
|
|
const { DesignableBar } = useDesignableBar();
|
|
return (
|
|
<AntdMenu.Item
|
|
{...props}
|
|
// @ts-ignore
|
|
eventKey={schema.name}
|
|
key={schema.name}
|
|
onClick={(e) => {
|
|
window.open(props.url);
|
|
}}
|
|
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
|
>
|
|
{field.title}
|
|
<DesignableBar />
|
|
</AntdMenu.Item>
|
|
);
|
|
});
|
|
|
|
Menu.Link = observer((props) => {
|
|
const history = useHistory();
|
|
const field = useField();
|
|
const schema = useFieldSchema();
|
|
const { DesignableBar } = useDesignableBar();
|
|
return (
|
|
<AntdMenu.Item
|
|
{...props}
|
|
// @ts-ignore
|
|
eventKey={schema.name}
|
|
key={schema.name}
|
|
onClick={(e) => {
|
|
history.push(schema.name as string);
|
|
}}
|
|
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
|
>
|
|
{field.title}
|
|
<DesignableBar />
|
|
</AntdMenu.Item>
|
|
);
|
|
});
|
|
|
|
Menu.Item = observer((props: any) => {
|
|
const { useAction = useDefaultAction, ...others } = props;
|
|
const { run } = useAction();
|
|
const field = useField();
|
|
const schema = useFieldSchema();
|
|
const { DesignableBar } = useDesignableBar();
|
|
return (
|
|
<AntdMenu.Item
|
|
{...others}
|
|
onClick={async () => {
|
|
await run();
|
|
}}
|
|
schema={schema}
|
|
// @ts-ignore
|
|
eventKey={schema.name}
|
|
key={schema.name}
|
|
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
|
>
|
|
{field.title}
|
|
<DesignableBar />
|
|
</AntdMenu.Item>
|
|
);
|
|
});
|
|
|
|
Menu.SubMenu = observer((props) => {
|
|
const { DesignableBar } = useDesignableBar();
|
|
const schema = useFieldSchema();
|
|
const { mode } = useContext(MenuContext);
|
|
return mode === 'mix' ? (
|
|
<Menu.Item {...props} />
|
|
) : (
|
|
<AntdMenu.SubMenu
|
|
{...props}
|
|
// @ts-ignore
|
|
schema={schema}
|
|
title={
|
|
<>
|
|
{schema.title} <DesignableBar />
|
|
</>
|
|
}
|
|
eventKey={schema.name}
|
|
key={schema.name}
|
|
icon={
|
|
props.icon ? (
|
|
<RecursionField
|
|
name={schema.name}
|
|
schema={
|
|
new Schema({
|
|
type: 'string',
|
|
'x-read-pretty': true,
|
|
default: props.icon,
|
|
'x-component': 'IconPicker',
|
|
})
|
|
}
|
|
onlyRenderProperties
|
|
/>
|
|
) : undefined
|
|
}
|
|
/>
|
|
);
|
|
});
|
|
|
|
Menu.Divider = observer(AntdMenu.Divider);
|
|
|
|
function useDesigner() {
|
|
const field = useField();
|
|
}
|
|
|
|
Menu.DesignableBar = (props) => {
|
|
const field = useField();
|
|
const fieldSchema = useFieldSchema();
|
|
const [visible, setVisible] = useState(false);
|
|
const { schema, remove, refresh } = useDesignable();
|
|
return (
|
|
<div className={cls('designable-bar', { active: visible })}>
|
|
<div
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}}
|
|
className={'designable-bar-actions'}
|
|
>
|
|
<Dropdown
|
|
overlayStyle={{
|
|
minWidth: 150,
|
|
}}
|
|
// visible={visible}
|
|
// onVisibleChange={(visible) => {
|
|
// setVisible(visible);
|
|
// }}
|
|
trigger={['click']}
|
|
overlay={
|
|
<AntdMenu>
|
|
<AntdMenu.Item
|
|
onClick={() => {
|
|
const title = uid();
|
|
field.title = title;
|
|
field.componentProps['icon'] = 'DeleteOutlined';
|
|
schema['x-component-props'] = schema['x-component-props'] || {};
|
|
schema['x-component-props']['icon'] = 'DeleteOutlined';
|
|
schema.title = title;
|
|
fieldSchema.title = title;
|
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
|
fieldSchema['x-component-props']['icon'] = 'DeleteOutlined';
|
|
refresh();
|
|
}}
|
|
>
|
|
修改标题
|
|
</AntdMenu.Item>
|
|
<AntdMenu.Item
|
|
onClick={() => {
|
|
Modal.confirm({
|
|
title: '删除菜单',
|
|
content: '确认删除此菜单项吗?',
|
|
onOk: () => {
|
|
remove();
|
|
},
|
|
});
|
|
}}
|
|
>
|
|
<DeleteOutlined /> 删除菜单
|
|
</AntdMenu.Item>
|
|
</AntdMenu>
|
|
}
|
|
>
|
|
<MenuOutlined />
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Menu;
|