From 2a3e355437dd7484034515c63726c63cda5e4fd2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 22 May 2017 11:35:45 +0100 Subject: [PATCH] Add per-node filter option to Debug pane --- Gruntfile.js | 1 + editor/js/ui/common/checkboxSet.js | 131 +++++++++++++ editor/js/ui/common/editableList.js | 22 ++- editor/js/ui/utils.js | 17 +- editor/sass/debug.scss | 5 +- editor/sass/style.scss | 2 + editor/sass/ui/common/checkboxSet.scss | 29 +++ editor/sass/ui/common/editableList.scss | 112 ++++++----- editor/sass/ui/common/nodeList.scss | 65 ++++++ nodes/core/core/58-debug.html | 3 +- nodes/core/core/lib/debug/debug-utils.js | 240 ++++++++++++++++++++--- nodes/core/locales/en-US/messages.json | 7 +- 12 files changed, 538 insertions(+), 96 deletions(-) create mode 100644 editor/js/ui/common/checkboxSet.js create mode 100644 editor/sass/ui/common/checkboxSet.scss create mode 100644 editor/sass/ui/common/nodeList.scss diff --git a/Gruntfile.js b/Gruntfile.js index e921be3c5..98006b0b2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -123,6 +123,7 @@ module.exports = function(grunt) { "editor/js/validators.js", "editor/js/ui/utils.js", "editor/js/ui/common/editableList.js", + "editor/js/ui/common/checkboxSet.js", "editor/js/ui/common/menu.js", "editor/js/ui/common/panels.js", "editor/js/ui/common/popover.js", diff --git a/editor/js/ui/common/checkboxSet.js b/editor/js/ui/common/checkboxSet.js new file mode 100644 index 000000000..be7d2d4cd --- /dev/null +++ b/editor/js/ui/common/checkboxSet.js @@ -0,0 +1,131 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +(function($) { + $.widget( "nodered.checkboxSet", { + _create: function() { + var that = this; + this.uiElement = this.element.wrap( "" ).parent(); + this.uiElement.addClass("red-ui-checkboxSet"); + if (this.options.parent) { + this.parent = this.options.parent; + this.parent.checkboxSet('addChild',this.element); + } + this.children = []; + this.partialFlag = false; + this.stateValue = 0; + var initialState = this.element.prop('checked'); + this.options = [ + $('').appendTo(this.uiElement), + $('').appendTo(this.uiElement), + $('').appendTo(this.uiElement) + ]; + if (initialState) { + this.options[1].show(); + } else { + this.options[0].show(); + } + + this.element.change(function() { + if (this.checked) { + that.options[0].hide(); + that.options[1].show(); + that.options[2].hide(); + } else { + that.options[1].hide(); + that.options[0].show(); + that.options[2].hide(); + } + var isChecked = this.checked; + that.children.forEach(function(child) { + child.checkboxSet('state',isChecked,false,true); + }) + }) + this.uiElement.click(function(e) { + e.stopPropagation(); + // state returns null for a partial state. Clicking on that should + // result in false. + that.state((that.state()===false)?true:false); + }) + if (this.parent) { + this.parent.checkboxSet('updateChild',this); + } + }, + _destroy: function() { + if (this.parent) { + this.parent.checkboxSet('removeChild',this.element); + } + }, + addChild: function(child) { + var that = this; + this.children.push(child); + }, + removeChild: function(child) { + var index = this.children.indexOf(child); + if (index > -1) { + this.children.splice(index,1); + } + }, + updateChild: function(child) { + var checkedCount = 0; + this.children.forEach(function(c,i) { + if (c.checkboxSet('state') === true) { + checkedCount++; + } + }); + if (checkedCount === 0) { + + this.state(false,true); + } else if (checkedCount === this.children.length) { + this.state(true,true); + } else { + this.state(null,true); + } + }, + disable: function() { + this.uiElement.addClass('disabled'); + }, + state: function(state,suppressEvent,suppressParentUpdate) { + + if (arguments.length === 0) { + return this.partialFlag?null:this.element.is(":checked"); + } else { + this.partialFlag = (state === null); + var trueState = this.partialFlag||state; + this.element.prop('checked',trueState); + if (state === true) { + this.options[0].hide(); + this.options[1].show(); + this.options[2].hide(); + } else if (state === false) { + this.options[2].hide(); + this.options[1].hide(); + this.options[0].show(); + } else if (state === null) { + this.options[0].hide(); + this.options[1].hide(); + this.options[2].show(); + } + if (!suppressEvent) { + this.element.trigger('change',null); + } + if (!suppressParentUpdate && this.parent) { + this.parent.checkboxSet('updateChild',this); + } + } + } + }) + +})(jQuery); diff --git a/editor/js/ui/common/editableList.js b/editor/js/ui/common/editableList.js index 437da24e5..75a837682 100644 --- a/editor/js/ui/common/editableList.js +++ b/editor/js/ui/common/editableList.js @@ -50,9 +50,19 @@ this.uiContainer = this.element .wrap( "
" ) .parent(); - this.topContainer = this.uiContainer.wrap("
").parent(); + if (this.options.header) { + this.options.header.addClass("red-ui-editableList-header"); + this.borderContainer = this.uiContainer.wrap("
").parent(); + this.borderContainer.prepend(this.options.header); + this.topContainer = this.borderContainer.wrap("
").parent(); + } else { + this.topContainer = this.uiContainer.wrap("
").parent(); + } this.topContainer.addClass('red-ui-editableList'); + if (this.options.class) { + this.topContainer.addClass(this.options.class); + } if (this.options.addButton !== false) { var addLabel; @@ -86,6 +96,11 @@ this.uiContainer.css("position","absolute"); } + if (this.options.header) { + this.borderContainer.addClass("red-ui-editableList-border"); + } else { + this.uiContainer.addClass("red-ui-editableList-border"); + } this.uiContainer.addClass("red-ui-editableList-container"); this.uiHeight = this.element.height(); @@ -273,6 +288,11 @@ },0); } }, + addItems: function(items) { + for (var i=0; ispan:last-child { + float: right; + font-size: 14px; + } + } +} diff --git a/nodes/core/core/58-debug.html b/nodes/core/core/58-debug.html index e7a91380c..278c3f6e1 100644 --- a/nodes/core/core/58-debug.html +++ b/nodes/core/core/58-debug.html @@ -168,6 +168,7 @@ } } }; + RED.events.on("workspace:change", this.refreshMessageList); this.handleDebugMessage = function(t,o) { var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z); @@ -186,8 +187,6 @@ }; RED.comms.subscribe("debug",this.handleDebugMessage); - RED.events.on("workspace:change", this.refreshMessageList); - $("#debug-tab-open").click(function(e) { e.preventDefault(); subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600"); diff --git a/nodes/core/core/lib/debug/debug-utils.js b/nodes/core/core/lib/debug/debug-utils.js index 9b40645bd..3f6573d06 100644 --- a/nodes/core/core/lib/debug/debug-utils.js +++ b/nodes/core/core/lib/debug/debug-utils.js @@ -21,22 +21,28 @@ RED.debug = (function() { var config; var messageList; var messageTable; - var filter = false; + var filterType = "filterAll"; + var filteredNodes = {}; // id->true means hide, so default to all visible + var view = 'list'; var messages = []; var messagesByNode = {}; var sbc; var activeWorkspace; + var filterVisible = false; + + var debugNodeList; + var debugNodeListExpandedFlows = {}; + function init(_config) { config = _config; var content = $("
").css({"position":"relative","height":"100%"}); var toolbar = $('').appendTo(content); - var footerToolbar = $('
'+ // ''+ // 'list'+ @@ -52,11 +58,82 @@ RED.debug = (function() { var filterDialog = $('
'+ '
'+ ''+ - 'all flows'+ - 'current flow '+ + ''+ + ''+ + ' '+ ''+ '
'+ - '
').appendTo(content); + '
').appendTo(toolbar);//content); + + // var filterTypeRow = $('
').appendTo(filterDialog); + // $('').appendTo(filterTypeRow); + + var debugNodeListRow = $('
').appendTo(filterDialog); + var flowCheckboxes = {}; + var debugNodeListHeader = $('
Debug nodes
'); + var headerCheckbox = $('').appendTo(debugNodeListHeader.find("span")[1]).checkboxSet(); + + debugNodeList = $('
    ',{style:"text-align: left; min-height: 250px; max-height: 250px"}).appendTo(debugNodeListRow).editableList({ + header: debugNodeListHeader, + class: 'red-ui-nodeList', + addItem: function(container,i,node) { + var row = $("
    ").appendTo(container); + row.attr('id','debug-filter-node-list-node-'+node.id.replace(/\./g,"_")); + if (node.type === 'tab') { + container.parent().addClass('red-ui-editableList-section-header'); + if (!debugNodeListExpandedFlows.hasOwnProperty(node.id)) { + debugNodeListExpandedFlows[node.id] = true; + } + var chevron = $('').appendTo(row); + $('').text(RED.utils.getNodeLabel(node,node.id)).appendTo(row); + var muteControl = $('').appendTo($('').appendTo(row)); + muteControl.checkboxSet({ + parent: headerCheckbox + }); + flowCheckboxes[node.id] = muteControl; + row.click(function(e) { + e.stopPropagation(); + debugNodeListExpandedFlows[node.id] = !debugNodeListExpandedFlows[node.id]; + row.toggleClass('expanded',debugNodeListExpandedFlows[node.id]); + debugNodeList.editableList('filter'); + }) + row.addClass("expandable"); + if (node.disabled) { + container.addClass('disabled'); + muteControl.checkboxSet('disable'); + debugNodeListExpandedFlows[node.id] = false; + } + row.toggleClass('expanded',debugNodeListExpandedFlows[node.id]); + } else { + $('',{style: "margin-left: 20px"}).text(RED.utils.getNodeLabel(node,node.id)).appendTo(row); + row.on("mouseenter",function() { + config.messageMouseEnter(node.id); + }); + row.on("mouseleave",function() { + config.messageMouseLeave(node.id); + }); + var muteControl = $('').prop('checked',!filteredNodes[node.id]).appendTo($('').appendTo(row)); + muteControl.checkboxSet({ + parent: flowCheckboxes[node.z] + }).change(function(e) { + filteredNodes[node.id] = !$(this).prop('checked'); + $(".debug-message-node-"+node.id.replace(/\./g,"_")).toggleClass('hide',filteredNodes[node.id]); + }); + if (!node.active || RED.nodes.workspace(node.z).disabled) { + container.addClass('disabled'); + muteControl.checkboxSet('disable'); + } + } + }, + addButton: false, + scrollOnAdd: false, + filter: function(node) { + return (node.type === 'tab' || debugNodeListExpandedFlows[node.z] ) + }, + sort: function(A,B) { + + } + }); try { content.i18n(); @@ -64,25 +141,32 @@ RED.debug = (function() { console.log("TODO: i18n library support"); } + toolbar.find('#debug-tab-filter span').text(RED._('node-red:debug.sidebar.filterAll')); - filterDialog.find('#debug-tab-filter-all').on("click",function(e) { - e.preventDefault(); - if (filter) { - $(this).addClass('selected'); - $('#debug-tab-filter-current').removeClass('selected'); - filter = !filter; - refreshMessageList(); + var filterButtonHandler = function(type) { + return function(e) { + e.preventDefault(); + if (filterType !== type) { + $('.debug-tab-filter-option').removeClass('selected'); + $(this).addClass('selected'); + if (filterType === 'filterSelected') { + debugNodeListRow.slideUp(); + } + filterType = type; + if (filterType === 'filterSelected') { + debugNodeListRow.slideDown(); + } + + $('#debug-tab-filter span').text(RED._('node-red:debug.sidebar.'+filterType)); + refreshMessageList(); + } } - }); - filterDialog.find('#debug-tab-filter-current').on("click",function(e) { - e.preventDefault(); - if (!filter) { - $(this).addClass('selected'); - $('#debug-tab-filter-all').removeClass('selected'); - filter = !filter; - refreshMessageList(); - } - }); + } + filterDialog.find('#debug-tab-filterAll').on("click",filterButtonHandler('filterAll')); + filterDialog.find('#debug-tab-filterSelected').on("click",filterButtonHandler('filterSelected')); + filterDialog.find('#debug-tab-filterCurrent').on("click",filterButtonHandler('filterCurrent')); + + // $('#debug-tab-view-list').on("click",function(e) { // e.preventDefault(); // if (!$(this).hasClass('selected')) { @@ -101,13 +185,33 @@ RED.debug = (function() { // }); + var hideFilterTimeout; + toolbar.on('mouseleave',function() { + if ($('#debug-tab-filter').hasClass('selected')) { + clearTimeout(hideFilterTimeout); + hideFilterTimeout = setTimeout(function() { + filterVisible = false; + $('#debug-tab-filter').removeClass('selected'); + filterDialog.slideUp(200); + },300); + } + }); + toolbar.on('mouseenter',function() { + if ($('#debug-tab-filter').hasClass('selected')) { + clearTimeout(hideFilterTimeout); + } + }) toolbar.find('#debug-tab-filter').on("click",function(e) { e.preventDefault(); if ($(this).hasClass('selected')) { + filterVisible = false; $(this).removeClass('selected'); + clearTimeout(hideFilterTimeout); filterDialog.slideUp(200); } else { $(this).addClass('selected'); + filterVisible = true; + refreshDebugNodeList(); filterDialog.slideDown(200); } }) @@ -127,6 +231,38 @@ RED.debug = (function() { } + function refreshDebugNodeList() { + debugNodeList.editableList('empty'); + var candidateNodes = RED.nodes.filterNodes({type:'debug'}); + var workspaceOrder = RED.nodes.getWorkspaceOrder(); + var workspaceOrderMap = {}; + workspaceOrder.forEach(function(ws,i) { + workspaceOrderMap[ws] = i; + }); + candidateNodes.sort(function(A,B) { + var wsA = workspaceOrderMap[A.z]; + var wsB = workspaceOrderMap[B.z]; + if (wsA !== wsB) { + return wsA-wsB; + } + var labelA = RED.utils.getNodeLabel(A,A.id); + var labelB = RED.utils.getNodeLabel(B,B.id); + return labelA.localeCompare(labelB); + }) + var currentWs = null; + var nodeList = []; + candidateNodes.forEach(function(node) { + if (currentWs !== node.z) { + currentWs = node.z; + nodeList.push(RED.nodes.workspace(node.z)); + } + nodeList.push(node); + }) + + + debugNodeList.editableList('addItems',nodeList) + } + function getTimestamp() { var d = new Date(); return d.toLocaleString(); @@ -138,11 +274,22 @@ RED.debug = (function() { function refreshMessageList(_activeWorkspace) { if (_activeWorkspace) { - activeWorkspace = _activeWorkspace; + activeWorkspace = _activeWorkspace.replace(/\./g,"_"); + } + if (filterType === "filterAll") { + $(".debug-message").removeClass("hide"); + } else { + $(".debug-message").each(function() { + if (filterType === 'filterCurrent') { + $(this).toggleClass('hide',!$(this).hasClass('debug-message-flow-'+activeWorkspace)); + } else if (filterType === 'filterSelected') { + var id = $(this).data('source'); + if (id) { + $(this).toggleClass('hide',!!filteredNodes[id]); + } + } + }); } - $(".debug-message").each(function() { - $(this).toggleClass('hide',filter&&!$(this).hasClass('debug-message-flow-'+activeWorkspace)); - }); } function refreshMessageTable() { @@ -191,6 +338,20 @@ RED.debug = (function() { }}, {id:"debug-message-menu-item-clear-pins",label:RED._("node-red:debug.messageMenu.clearPinned"),onselect:function(){ activeMenuMessage.clearPinned(); + }}, + null, + {id:"debug-message-menu-item-filter",label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){ + var candidateNodes = RED.nodes.filterNodes({type:'debug'}); + candidateNodes.forEach(function(n) { + filteredNodes[n.id] = true; + }); + delete filteredNodes[sourceId]; + $("#debug-tab-filterSelected").click(); + refreshMessageList(); + }}, + {id:"debug-message-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ + $("#debug-tab-filterAll").click(); + refreshMessageList(); }} ] }); @@ -230,8 +391,25 @@ RED.debug = (function() { var property = sanitize(o.property?o.property:''); var payload = o.msg; var format = sanitize((o.format||"").toString()); - msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'') + - ((sourceNode&&sourceNode.z)?((" debug-message-flow-"+sourceNode.z+((filter&&(activeWorkspace!==sourceNode.z))?" hide":""))):""); + msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'')+ + (sourceNode?( + " debug-message-node-"+sourceNode.id.replace(/\./g,"_")+ + (sourceNode.z?" debug-message-flow-"+sourceNode.z.replace(/\./g,"_"):"") + ):""); + + if (sourceNode) { + $(msg).data('source',sourceNode.id); + if (filterType === "filterCurrent" && activeWorkspace) { + if (sourceNode.z && sourceNode.z.replace(/\./g,"_") !== activeWorkspace) { + $(msg).addClass('hide'); + } + } else if (filterType === 'filterSelected'){ + if (!!filteredNodes[sourceNode.id]) { + $(msg).addClass('hide'); + } + } + } + var metaRow = $('
    ').appendTo(msg); $(''+ getTimestamp()+'').appendTo(metaRow); if (sourceNode) { @@ -322,9 +500,11 @@ RED.debug = (function() { messageList.scrollTop(sbc.scrollHeight); } } + return { init: init, refreshMessageList:refreshMessageList, - handleDebugMessage: handleDebugMessage + handleDebugMessage: handleDebugMessage, + } })(); diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index c5bcc1021..5f2a2a75b 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -112,12 +112,15 @@ "sidebar": { "label": "debug", "name": "Debug messages", - "filterAll": "all flows", + "filterAll": "all nodes", + "filterSelected": "selected nodes", "filterCurrent": "current flow" }, "messageMenu": { "collapseAll": "Collapse all paths", - "clearPinned": "Clear pinned paths" + "clearPinned": "Clear pinned paths", + "filterNode": "Filter this node", + "clearFilter": "Clear filter" } }, "link": {