@droneblocks/node-red-dexi
Version:
You want to make sure you don't lose any of your flow development. Make sure to map the host "flows" directory to the container directory as shown below. This will store the flow on your host machine if/when the container is destroyed.
253 lines (234 loc) • 11.2 kB
HTML
<script type="text/javascript">
(function() {
const DEFAULT_IMAGE_WIDTH = 160
RED.nodes.registerType('image-preview', {
category: 'DEXI',
color: '#88A882',
defaults: {
name: { value: "" },
width: {
value: DEFAULT_IMAGE_WIDTH,
required: true,
validate: function (v) { return !v || !isNaN(parseInt(v, 10)) }
},
data: {
value: "payload",
required: true,
validate: RED.validators.typedInput("dataType")
},
dataType: { value: "msg" },
thumbnail: { value: false },
active: { value: true },
pass: { value: false },
outputs: {value: 0}
},
inputs: 1,
outputs: 0,
icon: "font-awesome/fa-image",
align: 'right',
palettelabel: "image preview",
label: function () {
return this.name || "image preview";
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
inputLabels: function () {
return `${this.dataType}.${this.data}`;
},
button: {
toggle: "active",
visible: function() { return !this.pass; },
onclick: function () {
const label = this.name || "image preview";
var node = this;
$.ajax({
url: `image-output/${this.id}/${this.active ? 'enable' : 'disable'}`,
type: "POST",
success: function (resp, textStatus, xhr) {
const historyEvent = {
t: 'edit',
node: node,
changes: {
active: !node.active
},
dirty: node.dirty,
changed: node.changed
};
node.changed = true;
node.dirty = true;
RED.nodes.dirty(true);
RED.history.push(historyEvent);
RED.view.redraw();
if (xhr.status == 200) {
RED.notify("Successfully " + resp + ": " + label, "success");
}
},
error: function (jqXHR, textStatus, errorThrown) {
var message;
switch (jqXHR.status) {
case 404:
message = "node not deployed";
break;
case 0:
message = "no response from server";
break;
default:
message = `unexpected error (${textStatus}) ${errorThrown}`;
}
RED.notify(`<strong>Error</strong>: ${message}`, "error");
}
});
}
},
oneditprepare: function () {
var that = this;
// Set a default width of 160 for existing nodes that don't have that field yet.
$('#node-input-width').val(this.width || DEFAULT_IMAGE_WIDTH);
$('#node-input-data').typedInput({
default: 'msg',
typeField: $("#node-input-dataType"),
types: ['msg']
});
var checkout = function() {
}
if ($("#node-input-pass").is(":checked")) { that.outputs = 1; }
$("#node-input-pass").change(function() {
if ($("#node-input-pass").is(":checked")) {
that.outputs = 1;
that.active = true;
}
else {
that.outputs = 0;
}
});
}
});
const latestImages = {}
var remove = function(nodeid) {
const id = nodeid
const $img = document.getElementById("image-output-img-" + id)
const $bubble = document.getElementById("image-output-bubble-" + id)
$img && $img.remove()
$bubble && $bubble.remove()
delete latestImages[id]
}
var redraw = function(node) {
const id = node.id
const $img = document.getElementById("image-output-img-" + id)
const $bubble = document.getElementById("image-output-bubble-" + id)
$img && $img.remove()
$bubble && $bubble.remove()
if (latestImages[id]) {
render(id, latestImages[id], node)
}
}
var render = function(id, data, node) {
let i = new Image();
let $img = document.getElementById("image-output-img-" + id)
if (!$img) {
const $container = document.getElementById(id)
if (!$container) { return }
const bubble = document.createElementNS("http://www.w3.org/2000/svg", 'polyline')
bubble.setAttribute('id', "image-output-bubble-" + id)
bubble.setAttribute('style', 'fill:#E8F0E8')
bubble.setAttribute('stroke', '#999999')
$container.insertBefore(bubble, $container.lastChild.nextSibling)
const img = document.createElementNS("http://www.w3.org/2000/svg", 'image')
img.setAttribute('id', "image-output-img-" + id)
img.setAttribute('x', '0')
img.setAttribute('y', '45')
img.setAttribute('width', node.width || DEFAULT_IMAGE_WIDTH)
img.addEventListener("click", function(){remove(id)}, {once:true})
$container.insertBefore(img, $container.lastChild.nextSibling)
$img = img
}
i.onload = function () {
$img.setAttribute('height', node.width * i.height / i.width)
const bubbleId = $img.id.replace("img", "bubble");
const $bubble = document.getElementById(bubbleId)
let imgBbox = {}
imgBbox.x = 0;
imgBbox.y = 45;
imgBbox.width = node.width || DEFAULT_IMAGE_WIDTH;
imgBbox.height = parseInt(node.width * i.height / i.width);
const left = imgBbox.x
const top = imgBbox.y + 2
const right = imgBbox.x + imgBbox.width
const bottom = imgBbox.y + imgBbox.height
const points =
(left + 4) + "," + (top - 17) + " " +
(left + 4) + "," + top + " " +
right + "," + top + " " +
right + "," + bottom + " " +
left + "," + bottom + " " +
left + "," + (top - 21)
$bubble.setAttribute('points', points)
}
if (data.startsWith("http")) {
$img.setAttribute('href', data);
i.src = data;
}
else {
$img.setAttribute('href', "data:image/png;base64," + data);
i.src = "data:image/png;base64," + data;
}
}
RED.events.on("editor:save", redraw)
RED.comms.subscribe('image-preview', function (event, data) {
if (data.hasOwnProperty("data")) {
latestImages[data.id] = data.data
render(data.id, data.data, RED.nodes.node(data.id))
}
else {
remove(data.id);
}
})
})();
</script>
<script type="text/html" data-template-name="image-preview">
<div class="form-row">
<label style="padding-top: 8px" for="node-input-data"><i class="fa fa-ellipsis-h"></i> Property</label>
<input type="text" id="node-input-data" style="width:70%">
<input type="hidden" id="node-input-dataType">
</div>
<div class="form-row">
<label for="node-input-width"><i class="fa fa-arrows-h"></i> Width</label>
<input type="number" id="node-input-width">
</div>
<div class="form-row">
<label> </label>
<input type="checkbox" id="node-input-pass" style="display:inline-block; width:auto; vertical-align:top;">
<label for="node-input-pass" style="width:70%;"> Allow image passthrough</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="image-preview">
<p>Simple image output node. Useful for previewing results of face detection, object recognition, etc.</p>
<p>The button on the node can be used to enable or disable the image preview.
It is recommended (for performance) to disable or remove any Image-Output nodes that are not being used.</p>
<p>Expects the selected msg property (default <code>msg.payload</code>) to contain one of the following formats:
<ul>
<li>A base64 encoded string</li>
<li>A buffer</li>
<li>A Jimp image object</li>
<li>A url to an image</li>
</ul>
</p>
<p>Sending a blank or null payload will remove/hide the preview panel.</p>
<p><strong>Width:</strong><br/>
The width (in pixels) that the image needs to be displayed in the flow.</p>
<p><strong>Resize images on server side:</strong><br/>
When too much data is pushed to the browser, the flow editor can become unresponse.
<ul>
<li>When activated, the images will be resized (to the specified width) on the server side. Then those small thumbnail images will be send to the browser, to reduce the bandwith.</li>
<li>When not activated, the (original) large images will be send to the browser. Once they arrive there, the browser will resize them to the specified width.</li>
</ul>
<p><strong>Allow image passthrough:</strong><br/>
When selected this adds an output wire to the node in order to pass the original message through to a following node.
This performs better than forking the wires, however it does remove the enable/disable button.
</p>
</script>