Skip to content

Commit

Permalink
GLSP-1388 Enhance extensibility of ContainerManager (#385)
Browse files Browse the repository at this point in the history
* GLSP-1388 Enhance extensibility of `ContainerManager`

- Added IContainerManager interface for easier customization
- Consume IContainerManager for dependency injection
- Extract isCreationAllowed submethod for element insertion checks

Resolves eclipse-glsp/glsp#1388

* Address review comments

- Ensure backward compatibility for ContainerManager binding by using bindAsService.
- Make isCreationAllowed public and reuse in KeyboardPointerPosition (accessibility tools)
  • Loading branch information
ndoschek authored Aug 21, 2024
1 parent da237f0 commit 4d96e60
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (c) 2023 Business Informatics Group (TU Wien) and others.
* Copyright (c) 2023-2024 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
Expand All @@ -14,11 +14,11 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { findChildrenAtPosition, findParentByFeature, GModelElement, Point } from '@eclipse-glsp/sprotty';
import { Containable, isContainable } from '../../hints/model';
import { CursorCSS } from '../../../base/feedback/css-feedback';
import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util';
import { ContainerElement, isContainable } from '../../hints/model';
import { KeyboardPointerMetadata } from './constants';
import { KeyboardPointer } from './keyboard-pointer';
import { CursorCSS } from '../../../base/feedback/css-feedback';

export class KeyboardPointerPosition {
public renderPosition: Point = { x: 20, y: 20 };
Expand Down Expand Up @@ -46,7 +46,7 @@ export class KeyboardPointerPosition {
}

containableParentAtDiagramPosition(elementTypeId: string): {
container: (GModelElement & Containable) | undefined;
container: ContainerElement | undefined;
status: CursorCSS;
} {
const children = this.childrenAtDiagramPosition();
Expand All @@ -64,15 +64,13 @@ export class KeyboardPointerPosition {
private containableParentOf(
target: GModelElement,
elementTypeId: string
): { container: (GModelElement & Containable) | undefined; status: CursorCSS } {
): { container: ContainerElement | undefined; status: CursorCSS } {
const container = findParentByFeature(target, isContainable);
return {
container,
status: this.isCreationAllowed(container, elementTypeId) ? CursorCSS.NODE_CREATION : CursorCSS.OPERATION_NOT_ALLOWED
status: this.keyboardPointer.containerManager.isCreationAllowed(container, elementTypeId)
? CursorCSS.NODE_CREATION
: CursorCSS.OPERATION_NOT_ALLOWED
};
}

private isCreationAllowed(container: (GModelElement & Containable) | undefined, elementTypeId: string): boolean | undefined {
return container && container.isContainableElement(elementTypeId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { inject, injectable } from 'inversify';
import { EditorContextService } from '../../../base/editor-context-service';
import { CursorCSS } from '../../../base/feedback/css-feedback';
import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension';
import { ContainerManager } from '../../tools/node-creation/container-manager';
import { KeyboardGridCellSelectedAction } from '../keyboard-grid/action';
import { SetKeyboardPointerRenderPositionAction } from './actions';
import { KeyboardPointerMetadata } from './constants';
Expand All @@ -31,10 +32,13 @@ export class KeyboardPointer extends GLSPAbstractUIExtension implements IActionH
kind: 'triggerNodeCreation'
};

protected position: KeyboardPointerPosition = new KeyboardPointerPosition(this);
protected keyListener: KeyboardPointerKeyboardListener;
@inject(EditorContextService)
public editorContextService: EditorContextService;
@inject(TYPES.IContainerManager)
public containerManager: ContainerManager;

protected position: KeyboardPointerPosition = new KeyboardPointerPosition(this);
protected keyListener: KeyboardPointerKeyboardListener;

constructor(@inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher) {
super();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,37 @@ export interface TrackedInsert {
options: InsertOptions;
}

export interface IContainerManager {
insert(proxy: GModelElement, location: Point, elementTypeId: string, opts?: Partial<InsertOptions>): TrackedInsert;
isCreationAllowed(container: ContainerElement | undefined, elementTypeId: string, opts?: Partial<InsertOptions>): boolean;
findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined;
addInsertFeedback(feedback: FeedbackEmitter, trackedInsert: TrackedInsert, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter;
}

/**
* The default {@link IContainerManager} implementation.
* This class class manages the insertion of elements into containers by validating their positions and types,
* providing feedback on the insertion process, and determining the appropriate container based on the location and context.
*/
@injectable()
export class ContainerManager {
export class ContainerManager implements IContainerManager {
@inject(ChangeBoundsManager) protected readonly changeBoundsManager: ChangeBoundsManager;

insert(proxy: GModelElement, location: Point, elementTypeId: string, opts?: Partial<InsertOptions>): TrackedInsert {
const options = { ...DEFAULT_INSERT_OPTIONS, ...opts };
const container = this.findContainer(location, proxy, opts?.evt);
let valid = !container || container.isContainableElement(elementTypeId);
let valid = this.isCreationAllowed(container, elementTypeId, opts);
if (valid && (!container || options.validateLocationInContainer)) {
// we need to check whether the location is valid either because we do not have a container or the option is set
valid = opts?.validLocationOverwrite ?? this.changeBoundsManager.hasValidPosition(proxy, location);
}
return { elementTypeId, container, location, valid, options };
}

isCreationAllowed(container: ContainerElement | undefined, elementTypeId: string, opts?: Partial<InsertOptions>): boolean {
return !container || container.isContainableElement(elementTypeId);
}

findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined {
// reverse order of children to find the innermost, top-rendered containers first
return findChildrenAtPosition(ctx.root, location)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { InsertIndicatorView } from './node-creation-views';
export const nodeCreationToolModule = new FeatureModule(
(bind, unbind, isBound, rebind) => {
const context = { bind, unbind, isBound, rebind };
bind(ContainerManager).toSelf().inSingletonScope();
bindAsService(context, TYPES.IContainerManager, ContainerManager);
bindAsService(context, TYPES.ITool, NodeCreationTool);
configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool);
configureModelElement(context, InsertIndicator.TYPE, InsertIndicator, InsertIndicatorView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { RemoveTemplateElementsAction } from '../../element-template/remove-temp
import { BaseCreationTool } from '../base-tools';
import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager';
import { TrackedMove } from '../change-bounds/change-bounds-tracker';
import { ContainerManager, TrackedInsert } from './container-manager';
import { IContainerManager, TrackedInsert } from './container-manager';
import { InsertIndicator } from './insert-indicator';

@injectable()
Expand All @@ -55,7 +55,7 @@ export class NodeCreationTool extends BaseCreationTool<TriggerNodeCreationAction
protected isTriggerAction = TriggerNodeCreationAction.is;

@inject(ChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager;
@inject(ContainerManager) readonly containerManager: ContainerManager;
@inject(TYPES.IContainerManager) readonly containerManager: IContainerManager;
@inject(TYPES.IModelFactory) modelFactory: IModelFactory;

get id(): string {
Expand Down Expand Up @@ -95,7 +95,7 @@ export class NodeCreationTool extends BaseCreationTool<TriggerNodeCreationAction
}

export interface ContainerPositioningTool extends PositioningTool {
readonly containerManager: ContainerManager;
readonly containerManager: IContainerManager;
}

export class NodeInsertTrackingListener extends MouseTrackingElementPositionListener {
Expand Down
1 change: 1 addition & 0 deletions packages/glsp-sprotty/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ export const TYPES = {
IDiagramOptions: Symbol('IDiagramOptions'),
IDiagramStartup: Symbol('IDiagramStartup'),
IToolManager: Symbol('IToolManager'),
IContainerManager: Symbol('IContainerManager'),
Grid: Symbol('Grid')
};

0 comments on commit 4d96e60

Please sign in to comment.