feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { LINE_OFFSET } from '../../../constants/lines';
|
||||
|
||||
export default function ArrowRenderer({
|
||||
id,
|
||||
pos,
|
||||
strokeWidth,
|
||||
}: {
|
||||
id: string;
|
||||
strokeWidth: number;
|
||||
pos: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<path
|
||||
d={`M ${pos.x - LINE_OFFSET},${pos.y - LINE_OFFSET} L ${pos.x},${
|
||||
pos.y
|
||||
} L ${pos.x - LINE_OFFSET},${pos.y + LINE_OFFSET}`}
|
||||
strokeLinecap="round"
|
||||
stroke={`url(#${id})`}
|
||||
fill="none"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import {
|
||||
type WorkflowLineEntity,
|
||||
POINT_RADIUS,
|
||||
WorkflowLineRenderData,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type IPoint } from '@flowgram-adapter/common';
|
||||
|
||||
import WithPopover from '../popover/with-popover';
|
||||
import styles from '../index.module.less';
|
||||
import ArrowRenderer from '../arrow';
|
||||
import { STROKE_WIDTH_SLECTED, STROKE_WIDTH } from '../../../constants/points';
|
||||
|
||||
// import { AddPoint } from '@/components/add-point';
|
||||
|
||||
const PADDING = 12;
|
||||
|
||||
export interface BezierLineProps {
|
||||
fromColor?: string;
|
||||
toColor?: string;
|
||||
color?: string; // 高亮颜色,优先级最高
|
||||
selected?: boolean;
|
||||
showControlPoints?: boolean;
|
||||
line: WorkflowLineEntity;
|
||||
version: string; // 用于控制 memo 刷新
|
||||
}
|
||||
|
||||
export const BezierLineRender = React.memo(
|
||||
WithPopover((props: BezierLineProps) => {
|
||||
const { line, color, fromColor, toColor, selected } = props;
|
||||
const renderData = line.getData(WorkflowLineRenderData);
|
||||
const { bounds: bbox } = renderData;
|
||||
const { position } = line;
|
||||
// 相对位置
|
||||
const toRelative = (p: IPoint) => ({
|
||||
x: p.x - bbox.x + PADDING,
|
||||
y: p.y - bbox.y + PADDING,
|
||||
});
|
||||
const fromPos = toRelative(position.from);
|
||||
const toPos = toRelative(position.to);
|
||||
// 真正连接线需要到的点的位置
|
||||
const arrowToPos = {
|
||||
x: toPos.x - POINT_RADIUS,
|
||||
y: toPos.y,
|
||||
};
|
||||
const linearStartColor = fromPos.x < arrowToPos.x ? fromColor : toColor;
|
||||
const linerarEndColor = fromPos.x < arrowToPos.x ? toColor : fromColor;
|
||||
|
||||
const strokeWidth = selected ? STROKE_WIDTH_SLECTED : STROKE_WIDTH;
|
||||
const path = (
|
||||
<path
|
||||
d={renderData.path}
|
||||
fill="none"
|
||||
stroke={`url(#${line.id})`}
|
||||
strokeWidth={strokeWidth}
|
||||
className={line.processing ? styles.processingLine : ''}
|
||||
/>
|
||||
);
|
||||
|
||||
// const cls = clx('gedit-mindmap-line', {
|
||||
// hovered,
|
||||
// drawing,
|
||||
// processing: props.processing
|
||||
// });
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="gedit-flow-activity-edge"
|
||||
style={{
|
||||
left: bbox.x - PADDING,
|
||||
top: bbox.y - PADDING,
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width={bbox.width + PADDING * 2}
|
||||
height={bbox.height + PADDING * 2}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
x1="0%"
|
||||
y1="100%"
|
||||
x2="100%"
|
||||
y2="100%"
|
||||
id={line.id}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={color || linearStartColor} offset="0%" />
|
||||
<stop stopColor={color || linerarEndColor} offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
{path}
|
||||
<ArrowRenderer
|
||||
id={line.id}
|
||||
pos={arrowToPos}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import {
|
||||
POINT_RADIUS,
|
||||
WorkflowLineRenderData,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import WithPopover from '../popover/with-popover';
|
||||
import styles from '../index.module.less';
|
||||
import { type BezierLineProps } from '../bezier-line';
|
||||
import ArrowRenderer from '../arrow';
|
||||
import { STROKE_WIDTH, STROKE_WIDTH_SLECTED } from '../../../constants/points';
|
||||
|
||||
/**
|
||||
* 折叠线
|
||||
*/
|
||||
export const FoldLineRender = React.memo(
|
||||
WithPopover((props: BezierLineProps) => {
|
||||
const { selected, color, line } = props;
|
||||
const { to } = line.position;
|
||||
const strokeWidth = selected ? STROKE_WIDTH_SLECTED : STROKE_WIDTH;
|
||||
// 真正连接线需要到的点的位置
|
||||
const arrowToPos = {
|
||||
x: to.x - POINT_RADIUS,
|
||||
y: to.y,
|
||||
};
|
||||
const renderData = line.getData(WorkflowLineRenderData);
|
||||
// const bounds = line.bezier.foldBounds
|
||||
// const points = line.bezier.foldPoints
|
||||
//
|
||||
// const debug = (
|
||||
// <>
|
||||
// <div
|
||||
// style={{
|
||||
// left: bounds.left,
|
||||
// top: bounds.top,
|
||||
// width: bounds.width,
|
||||
// height: bounds.height,
|
||||
// position: 'absolute',
|
||||
// background: 'red',
|
||||
// zIndex: 1000,
|
||||
// opacity: 0.3,
|
||||
// }}
|
||||
// />
|
||||
// {points.map((p, i) => (
|
||||
// <div
|
||||
// key={i}
|
||||
// style={{
|
||||
// left: p.x,
|
||||
// top: p.y,
|
||||
// width: 10,
|
||||
// height: 10,
|
||||
// marginTop: -5,
|
||||
// marginBottom: -5,
|
||||
// position: 'absolute',
|
||||
// background: 'blue',
|
||||
// zIndex: 1000,
|
||||
// }}
|
||||
// />
|
||||
// ))}
|
||||
// </>
|
||||
// )
|
||||
return (
|
||||
<div
|
||||
className="gedit-flow-activity-edge"
|
||||
style={{ position: 'absolute' }}
|
||||
>
|
||||
<svg overflow="visible">
|
||||
<defs>
|
||||
<linearGradient
|
||||
x1="0%"
|
||||
y1="100%"
|
||||
x2="100%"
|
||||
y2="100%"
|
||||
id={line.id}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={color} offset="0%" />
|
||||
<stop stopColor={color} offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path
|
||||
d={renderData.path}
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
className={line.processing ? styles.processingLine : ''}
|
||||
/>
|
||||
<ArrowRenderer
|
||||
id={line.id}
|
||||
pos={arrowToPos}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
@keyframes dashdraw {
|
||||
from {
|
||||
stroke-dashoffset: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.processing-line {
|
||||
stroke-dasharray: 5;
|
||||
animation: dashdraw 0.5s linear infinite;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { FlowRendererRegistry } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
useService,
|
||||
WorkflowHoverService,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
const LINE_POPOVER = 'line-popover';
|
||||
|
||||
export default function WithPopover(Component) {
|
||||
return function WrappedComponent(props) {
|
||||
const hoverService = useService<WorkflowHoverService>(WorkflowHoverService);
|
||||
|
||||
const renderRegistry =
|
||||
useService<FlowRendererRegistry>(FlowRendererRegistry);
|
||||
|
||||
const Popover =
|
||||
renderRegistry.tryToGetRendererComponent(LINE_POPOVER)?.renderer;
|
||||
|
||||
const { line } = props;
|
||||
const isHovered = hoverService.isHovered(line._id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Component {...props} />
|
||||
{Popover ? <Popover line={line} isHovered={isHovered} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.selector-bounds-background {
|
||||
cursor: move;
|
||||
display: none;
|
||||
// background: rgba(255, 255, 255, 0);
|
||||
}
|
||||
.selector-bounds-forground {
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
outline: 1px solid var(--g-playground-selectBox-outline);
|
||||
// 在节点的上边
|
||||
z-index: 33;
|
||||
background-color: var(--g-playground-selectBox-background);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
// import { domUtils } from '@flowgram-adapter/common';
|
||||
import { type FlowSelectorBoundsLayerOptions } from '@flowgram-adapter/free-layout-editor';
|
||||
import { SelectionService, useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { getSelectionBounds } from '../../utils/selection-utils';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
/**
|
||||
* 选择框
|
||||
* @param props
|
||||
* @constructor
|
||||
*/
|
||||
export const SelectorBounds: React.FC<
|
||||
FlowSelectorBoundsLayerOptions
|
||||
> = props => {
|
||||
const selectService = useService<SelectionService>(SelectionService);
|
||||
const bounds = getSelectionBounds(selectService, true);
|
||||
if (bounds.width === 0 || bounds.height === 0) {
|
||||
// domUtils.setStyle(domNode, {
|
||||
// display: 'none',
|
||||
// });
|
||||
return <></>;
|
||||
}
|
||||
const style = {
|
||||
display: 'block',
|
||||
left: bounds.left,
|
||||
top: bounds.top,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
};
|
||||
// domUtils.setStyle(domNode, style);
|
||||
return <div className={styles.selectorBoundsForground} style={style} />;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
// demo 环境自绘 cross-hair,正式环境使用 IconAdd
|
||||
export default function CrossHair(): JSX.Element {
|
||||
return (
|
||||
<div className={styles.symbol}>
|
||||
<div className={styles.crossHair} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
// 背景的白色圆圈
|
||||
.workflow-point {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -10px;
|
||||
margin-left: -10px;
|
||||
// 非 hover 状态下的样式
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
|
||||
& > .symbol {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.bg-circle {
|
||||
position: absolute;
|
||||
transform: scale(0.5);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
|
||||
transition: all 0.2s linear 0s;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: relative;
|
||||
transform: scale(0.4, 0.4);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: #9197f1;
|
||||
border-radius: 50%;
|
||||
|
||||
transition: all 0.2s linear 0s;
|
||||
|
||||
&.hasError {
|
||||
background: red;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
color: #fff;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 0.2s linear 0s;
|
||||
|
||||
& > svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.focus-circle {
|
||||
position: absolute;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
||||
opacity: 0;
|
||||
background: #9197f1;
|
||||
border-radius: 50%;
|
||||
|
||||
transition: opacity 0.2s linear 0s;
|
||||
}
|
||||
}
|
||||
|
||||
&.linked .bg:not(.hasError) {
|
||||
background: #4d53e8;
|
||||
}
|
||||
|
||||
&.hovered .bg:not(.hasError) {
|
||||
cursor: crosshair;
|
||||
transform: scale(1, 1);
|
||||
background: #4d53e8;
|
||||
border: none;
|
||||
|
||||
& > .symbol {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .bg:is(.hasError) {
|
||||
transform: scale(1, 1);
|
||||
background: red;
|
||||
border: none;
|
||||
|
||||
& > .symbol {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cross-hair {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: 4px;
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
box-shadow: 0 4px #fff;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 4px;
|
||||
width: 6px;
|
||||
height: 2px;
|
||||
box-shadow: 4px 0 #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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 ReactDOM from 'react-dom';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
usePlaygroundReadonlyState,
|
||||
WorkflowDragService,
|
||||
WorkflowHoverService,
|
||||
WorkflowLinesManager,
|
||||
type WorkflowPortEntity,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { PORT_BG_CLASS_NAME } from '../../constants/points';
|
||||
import { Warning } from './warning';
|
||||
import CrossHair from './cross-hair';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface WorkflowPortRenderProps {
|
||||
entity: WorkflowPortEntity;
|
||||
onClick?: (event: React.MouseEvent, port: WorkflowPortEntity) => void;
|
||||
}
|
||||
|
||||
export const WorkflowPortRender: React.FC<WorkflowPortRenderProps> =
|
||||
React.memo<WorkflowPortRenderProps>(props => {
|
||||
const dragService = useService<WorkflowDragService>(WorkflowDragService);
|
||||
const hoverService = useService<WorkflowHoverService>(WorkflowHoverService);
|
||||
const linesManager = useService<WorkflowLinesManager>(WorkflowLinesManager);
|
||||
const { entity, onClick } = props;
|
||||
const { portType, portID, relativePosition, disabled, errorMessage } =
|
||||
entity as WorkflowPortEntity & {
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
const [targetElement, setTargetElement] = useState(entity.targetElement);
|
||||
const [posX, updatePosX] = useState(relativePosition.x);
|
||||
const [posY, updatePosY] = useState(relativePosition.y);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [linked, setLinked] = useState(Boolean(entity?.lines?.length));
|
||||
const [hasError, setHasError] = useState(props.entity.hasError);
|
||||
const readonly = usePlaygroundReadonlyState();
|
||||
|
||||
const onMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
const isMouseCenterButton = e.button === 1;
|
||||
if (portType === 'input' || disabled || isMouseCenterButton) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dragService.startDrawingLine(entity, e);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- custom
|
||||
[dragService, portType, portID],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// useEffect 时序问题可能导致 port.hasError 非最新,需重新触发一次 validate
|
||||
props.entity.validate();
|
||||
setHasError(props.entity.hasError);
|
||||
const dispose = props.entity.onEntityChange(() => {
|
||||
// 如果有挂载的节点,不需要更新位置信息
|
||||
if (entity.targetElement) {
|
||||
if (entity.targetElement !== targetElement) {
|
||||
setTargetElement(entity.targetElement);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const newPos = props.entity.relativePosition;
|
||||
// 加上 round 避免点位抖动
|
||||
updatePosX(Math.round(newPos.x));
|
||||
updatePosY(Math.round(newPos.y));
|
||||
});
|
||||
const dispose2 = hoverService.onHoveredChange(id => {
|
||||
setHovered(hoverService.isHovered(entity.id));
|
||||
});
|
||||
const dispose3 = props.entity.onErrorChanged(() => {
|
||||
setHasError(props.entity.hasError);
|
||||
});
|
||||
const dispose4 = linesManager.onAvailableLinesChange(() => {
|
||||
setTimeout(() => {
|
||||
setLinked(Boolean(entity?.lines?.length));
|
||||
}, 0);
|
||||
});
|
||||
return () => {
|
||||
dispose.dispose();
|
||||
dispose2.dispose();
|
||||
dispose3.dispose();
|
||||
dispose4.dispose();
|
||||
};
|
||||
}, [props.entity, hoverService, entity, targetElement, linesManager]);
|
||||
|
||||
// 监听变化
|
||||
const className = classNames(styles.workflowPoint, {
|
||||
[styles.hovered]:
|
||||
!readonly && hovered && !disabled && portType !== 'input',
|
||||
// 有线条链接的时候深蓝色小圆点
|
||||
[styles.linked]: linked,
|
||||
});
|
||||
|
||||
const icon = useMemo(() => {
|
||||
const iconComp = (
|
||||
<div
|
||||
className={classNames({
|
||||
[styles.bg]: true,
|
||||
[PORT_BG_CLASS_NAME]: true,
|
||||
'workflow-point-bg': true,
|
||||
[styles.hasError]: hasError,
|
||||
})}
|
||||
>
|
||||
{hasError ? <Warning /> : <CrossHair />}
|
||||
</div>
|
||||
);
|
||||
if (hasError && errorMessage) {
|
||||
return (
|
||||
<Tooltip
|
||||
className={styles.tooltip}
|
||||
content={errorMessage}
|
||||
trigger="hover"
|
||||
position="top"
|
||||
>
|
||||
{iconComp}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return iconComp;
|
||||
}, [hasError, errorMessage]);
|
||||
|
||||
const content = (
|
||||
<div
|
||||
className={className}
|
||||
style={targetElement ? undefined : { left: posX, top: posY }}
|
||||
onClick={e => onClick?.(e, entity)}
|
||||
onMouseDown={onMouseDown}
|
||||
data-port-entity-id={entity.id}
|
||||
data-testid="bot-edit-multi-agent-flow-node-add-button"
|
||||
>
|
||||
<div
|
||||
className={classNames(styles.bgCircle, 'workflow-bg-circle')}
|
||||
></div>
|
||||
{icon}
|
||||
<div className={styles['focus-circle']} />
|
||||
</div>
|
||||
);
|
||||
if (targetElement) {
|
||||
return ReactDOM.createPortal(content, targetElement);
|
||||
}
|
||||
return content;
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const Warning = () => (
|
||||
<div className={classNames(styles.symbol, styles.warning)}>
|
||||
<svg
|
||||
style={{ width: 10, height: 10 }}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
id="path1"
|
||||
fill="#ffffff"
|
||||
stroke="none"
|
||||
d="M 12 0 C 10.674479 0 9.6 1.074528 9.6 2.4 L 9.6 14.4 C 9.6 15.725521 10.674479 16.799999 12 16.799999 C 13.325521 16.799999 14.4 15.725521 14.4 14.4 L 14.4 2.4 C 14.4 1.074528 13.325521 0 12 0 Z"
|
||||
/>
|
||||
<path
|
||||
id="path2"
|
||||
fill="#ffffff"
|
||||
stroke="none"
|
||||
d="M 12 19.200001 C 10.674479 19.200001 9.6 20.274479 9.6 21.6 C 9.6 22.925518 10.674479 24 12 24 C 13.325521 24 14.4 22.925518 14.4 21.6 C 14.4 20.274479 13.325521 19.200001 12 19.200001 Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
Reference in New Issue
Block a user