From 33b5ed2cac3d20cfb692f8b5aa18cbb4450da1f9 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:36:33 +0200 Subject: [PATCH] fix(story): improve the 'Stencils' story Included fixes: - Configure the orthogonal edgeStyle - Display a tooltip when hovering over a cell - Let change the style of the first selected cell Add more space between the "style" and zoom buttons Migrate the story to TypeScript to ease the maintenance and detect errors earlier. --- ...tencils.stories.js => Stencils.stories.ts} | 135 ++++++++++-------- 1 file changed, 76 insertions(+), 59 deletions(-) rename packages/html/stories/{Stencils.stories.js => Stencils.stories.ts} (74%) diff --git a/packages/html/stories/Stencils.stories.js b/packages/html/stories/Stencils.stories.ts similarity index 74% rename from packages/html/stories/Stencils.stories.js rename to packages/html/stories/Stencils.stories.ts index 1be86264a1..9a8ff8b91c 100644 --- a/packages/html/stories/Stencils.stories.js +++ b/packages/html/stories/Stencils.stories.ts @@ -16,22 +16,24 @@ limitations under the License. */ import { - Graph, + type AbstractCanvas2D, + CellHighlight, + CellRenderer, + type CellState, ConnectionHandler, + constants, DomHelpers, EdgeHandler, + Graph, InternalEvent, + load, Point, - CellHighlight, - constants, - VertexHandler, + type Rectangle, RubberBandHandler, Shape, StencilShape, StencilShapeRegistry, - CellRenderer, - utils, - load, + VertexHandler, } from '@maxgraph/core'; import { contextMenuTypes, @@ -42,8 +44,7 @@ import { rubberBandValues, } from './shared/args.js'; import { createGraphContainer } from './shared/configure.js'; -// style required by RubberBand -import '@maxgraph/core/css/common.css'; +import '@maxgraph/core/css/common.css'; // style required by RubberBand export default { title: 'Shapes/Stencils', @@ -59,12 +60,12 @@ export default { }, }; -const Template = ({ label, ...args }) => { +const Template = ({ label, ...args }: Record) => { const div = document.createElement('div'); const container = createGraphContainer(args); div.appendChild(container); - // Allow overriding constants? + // Allow overriding constants. See https://github.com/maxGraph/maxGraph/issues/192 // Sets the global shadow color // constants.SHADOWCOLOR = '#C0C0C0'; // constants.SHADOW_OPACITY = 0.5; @@ -83,33 +84,42 @@ const Template = ({ label, ...args }) => { // Enable rotation handle VertexHandler.prototype.rotationEnabled = true; - // Uses the shape for resize previews - VertexHandler.prototype.createSelectionShape = function (bounds) { - const key = this.state.style.shape; - const stencil = StencilShapeRegistry.getStencil(key); - let shape = null; - - if (stencil != null) { - shape = new Shape(stencil); - shape.apply(this.state); - } else { - shape = new this.state.shape.constructor(); - } + class CustomVertexHandler extends VertexHandler { + // Uses the shape for resize previews + createSelectionShape(bounds: Rectangle) { + const stencil = StencilShapeRegistry.getStencil(this.state.style.shape ?? ''); + let shape = null; + + if (stencil != null) { + shape = new Shape(stencil); + shape.apply(this.state); + } else { + // @ts-ignore known to work at runtime + shape = new this.state.shape.constructor(); + } - shape.outline = true; - shape.bounds = bounds; - shape.stroke = constants.HANDLE_STROKECOLOR; - shape.strokewidth = this.getSelectionStrokeWidth(); - shape.isDashed = this.isSelectionDashed(); - shape.isShadow = false; - return shape; - }; + shape.outline = true; + shape.bounds = bounds; + shape.stroke = constants.HANDLE_STROKECOLOR; + shape.strokewidth = this.getSelectionStrokeWidth(); + shape.isDashed = this.isSelectionDashed(); + shape.isShadow = false; + return shape; + } + } - // Defines a custom stencil via the canvas API as defined here: - // http://jgraph.github.io/mxgraph/docs/js-api/files/util/mxXmlCanvas2D-js.html + class CustomGraph extends Graph { + constructor(container: HTMLElement) { + super(container); + } + createVertexHandler(state: CellState) { + return new CustomVertexHandler(state); + } + } + // Defines a custom Shape via the canvas API class CustomShape extends Shape { - paintBackground(c, x, y, w, h) { + paintBackground(c: AbstractCanvas2D, x: number, y: number, w: number, h: number) { c.translate(x, y); // Head @@ -143,12 +153,16 @@ const Template = ({ label, ...args }) => { // Loads the stencils into the registry const req = load('stencils.xml'); const root = req.getDocumentElement(); - let shape = root.firstChild; + let shape = root!.firstChild; + + function isElement(node: ChildNode): node is Element { + return node.nodeType === constants.NODETYPE.ELEMENT; + } while (shape != null) { - if (shape.nodeType === constants.NODETYPE.ELEMENT) { + if (isElement(shape)) { StencilShapeRegistry.addStencil( - shape.getAttribute('name'), + shape.getAttribute('name')!, // the "name" attribute is always set new StencilShape(shape) ); } @@ -159,26 +173,23 @@ const Template = ({ label, ...args }) => { if (!args.contextMenu) InternalEvent.disableContextMenu(container); // Creates the graph inside the given container - const graph = new Graph(container); + // const graph = new Graph(container); + const graph = new CustomGraph(container); graph.setConnectable(true); graph.setTooltips(true); graph.setPanning(true); graph.getTooltipForCell = function (cell) { - if (cell != null) { - return cell.style; - } - - return null; + return cell ? JSON.stringify(cell.style) : 'Cell without dedicated style'; }; // Changes default styles let style = graph.getStylesheet().getDefaultEdgeStyle(); - style.edge = 'orthogonalEdgeStyle'; + style.edgeStyle = 'orthogonalEdgeStyle'; style = graph.getStylesheet().getDefaultVertexStyle(); style.fillColor = '#adc5ff'; style.gradientColor = '#7d85df'; - style.shadow = '1'; + style.shadow = true; // Enables rubberband selection if (args.rubberBand) new RubberBandHandler(graph); @@ -193,9 +204,9 @@ const Template = ({ label, ...args }) => { const v2 = graph.insertVertex(parent, null, 'A2', 20, 220, 40, 80, { shape: 'and' }); const v3 = graph.insertVertex(parent, null, 'X1', 160, 110, 80, 80, { shape: 'xor' }); const e1 = graph.insertEdge(parent, null, '', v1, v3); - e1.geometry.points = [new Point(90, 60), new Point(90, 130)]; + e1.geometry!.points = [new Point(90, 60), new Point(90, 130)]; const e2 = graph.insertEdge(parent, null, '', v2, v3); - e2.geometry.points = [new Point(90, 260), new Point(90, 170)]; + e2.geometry!.points = [new Point(90, 260), new Point(90, 170)]; const v4 = graph.insertVertex(parent, null, 'A3', 520, 20, 40, 80, { shape: 'customShape', @@ -210,21 +221,21 @@ const Template = ({ label, ...args }) => { flipH: true, }); const e3 = graph.insertEdge(parent, null, '', v4, v6); - e3.geometry.points = [new Point(490, 60), new Point(130, 130)]; + e3.geometry!.points = [new Point(490, 60), new Point(130, 130)]; const e4 = graph.insertEdge(parent, null, '', v5, v6); - e4.geometry.points = [new Point(490, 260), new Point(130, 170)]; + e4.geometry!.points = [new Point(490, 260), new Point(130, 170)]; const v7 = graph.insertVertex(parent, null, 'O1', 250, 260, 80, 60, { shape: 'or', direction: 'south', }); const e5 = graph.insertEdge(parent, null, '', v6, v7); - e5.geometry.points = [new Point(310, 150)]; + e5.geometry!.points = [new Point(310, 150)]; const e6 = graph.insertEdge(parent, null, '', v3, v7); - e6.geometry.points = [new Point(270, 150)]; + e6.geometry!.points = [new Point(270, 150)]; const e7 = graph.insertEdge(parent, null, '', v7, v5); - e7.geometry.points = [new Point(290, 370)]; + e7.geometry!.points = [new Point(290, 370)]; }); const buttons = document.createElement('div'); @@ -257,7 +268,7 @@ const Template = ({ label, ...args }) => { if (geo != null) { graph.batchUpdate(() => { // Rotates the size and position in the geometry - geo = geo.clone(); + geo = geo!.clone(); geo.x += geo.width / 2 - geo.height / 2; geo.y += geo.height / 2 - geo.width / 2; const tmp = geo.width; @@ -318,17 +329,23 @@ const Template = ({ label, ...args }) => { buttons.appendChild( DomHelpers.button('Style', function () { const cell = graph.getSelectionCell(); + if (!cell) return; - if (cell != null) { - const style = utils.prompt('Style', cell.getStyle()); - - if (style != null) { - graph.getDataModel().setStyle(cell, style); - } + const newStyle = window.prompt( + `Style Cell #${cell.getId()}`, + JSON.stringify(cell.getStyle()) + ); + if (newStyle) { + graph.getDataModel().setStyle(cell, JSON.parse(newStyle)); } }) ); + buttons.appendChild(document.createTextNode('\u00a0')); + buttons.appendChild(document.createTextNode('\u00a0')); + buttons.appendChild(document.createTextNode('\u00a0')); + buttons.appendChild(document.createTextNode('\u00a0')); + buttons.appendChild( DomHelpers.button('+', function () { graph.zoomIn();