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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/********************************************************************************
* Copyright (c) 2026 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { ChangeContainerOperation, GCompartment, GModelChangeContainerOperationHandler, GModelElement } from '@eclipse-glsp/server';
import { injectable } from 'inversify';
import { Category } from '../graph-extension';
import { ModelTypes } from '../util/model-types';

/**
* Workflow-specific {@link GModelChangeContainerOperationHandler} that redirects reparenting into a
* {@link Category}'s structure compartment. The client references the outer {@link Category} node as
* container, so the actual child container (the `struct` compartment) has to be resolved on the server side.
*/
@injectable()
export class ChangeContainerWorkflowHandler extends GModelChangeContainerOperationHandler {
override getContainer(operation: ChangeContainerOperation): GModelElement | undefined {
const container = super.getContainer(operation);
if (container instanceof Category) {
const structComp = this.getCategoryCompartment(container);
if (structComp) {
return structComp;
}
}
return container;
}

getCategoryCompartment(category: Category): GCompartment | undefined {
return category.children
.filter(child => child instanceof GCompartment)
.map(child => child as GCompartment)
.find(comp => ModelTypes.STRUCTURE === comp.type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
DiagramConfiguration,
EdgeCreationChecker,
GLSPServerInitializer,
GModelChangeContainerOperationHandler,
GModelDiagramModule,
InstanceMultiBinding,
LabelEditValidator,
Expand All @@ -35,6 +36,7 @@ import {
SourceModelStorage
} from '@eclipse-glsp/server';
import { injectable } from 'inversify';
import { ChangeContainerWorkflowHandler } from './handler/change-container-workflow-handler';
import { CreateAutomatedTaskHandler } from './handler/create-automated-task-handler';
import { CreateCategoryHandler } from './handler/create-category-handler';
import { CreateDecisionNodeHandler } from './handler/create-decision-node-handler';
Expand Down Expand Up @@ -89,6 +91,7 @@ export class WorkflowDiagramModule extends GModelDiagramModule {
binding.add(CreateDecisionNodeHandler);
binding.add(CreateCategoryHandler);
binding.add(EditTaskOperationHandler);
binding.rebind(GModelChangeContainerOperationHandler, ChangeContainerWorkflowHandler);
}

protected bindDiagramConfiguration(): BindingTarget<DiagramConfiguration> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/********************************************************************************
* Copyright (c) 2025-2026 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { GModelElement, isGBoundsAware } from '@eclipse-glsp/graph';
import { ChangeContainerOperation, MaybePromise, Point } from '@eclipse-glsp/protocol';
import { injectable } from 'inversify';
import { Command } from '../command/command';
import { getRelativeLocation } from '../utils/layout-util';
import { GModelOperationHandler } from './gmodel-operation-handler';

/**
* Applies {@link ChangeContainerOperation} directly to the GModel by re-parenting the element
* from its current container to the target container.
*/
@injectable()
export class GModelChangeContainerOperationHandler extends GModelOperationHandler {
operationType = ChangeContainerOperation.KIND;

createCommand(operation: ChangeContainerOperation): MaybePromise<Command | undefined> {
const element = this.modelState.index.find(operation.elementId);
const targetContainer = this.getContainer(operation);
if (!element || !targetContainer) {
return undefined;
}
// resolve elements lazily inside the closure so that references remain valid
// even when preceding commands in a CompoundCommand rebuild the model tree
return this.commandOf(() => {
const currentElement = this.modelState.index.find(operation.elementId);
const currentContainer = this.getContainer(operation);
if (currentElement && currentContainer) {
this.executeChangeContainer(currentElement, currentContainer, operation.location);
}
});
}

/**
* Returns the {@link GModelElement} that will receive the reparented element. By default this is the
* target element referenced by {@link ChangeContainerOperation.targetContainerId}, but it could also
* be e.g. an intermediate compartment or a different node. Subclasses can override this to redirect the
* reparented element into another container.
*
* @param operation the operation to resolve the container for.
* @returns the {@link GModelElement} that will contain the reparented element, or `undefined` if it cannot be resolved.
*/
getContainer(operation: ChangeContainerOperation): GModelElement | undefined {
return this.modelState.index.find(operation.targetContainerId);
}

protected executeChangeContainer(element: GModelElement, targetContainer: GModelElement, location?: Point): void {
// remove from current parent
const currentParent = element.parent;
if (currentParent) {
const index = currentParent.children.indexOf(element);
if (index >= 0) {
currentParent.children.splice(index, 1);
}
}
// add to new parent
targetContainer.children.push(element);
element.parent = targetContainer;
// update position relative to the new container
if (location && isGBoundsAware(element)) {
const relativeLocation = getRelativeLocation(location, targetContainer);
if (relativeLocation) {
element.position = relativeLocation;
}
}
}
}
5 changes: 4 additions & 1 deletion packages/server/src/common/gmodel/gmodel-diagram-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (c) 2022-2023 STMicroelectronics and others.
* Copyright (c) 2022-2026 STMicroelectronics and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -25,6 +25,7 @@ import { DefaultModelState, ModelState } from '../features/model/model-state';
import { OperationHandler, OperationHandlerConstructor } from '../operations/operation-handler';
import { GModelApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler';
import { GModelChangeBoundsOperationHandler } from './change-bounds-operation-handler';
import { GModelChangeContainerOperationHandler } from './change-container-operation-handler';
import { GModelChangeRoutingPointsOperationHandler } from './change-routing-points-operation-handler';
import { GModelCutOperationHandler } from './cut-operation-handler';
import { GModelDeleteOperationHandler } from './delete-operation-handler';
Expand All @@ -41,6 +42,7 @@ import { GModelReconnectEdgeOperationHandler } from './reconnect-edge-operation-
* - {@link RequestClipboardDataActionHandler} to {@link ActionHandler}
* - {@link GModelApplyLabelEditOperationHandler} to {@link OperationHandler}
* - {@link GModelChangeBoundsOperationHandler} to {@link OperationHandler}
* - {@link GModelChangeContainerOperationHandler} to {@link OperationHandler}
* - {@link GModelCutOperationHandler} to {@link OperationHandler}
* - {@link GModelDeleteOperationHandler} to {@link OperationHandler}
* - {@link GModelPasteOperationHandler} to {@link OperationHandler}
Expand All @@ -67,6 +69,7 @@ export abstract class GModelDiagramModule extends DiagramModule {
super.configureOperationHandlers(binding);
binding.add(GModelApplyLabelEditOperationHandler);
binding.add(GModelChangeBoundsOperationHandler);
binding.add(GModelChangeContainerOperationHandler);
binding.add(GModelCutOperationHandler);
binding.add(GModelDeleteOperationHandler);
binding.add(GModelPasteOperationHandler);
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export * from './features/validation/model-validator';
export * from './features/validation/request-markers-handler';
export * from './gmodel/apply-label-edit-operation-handler';
export * from './gmodel/change-bounds-operation-handler';
export * from './gmodel/change-container-operation-handler';
export * from './gmodel/change-routing-points-operation-handler';
export * from './gmodel/cut-operation-handler';
export * from './gmodel/delete-operation-handler';
Expand Down
12 changes: 11 additions & 1 deletion packages/server/src/common/protocol/glsp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import {
Action,
ActionMessage,
Args,
CompoundOperation,
DisposeClientSessionParameters,
GLSPClientProxy,
GLSPServer,
Expand Down Expand Up @@ -167,7 +169,7 @@ export class DefaultGLSPServer implements GLSPServer {

process(message: ActionMessage): void {
this.validateServerInitialized();
this.logger.info(`process [action=${message.action.kind}, clientId=${message.clientId}]`);
this.logger.info(`process [action=${this.formatAction(message.action)}, clientId=${message.clientId}]`);
const clientSessionId = message.clientId;
const clientSession = this.clientSessions.get(clientSessionId);
if (!clientSession) {
Expand Down Expand Up @@ -228,6 +230,14 @@ export class DefaultGLSPServer implements GLSPServer {
this.sendToClient({ clientId: message.clientId, action: errorAction });
}

protected formatAction(action: Action): string {
if (CompoundOperation.is(action)) {
const children = action.operationList.map(op => op.kind).join(', ');
return `${action.kind}(${children})`;
}
return action.kind;
}

protected sendToClient(message: ActionMessage): void {
this.glspClientProxy.process(message);
}
Expand Down