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

[FEAT] Allow to zoom the BPMN diagram with mouse wheel #734

Merged
merged 11 commits into from
Oct 15, 2020
47 changes: 42 additions & 5 deletions src/component/mxgraph/MxGraphConfigurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import ShapeConfigurator from './config/ShapeConfigurator';
import MarkerConfigurator from './config/MarkerConfigurator';
import MxClientConfigurator from './config/MxClientConfigurator';
import { BpmnVisualizationOptions } from '../BpmnVisualization';
import { mxgraph } from 'ts-mxgraph';
// TODO unable to load mxClient from [email protected]
declare const mxClient: typeof mxgraph.mxClient;

/**
* Configure the mxGraph graph that can be used by the lib
Expand All @@ -29,20 +32,21 @@ import { BpmnVisualizationOptions } from '../BpmnVisualization';
export default class MxGraphConfigurator {
private readonly graph: mxGraph;

constructor(container: HTMLElement) {
constructor(readonly container: HTMLElement) {
this.graph = new mxGraph(container);
}

public configure(options?: BpmnVisualizationOptions): mxGraph {
this.configureGraph(options);
this.configureGraph();
this.configureMouseNavigationSupport(options);
new StyleConfigurator(this.graph).configureStyles();
new ShapeConfigurator().configureShapes();
new MarkerConfigurator().configureMarkers();
new MxClientConfigurator().configureMxCodec();
return this.graph;
}

private configureGraph(options?: BpmnVisualizationOptions): void {
private configureGraph(): void {
this.graph.setCellsLocked(true);
this.graph.setCellsSelectable(false);
this.graph.setEdgeLabelsMovable(false);
Expand All @@ -56,15 +60,48 @@ export default class MxGraphConfigurator {
// Disable folding for container mxCell (pool, lane, sub process, call activity) because we don't need it.
// This also prevents requesting unavailable images (see #185) as we don't override mxGraph folding default images.
this.graph.foldingEnabled = false;
}

private configureMouseNavigationSupport(options?: BpmnVisualizationOptions): void {
const mouseNavigationSupport = options?.mouseNavigationSupport;
// Pan configuration
if (options?.mouseNavigationSupport) {
if (mouseNavigationSupport) {
this.graph.panningHandler.useLeftButtonForPanning = true;
this.graph.panningHandler.ignoreCell = true; // ok here as we cannot select cells
this.graph.setPanning(true);
} else {
this.graph.setPanning(false);
this.graph.panningHandler.setPinchEnabled(false); // ensure gesture support is disabled (pan and zoom)
this.graph.panningHandler.setPinchEnabled(false); // ensure gesture support is disabled (zoom only for now!)
}

this.configureMouseEvent(mouseNavigationSupport);
}

private configureMouseEvent(activated = false): void {
if (!activated) {
return;
}

mxEvent.addMouseWheelListener((event: Event, up: boolean) => {
// TODO review type: this hack is due to the introduction of mxgraph-type-definitions
const evt = (event as unknown) as MouseEvent;
if (mxEvent.isConsumed((evt as unknown) as mxMouseEvent)) {
return;
}
// only the ctrl key or the meta key on mac
const isZoomWheelEvent = (evt.ctrlKey || (mxClient.IS_MAC && evt.metaKey)) && !evt.altKey && !evt.shiftKey;
if (isZoomWheelEvent) {
this.zoom(up);
mxEvent.consume(evt);
}
}, this.container);
}

private zoom(zoomIn: boolean): void {
if (zoomIn) {
this.graph.zoomIn();
} else {
this.graph.zoomOut();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 43 additions & 5 deletions test/e2e/bpmn.navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@
* limitations under the License.
*/
import { BpmnDiagramPreparation, ImageSnapshotConfigurator, ImageSnapshotThresholdConfig, PageTester } from './helpers/visu-utils';
import { Mouse } from 'puppeteer';
// FIXME - to be fixed when new release of puppeteer comes out
// wheel is added in version @types/puppeteer 2.1.5 but for some reason not in 3.0.2
// perhaps will be soon available in 3.0.3
// @see https://github.com/puppeteer/puppeteer/pull/6141/files
interface MouseWheelOptions {
/**
* X delta in CSS pixels for mouse wheel event (default: 0). Positive values emulate a scroll up and negative values a scroll down event.
* @default 0
*/
deltaX?: number;
/**
* Y delta in CSS pixels for mouse wheel event (default: 0). Positive values emulate a scroll right and negative values a scroll left event.
* @default 0
*/
deltaY?: number;
}
interface MouseWithWheel extends Mouse {
/**
* Dispatches a `mousewheel` event.
* @param options The mouse wheel options.
*/
wheel(options?: MouseWheelOptions): Promise<void>;
}

describe('diagram navigation', () => {
const imageSnapshotConfigurator = new ImageSnapshotConfigurator(
Expand All @@ -38,15 +62,18 @@ describe('diagram navigation', () => {

const pageTester = new PageTester(bpmnDiagramPreparation, 'bpmn-viewport', 'BPMN Visualization - Diagram Navigation');

it('mouse panning', async () => {
const fileName = 'simple-2_start_events-1_task';
const fileName = 'simple-2_start_events-1_task';
let viewportCenterX: number;
let viewportCenterY: number;
beforeEach(async () => {
const bpmnViewportElementHandle = await pageTester.expectBpmnDiagramToBeDisplayed(fileName);

const bounding_box = await bpmnViewportElementHandle.boundingBox();
viewportCenterX = bounding_box.x + bounding_box.width / 2;
viewportCenterY = bounding_box.y + bounding_box.height / 2;
});

it('mouse panning', async () => {
// simulate mouse panning
const viewportCenterX = bounding_box.x + bounding_box.width / 2;
const viewportCenterY = bounding_box.y + bounding_box.height / 2;
await page.mouse.move(viewportCenterX, viewportCenterY);
await page.mouse.down();
await page.mouse.move(viewportCenterX + 150, viewportCenterY + 40);
Expand All @@ -55,4 +82,15 @@ describe('diagram navigation', () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot(imageSnapshotConfigurator.getConfig(fileName));
});

it.each(['zoom in', 'zoom out'])(`ctrl + mouse: %s`, async (zoom: string) => {
const deltaX = zoom === 'zoom in' ? -100 : 100;
// simulate mouse+ctrl zoom
await page.mouse.move(viewportCenterX, viewportCenterY);
await page.keyboard.down('Control');
Copy link
Contributor

@aibcmars aibcmars Oct 15, 2020

Choose a reason for hiding this comment

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

I forgot to check it but it seems it is working well on Mac in our GitHub 'checks' :)
perhaps puppeteer handles that

Copy link
Member Author

Choose a reason for hiding this comment

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

puppeteer is running Chromium. Let's review this in #773

await (<MouseWithWheel>page.mouse).wheel({ deltaX: deltaX });

const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot(imageSnapshotConfigurator.getConfig(fileName));
});
});