Skip to content
Open
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
Binary file modified diagram-editor/dist.tar.gz
Binary file not shown.
115 changes: 84 additions & 31 deletions diagram-editor/frontend/api.preprocessed.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -849,49 +849,102 @@
}
]
},
"InteractionSessionMessage": {
"InteractionSessionFeedback": {
"oneOf": [
{
"allOf": [
{
"oneOf": [
{
"properties": {
"operationStarted": {
"type": "string"
}
},
"required": [
"operationStarted"
],
"type": "object"
"additionalProperties": false,
"properties": {
"operationStarted": {
"properties": {
"executionId": {
"type": "string"
},
{
"properties": {
"operationFinished": {
"type": "string"
}
},
"required": [
"operationFinished"
],
"type": "object"
"operationId": {
"type": "string"
}
]
},
{
},
"required": [
"operationId",
"executionId"
],
"type": "object"
}
},
"required": [
"operationStarted"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"operationFinished": {
"properties": {
"type": {
"const": "feedback",
"executionId": {
"type": "string"
},
"operationId": {
"type": "string"
}
},
"required": [
"type"
"operationId",
"executionId"
],
"type": "object"
}
]
},
"required": [
"operationFinished"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"connectionActivity": {
"properties": {
"sourceOperationId": {
"type": "string"
},
"targetOperationId": {
"type": "string"
}
},
"required": [
"sourceOperationId",
"targetOperationId"
],
"type": "object"
}
},
"required": [
"connectionActivity"
],
"type": "object"
}
]
},
"InteractionSessionMessage": {
"oneOf": [
{
"properties": {
"events": {
"items": {
"$ref": "#/$defs/InteractionSessionFeedback"
},
"type": "array"
},
"type": {
"const": "feedback",
"type": "string"
}
},
"required": [
"type",
"events"
],
"type": "object"
},
{
"allOf": [
Expand Down
29 changes: 17 additions & 12 deletions diagram-editor/frontend/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
.react-flow.dark {
--xy-edge-stroke-default: var(--mui-palette-grey-700);
}
.react-flow__edge .react-flow__edge-path {
transition:
stroke 450ms ease-out,
filter 450ms ease-out;
}
.react-flow__edge.message-active .react-flow__edge-path {
stroke: var(--mui-palette-warning-dark);
filter: drop-shadow(0 0 4px var(--mui-palette-warning-dark));
transition-duration: 0ms;
}
.react-flow__pane.draggable {
cursor: default;
}
Expand Down Expand Up @@ -34,24 +44,19 @@
background-color: var(--mui-palette-secondary-dark);
}
.react-flow__handle.handle-data-buffer {
background: linear-gradient(
-45deg,
var(--mui-palette-secondary-dark) 50%,
var(--xy-handle-background-color-default) 50%
);
}
.react-flow__handle.handle-data-stream {
background: linear-gradient(
-45deg,
var(--mui-palette-warning-dark) 50%,
var(--xy-handle-background-color-default) 50%
);
background-color: var(--mui-palette-secondary-main);
}
.react-flow__handle.connectionindicator {
opacity: 1;
border: 1px solid transparent;
box-shadow: none;
}
.react-flow__handle.handle-data-stream {
width: 10px;
height: 10px;
background-color: var(--mui-palette-background-default);
border: 2px solid var(--mui-palette-warning-dark);
}
.react-flow__handle.handle-compatible {
border: 1px solid var(--mui-palette-success-main);
box-shadow: 0 0 0 4px rgb(76 175 80 / 22%);
Expand Down
73 changes: 66 additions & 7 deletions diagram-editor/frontend/diagram-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
useDiagramProperties,
} from './diagram-properties-provider';
import { useDiagramSidePanel } from './diagram-side-panel-controller';
import { getEditPopoverPositionForNode } from './diagram-side-panel-layout';
import {
clearDraftWorkspace,
type DraftWorkspaceContent,
Expand All @@ -66,6 +67,7 @@ import { EditEdgeForm, EditNodeForm } from './forms';
import EditScopeForm from './forms/edit-scope-form';
import type { ScriptNodeEnvironmentBinding } from './forms/script-environment-workspace';
import { useScriptEnvironmentNavigation } from './forms/use-script-environment-navigation';
import { glowEdge } from './handles';
import {
type InteractionVisualizationContext,
InteractionVisualizationProvider,
Expand All @@ -82,6 +84,7 @@ import {
MaterialSymbol,
NODE_TYPES,
type OperationNode,
START_ID,
TERMINATE_ID,
} from './nodes';
import { NotificationProvider } from './notification-provider';
Expand Down Expand Up @@ -248,6 +251,22 @@ function getInteractionNodeId(
? operationId.slice(1)
: operationId;

if (normalizedId === '(start)') {
return (
nodeManager.tryGetNode(joinNamespaces(ROOT_NAMESPACE, START_ID))?.id ??
null
);
}

if (normalizedId.endsWith(':(start)')) {
const namespace = normalizedId.slice(0, -':(start)'.length);
return (
nodeManager.tryGetNode(
joinNamespaces(ROOT_NAMESPACE, namespace, START_ID),
)?.id ?? null
);
}

if (normalizedId === '(terminate)') {
return (
nodeManager.tryGetNode(joinNamespaces(ROOT_NAMESPACE, TERMINATE_ID))
Expand Down Expand Up @@ -303,21 +322,32 @@ function DiagramEditor() {
React.useState(() => new Set<string>());
const [interactionVisitedNodeIds, setInteractionVisitedNodeIds] =
React.useState(() => new Set<string>());
const interactionExecutionNodeIds = React.useRef(new Map<string, string>());
const clearInteractionVisualization = React.useCallback(() => {
interactionExecutionNodeIds.current.clear();
setInteractionActiveNodeIds(new Set());
setInteractionVisitedNodeIds(new Set());
}, []);
const markInteractionFinished = React.useCallback(() => {
interactionExecutionNodeIds.current.clear();
setInteractionActiveNodeIds(new Set());
}, []);
const markInteractionOperationFinished = React.useCallback(
(operationId: string) => {
const nodeId = getInteractionNodeId(operationId, nodeManager);
(operationId: string, executionId: string) => {
const nodeId =
interactionExecutionNodeIds.current.get(executionId) ??
getInteractionNodeId(operationId, nodeManager);
if (!nodeId) {
return;
}

interactionExecutionNodeIds.current.delete(executionId);
setInteractionActiveNodeIds((prev) => {
if (
[...interactionExecutionNodeIds.current.values()].includes(nodeId)
) {
return prev;
}
const next = new Set(prev);
next.delete(nodeId);
return next;
Expand All @@ -331,12 +361,13 @@ function DiagramEditor() {
[nodeManager],
);
const markInteractionOperationStarted = React.useCallback(
(operationId: string) => {
(operationId: string, executionId: string) => {
const nodeId = getInteractionNodeId(operationId, nodeManager);
if (!nodeId) {
return;
}

interactionExecutionNodeIds.current.set(executionId, nodeId);
setInteractionVisitedNodeIds((prev) => {
const next = new Set(prev);
next.delete(nodeId);
Expand All @@ -350,6 +381,23 @@ function DiagramEditor() {
},
[nodeManager],
);
const markInteractionConnection = React.useCallback(
(sourceOperationId: string, targetOperationId: string) => {
const sourceNodeId = getInteractionNodeId(sourceOperationId, nodeManager);
const targetNodeId = getInteractionNodeId(targetOperationId, nodeManager);
if (!sourceNodeId || !targetNodeId) {
return;
}
reactFlowInstance.current
?.getEdges()
.filter(
(edge) =>
edge.source === sourceNodeId && edge.target === targetNodeId,
)
.forEach((edge) => glowEdge(edge.id));
},
[nodeManager],
);
const interactionVisualizationContext =
React.useMemo<InteractionVisualizationContext>(
() => ({
Expand All @@ -359,6 +407,7 @@ function DiagramEditor() {
markInteractionFinished,
markInteractionOperationFinished,
markInteractionOperationStarted,
markInteractionConnection,
}),
[
clearInteractionVisualization,
Expand All @@ -367,6 +416,7 @@ function DiagramEditor() {
markInteractionFinished,
markInteractionOperationFinished,
markInteractionOperationStarted,
markInteractionConnection,
],
);
const savedNodes = React.useRef<DiagramEditorNode[]>([]);
Expand All @@ -387,7 +437,11 @@ function DiagramEditor() {
} = useTransientEditorDrafts();
const openScriptEnvironment = useScriptEnvironmentNavigation();
const {
state: { open: sidePanelOpen, tab: sidePanelTab },
state: {
open: sidePanelOpen,
expanded: sidePanelExpanded,
tab: sidePanelTab,
},
} = useDiagramSidePanel();

const updateEditorModeAction = React.useCallback(
Expand Down Expand Up @@ -761,7 +815,6 @@ function DiagramEditor() {
handleNodeChange(change);
closeAllPopovers();
};

if (node.type === 'scope') {
return (
<EditScopeForm
Expand Down Expand Up @@ -1412,11 +1465,17 @@ function DiagramEditor() {
);
}
setEditingNodeId(node.id);

setEditOpFormPopoverProps({
open: true,
anchorReference: 'anchorPosition',
anchorPosition: { left: ev.clientX, top: ev.clientY },
anchorPosition: getEditPopoverPositionForNode({
nodeRect: ev.currentTarget.getBoundingClientRect(),
viewportWidth: window.innerWidth,
sidePanel: {
open: sidePanelOpen,
expanded: sidePanelExpanded,
},
}),
});
}}
onEdgeClick={(ev, edge) => {
Expand Down
21 changes: 21 additions & 0 deletions diagram-editor/frontend/diagram-side-panel-layout.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
constrainEditPopoverPosition,
getDiagramSidePanelWidth,
getEditPopoverPositionForNode,
} from './diagram-side-panel-layout';

describe('diagram side-panel layout', () => {
Expand Down Expand Up @@ -68,4 +69,24 @@ describe('diagram side-panel layout', () => {
}),
).toEqual({ left: 16, top: 300 });
});

test('places the editor right of a node when it fits', () => {
expect(
getEditPopoverPositionForNode({
nodeRect: { left: 200, right: 242, top: 300 },
viewportWidth: 1440,
sidePanel: { open: false, expanded: false },
}),
).toEqual({ left: 258, top: 300 });
});

test('flips the editor left of a node when the drawer leaves no room', () => {
expect(
getEditPopoverPositionForNode({
nodeRect: { left: 600, right: 642, top: 300 },
viewportWidth: 1440,
sidePanel: { open: true, expanded: true },
}),
).toEqual({ left: 164, top: 300 });
});
});
Loading