chore(auto-layout): auto layout animation disabled by default (#449)

This commit is contained in:
Louis Young 2025-07-02 23:01:50 +08:00 committed by GitHub
parent de7f2d3c07
commit 25e20d8c20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 64 additions and 25 deletions

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
import { LayoutConfig } from './type'; import { LayoutConfig, LayoutOptions } from './type';
export const DefaultLayoutConfig: LayoutConfig = { export const DefaultLayoutConfig: LayoutConfig = {
rankdir: 'LR', rankdir: 'LR',
@ -16,3 +16,8 @@ export const DefaultLayoutConfig: LayoutConfig = {
acyclicer: undefined, acyclicer: undefined,
ranker: 'network-simplex', ranker: 'network-simplex',
}; };
export const DefaultLayoutOptions: LayoutOptions = {
getFollowNode: undefined,
enableAnimation: false,
};

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
import { GetFollowNode, LayoutConfig, LayoutOptions, LayoutParams } from './type'; import { LayoutConfig, LayoutOptions, LayoutParams } from './type';
import { LayoutStore } from './store'; import { LayoutStore } from './store';
import { LayoutPosition } from './position'; import { LayoutPosition } from './position';
import { DagreLayout } from './dagre'; import { DagreLayout } from './dagre';
@ -21,9 +21,8 @@ export class Layout {
this._position = new LayoutPosition(this._store); this._position = new LayoutPosition(this._store);
} }
public init(params: LayoutParams, options: LayoutOptions = {}): void { public init(params: LayoutParams, options: LayoutOptions): void {
this._store.create(params); this._store.create(params, options);
this.setFollowNode(options.getFollowNode);
} }
public layout(): void { public layout(): void {
@ -39,20 +38,4 @@ export class Layout {
} }
return await this._position.position(); return await this._position.position();
} }
public setFollowNode(getFollowNode?: GetFollowNode): void {
if (!getFollowNode) return;
const context = { store: this._store };
this._store.nodes.forEach((node) => {
const followTo = getFollowNode(node, context)?.followTo;
if (!followTo) return;
const followToNode = this._store.getNode(followTo);
if (!followToNode) return;
if (!followToNode.followedBy) {
followToNode.followedBy = [];
}
followToNode.followedBy.push(node.id);
node.followTo = followTo;
});
}
} }

View File

@ -13,6 +13,19 @@ export class LayoutPosition {
constructor(private readonly store: LayoutStore) {} constructor(private readonly store: LayoutStore) {}
public async position(): Promise<void> { public async position(): Promise<void> {
if (this.store.options.enableAnimation) {
return this.positionWithAnimation();
}
return this.positionDirectly();
}
private positionDirectly(): void {
this.store.nodes.forEach((layoutNode) => {
this.updateNodePosition({ layoutNode, step: 100 });
});
}
private async positionWithAnimation(): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
startTween({ startTween({
from: { d: 0 }, from: { d: 0 },

View File

@ -10,7 +10,14 @@ import {
} from '@flowgram.ai/free-layout-core'; } from '@flowgram.ai/free-layout-core';
import { FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document'; import { FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document';
import type { LayoutConfig, LayoutEdge, LayoutNode, LayoutParams } from './type'; import type {
GetFollowNode,
LayoutConfig,
LayoutEdge,
LayoutNode,
LayoutOptions,
LayoutParams,
} from './type';
interface LayoutStoreData { interface LayoutStoreData {
nodes: Map<string, LayoutNode>; nodes: Map<string, LayoutNode>;
@ -26,6 +33,8 @@ export class LayoutStore {
private container: WorkflowNodeEntity; private container: WorkflowNodeEntity;
public options: LayoutOptions;
constructor(public readonly config: LayoutConfig) {} constructor(public readonly config: LayoutConfig) {}
public get initialized(): boolean { public get initialized(): boolean {
@ -56,9 +65,10 @@ export class LayoutStore {
return Array.from(this.store.edges.values()); return Array.from(this.store.edges.values());
} }
public create(params: LayoutParams): void { public create(params: LayoutParams, options: LayoutOptions): void {
this.store = this.createStore(params); this.store = this.createStore(params);
this.indexMap = this.createIndexMap(); this.indexMap = this.createIndexMap();
this.setOptions(options);
this.init = true; this.init = true;
} }
@ -279,4 +289,27 @@ export class LayoutStore {
return uniqueNodeIds; return uniqueNodeIds;
} }
/** 记录运行选项 */
private setOptions(options: LayoutOptions): void {
this.options = options;
this.setFollowNode(options.getFollowNode);
}
/** 设置跟随节点配置 */
private setFollowNode(getFollowNode?: GetFollowNode): void {
if (!getFollowNode) return;
const context = { store: this };
this.nodes.forEach((node) => {
const followTo = getFollowNode(node, context)?.followTo;
if (!followTo) return;
const followToNode = this.getNode(followTo);
if (!followToNode) return;
if (!followToNode.followedBy) {
followToNode.followedBy = [];
}
followToNode.followedBy.push(node.id);
node.followTo = followTo;
});
}
} }

View File

@ -71,6 +71,7 @@ export interface LayoutParams {
export interface LayoutOptions { export interface LayoutOptions {
getFollowNode?: GetFollowNode; getFollowNode?: GetFollowNode;
enableAnimation: boolean;
} }
export interface LayoutConfig { export interface LayoutConfig {

View File

@ -13,6 +13,7 @@ import {
import { AutoLayoutOptions } from './type'; import { AutoLayoutOptions } from './type';
import { LayoutConfig } from './layout/type'; import { LayoutConfig } from './layout/type';
import { DefaultLayoutOptions } from './layout/constant';
import { DefaultLayoutConfig, Layout, type LayoutOptions } from './layout'; import { DefaultLayoutConfig, Layout, type LayoutOptions } from './layout';
@injectable() @injectable()
@ -28,8 +29,11 @@ export class AutoLayoutService {
}; };
} }
public async layout(options: LayoutOptions = {}): Promise<void> { public async layout(options: Partial<LayoutOptions> = {}): Promise<void> {
await this.layoutNode(this.document.root, options); await this.layoutNode(this.document.root, {
...DefaultLayoutOptions,
...options,
});
} }
private async layoutNode(node: WorkflowNodeEntity, options: LayoutOptions): Promise<void> { private async layoutNode(node: WorkflowNodeEntity, options: LayoutOptions): Promise<void> {