Merge pull request #106 from toeverything/feat/editable-kanban-card

Feat/editable kanban card
This commit is contained in:
Whitewater
2022-08-19 11:02:42 +08:00
committed by GitHub
12 changed files with 110 additions and 63 deletions

View File

@@ -1,12 +1,12 @@
import { useState } from 'react';
import { CreateView } from '@toeverything/framework/virgo';
import { import {
BlockPendantProvider, BlockPendantProvider,
useOnSelect, useOnSelect,
} from '@toeverything/components/editor-core'; } from '@toeverything/components/editor-core';
import { Upload } from '../../components/upload/upload'; import { CreateView } from '@toeverything/framework/virgo';
import { useState } from 'react';
import { SourceView } from '../../components/source-view'; import { SourceView } from '../../components/source-view';
import { LinkContainer } from '../../components/style-container'; import { LinkContainer } from '../../components/style-container';
import { Upload } from '../../components/upload/upload';
const MESSAGES = { const MESSAGES = {
ADD_EMBED_LINK: 'Add embed link', ADD_EMBED_LINK: 'Add embed link',
@@ -38,7 +38,6 @@ export const EmbedLinkView = (props: EmbedLinkView) => {
{embedLinkUrl ? ( {embedLinkUrl ? (
<SourceView <SourceView
block={block} block={block}
editorElement={props.editorElement}
isSelected={isSelect} isSelected={isSelect}
viewType="embedLink" viewType="embedLink"
link={embedLinkUrl} link={embedLinkUrl}

View File

@@ -1,11 +1,18 @@
import type { KanbanCard } from '@toeverything/components/editor-core';
import { import {
KanbanCard,
RenderBlock, RenderBlock,
useEditor,
useKanban, useKanban,
useRefPage,
} from '@toeverything/components/editor-core'; } from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui'; import { PenIcon } from '@toeverything/components/icons';
import {
IconButton,
MuiClickAwayListener,
styled,
} from '@toeverything/components/ui';
import { useFlag } from '@toeverything/datasource/feature-flags'; import { useFlag } from '@toeverything/datasource/feature-flags';
import { useState, type MouseEvent } from 'react';
import { useRefPage } from './RefPage';
const CardContent = styled('div')({ const CardContent = styled('div')({
margin: '20px', margin: '20px',
@@ -23,6 +30,7 @@ const CardActions = styled('div')({
fontWeight: '300', fontWeight: '300',
color: '#98ACBD', color: '#98ACBD',
transition: 'all ease-in 0.2s', transition: 'all ease-in 0.2s',
zIndex: 1,
':hover': { ':hover': {
background: '#F5F7F8', background: '#F5F7F8',
@@ -39,11 +47,13 @@ const PlusIcon = styled('div')({
}); });
const CardContainer = styled('div')({ const CardContainer = styled('div')({
position: 'relative',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
backgroundColor: '#fff', backgroundColor: '#fff',
border: '1px solid #E2E7ED', border: '1px solid #E2E7ED',
borderRadius: '5px', borderRadius: '5px',
overflow: 'hidden',
[CardActions.toString()]: { [CardActions.toString()]: {
opacity: '0', opacity: '0',
@@ -55,6 +65,23 @@ const CardContainer = styled('div')({
}, },
}); });
const Overlay = styled('div')({
position: 'absolute',
width: '100%',
height: '100%',
background: 'transparent',
'& > *': {
visibility: 'hidden',
position: 'absolute',
right: '24px',
top: '16px',
},
'&:hover > *': {
visibility: 'visible',
},
});
export const CardItem = ({ export const CardItem = ({
id, id,
block, block,
@@ -64,24 +91,43 @@ export const CardItem = ({
}) => { }) => {
const { addSubItem } = useKanban(); const { addSubItem } = useKanban();
const { openSubPage } = useRefPage(); const { openSubPage } = useRefPage();
const [editable, setEditable] = useState(false);
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false); const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
const { editor } = useEditor();
const onAddItem = async () => { const onAddItem = async () => {
setEditable(true);
await addSubItem(block); await addSubItem(block);
}; };
const onClickCard = async () => { const onClickCard = async () => {
showKanbanRefPageFlag && openSubPage(id); openSubPage(id);
};
const onClickPen = (e: MouseEvent<Element>) => {
e.stopPropagation();
setEditable(true);
editor.selectionManager.activeNodeByNodeId(block.id);
}; };
return ( return (
<CardContainer onClick={onClickCard}> <MuiClickAwayListener onClickAway={() => setEditable(false)}>
<CardContent> <CardContainer>
<RenderBlock blockId={id} /> <CardContent>
</CardContent> <RenderBlock blockId={id} />
<CardActions onClick={onAddItem}> </CardContent>
<PlusIcon /> {showKanbanRefPageFlag && !editable && (
<span>Add a sub-block</span> <Overlay onClick={onClickCard}>
</CardActions> <IconButton backgroundColor="#fff" onClick={onClickPen}>
</CardContainer> <PenIcon />
</IconButton>
</Overlay>
)}
<CardActions onClick={onAddItem}>
<PlusIcon />
<span>Add a sub-block</span>
</CardActions>
</CardContainer>
</MuiClickAwayListener>
); );
}; };

View File

@@ -1,7 +1,7 @@
import { useEditor } from '@toeverything/components/editor-core';
import { MuiBackdrop, styled, useTheme } from '@toeverything/components/ui'; import { MuiBackdrop, styled, useTheme } from '@toeverything/components/ui';
import { createContext, ReactNode, useContext, useState } from 'react'; import { createContext, ReactNode, useContext, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { RenderBlock } from '../render-block';
const Dialog = styled('div')({ const Dialog = styled('div')({
flex: 1, flex: 1,
@@ -30,7 +30,7 @@ const Modal = ({ open, children }: { open: boolean; children?: ReactNode }) => {
onClick={closeSubPage} onClick={closeSubPage}
> >
<Dialog <Dialog
onClick={e => { onClick={(e: { stopPropagation: () => void }) => {
e.stopPropagation(); e.stopPropagation();
}} }}
> >
@@ -43,9 +43,21 @@ const Modal = ({ open, children }: { open: boolean; children?: ReactNode }) => {
}; };
const ModalPage = ({ blockId }: { blockId: string | null }) => { const ModalPage = ({ blockId }: { blockId: string | null }) => {
const { editor, editorElement } = useEditor();
const AffineEditor = editorElement as any;
return ( return (
<Modal open={!!blockId}> <Modal open={!!blockId}>
{blockId && <RenderBlock blockId={blockId} />} {blockId && (
<AffineEditor
workspace={editor.workspace}
rootBlockId={blockId}
scrollBlank={false}
// use edgeless mode prevent padding and blank bottom
isEdgeless
/>
)}
</Modal> </Modal>
); );
}; };

View File

@@ -4,30 +4,33 @@ import { SceneKanbanContext } from './context';
import { CardContainerWrapper } from './dndable/wrapper/CardContainerWrapper'; import { CardContainerWrapper } from './dndable/wrapper/CardContainerWrapper';
import type { ComponentType } from 'react'; import type { ComponentType } from 'react';
import type { CreateView } from '@toeverything/framework/virgo'; import type { CreateView } from '@toeverything/framework/virgo';
import { RefPageProvider } from './RefPage';
export const SceneKanban: ComponentType<CreateView> = withKanban<CreateView>( export const SceneKanban: ComponentType<CreateView> = withKanban<CreateView>(
({ editor, block }) => { ({ editor, block }) => {
const { kanban } = useKanban(); const { kanban } = useKanban();
return ( return (
<SceneKanbanContext.Provider value={{ editor, block }}> <RefPageProvider>
<CardContainerWrapper <SceneKanbanContext.Provider value={{ editor, block }}>
dataSource={kanban} <CardContainerWrapper
render={({ dataSource={kanban}
activeId, render={({
items, activeId,
containerIds, items,
isSortingContainer, containerIds,
}) => ( isSortingContainer,
<CardContainer }) => (
activeId={activeId} <CardContainer
items={items} activeId={activeId}
isSortingContainer={isSortingContainer} items={items}
containerIds={containerIds} isSortingContainer={isSortingContainer}
/> containerIds={containerIds}
)} />
/> )}
</SceneKanbanContext.Provider> />
</SceneKanbanContext.Provider>
</RefPageProvider>
); );
} }
); );

View File

@@ -1,9 +1,9 @@
import { memo, useEffect, useRef, useState } from 'react';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { memo, useEffect, useRef, useState } from 'react';
import { StyledBlockPreview } from '@toeverything/components/common'; import { StyledBlockPreview } from '@toeverything/components/common';
import { AsyncBlock, useEditor } from '@toeverything/components/editor-core';
import { services } from '@toeverything/datasource/db-service'; import { services } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '@toeverything/framework/virgo';
import { debounce, sleep } from '@toeverything/utils'; import { debounce, sleep } from '@toeverything/utils';
const updateTitle = async ( const updateTitle = async (
@@ -73,15 +73,15 @@ const useBlockTitle = (block: AsyncBlock, blockId: string) => {
type BlockPreviewProps = { type BlockPreviewProps = {
block: AsyncBlock; block: AsyncBlock;
blockId: string; blockId: string;
editorElement?: () => JSX.Element;
}; };
const InternalBlockPreview = (props: BlockPreviewProps) => { const InternalBlockPreview = (props: BlockPreviewProps) => {
const container = useRef<HTMLDivElement>(); const container = useRef<HTMLDivElement>();
const [preview, setPreview] = useState(true); const [preview, setPreview] = useState(true);
const title = useBlockTitle(props.block, props.blockId); const title = useBlockTitle(props.block, props.blockId);
const { editorElement } = useEditor();
const AffineEditor = props.editorElement as any; const AffineEditor = editorElement as any;
useEffect(() => { useEffect(() => {
if (container?.current) { if (container?.current) {

View File

@@ -1,17 +1,15 @@
import { import {
AsyncBlock, AsyncBlock,
useCurrentView, useCurrentView,
useLazyIframe,
} from '@toeverything/components/editor-core'; } from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui'; import { styled } from '@toeverything/components/ui';
import { ReactElement, ReactNode, useEffect, useRef, useState } from 'react'; import { ReactNode, useEffect, useRef, useState } from 'react';
import { SCENE_CONFIG } from '../../blocks/group/config'; import { SCENE_CONFIG } from '../../blocks/group/config';
import { BlockPreview } from './BlockView'; import { BlockPreview } from './BlockView';
import { formatUrl } from './format-url'; import { formatUrl } from './format-url';
export interface Props { export interface Props {
block: AsyncBlock; block: AsyncBlock;
editorElement?: () => JSX.Element;
viewType?: string; viewType?: string;
link: string; link: string;
// onResizeEnd: (data: any) => void; // onResizeEnd: (data: any) => void;
@@ -150,7 +148,7 @@ const LoadingContiner = () => {
}; };
export const SourceView = (props: Props) => { export const SourceView = (props: Props) => {
const { link, isSelected, block, editorElement } = props; const { link, isSelected, block } = props;
const src = formatUrl(link); const src = formatUrl(link);
// let iframeShow = useLazyIframe(src, 3000, iframeContainer); // let iframeShow = useLazyIframe(src, 3000, iframeContainer);
const [currentView] = useCurrentView(); const [currentView] = useCurrentView();
@@ -161,10 +159,7 @@ export const SourceView = (props: Props) => {
<SourceViewContainer isSelected={isSelected} scene={type}> <SourceViewContainer isSelected={isSelected} scene={type}>
<MouseMaskContainer /> <MouseMaskContainer />
<LazyIframe <LazyIframe src={src} fallback={LoadingContiner()} />
src={src}
fallback={LoadingContiner()}
></LazyIframe>
</SourceViewContainer> </SourceViewContainer>
</div> </div>
); );
@@ -175,11 +170,7 @@ export const SourceView = (props: Props) => {
style={{ padding: '0' }} style={{ padding: '0' }}
scene={type} scene={type}
> >
<BlockPreview <BlockPreview block={block} blockId={src} />
block={block}
editorElement={editorElement}
blockId={src}
/>
</SourceViewContainer> </SourceViewContainer>
); );
} }

View File

@@ -18,7 +18,6 @@ import { SelectBlock } from '../selection';
export interface CreateView { export interface CreateView {
block: AsyncBlock; block: AsyncBlock;
editor: Editor; editor: Editor;
editorElement: () => JSX.Element;
/** /**
* @deprecated Use recast table instead * @deprecated Use recast table instead
*/ */

View File

@@ -16,4 +16,4 @@ export * from './utils';
export * from './editor'; export * from './editor';
export { RefPageProvider, useRefPage } from './ref-page'; export { useEditor } from './Contexts';

View File

@@ -2,7 +2,6 @@ import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '../editor'; import { AsyncBlock } from '../editor';
import { ComponentType, createContext, ReactNode, useContext } from 'react'; import { ComponentType, createContext, ReactNode, useContext } from 'react';
import { RecastBlock } from './types'; import { RecastBlock } from './types';
import { RefPageProvider } from '../ref-page';
/** /**
* Determine whether the block supports RecastBlock * Determine whether the block supports RecastBlock
@@ -48,7 +47,7 @@ export const RecastBlockProvider = ({
return ( return (
<RecastBlockContext.Provider value={block}> <RecastBlockContext.Provider value={block}>
<RefPageProvider>{children}</RefPageProvider> {children}
</RecastBlockContext.Provider> </RecastBlockContext.Provider>
); );
}; };

View File

@@ -1 +0,0 @@
export { useRefPage, RefPageProvider } from './ModalPage';

View File

@@ -13,7 +13,7 @@ export function RenderBlock({
blockId, blockId,
hasContainer = true, hasContainer = true,
}: RenderBlockProps) { }: RenderBlockProps) {
const { editor, editorElement } = useEditor(); const { editor } = useEditor();
const { block } = useBlock(blockId); const { block } = useBlock(blockId);
const setRef = useCallback( const setRef = useCallback(
@@ -50,7 +50,6 @@ export function RenderBlock({
block={block} block={block}
columns={columns.columns} columns={columns.columns}
columnsFromId={columns.fromId} columnsFromId={columns.fromId}
editorElement={editorElement}
/> />
); );

View File

@@ -21,7 +21,7 @@ export const plugins: PluginCreator[] = [
CommandMenuPlugin, CommandMenuPlugin,
ReferenceMenuPlugin, ReferenceMenuPlugin,
TemplatePlugin, TemplatePlugin,
SelectionGroupPlugin, // SelectionGroupPlugin,
AddCommentPlugin, AddCommentPlugin,
GroupMenuPlugin, GroupMenuPlugin,
]; ];