214 lines
6.6 KiB
TypeScript
214 lines
6.6 KiB
TypeScript
import {
|
|
AsyncBlock,
|
|
HookType,
|
|
PluginHooks,
|
|
Virgo,
|
|
} from '@toeverything/components/editor-core';
|
|
import { Point } from '@toeverything/utils';
|
|
import { GroupDirection } from '@toeverything/framework/virgo';
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { DragItem } from './DragItem';
|
|
import { Line } from './Line';
|
|
import { Menu } from './Menu';
|
|
|
|
type GroupMenuProps = {
|
|
editor?: Virgo;
|
|
hooks: PluginHooks;
|
|
};
|
|
export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|
const [groupBlock, setGroupBlock] = useState<AsyncBlock | null>(null);
|
|
const [showMenu, setShowMenu] = useState(false);
|
|
const [position, setPosition] = useState<Point>(new Point(0, 0));
|
|
const [dragOverGroup, setDragOverGroup] = useState<AsyncBlock | null>(null);
|
|
const [direction, setDirection] = useState<GroupDirection>(
|
|
GroupDirection.down
|
|
);
|
|
const dragItemRef = useRef<HTMLDivElement>(null);
|
|
const menuRef = useRef<HTMLUListElement>(null);
|
|
|
|
const handleRootMouseMove = useCallback(
|
|
async (e: React.MouseEvent<HTMLDivElement>) => {
|
|
const groupBlockNew = await editor.getGroupBlockByPoint(
|
|
new Point(e.clientX, e.clientY)
|
|
);
|
|
if (groupBlockNew) {
|
|
setGroupBlock(groupBlockNew);
|
|
}
|
|
},
|
|
[editor, setGroupBlock]
|
|
);
|
|
|
|
const handleRootMouseDown = useCallback(
|
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
if (
|
|
menuRef.current &&
|
|
!menuRef.current.contains(e.target as Node)
|
|
) {
|
|
setShowMenu(false);
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
const handleRootDragOver = useCallback(
|
|
async (e: React.DragEvent<Element>) => {
|
|
e.preventDefault();
|
|
let groupBlockOnDragOver = null;
|
|
const mousePoint = new Point(e.clientX, e.clientY);
|
|
if (editor.dragDropManager.isDragGroup(e)) {
|
|
groupBlockOnDragOver = await editor.getGroupBlockByPoint(
|
|
mousePoint
|
|
);
|
|
if (groupBlockOnDragOver?.id === groupBlock?.id) {
|
|
groupBlockOnDragOver = null;
|
|
}
|
|
}
|
|
setDragOverGroup(groupBlockOnDragOver || null);
|
|
const direction =
|
|
await editor.dragDropManager.checkDragGroupDirection(
|
|
groupBlock,
|
|
groupBlockOnDragOver,
|
|
mousePoint
|
|
);
|
|
setDirection(direction);
|
|
},
|
|
[editor, groupBlock]
|
|
);
|
|
|
|
const handleRootDrop = useCallback(
|
|
async (e: React.DragEvent<Element>) => {
|
|
let groupBlockOnDrop = null;
|
|
if (editor.dragDropManager.isDragGroup(e)) {
|
|
groupBlockOnDrop = await editor.getGroupBlockByPoint(
|
|
new Point(e.clientX, e.clientY)
|
|
);
|
|
if (groupBlockOnDrop?.id === groupBlock?.id) {
|
|
groupBlockOnDrop = null;
|
|
}
|
|
}
|
|
},
|
|
[editor, groupBlock]
|
|
);
|
|
|
|
const handleRootMouseLeave = useCallback(() => setGroupBlock(null), []);
|
|
|
|
const handleRootDragEnd = () => {
|
|
setDragOverGroup(null);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const sub = hooks
|
|
.get(HookType.ON_ROOTNODE_MOUSE_MOVE)
|
|
.subscribe(handleRootMouseMove);
|
|
sub.add(
|
|
hooks
|
|
.get(HookType.ON_ROOTNODE_MOUSE_DOWN)
|
|
.subscribe(handleRootMouseDown)
|
|
);
|
|
sub.add(
|
|
hooks
|
|
.get(HookType.ON_ROOTNODE_DRAG_OVER)
|
|
.subscribe(handleRootDragOver)
|
|
);
|
|
sub.add(
|
|
hooks
|
|
.get(HookType.ON_ROOTNODE_DRAG_END)
|
|
.subscribe(handleRootDragEnd)
|
|
);
|
|
sub.add(
|
|
hooks
|
|
.get(HookType.ON_ROOTNODE_MOUSE_LEAVE)
|
|
.subscribe(handleRootMouseLeave)
|
|
);
|
|
return () => {
|
|
sub.unsubscribe();
|
|
};
|
|
}, [
|
|
hooks,
|
|
handleRootMouseMove,
|
|
handleRootMouseDown,
|
|
handleRootDragOver,
|
|
handleRootDrop,
|
|
handleRootMouseLeave,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (groupBlock && groupBlock.dom) {
|
|
if (editor.container) {
|
|
setPosition(
|
|
new Point(
|
|
groupBlock.dom.offsetLeft,
|
|
groupBlock.dom.offsetTop
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}, [groupBlock, editor]);
|
|
|
|
const handleClick = () => {
|
|
setShowMenu(!showMenu);
|
|
};
|
|
|
|
const handleDragStart = async (e: React.DragEvent<HTMLDivElement>) => {
|
|
editor.dragDropManager.isOnDrag = true;
|
|
const dragImage = await editor.blockHelper.getBlockDragImg(
|
|
groupBlock.id
|
|
);
|
|
if (dragImage) {
|
|
editor.dragDropManager.setDragGroupInfo(e, groupBlock.id);
|
|
dragImage.style.cursor = 'grabbing !important';
|
|
e.dataTransfer.setDragImage(dragImage, 0, 0);
|
|
}
|
|
e.dataTransfer.setData('text/plain', groupBlock.id);
|
|
};
|
|
|
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
e.stopPropagation();
|
|
};
|
|
|
|
useEffect(() => {
|
|
setShowMenu(false);
|
|
|
|
if (groupBlock) {
|
|
const unobserve = groupBlock.onUpdate(() => setGroupBlock(null));
|
|
return unobserve;
|
|
}
|
|
return undefined;
|
|
}, [groupBlock]);
|
|
|
|
return (
|
|
<>
|
|
{groupBlock ? (
|
|
<Menu
|
|
editor={editor}
|
|
groupBlock={groupBlock}
|
|
position={position}
|
|
visible={showMenu}
|
|
setVisible={setShowMenu}
|
|
setGroupBlock={setGroupBlock}
|
|
menuRef={menuRef}
|
|
>
|
|
<DragItem
|
|
item={dragItemRef}
|
|
editor={editor}
|
|
isShow={!!groupBlock}
|
|
groupBlock={groupBlock}
|
|
onClick={handleClick}
|
|
onDragStart={handleDragStart}
|
|
onDragCapture={() => {
|
|
editor.dragDropManager.isOnDrag = true;
|
|
}}
|
|
onMouseDown={handleMouseDown}
|
|
draggable={true}
|
|
/>
|
|
</Menu>
|
|
) : null}
|
|
<Line
|
|
groupBlock={dragOverGroup}
|
|
editor={editor}
|
|
direction={direction}
|
|
/>
|
|
</>
|
|
);
|
|
};
|