import { useState, useEffect, FC } from 'react'; import { Virgo, BlockDomInfo, HookType, PluginHooks, BlockDropPlacement, } from '@toeverything/framework/virgo'; import { Button } from '@toeverything/components/common'; import { styled } from '@toeverything/components/ui'; import { LeftMenu } from './LeftMenu'; import { debounce } from '@toeverything/utils'; import type { Subject } from 'rxjs'; import { HandleChildIcon } from '@toeverything/components/icons'; import { MENU_WIDTH } from './menu-config'; const MENU_BUTTON_OFFSET = 12; export type LineInfoSubject = Subject< | { direction: BlockDropPlacement; blockInfo: BlockDomInfo; } | undefined >; export type LeftMenuProps = { editor: Virgo; hooks: PluginHooks; defaultVisible?: boolean; blockInfo: Subject; lineInfo: LineInfoSubject; }; type LineInfo = { direction: BlockDropPlacement; blockInfo: BlockDomInfo; }; function Line(props: { lineInfo: LineInfo }) { const { lineInfo } = props; if (!lineInfo || lineInfo.direction === BlockDropPlacement.none) { return null; } const { direction, blockInfo } = lineInfo; const finalDirection = direction; const lineStyle = { zIndex: 2, position: 'absolute' as const, background: '#502EC4', }; const intersectionRect = blockInfo.rect; const horizontalLineStyle = { ...lineStyle, width: intersectionRect.width, height: 2, left: intersectionRect.x - blockInfo.rootRect.x, }; const topLineStyle = { ...horizontalLineStyle, top: intersectionRect.top, }; const bottomLineStyle = { ...horizontalLineStyle, top: intersectionRect.bottom + 1 - blockInfo.rootRect.y, }; const verticalLineStyle = { ...lineStyle, width: 2, height: intersectionRect.height, top: intersectionRect.y - blockInfo.rootRect.y, }; const leftLineStyle = { ...verticalLineStyle, left: intersectionRect.x - 10 - blockInfo.rootRect.x, }; const rightLineStyle = { ...verticalLineStyle, left: intersectionRect.right + 10 - blockInfo.rootRect.x, }; const styleMap = { left: leftLineStyle, right: rightLineStyle, top: topLineStyle, bottom: bottomLineStyle, }; return (
); } function DragComponent(props: { children: React.ReactNode; style: React.CSSProperties; onDragStart: (event: React.DragEvent) => void; onDragEnd: (event: React.DragEvent) => void; }) { const { style, children, onDragStart, onDragEnd } = props; return ( event.stopPropagation()} onMouseDown={event => event.stopPropagation()} onDragStart={onDragStart} onDragEnd={onDragEnd} > {children} ); } export const LeftMenuDraggable: FC = props => { const { editor, blockInfo, defaultVisible, hooks, lineInfo } = props; const [visible, setVisible] = useState(defaultVisible); const [anchorEl, setAnchorEl] = useState(); const [block, setBlock] = useState(); const [line, setLine] = useState(undefined); const handleDragStart = (event: React.DragEvent) => { window.addEventListener('dragover', handleDragOverCapture, { capture: true, }); hooks.addHook( HookType.ON_ROOTNODE_DRAG_OVER_CAPTURE, handleDragOverCapture ); const onDragStart = async (event: React.DragEvent) => { if (block == null) return; const dragImage = await editor.blockHelper.getBlockDragImg( block.blockId ); if (dragImage) { event.dataTransfer.setDragImage(dragImage, -50, -10); editor.dragDropManager.setDragBlockInfo(event, block.blockId); } setVisible(false); }; onDragStart(event); event.stopPropagation(); }; const handleDragEnd = (event: React.DragEvent) => { event.preventDefault(); window.removeEventListener('dragover', handleDragOverCapture, { capture: true, }); setLine(undefined); }; const onClick = (event: React.MouseEvent) => { if (block == null) return; const currentTarget = event.currentTarget; editor.selection.setSelectedNodesIds([block.blockId]); setVisible(true); setAnchorEl(currentTarget); }; /** * clear line info */ const handleDragOverCapture = debounce((e: MouseEvent) => { const { target } = e; if ( target instanceof HTMLElement && (!target.closest('[data-block-id]') || !editor.container.contains(target)) ) { setLine(undefined); } }, 10); useEffect(() => { const sub = blockInfo.subscribe(block => { setBlock(block); if (block != null) { setVisible(true); } }); return () => sub.unsubscribe(); }, [blockInfo, editor]); useEffect(() => { const sub = lineInfo.subscribe(data => { if (data == null) { setLine(undefined); } else { const { direction, blockInfo } = data; setLine(prev => { if ( prev?.blockInfo.blockId !== blockInfo.blockId || prev?.direction !== direction ) { return { direction, blockInfo, }; } else { return prev; } }); } }); return () => sub.unsubscribe(); }, [editor.dragDropManager, lineInfo]); return ( <> {block && ( { setAnchorEl(undefined)} blockId={block.blockId} > } )} ); }; const Draggable = styled(Button)({ cursor: 'grab', padding: '0', display: 'inlineFlex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'transparent', width: '24px', height: '24px', ':hover': { backgroundColor: '#edeef0', borderRadius: '4px', }, }); const LigoLeftMenu = styled('div')({ backgroundColor: 'transparent', marginRight: '4px', });