smart-nodes
Version:
334 lines (302 loc) • 12.1 kB
HTML
<script type="text/javascript">
(function ()
{
let treeList; // The tree control itself
let candidateNodesCount = 0; // The total entries shown in the list, used for the search box as info
let flows = []; // A List with all flow maps
let flowMap = {}; // A key value list with all flow id => flow details and children
function onEditPrepare(node, targetTypes)
{
if (!node.links)
node.links = [];
node.oldLinks = [];
const activeSubflow = RED.nodes.subflow(node.z);
// init tree list control
treeList = $("<div>")
.css({ width: "100%", height: "100%" })
.appendTo(".node-input-link-row")
.treeList({ autoSelect: false })
.on("treelistitemmouseover", function (e, item)
{
if (item.node)
{
item.node.highlighted = true;
item.node.dirty = true;
RED.view.redraw();
}
})
.on("treelistitemmouseout", function (e, item)
{
if (item.node)
{
item.node.highlighted = false;
item.node.dirty = true;
RED.view.redraw();
}
});
// init search box
const search = $("#node-input-link-target-filter").searchBox({
style: "compact",
delay: 300,
change: function ()
{
var val = $(this).val().trim().toLowerCase();
if (val === "")
{
treeList.treeList("filter", null);
search.searchBox("count", "");
}
else
{
const count = treeList.treeList("filter", function (item)
{
return item.label.toLowerCase().indexOf(val) > -1 || (item.parent && item.parent.label.toLowerCase().indexOf(val) > -1) || (item.node && item.node.type.toLowerCase().indexOf(val) > -1);
});
search.searchBox("count", count + " / " + candidateNodesCount);
}
},
});
// clear shown tree data
flows = [];
flowMap = {};
// Add all existing flows
if (activeSubflow)
{
flowMap[activeSubflow.id] = {
id: activeSubflow.id,
class: "red-ui-palette-header",
label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
expanded: true,
children: [],
};
flows.push(flowMap[activeSubflow.id]);
}
else
{
RED.nodes.eachWorkspace(function (ws)
{
if (!ws.disabled)
{
flowMap[ws.id] = {
id: ws.id,
class: "red-ui-palette-header",
label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
expanded: true,
children: [],
};
flows.push(flowMap[ws.id]);
}
});
}
setTimeout(function ()
{
treeList.treeList("show", node.z);
}, 100);
}
function initTreeList(node, targetTypes)
{
candidateNodesCount = 0;
for (const key in flowMap)
{
flowMap[key].children = [];
}
let candidateNodes = [];
targetTypes.forEach(function (targetType)
{
candidateNodes = candidateNodes.concat(RED.nodes.filterNodes({ type: targetType }));
});
// Add all nodes, matching the given types
candidateNodes.forEach(function (n)
{
if (flowMap[n.z])
{
const isChecked = node.links.indexOf(n.id) !== -1 || (n.links || []).indexOf(node.id) !== -1;
if (isChecked)
node.oldLinks.push(n.id);
flowMap[n.z].children.push({
id: n.id,
node: n,
label: n.name || n._def.paletteLabel + "(" + n.id + ")",
selected: isChecked,
checkbox: true,
radio: false,
});
candidateNodesCount++;
}
});
// Sort nodes by name
for (const key in flowMap)
{
flowMap[key].children.sort((a, b) => a.label.localeCompare(b.label));
}
// filter empty flows
const flowsFiltered = flows.filter(function (f)
{
return f.children.length > 0;
});
// clear list and refill it
treeList.treeList("empty");
treeList.treeList("data", flowsFiltered);
}
function resizeNodeList()
{
// calculate new heigt if browser is beeing resized
var rows = $("#dialog-form>div:not(.node-input-link-row)");
var height = $("#dialog-form").height();
for (var i = 0; i < rows.length; i++)
{
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-link-row");
height -= parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"));
$(".node-input-link-row").css("height", height + "px");
}
function onEditSave(node)
{
var flows = treeList.treeList("data");
node.links = [];
// Set own links for selected nodes
flows.forEach(function (f)
{
f.children.forEach(function (n)
{
if (n.selected)
node.links.push(n.id);
});
});
node.oldLinks.sort();
node.links.sort();
// Mark new and old links
var nodeMap = {};
var length = Math.max(node.oldLinks.length, node.links.length);
for (var i = 0; i < length; i++)
{
if (i < node.oldLinks.length)
{
nodeMap[node.oldLinks[i]] = nodeMap[node.oldLinks[i]] || {};
nodeMap[node.oldLinks[i]].old = true;
}
if (i < node.links.length)
{
nodeMap[node.links[i]] = nodeMap[node.links[i]] || {};
nodeMap[node.links[i]].new = true;
}
}
// Remove own node from old linked nodes
// Add own node to new linked nodes
var n;
for (var id in nodeMap)
{
if (nodeMap.hasOwnProperty(id))
{
n = RED.nodes.node(id);
if (n)
{
// init links and remove duplicate links
if (!n.links)
n.links = [];
else
n.links = n.links.filter((value, index, array) => array.indexOf(value) === index);
if (nodeMap[id].old && !nodeMap[id].new)
{
// Removed id
i = n.links.indexOf(node.id);
if (i > -1) n.links.splice(i, 1);
}
else if (nodeMap[id].new)
{
// Added id
i = n.links.indexOf(node.id);
if (i === -1) n.links.push(node.id);
}
}
}
}
}
// This function is called, when copying and pasting central node
function onAdd()
{
for (var i = 0; i < this.links.length; i++)
{
var n = RED.nodes.node(this.links[i]);
if (n && n.links.indexOf(this.id) === -1)
{
n.links.push(this.id);
}
}
}
RED.nodes.registerType("smart_central-control", {
category: "Smart Nodes",
paletteLabel: "Central control",
color: "#6EE2D9",
defaults: {
name: { value: "" },
mode: { value: "LIGHT" }, // LIGHT | SHUTTER
links: { value: [], type: "smart_shutter-complex-control[]" },
},
inputs: 1,
outputs: 0,
icon: function ()
{
if (this.mode === "SHUTTER")
return "font-awesome/fa-align-justify";
return "font-awesome/fa-lightbulb-o";
},
label: function ()
{
return this.name || "Central control";
},
oneditprepare: function ()
{
let node = this;
onEditPrepare(this, ["smart_shutter-complex-control", "smart_shutter-control"]);
$("#node-input-mode").on("change", function ()
{
if (this.value == "SHUTTER")
initTreeList(node, ["smart_shutter-complex-control", "smart_shutter-control"]);
else
initTreeList(node, ["smart_light-control", "smart_scene-control"]);
});
$("#node-input-mode")
.css("max-width", "70%")
.typedInput({
types: [{
default: "LIGHT",
value: "mode",
options: [
{ value: "LIGHT", label: node._("central.ui.light_outlet") },
{ value: "SHUTTER", label: node._("central.ui.cover") }
],
}],
});
// Backward compatibility
this.mode = this.mode?.toUpperCase();
if (this.mode)
$("#node-input-mode").val(this.mode).trigger("change");
else
$("#node-input-mode").val("SHUTTER").trigger("change");
},
oneditsave: function ()
{
let node = this;
onEditSave(this);
},
onadd: onAdd,
oneditresize: resizeNodeList,
});
})();
</script>
<script type="text/html" data-template-name="smart_central-control">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="central.ui.name"></span></label>
<input type="text" id="node-input-name" />
</div>
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-arrow-right"></i> <span data-i18n="central.ui.type"></span></label>
<input id="node-input-mode"/>
</div>
<div class="node-input-link-rows" style="position:relative; height: 30px; text-align: right;">
<div style="display:inline-block"><input type="text" id="node-input-link-target-filter" /></div>
</div>
<div class="form-row node-input-link-row node-input-link-rows"></div>
</script>