diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5733e754..e132a92d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,9 @@ # 文件路径与代码负责人分配 # 对整个仓库设置代码负责人 -* @xiamidaxia @luics @dragooncjw +* @xiamidaxia @luics @dragooncjw @YuanHeDx @sanmaopep @louisyoungx # 对特定目录设置代码负责人 -/apps/docs/ @xiamidaxia @dragooncjw @YuanHeDx @sanmaopep +/apps/docs/ @xiamidaxia @dragooncjw @YuanHeDx @sanmaopep @louisyoungx /apps/demo-node-form/ @xiamidaxia @dragooncjw @YuanHeDx /packages/node-engine/ @xiamidaxia @dragooncjw @YuanHeDx /packages/variable-engine/ @xiamidaxia @dragooncjw @sanmaopep diff --git a/apps/demo-fixed-layout-simple/src/components/base-node.tsx b/apps/demo-fixed-layout-simple/src/components/base-node.tsx index 10d6dc7a..45f8cde8 100644 --- a/apps/demo-fixed-layout-simple/src/components/base-node.tsx +++ b/apps/demo-fixed-layout-simple/src/components/base-node.tsx @@ -38,10 +38,12 @@ export const BaseNode = ({ node }: { node: FlowNodeEntity }) => { ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}), }} > - ctx.operation.deleteNode(nodeRender.node)} - /> + {!nodeRender.readonly && ( + ctx.operation.deleteNode(nodeRender.node)} + /> + )} {form?.render()} ); diff --git a/apps/demo-fixed-layout-simple/src/components/branch-adder.tsx b/apps/demo-fixed-layout-simple/src/components/branch-adder.tsx index 60765a13..2b16e017 100644 --- a/apps/demo-fixed-layout-simple/src/components/branch-adder.tsx +++ b/apps/demo-fixed-layout-simple/src/components/branch-adder.tsx @@ -25,6 +25,15 @@ export function BranchAdder(props: PropsType) { content: '', }, }); + } else if (node.flowNodeType === 'multiInputs') { + block = operation.addBlock(node, { + id: `input_${nanoid(5)}`, + type: 'input', + data: { + title: 'New Input', + content: '', + }, + }); } else { block = operation.addBlock(node, { id: `branch_${nanoid(5)}`, @@ -43,6 +52,7 @@ export function BranchAdder(props: PropsType) { }); }, 10); } + if (playground.config.readonlyOrDisabled) return null; const className = [ diff --git a/apps/demo-fixed-layout-simple/src/components/flow-select.tsx b/apps/demo-fixed-layout-simple/src/components/flow-select.tsx index 09784505..e55512cd 100644 --- a/apps/demo-fixed-layout-simple/src/components/flow-select.tsx +++ b/apps/demo-fixed-layout-simple/src/components/flow-select.tsx @@ -13,9 +13,10 @@ export function FlowSelect() { const targetDemoJSON = FLOW_LIST[demoKey]; if (targetDemoJSON) { clientContext.history.stop(); // Stop redo/undo - clientContext.document.fromJSON(targetDemoJSON); - console.log(clientContext.document.toString()); - clientContext.history.start(); + clientContext.history.clear(); // Clear redo/undo + clientContext.document.fromJSON(targetDemoJSON); // Reload Data + console.log(clientContext.document.toString()); // Print the document tree + clientContext.history.start(); // Restart redo/undo clientContext.document.setLayout( targetDemoJSON.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT ); @@ -26,7 +27,7 @@ export function FlowSelect() { } // Fit View setTimeout(() => { - clientContext.playground.config.fitView(clientContext.document.root.bounds); + clientContext.playground.config.fitView(clientContext.document.root.bounds, true, 40); }, 20); } }, [demoKey]); diff --git a/apps/demo-fixed-layout-simple/src/components/tools.tsx b/apps/demo-fixed-layout-simple/src/components/tools.tsx index 89a8ea00..9bf4d630 100644 --- a/apps/demo-fixed-layout-simple/src/components/tools.tsx +++ b/apps/demo-fixed-layout-simple/src/components/tools.tsx @@ -1,12 +1,13 @@ import { useEffect, useState, useCallback } from 'react'; -import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor'; +import { usePlaygroundTools, useClientContext, useRefresh } from '@flowgram.ai/fixed-layout-editor'; import { IconButton, Space } from '@douyinfe/semi-ui'; import { IconUnlock, IconLock } from '@douyinfe/semi-icons'; export function Tools() { const { history, playground } = useClientContext(); const tools = usePlaygroundTools(); + const refresh = useRefresh(); const [canUndo, setCanUndo] = useState(false); const [canRedo, setCanRedo] = useState(false); const toggleReadonly = useCallback(() => { @@ -21,6 +22,11 @@ export function Tools() { return () => disposable.dispose(); }, [history]); + useEffect(() => { + const disposable = playground.config.onReadonlyOrDisabledChange(() => refresh()); + return () => disposable.dispose(); + }, [playground]); + return ( = { condition, mindmap: { ...mindmap, defaultLayout: FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT }, + tryCatch, }; diff --git a/apps/demo-fixed-layout-simple/src/data/mindmap.ts b/apps/demo-fixed-layout-simple/src/data/mindmap.ts index 18214398..40d21c40 100644 --- a/apps/demo-fixed-layout-simple/src/data/mindmap.ts +++ b/apps/demo-fixed-layout-simple/src/data/mindmap.ts @@ -20,6 +20,13 @@ export const mindmap: FlowDocumentJSON = { title: 'input_1', }, }, + { + id: 'input_3', + type: 'input', + data: { + title: 'input_3', + }, + }, // { // id: 'multiInputs_2', // type: 'multiInputs', diff --git a/apps/demo-fixed-layout-simple/src/data/trycatch.ts b/apps/demo-fixed-layout-simple/src/data/trycatch.ts index e69de29b..dc9a64d4 100644 --- a/apps/demo-fixed-layout-simple/src/data/trycatch.ts +++ b/apps/demo-fixed-layout-simple/src/data/trycatch.ts @@ -0,0 +1,56 @@ +import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor'; + +export const tryCatch: FlowDocumentJSON = { + nodes: [ + // 开始节点 + { + id: 'start_0', + type: 'start', + data: { + title: 'Start', + content: 'start content', + }, + blocks: [], + }, + // 分支节点 + { + id: 'tryCatch_0', + type: 'tryCatch', + data: { + title: 'TryCatch', + }, + blocks: [ + { + id: 'tryBlock_0', + type: 'tryBlock', + blocks: [], + }, + { + id: 'catchBlock_0', + type: 'catchBlock', + data: { + title: 'Catch Block 1', + }, + blocks: [], + }, + { + id: 'catchBlock_1', + type: 'catchBlock', + data: { + title: 'Catch Block 2', + }, + blocks: [], + }, + ], + }, + // 结束节点 + { + id: 'end_0', + type: 'end', + data: { + title: 'End', + content: 'end content', + }, + }, + ], +}; diff --git a/apps/demo-fixed-layout-simple/src/editor.tsx b/apps/demo-fixed-layout-simple/src/editor.tsx index 5aacc21a..5019cea7 100644 --- a/apps/demo-fixed-layout-simple/src/editor.tsx +++ b/apps/demo-fixed-layout-simple/src/editor.tsx @@ -6,12 +6,16 @@ import './index.css'; import { nodeRegistries } from './node-registries'; import { initialData } from './initial-data'; import { useEditorProps } from './hooks/use-editor-props'; +import { FLOW_LIST } from './data'; import { Tools } from './components/tools'; import { Minimap } from './components/minimap'; import { FlowSelect } from './components/flow-select'; -export const Editor = () => { - const editorProps = useEditorProps(initialData, nodeRegistries); +export const Editor = (props: { demoKey?: string }) => { + const editorProps = useEditorProps( + props.demoKey ? FLOW_LIST[props.demoKey] : initialData, + nodeRegistries + ); return (
diff --git a/apps/demo-fixed-layout-simple/src/hooks/use-editor-props.tsx b/apps/demo-fixed-layout-simple/src/hooks/use-editor-props.tsx index 4b623363..4e083add 100644 --- a/apps/demo-fixed-layout-simple/src/hooks/use-editor-props.tsx +++ b/apps/demo-fixed-layout-simple/src/hooks/use-editor-props.tsx @@ -106,6 +106,7 @@ export function useEditorProps( }, }, /** + * Playground init * 画布初始化 */ onInit: (ctx) => { @@ -117,11 +118,30 @@ export function useEditorProps( console.log('---- Playground Init ----'); }, /** + * Playground dispose * 画布销毁 */ onDispose: () => { console.log('---- Playground Dispose ----'); }, + /** + * 节点数据转换, 由 ctx.document.fromJSON 调用 + * Node data transformation, called by ctx.document.fromJSON + * @param node + * @param json + */ + fromNodeJSON(node, json) { + return json; + }, + /** + * 节点数据转换, 由 ctx.document.toJSON 调用 + * Node data transformation, called by ctx.document.toJSON + * @param node + * @param json + */ + toNodeJSON(node, json) { + return json; + }, plugins: () => [ /** * Minimap plugin diff --git a/apps/demo-fixed-layout-simple/src/node-registries.ts b/apps/demo-fixed-layout-simple/src/node-registries.ts index b531258a..7e24f600 100644 --- a/apps/demo-fixed-layout-simple/src/node-registries.ts +++ b/apps/demo-fixed-layout-simple/src/node-registries.ts @@ -1,9 +1,5 @@ import { nanoid } from 'nanoid'; -import { - FlowNodeRegistry, - FlowNodeEntity, - FlowNodeBaseType, -} from '@flowgram.ai/fixed-layout-editor'; +import { FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor'; /** * 自定义节点注册 @@ -77,35 +73,4 @@ export const nodeRegistries: FlowNodeRegistry[] = [ }; }, }, - { - type: 'multiStart2', - extend: 'dynamicSplit', - meta: { - isStart: true, - }, - onCreate(node, json) { - const doc = node.document; - const addedNodes: FlowNodeEntity[] = []; - const blocks = json.blocks || []; - - if (blocks.length > 0) { - // 水平布局 - const inlineBlocksNode = doc.addNode({ - id: `$inlineBlocks$${node.id}`, - type: FlowNodeBaseType.INLINE_BLOCKS, - originParent: node, - parent: node, - }); - addedNodes.push(inlineBlocksNode); - blocks.forEach((blockData) => { - doc.addBlock(node, blockData, addedNodes); - }); - } - return addedNodes; - }, - }, - { - type: 'tree', - extend: 'simpleSplit', - }, ]; diff --git a/apps/demo-fixed-layout/src/hooks/use-editor-props.ts b/apps/demo-fixed-layout/src/hooks/use-editor-props.ts index 5868fa74..2f5ad5ef 100644 --- a/apps/demo-fixed-layout/src/hooks/use-editor-props.ts +++ b/apps/demo-fixed-layout/src/hooks/use-editor-props.ts @@ -63,6 +63,24 @@ export function useEditorProps( }, }; }, + /** + * 节点数据转换, 由 ctx.document.fromJSON 调用 + * Node data transformation, called by ctx.document.fromJSON + * @param node + * @param json + */ + fromNodeJSON(node, json) { + return json; + }, + /** + * 节点数据转换, 由 ctx.document.toJSON 调用 + * Node data transformation, called by ctx.document.toJSON + * @param node + * @param json + */ + toNodeJSON(node, json) { + return json; + }, /** * Set default layout */ diff --git a/apps/demo-free-layout-simple/src/hooks/use-editor-props.tsx b/apps/demo-free-layout-simple/src/hooks/use-editor-props.tsx index 84163317..ef09c61d 100644 --- a/apps/demo-free-layout-simple/src/hooks/use-editor-props.tsx +++ b/apps/demo-free-layout-simple/src/hooks/use-editor-props.tsx @@ -34,6 +34,24 @@ export const useEditorProps = () => * 节点注册 */ nodeRegistries, + /** + * 节点数据转换, 由 ctx.document.fromJSON 调用 + * Node data transformation, called by ctx.document.fromJSON + * @param node + * @param json + */ + fromNodeJSON(node, json) { + return json; + }, + /** + * 节点数据转换, 由 ctx.document.toJSON 调用 + * Node data transformation, called by ctx.document.toJSON + * @param node + * @param json + */ + toNodeJSON(node, json) { + return json; + }, /** * Get the default node registry, which will be merged with the 'nodeRegistries' * 提供默认的节点注册,这个会和 nodeRegistries 做合并 diff --git a/apps/demo-free-layout/src/hooks/use-editor-props.tsx b/apps/demo-free-layout/src/hooks/use-editor-props.tsx index fa675fc4..d634ddde 100644 --- a/apps/demo-free-layout/src/hooks/use-editor-props.tsx +++ b/apps/demo-free-layout/src/hooks/use-editor-props.tsx @@ -57,6 +57,24 @@ export function useEditorProps( formMeta: defaultFormMeta, }; }, + /** + * 节点数据转换, 由 ctx.document.fromJSON 调用 + * Node data transformation, called by ctx.document.fromJSON + * @param node + * @param json + */ + fromNodeJSON(node, json) { + return json; + }, + /** + * 节点数据转换, 由 ctx.document.toJSON 调用 + * Node data transformation, called by ctx.document.toJSON + * @param node + * @param json + */ + toNodeJSON(node, json) { + return json; + }, lineColor: { hidden: 'transparent', default: '#4d53e8', diff --git a/packages/canvas-engine/document/src/flow-document.ts b/packages/canvas-engine/document/src/flow-document.ts index cebad07f..1f75876e 100644 --- a/packages/canvas-engine/document/src/flow-document.ts +++ b/packages/canvas-engine/document/src/flow-document.ts @@ -344,20 +344,17 @@ export class FlowDocument implements Disposable { parent: node, }); addedNodes.push(blockIconNode); - // inlineblocks 为空则不创建 - if (blocks.length > 0) { - // 水平布局 - const inlineBlocksNode = this.addNode({ - id: `$inlineBlocks$${node.id}`, - type: FlowNodeBaseType.INLINE_BLOCKS, - originParent: node, - parent: node, - }); - addedNodes.push(inlineBlocksNode); - blocks.forEach((blockData) => { - this.addBlock(node, blockData, addedNodes); - }); - } + // 水平布局 + const inlineBlocksNode = this.addNode({ + id: `$inlineBlocks$${node.id}`, + type: FlowNodeBaseType.INLINE_BLOCKS, + originParent: node, + parent: node, + }); + addedNodes.push(inlineBlocksNode); + blocks.forEach((blockData) => { + this.addBlock(node, blockData, addedNodes); + }); return addedNodes; } diff --git a/packages/canvas-engine/document/src/typings/flow-transition.ts b/packages/canvas-engine/document/src/typings/flow-transition.ts index 039d063a..58a0b32d 100644 --- a/packages/canvas-engine/document/src/typings/flow-transition.ts +++ b/packages/canvas-engine/document/src/typings/flow-transition.ts @@ -29,6 +29,7 @@ export interface FlowTransitionLine { arrow?: boolean; // 是否有箭头 renderKey?: string; // 只有自定义线条需要 isHorizontal?: boolean; // 是否为水平布局 + isDraggingLine?: boolean; // 是否是拖拽线条 activated?: boolean; // 是否激活态 side?: LABEL_SIDE_TYPE; // 区分是否分支前缀线条 style?: React.CSSProperties; diff --git a/packages/canvas-engine/fixed-layout-core/__tests__/__snapshots__/flow-activities.spec.ts.snap b/packages/canvas-engine/fixed-layout-core/__tests__/__snapshots__/flow-activities.spec.ts.snap index 3810bcc3..f41305e5 100644 --- a/packages/canvas-engine/fixed-layout-core/__tests__/__snapshots__/flow-activities.spec.ts.snap +++ b/packages/canvas-engine/fixed-layout-core/__tests__/__snapshots__/flow-activities.spec.ts.snap @@ -118,6 +118,7 @@ exports[`flow-activities > extend block addChild 1`] = ` |-------- $blockOrderIcon$test-extend-block |-- empty-split |---- $blockIcon$empty-split +|---- $inlineBlocks$empty-split |-- end_0" `; diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/block.ts b/packages/canvas-engine/fixed-layout-core/src/activities/block.ts index 01f846c2..b271cc36 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/block.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/block.ts @@ -38,8 +38,13 @@ export const BlockRegistry: FlowNodeRegistry = { // 当有其余分支的时候,绘制一条两个分支之间的线条 if (hasBranchDraggingAdder) { if (isVertical) { - const currentOffsetRightX = currentTransform.firstChild?.bounds?.right || 0; - const nextOffsetLeftX = currentTransform.next?.firstChild?.bounds?.left || 0; + const currentOffsetRightX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.right + : currentTransform.bounds.right; + const nextOffsetLeftX = + (currentTransform.next?.firstChild + ? currentTransform.next?.firstChild.bounds?.left + : currentTransform.next?.bounds?.left) || 0; const currentInputPointY = currentTransform.inputPoint.y; if (currentTransform?.next) { lines.push({ @@ -53,8 +58,13 @@ export const BlockRegistry: FlowNodeRegistry = { }); } } else { - const currentOffsetRightY = currentTransform.firstChild?.bounds?.bottom || 0; - const nextOffsetLeftY = currentTransform.next?.firstChild?.bounds?.top || 0; + const currentOffsetBottomX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.bottom + : currentTransform.bounds.bottom; + const nextOffsetTopX = + (currentTransform.next?.firstChild + ? currentTransform.next?.firstChild.bounds?.top + : currentTransform.next?.bounds?.top) || 0; const currentInputPointX = currentTransform.inputPoint.x; if (currentTransform?.next) { lines.push({ @@ -62,7 +72,7 @@ export const BlockRegistry: FlowNodeRegistry = { from: currentTransform.parent!.inputPoint, to: { x: currentInputPointX, - y: (currentOffsetRightY + nextOffsetLeftY) / 2, + y: (currentOffsetBottomX + nextOffsetTopX) / 2, }, side: LABEL_SIDE_TYPE.NORMAL_BRANCH, }); @@ -112,8 +122,13 @@ export const BlockRegistry: FlowNodeRegistry = { // 获取两个分支节点中间点作为拖拽标签插入位置 if (hasBranchDraggingAdder) { if (isVertical) { - const currentOffsetRightX = currentTransform.firstChild?.bounds?.right || 0; - const nextOffsetLeftX = currentTransform.next?.firstChild?.bounds?.left || 0; + const currentOffsetRightX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.right + : currentTransform.bounds.right; + const nextOffsetLeftX = + (currentTransform.next?.firstChild + ? currentTransform.next.firstChild.bounds?.left + : currentTransform.next?.bounds?.left) || 0; const currentInputPointY = currentTransform.inputPoint.y; if (currentTransform?.next) { draggingLabel.push({ @@ -129,17 +144,22 @@ export const BlockRegistry: FlowNodeRegistry = { }); } } else { - const currentOffsetRightY = currentTransform.firstChild?.bounds?.bottom || 0; - const nextOffsetLeftY = currentTransform.next?.firstChild?.bounds?.top || 0; + const currentOffsetBottomX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.bottom + : currentTransform.bounds.bottom; + const nextOffsetTopX = + (currentTransform.next?.firstChild + ? currentTransform.next.firstChild.bounds?.top + : currentTransform.next?.bounds?.top) || 0; const currentInputPointX = currentTransform.inputPoint.x; if (currentTransform?.next) { draggingLabel.push({ offset: { x: currentInputPointX, - y: (currentOffsetRightY + nextOffsetLeftY) / 2, + y: (currentOffsetBottomX + nextOffsetTopX) / 2, }, type: FlowTransitionLabelEnum.BRANCH_DRAGGING_LABEL, - width: nextOffsetLeftY - currentOffsetRightY, + width: nextOffsetTopX - currentOffsetBottomX, props: { side: LABEL_SIDE_TYPE.NORMAL_BRANCH, }, diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/input.ts b/packages/canvas-engine/fixed-layout-core/src/activities/input.ts index 616baf43..ea3b4e40 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/input.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/input.ts @@ -4,6 +4,7 @@ import { type FlowTransitionLine, FlowTransitionLineEnum, LABEL_SIDE_TYPE, + FlowTransitionLabelEnum, } from '@flowgram.ai/document'; /** @@ -15,7 +16,7 @@ export const InputRegistry: FlowNodeRegistry = { meta: { hidden: false, }, - getLines(transition) { + getLines(transition, layout) { const currentTransform = transition.transform; const { isVertical } = transition.entity; const lines: FlowTransitionLine[] = []; @@ -27,32 +28,44 @@ export const InputRegistry: FlowNodeRegistry = { // 当有其余分支的时候,绘制一条两个分支之间的线条 if (hasBranchDraggingAdder) { if (isVertical) { - const currentOffsetRightX = currentTransform.firstChild?.bounds?.right || 0; - const nextOffsetLeftX = currentTransform.next?.firstChild?.bounds?.left || 0; - const currentInputPointY = currentTransform.inputPoint.y; + const currentOffsetRightX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.right + : currentTransform.bounds.right; + const nextOffsetLeftX = + (currentTransform.next?.firstChild + ? currentTransform.next?.firstChild.bounds?.left + : currentTransform.next?.bounds?.left) || 0; + const currentInputPointY = currentTransform.outputPoint.y; if (currentTransform?.next) { lines.push({ - type: FlowTransitionLineEnum.DRAGGING_LINE, - from: currentTransform.parent!.inputPoint, - to: { + type: FlowTransitionLineEnum.MERGE_LINE, + isDraggingLine: true, + from: { x: (currentOffsetRightX + nextOffsetLeftX) / 2, y: currentInputPointY, }, + to: currentTransform.parent!.outputPoint, side: LABEL_SIDE_TYPE.NORMAL_BRANCH, }); } } else { - const currentOffsetRightY = currentTransform.firstChild?.bounds?.bottom || 0; - const nextOffsetLeftY = currentTransform.next?.firstChild?.bounds?.top || 0; - const currentInputPointX = currentTransform.inputPoint.x; + const currentOffsetBottomX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.bottom + : currentTransform.bounds.bottom; + const nextOffsetTopX = + (currentTransform.next?.firstChild + ? currentTransform.next?.firstChild.bounds?.top + : currentTransform.next?.bounds?.top) || 0; + const currentInputPointX = currentTransform.outputPoint.x; if (currentTransform?.next) { lines.push({ - type: FlowTransitionLineEnum.DRAGGING_LINE, - from: currentTransform.parent!.inputPoint, - to: { + type: FlowTransitionLineEnum.MERGE_LINE, + isDraggingLine: true, + from: { x: currentInputPointX, - y: (currentOffsetRightY + nextOffsetLeftY) / 2, + y: (currentOffsetBottomX + nextOffsetTopX) / 2, }, + to: currentTransform.parent!.outputPoint, side: LABEL_SIDE_TYPE.NORMAL_BRANCH, }); } @@ -71,7 +84,64 @@ export const InputRegistry: FlowNodeRegistry = { return lines; }, - getLabels() { - return []; + getLabels(transition) { + const currentTransform = transition.transform; + const { isVertical } = transition.entity; + + const draggingLabel = []; + + const hasBranchDraggingAdder = + currentTransform && currentTransform.entity.isInlineBlock && transition.renderData.draggable; + + // 获取两个分支节点中间点作为拖拽标签插入位置 + if (hasBranchDraggingAdder) { + if (isVertical) { + const currentOffsetRightX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.right + : currentTransform.bounds.right; + const nextOffsetLeftX = + (currentTransform.next?.firstChild + ? currentTransform.next.firstChild.bounds?.left + : currentTransform.next?.bounds?.left) || 0; + const currentInputPointY = currentTransform.outputPoint.y; + if (currentTransform?.next) { + draggingLabel.push({ + offset: { + x: (currentOffsetRightX + nextOffsetLeftX) / 2, + y: currentInputPointY, + }, + type: FlowTransitionLabelEnum.BRANCH_DRAGGING_LABEL, + width: nextOffsetLeftX - currentOffsetRightX, + props: { + side: LABEL_SIDE_TYPE.NORMAL_BRANCH, + }, + }); + } + } else { + const currentOffsetBottomX = currentTransform.firstChild + ? currentTransform.firstChild.bounds.bottom + : currentTransform.bounds.bottom; + const nextOffsetTopX = + (currentTransform.next?.firstChild + ? currentTransform.next.firstChild.bounds?.top + : currentTransform.next?.bounds?.top) || 0; + const currentInputPointX = currentTransform.outputPoint.x; + if (currentTransform?.next) { + draggingLabel.push({ + offset: { + x: currentInputPointX, + y: (currentOffsetBottomX + nextOffsetTopX) / 2, + }, + type: FlowTransitionLabelEnum.BRANCH_DRAGGING_LABEL, + width: nextOffsetTopX - currentOffsetBottomX, + props: { + side: LABEL_SIDE_TYPE.NORMAL_BRANCH, + }, + }); + } + } + } + + return [...draggingLabel]; }, }; diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/multi-inputs.ts b/packages/canvas-engine/fixed-layout-core/src/activities/multi-inputs.ts index 2afa3f52..50fc049a 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/multi-inputs.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/multi-inputs.ts @@ -1,4 +1,14 @@ -import { FlowNodeBaseType, FlowNodeSplitType, type FlowNodeRegistry } from '@flowgram.ai/document'; +import { Point } from '@flowgram.ai/utils'; +import { FlowRendererKey } from '@flowgram.ai/renderer'; +import { + FlowNodeBaseType, + type FlowNodeRegistry, + FlowNodeRenderData, + FlowTransitionLabelEnum, + FlowNodeSplitType, + getDefaultSpacing, + ConstantKeys, +} from '@flowgram.ai/document'; /** * 多输入节点, 只能作为 开始节点 @@ -15,6 +25,7 @@ export const MultiInputsRegistry: FlowNodeRegistry = { type: FlowNodeBaseType.BLOCK_ICON, meta: { hidden: true, + spacing: 0, }, getLines() { return []; @@ -25,8 +36,54 @@ export const MultiInputsRegistry: FlowNodeRegistry = { }, { type: FlowNodeBaseType.INLINE_BLOCKS, - getLabels() { - return []; + meta: { + inlineSpacingPre: 0, + }, + getLabels(transition) { + const isVertical = transition.entity.isVertical; + const currentTransform = transition.transform; + const spacing = getDefaultSpacing( + transition.entity, + ConstantKeys.INLINE_BLOCKS_PADDING_BOTTOM + ); + + if (currentTransform.collapsed || transition.entity.childrenLength === 0) { + return [ + { + type: FlowTransitionLabelEnum.CUSTOM_LABEL, + renderKey: FlowRendererKey.BRANCH_ADDER, + offset: Point.move( + currentTransform.outputPoint, + isVertical ? { y: spacing } : { x: spacing } + ), + props: { + // 激活状态 + activated: transition.entity.getData(FlowNodeRenderData)!.activated, + transform: currentTransform, + // 传给外部使用的 node 信息 + node: currentTransform.originParent?.entity, + }, + }, + ]; + } + + return [ + { + type: FlowTransitionLabelEnum.CUSTOM_LABEL, + renderKey: FlowRendererKey.BRANCH_ADDER, + offset: Point.move( + currentTransform.outputPoint, + isVertical ? { y: -spacing / 2 } : { x: -spacing / 2 } + ), + props: { + // 激活状态 + activated: transition.entity.getData(FlowNodeRenderData)!.activated, + transform: currentTransform, + // 传给外部使用的 node 信息 + node: currentTransform.originParent?.entity, + }, + }, + ]; }, }, ], diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/multi-outputs.ts b/packages/canvas-engine/fixed-layout-core/src/activities/multi-outputs.ts index 3401b328..d6812f21 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/multi-outputs.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/multi-outputs.ts @@ -1,5 +1,11 @@ -import { FlowNodeBaseType, type FlowNodeRegistry, FlowNodeSplitType } from '@flowgram.ai/document'; +import { + FlowLayoutDefault, + type FlowNodeRegistry, + FlowNodeSplitType, + FlowNodeBaseType, +} from '@flowgram.ai/document'; +import { DynamicSplitRegistry } from './dynamic-split'; import { BlockRegistry } from './block'; /** @@ -13,10 +19,42 @@ import { BlockRegistry } from './block'; export const MultiOuputsRegistry: FlowNodeRegistry = { type: FlowNodeBaseType.MULTI_OUTPUTS, extend: FlowNodeSplitType.SIMPLE_SPLIT, + meta: { + isNodeEnd: true, + }, getLines: (transition, layout) => { + // 嵌套在 mutliOutputs 下边 if (transition.entity.parent?.flowNodeType === FlowNodeBaseType.INLINE_BLOCKS) { return BlockRegistry.getLines!(transition, layout); } return []; }, + getLabels: (transition, layout) => [ + ...DynamicSplitRegistry.getLabels!(transition, layout), + ...BlockRegistry.getLabels!(transition, layout), + ], + getOutputPoint(transform, layout) { + const isVertical = FlowLayoutDefault.isVertical(layout); + const lastChildOutput = transform.lastChild?.outputPoint; + + if (isVertical) { + return { + x: lastChildOutput ? lastChildOutput.x : transform.bounds.center.x, + y: transform.bounds.bottom, + }; + } + + return { + x: transform.bounds.right, + y: lastChildOutput ? lastChildOutput.y : transform.bounds.center.y, + }; + }, + extendChildRegistries: [ + { + type: FlowNodeBaseType.BLOCK_ICON, + meta: { + // isNodeEnd: true + }, + }, + ], }; diff --git a/packages/canvas-engine/renderer/src/components/LinesRenderer.tsx b/packages/canvas-engine/renderer/src/components/LinesRenderer.tsx index 2c1aedb4..66715777 100644 --- a/packages/canvas-engine/renderer/src/components/LinesRenderer.tsx +++ b/packages/canvas-engine/renderer/src/components/LinesRenderer.tsx @@ -37,11 +37,11 @@ export function createLines(props: PropsType): void { const { lineActivated } = renderData || {}; const draggingLineHide = - line.type === FlowTransitionLineEnum.DRAGGING_LINE && + (line.type === FlowTransitionLineEnum.DRAGGING_LINE || line.isDraggingLine) && !dragService.isDroppableBranch(data.entity, line.side); const draggingLineActivated = - line.type === FlowTransitionLineEnum.DRAGGING_LINE && + (line.type === FlowTransitionLineEnum.DRAGGING_LINE || line.isDraggingLine) && data.entity?.id === dragService.dropNodeId && line.side === dragService.labelSide; diff --git a/packages/client/fixed-layout-editor/__tests__/services/history-operation-service/move-node.test.ts b/packages/client/fixed-layout-editor/__tests__/services/history-operation-service/move-node.test.ts index 7c64569d..4d3fd324 100644 --- a/packages/client/fixed-layout-editor/__tests__/services/history-operation-service/move-node.test.ts +++ b/packages/client/fixed-layout-editor/__tests__/services/history-operation-service/move-node.test.ts @@ -89,11 +89,10 @@ describe('history-operation-service moveNode', () => { const split = flowDocument.getNode('dynamicSplit_0'); const split1 = flowDocument.getNode('dynamicSplit_1'); - // 没有执行成功,因为没有 children 的分支节点,$inlineBlocks$dynamicSplit_1 不存在 - expect(getNodeChildrenIds(split, true)).toEqual(['block_0', 'block_1', 'block_2']); - expect(getNodeChildrenIds(split1, true)).toEqual([]); + expect(getNodeChildrenIds(split, true)).toEqual(['block_0', 'block_2']); + expect(getNodeChildrenIds(split1, true)).toEqual(['block_1']); - expect(historyService.canUndo()).toBe(false); + expect(historyService.canUndo()).toBe(true); }); it('move node without parent and index', async () => {