Merge pull request #318 from toeverything/feat/block-render

Feat/block render
This commit is contained in:
Whitewater 2022-08-24 10:32:06 +08:00 committed by GitHub
commit 15055bc7c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 333 additions and 268 deletions

View File

@ -1,7 +1,6 @@
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
import { import {
RenderBlock,
RenderRoot, RenderRoot,
type BlockEditor, type BlockEditor,
} from '@toeverything/components/editor-core'; } from '@toeverything/components/editor-core';
@ -88,9 +87,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
editor={editor} editor={editor}
editorElement={AffineEditor as any} editorElement={AffineEditor as any}
scrollBlank={scrollBlank} scrollBlank={scrollBlank}
> />
<RenderBlock blockId={editor.getRootBlockId()} />
</RenderRoot>
); );
} }
); );

View File

@ -1,29 +1,28 @@
import type { TextProps } from '@toeverything/components/common'; import type { TextProps } from '@toeverything/components/common';
import { import {
ContentColumnValue, ContentColumnValue,
services,
Protocol, Protocol,
services,
} from '@toeverything/datasource/db-service'; } from '@toeverything/datasource/db-service';
import { type CreateView } from '@toeverything/framework/virgo'; import { type CreateView } from '@toeverything/framework/virgo';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import {
BlockPendantProvider,
RenderBlockChildren,
supportChildren,
useOnSelect,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { BlockContainer } from '../../components/BlockContainer';
import { List } from '../../components/style-container';
import { import {
TextManage, TextManage,
type ExtendedTextUtils, type ExtendedTextUtils,
} from '../../components/text-manage'; } from '../../components/text-manage';
import { tabBlock } from '../../utils/indent'; import { tabBlock } from '../../utils/indent';
import { BulletIcon, getChildrenType, NumberType } from './data';
import { BulletBlock, BulletProperties } from './types'; import { BulletBlock, BulletProperties } from './types';
import {
supportChildren,
RenderBlockChildren,
useOnSelect,
BlockPendantProvider,
} from '@toeverything/components/editor-core';
import { List } from '../../components/style-container';
import { getChildrenType, BulletIcon, NumberType } from './data';
import { IndentWrapper } from '../../components/IndentWrapper';
import { BlockContainer } from '../../components/BlockContainer';
import { styled } from '@toeverything/components/ui';
export const defaultBulletProps: BulletProperties = { export const defaultBulletProps: BulletProperties = {
text: { value: [{ text: '' }] }, text: { value: [{ text: '' }] },
@ -208,9 +207,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
</div> </div>
</List> </List>
</BlockPendantProvider> </BlockPendantProvider>
<IndentWrapper>
<RenderBlockChildren block={block} /> <RenderBlockChildren block={block} />
</IndentWrapper>
</BlockContainer> </BlockContainer>
); );
}; };

View File

@ -1,18 +1,16 @@
import {
DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service';
import { import {
AsyncBlock, AsyncBlock,
BaseView, BaseView,
CreateView, getTextHtml,
getTextProperties, getTextProperties,
SelectBlock, SelectBlock,
getTextHtml,
} from '@toeverything/framework/virgo'; } from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
// import { withTreeViewChildren } from '../../utils/with-tree-view-children'; // import { withTreeViewChildren } from '../../utils/with-tree-view-children';
import { defaultBulletProps, BulletView } from './BulletView'; import { BulletView, defaultBulletProps } from './BulletView';
import { IndentWrapper } from '../../components/IndentWrapper';
export class BulletBlock extends BaseView { export class BulletBlock extends BaseView {
public type = Protocol.Block.Type.bullet; public type = Protocol.Block.Type.bullet;

View File

@ -1,4 +1,4 @@
import { RenderBlock } from '@toeverything/components/editor-core'; import { useBlockRender } from '@toeverything/components/editor-core';
import { ChildrenView, CreateView } from '@toeverything/framework/virgo'; import { ChildrenView, CreateView } from '@toeverything/framework/virgo';
export const GridItemRender = function ( export const GridItemRender = function (
@ -6,10 +6,11 @@ export const GridItemRender = function (
) { ) {
const GridItem = function (props: CreateView) { const GridItem = function (props: CreateView) {
const { block } = props; const { block } = props;
const { BlockRender } = useBlockRender();
const children = ( const children = (
<> <>
{block.childrenIds.map(id => { {block.childrenIds.map(id => {
return <RenderBlock key={id} blockId={id} />; return <BlockRender key={id} blockId={id} />;
})} })}
</> </>
); );

View File

@ -1,16 +1,16 @@
import { RenderBlock } from '@toeverything/components/editor-core'; import { useBlockRender } from '@toeverything/components/editor-core';
import { CreateView } from '@toeverything/framework/virgo';
import React, { useEffect, useRef, useState } from 'react';
import { GridHandle } from './GirdHandle';
import { styled } from '@toeverything/components/ui'; import { styled } from '@toeverything/components/ui';
import { Protocol } from '@toeverything/datasource/db-service';
import { CreateView } from '@toeverything/framework/virgo';
import { debounce, domToRect, Point } from '@toeverything/utils';
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { import {
GRID_ITEM_CLASS_NAME, GRID_ITEM_CLASS_NAME,
GRID_ITEM_CONTENT_CLASS_NAME, GRID_ITEM_CONTENT_CLASS_NAME,
} from '../grid-item/GridItem'; } from '../grid-item/GridItem';
import { debounce, domToRect, Point } from '@toeverything/utils'; import { GridHandle } from './GirdHandle';
import clsx from 'clsx';
import { Protocol } from '@toeverything/datasource/db-service';
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';
@ -31,6 +31,7 @@ export const Grid = function (props: CreateView) {
const originalLeftWidth = useRef<number>(gridItemMinWidth); const originalLeftWidth = useRef<number>(gridItemMinWidth);
const originalRightWidth = useRef<number>(gridItemMinWidth); const originalRightWidth = useRef<number>(gridItemMinWidth);
const [alertHandleId, setAlertHandleId] = useState<string>(null); const [alertHandleId, setAlertHandleId] = useState<string>(null);
const { BlockRender } = useBlockRender();
const getLeftRightGridItemDomByIndex = (index: number) => { const getLeftRightGridItemDomByIndex = (index: number) => {
const gridItems = Array.from(gridContainerRef.current?.children).filter( const gridItems = Array.from(gridContainerRef.current?.children).filter(
@ -226,7 +227,7 @@ export const Grid = function (props: CreateView) {
key={id} key={id}
className={GRID_ITEM_CLASS_NAME} className={GRID_ITEM_CLASS_NAME}
> >
<RenderBlock hasContainer={false} blockId={id} /> <BlockRender hasContainer={false} blockId={id} />
<GridHandle <GridHandle
onDrag={event => handleDragGrid(event, i)} onDrag={event => handleDragGrid(event, i)}
editor={editor} editor={editor}

View File

@ -2,5 +2,5 @@ import { RenderBlockChildren } from '@toeverything/components/editor-core';
import type { CreateView } from '@toeverything/framework/virgo'; import type { CreateView } from '@toeverything/framework/virgo';
export const ScenePage = ({ block }: CreateView) => { export const ScenePage = ({ block }: CreateView) => {
return <RenderBlockChildren block={block} />; return <RenderBlockChildren block={block} indent={false} />;
}; };

View File

@ -1,6 +1,6 @@
import { import {
KanbanCard, KanbanCard,
RenderBlock, useBlockRender,
useEditor, useEditor,
useKanban, useKanban,
} from '@toeverything/components/editor-core'; } from '@toeverything/components/editor-core';
@ -94,6 +94,7 @@ export const CardItem = ({
const [editable, setEditable] = useState(false); const [editable, setEditable] = useState(false);
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false); const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
const { editor } = useEditor(); const { editor } = useEditor();
const { BlockRender } = useBlockRender();
const onAddItem = async () => { const onAddItem = async () => {
setEditable(true); setEditable(true);
@ -114,7 +115,7 @@ export const CardItem = ({
<MuiClickAwayListener onClickAway={() => setEditable(false)}> <MuiClickAwayListener onClickAway={() => setEditable(false)}>
<CardContainer> <CardContainer>
<CardContent> <CardContent>
<RenderBlock blockId={id} /> <BlockRender blockId={id} />
</CardContent> </CardContent>
{showKanbanRefPageFlag && !editable && ( {showKanbanRefPageFlag && !editable && (
<Overlay onClick={onClickCard}> <Overlay onClick={onClickCard}>

View File

@ -1,8 +1,8 @@
import { TextProps } from '@toeverything/components/common'; import { TextProps } from '@toeverything/components/common';
import { import {
ContentColumnValue, ContentColumnValue,
services,
Protocol, Protocol,
services,
} from '@toeverything/datasource/db-service'; } from '@toeverything/datasource/db-service';
import { type CreateView } from '@toeverything/framework/virgo'; import { type CreateView } from '@toeverything/framework/virgo';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
@ -11,18 +11,17 @@ import {
type ExtendedTextUtils, type ExtendedTextUtils,
} from '../../components/text-manage'; } from '../../components/text-manage';
import { tabBlock } from '../../utils/indent'; import { tabBlock } from '../../utils/indent';
import { IndentWrapper } from '../../components/IndentWrapper';
import type { Numbered, NumberedAsyncBlock } from './types'; import type { Numbered, NumberedAsyncBlock } from './types';
import { getChildrenType, getNumber } from './data';
import { import {
supportChildren,
RenderBlockChildren,
useOnSelect,
BlockPendantProvider, BlockPendantProvider,
RenderBlockChildren,
supportChildren,
useOnSelect,
} from '@toeverything/components/editor-core'; } from '@toeverything/components/editor-core';
import { List } from '../../components/style-container';
import { BlockContainer } from '../../components/BlockContainer'; import { BlockContainer } from '../../components/BlockContainer';
import { List } from '../../components/style-container';
import { getChildrenType, getNumber } from './data';
export const defaultTodoProps: Numbered = { export const defaultTodoProps: Numbered = {
text: { value: [{ text: '' }] }, text: { value: [{ text: '' }] },
@ -204,9 +203,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
</List> </List>
</BlockPendantProvider> </BlockPendantProvider>
<IndentWrapper>
<RenderBlockChildren block={block} /> <RenderBlockChildren block={block} />
</IndentWrapper>
</BlockContainer> </BlockContainer>
); );
}; };

View File

@ -1,17 +1,16 @@
import {
DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service';
import { import {
AsyncBlock, AsyncBlock,
BaseView, BaseView,
getTextHtml,
getTextProperties, getTextProperties,
SelectBlock, SelectBlock,
getTextHtml,
} from '@toeverything/framework/virgo'; } from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
// import { withTreeViewChildren } from '../../utils/with-tree-view-children'; // import { withTreeViewChildren } from '../../utils/with-tree-view-children';
import { defaultTodoProps, NumberedView } from './NumberedView'; import { defaultTodoProps, NumberedView } from './NumberedView';
import { IndentWrapper } from '../../components/IndentWrapper';
export class NumberedBlock extends BaseView { export class NumberedBlock extends BaseView {
public type = Protocol.Block.Type.numbered; public type = Protocol.Block.Type.numbered;

View File

@ -12,7 +12,6 @@ import { styled } from '@toeverything/components/ui';
import { Protocol } from '@toeverything/datasource/db-service'; import { Protocol } from '@toeverything/datasource/db-service';
import { CreateView } from '@toeverything/framework/virgo'; import { CreateView } from '@toeverything/framework/virgo';
import { BlockContainer } from '../../components/BlockContainer'; import { BlockContainer } from '../../components/BlockContainer';
import { IndentWrapper } from '../../components/IndentWrapper';
import { TextManage } from '../../components/text-manage'; import { TextManage } from '../../components/text-manage';
import { dedentBlock, tabBlock } from '../../utils/indent'; import { dedentBlock, tabBlock } from '../../utils/indent';
interface CreateTextView extends CreateView { interface CreateTextView extends CreateView {
@ -255,9 +254,7 @@ export const TextView = ({
handleTab={onTab} handleTab={onTab}
/> />
</BlockPendantProvider> </BlockPendantProvider>
<IndentWrapper>
<RenderBlockChildren block={block} /> <RenderBlockChildren block={block} />
</IndentWrapper>
</BlockContainer> </BlockContainer>
); );
}; };

View File

@ -1,11 +1,17 @@
import { TextProps } from '@toeverything/components/common'; import { TextProps } from '@toeverything/components/common';
import {
AsyncBlock,
BlockPendantProvider,
CreateView,
useOnSelect,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui'; import { styled } from '@toeverything/components/ui';
import { import {
ContentColumnValue, ContentColumnValue,
Protocol, Protocol,
} from '@toeverything/datasource/db-service'; } from '@toeverything/datasource/db-service';
import { AsyncBlock, type CreateView } from '@toeverything/framework/virgo'; import { useRef, useState } from 'react';
import { useRef } from 'react'; import { BlockContainer } from '../../components/BlockContainer';
import { import {
TextManage, TextManage,
type ExtendedTextUtils, type ExtendedTextUtils,
@ -36,6 +42,10 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
export const TodoView = ({ block, editor }: CreateView) => { export const TodoView = ({ block, editor }: CreateView) => {
const properties = { ...defaultTodoProps, ...block.getProperties() }; const properties = { ...defaultTodoProps, ...block.getProperties() };
const text_ref = useRef<ExtendedTextUtils>(null); const text_ref = useRef<ExtendedTextUtils>(null);
const [isSelect, setIsSelect] = useState<boolean>(false);
useOnSelect(block.id, (isSelect: boolean) => {
setIsSelect(isSelect);
});
const turn_into_text_block = async () => { const turn_into_text_block = async () => {
// Convert to text block // Convert to text block
@ -121,6 +131,8 @@ export const TodoView = ({ block, editor }: CreateView) => {
}; };
return ( return (
<BlockContainer editor={editor} block={block} selected={isSelect}>
<BlockPendantProvider block={block}>
<TodoBlock> <TodoBlock>
<div className={'checkBoxContainer'}> <div className={'checkBoxContainer'}>
<CheckBox <CheckBox
@ -131,7 +143,9 @@ export const TodoView = ({ block, editor }: CreateView) => {
<div className={'textContainer'}> <div className={'textContainer'}>
<TextManage <TextManage
className={properties.checked?.value ? 'checked' : ''} className={
properties.checked?.value ? 'checked' : ''
}
ref={text_ref} ref={text_ref}
editor={editor} editor={editor}
block={block} block={block}
@ -143,6 +157,8 @@ export const TodoView = ({ block, editor }: CreateView) => {
/> />
</div> </div>
</TodoBlock> </TodoBlock>
</BlockPendantProvider>
</BlockContainer>
); );
}; };

View File

@ -1,18 +1,16 @@
import { import {
BaseView,
getTextProperties,
AsyncBlock, AsyncBlock,
SelectBlock, BaseView,
getTextHtml, getTextHtml,
} from '@toeverything/framework/virgo'; getTextProperties,
// import type { CreateView } from '@toeverything/framework/virgo'; SelectBlock,
withTreeViewChildren,
} from '@toeverything/components/editor-core';
import { import {
Protocol,
DefaultColumnsValue, DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service'; } from '@toeverything/datasource/db-service';
// import { withTreeViewChildren } from '../../utils/with-tree-view-children'; import { defaultTodoProps, TodoView } from './TodoView';
import { withTreeViewChildren } from '../../utils/WithTreeViewChildren';
import { TodoView, defaultTodoProps } from './TodoView';
import type { TodoAsyncBlock } from './types'; import type { TodoAsyncBlock } from './types';
export class TodoBlock extends BaseView { export class TodoBlock extends BaseView {

View File

@ -23,7 +23,7 @@ export const BlockContainer = function ({
); );
}; };
export const Container = styled('div')<{ selected: boolean }>( export const Container = styled('div')<{ selected?: boolean }>(
({ selected, theme }) => ({ ({ selected, theme }) => ({
backgroundColor: selected ? theme.affine.palette.textSelected : '', backgroundColor: selected ? theme.affine.palette.textSelected : '',
marginBottom: '2px', marginBottom: '2px',

View File

@ -1,17 +0,0 @@
import { PropsWithChildren } from 'react';
import { ChildrenView } from '@toeverything/framework/virgo';
import { styled } from '@toeverything/components/ui';
/**
* Indent rendering child nodes
*/
export const IndentWrapper = (props: PropsWithChildren) => {
return <StyledIdentWrapper>{props.children}</StyledIdentWrapper>;
};
const StyledIdentWrapper = styled('div')({
display: 'flex',
flexDirection: 'column',
// TODO: marginLeft should use theme provided by styled
marginLeft: '30px',
});

View File

@ -1 +0,0 @@
export * from './IndentWrapper';

View File

@ -1,4 +1,5 @@
export const isYoutubeUrl = (url?: string): boolean => { export const isYoutubeUrl = (url?: string): boolean => {
if (!url) return false;
const allowedHosts = ['www.youtu.be', 'www.youtube.com']; const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
const host = new URL(url).host; const host = new URL(url).host;
return allowedHosts.includes(host); return allowedHosts.includes(host);

View File

@ -1,22 +1,34 @@
import { createContext, useContext } from 'react';
import type { BlockEditor, AsyncBlock } from './editor';
import { genErrorObj } from '@toeverything/utils'; import { genErrorObj } from '@toeverything/utils';
import { createContext, PropsWithChildren, useContext } from 'react';
import type { AsyncBlock, BlockEditor } from './editor';
const RootContext = createContext<{ type EditorProps = {
editor: BlockEditor; editor: BlockEditor;
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top // TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
editorElement: () => JSX.Element; editorElement: () => JSX.Element;
}>( };
const EditorContext = createContext<EditorProps>(
genErrorObj( genErrorObj(
'Failed to get context! The context only can use under the "render-root"' 'Failed to get EditorContext! The context only can use under the "render-root"'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any ) as any
); );
export const EditorProvider = RootContext.Provider;
export const useEditor = () => { export const useEditor = () => {
return useContext(RootContext); return useContext(EditorContext);
};
export const EditorProvider = ({
editor,
editorElement,
children,
}: PropsWithChildren<EditorProps>) => {
return (
<EditorContext.Provider value={{ editor, editorElement }}>
{children}
</EditorContext.Provider>
);
}; };
/** /**

View File

@ -4,12 +4,12 @@ import {
services, services,
type ReturnUnobserve, type ReturnUnobserve,
} from '@toeverything/datasource/db-service'; } from '@toeverything/datasource/db-service';
import type { PropsWithChildren } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { EditorProvider } from './Contexts'; import { EditorProvider } from './Contexts';
import type { BlockEditor } from './editor'; import type { BlockEditor } from './editor';
import { useIsOnDrag } from './hooks'; import { useIsOnDrag } from './hooks';
import { addNewGroup, appendNewGroup } from './recast-block'; import { addNewGroup, appendNewGroup } from './recast-block';
import { BlockRenderProvider, RenderBlock } from './render-block';
import { SelectionRect, SelectionRef } from './Selection'; import { SelectionRect, SelectionRef } from './Selection';
interface RenderRootProps { interface RenderRootProps {
@ -24,11 +24,7 @@ interface RenderRootProps {
const MAX_PAGE_WIDTH = 5000; const MAX_PAGE_WIDTH = 5000;
export const MIN_PAGE_WIDTH = 1480; export const MIN_PAGE_WIDTH = 1480;
export const RenderRoot = ({ export const RenderRoot = ({ editor, editorElement }: RenderRootProps) => {
editor,
editorElement,
children,
}: PropsWithChildren<RenderRootProps>) => {
const selectionRef = useRef<SelectionRef>(null); const selectionRef = useRef<SelectionRef>(null);
const triggeredBySelect = useRef(false); const triggeredBySelect = useRef(false);
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH); const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
@ -158,7 +154,8 @@ export const RenderRoot = ({
}; };
return ( return (
<EditorProvider value={{ editor, editorElement }}> <EditorProvider editor={editor} editorElement={editorElement}>
<BlockRenderProvider blockRender={RenderBlock}>
<Container <Container
isEdgeless={editor.isEdgeless} isEdgeless={editor.isEdgeless}
ref={ref => { ref={ref => {
@ -184,13 +181,16 @@ export const RenderRoot = ({
isOnDrag={isOnDrag} isOnDrag={isOnDrag}
> >
<Content style={{ maxWidth: pageWidth + 'px' }}> <Content style={{ maxWidth: pageWidth + 'px' }}>
{children} <RenderBlock blockId={editor.getRootBlockId()} />
</Content> </Content>
{/** TODO: remove selectionManager insert */} {/** TODO: remove selectionManager insert */}
{editor && <SelectionRect ref={selectionRef} editor={editor} />} {editor && (
<SelectionRect ref={selectionRef} editor={editor} />
)}
{editor.isEdgeless ? null : <ScrollBlank editor={editor} />} {editor.isEdgeless ? null : <ScrollBlank editor={editor} />}
{patchedNodes} {patchedNodes}
</Container> </Container>
</BlockRenderProvider>
</EditorProvider> </EditorProvider>
); );
}; };
@ -251,7 +251,7 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
); );
return ( return (
<ScrollBlankContainter <ScrollBlankContainer
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
onClick={onClick} onClick={onClick}
@ -283,7 +283,7 @@ const Content = styled('div')({
transitionTimingFunction: 'ease-in', transitionTimingFunction: 'ease-in',
}); });
const ScrollBlankContainter = styled('div')({ const ScrollBlankContainer = styled('div')({
paddingBottom: '30vh', paddingBottom: '30vh',
margin: `0 -${PADDING_X}px`, margin: `0 -${PADDING_X}px`,
}); });

View File

@ -1,19 +1,16 @@
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
export * from './render-block';
export * from './hooks';
export { RenderBlock } from './render-block';
export * from './recast-block';
export * from './recast-block/types';
export * from './block-pendant'; export * from './block-pendant';
export { useEditor } from './Contexts';
export * from './editor';
export * from './hooks';
export * from './kanban'; export * from './kanban';
export * from './kanban/types'; export * from './kanban/types';
export * from './recast-block';
export * from './recast-block/types';
export {
BlockRenderProvider,
RenderBlockChildren,
useBlockRender,
withTreeViewChildren,
} from './render-block';
export { MIN_PAGE_WIDTH, RenderRoot } from './RenderRoot';
export * from './utils'; export * from './utils';
export * from './editor';
export { useEditor } from './Contexts';

View File

@ -10,6 +10,8 @@ import {
RecastMetaProperty, RecastMetaProperty,
RecastPropertyId, RecastPropertyId,
} from '../recast-block/types'; } from '../recast-block/types';
import { BlockRenderProvider } from '../render-block';
import { KanbanBlockRender } from '../render-block/RenderKanbanBlock';
import { useInitKanbanEffect, useRecastKanban } from './kanban'; import { useInitKanbanEffect, useRecastKanban } from './kanban';
import { KanbanGroup } from './types'; import { KanbanGroup } from './types';
@ -54,9 +56,11 @@ export const KanbanProvider = ({
}; };
return ( return (
<BlockRenderProvider blockRender={KanbanBlockRender}>
<KanbanContext.Provider value={value}> <KanbanContext.Provider value={value}>
{children} {children}
</KanbanContext.Provider> </KanbanContext.Provider>
</BlockRenderProvider>
); );
}; };

View File

@ -0,0 +1,35 @@
import { genErrorObj } from '@toeverything/utils';
import { createContext, PropsWithChildren, useContext } from 'react';
import { RenderBlockProps } from './RenderBlock';
type BlockRenderProps = {
blockRender: (args: RenderBlockProps) => JSX.Element | null;
};
export const BlockRenderContext = createContext<BlockRenderProps>(
genErrorObj(
'Failed to get BlockChildrenContext! The context only can use under the "render-root"'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
);
/**
* CAUTION! DO NOT PROVIDE A DYNAMIC BLOCK RENDER!
*/
export const BlockRenderProvider = ({
blockRender,
children,
}: PropsWithChildren<BlockRenderProps>) => {
return (
<BlockRenderContext.Provider value={{ blockRender }}>
{children}
</BlockRenderContext.Provider>
);
};
export const useBlockRender = () => {
const { blockRender } = useContext(BlockRenderContext);
return {
BlockRender: blockRender,
};
};

View File

@ -4,7 +4,12 @@ import { useCallback, useMemo } from 'react';
import { useEditor } from '../Contexts'; import { useEditor } from '../Contexts';
import { useBlock } from '../hooks'; import { useBlock } from '../hooks';
interface RenderBlockProps { /**
* Render nothing
*/
export const NullBlockRender = (): null => null;
export interface RenderBlockProps {
blockId: string; blockId: string;
hasContainer?: boolean; hasContainer?: boolean;
} }
@ -29,7 +34,7 @@ export function RenderBlock({
if (block?.type) { if (block?.type) {
return editor.getView(block.type).View; return editor.getView(block.type).View;
} }
return () => null; return (): null => null;
}, [editor, block?.type]); }, [editor, block?.type]);
if (!block) { if (!block) {
@ -64,4 +69,5 @@ export function RenderBlock({
const BlockContainer = styled('div')(({ theme }) => ({ const BlockContainer = styled('div')(({ theme }) => ({
fontSize: theme.typography.body1.fontSize, fontSize: theme.typography.body1.fontSize,
flex: 1,
})); }));

View File

@ -1,16 +1,39 @@
import { styled } from '@toeverything/components/ui';
import type { AsyncBlock } from '../editor'; import type { AsyncBlock } from '../editor';
import { RenderBlock } from './RenderBlock'; import { useBlockRender } from './Context';
import { NullBlockRender } from './RenderBlock';
interface RenderChildrenProps { export interface RenderChildrenProps {
block: AsyncBlock; block: AsyncBlock;
indent?: boolean;
}
export const RenderBlockChildren = ({
block,
indent = true,
}: RenderChildrenProps) => {
const { BlockRender } = useBlockRender();
if (BlockRender === NullBlockRender) {
return null;
} }
export const RenderBlockChildren = ({ block }: RenderChildrenProps) => {
return block.childrenIds.length ? ( return block.childrenIds.length ? (
<> <StyledIdentWrapper indent={indent}>
{block.childrenIds.map(childId => { {block.childrenIds.map(childId => {
return <RenderBlock key={childId} blockId={childId} />; return <BlockRender key={childId} blockId={childId} />;
})} })}
</> </StyledIdentWrapper>
) : null; ) : null;
}; };
/**
* Indent rendering child nodes
*/
const StyledIdentWrapper = styled('div')<{ indent?: boolean }>(
({ indent }) => ({
display: 'flex',
flexDirection: 'column',
// TODO: marginLeft should use theme provided by styled
...(indent && { marginLeft: '30px' }),
})
);

View File

@ -0,0 +1,56 @@
import { styled } from '@toeverything/components/ui';
import { useBlock } from '../hooks';
import { BlockRenderProvider } from './Context';
import { NullBlockRender, RenderBlock, RenderBlockProps } from './RenderBlock';
/**
* Render block without children.
*/
const BlockWithoutChildrenRender = ({ blockId }: RenderBlockProps) => {
return (
<BlockRenderProvider blockRender={NullBlockRender}>
<RenderBlock blockId={blockId} />
</BlockRenderProvider>
);
};
/**
* Render a block, but only one level of children.
*/
const OneLevelBlockRender = ({ blockId }: RenderBlockProps) => {
return (
<BlockRenderProvider blockRender={BlockWithoutChildrenRender}>
<RenderBlock blockId={blockId} />
</BlockRenderProvider>
);
};
export const KanbanBlockRender = ({ blockId }: RenderBlockProps) => {
const { block } = useBlock(blockId);
if (!block) {
return (
<BlockRenderProvider blockRender={NullBlockRender}>
<RenderBlock blockId={blockId} />
</BlockRenderProvider>
);
}
return (
<BlockRenderProvider blockRender={NullBlockRender}>
<RenderBlock blockId={blockId} />
{block?.childrenIds.map(childId => (
<StyledBorder key={childId}>
<RenderBlock blockId={childId} />
</StyledBorder>
))}
</BlockRenderProvider>
);
};
const StyledBorder = styled('div')({
border: '1px solid #E0E6EB',
borderRadius: '5px',
margin: '4px',
padding: '0 4px',
});

View File

@ -1,10 +1,3 @@
import {
BlockPendantProvider,
CreateView,
RenderBlock,
useCurrentView,
useOnSelect,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui'; import { styled } from '@toeverything/components/ui';
import type { import type {
ComponentPropsWithoutRef, ComponentPropsWithoutRef,
@ -12,9 +5,10 @@ import type {
CSSProperties, CSSProperties,
ReactElement, ReactElement,
} from 'react'; } from 'react';
import { forwardRef, useState } from 'react'; import { forwardRef } from 'react';
import { SCENE_CONFIG } from '../blocks/group/config'; import { CreateView } from '../editor';
import { BlockContainer } from '../components/BlockContainer'; import { useBlockRender } from './Context';
import { NullBlockRender } from './RenderBlock';
type WithChildrenConfig = { type WithChildrenConfig = {
indent: CSSProperties['marginLeft']; indent: CSSProperties['marginLeft'];
@ -41,45 +35,6 @@ const TreeView = forwardRef<
); );
}); });
interface ChildrenViewProp {
childrenIds: string[];
handleCollapse: () => void;
indent?: string | number;
}
const ChildrenView = ({
childrenIds,
handleCollapse,
indent,
}: ChildrenViewProp) => {
const [currentView] = useCurrentView();
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
return (
<Children style={{ ...(!isKanbanScene && { marginLeft: indent }) }}>
{childrenIds.map((childId, idx) => {
if (isKanbanScene) {
return (
<StyledBorder key={childId}>
<RenderBlock blockId={childId} />
</StyledBorder>
);
}
return (
<TreeView
key={childId}
lastItem={idx === childrenIds.length - 1}
onClick={handleCollapse}
>
<RenderBlock key={childId} blockId={childId} />
</TreeView>
);
})}
</Children>
);
};
const CollapsedNode = forwardRef< const CollapsedNode = forwardRef<
HTMLDivElement, HTMLDivElement,
ComponentPropsWithoutRef<'div'> ComponentPropsWithoutRef<'div'>
@ -104,15 +59,15 @@ export const withTreeViewChildren = (
}; };
return (props: CreateView) => { return (props: CreateView) => {
const { block, editor } = props; const { block } = props;
const { BlockRender } = useBlockRender();
const collapsed = block.getProperty('collapsed')?.value; const collapsed = block.getProperty('collapsed')?.value;
const childrenIds = block.childrenIds; const childrenIds = block.childrenIds;
const showChildren = !collapsed && childrenIds.length > 0; const showChildren =
!collapsed &&
childrenIds.length > 0 &&
BlockRender !== NullBlockRender;
const [isSelect, setIsSelect] = useState<boolean>(false);
useOnSelect(block.id, (isSelect: boolean) => {
setIsSelect(isSelect);
});
const handleCollapse = () => { const handleCollapse = () => {
block.setProperty('collapsed', { value: true }); block.setProperty('collapsed', { value: true });
}; };
@ -122,15 +77,8 @@ export const withTreeViewChildren = (
}; };
return ( return (
<BlockContainer <>
editor={props.editor} {creator(props)}
block={block}
selected={isSelect}
className={Wrapper.toString()}
>
<BlockPendantProvider block={block}>
<div>{creator(props)}</div>
</BlockPendantProvider>
{collapsed && ( {collapsed && (
<CollapsedNode <CollapsedNode
@ -138,22 +86,24 @@ export const withTreeViewChildren = (
style={{ marginLeft: config.indent }} style={{ marginLeft: config.indent }}
/> />
)} )}
{showChildren && ( {showChildren &&
<ChildrenView childrenIds.map((childId, idx) => {
childrenIds={childrenIds} return (
handleCollapse={handleCollapse} <TreeView
indent={config.indent} key={childId}
/> lastItem={idx === childrenIds.length - 1}
)} onClick={handleCollapse}
</BlockContainer> style={{ marginLeft: config.indent }}
>
<BlockRender key={childId} blockId={childId} />
</TreeView>
);
})}
</>
); );
}; };
}; };
const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
const Children = Wrapper;
const TREE_COLOR = '#D5DFE6'; const TREE_COLOR = '#D5DFE6';
// adjust left and right margins of the the tree line // adjust left and right margins of the the tree line
const TREE_LINE_LEFT_OFFSET = '-16px'; const TREE_LINE_LEFT_OFFSET = '-16px';
@ -163,6 +113,7 @@ const TREE_LINE_WIDTH = '12px';
const TreeWrapper = styled('div')({ const TreeWrapper = styled('div')({
position: 'relative', position: 'relative',
display: 'flex',
}); });
const StyledTreeView = styled('div')({ const StyledTreeView = styled('div')({
@ -228,9 +179,3 @@ const LastItemRadius = styled('div')({
borderRadius: '0 0 0 3px', borderRadius: '0 0 0 3px',
pointerEvents: 'none', pointerEvents: 'none',
}); });
const StyledBorder = styled('div')({
border: '1px solid #E0E6EB',
borderRadius: '5px',
margin: '4px',
});

View File

@ -1,2 +1,4 @@
export { BlockRenderProvider, useBlockRender } from './Context';
export * from './RenderBlock'; export * from './RenderBlock';
export * from './RenderBlockChildren'; export * from './RenderBlockChildren';
export { withTreeViewChildren } from './WithTreeViewChildren';