@shockers/node-red-plugin-grouped-flows 0.1.26 → 0.1.31
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.
- package/grouped-flows.html +139 -26
- package/package.json +1 -1
package/grouped-flows.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
5
|
var PLUGIN_ID = "nr-grouped-flows";
|
|
6
|
-
var PLUGIN_VERSION = "0.1.
|
|
6
|
+
var PLUGIN_VERSION = "0.1.31";
|
|
7
7
|
var ORIGINAL_UL_ID = "red-ui-workspace-tabs";
|
|
8
8
|
var CONTAINER_ID = "nr-grouped-flows";
|
|
9
9
|
var GROUPS_UL_ID = "nr-grouped-flows-groups";
|
|
@@ -247,7 +247,25 @@
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
function makeStableGroupTabId(groupName) {
|
|
250
|
-
|
|
250
|
+
var normalized = String(groupName || "");
|
|
251
|
+
var slug = normalized
|
|
252
|
+
.toLowerCase()
|
|
253
|
+
.replace(/\s+/g, "-")
|
|
254
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
255
|
+
.replace(/-+/g, "-")
|
|
256
|
+
.replace(/^-+|-+$/g, "");
|
|
257
|
+
if (!slug) {
|
|
258
|
+
slug = "group";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Deterministic hash keeps IDs stable and avoids collisions for similar slugs.
|
|
262
|
+
var hash = 2166136261;
|
|
263
|
+
for (var i = 0; i < normalized.length; i += 1) {
|
|
264
|
+
hash ^= normalized.charCodeAt(i);
|
|
265
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
266
|
+
}
|
|
267
|
+
var suffix = (hash >>> 0).toString(36);
|
|
268
|
+
return "nr-group-tab-" + slug + "-" + suffix;
|
|
251
269
|
}
|
|
252
270
|
|
|
253
271
|
function makeStableTopFlowTabId(workspaceId) {
|
|
@@ -407,14 +425,63 @@
|
|
|
407
425
|
};
|
|
408
426
|
}
|
|
409
427
|
|
|
410
|
-
function
|
|
411
|
-
if (!
|
|
428
|
+
function getTabId(tab) {
|
|
429
|
+
if (!tab) {
|
|
430
|
+
return "";
|
|
431
|
+
}
|
|
432
|
+
if (typeof tab === "string") {
|
|
433
|
+
return tab;
|
|
434
|
+
}
|
|
435
|
+
if (typeof tab.id === "string" && tab.id) {
|
|
436
|
+
return tab.id;
|
|
437
|
+
}
|
|
438
|
+
if (typeof tab.tabId === "string" && tab.tabId) {
|
|
439
|
+
return tab.tabId;
|
|
440
|
+
}
|
|
441
|
+
if (typeof tab.href === "string" && tab.href.charAt(0) === "#") {
|
|
442
|
+
return tab.href.slice(1);
|
|
443
|
+
}
|
|
444
|
+
return "";
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function listTabIds(tabApi, ulId) {
|
|
448
|
+
var tabIds = [];
|
|
449
|
+
if (tabApi && typeof tabApi.listTabs === "function") {
|
|
450
|
+
(tabApi.listTabs() || []).forEach(function(tabEntry) {
|
|
451
|
+
var tabId = getTabId(tabEntry);
|
|
452
|
+
if (tabId) {
|
|
453
|
+
tabIds.push(tabId);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (!tabIds.length && ulId) {
|
|
459
|
+
$("#" + ulId + " a.red-ui-tab-label[href^='#']").each(function() {
|
|
460
|
+
var href = $(this).attr("href") || "";
|
|
461
|
+
if (href.charAt(0) === "#") {
|
|
462
|
+
tabIds.push(href.slice(1));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
var uniqueTabIds = [];
|
|
468
|
+
var seen = {};
|
|
469
|
+
tabIds.forEach(function(tabId) {
|
|
470
|
+
if (!seen[tabId]) {
|
|
471
|
+
seen[tabId] = true;
|
|
472
|
+
uniqueTabIds.push(tabId);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
return uniqueTabIds;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function clearTabs(tabApi, ulId) {
|
|
479
|
+
if (!tabApi || typeof tabApi.removeTab !== "function") {
|
|
412
480
|
return;
|
|
413
481
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
tabApi.removeTab(id);
|
|
482
|
+
|
|
483
|
+
listTabIds(tabApi, ulId).forEach(function(tabId) {
|
|
484
|
+
tabApi.removeTab(tabId);
|
|
418
485
|
});
|
|
419
486
|
}
|
|
420
487
|
|
|
@@ -486,6 +553,40 @@
|
|
|
486
553
|
}
|
|
487
554
|
});
|
|
488
555
|
}
|
|
556
|
+
|
|
557
|
+
function getGroupKeyForTab(tab) {
|
|
558
|
+
if (tab && typeof tab.groupKey === "string" && tab.groupKey) {
|
|
559
|
+
return tab.groupKey;
|
|
560
|
+
}
|
|
561
|
+
var tabId = getTabId(tab);
|
|
562
|
+
if (!tabId) {
|
|
563
|
+
var groupHref = $("#" + GROUPS_UL_ID + " li.red-ui-tab.active a.red-ui-tab-label").first().attr("href") || "";
|
|
564
|
+
if (groupHref.charAt(0) === "#") {
|
|
565
|
+
tabId = groupHref.slice(1);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (tabId && state.groupTabGroupMap[tabId]) {
|
|
569
|
+
return state.groupTabGroupMap[tabId];
|
|
570
|
+
}
|
|
571
|
+
return "";
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function getWorkspaceIdForFlowTab(tab) {
|
|
575
|
+
if (tab && typeof tab.workspaceId === "string" && tab.workspaceId) {
|
|
576
|
+
return tab.workspaceId;
|
|
577
|
+
}
|
|
578
|
+
var tabId = getTabId(tab);
|
|
579
|
+
if (!tabId) {
|
|
580
|
+
var flowHref = $("#" + FLOWS_UL_ID + " li.red-ui-tab.active a.red-ui-tab-label").first().attr("href") || "";
|
|
581
|
+
if (flowHref.charAt(0) === "#") {
|
|
582
|
+
tabId = flowHref.slice(1);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (tabId && state.flowTabWorkspaceMap[tabId]) {
|
|
586
|
+
return state.flowTabWorkspaceMap[tabId];
|
|
587
|
+
}
|
|
588
|
+
return "";
|
|
589
|
+
}
|
|
489
590
|
|
|
490
591
|
function applyFlowReorder(newOrder) {
|
|
491
592
|
if (state.applyingFlowReorder) {
|
|
@@ -828,7 +929,7 @@
|
|
|
828
929
|
|
|
829
930
|
function resetFlowTabsState() {
|
|
830
931
|
state.suspendFlowChange = true;
|
|
831
|
-
clearTabs(state.flowTabs);
|
|
932
|
+
clearTabs(state.flowTabs, FLOWS_UL_ID);
|
|
832
933
|
state.suspendFlowChange = false;
|
|
833
934
|
state.flowTabWorkspaceMap = {};
|
|
834
935
|
state.lastRenderedFlowGroup = null;
|
|
@@ -866,8 +967,10 @@
|
|
|
866
967
|
if (!workspace || !input.length) {
|
|
867
968
|
return;
|
|
868
969
|
}
|
|
869
|
-
|
|
870
|
-
var oldValue = normalizeGroupName(
|
|
970
|
+
|
|
971
|
+
var oldValue = normalizeGroupName(
|
|
972
|
+
workspace[WORKSPACE_GROUP_PROPERTY] || getPersistedGroup(workspaceId)
|
|
973
|
+
);
|
|
871
974
|
var newValue = normalizeGroupName(input.val());
|
|
872
975
|
if (newValue === oldValue) {
|
|
873
976
|
return;
|
|
@@ -938,7 +1041,9 @@
|
|
|
938
1041
|
}
|
|
939
1042
|
}
|
|
940
1043
|
|
|
941
|
-
row.find("#" + DIALOG_GROUP_INPUT_ID).val(
|
|
1044
|
+
row.find("#" + DIALOG_GROUP_INPUT_ID).val(
|
|
1045
|
+
normalizeGroupName(workspace[WORKSPACE_GROUP_PROPERTY] || getPersistedGroup(workspaceId))
|
|
1046
|
+
);
|
|
942
1047
|
bindDialogGroupSave(workspaceId, form);
|
|
943
1048
|
return true;
|
|
944
1049
|
}
|
|
@@ -1053,7 +1158,7 @@
|
|
|
1053
1158
|
|
|
1054
1159
|
if (groupsChanged) {
|
|
1055
1160
|
state.suspendGroupChange = true;
|
|
1056
|
-
clearTabs(state.groupTabs);
|
|
1161
|
+
clearTabs(state.groupTabs, GROUPS_UL_ID);
|
|
1057
1162
|
state.groupTabGroupMap = {};
|
|
1058
1163
|
|
|
1059
1164
|
state.model.groups.forEach(function(group) {
|
|
@@ -1128,7 +1233,7 @@
|
|
|
1128
1233
|
|
|
1129
1234
|
if (flowsChanged) {
|
|
1130
1235
|
state.suspendFlowChange = true;
|
|
1131
|
-
clearTabs(state.flowTabs);
|
|
1236
|
+
clearTabs(state.flowTabs, FLOWS_UL_ID);
|
|
1132
1237
|
state.flowTabWorkspaceMap = {};
|
|
1133
1238
|
|
|
1134
1239
|
selectedGroup.flows.forEach(function(flow) {
|
|
@@ -1335,16 +1440,22 @@
|
|
|
1335
1440
|
applyGroupReorder(newOrder);
|
|
1336
1441
|
},
|
|
1337
1442
|
onchange: function(tab) {
|
|
1338
|
-
if (state.suspendGroupChange
|
|
1443
|
+
if (state.suspendGroupChange) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
var groupKey = getGroupKeyForTab(tab);
|
|
1447
|
+
if (!groupKey) {
|
|
1448
|
+
debug("group onchange ignored: missing group key", { tab: tab });
|
|
1339
1449
|
return;
|
|
1340
1450
|
}
|
|
1341
|
-
onGroupChange(
|
|
1451
|
+
onGroupChange(groupKey);
|
|
1342
1452
|
},
|
|
1343
1453
|
ondblclick: function(tab) {
|
|
1344
|
-
|
|
1454
|
+
var groupKey = getGroupKeyForTab(tab);
|
|
1455
|
+
if (!groupKey) {
|
|
1345
1456
|
return;
|
|
1346
1457
|
}
|
|
1347
|
-
var entry = state.model.groupByName[
|
|
1458
|
+
var entry = state.model.groupByName[groupKey];
|
|
1348
1459
|
if (!entry || !entry.standalone || !entry.standaloneWorkspaceId) {
|
|
1349
1460
|
return;
|
|
1350
1461
|
}
|
|
@@ -1364,30 +1475,32 @@
|
|
|
1364
1475
|
applyFlowReorder(newOrder);
|
|
1365
1476
|
},
|
|
1366
1477
|
onchange: function(tab) {
|
|
1367
|
-
|
|
1478
|
+
var workspaceId = getWorkspaceIdForFlowTab(tab);
|
|
1479
|
+
if (state.suspendFlowChange || !workspaceId) {
|
|
1368
1480
|
return;
|
|
1369
1481
|
}
|
|
1370
|
-
activateCoreWorkspace(
|
|
1482
|
+
activateCoreWorkspace(workspaceId);
|
|
1371
1483
|
},
|
|
1372
1484
|
ondblclick: function(tab) {
|
|
1373
|
-
|
|
1485
|
+
var workspaceId = getWorkspaceIdForFlowTab(tab);
|
|
1486
|
+
if (!workspaceId) {
|
|
1374
1487
|
debug("flow ondblclick ignored: missing tab/workspace");
|
|
1375
1488
|
return;
|
|
1376
1489
|
}
|
|
1377
1490
|
debug("flow ondblclick received", {
|
|
1378
|
-
workspaceId:
|
|
1491
|
+
workspaceId: workspaceId,
|
|
1379
1492
|
pending: state.pendingFlowDoubleWorkspaceId
|
|
1380
1493
|
});
|
|
1381
|
-
if (state.pendingFlowDoubleWorkspaceId !==
|
|
1494
|
+
if (state.pendingFlowDoubleWorkspaceId !== workspaceId) {
|
|
1382
1495
|
debug("flow ondblclick blocked by guard", {
|
|
1383
|
-
workspaceId:
|
|
1496
|
+
workspaceId: workspaceId,
|
|
1384
1497
|
pending: state.pendingFlowDoubleWorkspaceId
|
|
1385
1498
|
});
|
|
1386
1499
|
return;
|
|
1387
1500
|
}
|
|
1388
1501
|
state.pendingFlowDoubleWorkspaceId = null;
|
|
1389
|
-
debug("flow ondblclick accepted", { workspaceId:
|
|
1390
|
-
openWorkspaceEditDialog(
|
|
1502
|
+
debug("flow ondblclick accepted", { workspaceId: workspaceId });
|
|
1503
|
+
openWorkspaceEditDialog(workspaceId);
|
|
1391
1504
|
}
|
|
1392
1505
|
});
|
|
1393
1506
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shockers/node-red-plugin-grouped-flows",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Replaces the built-in Node-RED flows tab bar with a grouped two-tier tab bar to improve navigation of large Node-RED environments.",
|
|
5
5
|
"author": "Sven Hockers",
|
|
6
6
|
"license": "Apache-2.0",
|