dragooncjw hai 10 meses
pai
achega
45697d866d

+ 11 - 0
apps/docs/global.less

@@ -68,6 +68,7 @@
   --background-color: #ffffff !important; /* 强制使用浅色背景 */
   --text-color: #000000 !important; /* 强制使用深色文本 */
   --rp-c-bg: #fff !important;
+  --rp-c-text-1: #000000 !important; /* 强制使用深色文本 */
 
   button {
     background: transparent;
@@ -77,3 +78,13 @@
 .light-mode * {
   color: var(--text-color) !important;
 }
+
+.semi-tooltip-content {
+  color: black;
+}
+
+.dark {
+  .invert-img {
+    filter: invert(0.9);
+  }
+}

+ 3 - 0
apps/docs/src/en/guide/advanced/_meta.json

@@ -2,6 +2,9 @@
   "form",
   "variable",
   "history",
+  "lines",
+  "custom-service",
+  "custom-plugin",
   {
     "type": "dir",
     "name": "interactive",

+ 88 - 0
apps/docs/src/en/guide/advanced/custom-plugin.mdx

@@ -0,0 +1,88 @@
+# Custom Plugin
+
+## Plugin Lifecycle Explanation
+
+```tsx pure
+/**
+ * from: https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/core/src/plugin/plugin.ts
+ */
+import { ContainerModule, interfaces } from 'inversify';
+
+export interface PluginBindConfig {
+  bind: interfaces.Bind;
+  unbind: interfaces.Unbind;
+  isBound: interfaces.IsBound;
+  rebind: interfaces.Rebind;
+}
+export interface PluginConfig<Opts, CTX extends PluginContext = PluginContext> {
+  /**
+   * Plugin IOC registration, equivalent to containerModule
+   * @param ctx
+   */
+  onBind?: (bindConfig: PluginBindConfig, opts: Opts) => void;
+  /**
+   * Canvas registration phase
+   */
+  onInit?: (ctx: CTX, opts: Opts) => void;
+  /**
+   * Canvas preparation phase, generally used for DOM event registration, etc.
+   */
+  onReady?: (ctx: CTX, opts: Opts) => void;
+  /**
+   * Canvas destruction phase
+   */
+  onDispose?: (ctx: CTX, opts: Opts) => void;
+  /**
+   * After all layers of the canvas are rendered
+   */
+  onAllLayersRendered?: (ctx: CTX, opts: Opts) => void;
+  /**
+   * IOC module, used for more low - level plugin extensions
+   */
+  containerModules?: interfaces.ContainerModule[];
+}
+
+```
+
+## Create a Plugin
+
+```tsx pure
+/**
+ * If you want the plugin to be usable in both fixed and free layouts, please use
+ *  import { definePluginCreator } from '@flowgram.ai/core'
+ */
+import { definePluginCreator, FixedLayoutPluginContext } from '@flowgram.ai/fixed-layout-editor'
+
+export interface MyPluginOptions {
+  opt1: string;
+}
+
+export const createMyPlugin = definePluginCreator<MyPluginOptions, FixedLayoutPluginContext>({
+  onBind: (bindConfig, opts) => {
+    // Register the IOC module. See Custom Service for how to define a Service.
+    bindConfig.bind(MyService).toSelf().inSingletonScope()
+  },
+  onInit: (ctx, opts) => {
+    // Plugin configuration
+    console.log(opts.opt1)
+    // ctx corresponds to FixedLayoutPluginContext or FreeLayoutPluginContext
+    console.log(ctx.document)
+    console.log(ctx.playground)
+    console.log(ctx.get<MyService>(MyService)) // Get the IOC module
+  },
+});
+```
+
+## Add a Plugin
+
+```tsx pure title="use-editor-props.ts"
+
+// EditorProps
+{
+  plugins: () => [
+    createMyPlugin({
+      opt1: 'xxx'
+    })
+  ]
+}
+```

+ 40 - 0
apps/docs/src/en/guide/advanced/custom-service.mdx

@@ -0,0 +1,40 @@
+# Custom Service
+
+In business, it is necessary to abstract singleton services for easy plug-in management.
+
+```tsx pure
+/**
+ *  inversify: https://github.com/inversify/InversifyJS
+ */
+import { injectable, inject } from 'inversify'
+import { useMemo } from 'react';
+import { FlowDocument, type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor'
+
+@injectable()
+class MyService {
+  // Dependency injection of singleton module
+  @inject(FlowDocument) flowDocument: FlowDocument
+  // ...
+}
+
+function BaseNode() {
+  const mySerivce = useService<MyService>(MyService)
+}
+
+export function useEditorProps(
+): FixedLayoutProps {
+  return useMemo<FixedLayoutProps>(
+    () => ({
+      // ....other props
+      onBind: ({ bind }) => {
+        bind(MyService).toSelf().inSingletonScope()
+      },
+      materials: {
+        renderDefaultNode: BaseNode
+      }
+    }),
+    [],
+  );
+}
+
+```

+ 57 - 0
apps/docs/src/en/guide/advanced/lines.mdx

@@ -0,0 +1,57 @@
+# Free Layout Lines
+
+The lines in the free layout are managed by [WorkflowLinesManager](/flowgram.ai/api/core/workflow-lines-manager.html).
+
+## Get the Input/Output Nodes of the Current Node
+
+```ts pure
+import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor'
+
+// Get the input nodes of the current node (calculated through connection lines)
+node.geData(WorkflowNodeLinesData).inputNodes;
+// Get all input nodes (recursively get all upward)
+node.geData(WorkflowNodeLinesData).allInputNodes;
+// Get the output nodes
+node.geData(WorkflowNodeLinesData).outputNodes;
+// Get all output nodes
+node.geData(WorkflowNodeLinesData).allOutputNodes;
+```
+
+## Node listens to its own connection changes and refreshes
+
+```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 for connection changes of all lines
+
+```ts pure
+import { useEffect } from 'react'
+import { useClientContext, useRefresh } from '@flowgram.ai/free-layout-editor'
+
+
+function SomeReact() {
+  const refresh = useRefresh()
+  const linesManager = useClientContext().document.linesManager
+  useEffect(() => {
+      const dispose = linesManager.onAvailableLinesChange(() => refresh())
+      return () => dispose.dispose()
+  }, [])
+  console.log(ctx.document.linesManager.getAllLines())
+}
+```

+ 172 - 1
apps/docs/src/en/guide/concepts/canvas-engine.mdx

@@ -1,3 +1,174 @@
 # Canvas Engine
 
-TODO
+## Playground
+The underlying canvas engine provides its own coordinate system, which is mainly driven by the Playground.
+
+```ts
+interface Playground {
+   node: HTMLDivElement // The DOM node where the canvas is mounted
+   toReactComponent() // Render as a React node
+   readonly: boolean // Read-only mode
+   config: PlaygroundConfigEntity // Contains canvas data such as zoom and scroll
+}
+// Quick access hook
+const { playground } = useClientContext()
+```
+
+## Layer
+
+:::warning P.S.
+- The rendering layer establishes its own coordinate system at the underlying level. Based on this coordinate system, logics such as simulated scrolling and zooming are implemented. When calculating the viewport, nodes also need to be converted to this coordinate system.
+- The rendering is split into multiple layers (Layer) according to the canvas. The layered design is based on the data segmentation concept of ECS. Different Layers only listen to the data they want and render independently without interference. A Layer can be understood as an ECS System, which is the final consumption place for Entity data.
+- The Layer implements observer-style reactive dynamic dependency collection similar to MobX. Data updates will trigger autorun or render.
+:::
+
+![Aspect-oriented programming](@/public/layer-uml.jpg)
+
+- Layer Lifecycle
+
+```ts
+interface Layer {
+    /**
+     * Triggered during initialization
+     */
+    onReady?(): void;
+
+    /**
+     * Triggered when the size of the playground changes
+     */
+    onResize?(size: PipelineDimension): void;
+
+    /**
+     * Triggered when the playground gets focus
+     */
+    onFocus?(): void;
+
+    /**
+     * Triggered when the playground loses focus
+     */
+    onBlur?(): void;
+
+    /**
+     * Listen for zoom events
+     */
+    onZoom?(scale: number): void;
+
+    /**
+     * Listen for scroll events
+     */
+    onScroll?(scroll: { scrollX: number; scrollY: number }): void;
+
+    /**
+     * Triggered when the viewport is updated
+     */
+    onViewportChange?(): void;
+
+    /**
+     * Triggered when the readonly or disable state changes
+     * @param state
+     */
+    onReadonlyOrDisabledChange?(state: { disabled: boolean; readonly: boolean }): void;
+
+    /**
+     * Automatically trigger React rendering when data is updated. If not provided, React rendering will not be called.
+     */
+    render?(): JSX.Element
+ }
+```
+
+The positioning of the Layer is actually similar to the [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) provided by the Unity game engine. The script extensions of the Unity game engine are all based on this. It can be considered the core design, and the underlying layer also relies on the dependency injection capability provided by C#'s reflection.
+
+```C#
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+public class MyMonoBehavior : MonoBehaviour
+{
+    void Awake()
+    {
+        Debug.Log("Awake method is always called before application starts.");
+    }
+    void Start()
+    {
+        Debug.Log("Start method is always called after Awake().");
+    }
+    void Update()
+    {
+        Debug.Log("Update method is called in every frame.");
+    }
+}
+```
+
+- Reactive updates of the Layer
+
+```ts
+export class DemoLayer extends Layer {
+    // Injection of any Inversify module
+    @inject(FlowDocument) document: FlowDocument
+    // Listen to a single Entity
+    @observeEntity(SomeEntity) entity: SomeEntity
+    // Listen to multiple Entities
+    @observeEntities(SomeEntity) entities: SomeEntity[]
+    // Listen to changes in the data block (ECS - Component) of an Entity
+    @observeEntityDatas(SomeEntity, SomeEntityData) transforms: SomeEntityData[]
+    autorun() {}
+    render() {
+      return <div></div>
+    }
+}
+```
+
+## FlowNodeEntity
+
+- The node is a tree, containing child nodes (blocks) and a parent node. The node uses the ECS architecture.
+```ts
+interface FlowNodeEntity {
+    id: string
+    blocks: FlowNodeEntity[]
+    pre?: FlowNodeEntity
+    next?: FlowNodeEntity
+    parent?: FlowNodeEntity
+    collapsed: boolean // Whether it is expanded
+    getData(dataRegistry): NodeEntityData
+    addData(dataRegistry)
+}
+```
+
+## FlowNodeTransformData: Position and Size Data of the Node
+
+```ts
+class FlowNodeTransformData {
+    localTransform: Matrix, // Relative offset, only relative to the previous sibling node in the same block
+    worldTransform: Matrix, // Absolute offset, relative to the superposition of the parent and sibling nodes
+    delta: Point // Centering and left-alignment offset, independent of the matrix, controlled by each node itself
+    getSize(): Size, // Calculated by the width, height, and spacing of the node itself (independent node) or its child branch nodes
+    getBounds(): Rectangle // Calculated by the world matrix and size, used for final rendering. This range can also be used to determine the highlighted selection area
+    inputPoint(): Point // Input point position, usually the middle-top position of the first node in the block (centered layout)
+    outputPoint(): Point // Output point position, default is the middle-bottom position of the node. For conditional branches, it is determined by specific logic such as the built-in end node
+   // ...others
+}
+```
+
+## FlowNodeRenderData: Node Content Rendering Data
+
+```ts
+class FlowNodeRenderData {
+  node: HTMLDivElement // The DOM of the current node
+  expanded: boolean // Whether it is expanded
+  activated: boolean // Whether it is activated
+  hidden: boolean // Whether it is hidden
+  // ...others
+}
+```
+
+## FlowDocument
+
+```ts
+interface FlowDocument {
+    root: FlowNodeEntity // The root node of the canvas
+    fromJSON(data): void // Import data
+    toJSON(): FlowDocumentJSON // Export data
+    addNode(type: string, meta: any): FlowNodeEntity // Add a node
+    traverse(fn: (node: FlowNodeEntity) => void, startNode = this.root) // Traverse
+}
+```

+ 93 - 0
apps/docs/src/en/guide/concepts/ecs.mdx

@@ -1 +1,94 @@
 # ECS
+
+## Why do we need ECS
+
+:::warning ECS (Entity-Component-System)
+It is suitable for decoupling large data objects and is often used in games. The data of each character (Entity) in a game is very large and needs to be split into data related to the physics engine, skin, character attributes, etc. (multiple Components), which are consumed by different subsystems (Systems). The data structure of the process is complex, and it is very suitable to be disassembled using ECS.
+:::
+
+<img loading="lazy" className="invert-img" src="/ecs.png"/>
+
+ReduxStore pseudo-code
+```jsx pure
+const store = () => ({
+  nodes: [{
+    position: any
+    form: any
+    data3: any
+
+  }],
+  edges: []
+})
+
+function Playground() {
+  const { nodes } = useStore(store)
+
+  return nodes.map(node => <Node data={node} />)
+}
+```
+Advantages:
+- Centralized data management is easy to use.
+
+Disadvantages:
+- Centralized data management cannot perform precise updates, leading to performance bottlenecks.
+- Poor scalability. When adding new data to a node, it is all coupled into a large JSON.
+
+ECS Solution
+Notes:
+- NodeData corresponds to ECS - Component.
+- Layer corresponds to ECS - System.
+```jsx pure
+class FlowDocument {
+dataDefines: [
+  NodePositionData,
+  NodeFormData,
+  NodeLineData
+]
+nodeEntities: Entity[] = []
+}
+
+
+class Entity {
+id: string // Only has an ID, no data.
+getData: (dataId: string) => EntityData
+}
+
+// Render lines
+class LinesLayer {
+@observeEntityData(NodeLineData) lines
+render() {
+  return lines.map(line => <Line data={line} />)
+}
+}
+
+// Render node positions
+class NodePositionsLayer {
+@observeEntityData(NodePositionData) positions
+return() {
+
+}
+}
+
+// Render node forms
+class  NodeFormsLayer {
+  @observeEntityData(NodeFormData) contents
+return() {}
+}
+
+class Playground {
+layers: [
+  LinesLayer, // Line rendering
+  NodePositionsLayer, // Position rendering
+  NodeFormsLayer // Content rendering
+]
+render() {
+  return this.layers.map(layer => layer.render())
+}
+}
+```
+Advantages:
+- Node data is split for separate rendering control, enabling precise performance updates.
+- High scalability. When adding new node data, simply add a new XXXData + XXXLayer.
+
+Disadvantages:
+- There is a certain learning curve.

+ 69 - 0
apps/docs/src/en/guide/concepts/ioc.mdx

@@ -1 +1,70 @@
 # IOC
+
+## Why is IOC needed?
+
+:::warning Several concepts
+
+- Inversion of Control: Inversion of Control, a design principle in object - oriented programming, can be used to reduce the coupling between code modules. The most common way is called Dependency Injection (DI for short).
+- Domain Logic: Domain Logic, also known as Business Logic, is related to specific product features.
+- Aspect - Oriented Programming: AOP (Aspect - Oriented Programming). Its core design principle is to split the software system into multiple aspects (Aspect) of common logic (cross - cutting, with the meaning of penetration) and domain logic (vertical cutting). The cross - cutting part can be "consumed on demand" by all vertical - cutting parts.
+
+:::
+
+Before answering this question, let's first understand aspect - oriented programming. The purpose of aspect - oriented programming is to split the granularity of domain logic into smaller parts. The cross - cutting part can be "consumed on demand" by the vertical - cutting part. The connection between the cross - cutting and vertical - cutting parts is also called weaving. And IOC plays the role of weaving and is injected into the vertical - cutting part.
+
+![Aspect - Oriented Programming](@/public/weaving.png)
+
+Ideal Aspect - Oriented Programming
+
+```ts
+- myAppliation provides business logic
+  - service Specific business logic services
+     - customDomainLogicService
+  - contributionImplement Instantiation of hook registrations
+    - MyApplicationContributionImpl
+  - component Business components
+
+- core provides common logic
+  - model Common models
+  - contribution Hook interfaces
+     - LifecycleContribution Application lifecycle
+     - CommandContribution
+  - service Common service services
+     - CommandService
+     - ClipboardService
+  - component Common components
+  ```
+
+  ```ts
+  // IOC injection
+@injectable()
+export class CustomDomainLogicService {
+  @inject(FlowContextService) protected flowContextService: FlowContextService;
+  @inject(CommandService) protected commandService: CommandService;
+  @inject(SelectionService) protected selectionService: SelectionService;
+}
+// IOC interface declaration
+interface LifecycleContribution {
+   onInit(): void
+   onStart(): void
+   onDispose(): void
+}
+// IOC interface implementation
+@injectable()
+export class MyApplicationContributionImpl implements LifecycleContribution {
+    onStart(): void {
+      // Specific business logic code
+    }
+}
+
+// Manually attach to the lifecycle hook
+bind(LifecycleContribution).toService(MyApplicationContributionImpl)
+```
+
+
+:::warning IOC is an aspect-oriented programming technique, after the introduction, the underlying module can be exposed to the interface in the form of the external registration, which brings the following benefits:
+- Implement a micro - kernel + plug - in design to achieve plug - and - play and on - demand consumption of plug - ins.
+- Allow the package to be split more cleanly and achieve feature - based package splitting.
+
+:::
+

+ 29 - 1
apps/docs/src/en/guide/concepts/node-engine.mdx

@@ -1,3 +1,31 @@
 # Node Engine
 
-TODO
+The Node Engine is a framework for writing the logic of process nodes. It allows businesses to focus on their own rendering and data logic without having to worry about the underlying APIs of the canvas and the interaction between nodes. At the same time, the Node Engine has precipitated the best practices for writing nodes, which helps businesses solve various problems that may arise in process - related business, such as the coupling of data logic and rendering.
+
+The Node Engine is optional. If you don't have the following complex node logic, you can choose not to enable the Node Engine and maintain node data and rendering on your own. Examples of complex node logic include: 1) The ability to validate or trigger data side - effects even when nodes are not rendered; 2) Rich interaction between nodes; 3) Redo/undo functionality; and so on.
+
+## Basic Concepts
+
+### FlowNodeEntity
+The process node model.
+
+### FlowNodeRegistry
+The static configuration of process nodes.
+
+### FormMeta
+The static configuration of the Node Engine. It is configured in the `formMeta` field of the `FlowNodeRegistry`.
+
+### Form
+The form in the Node Engine. It maintains the data of nodes and provides capabilities such as rendering, validation, and side - effects. Its model, `FormModel`, provides the ability to access and modify node data and trigger validations.
+
+### Field
+A rendering field in the node form. Note that the `Form` has already provided the data - layer logic, and the `Field` is more of a rendering - layer model. It only exists after the form field is rendered.
+
+### validate
+Form validation. Usually, there is validation for individual fields as well as overall form validation.
+
+### effect
+Side - effects of form data. Usually, it refers to triggering specific logic when certain events occur to the form data. For example, synchronizing some information to a certain store when the data of a certain field changes can be called an effect.
+
+### FormPlugin
+Form plugins. They can be configured in the `formMeta`. Plugins can perform a series of in - depth operations on the form, such as variable plugins.

+ 82 - 1
apps/docs/src/en/guide/concepts/variable-engine.mdx

@@ -1,3 +1,84 @@
 # Variable Engine
 
-TODO
+## Overall Design
+
+### Architecture Layers
+
+:::warning Architecture Layers
+The variable engine is designed to follow the DIP (Dependency Inversion Principle). It is divided into three layers according to code stability, abstraction level, and proximity to the business:
+- Variable Abstraction Layer: The part with the highest level of abstraction and the most stable code in the variable architecture.
+- Variable Implementation Layer: The part that changes significantly and usually requires adjustments between different businesses in the variable architecture.
+- Variable Business Layer: The Facade provided to the business in the variable architecture, which interacts with the canvas engine and node engine.
+
+:::
+
+![Architecture Layers Diagram](@/public/variable-engine.png)
+
+### Glossary
+
+#### 🌟 Scope
+:::warning ⭐️⭐️⭐️ Definition:
+A predefined space where the declaration and consumption of variables are described through an AST.
+- Predefined space: The nature of the space is entirely defined by the business.
+- In the low-code design state, it can be a node, a component, a right-side panel, etc.
+- In a piece of code, it can be a single statement, a code block, a function, a file, etc.
+
+:::
+
+What the scope space is can be defined by different businesses.
+
+#### 🌟 Abstract Syntax Tree (AST)
+
+:::warning Definition:
+⭐️⭐️⭐️ A protocol that combines AST nodes in a tree structure to explicitly or implicitly perform CRUD operations on variable information.
+- AST nodes: Reactive protocol nodes in the AST.
+- Explicit CRUD, e.g., the business explicitly sets the variable type of a variable.
+- Implicit CRUD, e.g., the business declares a variable, and the variable type is automatically inferred based on its initialization parameters.
+
+:::
+
+:::warning Variable information such as variables, types, expressions, and structures within the scope... are essentially combinations of AST nodes.
+- Variable -> VariableDeclaration node
+- Expression -> Expression node
+- Type -> TypeNode node
+- Structure -> StructDeclaration node
+
+:::
+
+Reference link: https://ts-ast-viewer.com/
+
+#### Variable
+
+:::warning Definition:
+An AST node used to declare a new variable, which points to a value that changes within a specific set of ranges through a unique identifier.
+- A value that changes within a specific set of ranges: The value of the variable must be within the range described by the variable type.
+- Unique identifier: The variable must have a unique key value.
+
+:::
+
+[Variables in JavaScript, with a unique key + pointing to a changing value](@/public/variable-code.png)
+
+#### Variable Type
+
+:::warning Definition:
+⭐️⭐️⭐️ An AST node used to constrain a variable. The value of the constrained variable can only change within a pre - set set of ranges.
+- A variable can be bound to a variable type.
+
+:::
+<table>
+  <tr>
+    <td><img loading="lazy" src="/variable-type1.png"/></td>
+    <td><img loading="lazy" src="/variable-type2.png"/></td>
+  </tr>
+</table>
+
+### An Intuitive Understanding of the Variable Engine
+
+:::warning Imagine a world of the variable engine:
+- Different scopes are used to define different "countries".
+- Each "country" contains three main "citizens": declarations, types, and expressions.
+- Communication between "countries" is achieved through the scope chain.
+
+:::
+
+![Illustration](@/public/varaible-zone.png)

+ 1 - 1
apps/docs/src/zh/guide/concepts/ecs.mdx

@@ -7,7 +7,7 @@
 
 :::
 
-<img loading="lazy" style={{filter: 'invert(0.9)'}} src="/ecs.png"/>
+<img loading="lazy"  className="invert-img" src="/ecs.png"/>
 
 ReduxStore 伪代码
 ```jsx pure

+ 1 - 1
apps/docs/src/zh/guide/concepts/ioc.mdx

@@ -51,7 +51,7 @@ interface LifecycleContribution {
 }
 // IOC 的接口实现
 @injectable()
-export class MyApplicationContributionImpl implement LifecycleContribution {
+export class MyApplicationContributionImpl implements LifecycleContribution {
     onStart(): void {
       // 特定的业务逻辑代码
     }