Skip to content

Commit b0c0c62

Browse files
committed
Funziona
1 parent f41957c commit b0c0c62

34 files changed

+2565
-1340
lines changed

nodes/knxUltimateHueLight.html

Lines changed: 205 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@
222222
ensureVerticalTabsStyle();
223223
const $knxServerInput = $("#node-input-server");
224224
const KNX_EMPTY_VALUES = new Set(['', 'none', '_ADD_', '__NONE__']);
225+
const $hueServerInput = $("#node-input-serverHue");
226+
const $hueDeviceInput = $("#node-input-hueDevice");
227+
const $deviceNameInput = $("#node-input-name");
228+
const $refreshDevicesButton = $(".hue-refresh-devices");
229+
const $hueDevicesLoading = $(".hue-devices-loading");
230+
let cachedHueDevices = Array.isArray(node._cachedHueLightDevices) ? node._cachedHueLightDevices : [];
231+
node._cachedHueLightDevices = cachedHueDevices;
232+
const defaultHueDevicePlaceholder = $deviceNameInput.attr('placeholder') || '';
233+
let showingNoHueDevicesPlaceholder = false;
234+
const HUE_EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__', '__null__', 'null', 'undefined']);
225235

226236
const resolveKnxServerValue = () => {
227237
const domValue = $knxServerInput.val();
@@ -239,6 +249,117 @@
239249
if (val === undefined || val === null) return false;
240250
return !KNX_EMPTY_VALUES.has(val);
241251
};
252+
253+
const resolveHueServerValue = () => {
254+
const domValue = $hueServerInput.val();
255+
if (domValue !== undefined && domValue !== null) {
256+
const trimmed = String(domValue).trim();
257+
if (trimmed !== '' && !HUE_EMPTY_SERVER_VALUES.has(trimmed.toLowerCase())) return trimmed;
258+
}
259+
if (node.serverHue !== undefined && node.serverHue !== null) {
260+
const stored = String(node.serverHue).trim();
261+
if (stored !== '' && !HUE_EMPTY_SERVER_VALUES.has(stored.toLowerCase())) return stored;
262+
}
263+
return '';
264+
};
265+
266+
const getHueServerConfig = () => {
267+
const id = resolveHueServerValue();
268+
if (id === '') return null;
269+
try {
270+
return RED.nodes.node(id) || null;
271+
} catch (error) {
272+
return null;
273+
}
274+
};
275+
276+
const resolveDeviceTypeSuffix = (deviceType) => {
277+
const normalized = (deviceType || '').toLowerCase();
278+
return normalized === 'grouped_light' ? '#grouped_light' : '#light';
279+
};
280+
281+
const applyHueDevicesPlaceholder = (hasDevices) => {
282+
if (!$deviceNameInput.length) return;
283+
if (hasDevices) {
284+
if (showingNoHueDevicesPlaceholder) {
285+
showingNoHueDevicesPlaceholder = false;
286+
$deviceNameInput.attr('placeholder', defaultHueDevicePlaceholder);
287+
}
288+
return;
289+
}
290+
const message = node._('knxUltimateHueLight.no_devices') || defaultHueDevicePlaceholder;
291+
showingNoHueDevicesPlaceholder = true;
292+
$deviceNameInput.attr('placeholder', message);
293+
if (($deviceNameInput.val() || '').trim() === '') {
294+
$deviceNameInput.val('');
295+
}
296+
};
297+
298+
const filterHueDevices = (devices, term) => {
299+
const cleaned = (term || '').replace(/exactmatch/gi, '').trim();
300+
return $.map(devices, (value) => {
301+
if (!value || !value.id) return null;
302+
const deviceName = value.name || value.id;
303+
if (cleaned !== '' && !htmlUtilsfullCSVSearch(deviceName, cleaned)) {
304+
return null;
305+
}
306+
const suffix = resolveDeviceTypeSuffix(value.deviceType);
307+
return {
308+
hueDevice: `${value.id}${suffix}`,
309+
value: deviceName,
310+
deviceObject: value.deviceObject || value,
311+
deviceType: value.deviceType || 'light'
312+
};
313+
});
314+
};
315+
316+
const fetchHueDevices = (term, respond, { forceRefresh = false } = {}) => {
317+
const hueServer = getHueServerConfig();
318+
if (!hueServer) {
319+
applyHueDevicesPlaceholder(true);
320+
if (typeof respond === 'function') respond([]);
321+
return;
322+
}
323+
if (!forceRefresh && Array.isArray(cachedHueDevices) && cachedHueDevices.length > 0) {
324+
applyHueDevicesPlaceholder(cachedHueDevices.length > 0);
325+
if (typeof respond === 'function') respond(filterHueDevices(cachedHueDevices, term));
326+
return;
327+
}
328+
if ($hueDevicesLoading.length) {
329+
$hueDevicesLoading.show();
330+
}
331+
$.getJSON(`KNXUltimateGetResourcesHUE?rtype=light&serverId=${encodeURIComponent(hueServer.id)}&_=${Date.now()}`, (data) => {
332+
const listCandidates = Array.isArray(data) ? data : (Array.isArray(data?.devices) ? data.devices : []);
333+
cachedHueDevices = listCandidates.map((value) => {
334+
const deviceObject = value.deviceObject || value;
335+
const rawId = deviceObject?.id || value.id || value.rid || '';
336+
const trimmedId = typeof rawId === 'string' ? rawId.trim() : rawId;
337+
if (trimmedId === undefined || trimmedId === null || String(trimmedId).trim() === '') return null;
338+
return {
339+
id: String(trimmedId).trim(),
340+
name: value.name || value.metadata?.name || deviceObject?.metadata?.name || '',
341+
deviceType: deviceObject?.type || value.type || '',
342+
deviceObject
343+
};
344+
}).filter(Boolean);
345+
node._cachedHueLightDevices = cachedHueDevices;
346+
applyHueDevicesPlaceholder(cachedHueDevices.length > 0);
347+
if (typeof respond === 'function') respond(filterHueDevices(cachedHueDevices, term));
348+
}).always(() => {
349+
if ($hueDevicesLoading.length) {
350+
$hueDevicesLoading.hide();
351+
}
352+
}).fail(() => {
353+
cachedHueDevices = [];
354+
node._cachedHueLightDevices = cachedHueDevices;
355+
applyHueDevicesPlaceholder(false);
356+
if ($hueDevicesLoading.length) {
357+
$hueDevicesLoading.hide();
358+
}
359+
if (typeof respond === 'function') respond([]);
360+
});
361+
};
362+
242363
// TIMER BLINK ####################################################
243364
let blinkStatus = 2;
244365
let timerBlinkBackground;
@@ -437,7 +558,6 @@
437558
const $pinSectionRow = $("#node-input-enableNodePINS").closest('.form-row');
438559
const $pinSelect = $("#node-input-enableNodePINS");
439560
const $pinInfoRow = $pinSectionRow.next('.form-tips');
440-
const $hueDeviceInput = $("#node-input-hueDevice");
441561
$tabs.addClass('hue-vertical-tabs');
442562
$tabs.tabs(); // Tabs gestione KNX
443563
$tabs.find('ul').addClass('ui-tabs-nav');
@@ -655,6 +775,76 @@
655775

656776
$hueDeviceInput.on('change.knxUltimateHueLight input.knxUltimateHueLight', updateTabsVisibility);
657777

778+
if (($deviceNameInput.val() || '').trim() !== '') {
779+
applyHueDevicesPlaceholder(true);
780+
} else {
781+
applyHueDevicesPlaceholder(cachedHueDevices.length > 0);
782+
}
783+
784+
if ($deviceNameInput.length) {
785+
if ($deviceNameInput.data('ui-autocomplete')) {
786+
try { $deviceNameInput.autocomplete('destroy'); } catch (error) { /* empty */ }
787+
}
788+
$deviceNameInput.autocomplete({
789+
minLength: 0,
790+
source(request, response) {
791+
fetchHueDevices(request.term, response);
792+
},
793+
select(event, ui) {
794+
if (!ui.item || !ui.item.hueDevice || ui.item.hueDevice === 'error') {
795+
event.preventDefault();
796+
return;
797+
}
798+
$deviceNameInput.val(ui.item.value || '');
799+
node.name = ui.item.value || node.name;
800+
$hueDeviceInput.val(ui.item.hueDevice);
801+
node.hueDevice = ui.item.hueDevice;
802+
node.hueDeviceObject = ui.item.deviceObject || { type: ui.item.deviceType };
803+
updateTabsVisibility();
804+
setTimeout(() => {
805+
try { $deviceNameInput.autocomplete('close'); } catch (error) { /* empty */ }
806+
Go();
807+
}, 0);
808+
},
809+
focus(event, ui) {
810+
event.preventDefault();
811+
$deviceNameInput.val(ui.item && ui.item.value ? ui.item.value : '');
812+
}
813+
}).focus(function () {
814+
$(this).autocomplete('search', `${$(this).val()}exactmatch`);
815+
}).on('input.knxUltimateHueLight', function () {
816+
if ($(this).val().trim() === '') {
817+
$hueDeviceInput.val('');
818+
node.hueDevice = '';
819+
updateTabsVisibility();
820+
}
821+
});
822+
}
823+
824+
if ($refreshDevicesButton.length) {
825+
$refreshDevicesButton.off('.knxUltimateHueLight').on('click.knxUltimateHueLight', () => {
826+
cachedHueDevices = [];
827+
node._cachedHueLightDevices = cachedHueDevices;
828+
fetchHueDevices('', () => {
829+
if ($deviceNameInput.length) {
830+
$deviceNameInput.autocomplete('search', `${$deviceNameInput.val()}exactmatch`);
831+
}
832+
}, { forceRefresh: true });
833+
});
834+
}
835+
836+
if ($hueServerInput.length) {
837+
$hueServerInput.off('.knxUltimateHueLightDevices').on('change.knxUltimateHueLightDevices', () => {
838+
cachedHueDevices = [];
839+
node._cachedHueLightDevices = cachedHueDevices;
840+
showingNoHueDevicesPlaceholder = false;
841+
applyHueDevicesPlaceholder(true);
842+
if ($hueDevicesLoading.length) {
843+
$hueDevicesLoading.hide();
844+
}
845+
});
846+
}
847+
658848
// Get the HUE capabilities to enable/disable UI parts
659849
var getJsonPromise;
660850
const initialHueDeviceRaw = getHueDeviceValue();
@@ -1038,54 +1228,6 @@
10381228
} catch (error) { }
10391229
});
10401230

1041-
// Autocomplete suggestion with HUE Lights
1042-
$("#node-input-name").autocomplete({
1043-
minLength: 0,
1044-
source: function (request, response) {
1045-
$.getJSON("KNXUltimateGetResourcesHUE?rtype=light&serverId=" + $("#node-input-serverHue").val() + "&" + { _: new Date().getTime() }, (data) => {
1046-
response(
1047-
$.map(data.devices, function (value, key) {
1048-
var sSearch = value.name;
1049-
if (!value.name.includes("I'm still connecting")) {
1050-
if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1051-
return {
1052-
hueDevice: value.id,
1053-
value: value.name,
1054-
};
1055-
} else {
1056-
return null;
1057-
}
1058-
} else {
1059-
return {
1060-
hueDevice: value.id,
1061-
value: value.name,
1062-
};
1063-
}
1064-
})
1065-
);
1066-
});
1067-
},
1068-
select: function (event, ui) {
1069-
// Distinguish between group of lights an single light.
1070-
const $nameInput = $("#node-input-name");
1071-
$nameInput.val(ui.item.value || '');
1072-
node.name = ui.item.value || node.name;
1073-
if (ui.item.value.toLowerCase().startsWith("grouped_light")) {
1074-
const storedValue = ui.item.hueDevice + "#grouped_light";
1075-
$("#node-input-hueDevice").val(storedValue);
1076-
node.hueDevice = storedValue;
1077-
} else {
1078-
const storedValue = ui.item.hueDevice + "#light";
1079-
$("#node-input-hueDevice").val(storedValue);
1080-
node.hueDevice = storedValue;
1081-
}
1082-
updateTabsVisibility();
1083-
Go();
1084-
}
1085-
}).focus(function () {
1086-
$(this).autocomplete('search', $(this).val() + 'exactmatch');
1087-
});
1088-
10891231
// Timer connection to backend ####################################################
10901232
let timerWaitBackEndCounter = 0;
10911233
function checkConnection() {
@@ -1174,6 +1316,7 @@
11741316
const serialized = JSON.stringify(collectedEffectRules);
11751317
$("#node-input-effectRules").val(serialized);
11761318
this.effectRules = serialized;
1319+
this._cachedHueLightDevices = [];
11771320
},
11781321
oneditcancel: function () {
11791322
// Return to the info tab
@@ -1183,6 +1326,7 @@
11831326
//RED.sidebar.removeTab("tabNRColor");
11841327
//RED.sidebar.show("help");
11851328

1329+
this._cachedHueLightDevices = [];
11861330
},
11871331
oneditresize: function (size) {
11881332
//var height = size.height;
@@ -1307,9 +1451,18 @@
13071451
</p>
13081452

13091453
<div class="form-row">
1310-
<label for="node-input-hueDevice">
1311-
<i class="fa fa-play-circle"></i>&nbsp<span data-i18n="common.name"></span></label>
1312-
<input type="text" id="node-input-name" placeholder="Enter your hue device name" />
1454+
<label for="node-input-name">
1455+
<i class="fa fa-play-circle"></i> <span data-i18n="common.name"></span>
1456+
</label>
1457+
<input type="text" id="node-input-name" placeholder="Enter your hue device name"
1458+
style="flex:1 1 240px; min-width:240px; max-width:240px;" />
1459+
<button type="button" class="red-ui-button hue-refresh-devices"
1460+
style="margin-left:6px; color:#1b7d33; border-color:#1b7d33;">
1461+
<i class="fa fa-sync"></i>
1462+
</button>
1463+
<span class="hue-devices-loading" style="margin-left:6px; display:none; color:#1b7d33;">
1464+
<i class="fa fa-circle-notch fa-spin"></i>
1465+
</span>
13131466
<input type="hidden" id="node-input-hueDevice" />
13141467
</div>
13151468

0 commit comments

Comments
 (0)