Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,13 @@ export const HandleButton = memo(
</div>
);

const shouldRenderPortal = Boolean(visible || (label && labelVisible));

if (portal) {
if (!shouldRenderPortal) {
return null;
}

const { nodeId, ...anchor } = portal;

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { type ReactNode, useMemo } from 'react';
import { useEdgeExecutionState, useElementValidationStatus } from '../../../../hooks';
import type { ElementStatus, NodeExecutionStateWithDebug } from '../../../../types/execution';
import {
type ElementStatus,
ElementStatusValues,
type NodeExecutionStateWithDebug,
} from '../../../../types/execution';
import type { ValidationErrorSeverity } from '../../../../types/validation';
import { edgeTargetStatusToEdgeColor, getStatusAnimation } from '../../EdgeUtils';

Expand Down Expand Up @@ -39,7 +43,15 @@ export function useExecutionEdge(args: UseExecutionEdgeArgs): ExecutionEdge {

const status = enabled ? resolveStatus(executionState, validation) : undefined;
const statusColor = status ? edgeTargetStatusToEdgeColor[status] : undefined;
const animation = useMemo(() => getStatusAnimation(status, edgePath), [status, edgePath]);
// Non-animating edges should not recompute animation output on geometry-only updates.
const animationPath = status === ElementStatusValues.InProgress ? edgePath : undefined;
const animation = useMemo(() => {
if (animationPath == null) {
return null;
}

return getStatusAnimation(status, animationPath);
}, [status, animationPath]);

return { statusColor, animation };
}
24 changes: 17 additions & 7 deletions packages/apollo-react/src/canvas/components/LoopNode/LoopNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function resolveInteractionState(
function useHasChildNodes(id: string, enabled: boolean): boolean {
return useStore(
useCallback(
(state: ReactFlowState) => !enabled || state.nodes.some((node) => node.parentId === id),
(state: ReactFlowState) => !enabled || (state.parentLookup.get(id)?.size ?? 0) > 0,
[id, enabled]
)
);
Expand Down Expand Up @@ -555,7 +555,13 @@ function EmptyState({ label, onAddFirstChild }: { label: string; onAddFirstChild
);
}

function BodyFrame({ isEmpty, isLoading }: { isEmpty?: boolean; isLoading?: boolean }) {
const BodyFrame = memo(function BodyFrame({
isEmpty,
isLoading,
}: {
isEmpty?: boolean;
isLoading?: boolean;
}) {
return (
<div
data-testid="loop-body-frame"
Expand All @@ -571,9 +577,9 @@ function BodyFrame({ isEmpty, isLoading }: { isEmpty?: boolean; isLoading?: bool
) : null}
</div>
);
}
});

function ResizeControls({
const ResizeControls = memo(function ResizeControls({
minimums = DEFAULT_RESIZE_MINIMUMS,
onResize,
onResizeStart,
Expand Down Expand Up @@ -605,9 +611,13 @@ function ResizeControls({
))}
</>
);
}
});

function ResizeCornerIndicators({ visible }: { visible: boolean }) {
const ResizeCornerIndicators = memo(function ResizeCornerIndicators({
visible,
}: {
visible: boolean;
}) {
return (
<>
{RESIZE_CONTROLS.map(({ position, indicatorClassName }) => (
Expand All @@ -624,7 +634,7 @@ function ResizeCornerIndicators({ visible }: { visible: boolean }) {
))}
</>
);
}
});

type SharedHandleGroupProps = {
nodeId: string;
Expand Down
51 changes: 41 additions & 10 deletions packages/apollo-react/src/canvas/components/NodeViewportOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { useInternalNode, ViewportPortal } from '@uipath/apollo-react/canvas/xyflow/react';
import {
type ReactFlowState,
useStore,
ViewportPortal,
} from '@uipath/apollo-react/canvas/xyflow/react';
import type { CSSProperties, ReactNode } from 'react';
import { useCallback } from 'react';
import { shallow } from 'zustand/shallow';

// React Flow adds 1000 to selected node z-index and styles its connection line at 1001.
// Node viewport overlays sit just above those library layers.
Expand All @@ -25,13 +31,38 @@ export type NodeViewportOverlayProps = {
children: ReactNode;
};

type NodeViewportOverlayGeometry = {
x: number | undefined;
y: number | undefined;
width: number | undefined;
height: number | undefined;
};

export function NodeViewportOverlay({ nodeId, anchor, layer, children }: NodeViewportOverlayProps) {
const internalNode = useInternalNode(nodeId);
const positionAbsolute = internalNode?.internals.positionAbsolute;
const width = anchor?.width ?? internalNode?.measured?.width ?? internalNode?.width;
const height = anchor?.height ?? internalNode?.measured?.height ?? internalNode?.height;
const geometry = useStore(
useCallback(
(state: ReactFlowState): NodeViewportOverlayGeometry => {
const internalNode = state.nodeLookup.get(nodeId);
const positionAbsolute = internalNode?.internals.positionAbsolute;

return {
x: positionAbsolute?.x,
y: positionAbsolute?.y,
width: anchor?.width ?? internalNode?.measured?.width ?? internalNode?.width,
height: anchor?.height ?? internalNode?.measured?.height ?? internalNode?.height,
};
},
[anchor?.height, anchor?.width, nodeId]
),
shallow
);

if (!positionAbsolute || width == null || height == null) {
if (
geometry.x == null ||
geometry.y == null ||
geometry.width == null ||
geometry.height == null
) {
return children;
}

Expand All @@ -41,10 +72,10 @@ export function NodeViewportOverlay({ nodeId, anchor, layer, children }: NodeVie
className="absolute pointer-events-none"
style={{
position: 'absolute',
left: positionAbsolute.x + (anchor?.left ?? 0),
top: positionAbsolute.y + (anchor?.top ?? 0),
width,
height,
left: geometry.x + (anchor?.left ?? 0),
top: geometry.y + (anchor?.top ?? 0),
width: geometry.width,
height: geometry.height,
transform: anchor?.transform,
zIndex: layer ? NODE_VIEWPORT_OVERLAY_Z_INDEX[layer] : undefined,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import remarkBreaks from 'remark-breaks';
import remarkGfm from 'remark-gfm';
import { useSafeLingui } from '../../../i18n';
import { GRID_SPACING } from '../../constants';
import { areNodePropsEqualIgnoringPosition } from '../../utils/nodePropsEqual';
import { useSelectionState } from '../BaseCanvas/SelectionStateContext';
import { NodeViewportOverlay } from '../NodeViewportOverlay';
import type { ToolbarAction } from '../Toolbar';
import { NodeToolbar } from '../Toolbar';
Expand Down Expand Up @@ -71,6 +73,7 @@ const StickyNoteNodeComponent = ({
}: StickyNoteNodeProps) => {
const { _ } = useSafeLingui();
const { updateNodeData, deleteElements } = useReactFlow();
const { multipleNodesSelected } = useSelectionState();
const [isEditing, setIsEditing] = useState(!readOnly && (data.autoFocus ?? false));
const [isResizing, setIsResizing] = useState(false);
const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
Expand Down Expand Up @@ -108,10 +111,10 @@ const StickyNoteNodeComponent = ({
}, [isEditing, data.autoFocus, id, updateNodeData, readOnly]);

useEffect(() => {
if (!selected || isResizing) {
if (!selected || dragging || isResizing || multipleNodesSelected) {
setIsColorPickerOpen(false);
}
}, [selected, isResizing]);
}, [selected, dragging, isResizing, multipleNodesSelected]);

useEffect(() => {
if (readOnly) {
Expand Down Expand Up @@ -341,6 +344,9 @@ const StickyNoteNodeComponent = ({
};
}, [_, handleEditClick, handleToggleColorPicker, color, handleDelete]);

const shouldRenderToolbarOverlay =
!readOnly && selected && !dragging && !isResizing && !multipleNodesSelected;

return (
<>
<Global styles={stickyNoteGlobalStyles} />
Expand Down Expand Up @@ -453,10 +459,10 @@ const StickyNoteNodeComponent = ({
)}
</StickyNoteContainer>

{!readOnly && selected && !dragging && !isResizing && (
{shouldRenderToolbarOverlay && (
<NodeToolbar nodeId={id} config={toolbarConfig} expanded={true} portalToNodeOverlay />
)}
{!readOnly && selected && !dragging && !isResizing && (
{shouldRenderToolbarOverlay && (
<NodeViewportOverlay nodeId={id} layer="nodeToolbar">
<AnimatePresence>
{isColorPickerOpen && (
Expand Down Expand Up @@ -506,4 +512,4 @@ const StickyNoteNodeComponent = ({
);
};

export const StickyNoteNode = memo(StickyNoteNodeComponent);
export const StickyNoteNode = memo(StickyNoteNodeComponent, areNodePropsEqualIgnoringPosition);
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ const NodeToolbarComponent = ({
return null;
}

if (portalToNodeOverlay && displayState === 'hidden') {
return null;
}

const toolbarContent = (
<AnimatePresence>
{displayState !== 'hidden' && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Column, Row } from '@uipath/apollo-react/canvas/layouts';
import { Panel } from '@uipath/apollo-react/canvas/xyflow/react';
import { Button } from '@uipath/apollo-wind';
import { useState } from 'react';
import { memo, useState } from 'react';
import { CanvasIcon } from '../../utils/icon-registry';

export interface StoryInfoPanelProps {
Expand All @@ -19,7 +19,7 @@ export interface StoryInfoPanelProps {
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
}

export function StoryInfoPanel({
export const StoryInfoPanel = memo(function StoryInfoPanel({
title,
description,
children,
Expand Down Expand Up @@ -77,4 +77,4 @@ export function StoryInfoPanel({
</Column>
</Panel>
);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ export const allNodeManifests: NodeManifest[] = [
handles: [
{
id: 'success',
label: 'Success',
type: 'source',
handleType: 'output',
},
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-react/src/canvas/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './GroupModificationUtils';
export * from './handle-positioning';
export * from './icon-registry';
export * from './manifest-resolver';
export * from './nodePropsEqual';
export * from './NodeUtils';
export * from './props-helpers';
export * from './resource-operations';
Expand Down
Loading