From 5f752d28d755cae10b91c6055b9934109b8164dc Mon Sep 17 00:00:00 2001 From: chenos Date: Sun, 6 Jun 2021 18:21:04 +0800 Subject: [PATCH] improve a lot of details --- packages/client/src/blocks/grid/Block.tsx | 102 +++++ packages/client/src/blocks/grid/Col.tsx | 49 ++- packages/client/src/blocks/grid/DND.tsx | 239 ++++++++--- packages/client/src/blocks/grid/Drop.tsx | 150 ------- packages/client/src/blocks/grid/Grid.tsx | 387 ++++-------------- packages/client/src/blocks/grid/Row.tsx | 53 ++- .../client/src/blocks/grid/demos/demo2.tsx | 133 ------ .../client/src/blocks/grid/demos/demo3.tsx | 200 --------- .../client/src/blocks/grid/demos/demo4.tsx | 32 +- .../client/src/blocks/grid/demos/demo5.less | 99 ++++- .../client/src/blocks/grid/demos/demo6.tsx | 208 ++++++++++ packages/client/src/blocks/grid/index.md | 14 +- packages/client/src/blocks/grid/index.tsx | 270 +++++++++++- packages/client/src/blocks/grid/utils.tsx | 284 ++++++++++++- packages/client/src/fields/index.tsx | 294 ------------- 15 files changed, 1278 insertions(+), 1236 deletions(-) create mode 100644 packages/client/src/blocks/grid/Block.tsx delete mode 100644 packages/client/src/blocks/grid/Drop.tsx delete mode 100644 packages/client/src/blocks/grid/demos/demo2.tsx delete mode 100644 packages/client/src/blocks/grid/demos/demo3.tsx create mode 100644 packages/client/src/blocks/grid/demos/demo6.tsx diff --git a/packages/client/src/blocks/grid/Block.tsx b/packages/client/src/blocks/grid/Block.tsx new file mode 100644 index 000000000..63ad406e9 --- /dev/null +++ b/packages/client/src/blocks/grid/Block.tsx @@ -0,0 +1,102 @@ +import React, { useState } from 'react'; +import { useDrag, mergeRefs, useDrop } from './DND'; +import classNames from 'classnames'; +import { useField } from '@formily/react'; +import { Dropdown, Menu } from 'antd'; +import { useSchemaQuery } from '.'; +import { MenuOutlined, ArrowUpOutlined, ArrowDownOutlined, DeleteOutlined } from '@ant-design/icons'; + +export const Block = (props) => { + const { children, title } = props; + const field = useField(); + const { isDragging, dragRef, previewRef } = useDrag({ + type: 'grid', + onDragStart() { + // console.log('onDragStart'); + }, + onDragEnd(event) { + console.log('onDragEnd', event); + }, + onDrag(event) { + // console.log('onDrag'); + }, + item: { + path: field.address.segments, + }, + }); + console.log('useDrag', 'previewElement', field.address.segments); + const { isOver, onTopHalf, dropRef } = useDrop({ + accept: 'grid', + data: {}, + canDrop: !isDragging, + }); + const { addBlock } = useSchemaQuery(); + return ( +
+ +
+ {children} {field.path.entire} +
+
+ ); +}; + +const ActionBar = ({ dragRef }) => { + const { addBlock, removeBlock } = useSchemaQuery(); + const [active, setActive] = useState(false); + return ( +
+ + { + addBlock({}, { + insertBefore: true, + }); + setActive(false); + }} + icon={} + > + 在上方插入区块 + + { + addBlock(); + setActive(false); + }} + icon={} + > + 在下方插入区块 + + + { + removeBlock(); + setActive(false); + }} + icon={} + > + 删除区块 + + + } + > + + +
+ ); +}; + +export default Block; diff --git a/packages/client/src/blocks/grid/Col.tsx b/packages/client/src/blocks/grid/Col.tsx index cf6c6dbf7..0539b4100 100644 --- a/packages/client/src/blocks/grid/Col.tsx +++ b/packages/client/src/blocks/grid/Col.tsx @@ -1,5 +1,7 @@ -import React, { useRef, useState } from 'react'; +import React, { cloneElement, useRef, useState } from 'react'; import { useMouseEvents } from 'beautiful-react-hooks'; +import { mergeRefs, useDrop } from './DND'; +import classNames from 'classnames'; export function useColResizer(options?: any) { const { onDragStart, onDrag, onDragEnd } = options || {}; @@ -12,9 +14,15 @@ export function useColResizer(options?: any) { const [initial, setInitial] = useState(null); onMouseDown((event: React.MouseEvent) => { - setIsDragging(true); + if (event.button !== 0) { + return; + } const prev = dragRef.current.previousElementSibling as HTMLDivElement; const next = dragRef.current.nextElementSibling as HTMLDivElement; + if (!prev || !next) { + return; + } + setIsDragging(true); if (!initial) { setInitial({ offset: event.clientX, @@ -41,7 +49,7 @@ export function useColResizer(options?: any) { setInitial(null); // @ts-ignore event.data = { size }; - onDragEnd(event); + onDragEnd && onDragEnd(event); }); onMouseMove((event: React.MouseEvent) => { @@ -57,31 +65,36 @@ export function useColResizer(options?: any) { // console.log('dragRef.current.nextSibling', prev.style.width); }); - return { dragOffset, dragRef, columns }; + return { isDragging, dragOffset, dragRef, columns }; } export const Col: any = (props) => { - const { size, children } = props; + const { size, children, position = {}, isLast } = props; return ( -
- {children} -
+ <> +
+ {children} +
+ + ); -} +}; Col.Divider = (props) => { - const { onDragEnd } = props; - const { dragRef } = useColResizer({ onDragEnd }); + const { onDragEnd, resizable = true } = props; + const { isDragging, dragRef } = useColResizer({ onDragEnd }); + const { isOver, dropRef } = useDrop({ + accept: 'grid', + data: { }, + }); return (
); -} +}; export default Col; diff --git a/packages/client/src/blocks/grid/DND.tsx b/packages/client/src/blocks/grid/DND.tsx index 79c3cb2a8..50dd164f9 100644 --- a/packages/client/src/blocks/grid/DND.tsx +++ b/packages/client/src/blocks/grid/DND.tsx @@ -1,15 +1,24 @@ import React, { createContext, useContext, useEffect, useRef } from 'react'; import { useState } from 'react'; -import { useMouseEvents } from 'beautiful-react-hooks'; +import { useMouseEvents, useWillUnmount } from 'beautiful-react-hooks'; +import { useField } from '@formily/react'; -export const DragDropManagerContext = createContext({ drag: null, drops: {} }); +export const DragDropManagerContext = createContext({ + drag: null, + drops: {}, + name: `${Math.random()}`, +}); -export function DragDropProvider({ children }) { +export function DragDropProvider(props) { + const { gridRef, onDrop, children } = props; return ( {children} @@ -18,11 +27,11 @@ export function DragDropProvider({ children }) { } export function mergeRefs( - refs: Array | React.LegacyRef> + refs: Array | React.LegacyRef>, ): React.RefCallback { return (value) => { refs.forEach((ref) => { - if (typeof ref === "function") { + if (typeof ref === 'function') { ref(value); } else if (ref != null) { (ref as React.MutableRefObject).current = value; @@ -32,34 +41,62 @@ export function mergeRefs( } export function useDrag(options?: any) { - const { type, onDragStart, onDrag, onDragEnd } = options; - const dragRef = useRef(); + const { type, onDragStart, onDrag, onDragEnd, item = {} } = options; + const dragRef = useRef(); const previewRef = useRef(); - const [dragOffset, setDragOffset] = useState({ left: 0, top: 0 }); - const [previewOffset, setPreviewOffset] = useState({ left: 0, top: 0 }); const { onMouseDown } = useMouseEvents(dragRef); const { onMouseMove, onMouseUp } = useMouseEvents(); const [previewElement, setPreviewElement] = useState(); const [isDragging, setIsDragging] = useState(false); + const [mousePostionWithinPreview, setMousePostionWithinPreview] = useState({ + left: 0, + top: 0, + }); const dragDropManager = useContext(DragDropManagerContext); + const field = useField(); + console.log( + 'init', + { isDragging, previewElement }, + field ? field.address.segments : null, + ); + + useWillUnmount(() => { + setIsDragging(false); + console.log( + 'useWillUnmount', + { isDragging, previewElement }, + dragDropManager.previewElement, + field ? field.address.segments : null, + ); + // @ts-ignore + window.__previewElement && window.__previewElement.remove(); + // @ts-ignore + window.__previewElement = undefined; + + dragDropManager.drag = null; + document.body.style.cursor = null; + document.body.style.userSelect = null; + }) onMouseDown((event: React.MouseEvent) => { - dragDropManager.drag = { type }; + if (event.button !== 0) { + return; + } + dragDropManager.drag = { type, item }; + dragDropManager.drop = { type, item }; setIsDragging(true); + const postion = { + left: event.clientX - previewRef.current.getBoundingClientRect().left, + top: event.clientY - previewRef.current.getBoundingClientRect().top, + }; + + setMousePostionWithinPreview(postion); + const offset = { - left: event.clientX - previewRef.current.offsetLeft, - top: event.clientY - previewRef.current.offsetTop, + left: event.pageX - postion.left, + top: event.pageY - postion.top, }; - setDragOffset(offset); - - const offset2 = { - left: event.clientX - offset.left, - top: event.clientY - offset.top, - }; - setPreviewOffset(offset2); - - console.log('previewRef.current.clientWidth', previewRef.current.clientWidth); const wrap = document.createElement('div'); wrap.style.position = 'absolute'; @@ -69,73 +106,100 @@ export function useDrag(options?: any) { wrap.style.top = `0px`; wrap.style.zIndex = '9999'; wrap.style.width = `${previewRef.current.clientWidth}px`; - wrap.style.transform = `translate(${offset2.left}px, ${offset2.top}px)`; + wrap.style.transform = `translate(${offset.left}px, ${offset.top}px)`; setPreviewElement(wrap); + // @ts-ignore + window.__previewElement = wrap; + console.log('dragDropManager.previewElement', dragDropManager.previewElement) document.body.appendChild(wrap); const el = document.createElement('div'); wrap.appendChild(el); el.outerHTML = previewRef.current.outerHTML; onDragStart && onDragStart(event); document.body.style.cursor = 'grab'; + document.body.style.userSelect = 'none'; + document.body.className = 'dragging'; - console.log('onMouseDown', dragDropManager); + console.log( + 'onMouseDown', + { isDragging, previewElement }, + dragDropManager.previewElement, + field ? field.address.segments : null, + ); + // console.log('onMouseDown', event); }); onMouseUp((event: React.MouseEvent) => { - setIsDragging(false); - dragDropManager.drag = null; - if (!previewElement) { + if (!isDragging || !previewElement) { return; } - previewElement.remove(); - document.body.style.cursor = null; + console.log( + 'onMouseUp', + { isDragging, previewElement }, + field ? field.address.segments : null, + ); - if (type) { - let dropElement = document.elementFromPoint(event.clientX, event.clientY); - const dropIds = []; - while (dropElement) { - if (!dropElement.getAttribute) { - dropElement = dropElement.parentNode as Element; - continue; - } - const dropId = dropElement.getAttribute('data-drop-id'); - const dropContext = dropId ? dragDropManager.drops[dropId] : null; - if (dropContext && dropContext.accept === type) { - if ( - !dropContext.shallow || - (dropContext.shallow && dropIds.length === 0) - ) { - // @ts-ignore - event.data = dropContext.data; - onDragEnd && onDragEnd(event); - dropIds.push(dropId); - } - } - dropElement = dropElement.parentNode as Element; - } - } else { + // @ts-ignore + event.dragItem = item; + + setIsDragging(false); + dragDropManager.drag = null; + previewElement.remove(); + setPreviewElement(undefined); + document.body.style.cursor = null; + document.body.style.userSelect = null; + // @ts-ignore + window.__previewElement && window.__previewElement.remove(); + // @ts-ignore + window.__previewElement = undefined; + + if (!type) { onDragEnd && onDragEnd(event); } + + let dropElement = document.elementFromPoint(event.clientX, event.clientY); + const dropIds = []; + while (dropElement) { + if (!dropElement.getAttribute) { + dropElement = dropElement.parentNode as HTMLElement; + continue; + } + const dropId = dropElement.getAttribute('data-drop-id'); + const dropContext = dropId ? dragDropManager.drops[dropId] : null; + if (dropContext && dropContext.accept === type) { + if ( + !dropContext.shallow || + (dropContext.shallow && dropIds.length === 0) + ) { + // @ts-ignore + event.data = dropContext.data; + onDragEnd && onDragEnd(event); + dropIds.push(dropId); + } + } + dropElement = dropElement.parentNode as HTMLElement; + } }); onMouseMove((event: React.MouseEvent) => { - if (!isDragging) { - return; - } - - if (!previewElement) { + if (!isDragging || !previewElement) { return; } + console.log( + 'onMouseMove', + { isDragging, previewElement }, + dragDropManager.previewElement, + field ? field.address.segments : null, + ); + // console.log({previewElement}) const offset = { - left: event.clientX - dragOffset.left, - top: event.clientY - dragOffset.top, + left: event.pageX - mousePostionWithinPreview.left, + top: event.pageY - mousePostionWithinPreview.top, }; - setPreviewOffset(offset); - previewElement.style.transform = `translate(${offset.left}px, ${offset.top}px)`; if (type) { @@ -143,7 +207,7 @@ export function useDrag(options?: any) { const dropIds = []; while (dropElement) { if (!dropElement.getAttribute) { - dropElement = dropElement.parentNode as Element; + dropElement = dropElement.parentNode as HTMLElement; continue; } const dropId = dropElement.getAttribute('data-drop-id'); @@ -158,7 +222,7 @@ export function useDrag(options?: any) { // @ts-ignore // event.data = dropContext.data; } - dropElement = dropElement.parentNode as Element; + dropElement = dropElement.parentNode as HTMLElement; } dragDropManager.drag = { type, dropIds }; } @@ -166,15 +230,16 @@ export function useDrag(options?: any) { onDrag && onDrag(event); }); - return { isDragging, previewOffset, dragOffset, dragRef, previewRef }; + return { isDragging, dragRef, previewRef }; } export function useDrop(options) { - const { accept, data, shallow } = options; + const { accept, data, shallow, onDrop, onHover, canDrop = true } = options; const dropRef = useRef(); const { onMouseEnter, onMouseLeave, onMouseMove, onMouseUp } = useMouseEvents(dropRef); const [isOver, setIsOver] = useState(false); + const [onTopHalf, setOnTopHalf] = useState(null); const [dropId] = useState(`d${Math.random()}`); const dragDropManager = useContext(DragDropManagerContext); @@ -188,14 +253,20 @@ export function useDrop(options) { }, [accept, data, shallow]); onMouseEnter((event) => { - console.log({ dragDropManager }); + if (!canDrop) { + return; + } + // console.log({ dragDropManager }); if (!dragDropManager.drag || dragDropManager.drag.type !== accept) { return; } setIsOver(true); }); - onMouseMove(() => { + onMouseMove((event: React.MouseEvent) => { + if (!canDrop) { + return; + } if (!dragDropManager.drag || dragDropManager.drag.type !== accept) { return; } @@ -203,21 +274,53 @@ export function useDrop(options) { dragDropManager.drag.dropIds && dragDropManager.drag.dropIds.includes(dropId) ) { + const top = event.clientY - dropRef.current.getBoundingClientRect().top; + const onTop = top < dropRef.current.clientHeight / 2; + setOnTopHalf(onTop); + // @ts-ignore + event.onTopHalf = onTop; setIsOver(true); + onHover && onHover(event); } else { setIsOver(false); } }); - onMouseUp((event) => { + onMouseUp((event: React.MouseEvent) => { + if (!canDrop) { + return; + } + if (event.button !== 0) { + return; + } + if (isOver) { + const top = event.clientY - dropRef.current.getBoundingClientRect().top; + const onTop = top < dropRef.current.clientHeight / 2; + setOnTopHalf(onTop); + // @ts-ignore + event.onTopHalf = onTop; + // @ts-ignore + event.data = data; + // @ts-ignore + event.dragItem = dragDropManager.drop.item; + // @ts-ignore + event.dropElement = dropRef.current; + onDrop && onDrop(event); + dragDropManager.onDrop && dragDropManager.onDrop(event); + dragDropManager.drop = null; + } setIsOver(false); }); onMouseLeave(() => { + if (!canDrop) { + return; + } setIsOver(false); }); return { + onTopHalf: isOver ? onTopHalf : null, isOver, dropRef, }; diff --git a/packages/client/src/blocks/grid/Drop.tsx b/packages/client/src/blocks/grid/Drop.tsx deleted file mode 100644 index ada2893fa..000000000 --- a/packages/client/src/blocks/grid/Drop.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useContext } from 'react'; -import { DndProvider, useDrag, useDrop } from 'react-dnd'; -import classNames from 'classnames'; -import { useSchema } from '../../fields'; -import { AcceptContext } from './Grid'; -import { useField } from '@formily/react'; - -export function DropFirstRow() { - const field = useField(); - const { schema } = useSchema(); - const accept = useContext(AcceptContext); - const [{ canDrop, isOver }, drop] = useDrop( - () => ({ - accept, - drop: () => ({ - gridType: 'first-row', - schema, - segments: field.address.segments, - }), - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - }), - [schema], - ); - - const active = canDrop && isOver; - - return ( -
- ); -} - -export function DropRow() { - const { schema } = useSchema(); - const field = useField(); - const accept = useContext(AcceptContext); - const [{ canDrop, isOver }, drop] = useDrop( - () => ({ - accept, - drop: () => ({ - gridType: 'row', - schema, - segments: field.address.segments, - }), - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - }), - [schema], - ); - - const active = canDrop && isOver; - - return
; -} - -export function DropColumn() { - const field = useField(); - const { schema } = useSchema(); - const accept = useContext(AcceptContext); - const [{ canDrop, isOver }, drop] = useDrop( - () => ({ - accept, - drop: () => ({ - gridType: 'column', - schema, - segments: field.address.segments, - }), - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - }), - [schema], - ); - - const active = canDrop && isOver; - - return ( -
- ); -} - -export function DropLastColumn() { - const field = useField(); - const { schema } = useSchema(); - const accept = useContext(AcceptContext); - const [{ canDrop, isOver }, drop] = useDrop( - () => ({ - accept, - drop: () => ({ - gridType: 'last-column', - schema, - segments: field.address.segments, - }), - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - }), - [schema], - ); - - const active = canDrop && isOver; - - return ( -
- ); -} - -export function DropBlock({ canDrop }) { - const { schema } = useSchema(); - const accept = useContext(AcceptContext); - const [{ isOver }, drop] = useDrop( - () => ({ - accept, - drop: () => ({ schema }), - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - canDrop: () => canDrop, - }), - [canDrop], - ); - - console.log({ canDrop }); - - const active = canDrop && isOver; - - return ( -
- ); -} - -export default { - DropRow, - DropColumn, - DropLastColumn, - DropBlock, -}; diff --git a/packages/client/src/blocks/grid/Grid.tsx b/packages/client/src/blocks/grid/Grid.tsx index 84cc50f76..10641b248 100644 --- a/packages/client/src/blocks/grid/Grid.tsx +++ b/packages/client/src/blocks/grid/Grid.tsx @@ -1,319 +1,88 @@ -import React, { createContext, useContext, useState } from 'react'; -import { Row, Col, Dropdown, Menu } from 'antd'; -import { DndProvider, useDrag, useDrop } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { - DropFirstRow, - DropRow, - DropColumn, - DropBlock, - DropLastColumn, -} from './Drop'; -import { - CloseOutlined, - DeleteOutlined, - MenuOutlined, - PlusOutlined, - ArrowDownOutlined, - ArrowUpOutlined, -} from '@ant-design/icons'; -import { - getFullPaths, - SchemaDesignerContext, - useSchemaQuery, - useSchema, -} from '../../fields'; -import { TouchBackend } from 'react-dnd-touch-backend'; -import { usePreview } from 'react-dnd-preview'; -import classNames from 'classnames'; -import './style.less'; -import { useField, useFieldSchema } from '@formily/react'; +import React, { cloneElement, useRef } from 'react'; +import { Col } from './Col'; +import { Row } from './Row'; +import { Block } from './Block'; +import { DragDropProvider } from './DND'; +import { useSchemaQuery } from './'; -const Preview = () => { - const accept = useContext(AcceptContext); - const { item, style, display, itemType } = usePreview(); - if (itemType !== accept) { - return null; - } - if (!display) { - return null; - } - if (!item.ref) { - return null; - } - if (!item.ref.current) { - return null; - } - const el = item.ref.current as HTMLDivElement; - console.log({ itemType }); +type Event = React.MouseEvent & { + dropElement: HTMLElement; + onTopHalf?: boolean; + dragItem?: any; +}; + +export const Grid = (props) => { + const { children, onDrop } = props; + const ref = useRef(); + const { schema, moveTo, refresh } = useSchemaQuery(); return ( -
-
-
- ); -}; - -export interface GridPorps { - children?: React.ReactNode; -} - -export interface GridRowPorps { - children?: React.ReactNode; - rowOrder?: number; -} - -export interface GridColumnPorps { - children?: React.ReactNode; - span?: any; -} - -export interface GridBlockProps { - children?: React.ReactNode; - lastComponentType?: string; -} - -export type GridComponent = React.FC & { - Row?: React.FC; - Column?: React.FC; - Block?: React.FC; -}; - -export const AcceptContext = createContext(null); - -export const Grid: GridComponent = (props) => { - const { children } = props; - const schema = useFieldSchema(); - const field = useField(); - console.log({ accept: schema.name, schema: schema.toJSON() }); - return ( -
- - - {children} - - - -
- ); -}; - -Grid.Row = (props) => { - const { children, rowOrder } = props; - const { schema } = useSchema(); - const field = useField(); - const accept = useContext(AcceptContext); - console.log({ accept }); - const [{ canDrop, isOverCurrent }, drop] = useDrop(() => ({ - accept, - drop: (item, monitor) => { - const didDrop = monitor.didDrop(); - if (didDrop) { - return; - } - return { gridType: 'row', schema, segments: field.address.segments }; - }, - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - isOverCurrent: monitor.isOver({ shallow: true }), - }), - })); - - const active = canDrop && isOverCurrent; - - return ( -
- {rowOrder === 0 && } - {children} - - {/* */} -
- ); -}; - -Grid.Column = (props) => { - const { children, span } = props; - return ( - - - {children} - - ); -}; - -interface DropResult { - [key: string]: any; -} - -Grid.Block = (props) => { - const { children, lastComponentType } = props; - const { schema, refresh } = useSchema(); - const fieldSchema = useFieldSchema(); - const field = useField(); - const accept = useContext(AcceptContext); - const context = useContext(SchemaDesignerContext); - const { - insertAfter, - insertAfterWithAddRow, - insertBeforeWithAddRow, - insertBeforeWithAddColumn, - appendToRowWithAddColumn, - } = useSchemaQuery(); - const ref = React.useRef(); - console.log({ accept }); - const [{ opacity, isDragging }, drag, preview] = useDrag( - () => ({ - type: accept, - item: { - ref, - preview, - schema, - }, - collect: (monitor) => ({ - isDragging: monitor.isDragging(), - opacity: monitor.isDragging() ? 0.9 : 1, - }), - end: (item, monitor) => { - const dropResult = monitor.getDropResult(); - if (item && dropResult) { - if (dropResult.gridType === 'block') { - insertAfter(field.address.segments, dropResult.segments); - } else if (dropResult.gridType === 'row') { - insertAfterWithAddRow(field.address.segments, dropResult.segments); - } else if (dropResult.gridType === 'column') { - insertBeforeWithAddColumn( - field.address.segments, - dropResult.segments, - ); - } else if (dropResult.gridType === 'last-column') { - appendToRowWithAddColumn( - field.address.segments, - dropResult.segments, - ); - } else if (dropResult.gridType === 'first-row') { - insertBeforeWithAddRow( - field.address.segments, - dropResult.segments, +
+ { + const el = event.dropElement; + const type = el.getAttribute('data-type'); + const getIndex = (el) => { + const type = el.getAttribute('data-type'); + return Array.prototype.indexOf.call( + el.parentNode.querySelectorAll(`.${type}`), + el, ); + }; + let position: any = { type }; + if (type === 'row') { + // position.rowIndex = getIndex(el); + position = { + type: 'row-divider', + rowDividerIndex: getIndex( + event.onTopHalf ? el.previousSibling : el.nextSibling, + ), + }; } - refresh(); - } - }, - }), - [field, schema], - ); - - const segments = field.address.segments; - - const [{ canDrop, isOver }, drop] = useDrop( - () => ({ - accept, - drop: () => { - console.log('source.segments', segments); - return { gridType: 'block', segments, schema }; - }, - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - // hover: (item, monitor) => { - // console.log(monitor.getSourceClientOffset()); - // }, - canDrop: () => !isDragging, - }), - [isDragging, schema], - ); - - const active = canDrop && isOver; - - drop(ref); - - console.log({ isDragging }); - - return ( -
- - {children} - {/* */} -
- ); -}; - -const ActionBar = ({ dragRef }) => { - const { addBlock, removeBlock } = useSchemaQuery(); - const [active, setActive] = useState(false); - return ( -
- - { - addBlock({}, true); - setActive(false); - }} - icon={} - > - 在上方插入区块 - - { - addBlock({}); - setActive(false); - }} - icon={} - > - 在下方插入区块 - - - { - removeBlock(); - setActive(false); - }} - icon={} - > - 删除区块 - - - } + if (type === 'row-divider') { + position.rowDividerIndex = getIndex(el); + } + if (type === 'col-divider') { + position.colDividerIndex = getIndex(el); + position.rowIndex = getIndex(el.parentNode); + } + if (type === 'block') { + const rowNode = el.parentNode.parentNode; + position.blockIndex = getIndex(el); + position.colIndex = getIndex(el.parentNode); + position.rowIndex = getIndex(rowNode); + const colsize = rowNode.querySelectorAll('.col').length; + if (colsize === 1) { + position = { + type: 'row-divider', + rowDividerIndex: getIndex( + event.onTopHalf + ? rowNode.previousSibling + : rowNode.nextSibling, + ), + }; + } else { + position.type = 'block-divider'; + position.blockDividerIndex = getIndex(el); + if (!event.onTopHalf) { + position.blockDividerIndex += 1 + } + } + } + onDrop && onDrop(event); + moveTo(event.dragItem.path, position); + console.log('onDrop', position, event.dragItem); + }} > - - + + {children} +
); }; +Grid.Row = Row; +Grid.Col = Col; +Grid.Block = Block; + export default Grid; diff --git a/packages/client/src/blocks/grid/Row.tsx b/packages/client/src/blocks/grid/Row.tsx index 5fa9a1fa9..d9a06fce5 100644 --- a/packages/client/src/blocks/grid/Row.tsx +++ b/packages/client/src/blocks/grid/Row.tsx @@ -1,21 +1,46 @@ -import React from 'react'; +import React, { cloneElement, useRef } from 'react'; +import classNames from 'classnames'; import { Col } from './Col'; +import { DragDropProvider, useDrop } from './DND'; +import { useField } from '@formily/react'; export const Row = (props) => { - const { children, onColResize } = props; - const len = children.length; + const { children, onColResize, position = {}, isLast } = props; + const { isOver, onTopHalf, dropRef } = useDrop({ + accept: 'grid', + data: {}, + shallow: true, + }); return ( -
- {children.map((child, index) => { - return ( - <> - {child} - {len > index + 1 && } - - ); - })} -
+ <> +
+ + {children} +
+ + ); -} +}; + +Row.Divider = (props) => { + const { style = {}, position } = props; + const { isOver, dropRef } = useDrop({ + accept: 'grid', + data: { position }, + }); + return ( +
+ ); +}; export default Row; diff --git a/packages/client/src/blocks/grid/demos/demo2.tsx b/packages/client/src/blocks/grid/demos/demo2.tsx deleted file mode 100644 index 672d81bd0..000000000 --- a/packages/client/src/blocks/grid/demos/demo2.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useMemo } from 'react'; -import { FormProvider, FormConsumer, useField, useFieldSchema } from '@formily/react'; -import { createForm } from '@formily/core'; -import { - SchemaFieldWithDesigner, - registerFieldComponents, - useSchema, -} from '../../../fields'; -import { grid, row, column, block } from '../utils'; -import { blocks2properties } from '../utils'; - -function Designer(props) { - const form = useMemo(() => createForm({}), []); - const { schema } = props; - return ( -
- - - {/* - {(form) => { - return
{JSON.stringify(form.values, null, 2)}
; - }} -
*/} -
-
- ); -} - -function Hello(props) { - const schema = useFieldSchema(); - return ( -
- Hello {schema.title} -
- ); -} - -registerFieldComponents({ Hello }); -const blocks = [ - { - type: 'string', - title: `Block 1`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 2`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 2, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 3`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 2, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 4`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 1, - blockOrder: 2, - }, - { - type: 'string', - title: `Block 5`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 3, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 6`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 3, - columnOrder: 2, - blockOrder: 1, - }, -]; -const schema = blocks2properties(blocks); - -export default () => { - console.log({schema}); - return ( -
- - {/*
{JSON.stringify(schema, null, 2)}
*/} -
- ); -}; diff --git a/packages/client/src/blocks/grid/demos/demo3.tsx b/packages/client/src/blocks/grid/demos/demo3.tsx deleted file mode 100644 index f0ca50fdb..000000000 --- a/packages/client/src/blocks/grid/demos/demo3.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import React, { useMemo } from 'react'; -import { FormProvider, FormConsumer, useField, useFieldSchema } from '@formily/react'; -import { createForm } from '@formily/core'; -import { - SchemaFieldWithDesigner, - registerFieldComponents, -} from '../../../fields'; -import { grid, row, column, block } from '../utils'; -import { blocks2properties } from '../utils'; -import { Card } from 'antd'; - -function Designer(props) { - const form = useMemo(() => createForm({}), []); - const { schema } = props; - return ( -
- - - {/* - {(form) => { - return
{JSON.stringify(form.values, null, 2)}
; - }} -
*/} -
-
- ); -} - -function Hello(props) { - const schema = useFieldSchema(); - return ( -
- Hello {schema.title} -
- ); -} - -registerFieldComponents({ Hello, Designer, Card }); - -const nested = blocks2properties([ - { - type: 'string', - title: `Nested Block 1`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Nested Block 2`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 2, - blockOrder: 1, - }, - { - type: 'string', - title: `Nested Block 3`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 2, - columnOrder: 1, - blockOrder: 1, - }, -]); - -const blocks = [ - { - type: 'string', - title: `Block 1`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 2`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 2, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 3`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 2, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 4`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 1, - columnOrder: 1, - blockOrder: 2, - }, - { - type: 'string', - title: `Block 5`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 3, - columnOrder: 1, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 6`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 3, - columnOrder: 2, - blockOrder: 1, - }, - { - type: 'string', - title: `Block 7`, - required: true, - 'x-read-pretty': false, - 'x-decorator': 'Card', - 'x-decorator-props': { - title: '内嵌区块(只能在当前区域内部拖拽)', - }, - 'x-component': 'Designer', - 'x-component-props': { - schema: { - type: 'object', - properties: { - layout: { - type: 'void', - 'x-component': 'FormLayout', - 'x-component-props': { - layout: 'vertical', - }, - properties: { - [nested.name]: nested, - }, - }, - }, - }, - }, - rowOrder: 4, - columnOrder: 1, - blockOrder: 1, - }, -]; -const schema = blocks2properties(blocks); -export default () => { - console.log({schema}); - return ( -
- - {/*
{JSON.stringify(schema, null, 2)}
*/} -
- ); -}; diff --git a/packages/client/src/blocks/grid/demos/demo4.tsx b/packages/client/src/blocks/grid/demos/demo4.tsx index 0106c1e65..267cbdeec 100644 --- a/packages/client/src/blocks/grid/demos/demo4.tsx +++ b/packages/client/src/blocks/grid/demos/demo4.tsx @@ -1,12 +1,12 @@ import React, { createContext, useContext, useEffect, useRef } from 'react'; -import { useDrag, useDrop, DragDropProvider } from '../'; +import { useDrag, useDrop, DragDropProvider, mergeRefs } from '../'; import { Button, Space } from 'antd'; function DropZone({ options, children }) { const { isOver, dropRef } = useDrop(options); return (
([dragRef, previewRef])}>拖拽1 - ); -} - -function mergeRefs( - refs: Array | React.LegacyRef> -): React.RefCallback { - return (value) => { - refs.forEach((ref) => { - if (typeof ref === "function") { - ref(value); - } else if (ref != null) { - (ref as React.MutableRefObject).current = value; - } - }); - }; + return ; } function Dragable2() { @@ -64,17 +48,15 @@ function Dragable2() { // console.log('onDrag'); }, }); - return ( - - ); + return ; } export default () => { return ( - - - + + + .action-bar { + position: absolute; + top: 5px; + right: 8px; + } +} + +.draggable { + cursor: grab !important; +} \ No newline at end of file diff --git a/packages/client/src/blocks/grid/demos/demo6.tsx b/packages/client/src/blocks/grid/demos/demo6.tsx new file mode 100644 index 000000000..3b4347d69 --- /dev/null +++ b/packages/client/src/blocks/grid/demos/demo6.tsx @@ -0,0 +1,208 @@ +import { Grid, Row, Col, Block, SchemaFieldWithDesigner } from '../'; +import './demo5.less'; +import { Card } from 'antd'; +import classNames from 'classnames'; +import React, { useMemo } from 'react'; +import { + FormProvider, + FormConsumer, + useField, + useFieldSchema, + ISchema, + Schema, +} from '@formily/react'; +import { createForm } from '@formily/core'; + +function Designer(props: { schema?: ISchema }) { + const form = useMemo(() => createForm({}), []); + const { schema } = props; + return ( +
+ + + {/* + {(form) => { + return
{JSON.stringify(form.values, null, 2)}
; + }} +
*/} +
+
+ ); +} + +const schema = new Schema({ + type: 'object', + properties: { + grid: { + type: 'void', + title: 'aa', + 'x-component': 'Grid', + properties: { + row1: { + type: 'void', + 'x-component': 'Grid.Row', + properties: { + col1: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1 / 2, + }, + properties: { + block11: { + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + title: 'block11', + }, + }, + }, + }, + col2: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1 / 2, + isLast: true, + }, + properties: { + block21: { + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + title: 'block21', + }, + }, + }, + }, + }, + }, + row2: { + type: 'void', + 'x-component': 'Grid.Row', + properties: { + col21: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1 / 3, + }, + properties: { + block211: { + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + title: 'block211', + }, + }, + }, + }, + col22: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 2 / 3, + isLast: true, + }, + properties: { + block221: { + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + title: 'block221', + }, + }, + }, + }, + }, + }, + row3: { + type: 'void', + 'x-component': 'Grid.Row', + "x-component-props": { + isLast: true, + }, + properties: { + col31: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1, + isLast: true, + }, + properties: { + block311: { + type: 'void', + 'x-component': 'Grid.Block', + 'x-component-props': { + title: 'block311', + }, + }, + }, + }, + }, + }, + }, + }, + }, +}) + +export default () => { + return ( + + + {/* { + console.log('onDrop', e, e.data); + }} + > + { + console.log(e.data); + }} + > + {[1, 2, 3].map((index) => ( + + col {index} + + ))} + + { + console.log(e.data); + }} + > + {[4, 5, 6].map((index) => ( + + col {index} + + ))} + + { + console.log(e.data); + }} + > + {[7, 8].map((index) => ( + + col {index} + + ))} + + { + console.log(e.data); + }} + > + {[9].map((index) => ( + + col {index} + + ))} + + */} + + ); +}; diff --git a/packages/client/src/blocks/grid/index.md b/packages/client/src/blocks/grid/index.md index e709636ba..1acbce3a2 100644 --- a/packages/client/src/blocks/grid/index.md +++ b/packages/client/src/blocks/grid/index.md @@ -14,14 +14,14 @@ group: 基于行(Row)和列(Col)来定义区块(Block)的外部框架。 ## 代码演示 - + ### useDrag & useDrop @@ -31,6 +31,10 @@ group: +### Grid + + + ## API 说明 ### Grid @@ -67,7 +71,11 @@ interface BlockOptions { 原始 schema 需要至少 grid->row->col->block->custom 五层嵌套,写起来非常繁琐,`blocks2properties` 方法可以简化配置。 -### useDrag +### useDrag & useDrop + +拖拽 hooks + +原生态的 ### useDrop diff --git a/packages/client/src/blocks/grid/index.tsx b/packages/client/src/blocks/grid/index.tsx index 2fe3c77f1..2ae6fb2f1 100644 --- a/packages/client/src/blocks/grid/index.tsx +++ b/packages/client/src/blocks/grid/index.tsx @@ -1,3 +1,271 @@ +import React, { useContext, createContext, useState } from 'react'; +import { + Schema, + ISchema, + useFieldSchema, + useForm, + useField, +} from '@formily/react'; +import { uid } from '@formily/shared'; +import { SchemaField } from '../../fields'; +import set from 'lodash/set'; + +export const SchemaDesignerContext = createContext(new Schema({})); +export const SchemaRefreshContext = createContext(null); + +export function SchemaFieldWithDesigner(props: { schema?: ISchema }) { + function Container(props) { + const { schema } = props; + const [, refresh] = useState(0); + return ( + { + refresh(Math.random()); + }} + > + + + +
{JSON.stringify(schema.toJSON(), null, 2)}
+
+ ); + } + return ; +} + +export function removeProperty(property: Schema) { + property.parent.removeProperty(property.name); +} + +export function addPropertyBefore(target, prop) { + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(prop.name, prop); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + }); +} + +export function addPropertyAfter(target, prop) { + Object.keys(target.parent.properties).forEach((name) => { + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + target.parent.addProperty(prop.name, prop); + } + }); +} + +export const getSchemaAddressSegments = (schema: Schema) => { + if (!schema) { + return []; + } + const segments = [schema.name]; + if (schema.parent && schema.parent.name) { + segments.unshift(...getSchemaAddressSegments(schema.parent)); + } + return segments; +}; + +export function useSchemaQuery() { + const context = useContext(SchemaDesignerContext); + const refresh = useContext(SchemaRefreshContext); + const fieldSchema = useFieldSchema(); + const field = useField(); + const form = useForm(); + + const getSchemaByPath = (path) => { + let s: Schema = context; + const names = [...path]; + while (names.length) { + s = s.properties[names.shift()]; + } + return s; + }; + + const schema = getSchemaByPath(field.address.segments); + + const getPropertyByPosition = (position) => { + if (position.type === 'row-divider') { + const names = Object.keys(schema.properties); + const isOver = position.rowDividerIndex > names.length - 1; + const index = isOver ? names.length - 1 : position.rowDividerIndex; + const name = names[index]; + const property = schema.properties[name]; + const addProperty = isOver ? addPropertyAfter : addPropertyBefore; + return (data) => { + return addProperty(property, { + type: 'void', + name: `r_${uid()}`, + 'x-component': 'Grid.Row', + properties: { + [`c_${uid()}`]: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1, + }, + properties: { + [data.name]: data, + }, + }, + }, + }); + }; + } + const rowNames = Object.keys(schema.properties); + const rowName = rowNames[position.rowIndex]; + const row = schema.properties[rowName]; + if (position.type === 'col-divider') { + const names = Object.keys(row.properties); + const isOver = position.colDividerIndex > names.length - 1; + const index = isOver ? names.length - 1 : position.colDividerIndex; + const name = names[index]; + const property = row.properties[name]; + const addProperty = isOver ? addPropertyAfter : addPropertyBefore; + const count = Object.keys(row.properties).length+1; + return (data) => { + const other = 1-(1/count); + Object.keys(row.properties).forEach(name => { + const prop = row.properties[name]; + const segments = getSchemaAddressSegments(prop); + form.setFieldState(segments.join('.'), state => { + state.componentProps.size = other * state.componentProps.size; + console.log({state}, other * state.componentProps.size); + }); + }); + addProperty(property, { + type: 'void', + name: `c_${uid()}`, + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1/count, + }, + properties: { + [data.name]: data, + }, + }); + }; + } + const colNames = Object.keys(row.properties); + const colName = colNames[position.colIndex]; + const col = row.properties[colName]; + if (position.type === 'block-divider') { + const names = Object.keys(col.properties); + const isOver = position.blockDividerIndex > names.length - 1; + const index = isOver ? names.length - 1 : position.blockDividerIndex; + const name = names[index]; + const property = col.properties[name]; + const addProperty = isOver ? addPropertyAfter : addPropertyBefore; + return (data) => { + return addProperty(property, data); + }; + } + }; + + return { + schema, + fieldSchema, + refresh, + removeBlock: () => { + if (Object.keys(schema.parent.parent.properties).length === 1) { + removeProperty(schema.parent.parent); + } else if (Object.keys(schema.parent.properties).length === 1) { + removeProperty(schema.parent); + const cols = []; + let allSize = 0; + Object.keys(schema.parent.parent.properties).forEach(name => { + const prop = schema.parent.parent.properties[name]; + const segments = getSchemaAddressSegments(prop); + cols.push(segments); + form.setFieldState(segments.join('.'), state => { + allSize += state.componentProps.size; + }); + return; + }); + for (const segments of cols) { + form.setFieldState(segments.join('.'), state => { + state.componentProps.size = state.componentProps.size/allSize; + }); + } + } + refresh(); + }, + addBlock: (data?: any, options?: any) => { + const { insertBefore = false } = options || {}; + data = { + type: 'void', + name: `b_${uid()}`, + 'x-component': 'Grid.Block', + }; + const addProperty = insertBefore ? addPropertyBefore : addPropertyAfter; + if (Object.keys(schema.parent.parent.properties).length === 1) { + addProperty(schema.parent.parent, { + type: 'void', + name: `r_${uid()}`, + 'x-component': 'Grid.Row', + properties: { + [`c_${uid()}`]: { + type: 'void', + 'x-component': 'Grid.Col', + 'x-component-props': { + size: 1, + }, + properties: { + [data.name]: data, + }, + }, + }, + }) + } else { + addProperty(schema, data); + } + refresh(); + }, + moveTo: (path, position) => { + const source = getSchemaByPath(path); + const insert = getPropertyByPosition(position); + if (!insert) { + return; + } + + // 只有一列时,删除当前行 + if (Object.keys(source.parent.parent.properties).length === 1) { + source.parent.parent.parent.removeProperty(source.parent.parent.name); + } + // 某列只有一个区块时删除当前列 + else if (Object.keys(source.parent.properties).length === 1) { + source.parent.parent.removeProperty(source.parent.name); + const cols = []; + let allSize = 0; + Object.keys(source.parent.parent.properties).forEach(name => { + const prop = source.parent.parent.properties[name]; + const segments = getSchemaAddressSegments(prop); + cols.push(segments); + form.setFieldState(segments.join('.'), state => { + allSize += state.componentProps.size; + }); + return; + }); + for (const segments of cols) { + form.setFieldState(segments.join('.'), state => { + state.componentProps.size = state.componentProps.size/allSize; + }); + } + } else { + source.parent.removeProperty(source.name); + } + insert(source.toJSON()); + refresh(); + }, + }; +} + +export * from './DND'; export * from './Row'; export * from './Col'; -export * from './DND'; +export * from './Grid'; +export * from './Block'; diff --git a/packages/client/src/blocks/grid/utils.tsx b/packages/client/src/blocks/grid/utils.tsx index d2efc14db..27c471b32 100644 --- a/packages/client/src/blocks/grid/utils.tsx +++ b/packages/client/src/blocks/grid/utils.tsx @@ -1,5 +1,267 @@ -import set from 'lodash/set'; +import React, { useContext, createContext, useState } from 'react'; +import { Schema, useFieldSchema, useForm } from '@formily/react'; import { uid } from '@formily/shared'; +import { SchemaField } from '../../fields'; +import set from 'lodash/set'; + +export const SchemaDesignerContext = createContext(new Schema({})); +export const SchemaRefreshContext = createContext(null); + +export function SchemaFieldWithDesigner(props) { + function Container(props) { + const { schema } = props; + const [, refresh] = useState(0); + return ( + { + refresh(Math.random()); + }} + > + + + + + ); + } + return ; +} + +export const getFullPaths = (schema: Schema) => { + if (!schema) { + return []; + } + const paths = [schema.name]; + if (schema.parent && schema.parent.name) { + paths.unshift(...getFullPaths(schema.parent)); + } + return paths; +}; + +export function useSchema() { + const context = useContext(SchemaDesignerContext); + const refresh = useContext(SchemaRefreshContext); + const fieldSchema = useFieldSchema(); + const paths = getFullPaths(fieldSchema); + let schema: Schema = context; + const names = [...paths]; + while (names.length) { + schema = schema.properties[names.shift()]; + } + return { schema, fieldSchema, refresh }; +} + +export function getSchema(context) { + return (paths) => { + const fullPaths = Array.isArray(paths) ? paths : getFullPaths(paths); + let s: Schema = context; + const names = [...fullPaths]; + while (names.length) { + s = s.properties[names.shift()]; + } + return s; + }; +} + +export function addPropertyBefore(target, prop) { + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(prop.name, prop); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + }); +} + +export function addPropertyAfter(target, prop) { + Object.keys(target.parent.properties).forEach((name) => { + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + target.parent.addProperty(prop.name, prop); + } + }); +} + +export function useSchemaQuery() { + const context = useContext(SchemaDesignerContext); + const form = useForm(); + const { schema, refresh } = useSchema(); + + return { + resizeColumn() { + function getPrevProperty() { + const names = Object.keys(schema.parent.properties); + const index = names.indexOf(schema.name as string); + return schema.parent.properties[names[index-1]]; + } + console.log('onMouseDown', schema.name); + console.log('onMouseDown', schema.parent.properties); + console.log('onMouseDown', getFullPaths(getPrevProperty())); + form.setFieldState(getFullPaths(getPrevProperty()).join('.'), (state) => { + const span = state.componentProps.span; + let width = state.componentProps.width||50; + width -= 1; + state.componentProps = { + span: 1*span - 1, + width, + style: { + flex: `0 0 ${width}%`, + maxWidth: `${width}%`, + }, + }; + }); + console.log('onMouseDown', getFullPaths(schema)); + form.setFieldState(getFullPaths(schema).join('.'), (state) => { + const span = state.componentProps.span; + console.log({span}) + let width = state.componentProps.width||50; + width += 1; + state.componentProps = { + span: 1*span + 1, + style: { + flex: `0 0 ${width}%`, + maxWidth: `${width}%`, + }, + }; + }); + refresh(); + }, + removeBlock() { + schema.parent.removeProperty(schema.name); + refresh(); + }, + addBlock: (data, up = false) => { + const blockSchema = block({ + type: 'string', + title: `Block ${uid()}`, + required: true, + 'x-read-pretty': false, + // 'x-decorator': 'FormItem', + 'x-component': 'Hello', + rowOrder: 2, + columnOrder: 1, + blockOrder: 1, + }); + if ('Grid.Column' === schema.parent['x-component']) { + if (Object.keys(schema.parent.parent.properties).length > 1) { + up + ? addPropertyBefore(schema, blockSchema) + : addPropertyAfter(schema, blockSchema); + } else { + const rowSchema = row(column(blockSchema)); + up + ? addPropertyAfter(schema.parent.parent, rowSchema) + : addPropertyAfter(schema.parent.parent, rowSchema); + } + } + console.log('x-component', schema.parent['x-component']); + refresh(); + // schema.parent['x-component'] + }, + insertAfter: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + if (!source || !target) { + return; + } + console.log({ + sourcePath, + source, + target, + targetPath, + sourceParentproperties: source.parent.properties, + }); + const names = []; + Object.keys(target.parent.properties).forEach((name) => { + // if (names.includes(source.name)) { + // return; + // } + // names.push(name); + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + // names.push(source.name); + source.parent.removeProperty(source.name); + console.log('source.parent.properties', source.parent.properties); + target.parent.addProperty(source.name, source.toJSON()); + } + }); + }, + insertAfterWithAddRow: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const rowSchema = row(column(source.toJSON())); + source.parent.removeProperty(source.name); + Object.keys(target.parent.properties).forEach((name) => { + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + if (name === target.name) { + target.parent.addProperty(rowSchema.name, rowSchema); + } + }); + }, + insertBeforeWithAddRow: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const rowSchema = row(column(source.toJSON())); + source.parent.removeProperty(source.name); + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(rowSchema.name, rowSchema); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + target.parent.addProperty(property.name, property.toJSON()); + }); + }, + appendToRowWithAddColumn: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const colSchema = column(source.toJSON()); + source.parent.removeProperty(source.name); + const len = Object.keys(target.properties).length + 1; + target.addProperty(colSchema.name, colSchema); + console.log('target.properties', target.properties); + Object.keys(target.properties).forEach((name) => { + const prop = target.properties[name]; + form.setFieldState(getFullPaths(prop).join('.'), (state) => { + state.componentProps = { + span: 24 / len, + }; + }); + }); + }, + insertBeforeWithAddColumn: (sourcePath, targetPath) => { + const source = getSchema(context)(sourcePath); + const target = getSchema(context)(targetPath); + const colSchema = column(source.toJSON()); + source.parent.removeProperty(source.name); + const len = Object.keys(target.parent.properties).length + 1; + console.log('x-component-props.span', 24 / len); + Object.keys(target.parent.properties).forEach((name) => { + if (name === target.name) { + target.parent.addProperty(colSchema.name, colSchema); + } + const property = target.parent.properties[name]; + property.parent.removeProperty(property.name); + const json = property.toJSON(); + target.parent.addProperty(property.name, json); + }); + Object.keys(target.parent.properties).forEach((name) => { + const prop = target.parent.properties[name]; + form.setFieldState(getFullPaths(prop).join('.'), (state) => { + state.componentProps = { + span: 24 / len, + }; + }); + }); + }, + }; +} export const grid = (...rows: any[]) => { const rowProperties = {}; @@ -59,6 +321,7 @@ export const block = (...fields: any[]) => { const properties = {}; fields.forEach((item) => { const name = item.name || `f_${uid()}`; + // item.title = `${item.title} ${name}`; properties[name] = item; }); const lastComponentType = fields[fields.length - 1]['x-component']; @@ -73,22 +336,3 @@ export const block = (...fields: any[]) => { properties, }; }; - -export function blocks2properties(blocks: any[]) { - const obj = { - rows: [], - }; - blocks.forEach(block => { - const path = ['rows', block['rowOrder'], block['columnOrder'], block['blockOrder']]; - console.log(path); - set(obj, path, block); - }); - return grid(...obj.rows.filter(Boolean).map((cols) => { - return row( - ...cols.filter(Boolean).map((items: any[]) => { - console.log({items: items.filter(Boolean)}) - return column(...items.filter(Boolean).map(item => block(item))); - }), - ); - })); -} diff --git a/packages/client/src/fields/index.tsx b/packages/client/src/fields/index.tsx index f37907126..6a815a66f 100644 --- a/packages/client/src/fields/index.tsx +++ b/packages/client/src/fields/index.tsx @@ -90,300 +90,6 @@ export const SchemaField = createSchemaField({ scope: fieldScope, }); -export const SchemaDesignerContext = createContext(new Schema({})); -export const SchemaRefreshContext = createContext(null); - -export function SchemaFieldWithDesigner(props) { - function Container(props) { - const { schema } = props; - const [, refresh] = useState(0); - return ( - { - refresh(Math.random()); - }} - > - - - - - ); - } - return ; -} - -export const getFullPaths = (schema: Schema) => { - if (!schema) { - return []; - } - const paths = [schema.name]; - if (schema.parent && schema.parent.name) { - paths.unshift(...getFullPaths(schema.parent)); - } - return paths; -}; - -export function useSchema() { - const context = useContext(SchemaDesignerContext); - const refresh = useContext(SchemaRefreshContext); - const fieldSchema = useFieldSchema(); - const paths = getFullPaths(fieldSchema); - let schema: Schema = context; - const names = [...paths]; - while (names.length) { - schema = schema.properties[names.shift()]; - } - return { schema, fieldSchema, refresh }; -} - -export function getSchema(context) { - return (paths) => { - const fullPaths = Array.isArray(paths) ? paths : getFullPaths(paths); - let s: Schema = context; - const names = [...fullPaths]; - while (names.length) { - s = s.properties[names.shift()]; - } - return s; - }; -} - -export function addPropertyBefore(target, prop) { - Object.keys(target.parent.properties).forEach((name) => { - if (name === target.name) { - target.parent.addProperty(prop.name, prop); - } - const property = target.parent.properties[name]; - property.parent.removeProperty(property.name); - target.parent.addProperty(property.name, property.toJSON()); - }); -} - -export function addPropertyAfter(target, prop) { - Object.keys(target.parent.properties).forEach((name) => { - const property = target.parent.properties[name]; - property.parent.removeProperty(property.name); - target.parent.addProperty(property.name, property.toJSON()); - if (name === target.name) { - target.parent.addProperty(prop.name, prop); - } - }); -} - -export function useSchemaQuery() { - const context = useContext(SchemaDesignerContext); - const form = useForm(); - const { schema, refresh } = useSchema(); - return { - removeBlock() { - schema.parent.removeProperty(schema.name); - refresh(); - }, - addBlock: (data, up = false) => { - const blockSchema = block({ - type: 'string', - title: `Block ${uid()}`, - required: true, - 'x-read-pretty': false, - // 'x-decorator': 'FormItem', - 'x-component': 'Hello', - rowOrder: 2, - columnOrder: 1, - blockOrder: 1, - }); - if ('Grid.Column' === schema.parent['x-component']) { - if (Object.keys(schema.parent.parent.properties).length > 1) { - up - ? addPropertyBefore(schema, blockSchema) - : addPropertyAfter(schema, blockSchema); - } else { - const rowSchema = row(column(blockSchema)); - up - ? addPropertyAfter(schema.parent.parent, rowSchema) - : addPropertyAfter(schema.parent.parent, rowSchema); - } - } - console.log('x-component', schema.parent['x-component']); - refresh(); - // schema.parent['x-component'] - }, - insertAfter: (sourcePath, targetPath) => { - const source = getSchema(context)(sourcePath); - const target = getSchema(context)(targetPath); - if (!source || !target) { - return; - } - console.log({ - sourcePath, - source, - target, - targetPath, - sourceParentproperties: source.parent.properties, - }); - const names = []; - Object.keys(target.parent.properties).forEach((name) => { - // if (names.includes(source.name)) { - // return; - // } - // names.push(name); - const property = target.parent.properties[name]; - property.parent.removeProperty(property.name); - target.parent.addProperty(property.name, property.toJSON()); - if (name === target.name) { - // names.push(source.name); - source.parent.removeProperty(source.name); - console.log('source.parent.properties', source.parent.properties); - target.parent.addProperty(source.name, source.toJSON()); - } - }); - }, - insertAfterWithAddRow: (sourcePath, targetPath) => { - const source = getSchema(context)(sourcePath); - const target = getSchema(context)(targetPath); - const rowSchema = row(column(source.toJSON())); - source.parent.removeProperty(source.name); - Object.keys(target.parent.properties).forEach((name) => { - const property = target.parent.properties[name]; - property.parent.removeProperty(property.name); - target.parent.addProperty(property.name, property.toJSON()); - if (name === target.name) { - target.parent.addProperty(rowSchema.name, rowSchema); - } - }); - }, - insertBeforeWithAddRow: (sourcePath, targetPath) => { - const source = getSchema(context)(sourcePath); - const target = getSchema(context)(targetPath); - const rowSchema = row(column(source.toJSON())); - source.parent.removeProperty(source.name); - Object.keys(target.parent.properties).forEach((name) => { - if (name === target.name) { - target.parent.addProperty(rowSchema.name, rowSchema); - } - const property = target.parent.properties[name]; - property.parent.removeProperty(property.name); - target.parent.addProperty(property.name, property.toJSON()); - }); - }, - appendToRowWithAddColumn: (sourcePath, targetPath) => { - const source = getSchema(context)(sourcePath); - const target = getSchema(context)(targetPath); - const colSchema = column(source.toJSON()); - source.parent.removeProperty(source.name); - const len = Object.keys(target.properties).length + 1; - target.addProperty(colSchema.name, colSchema); - console.log('target.properties', target.properties); - Object.keys(target.properties).forEach((name) => { - const prop = target.properties[name]; - form.setFieldState(getFullPaths(prop).join('.'), (state) => { - state.componentProps = { - span: 24 / len, - }; - }); - }); - }, - insertBeforeWithAddColumn: (sourcePath, targetPath) => { - const source = getSchema(context)(sourcePath); - const target = getSchema(context)(targetPath); - const colSchema = column(source.toJSON()); - source.parent.removeProperty(source.name); - const len = Object.keys(target.parent.properties).length + 1; - console.log('x-component-props.span', 24 / len); - Object.keys(target.parent.properties).forEach((name) => { - if (name === target.name) { - target.parent.addProperty(colSchema.name, colSchema); - } - const property = target.parent.properties[name]; - property.parent.removeProperty(property.name); - const json = property.toJSON(); - target.parent.addProperty(property.name, json); - }); - Object.keys(target.parent.properties).forEach((name) => { - const prop = target.parent.properties[name]; - form.setFieldState(getFullPaths(prop).join('.'), (state) => { - state.componentProps = { - span: 24 / len, - }; - }); - }); - }, - }; -} - -export const grid = (...rows: any[]) => { - const rowProperties = {}; - rows.forEach((row, index) => { - set(row, 'x-component-props.rowOrder', index); - rowProperties[row.name] = row; - }); - const name = `g_${uid()}`; - return { - type: 'void', - name, - 'x-component': 'Grid', - 'x-component-props': {}, - properties: rowProperties, - }; -}; - -export const row = (...cols: any[]) => { - const rowName = `r_${uid()}`; - const colsProperties = {}; - cols.forEach((col, index) => { - set(col, 'x-component-props.columnOrder', index); - set(col, 'x-component-props.span', 24 / cols.length); - colsProperties[col.name] = col; - }); - return { - type: 'void', - name: rowName, - 'x-component': 'Grid.Row', - 'x-component-props': {}, - properties: colsProperties, - }; -}; - -export const column = (...blocks: any[]) => { - const colName = `c_${uid()}`; - const properties = {}; - blocks.forEach((item) => { - properties[item.name] = item; - }); - return { - name: colName, - type: 'void', - 'x-component': 'Grid.Column', - 'x-read-pretty': true, - 'x-component-props': { - labelCol: 6, - wrapperCol: 10, - span: 24, - }, - properties, - }; -}; - -export const block = (...fields: any[]) => { - const blockName = `b_${uid()}`; - const properties = {}; - fields.forEach((item) => { - const name = item.name || `f_${uid()}`; - // item.title = `${item.title} ${name}`; - properties[name] = item; - }); - const lastComponentType = fields[fields.length - 1]['x-component']; - return { - name: blockName, - type: 'void', - 'x-component': 'Grid.Block', - 'x-component-props': { - lastComponentType, - }, - 'x-read-pretty': false, - properties, - }; -}; - export function parseEffects(effects: any, form?: any) { if (!effects) { return;