init: the first public commit for AFFiNE

This commit is contained in:
DarkSky
2022-07-22 15:49:21 +08:00
commit e3e3741393
1451 changed files with 108124 additions and 0 deletions
@@ -0,0 +1,50 @@
import { Button } from '@toeverything/components/common';
import { AsyncBlock, Virgo } from '@toeverything/components/editor-core';
import { HandleParentIcon } from '@toeverything/components/icons';
import { styled } from '@toeverything/components/ui';
import { Point } from '@toeverything/utils';
export const ICON_WIDTH = 24;
type DragItemProps = {
isShow: boolean;
groupBlock: AsyncBlock;
editor: Virgo;
onPositionChange?: (position: Point) => void;
} & React.HTMLAttributes<HTMLDivElement>;
export const DragItem = function ({
isShow,
editor,
groupBlock,
...divProps
}: DragItemProps) {
return (
<StyledDiv {...divProps}>
<StyledButton>
<HandleParentIcon />
</StyledButton>
</StyledDiv>
);
};
const StyledDiv = styled('div')({
padding: '0',
display: 'inlineFlex',
width: `${ICON_WIDTH}px`,
height: `${ICON_WIDTH}px`,
':hover': {
backgroundColor: '#edeef0',
borderRadius: '4px',
},
});
const StyledButton = styled(Button)({
padding: '0',
display: 'inlineFlex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent',
width: '100%',
height: '100%',
});
@@ -0,0 +1,189 @@
import {
AsyncBlock,
HookType,
PluginHooks,
Virgo,
} from '@toeverything/components/editor-core';
import { domToRect, 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 menuRef = useRef<HTMLUListElement>(null);
const handleRootMouseMove = useCallback(
async (e: React.MouseEvent<HTMLDivElement>) => {
const groupBlockNew =
await editor.dragDropManager.getGroupBlockByPoint(
new Point(e.clientX, e.clientY)
);
groupBlockNew && setGroupBlock(groupBlockNew || null);
},
[editor, setGroupBlock]
);
const handleRootMouseDown = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (
menuRef.current &&
!menuRef.current.contains(e.target as Node)
) {
setShowMenu(false);
}
},
[setShowMenu]
);
const handleRootDragOver = useCallback(
async (e: React.DragEvent<Element>) => {
let groupBlockOnDragOver = null;
const mousePoint = new Point(e.clientX, e.clientY);
if (editor.dragDropManager.isDragGroup(e)) {
groupBlockOnDragOver =
await editor.dragDropManager.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.dragDropManager.getGroupBlockByPoint(
new Point(e.clientX, e.clientY)
);
if (groupBlockOnDrop?.id === groupBlock?.id) {
groupBlockOnDrop = null;
}
}
},
[editor, groupBlock]
);
const handleRootDragEnd = () => {
setDragOverGroup(null);
};
useEffect(() => {
hooks.addHook(HookType.ON_ROOTNODE_MOUSE_MOVE, handleRootMouseMove);
hooks.addHook(HookType.ON_ROOTNODE_MOUSE_DOWN, handleRootMouseDown);
hooks.addHook(HookType.ON_ROOTNODE_DRAG_OVER, handleRootDragOver);
hooks.addHook(HookType.ON_ROOTNODE_DRAG_END, handleRootDragEnd);
return () => {
hooks.removeHook(
HookType.ON_ROOTNODE_MOUSE_MOVE,
handleRootMouseMove
);
hooks.removeHook(
HookType.ON_ROOTNODE_MOUSE_DOWN,
handleRootMouseDown
);
hooks.removeHook(
HookType.ON_ROOTNODE_DRAG_OVER,
handleRootDragOver
);
hooks.removeHook(HookType.ON_ROOTNODE_DRAG_END, handleRootDragEnd);
};
}, [
hooks,
handleRootMouseMove,
handleRootMouseDown,
handleRootDragOver,
handleRootDrop,
]);
useEffect(() => {
if (groupBlock && groupBlock.dom) {
if (editor.container) {
setPosition(
new Point(
groupBlock.dom.offsetLeft - editor.container.offsetLeft,
groupBlock.dom.offsetTop - editor.container.offsetTop
)
);
}
}
}, [groupBlock, editor]);
const handleClick = () => {
setShowMenu(!showMenu);
};
const handleDragStart = async (e: React.DragEvent<HTMLDivElement>) => {
const dragImage = await editor.blockHelper.getBlockDragImg(
groupBlock.id
);
if (dragImage) {
e.dataTransfer.setDragImage(dragImage, 0, 0);
editor.dragDropManager.setDragGroupInfo(e, groupBlock.id);
}
};
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
};
useEffect(() => {
setShowMenu(false);
}, [groupBlock]);
return (
<>
{groupBlock ? (
<Menu
editor={editor}
groupBlock={groupBlock}
position={position}
visible={showMenu}
setVisible={setShowMenu}
setGroupBlock={setGroupBlock}
menuRef={menuRef}
>
<DragItem
editor={editor}
isShow={!!groupBlock}
groupBlock={groupBlock}
onClick={handleClick}
onDragStart={handleDragStart}
onMouseDown={handleMouseDown}
draggable={true}
/>
</Menu>
) : null}
<Line
groupBlock={dragOverGroup}
editor={editor}
direction={direction}
/>
</>
);
};
@@ -0,0 +1,63 @@
import {
AsyncBlock,
GroupDirection,
Virgo,
} from '@toeverything/components/editor-core';
import { Rect } from '@toeverything/utils';
import { styled } from '@toeverything/components/ui';
import { useEffect, useState } from 'react';
type LineProps = {
groupBlock: AsyncBlock | null;
editor: Virgo;
direction: GroupDirection;
};
const LINE_WEIGHT = 2;
export const Line = function ({ direction, editor, groupBlock }: LineProps) {
const [isShow, setIsShow] = useState<boolean>(false);
const [rect, setRect] = useState<Rect | null>(null);
useEffect(() => {
if (groupBlock && groupBlock.dom && editor.container) {
setRect(
Rect.fromLWTH(
groupBlock.dom.offsetLeft - editor.container.offsetLeft,
groupBlock.dom.offsetWidth,
groupBlock.dom.offsetTop - editor.container.offsetTop,
groupBlock.dom.offsetHeight
)
);
setIsShow(true);
} else {
setIsShow(false);
}
}, [groupBlock, editor.container]);
const computeLineStyle = (): React.CSSProperties => {
if (!rect) {
return {};
}
return direction === GroupDirection.down
? {
top: rect.bottom + LINE_WEIGHT,
left: rect.left,
width: rect.width,
}
: {
top: rect.top - LINE_WEIGHT,
left: rect.left,
width: rect.width,
};
};
return isShow ? <LineDiv style={computeLineStyle()} /> : null;
};
// TODO: use absolute position
const LineDiv = styled('div')({
zIndex: 2,
position: 'absolute',
background: '#502EC4',
height: LINE_WEIGHT,
});
@@ -0,0 +1,98 @@
import { AsyncBlock, Virgo } from '@toeverything/components/editor-core';
import { DeleteCashBinIcon } from '@toeverything/components/icons';
import { Popover, styled } from '@toeverything/components/ui';
import { Point } from '@toeverything/utils';
import { PropsWithChildren } from 'react';
import { ICON_WIDTH } from './DragItem';
type MenuProps = {
visible: boolean;
setVisible: (visible: boolean) => void;
setGroupBlock: (groupBlock: AsyncBlock | null) => void;
position: Point;
editor: Virgo;
groupBlock: AsyncBlock;
menuRef: React.RefObject<HTMLUListElement>;
};
export const Menu = ({
children,
position,
groupBlock,
setVisible,
visible,
setGroupBlock,
menuRef,
}: PropsWithChildren<MenuProps>) => {
const handlerDeleteGroup = () => {
groupBlock.remove();
setVisible(false);
setGroupBlock(null);
};
const Content = () => {
return (
<MenuUl ref={menuRef}>
<MenuItem onClick={handlerDeleteGroup}>
<IconContainer>
<DeleteCashBinIcon />
</IconContainer>
Delete
</MenuItem>
</MenuUl>
);
};
return (
<Popover
content={<Content />}
placement={'bottom-start'}
visible={visible}
anchorStyle={{
position: 'absolute',
top: position.y,
left: position.x - ICON_WIDTH,
height: ICON_WIDTH,
}}
>
{children}
</Popover>
);
};
const MenuUl = styled('ul')(({ theme }) => ({
fontFamily: 'PingFang SC',
background: '#FFF',
color: '#4C6275',
fontWeight: '400',
}));
const MenuItem = styled('li')(({ theme }) => ({
fontWeight: '400',
width: '268px',
height: '32px',
lineHeight: '32px',
display: 'flex',
fontSize: '14px',
borderRadius: '5px',
cursor: 'pointer',
'&:hover': {
backgroundColor: '#F5F7F8',
},
}));
const IconContainer = styled('div')(({ theme }) => ({
display: 'flex',
height: '32px',
margin: '0 8px',
flexDirection: 'column',
justifyContent: 'center',
lineHeight: '32px',
fontSize: '20px',
'&, & > svg': {
width: '20px',
},
'& > svg': {
height: '20px',
},
}));
@@ -0,0 +1,41 @@
import { StrictMode } from 'react';
import { BasePlugin } from '../../base-plugin';
import { PluginRenderRoot } from '../../utils';
import { GroupMenu } from './GropuMenu';
// import { CommandMenu } from './Menu';
const PLUGIN_NAME = 'group-menu';
export class GroupMenuPlugin extends BasePlugin {
private root?: PluginRenderRoot;
public static override get pluginName(): string {
return PLUGIN_NAME;
}
protected override on_render(): void {
if (this.editor.isWhiteboard) return;
const container = document.createElement('div');
// TODO remove
container.classList.add(`id-${PLUGIN_NAME}`);
this.root = new PluginRenderRoot({
name: PLUGIN_NAME,
render: this.editor.reactRenderRoot.render,
});
this.root.mount();
this._renderGroupMenu();
}
public override dispose() {
this.root?.unmount();
super.dispose();
}
private _renderGroupMenu(): void {
this.root?.render(
<StrictMode>
<GroupMenu editor={this.editor} hooks={this.hooks} />
</StrictMode>
);
}
}
@@ -0,0 +1 @@
export * from './Plugin';