@flowgram-vue/free-lines-plugin 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +1026 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.css +120 -0
  5. package/dist/index.css.map +1 -0
  6. package/dist/index.d.ts +269 -0
  7. package/dist/index.js +1013 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +63 -0
  10. package/src/__tests__/__snapshots__/bezier-controls.spec.ts.snap +20 -0
  11. package/src/__tests__/bezier-controls.spec.ts +25 -0
  12. package/src/components/index.ts +8 -0
  13. package/src/components/workflow-line-render/arrow.ts +60 -0
  14. package/src/components/workflow-line-render/index.css +22 -0
  15. package/src/components/workflow-line-render/index.ts +6 -0
  16. package/src/components/workflow-line-render/line-svg.ts +131 -0
  17. package/src/components/workflow-port-render/cross-hair.ts +16 -0
  18. package/src/components/workflow-port-render/index.css +121 -0
  19. package/src/components/workflow-port-render/index.ts +183 -0
  20. package/src/constants/lines.ts +9 -0
  21. package/src/constants/points.ts +12 -0
  22. package/src/contributions/bezier/bezier-controls.ts +105 -0
  23. package/src/contributions/bezier/index.ts +121 -0
  24. package/src/contributions/fold/fold-line.ts +293 -0
  25. package/src/contributions/fold/index.ts +97 -0
  26. package/src/contributions/index.ts +8 -0
  27. package/src/contributions/straight/index.ts +89 -0
  28. package/src/contributions/straight/point-on-line.ts +37 -0
  29. package/src/contributions/utils.ts +37 -0
  30. package/src/create-free-lines-plugin.ts +44 -0
  31. package/src/css.d.ts +6 -0
  32. package/src/env.d.ts +10 -0
  33. package/src/index.ts +12 -0
  34. package/src/layer/index.ts +8 -0
  35. package/src/layer/workflow-lines-layer.ts +201 -0
  36. package/src/type.ts +40 -0
  37. package/src/types/arrow-renderer.ts +32 -0
  38. package/src/wrap-layer-render.ts +38 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Fragment, h, Teleport, type Component, type VNode } from 'vue';
7
+
8
+ import { inject, injectable } from 'inversify';
9
+ import { domUtils } from '@flowgram-vue/utils';
10
+ import { FlowRendererRegistry } from '@flowgram-vue/renderer';
11
+ import { StackingContextManager } from '@flowgram-vue/free-stack-plugin';
12
+ import {
13
+ nanoid,
14
+ WorkflowDocument,
15
+ WorkflowHoverService,
16
+ WorkflowLineEntity,
17
+ WorkflowLineRenderData,
18
+ WorkflowNodeEntity,
19
+ WorkflowPortEntity,
20
+ WorkflowSelectService,
21
+ } from '@flowgram-vue/free-layout-core';
22
+ import {
23
+ Layer,
24
+ observeEntities,
25
+ observeEntityDatas,
26
+ PlaygroundContainerFactory,
27
+ TransformData,
28
+ } from '@flowgram-vue/core';
29
+
30
+ import { wrapLayerRender } from '../wrap-layer-render';
31
+ import { LineRenderProps, LinesLayerOptions } from '../type';
32
+ import { WorkflowLineRender } from '../components';
33
+
34
+ @injectable()
35
+ export class WorkflowLinesLayer extends Layer<LinesLayerOptions> {
36
+ static type = 'WorkflowLinesLayer';
37
+
38
+ @inject(WorkflowHoverService) hoverService: WorkflowHoverService;
39
+
40
+ @inject(WorkflowSelectService) selectService: WorkflowSelectService;
41
+
42
+ @inject(StackingContextManager) stackContext: StackingContextManager;
43
+
44
+ @inject(FlowRendererRegistry) rendererRegistry: FlowRendererRegistry;
45
+
46
+ @inject(PlaygroundContainerFactory) playgroundContainer: PlaygroundContainerFactory;
47
+
48
+ @observeEntities(WorkflowLineEntity) readonly lines: WorkflowLineEntity[];
49
+
50
+ @observeEntities(WorkflowPortEntity) readonly ports: WorkflowPortEntity[];
51
+
52
+ @observeEntityDatas(WorkflowNodeEntity, TransformData)
53
+ readonly trans: TransformData[];
54
+
55
+ @inject(WorkflowDocument) protected workflowDocument: WorkflowDocument;
56
+
57
+ private layerID = nanoid();
58
+
59
+ private mountedLines: Map<
60
+ string,
61
+ {
62
+ line: WorkflowLineEntity;
63
+ portal: VNode;
64
+ version: string;
65
+ }
66
+ > = new Map();
67
+
68
+ private _version = 0;
69
+
70
+ private rafId?: number;
71
+
72
+ /**
73
+ * 节点线条
74
+ */
75
+ public node = domUtils.createDivWithClass('gedit-playground-layer gedit-flow-lines-layer');
76
+
77
+ public onZoom(scale: number): void {
78
+ this.node.style.transform = `scale(${scale})`;
79
+ }
80
+
81
+ public onReady() {
82
+ this.pipelineNode.appendChild(this.node);
83
+ this.toDispose.pushAll([
84
+ this.selectService.onSelectionChanged(() => this.render()),
85
+ this.hoverService.onHoveredChange(() => this.render()),
86
+ this.workflowDocument.linesManager.onForceUpdate(() => {
87
+ this.mountedLines.clear();
88
+ this.bumpVersion();
89
+ this.render();
90
+ }),
91
+ ]);
92
+ }
93
+
94
+ public dispose() {
95
+ if (this.rafId != null) {
96
+ cancelAnimationFrame(this.rafId);
97
+ this.rafId = undefined;
98
+ }
99
+ this.mountedLines.clear();
100
+ }
101
+
102
+ public render(): VNode {
103
+ this.scheduleLineRenderUpdate();
104
+ const lines = this.lines.map((line) => this.renderLine(line));
105
+ return wrapLayerRender(this.playgroundContainer, h(Fragment, null, lines));
106
+ }
107
+
108
+ private scheduleLineRenderUpdate(): void {
109
+ if (this.rafId != null) {
110
+ cancelAnimationFrame(this.rafId);
111
+ }
112
+ this.rafId = requestAnimationFrame(() => {
113
+ this.rafId = undefined;
114
+ let needsUpdate = false;
115
+ this.lines.forEach((line) => {
116
+ const renderData = line.getData(WorkflowLineRenderData);
117
+ const oldVersion = renderData.renderVersion;
118
+ renderData.update();
119
+ if (renderData.renderVersion !== oldVersion) {
120
+ needsUpdate = true;
121
+ }
122
+ });
123
+ if (needsUpdate) {
124
+ this.render();
125
+ }
126
+ });
127
+ }
128
+
129
+ // 用来绕过 memo
130
+ private bumpVersion() {
131
+ this._version = this._version + 1;
132
+ if (this._version === Number.MAX_SAFE_INTEGER) {
133
+ this._version = 0;
134
+ }
135
+ }
136
+
137
+ private lineProps(line: WorkflowLineEntity): LineRenderProps {
138
+ const { lineType } = this.workflowDocument.linesManager;
139
+ const selected = this.selectService.isSelected(line.id);
140
+ const hovered = this.hoverService.isHovered(line.id);
141
+ const version = this.lineVersion(line);
142
+
143
+ const oldProps: LineRenderProps = {
144
+ key: line.id,
145
+ color: line.color,
146
+ selected,
147
+ hovered,
148
+ line,
149
+ lineType,
150
+ version,
151
+ strokePrefix: this.layerID,
152
+ rendererRegistry: this.rendererRegistry,
153
+ };
154
+ return this.options.customLineProps ? this.options.customLineProps(line, oldProps) : oldProps;
155
+ }
156
+
157
+ private lineVersion(line: WorkflowLineEntity): string {
158
+ const renderData = line.getData(WorkflowLineRenderData);
159
+ const { renderVersion } = renderData;
160
+ const selected = this.selectService.isSelected(line.id);
161
+ const hovered = this.hoverService.isHovered(line.id);
162
+ const { version: lineVersion, color } = line;
163
+
164
+ const version = `v:${this._version},lv:${lineVersion},rv:${renderVersion},c:${color},s:${
165
+ selected ? 'T' : 'F'
166
+ },h:${hovered ? 'T' : 'F'}`;
167
+
168
+ return version;
169
+ }
170
+
171
+ private lineComponent(props: LineRenderProps): VNode {
172
+ const RenderInsideLine = this.options.renderInsideLine;
173
+ const RenderLine = (this.options.renderLine ?? WorkflowLineRender) as Component;
174
+ const inside = RenderInsideLine ? h(RenderInsideLine, props) : null;
175
+ return h(RenderLine, props, () => inside);
176
+ }
177
+
178
+ private renderLine(line: WorkflowLineEntity): VNode {
179
+ const lineProps = this.lineProps(line);
180
+ const cache = this.mountedLines.get(line.id);
181
+ const isCached = cache !== undefined;
182
+ const { portal: cachedPortal, version: cachedVersion } = cache ?? {};
183
+ if (isCached && cachedVersion === lineProps.version) {
184
+ return cachedPortal!;
185
+ }
186
+ if (!isCached) {
187
+ this.renderElement.appendChild(line.node);
188
+ line.onDispose(() => {
189
+ this.mountedLines.delete(line.id);
190
+ line.node.remove();
191
+ });
192
+ }
193
+ const portal = h(Teleport, { to: line.node, key: line.id }, [this.lineComponent(lineProps)]);
194
+ this.mountedLines.set(line.id, { line, portal, version: lineProps.version });
195
+ return portal;
196
+ }
197
+
198
+ private get renderElement(): HTMLElement {
199
+ return this.stackContext.node;
200
+ }
201
+ }
package/src/type.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import type { Component, VNodeChild } from 'vue';
7
+
8
+ import { type FlowRendererRegistry } from '@flowgram-vue/renderer';
9
+ import type {
10
+ WorkflowLineEntity,
11
+ WorkflowLineRenderContributionFactory,
12
+ WorkflowLineUIState,
13
+ } from '@flowgram-vue/free-layout-core';
14
+ import { LineRenderType } from '@flowgram-vue/free-layout-core';
15
+
16
+ export interface LineRenderProps {
17
+ key: string;
18
+ color?: string; // 高亮颜色,优先级最高
19
+ selected?: boolean;
20
+ hovered?: boolean;
21
+ line: WorkflowLineEntity;
22
+ lineType: LineRenderType;
23
+ version: string; // 用于控制 memo 刷新
24
+ strokePrefix?: string;
25
+ children?: VNodeChild;
26
+ rendererRegistry?: FlowRendererRegistry; // 渲染器注册表,用于获取自定义箭头组件
27
+ [key: string]: any;
28
+ }
29
+
30
+ export interface LinesLayerOptions {
31
+ renderInsideLine?: Component;
32
+ renderLine?: Component;
33
+ customLineProps?: (line: WorkflowLineEntity, oldProps: LineRenderProps) => LineRenderProps; // 自定义线条属性
34
+ }
35
+
36
+ export interface FreeLinesPluginOptions extends LinesLayerOptions {
37
+ contributions?: WorkflowLineRenderContributionFactory[];
38
+ defaultLineUIState?: Partial<WorkflowLineUIState>;
39
+ defaultLineType?: LineRenderType;
40
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import type { Component } from 'vue';
7
+
8
+ import { type IPoint } from '@flowgram-vue/utils';
9
+ import { LinePointLocation } from '@flowgram-vue/free-layout-core';
10
+ import { type WorkflowLineEntity } from '@flowgram-vue/free-layout-core';
11
+
12
+ /**
13
+ * 箭头渲染器属性接口
14
+ */
15
+ export interface ArrowRendererProps {
16
+ /** 用于渐变的唯一ID */
17
+ id: string;
18
+ /** 箭头位置 */
19
+ pos: IPoint;
20
+ location: LinePointLocation;
21
+ /** 描边宽度 */
22
+ strokeWidth: number;
23
+ /** 是否隐藏箭头 */
24
+ hide?: boolean;
25
+ /** 线条实体,提供更多上下文信息 */
26
+ line: WorkflowLineEntity;
27
+ }
28
+
29
+ /**
30
+ * 箭头渲染器组件类型
31
+ */
32
+ export type ArrowRendererComponent = Component;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { defineComponent, h, provide, type VNodeChild } from 'vue';
7
+ import {
8
+ Playground,
9
+ PlaygroundVueContainerKey,
10
+ PlaygroundVueRefKey,
11
+ type PlaygroundContainerFactory,
12
+ } from '@flowgram-vue/core';
13
+
14
+ export const LayerVueProvide = defineComponent({
15
+ name: 'LayerVueProvide',
16
+ props: {
17
+ factory: {
18
+ type: Object,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const factory = props.factory as PlaygroundContainerFactory;
24
+ provide(PlaygroundVueContainerKey, factory as any);
25
+ try {
26
+ provide(PlaygroundVueRefKey, factory.get(Playground));
27
+ } catch {
28
+ // 测试容器可能重复 bind PlaygroundConfig,此时仅 provide container
29
+ }
30
+ },
31
+ render() {
32
+ return this.$slots.default?.();
33
+ },
34
+ });
35
+
36
+ export function wrapLayerRender(factory: PlaygroundContainerFactory, children: VNodeChild) {
37
+ return h(LayerVueProvide, { factory }, () => children);
38
+ }