feat: change cursor when drag
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
|||||||
type ReturnUnobserve,
|
type ReturnUnobserve,
|
||||||
} from '@toeverything/datasource/db-service';
|
} from '@toeverything/datasource/db-service';
|
||||||
import { addNewGroup } from './recast-block';
|
import { addNewGroup } from './recast-block';
|
||||||
import { HookType } from './editor';
|
import { useIsOnDrag } from '.';
|
||||||
|
|
||||||
interface RenderRootProps {
|
interface RenderRootProps {
|
||||||
editor: BlockEditor;
|
editor: BlockEditor;
|
||||||
@@ -35,6 +35,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
const triggeredBySelect = useRef(false);
|
const triggeredBySelect = useRef(false);
|
||||||
const [container, setContainer] = useState<HTMLDivElement>();
|
const [container, setContainer] = useState<HTMLDivElement>();
|
||||||
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
|
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
|
||||||
|
const isOnDrag = useIsOnDrag(editor);
|
||||||
|
|
||||||
const { patch, has, patchedNodes } = usePatchNodes();
|
const { patch, has, patchedNodes } = usePatchNodes();
|
||||||
|
|
||||||
@@ -147,6 +148,8 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDragOver = (event: React.DragEvent<Element>) => {
|
const onDragOver = (event: React.DragEvent<Element>) => {
|
||||||
|
event.dataTransfer.dropEffect = 'move';
|
||||||
|
event.preventDefault();
|
||||||
if (!contentRef.current) {
|
if (!contentRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -155,10 +158,10 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
if (editor.dragDropManager.isEnabled()) {
|
if (editor.dragDropManager.isEnabled()) {
|
||||||
editor.getHooks().onRootNodeDragOver(event, rootRect);
|
editor.getHooks().onRootNodeDragOver(event, rootRect);
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragOverCapture = (event: React.DragEvent<Element>) => {
|
const onDragOverCapture = (event: React.DragEvent<Element>) => {
|
||||||
|
event.preventDefault();
|
||||||
if (!contentRef.current) {
|
if (!contentRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -202,7 +205,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
onDragOverCapture={onDragOverCapture}
|
onDragOverCapture={onDragOverCapture}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onScroll={onScroll}
|
isOnDrag={isOnDrag}
|
||||||
>
|
>
|
||||||
<Content
|
<Content
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
@@ -278,13 +281,25 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled('div')(
|
const Container = styled('div')(
|
||||||
({ isWhiteboard }: { isWhiteboard: boolean }) => ({
|
({
|
||||||
|
isWhiteboard,
|
||||||
|
isOnDrag,
|
||||||
|
}: {
|
||||||
|
isWhiteboard: boolean;
|
||||||
|
isOnDrag: boolean;
|
||||||
|
}) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
overflowY: isWhiteboard ? 'unset' : 'auto',
|
overflowY: isWhiteboard ? 'unset' : 'auto',
|
||||||
padding: isWhiteboard ? 0 : '96px 150px 0 150px',
|
padding: isWhiteboard ? 0 : '96px 150px 0 150px',
|
||||||
minWidth: isWhiteboard ? 'unset' : '940px',
|
minWidth: isWhiteboard ? 'unset' : '940px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
...(isOnDrag && {
|
||||||
|
cursor: 'grabbing',
|
||||||
|
// expected css selector
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'& *': { cursor: 'grabbing' },
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export function DragDropWrapper({
|
|||||||
const handler_drag_over: React.DragEventHandler<
|
const handler_drag_over: React.DragEventHandler<
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
> = async event => {
|
> = async event => {
|
||||||
|
event.preventDefault();
|
||||||
const rootDom = await editor.getBlockDomById(editor.getRootBlockId());
|
const rootDom = await editor.getBlockDomById(editor.getRootBlockId());
|
||||||
|
|
||||||
if (block.dom && rootDom) {
|
if (block.dom && rootDom) {
|
||||||
editor.getHooks().afterOnNodeDragOver(event, {
|
editor.getHooks().afterOnNodeDragOver(event, {
|
||||||
blockId: block.id,
|
blockId: block.id,
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import EventEmitter from 'eventemitter3';
|
|||||||
type DargType =
|
type DargType =
|
||||||
| ValueOf<InstanceType<typeof DragDropManager>['dragActions']>
|
| ValueOf<InstanceType<typeof DragDropManager>['dragActions']>
|
||||||
| '';
|
| '';
|
||||||
|
|
||||||
|
const DRAG_STATE_CHANGE_EVENT_KEY = 'dragStateChange';
|
||||||
export class DragDropManager {
|
export class DragDropManager {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
private _enabled: boolean;
|
private _enabled: boolean;
|
||||||
|
private _events = new EventEmitter();
|
||||||
|
|
||||||
private _blockIdKey = 'blockId';
|
private _blockIdKey = 'blockId';
|
||||||
private _rootIdKey = 'rootId';
|
private _rootIdKey = 'rootId';
|
||||||
@@ -26,15 +29,29 @@ export class DragDropManager {
|
|||||||
dragGroup: 'dragGroup',
|
dragGroup: 'dragGroup',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
private _isOnDrag = false;
|
||||||
|
|
||||||
get dragActions() {
|
get dragActions() {
|
||||||
return this._dragActions;
|
return this._dragActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isOnDrag() {
|
||||||
|
return this._isOnDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
set isOnDrag(isOnDrag: boolean) {
|
||||||
|
if (this._isOnDrag !== isOnDrag) {
|
||||||
|
this._events.emit(DRAG_STATE_CHANGE_EVENT_KEY, isOnDrag);
|
||||||
|
}
|
||||||
|
this._isOnDrag = isOnDrag;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(editor: Editor) {
|
constructor(editor: Editor) {
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
this._enabled = true;
|
this._enabled = true;
|
||||||
this._dragType = '';
|
this._dragType = '';
|
||||||
this._blockDragDirection = BlockDropPlacement.none;
|
this._blockDragDirection = BlockDropPlacement.none;
|
||||||
|
this._initMouseEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
get dragType() {
|
get dragType() {
|
||||||
@@ -45,6 +62,15 @@ export class DragDropManager {
|
|||||||
this._dragType = type;
|
this._dragType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _initMouseEvent() {
|
||||||
|
this._editor.mouseManager.onMouseUp(() => {
|
||||||
|
// IMP: temporarily last protect for dragging across editor instances,bad case
|
||||||
|
if (this.isOnDrag) {
|
||||||
|
this.isOnDrag = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _setBlockDragDirection(direction: BlockDropPlacement) {
|
private _setBlockDragDirection(direction: BlockDropPlacement) {
|
||||||
this._blockDragDirection = direction;
|
this._blockDragDirection = direction;
|
||||||
}
|
}
|
||||||
@@ -244,6 +270,9 @@ export class DragDropManager {
|
|||||||
|
|
||||||
public handlerEditorDragEnd(event: React.DragEvent<Element>) {
|
public handlerEditorDragEnd(event: React.DragEvent<Element>) {
|
||||||
this._resetDragDropData();
|
this._resetDragDropData();
|
||||||
|
if (this.isOnDrag) {
|
||||||
|
this.isOnDrag = false;
|
||||||
|
}
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
// TODO: handle drag end event flow
|
// TODO: handle drag end event flow
|
||||||
}
|
}
|
||||||
@@ -287,4 +316,16 @@ export class DragDropManager {
|
|||||||
}
|
}
|
||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onDragStateChange(cb: (isOnDrag: boolean) => void) {
|
||||||
|
this._events.on(DRAG_STATE_CHANGE_EVENT_KEY, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public offDragStateChange(cb: (isOnDrag: boolean) => void) {
|
||||||
|
this._events.off(DRAG_STATE_CHANGE_EVENT_KEY, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
this._events.removeAllListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { AsyncBlock } from './block';
|
|||||||
import type { BlockHelper } from './block/block-helper';
|
import type { BlockHelper } from './block/block-helper';
|
||||||
import type { BlockCommands } from './commands/block-commands';
|
import type { BlockCommands } from './commands/block-commands';
|
||||||
import type { DragDropManager } from './drag-drop';
|
import type { DragDropManager } from './drag-drop';
|
||||||
|
import { MouseManager } from './mouse';
|
||||||
|
|
||||||
// import { BrowserClipboard } from './clipboard/browser-clipboard';
|
// import { BrowserClipboard } from './clipboard/browser-clipboard';
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ export interface Virgo {
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
getBlockDomById: (id: string) => Promise<HTMLElement>;
|
getBlockDomById: (id: string) => Promise<HTMLElement>;
|
||||||
isWhiteboard: boolean;
|
isWhiteboard: boolean;
|
||||||
|
mouseManager: MouseManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Plugin {
|
export interface Plugin {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
|
BlockEditor,
|
||||||
SelectEventTypes,
|
SelectEventTypes,
|
||||||
SelectionInfo,
|
SelectionInfo,
|
||||||
SelectionSettingsMap,
|
SelectionSettingsMap,
|
||||||
@@ -202,3 +203,21 @@ export const useOnSelectStartWith = (
|
|||||||
};
|
};
|
||||||
}, [editor, cb, blockId]);
|
}, [editor, cb, blockId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* hooks run when drag state change
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const useIsOnDrag = (editor: BlockEditor) => {
|
||||||
|
const editorInstance = editor;
|
||||||
|
const [isOnDrag, setIsOnDrag] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const callback = (isDrag: boolean) => setIsOnDrag(isDrag);
|
||||||
|
editorInstance.dragDropManager.onDragStateChange(callback);
|
||||||
|
return () => {
|
||||||
|
editorInstance.dragDropManager.offDragStateChange(callback);
|
||||||
|
};
|
||||||
|
}, [editorInstance]);
|
||||||
|
return isOnDrag;
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type DragItemProps = {
|
|||||||
isShow: boolean;
|
isShow: boolean;
|
||||||
groupBlock: AsyncBlock;
|
groupBlock: AsyncBlock;
|
||||||
editor: Virgo;
|
editor: Virgo;
|
||||||
|
item: React.MutableRefObject<HTMLDivElement>;
|
||||||
onPositionChange?: (position: Point) => void;
|
onPositionChange?: (position: Point) => void;
|
||||||
} & React.HTMLAttributes<HTMLDivElement>;
|
} & React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
@@ -17,10 +18,11 @@ export const DragItem = function ({
|
|||||||
isShow,
|
isShow,
|
||||||
editor,
|
editor,
|
||||||
groupBlock,
|
groupBlock,
|
||||||
|
item,
|
||||||
...divProps
|
...divProps
|
||||||
}: DragItemProps) {
|
}: DragItemProps) {
|
||||||
return (
|
return (
|
||||||
<StyledDiv {...divProps}>
|
<StyledDiv {...divProps} ref={item}>
|
||||||
<StyledButton>
|
<StyledButton>
|
||||||
<HandleParentIcon />
|
<HandleParentIcon />
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
@@ -33,13 +35,14 @@ const StyledDiv = styled('div')({
|
|||||||
display: 'inlineFlex',
|
display: 'inlineFlex',
|
||||||
width: `${ICON_WIDTH}px`,
|
width: `${ICON_WIDTH}px`,
|
||||||
height: `${ICON_WIDTH}px`,
|
height: `${ICON_WIDTH}px`,
|
||||||
|
cursor: 'grab',
|
||||||
':hover': {
|
':hover': {
|
||||||
backgroundColor: '#edeef0',
|
backgroundColor: '#edeef0',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledButton = styled(Button)({
|
const StyledButton = styled('div')({
|
||||||
padding: '0',
|
padding: '0',
|
||||||
display: 'inlineFlex',
|
display: 'inlineFlex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
const [direction, setDirection] = useState<GroupDirection>(
|
const [direction, setDirection] = useState<GroupDirection>(
|
||||||
GroupDirection.down
|
GroupDirection.down
|
||||||
);
|
);
|
||||||
|
const dragItemRef = useRef<HTMLDivElement>(null);
|
||||||
const menuRef = useRef<HTMLUListElement>(null);
|
const menuRef = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
const handleRootMouseMove = useCallback(
|
const handleRootMouseMove = useCallback(
|
||||||
@@ -50,6 +51,7 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
|
|
||||||
const handleRootDragOver = useCallback(
|
const handleRootDragOver = useCallback(
|
||||||
async (e: React.DragEvent<Element>) => {
|
async (e: React.DragEvent<Element>) => {
|
||||||
|
e.preventDefault();
|
||||||
let groupBlockOnDragOver = null;
|
let groupBlockOnDragOver = null;
|
||||||
const mousePoint = new Point(e.clientX, e.clientY);
|
const mousePoint = new Point(e.clientX, e.clientY);
|
||||||
if (editor.dragDropManager.isDragGroup(e)) {
|
if (editor.dragDropManager.isDragGroup(e)) {
|
||||||
@@ -89,7 +91,7 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
[editor, groupBlock]
|
[editor, groupBlock]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRootDragEnd = () => {
|
const handleRootDragEnd = (e: DragEvent) => {
|
||||||
setDragOverGroup(null);
|
setDragOverGroup(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,13 +141,16 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDragStart = async (e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragStart = async (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
editor.dragDropManager.isOnDrag = true;
|
||||||
const dragImage = await editor.blockHelper.getBlockDragImg(
|
const dragImage = await editor.blockHelper.getBlockDragImg(
|
||||||
groupBlock.id
|
groupBlock.id
|
||||||
);
|
);
|
||||||
if (dragImage) {
|
if (dragImage) {
|
||||||
e.dataTransfer.setDragImage(dragImage, 0, 0);
|
|
||||||
editor.dragDropManager.setDragGroupInfo(e, groupBlock.id);
|
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>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
@@ -169,11 +174,15 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
menuRef={menuRef}
|
menuRef={menuRef}
|
||||||
>
|
>
|
||||||
<DragItem
|
<DragItem
|
||||||
|
item={dragItemRef}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
isShow={!!groupBlock}
|
isShow={!!groupBlock}
|
||||||
groupBlock={groupBlock}
|
groupBlock={groupBlock}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
|
onDragCapture={() => {
|
||||||
|
editor.dragDropManager.isOnDrag = true;
|
||||||
|
}}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ export class GroupMenuPlugin extends BasePlugin {
|
|||||||
|
|
||||||
protected override on_render(): void {
|
protected override on_render(): void {
|
||||||
if (this.editor.isWhiteboard) return;
|
if (this.editor.isWhiteboard) return;
|
||||||
const container = document.createElement('div');
|
|
||||||
// TODO remove
|
|
||||||
container.classList.add(`id-${PLUGIN_NAME}`);
|
|
||||||
this.root = new PluginRenderRoot({
|
this.root = new PluginRenderRoot({
|
||||||
name: PLUGIN_NAME,
|
name: PLUGIN_NAME,
|
||||||
render: this.editor.reactRenderRoot.render,
|
render: this.editor.reactRenderRoot.render,
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ export const LeftMenuDraggable: FC<LeftMenuProps> = props => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onDragStart = async (event: React.DragEvent<Element>) => {
|
const onDragStart = async (event: React.DragEvent<Element>) => {
|
||||||
|
editor.dragDropManager.isOnDrag = true;
|
||||||
if (block == null) return;
|
if (block == null) return;
|
||||||
const dragImage = await editor.blockHelper.getBlockDragImg(
|
const dragImage = await editor.blockHelper.getBlockDragImg(
|
||||||
block.blockId
|
block.blockId
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export class LeftMenuPlugin extends BasePlugin {
|
|||||||
blockInfo: BlockDomInfo
|
blockInfo: BlockDomInfo
|
||||||
) => {
|
) => {
|
||||||
const { type, dom, blockId } = blockInfo;
|
const { type, dom, blockId } = blockInfo;
|
||||||
|
event.preventDefault();
|
||||||
if (this.editor.dragDropManager.isDragBlock(event)) {
|
if (this.editor.dragDropManager.isDragBlock(event)) {
|
||||||
event.preventDefault();
|
|
||||||
if (ignoreBlockTypes.includes(type)) {
|
if (ignoreBlockTypes.includes(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user