@bldgblocks/node-red-contrib-control 0.2.1 → 0.2.2

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.
@@ -96,19 +96,21 @@ module.exports = function(RED) {
96
96
  // 1. msg.priority (number 1-16 or "default") — explicit per-message override
97
97
  // 2. msg.context ("priority1"–"priority16" or "default") — tagged-input pattern (matches priority-block)
98
98
  // 3. Configured writePriority (dropdown / msg / flow typed-input)
99
+ // Use local variable — never mutate node.writePriority so the config default is preserved across messages
100
+ let activePrioritySlot = null;
99
101
  try {
100
- if (msg.hasOwnProperty("priority")) {
101
- // Source 1: msg.priority (direct number or "default")
102
+ if (msg.hasOwnProperty("priority") && (typeof msg.priority === "number" || typeof msg.priority === "string")) {
103
+ // Source 1: msg.priority (direct number or "default") — skip objects (e.g. priority array from state)
102
104
  const mp = msg.priority;
103
105
  if (mp === "default") {
104
- node.writePriority = "default";
106
+ activePrioritySlot = "default";
105
107
  } else {
106
108
  const p = parseInt(mp, 10);
107
109
  if (isNaN(p) || p < 1 || p > 16) {
108
110
  node.isBusy = false;
109
111
  return utils.sendError(node, msg, done, `Invalid msg.priority: ${mp}`);
110
112
  }
111
- node.writePriority = String(p);
113
+ activePrioritySlot = String(p);
112
114
  }
113
115
  } else if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
114
116
  // Source 2: msg.context tagged-input ("priority8", "default", etc.)
@@ -116,21 +118,20 @@ module.exports = function(RED) {
116
118
  const ctx = msg.context;
117
119
  const priorityMatch = /^priority([1-9]|1[0-6])$/.exec(ctx);
118
120
  if (priorityMatch) {
119
- node.writePriority = priorityMatch[1];
121
+ activePrioritySlot = priorityMatch[1];
120
122
  } else if (ctx === "default") {
121
- node.writePriority = "default";
123
+ activePrioritySlot = "default";
124
+ }
125
+ // Other contexts (e.g. "reload") leave activePrioritySlot null → falls to config
126
+ }
127
+
128
+ // Source 3: Fall back to configured typed-input when no message override matched
129
+ if (activePrioritySlot === null) {
130
+ if (utils.requiresEvaluation(config.writePriorityType)) {
131
+ activePrioritySlot = await utils.evaluateNodeProperty(config.writePriority, config.writePriorityType, node, msg);
132
+ } else {
133
+ activePrioritySlot = config.writePriority;
122
134
  }
123
- // Other contexts (e.g. "reload") fall through — config stays as-is
124
- } else {
125
- // Source 3: Configured typed-input (dropdown, msg path, flow variable)
126
- const evaluations = [];
127
- evaluations.push(
128
- utils.requiresEvaluation(config.writePriorityType)
129
- ? utils.evaluateNodeProperty(config.writePriority, config.writePriorityType, node, msg)
130
- : Promise.resolve(node.writePriority)
131
- );
132
- const results = await Promise.all(evaluations);
133
- node.writePriority = results[0];
134
135
  }
135
136
  } catch (err) {
136
137
  throw new Error(`Property Eval Error: ${err.message}`);
@@ -168,15 +169,15 @@ module.exports = function(RED) {
168
169
  }
169
170
 
170
171
  // Update State
171
- if (node.writePriority === 'default') {
172
+ if (activePrioritySlot === 'default') {
172
173
  state.defaultValue = inputValue === null || inputValue === "null" ? node.defaultValue : inputValue;
173
174
  } else {
174
- const priority = parseInt(node.writePriority, 10);
175
+ const priority = parseInt(activePrioritySlot, 10);
175
176
  if (isNaN(priority) || priority < 1 || priority > 16) {
176
- return utils.sendError(node, msg, done, `Invalid priority: ${node.writePriority}`);
177
+ return utils.sendError(node, msg, done, `Invalid priority: ${activePrioritySlot}`);
177
178
  }
178
179
  if (inputValue !== undefined) {
179
- state.priority[node.writePriority] = inputValue;
180
+ state.priority[activePrioritySlot] = inputValue;
180
181
  }
181
182
  }
182
183
 
@@ -196,9 +197,9 @@ module.exports = function(RED) {
196
197
  if (node.storeName !== 'default') {
197
198
  await utils.setGlobalState(node, node.varName, 'default', state);
198
199
  }
199
- prefix = `${node.writePriority === 'default' ? '' : 'P'}`;
200
+ prefix = `${activePrioritySlot === 'default' ? '' : 'P'}`;
200
201
  const statePrefix = `${state.activePriority === 'default' ? '' : 'P'}`;
201
- const noChangeText = `no change: ${prefix}${node.writePriority}:${inputValue} > active: ${statePrefix}${state.activePriority}:${state.value}${state.units || ''}`;
202
+ const noChangeText = `no change: ${prefix}${activePrioritySlot}:${inputValue} > active: ${statePrefix}${state.activePriority}:${state.value}${state.units || ''}`;
202
203
  utils.setStatusUnchanged(node, noChangeText);
203
204
  // Pass message through even if no context change
204
205
  send({ ...state });
@@ -236,9 +237,9 @@ module.exports = function(RED) {
236
237
  await utils.setGlobalState(node, node.varName, 'default', state);
237
238
  }
238
239
 
239
- prefix = `${node.writePriority === 'default' ? '' : 'P'}`;
240
+ prefix = `${activePrioritySlot === 'default' ? '' : 'P'}`;
240
241
  const statePrefix = `${state.activePriority === 'default' ? '' : 'P'}`;
241
- const statusText = `write: ${prefix}${node.writePriority}:${inputValue}${state.units || ''} > active: ${statePrefix}${state.activePriority}:${state.value}${state.units || ''}`;
242
+ const statusText = `write: ${prefix}${activePrioritySlot}:${inputValue}${state.units || ''} > active: ${statePrefix}${state.activePriority}:${state.value}${state.units || ''}`;
242
243
 
243
244
  RED.events.emit("bldgblocks:global:value-changed", {
244
245
  key: node.varName,
@@ -105,7 +105,7 @@ module.exports = function(RED) {
105
105
 
106
106
  // Passthrough
107
107
  const prefix = msg.activePriority === 'default' ? '' : 'P';
108
- const statusText = `Passthrough: ${prefix}${msg.activePriority}:${msg.value}${msg.units}`;
108
+ const statusText = `Passthrough: ${prefix}${msg.activePriority}:${msg.value}${msg.units === null ? "" : ` ${msg.units}`}`;
109
109
  utils.sendSuccess(node, msg, done, statusText, node.pointId, "ring");
110
110
 
111
111
  } catch (err) {
@@ -29,6 +29,7 @@
29
29
  <tr style="border-bottom:2px solid #ccc;text-align:left;">
30
30
  <th style="padding:4px;width:28px;"><input type="checkbox" id="node-input-select-all" title="Select all"></th>
31
31
  <th style="padding:4px;width:70px;">ID</th>
32
+ <th style="padding:4px;width:30px;" title="Writable"><i class="fa fa-pencil"></i></th>
32
33
  <th style="padding:4px;">Path</th>
33
34
  <th style="padding:4px;width:30px;"></th>
34
35
  </tr>
@@ -106,7 +107,7 @@
106
107
  $selectAll.prop('checked', false);
107
108
 
108
109
  if (!points.length) {
109
- $tbody.append('<tr><td colspan="4" style="padding:8px;color:#999;">No points defined (deploy first if newly added)</td></tr>');
110
+ $tbody.append('<tr><td colspan="5" style="padding:8px;color:#999;">No points defined (deploy first if newly added)</td></tr>');
110
111
  $status.text("");
111
112
  return;
112
113
  }
@@ -141,6 +142,13 @@
141
142
 
142
143
  const $tdId = $('<td>').css({ padding: '3px 4px' }).append($idInput);
143
144
 
145
+ // Writable checkbox
146
+ const $wrCb = $('<input type="checkbox" class="point-writable">')
147
+ .prop('checked', !!pt.writable)
148
+ .attr('data-original-writable', pt.writable ? '1' : '0')
149
+ .attr('title', 'Allow network writes to this point');
150
+ const $tdWr = $('<td>').css({ padding: '3px 4px', textAlign: 'center' }).append($wrCb);
151
+
144
152
  // Path display
145
153
  const displayPath = pt.path && pt.path !== "not ready" ? pt.path : (pt.editorName || 'not deployed');
146
154
  const $tdPath = $('<td>').css({ padding: '3px 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '200px' })
@@ -157,7 +165,7 @@
157
165
  });
158
166
  const $tdBtn = $('<td>').css({ padding: '3px 4px' }).append($btn);
159
167
 
160
- $tr.append($tdCb, $tdId, $tdPath, $tdBtn);
168
+ $tr.append($tdCb, $tdId, $tdWr, $tdPath, $tdBtn);
161
169
  $tbody.append($tr);
162
170
  });
163
171
 
@@ -267,11 +275,25 @@
267
275
  changeCount++;
268
276
  }
269
277
  }
278
+
279
+ // Check writable change
280
+ const $wrCb = $(this).find('.point-writable');
281
+ const newWr = $wrCb.is(':checked');
282
+ const origWr = $wrCb.attr('data-original-writable') === '1';
283
+ if (newWr !== origWr) {
284
+ const editorNode = RED.nodes.node(nid);
285
+ if (editorNode) {
286
+ editorNode.writable = newWr;
287
+ editorNode.changed = true;
288
+ editorNode.dirty = true;
289
+ changeCount++;
290
+ }
291
+ }
270
292
  });
271
293
 
272
294
  if (changeCount > 0) {
273
295
  RED.nodes.dirty(true);
274
- RED.notify(changeCount + " point ID" + (changeCount !== 1 ? "s" : "") + " updated. Deploy to apply.", "success");
296
+ RED.notify(changeCount + " change" + (changeCount !== 1 ? "s" : "") + " applied. Deploy to take effect.", "success");
275
297
  }
276
298
  }
277
299
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bldgblocks/node-red-contrib-control",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Sedona-inspired control nodes for Node-RED",
5
5
  "keywords": [
6
6
  "node-red",