smart-nodes
Version:
Controls light, shutters and more. Includes common used logic and statistic nodes to control your home.
304 lines (274 loc) • 11 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);
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 (!n.exec_text_names)
{
// No exec name defined but checked, so remove it from the linked list.
if (isChecked)
{
node.links.splice(node.links.indexOf(n.id), 1);
console.log("Unchecked " + n.name);
}
return;
}
if (isChecked)
node.oldLinks.push(n.id);
flowMap[n.z].children.push({
id: n.id,
node: n,
label: n.type.replace("smart_", "").replace("-control", "").replace("-complex", "") + ": " + (n.name || 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", Math.max(height, 200) + "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_text-exec", {
category: "Smart Nodes",
paletteLabel: "Text exec control",
color: "#E9967A",
defaults: {
name: { value: "" },
links: { value: [], type: "smart_shutter-complex-control[]" },
},
inputs: 1,
outputs: 1,
icon: "font-awesome/fa-cog",
label: function ()
{
return this.name || "Text exec control";
},
oneditprepare: function ()
{
let node = this;
onEditPrepare(this, ["smart_light-control", "smart_scene-control", "smart_shutter-complex-control", "smart_shutter-control"]);
},
oneditsave: function ()
{
let node = this;
onEditSave(this);
},
onadd: onAdd,
oneditresize: resizeNodeList,
});
})();
</script>
<script type="text/html" data-template-name="smart_text-exec">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="text-exec.ui.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]text-exec.ui.name" />
</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>