feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
Emitter,
|
||||
EntityManager,
|
||||
FlowDocument,
|
||||
type IPoint,
|
||||
type PositionSchema,
|
||||
type FlowNodeEntity,
|
||||
} from '@flowgram-adapter/fixed-layout-editor';
|
||||
|
||||
import {
|
||||
getLineId,
|
||||
getNodeIdFromTreeId,
|
||||
getTreeIdFromNodeId,
|
||||
calcDistance,
|
||||
} from '../utils';
|
||||
import { type CustomLine, type EdgeItem } from '../typings';
|
||||
import { CustomRenderStateConfigEntity } from '../entities';
|
||||
import { LINE_HOVER_DISTANCE } from '../constants';
|
||||
import { TreeService } from './tree-service';
|
||||
import { CustomLinesManager } from './custom-lines-manager';
|
||||
|
||||
@injectable()
|
||||
export class CustomHoverService {
|
||||
@inject(CustomLinesManager) declare linesManager: CustomLinesManager;
|
||||
|
||||
@inject(FlowDocument) declare document: FlowDocument;
|
||||
|
||||
@inject(TreeService) declare treeService: TreeService;
|
||||
|
||||
@inject(EntityManager) declare entityManager: EntityManager;
|
||||
|
||||
@inject(CustomRenderStateConfigEntity)
|
||||
declare renderStateEntity: CustomRenderStateConfigEntity;
|
||||
|
||||
get edges(): EdgeItem[] {
|
||||
return this.treeService.getUnCollapsedEdges();
|
||||
}
|
||||
|
||||
private onBackgroundClickEmitter = new Emitter<void>();
|
||||
|
||||
onBackgroundClick = this.onBackgroundClickEmitter.event;
|
||||
|
||||
private onHoverCollapseEmitter = new Emitter<FlowNodeEntity | undefined>();
|
||||
|
||||
onHoverCollapse = this.onHoverCollapseEmitter.event;
|
||||
|
||||
private onHoverLineEmitter = new Emitter<CustomLine | undefined>();
|
||||
|
||||
onHoverLine = this.onHoverLineEmitter.event;
|
||||
|
||||
private onSelectNodeEmitter = new Emitter<void>();
|
||||
|
||||
onSelectNode = this.onSelectNodeEmitter.event;
|
||||
|
||||
private _hoveredLine: CustomLine | undefined;
|
||||
|
||||
get hoveredLine() {
|
||||
return this._hoveredLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据鼠标位置计算和线条的间距
|
||||
*/
|
||||
getCloseInLineFromMousePos(
|
||||
mousePos: IPoint,
|
||||
minDistance: number = LINE_HOVER_DISTANCE,
|
||||
): CustomLine | undefined {
|
||||
let targetLine: CustomLine | undefined, targetLineDist: number | undefined;
|
||||
(this.linesManager?.lines || []).forEach(line => {
|
||||
const dist = calcDistance(mousePos, line);
|
||||
|
||||
if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
|
||||
targetLineDist = dist;
|
||||
targetLine = line;
|
||||
}
|
||||
});
|
||||
return targetLine;
|
||||
}
|
||||
|
||||
updateHoverLine(pos: PositionSchema, checkTarget: boolean) {
|
||||
if (!checkTarget) {
|
||||
this.onHoverLineEmitter.fire(undefined);
|
||||
}
|
||||
const hoverLine = this.getCloseInLineFromMousePos(pos);
|
||||
if (hoverLine) {
|
||||
this.onHoverLineEmitter.fire(hoverLine);
|
||||
this._hoveredLine = hoverLine;
|
||||
} else {
|
||||
this.onHoverLineEmitter.fire(undefined);
|
||||
this._hoveredLine = undefined;
|
||||
}
|
||||
return hoverLine;
|
||||
}
|
||||
|
||||
backgroundClick(updateLines = true) {
|
||||
this.onBackgroundClickEmitter.fire();
|
||||
this.renderStateEntity.setSelectNodes([]);
|
||||
this.renderStateEntity.setActivatedNode(undefined);
|
||||
if (this.hoveredLine) {
|
||||
const lineId = getLineId(this._hoveredLine);
|
||||
this.renderStateEntity.activeLines = [lineId!];
|
||||
} else if (updateLines) {
|
||||
this.renderStateEntity.activeLines = [];
|
||||
} else {
|
||||
this.renderStateEntity.activeLines = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠策略:访问所有连线的元素,同时访问其父元素,执行 collapse。
|
||||
hoverCollapse(from?: FlowNodeEntity) {
|
||||
this.onHoverCollapseEmitter.fire(from);
|
||||
}
|
||||
|
||||
getParent(node?: FlowNodeEntity): FlowNodeEntity | undefined {
|
||||
if (!node) {
|
||||
return node;
|
||||
}
|
||||
if (node.flowNodeType !== 'blockIcon') {
|
||||
return this.getParent(node.parent);
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
selectNode(node: FlowNodeEntity) {
|
||||
const selectNodes = this.getRelatedNodes(node);
|
||||
this.renderStateEntity.setSelectNodes(selectNodes);
|
||||
this.renderStateEntity.setActivatedNode(node);
|
||||
// 高亮所有相关的线条
|
||||
const activeLines: string[] = [];
|
||||
this.linesManager.lines.map(line => {
|
||||
const fromInclude = selectNodes.includes(line.from.id);
|
||||
const toInclude = selectNodes.includes(line.to.id);
|
||||
if (fromInclude && toInclude) {
|
||||
activeLines.push(getLineId(line)!);
|
||||
}
|
||||
});
|
||||
this.renderStateEntity.activeLines = activeLines;
|
||||
this.linesManager.renderLines();
|
||||
}
|
||||
|
||||
traverseAncestors = (node: FlowNodeEntity): string[] => {
|
||||
let ancArr: string[] = [];
|
||||
if (node.parent) {
|
||||
const arr = this.traverseAncestors(node.parent);
|
||||
ancArr = ancArr.concat(arr);
|
||||
}
|
||||
this.edges.forEach(edge => {
|
||||
if (edge.to === getTreeIdFromNodeId(node.id)) {
|
||||
// push 额外线条
|
||||
ancArr.push(edge.from);
|
||||
// 遍历之前的节点信息
|
||||
const fromNode = this.document.getNode(getNodeIdFromTreeId(edge.from));
|
||||
if (fromNode) {
|
||||
const arr = this.traverseAncestors(fromNode);
|
||||
ancArr.concat(arr);
|
||||
}
|
||||
}
|
||||
});
|
||||
ancArr.push(node.id);
|
||||
return ancArr;
|
||||
};
|
||||
|
||||
traverseDescendants = (node: FlowNodeEntity): string[] => {
|
||||
let ancArr: string[] = [];
|
||||
if (node.children?.length) {
|
||||
node.children?.forEach(child => {
|
||||
ancArr.push(child.id);
|
||||
const childArr = this.traverseDescendants(child);
|
||||
ancArr = ancArr.concat(childArr);
|
||||
});
|
||||
}
|
||||
if (node.next) {
|
||||
const childArr = this.traverseDescendants(node.next);
|
||||
ancArr = ancArr.concat(childArr);
|
||||
}
|
||||
return ancArr;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据当前树结构,遍历节点并且选中
|
||||
*/
|
||||
getRelatedNodes = (node: FlowNodeEntity) => {
|
||||
const parentRelated = this.traverseAncestors(node);
|
||||
const childRelated = this.traverseDescendants(node);
|
||||
return [...parentRelated, ...childRelated];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user