flow-node-render-data.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import { Compare, Disposable, domUtils, Emitter } from '@flowgram.ai/utils';
  6. import { EntityData } from '@flowgram.ai/core';
  7. import { FlowNodeBaseType } from '../typings';
  8. import type { FlowNodeEntity } from '../entities';
  9. import { FlowNodeTransformData } from './index';
  10. export interface FlowNodeRenderSchema {
  11. addable: boolean; // 是否可添加节点
  12. expandable: boolean; // 是否可展开
  13. collapsed?: boolean; // 复合节点是否收起
  14. expanded: boolean;
  15. activated: boolean; // 是否高亮节点
  16. hovered: boolean; // 是否悬浮在节点上
  17. dragging: boolean; // 是否正在拖拽
  18. stackIndex: number; // 渲染层级
  19. extInfo?: Record<string, any>; // 扩展渲染状态字段
  20. }
  21. /**
  22. * 节点渲染状态相关数据
  23. */
  24. export class FlowNodeRenderData extends EntityData<FlowNodeRenderSchema> {
  25. static type = 'FlowNodeRenderData';
  26. declare entity: FlowNodeEntity;
  27. private _node?: HTMLDivElement;
  28. protected onExtInfoChangeEmitter = new Emitter<{ newInfo: any; oldInfo: any }>();
  29. readonly onExtInfoChange = this.onExtInfoChangeEmitter.event;
  30. get key(): string {
  31. return this.entity.id;
  32. }
  33. getDefaultData(): FlowNodeRenderSchema {
  34. const { addable, expandable, defaultExpanded } = this.entity.getNodeMeta();
  35. return {
  36. addable,
  37. expandable,
  38. expanded: defaultExpanded || false,
  39. activated: false,
  40. hovered: false,
  41. dragging: false,
  42. stackIndex: 0,
  43. };
  44. }
  45. updateExtInfo(info: Record<string, any>, fullUpdate?: boolean) {
  46. const oldInfo = this.data.extInfo;
  47. const newInfo = fullUpdate ? info : { ...oldInfo, ...info };
  48. if (Compare.isChanged(oldInfo, newInfo)) {
  49. this.update({
  50. extInfo: newInfo,
  51. });
  52. this.onExtInfoChangeEmitter.fire({ oldInfo, newInfo });
  53. }
  54. }
  55. getExtInfo(): Record<string, any> | undefined {
  56. return this.data.extInfo;
  57. }
  58. constructor(entity: FlowNodeEntity) {
  59. super(entity);
  60. this.toDispose.push(
  61. Disposable.create(() => {
  62. if (this._node) this._node.remove();
  63. })
  64. );
  65. }
  66. get addable(): boolean {
  67. return this.data.addable;
  68. }
  69. get expandable(): boolean {
  70. return this.data.expandable;
  71. }
  72. get draggable(): boolean {
  73. const { draggable } = this.entity.getNodeMeta();
  74. if (typeof draggable === 'function') {
  75. return draggable(this.entity);
  76. }
  77. return draggable;
  78. }
  79. get expanded(): boolean {
  80. return this.data.expanded;
  81. }
  82. set expanded(expanded: boolean) {
  83. if (this.expandable && this.data.expanded !== expanded) {
  84. this.data.expanded = expanded;
  85. this.fireChange();
  86. }
  87. }
  88. toggleExpand() {
  89. this.expanded = !this.expanded;
  90. }
  91. mouseLeaveTimeout?: ReturnType<typeof setTimeout>;
  92. toggleMouseEnter(silent = false) {
  93. this.entity.document.renderState.setNodeHovered(this.entity);
  94. if (silent) return;
  95. const transform = this.entity.getData(FlowNodeTransformData)!;
  96. if (transform.renderState.hidden) {
  97. return;
  98. }
  99. if (this.mouseLeaveTimeout) {
  100. clearTimeout(this.mouseLeaveTimeout);
  101. this.mouseLeaveTimeout = undefined;
  102. }
  103. transform.renderState.hovered = true;
  104. if (this.entity.isFirst && this.entity.parent?.id !== 'root') {
  105. // 分支中第一个节点 hover,parent activated 设置为 true
  106. transform.parent!.renderState.activated = true;
  107. } else {
  108. transform.renderState.activated = true;
  109. }
  110. }
  111. toggleMouseLeave(silent = false) {
  112. this.entity.document.renderState.setNodeHovered(undefined);
  113. if (silent) return;
  114. const transform = this.entity.getData(FlowNodeTransformData)!;
  115. this.mouseLeaveTimeout = setTimeout(() => {
  116. transform.renderState.hovered = false;
  117. if (this.entity.isFirst && this.entity.parent?.id !== 'root') {
  118. transform.parent!.renderState.activated = false;
  119. }
  120. transform.renderState.activated = false;
  121. }, 200);
  122. }
  123. get hidden(): boolean {
  124. return this.entity.hidden;
  125. }
  126. set hovered(hovered: boolean) {
  127. this.data.hovered = hovered;
  128. this.fireChange();
  129. }
  130. get hovered() {
  131. return this.data.hovered;
  132. }
  133. get dragging(): boolean {
  134. return this.data.dragging;
  135. }
  136. set dragging(dragging: boolean) {
  137. if (this.data.dragging !== dragging) {
  138. this.data.dragging = dragging;
  139. this.fireChange();
  140. }
  141. }
  142. set activated(activated: boolean) {
  143. if (this.entity.flowNodeType === FlowNodeBaseType.BLOCK_ICON && this.entity.parent) {
  144. this.entity.parent.getData<FlowNodeRenderData>(FlowNodeRenderData)!.activated = activated;
  145. return;
  146. }
  147. if (this.data.activated !== activated) {
  148. this.data.activated = activated;
  149. this.fireChange();
  150. }
  151. }
  152. get activated() {
  153. const { entity } = this;
  154. if (entity.parent && entity.parent.getData<FlowNodeRenderData>(FlowNodeRenderData)!.activated) {
  155. return true;
  156. }
  157. return this.data.activated;
  158. }
  159. get stackIndex(): number {
  160. return this.data.stackIndex;
  161. }
  162. set stackIndex(index: number) {
  163. this.data.stackIndex = index;
  164. }
  165. get lineActivated() {
  166. const { activated } = this;
  167. if (!activated) return false;
  168. // 只有 parent 高亮的情况才高亮下面的线条,否则只高亮 node
  169. // inlineBlock 仅看自身
  170. // 圈选情况下个节点被高量,则也跟着高量
  171. return Boolean(
  172. this.entity.parent?.getData(FlowNodeRenderData)?.activated ||
  173. this.entity.isInlineBlock ||
  174. this.entity.next?.getData(FlowNodeRenderData)!.activated
  175. );
  176. }
  177. get node(): HTMLDivElement {
  178. if (this._node) return this._node;
  179. this._node = domUtils.createDivWithClass('gedit-flow-activity-node');
  180. this._node.dataset.testid = 'sdk.workflow.canvas.node';
  181. this._node.dataset.nodeId = this.entity.id;
  182. return this._node;
  183. }
  184. dispose() {
  185. super.dispose();
  186. this.onExtInfoChangeEmitter.dispose();
  187. }
  188. }