tachybase_todo/packages/client/src/schemas/action/ActionBar.tsx
2021-09-03 15:52:58 +08:00

272 lines
7.9 KiB
TypeScript

import { DndContext, DragOverlay } from '@dnd-kit/core';
import { observer, RecursionField, Schema } from '@formily/react';
import React, { useState } from 'react';
import { createSchema, ISchema, removeSchema, updateSchema } from '..';
import {
findPropertyByPath,
getSchemaPath,
useDesignable,
} from '../../components/schema-renderer';
import { DisplayedMapProvider, useDisplayedMapContext } from '../../constate';
import cls from 'classnames';
import { Button, Dropdown, Menu, Space } from 'antd';
import SwitchMenuItem from '../../components/SwitchMenuItem';
import { uid } from '@formily/shared';
import { PlusOutlined } from '@ant-design/icons';
import { Droppable, SortableItem } from '../../components/Sortable';
export const ActionBar = observer((props: any) => {
const { align = 'top' } = props;
// const { schema, designable } = useDesignable();
const { root, schema, insertAfter, remove, appendChild } = useDesignable();
const moveToAfter = (path1, path2, extra = {}) => {
if (!path1 || !path2) {
return;
}
if (path1.join('.') === path2.join('.')) {
return;
}
const data = findPropertyByPath(root, path1);
if (!data) {
return;
}
remove(path1);
return insertAfter(
{
...data.toJSON(),
...extra,
},
path2,
);
};
const [dragOverlayContent, setDragOverlayContent] = useState('');
return (
<DndContext
onDragStart={(event) => {
setDragOverlayContent(event.active.data?.current?.title || '');
// const previewRef = event.active.data?.current?.previewRef;
// if (previewRef) {
// setDragOverlayContent(previewRef?.current?.innerHTML);
// } else {
// setDragOverlayContent('');
// }
}}
onDragEnd={async (event) => {
const path1 = event.active?.data?.current?.path;
const path2 = event.over?.data?.current?.path;
const align = event.over?.data?.current?.align;
const draggable = event.over?.data?.current?.draggable;
if (!path1 || !path2) {
return;
}
if (path1.join('.') === path2.join('.')) {
return;
}
if (!draggable) {
console.log('alignalignalignalign', align);
const p = findPropertyByPath(root, path1);
if (!p) {
return;
}
remove(path1);
const data = appendChild(
{
...p.toJSON(),
'x-align': align,
},
path2,
);
await updateSchema(data);
} else {
const data = moveToAfter(path1, path2, {
'x-align': align,
});
await updateSchema(data);
}
}}
>
<DragOverlay
dropAnimation={{
duration: 10,
easing: 'cubic-bezier(0.18, 0.67, 0.6, 1.22)',
}}
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
>
{dragOverlayContent}
{/* <div style={{ transform: 'translateX(-100%)' }} dangerouslySetInnerHTML={{__html: dragOverlayContent}}></div> */}
</DragOverlay>
<DisplayedMapProvider>
<div className={cls('nb-action-bar', `align-${align}`)}>
<div style={{ width: '50%' }}>
<Actions align={'left'} />
</div>
<div style={{ marginLeft: 'auto', width: '50%', textAlign: 'right' }}>
<Actions align={'right'} />
</div>
<AddActionButton />
</div>
</DisplayedMapProvider>
</DndContext>
);
});
function generateActionSchema(type) {
const actions: { [key: string]: ISchema } = {
update: {
type: 'void',
title: '编辑',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'update',
},
'x-component': 'Action',
'x-component-props': {
type: 'primary',
},
'x-action-type': 'update',
'x-designable-bar': 'Action.DesignableBar',
properties: {
modal: {
type: 'void',
title: '编辑数据',
'x-decorator': 'Form',
'x-decorator-props': {
useResource: '{{ Table.useResource }}',
useValues: '{{ Table.useTableRowRecord }}',
},
'x-component': 'Action.Drawer',
'x-component-props': {
useOkAction: '{{ Table.useTableUpdateAction }}',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
},
},
destroy: {
type: 'void',
title: '删除',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'destroy',
},
'x-action-type': 'destroy',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {
confirm: {
title: '删除数据',
content: '删除后无法恢复,确定要删除吗?',
},
useAction: '{{ Table.useTableDestroyAction }}',
},
},
};
return actions[type];
}
function AddActionButton() {
const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext();
const { appendChild, remove } = useDesignable();
const { schema, designable } = useDesignable();
if (!designable) {
return null;
}
return (
<Dropdown
trigger={['hover']}
visible={visible}
onVisibleChange={setVisible}
overlay={
<Menu>
<Menu.ItemGroup title={'启用操作'}>
{[
{ title: '编辑', name: 'update' },
{ title: '删除', name: 'destroy' },
].map((item) => (
<SwitchMenuItem
key={item.name}
checked={displayed.has(item.name)}
title={item.title}
onChange={async (checked) => {
if (!checked) {
const s = displayed.get(item.name) as Schema;
const path = getSchemaPath(s);
displayed.remove(item.name);
const removed = remove(path);
await removeSchema(removed);
} else {
const s = generateActionSchema(item.name);
const data = appendChild(s);
await createSchema(data);
}
}}
/>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu title={'自定义'}>
<Menu.Item style={{ minWidth: 120 }}></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
</Menu.SubMenu>
</Menu>
}
>
<Button
className={'designable-btn designable-btn-dash'}
style={{ marginLeft: 8 }}
type={'dashed'}
icon={<PlusOutlined />}
>
</Button>
</Dropdown>
);
}
function Actions(props: any) {
const { align = 'left' } = props;
const { schema, designable } = useDesignable();
return (
<Droppable
id={`${schema.name}-${align}`}
className={`action-bar-align-${align}`}
data={{ align, path: getSchemaPath(schema) }}
>
<Space>
{schema.mapProperties((s) => {
const currentAlign = s['x-align'] || 'left';
if (currentAlign !== align) {
return null;
}
return (
<SortableItem
id={s.name}
data={{
align,
draggable: true,
title: s.title,
path: getSchemaPath(s),
}}
>
<RecursionField name={s.name} schema={s} />
</SortableItem>
);
})}
</Space>
</Droppable>
);
}