206 lines
6.1 KiB
TypeScript
206 lines
6.1 KiB
TypeScript
/*
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Calculate and line spacing based on mouse position
|
|
*/
|
|
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 strategy: access all connected elements and access their parent elements simultaneously, performing 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);
|
|
// Highlight all relevant lines
|
|
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 extra lines
|
|
ancArr.push(edge.from);
|
|
// Traverse the previous node information
|
|
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;
|
|
};
|
|
|
|
/**
|
|
* According to the current tree structure, iterate through the nodes and select
|
|
*/
|
|
getRelatedNodes = (node: FlowNodeEntity) => {
|
|
const parentRelated = this.traverseAncestors(node);
|
|
const childRelated = this.traverseDescendants(node);
|
|
return [...parentRelated, ...childRelated];
|
|
};
|
|
}
|