tachybase_todo/packages/client/src/schemas/menu/index.tsx
2021-07-08 14:21:18 +08:00

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;