diff --git a/static/diagram.js b/static/diagram.js index a666c85..16af6a5 100644 --- a/static/diagram.js +++ b/static/diagram.js @@ -67,6 +67,103 @@ var DiagramNode = /** @class */ (function () { }; return DiagramNode; }()); +var ContextMenuItem = /** @class */ (function () { + function ContextMenuItem(label, callback) { + this.x = 0; + this.y = 0; + this.hover = false; + this.callback = function (node) { }; + this.label = label; + this.callback = callback; + } + return ContextMenuItem; +}()); +var ContextMenu = /** @class */ (function () { + function ContextMenu(ctx) { + this.x = 0; + this.y = 0; + this.active = false; + this.mouseOver = false; + this.textWidth = 0; + this.textHeight = 0; + this.textMargin = 0; + this.width = 0; + this.height = 0; + this.items = new Array(); + this.contextNode = null; + this.ctx = ctx; + this.ctx.font = "20px Helvetica"; + var textSize = this.ctx.measureText("SomeLongerText"); + this.textWidth = textSize.width; + this.textHeight = textSize.actualBoundingBoxAscent + textSize.actualBoundingBoxDescent; + this.textMargin = this.textWidth / 8; + } + ContextMenu.prototype.fitContextMenu = function () { + this.width = this.textWidth + this.textMargin * 2; + this.height = this.textHeight + this.textMargin * (this.items.length + 2); + var index = 0; + for (var _i = 0, _a = this.items; _i < _a.length; _i++) { + var item = _a[_i]; + item.x = this.textMargin; + item.y = this.textHeight * index + this.textMargin * (index + 2); + index++; + } + }; + ContextMenu.prototype.pointIn = function (x, y) { + if (x < this.x) { + this.mouseOver = false; + return false; + } + if (y < this.y) { + this.mouseOver = false; + return false; + } + if (x > this.x + this.width) { + this.mouseOver = false; + return false; + } + if (y > this.y + this.height) { + this.mouseOver = false; + return false; + } + for (var _i = 0, _a = this.items; _i < _a.length; _i++) { + var item = _a[_i]; + if (y >= this.y + item.y - this.textHeight && y <= this.y + item.y + this.textHeight) { + item.hover = true; + } + else { + item.hover = false; + } + } + this.mouseOver = true; + return true; + }; + ContextMenu.prototype.clickOn = function () { + if (this.contextNode == null) { + console.warn("No contextNode"); + return; + } + for (var _i = 0, _a = this.items; _i < _a.length; _i++) { + var item = _a[_i]; + console.log(item.hover); + if (item.hover) { + item.callback(this.contextNode); + } + } + }; + ContextMenu.prototype.draw = function () { + var cameraX = _diagram.cameraX; + var cameraY = _diagram.cameraY; + this.ctx.fillStyle = "lightblue"; + this.ctx.fillRect(this.x + cameraX, this.y + cameraY, this.width, this.height); + for (var _i = 0, _a = this.items; _i < _a.length; _i++) { + var item = _a[_i]; + this.ctx.fillStyle = this.mouseOver && item.hover ? "red" : "black"; + this.ctx.fillText(item.label, this.x + item.x + cameraX, this.y + item.y + cameraY); + } + }; + return ContextMenu; +}()); var _diagram; function diargramOnResize() { _diagram.onresize(); @@ -113,6 +210,10 @@ var Diagrams = /** @class */ (function () { } _diagram = this; this.ctx = ctx; + this.contextMenu = new ContextMenu(this.ctx); + this.contextMenu.items.push(new ContextMenuItem("Edit", editNodeCallback)); + this.contextMenu.items.push(new ContextMenuItem("Delete", editNodeCallback)); + this.contextMenu.fitContextMenu(); this.ctx.font = "20px Helvetica"; this.canvas.onmousemove = diagramOnMouseMove; this.canvas.onmousedown = diagramOnMouseDown; @@ -128,7 +229,10 @@ var Diagrams = /** @class */ (function () { this.mouseY = ev.y - canvasRect.top; this.worldX = this.mouseX - this.cameraX; this.worldY = this.mouseY - this.cameraY; - if (this.nodeHover != null) { + if (this.contextMenu.active) { + this.contextMenu.pointIn(this.worldX, this.worldY); + } + else if (this.nodeHover != null) { this.nodeHover.hover = false; this.nodeHover.inputHover = false; this.nodeHover.outputHover = false; @@ -172,6 +276,7 @@ var Diagrams = /** @class */ (function () { if (ev.button != 0) { return; } + //this.contextMenu.active = false; var canvasRect = this.canvas.getBoundingClientRect(); this.mouseX = ev.x - canvasRect.left; this.mouseY = ev.y - canvasRect.top; @@ -200,7 +305,11 @@ var Diagrams = /** @class */ (function () { for (var _i = 0, _a = this.nodes; _i < _a.length; _i++) { var node = _a[_i]; if (node.pointInNode(this.worldX, this.worldY)) { - this.editNodeCallback(node); + this.contextMenu.x = this.worldX; + this.contextMenu.y = this.worldY; + this.contextMenu.active = true; + this.contextMenu.contextNode = node; + this.draw(); } } } @@ -230,6 +339,13 @@ var Diagrams = /** @class */ (function () { } this.makingConnectionNode = null; } + if (this.contextMenu.active) { + if (this.contextMenu.pointIn(this.worldX, this.worldY)) { + this.contextMenu.clickOn(); + this.draw(); + } + this.contextMenu.active = false; + } for (var _g = 0, _h = this.connections; _g < _h.length; _g++) { var _j = _h[_g], output = _j[0], input = _j[1]; var _k = output.getOutputCircleXY(), outputX = _k[0], outputY = _k[1]; @@ -338,6 +454,9 @@ var Diagrams = /** @class */ (function () { this.ctx.arc(node.x + node.width + this.cameraX, node.y + node.height / 2 + this.cameraY, node.height / 3, 0, fullCircleRadians); this.ctx.fill(); } + if (this.contextMenu.active) { + this.contextMenu.draw(); + } }; Diagrams.prototype.addNode = function (id, x, y, label, meta) { if (meta === void 0) { meta = {}; } diff --git a/static/diagram.ts b/static/diagram.ts index d48b247..b8664f0 100644 --- a/static/diagram.ts +++ b/static/diagram.ts @@ -91,6 +91,106 @@ class DiagramNode { } } +class ContextMenuItem { + x: number = 0; + y: number = 0; + hover: boolean = false; + callback: (node: DiagramNode) => void = function(node){} + label: string; + + constructor(label: string, callback: (node: DiagramNode) => void){ + this.label = label; + this.callback = callback; + } +} +class ContextMenu { + x: number = 0; + y: number = 0; + active: boolean = false; + mouseOver: boolean = false; + textWidth: number = 0; + textHeight: number = 0; + textMargin: number = 0; + width: number = 0; + height: number = 0; + ctx: CanvasRenderingContext2D; + items: Array = new Array(); + + contextNode: DiagramNode | null = null; + + constructor(ctx: CanvasRenderingContext2D){ + this.ctx = ctx; + this.ctx.font = "20px Helvetica"; + let textSize = this.ctx.measureText("SomeLongerText"); + this.textWidth = textSize.width; + this.textHeight = textSize.actualBoundingBoxAscent + textSize.actualBoundingBoxDescent; + this.textMargin = this.textWidth / 8; + } + + fitContextMenu(){ + this.width = this.textWidth + this.textMargin * 2; + this.height = this.textHeight + this.textMargin * (this.items.length + 2) + let index = 0; + for (let item of this.items){ + item.x = this.textMargin; + item.y = this.textHeight * index + this.textMargin * (index + 2); + index++; + } + } + + pointIn(x: number, y: number){ + if (x < this.x){ + this.mouseOver = false; + return false; + } + if (y < this.y){ + this.mouseOver = false; + return false; + } + if (x > this.x + this.width) { + this.mouseOver = false; + return false; + } + if (y > this.y + this.height) { + this.mouseOver = false; + return false; + } + for (let item of this.items){ + if (y >= this.y + item.y - this.textHeight && y <= this.y + item.y + this.textHeight){ + item.hover = true; + } else { + item.hover = false; + } + } + this.mouseOver = true; + return true; + } + + clickOn(){ + if(this.contextNode == null){ + console.warn("No contextNode"); + return + } + for (let item of this.items){ + console.log(item.hover); + if(item.hover){ + item.callback(this.contextNode); + } + } + } + + draw(){ + let cameraX = _diagram.cameraX; + let cameraY = _diagram.cameraY; + this.ctx.fillStyle = "lightblue"; + this.ctx.fillRect(this.x + cameraX, this.y + cameraY, this.width, this.height); + for (let item of this.items){ + this.ctx.fillStyle = this.mouseOver && item.hover ? "red" : "black"; + this.ctx.fillText(item.label, this.x + item.x + cameraX, this.y + item.y + cameraY); + } + } +} + let _diagram: Diagrams; function diargramOnResize(){ _diagram.onresize(); @@ -137,6 +237,8 @@ class Diagrams { editNodeCallback: (node: DiagramNode) => void = function (){}; + contextMenu: ContextMenu; + constructor(canvasId: string, editNodeCallback: (node: DiagramNode) => void = function (){}){ this.canvas = document.getElementById(canvasId) as HTMLCanvasElement; if (this.canvas === null){ @@ -148,6 +250,10 @@ class Diagrams { } _diagram = this; this.ctx = ctx; + this.contextMenu = new ContextMenu(this.ctx); + this.contextMenu.items.push(new ContextMenuItem("Edit", editNodeCallback)) + this.contextMenu.items.push(new ContextMenuItem("Delete", editNodeCallback)) + this.contextMenu.fitContextMenu(); this.ctx.font = "20px Helvetica"; this.canvas.onmousemove = diagramOnMouseMove; this.canvas.onmousedown = diagramOnMouseDown; @@ -165,7 +271,9 @@ class Diagrams { this.worldX = this.mouseX - this.cameraX; this.worldY = this.mouseY - this.cameraY; - if (this.nodeHover != null){ + if (this.contextMenu.active){ + this.contextMenu.pointIn(this.worldX, this.worldY); + } else if (this.nodeHover != null){ this.nodeHover.hover = false; this.nodeHover.inputHover = false this.nodeHover.outputHover = false @@ -207,6 +315,8 @@ class Diagrams { if (ev.button != 0){ return; } + + //this.contextMenu.active = false; let canvasRect = this.canvas.getBoundingClientRect(); this.mouseX = ev.x - canvasRect.left; @@ -235,7 +345,11 @@ class Diagrams { if (ev.button == 2) { for (let node of this.nodes){ if (node.pointInNode(this.worldX, this.worldY)){ - this.editNodeCallback(node); + this.contextMenu.x = this.worldX; + this.contextMenu.y = this.worldY; + this.contextMenu.active = true; + this.contextMenu.contextNode = node; + this.draw(); } } } @@ -264,6 +378,13 @@ class Diagrams { } this.makingConnectionNode = null; } + if (this.contextMenu.active){ + if (this.contextMenu.pointIn(this.worldX, this.worldY)){ + this.contextMenu.clickOn(); + this.draw(); + } + this.contextMenu.active = false; + } for (let [output, input] of this.connections){ let [outputX, outputY] = output.getOutputCircleXY(); @@ -399,6 +520,9 @@ class Diagrams { this.ctx.arc(node.x + node.width + this.cameraX, node.y + node.height / 2 + this.cameraY, node.height / 3, 0, fullCircleRadians); this.ctx.fill(); } + if (this.contextMenu.active){ + this.contextMenu.draw(); + } } addNode(id: number, x: number, y: number, label: string, meta: Object = {}){