From 2f39e24bcb60f1495b6e837e945d7a4a56d89be1 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:30:35 +0200 Subject: [PATCH 1/8] 1st integration of drawio 20.2.6 considered as required for useCssTransforms in bpmn-visualization --- src/component/mxgraph/BpmnGraph.ts | 214 +++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index 14909c8879..989270f746 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -21,6 +21,8 @@ import debounce from 'lodash.debounce'; import throttle from 'lodash.throttle'; import { mxgraph } from './initializer'; import type { mxCellState, mxGraphView, mxPoint } from 'mxgraph'; +import type { mxCell } from 'mxgraph'; +import type { mxRectangle } from 'mxgraph'; const zoomFactorIn = 1.25; const zoomFactorOut = 1 / zoomFactorIn; @@ -28,6 +30,26 @@ const zoomFactorOut = 1 / zoomFactorIn; export class BpmnGraph extends mxgraph.mxGraph { private currentZoomLevel = 1; + // =========================================================================== + // POC transform with CSS + // =========================================================================== + /** + * Uses CSS transforms for scale and translate. + */ + // TODO test with true + useCssTransforms = false; + + /** + * Contains the scale. + */ + currentScale = 1; + + /** + * Contains the offset. + */ + currentTranslate = new mxgraph.mxPoint(0, 0); + // =========================================================================== + /** * @internal */ @@ -204,6 +226,113 @@ export class BpmnGraph extends mxgraph.mxGraph { const factor = scale / this.view.scale; return [factor, scale]; } + + // =========================================================================== + // POC transform with CSS + // =========================================================================== + + /** + * Function: getCellAt + * + * Needs to modify original method for recursive call. + */ + // eslint-disable-next-line @typescript-eslint/ban-types -- Function is type is required by typed-mxgraph + override getCellAt(x: number, y: number, parent?: mxCell, vertices?: boolean, edges?: boolean, ignoreFn?: Function): mxCell { + // getCellAt = function(x, y, parent, vertices, edges, ignoreFn) + if (this.useCssTransforms) { + x = x / this.currentScale - this.currentTranslate.x; + y = y / this.currentScale - this.currentTranslate.y; + } + + return this.getScaledCellAt(x, y, parent, vertices, edges, ignoreFn); + // return null; + } + + /** + * Function: getScaledCellAt + * + * Overridden for recursion. + */ + // eslint-disable-next-line @typescript-eslint/ban-types -- Function is type is required by typed-mxgraph + private getScaledCellAt(x: number, y: number, parent?: mxCell, vertices?: boolean, edges?: boolean, ignoreFn?: Function): mxCell { + vertices = vertices != null ? vertices : true; + edges = edges != null ? edges : true; + + if (parent == null) { + parent = this.getCurrentRoot(); + + if (parent == null) { + parent = this.getModel().getRoot(); + } + } + + if (parent != null) { + const childCount = this.model.getChildCount(parent); + + for (let i = childCount - 1; i >= 0; i--) { + const cell = this.model.getChildAt(parent, i); + const result = this.getScaledCellAt(x, y, cell, vertices, edges, ignoreFn); + + if (result != null) { + return result; + } else if (this.isCellVisible(cell) && ((edges && this.model.isEdge(cell)) || (vertices && this.model.isVertex(cell)))) { + const state = this.view.getState(cell); + + if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) && this.intersects(state, x, y)) { + return cell; + } + } + } + } + + return null; + } + + // TODO check scrollRectToVisible - not used by bpmn-visualization today + // override scrollRectToVisible(r: mxRectangle): boolean + + /** + * Only foreignObject supported for now (no IE11). Safari disabled as it ignores + * overflow visible on foreignObject in negative space (lightbox and viewer). + * Check the following test case on page 1 before enabling this in production: + * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1 + */ + // TODO test if Safari still fails + isCssTransformsSupported(): boolean { + // this.updateCssTransform + return this.dialect == mxgraph.mxConstants.DIALECT_SVG && !mxgraph.mxClient.NO_FO && !mxgraph.mxClient.IS_SF; + // return this.dialect == mxgraph.mxConstants.DIALECT_SVG && !mxgraph.mxClient.NO_FO && (!this.lightbox || !mxgraph.mxClient.IS_SF); + } + + /** + * Zooms out of the graph by . + */ + updateCssTransform(): void { + console.warn('@@@@@updateCssTransform - start'); + const temp = this.view.getDrawPane(); + + if (temp != null) { + const g = temp.parentNode; + + if (!this.useCssTransforms) { + g.removeAttribute('transformOrigin'); + g.removeAttribute('transform'); + } else { + // const prev = g.getAttribute('transform'); + g.setAttribute('transformOrigin', '0 0'); + const s = Math.round(this.currentScale * 100) / 100; + const dx = Math.round(this.currentTranslate.x * 100) / 100; + const dy = Math.round(this.currentTranslate.y * 100) / 100; + g.setAttribute('transform', 'scale(' + s + ',' + s + ')' + 'translate(' + dx + ',' + dy + ')'); + + // Applies workarounds only if translate has changed + // TODO make the implem pass type check, disable 'cssTransformChanged' event firing for now + // if (prev != g.getAttribute('transform')) { + // this.fireEvent(new mxgraph.mxEventObject('cssTransformChanged'), 'transform', g.getAttribute('transform')); + // } + } + } + } } class BpmnGraphView extends mxgraph.mxGraphView { @@ -218,4 +347,89 @@ class BpmnGraphView extends mxgraph.mxGraphView { const pts = edge.absolutePoints; return source ? pts[1] : pts[pts.length - 2]; } + + // =========================================================================== + // POC transform with CSS + // =========================================================================== + // TODO improve types: make this.graph considered as BpmnGraph out of the box + + /** + * Overrides getGraphBounds to use bounding box from SVG. + */ + override getGraphBounds(): mxRectangle { + let b = this.graphBounds; + + if ((this.graph).useCssTransforms) { + const t = (this.graph).currentTranslate; + const s = (this.graph).currentScale; + + b = new mxgraph.mxRectangle((b.x + t.x) * s, (b.y + t.y) * s, b.width * s, b.height * s); + } + return b; + } + + /** + * Overrides to bypass full cell tree validation. + * TODO: Check if this improves performance + */ + override viewStateChanged(): void { + if ((this.graph).useCssTransforms) { + this.validate(); + this.graph.sizeDidChange(); + } else { + this.revalidate(); + this.graph.sizeDidChange(); + } + } + + /** + * Overrides validate to normalize validation view state and pass + * current state to CSS transform. + */ + // var graphViewValidate = mxGraphView.prototype.validate; + override validate(cell?: mxCell): void { + if ((this.graph).useCssTransforms) { + (this.graph).currentScale = this.scale; + (this.graph).currentTranslate.x = this.translate.x; + (this.graph).currentTranslate.y = this.translate.y; + + this.scale = 1; + this.translate.x = 0; + this.translate.y = 0; + } + + // graphViewValidate.apply(this, arguments); + super.validate(cell); + + if ((this.graph).useCssTransforms) { + (this.graph).updateCssTransform(); + + this.scale = (this.graph).currentScale; + this.translate.x = (this.graph).currentTranslate.x; + this.translate.y = (this.graph).currentTranslate.y; + } + } + + // TODO check if validateBackgroundPage is used by bpmn-visualization today, otherwise remove override + // var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage; + override validateBackgroundPage(): void { + const useCssTransforms = (this.graph).useCssTransforms, + scale = this.scale, + translate = this.translate; + + if (useCssTransforms) { + this.scale = (this.graph).currentScale; + this.translate = (this.graph).currentTranslate; + } + + // graphViewValidateBackgroundPage.apply(this, arguments); + super.validateBackgroundPage(); + + if (useCssTransforms) { + this.scale = scale; + this.translate = translate; + } + } + + // TODO check updatePageBreaks - not used by bpmn-visualization today } From cd80d67aee138d29c338f94901edc7607e67de44 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:30:52 +0200 Subject: [PATCH 2/8] useCssTransforms on: 1st version where the panning is correctly working - needed adaptations This was working with scrollbars as in draw.io but not without In BpmnGraph, adapt the code to make it work. The panGraph code update the "transform" attribute directly during the pan operations. Once pan ends, the transform is set to the same value by the regular code --- src/component/mxgraph/BpmnGraph.ts | 240 +++++++++++++++++++++++++++-- 1 file changed, 229 insertions(+), 11 deletions(-) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index 989270f746..5f73d9be18 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -37,7 +37,7 @@ export class BpmnGraph extends mxgraph.mxGraph { * Uses CSS transforms for scale and translate. */ // TODO test with true - useCssTransforms = false; + useCssTransforms = true; /** * Contains the scale. @@ -76,6 +76,8 @@ export class BpmnGraph extends mxgraph.mxGraph { override fit(border: number, keepOrigin?: boolean, margin?: number, enabled?: boolean, ignoreWidth?: boolean, ignoreHeight?: boolean, maxHeight?: number): number { const scale = super.fit(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight, maxHeight); this.setCurrentZoomLevel(scale); + console.warn('#####fit done, computed scale', scale); + console.warn('#####fit done, currentScale', this.currentScale); return scale; } @@ -90,6 +92,7 @@ export class BpmnGraph extends mxgraph.mxGraph { override zoomActual(): void { super.zoomActual(); this.setCurrentZoomLevel(); + console.warn('#####zoomActual done, currentScale', this.currentScale); } /** @@ -161,9 +164,17 @@ export class BpmnGraph extends mxgraph.mxGraph { * @internal */ registerMouseWheelZoomListeners(config: ZoomConfiguration): void { - config = ensureValidZoomConfiguration(config); - mxgraph.mxEvent.addMouseWheelListener(debounce(this.createMouseWheelZoomListener(true), config.debounceDelay), this.container); - mxgraph.mxEvent.addMouseWheelListener(throttle(this.createMouseWheelZoomListener(false), config.throttleDelay), this.container); + console.warn('@@@@@registerMouseWheelZoomListeners start'); + console.warn('@@@@@registerMouseWheelZoomListeners isFirefox?', mxgraph.mxClient.IS_FF); + if (!this.useCssTransforms) { + console.warn('@@@@@registerMouseWheelZoomListeners NO useCssTransforms - use throttle/debounce for zoom'); + config = ensureValidZoomConfiguration(config); + mxgraph.mxEvent.addMouseWheelListener(debounce(this.createMouseWheelZoomListener(true), config.debounceDelay), this.container); + mxgraph.mxEvent.addMouseWheelListener(throttle(this.createMouseWheelZoomListener(false), config.throttleDelay), this.container); + } else { + // TODO implementation + console.warn('@@@@@registerMouseWheelZoomListeners detected useCssTransforms - no mouse Zoom for now!!!!!!!'); + } } // Update the currentZoomLevel when performScaling is false, use the currentZoomLevel to set the scale otherwise @@ -289,7 +300,10 @@ export class BpmnGraph extends mxgraph.mxGraph { } // TODO check scrollRectToVisible - not used by bpmn-visualization today - // override scrollRectToVisible(r: mxRectangle): boolean + override scrollRectToVisible(r: mxRectangle): boolean { + console.warn('#######Called scrollRectToVisible!'); + return super.scrollRectToVisible(r); + } /** * Only foreignObject supported for now (no IE11). Safari disabled as it ignores @@ -312,27 +326,230 @@ export class BpmnGraph extends mxgraph.mxGraph { const temp = this.view.getDrawPane(); if (temp != null) { + // TODO consistency, the parentNode is supposed to be this.view.getCanvas(), why not calling it directly? const g = temp.parentNode; + // TODO this check is probably not needed in our implementation as updateCssTransform is only called when useCssTransforms is true if (!this.useCssTransforms) { + console.warn('@@@@@updateCssTransform - useCssTransforms = false'); g.removeAttribute('transformOrigin'); g.removeAttribute('transform'); } else { - // const prev = g.getAttribute('transform'); + console.warn('@@@@@updateCssTransform - useCssTransforms'); + const prev = g.getAttribute('transform'); + + // the transformOrigin attribute seems specific to the draw.io/mxgraph EditorUI implementation g.setAttribute('transformOrigin', '0 0'); const s = Math.round(this.currentScale * 100) / 100; const dx = Math.round(this.currentTranslate.x * 100) / 100; const dy = Math.round(this.currentTranslate.y * 100) / 100; - g.setAttribute('transform', 'scale(' + s + ',' + s + ')' + 'translate(' + dx + ',' + dy + ')'); + const computedTransformDirective = `scale(${s},${s})translate(${dx},${dy})`; + console.warn('@@@@updateCssTransform - computed transform directive', computedTransformDirective); + g.setAttribute('transform', computedTransformDirective); // Applies workarounds only if translate has changed - // TODO make the implem pass type check, disable 'cssTransformChanged' event firing for now - // if (prev != g.getAttribute('transform')) { - // this.fireEvent(new mxgraph.mxEventObject('cssTransformChanged'), 'transform', g.getAttribute('transform')); - // } + if (prev != g.getAttribute('transform')) { + console.warn('@@@@@updateCssTransform - transform value changed'); + // TODO make the implem pass type check, disable 'cssTransformChanged' event firing for now + // this.fireEvent(new mxgraph.mxEventObject('cssTransformChanged'), 'transform', g.getAttribute('transform')); + } } } } + + // =========================================================================== + // custom for bpmn-visualization, draw.io is using scrollbar, without scrollbar, the original mxGraph implementation doesn't work + // =========================================================================== + + /** + * Function: panGraph + * + * Shifts the graph display by the given amount. This is used to preview + * panning operations, use to set a persistent + * translation of the view. Fires . + * + * Parameters: + * + * dx - Amount to shift the graph along the x-axis. + * dy - Amount to shift the graph along the y-axis. + */ + override panGraph(dx: number, dy: number): void { + if (!this.useCssTransforms) { + console.warn('@@@@panGraph - no useCssTransforms'); + super.panGraph(dx, dy); + } else { + console.warn('@@@@panGraph - useCssTransforms'); + if (this.useScrollbarsForPanning && mxgraph.mxUtils.hasScrollbars(this.container)) { + console.warn('@@@@panGraph - has scrollbars'); + this.container.scrollLeft = -dx; + this.container.scrollTop = -dy; + } else { + // at the end of pan, panning handler does + // var scale = this.graph.getView().scale; + // var t = this.graph.getView().translate; + // this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale); + // panGraph = function(dx, dy) ==> this.graph.getView().setTranslate(dx, dy); + + console.warn(`@@@@panGraph - useCssTransforms - dx=${dx} dy=${dy}`); + // TODO manage rounding duplication with updateCssTransform (introduce private method) + const roundedCurrentScale = Math.round(this.currentScale * 100) / 100; + const roundedPannedTranslateX = Math.round((this.currentTranslate.x + dx / this.currentScale) * 100) / 100; + const roundedPannedTranslateY = Math.round((this.currentTranslate.y + dy / this.currentScale) * 100) / 100; + const computedTransformDirective = `scale(${roundedCurrentScale},${roundedCurrentScale})translate(${roundedPannedTranslateX},${roundedPannedTranslateY})`; + // const roundedCurrentTranslateX = Math.round(this.currentTranslate.x * 100) / 100; + // const roundedCurrentTranslateY = Math.round(this.currentTranslate.y * 100) / 100; + // const computedTransformDirective = `scale(${roundedCurrentScale},${roundedCurrentScale})translate(${roundedCurrentTranslateX + dx},${roundedCurrentTranslateY + dy})`; + console.warn('@@@@panGraph - computed transform directive', computedTransformDirective); + + const canvas = this.view.getCanvas(); + canvas.setAttribute('transform', computedTransformDirective); + } + + this.panDx = dx; + this.panDy = dy; + + this.fireEvent(new mxgraph.mxEventObject(mxgraph.mxEvent.PAN), undefined); + + // DEBUG code + // console.warn(`@@@@panGraph - useCssTransforms - dx=${dx} dy=${dy}`); + // //console.warn(`@@@@panGraph - useCssTransforms - currentScale=${this.currentScale} currentTranslate=${this.currentTranslate.x}/${this.currentTranslate.y}`); + // console.warn(`@@@@panGraph - useCssTransforms - before pan - panDx=${this.panDx} panDy=${this.panDy}`); + // // this.panDx = dx; + // // this.panDy + // super.panGraph(dx, dy); + // console.warn(`@@@@panGraph - useCssTransforms - after pan - panDx=${this.panDx} panDy=${this.panDy}`); + } + } + + // mxGraph.prototype.panGraph = function(dx, dy) + // { + // if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container)) + // { + // this.container.scrollLeft = -dx; + // this.container.scrollTop = -dy; + // } + // else + // { + // var canvas = this.view.getCanvas(); + // + // if (this.dialect == mxConstants.DIALECT_SVG) + // { + // // Puts everything inside the container in a DIV so that it + // // can be moved without changing the state of the container + // if (dx == 0 && dy == 0) + // { + // // Workaround for ignored removeAttribute on SVG element in IE9 standards + // if (mxClient.IS_IE) + // { + // canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); + // } + // else + // { + // canvas.removeAttribute('transform'); + // } + // + // if (this.shiftPreview1 != null) + // { + // var child = this.shiftPreview1.firstChild; + // + // while (child != null) + // { + // var next = child.nextSibling; + // this.container.appendChild(child); + // child = next; + // } + // + // if (this.shiftPreview1.parentNode != null) + // { + // this.shiftPreview1.parentNode.removeChild(this.shiftPreview1); + // } + // + // this.shiftPreview1 = null; + // + // this.container.appendChild(canvas.parentNode); + // + // child = this.shiftPreview2.firstChild; + // + // while (child != null) + // { + // var next = child.nextSibling; + // this.container.appendChild(child); + // child = next; + // } + // + // if (this.shiftPreview2.parentNode != null) + // { + // this.shiftPreview2.parentNode.removeChild(this.shiftPreview2); + // } + // + // this.shiftPreview2 = null; + // } + // } + // else + // { + // canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); + // + // if (this.shiftPreview1 == null) + // { + // // Needs two divs for stuff before and after the SVG element + // this.shiftPreview1 = document.createElement('div'); + // this.shiftPreview1.style.position = 'absolute'; + // this.shiftPreview1.style.overflow = 'visible'; + // + // this.shiftPreview2 = document.createElement('div'); + // this.shiftPreview2.style.position = 'absolute'; + // this.shiftPreview2.style.overflow = 'visible'; + // + // var current = this.shiftPreview1; + // var child = this.container.firstChild; + // + // while (child != null) + // { + // var next = child.nextSibling; + // + // // SVG element is moved via transform attribute + // if (child != canvas.parentNode) + // { + // current.appendChild(child); + // } + // else + // { + // current = this.shiftPreview2; + // } + // + // child = next; + // } + // + // // Inserts elements only if not empty + // if (this.shiftPreview1.firstChild != null) + // { + // this.container.insertBefore(this.shiftPreview1, canvas.parentNode); + // } + // + // if (this.shiftPreview2.firstChild != null) + // { + // this.container.appendChild(this.shiftPreview2); + // } + // } + // + // this.shiftPreview1.style.left = dx + 'px'; + // this.shiftPreview1.style.top = dy + 'px'; + // this.shiftPreview2.style.left = dx + 'px'; + // this.shiftPreview2.style.top = dy + 'px'; + // } + // } + // else + // { + // canvas.style.left = dx + 'px'; + // canvas.style.top = dy + 'px'; + // } + // + // this.panDx = dx; + // this.panDy = dy; + // + // this.fireEvent(new mxEventObject(mxEvent.PAN)); + // } + // }; } class BpmnGraphView extends mxgraph.mxGraphView { @@ -413,6 +630,7 @@ class BpmnGraphView extends mxgraph.mxGraphView { // TODO check if validateBackgroundPage is used by bpmn-visualization today, otherwise remove override // var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage; override validateBackgroundPage(): void { + console.warn('****validateBackgroundPage - start'); const useCssTransforms = (this.graph).useCssTransforms, scale = this.scale, translate = this.translate; From b138c96dcfd2499751c2a67fad341a7374673fe3 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:31:31 +0200 Subject: [PATCH 3/8] EXTRA add todo for the mouse zoom code --- src/component/mxgraph/BpmnGraph.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index 5f73d9be18..a9c3a38e48 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -205,6 +205,8 @@ export class BpmnGraph extends mxgraph.mxGraph { } private getEventRelativeCoordinates(evt: MouseEvent): [number, number] { + // TODO EXTRA mouse zoom impl probably better with call of mxUtils.convertPoint + // mxgraph.mxUtils.convertPoint(this.container, mxgraph.mxEvent.getClientX(evt), mxgraph.mxEvent.getClientY(evt)); const rect = this.container.getBoundingClientRect(); const x = evt.clientX - rect.left; const y = evt.clientY - rect.top; From 02eb4cccc8d429c5d26646fbe32a9cd6dc85ebf7 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:39:00 +0200 Subject: [PATCH 4/8] update the zoomFactor value --- src/component/mxgraph/BpmnGraph.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index a9c3a38e48..ab56764b9b 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -24,7 +24,8 @@ import type { mxCellState, mxGraphView, mxPoint } from 'mxgraph'; import type { mxCell } from 'mxgraph'; import type { mxRectangle } from 'mxgraph'; -const zoomFactorIn = 1.25; +// TODO change value when using CSS transform (was 1.25) (we should have different values depending on the useCssTransforms value +const zoomFactorIn = 1.05; const zoomFactorOut = 1 / zoomFactorIn; export class BpmnGraph extends mxgraph.mxGraph { @@ -36,7 +37,7 @@ export class BpmnGraph extends mxgraph.mxGraph { /** * Uses CSS transforms for scale and translate. */ - // TODO test with true + // TODO make this configurable useCssTransforms = true; /** From 0ba607afaa50d27fffbf6d6c088ee0f9106e3e23 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:51:58 +0200 Subject: [PATCH 5/8] 1st implementation of zoom with mouse wheel --- src/component/mxgraph/BpmnGraph.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index ab56764b9b..b8195c92fb 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -175,6 +175,7 @@ export class BpmnGraph extends mxgraph.mxGraph { } else { // TODO implementation console.warn('@@@@@registerMouseWheelZoomListeners detected useCssTransforms - no mouse Zoom for now!!!!!!!'); + mxgraph.mxEvent.addMouseWheelListener(this.createMouseWheelZoomListenerForCssTransforms(), this.container); } } @@ -190,6 +191,35 @@ export class BpmnGraph extends mxgraph.mxGraph { mxgraph.mxEvent.consume(evt); } } + // TODO duplication with createMouseWheelZoomListener + private createMouseWheelZoomListenerForCssTransforms() { + return (event: Event, up: boolean) => { + if (mxgraph.mxEvent.isConsumed(event)) { + return; + } + const evt = event as MouseEvent; + // only the ctrl key + const isZoomWheelEvent = evt.ctrlKey && !evt.altKey && !evt.shiftKey && !evt.metaKey; + if (isZoomWheelEvent) { + this.manageMouseWheelZoomEventForCssTransforms(up, evt); + } + }; + } + + private manageMouseWheelZoomEventForCssTransforms(up: boolean, evt: MouseEvent): void { + const factor = up ? zoomFactorIn : zoomFactorOut; + console.warn('==========manageMouseWheelZoomEventForCssTransforms - factor', factor); + + // or view.scale? + const newScale = this.currentScale * factor; + + const [offsetX, offsetY] = this.getEventRelativeCoordinates(evt); + // const [newScale, dx, dy] = this.getScaleAndTranslationDeltas(offsetX, offsetY); + const [dx, dy] = this.calculateTranslationDeltas(factor, newScale, offsetX * 2, offsetY * 2); + + this.view.scaleAndTranslate(newScale, this.view.translate.x + dx, this.view.translate.y + dy); + mxgraph.mxEvent.consume(evt); + } private createMouseWheelZoomListener(performScaling: boolean) { return (event: Event, up: boolean) => { From 150a01d7047cf3bd36987df24b37a64e57373aee Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:21:26 +0200 Subject: [PATCH 6/8] restore the original zoomFactorIn: bad visual effects - to tune later if needed --- src/component/mxgraph/BpmnGraph.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index b8195c92fb..0b49a8d178 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -24,8 +24,8 @@ import type { mxCellState, mxGraphView, mxPoint } from 'mxgraph'; import type { mxCell } from 'mxgraph'; import type { mxRectangle } from 'mxgraph'; -// TODO change value when using CSS transform (was 1.25) (we should have different values depending on the useCssTransforms value -const zoomFactorIn = 1.05; +// TODO change value when using CSS transform (we should have different values depending on the useCssTransforms value +const zoomFactorIn = 1.25; const zoomFactorOut = 1 / zoomFactorIn; export class BpmnGraph extends mxgraph.mxGraph { From 2a104219204ad7f3adbe8ae84f19d139a6fb3003 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Fri, 26 Aug 2022 16:30:13 +0200 Subject: [PATCH 7/8] POC log management: clean + allow enable/disable --- src/component/mxgraph/BpmnGraph.ts | 214 +++++++---------------------- 1 file changed, 48 insertions(+), 166 deletions(-) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index 0b49a8d178..76bfc47695 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -28,6 +28,32 @@ import type { mxRectangle } from 'mxgraph'; const zoomFactorIn = 1.25; const zoomFactorOut = 1 / zoomFactorIn; +const enableAllPocLog = false; + +const enableLogPanGraph = false; +function logPanGraph(msg: string): void { + // eslint-disable-next-line no-console + (enableLogPanGraph || enableAllPocLog) && console.info(` - ${msg}`); +} + +const enableLogRegisterMouseListener = false; +function logRegisterMouseListener(msg: string): void { + // eslint-disable-next-line no-console + (enableLogRegisterMouseListener || enableAllPocLog) && console.info(` - ${msg}`); +} + +const enableLogUpdateCssTransform = false; +function logUpdateCssTransform(msg: string): void { + // eslint-disable-next-line no-console + (enableLogUpdateCssTransform || enableAllPocLog) && console.info(` - ${msg}`); +} + +const enableLogValidateBackgroundPage = false; +function logValidateBackgroundPage(msg: string): void { + // eslint-disable-next-line no-console + (enableLogValidateBackgroundPage || enableAllPocLog) && console.info(` - ${msg}`); +} + export class BpmnGraph extends mxgraph.mxGraph { private currentZoomLevel = 1; @@ -77,8 +103,6 @@ export class BpmnGraph extends mxgraph.mxGraph { override fit(border: number, keepOrigin?: boolean, margin?: number, enabled?: boolean, ignoreWidth?: boolean, ignoreHeight?: boolean, maxHeight?: number): number { const scale = super.fit(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight, maxHeight); this.setCurrentZoomLevel(scale); - console.warn('#####fit done, computed scale', scale); - console.warn('#####fit done, currentScale', this.currentScale); return scale; } @@ -93,7 +117,6 @@ export class BpmnGraph extends mxgraph.mxGraph { override zoomActual(): void { super.zoomActual(); this.setCurrentZoomLevel(); - console.warn('#####zoomActual done, currentScale', this.currentScale); } /** @@ -165,16 +188,14 @@ export class BpmnGraph extends mxgraph.mxGraph { * @internal */ registerMouseWheelZoomListeners(config: ZoomConfiguration): void { - console.warn('@@@@@registerMouseWheelZoomListeners start'); - console.warn('@@@@@registerMouseWheelZoomListeners isFirefox?', mxgraph.mxClient.IS_FF); + logRegisterMouseListener('start'); if (!this.useCssTransforms) { - console.warn('@@@@@registerMouseWheelZoomListeners NO useCssTransforms - use throttle/debounce for zoom'); + logRegisterMouseListener('NO useCssTransforms - use throttle/debounce for zoom'); config = ensureValidZoomConfiguration(config); mxgraph.mxEvent.addMouseWheelListener(debounce(this.createMouseWheelZoomListener(true), config.debounceDelay), this.container); mxgraph.mxEvent.addMouseWheelListener(throttle(this.createMouseWheelZoomListener(false), config.throttleDelay), this.container); } else { - // TODO implementation - console.warn('@@@@@registerMouseWheelZoomListeners detected useCssTransforms - no mouse Zoom for now!!!!!!!'); + logRegisterMouseListener('detected useCssTransforms - use poc code'); mxgraph.mxEvent.addMouseWheelListener(this.createMouseWheelZoomListenerForCssTransforms(), this.container); } } @@ -208,9 +229,8 @@ export class BpmnGraph extends mxgraph.mxGraph { private manageMouseWheelZoomEventForCssTransforms(up: boolean, evt: MouseEvent): void { const factor = up ? zoomFactorIn : zoomFactorOut; - console.warn('==========manageMouseWheelZoomEventForCssTransforms - factor', factor); - // or view.scale? + // TODO use view.scale instead of currentScale? const newScale = this.currentScale * factor; const [offsetX, offsetY] = this.getEventRelativeCoordinates(evt); @@ -333,6 +353,7 @@ export class BpmnGraph extends mxgraph.mxGraph { } // TODO check scrollRectToVisible - not used by bpmn-visualization today + // in draw.io code, the implementation is updated override scrollRectToVisible(r: mxRectangle): boolean { console.warn('#######Called scrollRectToVisible!'); return super.scrollRectToVisible(r); @@ -355,7 +376,7 @@ export class BpmnGraph extends mxgraph.mxGraph { * Zooms out of the graph by . */ updateCssTransform(): void { - console.warn('@@@@@updateCssTransform - start'); + logUpdateCssTransform('start'); const temp = this.view.getDrawPane(); if (temp != null) { @@ -364,25 +385,26 @@ export class BpmnGraph extends mxgraph.mxGraph { // TODO this check is probably not needed in our implementation as updateCssTransform is only called when useCssTransforms is true if (!this.useCssTransforms) { - console.warn('@@@@@updateCssTransform - useCssTransforms = false'); + logUpdateCssTransform('useCssTransforms = false'); g.removeAttribute('transformOrigin'); g.removeAttribute('transform'); } else { - console.warn('@@@@@updateCssTransform - useCssTransforms'); + logUpdateCssTransform('useCssTransforms'); const prev = g.getAttribute('transform'); - // the transformOrigin attribute seems specific to the draw.io/mxgraph EditorUI implementation + // TODO remove setting attribute transformOrigin + // the transformOrigin attribute seems specific to the draw.io/mxgraph EditorUI implementation g.setAttribute('transformOrigin', '0 0'); const s = Math.round(this.currentScale * 100) / 100; const dx = Math.round(this.currentTranslate.x * 100) / 100; const dy = Math.round(this.currentTranslate.y * 100) / 100; const computedTransformDirective = `scale(${s},${s})translate(${dx},${dy})`; - console.warn('@@@@updateCssTransform - computed transform directive', computedTransformDirective); + logUpdateCssTransform(`computed transform directive: ${computedTransformDirective}`); g.setAttribute('transform', computedTransformDirective); // Applies workarounds only if translate has changed if (prev != g.getAttribute('transform')) { - console.warn('@@@@@updateCssTransform - transform value changed'); + logUpdateCssTransform('transform value changed'); // TODO make the implem pass type check, disable 'cssTransformChanged' event firing for now // this.fireEvent(new mxgraph.mxEventObject('cssTransformChanged'), 'transform', g.getAttribute('transform')); } @@ -408,31 +430,29 @@ export class BpmnGraph extends mxgraph.mxGraph { */ override panGraph(dx: number, dy: number): void { if (!this.useCssTransforms) { - console.warn('@@@@panGraph - no useCssTransforms'); + logPanGraph('no useCssTransforms'); super.panGraph(dx, dy); } else { - console.warn('@@@@panGraph - useCssTransforms'); + logPanGraph('useCssTransforms'); if (this.useScrollbarsForPanning && mxgraph.mxUtils.hasScrollbars(this.container)) { - console.warn('@@@@panGraph - has scrollbars'); + logPanGraph('has scrollbars'); this.container.scrollLeft = -dx; this.container.scrollTop = -dy; } else { - // at the end of pan, panning handler does + // at the end of pan, the panning handler does // var scale = this.graph.getView().scale; // var t = this.graph.getView().translate; // this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale); // panGraph = function(dx, dy) ==> this.graph.getView().setTranslate(dx, dy); - console.warn(`@@@@panGraph - useCssTransforms - dx=${dx} dy=${dy}`); - // TODO manage rounding duplication with updateCssTransform (introduce private method) + logPanGraph(`useCssTransforms - dx=${dx} dy=${dy}`); + // TODO manage rounding duplication with updateCssTransform (introduce private function roundKeepLastTwoDecimals) + // 1.4594 --> 1.45 const roundedCurrentScale = Math.round(this.currentScale * 100) / 100; const roundedPannedTranslateX = Math.round((this.currentTranslate.x + dx / this.currentScale) * 100) / 100; const roundedPannedTranslateY = Math.round((this.currentTranslate.y + dy / this.currentScale) * 100) / 100; const computedTransformDirective = `scale(${roundedCurrentScale},${roundedCurrentScale})translate(${roundedPannedTranslateX},${roundedPannedTranslateY})`; - // const roundedCurrentTranslateX = Math.round(this.currentTranslate.x * 100) / 100; - // const roundedCurrentTranslateY = Math.round(this.currentTranslate.y * 100) / 100; - // const computedTransformDirective = `scale(${roundedCurrentScale},${roundedCurrentScale})translate(${roundedCurrentTranslateX + dx},${roundedCurrentTranslateY + dy})`; - console.warn('@@@@panGraph - computed transform directive', computedTransformDirective); + logPanGraph(`computed transform directive: ${computedTransformDirective}`); const canvas = this.view.getCanvas(); canvas.setAttribute('transform', computedTransformDirective); @@ -442,147 +462,8 @@ export class BpmnGraph extends mxgraph.mxGraph { this.panDy = dy; this.fireEvent(new mxgraph.mxEventObject(mxgraph.mxEvent.PAN), undefined); - - // DEBUG code - // console.warn(`@@@@panGraph - useCssTransforms - dx=${dx} dy=${dy}`); - // //console.warn(`@@@@panGraph - useCssTransforms - currentScale=${this.currentScale} currentTranslate=${this.currentTranslate.x}/${this.currentTranslate.y}`); - // console.warn(`@@@@panGraph - useCssTransforms - before pan - panDx=${this.panDx} panDy=${this.panDy}`); - // // this.panDx = dx; - // // this.panDy - // super.panGraph(dx, dy); - // console.warn(`@@@@panGraph - useCssTransforms - after pan - panDx=${this.panDx} panDy=${this.panDy}`); } } - - // mxGraph.prototype.panGraph = function(dx, dy) - // { - // if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container)) - // { - // this.container.scrollLeft = -dx; - // this.container.scrollTop = -dy; - // } - // else - // { - // var canvas = this.view.getCanvas(); - // - // if (this.dialect == mxConstants.DIALECT_SVG) - // { - // // Puts everything inside the container in a DIV so that it - // // can be moved without changing the state of the container - // if (dx == 0 && dy == 0) - // { - // // Workaround for ignored removeAttribute on SVG element in IE9 standards - // if (mxClient.IS_IE) - // { - // canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); - // } - // else - // { - // canvas.removeAttribute('transform'); - // } - // - // if (this.shiftPreview1 != null) - // { - // var child = this.shiftPreview1.firstChild; - // - // while (child != null) - // { - // var next = child.nextSibling; - // this.container.appendChild(child); - // child = next; - // } - // - // if (this.shiftPreview1.parentNode != null) - // { - // this.shiftPreview1.parentNode.removeChild(this.shiftPreview1); - // } - // - // this.shiftPreview1 = null; - // - // this.container.appendChild(canvas.parentNode); - // - // child = this.shiftPreview2.firstChild; - // - // while (child != null) - // { - // var next = child.nextSibling; - // this.container.appendChild(child); - // child = next; - // } - // - // if (this.shiftPreview2.parentNode != null) - // { - // this.shiftPreview2.parentNode.removeChild(this.shiftPreview2); - // } - // - // this.shiftPreview2 = null; - // } - // } - // else - // { - // canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); - // - // if (this.shiftPreview1 == null) - // { - // // Needs two divs for stuff before and after the SVG element - // this.shiftPreview1 = document.createElement('div'); - // this.shiftPreview1.style.position = 'absolute'; - // this.shiftPreview1.style.overflow = 'visible'; - // - // this.shiftPreview2 = document.createElement('div'); - // this.shiftPreview2.style.position = 'absolute'; - // this.shiftPreview2.style.overflow = 'visible'; - // - // var current = this.shiftPreview1; - // var child = this.container.firstChild; - // - // while (child != null) - // { - // var next = child.nextSibling; - // - // // SVG element is moved via transform attribute - // if (child != canvas.parentNode) - // { - // current.appendChild(child); - // } - // else - // { - // current = this.shiftPreview2; - // } - // - // child = next; - // } - // - // // Inserts elements only if not empty - // if (this.shiftPreview1.firstChild != null) - // { - // this.container.insertBefore(this.shiftPreview1, canvas.parentNode); - // } - // - // if (this.shiftPreview2.firstChild != null) - // { - // this.container.appendChild(this.shiftPreview2); - // } - // } - // - // this.shiftPreview1.style.left = dx + 'px'; - // this.shiftPreview1.style.top = dy + 'px'; - // this.shiftPreview2.style.left = dx + 'px'; - // this.shiftPreview2.style.top = dy + 'px'; - // } - // } - // else - // { - // canvas.style.left = dx + 'px'; - // canvas.style.top = dy + 'px'; - // } - // - // this.panDx = dx; - // this.panDy = dy; - // - // this.fireEvent(new mxEventObject(mxEvent.PAN)); - // } - // }; } class BpmnGraphView extends mxgraph.mxGraphView { @@ -663,7 +544,7 @@ class BpmnGraphView extends mxgraph.mxGraphView { // TODO check if validateBackgroundPage is used by bpmn-visualization today, otherwise remove override // var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage; override validateBackgroundPage(): void { - console.warn('****validateBackgroundPage - start'); + logValidateBackgroundPage('start'); const useCssTransforms = (this.graph).useCssTransforms, scale = this.scale, translate = this.translate; @@ -680,6 +561,7 @@ class BpmnGraphView extends mxgraph.mxGraphView { this.scale = scale; this.translate = translate; } + logValidateBackgroundPage('end'); } // TODO check updatePageBreaks - not used by bpmn-visualization today From 1718816ef9d780a66b0ecd2cfbf5c8e973036fb3 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Fri, 26 Aug 2022 16:43:40 +0200 Subject: [PATCH 8/8] assume css transforms are supported in all browsers supported by bpmn-visualization --- src/component/mxgraph/BpmnGraph.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/component/mxgraph/BpmnGraph.ts b/src/component/mxgraph/BpmnGraph.ts index 76bfc47695..7d21bfb8c8 100644 --- a/src/component/mxgraph/BpmnGraph.ts +++ b/src/component/mxgraph/BpmnGraph.ts @@ -302,14 +302,12 @@ export class BpmnGraph extends mxgraph.mxGraph { */ // eslint-disable-next-line @typescript-eslint/ban-types -- Function is type is required by typed-mxgraph override getCellAt(x: number, y: number, parent?: mxCell, vertices?: boolean, edges?: boolean, ignoreFn?: Function): mxCell { - // getCellAt = function(x, y, parent, vertices, edges, ignoreFn) if (this.useCssTransforms) { x = x / this.currentScale - this.currentTranslate.x; y = y / this.currentScale - this.currentTranslate.y; } return this.getScaledCellAt(x, y, parent, vertices, edges, ignoreFn); - // return null; } /** @@ -365,12 +363,12 @@ export class BpmnGraph extends mxgraph.mxGraph { * Check the following test case on page 1 before enabling this in production: * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1 */ - // TODO test if Safari still fails - isCssTransformsSupported(): boolean { - // this.updateCssTransform - return this.dialect == mxgraph.mxConstants.DIALECT_SVG && !mxgraph.mxClient.NO_FO && !mxgraph.mxClient.IS_SF; - // return this.dialect == mxgraph.mxConstants.DIALECT_SVG && !mxgraph.mxClient.NO_FO && (!this.lightbox || !mxgraph.mxClient.IS_SF); - } + // we test no Safari latest and it works + // isCssTransformsSupported(): boolean { + // // this.updateCssTransform + // return this.dialect == mxgraph.mxConstants.DIALECT_SVG && !mxgraph.mxClient.NO_FO && !mxgraph.mxClient.IS_SF; + // // return this.dialect == mxgraph.mxConstants.DIALECT_SVG && !mxgraph.mxClient.NO_FO && (!this.lightbox || !mxgraph.mxClient.IS_SF); + // } /** * Zooms out of the graph by .