This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new a8c5a9ac56 Issue #8432 : Give each Hop Web graph canvas its own SVG
renderer remote (#8439)
a8c5a9ac56 is described below
commit a8c5a9ac56759ecc07f8cf23ea725a4ed18188bf
Author: Matt Casters <[email protected]>
AuthorDate: Thu Sep 17 13:21:21 2026 +0200
Issue #8432 : Give each Hop Web graph canvas its own SVG renderer remote
(#8439)
---
.../apache/hop/ui/hopgui/CanvasSvgFacadeImpl.java | 13 +---
.../apache/hop/ui/hopgui/CanvasZoomHandler.java | 53 +++++++------
.../hop/ui/hopgui/canvas/CanvasGraphRegistry.java | 82 +++++++++++++-------
.../ui/hopgui/canvas/CanvasInteractionHandler.java | 25 +++---
.../ui/hopgui/canvas/CanvasSvgRendererHandler.java | 58 ++++++++++----
.../org/apache/hop/ui/hopgui/canvas-svg.js | 89 ++++++++++++++++------
.../org/apache/hop/ui/hopgui/canvas-zoom.js | 10 ++-
.../hop/ui/hopgui/CanvasOverlayAttachTest.java | 18 ++++-
.../ui/hopgui/canvas/CanvasGraphRegistryTest.java | 44 +++++++++++
.../perspective/explorer/ExplorerPerspective.java | 2 +-
10 files changed, 271 insertions(+), 123 deletions(-)
diff --git
a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasSvgFacadeImpl.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasSvgFacadeImpl.java
index 43e014c00a..622824c2ac 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasSvgFacadeImpl.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasSvgFacadeImpl.java
@@ -48,13 +48,12 @@ public class CanvasSvgFacadeImpl extends CanvasSvgFacade {
CanvasGraphRegistry.getInstance().register(canvasId, canvas, graph);
canvas.setData("canvasId", canvasId);
canvas.setData("sessionUuid", getSessionUuidInternal());
- new CanvasSvgRendererHandler((Composite) canvas.getParent());
+ CanvasSvgRendererHandler.ensureRemote(canvas);
}
@Override
void unregisterCanvasInternal(Canvas canvas) {
- String canvasId = WidgetUtil.getId(canvas);
- CanvasGraphRegistry.getInstance().unregister(canvasId);
+ CanvasSvgRendererHandler.unregister(canvas);
}
@Override
@@ -125,13 +124,7 @@ public class CanvasSvgFacadeImpl extends CanvasSvgFacade {
Object graph = registry.getGraph(canvasId);
syncAreaOwnersToGraph(graph, result.getAreaOwners());
setCanvasWidgetDataInternal(canvas, revision);
- // Single shared client renderer for the session: only rebind when this
canvas is the active
- // tab. Otherwise a background paint (e.g. mouse-up on the graph we just
navigated away from
- // after following a BV/DV link) steals the overlay back to the previous
model.
- Canvas active = registry.getActiveCanvas();
- if (active == null || active.isDisposed() || active == canvas) {
- CanvasSvgRendererHandler.notifyCanvasReady(canvas, revision);
- }
+ CanvasSvgRendererHandler.notifyCanvasReady(canvas, revision);
}
@Override
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasZoomHandler.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasZoomHandler.java
index ac459b2e91..4f0d66d357 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasZoomHandler.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasZoomHandler.java
@@ -53,44 +53,41 @@ public class CanvasZoomHandler extends Widget {
}
CanvasGraphRegistry registry = CanvasGraphRegistry.getInstance();
- registry.setActiveCanvas(canvas);
- registry.setActiveZoomable(zoomable);
-
- RemoteObject remoteObject = registry.getZoomRemote();
+ String canvasId = WidgetUtil.getId(canvas);
+ RemoteObject remoteObject = registry.getZoomRemote(canvasId);
if (remoteObject == null) {
- createRemoteObject(registry);
+ createRemoteObject(registry, canvasId);
} else {
- updateCanvas(remoteObject);
+ remoteObject.call("attachListener", null);
}
}
- private void createRemoteObject(CanvasGraphRegistry registry) {
+ private void createRemoteObject(CanvasGraphRegistry registry, String
canvasId) {
try {
Connection connection = RWT.getUISession().getConnection();
- Canvas currentCanvas = registry.getActiveCanvas();
RemoteObject remoteObject =
connection.createRemoteObject("hop.CanvasZoom");
remoteObject.set("self", remoteObject.getId());
- remoteObject.set("canvas", WidgetUtil.getId(currentCanvas));
+ remoteObject.set("canvas", canvasId);
remoteObject.setHandler(
new AbstractOperationHandler() {
@Override
public void handleNotify(String event, JsonObject properties) {
if ("zoom".equals(event)) {
- handleZoom(registry, properties);
+ handleZoom(properties);
}
}
});
remoteObject.listen("zoom", true);
- registry.setZoomRemote(remoteObject);
+ registry.putZoomRemote(canvasId, remoteObject);
getDisplay()
.timerExec(
50,
() -> {
- RemoteObject ro = registry.getZoomRemote();
+ RemoteObject ro = registry.getZoomRemote(canvasId);
if (ro != null) {
ro.call("attachListener", null);
}
@@ -101,13 +98,8 @@ public class CanvasZoomHandler extends Widget {
}
}
- private static void handleZoom(CanvasGraphRegistry registry, JsonObject
properties) {
- Canvas currentCanvas = registry.getActiveCanvas();
- Object zoomable = registry.getActiveZoomable();
- if (currentCanvas == null || zoomable == null) {
- return;
- }
- if (!(zoomable instanceof IZoomable activeZoomable)) {
+ private void handleZoom(JsonObject properties) {
+ if (canvas == null || canvas.isDisposed() || zoomable == null) {
return;
}
@@ -116,26 +108,33 @@ public class CanvasZoomHandler extends Widget {
int y = properties.get("y").asInt();
org.eclipse.swt.widgets.Event swtEvent = new
org.eclipse.swt.widgets.Event();
- swtEvent.widget = currentCanvas;
+ swtEvent.widget = canvas;
swtEvent.x = x;
swtEvent.y = y;
swtEvent.count = count;
MouseEvent mouseEvent = new MouseEvent(swtEvent);
if (count > 0) {
- activeZoomable.zoomIn(mouseEvent);
+ zoomable.zoomIn(mouseEvent);
} else {
- activeZoomable.zoomOut(mouseEvent);
+ zoomable.zoomOut(mouseEvent);
}
}
- private void updateCanvas(RemoteObject remoteObject) {
- remoteObject.set("canvas", WidgetUtil.getId(canvas));
- remoteObject.call("attachListener", null);
- }
-
@Override
public void dispose() {
+ try {
+ if (canvas != null && !canvas.isDisposed()) {
+ RemoteObject remoteObject =
+
CanvasGraphRegistry.getInstance().removeZoomRemote(WidgetUtil.getId(canvas));
+ if (remoteObject != null) {
+ remoteObject.destroy();
+ }
+ }
+ } catch (Exception e) {
+ LogChannel.UI.logDebug(
+ "Failed to destroy CanvasZoomHandler remote object: " +
e.getMessage());
+ }
CanvasGraphRegistry registry = CanvasGraphRegistry.getInstance();
if (registry.getActiveCanvas() == this.canvas) {
registry.setActiveCanvas(null);
diff --git
a/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistry.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistry.java
index 2c3ccf08b2..2d130b5145 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistry.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistry.java
@@ -20,13 +20,15 @@ package org.apache.hop.ui.hopgui.canvas;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
+import org.apache.hop.core.logging.LogChannel;
import org.eclipse.rap.rwt.SingletonUtil;
import org.eclipse.rap.rwt.remote.RemoteObject;
import org.eclipse.swt.widgets.Canvas;
/**
* Per UISession registry of pipeline/workflow canvas renders. Stores the
latest SVG snapshot and
- * click-map for each canvas widget.
+ * click-map for each canvas widget, plus one RAP remote object per canvas for
the SVG overlay,
+ * hover bridge, and wheel zoom.
*/
public class CanvasGraphRegistry {
@@ -35,20 +37,14 @@ public class CanvasGraphRegistry {
private final Map<String, CanvasRenderSnapshot> snapshots = new
ConcurrentHashMap<>();
private final Map<String, Object> graphsByCanvasId = new
ConcurrentHashMap<>();
private final Map<String, Canvas> canvasById = new ConcurrentHashMap<>();
+ private final Map<String, RemoteObject> svgRendererRemotes = new
ConcurrentHashMap<>();
+ private final Map<String, RemoteObject> interactionRemotes = new
ConcurrentHashMap<>();
+ private final Map<String, RemoteObject> zoomRemotes = new
ConcurrentHashMap<>();
private final AtomicLong revisionCounter = new AtomicLong(1);
- /** Per-session remote object for the SVG canvas client (must not be static
across sessions). */
- private RemoteObject svgRendererRemote;
-
- /** Per-session remote object for canvas hover notifications. */
- private RemoteObject interactionRemote;
-
- /** Active canvas for hover routing within this UI session. */
+ /** Active canvas for hover/zoom routing within this UI session. */
private Canvas activeCanvas;
- /** Per-session remote object for mouse-wheel zoom. */
- private RemoteObject zoomRemote;
-
/** Active zoom target (HopGuiPipelineGraph / HopGuiWorkflowGraph). */
private Object activeZoomable;
@@ -62,9 +58,16 @@ public class CanvasGraphRegistry {
}
public void unregister(String canvasId) {
- canvasById.remove(canvasId);
+ Canvas canvas = canvasById.remove(canvasId);
graphsByCanvasId.remove(canvasId);
snapshots.remove(canvasId);
+ destroyRemote(svgRendererRemotes.remove(canvasId));
+ destroyRemote(interactionRemotes.remove(canvasId));
+ destroyRemote(zoomRemotes.remove(canvasId));
+ if (activeCanvas != null && (activeCanvas == canvas ||
isDisposed(activeCanvas))) {
+ activeCanvas = null;
+ activeZoomable = null;
+ }
}
public void updateSnapshot(String canvasId, CanvasRenderSnapshot snapshot) {
@@ -92,36 +95,40 @@ public class CanvasGraphRegistry {
return snapshot == null ? 0 : snapshot.getRevision();
}
- public RemoteObject getSvgRendererRemote() {
- return svgRendererRemote;
+ public RemoteObject getSvgRendererRemote(String canvasId) {
+ return canvasId == null ? null : svgRendererRemotes.get(canvasId);
}
- public void setSvgRendererRemote(RemoteObject svgRendererRemote) {
- this.svgRendererRemote = svgRendererRemote;
+ public void putSvgRendererRemote(String canvasId, RemoteObject remoteObject)
{
+ svgRendererRemotes.put(canvasId, remoteObject);
}
- public RemoteObject getInteractionRemote() {
- return interactionRemote;
+ public RemoteObject getInteractionRemote(String canvasId) {
+ return canvasId == null ? null : interactionRemotes.get(canvasId);
}
- public void setInteractionRemote(RemoteObject interactionRemote) {
- this.interactionRemote = interactionRemote;
+ public void putInteractionRemote(String canvasId, RemoteObject remoteObject)
{
+ interactionRemotes.put(canvasId, remoteObject);
}
- public Canvas getActiveCanvas() {
- return activeCanvas;
+ public RemoteObject getZoomRemote(String canvasId) {
+ return canvasId == null ? null : zoomRemotes.get(canvasId);
}
- public void setActiveCanvas(Canvas activeCanvas) {
- this.activeCanvas = activeCanvas;
+ public void putZoomRemote(String canvasId, RemoteObject remoteObject) {
+ zoomRemotes.put(canvasId, remoteObject);
}
- public RemoteObject getZoomRemote() {
- return zoomRemote;
+ public RemoteObject removeZoomRemote(String canvasId) {
+ return canvasId == null ? null : zoomRemotes.remove(canvasId);
}
- public void setZoomRemote(RemoteObject zoomRemote) {
- this.zoomRemote = zoomRemote;
+ public Canvas getActiveCanvas() {
+ return activeCanvas;
+ }
+
+ public void setActiveCanvas(Canvas activeCanvas) {
+ this.activeCanvas = activeCanvas;
}
public Object getActiveZoomable() {
@@ -131,4 +138,23 @@ public class CanvasGraphRegistry {
public void setActiveZoomable(Object activeZoomable) {
this.activeZoomable = activeZoomable;
}
+
+ private static boolean isDisposed(Canvas canvas) {
+ try {
+ return canvas.isDisposed();
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ private static void destroyRemote(RemoteObject remoteObject) {
+ if (remoteObject == null) {
+ return;
+ }
+ try {
+ remoteObject.destroy();
+ } catch (Exception e) {
+ LogChannel.UI.logDebug("Failed to destroy canvas remote object: " +
e.getMessage());
+ }
+ }
}
diff --git
a/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasInteractionHandler.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasInteractionHandler.java
index 81b3e08035..4b36d18591 100644
---
a/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasInteractionHandler.java
+++
b/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasInteractionHandler.java
@@ -25,6 +25,7 @@ import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.remote.AbstractOperationHandler;
import org.eclipse.rap.rwt.remote.Connection;
import org.eclipse.rap.rwt.remote.RemoteObject;
+import org.eclipse.rap.rwt.widgets.WidgetUtil;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Widget;
@@ -44,21 +45,21 @@ public class CanvasInteractionHandler extends Widget {
return;
}
CanvasGraphRegistry registry = CanvasGraphRegistry.getInstance();
- registry.setActiveCanvas(canvas);
- RemoteObject remoteObject = registry.getInteractionRemote();
+ String canvasId = WidgetUtil.getId(canvas);
+ RemoteObject remoteObject = registry.getInteractionRemote(canvasId);
if (remoteObject == null) {
- createRemoteObject(registry, canvas);
+ createRemoteObject(registry, canvasId);
} else {
- updateCanvas(remoteObject, canvas);
+ remoteObject.call("attachListener", null);
}
}
- private static void createRemoteObject(CanvasGraphRegistry registry, Canvas
canvas) {
+ private static void createRemoteObject(CanvasGraphRegistry registry, String
canvasId) {
try {
Connection connection = RWT.getUISession().getConnection();
RemoteObject remoteObject =
connection.createRemoteObject("hop.CanvasInteraction");
remoteObject.set("self", remoteObject.getId());
- remoteObject.set("canvas",
org.eclipse.rap.rwt.widgets.WidgetUtil.getId(canvas));
+ remoteObject.set("canvas", canvasId);
remoteObject.setHandler(
new AbstractOperationHandler() {
@Override
@@ -69,25 +70,19 @@ public class CanvasInteractionHandler extends Widget {
}
});
remoteObject.listen("hover", true);
- registry.setInteractionRemote(remoteObject);
+ registry.putInteractionRemote(canvasId, remoteObject);
remoteObject.call("attachListener", null);
} catch (Exception e) {
LogChannel.UI.logError("Failed to create CanvasInteractionHandler remote
object", e);
}
}
- private static void updateCanvas(RemoteObject remoteObject, Canvas canvas) {
- remoteObject.set("canvas",
org.eclipse.rap.rwt.widgets.WidgetUtil.getId(canvas));
- remoteObject.call("attachListener", null);
- }
-
private static void handleHover(JsonObject properties) {
- CanvasGraphRegistry registry = CanvasGraphRegistry.getInstance();
- if (registry.getActiveCanvas() == null) {
+ if (properties.get("canvasId") == null) {
return;
}
String canvasId = properties.get("canvasId").asString();
- Object graph = registry.getGraph(canvasId);
+ Object graph = CanvasGraphRegistry.getInstance().getGraph(canvasId);
if (graph == null) {
return;
}
diff --git
a/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasSvgRendererHandler.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasSvgRendererHandler.java
index 002675bee0..5edca97115 100644
---
a/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasSvgRendererHandler.java
+++
b/rap/src/main/java/org/apache/hop/ui/hopgui/canvas/CanvasSvgRendererHandler.java
@@ -4,7 +4,7 @@
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
+ * the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
@@ -24,14 +24,24 @@ import org.eclipse.rap.rwt.remote.Connection;
import org.eclipse.rap.rwt.remote.RemoteObject;
import org.eclipse.rap.rwt.widgets.WidgetUtil;
import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Widget;
-/** Pushes SVG render metadata to the Hop Web canvas client. */
-public class CanvasSvgRendererHandler extends Widget {
+/**
+ * Pushes SVG render metadata to the Hop Web canvas client. One {@code
hop.CanvasSvgRenderer} remote
+ * object is created per canvas so dialogs and editor tabs can keep
independent overlays.
+ */
+public final class CanvasSvgRendererHandler {
+
+ private CanvasSvgRendererHandler() {}
- public CanvasSvgRendererHandler(Composite parent) {
- super(parent, 0);
+ public static void ensureRemote(Canvas canvas) {
+ if (canvas == null || canvas.isDisposed()) {
+ return;
+ }
+ String canvasId = WidgetUtil.getId(canvas);
+ CanvasGraphRegistry registry = CanvasGraphRegistry.getInstance();
+ if (registry.getSvgRendererRemote(canvasId) == null) {
+ createRemoteObject(registry, canvasId);
+ }
}
public static void notifyCanvasReady(Canvas canvas, long revision) {
@@ -39,23 +49,40 @@ public class CanvasSvgRendererHandler extends Widget {
return;
}
CanvasGraphRegistry registry = CanvasGraphRegistry.getInstance();
- registry.setActiveCanvas(canvas);
- RemoteObject remoteObject = registry.getSvgRendererRemote();
+ String canvasId = WidgetUtil.getId(canvas);
+ RemoteObject remoteObject = registry.getSvgRendererRemote(canvasId);
if (remoteObject == null) {
- remoteObject = createRemoteObject(registry, canvas);
+ remoteObject = createRemoteObject(registry, canvasId);
}
if (remoteObject != null) {
- updateRemoteObject(remoteObject, canvas, revision);
+ updateRemoteObject(remoteObject, revision);
+ }
+ }
+
+ public static void unregister(Canvas canvas) {
+ if (canvas == null) {
+ return;
+ }
+ try {
+ CanvasGraphRegistry.getInstance().unregister(WidgetUtil.getId(canvas));
+ } catch (Exception e) {
+ LogChannel.UI.logDebug(
+ "Failed to unregister Canvas SVG renderer RemoteObject: " +
e.getMessage());
}
}
- private static RemoteObject createRemoteObject(CanvasGraphRegistry registry,
Canvas canvas) {
+ private static RemoteObject createRemoteObject(CanvasGraphRegistry registry,
String canvasId) {
try {
Connection connection = RWT.getUISession().getConnection();
RemoteObject remoteObject =
connection.createRemoteObject("hop.CanvasSvgRenderer");
remoteObject.set("self", remoteObject.getId());
- registry.setSvgRendererRemote(remoteObject);
- updateRemoteObject(remoteObject, canvas, 0);
+ remoteObject.set("sessionUuid", CanvasSvgFacade.getSessionUuid());
+ remoteObject.set("canvasId", canvasId);
+ remoteObject.set("renderRevision", 0L);
+ remoteObject.set(
+ "serviceHandlerUrl",
+
RWT.getServiceManager().getServiceHandlerUrl(CanvasRenderServiceHandler.SERVICE_ID));
+ registry.putSvgRendererRemote(canvasId, remoteObject);
remoteObject.call("attachListener", null);
return remoteObject;
} catch (Exception e) {
@@ -64,9 +91,8 @@ public class CanvasSvgRendererHandler extends Widget {
}
}
- private static void updateRemoteObject(RemoteObject remoteObject, Canvas
canvas, long revision) {
+ private static void updateRemoteObject(RemoteObject remoteObject, long
revision) {
remoteObject.set("sessionUuid", CanvasSvgFacade.getSessionUuid());
- remoteObject.set("canvasId", WidgetUtil.getId(canvas));
remoteObject.set("renderRevision", revision);
remoteObject.set(
"serviceHandlerUrl",
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-svg.js
b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-svg.js
index d245a2af71..f6033d4a38 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-svg.js
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-svg.js
@@ -22,6 +22,9 @@
if (!window.hop) {
window.hop = {};
}
+ if (!hop._canvasInteractions) {
+ hop._canvasInteractions = {};
+ }
var serviceBasePath = (function () {
var path = window.location.pathname;
@@ -398,6 +401,25 @@
if (this._overlay && this._overlay.parentNode) {
this._overlay.parentNode.removeChild(this._overlay);
}
+ this._resetOverlayDom();
+ },
+
+ _resetOverlayDom: function () {
+ this._overlay = null;
+ this._svgHost = null;
+ this._effectsLayer = null;
+ this._panBoundsOutline = null;
+ this._navViewportPreview = null;
+ this._selectLasso = null;
+ this._ghostSvg = null;
+ this._ghostRectPool = null;
+ this._hopLineEl = null;
+ this._dragPreviewRects = null;
+ this._noteHandleRects = null;
+ },
+
+ _overlayIsLive: function () {
+ return !!(this._overlay && this._overlay.isConnected);
},
attachListener: function () {
@@ -409,6 +431,9 @@
},
setCanvasId: function (properties) {
+ if (this._canvasId && this._canvasId !== properties.value) {
+ return;
+ }
this._canvasId = properties.value;
},
@@ -445,13 +470,11 @@
return;
}
self._findTimer = null;
- if (self._canvas === canvas) {
- var parent = canvas.parentElement;
- if (parent && self._overlay && self._overlay.parentNode
!== parent) {
- self._attachToCanvas(canvas);
- } else if (self._overlay) {
- self._syncOverlayLayout(canvas);
- }
+ if (!self._overlayIsLive()) {
+ self._resetOverlayDom();
+ }
+ if (self._canvas === canvas && self._overlayIsLive()) {
+ self._syncOverlayLayout(canvas);
return;
}
self._attachToCanvas(canvas);
@@ -473,6 +496,10 @@
this._detachInteractionListeners();
this._canvas = canvas;
+ if (this._overlay && !this._overlayIsLive()) {
+ this._resetOverlayDom();
+ }
+
if (!this._overlay) {
this._overlay = document.createElement("div");
this._overlay.style.position = "absolute";
@@ -533,7 +560,7 @@
}
var parent = canvas.parentElement;
- if (parent && this._overlay.parentNode !== parent) {
+ if (parent && !this._overlay.parentNode) {
if (window.getComputedStyle(parent).position === "static") {
parent.style.position = "relative";
}
@@ -2194,7 +2221,7 @@
return;
}
this._lastHoverKey = hoverKey;
- var interaction = hop._canvasInteractionInstance;
+ var interaction = hop._canvasInteractions &&
hop._canvasInteractions[this._canvasId];
if (area && area.hover && interaction &&
interaction._remoteObject) {
interaction._remoteObject.notify("hover", {
canvasId: this._canvasId,
@@ -2220,19 +2247,13 @@
widget._sessionUuid = value;
},
canvasId: function (widget, value) {
- var changed = widget._canvasId !== value;
- widget._canvasId = value;
- // Tab switch: re-bind overlay to the newly active canvas and
force SVG fetch.
- if (changed) {
- widget._revision = 0;
- if (widget._svgHost) {
- widget._svgHost.innerHTML = "";
- }
- widget._findAndAttachCanvas();
- widget._fetchAndRender(0);
- } else {
- widget._findAndAttachCanvas();
+ // canvasId is instance identity. A second canvas must get its
own remote;
+ // never steal this overlay by rewriting the id (issue #8432).
+ if (widget._canvasId && widget._canvasId !== value) {
+ return;
}
+ widget._canvasId = value;
+ widget._findAndAttachCanvas();
},
renderRevision: function (widget, value) {
if (value !== widget._revision) {
@@ -2249,17 +2270,28 @@
hop.CanvasInteraction = function (properties) {
this._canvasId = properties.canvas;
this._remoteObject = null;
- hop._canvasInteractionInstance = this;
+ if (!hop._canvasInteractions) {
+ hop._canvasInteractions = {};
+ }
+ if (this._canvasId) {
+ hop._canvasInteractions[this._canvasId] = this;
+ }
};
hop.CanvasInteraction.prototype = {
destroy: function () {
- if (hop._canvasInteractionInstance === this) {
- hop._canvasInteractionInstance = null;
+ if (this._canvasId && hop._canvasInteractions &&
hop._canvasInteractions[this._canvasId] === this) {
+ delete hop._canvasInteractions[this._canvasId];
}
},
attachListener: function () {
this._remoteObject = rap.getRemoteObject(this);
+ if (this._canvasId) {
+ if (!hop._canvasInteractions) {
+ hop._canvasInteractions = {};
+ }
+ hop._canvasInteractions[this._canvasId] = this;
+ }
}
};
@@ -2273,7 +2305,16 @@
events: ["hover"],
propertyHandler: {
canvas: function (widget, value) {
+ if (widget._canvasId && hop._canvasInteractions &&
hop._canvasInteractions[widget._canvasId] === widget) {
+ delete hop._canvasInteractions[widget._canvasId];
+ }
widget._canvasId = value;
+ if (!hop._canvasInteractions) {
+ hop._canvasInteractions = {};
+ }
+ if (value) {
+ hop._canvasInteractions[value] = widget;
+ }
}
}
});
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-zoom.js
b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-zoom.js
index 8c964e3d2f..1e3f47a28b 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-zoom.js
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas-zoom.js
@@ -147,7 +147,11 @@
// Method called when the canvas property is updated from Java
setCanvas: function(properties) {
- this._canvasId = properties.canvasId;
+ var canvasId = properties.canvasId;
+ if (this._canvasId && this._canvasId !== canvasId) {
+ return;
+ }
+ this._canvasId = canvasId;
this._findAndAttachCanvas();
},
@@ -256,6 +260,10 @@
events: ["zoom"],
propertyHandler: {
canvas: function(widget, value) {
+ // canvas is instance identity. Do not steal another graph's
wheel listener.
+ if (widget._canvasId && widget._canvasId !== value) {
+ return;
+ }
// When canvas property is updated from Java, re-attach wheel
to that canvas.
widget._canvasId = value;
widget._findAndAttachCanvas();
diff --git
a/rap/src/test/java/org/apache/hop/ui/hopgui/CanvasOverlayAttachTest.java
b/rap/src/test/java/org/apache/hop/ui/hopgui/CanvasOverlayAttachTest.java
index c78ae9c836..701f07516f 100644
--- a/rap/src/test/java/org/apache/hop/ui/hopgui/CanvasOverlayAttachTest.java
+++ b/rap/src/test/java/org/apache/hop/ui/hopgui/CanvasOverlayAttachTest.java
@@ -27,11 +27,14 @@ import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
/**
- * Guards the Hop Web canvas overlay against issue #8286.
+ * Guards the Hop Web canvas overlay against issues #8286 and #8432.
*
* <p>RAP does not put widget ids on DOM elements unless enableUITests is on,
so looking up {@code
* document.getElementById(canvasId)} and then guessing "the first canvas
larger than 500x500" left
* a blank graph in a small viewport and drew the graph inside a dialog when
one was open.
+ *
+ * <p>A session-wide {@code hop.CanvasSvgRenderer} remote then moved that
overlay into a second
+ * canvas (dialog over editor tab). Each canvas must keep its own overlay for
its lifetime.
*/
class CanvasOverlayAttachTest {
@@ -45,6 +48,18 @@ class CanvasOverlayAttachTest {
assertTrue(js.contains("this._canvasId = properties.canvasId"), js);
}
+ @Test
+ void svgOverlayIsPinnedToOneCanvasAndRecreatedIfDestroyed() throws
IOException {
+ String js = readResource("org/apache/hop/ui/hopgui/canvas-svg.js");
+
+ assertFalse(js.contains("_overlay.parentNode !== parent"), js);
+ assertFalse(js.contains("widget._svgHost.innerHTML = \"\""), js);
+ assertTrue(js.contains("_overlayIsLive"), js);
+ assertTrue(js.contains("isConnected"), js);
+ assertTrue(js.contains("hop._canvasInteractions"), js);
+ assertFalse(js.contains("hop._canvasInteractionInstance"), js);
+ }
+
@Test
void zoomResolvesTheRapWidgetNotALargeCanvas() throws IOException {
String js = readResource("org/apache/hop/ui/hopgui/canvas-zoom.js");
@@ -52,6 +67,7 @@ class CanvasOverlayAttachTest {
assertFalse(js.contains("rect.width > 500"), js);
assertTrue(js.contains("ObjectRegistry"), js);
assertTrue(js.contains("this._canvasId = properties.canvas"), js);
+ assertTrue(js.contains("widget._canvasId && widget._canvasId !== value"),
js);
}
@Test
diff --git
a/rap/src/test/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistryTest.java
b/rap/src/test/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistryTest.java
new file mode 100644
index 0000000000..4352f80468
--- /dev/null
+++
b/rap/src/test/java/org/apache/hop/ui/hopgui/canvas/CanvasGraphRegistryTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui.canvas;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.List;
+import org.eclipse.rap.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+class CanvasGraphRegistryTest {
+
+ @Test
+ void unregisterDropsOnlyThatCanvasSnapshot() {
+ CanvasGraphRegistry registry = new CanvasGraphRegistry();
+ registry.updateSnapshot(
+ "a", new CanvasRenderSnapshot(1, "<svg a/>", List.of(), new
JsonObject()));
+ registry.updateSnapshot(
+ "b", new CanvasRenderSnapshot(2, "<svg b/>", List.of(), new
JsonObject()));
+
+ registry.unregister("a");
+
+ assertNull(registry.getSnapshot("a"));
+ assertEquals(0, registry.getCurrentRevision("a"));
+ assertEquals(2, registry.getCurrentRevision("b"));
+ assertEquals("<svg b/>", registry.getSnapshot("b").getSvg());
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index 270a1406e1..f8a537f101 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -5097,7 +5097,7 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
CanvasSvgHelper.notifyCanvasReady(workflowGraph.getCanvas());
} else if (activeHandler instanceof HopGuiAbstractGraph abstractGraph) {
// Plugin model graphs (Data Vault, Business Vault, dimensional, source
model, …)
- // share the single Hop Web SVG renderer and zoom remote; re-bind on tab
switch.
+ // each have their own Hop Web SVG renderer and zoom remotes; refresh on
tab switch.
Object zoomHandler = getModelGraphZoomHandler(abstractGraph);
if (zoomHandler != null) {
CanvasZoomHelper.notifyCanvasReady(zoomHandler);