Merge branch 'master' into bugfix/iframe

This commit is contained in:
DiamondThree
2022-08-04 14:21:56 +08:00
31 changed files with 544 additions and 291 deletions

View File

@@ -12,6 +12,8 @@ type GridHandleProps = {
blockId: string;
enabledAddItem: boolean;
draggable: boolean;
alertHandleId: string;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
};
export const GridHandle: FC<GridHandleProps> = function ({
@@ -21,6 +23,8 @@ export const GridHandle: FC<GridHandleProps> = function ({
onDrag,
onMouseDown,
draggable,
alertHandleId,
onMouseEnter,
}) {
const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = e => {
@@ -44,16 +48,17 @@ export const GridHandle: FC<GridHandleProps> = function ({
editor.selectionManager.setActivatedNodeId(textBlock.id);
}
};
const handleMouseEnter: React.MouseEventHandler<HTMLDivElement> = e => {
onMouseEnter && onMouseEnter(e);
};
return (
<GridHandleContainer
style={
isMouseDown
? {
backgroundColor: '#3E6FDB',
}
: {}
}
isMouseDown={isMouseDown}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
isAlert={alertHandleId === blockId}
>
{enabledAddItem ? (
<AddGridHandle
@@ -67,7 +72,10 @@ export const GridHandle: FC<GridHandleProps> = function ({
);
};
const GridHandleContainer = styled('div')(({ theme }) => ({
const GridHandleContainer = styled('div')<{
isMouseDown: boolean;
isAlert: boolean;
}>(({ theme, isMouseDown, isAlert }) => ({
position: 'relative',
width: '10px',
flexGrow: '0',
@@ -78,11 +86,15 @@ const GridHandleContainer = styled('div')(({ theme }) => ({
borderRadius: '1px',
backgroundClip: 'content-box',
' &:hover': {
backgroundColor: theme.affine.palette.primary,
backgroundColor: isAlert ? 'red' : theme.affine.palette.primary,
[`.${GRID_ADD_HANDLE_NAME}`]: {
display: 'block',
},
},
...(isMouseDown &&
(isAlert
? { backgroundColor: 'red' }
: { backgroundColor: theme.affine.palette.primary })),
}));
const AddGridHandle = styled('div')(({ theme }) => ({

View File

@@ -31,6 +31,7 @@ export const Grid: FC<CreateView> = function (props) {
const gridItemCountRef = useRef<number>();
const originalLeftWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
const originalRightWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
const [alertHandleId, setAlertHandleId] = useState<string>(null);
const getLeftRightGridItemDomByIndex = (index: number) => {
const gridItems = Array.from(gridContainerRef.current?.children).filter(
@@ -117,7 +118,7 @@ export const Grid: FC<CreateView> = function (props) {
itemDom.style.width = width;
};
const handleDragGrid = (e: MouseEvent, index: number) => {
const handleDragGrid = async (e: MouseEvent, index: number) => {
setIsOnDrag(true);
window.getSelection().removeAllRanges();
if (!isSetMouseUp.current) {
@@ -165,39 +166,47 @@ export const Grid: FC<CreateView> = function (props) {
setItemWidth(leftGrid, newLeft);
setItemWidth(rightGrid, newRight);
updateDbWidth(leftBlockId, newLeft, rightBlockId, newRight);
[leftBlockId, rightBlockId].forEach(async blockId => {
if (await checkGridItemHasOverflow(blockId)) {
setAlertHandleId(leftBlockId);
} else {
setAlertHandleId(null);
}
});
}
}
};
const children = (
<>
{block.childrenIds.map((id, i) => {
return (
<GridItem
style={{
transition: isOnDrag
? 'none'
: 'all 0.2s ease-in-out',
}}
key={id}
className={GRID_ITEM_CLASS_NAME}
>
<RenderBlock hasContainer={false} blockId={id} />
<GridHandle
onDrag={event => handleDragGrid(event, i)}
editor={editor}
onMouseDown={event => handleMouseDown(event, i)}
blockId={id}
enabledAddItem={
block.childrenIds.length < MAX_ITEM_COUNT
}
draggable={i !== block.childrenIds.length - 1}
/>
</GridItem>
);
})}
</>
);
const checkGridItemHasOverflow = async (blockId: string) => {
let isOverflow = false;
const block = await editor.getBlockById(blockId);
if (block) {
const blockDom = block.dom;
if (blockDom) {
block.dom.style.overflow = 'scroll';
if (block.dom.clientWidth !== block.dom.scrollWidth) {
isOverflow = true;
}
blockDom.style.overflow = 'visible';
}
}
return isOverflow;
};
const handleHandleMouseEnter = (
e: React.MouseEvent<HTMLDivElement>,
index: number
) => {
const leftBlockId = block.childrenIds[index];
const rightBlockId = block.childrenIds[index + 1];
[leftBlockId, rightBlockId].forEach(async blockId => {
if (await checkGridItemHasOverflow(blockId)) {
setAlertHandleId(leftBlockId);
} else {
setAlertHandleId(null);
}
});
};
return (
<>
@@ -206,7 +215,35 @@ export const Grid: FC<CreateView> = function (props) {
ref={gridContainerRef}
isOnDrag={isOnDrag}
>
{children}
{block.childrenIds.map((id, i) => {
return (
<GridItem
style={{
transition: isOnDrag
? 'none'
: 'all 0.2s ease-in-out',
}}
key={id}
className={GRID_ITEM_CLASS_NAME}
>
<RenderBlock hasContainer={false} blockId={id} />
<GridHandle
onDrag={event => handleDragGrid(event, i)}
editor={editor}
onMouseDown={event => handleMouseDown(event, i)}
blockId={id}
enabledAddItem={
block.childrenIds.length < MAX_ITEM_COUNT
}
onMouseEnter={event =>
handleHandleMouseEnter(event, i)
}
alertHandleId={alertHandleId}
draggable={i !== block.childrenIds.length - 1}
/>
</GridItem>
);
})}
</GridContainer>
{isOnDrag
? ReactDOM.createPortal(<GridMask />, window.document.body)

View File

@@ -60,7 +60,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
({ isSelect, theme }) => ({
background: theme.affine.palette.white,
border: '2px solid rgba(236,241,251,.5)',
padding: '15px 12px',
padding: `15px 16px 0 16px`,
borderRadius: '10px',
...(isSelect
? {

View File

@@ -9,6 +9,7 @@ type BlockContentWrapperProps = {
children: ReactElement | null;
};
// TODO: remove
export const WrapperWithPendantAndDragDrop: FC<BlockContentWrapperProps> =
function ({ block, children, editor }) {
return (

View File

@@ -1,5 +1,4 @@
import type { FC, PropsWithChildren } from 'react';
import React, { useState } from 'react';
import { styled } from '@toeverything/components/ui';
import type { AsyncBlock } from '../editor';
import { PendantPopover } from './pendant-popover';
@@ -11,74 +10,68 @@ interface BlockTagProps {
block: AsyncBlock;
}
/**
* @deprecated Need to be refactored
*/
export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
block,
children,
}) => {
const [container, setContainer] = useState<HTMLElement>(null);
const [isHover, setIsHover] = useState(false);
return (
<Container ref={(dom: HTMLElement) => setContainer(dom)}>
<Container>
{children}
{container && (
<PendantPopover
block={block}
container={container}
onVisibleChange={visible => {
setIsHover(visible);
}}
>
<StyledTriggerLine
className="triggerLine"
isHover={isHover}
/>
</PendantPopover>
)}
<PendantPopover block={block}>
<StyledTriggerLine />
</PendantPopover>
<PendantRender block={block} />
</Container>
);
};
const Container = styled('div')({
export const LINE_GAP = 16;
const TAG_GAP = 4;
const StyledTriggerLine = styled('div')({
padding: `${TAG_GAP}px 0`,
width: '100px',
cursor: 'default',
display: 'flex',
alignItems: 'flex-end',
position: 'relative',
padding: '4px',
'&:hover .triggerLine::before': {
display: 'flex',
'::before': {
content: "''",
width: '100%',
height: '2px',
background: '#dadada',
display: 'none',
position: 'absolute',
left: '0',
top: '4px',
},
'::after': {
content: "''",
width: '0',
height: '2px',
background: '#aac4d5',
display: 'block',
position: 'absolute',
left: '0',
top: '4px',
transition: 'width .3s',
},
});
const StyledTriggerLine = styled('div')<{ isHover: boolean }>(({ isHover }) => {
return {
padding: '4px 0',
width: '100px',
cursor: 'default',
display: 'flex',
alignItems: 'flex-end',
position: 'relative',
'::before': {
content: "''",
width: '100%',
height: '2px',
background: '#dadada',
display: 'none',
position: 'absolute',
left: '0',
top: '4px',
const Container = styled('div')({
position: 'relative',
paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`,
'&:hover': {
[StyledTriggerLine.toString()]: {
'&::before': {
display: 'flex',
},
'&::after': {
width: '100%',
},
},
'::after': {
content: "''",
width: isHover ? '100%' : '0',
height: '2px',
background: '#aac4d5',
display: 'block',
position: 'absolute',
left: '0',
top: '4px',
transition: 'width .3s',
},
};
},
});

View File

@@ -1,4 +1,4 @@
import React, { FC, useRef } from 'react';
import { FC, useRef } from 'react';
import { AsyncBlock } from '../../editor';
import { PendantHistoryPanel } from '../pendant-history-panel';
import {
@@ -21,8 +21,6 @@ export const PendantPopover: FC<
pointerEnterDelay={300}
pointerLeaveDelay={200}
placement="bottom-start"
// visible={true}
// trigger="click"
content={
<PendantHistoryPanel
block={block}

View File

@@ -12,6 +12,7 @@ enum DragType {
}
const DRAG_STATE_CHANGE_EVENT_KEY = 'dragStateChange';
const MAX_GRID_BLOCK_FLOOR = 3;
export class DragDropManager {
private _editor: Editor;
private _enabled: boolean;
@@ -231,6 +232,17 @@ export class DragDropManager {
if (!(await this._canBeDrop(event))) {
direction = BlockDropPlacement.none;
}
if (
direction === BlockDropPlacement.left ||
direction === BlockDropPlacement.right
) {
const path = await this._editor.getBlockPath(blockId);
const gridBlocks = path.filter(block => block.type === 'grid');
// limit grid block floor counts
if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
direction = BlockDropPlacement.none;
}
}
this._setBlockDragDirection(direction);
return direction;
}

View File

@@ -340,7 +340,20 @@ export class Editor implements Virgo {
const rootBlockId = this.getRootBlockId();
const rootBlock = await this.getBlockById(rootBlockId);
const blockList: Array<AsyncBlock> = rootBlock ? [rootBlock] : [];
const children = (await rootBlock?.children()) || [];
return [...blockList, ...(await this.getOffspring(rootBlockId))];
}
/**
*
* get all offspring of block
* @param {string} id
* @return {*}
* @memberof Editor
*/
async getOffspring(id: string) {
const block = await this.getBlockById(id);
const blockList: Array<AsyncBlock> = [];
const children = (await block?.children()) || [];
for (const block of children) {
if (!block) {
continue;
@@ -379,6 +392,20 @@ export class Editor implements Virgo {
return lastBlock;
}
async getBlockPath(id: string) {
const block = await this.getBlockById(id);
if (!block) {
return [];
}
const path = [block];
let parent = await block.parent();
while (parent) {
path.unshift(parent);
parent = await parent.parent();
}
return path;
}
async getBlockByPoint(point: Point) {
const blockList = await this.getBlockList();

View File

@@ -4,7 +4,7 @@ import { HandleParentIcon } from '@toeverything/components/icons';
import { styled } from '@toeverything/components/ui';
import { Point } from '@toeverything/utils';
export const ICON_WIDTH = 24;
export const ICON_WIDTH = 16;
type DragItemProps = {
isShow: boolean;
@@ -30,17 +30,21 @@ export const DragItem = function ({
);
};
const StyledDiv = styled('div')({
const StyledDiv = styled('div')(({ theme }) => ({
padding: '0',
display: 'inlineFlex',
display: 'inline-flex',
width: `${ICON_WIDTH}px`,
height: `${ICON_WIDTH}px`,
height: '20px',
cursor: 'grab',
'& svg': {
fontSize: '20px',
marginLeft: '-2px',
},
':hover': {
backgroundColor: '#edeef0',
backgroundColor: '#F5F7F8',
borderRadius: '4px',
},
});
}));
const StyledButton = styled('div')({
padding: '0',

View File

@@ -1,10 +1,19 @@
import { useState, useEffect, FC } from 'react';
import {
useState,
useEffect,
FC,
type MouseEvent,
type DragEvent,
type ReactNode,
type CSSProperties,
} from 'react';
import {
Virgo,
BlockDomInfo,
PluginHooks,
BlockDropPlacement,
LINE_GAP,
} from '@toeverything/framework/virgo';
import { Button } from '@toeverything/components/common';
import { styled } from '@toeverything/components/ui';
@@ -14,7 +23,7 @@ import { distinctUntilChanged, Subject } from 'rxjs';
import { HandleChildIcon } from '@toeverything/components/icons';
import { MENU_WIDTH } from './menu-config';
const MENU_BUTTON_OFFSET = 12;
const MENU_BUTTON_OFFSET = 4;
export type LineInfoSubject = Subject<
| {
@@ -64,13 +73,13 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) {
};
const bottomLineStyle = {
...horizontalLineStyle,
top: intersectionRect.bottom + 1 - rootRect.y,
top: intersectionRect.bottom + 1 - rootRect.y - LINE_GAP,
};
const verticalLineStyle = {
...lineStyle,
width: 2,
height: intersectionRect.height,
height: intersectionRect.height - LINE_GAP,
top: intersectionRect.y - rootRect.y,
};
const leftLineStyle = {
@@ -93,10 +102,10 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) {
}
function DragComponent(props: {
children: React.ReactNode;
style: React.CSSProperties;
onDragStart: (event: React.DragEvent<Element>) => void;
onDragEnd: (event: React.DragEvent<Element>) => void;
children: ReactNode;
style: CSSProperties;
onDragStart: (event: DragEvent<Element>) => void;
onDragEnd: (event: DragEvent<Element>) => void;
}) {
const { style, children, onDragStart, onDragEnd } = props;
return (
@@ -122,7 +131,7 @@ export const LeftMenuDraggable: FC<LeftMenuProps> = props => {
const [block, setBlock] = useState<BlockDomInfo | undefined>();
const [line, setLine] = useState<LineInfo | undefined>(undefined);
const handleDragStart = async (event: React.DragEvent<Element>) => {
const handleDragStart = async (event: DragEvent<Element>) => {
event.stopPropagation();
setVisible(false);
@@ -138,12 +147,12 @@ export const LeftMenuDraggable: FC<LeftMenuProps> = props => {
}
};
const handleDragEnd = (event: React.DragEvent<Element>) => {
const handleDragEnd = (event: DragEvent<Element>) => {
event.preventDefault();
setLine(undefined);
};
const onClick = (event: React.MouseEvent) => {
const onClick = (event: MouseEvent<Element>) => {
if (block == null) return;
const currentTarget = event.currentTarget;
editor.selection.setSelectedNodesIds([block.blockId]);
@@ -200,11 +209,10 @@ export const LeftMenuDraggable: FC<LeftMenuProps> = props => {
style={{
position: 'absolute',
left:
Math.min(
block.rect.left -
MENU_WIDTH -
MENU_BUTTON_OFFSET
) - rootRect.left,
block.rect.left -
MENU_WIDTH -
MENU_BUTTON_OFFSET -
rootRect.left,
top: block.rect.top - rootRect.top,
opacity: visible ? 1 : 0,
zIndex: 1,
@@ -239,15 +247,19 @@ const Draggable = styled(Button)({
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent',
width: '24px',
height: '24px',
width: '16px',
height: '22px',
'& svg': {
fontSize: '20px',
marginLeft: '-2px',
},
':hover': {
backgroundColor: '#edeef0',
backgroundColor: '#F5F7F8',
borderRadius: '4px',
},
});
const LigoLeftMenu = styled('div')({
backgroundColor: 'transparent',
marginRight: '4px',
// marginRight: '4px',
});

View File

@@ -1,5 +1,5 @@
import { BlockDomInfo, HookType } from '@toeverything/framework/virgo';
import React, { StrictMode } from 'react';
import { StrictMode } from 'react';
import { BasePlugin } from '../../base-plugin';
import { ignoreBlockTypes } from './menu-config';
import { LineInfoSubject, LeftMenuDraggable } from './LeftMenuDraggable';

View File

@@ -141,8 +141,7 @@ export const PlaceholderPanel = (props: PlaceholderPanelProps) => {
setOpen(false);
props.onClickTips();
};
const templateList: Array<TemplateMeta> =
TemplateFactory.defaultTemplateList;
const templateList = TemplateFactory.defaultTemplateList;
const handleNewFromTemplate = async (template: TemplateMeta) => {
const pageId = await editor.getRootBlockId();
const newPage = await services.api.editorBlock.getBlock(
@@ -162,33 +161,30 @@ export const PlaceholderPanel = (props: PlaceholderPanelProps) => {
setOpen(false);
};
return (
<>
<PlaceholderPanelContainer
style={{
top: point.y + 'px',
left: point.x + 'px',
display: open ? 'block' : 'none',
}}
>
<EmptyPageTipContainer onClick={handleClickEmptyPage}>
Press Enter to continue with an empty page, or pick a
template
</EmptyPageTipContainer>
<div style={{ marginTop: '4px', marginLeft: '-8px' }}>
{templateList.map((template, index) => {
return (
<TemplateItemContainer
key={index}
onClick={() => {
handleNewFromTemplate(template);
}}
>
<BaseButton>{template.name}</BaseButton>
</TemplateItemContainer>
);
})}
</div>
</PlaceholderPanelContainer>
</>
<PlaceholderPanelContainer
style={{
top: point.y + 'px',
left: point.x + 'px',
display: open ? 'block' : 'none',
}}
>
<EmptyPageTipContainer onClick={handleClickEmptyPage}>
Press Enter to continue with an empty page, or pick a template
</EmptyPageTipContainer>
<div style={{ marginTop: '4px', marginLeft: '-8px' }}>
{templateList.map((template, index) => {
return (
<TemplateItemContainer
key={index}
onClick={() => {
handleNewFromTemplate(template);
}}
>
<BaseButton>{template.name}</BaseButton>
</TemplateItemContainer>
);
})}
</div>
</PlaceholderPanelContainer>
);
};

View File

@@ -15,11 +15,11 @@ export const PopoverContainer = styled('div')<
const shadow = theme.affine.shadows.shadowSxDownLg;
const white = theme.affine.palette.white;
const border_radius =
const borderRadius =
border_radius_map[direction] || border_radius_map['left-top'];
return {
boxShadow: shadow,
borderRadius: border_radius,
borderRadius: borderRadius,
padding: '8px 4px',
backgroundColor: white,
...style,

View File

@@ -1,7 +1,7 @@
import type { MuiPopperPlacementType as PopperPlacementType } from '../mui';
import React, { forwardRef, type PropsWithChildren } from 'react';
import { type PopperHandler, Popper } from '../popper';
import { PopoverContainer } from './container';
import { PopoverContainer } from './Container';
import type { PopoverProps, PopoverDirection } from './interface';
export const placementToContainerDirection: Record<

View File

@@ -1,3 +1,3 @@
export * from './Popover';
export * from './interface';
export { PopoverContainer } from './container';
export { PopoverContainer } from './Container';