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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ platform API version.

### Changed

- **One layout snapshot for every handler.** The six handlers that lay nodes
out or route edges each carried their own copy of "walk the model, key the
nodes and the routes, save": eight `collectNodePositions`, eight
`collectEdgeRoutes`, five signature helpers, four snapshot/restore sets,
about 1,400 lines, drifted. `diagram-server/src/server/layout-snapshot.ts`
holds them once (`collectNodePositions`, `collectEdgeRoutes`,
`movableNodeIdsIn`, `snapshot*ById`, `restoreUnselectedLayout`,
`persistLayout`), and `persisted-edge-routes.ts` exports the route-key and
anchor helpers it always had. Every handler keys a saved route the same way
(endpoint signature, AST path where it repeats; four keyed by path alone,
which the loader read through its fallback). Reset Routes and the reroute,
which noted their save under a model-state key nothing read, now leave the
layout meta alone on purpose: lowering "has persisted edge routes" would arm
the bootstrap reroute that redraws what was just cleared.
- **After a drop, the routes an edge cannot keep are drawn by libavoid, with
the edges on one port as one shared path.** The layout draws the edges into
a port as a trunk; the built-in router gives every edge its own channel and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ import {
} from '@eclipse-glsp/server';
import { inject, injectable } from 'inversify';

import { WorkflowDiagramMetadata } from '@dialogram/shared';
import { LayoutPersistenceService } from '../services/layout-persistence-service';
import { WORKFLOW_LAYOUT_PERSISTENCE_KEY, WORKFLOW_NETWORK_MODEL_KEY } from '@dialogram/shared';
import { WorkflowDiagramConstants, portSide } from '@dialogram/shared';
import type { WorkflowDiagramModel } from '@dialogram/shared';
import { GModelSerializer } from '@eclipse-glsp/server';
import { layoutTargetOf } from '../server/layout-target';
import { persistLayout } from '../server/layout-snapshot';

@injectable()
export class WorkflowChangeRoutingPointsOperationHandler extends OperationHandler {
Expand Down Expand Up @@ -207,65 +204,6 @@ export class WorkflowChangeRoutingPointsOperationHandler extends OperationHandle
return true;
}

private collectNodePositions(root: any): Map<string, { x: number; y: number }> {
const result = new Map<string, { x: number; y: number }>();
const visit = (element: any, parentAbsX: number, parentAbsY: number): void => {
if (!element) {
return;
}

const relX = element.position?.x ?? 0;
const relY = element.position?.y ?? 0;
const absX = parentAbsX + relX;
const absY = parentAbsY + relY;

const type = element.type as string | undefined;
if (typeof type === 'string' && type.startsWith('node:')) {
const args = element.args as Record<string, unknown> | undefined;
const key = (args?.[WorkflowDiagramMetadata.AST_PATH] ??
args?.[WorkflowDiagramMetadata.ENTITY_NAME] ??
args?.[WorkflowDiagramMetadata.PORT_NAME]) as unknown;
if (typeof key === 'string' && key.trim() !== '' && element.position) {
result.set(key, { x: absX, y: absY });
}
}

const children = element.children as any[] | undefined;
if (Array.isArray(children)) {
for (const child of children) {
visit(child, absX, absY);
}
}
};
visit(root, 0, 0);
return result;
}

private collectEdgeRoutes(root: any): Map<string, { x: number; y: number }[]> {
const result = new Map<string, { x: number; y: number }[]>();
const visit = (element: any): void => {
if (!element) {
return;
}
if (element instanceof GEdge) {
const args = element.args as Record<string, unknown> | undefined;
const key = args?.[WorkflowDiagramMetadata.AST_PATH];
const routingPoints = (element as any).routingPoints as { x: number; y: number }[] | undefined;
if (typeof key === 'string' && Array.isArray(routingPoints) && routingPoints.length > 0) {
result.set(key, routingPoints);
}
}
const children = element.children as any[] | undefined;
if (Array.isArray(children)) {
for (const child of children) {
visit(child);
}
}
};
visit(root);
return result;
}

private async executeOperation(operation: ChangeRoutingPointsOperation): Promise<void> {
let didApply = false;
for (const entry of operation.newRoutingPoints) {
Expand All @@ -275,28 +213,7 @@ export class WorkflowChangeRoutingPointsOperationHandler extends OperationHandle
return;
}

const diagramModel = this.modelState.get(WORKFLOW_NETWORK_MODEL_KEY) as WorkflowDiagramModel | undefined;
if (!diagramModel) {
return;
}

const { filePath: workflowFilePath, networkId } = layoutTargetOf(diagramModel as any);

// Prefer persisted positions (stable keys) and only update routes.
const persistedPositions = await this.layoutPersistence.loadLayout(workflowFilePath, networkId);
const positions = persistedPositions ?? this.collectNodePositions(this.modelState.root);
const routes = this.collectEdgeRoutes(this.modelState.root);

await this.layoutPersistence.saveLayoutImmediate(workflowFilePath, networkId, positions, routes);

const layoutMeta = this.modelState.get(WORKFLOW_LAYOUT_PERSISTENCE_KEY) as
| {
hasPersistedEdgeRoutes: boolean;
}
| undefined;
if (layoutMeta) {
layoutMeta.hasPersistedEdgeRoutes = routes.size > 0;
this.modelState.set(WORKFLOW_LAYOUT_PERSISTENCE_KEY, layoutMeta);
}
// A route change alone: the node positions already on disk stand.
await persistLayout(this.modelState, this.layoutPersistence, { keepPersistedPositions: true });
}
}
Loading
Loading