mattcasters opened a new pull request, #7431:
URL: https://github.com/apache/hop/pull/7431
# Issue #7430 — Hop Web Canvas Rendering: Reviewer Recap
**Branch:** `issue-7430`
**Commit:** `010a23e8ae` — *issue #7430 : Improve canvas rendering in Hop
Web*
**Scope:** 39 files changed, +3922 / −260 lines
## Problem
Hop Web pipeline and workflow canvases were unreliable compared to the
desktop Hop GUI:
- Canvas could appear blank after a server/browser restart.
- Transform/action icons were incorrectly scaled.
- Graph content was vertically misaligned relative to the toolbar and
overlay.
- Dark-mode transform icons lacked the contrast treatment used on desktop.
- Interactive feedback (drag, pan, selection, navigation minimap, name
hover) was missing or broken because the visible layer is now server-rendered
SVG, while legacy RAP `canvas.js` drew underneath a hidden canvas.
- Pipeline execution could crash SVG rendering when the layout resized (e.g.
execution pane opening), due to missing status-indicator images.
- Name hover underline positioning on the client was fragile and misaligned
with SVG text.
## Solution Overview
Introduce a **server-side SVG rendering pipeline** for Hop Web that reuses
the existing `PipelinePainter` / `WorkflowPainter` logic via `SvgGc`, paired
with a **client overlay** (`canvas-svg.js`) for responsive interaction
previews. Desktop (RCP) behavior is unchanged via a no-op `CanvasSvgFacadeImpl`
in the `rcp` module.
```mermaid
flowchart LR
subgraph server [Hop Web Server]
Graph[HopGuiPipelineGraph / HopGuiWorkflowGraph]
Renderer[PipelineCanvasSvgRenderer / WorkflowCanvasSvgRenderer]
Painter[PipelinePainter / WorkflowPainter]
SvgGc[SvgGc]
Registry[CanvasGraphRegistry]
Service[CanvasRenderServiceHandler]
Graph --> Renderer --> Painter --> SvgGc
Renderer --> Registry
Registry --> Service
end
subgraph client [Browser]
Overlay[canvas-svg.js overlay]
Legacy[canvas.js - interaction modes]
Service -->|JSON: svg + areas + props| Overlay
Overlay --> Legacy
end
```
### Data flow
1. Graph paint on web calls `CanvasSvgFacade.renderPipeline()` /
`renderWorkflow()`.
2. Renderer produces SVG XML plus `AreaOwner` click regions and canvas props
(magnification, offsets, theme, viewPort, graphPort, etc.).
3. Snapshot is stored in per-session `CanvasGraphRegistry` with a monotonic
revision.
4. `CanvasSvgRendererHandler` pushes `renderRevision` to the browser; client
fetches via `CanvasRenderServiceHandler` (`?servicehandler=canvasRender`).
5. Client injects SVG into an overlay div aligned on top of the RAP canvas;
`canvas.js` continues to handle SWT event modes (drag, select, hop, pan).
---
## Changes by Area
### 1. Server-side SVG rendering (engine)
| File | Purpose |
|------|---------|
| `PipelineCanvasSvgRenderer.java` | Renders pipeline graphs to SVG using
`PipelinePainter` |
| `WorkflowCanvasSvgRenderer.java` | Same for workflows |
| `CanvasSvgRenderResult.java` | SVG string + area owners + view/graph ports
|
| `CanvasColorPalette.java` | Shared light/dark canvas color palette for
`SvgGc` |
| `SvgGc.java` | Major updates: dark mode palette, icon scale fix, dark-mode
icon contrast, missing `SUCCESS`/`DATA` images, null-safe `drawImage`,
`GRAPH_BOLD` font |
| `BasePainter.java` | `isWebCanvasRendering()` helper (`gc instanceof
SvgGc`) |
| `PipelinePainter.java` / `WorkflowPainter.java` | **Web:** bold name on
hover; **Desktop:** underline on hover (unchanged) |
| `IGc.java` | Added `EFont.GRAPH_BOLD` |
**Icon scaling fix:** `SvgGc.drawImage()` no longer double-applies
magnification; embed scale uses logical size only (see `SvgGcIconScaleTest`).
**Execution status crash fix:** `EImage.SUCCESS` (and `DATA`) were
referenced by `PipelinePainter.drawTransformStatusIndicator()` but not
registered in `SvgGc`. Added SVG assets and null guards in `drawImage()`
overloads.
### 2. Dark mode icon contrast (core)
| File | Purpose |
|------|---------|
| `SvgDarkModeContrast.java` | Shared clone-and-swap logic for SVG icon
colors in dark mode |
| `SvgDarkModeContrastTest.java` | Unit tests |
| `SwtUniversalImageSvg.java` | Refactored to use shared contrast helper |
`SvgGc` applies contrasting colors when `darkMode` is true, using
`PropsUi.getContrastingColorStrings()` passed through the renderer context.
Per-filename SVG document cache avoids repeated processing.
### 3. Hop Web integration (rap + ui)
| File | Purpose |
|------|---------|
| `CanvasSvgFacade.java` | Platform abstraction (loaded via
`ImplementationLoader`) |
| `CanvasSvgFacadeImpl.java` (rap) | Web implementation: render, snapshot,
notify client |
| `CanvasSvgFacadeImpl.java` (rcp) | No-op stub for desktop |
| `CanvasGraphRegistry.java` | Per-`UISession` registry (fixes blank canvas
after restart — **not** a static global) |
| `CanvasRenderServiceHandler.java` | HTTP endpoint serving cached SVG JSON |
| `CanvasRenderSnapshot.java` | Revisioned snapshot model |
| `CanvasSvgRendererHandler.java` | RAP remote object for client polling /
revision push |
| `CanvasInteractionHandler.java` | Hover notifications from client to
server |
| `AreaOwnerJsonSerializer.java` | Serializes click regions for client
hit-testing |
| `CanvasSvgHelper.java` | Utility to notify canvas ready from explorer
perspective |
| `HopGuiPipelineGraph.java` / `HopGuiWorkflowGraph.java` | Web paint path
via SVG facade; `handleWebCanvasHover()` updates `mouseOverName` when
`EnvironmentUtils.isWeb()` |
| `HopGuiAbstractGraph.java` | `applyMouseOverNameHover()` — shared hover
logic, fixes redraw when moving between two names |
| `DragViewZoomBase.java` | Navigation minimap / viewport support in web
props |
| `CanvasFacadeImpl.java` / `CanvasZoomHandler.java` | Adjustments for
coexistence with SVG overlay |
| `ExplorerPerspective.java` | Notify SVG renderer when graphs open |
| `HopWeb.java` / `HopWebEntryPoint.java` | Register `canvas-svg.js` and RAP
data keys |
### 4. Client overlay (rap/resources)
| File | Purpose |
|------|---------|
| `canvas-svg.js` | **New** (~1150 lines): SVG overlay, polling, interaction
previews |
| `canvas.js` | Minor coordinate fixes (`fx`/`fy`); still drives RAP
interaction modes |
**Client features in `canvas-svg.js`:**
- SVG host overlay synced to canvas position (`_syncOverlayLayout`).
- **Transform/action drag preview** — dashed selection rects follow cursor
during drag.
- **Pan preview** — middle-click or Ctrl+background drag; semi-transparent
SVG + pan bounds outline.
- **Navigation minimap drag** — blue viewport rectangle preview.
- **Selection lasso** — dashed rectangle on background click-drag (visible
above SVG; server still handles selection via `canvas.js` / graph logic).
- **Name hover** — server re-renders bold text; client only sets pointer
cursor (underline overlay removed).
- Hover events forwarded to `CanvasInteractionHandler` for tooltips and
`mouseOverName` updates.
### 5. Tests
| Test | Covers |
|------|--------|
| `PipelineCanvasSvgRendererTest` | SVG output and click regions for a
minimal pipeline |
| `SvgGcIconScaleTest` | Icon embed scale independent of magnification
parameter |
| `SvgDarkModeContrastTest` | Dark mode color substitution |
### 6. Local development
| File | Purpose |
|------|---------|
| `docker/run-hop-web-local.sh` | Convenience script to build and run Hop
Web locally |
| `.gitignore` | Ignores `docker/local-data/` |
---
## Behavioral Notes for Reviewers
### Desktop vs Hop Web divergence (intentional)
| Behavior | Desktop (RCP) | Hop Web |
|----------|---------------|---------|
| Canvas rendering | `SwtGc` via `drawPipelineImage()` | `SvgGc` via
`CanvasSvgFacade` |
| Name hover affordance | Underline | **Bold** (`EFont.GRAPH_BOLD`) |
| Interaction previews | `canvas.js` on visible canvas | `canvas-svg.js`
effects layer + `canvas.js` modes |
| `CanvasSvgFacade` | No-op (`rcp`) | Full implementation (`rap`) |
Web/desktop split in painters uses `isWebCanvasRendering()` (`SvgGc` vs
`SwtGc`), not `EnvironmentUtils` in the engine layer.
`EnvironmentUtils.isWeb()` is used in graph classes for hover wiring.
### Session scoping
`CanvasGraphRegistry` uses `SingletonUtil.getSessionInstance()` — each
browser session gets its own snapshots and remote objects. This was critical to
fix the post-restart blank canvas bug caused by stale static/global state.
### Revision / polling
- Client polls every 500ms as a fallback.
- `renderRevision` push triggers immediate fetch when the server redraws
(e.g. on name hover).
- Client periodically forces full refresh (`clientRev=0`) to recover missed
updates.
### Vertical alignment fix
- Pipeline graph toolbar attachment uses `FormAttachment(toolBar, 0)`
instead of toolbar height offset.
- Overlay layout sync matches canvas offset within parent composite.
- `canvas.js` coordinate helpers no longer double-apply offset.
---
## How to Test Locally
```bash
./mvnw -pl ui,rap,engine,core -am clean package -DskipTests
-Dspotless.check.skip=true -Drat.skip=true
./docker/run-hop-web-local.sh --skip-maven
```
Hard-refresh the browser after deploy.
### Suggested manual test checklist
- [ ] Open pipeline/workflow — graph renders, icons correct size
- [ ] Restart Hop Web container — canvas still renders (not blank)
- [ ] Dark mode — transform icons have readable contrast
- [ ] Drag transform — preview rects track cursor; drop updates position
- [ ] Middle-click / Ctrl+drag background — pan preview and release
- [ ] Drag navigation minimap viewport — blue rectangle preview
- [ ] Background click-drag — selection lasso visible; release selects
transforms
- [ ] Hover transform name — **bold** on web; click still opens edit
(single-click mode)
- [ ] Execute pipeline (e.g. `add-constants.hpl`) — no SVG NPE when
execution pane resizes; success indicators render
- [ ] Desktop Hop GUI — name hover still shows **underline**, not bold
---
## Review Focus Areas
1. **Session lifecycle** — `CanvasGraphRegistry` register/unregister on
graph open/close; no leaks across sessions.
2. **Performance** — full SVG re-render on hover enter/leave; acceptable for
typical graphs but worth watching on large pipelines.
3. **Coordinate consistency** — screen vs graph coords across
`canvas-svg.js`, `canvas.js`, and Java `AreaOwner` regions.
4. **Null safety in `SvgGc.drawImage()`** — silent skip when image missing;
confirm no visual gaps are acceptable vs logging.
5. **Facade split** — `ImplementationLoader` resolves `rap` vs `rcp`
`CanvasSvgFacadeImpl` correctly in both assemblies.
6. **Apache license headers** — all new Java/JS files include standard ASF
headers (RAT).
---
## File List (commit `010a23e8ae`)
```
core/ SvgDarkModeContrast.java, SvgDarkModeContrastTest.java,
test-icon.svg
engine/ PipelineCanvasSvgRenderer, WorkflowCanvasSvgRenderer,
CanvasColorPalette,
CanvasSvgRenderResult, SvgGc, BasePainter, PipelinePainter,
WorkflowPainter,
IGc, SvgGcIconScaleTest, PipelineCanvasSvgRendererTest
rap/ CanvasSvgFacadeImpl, CanvasGraphRegistry,
CanvasRenderServiceHandler,
CanvasSvgRendererHandler, CanvasInteractionHandler,
AreaOwnerJsonSerializer,
CanvasRenderSnapshot, canvas-svg.js, canvas.js, CanvasFacadeImpl,
CanvasZoomHandler, HopWeb, HopWebEntryPoint
rcp/ CanvasSvgFacadeImpl (no-op)
ui/ CanvasSvgFacade, CanvasSvgHelper, HopGuiPipelineGraph,
HopGuiWorkflowGraph,
HopGuiAbstractGraph, SwtUniversalImageSvg, DragViewZoomBase,
ExplorerPerspective,
ConfigKeyboardShortcutsTab
docker/ run-hop-web-local.sh
.gitignore docker/local-data/
```
---
## Summary
This change replaces Hop Web's unreliable client-side canvas drawing with
**server-rendered SVG** that shares the desktop painter code path, while
preserving RAP-based mouse interaction through a layered architecture.
Follow-up polish addressed alignment, dark mode, interaction previews,
selection lasso, execution-time rendering crashes, and web-specific bold name
hover — without altering desktop underline behavior.
--
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]