changed graph logic, so it is only updated when needed. Introduction of right click menu on nodes in the graph, for now details works only

This commit is contained in:
Stefan Etringer 2025-07-16 15:03:04 +00:00
parent 3d57688c3e
commit 824c387ed7
4 changed files with 923 additions and 8 deletions

View File

@ -2,9 +2,11 @@ let agentData = [];
let isCyInitialized = false; let isCyInitialized = false;
let cy; let cy;
function initializeCytoscape() { function initializeCytoscape() {
if (isCyInitialized) return; if (isCyInitialized) return;
// cytoscape.use(cxtmenu);
cy = cytoscape({ cy = cytoscape({
container: document.getElementById('cyto-graph'), container: document.getElementById('cyto-graph'),
@ -58,7 +60,8 @@ function initializeCytoscape() {
'width' : 3, 'width' : 3,
'line-color' : '#ccc', 'line-color' : '#ccc',
'target-arrow-color': '#ccc', 'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle' 'target-arrow-shape': 'triangle',
'curve-style': 'round-segments'
} }
}, },
{ {
@ -80,7 +83,7 @@ document.addEventListener('DOMContentLoaded', function () {
document.body.addEventListener('htmx:afterSwap', function (event) { document.body.addEventListener('htmx:afterSwap', function (event) {
if (event.target.id === 'agentList') { if (event.target.id === 'agentList') {
initializeCytoscape(); // initializeCytoscape();
loadGraphData(); loadGraphData();
} }
}); });
@ -139,10 +142,66 @@ async function updateGraph(agentData) {
/* --- relayout --------------------------------------------------- */ /* --- relayout --------------------------------------------------- */
cy.layout({ name: 'grid', rows: 2 }).run(); cy.layout({ name: 'grid', rows: 2 }).run();
/* --- CxtMenu --- */
if (window.nodeContextMenu) {
window.nodeContextMenu.destroy();
}
window.nodeContextMenu = cy.cxtmenu({
selector: 'node',
commands: [
{
content: 'Details',
select: function (ele) {
const data = ele.data();
alert(`Node Details:\n\nName: ${data.name}\nID: ${data.id}\nType: ${data.type}\nIP: ${data.ip}`);
}
},
{
content: 'Ping',
select: function(ele) {
const ip = ele.data().ip;
console.log(`Ping to ${ip} triggered!`);
}
}
],
menuRadius: 100,
fillColor: `rgba(0, 0, 0, 0.75)`,
activeFillColor: `rgba(255, 255, 255, 0.3)`,
activePadding: 10,
indicatorSize: 18,
separatorWidth: 3,
spotlightPadding: 4,
minSpotlightRadius: 20,
maxSpotlightRadius: 40,
openMenuEvents: 'cxttap',
});
window.edgeContextMenu = cy.cxtmenu({
selector: 'edge',
commands: [
{
content: 'View Info',
select: function (ele) {
const source = ele.source().data().name;
const target = ele.target().data().name;
alert(`Edge from "${source}" to "${target}"`);
}
},
{
content: 'Delete Edge',
select: function (ele) {
ele.remove();
}
}
],
menuRadius: 100,
});
} }
async function fetchData() { async function fetchData() {
const url = "http://localhost:3333/agents"; const url = "/agents";
try { try {
const response = await fetch(url, {headers: {Accept: 'application/json'}}); const response = await fetch(url, {headers: {Accept: 'application/json'}});
if (!response.ok) { if (!response.ok) {
@ -160,14 +219,19 @@ async function fetchData() {
async function loadGraphData() { async function loadGraphData() {
// console.log("Function loadGraphData()"); // console.log("Function loadGraphData()");
var agentDataCurrent = ""
if (agentData.length > 0) {
agentDataCurrent = JSON.stringify(agentData);
}
// Fetch agent data asynchronously // Fetch agent data asynchronously
agentData = await fetchData(); agentData = await fetchData();
// Check if the data is valid console.log('Extracted agent data:', agentData);
// console.log('Extracted agent data:', agentData);
// Only update the graph if agent data is available // Only update the graph if agent data is available
if (agentData && agentData.length > 0) { if (JSON.stringify(agentData) != agentDataCurrent) {
// if (agentData && agentData.length > 0) {
console.log("updating graph");
await updateGraph(agentData); await updateGraph(agentData);
} else { } else {
console.log('No agent data found or extracted.'); console.log('No agent data found or extracted.');

View File

@ -0,0 +1,849 @@
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["cytoscapeCxtmenu"] = factory();
else
root["cytoscapeCxtmenu"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 4);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var defaults = __webpack_require__(2);
var assign = __webpack_require__(1);
var _require = __webpack_require__(3),
removeEles = _require.removeEles,
setStyles = _require.setStyles,
createElement = _require.createElement,
getPixelRatio = _require.getPixelRatio,
getOffset = _require.getOffset;
var cxtmenu = function cxtmenu(params) {
var options = assign({}, defaults, params);
var cy = this;
var container = cy.container();
var target = void 0;
var data = {
options: options,
handlers: [],
container: createElement({ class: 'cxtmenu' })
};
var wrapper = data.container;
var parent = createElement();
var canvas = createElement({ tag: 'canvas' });
var commands = [];
var c2d = canvas.getContext('2d');
var r = 100; // defailt radius;
var containerSize = (r + options.activePadding) * 2;
var activeCommandI = void 0;
var offset = void 0;
container.insertBefore(wrapper, container.firstChild);
wrapper.appendChild(parent);
parent.appendChild(canvas);
setStyles(wrapper, {
position: 'absolute',
zIndex: options.zIndex,
userSelect: 'none',
pointerEvents: 'none' // prevent events on menu in modern browsers
});
// prevent events on menu in legacy browsers
['mousedown', 'mousemove', 'mouseup', 'contextmenu'].forEach(function (evt) {
wrapper.addEventListener(evt, function (e) {
e.preventDefault();
return false;
});
});
setStyles(parent, {
display: 'none',
width: containerSize + 'px',
height: containerSize + 'px',
position: 'absolute',
zIndex: 1,
marginLeft: -options.activePadding + 'px',
marginTop: -options.activePadding + 'px',
userSelect: 'none'
});
canvas.width = containerSize;
canvas.height = containerSize;
function createMenuItems(r, rs) {
removeEles('.cxtmenu-item', parent);
var dtheta = 2 * Math.PI / commands.length;
var theta1 = Math.PI / 2;
var theta2 = theta1 + dtheta;
for (var i = 0; i < commands.length; i++) {
var command = commands[i];
var midtheta = (theta1 + theta2) / 2;
var rx1 = (r + rs) / 2 * Math.cos(midtheta);
var ry1 = (r + rs) / 2 * Math.sin(midtheta);
// Arbitrary multiplier to increase the sizing of the space
// available for the item.
var width = 1 * Math.abs((r - rs) * Math.cos(midtheta));
var height = 1 * Math.abs((r - rs) * Math.sin(midtheta));
width = Math.max(width, height);
var item = createElement({ class: 'cxtmenu-item' });
setStyles(item, {
color: options.itemColor,
cursor: 'default',
display: 'table',
'text-align': 'center',
//background: 'red',
position: 'absolute',
'text-shadow': '-1px -1px 2px ' + options.itemTextShadowColor + ', 1px -1px 2px ' + options.itemTextShadowColor + ', -1px 1px 2px ' + options.itemTextShadowColor + ', 1px 1px 1px ' + options.itemTextShadowColor,
left: '50%',
top: '50%',
'min-height': width + 'px',
width: width + 'px',
height: width + 'px',
marginLeft: rx1 - width / 2 + 'px',
marginTop: -ry1 - width / 2 + 'px'
});
var content = createElement({ class: 'cxtmenu-content' });
if (command.content instanceof HTMLElement) {
content.appendChild(command.content);
} else {
content.innerHTML = command.content;
}
setStyles(content, {
'width': width + 'px',
'height': width + 'px',
'vertical-align': 'middle',
'display': 'table-cell'
});
setStyles(content, command.contentStyle || {});
if (command.disabled === true || command.enabled === false) {
content.setAttribute('class', 'cxtmenu-content cxtmenu-disabled');
}
parent.appendChild(item);
item.appendChild(content);
theta1 += dtheta;
theta2 += dtheta;
}
}
function queueDrawBg(radius, rspotlight) {
redrawQueue.drawBg = [radius, rspotlight];
}
function drawBg(radius, rspotlight) {
c2d.globalCompositeOperation = 'source-over';
c2d.clearRect(0, 0, containerSize, containerSize);
// draw background items
c2d.fillStyle = options.fillColor;
var dtheta = 2 * Math.PI / commands.length;
var theta1 = Math.PI / 2;
var theta2 = theta1 + dtheta;
for (var index = 0; index < commands.length; index++) {
var command = commands[index];
if (command.fillColor) {
c2d.fillStyle = command.fillColor;
}
c2d.beginPath();
c2d.moveTo(radius + options.activePadding, radius + options.activePadding);
c2d.arc(radius + options.activePadding, radius + options.activePadding, radius, 2 * Math.PI - theta1, 2 * Math.PI - theta2, true);
c2d.closePath();
c2d.fill();
theta1 += dtheta;
theta2 += dtheta;
c2d.fillStyle = options.fillColor;
}
// draw separators between items
c2d.globalCompositeOperation = 'destination-out';
c2d.strokeStyle = 'white';
c2d.lineWidth = options.separatorWidth;
theta1 = Math.PI / 2;
theta2 = theta1 + dtheta;
for (var i = 0; i < commands.length; i++) {
var rx1 = radius * Math.cos(theta1);
var ry1 = radius * Math.sin(theta1);
c2d.beginPath();
c2d.moveTo(radius + options.activePadding, radius + options.activePadding);
c2d.lineTo(radius + options.activePadding + rx1, radius + options.activePadding - ry1);
c2d.closePath();
c2d.stroke();
theta1 += dtheta;
theta2 += dtheta;
}
c2d.fillStyle = 'white';
c2d.globalCompositeOperation = 'destination-out';
c2d.beginPath();
c2d.arc(radius + options.activePadding, radius + options.activePadding, rspotlight + options.spotlightPadding, 0, Math.PI * 2, true);
c2d.closePath();
c2d.fill();
c2d.globalCompositeOperation = 'source-over';
}
function queueDrawCommands(rx, ry, radius, theta, rspotlight) {
redrawQueue.drawCommands = [rx, ry, radius, theta, rspotlight];
}
function drawCommands(rx, ry, radius, theta, rs) {
var dtheta = 2 * Math.PI / commands.length;
var theta1 = Math.PI / 2;
var theta2 = theta1 + dtheta;
theta1 += dtheta * activeCommandI;
theta2 += dtheta * activeCommandI;
c2d.fillStyle = options.activeFillColor;
c2d.strokeStyle = 'black';
c2d.lineWidth = 1;
c2d.beginPath();
c2d.moveTo(radius + options.activePadding, radius + options.activePadding);
c2d.arc(radius + options.activePadding, radius + options.activePadding, radius + options.activePadding, 2 * Math.PI - theta1, 2 * Math.PI - theta2, true);
c2d.closePath();
c2d.fill();
c2d.fillStyle = 'white';
c2d.globalCompositeOperation = 'destination-out';
var tx = radius + options.activePadding + rx / radius * (rs + options.spotlightPadding - options.indicatorSize / 4);
var ty = radius + options.activePadding + ry / radius * (rs + options.spotlightPadding - options.indicatorSize / 4);
var rot = Math.PI / 4 - theta;
c2d.translate(tx, ty);
c2d.rotate(rot);
// clear the indicator
// The indicator size (arrow) depends on the node size as well. If the indicator size is bigger and the rendered node size + padding,
// use the rendered node size + padding as the indicator size.
var indicatorSize = options.indicatorSize > rs + options.spotlightPadding ? rs + options.spotlightPadding : options.indicatorSize;
c2d.beginPath();
c2d.fillRect(-indicatorSize / 2, -indicatorSize / 2, indicatorSize, indicatorSize);
c2d.closePath();
c2d.fill();
c2d.rotate(-rot);
c2d.translate(-tx, -ty);
// c2d.setTransform( 1, 0, 0, 1, 0, 0 );
// clear the spotlight
c2d.beginPath();
c2d.arc(radius + options.activePadding, radius + options.activePadding, rs + options.spotlightPadding, 0, Math.PI * 2, true);
c2d.closePath();
c2d.fill();
c2d.globalCompositeOperation = 'source-over';
}
function updatePixelRatio() {
var pxr = getPixelRatio();
var w = containerSize;
var h = containerSize;
canvas.width = w * pxr;
canvas.height = h * pxr;
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
c2d.setTransform(1, 0, 0, 1, 0, 0);
c2d.scale(pxr, pxr);
}
var redrawing = true;
var redrawQueue = {};
var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (fn) {
return setTimeout(fn, 16);
};
var redraw = function redraw() {
if (redrawQueue.drawBg) {
drawBg.apply(null, redrawQueue.drawBg);
}
if (redrawQueue.drawCommands) {
drawCommands.apply(null, redrawQueue.drawCommands);
}
redrawQueue = {};
if (redrawing) {
raf(redraw);
}
};
// kick off
updatePixelRatio();
redraw();
var ctrx = void 0,
ctry = void 0,
rs = void 0;
var bindings = {
on: function on(events, selector, fn) {
var _fn = fn;
if (selector === 'core') {
_fn = function _fn(e) {
if (e.cyTarget === cy || e.target === cy) {
// only if event target is directly core
return fn.apply(this, [e]);
}
};
}
data.handlers.push({
events: events,
selector: selector,
fn: _fn
});
if (selector === 'core') {
cy.on(events, _fn);
} else {
cy.on(events, selector, _fn);
}
return this;
}
};
function addEventListeners() {
var grabbable = void 0;
var inGesture = false;
var dragHandler = void 0;
var zoomEnabled = void 0;
var panEnabled = void 0;
var boxEnabled = void 0;
var gestureStartEvent = void 0;
var hoverOn = void 0;
var restoreZoom = function restoreZoom() {
if (zoomEnabled) {
cy.userZoomingEnabled(true);
}
};
var restoreGrab = function restoreGrab() {
if (grabbable) {
target.grabify();
}
};
var restorePan = function restorePan() {
if (panEnabled) {
cy.userPanningEnabled(true);
}
};
var restoreBoxSeln = function restoreBoxSeln() {
if (boxEnabled) {
cy.boxSelectionEnabled(true);
}
};
var restoreGestures = function restoreGestures() {
restoreGrab();
restoreZoom();
restorePan();
restoreBoxSeln();
};
window.addEventListener('resize', updatePixelRatio);
bindings.on('resize', function () {
updatePixelRatio();
}).on(options.openMenuEvents, options.selector, function (e) {
target = this; // Remember which node the context menu is for
var ele = this;
var isCy = this === cy;
if (inGesture) {
parent.style.display = 'none';
inGesture = false;
restoreGestures();
}
if (typeof options.commands === 'function') {
var res = options.commands(target);
if (res.then) {
res.then(function (_commands) {
commands = _commands;
openMenu();
});
} else {
commands = res;
openMenu();
}
} else {
commands = options.commands;
openMenu();
}
function openMenu() {
if (!commands || commands.length === 0) {
return;
}
zoomEnabled = cy.userZoomingEnabled();
cy.userZoomingEnabled(false);
panEnabled = cy.userPanningEnabled();
cy.userPanningEnabled(false);
boxEnabled = cy.boxSelectionEnabled();
cy.boxSelectionEnabled(false);
grabbable = target.grabbable && target.grabbable();
if (grabbable) {
target.ungrabify();
}
var rp = void 0,
rw = void 0,
rh = void 0,
rs = void 0;
if (!isCy && ele && ele.isNode instanceof Function && ele.isNode() && !ele.isParent() && !options.atMouse) {
// If it's a node, the default spotlight radius for a node is the node width
rp = ele.renderedPosition();
rw = ele.renderedOuterWidth();
rh = ele.renderedOuterHeight();
rs = rw / 2;
// If adaptativeNodespotlightRadius is not enabled and min|maxSpotlighrRadius is defined, use those instead
rs = !options.adaptativeNodeSpotlightRadius && options.minSpotlightRadius ? Math.max(rs, options.minSpotlightRadius) : rs;
rs = !options.adaptativeNodeSpotlightRadius && options.maxSpotlightRadius ? Math.min(rs, options.maxSpotlightRadius) : rs;
} else {
// If it's the background or an edge, the spotlight radius is the min|maxSpotlightRadius
rp = e.renderedPosition || e.cyRenderedPosition;
rw = 1;
rh = 1;
rs = rw / 2;
rs = options.minSpotlightRadius ? Math.max(rs, options.minSpotlightRadius) : rs;
rs = options.maxSpotlightRadius ? Math.min(rs, options.maxSpotlightRadius) : rs;
}
offset = getOffset(container);
ctrx = rp.x;
ctry = rp.y;
r = rw / 2 + (options.menuRadius instanceof Function ? options.menuRadius(target) : Number(options.menuRadius));
containerSize = (r + options.activePadding) * 2;
updatePixelRatio();
setStyles(parent, {
width: containerSize + 'px',
height: containerSize + 'px',
display: 'block',
left: rp.x - r + 'px',
top: rp.y - r + 'px'
});
createMenuItems(r, rs);
queueDrawBg(r, rs);
activeCommandI = undefined;
inGesture = true;
gestureStartEvent = e;
}
}).on('cxtdrag tapdrag', options.selector, dragHandler = function dragHandler(e) {
if (!inGesture) {
return;
}
e.preventDefault(); // Otherwise, on mobile, the pull-down refresh gesture gets activated
var origE = e.originalEvent;
var isTouch = origE.touches && origE.touches.length > 0;
var pageX = (isTouch ? origE.touches[0].pageX : origE.pageX) - window.pageXOffset;
var pageY = (isTouch ? origE.touches[0].pageY : origE.pageY) - window.pageYOffset;
activeCommandI = undefined;
var dx = pageX - offset.left - ctrx;
var dy = pageY - offset.top - ctry;
if (dx === 0) {
dx = 0.01;
}
var d = Math.sqrt(dx * dx + dy * dy);
var cosTheta = (dy * dy - d * d - dx * dx) / (-2 * d * dx);
var theta = Math.acos(cosTheta);
var rw = void 0;
if (target && target.isNode instanceof Function && target.isNode() && !target.isParent() && !options.atMouse) {
// If it's a node, the default spotlight radius for a node is the node width
rw = target.renderedOuterWidth();
rs = rw / 2;
// If adaptativeNodespotlightRadius is not enabled and min|maxSpotlighrRadius is defined, use those instead
rs = !options.adaptativeNodeSpotlightRadius && options.minSpotlightRadius ? Math.max(rs, options.minSpotlightRadius) : rs;
rs = !options.adaptativeNodeSpotlightRadius && options.maxSpotlightRadius ? Math.min(rs, options.maxSpotlightRadius) : rs;
} else {
// If it's the background or an edge, the spotlight radius is the min|maxSpotlightRadius
rw = 1;
rs = rw / 2;
rs = options.minSpotlightRadius ? Math.max(rs, options.minSpotlightRadius) : rs;
rs = options.maxSpotlightRadius ? Math.min(rs, options.maxSpotlightRadius) : rs;
}
r = rw / 2 + (options.menuRadius instanceof Function ? options.menuRadius(target) : Number(options.menuRadius));
if (d < rs + options.spotlightPadding || typeof options.outsideMenuCancel === "number" && d > r + options.activePadding + options.outsideMenuCancel) {
//
queueDrawBg(r, rs);
return;
}
queueDrawBg(r, rs);
var rx = dx * r / d;
var ry = dy * r / d;
if (dy > 0) {
theta = Math.PI + Math.abs(theta - Math.PI);
}
var dtheta = 2 * Math.PI / commands.length;
var theta1 = Math.PI / 2;
var theta2 = theta1 + dtheta;
for (var i = 0; i < commands.length; i++) {
var command = commands[i];
var inThisCommand = theta1 <= theta && theta <= theta2 || theta1 <= theta + 2 * Math.PI && theta + 2 * Math.PI <= theta2;
if (command.disabled === true || command.enabled === false) {
inThisCommand = false;
}
if (inThisCommand) {
activeCommandI = i;
break;
}
theta1 += dtheta;
theta2 += dtheta;
}
queueDrawCommands(rx, ry, r, theta, rs);
}).on('tapdrag', dragHandler).on('mousemove', function () {
if (activeCommandI !== undefined) {
var hovered = commands[activeCommandI].hover;
if (hovered) {
if (hoverOn !== activeCommandI) {
hovered.apply(target, [target, gestureStartEvent]);
}
hoverOn = activeCommandI;
}
}
}).on('cxttapend tapend', function () {
parent.style.display = 'none';
if (activeCommandI !== undefined) {
var select = commands[activeCommandI].select;
if (select) {
select.apply(target, [target, gestureStartEvent]);
activeCommandI = undefined;
}
}
hoverOn = undefined;
inGesture = false;
restoreGestures();
});
}
function removeEventListeners() {
var handlers = data.handlers;
for (var i = 0; i < handlers.length; i++) {
var h = handlers[i];
if (h.selector === 'core') {
cy.off(h.events, h.fn);
} else {
cy.off(h.events, h.selector, h.fn);
}
}
window.removeEventListener('resize', updatePixelRatio);
}
function destroyInstance() {
redrawing = false;
removeEventListeners();
wrapper.remove();
}
addEventListeners();
return {
destroy: function destroy() {
destroyInstance();
}
};
};
module.exports = cxtmenu;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
// Simple, internal Object.assign() polyfill for options objects etc.
module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) {
for (var _len = arguments.length, srcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
srcs[_key - 1] = arguments[_key];
}
srcs.filter(function (src) {
return src != null;
}).forEach(function (src) {
Object.keys(src).forEach(function (k) {
return tgt[k] = src[k];
});
});
return tgt;
};
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var defaults = {
menuRadius: function menuRadius(ele) {
return 100;
}, // the radius of the circular menu in pixels
selector: 'node', // elements matching this Cytoscape.js selector will trigger cxtmenus
commands: [// an array of commands to list in the menu or a function that returns the array
/*
{ // example command
fillColor: 'rgba(200, 200, 200, 0.75)', // optional: custom background color for item
content: 'a command name' // html/text content to be displayed in the menu
contentStyle: {}, // css key:value pairs to set the command's css in js if you want
hover: function(ele){ // a function to execute when the command is hovered
console.log( ele.id() ) // `ele` holds the reference to the active element
},
select: function(ele){ // a function to execute when the command is selected
console.log( ele.id() ) // `ele` holds the reference to the active element
},
enabled: true // whether the command is selectable
}
*/
], // function( ele ){ return [ /*...*/ ] }, // example function for commands
fillColor: 'rgba(0, 0, 0, 0.75)', // the background colour of the menu
activeFillColor: 'rgba(1, 105, 217, 0.75)', // the colour used to indicate the selected command
activePadding: 20, // additional size in pixels for the active command
indicatorSize: 24, // the size in pixels of the pointer to the active command, will default to the node size if the node size is smaller than the indicator size,
separatorWidth: 3, // the empty spacing in pixels between successive commands
spotlightPadding: 4, // extra spacing in pixels between the element and the spotlight
adaptativeNodeSpotlightRadius: false, // specify whether the spotlight radius should adapt to the node size
minSpotlightRadius: 24, // the minimum radius in pixels of the spotlight (ignored for the node if adaptativeNodeSpotlightRadius is enabled but still used for the edge & background)
maxSpotlightRadius: 38, // the maximum radius in pixels of the spotlight (ignored for the node if adaptativeNodeSpotlightRadius is enabled but still used for the edge & background)
openMenuEvents: 'cxttapstart taphold', // space-separated cytoscape events that will open the menu; only `cxttapstart` and/or `taphold` work here
itemColor: 'white', // the colour of text in the command's content
itemTextShadowColor: 'transparent', // the text shadow colour of the command's content
zIndex: 9999, // the z-index of the ui div
atMouse: false, // draw menu at mouse position
outsideMenuCancel: false // if set to a number, this will cancel the command if the pointer is released outside of the spotlight, padded by the number given
};
module.exports = defaults;
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var removeEles = function removeEles(query) {
var ancestor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
var els = ancestor.querySelectorAll(query);
for (var i = 0; i < els.length; i++) {
var el = els[i];
el.parentNode.removeChild(el);
}
};
var setStyles = function setStyles(el, style) {
var props = Object.keys(style);
for (var i = 0, l = props.length; i < l; i++) {
el.style[props[i]] = style[props[i]];
}
};
var createElement = function createElement(options) {
options = options || {};
var el = document.createElement(options.tag || 'div');
el.className = options.class || '';
if (options.style) {
setStyles(el, options.style);
}
return el;
};
var getPixelRatio = function getPixelRatio() {
return window.devicePixelRatio || 1;
};
var getOffset = function getOffset(el) {
var offset = el.getBoundingClientRect();
return {
left: offset.left + document.body.scrollLeft + parseFloat(getComputedStyle(document.body)['padding-left']) + parseFloat(getComputedStyle(document.body)['border-left-width']),
top: offset.top + document.body.scrollTop + parseFloat(getComputedStyle(document.body)['padding-top']) + parseFloat(getComputedStyle(document.body)['border-top-width'])
};
};
module.exports = { removeEles: removeEles, setStyles: setStyles, createElement: createElement, getPixelRatio: getPixelRatio, getOffset: getOffset };
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var cxtmenu = __webpack_require__(0);
// registers the extension on a cytoscape lib ref
var register = function register(cytoscape) {
if (!cytoscape) {
return;
} // can't register if cytoscape unspecified
cytoscape('core', 'cxtmenu', cxtmenu); // register with cytoscape.js
};
if (typeof cytoscape !== 'undefined') {
// expose to global cytoscape (i.e. window.cytoscape)
register(cytoscape);
}
module.exports = register;
/***/ })
/******/ ]);
});

View File

@ -19,8 +19,9 @@
/* background-color: #f8f9fa; /1* optional: subtle background for log readability *1/ */ /* background-color: #f8f9fa; /1* optional: subtle background for log readability *1/ */
} }
.log-info, .log-warning, .log-error, .log-fatal, .log-debug{ font-family: "Roboto Mono", "Consolas", "Courier New", monospace; .log-info, .log-warning, .log-error, .log-fatal, .log-debug{
font-size: 14px; font-family: "Roboto Mono", "Consolas", "Courier New", monospace;
font-size: 14px;
} }
.log-info { .log-info {

View File

@ -9,6 +9,7 @@
<script type="text/javascript" src="static/bootstrap/bootstrap.bundle.min.js"></script> <script type="text/javascript" src="static/bootstrap/bootstrap.bundle.min.js"></script>
<script type="text/javascript" src="static/htmx/htmx.org@1.9.12.js"></script> <script type="text/javascript" src="static/htmx/htmx.org@1.9.12.js"></script>
<script type="text/javascript" src="static/cytoscape.min.js"></script> <script type="text/javascript" src="static/cytoscape.min.js"></script>
<script type="text/javascript" src="static/cytoscape-cxtmenu.js"></script>
<script type="text/javascript" src="static/agents-graph.js"></script> <script type="text/javascript" src="static/agents-graph.js"></script>
<script type="text/javascript" src="static/gontrol-helper.js"></script> <script type="text/javascript" src="static/gontrol-helper.js"></script>