designable bar

This commit is contained in:
chenos 2021-07-01 00:16:43 +08:00
parent 75cd158a27
commit 46e4089140
12 changed files with 976 additions and 361 deletions

View File

@ -1,6 +1,6 @@
import React, { createContext, useState } from 'react'; import React, { createContext, useState } from 'react';
import { import {
FormItem, // FormItem,
FormLayout, FormLayout,
FormButtonGroup, FormButtonGroup,
Submit, Submit,
@ -41,6 +41,9 @@ import { Table } from './table';
import { Tabs } from './tabs'; import { Tabs } from './tabs';
import { TimePicker } from './time-picker'; import { TimePicker } from './time-picker';
import { Upload } from './upload'; import { Upload } from './upload';
import { FormItem } from './form-item';
export const BlockContext = createContext({ dragRef: null });
const Div = (props) => <div {...props} />; const Div = (props) => <div {...props} />;
@ -51,7 +54,6 @@ const scope = {
}; };
const components = { const components = {
Div, Div,
Space, Space,
Card, Card,
@ -87,13 +89,13 @@ const components = {
}; };
export function registerScope(scopes) { export function registerScope(scopes) {
Object.keys(scopes).forEach(key => { Object.keys(scopes).forEach((key) => {
scope[key] = scopes[key]; scope[key] = scopes[key];
}); });
} }
export function registerComponents(values) { export function registerComponents(values) {
Object.keys(values).forEach(key => { Object.keys(values).forEach((key) => {
components[key] = values[key]; components[key] = values[key];
}); });
} }
@ -123,6 +125,9 @@ export function DesignableProvider(props) {
); );
} }
export const DesignableContext =
createContext<{ schema: Schema; refresh: any }>(null);
export function DesignableSchemaField(props: { schema?: ISchema }) { export function DesignableSchemaField(props: { schema?: ISchema }) {
return ( return (
<DesignableProvider schema={new Schema(props.schema)}> <DesignableProvider schema={new Schema(props.schema)}>

View File

@ -127,8 +127,8 @@ export type ActionType = React.FC<ActionProps> & {
DesignableBar?: React.FC<any>; DesignableBar?: React.FC<any>;
}; };
function Blank({ children }) { function Blank() {
return children; return null;
} }
function useDesignableBar() { function useDesignableBar() {
@ -171,45 +171,44 @@ export const Action: ActionType = observer((props) => {
); );
} }
return ( return (
<DesignableBar> <Button
<Button {...others}
{...others} onClick={async () => {
onClick={async () => { if (!childSchema) {
if (!childSchema) { return await run();
return await run(); }
} if (childSchema['x-component'] === 'Action.Drawer') {
if (childSchema['x-component'] === 'Action.Drawer') { Drawer.open({
Drawer.open({ title: childSchema.title,
title: childSchema.title, ...(childSchema['x-component-props'] || {}),
...(childSchema['x-component-props'] || {}), content: () => {
content: () => { return (
return ( <div>
<div> <SchemaBlock schema={childSchema} onlyRenderProperties />
<SchemaBlock schema={childSchema} onlyRenderProperties /> </div>
</div> );
); },
}, });
}); }
} if (childSchema['x-component'] === 'Action.Container') {
if (childSchema['x-component'] === 'Action.Container') { const el = document.createElement('div');
const el = document.createElement('div'); const target = document.querySelector(
const target = document.querySelector( childSchema['x-component-props']?.['container'],
childSchema['x-component-props']?.['container'], );
); target.childNodes.forEach((child) => child.remove());
target.childNodes.forEach((child) => child.remove()); target.appendChild(el);
target.appendChild(el); ReactDOM.render(
ReactDOM.render( <div>
<div> <SchemaBlock schema={childSchema} onlyRenderProperties />
<SchemaBlock schema={childSchema} onlyRenderProperties /> </div>,
</div>, el,
el, );
); }
} }}
}} >
> {field.title}
{field.title} <DesignableBar />
</Button> </Button>
</DesignableBar>
); );
}); });
@ -228,17 +227,17 @@ Action.URL = observer((props) => {
); );
}); });
Action.DesignableBar = (props) => { Action.DesignableBar = () => {
const field = useField(); const field = useField();
const { schema, refresh } = useSchemaQuery(); const { schema, refresh } = useSchemaQuery();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
return ( return (
<div className={'designable'}> <div className={classNames('designable-bar', { active: visible })}>
<span <span
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
className={classNames('designable-bar', { active: visible })} className={classNames('designable-bar-actions', { active: visible })}
> >
<Dropdown <Dropdown
trigger={['click']} trigger={['click']}
@ -266,7 +265,6 @@ Action.DesignableBar = (props) => {
<MenuOutlined /> <MenuOutlined />
</Dropdown> </Dropdown>
</span> </span>
{props.children}
</div> </div>
); );
}; };

View File

@ -1,6 +1,25 @@
.designable { // .designable {
display: inline-block; // display: inline-block;
position: relative; // position: relative;
// &:hover {
// .designable-bar {
// display: block;
// }
// }
// .designable-bar {
// display: none;
// background: #fff;
// z-index: 2;
// position: absolute;
// right: 10px;
// top: 50%;
// transform: translateY(-50%);
// &.active {
// display: block;
// }
// }
// }
.ant-btn {
&:hover { &:hover {
.designable-bar { .designable-bar {
display: block; display: block;
@ -8,14 +27,27 @@
} }
.designable-bar { .designable-bar {
display: none; display: none;
background: #fff;
z-index: 2;
position: absolute; position: absolute;
right: 10px; top: -1px;
top: 50%; right: -1px;
transform: translateY(-50%); left: -1px;
bottom: -1px;
border-radius: 2px;
border: 2px solid #1890ff;
&.active { &.active {
display: block; display: block;
} }
.designable-bar-actions {
position: absolute;
right: 0;
line-height: 1rem;
background-color: #1890ff;
color: #fff;
z-index: 10;
padding: 0 3px;
.anticon {
font-size: 10px;
}
}
} }
} }

View File

@ -75,10 +75,6 @@ const row = (schema) => {
[`gb_${uid()}`]: { [`gb_${uid()}`]: {
type: 'void', type: 'void',
'x-component': 'Grid.Block', 'x-component': 'Grid.Block',
'x-component-props': {
DesignableBar: `${component}.DesignableBar`,
},
'x-designable-bar': `${component}.DesignableBar`,
properties: { properties: {
[`gbn_${uid()}`]: schema, [`gbn_${uid()}`]: schema,
}, },
@ -394,6 +390,7 @@ AddNew.FormItem = observer((props) => {
name: `f_${uid()}`, name: `f_${uid()}`,
title: `字段${uid()}`, title: `字段${uid()}`,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-designable-bar': 'FormItem.DesignableBar',
'x-component': 'Input', 'x-component': 'Input',
}); });
console.log({ rowData }); console.log({ rowData });

View File

@ -41,11 +41,19 @@ const schema = {
interface: 'icon', interface: 'icon',
type: 'string', type: 'string',
title: `阅读模式`, title: `阅读模式`,
name: 'name2',
'x-read-pretty': true, 'x-read-pretty': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'ColorSelect', 'x-component': 'ColorSelect',
} },
designable: {
interface: 'icon',
type: 'string',
title: `配置模式`,
// 'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-designable-bar': 'FormItem.DesignableBar',
'x-component': 'ColorSelect',
},
} }
}; };

View File

@ -0,0 +1,71 @@
import React, { useContext, useState } from 'react';
import { connect, mapProps, mapReadPretty, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
import { FormItem as FormilyFormItem } from '@formily/antd';
import { Dropdown, Menu } from 'antd';
import classNames from 'classnames';
import { MenuOutlined } from '@ant-design/icons';
import './style.less';
import get from 'lodash/get';
import { BlockContext } from '../SchemaField';
function Blank() {
return null;
}
function useDesignableBar() {
const schema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const DesignableBar = get(options.components, schema['x-designable-bar']);
return {
DesignableBar: DesignableBar || Blank,
};
}
export const FormItem: any = connect((props) => {
const { DesignableBar } = useDesignableBar();
const { dragRef } = useContext(BlockContext);
return (
<div className={'designable-form-item'}>
<FormilyFormItem {...props} />
{dragRef && <div ref={dragRef}>Drag</div>}
<DesignableBar/>
</div>
);
});
FormItem.DesignableBar = () => {
const field = useField();
const [visible, setVisible] = useState(false);
return (
<div className={classNames('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={classNames('designable-bar-actions', { active: visible })}
>
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
onClick={(e) => {
setVisible(false);
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};

View File

@ -0,0 +1,36 @@
.designable-form-item {
position: relative;
&:hover {
.designable-bar {
display: block;
}
}
.designable-bar {
z-index: 1;
pointer-events: none;
display: none;
position: absolute;
top: 0;
right: -0;
left: -0;
bottom: -0;
border-radius: 2px;
border: 2px solid #1890ff;
&.active {
display: block;
}
.designable-bar-actions {
pointer-events: auto;
position: absolute;
right: 0;
line-height: 1rem;
background-color: #1890ff;
color: #fff;
z-index: 10;
padding: 0 3px;
.anticon {
font-size: 10px;
}
}
}
}

View File

@ -1,4 +1,4 @@
import React, { useState, useContext } from 'react'; import React, { useState, useContext, createContext } from 'react';
import { useDrag, mergeRefs, useDrop } from './DND'; import { useDrag, mergeRefs, useDrop } from './DND';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
@ -17,6 +17,7 @@ import {
DeleteOutlined, DeleteOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import get from 'lodash/get'; import get from 'lodash/get';
import { BlockContext } from '../SchemaField';
export const Block = observer((props: any) => { export const Block = observer((props: any) => {
const { children, title, DesignableBar } = props; const { children, title, DesignableBar } = props;
@ -50,6 +51,8 @@ export const Block = observer((props: any) => {
console.log({ DesignableBarComponent, DesignableBar }); console.log({ DesignableBarComponent, DesignableBar });
const { addBlock } = useSchemaQuery(); const { addBlock } = useSchemaQuery();
console.log({ children });
return ( return (
<div <div
data-type={'block'} data-type={'block'}
@ -57,10 +60,12 @@ export const Block = observer((props: any) => {
className={classNames('block', { 'top-half': onTopHalf, hover: isOver })} className={classNames('block', { 'top-half': onTopHalf, hover: isOver })}
// style={{ textAlign: 'center', lineHeight: '60px', background: '#f1f1f1' }} // style={{ textAlign: 'center', lineHeight: '60px', background: '#f1f1f1' }}
> >
{DesignableBarComponent && ( <BlockContext.Provider value={{ dragRef }}>
{children}
</BlockContext.Provider>
{/* {DesignableBarComponent && (
<DefaultActionBar DesignableBarComponent={DesignableBarComponent} dragRef={dragRef} /> <DefaultActionBar DesignableBarComponent={DesignableBarComponent} dragRef={dragRef} />
)} )} */}
{children}
</div> </div>
); );
}); });

View File

@ -30,7 +30,7 @@ const schema = {
menu1: { menu1: {
type: 'void', type: 'void',
'x-component': 'Menu', 'x-component': 'Menu',
// 'x-decorator': 'Menu.Designable', 'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': { 'x-component-props': {
mode: 'horizontal', mode: 'horizontal',
}, },
@ -124,7 +124,7 @@ const schema = {
export default () => { export default () => {
return ( return (
<SchemaBlock schema={schema} /> <div style={{width: 200}}><SchemaBlock schema={schema} /></div>
); );
}; };
``` ```

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,62 @@
.designable-action {
position: absolute;
right: 16px;
display: none;
z-index: 10;
}
.ant-menu-dark.ant-menu-horizontal { .ant-menu-dark.ant-menu-horizontal {
width: 100%; width: 100%;
} }
.ant-menu-dark .ant-menu-item-active .designable-action { .ant-menu-dark .ant-menu-item-active .designable-bar {
background-color: #001529;
display: inline-block; display: inline-block;
} }
.ant-menu-dark .ant-menu-item-active:hover .designable-action, .ant-menu-dark .ant-menu-item-active:hover .designable-bar,
.ant-menu-dark .ant-menu-item-active.ant-menu-item-selected .designable-action { .ant-menu-dark .ant-menu-item-active.ant-menu-item-selected .designable-bar {
background-color: #1890ff;
display: inline-block; display: inline-block;
} }
.ant-menu-light .ant-menu-submenu-active > .ant-menu-submenu-title .designable-action, .ant-menu-light .ant-menu-submenu-active > .ant-menu-submenu-title .designable-bar,
.ant-menu-light .ant-menu-item-active .designable-action { .ant-menu-light .ant-menu-item-active .designable-bar {
background-color: #fff;
display: inline-block; display: inline-block;
} }
.ant-menu-sub.ant-menu-inline .ant-menu-submenu-active > .ant-menu-submenu-title .designable-action, // .ant-menu-sub.ant-menu-inline .ant-menu-submenu-active > .ant-menu-submenu-title .designable-action,
.ant-menu-sub.ant-menu-inline .ant-menu-item-active .designable-action { // .ant-menu-sub.ant-menu-inline .ant-menu-item-active .designable-action {
background-color: #fafafa; // background-color: #fafafa;
// }
// .ant-menu-light .ant-menu-item-selected .designable-action {
// background-color: #e6f7ff !important;
// }
.ant-menu {
.designable-bar {
display: none;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
border: 2px solid #1890ff;
&.active {
display: block;
}
.designable-bar-actions {
position: absolute;
right: 0;
line-height: 1rem;
background-color: #1890ff;
color: #fff;
font-size: 12px;
z-index: 10;
.anticon {
font-size: 10px;
}
}
}
} }
.ant-menu-light .ant-menu-item-selected .designable-action { .ant-menu-submenu-horizontal > .ant-menu-submenu-title {
background-color: #e6f7ff !important; .designable-bar {
} position: absolute;
left: -20px;
right: -20px;
border: 2px solid #1890ff;
}
}

View File

@ -189,11 +189,11 @@ function useMenuSchema({ schema, selectedKey }) {
// const topMenuSchema = new Schema(cloneDeep(schema.toJSON())); // const topMenuSchema = new Schema(cloneDeep(schema.toJSON()));
let selected = find(schema).shift() as Schema; let selected = find(topMenuSchema).shift() as Schema;
console.log({ topMenuSchema, selected, schema }); console.log({ topMenuSchema, selected, schema });
if (selected.properties) { if (selected && selected.properties) {
const findChild = (properties) => { const findChild = (properties) => {
const keys = Object.keys(properties || {}); const keys = Object.keys(properties || {});
const firstKey = keys.shift(); const firstKey = keys.shift();
@ -420,8 +420,6 @@ function Content({ activeKey }) {
export function AdminLayout({ route, children }: any) { export function AdminLayout({ route, children }: any) {
const match = useRouteMatch<any>(); const match = useRouteMatch<any>();
console.log('match.params', match.params);
const { data = {}, loading } = useRequest( const { data = {}, loading } = useRequest(
`/api/blocks:getSchema/${route.blockId}`, `/api/blocks:getSchema/${route.blockId}`,
{ {