mirror of
https://github.com/toeverything/AFFiNE.git
synced 2025-01-05 15:43:11 +03:00
feat: outer drag 、fix new page selection
This commit is contained in:
parent
812af254aa
commit
5c51457761
@ -124,7 +124,12 @@ const EditorContainer = ({
|
|||||||
return (
|
return (
|
||||||
<StyledEditorContainer
|
<StyledEditorContainer
|
||||||
lockScroll={lockScroll}
|
lockScroll={lockScroll}
|
||||||
ref={scrollContainerRef}
|
ref={ref => {
|
||||||
|
scrollContainerRef.current = ref;
|
||||||
|
if (editorRef.current?.scrollManager) {
|
||||||
|
editorRef.current.scrollManager.scrollContainer = ref;
|
||||||
|
}
|
||||||
|
}}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
>
|
>
|
||||||
{pageId ? (
|
{pageId ? (
|
||||||
|
@ -2,13 +2,13 @@ import { FC, useEffect, useLayoutEffect, useRef } from 'react';
|
|||||||
import { ChildrenView } from '@toeverything/framework/virgo';
|
import { ChildrenView } from '@toeverything/framework/virgo';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { sleep } from '@toeverything/utils';
|
import { sleep } from '@toeverything/utils';
|
||||||
import { GRID_ITEM_MIN_WIDTH, GRID_PROPERTY_KEY, removePercent } from '../grid';
|
import { GRID_PROPERTY_KEY, removePercent } from '../grid';
|
||||||
|
|
||||||
export const GRID_ITEM_CLASS_NAME = 'grid-item';
|
export const GRID_ITEM_CLASS_NAME = 'grid-item';
|
||||||
export const GRID_ITEM_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`;
|
export const GRID_ITEM_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`;
|
||||||
|
|
||||||
export const GridItem: FC<ChildrenView> = function (props) {
|
export const GridItem: FC<ChildrenView> = function (props) {
|
||||||
const { children, block } = props;
|
const { children, block, editor } = props;
|
||||||
const RENDER_DELAY_TIME = 100;
|
const RENDER_DELAY_TIME = 100;
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ export const GridItem: FC<ChildrenView> = function (props) {
|
|||||||
|
|
||||||
const checkAndRefreshWidth = async () => {
|
const checkAndRefreshWidth = async () => {
|
||||||
const currentWidth = block.getProperty(GRID_PROPERTY_KEY);
|
const currentWidth = block.getProperty(GRID_PROPERTY_KEY);
|
||||||
|
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
||||||
if (currentWidth) {
|
if (currentWidth) {
|
||||||
setWidth(currentWidth);
|
setWidth(currentWidth);
|
||||||
} else if (!block.dom?.style.width) {
|
} else if (!block.dom?.style.width) {
|
||||||
@ -64,26 +65,23 @@ export const GridItem: FC<ChildrenView> = function (props) {
|
|||||||
if new width less then min width,
|
if new width less then min width,
|
||||||
set min width and next block will be fix width
|
set min width and next block will be fix width
|
||||||
*/
|
*/
|
||||||
if (newWidth < GRID_ITEM_MIN_WIDTH) {
|
if (newWidth < gridItemMinWidth) {
|
||||||
needFixWidth += GRID_ITEM_MIN_WIDTH - newWidth;
|
needFixWidth += gridItemMinWidth - newWidth;
|
||||||
newWidth = GRID_ITEM_MIN_WIDTH;
|
newWidth = gridItemMinWidth;
|
||||||
}
|
}
|
||||||
// if can fix width, fix width
|
// if can fix width, fix width
|
||||||
if (
|
if (newWidth > gridItemMinWidth && needFixWidth) {
|
||||||
newWidth > GRID_ITEM_MIN_WIDTH &&
|
|
||||||
needFixWidth
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
newWidth - needFixWidth >=
|
newWidth - needFixWidth >=
|
||||||
GRID_ITEM_MIN_WIDTH
|
gridItemMinWidth
|
||||||
) {
|
) {
|
||||||
newWidth = newWidth - needFixWidth;
|
newWidth = newWidth - needFixWidth;
|
||||||
needFixWidth = 0;
|
needFixWidth = 0;
|
||||||
} else {
|
} else {
|
||||||
needFixWidth =
|
needFixWidth =
|
||||||
needFixWidth -
|
needFixWidth -
|
||||||
(newWidth - GRID_ITEM_MIN_WIDTH);
|
(newWidth - gridItemMinWidth);
|
||||||
newWidth = GRID_ITEM_MIN_WIDTH;
|
newWidth = gridItemMinWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index === children.length - 2) {
|
if (index === children.length - 2) {
|
||||||
|
@ -12,10 +12,8 @@ import { debounce, domToRect, Point } from '@toeverything/utils';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
|
|
||||||
const MAX_ITEM_COUNT = 6;
|
|
||||||
const DB_UPDATE_DELAY = 50;
|
const DB_UPDATE_DELAY = 50;
|
||||||
const GRID_ON_DRAG_CLASS = 'grid-layout-on-drag';
|
const GRID_ON_DRAG_CLASS = 'grid-layout-on-drag';
|
||||||
export const GRID_ITEM_MIN_WIDTH = 100 / MAX_ITEM_COUNT;
|
|
||||||
export const GRID_PROPERTY_KEY = 'gridItemWidth';
|
export const GRID_PROPERTY_KEY = 'gridItemWidth';
|
||||||
|
|
||||||
export function removePercent(str: string) {
|
export function removePercent(str: string) {
|
||||||
@ -24,13 +22,14 @@ export function removePercent(str: string) {
|
|||||||
|
|
||||||
export const Grid: FC<CreateView> = function (props) {
|
export const Grid: FC<CreateView> = function (props) {
|
||||||
const { block, editor } = props;
|
const { block, editor } = props;
|
||||||
|
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
||||||
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
||||||
const isSetMouseUp = useRef<boolean>(false);
|
const isSetMouseUp = useRef<boolean>(false);
|
||||||
const gridContainerRef = useRef<HTMLDivElement>();
|
const gridContainerRef = useRef<HTMLDivElement>();
|
||||||
const mouseStartPoint = useRef<Point>();
|
const mouseStartPoint = useRef<Point>();
|
||||||
const gridItemCountRef = useRef<number>();
|
const gridItemCountRef = useRef<number>();
|
||||||
const originalLeftWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
const originalLeftWidth = useRef<number>(gridItemMinWidth);
|
||||||
const originalRightWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
const originalRightWidth = useRef<number>(gridItemMinWidth);
|
||||||
const [alertHandleId, setAlertHandleId] = useState<string>(null);
|
const [alertHandleId, setAlertHandleId] = useState<string>(null);
|
||||||
|
|
||||||
const getLeftRightGridItemDomByIndex = (index: number) => {
|
const getLeftRightGridItemDomByIndex = (index: number) => {
|
||||||
@ -126,8 +125,8 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
editor.mouseManager.onMouseupEventOnce(() => {
|
editor.mouseManager.onMouseupEventOnce(() => {
|
||||||
setIsOnDrag(false);
|
setIsOnDrag(false);
|
||||||
isSetMouseUp.current = false;
|
isSetMouseUp.current = false;
|
||||||
originalLeftWidth.current = GRID_ITEM_MIN_WIDTH;
|
originalLeftWidth.current = gridItemMinWidth;
|
||||||
originalRightWidth.current = GRID_ITEM_MIN_WIDTH;
|
originalRightWidth.current = gridItemMinWidth;
|
||||||
mouseStartPoint.current = null;
|
mouseStartPoint.current = null;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -153,12 +152,12 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
const newLeftWidth = originalLeftWidth.current - xDistance;
|
const newLeftWidth = originalLeftWidth.current - xDistance;
|
||||||
let newLeftPercent = (newLeftWidth / containerWidth) * 100;
|
let newLeftPercent = (newLeftWidth / containerWidth) * 100;
|
||||||
let newRightPercent = Number(totalWidth) - newLeftPercent;
|
let newRightPercent = Number(totalWidth) - newLeftPercent;
|
||||||
if (newLeftPercent < GRID_ITEM_MIN_WIDTH) {
|
if (newLeftPercent < gridItemMinWidth) {
|
||||||
newLeftPercent = GRID_ITEM_MIN_WIDTH;
|
newLeftPercent = gridItemMinWidth;
|
||||||
newRightPercent = totalWidth - GRID_ITEM_MIN_WIDTH;
|
newRightPercent = totalWidth - gridItemMinWidth;
|
||||||
} else if (newRightPercent < GRID_ITEM_MIN_WIDTH) {
|
} else if (newRightPercent < gridItemMinWidth) {
|
||||||
newRightPercent = GRID_ITEM_MIN_WIDTH;
|
newRightPercent = gridItemMinWidth;
|
||||||
newLeftPercent = totalWidth - GRID_ITEM_MIN_WIDTH;
|
newLeftPercent = totalWidth - gridItemMinWidth;
|
||||||
}
|
}
|
||||||
//XXX first change dom style is for animation speed, maybe not a good idea
|
//XXX first change dom style is for animation speed, maybe not a good idea
|
||||||
const newLeft = `${newLeftPercent}%`;
|
const newLeft = `${newLeftPercent}%`;
|
||||||
@ -213,6 +212,7 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
<GridContainer
|
<GridContainer
|
||||||
className={clsx({ [GRID_ON_DRAG_CLASS]: isOnDrag })}
|
className={clsx({ [GRID_ON_DRAG_CLASS]: isOnDrag })}
|
||||||
ref={gridContainerRef}
|
ref={gridContainerRef}
|
||||||
|
gridItemMinWidth={gridItemMinWidth}
|
||||||
isOnDrag={isOnDrag}
|
isOnDrag={isOnDrag}
|
||||||
>
|
>
|
||||||
{block.childrenIds.map((id, i) => {
|
{block.childrenIds.map((id, i) => {
|
||||||
@ -233,7 +233,8 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
onMouseDown={event => handleMouseDown(event, i)}
|
onMouseDown={event => handleMouseDown(event, i)}
|
||||||
blockId={id}
|
blockId={id}
|
||||||
enabledAddItem={
|
enabledAddItem={
|
||||||
block.childrenIds.length < MAX_ITEM_COUNT
|
block.childrenIds.length <
|
||||||
|
editor.configManager.grid.maxGridItemCount
|
||||||
}
|
}
|
||||||
onMouseEnter={event =>
|
onMouseEnter={event =>
|
||||||
handleHandleMouseEnter(event, i)
|
handleHandleMouseEnter(event, i)
|
||||||
@ -252,24 +253,25 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GridContainer = styled('div')<{ isOnDrag: boolean }>(
|
const GridContainer = styled('div')<{
|
||||||
({ isOnDrag, theme }) => ({
|
isOnDrag: boolean;
|
||||||
position: 'relative',
|
gridItemMinWidth: number;
|
||||||
display: 'flex',
|
}>(({ isOnDrag, theme, gridItemMinWidth }) => ({
|
||||||
alignItems: 'stretch',
|
position: 'relative',
|
||||||
borderRadius: '10px',
|
display: 'flex',
|
||||||
border: '1px solid #FFF',
|
alignItems: 'stretch',
|
||||||
minWidth: `${GRID_ITEM_MIN_WIDTH}%`,
|
borderRadius: '10px',
|
||||||
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
border: '1px solid #FFF',
|
||||||
|
minWidth: `${gridItemMinWidth}%`,
|
||||||
|
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
||||||
|
borderColor: theme.affine.palette.borderColor,
|
||||||
|
},
|
||||||
|
...(isOnDrag && {
|
||||||
|
[`& .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
||||||
borderColor: theme.affine.palette.borderColor,
|
borderColor: theme.affine.palette.borderColor,
|
||||||
},
|
},
|
||||||
...(isOnDrag && {
|
}),
|
||||||
[`& .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
}));
|
||||||
borderColor: theme.affine.palette.borderColor,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const GridMask = styled('div')({
|
const GridMask = styled('div')({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
@ -3,7 +3,7 @@ import { Protocol } from '@toeverything/datasource/db-service';
|
|||||||
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
||||||
import { GridItem } from '../grid-item/GridItem';
|
import { GridItem } from '../grid-item/GridItem';
|
||||||
import { GridRender } from './GridRender';
|
import { GridRender } from './GridRender';
|
||||||
export { GRID_ITEM_MIN_WIDTH, GRID_PROPERTY_KEY, removePercent } from './Grid';
|
export { GRID_PROPERTY_KEY, removePercent } from './Grid';
|
||||||
|
|
||||||
export class GridBlock extends BaseView {
|
export class GridBlock extends BaseView {
|
||||||
public override selectable = false;
|
public override selectable = false;
|
||||||
|
@ -187,7 +187,6 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollDirections = getScrollDirections(
|
const scrollDirections = getScrollDirections(
|
||||||
endPointRef.current,
|
endPointRef.current,
|
||||||
scrollManager.verticalScrollTriggerDistance,
|
scrollManager.verticalScrollTriggerDistance,
|
||||||
@ -204,6 +203,7 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
|||||||
mouseType.current = 'up';
|
mouseType.current = 'up';
|
||||||
startPointBlock.current = null;
|
startPointBlock.current = null;
|
||||||
setShow(false);
|
setShow(false);
|
||||||
|
setRect(Rect.fromLTRB(0, 0, 0, 0));
|
||||||
scrollManager.stopAutoScroll();
|
scrollManager.stopAutoScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,6 +159,33 @@ export class BlockCommands {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async moveInNewGridItem(
|
||||||
|
blockId: string,
|
||||||
|
gridItemId: string,
|
||||||
|
isBefore = false
|
||||||
|
) {
|
||||||
|
const block = await this._editor.getBlockById(blockId);
|
||||||
|
if (block) {
|
||||||
|
const gridItemBlock = await this._editor.createBlock(
|
||||||
|
Protocol.Block.Type.gridItem
|
||||||
|
);
|
||||||
|
const targetGridItemBlock = await this._editor.getBlockById(
|
||||||
|
gridItemId
|
||||||
|
);
|
||||||
|
await block.remove();
|
||||||
|
await gridItemBlock.append(block);
|
||||||
|
if (targetGridItemBlock && gridItemBlock) {
|
||||||
|
if (isBefore) {
|
||||||
|
await targetGridItemBlock.before(gridItemBlock);
|
||||||
|
} else {
|
||||||
|
await targetGridItemBlock.after(gridItemBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gridItemBlock;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
public async splitGroupFromBlock(blockId: string) {
|
public async splitGroupFromBlock(blockId: string) {
|
||||||
const block = await this._editor.getBlockById(blockId);
|
const block = await this._editor.getBlockById(blockId);
|
||||||
await splitGroup(this._editor, block);
|
await splitGroup(this._editor, block);
|
||||||
|
27
libs/components/editor-core/src/editor/config/grid.ts
Normal file
27
libs/components/editor-core/src/editor/config/grid.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { BlockEditor } from '../..';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* the global config for the editor
|
||||||
|
* @class GridConfig
|
||||||
|
*/
|
||||||
|
export class GridConfig {
|
||||||
|
private _maxGridItemCount = 6;
|
||||||
|
private _editor: BlockEditor;
|
||||||
|
|
||||||
|
constructor(editor: BlockEditor) {
|
||||||
|
this._editor = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get maxGridItemCount() {
|
||||||
|
return this._maxGridItemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
set maxGridItemCount(value) {
|
||||||
|
this._maxGridItemCount = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get gridItemMinWidth() {
|
||||||
|
return 100 / this.maxGridItemCount;
|
||||||
|
}
|
||||||
|
}
|
23
libs/components/editor-core/src/editor/config/index.ts
Normal file
23
libs/components/editor-core/src/editor/config/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { BlockEditor } from '../..';
|
||||||
|
import { GridConfig } from './grid';
|
||||||
|
|
||||||
|
// TODO: if config be complex, add children config abstract
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* the global config for the editor
|
||||||
|
* @class EditorConfig
|
||||||
|
*/
|
||||||
|
export class EditorConfig {
|
||||||
|
private _maxGridItemCount = 6;
|
||||||
|
private _editor: BlockEditor;
|
||||||
|
private _grid: GridConfig;
|
||||||
|
|
||||||
|
constructor(editor: BlockEditor) {
|
||||||
|
this._editor = editor;
|
||||||
|
this._grid = new GridConfig(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
get grid() {
|
||||||
|
return this._grid;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
import { domToRect, Point } from '@toeverything/utils';
|
import { domToRect, Point } from '@toeverything/utils';
|
||||||
import { AsyncBlock } from '../..';
|
import { AsyncBlock } from '../..';
|
||||||
import { GridDropType } from '../commands/types';
|
import { GridDropType } from '../commands/types';
|
||||||
@ -5,6 +6,7 @@ import { Editor } from '../editor';
|
|||||||
import { BlockDropPlacement, GroupDirection } from '../types';
|
import { BlockDropPlacement, GroupDirection } from '../types';
|
||||||
// TODO: Evaluate implementing custom events with Rxjs
|
// TODO: Evaluate implementing custom events with Rxjs
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
|
|
||||||
enum DragType {
|
enum DragType {
|
||||||
dragBlock = 'dragBlock',
|
dragBlock = 'dragBlock',
|
||||||
@ -86,6 +88,7 @@ export class DragDropManager {
|
|||||||
while (curr !== this._editor.getRootBlockId()) {
|
while (curr !== this._editor.getRootBlockId()) {
|
||||||
if (curr === blockId) return false;
|
if (curr === blockId) return false;
|
||||||
const block = await this._editor.getBlockById(curr);
|
const block = await this._editor.getBlockById(curr);
|
||||||
|
if (!block) return false;
|
||||||
curr = block.parentId;
|
curr = block.parentId;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -114,6 +117,48 @@ export class DragDropManager {
|
|||||||
: GridDropType.right
|
: GridDropType.right
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
BlockDropPlacement.outerLeft,
|
||||||
|
BlockDropPlacement.outerRight,
|
||||||
|
].includes(this._blockDragDirection)
|
||||||
|
) {
|
||||||
|
const targetBlock = await this._editor.getBlockById(
|
||||||
|
this._blockDragTargetId
|
||||||
|
);
|
||||||
|
if (targetBlock.type !== Protocol.Block.Type.grid) {
|
||||||
|
await this._editor.commands.blockCommands.createLayoutBlock(
|
||||||
|
blockId,
|
||||||
|
this._blockDragTargetId,
|
||||||
|
this._blockDragDirection ===
|
||||||
|
BlockDropPlacement.outerLeft
|
||||||
|
? GridDropType.left
|
||||||
|
: GridDropType.right
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (targetBlock.type === Protocol.Block.Type.grid) {
|
||||||
|
const gridItems = await targetBlock.children();
|
||||||
|
if (
|
||||||
|
BlockDropPlacement.outerRight ===
|
||||||
|
this._blockDragDirection
|
||||||
|
) {
|
||||||
|
await this._editor.commands.blockCommands.moveInNewGridItem(
|
||||||
|
blockId,
|
||||||
|
gridItems[gridItems.length - 1].id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
BlockDropPlacement.outerLeft ===
|
||||||
|
this._blockDragDirection
|
||||||
|
) {
|
||||||
|
await this._editor.commands.blockCommands.moveInNewGridItem(
|
||||||
|
blockId,
|
||||||
|
gridItems[0].id,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +254,93 @@ export class DragDropManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* check if drag block is out of blocks and return direction
|
||||||
|
* @param {React.DragEvent<Element>} event
|
||||||
|
* @return {
|
||||||
|
* direction: BlockDropPlacement.none, // none, outerLeft, outerRight
|
||||||
|
* block: undefined, // the block in the same clientY
|
||||||
|
* isOuter: false, // if is drag over outer
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @memberof DragDropManager
|
||||||
|
*/
|
||||||
|
public async checkOuterBlockDragTypes(event: React.DragEvent<Element>) {
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
const mousePoint = new Point(clientX, clientY);
|
||||||
|
const rootBlock = await this._editor.getBlockById(
|
||||||
|
this._editor.getRootBlockId()
|
||||||
|
);
|
||||||
|
let direction = BlockDropPlacement.none;
|
||||||
|
const rootBlockRect = domToRect(rootBlock.dom);
|
||||||
|
let targetBlock: AsyncBlock | undefined;
|
||||||
|
let typesInfo = {
|
||||||
|
direction: BlockDropPlacement.none,
|
||||||
|
block: undefined,
|
||||||
|
isOuter: false,
|
||||||
|
} as {
|
||||||
|
direction: BlockDropPlacement;
|
||||||
|
block: AsyncBlock | undefined;
|
||||||
|
isOuter: boolean;
|
||||||
|
};
|
||||||
|
if (rootBlockRect.isPointLeft(mousePoint)) {
|
||||||
|
direction = BlockDropPlacement.outerLeft;
|
||||||
|
typesInfo.isOuter = true;
|
||||||
|
}
|
||||||
|
if (rootBlockRect.isPointRight(mousePoint)) {
|
||||||
|
direction = BlockDropPlacement.outerRight;
|
||||||
|
typesInfo.isOuter = true;
|
||||||
|
}
|
||||||
|
if (direction !== BlockDropPlacement.none) {
|
||||||
|
const blockList = await this._editor.getBlockListByLevelOrder();
|
||||||
|
targetBlock = blockList.find(block => {
|
||||||
|
const domRect = domToRect(block.dom);
|
||||||
|
const pointChecker =
|
||||||
|
direction === BlockDropPlacement.outerLeft
|
||||||
|
? domRect.isPointLeft.bind(domRect)
|
||||||
|
: domRect.isPointRight.bind(domRect);
|
||||||
|
return (
|
||||||
|
block.type !== Protocol.Block.Type.page &&
|
||||||
|
block.type !== Protocol.Block.Type.group &&
|
||||||
|
pointChecker(mousePoint)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (targetBlock) {
|
||||||
|
if (targetBlock.type !== Protocol.Block.Type.grid) {
|
||||||
|
this._setBlockDragDirection(direction);
|
||||||
|
this._setBlockDragTargetId(targetBlock.id);
|
||||||
|
typesInfo = {
|
||||||
|
direction,
|
||||||
|
block: targetBlock,
|
||||||
|
isOuter: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (targetBlock.type === Protocol.Block.Type.grid) {
|
||||||
|
const children = await targetBlock.children();
|
||||||
|
if (
|
||||||
|
children.length <
|
||||||
|
this._editor.configManager.grid.maxGridItemCount
|
||||||
|
) {
|
||||||
|
typesInfo = {
|
||||||
|
direction,
|
||||||
|
block: targetBlock,
|
||||||
|
isOuter: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typesInfo.direction !== BlockDropPlacement.none &&
|
||||||
|
typesInfo.block
|
||||||
|
) {
|
||||||
|
this._setBlockDragTargetId(targetBlock.id);
|
||||||
|
}
|
||||||
|
this._setBlockDragDirection(typesInfo.direction);
|
||||||
|
return typesInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public async checkBlockDragTypes(
|
public async checkBlockDragTypes(
|
||||||
event: React.DragEvent<Element>,
|
event: React.DragEvent<Element>,
|
||||||
blockDom: HTMLElement,
|
blockDom: HTMLElement,
|
||||||
@ -216,10 +348,25 @@ export class DragDropManager {
|
|||||||
) {
|
) {
|
||||||
const { clientX, clientY } = event;
|
const { clientX, clientY } = event;
|
||||||
this._setBlockDragTargetId(blockId);
|
this._setBlockDragTargetId(blockId);
|
||||||
|
const path = await this._editor.getBlockPath(blockId);
|
||||||
const mousePoint = new Point(clientX, clientY);
|
const mousePoint = new Point(clientX, clientY);
|
||||||
const rect = domToRect(blockDom);
|
const rect = domToRect(blockDom);
|
||||||
|
/**
|
||||||
|
* IMP: compute the level of the target block
|
||||||
|
* future feature drag drop has level support do not delete
|
||||||
|
* const levelUnderGrid = Array.from(path)
|
||||||
|
.reverse()
|
||||||
|
.findIndex(block => block.type === Protocol.Block.Type.gridItem);
|
||||||
|
const levelUnderGroup = Array.from(path)
|
||||||
|
.reverse()
|
||||||
|
.findIndex(block => block.type === Protocol.Block.Type.group);
|
||||||
|
const blockLevel =
|
||||||
|
levelUnderGrid > 0 ? levelUnderGrid : levelUnderGroup;
|
||||||
|
console.log({ blockLevel, levelUnderGrid, levelUnderGroup });
|
||||||
|
*
|
||||||
|
*/
|
||||||
let direction = BlockDropPlacement.bottom;
|
let direction = BlockDropPlacement.bottom;
|
||||||
|
|
||||||
if (mousePoint.x - rect.left <= this._dragBlockHotDistance) {
|
if (mousePoint.x - rect.left <= this._dragBlockHotDistance) {
|
||||||
direction = BlockDropPlacement.left;
|
direction = BlockDropPlacement.left;
|
||||||
}
|
}
|
||||||
@ -236,9 +383,10 @@ export class DragDropManager {
|
|||||||
direction === BlockDropPlacement.left ||
|
direction === BlockDropPlacement.left ||
|
||||||
direction === BlockDropPlacement.right
|
direction === BlockDropPlacement.right
|
||||||
) {
|
) {
|
||||||
const path = await this._editor.getBlockPath(blockId);
|
const gridBlocks = path.filter(
|
||||||
const gridBlocks = path.filter(block => block.type === 'grid');
|
block => block.type === Protocol.Block.Type.grid
|
||||||
// limit grid block floor counts
|
);
|
||||||
|
// limit grid block floor counts, when drag block to init grid
|
||||||
if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
|
if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
|
||||||
direction = BlockDropPlacement.none;
|
direction = BlockDropPlacement.none;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ export enum BlockDropPlacement {
|
|||||||
right = 'right',
|
right = 'right',
|
||||||
top = 'top',
|
top = 'top',
|
||||||
bottom = 'bottom',
|
bottom = 'bottom',
|
||||||
|
outerLeft = 'outer-left',
|
||||||
|
outerRight = 'outer-right',
|
||||||
none = 'none',
|
none = 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import { BrowserClipboard } from './clipboard/browser-clipboard';
|
|||||||
import { ClipboardPopulator } from './clipboard/clipboard-populator';
|
import { ClipboardPopulator } from './clipboard/clipboard-populator';
|
||||||
import { BlockHelper } from './block/block-helper';
|
import { BlockHelper } from './block/block-helper';
|
||||||
import { DragDropManager } from './drag-drop';
|
import { DragDropManager } from './drag-drop';
|
||||||
|
import { EditorConfig } from './config';
|
||||||
|
|
||||||
export interface EditorCtorProps {
|
export interface EditorCtorProps {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@ -56,6 +57,7 @@ export class Editor implements Virgo {
|
|||||||
public dragDropManager = new DragDropManager(this);
|
public dragDropManager = new DragDropManager(this);
|
||||||
public commands = new EditorCommands(this);
|
public commands = new EditorCommands(this);
|
||||||
public blockHelper = new BlockHelper(this);
|
public blockHelper = new BlockHelper(this);
|
||||||
|
public configManager = new EditorConfig(this);
|
||||||
public bdCommands: Commands;
|
public bdCommands: Commands;
|
||||||
public ui_container?: HTMLDivElement;
|
public ui_container?: HTMLDivElement;
|
||||||
public version = '0.0.1';
|
public version = '0.0.1';
|
||||||
@ -343,6 +345,23 @@ export class Editor implements Virgo {
|
|||||||
return [...blockList, ...(await this.getOffspring(rootBlockId))];
|
return [...blockList, ...(await this.getOffspring(rootBlockId))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBlockListByLevelOrder() {
|
||||||
|
const rootBlockId = this.getRootBlockId();
|
||||||
|
const rootBlock = await this.getBlockById(rootBlockId);
|
||||||
|
const blockList: Array<AsyncBlock> = [];
|
||||||
|
let nextToVisit: Array<AsyncBlock> = rootBlock ? [rootBlock] : [];
|
||||||
|
while (nextToVisit.length) {
|
||||||
|
let next: Array<AsyncBlock> = [];
|
||||||
|
for (const block of nextToVisit) {
|
||||||
|
const children = await block.children();
|
||||||
|
blockList.push(block);
|
||||||
|
next = next.concat(children);
|
||||||
|
}
|
||||||
|
nextToVisit = next;
|
||||||
|
}
|
||||||
|
return blockList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* get all offspring of block
|
* get all offspring of block
|
||||||
|
@ -110,7 +110,6 @@ export class ScrollManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public emitScrollEvent(event: UIEvent) {
|
public emitScrollEvent(event: UIEvent) {
|
||||||
this.scrollContainer = event.target as HTMLElement;
|
|
||||||
this._scrollDirection = this._getScrollDirection();
|
this._scrollDirection = this._getScrollDirection();
|
||||||
this._scrollMoveOffset = Math.abs(
|
this._scrollMoveOffset = Math.abs(
|
||||||
this.scrollContainer.scrollTop - this._scrollRecord[0]
|
this.scrollContainer.scrollTop - this._scrollRecord[0]
|
||||||
|
@ -73,6 +73,7 @@ export interface Virgo {
|
|||||||
getBlockById(blockId: string): Promise<AsyncBlock | null>;
|
getBlockById(blockId: string): Promise<AsyncBlock | null>;
|
||||||
setHotKeysScope(scope?: string): void;
|
setHotKeysScope(scope?: string): void;
|
||||||
getBlockList: () => Promise<AsyncBlock[]>;
|
getBlockList: () => Promise<AsyncBlock[]>;
|
||||||
|
getBlockListByLevelOrder: () => Promise<AsyncBlock[]>;
|
||||||
// removeBlocks: () => void;
|
// removeBlocks: () => void;
|
||||||
storageManager: StorageManager | undefined;
|
storageManager: StorageManager | undefined;
|
||||||
selection: VirgoSelection;
|
selection: VirgoSelection;
|
||||||
|
@ -52,7 +52,6 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { direction, blockInfo } = lineInfo;
|
const { direction, blockInfo } = lineInfo;
|
||||||
const finalDirection = direction;
|
|
||||||
const lineStyle = {
|
const lineStyle = {
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
position: 'absolute' as const,
|
position: 'absolute' as const,
|
||||||
@ -91,14 +90,14 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) {
|
|||||||
left: intersectionRect.right + 10 - rootRect.x,
|
left: intersectionRect.right + 10 - rootRect.x,
|
||||||
};
|
};
|
||||||
const styleMap = {
|
const styleMap = {
|
||||||
left: leftLineStyle,
|
[BlockDropPlacement.left]: leftLineStyle,
|
||||||
right: rightLineStyle,
|
[BlockDropPlacement.right]: rightLineStyle,
|
||||||
top: topLineStyle,
|
[BlockDropPlacement.top]: topLineStyle,
|
||||||
bottom: bottomLineStyle,
|
[BlockDropPlacement.bottom]: bottomLineStyle,
|
||||||
|
[BlockDropPlacement.outerLeft]: leftLineStyle,
|
||||||
|
[BlockDropPlacement.outerRight]: rightLineStyle,
|
||||||
};
|
};
|
||||||
return (
|
return <div className="editor-menu-line" style={styleMap[direction]} />;
|
||||||
<div className="editor-menu-line" style={styleMap[finalDirection]} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DragComponent(props: {
|
function DragComponent(props: {
|
||||||
|
@ -5,8 +5,9 @@ import { ignoreBlockTypes } from './menu-config';
|
|||||||
import { LineInfoSubject, LeftMenuDraggable } from './LeftMenuDraggable';
|
import { LineInfoSubject, LeftMenuDraggable } from './LeftMenuDraggable';
|
||||||
import { PluginRenderRoot } from '../../utils';
|
import { PluginRenderRoot } from '../../utils';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { domToRect, last, Point } from '@toeverything/utils';
|
import { domToRect, last, Point, throttle } from '@toeverything/utils';
|
||||||
|
import { BlockDropPlacement } from '@toeverything/framework/virgo';
|
||||||
|
const DRAG_THROTTLE_DELAY = 150;
|
||||||
export class LeftMenuPlugin extends BasePlugin {
|
export class LeftMenuPlugin extends BasePlugin {
|
||||||
private _mousedown?: boolean;
|
private _mousedown?: boolean;
|
||||||
private _root?: PluginRenderRoot;
|
private _root?: PluginRenderRoot;
|
||||||
@ -35,11 +36,7 @@ export class LeftMenuPlugin extends BasePlugin {
|
|||||||
.get(HookType.ON_ROOTNODE_MOUSE_UP)
|
.get(HookType.ON_ROOTNODE_MOUSE_UP)
|
||||||
.subscribe(this._handleMouseUp)
|
.subscribe(this._handleMouseUp)
|
||||||
);
|
);
|
||||||
this.sub.add(
|
|
||||||
this.hooks
|
|
||||||
.get(HookType.ON_ROOTNODE_DRAG_OVER)
|
|
||||||
.subscribe(this._handleDragOverBlockNode)
|
|
||||||
);
|
|
||||||
this.sub.add(
|
this.sub.add(
|
||||||
this.hooks.get(HookType.ON_ROOTNODE_MOUSE_LEAVE).subscribe(() => {
|
this.hooks.get(HookType.ON_ROOTNODE_MOUSE_LEAVE).subscribe(() => {
|
||||||
this._hideLeftMenu();
|
this._hideLeftMenu();
|
||||||
@ -60,8 +57,47 @@ export class LeftMenuPlugin extends BasePlugin {
|
|||||||
this.sub.add(
|
this.sub.add(
|
||||||
this.hooks.get(HookType.ON_ROOTNODE_DROP).subscribe(this._onDrop)
|
this.hooks.get(HookType.ON_ROOTNODE_DROP).subscribe(this._onDrop)
|
||||||
);
|
);
|
||||||
|
this.sub.add(
|
||||||
|
this.hooks.get(HookType.ON_ROOTNODE_DRAG_OVER).subscribe(
|
||||||
|
throttle(
|
||||||
|
this._handleRootNodeDragover.bind(this),
|
||||||
|
DRAG_THROTTLE_DELAY,
|
||||||
|
{
|
||||||
|
leading: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleRootNodeDragover = async (
|
||||||
|
event: React.DragEvent<Element>
|
||||||
|
) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.editor.dragDropManager.isDragBlock(event)) {
|
||||||
|
const { direction, block, isOuter } =
|
||||||
|
await this.editor.dragDropManager.checkOuterBlockDragTypes(
|
||||||
|
event
|
||||||
|
);
|
||||||
|
if (direction !== BlockDropPlacement.none && block && block.dom) {
|
||||||
|
this._lineInfo.next({
|
||||||
|
direction,
|
||||||
|
blockInfo: {
|
||||||
|
blockId: block.id,
|
||||||
|
dom: block.dom,
|
||||||
|
type: block.type,
|
||||||
|
rect: block.dom.getBoundingClientRect(),
|
||||||
|
properties: block.getProperties(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (!isOuter) {
|
||||||
|
this._handleDragOverBlockNode(event);
|
||||||
|
} else {
|
||||||
|
this._lineInfo.next(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private _onDrop = () => {
|
private _onDrop = () => {
|
||||||
this._lineInfo.next(undefined);
|
this._lineInfo.next(undefined);
|
||||||
};
|
};
|
||||||
|
@ -121,20 +121,20 @@ export class Rect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isPointDown({ y }: Point) {
|
isPointDown({ x, y }: Point) {
|
||||||
return this.bottom < y;
|
return this.bottom < y && this.left <= x && this.right >= x;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPointUp({ y }: Point) {
|
isPointUp({ x, y }: Point) {
|
||||||
return y < this.top;
|
return y < this.top && this.left <= x && this.right >= x;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPointLeft({ x }: Point) {
|
isPointLeft({ x, y }: Point) {
|
||||||
return x < this.left;
|
return x < this.left && this.top <= y && this.bottom >= y;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPointRight({ x }: Point) {
|
isPointRight({ x, y }: Point) {
|
||||||
return x > this.right;
|
return x > this.right && this.top <= y && this.bottom >= y;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromNewLeft(left: number) {
|
fromNewLeft(left: number) {
|
||||||
|
Loading…
Reference in New Issue
Block a user