-
Notifications
You must be signed in to change notification settings - Fork 13
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
base: main
Are you sure you want to change the base?
Changes from 12 commits
32ceb71
ca2e9b1
510c9f7
85cb92c
b79650e
8b77069
0edc62d
cb71c3b
1f8cb5a
6a88438
19d6020
c6ba7bd
6b01139
c97bc4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
}; | ||
|
||
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 |
---|---|---|
|
@@ -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 | ||
|
@@ -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) | ||
); | ||
|
@@ -216,6 +219,7 @@ export abstract class DiagramModule extends GLSPModule { | |
binding.add(SaveModelActionHandler); | ||
binding.add(UndoRedoActionHandler); | ||
binding.add(ComputedBoundsActionHandler); | ||
binding.add(OpenSmartConnectorActionHandler); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be handled on client side |
||
} | ||
|
||
protected bindDiagramType(): BindingTarget<string> { | ||
|
@@ -345,6 +349,10 @@ export abstract class DiagramModule extends GLSPModule { | |
return DefaultToolPaletteItemProvider; | ||
} | ||
|
||
protected bindSmartConnectorItemProvider(): BindingTarget<SmartConnectorItemProvider> | undefined { | ||
return DefaultSmartConnectorItemProvider; | ||
} | ||
|
||
protected bindCommandPaletteActionProvider(): BindingTarget<CommandPaletteActionProvider> | undefined { | ||
return undefined; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/******************************************************************************** | ||
* 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be handled entirely on 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,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, | ||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed offline we would like to use a more generic |
||||||
/** | ||||||
* Returns the context id of the provider. | ||||||
*/ | ||||||
get contextId(): string { | ||||||
return 'smart-connector'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
/** | ||||||
* 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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
@inject(OperationHandlerRegistry) protected operationHandlerRegistry: OperationHandlerRegistry; | ||||||
@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 | ||||||
} | ||||||
]; | ||||||
} | ||||||
|
||||||
protected createSmartConnectorGroupItem( | ||||||
handlers: CreateOperationHandler[], | ||||||
kind: string, | ||||||
selectedNodeType: string, | ||||||
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.createSmartConnectorItems(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; | ||||||
} | ||||||
|
||||||
protected createSmartConnectorItems( | ||||||
action: PaletteItem.TriggerElementCreationAction, | ||||||
label: string, | ||||||
nodeType: string | ||||||
): PaletteItem | SmartConnectorNodeItem { | ||||||
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] | ||||||
}; | ||||||
} | ||||||
} |
There was a problem hiding this comment.
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.