# Lines

- [WorkflowLinesManager](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/workflow-lines-manager.ts) manages all lines
- [WorkflowNodeLinesData](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entity-datas/workflow-node-lines-data.ts) manages lines connected to nodes
- [WorkflowLineEntity](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entities/workflow-line-entity.ts) line entity

## Get All Line Entities

```ts pure
const allLines = ctx.document.linesManager.getAllLines() // line entities containing from/to representing connected nodes
```

## Create/Delete Lines

```ts pure
// from and to are the node IDs to connect, fromPort, toPort are port IDs, if node has single port can be omitted
const line = ctx.document.linesManager.createLine({ from, to, fromPort, toPort })

// delete line
line.dispose()
```

## Export Line Data

:::note Basic line structure:

- sourceNodeID: `string` source node id
- targetNodeID: `string` target node id
- sourcePortID?: `string | number` source port id, defaults to node's default port if omitted
- targetPortID?: `string | number` target port id, defaults to node's default port if omitted

:::
```ts pure
const json = ctx.document.linesManager.toJSON()
```

## Get Input/Output Nodes or Lines for Current Node

```ts pure
import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor'

// get input nodes (calculated through connection lines)
node.getData(WorkflowNodeLinesData).inputNodes
// get all input nodes (recursively gets all upstream nodes)
node.getData(WorkflowNodeLinesData).allInputNodes
// get output nodes
node.getData(WorkflowNodeLinesData).outputNodes
// get all output nodes
node.getData(WorkflowNodeLinesData).allOutputNodes
// input lines
node.getData(WorkflowNodeLinesData).inputLines
// output lines
node.getData(WorkflowNodeLinesData).outputLines
```

## Line Configuration

We provide rich line configuration parameters for `FreeLayoutEditorProvider`, see details in [FreeLayoutProps](https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/preset/free-layout-props.ts)

```tsx pure
interface FreeLayoutProps {
    /**
     * Line color configuration
     */
    lineColor?: LineColor;
    /**
     * Determine if line should be marked red
     * @param ctx
     * @param fromPort
     * @param toPort
     * @param lines
     */
    isErrorLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity | undefined, lines: WorkflowLinesManager) => boolean;
    /**
     * Determine if port should be marked red
     * @param ctx
     * @param port
     */
    isErrorPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean;
    /**
     * Determine if port should be disabled
     * @param ctx
     * @param port
     */
    isDisabledPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean;
    /**
     * Determine if line arrow should be reversed
     * @param ctx
     * @param line
     */
    isReverseLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
    /**
     * Determine if line arrow should be hidden
     * @param ctx
     * @param line
     */
    isHideArrowLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
    /**
     * Determine if line should show flowing effect
     * @param ctx
     * @param line
     */
    isFlowingLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
    /**
     * Determine if line should be disabled
     * @param ctx
     * @param line
     */
    isDisabledLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
    /**
     * Determine if line should be vertical
     * @param ctx
     * @param line
     */
    isVerticalLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
    /**
     * Line drag end
     * @param ctx
     * @param params
     */
    onDragLineEnd?: (ctx: FreeLayoutPluginContext, params: onDragLineEndParams) => Promise<void>;
    /**
     * Set line renderer type
     * @param ctx
     * @param line
     */
    setLineRenderType?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => LineRenderType | undefined;
    /**
     * Set line style
     * @param ctx
     * @param line
     */
    setLineClassName?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => string | undefined;
    /**
     * Whether to allow line creation
     * @param ctx
     * @param fromPort - start point
     * @param toPort - target point
     */
    canAddLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity, lines: WorkflowLinesManager, silent?: boolean) => boolean;
    /**
     * Whether to allow line deletion
     * @param ctx
     * @param line - target line
     * @param newLineInfo - new line info
     * @param silent - if false, can show toast
     */
    canDeleteLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity, newLineInfo?: Required<WorkflowLinePortInfo>, silent?: boolean) => boolean;
    /**
     * Whether to allow line reset
     * @param fromPort - start point
     * @param oldToPort - old connection point
     * @param newToPort - new connection point
     * @param lines - line manager
     */
    canResetLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, oldToPort: WorkflowPortEntity, newToPort: WorkflowPortEntity, lines: WorkflowLinesManager) => boolean;
}
```

### 1. Custom Colors

```tsx pure

function App() {
  const editorProps: FreeLayoutProps = {
      lineColor: {
        hidden: 'transparent',
        default: '#4d53e8',
        drawing: '#5DD6E3',
        hovered: '#37d0ff',
        selected: '#37d0ff',
        error: 'red',
        flowing: '#ff6b35', // Color for flowing lines (e.g., during workflow execution)
      },
      // ...others
  }
  return (
    <FreeLayoutEditorProvider {...editorProps}>
      <EditorRenderer className="demo-editor" />
    </FreeLayoutEditorProvider>
  )
}
```
### 2. Limit Single Output Port to One Line

<img loading="lazy" style={{ width: 500, margin: '0 auto' }} className="invert-img" src="/free-layout/line-limit.gif"/>

```tsx pure

function App() {
  const editorProps: FreeLayoutProps = {
      /*
       * Check whether the line can be added
       */
      canAddLine(ctx, fromPort, toPort) {
        // not the same node
        if (fromPort.node === toPort.node) {
          return false;
        }
        // control number of lines
        if (fromPort.availableLines.length >= 1) {
          return false
        }
        return true;
      },
      // ...others
  }
  return (
    <FreeLayoutEditorProvider {...editorProps}>
      <EditorRenderer className="demo-editor" />
    </FreeLayoutEditorProvider>
  )
}
```

### 3. Add Node by Connecting to Empty Space

See code in free layout best practices

<img loading="lazy" style={{ width: 500, margin: '0 auto' }}  className="invert-img" src="/free-layout/line-add-panel.gif"/>

```tsx pure

function App() {
  const editorProps: FreeLayoutProps = {
      /**
       * Drag the end of the line to create an add panel (feature optional)
       */
      async onDragLineEnd(ctx, params) {
        const { fromPort, toPort, mousePos, line, originLine } = params;
        if (originLine || !line) {
          return;
        }
        if (toPort) {
          return;
        }
        // Can open add panel based on mousePos here
        await ctx.get(WorkflowNodePanelService).call({
          fromPort,
          toPort: undefined,
          panelPosition: mousePos,
          enableBuildLine: true,
          panelProps: {
            enableNodePlaceholder: true,
            enableScrollClose: true,
          },
        });
      },
      // ...others
  }
  return (
    <FreeLayoutEditorProvider {...editorProps}>
      <EditorRenderer className="demo-editor" />
    </FreeLayoutEditorProvider>
  )
}
```

## Add Label to Line

See code in free layout best practices

<img loading="lazy" style={{ width: 500, margin: '0 auto' }}  className="invert-img" src="/free-layout/line-add-button.gif"/>

```ts pure

import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';

const editorProps = {
  plugins: () => [
      /**
       * Line render plugin
       */
      createFreeLinesPlugin({
        renderInsideLine: LineAddButton,
      }),
  ]
}
```

## Node Listening to Its Own Line Changes and Refresh

```tsx pure

import {
  useRefresh,
  WorkflowNodeLinesData,
} from '@flowgram.ai/free-layout-editor';

function NodeRender({ node }) {
  const refresh = useRefresh()
  const linesData = node.get(WorkflowNodeLinesData)
  useEffect(() => {
    const dispose = linesData.onDataChange(() => refresh())
    return () => dispose.dispose()
  }, [])
  return <div>xxxx</div>
}
```

## Listen to All Line Connection Changes

This scenario is used when you want to monitor line connections in external components

```ts pure
import { useEffect } from 'react'
import { WorkflowLinesManager, useRefresh } from '@flowgram.ai/free-layout-editor'


function SomeReact() {
  const refresh = useRefresh()
  const linesManager = useService(WorkflowLinesManager)
  useEffect(() => {
      const dispose = linesManager.onAvailableLinesChange(() => refresh())
      return () => dispose.dispose()
  }, [])
  console.log(ctx.document.linesManager.getAllLines())
}
```


