From aa62599d13e1e48e57e4a255c7816f72a43f5144 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Wed, 24 Aug 2022 18:54:30 +0800 Subject: [PATCH 1/5] refactor: kanban block render --- .../src/render-block/RenderKanbanBlock.tsx | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx b/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx index 54f9e2276e..9eb01dbde4 100644 --- a/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx +++ b/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx @@ -25,32 +25,64 @@ const OneLevelBlockRender = ({ blockId }: RenderBlockProps) => { ); }; -export const KanbanBlockRender = ({ blockId }: RenderBlockProps) => { +export const KanbanParentBlockRender = ({ + blockId, + active, +}: RenderBlockProps & { active?: boolean }) => { + return ( + + + + ); +}; + +const KanbanChildrenRender = ({ + blockId, + activeBlock, +}: RenderBlockProps & { activeBlock?: string | null }) => { const { block } = useBlock(blockId); if (!block) { - return ( - - - - ); + return null; } return ( - {block?.childrenIds.map(childId => ( - + - + ))} ); }; -const StyledBorder = styled('div')({ - border: '1px solid #E0E6EB', - borderRadius: '5px', - margin: '4px', - padding: '0 4px', -}); +export const KanbanBlockRender = ({ + blockId, + activeBlock, +}: RenderBlockProps & { activeBlock?: string | null }) => { + return ( + + + + + ); +}; + +const BlockBorder = styled('div')<{ active?: boolean }>( + ({ theme, active }) => ({ + borderRadius: '5px', + padding: '0 4px', + border: `1px solid ${ + active ? theme.affine.palette.primary : 'transparent' + }`, + }) +); + +const ChildBorder = styled(BlockBorder)(({ active, theme }) => ({ + border: `1px solid ${active ? theme.affine.palette.primary : '#E0E6EB'}`, + margin: '4px 0', +})); From 876a8755769fbef03806525bf0fb046982519573 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Wed, 24 Aug 2022 18:56:19 +0800 Subject: [PATCH 2/5] feat: add kanban card editable border --- .../blocks/group/scene-kanban/CardItem.tsx | 34 ++++++++----------- libs/components/editor-core/src/index.ts | 7 +--- .../editor-core/src/kanban/Context.tsx | 10 ++---- .../editor-core/src/render-block/index.ts | 5 +-- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx index 8f79a6c208..e50dccf826 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx @@ -1,6 +1,6 @@ import { + KanbanBlockRender, KanbanCard, - useBlockRender, useEditor, useKanban, } from '@toeverything/components/editor-core'; @@ -14,9 +14,9 @@ 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')(({ theme }) => ({ margin: '20px', -}); +})); const CardActions = styled('div')({ cursor: 'pointer', @@ -82,42 +82,38 @@ const Overlay = styled('div')({ }, }); -export const CardItem = ({ - id, - block, -}: { - id: KanbanCard['id']; - block: KanbanCard['block']; -}) => { +export const CardItem = ({ block }: { block: KanbanCard['block'] }) => { const { addSubItem } = useKanban(); const { openSubPage } = useRefPage(); - const [editable, setEditable] = useState(false); + const [editableBlock, setEditableBlock] = useState(null); const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false); const { editor } = useEditor(); - const { BlockRender } = useBlockRender(); const onAddItem = async () => { - setEditable(true); - await addSubItem(block); + const newItem = await addSubItem(block); + setEditableBlock(newItem.id); }; const onClickCard = async () => { - openSubPage(id); + openSubPage(block.id); }; const onClickPen = (e: MouseEvent) => { e.stopPropagation(); - setEditable(true); + setEditableBlock(block.id); editor.selectionManager.activeNodeByNodeId(block.id); }; return ( - setEditable(false)}> + setEditableBlock(null)}> - + - {showKanbanRefPageFlag && !editable && ( + {showKanbanRefPageFlag && !editableBlock && ( diff --git a/libs/components/editor-core/src/index.ts b/libs/components/editor-core/src/index.ts index 6059350bc9..3b793b0a6f 100644 --- a/libs/components/editor-core/src/index.ts +++ b/libs/components/editor-core/src/index.ts @@ -6,11 +6,6 @@ export * from './kanban'; export * from './kanban/types'; export * from './recast-block'; export * from './recast-block/types'; -export { - BlockRenderProvider, - RenderBlockChildren, - useBlockRender, - withTreeViewChildren, -} from './render-block'; +export * from './render-block'; export { MIN_PAGE_WIDTH, RenderRoot } from './RenderRoot'; export * from './utils'; diff --git a/libs/components/editor-core/src/kanban/Context.tsx b/libs/components/editor-core/src/kanban/Context.tsx index d0fc58ed0a..12cc0db2f1 100644 --- a/libs/components/editor-core/src/kanban/Context.tsx +++ b/libs/components/editor-core/src/kanban/Context.tsx @@ -10,8 +10,6 @@ import { RecastMetaProperty, RecastPropertyId, } from '../recast-block/types'; -import { BlockRenderProvider } from '../render-block'; -import { KanbanBlockRender } from '../render-block/RenderKanbanBlock'; import { useInitKanbanEffect, useRecastKanban } from './kanban'; import { KanbanGroup } from './types'; @@ -56,11 +54,9 @@ export const KanbanProvider = ({ }; return ( - - - {children} - - + + {children} + ); }; diff --git a/libs/components/editor-core/src/render-block/index.ts b/libs/components/editor-core/src/render-block/index.ts index 0d5c809c4c..5d3c6d122e 100644 --- a/libs/components/editor-core/src/render-block/index.ts +++ b/libs/components/editor-core/src/render-block/index.ts @@ -1,4 +1,5 @@ export { BlockRenderProvider, useBlockRender } from './Context'; -export * from './RenderBlock'; -export * from './RenderBlockChildren'; +export { NullBlockRender, RenderBlock } from './RenderBlock'; +export { RenderBlockChildren } from './RenderBlockChildren'; +export { KanbanBlockRender } from './RenderKanbanBlock'; export { withTreeViewChildren } from './WithTreeViewChildren'; From 797652d66c27a08614f341826081ffa0c05a886e Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Wed, 24 Aug 2022 18:57:46 +0800 Subject: [PATCH 3/5] chore: remove redundant kanban item id --- .../src/blocks/group/scene-kanban/CardContext.tsx | 12 ++++++------ .../drag-overlay/renderContainerDragOverlay.tsx | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx index 13fcaa45e3..74afbe087d 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx @@ -1,12 +1,12 @@ -import { useCallback } from 'react'; -import { CardItem } from './CardItem'; -import { styled } from '@toeverything/components/ui'; -import { useKanban } from '@toeverything/components/editor-core'; -import { CardItemPanelWrapper } from './dndable/wrapper/CardItemPanelWrapper'; import type { KanbanCard, KanbanGroup, } from '@toeverything/components/editor-core'; +import { useKanban } from '@toeverything/components/editor-core'; +import { styled } from '@toeverything/components/ui'; +import { useCallback } from 'react'; +import { CardItem } from './CardItem'; +import { CardItemPanelWrapper } from './dndable/wrapper/CardItemPanelWrapper'; const AddCardWrapper = styled('div')({ display: 'flex', @@ -48,7 +48,7 @@ export const CardContext = (props: Props) => { item={item} active={activeId === id} > - + ); diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/dndable/drag-overlay/renderContainerDragOverlay.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/dndable/drag-overlay/renderContainerDragOverlay.tsx index 957ee8599f..b2b1382e5c 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/dndable/drag-overlay/renderContainerDragOverlay.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/dndable/drag-overlay/renderContainerDragOverlay.tsx @@ -1,7 +1,6 @@ -import { CardItemWrapper } from '../wrapper/CardItemWrapper'; import { CardItem } from '../../CardItem'; -import type { KanbanCard } from '@toeverything/components/editor-core'; import type { DndableItems } from '../type'; +import { CardItemWrapper } from '../wrapper/CardItemWrapper'; export function renderContainerDragOverlay({ containerId, @@ -18,7 +17,7 @@ export function renderContainerDragOverlay({ return ( } + card={} index={index} /> ); From d98e44bfc9c1b631e803e0d89d8a74db51821ab6 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Wed, 24 Aug 2022 18:58:53 +0800 Subject: [PATCH 4/5] chore: clean code --- .../src/blocks/group/scene-kanban/CardItem.tsx | 8 +++----- .../src/block-pendant/BlockPendantProvider.tsx | 6 +++--- libs/components/editor-core/src/editor/views/base-view.ts | 4 ++-- libs/components/editor-core/src/kanban/kanban.ts | 1 + libs/components/editor-core/src/recast-block/group.ts | 2 +- libs/components/editor-core/src/recast-block/property.ts | 4 ++-- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx index e50dccf826..b527711217 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx @@ -10,13 +10,12 @@ import { MuiClickAwayListener, styled, } from '@toeverything/components/ui'; -import { useFlag } from '@toeverything/datasource/feature-flags'; import { useState, type MouseEvent } from 'react'; import { useRefPage } from './RefPage'; -const CardContent = styled('div')(({ theme }) => ({ +const CardContent = styled('div')({ margin: '20px', -})); +}); const CardActions = styled('div')({ cursor: 'pointer', @@ -86,7 +85,6 @@ export const CardItem = ({ block }: { block: KanbanCard['block'] }) => { const { addSubItem } = useKanban(); const { openSubPage } = useRefPage(); const [editableBlock, setEditableBlock] = useState(null); - const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false); const { editor } = useEditor(); const onAddItem = async () => { @@ -113,7 +111,7 @@ export const CardItem = ({ block }: { block: KanbanCard['block'] }) => { activeBlock={editableBlock} /> - {showKanbanRefPageFlag && !editableBlock && ( + {!editableBlock && ( diff --git a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx index 6d099d8ae4..52d397b1c2 100644 --- a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx +++ b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx @@ -1,10 +1,10 @@ -import type { PropsWithChildren } from 'react'; import { styled } from '@toeverything/components/ui'; +import type { PropsWithChildren } from 'react'; +import { useRef } from 'react'; import type { AsyncBlock } from '../editor'; +import { getRecastItemValue, useRecastBlockMeta } from '../recast-block'; import { PendantPopover } from './pendant-popover'; import { PendantRender } from './pendant-render'; -import { useRef } from 'react'; -import { getRecastItemValue, useRecastBlockMeta } from '../recast-block'; /** * @deprecated */ diff --git a/libs/components/editor-core/src/editor/views/base-view.ts b/libs/components/editor-core/src/editor/views/base-view.ts index ff6ab06098..d4867f2b59 100644 --- a/libs/components/editor-core/src/editor/views/base-view.ts +++ b/libs/components/editor-core/src/editor/views/base-view.ts @@ -1,15 +1,15 @@ -import { ComponentType, ReactElement } from 'react'; import type { Column } from '@toeverything/datasource/db-service'; import { ArrayOperation, BlockDecoration, MapOperation, } from '@toeverything/datasource/jwt'; +import type { ComponentType, ReactElement } from 'react'; import type { EventData } from '../block'; import { AsyncBlock } from '../block'; +import { HTML2BlockResult } from '../clipboard'; import type { Editor } from '../editor'; import { SelectBlock } from '../selection'; -import { HTML2BlockResult } from '../clipboard'; export interface CreateView { block: AsyncBlock; editor: Editor; diff --git a/libs/components/editor-core/src/kanban/kanban.ts b/libs/components/editor-core/src/kanban/kanban.ts index 218476ac44..9beb2a9cd6 100644 --- a/libs/components/editor-core/src/kanban/kanban.ts +++ b/libs/components/editor-core/src/kanban/kanban.ts @@ -358,6 +358,7 @@ export const useKanban = () => { } card.append(newBlock); editor.selectionManager.activeNodeByNodeId(newBlock.id); + return newBlock; }, [editor] ); diff --git a/libs/components/editor-core/src/recast-block/group.ts b/libs/components/editor-core/src/recast-block/group.ts index 9879ba43df..23dc0c6873 100644 --- a/libs/components/editor-core/src/recast-block/group.ts +++ b/libs/components/editor-core/src/recast-block/group.ts @@ -1,7 +1,7 @@ import { Protocol } from '@toeverything/datasource/db-service'; import type { AsyncBlock, BlockEditor } from '../editor'; -import type { RecastBlock } from '.'; import { cloneRecastMetaTo, mergeRecastMeta } from './property'; +import type { RecastBlock } from './types'; const mergeGroupProperties = async (...groups: RecastBlock[]) => { const [headGroup, ...restGroups] = groups; diff --git a/libs/components/editor-core/src/recast-block/property.ts b/libs/components/editor-core/src/recast-block/property.ts index 3676a1777d..d27b10ef58 100644 --- a/libs/components/editor-core/src/recast-block/property.ts +++ b/libs/components/editor-core/src/recast-block/property.ts @@ -2,6 +2,7 @@ import { nanoid } from 'nanoid'; import { useCallback } from 'react'; import { AsyncBlock } from '../editor'; import { useRecastBlock } from './Context'; +import { getHistory, removeHistory, setHistory } from './history'; import type { RecastBlock, RecastItem, StatusProperty } from './types'; import { META_PROPERTIES_KEY, @@ -15,7 +16,6 @@ import { SelectProperty, TABLE_VALUES_KEY, } from './types'; -import { getHistory, removeHistory, setHistory } from './history'; /** * Generate a unique id for a property @@ -275,7 +275,7 @@ const isSelectLikeProperty = ( metaProperty?: RecastMetaProperty ): metaProperty is SelectProperty | MultiSelectProperty | StatusProperty => { return ( - metaProperty && + !!metaProperty && (metaProperty.type === PropertyType.Status || metaProperty.type === PropertyType.Select || metaProperty.type === PropertyType.MultiSelect) From d6009e5e483da7cb29575b2eb42f59963691fc85 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Wed, 24 Aug 2022 20:39:45 +0800 Subject: [PATCH 5/5] feat: add kanban todo progress --- .../src/render-block/RenderKanbanBlock.tsx | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx b/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx index 9eb01dbde4..f9b532f6b3 100644 --- a/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx +++ b/libs/components/editor-core/src/render-block/RenderKanbanBlock.tsx @@ -1,4 +1,7 @@ import { styled } from '@toeverything/components/ui'; +import { Protocol } from '@toeverything/datasource/db-service'; +import { useEffect, useState } from 'react'; +import { AsyncBlock } from '../editor'; import { useBlock } from '../hooks'; import { BlockRenderProvider } from './Context'; import { NullBlockRender, RenderBlock, RenderBlockProps } from './RenderBlock'; @@ -36,18 +39,73 @@ export const KanbanParentBlockRender = ({ ); }; +const useBlockProgress = (block?: AsyncBlock) => { + const [progress, setProgress] = useState(1); + + useEffect(() => { + if (!block) { + return; + } + const updateProgress = async () => { + const children = await block.children(); + const todoChildren = children.filter( + child => child.type === Protocol.Block.Type.todo + ); + const checkedTodoChildren = todoChildren.filter( + child => child.getProperty('checked')?.value === true + ); + setProgress(checkedTodoChildren.length / todoChildren.length); + }; + + let childrenQueue: (() => void)[] = []; + const childrenUnobserve = () => { + childrenQueue.forEach(fn => fn()); + childrenQueue = []; + }; + + const observeChildren = async () => { + const children = await block.children(); + + childrenUnobserve(); + children.forEach(child => { + const unobserve = child.onUpdate(() => { + updateProgress(); + }); + childrenQueue.push(unobserve); + }); + }; + + observeChildren(); + updateProgress(); + + const unobserve = block.onUpdate(() => { + observeChildren(); + updateProgress(); + }); + + return () => { + unobserve(); + childrenUnobserve(); + }; + }, [block]); + + return progress; +}; + const KanbanChildrenRender = ({ blockId, activeBlock, }: RenderBlockProps & { activeBlock?: string | null }) => { const { block } = useBlock(blockId); + const progress = useBlockProgress(block); - if (!block) { + if (!block || !block?.childrenIds.length) { return null; } return ( + {block?.childrenIds.map(childId => ( @@ -82,6 +140,27 @@ const BlockBorder = styled('div')<{ active?: boolean }>( }) ); +const ProgressBar = styled('div')<{ progress?: number }>( + ({ progress = 1 }) => ({ + height: '3px', + width: '100%', + background: '#CFE5FF', + borderRadius: '5px', + overflow: 'hidden', + margin: '12px 0', + + '::after': { + content: '""', + position: 'relative', + display: 'flex', + background: '#60A5FA', + height: '100%', + width: `${(progress * 100).toFixed(2)}%`, + transition: 'ease 0.5s all', + }, + }) +); + const ChildBorder = styled(BlockBorder)(({ active, theme }) => ({ border: `1px solid ${active ? theme.affine.palette.primary : '#E0E6EB'}`, margin: '4px 0',