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

Ensure hidden bounds calculation works in all browser #324

Merged
merged 1 commit into from Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/client/css/ghost-element.css
Expand Up @@ -21,5 +21,7 @@
}

.ghost-element.hidden {
display: none;
width: 0;
height: 0;
visibility: hidden;
}
5 changes: 0 additions & 5 deletions packages/client/css/glsp-sprotty.css
Expand Up @@ -73,11 +73,6 @@
fill: #1d80d1;
}

tortmayr marked this conversation as resolved.
Show resolved Hide resolved
.sprotty-hidden .sprotty-resize-handle {
/** resize handles should not be considered as part of the elements bounds */
display: none;
}

.sprotty-edge {
fill: none;
stroke-width: 1.5px;
Expand Down
30 changes: 26 additions & 4 deletions packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts
Expand Up @@ -16,20 +16,21 @@

import {
Action,
BoundsData,
ComputedBoundsAction,
Deferred,
EdgeRouterRegistry,
ElementAndRoutingPoints,
GModelElement,
GRoutableElement,
HiddenBoundsUpdater,
IActionDispatcher,
ModelIndexImpl,
RequestAction,
ResponseAction
} from '@eclipse-glsp/sprotty';
import { inject, injectable, optional } from 'inversify';
import { VNode } from 'snabbdom';
import { calcElementAndRoute, isRoutable } from '../../utils/gmodel-util';
import { BoundsAwareModelElement, calcElementAndRoute, getDescendantIds, isRoutable } from '../../utils/gmodel-util';
import { LocalComputedBoundsAction, LocalRequestBoundsAction } from './local-bounds';

/**
Expand All @@ -42,8 +43,10 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater {
@inject(EdgeRouterRegistry) @optional() protected readonly edgeRouterRegistry?: EdgeRouterRegistry;

protected element2route: ElementAndRoutingPoints[] = [];
protected edges: GRoutableElement[] = [];
protected nodes: VNode[] = [];

protected getElement2BoundsData(): Map<BoundsAwareModelElement, BoundsData> {
return this['element2boundsData'];
}

override decorate(vnode: VNode, element: GModelElement): VNode {
super.decorate(vnode, element);
Expand All @@ -54,13 +57,32 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater {
}

override postUpdate(cause?: Action): void {
if (LocalRequestBoundsAction.is(cause) && cause.elementIDs) {
this.focusOnElements(cause.elementIDs);
}
const actions = this.captureActions(() => super.postUpdate(cause));
actions
.filter(action => ComputedBoundsAction.is(action))
.forEach(action => this.actionDispatcher.dispatch(this.enhanceAction(action as ComputedBoundsAction, cause)));
this.element2route = [];
}

protected focusOnElements(elementIDs: string[]): void {
const data = this.getElement2BoundsData();
if (data.size > 0) {
// expand given IDs to their descendent element IDs as we need their bounding boxes as well
const index = [...data.keys()][0].index;
const relevantIds = new Set(elementIDs.flatMap(elementId => this.expandElementId(elementId, index, elementIDs)));

// ensure we only keep the bounds of the elements we are interested in
data.forEach((_bounds, element) => !relevantIds.has(element.id) && data.delete(element));
}
}

protected expandElementId(id: string, index: ModelIndexImpl, elementIDs: string[]): string[] {
return getDescendantIds(index.getById(id));
}

protected captureActions(call: () => void): Action[] {
const capturingActionDispatcher = new CapturingActionDispatcher();
const actualActionDispatcher = this.actionDispatcher;
Expand Down
29 changes: 23 additions & 6 deletions packages/client/src/features/bounds/local-bounds.ts
Expand Up @@ -26,21 +26,38 @@ import {
GModelRootSchema,
RequestBoundsAction,
TYPES,
ViewerOptions
ViewerOptions,
hasArrayProp
} from '@eclipse-glsp/sprotty';
import { inject, injectable } from 'inversify';
import { ServerAction } from '../../base/model/glsp-model-source';

export interface LocalRequestBoundsAction extends RequestBoundsAction {
elementIDs?: string[];
}

export namespace LocalRequestBoundsAction {
export function is(object: unknown): object is RequestBoundsAction {
return RequestBoundsAction.is(object) && !ServerAction.is(object);
export function is(object: unknown): object is LocalRequestBoundsAction {
return RequestBoundsAction.is(object) && !ServerAction.is(object) && hasArrayProp(object, 'elementIDs', true);
}

export function create(newRoot: GModelRootSchema, elementIDs?: string[]): LocalRequestBoundsAction {
return {
...RequestBoundsAction.create(newRoot),
elementIDs
};
}

export function fromCommand(context: CommandExecutionContext, actionDispatcher: ActionDispatcher, cause?: Action): CommandResult {
export function fromCommand(
{ root }: CommandExecutionContext,
actionDispatcher: ActionDispatcher,
cause?: Action,
elementIDs?: string[]
): CommandResult {
// do not modify the main model (modelChanged = false) but request local bounds calculation on hidden model
actionDispatcher.dispatch(RequestBoundsAction.create(context.root as unknown as GModelRootSchema));
actionDispatcher.dispatch(LocalRequestBoundsAction.create(root as unknown as GModelRootSchema, elementIDs));
return {
model: context.root,
model: root,
modelChanged: false,
cause
};
Expand Down
Expand Up @@ -65,6 +65,7 @@ export class SetBoundsFeedbackCommand extends SetBoundsCommand implements Feedba
element.layoutOptions = options;
}
});
return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action);
const elementIDs = this.action.bounds.map(bounds => bounds.elementId);
return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action, elementIDs);
}
}
Expand Up @@ -64,13 +64,14 @@ export class AddTemplateElementsFeedbackCommand extends FeedbackCommand {
}

override execute(context: CommandExecutionContext): CommandResult {
this.action.templates
const templateElements = this.action.templates
.map(template => templateToSchema(template, context))
.filter(isNotUndefined)
.map(schema => context.modelFactory.createElement(schema))
.map(element => this.applyRootCssClasses(element, this.action.addClasses, this.action.removeClasses))
.forEach(templateElement => context.root.add(templateElement));
return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action);
.map(element => this.applyRootCssClasses(element, this.action.addClasses, this.action.removeClasses));
templateElements.forEach(templateElement => context.root.add(templateElement));
const templateElementIDs = templateElements.map(element => element.id);
return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action, templateElementIDs);
}

protected applyRootCssClasses(element: GChildElement, addClasses?: string[], removeClasses?: string[]): GChildElement {
Expand Down
19 changes: 15 additions & 4 deletions packages/client/src/utils/gmodel-util.ts
Expand Up @@ -22,6 +22,7 @@ import {
GChildElement,
GModelElement,
GModelElementSchema,
GParentElement,
GRoutableElement,
GRoutingHandle,
ModelIndexImpl,
Expand All @@ -30,13 +31,12 @@ import {
Selectable,
TypeGuard,
distinctAdd,
findParentByFeature,
getAbsoluteBounds,
getZoom,
isBoundsAware,
isMoveable,
isSelectable,
isSelected,
isViewport,
remove
} from '@eclipse-glsp/sprotty';

Expand Down Expand Up @@ -346,8 +346,7 @@ export function findTopLevelElementByFeature<T>(

export function calculateDeltaBetweenPoints(target: Point, source: Point, element: GModelElement): Point {
const delta = Point.subtract(target, source);
const viewport = findParentByFeature(element, isViewport);
const zoom = viewport?.zoom ?? 1;
const zoom = getZoom(element);
const adaptedDelta = { x: delta.x / zoom, y: delta.y / zoom };
return adaptedDelta;
}
Expand All @@ -362,3 +361,15 @@ export function isVisibleOnCanvas(model: BoundsAwareModelElement): boolean {
modelBounds.y + modelBounds.height >= 0
);
}

export function getDescendantIds(element?: GModelElement, skip?: (t: GModelElement) => boolean): string[] {
if (!element || skip?.(element)) {
return [];
}
const parent = element;
const ids = [parent.id];
if (parent instanceof GParentElement) {
ids.push(...parent.children.flatMap(child => getDescendantIds(child, skip)));
}
return ids;
}
1 change: 1 addition & 0 deletions packages/glsp-sprotty/src/re-exports.ts
Expand Up @@ -173,6 +173,7 @@ export {
SIssueSeverity as GIssueSeverity,
// Export as is, we extend it glsp-client to `GIssueMarker`
SIssueMarker,
decorationFeature,
isDecoration
} from 'sprotty/lib/features/decoration/model';
export * from 'sprotty/lib/features/decoration/views';
Expand Down