diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index c4390d7f5..93e847066 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -149,7 +149,11 @@ "toggle-navigator": "Toggle navigator", "zoom-out": "Zoom out", "zoom-reset": "Reset zoom", - "zoom-in": "Zoom in" + "zoom-in": "Zoom in", + "search-flows": "Search flows", + "search-prev": "Previous", + "search-next": "Next", + "search-counter": "\"__term__\" __result__ of __count__" }, "user": { "loggedInAs": "Logged in as __name__", diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index 87b559265..62a1f4414 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -91,6 +91,9 @@ "alt-a c": "core:align-selection-to-center", "alt-a h": "core:distribute-selection-horizontally", "alt-a v": "core:distribute-selection-vertically", + "shift-f": "core:search-previous", + "f": "core:search-next", "alt-l l": "core:split-wire-with-link-nodes" + } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 6834679e2..e477fbc22 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -25,6 +25,8 @@ RED.search = (function() { var searchHistory = []; var index = {}; var currentResults = []; + var activeResults = []; + var currentIndex = 0; var previousActiveElement; function indexProperty(node,label,property) { @@ -329,7 +331,8 @@ RED.search = (function() { } } else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) { if (currentResults.length > 0) { - reveal(currentResults[Math.max(0,selected)].node); + currentIndex = Math.max(0,selected); + reveal(currentResults[currentIndex].node); } } } @@ -413,6 +416,7 @@ RED.search = (function() { div.on("click", function(evt) { evt.preventDefault(); + currentIndex = i; reveal(node); }); } @@ -428,13 +432,59 @@ RED.search = (function() { if (existingIndex > -1) { searchHistory.splice(existingIndex,1); } - searchHistory.unshift(searchInput.val()); - hide(); + searchHistory.unshift(searchVal); + $("#red-ui-view-searchtools-search").data("term", searchVal); + activeResults = Object.assign([], currentResults); + hide(null, activeResults.length > 0); RED.view.reveal(node.id); } + function revealPrev() { + if (disabled) { + updateSearchToolbar(); + return; + } + if (!searchResults || !activeResults.length) { + show(); + return; + } + if (currentIndex > 0) { + currentIndex--; + } else { + currentIndex = activeResults.length - 1; + } + const n = activeResults[currentIndex]; + if (n && n.node && n.node.id) { + RED.view.reveal(n.node.id); + $("#red-ui-view-searchtools-prev").trigger("focus"); + } + updateSearchToolbar(); + } + function revealNext() { + if (disabled) { + updateSearchToolbar(); + return; + } + if (!searchResults || !activeResults.length) { + show(); + return; + } + if (currentIndex < activeResults.length - 1) { + currentIndex++ + } else { + currentIndex = 0; + } + const n = activeResults[currentIndex]; + if (n && n.node && n.node.id) { + RED.view.reveal(n.node.id); + $("#red-ui-view-searchtools-next").trigger("focus"); + } + updateSearchToolbar(); + } + function show(v) { if (disabled) { + updateSearchToolbar(); return; } if (!visible) { @@ -461,7 +511,7 @@ RED.search = (function() { searchInput.trigger("focus"); } - function hide() { + function hide(el, keepSearchToolbar) { if (visible) { visible = false; $("#red-ui-header-shade").hide(); @@ -475,13 +525,37 @@ RED.search = (function() { }); } RED.events.emit("search:close"); - if (previousActiveElement) { + if (previousActiveElement && (!keepSearchToolbar || !activeResults.length)) { $(previousActiveElement).trigger("focus"); - previousActiveElement = null; } + previousActiveElement = null; + } + if(!keepSearchToolbar) { + clearActiveSearch(); + } + updateSearchToolbar(); + if(keepSearchToolbar && activeResults.length) { + $("#red-ui-view-searchtools-next").trigger("focus"); + } + } + function updateSearchToolbar() { + if (!disabled && currentIndex >= 0 && activeResults && activeResults.length) { + let term = $("#red-ui-view-searchtools-search").data("term") || ""; + if (term.length > 16) { + term = term.substring(0, 12) + "..." + } + const i18nSearchCounterData = { + term: term, + result: (currentIndex + 1), + count: activeResults.length + } + $("#red-ui-view-searchtools-counter").text(RED._('actions.search-counter', i18nSearchCounterData)); + $("#view-search-tools > :not(:first-child)").show(); //show other tools + } else { + clearActiveSearch(); + $("#view-search-tools > :not(:first-child)").hide(); //hide all but search button } } - function clearIndex() { index = {}; } @@ -503,6 +577,12 @@ RED.search = (function() { addItemToIndex(item); } + function clearActiveSearch() { + activeResults = []; + currentIndex = 0; + $("#red-ui-view-searchtools-search").data("term", ""); + } + function getSearchOptions() { return [ {label:RED._("search.options.configNodes"), value:"is:config"}, @@ -513,10 +593,13 @@ RED.search = (function() { {label:RED._("search.options.unusedSubflows"), value:"is:subflow is:unused"}, {label:RED._("search.options.hiddenFlows"), value:"is:hidden"}, ] + } function init() { RED.actions.add("core:search",show); + RED.actions.add("core:search-previous",revealPrev); + RED.actions.add("core:search-next",revealNext); RED.events.on("editor:open",function() { disabled = true; }); RED.events.on("editor:close",function() { disabled = false; }); @@ -527,11 +610,21 @@ RED.search = (function() { RED.keyboard.add("red-ui-search","escape",hide); + RED.keyboard.add("view-search-tools","escape",function() { + clearActiveSearch(); + updateSearchToolbar(); + }); + $("#red-ui-header-shade").on('mousedown',hide); $("#red-ui-editor-shade").on('mousedown',hide); $("#red-ui-palette-shade").on('mousedown',hide); $("#red-ui-sidebar-shade").on('mousedown',hide); + $("#red-ui-view-searchtools-close").on("click", function close() { + clearActiveSearch(); + updateSearchToolbar(); + }); + $("#red-ui-view-searchtools-close").trigger("click"); RED.events.on("workspace:clear", clearIndex); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js index 4c9156207..7fde232c3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js @@ -31,6 +31,7 @@ RED.statusBar = (function() { function addWidget(options) { widgets[options.id] = options; var el = $(''); + el.prop('id', options.id); options.element.appendTo(el); if (options.align === 'left') { leftBucket.append(el); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js index 492600f91..109a7661b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js @@ -15,6 +15,8 @@ **/ RED.sidebar.config = (function() { + let flashingConfigNode; + let flashingConfigNodeTimer; var content = document.createElement("div"); content.className = "red-ui-sidebar-node-config"; @@ -145,6 +147,7 @@ RED.sidebar.config = (function() { var entry = $('
  • ').appendTo(list); var nodeDiv = $('
    ').appendTo(entry); entry.data('node',node.id); + nodeDiv.data('node',node.id); var label = $('
    ').text(label).appendTo(nodeDiv); if (node.d) { nodeDiv.addClass("red-ui-palette-node-config-disabled"); @@ -350,6 +353,32 @@ RED.sidebar.config = (function() { RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes")); } + + function flashConfigNode(el) { + if(flashingConfigNode && flashingConfigNode.length) { + //cancel current flashing node before flashing new node + clearInterval(flashingConfigNodeTimer); + flashingConfigNodeTimer = null; + flashingConfigNode.children("div").removeClass('highlighted'); + flashingConfigNode = null; + } + if(!el || !el.children("div").length) { return; } + + flashingConfigNodeTimer = setInterval(function(flashEndTime) { + if (flashEndTime >= Date.now()) { + const highlighted = el.children("div").hasClass("highlighted"); + el.children("div").toggleClass('highlighted', !highlighted) + } else { + clearInterval(flashingConfigNodeTimer); + flashingConfigNodeTimer = null; + flashingConfigNode = null; + el.children("div").removeClass('highlighted'); + } + }, 100, Date.now() + 2200); + flashingConfigNode = el; + el.children("div").addClass('highlighted'); + } + function show(id) { if (typeof id === 'boolean') { if (id) { @@ -374,19 +403,7 @@ RED.sidebar.config = (function() { } else if (y<0) { scrollWindow.animate({scrollTop: '+='+(y-10)},150); } - var flash = 21; - var flashFunc = function() { - if ((flash%2)===0) { - node.removeClass('node_highlighted'); - } else { - node.addClass('node_highlighted'); - } - flash--; - if (flash >= 0) { - setTimeout(flashFunc,100); - } - } - flashFunc(); + flashConfigNode(node, id); },100); } RED.sidebar.show("config"); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 7a43046da..f85ff66b1 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -92,6 +92,9 @@ RED.view = (function() { var lastClickPosition = []; var selectNodesOptions; + let flashingNodeId; + let flashingNodeTimer; + var clipboard = ""; // Note: these are the permitted status colour aliases. The actual RGB values @@ -452,6 +455,33 @@ RED.view = (function() { } }); + //add search to status-toolbar + RED.statusBar.add({ + id: "view-search-tools", + align: "left", + hidden: false, + element: $(''+ + '' + + '' + + '' + + '? of ?' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '') + }) + $("#red-ui-view-searchtools-search").on("click", searchFlows); + RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); + $("#red-ui-view-searchtools-prev").on("click", searchPrev); + RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); + $("#red-ui-view-searchtools-next").on("click", searchNext); + RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); + RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); + // Handle nodes dragged from the palette chart.droppable({ accept:".red-ui-palette-node", @@ -2032,6 +2062,9 @@ RED.view = (function() { } } function zoomZero() { zoomView(1); } + function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); } + function searchPrev() { RED.actions.invoke("core:search-previous"); } + function searchNext() { RED.actions.invoke("core:search-next"); } function zoomView(factor) { @@ -5805,6 +5838,37 @@ RED.view = (function() { return result; } + + function flashNode(n) { + let node = n; + if(typeof node === "string") { node = RED.nodes.node(n); } + if(!node) { return; } + + const flashingNode = flashingNodeTimer && flashingNodeId && RED.nodes.node(flashingNodeId); + if(flashingNode) { + //cancel current flashing node before flashing new node + clearInterval(flashingNodeTimer); + flashingNodeTimer = null; + flashingNode.dirty = true; + flashingNode.highlighted = false; + } + + flashingNodeTimer = setInterval(function(flashEndTime) { + node.dirty = true; + if (flashEndTime >= Date.now()) { + node.highlighted = !node.highlighted; + } else { + clearInterval(flashingNodeTimer); + flashingNodeTimer = null; + node.highlighted = false; + flashingNodeId = null; + } + RED.view.redraw(); + }, 100, Date.now() + 2200) + flashingNodeId = node.id; + node.highlighted = true; + RED.view.redraw(); + } return { init: init, state:function(state) { @@ -5901,7 +5965,7 @@ RED.view = (function() { getActiveGroup: function() { return activeGroup }, reveal: function(id,triggerHighlight) { if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { - RED.workspaces.show(id); + RED.workspaces.show(id, null, null, true); } else { var node = RED.nodes.node(id) || RED.nodes.group(id); if (node) { @@ -5926,24 +5990,7 @@ RED.view = (function() { },200); } if (triggerHighlight !== false) { - node.highlighted = true; - if (!node._flashing) { - node._flashing = true; - var flash = 22; - var flashFunc = function() { - flash--; - node.dirty = true; - if (flash >= 0) { - node.highlighted = !node.highlighted; - setTimeout(flashFunc,100); - } else { - node.highlighted = false; - delete node._flashing; - } - RED.view.redraw(); - } - flashFunc(); - } + flashNode(node); } } else if (node._def.category === 'config') { RED.sidebar.config.show(id); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index c899403eb..673607229 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -24,6 +24,9 @@ RED.workspaces = (function() { var hideStack = []; var viewStackPos = 0; + let flashingTab; + let flashingTabTimer; + function addToViewStack(id) { if (viewStackPos !== viewStack.length) { viewStack.splice(viewStackPos); @@ -531,6 +534,31 @@ RED.workspaces = (function() { workspace_tabs.order(order); } + function flashTab(tabId) { + if(flashingTab && flashingTab.length) { + //cancel current flashing node before flashing new node + clearInterval(flashingTabTimer); + flashingTabTimer = null; + flashingTab.removeClass('highlighted'); + flashingTab = null; + } + let tab = $("#red-ui-tab-" + tabId); + if(!tab || !tab.length) { return; } + + flashingTabTimer = setInterval(function(flashEndTime) { + if (flashEndTime >= Date.now()) { + const highlighted = tab.hasClass("highlighted"); + tab.toggleClass('highlighted', !highlighted) + } else { + clearInterval(flashingTabTimer); + flashingTabTimer = null; + flashingTab = null; + tab.removeClass('highlighted'); + } + }, 100, Date.now() + 2200); + flashingTab = tab; + tab.addClass('highlighted'); + } return { init: init, add: addWorkspace, @@ -563,7 +591,7 @@ RED.workspaces = (function() { isHidden: function(id) { return hideStack.includes(id) }, - show: function(id,skipStack,unhideOnly) { + show: function(id,skipStack,unhideOnly,flash) { if (!workspace_tabs.contains(id)) { var sf = RED.nodes.subflow(id); if (sf) { @@ -585,6 +613,9 @@ RED.workspaces = (function() { } workspace_tabs.activateTab(id); } + if(flash) { + flashTab(id.replace(".","-")) + } }, refresh: function() { RED.nodes.eachWorkspace(function(ws) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 787669fd7..f3ffcc7d8 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -42,6 +42,10 @@ ul.red-ui-sidebar-node-config-list { border-color: transparent; box-shadow: 0 0 0 2px $node-selected-color; } + &.highlighted { + border-color: transparent; + outline: dashed $node-selected-color 4px; + } } .red-ui-palette-label { margin-left: 8px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index b05a3575f..6603e8b21 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -85,6 +85,10 @@ &:not(.active) a:hover+a.red-ui-tab-close { background: $tab-background-hover; } + &.highlighted { + box-shadow: 0px 0px 4px 2px $node-selected-color; + border: dashed 1px $node-selected-color; + } &.active { background: $tab-background-active; font-weight: bold; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index 2adfb89ba..0ca7c2f84 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -135,6 +135,13 @@ margin-top: -1px; } } + + .search-counter { + display: inline-block; + font-size: smaller; + font-weight: 600; + white-space: nowrap; + } } a.red-ui-footer-button,