Skip to content

Commit 3fc99ad

Browse files
authored
feat(stack): support custom stack index (bytedance#892)
* fix(demo): comment node placeholder * feat(stack): support custom stack index * fix: test error
1 parent 1bd49fc commit 3fc99ad

File tree

12 files changed

+105
-16
lines changed

12 files changed

+105
-16
lines changed

apps/demo-free-layout/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@flowgram.ai/free-group-plugin": "workspace:*",
4646
"@flowgram.ai/form-materials": "workspace:*",
4747
"@flowgram.ai/panel-manager-plugin": "workspace:*",
48+
"@flowgram.ai/free-stack-plugin": "workspace:*",
4849
"@flowgram.ai/runtime-js": "workspace:*",
4950
"lodash-es": "^4.17.21",
5051
"nanoid": "^5.0.9",

apps/demo-free-layout/src/components/comment/components/editor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { type FC, type CSSProperties, useEffect, useRef } from 'react';
88
import { usePlayground } from '@flowgram.ai/free-layout-editor';
99

1010
import { CommentEditorModel } from '../model';
11+
import { usePlaceholder } from '../hooks';
1112
import { CommentEditorEvent } from '../constant';
1213

1314
interface ICommentEditor {
@@ -20,8 +21,8 @@ interface ICommentEditor {
2021
export const CommentEditor: FC<ICommentEditor> = (props) => {
2122
const { model, style, onChange } = props;
2223
const playground = usePlayground();
24+
const placeholder = usePlaceholder({ model });
2325
const editorRef = useRef<HTMLTextAreaElement | null>(null);
24-
const placeholder = model.value || model.focused ? undefined : 'Enter a comment...';
2526

2627
// 同步编辑器内部值变化
2728
useEffect(() => {

apps/demo-free-layout/src/components/comment/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
*/
55

66
export { useSize } from './use-size';
7+
export { usePlaceholder } from './use-placeholder';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
import { useState, useEffect } from 'react';
7+
8+
import { CommentEditorModel } from '../model';
9+
import { CommentEditorEvent } from '../constant';
10+
11+
export const usePlaceholder = (params: { model: CommentEditorModel }): string | undefined => {
12+
const { model } = params;
13+
14+
const [placeholder, setPlaceholder] = useState<string | undefined>('Enter a comment...');
15+
16+
// 监听 change 事件
17+
useEffect(() => {
18+
const changeDisposer = model.on((params) => {
19+
if (params.type !== CommentEditorEvent.Change && params.type !== CommentEditorEvent.Init) {
20+
return;
21+
}
22+
if (params.value) {
23+
setPlaceholder(undefined);
24+
} else {
25+
setPlaceholder('Enter a comment...');
26+
}
27+
});
28+
return () => {
29+
changeDisposer.dispose();
30+
};
31+
}, [model]);
32+
33+
return placeholder;
34+
};

apps/demo-free-layout/src/hooks/use-editor-props.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import { useMemo } from 'react';
99
import { debounce } from 'lodash-es';
1010
import { createPanelManagerPlugin } from '@flowgram.ai/panel-manager-plugin';
1111
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
12+
import { createFreeStackPlugin } from '@flowgram.ai/free-stack-plugin';
1213
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
1314
import { createFreeNodePanelPlugin } from '@flowgram.ai/free-node-panel-plugin';
1415
import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';
1516
import {
1617
FlowNodeBaseType,
1718
FreeLayoutPluginContext,
1819
FreeLayoutProps,
20+
WorkflowNodeEntity,
1921
} from '@flowgram.ai/free-layout-editor';
2022
import { createFreeGroupPlugin } from '@flowgram.ai/free-group-plugin';
2123
import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
@@ -274,6 +276,20 @@ export function useEditorProps(
274276
},
275277
},
276278
plugins: () => [
279+
createFreeStackPlugin({
280+
sortNodes: (nodes) => {
281+
const commentNodes: WorkflowNodeEntity[] = [];
282+
const otherNodes: WorkflowNodeEntity[] = [];
283+
nodes.forEach((node) => {
284+
if (node.flowNodeType === WorkflowNodeType.Comment) {
285+
commentNodes.push(node);
286+
} else {
287+
otherNodes.push(node);
288+
}
289+
});
290+
return [...commentNodes, ...otherNodes];
291+
},
292+
}),
277293
/**
278294
* Line render plugin
279295
* 连线渲染插件

common/config/rush/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/plugins/free-stack-plugin/__tests__/manager.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ describe('StackingContextManager private methods', () => {
125125
hoveredEntityID: undefined,
126126
selectedIDs: new Set(),
127127
selectedNodes: [],
128+
sortNodes: stackingContextManager.options.sortNodes,
128129
});
129130
hoverService.updateHoveredKey('start_0');
130131
const breakNode = document.getNode('break_0')!;

packages/plugins/free-stack-plugin/__tests__/type.mock.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
} from '@flowgram.ai/free-layout-core';
1414
import type { EntityManager, PipelineRegistry, PipelineRenderer } from '@flowgram.ai/core';
1515

16-
import type { StackingContext } from '../src/type';
16+
import type { StackContextManagerOptions, StackingContext } from '../src/type';
1717

1818
/** mock类型便于测试内部方法 */
1919
export interface IStackingContextManager {
@@ -25,6 +25,7 @@ export interface IStackingContextManager {
2525
selectService: WorkflowSelectService;
2626
node: HTMLDivElement;
2727
disposers: Disposable[];
28+
options: StackContextManagerOptions;
2829
init(): void;
2930
ready(): void;
3031
dispose(): void;

packages/plugins/free-stack-plugin/src/create-free-stack-plugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55

66
import { definePluginCreator } from '@flowgram.ai/core';
77

8+
import { FreeStackPluginOptions } from './type';
89
import { StackingContextManager } from './manager';
910

10-
export const createFreeStackPlugin = definePluginCreator({
11+
export const createFreeStackPlugin = definePluginCreator<FreeStackPluginOptions>({
12+
singleton: true,
1113
onBind({ bind }) {
1214
bind(StackingContextManager).toSelf().inSingletonScope();
1315
},
14-
onInit(ctx) {
16+
onInit(ctx, options) {
1517
const stackingContextManager = ctx.get<StackingContextManager>(StackingContextManager);
16-
stackingContextManager.init();
18+
stackingContextManager.init(options);
1719
},
1820
onReady(ctx) {
1921
const stackingContextManager = ctx.get<StackingContextManager>(StackingContextManager);

packages/plugins/free-stack-plugin/src/manager.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { WorkflowDocument } from '@flowgram.ai/free-layout-core';
1717
import { FlowNodeRenderData } from '@flowgram.ai/document';
1818
import { EntityManager, PipelineRegistry, PipelineRenderer } from '@flowgram.ai/core';
1919

20-
import type { StackingContext } from './type';
20+
import type { StackContextManagerOptions, StackingContext } from './type';
2121
import { StackingComputing } from './stacking-computing';
2222
import { BASE_Z_INDEX } from './constant';
2323

@@ -43,11 +43,16 @@ export class StackingContextManager {
4343
'gedit-playground-layer gedit-flow-render-layer'
4444
);
4545

46+
private options: StackContextManagerOptions = {
47+
sortNodes: (nodes: WorkflowNodeEntity[]) => nodes,
48+
};
49+
4650
private disposers: Disposable[] = [];
4751

4852
constructor() {}
4953

50-
public init(): void {
54+
public init(options: Partial<StackContextManagerOptions> = {}): void {
55+
this.options = { ...this.options, ...options };
5156
this.pipelineRenderer.node.appendChild(this.node);
5257
this.mountListener();
5358
}
@@ -116,6 +121,7 @@ export class StackingContextManager {
116121
hoveredEntityID: this.hoverService.someHovered?.id,
117122
selectedNodes: this.selectService.selectedNodes,
118123
selectedIDs: new Set(this.selectService.selection.map((entity) => entity.id)),
124+
sortNodes: this.options.sortNodes,
119125
};
120126
}
121127

0 commit comments

Comments
 (0)