diff --git a/Gruntfile.js b/Gruntfile.js index 847842420..93047c202 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -110,26 +110,28 @@ module.exports = function(grunt) { "editor/js/nodes.js", "editor/js/history.js", "editor/js/validators.js", + "editor/js/ui/common/editableList.js", + "editor/js/ui/common/menu.js", + "editor/js/ui/common/popover.js", + "editor/js/ui/common/searchBox.js", + "editor/js/ui/common/tabs.js", + "editor/js/ui/common/typedInput.js", "editor/js/ui/deploy.js", - "editor/js/ui/menu.js", "editor/js/ui/keyboard.js", - "editor/js/ui/tabs.js", - "editor/js/ui/popover.js", "editor/js/ui/workspaces.js", "editor/js/ui/view.js", "editor/js/ui/sidebar.js", "editor/js/ui/palette.js", "editor/js/ui/tab-info.js", "editor/js/ui/tab-config.js", + "editor/js/ui/palette-editor.js", "editor/js/ui/editor.js", "editor/js/ui/tray.js", "editor/js/ui/clipboard.js", "editor/js/ui/library.js", "editor/js/ui/notifications.js", "editor/js/ui/subflow.js", - "editor/js/ui/touch/radialMenu.js", - "editor/js/ui/typedInput.js", - "editor/js/ui/editableList.js" + "editor/js/ui/touch/radialMenu.js" ], dest: "public/red/red.js" }, diff --git a/editor/js/main.js b/editor/js/main.js index c6cf6c7d1..37cd6e81e 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -56,11 +56,9 @@ var RED = (function() { success: function(data) { $("body").append(data); $("body").i18n(); - - - $(".palette-spinner").hide(); - $(".palette-scroll").show(); - $("#palette-search").show(); + $("#palette > .palette-spinner").hide(); + $(".palette-scroll").removeClass("hide"); + $("#palette-search").removeClass("hide"); loadFlows(); } }); diff --git a/editor/js/nodes.js b/editor/js/nodes.js index 32085802c..7507798b0 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -32,12 +32,22 @@ RED.nodes = (function() { } var registry = (function() { + var moduleList = {}; var nodeList = []; var nodeSets = {}; var typeToId = {}; var nodeDefinitions = {}; var exports = { + getModule: function(module) { + return moduleList[module]; + }, + getNodeSetForType: function(nodeType) { + return exports.getNodeSet(typeToId[nodeType]); + }, + getModuleList: function() { + return moduleList; + }, getNodeList: function() { return nodeList; }, @@ -55,27 +65,33 @@ RED.nodes = (function() { typeToId[ns.types[j]] = ns.id; } nodeList.push(ns); + + moduleList[ns.module] = moduleList[ns.module] || { + name:ns.module, + version:ns.version, + local:ns.local, + sets:{} + }; + moduleList[ns.module].sets[ns.name] = ns; + RED.events.emit("registry:node-set-added",ns); }, removeNodeSet: function(id) { var ns = nodeSets[id]; for (var j=0;j').appendTo(this.element); + var li = $('
  • '); + var added = false; + if (this.activeSort) { + var items = this.items(); + var skip = false; + items.each(function(i,el) { + if (added) { return } + var itemData = el.data('data'); + if (that.activeSort(data,itemData) < 0) { + li.insertBefore(el.closest("li")); + added = true; + } + }); + } + if (!added) { + li.appendTo(this.element); + } var row = $('
    ').addClass("red-ui-editableList-item-content").appendTo(li); row.data('data',data); if (this.options.sortable === true) { @@ -177,9 +246,20 @@ var index = that.element.children().length-1; setTimeout(function() { that.options.addItem(row,index,data); - setTimeout(function() { - that.uiContainer.scrollTop(that.element.height()); - },0); + if (that.activeFilter) { + try { + if (!that.activeFilter(data)) { + li.hide(); + } + } catch(err) { + } + } + + if (!that.activeSort) { + setTimeout(function() { + that.uiContainer.scrollTop(that.element.height()); + },0); + } },0); } }, @@ -197,6 +277,21 @@ }, empty: function() { this.element.empty(); + }, + filter: function(filter) { + if (filter !== undefined) { + this.activeFilter = filter; + } + return this._refreshFilter(); + }, + sort: function(sort) { + if (sort !== undefined) { + this.activeSort = sort; + } + return this._refreshSort(); + }, + length: function() { + return this.element.children().length; } }); })(jQuery); diff --git a/editor/js/ui/menu.js b/editor/js/ui/common/menu.js similarity index 100% rename from editor/js/ui/menu.js rename to editor/js/ui/common/menu.js diff --git a/editor/js/ui/popover.js b/editor/js/ui/common/popover.js similarity index 100% rename from editor/js/ui/popover.js rename to editor/js/ui/common/popover.js diff --git a/editor/js/ui/common/searchBox.js b/editor/js/ui/common/searchBox.js new file mode 100644 index 000000000..a15b3ba92 --- /dev/null +++ b/editor/js/ui/common/searchBox.js @@ -0,0 +1,88 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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.searchBox", { + _create: function() { + var that = this; + + this.currentTimeout = null; + this.lastSent = ""; + this.element.val(""); + this.uiContainer = this.element.wrap("
    ").parent(); + this.uiContainer.addClass("red-ui-searchBox-container"); + + $('').prependTo(this.uiContainer); + this.clearButton = $('').appendTo(this.uiContainer); + this.clearButton.on("click",function(e) { + e.preventDefault(); + that.element.val(""); + that._change("",true); + that.element.focus(); + }); + + this.resultCount = $('',{class:"red-ui-searchBox-resultCount hide"}).appendTo(this.uiContainer); + + this.element.val(""); + this.element.on("keyup",function() { + that._change($(this).val()); + }); + + this.element.on("focus",function() { + $("body").one("mousedown",function() { + that.element.blur(); + }); + }); + + }, + _change: function(val,instant) { + var fireEvent = false; + if (val === "") { + this.clearButton.hide(); + fireEvent = true; + } else { + this.clearButton.show(); + fireEvent = (val.length >= (this.options.minimumLength||0)); + } + if (fireEvent) { + if (!instant && this.options.delay > 0) { + clearTimeout(this.currentTimeout); + var that = this; + this.currentTimeout = setTimeout(function() { + that._trigger("change"); + },this.options.delay); + } else { + this._trigger("change"); + } + } + }, + value: function(val) { + if (val === undefined) { + return this.element.val(); + } else { + this.element.val(val); + this._change(val); + } + }, + count: function(val) { + if (val === undefined || val === null || val === "") { + this.resultCount.text("").hide(); + } else { + this.resultCount.text(val).show(); + } + } + }); +})(jQuery); diff --git a/editor/js/ui/tabs.js b/editor/js/ui/common/tabs.js similarity index 100% rename from editor/js/ui/tabs.js rename to editor/js/ui/common/tabs.js diff --git a/editor/js/ui/typedInput.js b/editor/js/ui/common/typedInput.js similarity index 100% rename from editor/js/ui/typedInput.js rename to editor/js/ui/common/typedInput.js diff --git a/editor/js/ui/palette-editor.js b/editor/js/ui/palette-editor.js new file mode 100644 index 000000000..59932642c --- /dev/null +++ b/editor/js/ui/palette-editor.js @@ -0,0 +1,723 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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. + **/ +RED.palette.editor = (function() { + + var editorTabs; + var filterInput; + var searchInput; + var nodeList; + var packageList; + var loadedList = []; + var filteredList = []; + + var typesInUse = {}; + var nodeEntries = {}; + var eventTimers = {}; + var activeFilter = ""; + + function delayCallback(start,callback) { + var delta = Date.now() - start; + if (delta < 300) { + delta = 300; + } else { + delta = 0; + } + setTimeout(function() { + callback(); + },delta); + } + function changeNodeState(id,state,shade,callback) { + shade.show(); + var start = Date.now(); + $.ajax({ + url:"nodes/"+id, + type: "PUT", + data: JSON.stringify({ + enabled: state + }), + contentType: "application/json; charset=utf-8" + }).done(function(data,textStatus,xhr) { + delayCallback(start,function() { + shade.hide(); + callback(); + }); + }).fail(function(xhr,textStatus,err) { + delayCallback(start,function() { + shade.hide(); + callback(xhr); + }); + }) + } + function installNodeModule(id,shade,callback) { + shade.show(); + $.ajax({ + url:"nodes", + type: "POST", + data: JSON.stringify({ + module: id + }), + contentType: "application/json; charset=utf-8" + }).done(function(data,textStatus,xhr) { + shade.hide(); + callback(); + }).fail(function(xhr,textStatus,err) { + shade.hide(); + callback(xhr); + }); + } + function removeNodeModule(id,callback) { + $.ajax({ + url:"nodes/"+id, + type: "DELETE" + }).done(function(data,textStatus,xhr) { + callback(); + }).fail(function(xhr,textStatus,err) { + callback(xhr); + }) + } + function refreshNodeModule(module) { + if (!eventTimers.hasOwnProperty(module)) { + eventTimers[module] = setTimeout(function() { + delete eventTimers[module]; + _refreshNodeModule(module); + },100); + } + } + + + function getContrastingBorder(rgbColor){ + var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor); + if (parts) { + var r = parseInt(parts[1]); + var g = parseInt(parts[2]); + var b = parseInt(parts[3]); + var yiq = ((r*299)+(g*587)+(b*114))/1000; + if (yiq > 160) { + r = Math.floor(r*0.8); + g = Math.floor(g*0.8); + b = Math.floor(b*0.8); + return "rgb("+r+","+g+","+b+")"; + } + } + return rgbColor; + } + + function formatUpdatedAt(dateString) { + var now = new Date(); + var d = new Date(dateString); + var delta = (Date.now() - new Date(dateString).getTime())/1000; + + if (delta < 60) { + return RED._('palette.editor.times.seconds'); + } + delta = Math.floor(delta/60); + if (delta < 10) { + return RED._('palette.editor.times.minutes'); + } + if (delta < 60) { + return RED._('palette.editor.times.minutesV',{count:delta}); + } + + delta = Math.floor(delta/60); + + if (delta < 24) { + return RED._('palette.editor.times.hoursV',{count:delta}); + } + + delta = Math.floor(delta/24); + + if (delta < 7) { + return RED._('palette.editor.times.daysV',{count:delta}) + } + var weeks = Math.floor(delta/7); + var days = delta%7; + + if (weeks < 4) { + return RED._('palette.editor.times.weeksV',{count:weeks}) + } + + var months = Math.floor(weeks/4); + weeks = weeks%4; + + if (months < 12) { + return RED._('palette.editor.times.monthsV',{count:months}) + } + var years = Math.floor(months/12); + months = months%12; + + if (months === 0) { + return RED._('palette.editor.times.yearsV',{count:years}) + } else { + return RED._('palette.editor.times.year'+(years>1?'s':'')+'MonthsV',{y:years,count:months}) + } + } + + + function _refreshNodeModule(module) { + if (!nodeEntries.hasOwnProperty(module)) { + nodeEntries[module] = {info:RED.nodes.registry.getModule(module)}; + var index = [module]; + for (var s in nodeEntries[module].info.sets) { + if (nodeEntries[module].info.sets.hasOwnProperty(s)) { + index.push(s); + index = index.concat(nodeEntries[module].info.sets[s].types) + } + } + nodeEntries[module].index = index.join(",").toLowerCase(); + + nodeList.editableList('addItem', nodeEntries[module]); + //console.log(nodeList.editableList('items')); + + } else { + var moduleInfo = nodeEntries[module].info; + var nodeEntry = nodeEntries[module].elements; + if (nodeEntry) { + var activeTypeCount = 0; + var typeCount = 0; + nodeEntries[module].totalUseCount = 0; + nodeEntries[module].setUseCount = {}; + + for (var setName in moduleInfo.sets) { + if (moduleInfo.sets.hasOwnProperty(setName)) { + var inUseCount = 0; + var set = moduleInfo.sets[setName]; + var setElements = nodeEntry.sets[setName]; + + if (set.enabled) { + activeTypeCount += set.types.length; + } + typeCount += set.types.length; + for (var i=0;i 0) { + setElements.enableButton.html(RED._('palette.editor.inuse')); + setElements.enableButton.addClass('disabled'); + } else { + setElements.enableButton.removeClass('disabled'); + if (set.enabled) { + setElements.enableButton.html(RED._('palette.editor.disable')); + } else { + setElements.enableButton.html(RED._('palette.editor.enable')); + } + } + setElements.setRow.toggleClass("palette-module-set-disabled",!set.enabled); + } + } + var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount; + nodeEntry.setCount.html(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount})); + + if (nodeEntries[module].totalUseCount > 0) { + nodeEntry.enableButton.html(RED._('palette.editor.inuse')); + nodeEntry.enableButton.addClass('disabled'); + nodeEntry.removeButton.hide(); + } else { + nodeEntry.enableButton.removeClass('disabled'); + nodeEntry.removeButton.show(); + if (activeTypeCount === 0) { + nodeEntry.enableButton.html(RED._('palette.editor.enableall')); + } else { + nodeEntry.enableButton.html(RED._('palette.editor.disableall')); + } + nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0)); + } + } + } + + } + function showPaletteEditor() { + $("#header-shade").show(); + $("#editor-shade").show(); + $("#sidebar-shade").show(); + $("#main-container").addClass("palette-expanded"); + setTimeout(function() { + editorTabs.resize(); + },250); + + } + function hidePaletteEditor() { + $("#main-container").removeClass("palette-expanded"); + $("#header-shade").hide(); + $("#editor-shade").hide(); + $("#sidebar-shade").hide(); + $("#palette-editor").find('.expanded').each(function(i,el) { + $(el).find(".palette-module-content").slideUp(); + $(el).removeClass('expanded'); + }); + filterInput.searchBox('value',""); + searchInput.searchBox('value',""); + } + + function filterChange(val) { + activeFilter = val.toLowerCase(); + var visible = nodeList.editableList('filter'); + var size = nodeList.editableList('length'); + if (val === "") { + filterInput.searchBox('count'); + } else { + filterInput.searchBox('count',visible+" / "+size); + } + } + + + var catalogueCount; + var catalogueLoadStatus = []; + var catalogueLoadStart; + + var activeSort = sortModulesAZ; + + function handleCatalogResponse(catalog,index,v) { + catalogueLoadStatus.push(v); + if (v.modules) { + v.modules.forEach(function(m) { + m.index = [m.id]; + if (m.keywords) { + m.index = m.index.concat(m.keywords); + } + if (m.updated_at) { + m.timestamp = new Date(m.updated_at).getTime(); + } else { + m.timestamp = 0; + } + m.index = m.index.join(",").toLowerCase(); + }) + loadedList = loadedList.concat(v.modules); + } + searchInput.searchBox('count',loadedList.length); + if (catalogueCount > 1) { + $(".palette-module-shade-status").html(RED._('palette.editor.loading')+"
    "+catalogueLoadStatus.length+"/"+catalogueCount); + } + if (catalogueLoadStatus.length === catalogueCount) { + var delta = 250-(Date.now() - catalogueLoadStart); + setTimeout(function() { + $("#palette-module-install-shade").hide(); + },Math.max(delta,0)); + } + } + + function initInstallTab() { + if (loadedList.length === 0) { + loadedList = []; + packageList.editableList('empty'); + $(".palette-module-shade-status").html(RED._('palette.editor.loading')); + var catalogues = RED.settings.theme('palette.catalogues')||['http://catalogue.nodered.org/catalogue.json']; + catalogueLoadStatus = []; + catalogueCount = catalogues.length; + if (catalogues.length > 1) { + $(".palette-module-shade-status").html(RED._('palette.editor.loading')+"
    0/"+catalogues.length); + } + $("#palette-module-install-shade").show(); + catalogueLoadStart = Date.now(); + catalogues.forEach(function(catalog,index) { + $.getJSON(catalog, {_: new Date().getTime()},function(v) { + handleCatalogResponse(catalog,index,v); + }) + }); + } + } + + function refreshFilteredItems() { + packageList.editableList('empty'); + filteredList.sort(activeSort); + for (var i=0;i 10) { + packageList.editableList('addItem',{start:10,more:filteredList.length-10}) + } + } + function sortModulesAZ(A,B) { + return A.info.id.localeCompare(B.info.id); + } + function sortModulesRecent(A,B) { + return -1 * (A.info.timestamp-B.info.timestamp); + } + + function init() { + + $(".palette-editor-button").show(); + + editorTabs = RED.tabs.create({ + id:"palette-editor-tabs", + onchange:function(tab) { + $("#palette-editor .palette-editor-tab").hide(); + tab.content.show(); + if (filterInput) { + filterInput.searchBox('value',""); + } + if (searchInput) { + searchInput.searchBox('value',""); + } + if (tab.id === 'install') { + initInstallTab(); + if (searchInput) { + searchInput.focus(); + } + } else { + if (filterInput) { + filterInput.focus(); + } + } + }, + minimumActiveTabWidth: 110 + }); + + + $("#editor-shade").click(function() { + if ($("#main-container").hasClass("palette-expanded")) { + hidePaletteEditor(); + } + }); + $("#palette-edit").on("click",function(e) { + if ($("#main-container").hasClass("palette-expanded")) { + hidePaletteEditor(); + } else { + showPaletteEditor(); + } + }); + $("#palette-editor-close").on("click", function(e) { + hidePaletteEditor(); + }) + + var modulesTab = $('
    ',{class:"palette-editor-tab"}).appendTo("#palette-editor"); + + editorTabs.addTab({ + id: 'nodes', + label: RED._('palette.editor.tab-nodes'), + content: modulesTab + }) + + var filterDiv = $('
    ',{class:"palette-search"}).appendTo(modulesTab); + filterInput = $('') + .appendTo(filterDiv) + .searchBox({ + delay: 200, + change: function() { + filterChange($(this).val()); + } + }); + + + nodeList = $('
      ',{id:"palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({ + addButton: false, + sort: function(A,B) { + return A.info.name.localeCompare(B.info.name); + }, + filter: function(data) { + if (activeFilter === "" ) { + return true; + } + + return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1); + }, + addItem: function(container,i,object) { + var entry = object.info; + var headerRow = $('
      ',{class:"palette-module-header"}).appendTo(container); + var titleRow = $('
      ').appendTo(headerRow); + $('').html(entry.name).appendTo(titleRow); + var metaRow = $('
      ').appendTo(headerRow); + $('').html(entry.version).appendTo(metaRow); + var buttonRow = $('
      ',{class:"palette-module-meta"}).appendTo(headerRow); + var setButton = $(' ').appendTo(buttonRow); + var setCount = $('').appendTo(setButton); + var buttonGroup = $('
      ',{class:"palette-module-button-group"}).appendTo(buttonRow); + var removeButton = $('').html(RED._('palette.editor.remove')).appendTo(buttonGroup); + removeButton.click(function() { + shade.show(); + removeNodeModule(entry.name, function(xhr) { + console.log(xhr); + }) + }) + if (!entry.local) { + removeButton.hide(); + } + var enableButton = $('').html(RED._('palette.editor.disableall')).appendTo(buttonGroup); + + var contentRow = $('
      ',{class:"palette-module-content"}).appendTo(container); + var shade = $('
      ').appendTo(container); + + object.elements = { + removeButton: removeButton, + enableButton: enableButton, + setCount: setCount, + container: container, + shade: shade, + sets: {} + } + setButton.click(function() { + if (container.hasClass('expanded')) { + container.removeClass('expanded'); + contentRow.slideUp(); + } else { + container.addClass('expanded'); + contentRow.slideDown(); + } + }) + + var setList = Object.keys(entry.sets) + setList.sort(function(A,B) { + return A.toLowerCase().localeCompare(B.toLowerCase()); + }); + setList.forEach(function(setName) { + var set = entry.sets[setName]; + var setRow = $('
      ',{class:"palette-module-set"}).appendTo(contentRow); + var buttonGroup = $('
      ',{class:"palette-module-set-button-group"}).appendTo(setRow); + var typeSwatches = {}; + set.types.forEach(function(t) { + var typeDiv = $('
      ',{class:"palette-module-type"}).appendTo(setRow); + typeSwatches[t] = $('',{class:"palette-module-type-swatch"}).appendTo(typeDiv); + $('',{class:"palette-module-type-node"}).html(t).appendTo(typeDiv); + }) + + var enableButton = $('').appendTo(buttonGroup); + enableButton.click(function(evt) { + if (object.setUseCount[setName] === 0) { + var currentSet = RED.nodes.registry.getNodeSet(set.id); + shade.show(); + changeNodeState(set.id,!currentSet.enabled,shade,function(xhr){ + console.log(xhr) + }); + } + evt.preventDefault(); + }) + + object.elements.sets[set.name] = { + setRow: setRow, + enableButton: enableButton, + swatches: typeSwatches + }; + }); + enableButton.click(function(evt) { + if (object.totalUseCount === 0) { + changeNodeState(entry.name,(container.hasClass('disabled')),shade,function(xhr){ + console.log(xhr) + }); + } + evt.preventDefault(); + }) + refreshNodeModule(entry.name); + } + }); + + + + var installTab = $('
      ',{class:"palette-editor-tab hide"}).appendTo("#palette-editor"); + + editorTabs.addTab({ + id: 'install', + label: RED._('palette.editor.tab-install'), + content: installTab + }) + + var toolBar = $('
      ',{class:"palette-editor-toolbar"}).appendTo(installTab); + + var searchDiv = $('
      ',{class:"palette-search"}).appendTo(installTab); + searchInput = $('') + .appendTo(searchDiv) + .searchBox({ + delay: 300, + minimumLength: 2, + change: function() { + var searchTerm = $(this).val(); + if (searchTerm.length >= 2) { + filteredList = loadedList.filter(function(m) { + return (m.index.indexOf(searchTerm) > -1); + }).map(function(f) { return {info:f}}); + refreshFilteredItems(); + searchInput.searchBox('count',filteredList.length+" / "+loadedList.length); + } else { + searchInput.searchBox('count',loadedList.length); + if (searchTerm.length === 0) { + packageList.editableList('empty'); + } + } + } + }); + + + $('').html(RED._("palette.editor.sort")+' ').appendTo(toolBar); + var sortGroup = $(' ').appendTo(toolBar); + var sortAZ = $('').appendTo(sortGroup); + var sortRecent = $('').appendTo(sortGroup); + + sortAZ.click(function(e) { + e.preventDefault(); + if ($(this).hasClass("selected")) { + return; + } + $(this).addClass("selected"); + sortRecent.removeClass("selected"); + activeSort = sortModulesAZ; + refreshFilteredItems(); + }); + + sortRecent.click(function(e) { + e.preventDefault(); + if ($(this).hasClass("selected")) { + return; + } + $(this).addClass("selected"); + sortAZ.removeClass("selected"); + activeSort = sortModulesRecent; + refreshFilteredItems(); + }); + + + var refreshSpan = $('').appendTo(toolBar); + var refreshButton = $('').appendTo(refreshSpan); + refreshButton.click(function(e) { + e.preventDefault(); + loadedList = []; + initInstallTab(); + }) + + packageList = $('
        ',{id:"palette-module-list", style:"position: absolute;top: 78px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({ + addButton: false, + addItem: function(container,i,object) { + if (object.more) { + container.addClass('palette-module-more'); + var moreRow = $('
        ',{class:"palette-module-header palette-module"}).appendTo(container); + var moreLink = $('').html(RED._('palette.editor.more',{count:object.more})).appendTo(moreRow); + moreLink.click(function(e) { + e.preventDefault(); + packageList.editableList('removeItem',object); + for (var i=object.start;i 10) { + packageList.editableList('addItem',{start:object.start+10, more:object.more-10}) + } + }) + return; + } + var entry = object.info; + var headerRow = $('
        ',{class:"palette-module-header"}).appendTo(container); + var titleRow = $('
        ').appendTo(headerRow); + $('',{class:"palette-module-name"}).html(entry.name||entry.id).appendTo(titleRow); + $('').attr('href',entry.url).appendTo(titleRow); + var descRow = $('
        ').appendTo(headerRow); + $('
        ',{class:"palette-module-description"}).html(entry.description).appendTo(descRow); + + var metaRow = $('
        ').appendTo(headerRow); + $(' '+entry.version+'').appendTo(metaRow); + $(' '+formatUpdatedAt(entry.updated_at)+'').appendTo(metaRow); + var buttonRow = $('
        ',{class:"palette-module-meta"}).appendTo(headerRow); + var buttonGroup = $('
        ',{class:"palette-module-button-group"}).appendTo(buttonRow); + var shade = $('
        ').appendTo(container); + var installButton = $('').html(RED._('palette.editor.install')).appendTo(buttonGroup); + installButton.click(function(e) { + e.preventDefault(); + installNodeModule(entry.id,shade,function(xhr) { + if (xhr) { + if (xhr.responseJSON) { + RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message})); + } + } + }) + }) + if (nodeEntries.hasOwnProperty(entry.id)) { + installButton.hide(); + } + + object.elements = { + installButton:installButton + } + } + }); + + $('
        ').appendTo(installTab); + + RED.events.on('registry:node-set-enabled', function(ns) { + refreshNodeModule(ns.module); + }); + RED.events.on('registry:node-set-disabled', function(ns) { + refreshNodeModule(ns.module); + }); + RED.events.on('registry:node-type-added', function(nodeType) { + var ns = RED.nodes.registry.getNodeSetForType(nodeType); + refreshNodeModule(ns.module); + }); + RED.events.on('registry:node-type-removed', function(nodeType) { + var ns = RED.nodes.registry.getNodeSetForType(nodeType); + refreshNodeModule(ns.module); + }); + RED.events.on('registry:node-set-added', function(ns) { + refreshNodeModule(ns.module); + for (var i=0;i .palette-spinner").show(); + + $("#palette-search input").searchBox({ + delay: 100, + change: function() { + filterChange($(this).val()); + } + }) + + var categoryList = coreCategories; if (RED.settings.paletteCategories) { - RED.settings.paletteCategories.forEach(function(category){ - createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category})); - }); - } else { - core.forEach(function(category){ - createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category})); - }); + categoryList = RED.settings.paletteCategories; + } else if (RED.settings.theme('palette.categories')) { + categoryList = RED.settings.theme('palette.categories'); } - - $("#palette-search-clear").on("click",function(e) { - e.preventDefault(); - $("#palette-search-input").val(""); - filterChange(); - $("#palette-search-input").focus(); - }); - - $("#palette-search-input").val(""); - $("#palette-search-input").on("keyup",function() { - filterChange(); - }); - - $("#palette-search-input").on("focus",function() { - $("body").one("mousedown",function() { - $("#palette-search-input").blur(); - }); + if (!Array.isArray(categoryList)) { + categoryList = coreCategories + } + categoryList.forEach(function(category){ + createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category})); }); $("#palette-collapse-all").on("click", function(e) { @@ -438,6 +467,10 @@ RED.palette = (function() { } } }); + + if (RED.settings.theme('palette.editable') !== false) { + RED.palette.editor.init(); + } } return { diff --git a/editor/js/ui/tray.js b/editor/js/ui/tray.js index 3c821e5e3..747600b48 100644 --- a/editor/js/ui/tray.js +++ b/editor/js/ui/tray.js @@ -110,6 +110,7 @@ RED.tray = (function() { $("#header-shade").show(); $("#editor-shade").show(); + $("#palette-shade").show(); $(".sidebar-shade").show(); tray.preferredWidth = Math.max(el.width(),500); @@ -259,6 +260,7 @@ RED.tray = (function() { if (stack.length === 0) { $("#header-shade").hide(); $("#editor-shade").hide(); + $("#palette-shade").hide(); $(".sidebar-shade").hide(); RED.events.emit("editor:close"); RED.view.focus(); diff --git a/editor/sass/colors.scss b/editor/sass/colors.scss index 714e4f4ce..b2a691d99 100644 --- a/editor/sass/colors.scss +++ b/editor/sass/colors.scss @@ -20,6 +20,7 @@ $form-placeholder-color: #bbbbbb; $form-text-color: #444; $form-input-focus-color: rgba(85,150,230,0.8); $form-input-border-color: #ccc; +$form-input-border-selected-color: #aaa; $node-selected-color: #ff7f0e; diff --git a/editor/sass/editor.scss b/editor/sass/editor.scss index 52cf2b428..8b727426c 100644 --- a/editor/sass/editor.scss +++ b/editor/sass/editor.scss @@ -167,7 +167,7 @@ background: $background-color; color: $workspace-button-color; } -#editor-shade, #header-shade { +#palette-shade, #editor-shade, #header-shade, #sidebar-shade { position: absolute; top:0; bottom:0; diff --git a/editor/sass/mixins.scss b/editor/sass/mixins.scss index aff3f899f..58bd7de1e 100644 --- a/editor/sass/mixins.scss +++ b/editor/sass/mixins.scss @@ -1,5 +1,5 @@ /** - * Copyright 2015 IBM Corp. + * Copyright 2015, 2016 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,14 @@ user-select: none; } +@mixin enable-selection { + -webkit-user-select: auto; + -khtml-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; +} + @mixin component-border { border: 1px solid $primary-border-color; box-sizing: border-box; @@ -43,14 +51,15 @@ cursor: default; color: $workspace-button-color-disabled; } - &:not(.disabled):hover { + &:hover, &:focus { text-decoration: none; + } + &:not(.disabled):hover { color: $workspace-button-color-hover; background: $workspace-button-background-hover; } &:not(.disabled):focus { color: $workspace-button-color-focus; - text-decoration: none; } &:not(.disabled):active { color: $workspace-button-color-active; @@ -73,11 +82,15 @@ @mixin workspace-button-toggle { @include workspace-button; color: $workspace-button-color-selected; - background: $workspace-button-background-active; - + background:$workspace-button-background-active; + transition: all 0.1s ease-in-out; + margin-bottom: 1px; &.selected:not(.disabled) { color: $workspace-button-color; background: $workspace-button-background; + border-bottom-width: 2px; + border-bottom-color: $form-input-border-selected-color; + margin-bottom: 0; } } diff --git a/editor/sass/palette-editor.scss b/editor/sass/palette-editor.scss new file mode 100644 index 000000000..70b575d89 --- /dev/null +++ b/editor/sass/palette-editor.scss @@ -0,0 +1,232 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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. + **/ + +#palette-editor { + text-align: left; + display: none; + position: absolute; + top: 0px; + right: 0; + bottom: 25px; + left:0; + padding: 0; + box-sizing:border-box; + background: #fff; + + .red-ui-editableList-container { + border: none; + border-radius: 0; + padding: 0px; + + + li { + // border: none; + // border-top: 1px solid $primary-border-color; + padding: 0px; + .disabled { + background: #f3f3f3; + + .palette-module-name { + font-style: italic; + color: #aaa; + } + .palette-module-version { + color: #aaa; + } + + + + } + .red-ui-editableList-item-content { + padding: 12px 8px; + } + &:last-child { + // border-bottom: 1px solid $primary-border-color; + } + } + + } + .palette-editor-tab { + position:absolute; + top:115px; + left:0; + right:0; + bottom:0 + } + .palette-editor-toolbar { + background: #f3f3f3; + box-sizing: border-box; + padding: 8px 10px; + border-bottom: 1px solid $primary-border-color; + text-align: right; + + .button-group { + margin-right: 10px; + } + } + .palette-module-button-group { + position: absolute; + right: 0; + bottom: 0; + a { + margin-left: 5px; + } + } + .palette-module-shade { + position: absolute; + top: 0; + bottom:0; + left:0; + right:0; + background: rgba(255,255,255,0.8); + text-align: center; + padding-top: 20px; + } + #palette-module-install-shade { + padding-top: 80px; + } + .palette-module-shade-status { + color: #666; + } + + .palette-module-meta { + color: #666; + position: relative; + &.disabled { + color: #ccc; + } + + .fa { + width: 15px; + text-align: center; + margin-right: 5px; + } + } + .palette-module-name { + white-space: nowrap; + @include enable-selection; + } + .palette-module-version, .palette-module-updated, .palette-module-link { + font-style:italic; + font-size: 0.8em; + @include enable-selection; + } + .palette-module-updated { + margin-left: 10px; + } + .palette-module-link { + margin-left: 5px; + } + + .palette-module-description { + margin-left: 20px; + font-size: 0.9em; + color: #999; + } + .palette-module-link { + } + .palette-module-set-button-group { + } + .palette-module-count { + border-radius: 4px; + background: #eee; + padding: 2px 8px; + font-size: 12px; + } + .palette-module-content { + display: none; + padding: 10px 3px; + } + i.fa.palette-module-node-chevron { + width: 8px; + margin-right: 0; + transform: rotate(0deg); + transition: transform 0.2s ease-in-out; + } + .expanded { + i.fa.palette-module-node-chevron { + transform: rotate(90deg); + } + + .palette-module-set-button { + background:#f3f3f3 !important; + } + } + .palette-module-set { + border:1px solid $secondary-border-color; + border-radius: 0; + padding: 5px; + position: relative; + &:not(:last-child) { + border-bottom: none; + } + &:first-child { + border-top-right-radius: 2px; + border-top-left-radius: 2px; + } + &:last-child { + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; + } + } + + .palette-module-type { + color: #666; + padding-left: 5px; + font-size: 0.9em; + @include enable-selection; + } + .palette-module-type-swatch { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 3px; + vertical-align: middle; + margin-right: 5px; + background: #fff; + border: 1px solid #fff; + } + .palette-module-set-button-group { + position: absolute; + right: 4px; + top: 4px; + } + + .palette-module-set-disabled { + background: #eee; + .palette-module-type { + color: #999; + } + } + .palette-module-more { + padding: 0 !important; + margin-top: 10px; + margin-bottom: 10px; + background: $tab-background-inactive; + a { + display: block; + text-align: center; + padding: 12px 8px; + color: #AD1625; + + &:hover { + text-decoration: none; + background: $tab-background-hover; + } + } + } + +} diff --git a/editor/sass/palette.scss b/editor/sass/palette.scss index 07958edf3..988717013 100644 --- a/editor/sass/palette.scss +++ b/editor/sass/palette.scss @@ -25,10 +25,25 @@ text-align: center; @include disable-selection; @include component-border; - + transition: width 0.2s ease-in-out; } + +.palette-expanded { + & #palette { + width: 380px; + box-shadow: 1px 0 6px rgba(0,0,0,0.1); + } + & #workspace { left: 379px !important; } + & #palette-collapse-all { display: none; } + & #palette-expand-all { display: none; } + & #palette-container { display: none !important; } + & #palette-search { display: none !important; } + & #palette-edit { background: $workspace-button-background-active } + & #palette-editor { display: block !important } +} + + .palette-scroll { - display: none; position: absolute; top: 35px; right: 0; @@ -38,15 +53,11 @@ overflow-y: auto; box-sizing:border-box; } -.palette-spinner { - padding-top: 40px; +#palette > .palette-spinner { + padding-top: 80px; } -#palette-search { - position: absolute; - display: none; - top: 0; - left:0; - right:0; +.palette-search { + position: relative; overflow: hidden; background: #ffffff; text-align: center; @@ -55,48 +66,7 @@ border-bottom: 1px solid $primary-border-color; box-sizing:border-box; } -#palette-search i { - font-size: 10px; - color: #666; -} -#palette-search i.fa-search { - position: absolute; - pointer-events: none; - left: 12px; - top: 12px; -} -#palette-search i.fa-times { - position: absolute; - right: 7px; - top: 12px; -} -#palette-search-clear { - position: absolute; - right: 0; - top: 0; - bottom: 0; - width: 20px; - display: none; -} - -#palette-search input { - border-radius: 0; - border: none; - width: 100%; - box-shadow: none; - -webkit-box-shadow: none; - padding: 3px 17px 3px 22px; - margin: 0px; - height: 30px; - box-sizing:border-box; -} - -#palette-search input:focus { - border: none; - box-shadow: none; - -webkit-box-shadow: none; -} #palette-footer { @include component-footer; } diff --git a/editor/sass/style.scss b/editor/sass/style.scss index 2687d47fe..a38c7eae8 100644 --- a/editor/sass/style.scss +++ b/editor/sass/style.scss @@ -40,9 +40,12 @@ @import "tab-info"; @import "popover"; @import "flow"; +@import "palette-editor"; -@import "typedInput"; -@import "editableList"; + +@import "ui/common/editableList"; +@import "ui/common/searchBox"; +@import "ui/common/typedInput"; @import "dragdrop"; diff --git a/editor/sass/editableList.scss b/editor/sass/ui/common/editableList.scss similarity index 100% rename from editor/sass/editableList.scss rename to editor/sass/ui/common/editableList.scss diff --git a/editor/sass/ui/common/searchBox.scss b/editor/sass/ui/common/searchBox.scss new file mode 100644 index 000000000..733eb49f3 --- /dev/null +++ b/editor/sass/ui/common/searchBox.scss @@ -0,0 +1,69 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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. + **/ + + +.red-ui-searchBox-container { + i { + font-size: 10px; + color: #666; + } + i.fa-search { + position: absolute; + pointer-events: none; + left: 12px; + top: 12px; + } + i.fa-times { + position: absolute; + right: 7px; + top: 12px; + } + input { + border-radius: 0; + border: none; + width: 100%; + box-shadow: none; + -webkit-box-shadow: none; + padding: 3px 17px 3px 22px; + margin: 0px; + height: 30px; + box-sizing:border-box; + + &:focus { + border: none; + box-shadow: none; + -webkit-box-shadow: none; + } + } + a { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 20px; + display: none; + } + .red-ui-searchBox-resultCount { + position: absolute; + right: 22px; + top: 7px; + background: #eee; + color: #666; + padding: 1px 8px; + font-size: 9px; + border-radius: 4px; + } +} diff --git a/editor/sass/typedInput.scss b/editor/sass/ui/common/typedInput.scss similarity index 100% rename from editor/sass/typedInput.scss rename to editor/sass/ui/common/typedInput.scss diff --git a/editor/sass/widgetStyle.scss b/editor/sass/widgetStyle.scss new file mode 100644 index 000000000..2eb5fc1fb --- /dev/null +++ b/editor/sass/widgetStyle.scss @@ -0,0 +1,23 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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. + **/ + +@import "colors"; +@import "mixins"; + +@import "forms"; +@import "jquery"; +@import "typedInput"; +@import "editableList"; diff --git a/editor/sass/workspace.scss b/editor/sass/workspace.scss index 7f28ed210..d23d5cb81 100644 --- a/editor/sass/workspace.scss +++ b/editor/sass/workspace.scss @@ -40,6 +40,8 @@ right: 322px; overflow: hidden; @include component-border; + transition: left 0.2s ease-in-out; + } .workspace-footer-button { diff --git a/editor/templates/index.mst b/editor/templates/index.mst index ecd254376..0d9bdb313 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -43,19 +43,6 @@
        -
        +
        + + +
        +
        +
          +
          +
          + +
          +
          diff --git a/red/api/locales/en-US/editor.json b/red/api/locales/en-US/editor.json index 4808f2078..ed3e29607 100644 --- a/red/api/locales/en-US/editor.json +++ b/red/api/locales/en-US/editor.json @@ -185,6 +185,7 @@ "palette": { "noInfo": "no information available", "filter": "filter nodes", + "search": "search modules", "label": { "subflows": "subflows", "input": "input", @@ -204,6 +205,49 @@ "nodeEnabled_plural": "Nodes enabled:", "nodeDisabled": "Node disabled:", "nodeDisabled_plural": "Nodes disabled:" + }, + "editor": { + "title": "Manage palette", + "times": { + "seconds": "seconds ago", + "minutes": "minutes ago", + "minutesV": "__count__ minutes ago", + "hoursV": "__count__ hour ago", + "hoursV_plural": "__count__ hours ago", + "daysV": "__count__ day ago", + "daysV_plural": "__count__ days ago", + "weeksV": "__count__ week ago", + "weeksV_plural": "__count__ weeks ago", + "monthsV": "__count__ month ago", + "monthsV_plural": "__count__ months ago", + "yearsV": "__count__ year ago", + "yearsV_plural": "__count__ years ago", + + "yearMonthsV": "__y__ year, __count__ month ago", + "yearMonthsV_plural": "__y__ year, __count__ months ago", + "yearsMonthsV": "__y__ years, __count__ month ago", + "yearsMonthsV_plural": "__y__ years, __count__ months ago" + }, + "nodeCount": "__label__ node", + "nodeCount_plural": "__label__ nodes", + "inuse": "in use", + "enableall": "enable all", + "disableall": "disable all", + "enable": "enable", + "disable": "disable", + "remove": "remove", + "install": "install", + "loading": "Loading catalogues...", + "tab-nodes": "Nodes", + "tab-install": "Install", + "sort": "sort:", + "sortAZ": "a-z", + "sortRecent": "recent", + "more": "+ __count__ more", + "errors": { + "installFailed": "Failed to install: __module__
          __message__
          Check the log for more information" + } + } }, "sidebar": { @@ -230,6 +274,10 @@ "filterUnused":"unused", "filterAll":"all", "filtered": "__count__ hidden" + }, + "palette": { + "name": "Palette management", + "label": "palette" } }, "typedInput": { diff --git a/red/api/theme.js b/red/api/theme.js index 067e50a3b..56c40fcf9 100644 --- a/red/api/theme.js +++ b/red/api/theme.js @@ -1,5 +1,5 @@ /** - * Copyright 2015 IBM Corp. + * Copyright 2015, 2016 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,7 +94,7 @@ module.exports = { themeContext.page.favicon = url; } } - + if (theme.page.tabicon) { url = serveFile(themeApp,"/tabicon/",theme.page.tabicon) if (url) { @@ -161,6 +161,9 @@ module.exports = { themeSettings.menu = theme.menu; } + if (theme.hasOwnProperty("palette")) { + themeSettings.palette = theme.palette; + } return themeApp; }, context: function() { diff --git a/red/runtime/nodes/registry/loader.js b/red/runtime/nodes/registry/loader.js index 9e5897593..d51b427e9 100644 --- a/red/runtime/nodes/registry/loader.js +++ b/red/runtime/nodes/registry/loader.js @@ -188,7 +188,8 @@ function loadNodeConfig(fileInfo) { template: file.replace(/\.js$/,".html"), enabled: isEnabled, loaded:false, - version: version + version: version, + local: fileInfo.local }; if (fileInfo.hasOwnProperty("types")) { node.types = fileInfo.types; diff --git a/red/runtime/nodes/registry/localfilesystem.js b/red/runtime/nodes/registry/localfilesystem.js index 112ed72ee..9ca1307e3 100644 --- a/red/runtime/nodes/registry/localfilesystem.js +++ b/red/runtime/nodes/registry/localfilesystem.js @@ -141,7 +141,8 @@ function scanTreeForNodesModules(moduleName) { if (settings.userDir) { userDir = path.join(settings.userDir,"node_modules"); - results = results.concat(scanDirForNodesModules(userDir,moduleName)); + results = scanDirForNodesModules(userDir,moduleName); + results.forEach(function(r) { r.local = true; }); } if (dir) { @@ -240,12 +241,14 @@ function getNodeFiles(disableNodePathScan) { nodeList[moduleFile.package.name] = { name: moduleFile.package.name, version: moduleFile.package.version, + local: moduleFile.local||false, nodes: {} }; if (moduleFile.package['node-red'].version) { nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version; } nodeModuleFiles.forEach(function(node) { + node.local = moduleFile.local||false; nodeList[moduleFile.package.name].nodes[node.name] = node; }); nodeFiles = nodeFiles.concat(nodeModuleFiles); diff --git a/red/runtime/nodes/registry/registry.js b/red/runtime/nodes/registry/registry.js index 74606853e..02a3b304d 100644 --- a/red/runtime/nodes/registry/registry.js +++ b/red/runtime/nodes/registry/registry.js @@ -56,7 +56,8 @@ function filterNodeInfo(n) { id: n.id||n.module+"/"+n.name, name: n.name, types: n.types, - enabled: n.enabled + enabled: n.enabled, + local: n.local||false }; if (n.hasOwnProperty("module")) { r.module = n.module; @@ -90,6 +91,7 @@ function saveNodeList() { moduleList[module] = { name: module, version: moduleConfigs[module].version, + local: moduleConfigs[module].local||false, nodes: {} }; } @@ -179,6 +181,7 @@ function addNodeSet(id,set,version) { if (version) { moduleConfigs[set.module].version = version; } + moduleConfigs[set.module].local = set.local; moduleConfigs[set.module].nodes[set.name] = set; nodeList.push(id); @@ -306,6 +309,7 @@ function getModuleInfo(module) { var m = { name: module, version: moduleConfigs[module].version, + local: moduleConfigs[module].local, nodes: [] }; for (var i = 0; i < nodes.length; ++i) {