Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handlers for Smart Connector UIExtension #70

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
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,58 @@
/********************************************************************************
* Copyright (c) 2023 Business Informatics Group (TU Wien) 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 {
DefaultSelectionPaletteItemProvider, SelectionPaletteSettings
} from '@eclipse-glsp/server';
import {
SelectionPalettePosition,
SelectionPaletteGroupUIType,
DefaultTypes
} from '@eclipse-glsp/protocol';
import { injectable } from 'inversify';
import { ModelTypes } from '../util/model-types';

@injectable()
export class WorkflowSelectionPaletteItemProvider extends DefaultSelectionPaletteItemProvider {

protected override selectionPaletteNodeSettings: SelectionPaletteSettings = {
position: SelectionPalettePosition.Top,
showTitle: true,
submenu: false,
showOnlyForChildren: SelectionPaletteGroupUIType.Labels
};

protected override selectionPaletteEdgeSettings: SelectionPaletteSettings = {
position: SelectionPalettePosition.Right,
showTitle: true,
submenu: true
};

protected override nodeOperationFilter = {
[ModelTypes.AUTOMATED_TASK]: [ModelTypes.WEIGHTED_EDGE, ModelTypes.AUTOMATED_TASK, ModelTypes.MANUAL_TASK,
ModelTypes.ACTIVITY_NODE],
[ModelTypes.MERGE_NODE]: [DefaultTypes.EDGE, ModelTypes.MERGE_NODE, ModelTypes.CATEGORY],
[ModelTypes.FORK_NODE]: [DefaultTypes.EDGE, ModelTypes.FORK_NODE],
[ModelTypes.CATEGORY]: [ModelTypes.WEIGHTED_EDGE, ModelTypes.FORK_NODE],
[ModelTypes.JOIN_NODE]: [ModelTypes.AUTOMATED_TASK, ModelTypes.FORK_NODE, ModelTypes.JOIN_NODE]
};

protected override defaultEdge = DefaultTypes.EDGE;

protected override edgeTypes = {
[ModelTypes.AUTOMATED_TASK]: DefaultTypes.EDGE,
[ModelTypes.MERGE_NODE]: DefaultTypes.EDGE
};
}
Original file line number Diff line number Diff line change
@@ -32,7 +32,8 @@ import {
OperationHandlerConstructor,
PopupModelFactory,
ServerModule,
SourceModelStorage
SourceModelStorage,
SelectionPaletteItemProvider
} from '@eclipse-glsp/server';
import { injectable } from 'inversify';
import { CreateAutomatedTaskHandler } from './handler/create-automated-task-handler';
@@ -52,6 +53,7 @@ import { NodeDocumentationNavigationTargetProvider } from './provider/node-docum
import { PreviousNodeNavigationTargetProvider } from './provider/previous-node-navigation-target-provider';
import { WorkflowCommandPaletteActionProvider } from './provider/workflow-command-palette-action-provider';
import { WorkflowContextMenuItemProvider } from './provider/workflow-context-menu-item-provider';
import { WorkflowSelectionPaletteItemProvider } from './provider/workflow-selection-palette-item-provider';
import { EditTaskOperationHandler } from './taskedit/edit-task-operation-handler';
import { TaskEditContextActionProvider } from './taskedit/task-edit-context-provider';
import { TaskEditValidator } from './taskedit/task-edit-validator';
@@ -107,6 +109,10 @@ export class WorkflowDiagramModule extends GModelDiagramModule {
return WorkflowCommandPaletteActionProvider;
}

protected override bindSelectionPaletteItemProvider(): BindingTarget<SelectionPaletteItemProvider> | undefined {
return WorkflowSelectionPaletteItemProvider;
}

protected override bindLabelEditValidator(): BindingTarget<LabelEditValidator> | undefined {
return WorkflowLabelEditValidator;
}
9 changes: 9 additions & 0 deletions packages/server/src/common/di/diagram-module.ts
Original file line number Diff line number Diff line change
@@ -77,6 +77,10 @@ import {
NavigationTargetProviders,
Operations
} from './service-identifiers';
import {
DefaultSelectionPaletteItemProvider,
SelectionPaletteItemProvider
} from '../features/contextactions/selection-palette-item-provider';

/**
* The diagram module is the central configuration artifact for configuring a client session specific injector. For each
@@ -154,6 +158,7 @@ export abstract class DiagramModule extends GLSPModule {
applyOptionalBindingTarget(context, ToolPaletteItemProvider, this.bindToolPaletteItemProvider());
applyOptionalBindingTarget(context, CommandPaletteActionProvider, this.bindCommandPaletteActionProvider());
applyOptionalBindingTarget(context, ContextMenuItemProvider, this.bindContextMenuItemProvider());
applyOptionalBindingTarget(context, SelectionPaletteItemProvider, this.bindSelectionPaletteItemProvider());
this.configureMultiBinding(new MultiBinding<ContextActionsProvider>(ContextActionsProviders), binding =>
this.configureContextActionProviders(binding)
);
@@ -345,6 +350,10 @@ export abstract class DiagramModule extends GLSPModule {
return DefaultToolPaletteItemProvider;
}

protected bindSelectionPaletteItemProvider(): BindingTarget<SelectionPaletteItemProvider> | undefined {
return DefaultSelectionPaletteItemProvider;
}

protected bindCommandPaletteActionProvider(): BindingTarget<CommandPaletteActionProvider> | undefined {
return undefined;
}
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import { CommandPaletteActionProvider } from './command-palette-action-provider'
import { ContextActionsProvider } from './context-actions-provider';
import { ContextMenuItemProvider } from './context-menu-item-provider';
import { ToolPaletteItemProvider } from './tool-palette-item-provider';
import { SelectionPaletteItemProvider } from './selection-palette-item-provider';

/**
* A registry that keeps track of all registered {@link ContextActionsProvider}s.
@@ -30,7 +31,8 @@ export class ContextActionsProviderRegistry extends Registry<string, ContextActi
@multiInject(ContextActionsProviders) @optional() contextActionsProvider: ContextActionsProvider[] = [],
@inject(ContextMenuItemProvider) @optional() contextMenuItemProvider?: ContextMenuItemProvider,
@inject(CommandPaletteActionProvider) @optional() commandPaletteActionProvider?: CommandPaletteActionProvider,
@inject(ToolPaletteItemProvider) @optional() toolPaletteItemProvider?: ToolPaletteItemProvider
@inject(ToolPaletteItemProvider) @optional() toolPaletteItemProvider?: ToolPaletteItemProvider,
@inject(SelectionPaletteItemProvider) @optional() selectionPaletteItemProvider?: SelectionPaletteItemProvider
) {
super();
contextActionsProvider.forEach(provider => this.register(provider.contextId, provider));
@@ -43,5 +45,8 @@ export class ContextActionsProviderRegistry extends Registry<string, ContextActi
if (toolPaletteItemProvider) {
this.register(toolPaletteItemProvider.contextId, toolPaletteItemProvider);
}
if (selectionPaletteItemProvider) {
this.register(selectionPaletteItemProvider.contextId, selectionPaletteItemProvider);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/********************************************************************************
* Copyright (c) 2023 Business Informatics Group (TU Wien) 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 {
Args,
CreateEdgeOperation,
CreateNodeOperation,
PaletteItem,
SelectionPaletteGroupItem,
EditorContext,
LabeledAction,
MaybePromise,
SelectionPalettePosition,
SelectionPaletteGroupUIType,
SelectionPaletteNodeItem,
TriggerNodeCreationAction
} from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { CreateOperationHandler } from '../../operations/create-operation-handler';
import { OperationHandlerRegistry } from '../../operations/operation-handler-registry';
import { ContextActionsProvider } from './context-actions-provider';
import { Logger } from '../../utils/logger';

/**
* A {@link ContextActionsProvider} for {@link PaletteItem}s in the Selection palette which appears when a node is selected.
*/
@injectable()
export abstract class SelectionPaletteItemProvider implements ContextActionsProvider {
/**
* Returns the context id of the provider.
*/
get contextId(): string {
return 'selection-palette';
}

/**
* Returns a list of {@link LabeledAction}s for a given {@link EditorContext}.
*
* @param editorContext The editorContext for which the actions are returned.
* @returns A list of {@link LabeledAction}s for a given {@link EditorContext}.
*/
async getActions(editorContext: EditorContext): Promise<LabeledAction[]> {
return this.getItems(editorContext.args);
}

/**
* Constructs a list of {@link PaletteItem}s for a given map of string arguments.
*
* @param args A map of string arguments.
* @returns A list of {@link PaletteItem}s for a given map of string arguments.
*/
abstract getItems(args?: Args): MaybePromise<SelectionPaletteGroupItem[]>;
}

export type SelectionPaletteSettings =
| {
position: SelectionPalettePosition;
showTitle: true;
submenu: boolean;
showOnlyForChildren?: SelectionPaletteGroupUIType;
}
| {
position: SelectionPalettePosition;
showTitle: false;
showOnlyForChildren?: SelectionPaletteGroupUIType;
};

@injectable()
export class DefaultSelectionPaletteItemProvider extends SelectionPaletteItemProvider {
@inject(OperationHandlerRegistry) protected operationHandlerRegistry: OperationHandlerRegistry;
@inject(Logger)
protected logger: Logger;

protected counter: number;

protected selectionPaletteNodeSettings: SelectionPaletteSettings = {
position: SelectionPalettePosition.Right,
showTitle: true,
submenu: true,
showOnlyForChildren: SelectionPaletteGroupUIType.Icons
};

protected selectionPaletteEdgeSettings: SelectionPaletteSettings = {
position: SelectionPalettePosition.Right,
showTitle: true,
submenu: false
};
/** filter that excludes nodes/edges from options, given a node ID as key */
protected nodeOperationFilter: Record<string, string[] | undefined> = {};

/** edge that is used between source and destination by default when a new node is created
* (if not given, no edge will be created when creating new node) */
protected defaultEdge?: string;

/** list of edges where the key is a node ID and the value is a edge ID
* the edge to a new node when the source node has the ID of the key
* otherwise, the default edge will be used */
protected edgeTypes: Record<string, string | undefined>;

getItems(args?: Args): SelectionPaletteGroupItem[] {
const handlers = this.operationHandlerRegistry.getAll().filter(CreateOperationHandler.is) as CreateOperationHandler[];
this.counter = 0;
const nodes = this.createSelectionPaletteGroupItem(
handlers,
CreateNodeOperation.KIND,
args?.nodeType as string,
this.selectionPaletteNodeSettings.showOnlyForChildren
);
const edges = this.createSelectionPaletteGroupItem(
handlers,
CreateEdgeOperation.KIND,
args?.nodeType as string,
this.selectionPaletteEdgeSettings.showOnlyForChildren
);
return [
{
id: 'selection-palette-node-group',
label: 'Nodes',
actions: [],
children: nodes,
icon: 'symbol-property',
sortString: 'A',
...this.selectionPaletteNodeSettings
},
{
id: 'selection-palette-edge-group',
label: 'Edges',
actions: [],
children: edges,
icon: 'symbol-property',
sortString: 'B',
...this.selectionPaletteEdgeSettings
}
];
}

protected createSelectionPaletteGroupItem(
handlers: CreateOperationHandler[],
kind: string,
selectedNodeType: string,
showOnly?: SelectionPaletteGroupUIType
): PaletteItem[] {
const includedInNodeFilter = (e: string): boolean => !!this.nodeOperationFilter[selectedNodeType]?.includes(e);
const paletteItems = handlers
.filter(
handler =>
handler.operationType === kind &&
(selectedNodeType && this.nodeOperationFilter[selectedNodeType]
? !handler.elementTypeIds.some(includedInNodeFilter)
: true)
)
.map(handler =>
handler.getTriggerActions().map(action => this.createSelectionPaletteItem(action, handler.label, selectedNodeType))
)
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.sortString.localeCompare(b.sortString));
if (showOnly === SelectionPaletteGroupUIType.Icons) {
if (paletteItems.every(paletteItem => paletteItem.icon !== '')) {
this.logger.warn('Not all elements have icons. Labels will be shown, check settings for selection palette.');
return paletteItems;
}
paletteItems.forEach(paletteItem => (paletteItem.label = ''));
} else if (showOnly === SelectionPaletteGroupUIType.Labels) {
paletteItems.forEach(paletteItem => (paletteItem.icon = ''));
}
return paletteItems;
}

protected createSelectionPaletteItem(
action: PaletteItem.TriggerElementCreationAction,
label: string,
nodeType: string
): PaletteItem | SelectionPaletteNodeItem {
if (TriggerNodeCreationAction.is(action)) {
let edgeType = this.edgeTypes[nodeType];
if (!edgeType) {
edgeType = this.defaultEdge;
}
return {
id: `selection-palette-palette-item${this.counter++}`,
sortString: label.charAt(0),
label,
actions: [action],
edgeType: edgeType
};
}
return {
id: `selection-palette-palette-item${this.counter++}`,
sortString: label.charAt(0),
label,
actions: [action]
};
}
}
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
import { GModelElement, GNode } from '@eclipse-glsp/graph';
import {
Args, CreateNodeOperation, GhostElement, MaybePromise, Point, SelectAction,
TriggerNodeCreationAction
TriggerNodeCreationAction, CreateEdgeOperation
} from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ActionDispatcher } from '../actions/action-dispatcher';
@@ -54,6 +54,14 @@ export abstract class GModelCreateNodeOperationHandler extends GModelOperationHa
container.children.push(element);
element.parent = container;
this.actionDispatcher.dispatchAfterNextUpdate(SelectAction.create({ selectedElementsIDs: [element.id] }));
// Creates default edge on node creation when a source ID is given in the CreateNodeOperation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why this is done this way, but we should find a more generic solution here.

Ideally we would dispatch a CompoundOperation on the client-side that contains the CreateNode and CreateEdge operations. Currently the API does not support this, because we need context-specific information. i.e. the CreateEdge operation needs to know the id of the newly created node it should connect to.

We should create a follow-up for that.

if (operation.args?.createEdge && operation.args?.edgeType) {
this.actionDispatcher.dispatchAfterNextUpdate(CreateEdgeOperation.create({
elementTypeId: operation.args?.edgeType as string,
sourceElementId: operation.args?.source as string,
targetElementId: element.id
}));
}
}
}

1 change: 1 addition & 0 deletions packages/server/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ export * from './features/contextactions/context-actions-provider';
export * from './features/contextactions/context-actions-provider-registry';
export * from './features/contextactions/context-menu-item-provider';
export * from './features/contextactions/request-context-actions-handler';
export * from './features/contextactions/selection-palette-item-provider';
export * from './features/contextactions/tool-palette-item-provider';
export * from './features/directediting/context-edit-validator';
export * from './features/directediting/context-edit-validator-registry';