@rosepetal/node-red-contrib-utils 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @file Node.js logic for the Array-In node with input path and array position.
3
+ * @author Rosepetal
4
+ */
5
+
6
+ module.exports = function(RED) {
7
+ const NodeUtils = require('../../lib/node-utils.js')(RED);
8
+
9
+ function ArrayInNode(config) {
10
+ RED.nodes.createNode(this, config);
11
+ const node = this;
12
+
13
+ // Store configuration with validation
14
+ node.inputPath = config.inputPath || 'payload';
15
+ node.inputPathType = config.inputPathType || 'msg';
16
+
17
+ // Dynamic position configuration
18
+ node.arrayPositionType = config.arrayPositionType || 'num';
19
+ node.arrayPositionValue = config.arrayPositionValue !== undefined ? config.arrayPositionValue : 0;
20
+
21
+ // Set initial status
22
+ const statusText = node.arrayPositionType === 'num'
23
+ ? `Position: ${node.arrayPositionValue}`
24
+ : `Position: ${node.arrayPositionType}.${node.arrayPositionValue}`;
25
+ node.status({
26
+ fill: "blue",
27
+ shape: "dot",
28
+ text: statusText
29
+ });
30
+
31
+ // Handle incoming messages
32
+ node.on('input', function(msg, send, done) {
33
+ try {
34
+ // Resolve array position dynamically
35
+ let resolvedPosition;
36
+ try {
37
+ resolvedPosition = NodeUtils.resolveArrayPosition(
38
+ node,
39
+ node.arrayPositionType,
40
+ node.arrayPositionValue,
41
+ msg
42
+ );
43
+ } catch (err) {
44
+ node.warn(`Failed to resolve array position: ${err.message}`);
45
+ node.status({ fill: "red", shape: "ring", text: "Invalid position" });
46
+ return done ? done() : null;
47
+ }
48
+
49
+ // Get data from configured input path
50
+ let arrayData;
51
+ if (node.inputPathType === 'msg') {
52
+ arrayData = RED.util.getMessageProperty(msg, node.inputPath);
53
+ } else if (node.inputPathType === 'flow') {
54
+ arrayData = node.context().flow.get(node.inputPath);
55
+ } else if (node.inputPathType === 'global') {
56
+ arrayData = node.context().global.get(node.inputPath);
57
+ }
58
+
59
+ // Handle undefined data gracefully
60
+ if (arrayData === undefined) {
61
+ node.warn(`No data found at ${node.inputPathType}.${node.inputPath}`);
62
+ arrayData = null;
63
+ }
64
+
65
+ // Initialize msg.meta if it doesn't exist
66
+ if (!msg.meta) {
67
+ msg.meta = {};
68
+ }
69
+
70
+ // Add array metadata and optionally mirror into payload
71
+ msg.meta.arrayPosition = resolvedPosition;
72
+ msg.meta.arrayData = arrayData;
73
+
74
+ const normalizedPath = typeof node.inputPath === 'string'
75
+ ? node.inputPath.trim()
76
+ : node.inputPath;
77
+ const shouldMirrorPayload =
78
+ node.inputPathType === 'msg' &&
79
+ normalizedPath === 'payload';
80
+
81
+ if (shouldMirrorPayload) {
82
+ msg.payload = arrayData;
83
+ }
84
+
85
+ // Update status
86
+ node.status({
87
+ fill: "green",
88
+ shape: "dot",
89
+ text: `Position: ${resolvedPosition}`
90
+ });
91
+
92
+ // Send the message with array metadata
93
+ send(msg);
94
+ if (done) { done(); }
95
+
96
+ } catch (err) {
97
+ node.status({ fill: "red", shape: "ring", text: "Error" });
98
+ if (done) {
99
+ done(err);
100
+ } else {
101
+ node.error(err, msg);
102
+ }
103
+ }
104
+ });
105
+
106
+ // Clean up on node removal
107
+ node.on('close', function() {
108
+ node.status({});
109
+ });
110
+ }
111
+
112
+ RED.nodes.registerType("rp-array-in", ArrayInNode);
113
+ };
@@ -0,0 +1,125 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('rp-array-out', {
3
+ category: 'RP Utils',
4
+ color: '#87CEEB',
5
+ defaults: {
6
+ name: { value: "" },
7
+ timeout: { value: 5000 },
8
+ expectedCount: { value: 2, required: true, validate: RED.validators.number() },
9
+ outputPath: { value: "payload" },
10
+ outputPathType: { value: "msg" }
11
+ },
12
+ inputs: 1,
13
+ outputs: 2,
14
+ icon: "font-awesome/fa-sign-out",
15
+ label: function() {
16
+ return this.name || `Array Out (${this.expectedCount} items)`;
17
+ },
18
+ oneditprepare: function() {
19
+ // Set up TypedInput for output path
20
+ $("#node-input-outputPath").typedInput({
21
+ default: 'msg',
22
+ types: ['msg', 'flow', 'global'],
23
+ typeField: "#node-input-outputPathType"
24
+ });
25
+ }
26
+ });
27
+ </script>
28
+
29
+ <script type="text/x-red" data-template-name="rp-array-out">
30
+ <div class="form-row">
31
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
32
+ <input type="text" id="node-input-name" placeholder="Name">
33
+ </div>
34
+
35
+ <hr>
36
+
37
+ <div class="form-row">
38
+ <label for="node-input-timeout"><i class="fa fa-clock-o"></i> Timeout (ms)</label>
39
+ <input type="number" id="node-input-timeout" min="100" step="100" style="width: 70%;" placeholder="5000">
40
+ </div>
41
+
42
+ <div class="form-row">
43
+ <label for="node-input-expectedCount"><i class="fa fa-hashtag"></i> Expected Count</label>
44
+ <input type="number" id="node-input-expectedCount" min="1" step="1" style="width: 70%;" placeholder="2" required>
45
+ </div>
46
+
47
+ <div class="form-row">
48
+ <label for="node-input-outputPath"><i class="fa fa-sign-out"></i> Output to</label>
49
+ <input type="text" id="node-input-outputPath" style="width: 70%;">
50
+ <input type="hidden" id="node-input-outputPathType">
51
+ </div>
52
+
53
+ <div class="form-tips">
54
+ <b>Timeout:</b> Maximum time to wait for all array elements after the first message arrives. If timeout occurs, collected data and missing position info are sent to output 2.<br>
55
+ <b>Expected Count:</b> Number of array elements required before outputting the assembled array.
56
+ </div>
57
+ </script>
58
+
59
+ <script type="text/x-red" data-help-name="rp-array-out">
60
+ <p>Collects messages from multiple rp-array-in nodes and assembles them into an ordered array based on position indices.</p>
61
+
62
+ <h3>Details</h3>
63
+ <p>This node works as the collection point for multiple rp-array-in nodes. It waits for messages containing array metadata, collects them based on their position indices, and outputs a complete array when all expected elements are received. The node includes timeout protection and performance monitoring.</p>
64
+
65
+ <h3>Properties</h3>
66
+ <dl class="message-properties">
67
+ <dt>Timeout <span class="property-type">number</span></dt>
68
+ <dd>Maximum time in milliseconds to wait for all array elements after the first message arrives. Default: 5000ms. If timeout occurs, collected data and missing position info are sent to output 2.</dd>
69
+ <dt>Expected Count <span class="property-type">number</span></dt>
70
+ <dd>Number of array elements required before outputting the assembled array. Must match the number of rp-array-in nodes connected. Required field, minimum value: 1.</dd>
71
+ <dt>Output to <span class="property-type">string</span></dt>
72
+ <dd>The location where the assembled array will be stored. You can select between <code>msg</code>, <code>flow</code>, or <code>global</code> context. Defaults to <code>msg.payload</code>.</dd>
73
+ </dl>
74
+
75
+ <h3>Inputs</h3>
76
+ <dl class="message-properties">
77
+ <dt>payload <span class="property-type">any</span></dt>
78
+ <dd>Messages from rp-array-in nodes containing array metadata.</dd>
79
+ <dt>meta.arrayPosition <span class="property-type">number</span></dt>
80
+ <dd>The array position index (0, 1, 2...) provided by rp-array-in nodes.</dd>
81
+ <dt>meta.arrayData <span class="property-type">any</span></dt>
82
+ <dd>The actual data to be placed in the array at the specified position.</dd>
83
+ </dl>
84
+
85
+ <h3>Outputs</h3>
86
+ <dl class="message-properties">
87
+ <dt>Output 1 - Complete Array <span class="property-type">array</span></dt>
88
+ <dd>The complete assembled array when all expected elements are received: [data0, data1, data2...]</dd>
89
+ <dt>Output 2 - Timeout Data <span class="property-type">object</span></dt>
90
+ <dd>On timeout: collected data array (with nulls for missing) plus metadata about missing positions</dd>
91
+ </dl>
92
+
93
+ <h3>Operation Flow</h3>
94
+ <ol>
95
+ <li><strong>First Message:</strong> Starts timeout timer and begins collection</li>
96
+ <li><strong>Collection:</strong> Gathers messages from rp-array-in nodes, validates positions</li>
97
+ <li><strong>Assembly:</strong> Places data at specified array positions based on meta.arrayPosition</li>
98
+ <li><strong>Success:</strong> Outputs assembled array when all expected elements received</li>
99
+ <li><strong>Timeout:</strong> Sends collected data + missing info to output 2</li>
100
+ <li><strong>Reset:</strong> Ready for next collection cycle</li>
101
+ </ol>
102
+
103
+ <h3>Examples</h3>
104
+ <ul>
105
+ <li><strong>Basic Array Assembly:</strong> With Expected Count = 3, rp-array-in nodes at positions 0, 1, 2 will produce: <code>["data0", "data1", "data2"]</code></li>
106
+ <li><strong>Image Array:</strong> Three image-in → rp-array-in (positions 0,1,2) → rp-array-out → creates array of image objects for batch processing</li>
107
+ <li><strong>Mixed Data:</strong> Combine different data types from various sources into a single ordered array</li>
108
+ </ul>
109
+
110
+ <h3>Node Interactions</h3>
111
+ <p><strong>Requires:</strong> Multiple rp-array-in nodes, each with unique position indices (0, 1, 2...)</p>
112
+ <p><strong>Works with:</strong> Transform nodes that support array inputs (resize, rotate, etc.), rp-array-select for element extraction</p>
113
+ <p><strong>Best Practice:</strong> Set Expected Count to match the number of rp-array-in nodes. Use reasonable timeout values based on your processing requirements.</p>
114
+
115
+ <h3>Error Handling</h3>
116
+ <p>The node handles various error conditions:</p>
117
+ <ul>
118
+ <li><strong>Missing metadata:</strong> Messages without proper array metadata are ignored</li>
119
+ <li><strong>Duplicate positions:</strong> Later messages overwrite earlier ones at the same position</li>
120
+ <li><strong>Timeout:</strong> Collection resets, node status shows timeout, no output sent</li>
121
+ </ul>
122
+
123
+ <h3>Performance</h3>
124
+ <p>Node status displays collection timing when successful. The timeout mechanism prevents indefinite waiting for missing array elements.</p>
125
+ </script>
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @file Node.js logic for the Array‑Out node with timeout, ordering and validation.
3
+ * @author Rosepetal
4
+ */
5
+
6
+ module.exports = function (RED) {
7
+ function ArrayOutNode(config) {
8
+ RED.nodes.createNode(this, config);
9
+ const node = this;
10
+
11
+ /* ────────────────────────────
12
+ ░░ 1. Read & validate cfg ░░
13
+ ──────────────────────────── */
14
+ node.timeout = parseInt(config.timeout) || 5000;
15
+ node.expectedCount = parseInt(config.expectedCount);
16
+ node.outputPath = config.outputPath || 'payload';
17
+ node.outputPathType = config.outputPathType || 'msg';
18
+
19
+ if (!Number.isInteger(node.expectedCount) || node.expectedCount < 1) {
20
+ node.error('Invalid expectedCount. Must be a positive integer.');
21
+ node.expectedCount = 2; // sensible default
22
+ }
23
+
24
+ /* ────────────────────────────
25
+ ░░ 2. Internal state ░░
26
+ ──────────────────────────── */
27
+ node.collection = {};
28
+ node.timeoutHandle = null;
29
+ node.isCollecting = false;
30
+ node.collectionStartTime = null;
31
+
32
+ setStatusReady();
33
+
34
+ /* ────────────────────────────
35
+ ░░ 3. Helper functions ░░
36
+ ──────────────────────────── */
37
+ function setStatusReady() {
38
+ node.status({
39
+ fill: 'blue',
40
+ shape: 'dot',
41
+ text: `Ready (expect ${node.expectedCount})`
42
+ });
43
+ }
44
+
45
+ function resetCollection() {
46
+ if (node.timeoutHandle) {
47
+ clearTimeout(node.timeoutHandle);
48
+ node.timeoutHandle = null;
49
+ }
50
+ node.collection = {};
51
+ node.isCollecting = false;
52
+ node.collectionStartTime = null;
53
+ }
54
+
55
+ function handleTimeout() {
56
+ const elapsed = Date.now() - node.collectionStartTime;
57
+
58
+ /* Build timeout message with collected data + missing info */
59
+ const collectedPositions = Object.keys(node.collection).map(k => parseInt(k)).sort((a, b) => a - b);
60
+ const missingPositions = [];
61
+ for (let i = 0; i < node.expectedCount; i++) {
62
+ if (!node.collection.hasOwnProperty(i)) {
63
+ missingPositions.push(i);
64
+ }
65
+ }
66
+
67
+ /* Build the partial array (same logic as normal completion) */
68
+ const result = Array.from({ length: node.expectedCount }, (_, i) =>
69
+ node.collection.hasOwnProperty(i) ? node.collection[i] : null
70
+ );
71
+
72
+ /* Create timeout message */
73
+ const timeoutMsg = {
74
+ meta: {
75
+ timeout: true,
76
+ missingPositions: missingPositions,
77
+ collectedPositions: collectedPositions,
78
+ expectedCount: node.expectedCount,
79
+ collectedCount: collectedPositions.length,
80
+ elapsed: elapsed
81
+ }
82
+ };
83
+
84
+ /* Place the partial array where the user wants it */
85
+ if (node.outputPathType === 'msg') {
86
+ RED.util.setMessageProperty(timeoutMsg, node.outputPath, result, true);
87
+ } else if (node.outputPathType === 'flow') {
88
+ node.context().flow.set(node.outputPath, result);
89
+ } else if (node.outputPathType === 'global') {
90
+ node.context().global.set(node.outputPath, result);
91
+ }
92
+
93
+ /* Send to output 2 only */
94
+ node.send([null, timeoutMsg]);
95
+
96
+ node.status({
97
+ fill: 'yellow',
98
+ shape: 'ring',
99
+ text: `Timeout: sent ${collectedPositions.length}/${node.expectedCount} to output 2`
100
+ });
101
+
102
+ resetCollection();
103
+ setTimeout(setStatusReady, 3000);
104
+ }
105
+
106
+ function assembleAndOutput(sourceMsg) {
107
+ const elapsed = Date.now() - node.collectionStartTime;
108
+
109
+ /* Build the ordered array (fill gaps with null) */
110
+ const result = Array.from({ length: node.expectedCount }, (_, i) =>
111
+ node.collection.hasOwnProperty(i) ? node.collection[i] : null
112
+ );
113
+
114
+ /* Place the array where the user wants it */
115
+ const outMsg = { ...sourceMsg }; // shallow‑clone to keep headers etc.
116
+ if (node.outputPathType === 'msg') {
117
+ RED.util.setMessageProperty(outMsg, node.outputPath, result, true);
118
+ } else if (node.outputPathType === 'flow') {
119
+ node.context().flow.set(node.outputPath, result);
120
+ } else if (node.outputPathType === 'global') {
121
+ node.context().global.set(node.outputPath, result);
122
+ }
123
+
124
+ node.status({
125
+ fill: 'green',
126
+ shape: 'dot',
127
+ text: `Complete: ${result.length} (${elapsed} ms)`
128
+ });
129
+ node.send([outMsg, null]);
130
+
131
+ /* Return to ready after a short pause */
132
+ setTimeout(setStatusReady, 2000);
133
+ resetCollection();
134
+ }
135
+
136
+ /* ────────────────────────────
137
+ ░░ 4. Input handler ░░
138
+ ──────────────────────────── */
139
+ node.on('input', function (msg, send, done) {
140
+ try {
141
+ /* 4.1 Basic sanity checks */
142
+ if (!msg.meta || typeof msg.meta.arrayPosition !== 'number') {
143
+ node.warn(
144
+ 'Message missing array metadata. Expected msg.meta.arrayPosition (number) from rp-array-in node'
145
+ );
146
+ return done?.();
147
+ }
148
+
149
+ const position = msg.meta.arrayPosition;
150
+
151
+ /* 4.2 Validate range */
152
+ if (position < 0 || position >= node.expectedCount) {
153
+ node.warn(
154
+ `Position ${position} out of range 0‑${node.expectedCount - 1}`
155
+ );
156
+ return done?.();
157
+ }
158
+
159
+ /* 4.3 Extract data – prefer meta.arrayData, fall back to payload */
160
+ const data =
161
+ msg.meta && msg.meta.arrayData !== undefined
162
+ ? msg.meta.arrayData
163
+ : msg.payload;
164
+
165
+ /* 4.4 Start collection if first element */
166
+ if (!node.isCollecting) {
167
+ node.isCollecting = true;
168
+ node.collection = {};
169
+ node.collectionStartTime = Date.now();
170
+ node.timeoutHandle = setTimeout(handleTimeout, node.timeout);
171
+ node.status({
172
+ fill: 'yellow',
173
+ shape: 'dot',
174
+ text: 'Collecting… (1/' + node.expectedCount + ')'
175
+ });
176
+ }
177
+
178
+ /* 4.5 Store element */
179
+ node.collection[position] = data;
180
+
181
+ /* 4.6 Update collecting status */
182
+ const collected = Object.keys(node.collection).length;
183
+ node.status({
184
+ fill: 'yellow',
185
+ shape: 'dot',
186
+ text: `Collecting… (${collected}/${node.expectedCount})`
187
+ });
188
+
189
+ /* 4.7 Check completion */
190
+ if (collected === node.expectedCount) {
191
+ assembleAndOutput(msg);
192
+ }
193
+
194
+ done?.();
195
+ } catch (err) {
196
+ node.status({ fill: 'red', shape: 'ring', text: 'Error' });
197
+ resetCollection();
198
+ if (done) done(err);
199
+ else node.error(err, msg);
200
+ }
201
+ });
202
+
203
+ /* ────────────────────────────
204
+ ░░ 5. Cleanup ░░
205
+ ──────────────────────────── */
206
+ node.on('close', function () {
207
+ resetCollection();
208
+ node.status({});
209
+ });
210
+ }
211
+
212
+ RED.nodes.registerType('rp-array-out', ArrayOutNode);
213
+ };
@@ -0,0 +1,156 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('rp-array-select', {
3
+ category: 'RP Utils',
4
+ color: '#7b8dff',
5
+ defaults: {
6
+ name: { value: "" },
7
+ inputPath: { value: "payload" },
8
+ inputPathType: { value: "msg" },
9
+ outputPath: { value: "payload" },
10
+ outputPathType: { value: "msg" },
11
+ selection: { value: "0" },
12
+ asArray: { value: false }
13
+ },
14
+ inputs: 1,
15
+ outputs: 1,
16
+ icon: "font-awesome/fa-columns",
17
+ label: function() {
18
+ if (this.name) return this.name;
19
+ return `Array Select [${this.selection}]`;
20
+ },
21
+ oneditprepare: function() {
22
+ // Set up TypedInput for input path
23
+ $("#node-input-inputPath").typedInput({
24
+ default: 'msg',
25
+ types: ['msg', 'flow', 'global'],
26
+ typeField: "#node-input-inputPathType"
27
+ });
28
+
29
+ // Set up TypedInput for output path
30
+ $("#node-input-outputPath").typedInput({
31
+ default: 'msg',
32
+ types: ['msg', 'flow', 'global'],
33
+ typeField: "#node-input-outputPathType"
34
+ });
35
+ }
36
+ });
37
+ </script>
38
+
39
+ <script type="text/x-red" data-template-name="rp-array-select">
40
+ <div class="form-row">
41
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
42
+ <input type="text" id="node-input-name" placeholder="Name">
43
+ </div>
44
+
45
+ <hr>
46
+
47
+ <div class="form-row">
48
+ <label for="node-input-inputPath"><i class="fa fa-sign-in"></i> Input from</label>
49
+ <input type="text" id="node-input-inputPath" style="width: 70%;">
50
+ <input type="hidden" id="node-input-inputPathType">
51
+ </div>
52
+
53
+ <div class="form-row">
54
+ <label for="node-input-outputPath"><i class="fa fa-sign-out"></i> Output to</label>
55
+ <input type="text" id="node-input-outputPath" style="width: 70%;">
56
+ <input type="hidden" id="node-input-outputPathType">
57
+ </div>
58
+
59
+ <div class="form-row">
60
+ <label for="node-input-selection"><i class="fa fa-filter"></i> Selection</label>
61
+ <input type="text" id="node-input-selection" style="width: 70%;" placeholder="e.g., 0, 1,3,5, 1:4, 0::2">
62
+ </div>
63
+
64
+ <div class="form-row">
65
+ <label for="node-input-asArray"><i class="fa fa-list"></i> Force Array</label>
66
+ <input type="checkbox" id="node-input-asArray">
67
+ <span style="margin-left: 5px;">Always output as array (even for single items)</span>
68
+ </div>
69
+
70
+ <div class="form-tips">
71
+ <b>Selection Examples:</b><br>
72
+ • <code>0</code> - First element<br>
73
+ • <code>1,3,5</code> - Elements at indices 1, 3, 5<br>
74
+ • <code>1:4</code> - Elements 1 to 3 (range)<br>
75
+ • <code>0::2</code> - Every 2nd element starting from 0<br>
76
+ • <code>-1</code> - Last element<br>
77
+ • <code>-2:-1</code> - Last two elements
78
+ </div>
79
+ </script>
80
+
81
+ <script type="text/x-red" data-help-name="rp-array-select">
82
+ <p>Selects specific elements from an input array using flexible selection syntax including indices, ranges, and patterns.</p>
83
+
84
+ <h3>Details</h3>
85
+ <p>This node provides powerful array element selection capabilities with Python-like slicing syntax. It can extract single elements, multiple specific indices, ranges, or patterns from arrays. The output format is automatically determined based on the selection size, with an option to force array output.</p>
86
+
87
+ <h3>Properties</h3>
88
+ <dl class="message-properties">
89
+ <dt>Input from <span class="property-type">string</span></dt>
90
+ <dd>The location to read the input array from. You can select between <code>msg</code>, <code>flow</code>, or <code>global</code> context. Defaults to <code>msg.payload</code>.</dd>
91
+ <dt>Output to <span class="property-type">string</span></dt>
92
+ <dd>The location where to write the selected element(s). You can select between <code>msg</code>, <code>flow</code>, or <code>global</code> context. Defaults to <code>msg.payload</code>.</dd>
93
+ <dt>Selection <span class="property-type">string</span></dt>
94
+ <dd>Specify which array elements to select using various formats: single indices, comma-separated lists, ranges, or step patterns.</dd>
95
+ <dt>Force Array <span class="property-type">boolean</span></dt>
96
+ <dd>When checked, always outputs an array even for single elements. Default: false (single elements output directly).</dd>
97
+ </dl>
98
+
99
+ <h3>Inputs</h3>
100
+ <dl class="message-properties">
101
+ <dt>payload <span class="property-type">array</span></dt>
102
+ <dd>Input array from which to select elements. <strong>Must be an array</strong> - the node will warn and skip processing if input is not an array.</dd>
103
+ </dl>
104
+
105
+ <h3>Outputs</h3>
106
+ <dl class="message-properties">
107
+ <dt>configured output <span class="property-type">any | array</span></dt>
108
+ <dd>Selected element(s) written to the configured output path. Single elements output directly unless "Force Array" is enabled. Multiple elements always output as array.</dd>
109
+ </dl>
110
+
111
+ <h3>Selection Syntax</h3>
112
+ <ul>
113
+ <li><strong>Single Index:</strong> <code>0</code>, <code>2</code>, <code>-1</code> (negative indices count from end)</li>
114
+ <li><strong>Multiple Indices:</strong> <code>0,2,4</code>, <code>1,3,5</code> (comma-separated)</li>
115
+ <li><strong>Range:</strong> <code>1:4</code> (indices 1 to 3), <code>0:5</code> (indices 0 to 4)</li>
116
+ <li><strong>Step Range:</strong> <code>0::2</code> (every 2nd element), <code>1:7:2</code> (indices 1,3,5)</li>
117
+ <li><strong>Negative Ranges:</strong> <code>-2:</code> (last two elements), <code>:-2</code> (all but last two)</li>
118
+ </ul>
119
+
120
+ <h3>Examples</h3>
121
+ <p><strong>Input Array:</strong> <code>["A", "B", "C", "D", "E"]</code></p>
122
+ <ul>
123
+ <li><strong>Single:</strong> <code>0</code> → <code>"A"</code> (first element)</li>
124
+ <li><strong>Multiple:</strong> <code>1,3</code> → <code>["B", "D"]</code> (specific indices)</li>
125
+ <li><strong>Range:</strong> <code>1:4</code> → <code>["B", "C", "D"]</code> (slice range)</li>
126
+ <li><strong>Step:</strong> <code>0::2</code> → <code>["A", "C", "E"]</code> (every 2nd element)</li>
127
+ <li><strong>Negative:</strong> <code>-1</code> → <code>"E"</code> (last element)</li>
128
+ <li><strong>Last Two:</strong> <code>-2:</code> → <code>["D", "E"]</code> (from 2nd last to end)</li>
129
+ </ul>
130
+
131
+ <h3>Node Interactions</h3>
132
+ <p><strong>Works with:</strong> rp-array-out (to extract from assembled arrays), Transform nodes (to select processed results), any node that produces arrays</p>
133
+ <p><strong>Common Patterns:</strong> rp-array-out → rp-array-select (extract specific results), Transform → rp-array-select (pick best results), Image processing → rp-array-select (choose specific images)</p>
134
+
135
+ <h3>Output Behavior</h3>
136
+ <ul>
137
+ <li><strong>Single Element:</strong> Outputs the element directly (unless "Force Array" is checked)</li>
138
+ <li><strong>Multiple Elements:</strong> Always outputs as an array</li>
139
+ <li><strong>Empty Selection:</strong> Outputs empty array <code>[]</code></li>
140
+ <li><strong>Invalid Selection:</strong> No message sent, warning logged</li>
141
+ <li><strong>Out of Bounds:</strong> Invalid indices are ignored, warning logged</li>
142
+ </ul>
143
+
144
+ <h3>Error Handling</h3>
145
+ <p>Comprehensive validation and error reporting:</p>
146
+ <ul>
147
+ <li><strong>Non-array input:</strong> Warning logged, message not propagated</li>
148
+ <li><strong>Empty array:</strong> Warning logged, selection processed normally</li>
149
+ <li><strong>Invalid syntax:</strong> Warning logged, message not propagated</li>
150
+ <li><strong>Out-of-bounds indices:</strong> Invalid indices skipped, warning logged</li>
151
+ <li><strong>Status display:</strong> Shows current selection and processing state</li>
152
+ </ul>
153
+
154
+ <h3>Performance</h3>
155
+ <p>Selection operations are performed efficiently with minimal memory copying. Node status displays selection information and processing state.</p>
156
+ </script>