mattcasters opened a new issue, #8432:
URL: https://github.com/apache/hop/issues/8432

   ### Apache Hop version?
   
   2.20.0-SNAPSHOT
   
   ### Java version?
   
   21
   
   ### Operating system
   
   Linux
   
   ### What happened?
   
   In **Hop Web**, the vector SVG canvas rendering infrastructure 
(`CanvasSvgFacade`, `CanvasSvgRendererHandler`, and `canvas-svg.js`) only 
permits **one active SVG canvas per UI session**.
   
   When an interactive dialog or viewer opens in the foreground and draws an 
SVG (e.g. the Kimball Bus Matrix viewer dialog, model preview dialogs, or 
action/context dialogs using SVG rendering), any canvas already rendered in the 
background editor tab (such as a Dimensional Model, Data Vault model, Pipeline, 
or Workflow) **immediately loses its SVG overlay and becomes blank**.
   
   Furthermore, after the foreground dialog is closed, the background graph is 
unable to draw or recover its overlay until the user manually switches tabs 
back and forth.
   
   ---
   
   ### In-Depth Cause Analysis
   
   The failure is caused by a session-wide singleton pattern spanning both the 
Java RAP bridge and the client-side JavaScript:
   
   #### 1. Single RemoteObject in `CanvasGraphRegistry` & 
`CanvasSvgRendererHandler`
   In `org.apache.hop.ui.hopgui.canvas.CanvasGraphRegistry`:
   ```java
   private RemoteObject svgRendererRemote;
   private Canvas activeCanvas;
   ```
   There is only **one** `RemoteObject` of type `"hop.CanvasSvgRenderer"` 
created per UI session.
   
   When a dialog canvas initializes and renders, 
`CanvasSvgRendererHandler.notifyCanvasReady(canvas, revision)` reuses the 
session's single `RemoteObject`, overwriting `canvasId` with the dialog's 
widget ID:
   ```java
   remoteObject.set("canvasId", WidgetUtil.getId(canvas));
   remoteObject.call("attachListener", null);
   ```
   At the same time, `registry.setActiveCanvas(canvas)` re-points 
`activeCanvas` to the dialog canvas.
   
   #### 2. DOM Overlay Hijacking in `canvas-svg.js`
   In `canvas-svg.js`, the type handler for `"hop.CanvasSvgRenderer"` maintains 
a single instance holding a single `this._overlay` DOM element.
   
   When `canvasId` changes to the dialog canvas:
   1. It blanks the existing SVG:
      ```javascript
      if (widget._svgHost) {
          widget._svgHost.innerHTML = "";
      }
      ```
   2. In `_attachToCanvas(canvas)`:
      ```javascript
      var parent = canvas.parentElement;
      if (parent && this._overlay.parentNode !== parent) {
          parent.appendChild(this._overlay);
      }
      ```
      `parent.appendChild(this._overlay)` **physically moves** the existing DOM 
overlay node out of the background model tab's container and into the dialog 
shell's DOM container.
      The background graph is left with no overlay element in the DOM.
   
   #### 3. Background Repaints Blocked in `CanvasSvgFacadeImpl.java`
   In `CanvasSvgFacadeImpl.publishSnapshotInternal`:
   ```java
   Canvas active = registry.getActiveCanvas();
   if (active == null || active.isDisposed() || active == canvas) {
       CanvasSvgRendererHandler.notifyCanvasReady(canvas, revision);
   }
   ```
   Because `active` is now the foreground dialog canvas, any subsequent render 
of the background graph is suppressed because `active == canvas` evaluates to 
`false`.
   
   #### 4. Overlay Destruction on Dialog Close
   When the foreground dialog closes:
   1. RAP disposes the dialog shell and purges all its DOM nodes from the 
browser.
   2. Because `this._overlay` was reparented inside the dialog's DOM, **the 
client overlay DOM element is destroyed**.
   3. In Java, `CanvasSvgFacade.unregisterCanvas(canvas)` removes the dialog 
canvas from the registry maps, but:
      - `activeCanvas` in `CanvasGraphRegistry` is left pointing to a disposed 
canvas.
      - The background canvas is never re-notified.
      - The client-side JS instance is left with a destroyed/orphaned DOM 
element.
   The background graph remains completely dead and un-rendered until a tab 
switch forces a rebind.
   
   ---
   
   ### Fix Proposal
   
   To allow multiple canvases to co-exist (such as dialogs over editor tabs, 
split-screen editor views, or side-by-side diff viewers), the SVG canvas 
infrastructure should manage `RemoteObject`s and overlays **per canvas 
instance**, similar to `ContextDialogSvgRendererHandler`:
   
   1. **`CanvasGraphRegistry.java`**:
      - Replace the single `svgRendererRemote` with a map keyed by `canvasId`:
        ```java
        private final Map<String, RemoteObject> svgRendererRemotes = new 
ConcurrentHashMap<>();
        ```
   2. **`CanvasSvgRendererHandler.java`**:
      - In `notifyCanvasReady(Canvas canvas, long revision)` (or during 
`registerCanvas`):
        Create a dedicated `RemoteObject("hop.CanvasSvgRenderer")` for that 
specific canvas ID.
      - In `unregister(Canvas canvas)`:
        Call `remoteObject.destroy()`, allowing RAP to notify the client to 
clean up that specific canvas's overlay.
   3. **`canvas-svg.js`**:
      - Each `hop.CanvasSvgRenderer` instance (one per `RemoteObject`) owns its 
own `this._overlay` and permanently attaches it to its assigned `canvasId`.
      - Remove the `this._overlay.parentNode !== parent` reparenting logic.
      - In `destroy()`: remove only that instance's own `_overlay` and clean up 
its listeners/timers.
      - Map hover interactions by `canvasId` 
(`hop._canvasInteractions[canvasId] = this;`) instead of using a single global 
`hop._canvasInteractionInstance`.
   4. **`CanvasSvgFacadeImpl.java`**:
      - Remove the `active == canvas` check in `publishSnapshotInternal`, 
allowing visible canvases to render independently.
   
   ### Issue Priority
   
   Priority: 2
   
   ### Issue Component
   
   Component: Hop Web
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to