@shockers/node-red-plugin-grouped-flows 0.1.26 → 0.1.30
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 +132 -23
- 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.30";
|
|
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;
|
|
@@ -1053,7 +1154,7 @@
|
|
|
1053
1154
|
|
|
1054
1155
|
if (groupsChanged) {
|
|
1055
1156
|
state.suspendGroupChange = true;
|
|
1056
|
-
clearTabs(state.groupTabs);
|
|
1157
|
+
clearTabs(state.groupTabs, GROUPS_UL_ID);
|
|
1057
1158
|
state.groupTabGroupMap = {};
|
|
1058
1159
|
|
|
1059
1160
|
state.model.groups.forEach(function(group) {
|
|
@@ -1128,7 +1229,7 @@
|
|
|
1128
1229
|
|
|
1129
1230
|
if (flowsChanged) {
|
|
1130
1231
|
state.suspendFlowChange = true;
|
|
1131
|
-
clearTabs(state.flowTabs);
|
|
1232
|
+
clearTabs(state.flowTabs, FLOWS_UL_ID);
|
|
1132
1233
|
state.flowTabWorkspaceMap = {};
|
|
1133
1234
|
|
|
1134
1235
|
selectedGroup.flows.forEach(function(flow) {
|
|
@@ -1335,16 +1436,22 @@
|
|
|
1335
1436
|
applyGroupReorder(newOrder);
|
|
1336
1437
|
},
|
|
1337
1438
|
onchange: function(tab) {
|
|
1338
|
-
if (state.suspendGroupChange
|
|
1439
|
+
if (state.suspendGroupChange) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
var groupKey = getGroupKeyForTab(tab);
|
|
1443
|
+
if (!groupKey) {
|
|
1444
|
+
debug("group onchange ignored: missing group key", { tab: tab });
|
|
1339
1445
|
return;
|
|
1340
1446
|
}
|
|
1341
|
-
onGroupChange(
|
|
1447
|
+
onGroupChange(groupKey);
|
|
1342
1448
|
},
|
|
1343
1449
|
ondblclick: function(tab) {
|
|
1344
|
-
|
|
1450
|
+
var groupKey = getGroupKeyForTab(tab);
|
|
1451
|
+
if (!groupKey) {
|
|
1345
1452
|
return;
|
|
1346
1453
|
}
|
|
1347
|
-
var entry = state.model.groupByName[
|
|
1454
|
+
var entry = state.model.groupByName[groupKey];
|
|
1348
1455
|
if (!entry || !entry.standalone || !entry.standaloneWorkspaceId) {
|
|
1349
1456
|
return;
|
|
1350
1457
|
}
|
|
@@ -1364,30 +1471,32 @@
|
|
|
1364
1471
|
applyFlowReorder(newOrder);
|
|
1365
1472
|
},
|
|
1366
1473
|
onchange: function(tab) {
|
|
1367
|
-
|
|
1474
|
+
var workspaceId = getWorkspaceIdForFlowTab(tab);
|
|
1475
|
+
if (state.suspendFlowChange || !workspaceId) {
|
|
1368
1476
|
return;
|
|
1369
1477
|
}
|
|
1370
|
-
activateCoreWorkspace(
|
|
1478
|
+
activateCoreWorkspace(workspaceId);
|
|
1371
1479
|
},
|
|
1372
1480
|
ondblclick: function(tab) {
|
|
1373
|
-
|
|
1481
|
+
var workspaceId = getWorkspaceIdForFlowTab(tab);
|
|
1482
|
+
if (!workspaceId) {
|
|
1374
1483
|
debug("flow ondblclick ignored: missing tab/workspace");
|
|
1375
1484
|
return;
|
|
1376
1485
|
}
|
|
1377
1486
|
debug("flow ondblclick received", {
|
|
1378
|
-
workspaceId:
|
|
1487
|
+
workspaceId: workspaceId,
|
|
1379
1488
|
pending: state.pendingFlowDoubleWorkspaceId
|
|
1380
1489
|
});
|
|
1381
|
-
if (state.pendingFlowDoubleWorkspaceId !==
|
|
1490
|
+
if (state.pendingFlowDoubleWorkspaceId !== workspaceId) {
|
|
1382
1491
|
debug("flow ondblclick blocked by guard", {
|
|
1383
|
-
workspaceId:
|
|
1492
|
+
workspaceId: workspaceId,
|
|
1384
1493
|
pending: state.pendingFlowDoubleWorkspaceId
|
|
1385
1494
|
});
|
|
1386
1495
|
return;
|
|
1387
1496
|
}
|
|
1388
1497
|
state.pendingFlowDoubleWorkspaceId = null;
|
|
1389
|
-
debug("flow ondblclick accepted", { workspaceId:
|
|
1390
|
-
openWorkspaceEditDialog(
|
|
1498
|
+
debug("flow ondblclick accepted", { workspaceId: workspaceId });
|
|
1499
|
+
openWorkspaceEditDialog(workspaceId);
|
|
1391
1500
|
}
|
|
1392
1501
|
});
|
|
1393
1502
|
|
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.30",
|
|
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",
|