mirror of https://github.com/MISP/MISP
new: [workflow:editor] Added support of frame nodes in editor and drawflow lib
parent
68ca753593
commit
9ddc335c07
|
@ -6,16 +6,18 @@ class GraphUtil
|
|||
{
|
||||
public function __construct($graphData)
|
||||
{
|
||||
$this->graph = $graphData;
|
||||
$this->graph = array_filter($graphData, function($i) {
|
||||
return $i != '_frames';
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
$this->numberNodes = count($this->graph);
|
||||
$this->edgeList = $this->_buildEdgeList($graphData);
|
||||
$this->edgeList = $this->_buildEdgeList($this->graph);
|
||||
$this->properties = [];
|
||||
}
|
||||
|
||||
private function _buildEdgeList($graphData): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($graphData as $node) {
|
||||
foreach ($graphData as $i => $node) {
|
||||
$list[(int)$node['id']] = [];
|
||||
foreach (($node['outputs'] ?? []) as $output_id => $outputs) {
|
||||
foreach ($outputs as $connections) {
|
||||
|
@ -356,6 +358,20 @@ class WorkflowRoamingData
|
|||
|
||||
class WorkflowGraphTool
|
||||
{
|
||||
|
||||
/**
|
||||
* cleanGraphData Remove frame nodes from the graph data
|
||||
*
|
||||
* @param array $graphData
|
||||
* @return array
|
||||
*/
|
||||
public static function cleanGraphData(array $graphData): array
|
||||
{
|
||||
return array_filter($graphData, function($i) {
|
||||
return $i != '_frames';
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* extractTriggerFromWorkflow Return the trigger id (or full module) that are specified in the workflow
|
||||
*
|
||||
|
@ -382,8 +398,9 @@ class WorkflowGraphTool
|
|||
*/
|
||||
public static function extractTriggersFromWorkflow(array $graphData, bool $fullNode = false): array
|
||||
{
|
||||
$graphData = self::cleanGraphData($graphData);
|
||||
$triggers = [];
|
||||
foreach ($graphData as $node) {
|
||||
foreach ($graphData as $i => $node) {
|
||||
if ($node['data']['module_type'] == 'trigger') {
|
||||
if (!empty($fullNode)) {
|
||||
$triggers[] = $node;
|
||||
|
@ -404,8 +421,9 @@ class WorkflowGraphTool
|
|||
*/
|
||||
public static function extractConcurrentTasksFromWorkflow(array $graphData, bool $fullNode = false): array
|
||||
{
|
||||
$graphData = self::cleanGraphData($graphData);
|
||||
$nodes = [];
|
||||
foreach ($graphData as $node) {
|
||||
foreach ($graphData as $i => $node) {
|
||||
if ($node['data']['module_type'] == 'logic' && $node['data']['id'] == 'concurrent-task') {
|
||||
if (!empty($fullNode)) {
|
||||
$nodes[] = $node;
|
||||
|
@ -426,8 +444,9 @@ class WorkflowGraphTool
|
|||
*/
|
||||
public static function extractFilterNodesFromWorkflow(array $graphData, bool $fullNode = false): array
|
||||
{
|
||||
$graphData = self::cleanGraphData($graphData);
|
||||
$nodes = [];
|
||||
foreach ($graphData as $node) {
|
||||
foreach ($graphData as $i => $node) {
|
||||
if ($node['data']['module_type'] == 'logic' && $node['data']['id'] == 'generic-filter-data') {
|
||||
if (!empty($fullNode)) {
|
||||
$nodes[] = $node;
|
||||
|
@ -448,8 +467,9 @@ class WorkflowGraphTool
|
|||
*/
|
||||
public static function extractResetFilterFromWorkflow(array $graphData, bool $fullNode = false): array
|
||||
{
|
||||
$graphData = self::cleanGraphData($graphData);
|
||||
$nodes = [];
|
||||
foreach ($graphData as $node) {
|
||||
foreach ($graphData as $i => $node) {
|
||||
if ($node['data']['module_type'] == 'logic' && $node['data']['id'] == 'generic-filter-reset') {
|
||||
if (!empty($fullNode)) {
|
||||
$nodes[] = $node;
|
||||
|
|
|
@ -1373,6 +1373,9 @@ class Workflow extends AppModel
|
|||
}
|
||||
$labelsByNodes = $this->getLabelsForConnections($workflow, $trigger_id);
|
||||
foreach ($graphData as $i => $node) {
|
||||
if ($i == '_frames') {
|
||||
continue;
|
||||
}
|
||||
if (!empty($labelsByNodes[$node['id']])) {
|
||||
foreach ($node['inputs'] as $inputName => $inputs) {
|
||||
foreach ($inputs['connections'] as $j => $connection) {
|
||||
|
|
|
@ -179,6 +179,9 @@ $debugEnabled = !empty($selectedWorkflow['Workflow']['debug_enabled']);
|
|||
<button id="control-duplicate" class="btn btn-small btn-primary disabled" type="button" title="<?= __('Duplicate') ?>">
|
||||
<i class="fa-fw <?= $this->FontAwesome->getClass('clone') ?>"></i> <?= __('Duplicate') ?>
|
||||
</button>
|
||||
<button id="control-frame-node" class="btn btn-small btn-primary disabled" type="button" title="<?= __('Create frame node') ?>">
|
||||
<i class="fa-fw <?= $this->FontAwesome->getClass('object-group') ?>"></i> <?= __('Frame') ?>
|
||||
</button>
|
||||
<button id="control-delete" class="btn btn-small btn-danger disabled" type="button" title="<?= __('Delete') ?>">
|
||||
<i class="fa-fw <?= $this->FontAwesome->getClass('trash') ?>"></i> <?= __('Delete') ?>
|
||||
</button>
|
||||
|
@ -309,6 +312,7 @@ echo $this->element('genericElements/assetLoader', [
|
|||
var $blockNotificationModal = $('#block-notifications-modal')
|
||||
var $blockFilteringModal = $('#block-filtering-modal')
|
||||
var $controlDuplicateButton = $('.control-buttons #control-duplicate')
|
||||
var $controlFrameNodeButton = $('.control-buttons #control-frame-node')
|
||||
var $controlDeleteButton = $('.control-buttons #control-delete')
|
||||
var $controlExportBlocksLi = $('.control-buttons #control-export-blocks')
|
||||
var $controlSaveBlocksLi = $('.control-buttons #control-save-blocks')
|
||||
|
|
|
@ -425,6 +425,13 @@
|
|||
background-color: #2fa1db;
|
||||
} */
|
||||
|
||||
.drawflow .drawflow-framenode.selected {
|
||||
box-shadow: 0 0 5px 2px #33333366;
|
||||
}
|
||||
.drawflow .drawflow-framenode .drawflow-framenode-text {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.drawflow .drawflow-node > .inputs > .input::before {
|
||||
line-height: var(--dfInputHeight);
|
||||
vertical-align: top;
|
||||
|
|
|
@ -6,6 +6,7 @@ class Drawflow {
|
|||
this.nodeId = 1;
|
||||
this.ele_selected = null;
|
||||
this.node_selected = null;
|
||||
this.framenode_selected = null;
|
||||
this.drag = false;
|
||||
this.reroute = false;
|
||||
this.reroute_fix_curvature = false;
|
||||
|
@ -32,10 +33,12 @@ class Drawflow {
|
|||
this.draggable_inputs = true;
|
||||
this.useuuid = false;
|
||||
this.parent = parent;
|
||||
this.frame_to_nodes_registry = {};
|
||||
this.nodes_to_frames_registry = {};
|
||||
|
||||
this.noderegister = {};
|
||||
this.render = render;
|
||||
this.drawflow = { "drawflow": { "Home": { "data": {} } } };
|
||||
this.drawflow = { "drawflow": { "Home": { "data": { _frames: {}} } } };
|
||||
// Configurable options
|
||||
this.module = 'Home';
|
||||
this.editor_mode = 'edit';
|
||||
|
@ -139,7 +142,9 @@ class Drawflow {
|
|||
/* End Mobile Zoom */
|
||||
load() {
|
||||
for (var key in this.drawflow.drawflow[this.module].data) {
|
||||
this.addNodeImport(this.drawflow.drawflow[this.module].data[key], this.precanvas);
|
||||
if (!key.startsWith('_')) {
|
||||
this.addNodeImport(this.drawflow.drawflow[this.module].data[key], this.precanvas);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.reroute) {
|
||||
|
@ -212,6 +217,10 @@ class Drawflow {
|
|||
this.removeReouteConnectionSelected();
|
||||
this.connection_selected = null;
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
if (this.node_selected != this.ele_selected) {
|
||||
this.dispatch('nodeSelected', this.ele_selected.id.slice(5));
|
||||
}
|
||||
|
@ -239,8 +248,30 @@ class Drawflow {
|
|||
this.removeReouteConnectionSelected();
|
||||
this.connection_selected = null;
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
this.drawConnection(e.target);
|
||||
break;
|
||||
case 'drawflow-framenode':
|
||||
if (this.node_selected != null) {
|
||||
this.node_selected.classList.remove("selected");
|
||||
this.node_selected = null
|
||||
this.dispatch('nodeUnselected', true);
|
||||
}
|
||||
if (this.connection_selected != null) {
|
||||
this.connection_selected.classList.remove("selected");
|
||||
this.removeReouteConnectionSelected();
|
||||
this.connection_selected = null;
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
this.framenode_selected = this.ele_selected;
|
||||
this.framenode_selected.classList.add("selected");
|
||||
break;
|
||||
case 'parent-drawflow':
|
||||
if (this.node_selected != null) {
|
||||
this.node_selected.classList.remove("selected");
|
||||
|
@ -252,6 +283,10 @@ class Drawflow {
|
|||
this.removeReouteConnectionSelected();
|
||||
this.connection_selected = null;
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
this.editor_selected = true;
|
||||
break;
|
||||
case 'drawflow':
|
||||
|
@ -265,6 +300,10 @@ class Drawflow {
|
|||
this.removeReouteConnectionSelected();
|
||||
this.connection_selected = null;
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
this.editor_selected = true;
|
||||
break;
|
||||
case 'main-path':
|
||||
|
@ -273,6 +312,10 @@ class Drawflow {
|
|||
this.node_selected = null;
|
||||
this.dispatch('nodeUnselected', true);
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
if (this.connection_selected != null) {
|
||||
this.connection_selected.classList.remove("selected");
|
||||
this.removeReouteConnectionSelected();
|
||||
|
@ -303,6 +346,10 @@ class Drawflow {
|
|||
this.removeConnection();
|
||||
}
|
||||
|
||||
if (this.framenode_selected) {
|
||||
this.removeFrameNodeId(this.framenode_selected.id);
|
||||
}
|
||||
|
||||
if (this.node_selected != null) {
|
||||
this.node_selected.classList.remove("selected");
|
||||
this.node_selected = null;
|
||||
|
@ -313,6 +360,10 @@ class Drawflow {
|
|||
this.removeReouteConnectionSelected();
|
||||
this.connection_selected = null;
|
||||
}
|
||||
if (this.framenode_selected != null) {
|
||||
this.framenode_selected.classList.remove("selected");
|
||||
this.framenode_selected = null
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
|
@ -543,6 +594,21 @@ class Drawflow {
|
|||
|
||||
}
|
||||
|
||||
} else {
|
||||
if (e.target.classList.contains('drawflow-framenode') || e.target.classList.contains('drawflow-framenode-text')) {
|
||||
const frameNode = e.target.classList.contains('drawflow-framenode') ? e.target : e.target.parentElement
|
||||
const deletebox = document.createElement('div');
|
||||
deletebox.classList.add("drawflow-delete");
|
||||
deletebox.innerHTML = "x";
|
||||
|
||||
const frameNodeBCR = frameNode.getBoundingClientRect()
|
||||
const pos_y = frameNodeBCR.top - 30 // roughly include margin and node size
|
||||
const pos_x = frameNodeBCR.left + frameNodeBCR.width
|
||||
deletebox.style.top = pos_y * (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom)) - (this.precanvas.getBoundingClientRect().y * (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom))) + "px";
|
||||
deletebox.style.left = pos_x * (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom)) - (this.precanvas.getBoundingClientRect().x * (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom))) + "px";
|
||||
|
||||
this.precanvas.appendChild(deletebox);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1092,6 +1158,16 @@ class Drawflow {
|
|||
|
||||
}
|
||||
})
|
||||
|
||||
var nodeIdInt = parseInt(id.slice(5));
|
||||
if (this.nodes_to_frames_registry[nodeIdInt] !== undefined && this.nodes_to_frames_registry[nodeIdInt].length > 0) {
|
||||
var _this = this;
|
||||
this.nodes_to_frames_registry[nodeIdInt].forEach((frameNodeUuid) => {
|
||||
const containedNodeIDs = _this.frame_to_nodes_registry[frameNodeUuid]
|
||||
const frameNode = _this.container.querySelector('#framenode-' + frameNodeUuid)
|
||||
_this.updateFrameNodePosition(frameNode, containedNodeIDs);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
dblclick(e) {
|
||||
|
@ -1516,6 +1592,133 @@ class Drawflow {
|
|||
});
|
||||
}
|
||||
|
||||
addFrameNode(frameNodeConfig) {
|
||||
frameNodeConfig.nodes = [...new Set(frameNodeConfig.nodes)]
|
||||
if (frameNodeConfig.nodes.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
var newNodeId = this.getUuid();
|
||||
this.frame_to_nodes_registry[newNodeId] = frameNodeConfig.nodes
|
||||
frameNodeConfig.nodes.forEach(nodeId => {
|
||||
if (this.nodes_to_frames_registry[nodeId] === undefined) {
|
||||
this.nodes_to_frames_registry[nodeId] = []
|
||||
}
|
||||
this.nodes_to_frames_registry[nodeId].push(newNodeId)
|
||||
});
|
||||
const parent = document.createElement('div');
|
||||
parent.classList.add(...["parent-framenode"]);
|
||||
|
||||
const frameNode = document.createElement('div');
|
||||
frameNode.innerHTML = '';
|
||||
frameNode.setAttribute('id', 'framenode-' + newNodeId);
|
||||
frameNode.classList.add(...['drawflow-framenode']);
|
||||
if (frameNodeConfig.class !== undefined && frameNodeConfig.class != '') {
|
||||
frameNode.classList.add(...frameNodeConfig.class.split(' '));
|
||||
}
|
||||
|
||||
frameNode.style.backgroundColor = '#b5b5b570';
|
||||
frameNode.style.border = '1px solid #333333aa';
|
||||
frameNode.style.position = 'absolute'
|
||||
frameNode.style['text-align'] = 'center'
|
||||
frameNode.style['z-index'] = -1
|
||||
frameNode.style['border-radius'] = '5px'
|
||||
this.updateFrameNodePosition(frameNode, frameNodeConfig.nodes)
|
||||
|
||||
const frameTextNode = document.createElement('span');
|
||||
frameTextNode.innerText = frameNodeConfig.text;
|
||||
frameTextNode.style['line-height'] = '1.5em'
|
||||
frameTextNode.style.color = '#505050'
|
||||
frameTextNode.style['font-size'] = '1.5em'
|
||||
frameTextNode.style['font-weight'] = 'bold'
|
||||
frameTextNode.classList.add('drawflow-framenode-text')
|
||||
frameNode.appendChild(frameTextNode);
|
||||
|
||||
parent.appendChild(frameNode);
|
||||
this.precanvas.appendChild(parent);
|
||||
|
||||
var json = {
|
||||
id: newNodeId,
|
||||
text: frameNodeConfig.text,
|
||||
nodes: frameNodeConfig.nodes,
|
||||
class: frameNodeConfig.class,
|
||||
}
|
||||
this.drawflow.drawflow[this.module].data._frames[newNodeId] = json;
|
||||
this.dispatch('frameNodeCreated', newNodeId);
|
||||
}
|
||||
|
||||
updateFrameNodePosition(frameNode, nodeIDs) {
|
||||
if (!frameNode) {
|
||||
return
|
||||
}
|
||||
|
||||
var offsetPadding = {
|
||||
top: 100,
|
||||
left: 30,
|
||||
right: 30,
|
||||
bottom: 20,
|
||||
}
|
||||
|
||||
var _this = this
|
||||
var nodesHtml = nodeIDs.map(function (id) {
|
||||
return _this.container.querySelector('#node-' + id)
|
||||
})
|
||||
|
||||
function getFrameCoordinatesFromNodes(nodesHtml) {
|
||||
var topLeft = { x: Number.MAX_SAFE_INTEGER, y: Number.MAX_SAFE_INTEGER }
|
||||
var bottomRight = { x: Number.MIN_SAFE_INTEGER, y: Number.MIN_SAFE_INTEGER }
|
||||
nodesHtml.forEach(function (nodeHtml) {
|
||||
var bcr = nodeHtml.getBoundingClientRect()
|
||||
topLeft.x = Math.min(topLeft.x, bcr.left)
|
||||
topLeft.y = Math.min(topLeft.y, bcr.top)
|
||||
bottomRight.x = Math.max(bottomRight.x, bcr.left + bcr.width)
|
||||
bottomRight.y = Math.max(bottomRight.y, bcr.top + bcr.height)
|
||||
})
|
||||
return { topLeft: topLeft, bottomRight: bottomRight }
|
||||
}
|
||||
|
||||
var frameCoordinates = getFrameCoordinatesFromNodes(nodesHtml)
|
||||
|
||||
var pos_x = frameCoordinates.topLeft.x - offsetPadding.left / 2
|
||||
var pos_y = frameCoordinates.topLeft.y - offsetPadding.top / 2
|
||||
var frameNodeHeight = (frameCoordinates.bottomRight.y - frameCoordinates.topLeft.y) + (offsetPadding.top / 2 + offsetPadding.bottom)
|
||||
var frameNodeWidth = (frameCoordinates.bottomRight.x - frameCoordinates.topLeft.x) + (offsetPadding.left / 2 + offsetPadding.right)
|
||||
|
||||
var transpositionFactorX = (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom))
|
||||
var transpositionOffsetX = -(this.precanvas.getBoundingClientRect().x * (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom)))
|
||||
var transpositionFactorY = (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom))
|
||||
var transpositionOffsetY = -(this.precanvas.getBoundingClientRect().y * (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom)))
|
||||
|
||||
pos_x = pos_x * transpositionFactorX + transpositionOffsetX
|
||||
pos_y = pos_y * transpositionFactorY + transpositionOffsetY
|
||||
frameNodeWidth = frameNodeWidth * transpositionFactorX
|
||||
frameNodeHeight = frameNodeHeight * transpositionFactorY
|
||||
|
||||
frameNode.style.top = pos_y + 'px';
|
||||
frameNode.style.left = pos_x + 'px';
|
||||
frameNode.style.height = frameNodeHeight + 'px';
|
||||
frameNode.style.width = frameNodeWidth + 'px';
|
||||
}
|
||||
|
||||
updateFramenode(frameNodeUuid, frameNodeConfig) {
|
||||
const frameNode = document.getElementById(frameNodeUuid);
|
||||
frameNode.classList.remove(...frameNode.classList)
|
||||
frameNode.classList.add(...['drawflow-framenode']);
|
||||
if (frameNodeConfig.class !== undefined && frameNodeConfig.class != '') {
|
||||
frameNode.classList.add(...frameNodeConfig.class.split(' '));
|
||||
}
|
||||
|
||||
const frameTextNode = frameNode.querySelector('.drawflow-framenode-text')
|
||||
frameTextNode.innerText = frameNodeConfig.text
|
||||
|
||||
var oldJson = this.drawflow.drawflow[this.module].data._frames[frameNodeUuid.slice(10)]
|
||||
oldJson.text = frameNodeConfig.text
|
||||
oldJson.class = frameNodeConfig.class
|
||||
this.drawflow.drawflow[this.module].data._frames[frameNodeUuid.slice(10)] = oldJson;
|
||||
|
||||
this.dispatch('frameNodeUpdated', frameNodeUuid.slice(10));
|
||||
}
|
||||
|
||||
updateNodeValue(event) {
|
||||
var attr = event.target.attributes
|
||||
for (var i = 0; i < attr.length; i++) {
|
||||
|
@ -1769,6 +1972,25 @@ class Drawflow {
|
|||
if (this.module === moduleName) {
|
||||
this.container.querySelector(`#${id}`).remove();
|
||||
}
|
||||
|
||||
var _this = this
|
||||
var new_frame_to_nodes_registry = []
|
||||
if (this.nodes_to_frames_registry[id.slice(5)] !== undefined) {
|
||||
this.nodes_to_frames_registry[id.slice(5)].forEach((frameUuid) => {
|
||||
new_frame_to_nodes_registry = _this.frame_to_nodes_registry[frameUuid].filter((nodeId) => {
|
||||
return id.slice(5) != nodeId;
|
||||
})
|
||||
if (new_frame_to_nodes_registry.length == 0) {
|
||||
_this.removeFrameNodeId('framenode-' + frameUuid)
|
||||
} else {
|
||||
const frameNode = _this.container.querySelector('#framenode-' + frameUuid)
|
||||
_this.frame_to_nodes_registry[frameUuid] = new_frame_to_nodes_registry
|
||||
_this.updateFrameNodePosition(frameNode, _this.frame_to_nodes_registry[frameUuid]);
|
||||
}
|
||||
})
|
||||
delete this.nodes_to_frames_registry[id.slice(5)]
|
||||
}
|
||||
|
||||
delete this.drawflow.drawflow[moduleName].data[id.slice(5)];
|
||||
this.dispatch('nodeRemoved', id.slice(5));
|
||||
}
|
||||
|
@ -1874,6 +2096,21 @@ class Drawflow {
|
|||
}
|
||||
}
|
||||
|
||||
removeFrameNodeId(fullID) {
|
||||
const frameUuid = fullID.slice(10);
|
||||
const moduleName = this.getModuleFromNodeId(this.frame_to_nodes_registry[frameUuid][0])
|
||||
this.container.querySelector(`#${fullID}`).remove();
|
||||
var _this = this;
|
||||
this.frame_to_nodes_registry[frameUuid].forEach((nodeID) => {
|
||||
_this.nodes_to_frames_registry[nodeID] = _this.nodes_to_frames_registry[nodeID].filter((frameNodeUuid) => {
|
||||
return frameNodeUuid != frameUuid;
|
||||
})
|
||||
})
|
||||
delete this.frame_to_nodes_registry[frameUuid];
|
||||
delete this.drawflow.drawflow[moduleName].data._frames[frameUuid];
|
||||
this.dispatch('frameNodeRemoved', frameUuid);
|
||||
}
|
||||
|
||||
getModuleFromNodeId(id) {
|
||||
var nameModule;
|
||||
const editor = this.drawflow.drawflow
|
||||
|
@ -1888,7 +2125,7 @@ class Drawflow {
|
|||
}
|
||||
|
||||
addModule(name) {
|
||||
this.drawflow.drawflow[name] = { "data": {} };
|
||||
this.drawflow.drawflow[name] = { "data": { _frames: {} } };
|
||||
this.dispatch('moduleCreated', name);
|
||||
}
|
||||
changeModule(name) {
|
||||
|
@ -1917,12 +2154,16 @@ class Drawflow {
|
|||
|
||||
clearModuleSelected() {
|
||||
this.precanvas.innerHTML = "";
|
||||
this.drawflow.drawflow[this.module] = { "data": {} };
|
||||
this.drawflow.drawflow[this.module] = { "data": { _frames: {}} };
|
||||
this.frame_to_nodes_registry = {};
|
||||
this.nodes_to_frames_registry = {};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.precanvas.innerHTML = "";
|
||||
this.drawflow = { "drawflow": { "Home": { "data": {} } } };
|
||||
this.drawflow = { "drawflow": { "Home": { "data": { _frames: {} } } } };
|
||||
this.frame_to_nodes_registry = {};
|
||||
this.nodes_to_frames_registry = {};
|
||||
}
|
||||
export() {
|
||||
const dataExport = JSON.parse(JSON.stringify(this.drawflow));
|
||||
|
|
|
@ -175,6 +175,21 @@ function initDrawflow() {
|
|||
editor.on('nodeRemoved', function () {
|
||||
invalidateContentCache()
|
||||
})
|
||||
editor.on('frameNodeCreated', function (framenodeUuid) {
|
||||
invalidateContentCache()
|
||||
var frameNodeFullUuid = 'framenode-' + framenodeUuid
|
||||
$('#' + frameNodeFullUuid).find('.drawflow-framenode-text').dblclick(function() {
|
||||
var existingText = $(this).text()
|
||||
var newText = prompt('Edit frame node text', existingText)
|
||||
editor.updateFramenode(frameNodeFullUuid, {text: newText})
|
||||
})
|
||||
})
|
||||
editor.on('frameNodeUpdated', function () {
|
||||
invalidateContentCache()
|
||||
})
|
||||
editor.on('frameNodeRemoved', function () {
|
||||
invalidateContentCache()
|
||||
})
|
||||
editor.on('nodeDataChanged', invalidateContentCache)
|
||||
editor.on('nodeMoved', invalidateContentCache)
|
||||
editor.on('connectionCreated', function() {
|
||||
|
@ -204,6 +219,9 @@ function initDrawflow() {
|
|||
duplicateSelection()
|
||||
evt.preventDefault()
|
||||
}
|
||||
if (evt.keyCode == 70 && $drawflow.is(evt.target)) {
|
||||
createFrameNodeForSelected()
|
||||
}
|
||||
})
|
||||
editor.translate_to = function (x, y) {
|
||||
this.canvas_x = x;
|
||||
|
@ -415,6 +433,7 @@ function initDrawflow() {
|
|||
})
|
||||
editor.on('nodeSelected', function(node_id) {
|
||||
$controlDuplicateButton.removeClass('disabled')
|
||||
$controlFrameNodeButton.removeClass('disabled')
|
||||
$controlDeleteButton.removeClass('disabled')
|
||||
$controlSaveBlocksLi.removeClass('disabled')
|
||||
$controlEditBlocksLiContainer.removeClass('disabled').find('.dropdown-menu')
|
||||
|
@ -426,6 +445,7 @@ function initDrawflow() {
|
|||
})
|
||||
selection.clearSelection()
|
||||
$controlDuplicateButton.addClass('disabled')
|
||||
$controlFrameNodeButton.addClass('disabled')
|
||||
$controlDeleteButton.addClass('disabled')
|
||||
$controlSaveBlocksLi.addClass('disabled')
|
||||
$controlEditBlocksLiContainer.addClass('disabled').find('.dropdown-menu')
|
||||
|
@ -473,6 +493,9 @@ function initDrawflow() {
|
|||
$controlDuplicateButton.click(function() {
|
||||
duplicateSelection()
|
||||
})
|
||||
$controlFrameNodeButton.click(function() {
|
||||
createFrameNodeForSelected()
|
||||
})
|
||||
$controlDeleteButton.click(function() {
|
||||
deleteSelectedNodes(false)
|
||||
})
|
||||
|
@ -574,6 +597,26 @@ function duplicateSelection() {
|
|||
selection.select(newNodes)
|
||||
}
|
||||
|
||||
function createFrameNodeForSelected() {
|
||||
var text = prompt('Enter text for the frame node')
|
||||
var selectedNodesHtml = selection.getSelection()
|
||||
var selectedIDs = selectedNodesHtml.map(function(nodeHtml) {
|
||||
return parseInt(nodeHtml.id.slice(5))
|
||||
})
|
||||
createFrameForNodes(selectedIDs, text)
|
||||
selection.clearSelection()
|
||||
invalidateContentCache()
|
||||
}
|
||||
|
||||
function createFrameForNodes(nodesIDs, text) {
|
||||
const frameNode = {
|
||||
nodes: nodesIDs,
|
||||
text: text,
|
||||
class: "",
|
||||
}
|
||||
editor.addFrameNode(frameNode)
|
||||
}
|
||||
|
||||
function buildModalForBlock(node_id, node) {
|
||||
var html = genNodeParamHtml(node, false)
|
||||
$blockModal
|
||||
|
@ -701,6 +744,8 @@ function addNode(block, position, additionalData={}) {
|
|||
function getEditorData(cleanNodes) {
|
||||
var data = {} // Make sure nodes are index by their internal IDs
|
||||
var editorExport = editor.export().drawflow.Home.data
|
||||
var frameNodes = editorExport._frames !== undefined ? editorExport._frames : {}
|
||||
delete editorExport._frames
|
||||
editorExport = Array.isArray(editorExport) ? editorExport : Object.values(editorExport)
|
||||
editorExport.forEach(function(node) {
|
||||
if (node !== null) { // for some reason, the editor create null nodes
|
||||
|
@ -725,6 +770,7 @@ function getEditorData(cleanNodes) {
|
|||
data[node.id] = node
|
||||
}
|
||||
})
|
||||
data._frames = frameNodes
|
||||
return data
|
||||
}
|
||||
|
||||
|
@ -756,7 +802,12 @@ function loadWorkflow(workflow) {
|
|||
}
|
||||
// We cannot rely on the editor's import function as it recreates the nodes with the saved HTML instead of rebuilding them
|
||||
// We have to manually add the nodes and their connections
|
||||
Object.values(workflow.data).forEach(function (node) {
|
||||
Object.entries(workflow.data).forEach(function (entry) {
|
||||
var i = entry[0]
|
||||
var node = entry[1]
|
||||
if (i == '_frames') {
|
||||
return
|
||||
}
|
||||
var module = all_modules_by_id[node.data.id] || all_triggers_by_id[node.data.id]
|
||||
if (!module) {
|
||||
console.error('Tried to add node for unknown module ' + node.data.id + ' (' + node.id + ')')
|
||||
|
@ -818,6 +869,10 @@ function loadWorkflow(workflow) {
|
|||
})
|
||||
}
|
||||
})
|
||||
var frameNodes = workflow.data._frames || {}
|
||||
Object.values(frameNodes).forEach(function (frameNode) {
|
||||
editor.addFrameNode(frameNode)
|
||||
})
|
||||
}
|
||||
|
||||
function filterModules(clicked) {
|
||||
|
@ -1294,6 +1349,11 @@ function addWorkflowBlueprint(blueprintId, cursorPosition) {
|
|||
if (newNodes.length > 0) {
|
||||
selection.clearSelection()
|
||||
selection.select(newNodes)
|
||||
var newNodeIDs = newNodes.map(function(node) {
|
||||
return node.id.slice(5)
|
||||
})
|
||||
createFrameForNodes(newNodeIDs, workflowBlueprint.WorkflowBlueprint.name)
|
||||
|
||||
editor.dispatch('nodeSelected', newNodes[0].id);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue