feat: change cursor when drag

This commit is contained in:
SaikaSakura
2022-07-26 18:49:18 +08:00
parent 9cc55d7f63
commit 8b8529c1be
10 changed files with 100 additions and 13 deletions

View File

@@ -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' },
}),
}) })
); );

View File

@@ -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,

View File

@@ -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();
}
} }

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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',

View File

@@ -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}
/> />

View File

@@ -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,

View File

@@ -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

View File

@@ -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;
} }