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
Show file tree
Hide file tree
Changes from 8 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 {
DefaultSmartConnectorItemProvider, SmartConnectorSettings
} from '@eclipse-glsp/server';
import {
SmartConnectorPosition,
SmartConnectorGroupUIType,
DefaultTypes
} from '@eclipse-glsp/protocol';
import { injectable } from 'inversify';
import { ModelTypes } from '../util/model-types';

@injectable()
export class WorkflowSmartConnectorItemProvider extends DefaultSmartConnectorItemProvider {

protected override smartConnectorNodeSettings: SmartConnectorSettings = {
position: SmartConnectorPosition.Top,
showTitle: true,
submenu: false,
showOnlyForChildren: SmartConnectorGroupUIType.Labels
};

protected override smartConnectorEdgeSettings: SmartConnectorSettings = {
position: SmartConnectorPosition.Right,
showTitle: true,
submenu: true
};

override nodeOperationFilter = {
yentelmanero marked this conversation as resolved.
Show resolved Hide resolved
[ModelTypes.AUTOMATED_TASK]: [ModelTypes.WEIGHTED_EDGE, ModelTypes.AUTOMATED_TASK, ModelTypes.MANUAL_TASK,
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be handled generically. All the required information is present in the typehints of the DiagramConfiguration.
Could be moved up to the DefaultSmartConnectorProvider.

I don't expect that this is handled with this PR but we should create a follow-up for that.

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]
};

override defaultEdge = DefaultTypes.EDGE;
yentelmanero marked this conversation as resolved.
Show resolved Hide resolved

override edgeTypes = {
yentelmanero marked this conversation as resolved.
Show resolved Hide resolved
[ModelTypes.AUTOMATED_TASK]: DefaultTypes.EDGE,
[ModelTypes.MERGE_NODE]: DefaultTypes.EDGE
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
OperationHandlerConstructor,
PopupModelFactory,
ServerModule,
SourceModelStorage
SourceModelStorage,
SmartConnectorItemProvider
} from '@eclipse-glsp/server';
import { injectable } from 'inversify';
import { CreateAutomatedTaskHandler } from './handler/create-automated-task-handler';
Expand All @@ -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 { WorkflowSmartConnectorItemProvider } from './provider/workflow-smart-connector-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';
Expand Down Expand Up @@ -107,6 +109,10 @@ export class WorkflowDiagramModule extends GModelDiagramModule {
return WorkflowCommandPaletteActionProvider;
}

protected override bindSmartConnectorItemProvider(): BindingTarget<SmartConnectorItemProvider> | undefined {
return WorkflowSmartConnectorItemProvider;
}

protected override bindLabelEditValidator(): BindingTarget<LabelEditValidator> | undefined {
return WorkflowLabelEditValidator;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/server/src/common/di/diagram-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ import {
NavigationTargetProviders,
Operations
} from './service-identifiers';
import { DefaultSmartConnectorItemProvider, SmartConnectorItemProvider } from '../features/contextactions/smart-connector-item-provider';
import { OpenSmartConnectorActionHandler } from '../features/contextactions/smart-connector-action-handler';

/**
* The diagram module is the central configuration artifact for configuring a client session specific injector. For each
Expand Down Expand Up @@ -154,6 +156,7 @@ export abstract class DiagramModule extends GLSPModule {
applyOptionalBindingTarget(context, ToolPaletteItemProvider, this.bindToolPaletteItemProvider());
applyOptionalBindingTarget(context, CommandPaletteActionProvider, this.bindCommandPaletteActionProvider());
applyOptionalBindingTarget(context, ContextMenuItemProvider, this.bindContextMenuItemProvider());
applyOptionalBindingTarget(context, SmartConnectorItemProvider, this.bindSmartConnectorItemProvider());
this.configureMultiBinding(new MultiBinding<ContextActionsProvider>(ContextActionsProviders), binding =>
this.configureContextActionProviders(binding)
);
Expand Down Expand Up @@ -216,6 +219,7 @@ export abstract class DiagramModule extends GLSPModule {
binding.add(SaveModelActionHandler);
binding.add(UndoRedoActionHandler);
binding.add(ComputedBoundsActionHandler);
binding.add(OpenSmartConnectorActionHandler);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be handled on client side

}

protected bindDiagramType(): BindingTarget<string> {
Expand Down Expand Up @@ -344,6 +348,10 @@ export abstract class DiagramModule extends GLSPModule {
protected bindToolPaletteItemProvider(): BindingTarget<ToolPaletteItemProvider> | undefined {
return DefaultToolPaletteItemProvider;
}

protected bindSmartConnectorItemProvider(): BindingTarget<SmartConnectorItemProvider> | undefined {
return DefaultSmartConnectorItemProvider;
}

protected bindCommandPaletteActionProvider(): BindingTarget<CommandPaletteActionProvider> | undefined {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { SmartConnectorItemProvider } from './smart-connector-item-provider';

/**
* A registry that keeps track of all registered {@link ContextActionsProvider}s.
Expand All @@ -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(SmartConnectorItemProvider) @optional() smartConnectorItemProvider?: SmartConnectorItemProvider
) {
super();
contextActionsProvider.forEach(provider => this.register(provider.contextId, provider));
Expand All @@ -43,5 +45,8 @@ export class ContextActionsProviderRegistry extends Registry<string, ContextActi
if (toolPaletteItemProvider) {
this.register(toolPaletteItemProvider.contextId, toolPaletteItemProvider);
}
if (smartConnectorItemProvider) {
this.register(smartConnectorItemProvider.contextId, smartConnectorItemProvider);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/********************************************************************************
* 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 {
Action,
CloseSmartConnectorAction,
OpenSmartConnectorAction,
SelectAction,
MaybePromise} from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ActionHandler } from '../../actions/action-handler';
import { ModelState } from '../model/model-state';
import { GNode } from '@eclipse-glsp/graph';

@injectable()
export class OpenSmartConnectorActionHandler implements ActionHandler {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be handled entirely on client side.
The server should only provide the selection palette actions via the RequestContextActions handler but should not need to know any other implementation details about how they will be handled/rendered on the client side.

actionKinds = [SelectAction.KIND];

@inject(ModelState)
protected modelState: ModelState;

execute(action: Action): MaybePromise<Action[]> {
if (SelectAction.is(action)) {
const selectedElement = this.modelState.index.find(action.selectedElementsIDs[0]);
if (selectedElement && selectedElement instanceof GNode) {
return [OpenSmartConnectorAction.create(action.selectedElementsIDs[0])];
}
else {return [];}
}
return [CloseSmartConnectorAction.create()];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/********************************************************************************
* 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,
SmartConnectorGroupItem,
EditorContext,
LabeledAction,
MaybePromise,
SmartConnectorPosition,
SmartConnectorGroupUIType,
SmartConnectorNodeItem,
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 Smart Connector which appears when a node is selected.
*/
@injectable()
export abstract class SmartConnectorItemProvider implements ContextActionsProvider {
Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed offline we would like to use a more generic SelectionPaletteProvider API (that is inline with CommandPaletteProvider and ToolpaletteProvider) and the current SmartConnector is then just the default implementation of that provider.
So please rename this to SelectionPaletteProvider.

/**
* Returns the context id of the provider.
*/
get contextId(): string {
return 'smart-connector';
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return 'smart-connector';
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<SmartConnectorGroupItem[]>;
}

export type SmartConnectorSettings = {
position: SmartConnectorPosition
showTitle: true;
submenu: boolean;
showOnlyForChildren?: SmartConnectorGroupUIType
} | {
position: SmartConnectorPosition
showTitle: false;
showOnlyForChildren?: SmartConnectorGroupUIType
};

@injectable()
export class DefaultSmartConnectorItemProvider extends SmartConnectorItemProvider {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
export class DefaultSmartConnectorItemProvider extends SmartConnectorItemProvider {
export class DefaultSelectionPaletteProvider` extends SelectionPaletteProvider {


@inject(OperationHandlerRegistry) operationHandlerRegistry: OperationHandlerRegistry;
yentelmanero marked this conversation as resolved.
Show resolved Hide resolved
@inject(Logger)
protected logger: Logger;

protected counter: number;

protected smartConnectorNodeSettings: SmartConnectorSettings = {
position: SmartConnectorPosition.Right,
showTitle: true,
submenu: true,
showOnlyForChildren: SmartConnectorGroupUIType.Icons
};

protected smartConnectorEdgeSettings: SmartConnectorSettings = {
position: SmartConnectorPosition.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): SmartConnectorGroupItem[] {
const handlers = this.operationHandlerRegistry.getAll().filter(CreateOperationHandler.is) as CreateOperationHandler[];
this.counter = 0;
const nodes = this.createSmartConnectorGroupItem(handlers, CreateNodeOperation.KIND, args?.nodeType as string,
this.smartConnectorNodeSettings.showOnlyForChildren);
const edges = this.createSmartConnectorGroupItem(handlers, CreateEdgeOperation.KIND, args?.nodeType as string,
this.smartConnectorEdgeSettings.showOnlyForChildren);
return [
{
id: 'smart-connector-node-group',
label: 'Nodes',
actions: [],
children: nodes,
icon: 'symbol-property',
sortString: 'A',
...this.smartConnectorNodeSettings
},
{
id: 'smart-connector-edge-group',
label: 'Edges',
actions: [],
children: edges,
icon: 'symbol-property',
sortString: 'B',
...this.smartConnectorEdgeSettings
}
];
}

createSmartConnectorGroupItem(handlers: CreateOperationHandler[], kind: string, selectedNodeType: string,
yentelmanero marked this conversation as resolved.
Show resolved Hide resolved
showOnly?: SmartConnectorGroupUIType): 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.create(action, handler.label, selectedNodeType)))
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.sortString.localeCompare(b.sortString));
if (showOnly === SmartConnectorGroupUIType.Icons) {
if (paletteItems.every(paletteItem => paletteItem.icon !== '')) {
this.logger.warn('Not all elements have icons. Labels will be shown, check settings for smart connector.')
return paletteItems;
}
paletteItems.forEach(paletteItem => paletteItem.label = '');
}
else if (showOnly === SmartConnectorGroupUIType.Labels) {
paletteItems.forEach(paletteItem => paletteItem.icon = '');
}
return paletteItems;
}

create(action: PaletteItem.TriggerElementCreationAction, label: string, nodeType: string): PaletteItem | SmartConnectorNodeItem {
yentelmanero marked this conversation as resolved.
Show resolved Hide resolved
if (TriggerNodeCreationAction.is(action)) {
let edgeType = this.edgeTypes[nodeType];
if (!edgeType) {edgeType = this.defaultEdge;}
return {
id: `smart-connector-palette-item${this.counter++}`,
sortString: label.charAt(0),
label,
actions: [action],
edgeType: edgeType
};
}
return {
id: `smart-connector-palette-item${this.counter++}`,
sortString: label.charAt(0),
label,
actions: [action]
};
}

}
Loading