diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c872416 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +*.log +.DS_Store +dist/*.map diff --git a/ANALYSIS.md b/ANALYSIS.md new file mode 100644 index 0000000..0c12888 --- /dev/null +++ b/ANALYSIS.md @@ -0,0 +1,422 @@ +# draw.io Custom Shapes with JavaScript Logic — Architecture Analysis + +## Executive Summary + +**Can you build custom shapes with JS logic and import them as a library into draw.io?** + +**YES** — via the **Plugin system**. draw.io supports fully programmable shapes written in JavaScript that can be loaded as plugins in both the desktop (Electron) app and self-hosted/web instances. These plugins can define shapes with conditional rendering, computed geometry, dynamic behavior, and expose them in the sidebar palette for drag-and-drop use. + +The standard "library import" (File > Open Library) only supports declarative XML — but plugins give you **more** power, not less. + +--- + +## Table of Contents + +1. [Shape Systems Overview](#shape-systems-overview) +2. [XML Stencils (Declarative)](#xml-stencils-declarative) +3. [JavaScript Shape Classes (Programmatic)](#javascript-shape-classes-programmatic) +4. [The Plugin Architecture](#the-plugin-architecture) +5. [Loading Plugins in Desktop App](#loading-plugins-in-desktop-app) +6. [Loading Plugins in Web/Self-Hosted](#loading-plugins-in-webself-hosted) +7. [Shape Registration Flow](#shape-registration-flow) +8. [Canvas API Reference](#canvas-api-reference) +9. [Limitations and Considerations](#limitations-and-considerations) +10. [Comparison Matrix](#comparison-matrix) + +--- + +## Shape Systems Overview + +draw.io has **two completely separate shape definition systems**: + +| System | Format | JS Logic? | Import Method | +|--------|--------|-----------|---------------| +| XML Stencils | `.xml` stencil files | ❌ No (declarative only) | `mxStencilRegistry` / Open Library | +| JS Shape Classes | `.js` files | ✅ Full programmatic | Plugin system / `mxCellRenderer.registerShape()` | + +When a cell is rendered, the resolution order is: + +``` +Cell style "shape=NAME" → + 1. mxCellRenderer.defaultShapes[NAME] → JS Shape class + 2. mxStencilRegistry.stencils[NAME] → XML Stencil instance + 3. Dynamic loading (if enabled) → Load from library registry +``` + +--- + +## XML Stencils (Declarative) + +Located in: `src/main/webapp/stencils/*.xml` + +XML stencils define shapes using a drawing vocabulary: + +```xml + + + + + + + + + + + + + + + + +``` + +### Capabilities +- Drawing primitives: `move`, `line`, `quad`, `curve`, `arc`, `rect`, `roundrect`, `ellipse`, `close` +- Styling: `strokecolor`, `fillcolor`, `fontcolor`, `alpha`, `strokewidth`, `dashed` +- State: `save` / `restore` +- Sub-shapes: `` + +### Limitations +- **No loops, conditionals, or computed geometry** +- **No JavaScript execution** (except limited `allowEval` for text/image values) +- Purely static vector drawing instructions + +--- + +## JavaScript Shape Classes (Programmatic) + +Located in: `src/main/webapp/shapes/*.js` (~47 files) + +Examples: `mxBootstrap.js`, `mxKubernetes.js`, `mxElectrical.js`, `mxNetworks.js`, `mxRack.js` + +JS shapes extend `mxShape` and have **full programmatic power**: + +```javascript +function MyShape(bounds, fill, stroke, strokewidth) { + mxShape.call(this); + this.bounds = bounds; + this.fill = fill; + this.stroke = stroke; + this.strokewidth = (strokewidth != null) ? strokewidth : 1; +} + +mxUtils.extend(MyShape, mxShape); + +MyShape.prototype.paintVertexShape = function(c, x, y, w, h) { + c.translate(x, y); + + // Full JS: conditionals, loops, math, style reading + var mode = mxUtils.getValue(this.style, 'mode', 'default'); + var segments = parseInt(mxUtils.getValue(this.style, 'segments', '4')); + + if (mode === 'alert') { + c.setFillColor('#ff4444'); + } + + // Computed geometry + for (var i = 0; i < segments; i++) { + var segH = h / segments; + c.rect(0, i * segH, w, segH - 2); + c.fillAndStroke(); + } +}; + +// Register globally +mxCellRenderer.registerShape('mxgraph.myLib.myShape', MyShape); +``` + +### Capabilities +- Conditional rendering based on style properties +- Loops and computed geometry +- Math and dynamic calculations +- Reading per-instance style parameters via `mxUtils.getValue(this.style, ...)` +- Full canvas API access +- Custom connection points +- Override `getConstraints()` for dynamic connection point generation + +--- + +## The Plugin Architecture + +### Plugin Structure + +Every draw.io plugin follows this pattern: + +```javascript +// Shape definitions (execute immediately at load time) +function CustomShape() { /* ... */ } +mxUtils.extend(CustomShape, mxShape); +CustomShape.prototype.paintVertexShape = function(c, x, y, w, h) { /* ... */ }; +mxCellRenderer.registerShape('mxgraph.custom.shapeName', CustomShape); + +// UI integration (executes when draw.io is ready) +Draw.loadPlugin(function(ui) { + // ui = EditorUi instance — full access to the application + + // Add sidebar palette + var fns = [ + ui.sidebar.createVertexTemplateEntry( + 'shape=mxgraph.custom.shapeName;fillColor=#dae8fc;', + 120, 80, '', 'My Custom Shape', null, null, 'search keywords' + ) + ]; + ui.sidebar.addPaletteFunctions('customPalette', 'My Custom Library', true, fns); + + // Can also: add menus, actions, event listeners, modify graph behavior +}); +``` + +### What `Draw.loadPlugin(callback)` Provides + +The callback receives `ui` (EditorUi instance) with access to: + +| Object | Purpose | +|--------|---------| +| `ui.editor.graph` | The mxGraph instance (model, view, styles) | +| `ui.sidebar` | Sidebar/palette management | +| `ui.menus` | Menu system | +| `ui.actions` | Action registry | +| `ui.editor` | Editor instance | +| Global: `mxShape`, `mxCellRenderer`, `mxUtils`, `mxConstants` | All mxGraph APIs | + +### Production Example: `rackF5.js` + +The `plugins/rackF5.js` file (1674 lines) is a real-world plugin that: +- Defines ~20+ shape classes with full JS rendering +- Uses conditionals based on style (`hasEars`, `isFront`, `isDC`, `psNum`) +- Registers all shapes with `mxCellRenderer.registerShape()` +- Adds a complete sidebar palette via `Draw.loadPlugin()` + +--- + +## Loading Plugins in Desktop App + +The draw.io Electron app (desktop) has **full plugin support** via **Extras > Plugins**: + +### Method 1: Built-in Plugins +A dropdown of pre-packaged plugins (`App.publicPlugin`): +- `ex` (explore), `tips`, `svgdata`, `number`, `sql`, `props`, `text`, `anim`, `update`, `trees`, `replay`, `anon`, `webcola`, `tags` + +### Method 2: External Plugins (Custom `.js` Files) + +1. **Enable external plugins** (one-time): + - The app checks `requestSync('isPluginsEnabled')` + - If disabled, it shows "pluginsDisabled" message + - Enable via app preferences or configuration + +2. **Install your plugin**: + - Extras > Plugins → "Select File..." + - Native file dialog opens, filtered for `.js` files + - Plugin is copied to App Data folder (`installPlugin` action) + - Registered in `mxSettings` + +3. **Restart draw.io** — plugin loads on next startup + +### Loading sequence (from source): +``` +App startup + → mxSettings.getPlugins() returns saved plugin list + → App.initPluginCallback() creates Draw object + queue + → For each plugin: + ├─ Built-in (starts with ./plugins/) → load relative path + └─ External → requestSync('getPluginFile') → load from file:// App Data + → Scripts loaded via