fix: en doc images (#68)

* fix: en doc images

* chore: add ci trigger in merge queue
This commit is contained in:
chenjiawei.inizio 2025-03-20 14:37:56 +08:00 committed by GitHub
parent be5d086811
commit b2dc4f885b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 7 additions and 480 deletions

View File

@ -4,6 +4,8 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
merge_group:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest

View File

@ -22,7 +22,7 @@ const { playground } = useClientContext()
- 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)
![Aspect-oriented programming](@/public/en-layer-uml.jpg)
- Layer Lifecycle

View File

@ -12,7 +12,7 @@
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)
![Aspect - Oriented Programming](@/public/en-weaving.png)
Ideal Aspect - Oriented Programming

View File

@ -12,7 +12,7 @@ The variable engine is designed to follow the DIP (Dependency Inversion Principl
:::
![Architecture Layers Diagram](@/public/variable-engine.png)
![Architecture Layers Diagram](@/public/en-variable-engine.png)
### Glossary
@ -67,7 +67,7 @@ An AST node used to declare a new variable, which points to a value that changes
:::
<table>
<tr>
<td><img loading="lazy" src="/variable-type1.png"/></td>
<td><img loading="lazy" src="/en-variable-type1.png"/></td>
<td><img loading="lazy" src="/variable-type2.png"/></td>
</tr>
</table>
@ -81,4 +81,4 @@ An AST node used to declare a new variable, which points to a value that changes
:::
![Illustration](@/public/varaible-zone.png)
![Illustration](@/public/en-varaible-zone.png)

View File

@ -1,3 +0,0 @@
[
"introduction"
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@ -1,3 +0,0 @@
[
"index"
]

View File

@ -1,175 +0,0 @@
# Core Modeling
Playground provides a coordinate system at its foundation
```ts
interface Playground {
node: HTMLDivElement // DOM node where canvas is mounted
scrollToView({
entities?: Entity[] // Specified nodes
easing?: boolean // Enable easing
bounds?: Reactangle // Scroll to specific bbox position
}) // Make canvas scroll smoothly to a specific node and center it
toReactComponent() // Render as React node
readonly: boolean // Read-only mode
config: PlaygroundConfigEntity // Contains canvas data like zoom, scroll etc.
}
// Quick access hook
const playground = usePlayground()
```
## Layer
:::warning P.S.
- The rendering layer establishes its own coordinate system at the bottom layer, implementing simulated scrolling, zooming and other logic based on this coordinate system. When calculating viewport, nodes also need to be transformed to this coordinate system
- Rendering is split into multiple layers (Layer) on the canvas. The layered design is based on ECS data splitting concept. Different Layers only listen to the data they want and render independently without interference. Layer can be understood as the System in ECS, which is where Entity data is ultimately consumed
- Layer implements observer reactive dynamic dependency collection similar to mobx, data updates will trigger autorun or render
:::
![Aspect Programming](../assets/layer-uml.jpg)
- Layer lifecycle
```ts
interface Layer {
/**
* Triggered during initialization
*/
onReady?(): void;
/**
* Triggered when playground size changes
*/
onResize?(size: PipelineDimension): void;
/**
* Triggered when playground gains focus
*/
onFocus?(): void;
/**
* Triggered when playground loses focus
*/
onBlur?(): void;
/**
* Listen to zoom changes
*/
onZoom?(scale: number): void;
/**
* Listen to scroll changes
*/
onScroll?(scroll: { scrollX: number; scrollY: number }): void;
/**
* Triggered when viewport updates
*/
onViewportChange?(): void;
/**
* Triggered when readonly or disable state changes
* @param state
*/
onReadonlyOrDisabledChange?(state: { disabled: boolean; readonly: boolean }): void;
/**
* Data updates automatically trigger React render, if not provided React rendering won't be called
*/
render?(): JSX.Element
}
```
Layer's positioning is actually similar to Unity game engine's [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html). Unity game engine's script extensions are all based on this, which can be considered the core design, and the underlying implementation is also based on C#'s Reflection dependency injection capability
```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.");
}
}
```
- Layer's reactive updates
```ts
export class DemoLayer extends Layer {
// Any inversify module injection
@inject(FlowDocument) document: FlowDocument
// Observe single Entity
@observeEntity(SomeEntity) entity: SomeEntity
// Observe multiple Entities
@observeEntities(SomeEntity) entities: SomeEntity[]
// Observe Entity data block (ECS - Component) changes
@observeEntityDatas(SomeEntity, SomeEntityData) transforms: SomeEntityData[]
autorun() {}
render() {
return <div></div>
}
}
```
## FlowNodeEntity
- Nodes form a tree. In free canvas mode, nodes are flat without child nodes
```ts
inteface FlowNodeEntity {
id string
children: FlowNodeEntity[]
pre?: FlowNodeEntity
next?: FlowNodeEntity
parent?: FlowNodeEntity
collapsed: boolean // Whether expanded
getData(dataRegistry): NodeEntityData
addData(dataRegistry)
}
```
## 2.4.4 FlowNodeTransformData Node's bbox
```ts
class FlowNodeTransformData {
localTransform: Matrix, // Relative offset, only relative to the previous Sibling node offset in the same Block
worldTransform: Matrix, // Absolute offset, relative to the accumulated offset of Parent and Sibling nodes
deltaPoint // Center/left alignment offset, independent from Matrix, controlled by each node itself
getSize(): Size, // Calculated from own (independent node) or child branch node width/height spacing
getBounds(): Rectangle // Calculated from worldMatrix and size, used for final rendering, this range can also be used to determine highlight selection area
inputPoint(): Point // Input point position, generally the top center position of the first node in Block (centered layout)
outputPoint(): Point // Output point position, default is node's bottom center position, but for conditional branches, determined by built-in end node and specific logic
}
```
## FlowNodeRenderData Node content rendering data
```ts
class FlowNodeRenderData {
node: HTMLDivElement // Current node's DOM element
expandedboolean // Whether expanded
activated boolean // Whether activated
hidden boolean // Whether hidden
}
```
## FlowDocument
```ts
interface FLowDocument {
root: FlowNodeEntity // Canvas root node
fromJSON(data): void // Import data
toJSON(): FlowDocumentJSON // Export data
addNode(type: string, meta: any): FlowNodeEntity // Add node
traveseDFS(fn: (node: flowNodeEntity) => void, startNode = this.root) // Traverse
}
```

View File

@ -1,186 +0,0 @@
# Frequently Asked Questions
## Why Not Use ReactFlow
- ReactFlow doesn't do data modeling or provide layout algorithms, it only handles rendering. Development complexity and labor costs remain high
See: https://reactflow.dev/examples/nodes/custom-node
- ReactFlow's interaction customization cost is high. As shown below, it's difficult to select points when the canvas is zoomed out, and it doesn't support drag-and-drop line reconnection
<table>
<tr>
<td><img loading="lazy" src="/reactflow/reactflow-render.gif"/></td>
<td><img loading="lazy" src="/reactflow/reactflow-interaction.gif"/></td>
</tr>
</table>
## ReactFlow Paid Features
| Paid Features | Supported by FlowGramAI | Future Plan |
|----------------------------------|------------------------|--------------|
| Grouping | Yes | |
| Redo/Undo | Yes | |
| Copy/Paste | Yes | |
| HelpLines | Yes | |
| Custom Nodes and Shapes | Yes | |
| Custom Lines | Yes | |
| AutoLayout | Yes | |
| ForceLayout | No | No |
| Expand/Collapse | Yes | |
| Collaborative | No | Yes |
| WorkflowBuilder (Complete Auto-layout Case) | Yes | |
## Why Need IOC
:::tip Key Concepts:
- Inversion of Control: A design principle in object-oriented programming that reduces coupling between code modules. The most common form is Dependency Injection (DI)
- Domain Logic: Also called Business Logic, these are logic related to specific product features
- Aspect-Oriented Programming (AOP): The core design principle is to split software systems into common logic (cross-cutting, pervasive) and domain logic (vertical-cutting) aspects, where cross-cutting parts can be "consumed as needed" by all vertical-cutting parts
:::
Before answering this question, let's understand aspect-oriented programming. AOP aims to break down domain logic into finer granularity, where cross-cutting parts can be "consumed as needed" by vertical-cutting parts. The connection between cross-cutting and vertical-cutting is called Weaving, and IOC plays the role of Weaving injection into vertical-cutting parts.
![Aspect-Oriented Programming](@/public/weaving.png)
Ideal aspect-oriented programming
```ts
- myAppliation provides business logic
- service specific business logic services
- customDomainLogicService
- contributionImplement hook registration instantiation
- MyApplicationContributionImpl
- component business components
- core provides common logic
- model common models
- contribution hook interfaces
- LifecycleContribution application lifecycle
- CommandContribution
- service common 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 implement LifecycleContribution {
onStart(): void {
// Specific business logic code
}
}
// Manually mount to lifecycle hook
bind(LifecycleContribution).toService(MyApplicationContributionImpl)
```
:::warning IOC is a means of aspect-oriented programming. After introduction, underlying modules can expose interfaces for external registration, bringing benefits:
- Implements microkernel + plugin design, enabling plugin pluggability and on-demand consumption
- Allows for cleaner package separation, implementing feature-based package splitting
:::
## Why Need ECS
:::warning ECS (Entity-Component-System)
Suitable for decoupling large data objects, often used in games, where each character (Entity) in the game has very large data, which needs to be split into multiple components such as physical engine related data, skin related, role attributes, etc. (multiple Components), consumed by different subsystems (Systems). The data structure of the process is complex, which is very suitable for ECS for decomposition
:::
<img loading="lazy" style={{filter: 'invert(0.9)'}} src="./assets/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 simple to use
Disadvantages:
- Centralized data management cannot be precisely updated, leading to performance bottlenecks
- Poor scalability, adding a new node data will couple to 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 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 separately for control rendering, performance can be done precisely updated
- Strong scalability, adding a new node data, then adding a XXXData + XXXLayer
Disadvantages:
- There is a learning cost

View File

@ -1,30 +0,0 @@
# Node Engine
The Node Engine is a framework for writing flow node logic, allowing businesses to focus on their own rendering and data logic without concerning themselves with underlying canvas and node interaction APIs. Meanwhile, the node engine has established the best node writing paradigms, helping businesses solve various issues that may arise in flow business, such as coupling between data logic and rendering.
The node engine is optional. If you don't have these complex node logic requirements, you can choose not to enable the node engine and maintain node data and rendering yourself. Complex node logic includes: 1) Nodes can validate or trigger data side effects without rendering; 2) Rich node interactions; 3) Redo/undo; etc.
# Basic Concepts
FlowNodeEntity
Flow node model.
FlowNodeRegistry
Static configuration of flow nodes.
FormMeta
Static configuration of the node engine. Configured in the formMeta field of FlowNodeRegistry.
Form
Forms in the node engine. It maintains node data and provides rendering, validation, side effects, and other capabilities. Its FormModel provides access to and modification of node data and triggers validation capabilities.
Field
A rendering field in the node form. Note that Form already provides data layer logic, Field is more of a rendering layer model that only exists after form field rendering.
validate
Form validation. Usually includes validation for individual fields and overall form validation.
effect
Form data side effects. Usually refers to specific logic triggered when form data experiences certain events. For example, when a field's data changes and needs to synchronize information to a store, this can be called an effect.
FormPlugin
Form plugins. Can be configured in formMeta, plugins can perform a series of deep operations on forms. Such as variable plugins.

View File

@ -1,78 +0,0 @@
# Variable Engine
## Overall Design
### Architecture Layers
:::warning Architecture Layers
The variable engine design follows the DIP (Dependency Inversion Principle) and is divided into three layers based on code stability, abstraction level, and proximity to business:
- Variable Abstract Layer: The highest level of abstraction and most stable code in the variable architecture
- Variable Implementation Layer: The more volatile part of the variable architecture, typically requiring adjustments between different business needs
- Variable Business Layer: The Facade provided to business in the variable architecture, interacting with canvas engine and node engine
:::
![Architecture Layer Diagram](./assets/variable-engine-structure.png)
### Terminology
#### 🌟 Scope
:::warning ⭐️⭐️⭐️ Definition:
A conventional space where variable declarations and consumption are described through AST
- Conventional space: The space is entirely defined by business
- In low-code design state, it can be a node, a component, a right panel...
- In code, it can be a Statement, a code block, a function, a file...
:::
What is the scope space? It can be defined by different businesses.
#### 🌟 Abstract Syntax Tree (AST)
:::warning Definition:
⭐️⭐️⭐️ A protocol that combines AST nodes in tree form to achieve explicit/implicit CRUD of variable information
- AST nodes: Reactive protocol nodes in AST
- Explicit CRUD, e.g.: Business explicitly sets a variable's type
- Implicit CRUD, e.g.: Business declares a variable, and its type is automatically inferred from initialization parameters
:::
:::warning Variables, types, expressions, structures, and other variable information in 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 new variables, using a unique identifier to point to a value that changes within a specific set range
- Value changing within a specific set range: The variable's value must be within the range described by the variable type
- Unique identifier: The variable must have a unique Key value
:::
[Variables in JavaScript, unique Key + pointing to a changing value](./assets/variable-code.png)
#### Variable Type
:::warning Definition:
⭐️⭐️⭐️ An AST node used to constrain a variable, where the constrained variable value can only change within a predetermined set range
- A variable can be bound to a variable type
:::
<table>
<tr>
<td><img loading="lazy" src="./assets/variable-type1.png"/></td>
<td><img loading="lazy" src="./assets/variable-type2.png"/></td>
</tr>
</table>
### Visual Understanding of the Variable Engine
:::warning Imagine a variable engine world like this:
- Define countries through individual scopes
- Each country contains three main citizens: declarations, types, and expressions
- Countries communicate with each other through scope chains
:::
![Illustration](./assets/varaible-zone.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 289 KiB