diff --git a/.gitignore b/.gitignore index c3fa9624a..7b1f1f219 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ packages/node_modules/@node-red/editor-client/public !test/**/node_modules docs !packages/node_modules/**/docs +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a101eecb0..5a8eebb8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false language: node_js matrix: include: + - node_js: "14" - node_js: "12" - node_js: "10" script: diff --git a/Gruntfile.js b/Gruntfile.js index 7593fded9..32556f0a4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -125,6 +125,7 @@ module.exports = function(grunt) { src: [ // Ensure editor source files are concatenated in // the right order + "packages/node_modules/@node-red/editor-client/src/js/polyfills.js", "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js", "packages/node_modules/@node-red/editor-client/src/js/red.js", "packages/node_modules/@node-red/editor-client/src/js/events.js", @@ -151,6 +152,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js", "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js", "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js", @@ -163,6 +165,8 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", @@ -177,6 +181,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/group.js", "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js", diff --git a/package.json b/package.json index a5697035c..c20eb7f2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.6", + "version": "1.1.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -48,6 +48,7 @@ "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", "jsonata": "1.8.3", + "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", "memorystore": "1.6.2", "mime": "2.4.4", @@ -104,7 +105,7 @@ "minami": "1.2.3", "mocha": "^5.2.0", "mosca": "^2.8.3", - "node-red-node-test-helper": "^0.2.3", + "node-red-node-test-helper": "^0.2.5", "node-sass": "^4.13.1", "should": "^8.4.0", "sinon": "1.17.7", diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js index 2787a5c36..b78de9d75 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js @@ -44,6 +44,7 @@ module.exports = { user: req.user, module: req.body.module, version: req.body.version, + url: req.body.url, req: apiUtils.getRequestLogObject(req) } runtimeAPI.nodes.addModule(opts).then(function(info) { diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index 189f903d8..d4ec10f08 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.anonymousStrategy); +passport.use(strategies.tokensStrategy); var server = oauth2orize.createServer(); @@ -60,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() { if (!req.user) { return next(); } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index b17bf1473..87023a487 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,9 +123,38 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } +function TokensStrategy() { + passport.Strategy.call(this); + this.name = 'tokens'; +} +util.inherits(TokensStrategy, passport.Strategy); +TokensStrategy.prototype.authenticate = function(req) { + var self = this; + var token = null; + if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } + if (token) { + Users.tokens(token).then(function(admin) { + if (admin) { + self.success(admin,{scope:admin.permissions}); + } else { + self.fail(401); + } + }); + } else { + self.fail(401); + } +} + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, - anonymousStrategy: new AnonymousStrategy() + anonymousStrategy: new AnonymousStrategy(), + tokensStrategy: new TokensStrategy() } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index 24a762958..f032332db 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -59,7 +59,9 @@ function getDefaultUser() { var api = { get: get, authenticate: authenticate, - default: getDefaultUser + default: getDefaultUser, + tokens: getDefaultUser, + tokenHeader: "authorization" } function init(config) { @@ -105,6 +107,12 @@ function init(config) { } else { api.default = getDefaultUser; } + if (config.tokens && typeof config.tokens === "function") { + api.tokens = config.tokens; + if (config.tokenHeader && typeof config.tokenHeader === "string") { + api.tokenHeader = config.tokenHeader.toLowerCase(); + } + } } function cleanUser(user) { if (user && user.hasOwnProperty('password')) { @@ -118,5 +126,7 @@ module.exports = { init: init, get: function(username) { return api.get(username).then(cleanUser)}, authenticate: function() { return api.authenticate.apply(null, arguments) }, - default: function() { return api.default(); } + default: function() { return api.default(); }, + tokens: function(token) { return api.tokens(token); }, + tokenHeader: function() { return api.tokenHeader } }; diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 0dc06ab71..534a77869 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -59,6 +59,12 @@ function init(settings,_server,storage,runtimeAPI) { }); adminApp.use(corsHandler); + if (settings.httpAdminMiddleware) { + if (typeof settings.httpAdminMiddleware === "function") { + adminApp.use(settings.httpAdminMiddleware) + } + } + auth.init(settings,storage); var maxApiRequestSize = settings.apiMaxLength || '5mb'; diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 4f86c296d..936fe3edc 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.0.6", + "version": "1.1.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "1.0.6", - "@node-red/editor-client": "1.0.6", + "@node-red/util": "1.1.0", + "@node-red/editor-client": "1.1.0", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", 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 ed4d4f9a6..f71bd2577 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 @@ -14,7 +14,13 @@ "back": "Back", "next": "Next", "clone": "Clone project", - "cont": "Continue" + "cont": "Continue", + "style": "Style", + "line": "Outline", + "fill": "Fill", + "label": "Label", + "color": "Color", + "position": "Position" }, "type": { "string": "string", @@ -28,6 +34,13 @@ "null": "null" } }, + "event": { + "loadPalette": "Loading Palette", + "loadNodeCatalogs": "Loading Node catalogs", + "loadNodes": "Loading Nodes __count__", + "loadFlows": "Loading Flows", + "importFlows": "Adding Flows to workspace" + }, "workspace": { "defaultName": "Flow __number__", "editFlow": "Edit flow: __name__", @@ -91,7 +104,12 @@ "projects-new": "New", "projects-open": "Open", "projects-settings": "Project Settings", - "showNodeLabelDefault": "Show label of newly added nodes" + "showNodeLabelDefault": "Show label of newly added nodes", + "groups": "Groups", + "groupSelection": "Group selection", + "ungroupSelection": "Ungroup selection", + "groupMergeSelection": "Merge selection", + "groupRemoveSelection": "Remove from group" } }, "actions": { @@ -171,6 +189,8 @@ "node_plural": "__count__ nodes", "configNode": "__count__ configuration node", "configNode_plural": "__count__ configuration nodes", + "group": "__count__ group", + "group_plural": "__count__ groups", "flow": "__count__ flow", "flow_plural": "__count__ flows", "subflow": "__count__ subflow", @@ -186,6 +206,9 @@ "nodesImported": "Imported:", "nodeCopied": "__count__ node copied", "nodeCopied_plural": "__count__ nodes copied", + "groupCopied": "__count__ group copied", + "groupCopied_plural": "__count__ groups copied", + "groupStyleCopied": "Group style copied", "invalidFlow": "Invalid flow: __message__", "export": { "selected":"selected nodes", @@ -308,6 +331,13 @@ "multipleInputsToSelection": "Cannot create subflow: multiple inputs to selection" } }, + "group": { + "editGroup": "Edit group: __name__", + "errors": { + "cannotCreateDiffGroups": "Cannot create group using nodes from different groups", + "cannotAddSubflowPorts": "Cannot add subflow ports to a group" + } + }, "editor": { "configEdit": "Edit", "configAdd": "Add", @@ -351,7 +381,8 @@ "bool": "bool", "json": "JSON", "bin": "buffer", - "env": "env variable" + "env": "env variable", + "cred": "credential" }, "menu": { "input": "input", @@ -538,6 +569,7 @@ "label": "info", "node": "Node", "type": "Type", + "group": "Group", "module": "Module", "id": "ID", "status": "Status", @@ -560,7 +592,20 @@ "nodeHelp": "Node Help", "none":"None", "arrayItems": "__count__ items", - "showTips":"You can open the tips from the settings panel" + "showTips":"You can open the tips from the settings panel", + "outline": "Outline", + "empty": "empty", + "globalConfig": "Global Configuration Nodes" + }, + "help": { + "name": "Help", + "label": "help", + "search": "Search help", + "nodeHelp": "Node Help", + "showHelp": "Show help", + "showInOutline": "Show in outline", + "showTopics": "Show topics", + "noHelp": "No help topic selected" }, "config": { "name": "Configuration nodes", @@ -613,7 +658,6 @@ "removeFromProject": "remove from project", "addToProject": "add to project", "files": "Files", - "package": "Package", "flow": "Flow", "credentials": "Credentials", "package":"Package", @@ -757,7 +801,8 @@ "bin": "buffer", "date": "timestamp", "jsonata": "expression", - "env": "env variable" + "env": "env variable", + "cred": "credential" } }, "editableList": { @@ -804,9 +849,9 @@ "expandItems": "Expand items", "collapseItems": "Collapse items", "duplicate": "Duplicate", - "error": { - "invalidJSON": "Invalid JSON: " - } + "error": { + "invalidJSON": "Invalid JSON: " + } }, "markdownEditor": { "title": "Markdown editor", @@ -978,7 +1023,7 @@ "retry": "Retry", "update-failed": "Failed to update auth", "unhandled": "Unhandled error response", - "host-key-verify-failed": "

Host key verification failed.

The repository host key could not be verified. Please update your known_hosts file and try again." + "host-key-verify-failed": "

Host key verification failed.

The repository host key could not be verified. Please update your known_hosts file and try again.

" }, "create-branch-list": { "invalid": "Invalid branch", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json old mode 100755 new mode 100644 index cf04ced1d..9a0c23cc5 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -14,7 +14,13 @@ "back": "戻る", "next": "進む", "clone": "プロジェクトをクローン", - "cont": "続ける" + "cont": "続ける", + "style": "形式", + "line": "線", + "fill": "塗りつぶし", + "label": "ラベル", + "color": "色", + "position": "配置" }, "type": { "string": "文字列", @@ -28,6 +34,13 @@ "null": "null" } }, + "event": { + "loadPalette": "パレットを読み込み中", + "loadNodeCatalogs": "ノードカタログを読み込み中", + "loadNodes": "ノードを読み込み中 __count__", + "loadFlows": "フローを読み込み中", + "importFlows": "ワークスペースにフローを追加中" + }, "workspace": { "defaultName": "フロー __number__", "editFlow": "フローを編集: __name__", @@ -67,7 +80,7 @@ "settings": "設定", "userSettings": "ユーザ設定", "nodes": "ノード", - "displayStatus": "ノードの状態を表示", + "displayStatus": "ノードのステータスを表示", "displayConfig": "ノードの設定", "import": "読み込み", "export": "書き出し", @@ -91,7 +104,12 @@ "projects-new": "新規", "projects-open": "開く", "projects-settings": "設定", - "showNodeLabelDefault": "追加したノードのラベルを表示" + "showNodeLabelDefault": "追加したノードのラベルを表示", + "groups": "グループ", + "groupSelection": "選択部分をグループ化", + "ungroupSelection": "選択部分をグループ解除", + "groupMergeSelection": "選択部分をマージ", + "groupRemoveSelection": "グループから削除" } }, "actions": { @@ -171,6 +189,8 @@ "node_plural": "__count__ 個のノード", "configNode": "__count__ 個の設定ノード", "configNode_plural": "__count__ 個の設定ノード", + "group": "__count__ 個のグループ", + "group_plural": "__count__ 個のグループ", "flow": "__count__ 個のフロー", "flow_plural": "__count__ 個のフロー", "subflow": "__count__ 個のサブフロー", @@ -186,6 +206,9 @@ "nodesImported": "読み込みました:", "nodeCopied": "__count__ 個のノードをコピーしました", "nodeCopied_plural": "__count__ 個のノードをコピーしました", + "groupCopied": "__count__ 個のグループをコピーしました", + "groupCopied_plural": "__count__ 個のグループをコピーしました", + "groupStyleCopied": "グループの形式をコピーしました", "invalidFlow": "不正なフロー: __message__", "export": { "selected": "選択したフロー", @@ -308,6 +331,13 @@ "multipleInputsToSelection": "サブフローを作成できません: 複数の入力が選択されています" } }, + "group": { + "editGroup": "__name__ グループを編集", + "errors": { + "cannotCreateDiffGroups": "異なるグループのノードを使用してグループを作成することはできません", + "cannotAddSubflowPorts": "グループにサブフローの端子を追加できません" + } + }, "editor": { "configEdit": "編集", "configAdd": "追加", @@ -351,7 +381,8 @@ "bool": "真偽", "json": "JSON", "bin": "バッファ", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" }, "menu": { "input": "入力", @@ -538,6 +569,7 @@ "label": "情報", "node": "ノード", "type": "型", + "group": "グループ", "module": "モジュール", "id": "ID", "status": "状態", @@ -560,7 +592,20 @@ "nodeHelp": "ノードのヘルプ", "none": "なし", "arrayItems": "__count__ 要素", - "showTips": "設定からヒントを表示できます" + "showTips": "設定からヒントを表示できます", + "outline": "アウトライン", + "empty": "空", + "globalConfig": "グローバル設定ノード" + }, + "help": { + "name": "ヘルプ", + "label": "ヘルプ", + "search": "ヘルプを検索", + "nodeHelp": "ノードヘルプ", + "showHelp": "ヘルプを表示", + "showInOutline": "アウトラインに表示", + "showTopics": "トピックを表示", + "noHelp": "ヘルプのトピックが未選択" }, "config": { "name": "ノードの設定を表示", @@ -613,9 +658,9 @@ "removeFromProject": "プロジェクトから削除", "addToProject": "プロジェクトへ追加", "files": "ファイル", - "package": "パッケージ", "flow": "フロー", "credentials": "認証情報", + "package": "パッケージ", "packageCreate": "変更が保存された時にファイルが作成されます", "fileNotExist": "ファイルが存在しません", "selectFile": "ファイルを選択", @@ -756,7 +801,8 @@ "bin": "バッファ", "date": "日時", "jsonata": "JSONata式", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" } }, "editableList": { @@ -976,7 +1022,8 @@ "passphrase": "パスフレーズ", "retry": "リトライ", "update-failed": "認証の更新に失敗しました", - "unhandled": "エラー応答が処理されませんでした" + "unhandled": "エラー応答が処理されませんでした", + "host-key-verify-failed": "

ホストキーの検証に失敗

リポジトリのホストキーを検証できませんでした。known_hostsファイルを更新して、もう一度試してください。

" }, "create-branch-list": { "invalid": "不正なブランチ", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 22fe38f4b..6774df246 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.0.6", + "version": "1.1.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/editor-client/src/js/events.js b/packages/node_modules/@node-red/editor-client/src/js/events.js index f79cc864f..41e669aba 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/events.js +++ b/packages/node_modules/@node-red/editor-client/src/js/events.js @@ -32,11 +32,16 @@ } } } - function emit(evt,arg) { + function emit() { + var evt = arguments[0] + var args = Array.prototype.slice.call(arguments,1); + if (RED.events.DEBUG) { + console.log(evt,args); + } if (handlers[evt]) { for (var i=0;idiv:not(.node-input-env-container-row)"); var height = size.height; @@ -413,14 +450,18 @@ RED.nodes = (function() { } }); sf._def = RED.nodes.getType("subflow:"+sf.id); + RED.events.emit("subflows:add",sf); } function getSubflow(id) { return subflows[id]; } function removeSubflow(sf) { - delete subflows[sf.id]; - delete nodeTabMap[sf.id]; - registry.removeNodeType("subflow:"+sf.id); + if (subflows[sf.id]) { + delete subflows[sf.id]; + delete nodeTabMap[sf.id]; + registry.removeNodeType("subflow:"+sf.id); + RED.events.emit("subflows:remove",sf); + } } function subflowContains(sfid,nodeid) { @@ -493,6 +534,9 @@ RED.nodes = (function() { if (n.d === true) { node.d = true; } + if (n.g) { + node.g = n.g; + } if (node.type == "unknown") { for (var p in n._orig) { if (n._orig.hasOwnProperty(p)) { @@ -505,19 +549,33 @@ RED.nodes = (function() { node[d] = n[d]; } } - if(exportCreds && n.credentials) { + if (exportCreds) { var credentialSet = {}; - node.credentials = {}; - for (var cred in n._def.credentials) { - if (n._def.credentials.hasOwnProperty(cred)) { - if (n._def.credentials[cred].type == 'password') { + if (/^subflow:/.test(node.type) && n.credentials) { + // A subflow instance node can have arbitrary creds + for (var sfCred in n.credentials) { + if (n.credentials.hasOwnProperty(sfCred)) { if (!n.credentials._ || - n.credentials["has_"+cred] != n.credentials._["has_"+cred] || - (n.credentials["has_"+cred] && n.credentials[cred])) { + n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || + (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { + credentialSet[sfCred] = n.credentials[sfCred]; + } + } + } + } else if (n.credentials) { + node.credentials = {}; + // All other nodes have a well-defined list of possible credentials + for (var cred in n._def.credentials) { + if (n._def.credentials.hasOwnProperty(cred)) { + if (n._def.credentials[cred].type == 'password') { + if (!n.credentials._ || + n.credentials["has_"+cred] != n.credentials._["has_"+cred] || + (n.credentials["has_"+cred] && n.credentials[cred])) { + credentialSet[cred] = n.credentials[cred]; + } + } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { credentialSet[cred] = n.credentials[cred]; } - } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { - credentialSet[cred] = n.credentials[cred]; } } } @@ -526,6 +584,13 @@ RED.nodes = (function() { } } } + if (n.type === "group") { + node.x = n.x; + node.y = n.y; + node.w = n.w; + node.h = n.h; + node.nodes = node.nodes.map(function(n) { return n.id }); + } if (n._def.category != "config") { node.x = n.x; node.y = n.y; @@ -568,7 +633,7 @@ RED.nodes = (function() { return node; } - function convertSubflow(n) { + function convertSubflow(n, exportCreds) { var node = {}; node.id = n.id; node.type = n.type; @@ -578,6 +643,24 @@ RED.nodes = (function() { node.in = []; node.out = []; node.env = n.env; + + if (exportCreds) { + var credentialSet = {}; + // A subflow node can have arbitrary creds + for (var sfCred in n.credentials) { + if (n.credentials.hasOwnProperty(sfCred)) { + if (!n.credentials._ || + n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || + (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { + credentialSet[sfCred] = n.credentials[sfCred]; + } + } + } + if (Object.keys(credentialSet).length > 0) { + node.credentials = credentialSet; + } + } + node.color = n.color; n.in.forEach(function(p) { @@ -633,8 +716,18 @@ RED.nodes = (function() { /** * Converts the current node selection to an exportable JSON Object **/ - function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) { + function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) { var nns = []; + + exportedIds = exportedIds || {}; + set = set.filter(function(n) { + if (exportedIds[n.id]) { + return false; + } + exportedIds[n.id] = true; + return true; + }) + exportedConfigNodes = exportedConfigNodes || {}; exportedSubflows = exportedSubflows || {}; for (var n=0;n 1) { + throw new Error("Node-RED's IE11 Array.from polyfill doesn't support multiple arguments"); + } + var arrayLike = arguments[0] + var result = []; + if (arrayLike.forEach) { + arrayLike.forEach(function(i) { + result.push(i); + }) + } else { + for (var i=0;i)/); + var totalCount = configs.length; + var stepConfig = function() { + loader.reportProgress(RED._("event.loadNodes",{count:(totalCount-configs.length)+"/"+totalCount}), 30 + ((totalCount-configs.length)/totalCount)*40 ) + if (configs.length === 0) { $("#red-ui-editor").i18n(); $("#red-ui-palette > .red-ui-palette-spinner").hide(); $(".red-ui-palette-scroll").removeClass("hide"); $("#red-ui-palette-search").removeClass("hide"); - loadFlows(function() { - if (RED.settings.theme("projects.enabled",false)) { - RED.projects.refresh(function(activeProject) { + if (RED.settings.theme("projects.enabled",false)) { + RED.projects.refresh(function(activeProject) { + loadFlows(function() { RED.sidebar.info.refresh() if (!activeProject) { // Projects enabled but no active project @@ -140,12 +147,14 @@ var RED = (function() { } completeLoad(); }); - } else { + }); + } else { + loadFlows(function() { // Projects disabled by the user RED.sidebar.info.refresh() completeLoad(); - } - }); + }); + } } else { var config = configs.shift(); appendNodeConfig(config,stepConfig); @@ -157,6 +166,7 @@ var RED = (function() { } function loadFlows(done) { + loader.reportProgress(RED._("event.loadFlows"),80 ) $.ajax({ headers: { "Accept":"application/json", @@ -167,6 +177,7 @@ var RED = (function() { if (nodes) { var currentHash = window.location.hash; RED.nodes.version(nodes.rev); + loader.reportProgress(RED._("event.importFlows"),90 ) RED.nodes.import(nodes.flows); RED.nodes.dirty(false); RED.view.redraw(true); @@ -193,6 +204,7 @@ var RED = (function() { return; } if (notificationId === "project-update") { + loader.start("Loading project",0) RED.nodes.clear(); RED.history.clear(); RED.view.redraw(true); @@ -208,6 +220,7 @@ var RED = (function() { "revert": RED._("notification.project.revert", {project: msg.project}), "merge-complete": RED._("notification.project.merge-complete") }[msg.action]; + loader.end() RED.notify("

"+message+"

"); RED.sidebar.info.refresh() }); @@ -423,6 +436,12 @@ var RED = (function() { var id = topic.substring(9); RED.eventLog.log(id,payload); }); + + $(".red-ui-header-toolbar").show(); + + setTimeout(function() { + loader.end(); + },100); } function showAbout() { @@ -431,8 +450,7 @@ var RED = (function() { ''+ ''; - RED.sidebar.info.set(aboutHeader+RED.utils.renderMarkdown(data)); - RED.sidebar.info.show(); + RED.sidebar.help.set(aboutHeader+RED.utils.renderMarkdown(data)); }); } @@ -472,6 +490,14 @@ var RED = (function() { {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"}, {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"}, ]}); + menuOptions.push({id:"menu-item-group",label:RED._("menu.label.groups"), options: [ + {id:"menu-item-group-group",label:RED._("menu.label.groupSelection"),disabled:true,onselect:"core:group-selection"}, + {id:"menu-item-group-ungroup",label:RED._("menu.label.ungroupSelection"),disabled:true,onselect:"core:ungroup-selection"}, + null, + {id:"menu-item-group-merge",label:RED._("menu.label.groupMergeSelection"),disabled:true,onselect:"core:merge-selection-to-group"}, + {id:"menu-item-group-remove",label:RED._("menu.label.groupRemoveSelection"),disabled:true,onselect:"core:remove-selection-from-group"} + ]}); + menuOptions.push(null); if (RED.settings.theme('palette.editable') !== false) { menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); @@ -497,7 +523,6 @@ var RED = (function() { } function loadEditor() { - RED.workspaces.init(); RED.statusBar.init(); RED.view.init(); @@ -524,6 +549,7 @@ var RED = (function() { } RED.subflow.init(); + RED.group.init(); RED.clipboard.init(); RED.search.init(); RED.actionList.init(); @@ -539,13 +565,14 @@ var RED = (function() { RED.comms.connect(); $("#red-ui-main-container").show(); - $(".red-ui-header-toolbar").show(); + RED.actions.add("core:show-about", showAbout); loadNodeList(); } + function buildEditor(options) { var header = $('
').appendTo(options.target); var logo = $('').appendTo(header); @@ -560,6 +587,10 @@ var RED = (function() { '').appendTo(options.target); $('
').appendTo(options.target); $('
').appendTo(options.target); + + loader.init().appendTo("#red-ui-main-container"); + loader.start("...",0); + $.getJSON(options.apiRootUrl+"theme", function(theme) { if (theme.header) { if (theme.header.url) { @@ -592,12 +623,39 @@ var RED = (function() { options.target.addClass("red-ui-editor"); buildEditor(options); + RED.i18n.init(options, function() { RED.settings.init(options, loadEditor); }) } + var loader = { + init: function() { + var wrapper = $('
').hide(); + var container = $('
').appendTo(wrapper); + var label = $('
',{class:"red-ui-loading-bar-label"}).appendTo(container); + var bar = $('
',{class:"red-ui-loading-bar"}).appendTo(container); + var fill =$('').appendTo(bar); + return wrapper; + }, + start: function(text, prcnt) { + if (text) { + loader.reportProgress(text,prcnt) + } + $("#red-ui-loading-progress").show(); + }, + reportProgress: function(text, prcnt) { + $(".red-ui-loading-bar-label").text(text); + $(".red-ui-loading-bar span").width(prcnt+"%") + }, + end: function() { + $("#red-ui-loading-progress").hide(); + loader.reportProgress("",0); + } + } + return { - init: init + init: init, + loader: loader } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index cdff62919..33b306752 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -184,7 +184,7 @@ RED.clipboard = (function() { '
'+ '
'+ '
'+ - '
'+ + '
'+ ''+ '
'+ '
'+ @@ -216,7 +216,7 @@ RED.clipboard = (function() { ' '+ ''+ '
'+ - '
'+ + '
'+ ''+ '
'+ '
'+ @@ -474,6 +474,12 @@ RED.clipboard = (function() { },100) } + var dialogHeight = 400; + var winHeight = $(window).height(); + if (winHeight < 600) { + dialogHeight = 400 - (600 - winHeight); + } + $(".red-ui-clipboard-dialog-box").height(dialogHeight); dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open"); popover = RED.popover.create({ @@ -583,6 +589,7 @@ RED.clipboard = (function() { nodes = []; selection.forEach(function(n) { nodes.push(n); + nodes = nodes.concat(RED.nodes.groups(n.id)); nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); }); } else { @@ -592,7 +599,8 @@ RED.clipboard = (function() { nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'})); } else if (type === 'red-ui-clipboard-dialog-export-rng-flow') { var activeWorkspace = RED.workspaces.active(); - nodes = RED.nodes.filterNodes({z:activeWorkspace}); + nodes = RED.nodes.groups(activeWorkspace); + nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace})); var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace); nodes.unshift(parentNode); nodes = RED.nodes.createExportableNodeSet(nodes); @@ -637,6 +645,14 @@ RED.clipboard = (function() { $("#red-ui-clipboard-dialog-export-fmt-mini").trigger("click"); } tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+mode); + + var dialogHeight = 400; + var winHeight = $(window).height(); + if (winHeight < 600) { + dialogHeight = 400 - (600 - winHeight); + } + $(".red-ui-clipboard-dialog-box").height(dialogHeight); + dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" ); $("#red-ui-clipboard-dialog-export-text").trigger("focus"); @@ -738,6 +754,8 @@ RED.clipboard = (function() { RED.actions.add("core:show-library-export-dialog",function() { exportNodes('library') }); RED.actions.add("core:show-library-import-dialog",function() { importNodes('library') }); + RED.actions.add("core:show-examples-import-dialog",function() { importNodes('examples') }); + RED.events.on("editor:open",function() { disabled = true; }); RED.events.on("editor:close",function() { disabled = false; }); RED.events.on("search:open",function() { disabled = true; }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js new file mode 100644 index 000000000..3739cd970 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -0,0 +1,203 @@ +RED.colorPicker = (function() { + + function create(options) { + var color = options.value; + var id = options.id; + var colorPalette = options.palette || []; + var width = options.cellWidth || 30; + var height = options.cellHeight || 30; + var margin = options.cellMargin || 2; + var perRow = options.cellPerRow || 6; + + var container = $("
",{style:"display:inline-block"}); + var colorHiddenInput = $("", { id: id, type: "hidden", value: color }).appendTo(container); + var opacityHiddenInput = $("", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container); + + var colorButton = $('').css({ + width:"20px" + }).appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + var currentType = that.input.attr("type"); + if (currentType === "text") { + that.input.attr("type","password"); + eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + setTimeout(function() { + that.input.focus(); + },50); + } else { + that.input.attr("type","text"); + eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); + setTimeout(function() { + that.input.focus(); + },50); + } + }).hide(); + var eyeCon = $('').css("margin-left","-1px").appendTo(eyeButton); + + if (value === "__PWRD__") { + var innerContainer = $('
').css({ + padding:"6px 6px", + borderRadius:"4px" + }).addClass("red-ui-typedInput-value-label-inactive").appendTo(container); + var editButton = $('').appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + innerContainer.hide(); + container.css("background","none"); + container.css("pointer-events","none"); + that.input.val(""); + that.element.val(""); + that.elementDiv.show(); + editButton.hide(); + cancelButton.show(); + eyeButton.show(); + setTimeout(function() { + that.input.focus(); + },50); + }); + var cancelButton = $('').css("margin-left","3px").appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + innerContainer.show(); + container.css("background",""); + that.input.val("__PWRD__"); + that.element.val("__PWRD__"); + that.elementDiv.hide(); + editButton.show(); + cancelButton.hide(); + eyeButton.hide(); + that.input.attr("type","password"); + eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + + }).hide(); + } else { + container.css("background","none"); + container.css("pointer-events","none"); + this.elementDiv.show(); + eyeButton.show(); + } + } } }; var nlsd = false; @@ -220,6 +298,8 @@ that.input.attr(d,m); }); + this.defaultInputType = this.input.attr('type'); + this.uiSelect.addClass("red-ui-typedInput-container"); this.element.attr('type','hidden'); @@ -635,7 +715,7 @@ $('',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); } else { - $('',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); + $('',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel); } } if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { @@ -778,6 +858,11 @@ if (this.optionSelectTrigger) { this.optionSelectTrigger.hide(); } + if (opt.inputType) { + this.input.attr('type',opt.inputType) + } else { + this.input.attr('type',this.defaultInputType) + } if (opt.hasValue === false) { this.oldValue = this.input.val(); this.input.val(""); @@ -786,8 +871,8 @@ } else if (opt.valueLabel) { this.valueLabelContainer.show(); this.valueLabelContainer.empty(); - opt.valueLabel.call(this,this.valueLabelContainer,this.input.val()); this.elementDiv.hide(); + opt.valueLabel.call(this,this.valueLabelContainer,this.input.val()); } else { if (this.oldValue !== undefined) { this.input.val(this.oldValue); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 9e91410a9..fdff99a69 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -108,7 +108,7 @@ RED.deploy = (function() { - RED.events.on('nodes:change',function(state) { + RED.events.on('workspace:dirty',function(state) { if (state.dirty) { window.onbeforeunload = function() { return RED._("deploy.confirm.undeployedChanges"); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 6641e9b88..29e8ab06b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -490,8 +490,7 @@ RED.editor = (function() { done(); } } - - if (definition.credentials) { + if (definition.credentials || /^subflow:/.test(definition.type)) { if (node.credentials) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); @@ -499,7 +498,9 @@ RED.editor = (function() { $.getJSON(getCredentialsURL(node.type, node.id), function (data) { node.credentials = data; node.credentials._ = $.extend(true,{},data); - populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + if (!/^subflow:/.test(definition.type)) { + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + } completePrepare(); }); } @@ -513,7 +514,9 @@ RED.editor = (function() { for (var i=editStack.length-1;i').prependTo(dialogForm); $('').prependTo(dialogForm); + $('').prependTo(dialogForm); dialogForm.on("submit", function(e) { e.preventDefault();}); dialogForm.find('input').attr("autocomplete","off"); return dialogForm; @@ -784,6 +791,11 @@ RED.editor = (function() { nodeDiv.css({ 'backgroundColor': backgroundColor }); + var borderColor = RED.utils.getDarkerColor(backgroundColor); + if (borderColor !== backgroundColor) { + nodeDiv.css('border-color',borderColor) + } + } var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); RED.utils.createIconElement(icon_url, iconContainer, true); @@ -819,99 +831,6 @@ RED.editor = (function() { searchInput.trigger("focus"); } - function createColorPicker(colorRow, color) { - - var colorButton = $('').appendTo(popOverContent) $('

',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent); } } catch(err) { @@ -181,7 +182,11 @@ RED.palette = (function() { function setIcon(element,sf) { var icon_url = RED.utils.getNodeIcon(sf._def); var iconContainer = element.find(".red-ui-palette-icon-container"); - RED.utils.createIconElement(icon_url, iconContainer, true); + var currentIcon = iconContainer.attr("data-palette-icon"); + if (currentIcon !== icon_url) { + iconContainer.attr("data-palette-icon", icon_url); + RED.utils.createIconElement(icon_url, iconContainer, true); + } } function getPaletteNode(type) { @@ -224,6 +229,7 @@ RED.palette = (function() { var iconContainer = $('

', { class: "red-ui-palette-icon-container"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-icon-container-right" : "") }).appendTo(d); + iconContainer.attr("data-palette-icon", icon_url); RED.utils.createIconElement(icon_url, iconContainer, true); } @@ -250,6 +256,7 @@ RED.palette = (function() { var popover = RED.popover.create({ target:d, trigger: "hover", + interactive: true, width: "300px", content: "hi", delay: { show: 750, hide: 50 } @@ -265,25 +272,28 @@ RED.palette = (function() { // html: true, // container:'body' // }); - d.on("click", function() { - RED.view.focus(); - var helpText; - if (nt.indexOf("subflow:") === 0) { - helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); - } else { - helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||(''+RED._("sidebar.info.none")+''); - } - // Don't look too closely. RED.sidebar.info.set will set the 'Description' - // section of the sidebar. Pass in the title of the Help section so it looks - // right. - RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp")); - }); + // d.on("click", function() { + // RED.view.focus(); + // var helpText; + // if (nt.indexOf("subflow:") === 0) { + // helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); + // } else { + // helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||(''+RED._("sidebar.info.none")+''); + // } + // // Don't look too closely. RED.sidebar.info.set will set the 'Description' + // // section of the sidebar. Pass in the title of the Help section so it looks + // // right. + // RED.sidebar.type.show(helpText,RED._("sidebar.info.nodeHelp")); + // }); var chart = $("#red-ui-workspace-chart"); var chartSVG = $("#red-ui-workspace-chart>svg").get(0); var activeSpliceLink; var mouseX; var mouseY; var spliceTimer; + var groupTimer; + var activeGroup; + var hoverGroup; var paletteWidth; var paletteTop; $(d).draggable({ @@ -295,16 +305,53 @@ RED.palette = (function() { start: function() { paletteWidth = $("#red-ui-palette").width(); paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; + hoverGroup = null; + activeGroup = RED.view.getActiveGroup(); + if (activeGroup) { + document.getElementById("group_select_"+activeGroup.id).classList.add("red-ui-flow-group-active-hovered"); + } RED.view.focus(); }, - stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}}, + stop: function() { + d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); + if (hoverGroup) { + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + } + if (activeGroup) { + document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); + } + if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } + if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } + }, drag: function(e,ui) { var paletteNode = getPaletteNode(nt); ui.originalPosition.left = paletteNode.offset().left; + mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); + mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); + if (!groupTimer) { + groupTimer = setTimeout(function() { + mouseX /= RED.view.scale(); + mouseY /= RED.view.scale(); + var group = RED.view.getGroupAtPoint(mouseX,mouseY); + if (group !== hoverGroup) { + if (hoverGroup) { + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + } + if (group) { + document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered"); + } + hoverGroup = group; + if (hoverGroup) { + $(ui.helper).data('group',hoverGroup); + } else { + $(ui.helper).removeData('group'); + } + } + groupTimer = null; + },200) + } if (def.inputs > 0 && def.outputs > 0) { - mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); - mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); if (!spliceTimer) { spliceTimer = setTimeout(function() { var nodes = []; @@ -412,61 +459,67 @@ RED.palette = (function() { categoryNode.show(); paletteNode.show(); } - function refreshNodeTypes() { - RED.nodes.eachSubflow(function(sf) { - var paletteNode = getPaletteNode('subflow:'+sf.id); - var portInput = paletteNode.find(".red-ui-palette-port-input"); - var portOutput = paletteNode.find(".red-ui-palette-port-output"); + RED.nodes.eachSubflow(refreshSubflow) + } + function refreshSubflow(sf) { + var paletteNode = getPaletteNode('subflow:'+sf.id); + var portInput = paletteNode.find(".red-ui-palette-port-input"); + var portOutput = paletteNode.find(".red-ui-palette-port-output"); - var paletteLabel = paletteNode.find(".red-ui-palette-label"); - paletteLabel.attr("class","red-ui-palette-label" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : "")); + var paletteLabel = paletteNode.find(".red-ui-palette-label"); + paletteLabel.attr("class","red-ui-palette-label" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : "")); - var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container"); - paletteIconContainer.attr("class","red-ui-palette-icon-container" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : "")); + var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container"); + paletteIconContainer.attr("class","red-ui-palette-icon-container" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : "")); - if (portInput.length === 0 && sf.in.length > 0) { - var portIn = document.createElement("div"); - portIn.className = "red-ui-palette-port red-ui-palette-port-input"; - paletteNode.append(portIn); - } else if (portInput.length !== 0 && sf.in.length === 0) { - portInput.remove(); - } + if (portInput.length === 0 && sf.in.length > 0) { + var portIn = document.createElement("div"); + portIn.className = "red-ui-palette-port red-ui-palette-port-input"; + paletteNode.append(portIn); + } else if (portInput.length !== 0 && sf.in.length === 0) { + portInput.remove(); + } - if (portOutput.length === 0 && sf.out.length > 0) { - var portOut = document.createElement("div"); - portOut.className = "red-ui-palette-port red-ui-palette-port-output"; - paletteNode.append(portOut); - } else if (portOutput.length !== 0 && sf.out.length === 0) { - portOutput.remove(); - } + if (portOutput.length === 0 && sf.out.length > 0) { + var portOut = document.createElement("div"); + portOut.className = "red-ui-palette-port red-ui-palette-port-output"; + paletteNode.append(portOut); + } else if (portOutput.length !== 0 && sf.out.length === 0) { + portOutput.remove(); + } + var currentLabel = paletteNode.attr("data-palette-label"); + var currentInfo = paletteNode.attr("data-palette-info"); + + if (currentLabel !== sf.name || currentInfo !== sf.info) { + paletteNode.attr("data-palette-info",sf.info); setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||"")); - setIcon(paletteNode,sf); + } + setIcon(paletteNode,sf); - var currentCategory = paletteNode.data('category'); - var newCategory = (sf.category||"subflows"); - if (currentCategory !== newCategory) { - var category = escapeCategory(newCategory); - createCategory(newCategory,category,category,"node-red"); + var currentCategory = paletteNode.data('category'); + var newCategory = (sf.category||"subflows"); + if (currentCategory !== newCategory) { + var category = escapeCategory(newCategory); + createCategory(newCategory,category,category,"node-red"); - var currentCategoryNode = paletteNode.closest(".red-ui-palette-category"); - var newCategoryNode = $("#red-ui-palette-"+category); - newCategoryNode.append(paletteNode); - if (newCategoryNode.find(".red-ui-palette-node").length === 1) { - categoryContainers[category].open(); - } - - paletteNode.data('category',newCategory); - if (currentCategoryNode.find(".red-ui-palette-node").length === 0) { - if (currentCategoryNode.find("i").hasClass("expanded")) { - currentCategoryNode.find(".red-ui-palette-content").slideToggle(); - currentCategoryNode.find("i").toggleClass("expanded"); - } - } + var currentCategoryNode = paletteNode.closest(".red-ui-palette-category"); + var newCategoryNode = $("#red-ui-palette-"+category); + newCategoryNode.append(paletteNode); + if (newCategoryNode.find(".red-ui-palette-node").length === 1) { + categoryContainers[category].open(); } - paletteNode.css("backgroundColor", sf.color); - }); + paletteNode.data('category',newCategory); + if (currentCategoryNode.find(".red-ui-palette-node").length === 0) { + if (currentCategoryNode.find("i").hasClass("expanded")) { + currentCategoryNode.find(".red-ui-palette-content").slideToggle(); + currentCategoryNode.find("i").toggleClass("expanded"); + } + } + } + + paletteNode.css("backgroundColor", sf.color); } function filterChange(val) { @@ -504,6 +557,8 @@ RED.palette = (function() { $('').appendTo("#red-ui-palette"); $('
').appendTo("#red-ui-palette"); + $("#red-ui-palette > .red-ui-palette-spinner").show(); + RED.events.on('registry:node-type-added', function(nodeType) { var def = RED.nodes.getType(nodeType); @@ -545,7 +600,8 @@ RED.palette = (function() { } }); - $("#red-ui-palette > .red-ui-palette-spinner").show(); + RED.events.on("subflows:change",refreshSubflow); + $("#red-ui-palette-search input").searchBox({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index db9e8450c..462ad7575 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -685,6 +685,8 @@ RED.projects = (function() { } } },projectData).then(function() { + RED.menu.setDisabled('menu-item-projects-open',false); + RED.menu.setDisabled('menu-item-projects-settings',false); RED.events.emit("project:change", {name:name}); }).always(function() { setTimeout(function() { @@ -1495,7 +1497,6 @@ RED.projects = (function() { } } else if (projectType === 'open') { return switchProject(selectedProject.name,function(err,data) { - dialog.dialog( "close" ); if (err) { if (err.code !== 'credentials_load_failed') { console.log(RED._("projects.create.unexpected_error"),err) @@ -1604,6 +1605,7 @@ RED.projects = (function() { }, } },{active:true}).then(function() { + dialog.dialog( "close" ); RED.events.emit("project:change", {name:name}); }).always(function() { setTimeout(function() { @@ -1671,16 +1673,27 @@ RED.projects = (function() { if (typeof buttons === 'function') { buttons = buttons(options||{}); } + + + dialog.dialog('option','buttons',buttons); dialogBody.append(container); + + + var dialogHeight = 590; + var winHeight = $(window).height(); + if (winHeight < 750) { + dialogHeight = 590 - (750 - winHeight); + } + $(".red-ui-projects-dialog-box").height(dialogHeight); + $(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 180); dialog.dialog('option','title',screen.title||""); dialog.dialog("open"); - dialog.dialog({position: { 'my': 'center top', 'at': 'center top+20', 'of': window }}); } function createProjectList(options) { options = options||{}; - var height = options.height || "300px"; + var height = options.height || "200px"; var container = $('
',{class:"red-ui-projects-dialog-project-list-container" }); var filterTerm = ""; @@ -2252,7 +2265,7 @@ RED.projects = (function() { } function init() { - dialog = $('
') + dialog = $('
') .appendTo("#red-ui-editor") .dialog({ modal: true, @@ -2339,6 +2352,7 @@ RED.projects = (function() { if (data.active) { $.getJSON("projects/"+data.active, function(project) { activeProject = project; + RED.events.emit("projects:load",activeProject); RED.sidebar.versionControl.refresh(true); if (done) { done(activeProject); 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 9bb4cefb0..7be689c4a 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 @@ -23,8 +23,7 @@ RED.search = (function() { var visible = false; var index = {}; - var keys = []; - var results = []; + var currentResults = []; var previousActiveElement; @@ -66,23 +65,9 @@ RED.search = (function() { } } - function indexWorkspace() { - index = {}; - RED.nodes.eachWorkspace(indexNode); - RED.nodes.eachSubflow(indexNode); - RED.nodes.eachConfig(indexNode); - RED.nodes.eachNode(indexNode); - keys = Object.keys(index); - keys.sort(); - keys.forEach(function(key) { - index[key] = Object.keys(index[key]).map(function(id) { - return index[key][id]; - }) - }) - } - function search(val) { - searchResults.editableList('empty'); + var results = []; + var keys = Object.keys(index); var typeFilter; var m = /(?:^| )type:([^ ]+)/.exec(val); if (m) { @@ -92,8 +77,7 @@ RED.search = (function() { val = val.trim(); - selected = -1; - results = []; + if (val.length > 0 || typeFilter) { val = val.toLowerCase(); var i; @@ -104,10 +88,14 @@ RED.search = (function() { var key = keys[i]; var kpos = keys[i].indexOf(val); if (kpos > -1) { - for (j=0;j 0) { - for (i=0;i 25) { - searchResults.editableList('addItem', { - more: { - results: results, - start: 25 - } - }) - } - } else { - searchResults.editableList('addItem',{}); - } } + return results; } function ensureSelectedIsVisible() { @@ -161,13 +135,37 @@ RED.search = (function() { searchInput = $('').appendTo(searchDiv).searchBox({ delay: 200, change: function() { - search($(this).val()); + searchResults.editableList('empty'); + selected = -1; + currentResults = search($(this).val()); + if (currentResults.length > 0) { + for (i=0;i 25) { + searchResults.editableList('addItem', { + more: { + results: currentResults, + start: 25 + } + }) + } + } else { + searchResults.editableList('addItem',{}); + } + + } }); + var copySearchContainer = $('').appendTo(searchDiv).on('click', function(evt) { + evt.preventDefault(); + RED.sidebar.info.outliner.search(searchInput.val()) + hide(); + }); searchInput.on('keydown',function(evt) { var children; - if (results.length > 0) { + if (currentResults.length > 0) { if (evt.keyCode === 40) { // Down children = searchResults.children(); @@ -199,21 +197,21 @@ RED.search = (function() { var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data'); if (object) { searchResults.editableList('removeItem',object); - for (i=object.more.start;i object.more.start+25) { + if (currentResults.length > object.more.start+25) { searchResults.editableList('addItem', { more: { - results: results, + results: currentResults, start: object.more.start+25 } }) } } } else { - if (results.length > 0) { - reveal(results[Math.max(0,selected)].node); + if (currentResults.length > 0) { + reveal(currentResults[Math.max(0,selected)].node); } } } @@ -234,13 +232,13 @@ RED.search = (function() { div.on("click", function(evt) { evt.preventDefault(); searchResults.editableList('removeItem',object); - for (i=object.more.start;i object.more.start+25) { + if (currentResults.length > object.more.start+25) { searchResults.editableList('addItem', { more: { - results: results, + results: currentResults, start: object.more.start+25 } }) @@ -253,17 +251,7 @@ RED.search = (function() { var def = node._def; div = $('',{href:'#',class:"red-ui-search-result"}).appendTo(container); - var nodeDiv = $('
',{class:"red-ui-search-result-node"}).appendTo(div); - var colour = RED.utils.getNodeColor(node.type,def); - var icon_url = RED.utils.getNodeIcon(def,node); - if (node.type === 'tab') { - colour = "#C0DEED"; - } - nodeDiv.css('backgroundColor',colour); - - var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); - RED.utils.createIconElement(icon_url, iconContainer, true); - + RED.utils.createNodeIcon(node).appendTo(div); var contentDiv = $('
',{class:"red-ui-search-result-node-description"}).appendTo(div); if (node.z) { var workspace = RED.nodes.workspace(node.z); @@ -308,7 +296,7 @@ RED.search = (function() { $("#red-ui-palette-shade").show(); $("#red-ui-sidebar-shade").show(); $("#red-ui-sidebar-separator").hide(); - indexWorkspace(); + if (dialog === null) { createDialog(); } @@ -342,6 +330,28 @@ RED.search = (function() { } } + function clearIndex() { + index = {}; + } + + function addItemToIndex(item) { + indexNode(item); + } + function removeItemFromIndex(item) { + var keys = Object.keys(index); + for (var i=0,l=keys.length;i 0) { + n = tmplist.shift(); + if (n.type === "group") { + includedGroups.add(n.id); + tmplist = tmplist.concat(n.nodes); + } + nodeList.add(n); + } + + nodeList = Array.from(nodeList); + + var containingGroup = nodeList[0].g; + var nodesMovedFromGroup = []; + + for (i=0; i
'):undefined, addItem: function(container, i, opt) { + // If this is an instance node, these are properties unique to + // this instance - ie opt.parent will not be defined. + if (isTemplateNode) { container.addClass("red-ui-editor-subflow-env-editable") } @@ -859,52 +927,64 @@ RED.subflow = (function() { var nameField = null; var valueField = null; - // if (opt.parent) { - // buildEnvUIRow(envRow,opt,opt.parent.ui||{}) - // } else { - nameField = $('', { - class: "node-input-env-name", - type: "text", - placeholder: RED._("common.label.name") - }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); - valueField = $('',{ - style: "width:100%", - class: "node-input-env-value", - type: "text", - }).attr("autocomplete","disable").appendTo(envRow) - valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); - valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); - valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); - // } + nameField = $('', { + class: "node-input-env-name", + type: "text", + placeholder: RED._("common.label.name") + }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); + valueField = $('',{ + style: "width:100%", + class: "node-input-env-value", + type: "text", + }).attr("autocomplete","disable").appendTo(envRow) + valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED}); + valueField.typedInput('type', opt.type); + if (opt.type === "cred") { + if (opt.value) { + valueField.typedInput('value', opt.value); + } else if (node.credentials && node.credentials[opt.name]) { + valueField.typedInput('value', node.credentials[opt.name]); + } else if (node.credentials && node.credentials['has_'+opt.name]) { + valueField.typedInput('value', "__PWRD__"); + } else { + valueField.typedInput('value', ""); + } + } else { + valueField.typedInput('value', opt.value); + } opt.nameField = nameField; opt.valueField = valueField; - if (!opt.parent) { - var actionButton = $('
',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); - $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); - var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); - actionButton.on("click", function(evt) { - evt.preventDefault(); - removeTip.close(); - container.parent().addClass("red-ui-editableList-item-deleting") - container.fadeOut(300, function() { - envContainer.editableList('removeItem',opt); - }); + var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); + $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); + var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); + actionButton.on("click", function(evt) { + evt.preventDefault(); + removeTip.close(); + container.parent().addClass("red-ui-editableList-item-deleting") + container.fadeOut(300, function() { + envContainer.editableList('removeItem',opt); }); - } + }); if (isTemplateNode) { // Add the UI customisation row // if `opt.ui` does not exist, then apply defaults. If these // defaults do not change then they will get stripped off // before saving. - opt.ui = opt.ui || { - icon: "", - label: {}, - type: "input", - opts: {types:['str','num','bool','json','bin','env']} + if (opt.type === 'cred') { + opt.ui = opt.ui || { + icon: "", + type: "cred" + } + } else { + opt.ui = opt.ui || { + icon: "", + type: "input", + opts: {types:DEFAULT_ENV_TYPE_LIST} + } } opt.ui.label = opt.ui.label || {}; opt.ui.type = opt.ui.type || "input"; @@ -995,11 +1075,11 @@ RED.subflow = (function() { var row = $('
').appendTo(container); $('
').appendTo(row); - var typeOptions = { - 'input': {types:['str','num','bool','json','bin','env']}, + 'input': {types:DEFAULT_ENV_TYPE_LIST}, 'select': {opts:[]}, - 'spinner': {} + 'spinner': {}, + 'cred': {} }; if (ui.opts) { typeOptions[ui.type] = ui.opts; @@ -1054,7 +1134,7 @@ RED.subflow = (function() { } langs.forEach(function(l) { var row = $('
').appendTo(content); - $('').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); + $('').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); $('').text(ui.label[l]||"").appendTo(row); }); return content; @@ -1062,314 +1142,341 @@ RED.subflow = (function() { nameField.on('change',function(evt) { labelInput.attr("placeholder",$(this).val()) - }); + }); - var inputCell = $('
').appendTo(row); - var inputCellInput = $('').css("width","100%").appendTo(inputCell); - if (ui.type === "input") { - inputCellInput.val(ui.opts.types.join(",")); - } - var checkbox; - var selectBox; + var inputCell = $('
').appendTo(row); + var inputCellInput = $('').css("width","100%").appendTo(inputCell); + if (ui.type === "input") { + inputCellInput.val(ui.opts.types.join(",")); + } + var checkbox; + var selectBox; - inputCellInput.typedInput({ - types: [ - { - value:"input", - label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ - {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, - {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, - {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, - {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, - {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, - {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"} - ], - default: ['str','num','bool','json','bin','env'], - valueLabel: function(container,value) { - container.css("padding",0); - var innerContainer = $('
').css({ - "background":"white", - "height":"100%", - "box-sizing": "border-box" - }).appendTo(container); + inputCellInput.typedInput({ + types: [ + { + value:"input", + label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ + {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, + {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, + {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, + {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, + {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, + {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"}, + {value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"} + ], + default: DEFAULT_ENV_TYPE_LIST, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); - var input = $('
').appendTo(innerContainer); - $('').appendTo(input); - if (value.length) { - value.forEach(function(v) { - $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); - }) - } else { - $("").css({ - "color":"#aaa", - "padding-left": "4px" - }).text("select types...").appendTo(input); - } - } - }, - { - value:"select", - label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, - valueLabel: function(container,value) { - container.css("padding","0"); + var input = $('
').appendTo(innerContainer); + $('').appendTo(input); + if (value.length) { + value.forEach(function(v) { + if (!/^fa /.test(v.icon)) { + $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); + } else { + var s = $('',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); + $("",{class: v.icon}).appendTo(s); + } + }) + } else { + $("").css({ + "color":"#aaa", + "padding-left": "4px" + }).text("select types...").appendTo(input); + } + } + }, + { + value: "cred", + label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box", + "border-top-right-radius": "4px", + "border-bottom-right-radius": "4px" + }).appendTo(container); + $('
').html("••••••••").appendTo(innerContainer); + } + }, + { + value:"select", + label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, + valueLabel: function(container,value) { + container.css("padding","0"); - selectBox = $('').appendTo(container); - if (ui.opts && Array.isArray(ui.opts.opts)) { - ui.opts.opts.forEach(function(o) { - var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); - // $('
').appendTo(container); - var optList = $('
    ').appendTo(content).editableList({ - header:$("
    "+RED._("editor.select.label")+"
    "+RED._("editor.select.value")+"
    "), - addItem: function(row,index,itemData) { - var labelDiv = $('
    ').appendTo(row); - var label = lookupLabel(itemData.l, "", currentLocale); - itemData.label = $('').val(label).appendTo(labelDiv); - itemData.label.on('keydown', function(evt) { - if (evt.keyCode === 13) { - itemData.input.focus(); - evt.preventDefault(); - } - }); - var labelIcon = $('').appendTo(labelDiv); - RED.popover.tooltip(labelIcon,function() { - return currentLocale; - }) - itemData.input = $('').val(itemData.v).appendTo(row); + selectBox = $('').appendTo(container); + if (ui.opts && Array.isArray(ui.opts.opts)) { + ui.opts.opts.forEach(function(o) { + var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); + // $('
    ').appendTo(container); + var optList = $('
      ').appendTo(content).editableList({ + header:$("
      "+RED._("editor.select.label")+"
      "+RED._("editor.select.value")+"
      "), + addItem: function(row,index,itemData) { + var labelDiv = $('
      ').appendTo(row); + var label = lookupLabel(itemData.l, "", currentLocale); + itemData.label = $('').val(label).appendTo(labelDiv); + itemData.label.on('keydown', function(evt) { + if (evt.keyCode === 13) { + itemData.input.focus(); + evt.preventDefault(); + } + }); + var labelIcon = $('').appendTo(labelDiv); + RED.popover.tooltip(labelIcon,function() { + return currentLocale; + }) + itemData.input = $('').val(itemData.v).appendTo(row); - // Problem using a TI here: - // - this is in a popout panel - // - clicking the expand button in the TI will close the parent edit tray - // and open the type editor. - // - but it leaves the popout panel over the top. - // - there is no way to get back to the popout panel after closing the type editor - //.typedInput({default:itemData.t||'str', types:['str','num','bool','json','bin','env']}); - itemData.input.on('keydown', function(evt) { - if (evt.keyCode === 13) { - // Enter or Tab - var index = optList.editableList('indexOf',itemData); - var length = optList.editableList('length'); - if (index + 1 === length) { - var newItem = {}; - optList.editableList('addItem',newItem); - setTimeout(function() { - if (newItem.label) { - newItem.label.focus(); - } - },100) - } else { - var nextItem = optList.editableList('getItemAt',index+1); - if (nextItem.label) { - nextItem.label.focus() - } - } - evt.preventDefault(); - } - }); - }, - sortable: true, - removable: true, - height: 160 - }) - if (ui.opts.opts.length > 0) { - ui.opts.opts.forEach(function(o) { - optList.editableList('addItem',$.extend(true,{},o)) - }) - } else { - optList.editableList('addItem',{}) - } - return { - onclose: function() { - var items = optList.editableList('items'); - var vals = []; - items.each(function (i,el) { - var data = el.data('data'); - var l = data.label.val().trim(); - var v = data.input.val(); - // var t = data.input.typedInput('type'); - // var v = data.input.typedInput('value'); - if (l.length > 0) { - data.l = data.l || {}; - data.l[currentLocale] = l; - } - data.v = v; + // Problem using a TI here: + // - this is in a popout panel + // - clicking the expand button in the TI will close the parent edit tray + // and open the type editor. + // - but it leaves the popout panel over the top. + // - there is no way to get back to the popout panel after closing the type editor + //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST}); + itemData.input.on('keydown', function(evt) { + if (evt.keyCode === 13) { + // Enter or Tab + var index = optList.editableList('indexOf',itemData); + var length = optList.editableList('length'); + if (index + 1 === length) { + var newItem = {}; + optList.editableList('addItem',newItem); + setTimeout(function() { + if (newItem.label) { + newItem.label.focus(); + } + },100) + } else { + var nextItem = optList.editableList('getItemAt',index+1); + if (nextItem.label) { + nextItem.label.focus() + } + } + evt.preventDefault(); + } + }); + }, + sortable: true, + removable: true, + height: 160 + }) + if (ui.opts.opts.length > 0) { + ui.opts.opts.forEach(function(o) { + optList.editableList('addItem',$.extend(true,{},o)) + }) + } else { + optList.editableList('addItem',{}) + } + return { + onclose: function() { + var items = optList.editableList('items'); + var vals = []; + items.each(function (i,el) { + var data = el.data('data'); + var l = data.label.val().trim(); + var v = data.input.val(); + // var t = data.input.typedInput('type'); + // var v = data.input.typedInput('value'); + if (l.length > 0) { + data.l = data.l || {}; + data.l[currentLocale] = l; + } + data.v = v; - if (l.length > 0 || v.length > 0) { - var val = {l:data.l,v:data.v}; - // if (t !== 'str') { - // val.t = t; - // } - vals.push(val); - } - }); - ui.opts.opts = vals; - inputCellInput.typedInput('value',Date.now()) - } - } - } - } - }, - { - value:"checkbox", - label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, - valueLabel: function(container,value) { - container.css("padding",0); - checkbox = $('').appendTo(container); - checkbox.on('change', function(evt) { - valueField.typedInput('value',$(this).prop('checked')?"true":"false"); - }) - checkbox.prop('checked',valueField.typedInput('value')==="true"); - } - }, - { - value:"spinner", - label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, - valueLabel: function(container,value) { - container.css("padding",0); - var innerContainer = $('
      ').css({ - "background":"white", - "height":"100%", - "box-sizing": "border-box" - }).appendTo(container); + if (l.length > 0 || v.length > 0) { + var val = {l:data.l,v:data.v}; + // if (t !== 'str') { + // val.t = t; + // } + vals.push(val); + } + }); + ui.opts.opts = vals; + inputCellInput.typedInput('value',Date.now()) + } + } + } + } + }, + { + value:"checkbox", + label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, + valueLabel: function(container,value) { + container.css("padding",0); + checkbox = $('').appendTo(container); + checkbox.on('change', function(evt) { + valueField.typedInput('value',$(this).prop('checked')?"true":"false"); + }) + checkbox.prop('checked',valueField.typedInput('value')==="true"); + } + }, + { + value:"spinner", + label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
      ').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); - var input = $('
      ').appendTo(innerContainer); - $('').appendTo(input); + var input = $('
      ').appendTo(innerContainer); + $('').appendTo(input); - var min = ui.opts && ui.opts.min; - var max = ui.opts && ui.opts.max; - var label = ""; - if (min !== undefined && max !== undefined) { - label = Math.min(min,max)+" - "+Math.max(min,max); - } else if (min !== undefined) { - label = "> "+min; - } else if (max !== undefined) { - label = "< "+max; - } - $('').css("margin-left","15px").text(label).appendTo(input); - }, - expand: { - icon: "fa-caret-down", - content: function(container) { - var content = $('
      ').appendTo(container); - content.css("padding","8px 5px") - var min = ui.opts.min; - var max = ui.opts.max; - var minInput = $(''); - minInput.val(min); - var maxInput = $(''); - maxInput.val(max); - $('
      ').append(minInput).appendTo(content); - $('
      ').append(maxInput).appendTo(content); - return { - onclose: function() { - var min = minInput.val().trim(); - var max = maxInput.val().trim(); - if (min !== "") { - ui.opts.min = parseInt(min); - } else { - delete ui.opts.min; - } - if (max !== "") { - ui.opts.max = parseInt(max); - } else { - delete ui.opts.max; - } - inputCellInput.typedInput('value',Date.now()) - } - } - } - } - }, - { - value:"none", - label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false - }, - { - value:"hide", - label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false - } - ], - default: 'none' - }).on("typedinputtypechange", function(evt,type) { - ui.type = $(this).typedInput("type"); - ui.opts = typeOptions[ui.type]; - if (ui.type === 'input') { - // In the case of 'input' type, the typedInput uses the multiple-option - // mode. Its value needs to be set to a comma-separately list of the - // selected options. - inputCellInput.typedInput('value',ui.opts.types.join(",")) - } else { - // No other type cares about `value`, but doing this will - // force a refresh of the label now that `ui.opts` has - // been updated. - inputCellInput.typedInput('value',Date.now()) - } + var min = ui.opts && ui.opts.min; + var max = ui.opts && ui.opts.max; + var label = ""; + if (min !== undefined && max !== undefined) { + label = Math.min(min,max)+" - "+Math.max(min,max); + } else if (min !== undefined) { + label = "> "+min; + } else if (max !== undefined) { + label = "< "+max; + } + $('').css("margin-left","15px").text(label).appendTo(input); + }, + expand: { + icon: "fa-caret-down", + content: function(container) { + var content = $('
      ').appendTo(container); + content.css("padding","8px 5px") + var min = ui.opts.min; + var max = ui.opts.max; + var minInput = $(''); + minInput.val(min); + var maxInput = $(''); + maxInput.val(max); + $('
      ').append(minInput).appendTo(content); + $('
      ').append(maxInput).appendTo(content); + return { + onclose: function() { + var min = minInput.val().trim(); + var max = maxInput.val().trim(); + if (min !== "") { + ui.opts.min = parseInt(min); + } else { + delete ui.opts.min; + } + if (max !== "") { + ui.opts.max = parseInt(max); + } else { + delete ui.opts.max; + } + inputCellInput.typedInput('value',Date.now()) + } + } + } + } + }, + { + value:"none", + label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false + }, + { + value:"hide", + label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false + } + ], + default: 'none' + }).on("typedinputtypechange", function(evt,type) { + ui.type = $(this).typedInput("type"); + ui.opts = typeOptions[ui.type]; + if (ui.type === 'input') { + // In the case of 'input' type, the typedInput uses the multiple-option + // mode. Its value needs to be set to a comma-separately list of the + // selected options. + inputCellInput.typedInput('value',ui.opts.types.join(",")) + } else { + // No other type cares about `value`, but doing this will + // force a refresh of the label now that `ui.opts` has + // been updated. + inputCellInput.typedInput('value',Date.now()) + } - switch (ui.type) { - case 'input': + switch (ui.type) { + case 'input': valueField.typedInput('types',ui.opts.types); break; case 'select': - valueField.typedInput('types',['str']); - break; - case 'checkbox': + valueField.typedInput('types',['str']); + break; + case 'checkbox': valueField.typedInput('types',['bool']); break; case 'spinner': - valueField.typedInput('types',['num']) + valueField.typedInput('types',['num']); + break; + case 'cred': + valueField.typedInput('types',['cred']); break; default: - valueField.typedInput('types',['str','num','bool','json','bin','env']) - } - if (ui.type === 'checkbox') { - valueField.typedInput('type','bool'); - } else if (ui.type === 'spinner') { - valueField.typedInput('type','num'); - } - if (ui.type !== 'checkbox') { - checkbox = null; - } + valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST) + } + if (ui.type === 'checkbox') { + valueField.typedInput('type','bool'); + } else if (ui.type === 'spinner') { + valueField.typedInput('type','num'); + } + if (ui.type !== 'checkbox') { + checkbox = null; + } - }).on("change", function(evt,type) { - if (ui.type === 'input') { - ui.opts.types = inputCellInput.typedInput('value').split(","); - valueField.typedInput('types',ui.opts.types); - } - }); - valueField.on("change", function(evt) { - if (checkbox) { - checkbox.prop('checked',$(this).typedInput('value')==="true") - } - }) - // Set the input to the right type. This will trigger the 'typedinputtypechange' - // event handler (just above ^^) to update the value if needed - inputCellInput.typedInput('type',ui.type) + }).on("change", function(evt,type) { + if (ui.type === 'input') { + ui.opts.types = inputCellInput.typedInput('value').split(","); + valueField.typedInput('types',ui.opts.types); + } + }); + valueField.on("change", function(evt) { + if (checkbox) { + checkbox.prop('checked',$(this).typedInput('value')==="true") + } + }) + // Set the input to the right type. This will trigger the 'typedinputtypechange' + // event handler (just above ^^) to update the value if needed + inputCellInput.typedInput('type',ui.type) } - function buildEnvUIRow(row, tenv, ui) { + function buildEnvUIRow(row, tenv, ui, node) { ui.label = ui.label||{}; - if (!ui.type) { + if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { + ui.type = "cred"; + ui.opts = {}; + } else if (!ui.type) { ui.type = "input"; - ui.opts = {types:['str','num','bool','json','bin','env']} + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} } else { if (!ui.opts) { ui.opts = (ui.type === "select") ? {opts:[]} : {}; @@ -1467,6 +1574,24 @@ RED.subflow = (function() { input.spinner(spinnerOpts).parent().width('70%'); input.val(val.value); break; + case "cred": + input = $('').css('width','70%').appendTo(row); + if (node.credentials) { + if (node.credentials[tenv.name]) { + input.val(node.credentials[tenv.name]); + } else if (node.credentials['has_'+tenv.name]) { + input.val("__PWRD__") + } else { + input.val(""); + } + } else { + input.val(""); + } + input.typedInput({ + types: ['cred'], + default: 'cred' + }) + break; } if (input) { input.attr('id',getSubflowEnvPropertyName(tenv.name)) @@ -1478,7 +1603,7 @@ RED.subflow = (function() { * @param uiContainer - container for UI * @param envList - env var definitions of template */ - function buildEnvUI(uiContainer, envList) { + function buildEnvUI(uiContainer, envList,node) { uiContainer.empty(); var elementID = 0; for (var i = 0; i < envList.length; i++) { @@ -1487,7 +1612,7 @@ RED.subflow = (function() { continue; } var row = $("
      ", { class: "form-row" }).appendTo(uiContainer); - buildEnvUIRow(row,tenv, tenv.ui || {}); + buildEnvUIRow(row,tenv, tenv.ui || {}, node); // console.log(ui); } @@ -1525,7 +1650,7 @@ RED.subflow = (function() { // icon: "", // label: {}, // type: "input", - // opts: {types:['str','num','bool','json','bin','env']} + // opts: {types:DEFAULT_ENV_TYPE_LIST} // } if (!ui.icon) { delete ui.icon; @@ -1535,13 +1660,19 @@ RED.subflow = (function() { } switch (ui.type) { case "input": - if (JSON.stringify(ui.opts) === JSON.stringify({types:['str','num','bool','json','bin','env']})) { + if (JSON.stringify(ui.opts) === JSON.stringify({types:DEFAULT_ENV_TYPE_LIST})) { // This is the default input config. Delete it as it will // be applied automatically delete ui.type; delete ui.opts; } break; + case "cred": + if (envItem.type === "cred") { + delete ui.type; + } + delete ui.opts; + break; case "select": if (ui.opts && $.isEmptyObject(ui.opts.opts)) { // This is the default select config. @@ -1585,7 +1716,7 @@ RED.subflow = (function() { type: env.type, value: env.value }, - ui: env.ui + ui: $.extend(true,{},env.ui) } envList.push(item); parentEnv[env.name] = item; @@ -1621,13 +1752,17 @@ RED.subflow = (function() { var item; var ui = data.ui || {}; if (!ui.type) { - ui.type = "input"; - ui.opts = {types:['str','num','bool','json','bin','env']} + if (data.parent && data.parent.type === "cred") { + ui.type = "cred"; + } else { + ui.type = "input"; + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} + } } else { ui.opts = ui.opts || {}; } var input = $("#"+getSubflowEnvPropertyName(data.name)); - if (input.length) { + if (input.length || ui.type === "cred") { item = { name: data.name }; switch(ui.type) { case "input": @@ -1639,6 +1774,10 @@ RED.subflow = (function() { item.type = 'str'; } break; + case "cred": + item.value = input.val(); + item.type = 'cred'; + break; case "spinner": item.value = input.val(); item.type = 'num'; @@ -1652,7 +1791,7 @@ RED.subflow = (function() { item.value = ""+input.prop("checked"); break; } - if (item.type !== data.parent.type || item.value !== data.parent.value) { + if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { env.push(item); } } @@ -1702,14 +1841,17 @@ RED.subflow = (function() { return defaultLabel; } - function buildEditForm(container,type,node) { + function buildEditForm(type,node) { if (type === "subflow-template") { buildPropertiesList($('#node-input-env-container'), node); } else if (type === "subflow") { - buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node)); + // This gets called by the subflow type `oneditprepare` function + // registered in nodes.js#addSubflow() + buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node); } } - function buildPropertiesForm(container, node) { + function buildPropertiesForm(node) { + var container = $('#editor-subflow-envProperties-content'); var form = $('
      ').appendTo(container); var listContainer = $('
      ').appendTo(form); var list = $('
        ').appendTo(listContainer); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js new file mode 100644 index 000000000..4c469425b --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -0,0 +1,332 @@ +/** + * 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. + **/ +RED.sidebar.help = (function() { + + var content; + var toolbar; + var helpSection; + var panels; + var panelRatio; + var helpTopics = []; + var treeList; + var tocPanel; + var helpIndex = {}; + + + function resizeStack() { + var h = $(content).parent().height() - toolbar.outerHeight(); + panels.resize(h) + } + + function init() { + + content = document.createElement("div"); + content.className = "red-ui-sidebar-info" + + toolbar = $("
        ", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content); + $('').appendTo(toolbar) + var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc') + RED.popover.tooltip(showTOCButton,RED._("sidebar.help.showTopics")); + showTOCButton.on("click",function(e) { + e.preventDefault(); + if ($(this).hasClass('selected')) { + hideTOC(); + } else { + showTOC(); + } + }); + + var stackContainer = $("
        ",{class:"red-ui-sidebar-help-stack"}).appendTo(content); + + tocPanel = $("
        ", {class: "red-ui-sidebar-help-toc"}).appendTo(stackContainer); + var helpPanel = $("
        ").css({ + "overflow-y": "scroll" + }).appendTo(stackContainer); + + panels = RED.panels.create({ + container: stackContainer + }) + panels.ratio(0.3); + + helpSearch = $('').appendTo(toolbar).searchBox({ + delay: 100, + change: function() { + var val = $(this).val().toLowerCase(); + if (val) { + showTOC(); + var c = treeList.treeList('filter',function(item) { + if (item.depth === 0) { + return true; + } + return (item.nodeType && item.nodeType.indexOf(val) > -1) || + (item.subflowLabel && item.subflowLabel.indexOf(val) > -1) + },true) + } else { + treeList.treeList('filter',null); + var selected = treeList.treeList('selected'); + if (selected.id) { + treeList.treeList('show',selected.id); + } + + } + } + }) + + helpSection = $("
        ",{class:"red-ui-help"}).css({ + "padding":"6px", + }).appendTo(helpPanel) + + $(''+RED._("sidebar.help.noHelp")+'').appendTo(helpSection); + + treeList = $("
        ").css({width: "100%"}).appendTo(tocPanel).treeList({data: []}) + treeList.on('treelistselect', function(e,item) { + if (item.nodeType) { + showHelp(item.nodeType); + } + }) + + RED.sidebar.addTab({ + id: "help", + label: RED._("sidebar.help.label"), + name: RED._("sidebar.help.name"), + iconClass: "fa fa-book", + action:"core:show-help-tab", + content: content, + pinned: true, + enableOnEdit: true, + onchange: function() { + resizeStack() + } + }); + + $(window).on("resize", resizeStack); + $(window).on("focus", resizeStack); + + RED.events.on('registry:node-type-added', queueRefresh); + RED.events.on('registry:node-type-removed', queueRefresh); + RED.events.on('subflows:change', refreshSubflow); + + RED.actions.add("core:show-help-tab",show); + + } + + var refreshTimer; + function queueRefresh() { + if (!refreshTimer) { + refreshTimer = setTimeout(function() { + refreshTimer = null; + refreshHelpIndex(); + },500); + } + } + + function refreshSubflow(sf) { + var item = treeList.treeList('get',"node-type:subflow:"+sf.id); + item.subflowLabel = sf._def.label().toLowerCase(); + item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); + } + + function hideTOC() { + var tocButton = $('#red-ui-sidebar-help-show-toc') + if (tocButton.hasClass('selected')) { + tocButton.removeClass('selected'); + panelRatio = panels.ratio(); + tocPanel.css({"transition":"height 0.2s"}) + panels.ratio(0) + setTimeout(function() { + tocPanel.css({"transition":""}) + },250); + } + } + function showTOC() { + var tocButton = $('#red-ui-sidebar-help-show-toc') + if (!tocButton.hasClass('selected')) { + tocButton.addClass('selected'); + tocPanel.css({"transition":"height 0.2s"}) + panels.ratio(Math.max(0.3,Math.min(panelRatio,0.7))); + setTimeout(function() { + tocPanel.css({"transition":""}) + var selected = treeList.treeList('selected'); + if (selected.id) { + treeList.treeList('show',selected); + } + },250); + } + } + + function refreshHelpIndex() { + helpTopics = []; + var modules = RED.nodes.registry.getModuleList(); + var moduleNames = Object.keys(modules); + moduleNames.sort(); + + var helpData = [{ + label: RED._("sidebar.help.nodeHelp"), + children: [], + expanded: true + }] + + var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)}); + if (subflows.length > 0) { + helpData[0].children.push({ + label: RED._("menu.label.subflows"), + children: [] + }) + subflows.forEach(function(nodeType) { + var sf = RED.nodes.getType(nodeType); + helpData[0].children[0].children.push({ + id:"node-type:"+nodeType, + nodeType: nodeType, + subflowLabel: sf.label().toLowerCase(), + element: getNodeLabel({_def:sf,type:sf.label()}) + }) + }) + } + + + moduleNames.forEach(function(moduleName) { + var module = modules[moduleName]; + var nodeTypes = []; + + var setNames = Object.keys(module.sets); + setNames.forEach(function(setName) { + module.sets[setName].types.forEach(function(nodeType) { + if ($("script[data-help-name='"+nodeType+"']").length) { + nodeTypes.push({ + id: "node-type:"+nodeType, + nodeType: nodeType, + element:getNodeLabel({_def:RED.nodes.getType(nodeType),type:nodeType}) + }) + } + }) + }) + if (nodeTypes.length > 0) { + nodeTypes.sort(function(A,B) { + return A.nodeType.localeCompare(B.nodeType) + }) + helpData[0].children.push({ + id: moduleName, + icon: "fa fa-cube", + label: moduleName, + children: nodeTypes + }) + } + }); + treeList.treeList("data",helpData); + } + + function getNodeLabel(n) { + var div = $('
        ',{class:"red-ui-info-outline-item"}); + RED.utils.createNodeIcon(n).appendTo(div); + var contentDiv = $('
        ',{class:"red-ui-search-result-description"}).appendTo(div); + $('
        ',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).text(n.name||n.type).appendTo(contentDiv); + return div; + } + + function showHelp(nodeType) { + helpSection.empty(); + var helpText; + var title; + var m = /^subflow(:(.+))?$/.exec(nodeType); + if (m && m[2]) { + var subflowNode = RED.nodes.subflow(m[2]); + helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); + title = subflowNode.name || nodeType; + } else { + helpText = $("script[data-help-name='"+nodeType+"']").html()||(''+RED._("sidebar.info.none")+''); + title = nodeType; + } + setInfoText(title, helpText, helpSection); + + var ratio = panels.ratio(); + if (ratio > 0.7) { + panels.ratio(0.7) + } + treeList.treeList("show","node-type:"+nodeType) + treeList.treeList("select","node-type:"+nodeType, false); + + } + + function show(type) { + RED.sidebar.show("help"); + if (type) { + hideTOC(); + showHelp(type); + } + } + + // TODO: DRY - projects.js + function addTargetToExternalLinks(el) { + $(el).find("a").each(function(el) { + var href = $(this).attr('href'); + if (/^https?:/.test(href)) { + $(this).attr('target','_blank'); + } + }); + return el; + } + + function setInfoText(title, infoText,target) { + if (title) { + $("

        ",{class:"red-ui-help-title"}).text(title).appendTo(target); + } + var info = addTargetToExternalLinks($('
        '+infoText+'
        ')).appendTo(target); + info.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "" ); + var foldingHeader = "H3"; + info.find(foldingHeader).wrapInner('') + .find("a").prepend('').on("click", function(e) { + e.preventDefault(); + var isExpanded = $(this).hasClass('expanded'); + var el = $(this).parent().next(); + while(el.length === 1 && el[0].nodeName !== foldingHeader) { + el.toggle(!isExpanded); + el = el.next(); + } + $(this).toggleClass('expanded',!isExpanded); + }) + target.parent().scrollTop(0); + } + + function set(html,title) { + $(helpSection).empty(); + setInfoText(title,html,helpSection); + hideTOC(); + show(); + } + + function refreshSelection(selection) { + if (selection === undefined) { + selection = RED.view.selection(); + } + if (selection.nodes) { + if (selection.nodes.length == 1) { + var node = selection.nodes[0]; + if (node.type === "subflow" && node.direction) { + // ignore subflow virtual ports + } else if (node.type !== 'group'){ + showHelp(node.type); + } + } + } + } + RED.events.on("view:selection-changed",refreshSelection); + + return { + init: init, + show: show, + set: set + } +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js new file mode 100644 index 000000000..ccecfd043 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js @@ -0,0 +1,475 @@ +RED.sidebar.info.outliner = (function() { + + var treeList; + var searchInput; + var projectInfo; + var projectInfoLabel; + var flowList; + var subflowList; + var globalConfigNodes; + + var objects = {}; + var missingParents = {}; + + function getFlowData() { + var flowData = [ + { + label: RED._("menu.label.flows"), + expanded: true, + children: [] + }, + { + label: RED._("menu.label.subflows"), + children: [] + }, + { + id: "__global__", + label: RED._("sidebar.info.globalConfig"), + children: [] + } + ] + flowList = flowData[0]; + subflowList = flowData[1]; + globalConfigNodes = flowData[2]; + + return flowData; + } + + function getProjectLabel(p) { + var div = $('
        ',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); + div.css("width", "calc(100% - 40px)"); + var contentDiv = $('
        ',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); + contentDiv.text(p.name); + var controls = $('
        ',{class:"red-ui-info-outline-item-controls"}).appendTo(div); + var editProjectButton = $('') + .appendTo(controls) + .on("click", function(evt) { + evt.preventDefault(); + RED.projects.editProject(); + }); + RED.popover.tooltip(editProjectButton,RED._('sidebar.project.showProjectSettings')); + return div; + } + + var empties = {}; + function getEmptyItem(id) { + var item = { + empty: true, + element: $('
        ').text(RED._("sidebar.info.empty")), + } + empties[id] = item; + return item; + } + + function getNodeLabelText(n) { + var label = n.name || n.type+": "+n.id; + if (n._def.label) { + try { + label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||""; + } catch(err) { + console.log("Definition error: "+type+".label",err); + } + } + var newlineIndex = label.indexOf("\\n"); + if (newlineIndex > -1) { + label = label.substring(0,newlineIndex)+"..."; + } + return label; + } + + function getNodeLabel(n) { + var div = $('
        ',{class:"red-ui-info-outline-item"}); + RED.utils.createNodeIcon(n).appendTo(div); + var contentDiv = $('
        ',{class:"red-ui-search-result-description"}).appendTo(div); + var labelText = getNodeLabelText(n); + var label = $('
        ',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv); + if (labelText) { + label.text(labelText) + } else { + label.html(" ") + } + + addControls(n, div); + + return div; + } + + function getFlowLabel(n) { + var div = $('
        ',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); + var contentDiv = $('
        ',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); + var label = (typeof n === "string")? n : n.label; + var newlineIndex = label.indexOf("\\n"); + if (newlineIndex > -1) { + label = label.substring(0,newlineIndex)+"..."; + } + contentDiv.text(label); + addControls(n, div); + return div; + } + + function getSubflowLabel(n) { + + var div = $('
        ',{class:"red-ui-info-outline-item"}); + RED.utils.createNodeIcon(n).appendTo(div); + var contentDiv = $('
        ',{class:"red-ui-search-result-description"}).appendTo(div); + var labelText = getNodeLabelText(n); + var label = $('
        ',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv); + if (labelText) { + label.text(labelText) + } else { + label.html(" ") + } + + addControls(n, div); + + return div; + + + // var div = $('
        ',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); + // var contentDiv = $('
        ',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); + // contentDiv.text(n.name || n.id); + // addControls(n, div); + // return div; + } + + function addControls(n,div) { + var controls = $('
        ',{class:"red-ui-info-outline-item-controls red-ui-info-outline-item-hover-controls"}).appendTo(div); + if (n._def.button) { + $('').appendTo(controls).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.view.clickNodeButton(n); + }) + } + // $('').appendTo(controls).on("click",function(evt) { + // evt.preventDefault(); + // evt.stopPropagation(); + // RED.view.reveal(n.id); + // }) + if (n.type !== 'group' && n.type !== 'subflow') { + $('').appendTo(controls).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (n.type === 'tab') { + if (n.disabled) { + RED.workspaces.enable(n.id) + } else { + RED.workspaces.disable(n.id) + } + } else { + // TODO: this ought to be a utility function in RED.nodes + var historyEvent = { + t: "edit", + node: n, + changed: n.changed, + changes: { + d: n.d + }, + dirty:RED.nodes.dirty() + } + if (n.d) { + delete n.d; + } else { + n.d = true; + } + n.dirty = true; + n.changed = true; + RED.events.emit("nodes:change",n); + RED.nodes.dirty(true) + RED.view.redraw(); + } + }); + } else { + $('
        ').appendTo(controls) + } + controls.find("button").on("dblclick", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + }) + } + + function onProjectLoad(activeProject) { + objects = {}; + var newFlowData = getFlowData(); + projectInfoLabel.empty(); + getProjectLabel(activeProject).appendTo(projectInfoLabel); + projectInfo.show(); + treeList.treeList('data',newFlowData); + } + + function build() { + var container = $("
        ", {class:"red-ui-info-outline"}).css({'height': '100%'}); + var toolbar = $("
        ", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(container); + + searchInput = $('').appendTo(toolbar).searchBox({ + delay: 300, + change: function() { + var val = $(this).val(); + var searchResults = RED.search.search(val); + if (val) { + var resultMap = {}; + for (var i=0,l=searchResults.length;i
        ').hide().appendTo(container) + projectInfoLabel = $('').appendTo(projectInfo); + + //
        Space Monkey
        ').appendTo(container) + + treeList = $("
        ").css({width: "100%"}).appendTo(container).treeList({ + data:getFlowData() + }) + treeList.on('treelistselect', function(e,item) { + var node = RED.nodes.node(item.id) || RED.nodes.group(item.id); + if (node) { + if (node.type === 'group' || node._def.category !== "config") { + RED.view.select({nodes:[node]}) + } else { + RED.view.select({nodes:[]}) + } + } + }) + treeList.on('treelistconfirm', function(e,item) { + var node = RED.nodes.node(item.id); + if (node) { + if (node._def.category === "config") { + RED.editor.editConfig("", node.type, node.id); + } else { + RED.editor.edit(node); + } + } + }) + + RED.events.on("projects:load", onProjectLoad) + + RED.events.on("flows:add", onFlowAdd) + RED.events.on("flows:remove", onObjectRemove) + RED.events.on("flows:change", onFlowChange) + RED.events.on("flows:reorder", onFlowsReorder) + + RED.events.on("subflows:add", onSubflowAdd) + RED.events.on("subflows:remove", onObjectRemove) + RED.events.on("subflows:change", onSubflowChange) + + RED.events.on("nodes:add",onNodeAdd); + RED.events.on("nodes:remove",onObjectRemove); + RED.events.on("nodes:change",onNodeChange); + + RED.events.on("groups:add",onNodeAdd); + RED.events.on("groups:remove",onObjectRemove); + RED.events.on("groups:change",onNodeChange); + + RED.events.on("view:selection-changed", onSelectionChanged); + + RED.events.on("workspace:clear", onWorkspaceClear) + + return container; + } + function onWorkspaceClear() { + treeList.treeList('data',getFlowData()); + } + function onFlowAdd(ws) { + objects[ws.id] = { + id: ws.id, + element: getFlowLabel(ws), + children:[], + deferBuild: true, + icon: "red-ui-icons red-ui-icons-flow", + gutter: getGutter(ws) + } + if (missingParents[ws.id]) { + objects[ws.id].children = missingParents[ws.id]; + delete missingParents[ws.id] + } else { + objects[ws.id].children.push(getEmptyItem(ws.id)); + } + flowList.treeList.addChild(objects[ws.id]) + objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) + objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) + + + } + function onFlowChange(n) { + var existingObject = objects[n.id]; + + var label = n.label || n.id; + var newlineIndex = label.indexOf("\\n"); + if (newlineIndex > -1) { + label = label.substring(0,newlineIndex)+"..."; + } + existingObject.element.find(".red-ui-info-outline-item-label").text(label); + existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) + existingObject.treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) + } + function onFlowsReorder(order) { + var indexMap = {}; + order.forEach(function(id,index) { + indexMap[id] = index; + }) + + flowList.treeList.sortChildren(function(A,B) { + if (A.id === "__global__") { return -1 } + if (B.id === "__global__") { return 1 } + return indexMap[A.id] - indexMap[B.id] + }) + } + function onSubflowAdd(sf) { + objects[sf.id] = { + id: sf.id, + element: getNodeLabel(sf), + children:[], + deferBuild: true, + gutter: getGutter(sf) + } + if (missingParents[sf.id]) { + objects[sf.id].children = missingParents[sf.id]; + delete missingParents[sf.id] + } else { + objects[sf.id].children.push(getEmptyItem(sf.id)); + } + subflowList.treeList.addChild(objects[sf.id]) + } + function onSubflowChange(sf) { + var existingObject = objects[sf.id]; + existingObject.treeList.replaceElement(getNodeLabel(sf)); + // existingObject.element.find(".red-ui-info-outline-item-label").text(n.name || n.id); + RED.nodes.eachNode(function(n) { + if (n.type == "subflow:"+sf.id) { + var sfInstance = objects[n.id]; + sfInstance.treeList.replaceElement(getNodeLabel(n)); + } + }); + } + + function onNodeChange(n) { + var existingObject = objects[n.id]; + var parent = n.g||n.z; + + var nodeLabelText = getNodeLabelText(n); + if (nodeLabelText) { + existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText); + } else { + existingObject.element.find(".red-ui-info-outline-item-label").html(" "); + } + + if (parent !== existingObject.parent.id) { + existingObject.treeList.remove(); + if (!parent) { + globalConfigNodes.treeList.addChild(existingObject); + } else { + if (empties[parent]) { + empties[parent].treeList.remove(); + delete empties[parent]; + } + objects[parent].treeList.addChild(existingObject) + } + } + existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) + } + function onObjectRemove(n) { + var existingObject = objects[n.id]; + existingObject.treeList.remove(); + delete objects[n.id] + var parent = existingObject.parent; + if (parent.children.length === 0) { + parent.treeList.addChild(getEmptyItem(parent.id)); + } + if (existingObject.children && (existingObject.children.length > 0)) { + existingObject.children.forEach(function (nc) { + if (!nc.empty) { + var childObject = objects[nc.id]; + nc.parent = parent; + objects[parent.id].treeList.addChild(childObject); + } + }); + } + } + function getGutter(n) { + var span = $("",{class:"red-ui-info-outline-gutter"}); + $('').appendTo(span).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.view.reveal(n.id); + }) + return span; + } + function onNodeAdd(n) { + objects[n.id] = { + id: n.id, + element: getNodeLabel(n), + gutter: getGutter(n) + } + if (n.type === "group") { + objects[n.id].children = []; + objects[n.id].deferBuild = true; + if (missingParents[n.id]) { + objects[n.id].children = missingParents[n.id]; + delete missingParents[n.id] + } + } + var parent = n.g||n.z; + if (parent) { + if (objects[parent]) { + if (empties[parent]) { + empties[parent].treeList.remove(); + delete empties[parent]; + } + if (objects[parent].treeList) { + objects[parent].treeList.addChild(objects[n.id]); + } else { + objects[parent].children.push(objects[n.id]) + } + } else { + missingParents[parent] = missingParents[parent]||[]; + missingParents[parent].push(objects[n.id]) + } + } else { + // No parent - add to Global flow list + globalConfigNodes.treeList.addChild(objects[n.id]) + } + objects[n.id].element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) + } + + function onSelectionChanged(selection) { + // treeList.treeList('clearSelection'); + } + + return { + build: build, + search: function(val) { + searchInput.searchBox('value',val) + }, + select: function(node) { + if (node) { + if (Array.isArray(node)) { + treeList.treeList('select', node.map(function(n) { return objects[n.id] }), false) + } else { + treeList.treeList('select', objects[node.id], false) + + } + } else { + treeList.treeList('clearSelection') + } + }, + reveal: function(node) { + treeList.treeList('show', objects[node.id]) + } + } +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index bfa4e4b23..418939a72 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -16,16 +16,32 @@ RED.sidebar.info = (function() { var content; - var sections; - var propertiesSection; + var panels; var infoSection; - var helpSection; + + var propertiesPanelContent; + var propertiesPanelHeader; + var propertiesPanelHeaderIcon; + var propertiesPanelHeaderLabel; + var propertiesPanelHeaderReveal; + var propertiesPanelHeaderHelp; + + var selectedObject; + + var tipContainer; var tipBox; + // TODO: remove this var expandedSections = { "property": false }; + function resizeStack() { + if (panels) { + var h = $(content).parent().height() - tipContainer.outerHeight(); + panels.resize(h) + } + } function init() { content = document.createElement("div"); @@ -35,31 +51,79 @@ RED.sidebar.info = (function() { var stackContainer = $("
        ",{class:"red-ui-sidebar-info-stack"}).appendTo(content); - sections = RED.stack.create({ - container: stackContainer - }).hide(); + var outlinerPanel = $("
        ").css({ + "overflow": "hidden", + "height": "calc(70%)" + }).appendTo(stackContainer); + var propertiesPanel = $("
        ").css({ + "overflow":"hidden", + "height":"100%", + "display": "flex", + "flex-direction": "column" + }).appendTo(stackContainer); + propertiesPanelHeader = $("
        ", {class:"red-ui-palette-header red-ui-info-header"}).css({ + "flex":"0 0 auto" + }).appendTo(propertiesPanel); - propertiesSection = sections.add({ - title: RED._("sidebar.info.info"), - collapsible: true + propertiesPanelHeaderIcon = $("").appendTo(propertiesPanelHeader); + propertiesPanelHeaderLabel = $("").appendTo(propertiesPanelHeader); + propertiesPanelHeaderHelp = $('').css({ + position: 'absolute', + top: '12px', + right: '32px' + }).on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (selectedObject) { + RED.sidebar.help.show(selectedObject.type); + } + }).appendTo(propertiesPanelHeader); + RED.popover.tooltip(propertiesPanelHeaderHelp,RED._("sidebar.help.showHelp")); + + + propertiesPanelHeaderReveal = $('').css({ + position: 'absolute', + top: '12px', + right: '8px' + }).on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (selectedObject) { + RED.sidebar.info.outliner.reveal(selectedObject); + RED.view.reveal(selectedObject.id); + } + }).appendTo(propertiesPanelHeader); + RED.popover.tooltip(propertiesPanelHeaderReveal,RED._("sidebar.help.showInOutline")); + + + propertiesPanelContent = $("
        ").css({ + "flex":"1 1 auto", + "overflow-y":"scroll", + }).appendTo(propertiesPanel); + + + panels = RED.panels.create({container: stackContainer}) + panels.ratio(0.6); + RED.sidebar.info.outliner.build().appendTo(outlinerPanel); + + + RED.sidebar.addTab({ + id: "info", + label: RED._("sidebar.info.label"), + name: RED._("sidebar.info.name"), + iconClass: "fa fa-info", + action:"core:show-info-tab", + content: content, + pinned: true, + enableOnEdit: true }); - propertiesSection.expand(); - infoSection = sections.add({ - title: RED._("sidebar.info.desc"), - collapsible: true - }); - infoSection.expand(); - infoSection.content.css("padding","6px"); + $(window).on("resize", resizeStack); + $(window).on("focus", resizeStack); - helpSection = sections.add({ - title: RED._("sidebar.info.nodeHelp"), - collapsible: true - }); - helpSection.expand(); - helpSection.content.css("padding","6px"); - var tipContainer = $('
        ').appendTo(content); + // Tip Box + tipContainer = $('
        ').appendTo(content); tipBox = $('
        ').appendTo(tipContainer); var tipButtons = $('
        ').appendTo(tipContainer); @@ -75,17 +139,6 @@ RED.sidebar.info = (function() { RED.actions.invoke("core:toggle-show-tips"); RED.notify(RED._("sidebar.info.showTips")); }); - - RED.sidebar.addTab({ - id: "info", - label: RED._("sidebar.info.label"), - name: RED._("sidebar.info.name"), - iconClass: "fa fa-info", - action:"core:show-info-tab", - content: content, - pinned: true, - enableOnEdit: true - }); if (tips.enabled()) { tips.start(); } else { @@ -113,46 +166,36 @@ RED.sidebar.info = (function() { refreshSelection(); return; } - sections.show(); - $(propertiesSection.content).empty(); - $(infoSection.content).empty(); - $(helpSection.content).empty(); - infoSection.title.text(RED._("sidebar.info.desc")); + $(propertiesPanelContent).empty(); var propRow; - var table = $('
        ').appendTo(propertiesSection.content); + var table = $('
        ').appendTo(propertiesPanelContent); var tableBody = $('').appendTo(table); var subflowNode; var subflowUserCount; - var activeProject = RED.projects.getActiveProject(); - if (activeProject) { - propRow = $(''+ RED._("sidebar.project.name") + '').appendTo(tableBody); - $(propRow.children()[1]).text(activeProject.name||""); - $('').appendTo(tableBody); - var editProjectButton = $('') - .appendTo(propRow.children()[1]) - .on("click", function(evt) { - evt.preventDefault(); - RED.projects.editProject(); - }); - RED.popover.tooltip(editProjectButton,RED._('sidebar.project.showProjectSettings')); - } - propertiesSection.container.show(); - infoSection.container.show(); - helpSection.container.show(); if (node === null) { + RED.sidebar.info.outliner.select(null); return; } else if (Array.isArray(node)) { // Multiple things selected // - hide help and info sections + RED.sidebar.info.outliner.select(node); + + propertiesPanelHeaderIcon.empty(); + RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon); + propertiesPanelHeaderLabel.text("Selection"); + propertiesPanelHeaderReveal.hide(); + propertiesPanelHeaderHelp.hide(); + selectedObject = null; var types = { nodes:0, flows:0, - subflows:0 + subflows:0, + groups: 0 } node.forEach(function(n) { if (n.type === 'tab') { @@ -160,12 +203,13 @@ RED.sidebar.info = (function() { types.nodes += RED.nodes.filterNodes({z:n.id}).length; } else if (n.type === 'subflow') { types.subflows++; + } else if (n.type === 'group') { + types.groups++; } else { types.nodes++; } }); - helpSection.container.hide(); - infoSection.container.hide(); + // infoSection.container.hide(); // - show the count of selected nodes propRow = $(''+RED._("sidebar.info.selection")+"").appendTo(tableBody); @@ -179,14 +223,19 @@ RED.sidebar.info = (function() { if (types.nodes > 0) { $('
        ').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts); } + if (types.groups > 0) { + $('
        ').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts); + } } else { // A single 'thing' selected. + RED.sidebar.info.outliner.select(node); + // Check to see if this is a subflow or subflow instance - var m = /^subflow(:(.+))?$/.exec(node.type); - if (m) { - if (m[2]) { - subflowNode = RED.nodes.subflow(m[2]); + var subflowRegex = /^subflow(:(.+))?$/.exec(node.type); + if (subflowRegex) { + if (subflowRegex[2]) { + subflowNode = RED.nodes.subflow(subflowRegex[2]); } else { subflowNode = node; } @@ -199,33 +248,76 @@ RED.sidebar.info = (function() { } }); } + + propertiesPanelHeaderIcon.empty(); + RED.utils.createNodeIcon(node).appendTo(propertiesPanelHeaderIcon); + var objectLabel = RED.utils.getNodeLabel(node, node.type+": "+node.id) + var newlineIndex = objectLabel.indexOf("\\n"); + if (newlineIndex > -1) { + objectLabel = objectLabel.substring(0,newlineIndex)+"..."; + } + propertiesPanelHeaderLabel.text(objectLabel); + propertiesPanelHeaderReveal.show(); + selectedObject = node; + + propRow = $('').appendTo(tableBody); + var objectType = "node"; + if (node.type === "subflow" || subflowRegex) { + objectType = "subflow"; + } else if (node.type === "tab") { + objectType = "flow"; + }else if (node.type === "group") { + objectType = "group"; + } + $(propRow.children()[0]).text(RED._("sidebar.info."+objectType)) + RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); + if (node.type === "tab" || node.type === "subflow") { // If nothing is selected, but we're on a flow or subflow tab. - propRow = $(''+RED._("sidebar.info."+(node.type==='tab'?'flow':'subflow'))+'').appendTo(tableBody); - RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); - propRow = $(''+RED._("sidebar.info.tabName")+"").appendTo(tableBody); - $(propRow.children()[1]).text(node.label||node.name||""); - if (node.type === "tab") { - propRow = $(''+RED._("sidebar.info.status")+'').appendTo(tableBody); - $(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) + propertiesPanelHeaderHelp.hide(); + + } else if (node.type === "group") { + propertiesPanelHeaderHelp.hide(); + + propRow = $(' ').appendTo(tableBody); + + var typeCounts = { + nodes:0, + groups: 0 } + var allNodes = RED.group.getNodes(node,true); + allNodes.forEach(function(n) { + if (n.type === "group") { + typeCounts.groups++; + } else { + typeCounts.nodes++ + } + }); + var counts = $('
        ').appendTo($(propRow.children()[1])); + if (typeCounts.nodes > 0) { + $('
        ').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts); + } + if (typeCounts.groups > 0) { + $('
        ').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts); + } + + } else { - // An actual node is selected in the editor - build up its properties table - propRow = $(''+RED._("sidebar.info.node")+"").appendTo(tableBody); - RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); - if (node.type !== "subflow" && node.type !== "unknown" && node.name) { - propRow = $(''+RED._("common.label.name")+'').appendTo(tableBody); - $('').text(node.name).appendTo(propRow.children()[1]); - } - if (!m) { - propRow = $(''+RED._("sidebar.info.type")+"").appendTo(tableBody); + propertiesPanelHeaderHelp.show(); + + if (!subflowRegex) { + propRow = $(''+RED._("sidebar.info.type")+'').appendTo(tableBody); $(propRow.children()[1]).text((node.type === "unknown")?node._orig.type:node.type); if (node.type === "unknown") { $('').prependTo($(propRow.children()[1])) } } + var count = 0; - if (!m && node.type != "subflow") { + if (!subflowRegex && node.type != "subflow" && node.type != "group") { + + var blankRow = $('').appendTo(tableBody); + var defaults; if (node.type === 'unknown') { defaults = {}; @@ -240,7 +332,6 @@ RED.sidebar.info = (function() { $(propRow.children()[1]).text(RED.nodes.getType(node.type).set.module); count++; } - $('').appendTo(tableBody); if (defaults) { for (var n in defaults) { @@ -248,7 +339,8 @@ RED.sidebar.info = (function() { var val = node[n]; var type = typeof val; count++; - propRow = $(''+n+"").appendTo(tableBody); + propRow = $('').appendTo(tableBody); + $(propRow.children()[0]).text(n); if (defaults[n].type) { var configNode = RED.nodes.node(val); if (!configNode) { @@ -278,37 +370,35 @@ RED.sidebar.info = (function() { } } if (count > 0) { - $(''+RED._("sidebar.info.showMore")+''+RED._("sidebar.info.showLess")+' ').appendTo(tableBody); + $(''+RED._("sidebar.info.showMore")+''+RED._("sidebar.info.showLess")+' ').appendTo(blankRow.children()[0]); } } if (node.type !== 'tab') { - if (m) { + if (subflowRegex) { $(''+RED._("sidebar.info.subflow")+'').appendTo(tableBody); $(''+RED._("common.label.name")+''+RED.utils.sanitize(subflowNode.name)+'').appendTo(tableBody); } } } - if (m) { + if (subflowRegex) { propRow = $(''+RED._("subflow.category")+'').appendTo(tableBody); var category = subflowNode.category||"subflows"; $(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category})) $(''+RED._("sidebar.info.instances")+""+subflowUserCount+'').appendTo(tableBody); } - var helpText = ""; - if (node.type === "tab" || node.type === "subflow") { - $(helpSection.container).hide(); - } else { - $(helpSection.container).show(); - if (subflowNode && node.type !== "subflow") { - // Selected a subflow instance node. - // - The subflow template info goes into help - helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); - } else { - helpText = $("script[data-help-name='"+node.type+"']").html()||(''+RED._("sidebar.info.none")+''); - } - setInfoText(helpText, helpSection.content); - } + // var helpText = ""; + // if (node.type === "tab" || node.type === "subflow") { + // } else { + // if (subflowNode && node.type !== "subflow") { + // // Selected a subflow instance node. + // // - The subflow template info goes into help + // helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); + // } else { + // helpText = $("script[data-help-name='"+node.type+"']").html()||(''+RED._("sidebar.info.none")+''); + // } + // setInfoText(helpText, helpSection.content); + // } var infoText = ""; @@ -320,7 +410,26 @@ RED.sidebar.info = (function() { if (node.info) { infoText = infoText + RED.utils.renderMarkdown(node.info || "") } - setInfoText(infoText, infoSection.content); + var infoSectionContainer = $("
        ").css("padding","0 6px 6px").appendTo(propertiesPanelContent) + + // var editInfo = $('').appendTo(infoSectionContainer).on("click", function(evt) { + // //.text(RED._("sidebar.info.editDescription")) + // evt.preventDefault(); + // evt.stopPropagation(); + // if (node.type === 'tab') { + // + // } else if (node.type === 'subflow') { + // + // } else if (node.type === 'group') { + // + // } else if (node._def.category !== 'config') { + // RED.editor.edit(node,"editor-tab-description"); + // } else { + // + // } + // }) + + setInfoText(infoText, infoSectionContainer); $(".red-ui-sidebar-info-stack").scrollTop(0); $(".node-info-property-header").on("click", function(e) { @@ -336,7 +445,7 @@ RED.sidebar.info = (function() { // propRow = $('Actions').appendTo(tableBody); // var actionBar = $(propRow.children()[1]); // - // // var actionBar = $('
        ',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesSection.content); + // // var actionBar = $('
        ',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesPanel); // $('').appendTo(actionBar); // $('').appendTo(actionBar); // $('').appendTo(actionBar); @@ -411,6 +520,7 @@ RED.sidebar.info = (function() { } function startTips() { $(".red-ui-sidebar-info").addClass('show-tips'); + resizeStack(); if (enabled) { if (!startTimeout && !refreshTimeout) { if (tipCount === -1) { @@ -424,6 +534,7 @@ RED.sidebar.info = (function() { } function stopTips() { $(".red-ui-sidebar-info").removeClass('show-tips'); + resizeStack(); clearInterval(refreshTimeout); clearTimeout(startTimeout); refreshTimeout = null; @@ -448,15 +559,8 @@ RED.sidebar.info = (function() { } function set(html,title) { - // tips.stop(); - // sections.show(); - refresh(null); - propertiesSection.container.hide(); - helpSection.container.hide(); - infoSection.container.show(); - infoSection.title.text(title||RED._("sidebar.info.desc")); - setInfoText(html,infoSection.content); - $(".red-ui-sidebar-info-stack").scrollTop(0); + console.warn("Deprecated use of RED.sidebar.info.set - use RED.sidebar.help.set instead") + RED.sidebar.help.set(html,title); } function refreshSelection(selection) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index a9c1c9500..e8306bd0f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -806,9 +806,9 @@ RED.utils = (function() { function separateIconPath(icon) { var result = {module: "", file: ""}; if (icon) { - var index = icon.indexOf('icons/'); - if (index !== -1) { - icon = icon.substring(index+6); + var index = icon.indexOf(RED.settings.apiRootUrl+'icons/'); + if (index === 0) { + icon = icon.substring((RED.settings.apiRootUrl+'icons/').length); } index = icon.indexOf('/'); if (index !== -1) { @@ -859,10 +859,15 @@ RED.utils = (function() { } function getNodeIcon(def,node) { - if (def.category === 'config') { + if (node && node.type === '_selection_') { + return "font-awesome/fa-object-ungroup"; + } else if (node && node.type === 'group') { + return "font-awesome/fa-object-group" + } else if (def.category === 'config') { return RED.settings.apiRootUrl+"icons/node-red/cog.svg" } else if (node && node.type === 'tab') { - return RED.settings.apiRootUrl+"icons/node-red/subflow.svg" + return "red-ui-icons/red-ui-icons-flow" + // return RED.settings.apiRootUrl+"images/subflow_tab.svg" } else if (node && node.type === 'unknown') { return RED.settings.apiRootUrl+"icons/node-red/alert.svg" } else if (node && node.icon) { @@ -921,6 +926,8 @@ RED.utils = (function() { var l; if (node.type === 'tab') { l = node.label || defaultLabel + } else if (node.type === 'group') { + l = node.name || defaultLabel } else { l = node._def.label; try { @@ -1054,11 +1061,63 @@ RED.utils = (function() { } // If the specified name is not defined in font-awesome, show arrow-in icon. iconUrl = RED.settings.apiRootUrl+"icons/node-red/arrow-in.svg" + } else if (iconPath.module === "red-ui-icons") { + var redIconElement = $('').appendTo(iconContainer); + redIconElement.addClass("red-ui-palette-icon red-ui-icons " + iconPath.file); + return; } var imageIconElement = $('
        ',{class:"red-ui-palette-icon"}).appendTo(iconContainer); imageIconElement.css("backgroundImage", "url("+iconUrl+")"); } + function createNodeIcon(node) { + var def = node._def; + var nodeDiv = $('
        ',{class:"red-ui-search-result-node"}) + if (node.type === "_selection_") { + nodeDiv.addClass("red-ui-palette-icon-selection"); + } else if (node.type === "group") { + nodeDiv.addClass("red-ui-palette-icon-group"); + } else if (node.type === 'tab') { + nodeDiv.addClass("red-ui-palette-icon-flow"); + } else { + var colour = RED.utils.getNodeColor(node.type,def); + // if (node.type === 'tab') { + // colour = "#C0DEED"; + // } + nodeDiv.css('backgroundColor',colour); + var borderColor = getDarkerColor(colour); + if (borderColor !== colour) { + nodeDiv.css('border-color',borderColor) + } + } + + var icon_url = RED.utils.getNodeIcon(def,node); + var iconContainer = $('
        ',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); + RED.utils.createIconElement(icon_url, iconContainer, true); + return nodeDiv; + } + + function getDarkerColor(c) { + var r,g,b; + if (/^#[a-f0-9]{6}$/i.test(c)) { + r = parseInt(c.substring(1, 3), 16); + g = parseInt(c.substring(3, 5), 16); + b = parseInt(c.substring(5, 7), 16); + } else if (/^#[a-f0-9]{3}$/i.test(c)) { + r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16); + g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16); + b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16); + } else { + return c; + } + var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ; + r = Math.max(0,r-50); + g = Math.max(0,g-50); + b = Math.max(0,b-50); + var s = ((r<<16) + (g<<8) + b).toString(16); + return '#'+'000000'.slice(0, 6-s.length)+s; + } + return { createObjectElement: buildMessageElement, getMessageProperty: getMessageProperty, @@ -1076,6 +1135,8 @@ RED.utils = (function() { parseContextKey: parseContextKey, createIconElement: createIconElement, sanitize: sanitize, - renderMarkdown: renderMarkdown + renderMarkdown: renderMarkdown, + createNodeIcon: createNodeIcon, + getDarkerColor: getDarkerColor } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 0814892be..631e7ec1e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -67,9 +67,16 @@ RED.view.tools = (function() { function moveSelection(dx,dy) { if (moving_set === null) { + moving_set = []; var selection = RED.view.selection(); if (selection.nodes) { - moving_set = selection.nodes.map(function(n) { return {n:n}}); + while (selection.nodes.length > 0) { + var n = selection.nodes.shift(); + moving_set.push({n:n}); + if (n.type === "group") { + selection.nodes = selection.nodes.concat(n.nodes); + } + } } } if (moving_set && moving_set.length > 0) { @@ -93,6 +100,9 @@ RED.view.tools = (function() { node.n.x += dx; node.n.y += dy; node.n.dirty = true; + if (node.n.type === "group") { + RED.group.markDirty(node.n); + } minX = Math.min(node.n.x-node.n.w/2-5,minX); minY = Math.min(node.n.y-node.n.h/2-5,minY); } @@ -105,13 +115,86 @@ RED.view.tools = (function() { } } RED.view.redraw(); + } else { + RED.view.scroll(dx*10,dy*10); } } + function setSelectedNodeLabelState(labelShown) { + var selection = RED.view.selection(); + var historyEvents = []; + var nodes = []; + if (selection.nodes) { + selection.nodes.forEach(function(n) { + if (n.type !== 'subflow' && n.type !== 'group') { + nodes.push(n); + } else if (n.type === 'group') { + nodes = nodes.concat( RED.group.getNodes(n,true)); + } + }); + } + nodes.forEach(function(n) { + var modified = false; + var oldValue = n.l === undefined?true:n.l; + var isLink = /^link (in|out)$/.test(n._def.type); + + if (labelShown) { + if (n.l === false || (isLink && !n.hasOwnProperty('l'))) { + n.l = true; + modified = true; + } + } else { + if ((!isLink && (!n.hasOwnProperty('l') || n.l === true)) || (isLink && n.l === true) ) { + n.l = false; + modified = true; + } + } + if (modified) { + historyEvents.push({ + t: "edit", + node: n, + changed: n.changed, + changes: { + l: oldValue + } + }) + n.changed = true; + n.dirty = true; + n.resize = true; + } + }) + + if (historyEvents.length > 0) { + RED.history.push({ + t: "multi", + events: historyEvents, + dirty: RED.nodes.dirty() + }) + RED.nodes.dirty(true); + } + + RED.view.redraw(); + + + } + return { init: function() { + RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) + RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); }) + RED.actions.add("core:align-selection-to-grid", alignToGrid); + RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());}); + RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);}); + RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());}); + RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);}); + + RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());}); + RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);}); + RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());}); + RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);}); + RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);}); RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);}); RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);}); 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 80a9432f7..7f541204e 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 @@ -21,12 +21,15 @@ * \- .red-ui-workspace-chart-event-layer "eventLayer" * |- .red-ui-workspace-chart-background * |- .red-ui-workspace-chart-grid "gridLayer" + * |- "groupLayer" + * |- "groupSelectLayer" * |- "linkLayer" * |- "dragGroupLayer" - * \- "nodeLayer" + * |- "nodeLayer" */ RED.view = (function() { + var DEBUG_EVENTS = false; var space_width = 5000, space_height = 5000, lineCurveScale = 0.75, @@ -48,34 +51,42 @@ RED.view = (function() { var activeSpliceLink; var spliceActive = false; var spliceTimer; + var groupHoverTimer; var activeSubflow = null; var activeNodes = []; var activeLinks = []; var activeFlowLinks = []; var activeLinkNodes = {}; + var activeGroup = null; + var activeHoverGroup = null; + var activeGroups = []; + var dirtyGroups = {}; - var selected_link = null, - mousedown_link = null, - mousedown_node = null, - mousedown_port_type = null, - mousedown_port_index = 0, - mouseup_node = null, - mouse_offset = [0,0], - mouse_position = null, - mouse_mode = 0, - moving_set = [], - lasso = null, - ghostNode = null, - showStatus = false, - lastClickNode = null, - dblClickPrimed = null, - clickTime = 0, - clickElapsed = 0, - scroll_position = [], - quickAddActive = false, - quickAddLink = null, - showAllLinkPorts = -1; + var selected_link = null; + var mousedown_link = null; + var mousedown_node = null; + var mousedown_group = null; + var mousedown_port_type = null; + var mousedown_port_index = 0; + var mouseup_node = null; + var mouse_offset = [0,0]; + var mouse_position = null; + var mouse_mode = 0; + var mousedown_group_handle = null; + var moving_set = []; + var lasso = null; + var ghostNode = null; + var showStatus = false; + var lastClickNode = null; + var dblClickPrimed = null; + var clickTime = 0; + var clickElapsed = 0; + var scroll_position = []; + var quickAddActive = false; + var quickAddLink = null; + var showAllLinkPorts = -1; + var groupNodeSelectPrimed = false; var selectNodesOptions; @@ -100,7 +111,9 @@ RED.view = (function() { var gridLayer; var linkLayer; var dragGroupLayer; + var groupSelectLayer; var nodeLayer; + var groupLayer; var drag_lines; function init() { @@ -253,9 +266,12 @@ RED.view = (function() { gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); updateGrid(); + groupLayer = eventLayer.append("g"); + groupSelectLayer = eventLayer.append("g"); linkLayer = eventLayer.append("g"); dragGroupLayer = eventLayer.append("g"); nodeLayer = eventLayer.append("g"); + drag_lines = []; RED.events.on("workspace:change",function(event) { @@ -377,14 +393,36 @@ RED.view = (function() { historyEvent.removedLinks = [spliceLink]; } - RED.history.push(historyEvent); RED.nodes.add(nn); + + var group = $(ui.helper).data("group"); + if (group) { + RED.group.addToGroup(group, nn); + historyEvent = { + t: 'multi', + events: [historyEvent], + + } + historyEvent.events.push({ + t: "addToGroup", + group: group, + nodes: nn + }) + } + + RED.history.push(historyEvent); RED.editor.validateNode(nn); RED.nodes.dirty(true); // auto select dropped node - so info shows (if visible) + exitActiveGroup(); clearSelection(); nn.selected = true; moving_set.push({n:nn}); + if (group) { + selectGroup(group,false); + enterActiveGroup(group); + activeGroup = group; + } updateActiveNodes(); updateSelection(); redraw(); @@ -517,6 +555,53 @@ RED.view = (function() { source:{z:activeWorkspace}, target:{z:activeWorkspace} }); + + activeGroups = RED.nodes.groups(activeWorkspace)||[]; + activeGroups.forEach(function(g) { + if (g.g) { + g._root = g.g; + g._depth = 1; + } else { + g._root = g.id; + g._depth = 0; + } + }); + var changed = false; + do { + changed = false; + activeGroups.forEach(function(g) { + if (g.g) { + var parentGroup = RED.nodes.group(g.g); + if (parentGroup) { + var parentDepth = parentGroup._depth; + if (g._depth !== parentDepth + 1) { + g._depth = parentDepth + 1; + changed = true; + } + if (g._root !== parentGroup._root) { + g._root = parentGroup._root; + changed = true; + } + } + } + }); + } while (changed) + activeGroups.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }); + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }) } function generateLinkPath(origX,origY, destX, destY, sc) { @@ -671,6 +756,7 @@ RED.view = (function() { } function canvasMouseDown() { +if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } var point; if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); @@ -684,7 +770,7 @@ RED.view = (function() { scroll_position = [chart.scrollLeft(),chart.scrollTop()]; return; } - if (!mousedown_node && !mousedown_link) { + if (!mousedown_node && !mousedown_link && !mousedown_group) { selected_link = null; updateSelection(); } @@ -697,7 +783,13 @@ RED.view = (function() { if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { if (d3.event.metaKey || d3.event.ctrlKey) { d3.event.stopPropagation(); - showQuickAddDialog(d3.mouse(this)); + clearSelection(); + point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + if (drag_lines.length > 0) { + clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) + } + showQuickAddDialog(point, null, clickedGroup); } } if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { @@ -718,7 +810,13 @@ RED.view = (function() { } } - function showQuickAddDialog(point,spliceLink) { + function showQuickAddDialog(point, spliceLink, targetGroup) { + if (targetGroup && !targetGroup.active) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + RED.view.redraw(); + } + var ox = point[0]; var oy = point[1]; @@ -946,6 +1044,22 @@ RED.view = (function() { } } } + if (targetGroup) { + RED.group.addToGroup(targetGroup, nn); + if (historyEvent.t !== "multi") { + historyEvent = { + t:'multi', + events: [historyEvent] + } + } + historyEvent.events.push({ + t: "addToGroup", + group: targetGroup, + nodes: nn + }) + + } + if (spliceLink) { resetMouseVars(); // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog @@ -972,6 +1086,10 @@ RED.view = (function() { // auto select dropped node - so info shows (if visible) clearSelection(); nn.selected = true; + if (targetGroup) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + } moving_set.push({n:nn}); updateActiveNodes(); updateSelection(); @@ -1076,11 +1194,23 @@ RED.view = (function() { return; } - if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { + if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) { return; } var mousePos; + // if (mouse_mode === RED.state.GROUP_RESIZE) { + // mousePos = mouse_position; + // var nx = mousePos[0] + mousedown_group.dx; + // var ny = mousePos[1] + mousedown_group.dy; + // switch(mousedown_group.activeHandle) { + // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; + // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; + // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; + // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; + // } + // mousedown_group.dirty = true; + // } if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { // update drag line if (drag_lines.length === 0 && mousedown_port_type !== null) { @@ -1182,10 +1312,18 @@ RED.view = (function() { node.n.x = mousePos[0]+node.dx; node.n.y = mousePos[1]+node.dy; node.n.dirty = true; - minX = Math.min(node.n.x-node.n.w/2-5,minX); - minY = Math.min(node.n.y-node.n.h/2-5,minY); - maxX = Math.max(node.n.x+node.n.w/2+5,maxX); - maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + if (node.n.type === "group") { + RED.group.markDirty(node.n); + minX = Math.min(node.n.x-5,minX); + minY = Math.min(node.n.y-5,minY); + maxX = Math.max(node.n.x+node.n.w+5,maxX); + maxY = Math.max(node.n.y+node.n.h+5,maxY); + } else { + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + maxX = Math.max(node.n.x+node.n.w/2+5,maxX); + maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + } } if (minX !== 0 || minY !== 0) { for (i = 0; i 0) { - var gridOffset = [0,0]; - node = moving_set[0]; - gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); - gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize)); + var i = 0; + + // Prefer to snap nodes to the grid if there is one in the selection + do { + node = moving_set[i++]; + } while(i x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - moving_set.push({n:n}); + activeGroups.forEach(function(g) { + if (!g.selected) { + if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { + while (g.g) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } + } + } + }) + + activeNodes.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + if (n.g) { + var group = RED.nodes.group(n.g); + while (group.g) { + group = RED.nodes.group(group.g); + } + if (!group.selected) { + selectGroup(group,true); + } + } else { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } } } }); + + + + // var selectionChanged = false; + // do { + // selectionChanged = false; + // selectedGroups.forEach(function(g) { + // if (g.g && g.selected && RED.nodes.group(g.g).selected) { + // g.selected = false; + // selectionChanged = true; + // } + // }) + // } while(selectionChanged); + if (activeSubflow) { activeSubflow.in.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); @@ -1355,6 +1568,21 @@ RED.view = (function() { } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (moving_set.length > 0) { + var addedToGroup = null; + if (activeHoverGroup) { + for (var j=0;j 0) { historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; if (activeSpliceLink) { @@ -1386,12 +1615,31 @@ RED.view = (function() { historyEvent.removedLinks = [spliceLink]; updateActiveNodes(); } + if (addedToGroup) { + historyEvent.addToGroup = addedToGroup; + } RED.nodes.dirty(true); RED.history.push(historyEvent); } } } + // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { + // if (mousedown_node.gSelected) { + // delete mousedown_node.gSelected + // } else { + // if (!d3.event.ctrlKey && !d3.event.metaKey) { + // clearSelection(); + // } + // RED.nodes.group(mousedown_node.g).selected = true; + // mousedown_node.selected = true; + // mousedown_node.dirty = true; + // moving_set.push({n:mousedown_node}); + // } + // } if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { + // if (mousedown_node) { + // delete mousedown_node.gSelected; + // } for (i=0;i 0) { - selection.nodes = moving_set.map(function(n) { return n.n;}); - } - if (selected_link != null) { - selection.link = selected_link; - } + selection = getSelection(); activeLinks = RED.nodes.filterLinks({ source:{z:activeWorkspace}, target:{z:activeWorkspace} @@ -1606,6 +1872,8 @@ RED.view = (function() { var node = moving_set[0].n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); + } else if (node.type === "group") { + RED.editor.editGroup(node); } else { RED.editor.edit(node); } @@ -1632,6 +1900,7 @@ RED.view = (function() { dirty: RED.nodes.dirty(), nodes: [], links: [], + groups: [], workspaces: [], subflows: [] } @@ -1651,6 +1920,7 @@ RED.view = (function() { } historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes); historyEvent.links = historyEvent.links.concat(subEvent.links); + historyEvent.groups = historyEvent.groups.concat(subEvent.groups); } RED.history.push(historyEvent); RED.nodes.dirty(true); @@ -1661,6 +1931,7 @@ RED.view = (function() { var result; var removedNodes = []; var removedLinks = []; + var removedGroups = []; var removedSubflowOutputs = []; var removedSubflowInputs = []; var removedSubflowStatus; @@ -1668,11 +1939,14 @@ RED.view = (function() { var startDirty = RED.nodes.dirty(); var startChanged = false; + var selectedGroups = []; if (moving_set.length > 0) { for (var i=0;i 0) { + var g = selectedGroups.shift(); + if (removedGroups.indexOf(g) === -1) { + removedGroups.push(g); + g.nodes.forEach(function(n) { + if (n.type === "group") { + selectedGroups.push(n); + } + }) + RED.nodes.removeGroup(g); + } + } if (removedSubflowOutputs.length > 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { @@ -1716,7 +2009,7 @@ RED.view = (function() { subflowInstances = instances.instances; } moving_set = []; - if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) { + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) { RED.nodes.dirty(true); } } @@ -1769,6 +2062,7 @@ RED.view = (function() { t:"delete", nodes:removedNodes, links:removedLinks, + groups: removedGroups, subflowOutputs:removedSubflowOutputs, subflowInputs:removedSubflowInputs, subflow: { @@ -1801,20 +2095,36 @@ RED.view = (function() { selection.forEach(function(n) { if (n.type === 'tab') { nodes.push(n); + nodes = nodes.concat(RED.nodes.groups(n.id)); nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); } }); - } else if (moving_set.length > 0) { - nodes = moving_set.map(function(n) { return n.n }); + } else { + selection = RED.view.selection(); + if (selection.nodes) { + selection.nodes.forEach(function(n) { + nodes.push(n); + if (n.type === 'group') { + nodes = nodes.concat(RED.group.getNodes(n,true)); + } + }) + } } if (nodes.length > 0) { var nns = []; + var nodeCount = 0; + var groupCount = 0; for (var n=0;n 0) { + RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); + } else if (groupCount > 0) { + RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"}); + } } } function calculateTextWidth(str, className, offset) { - return calculateTextDimensions(str,className,offset,0)[0]; + var result=convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i 0 ? 1: 0) : (d.direction == "in" ? 0: 1) var wasJoining = false; if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { @@ -2276,7 +2666,24 @@ RED.view = (function() { } } + function prepareDrag(mouse) { + mouse_mode = RED.state.MOVING; + // Called when moving_set should be prepared to be dragged + for (i=0;i 0 && clickElapsed < 750) { + mouse_mode = RED.state.DEFAULT; + RED.editor.editGroup(g); + d3.event.stopPropagation(); + return; + } + + } + + function groupMouseDown(g) { + var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); + // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { + // return + // } + + focusView(); + if (d3.event.button === 1) { + return; + } + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove("escape"); + } else if (mouse_mode == RED.state.QUICK_JOINING) { + d3.event.stopPropagation(); + return; + } else if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + mousedown_group = g; + + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = ( + lastClickNode == g && + d3.event.button === 0 && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey + ); + lastClickNode = g; + + if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { + if (g === activeGroup) { + exitActiveGroup(); + } + deselectGroup(g); + d3.event.stopPropagation(); + } else { + if (!g.selected) { + if (!d3.event.ctrlKey && !d3.event.metaKey) { + clearSelection(); + } + if (activeGroup) { + if (!RED.group.contains(activeGroup,g)) { + // Clicked on a group that is outside the activeGroup + exitActiveGroup(); + } else { + } + } + selectGroup(g,true);//!wasSelected); + } else { + exitActiveGroup(); + } + + + if (d3.event.button != 2) { + var d = g.nodes[0]; + prepareDrag(mouse); + mousedown_group.dx = mousedown_group.x - mouse[0]; + mousedown_group.dy = mousedown_group.y - mouse[1]; + } + } + + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function selectGroup(g, includeNodes) { + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + moving_set.push({n:g}); + if (includeNodes) { + var currentSet = new Set(moving_set.map(function(n) { return n.n })); + var allNodes = RED.group.getNodes(g,true); + allNodes.forEach(function(n) { + if (!currentSet.has(n)) { + moving_set.push({n:n}) + // n.selected = true; + } + n.dirty = true; + }) + } + } + function enterActiveGroup(group) { + if (activeGroup) { + exitActiveGroup(); + } + group.active = true; + group.dirty = true; + activeGroup = group; + for (var i = moving_set.length-1; i >= 0; i -= 1) { + if (moving_set[i].n === group) { + moving_set.splice(i,1); + break; + } + } + } + function exitActiveGroup() { + if (activeGroup) { + activeGroup.active = false; + activeGroup.dirty = true; + deselectGroup(activeGroup); + selectGroup(activeGroup,true); + activeGroup = null; + } + } + function deselectGroup(g) { + if (g.selected) { + g.selected = false; + g.dirty = true; + } + var nodeSet = new Set(g.nodes); + nodeSet.add(g); + for (var i = moving_set.length-1; i >= 0; i -= 1) { + if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) { + moving_set[i].n.selected = false; + moving_set[i].n.dirty = true; + moving_set.splice(i,1); + } + } + } + function getGroupAt(x,y) { + var candidateGroups = {}; + for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { + candidateGroups[g.id] = g; + } + } + var ids = Object.keys(candidateGroups); + if (ids.length > 1) { + ids.forEach(function(id) { + if (candidateGroups[id] && candidateGroups[id].g) { + delete candidateGroups[candidateGroups[id].g] + } + }) + ids = Object.keys(candidateGroups); + } + if (ids.length === 0) { + return null; + } else { + return candidateGroups[ids[ids.length-1]] + } + } + function isButtonEnabled(d) { var buttonEnabled = true; var ws = RED.nodes.workspace(RED.workspaces.active()); @@ -2424,7 +3066,9 @@ RED.view = (function() { function nodeButtonClicked(d) { if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); + if (d3.event) { + d3.event.stopPropagation(); + } return; } var activeWorkspace = RED.workspaces.active(); @@ -2451,7 +3095,9 @@ RED.view = (function() { RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning"); } } - d3.event.preventDefault(); + if (d3.event) { + d3.event.preventDefault(); + } } function showTouchMenu(obj,pos) { @@ -2523,6 +3169,10 @@ RED.view = (function() { } function redraw() { + requestAnimationFrame(_redraw); + } + + function _redraw() { eventLayer.attr("transform","scale("+scaleFactor+")"); outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); @@ -2702,23 +3352,26 @@ RED.view = (function() { var nodeEnter = node.enter().insert("svg:g") .attr("class", "red-ui-flow-node red-ui-flow-node-group") - .classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; }); + .classed("red-ui-flow-subflow", activeSubflow != null); nodeEnter.each(function(d,i) { var node = d3.select(this); var isLink = (d.type === "link in" || d.type === "link out") var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; node.attr("id",d.id); - var l = RED.utils.getNodeLabel(d); + var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50); if (d.resize || d.w === undefined) { if (hideLabel) { d.w = node_height; } else { - d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } } - d.h = Math.max(node_height,(d.outputs||0) * 15); - + if (hideLabel) { + d.h = Math.max(node_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*separateTextByLineBreak.length, (d.outputs || 0) * 15, 30); + } // if (d._def.badge) { // var badge = node.append("svg:g").attr("class","node_badge_group"); // var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); @@ -2765,10 +3418,10 @@ RED.view = (function() { var mainRect = node.append("rect") .attr("class", "red-ui-flow-node") - .classed("red-ui-flow-node-unknown",function(d) { return d.type == "unknown"; }) + .classed("red-ui-flow-node-unknown", d.type == "unknown") .attr("rx", 5) .attr("ry", 5) - .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) + .attr("fill", RED.utils.getNodeColor(d.type,d._def)) .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) .on("touchstart",function(d) { @@ -2876,17 +3529,17 @@ RED.view = (function() { .attr("x",0).attr("y",0) .attr("class","red-ui-flow-node-icon-shade") .attr("width","30") - .attr("height",function(d){return Math.min(50,d.h-4);}); + .attr("height", Math.min(50,d.h-4)); createIconAttributes(icon_url, icon_group, d); var icon_shade_border = icon_group.append("path") - .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)}) + .attr("d","M 30 1 l 0 "+(d.h-2)) .attr("class","red-ui-flow-node-icon-shade-border"); if ("right" == d._def.align) { icon_group.attr("class","red-ui-flow-node-icon-group red-ui-flow-node-icon-group-"+d._def.align); - icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)}) + icon_shade_border.attr("d", "M 0 1 l 0 "+(d.h-2)) //icon.attr("class","red-ui-flow-node-icon red-ui-flow-node-icon-"+d._def.align); //icon.attr("class","red-ui-flow-node-icon-shade red-ui-flow-node-icon-shade-"+d._def.align); //icon.attr("class","red-ui-flow-node-icon-shade-border red-ui-flow-node-icon-shade-border-"+d._def.align); @@ -2904,17 +3557,22 @@ RED.view = (function() { //icon.style("pointer-events","none"); icon_group.style("pointer-events","none"); } - var text = node.append("svg:text") + var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; + var labelId = d.id.replace(".","-"); + for(var i=0;i0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } // d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); - d.h = Math.max(node_height,(d.outputs||0) * 15); d.x += (d.w-ow)/2; d.resize = false; } + if (hideLabel) { + d.h = Math.max(node_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*separateTextByLineBreak.length,(d.outputs || 0) * 15, 30); + } + var thisNode = d3.select(this); - thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true}); - thisNode.classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; }) + thisNode.classed("red-ui-flow-node-disabled", d.d === true); + thisNode.classed("red-ui-flow-subflow", activeSubflow != null) //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); - thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); + thisNode.attr("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); if (mouse_mode != RED.state.MOVING_ACTIVE) { - thisNode.classed("red-ui-flow-node-selected",function(d) { return d.selected }) + thisNode.classed("red-ui-flow-node-selected", d.selected ) thisNode.selectAll(".red-ui-flow-node") - .attr("width",function(d){return d.w}) - .attr("height",function(d){return d.h}) - .classed("red-ui-flow-node-highlighted",function(d) { return d.highlighted; }) + .attr("width", d.w) + .attr("height", d.h) + .classed("red-ui-flow-node-highlighted",d.highlighted ) ; + var l = ""; + if (d._def.label) { + l = d._def.label; + try { + l = (typeof l === "function" ? l.call(d) : l)||""; + l = RED.text.bidi.enforceTextDirectionWithUCC(l); + } catch(err) { + console.log("Definition error: "+d.type+".label",err); + l = d.type; + } + } + var sa = convertLineBreakCharacter(l); + var sn = sa.length; + var st = ""; + var yp = d.h / 2 - (sn / 2) * 24 + 16 + var labelId = d.id.replace(".","-"); + if(labelLineNumber0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -3067,35 +3790,6 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll("text.red-ui-flow-node-label").text(function(d,i){ - var l = ""; - if (d._def.label) { - l = d._def.label; - try { - l = (typeof l === "function" ? l.call(d) : l)||""; - l = RED.text.bidi.enforceTextDirectionWithUCC(l); - } catch(err) { - console.log("Definition error: "+d.type+".label",err); - l = d.type; - } - } - return l; - }) - .attr("y", function(d){return (d.h/2)-1;}) - .attr("class",function(d){ - var s = ""; - if (d._def.labelStyle) { - s = d._def.labelStyle; - try { - s = (typeof s === "function" ? s.call(d) : s)||""; - } catch(err) { - console.log("Definition error: "+d.type+".labelStyle",err); - s = ""; - } - s = " "+s; - } - return "red-ui-flow-node-label"+(d._def.align?" red-ui-flow-node-label-"+d._def.align:"")+s; - }).classed("hide",hideLabel); if (d._def.icon) { var icon = thisNode.select(".red-ui-flow-node-icon"); var faIcon = thisNode.select(".fa-lg"); @@ -3118,12 +3812,12 @@ RED.view = (function() { } thisNode.selectAll(".red-ui-flow-node-changed") - .attr("transform",function(d){return "translate("+(d.w-10)+", -2)"}) - .classed("hide",function(d) { return !(d.changed||d.moved); }); + .attr("transform", "translate("+(d.w-10)+", -2)") + .classed("hide", !(d.changed||d.moved)); thisNode.selectAll(".red-ui-flow-node-error") - .attr("transform",function(d){ return "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"}) - .classed("hide",function(d) { return d.valid; }); + .attr("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)") + .classed("hide", d.valid); thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { var port = d3.select(this); @@ -3131,18 +3825,14 @@ RED.view = (function() { }); thisNode.selectAll(".red-ui-flow-node-icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); - thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height",function(d){return d.h;}); - thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d", function (d) { - return "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2); - }); - thisNode.selectAll(".fa-lg").attr("y",function(d){return (d.h+13)/2;}); + thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height", d.h ); + thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d", + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + ); + thisNode.selectAll(".fa-lg").attr("y",(d.h+13)/2); - thisNode.selectAll(".red-ui-flow-node-button").attr("opacity",function(d) { - return (!isButtonEnabled(d))?0.4:1 - }); - thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d) { - return (!isButtonEnabled(d))?"":"pointer"; - }); + thisNode.selectAll(".red-ui-flow-node-button").attr("opacity", function(d2) { return !isButtonEnabled(d2)?0.4:1 }); + thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d2) { return isButtonEnabled(d2)?"":"pointer"}); thisNode.selectAll(".red-ui-flow-node-button").attr("transform",function(d){ var x = d._def.align == "right"?d.w-6:-25; if (d._def.button.toggle && !d[d._def.button.toggle]) { @@ -3208,6 +3898,15 @@ RED.view = (function() { } d.dirty = false; + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } } }); @@ -3238,7 +3937,9 @@ RED.view = (function() { d3.event.stopPropagation(); if (d3.event.metaKey || d3.event.ctrlKey) { l.classed("red-ui-flow-link-splice",true); - showQuickAddDialog(d3.mouse(this), selected_link); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog(point, selected_link, clickedGroup); } }) .on("touchstart",function(d) { @@ -3423,6 +4124,194 @@ RED.view = (function() { }) + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.exit().each(function(d,i) { + document.getElementById("group_select_"+d.id).remove() + }).remove(); + var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") + + var addedGroups = false; + groupEnter.each(function(d,i) { + addedGroups = true; + var g = d3.select(this); + g.attr("id",d.id); + + var groupBorderRadius = 4; + + var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4) + .style("stroke","rgba(255,255,255,0.8)") + .style("stroke-width",6) + + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4) + selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); + selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); + + g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); + + g.append('rect').classed("red-ui-flow-group-body",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({ + "fill":d.fill||"none", + "stroke": d.stroke||"none", + }) + g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + g.append('svg:text').attr("class","red-ui-flow-group-label"); + d.dirty = true; + }); + if (addedGroups) { + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }) + } + group[0].reverse(); + + group.each(function(d,i) { + if (d.resize) { + d.minWidth = 0; + delete d.resize; + } + if (d.dirty || dirtyGroups[d.id]) { + var g = d3.select(this); + if (d.nodes.length > 0) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + d.nodes.forEach(function(n) { + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + } else { + minX = Math.min(minX,n.x-25) + minY = Math.min(minY,n.y-25) + maxX = Math.max(maxX,n.x+n.w+25) + maxY = Math.max(maxY,n.y+n.h+25) + } + }); + + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + } else { + d.w = 40; + d.h = 40; + } + if (!d.minWidth) { + if (d.style.label && d.name) { + d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label",8); + d.labels = separateTextByLineBreak; + } else { + d.minWidth = 40; + } + } + d.w = Math.max(d.minWidth,d.w); + if (d.style.label && d.labels) { + var h = (d.labels.length -1) * 16; + var labelPos = d.style["label-position"] || "nw"; + d.h += h; + if (labelPos[0] === "n") { + d.y -= h; + } + } + + g.attr("transform","translate("+d.x+","+d.y+")") + g.selectAll(".red-ui-flow-group-outline") + .attr("width",d.w) + .attr("height",d.h) + + + var selectGroup = document.getElementById("group_select_"+d.id); + selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); + if (d.hovered) { + selectGroup.classList.add("red-ui-flow-group-hovered") + } else { + selectGroup.classList.remove("red-ui-flow-group-hovered") + } + var selectGroupRect = selectGroup.children[0]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + selectGroupRect = selectGroup.children[1]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + + if (d.highlighted) { + selectGroup.classList.add("red-ui-flow-node-highlighted"); + } else { + selectGroup.classList.remove("red-ui-flow-node-highlighted"); + } + + + g.selectAll(".red-ui-flow-group-body") + .attr("width",d.w) + .attr("height",d.h) + .style("stroke", d.style.stroke || "") + .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") + .style("fill", d.style.fill || "") + .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") + + var label = g.selectAll(".red-ui-flow-group-label"); + label.classed("hide",!!!d.style.label) + if (d.style.label) { + var labelPos = d.style["label-position"] || "nw"; + var labelX = 0; + var labelY = 0; + + if (labelPos[0] === 'n') { + labelY = 0+15; // Allow for font-height + } else { + labelY = d.h - 5 -(d.labels.length -1) * 16; + } + if (labelPos[1] === 'w') { + labelX = 5; + labelAnchor = "start" + } else if (labelPos[1] === 'e') { + labelX = d.w-5; + labelAnchor = "end" + } else { + labelX = d.w/2; + labelAnchor = "middle" + } + label + .style("fill", d.style.hasOwnProperty('color')?d.style.color:"#999") + .attr("transform","translate("+labelX+","+labelY+")") + .attr("text-anchor",labelAnchor); + if (d.labels) { + var ypos = 0; + g.selectAll(".red-ui-flow-group-label-text").remove(); + d.labels.forEach(function (name) { + label.append("tspan") + .classed("red-ui-flow-group-label-text", true) + .text(name) + .attr("x", 0) + .attr("y", ypos); + ypos += 16; + }); + } + } + + delete dirtyGroups[d.id]; + delete d.dirty; + } + }) + } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( @@ -3436,7 +4325,6 @@ RED.view = (function() { if (d3.event) { d3.event.preventDefault(); } - } function focusView() { @@ -3474,29 +4362,36 @@ RED.view = (function() { if (result) { var new_nodes = result[0]; var new_links = result[1]; - var new_workspaces = result[2]; - var new_subflows = result[3]; - var new_default_workspace = result[4]; + var new_groups = result[2]; + var new_workspaces = result[3]; + var new_subflows = result[4]; + var new_default_workspace = result[5]; if (addNewFlow && new_default_workspace) { RED.workspaces.show(new_default_workspace.id); } var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); + new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}).map(function(g) { return {n:g}})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); - // TODO: pick a more sensible root node if (new_ms.length > 0) { - var root_node = new_ms[0].n; - var dx = root_node.x; - var dy = root_node.y; + if (mouse_position == null) { mouse_position = [0,0]; } + var dx = mouse_position[0]; + var dy = mouse_position[1]; + if (new_ms.length > 0) { + var root_node = new_ms[0].n; + dx = root_node.x; + dy = root_node.y; + } + var minX = 0; var minY = 0; var i; - var node; + var node,group; for (i=0;i 0) { counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); } if (newNodeCount > 0) { counts.push(RED._("clipboard.node",{count:newNodeCount})); } + if (newGroupCount > 0) { + counts.push(RED._("clipboard.group",{count:newGroupCount})); + } if (newConfigNodeCount > 0) { counts.push(RED._("clipboard.configNode",{count:newNodeCount})); } @@ -3640,22 +4548,25 @@ RED.view = (function() { var historyEvents = []; for (var i=0;i 0) { @@ -3670,7 +4581,40 @@ RED.view = (function() { RED.view.redraw(); } + function getSelection() { + var selection = {}; + var allNodes = new Set(); + + if (moving_set.length > 0) { + moving_set.forEach(function(n) { + if (n.n.type !== 'group') { + allNodes.add(n.n); + } + }); + } + var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); + if (selectedGroups.length > 0) { + if (selectedGroups.length === 1 && selectedGroups[0].active) { + // Let nodes be nodes + } else { + selectedGroups.forEach(function(g) { + var groupNodes = RED.group.getNodes(g,true); + groupNodes.forEach(function(n) { + allNodes.delete(n); + }); + allNodes.add(g); + }); + } + } + if (allNodes.size > 0) { + selection.nodes = Array.from(allNodes); + } + if (selected_link != null) { + selection.link = selected_link; + } + return selection; + } return { init: init, state:function(state) { @@ -3681,12 +4625,17 @@ RED.view = (function() { } }, - redraw: function(updateActive) { + updateActive: updateActiveNodes, + redraw: function(updateActive, syncRedraw) { if (updateActive) { updateActiveNodes(); updateSelection(); } - redraw(); + if (syncRedraw) { + _redraw(); + } else { + redraw(); + } }, focus: focusView, importNodes: importNodes, @@ -3701,21 +4650,31 @@ RED.view = (function() { selectedNode.dirty = true; moving_set = [{n:selectedNode}]; } + } else if (selection) { + if (selection.nodes) { + updateActiveNodes(); + moving_set = []; + // TODO: this selection group span groups + // - if all in one group -> activate the group + // - if in multiple groups (or group/no-group) + // -> select the first 'set' of things in the same group/no-group + selection.nodes.forEach(function(n) { + if (n.type !== "group") { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } else { + selectGroup(n,true); + } + }) + } } } updateSelection(); - redraw(); - }, - selection: function() { - var selection = {}; - if (moving_set.length > 0) { - selection.nodes = moving_set.map(function(n) { return n.n;}); - } - if (selected_link != null) { - selection.link = selected_link; - } - return selection; + redraw(true); }, + selection: getSelection, + scale: function() { return scaleFactor; }, @@ -3730,47 +4689,57 @@ RED.view = (function() { } return result; }, - reveal: function(id) { + getGroupAtPoint: getGroupAt, + getActiveGroup: function() { return activeGroup }, + reveal: function(id,triggerHighlight) { if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { RED.workspaces.show(id); } else { - var node = RED.nodes.node(id); - if (node._def.category !== 'config' && node.z) { - node.highlighted = true; - node.dirty = true; - RED.workspaces.show(node.z); + var node = RED.nodes.node(id) || RED.nodes.group(id); + if (node) { + if (node.z && (node.type === "group" || node._def.category !== 'config')) { + node.dirty = true; + RED.workspaces.show(node.z); - var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor]; - var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; - - if (node.x < scrollPos[0] || node.y < scrollPos[1] || node.x > screenSize[0]+scrollPos[0] || node.y > screenSize[1]+scrollPos[1]) { - var deltaX = '-='+(((scrollPos[0] - node.x) + screenSize[0]/2)*scaleFactor); - var deltaY = '-='+(((scrollPos[1] - node.y) + screenSize[1]/2)*scaleFactor); - chart.animate({ - scrollLeft: deltaX, - scrollTop: deltaY - },200); - } - - 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(); + var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor]; + var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; + var cx = node.x; + var cy = node.y; + if (node.type === "group") { + cx += node.w/2; + cy += node.h/2; } - flashFunc(); + if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) { + var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor); + var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor); + chart.animate({ + scrollLeft: deltaX, + scrollTop: deltaY + },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(); + } + } + } else if (node._def.category === 'config') { + RED.sidebar.config.show(id); } - } else if (node._def.category === 'config') { - RED.sidebar.config.show(id); } } }, @@ -3795,7 +4764,6 @@ RED.view = (function() { mouse_mode = RED.state.SELECTING_NODE; clearSelection(); if (options.selected) { - console.log(options.selected); options.selected.forEach(function(id) { var n = RED.nodes.node(id); if (n) { @@ -3848,6 +4816,15 @@ RED.view = (function() { type: "compact", buttons: buttons }) + }, + scroll: function(x,y) { + chart.scrollLeft(chart.scrollLeft()+x); + chart.scrollTop(chart.scrollTop()+y) + }, + clickNodeButton: function(n) { + if (n._def.button) { + nodeButtonClicked(n); + } } }; })(); 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 1f361d0f7..290083ea6 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 @@ -128,10 +128,6 @@ RED.workspaces = (function() { RED.history.push(historyEvent); RED.nodes.dirty(true); RED.sidebar.config.refresh(); - var selection = RED.view.selection(); - if (!selection.nodes && !selection.links) { - RED.sidebar.info.refresh(workspace); - } if (changes.hasOwnProperty('disabled')) { RED.nodes.eachNode(function(n) { if (n.z === workspace.id) { @@ -140,6 +136,7 @@ RED.workspaces = (function() { }); RED.view.redraw(); } + RED.events.emit("flows:change",workspace); } RED.tray.close(); } @@ -219,7 +216,10 @@ RED.workspaces = (function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } - RED.sidebar.info.refresh(workspace); + var selection = RED.view.selection(); + if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) { + RED.sidebar.info.refresh(workspace); + } tabflowEditor.destroy(); } } @@ -371,7 +371,9 @@ RED.workspaces = (function() { var changes = { disabled: workspace.disabled }; workspace.disabled = disabled; $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); - $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); + if (id === activeWorkspace) { + $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); + } var historyEvent = { t: "edit", changes:changes, @@ -380,10 +382,11 @@ RED.workspaces = (function() { } workspace.changed = true; RED.history.push(historyEvent); + RED.events.emit("flows:change",workspace); RED.nodes.dirty(true); RED.sidebar.config.refresh(); var selection = RED.view.selection(); - if (!selection.nodes && !selection.links) { + if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) { RED.sidebar.info.refresh(workspace); } if (changes.hasOwnProperty('disabled')) { @@ -412,9 +415,14 @@ RED.workspaces = (function() { } function setWorkspaceOrder(order) { - RED.nodes.setWorkspaceOrder(order.filter(function(id) { + var newOrder = order.filter(function(id) { return RED.nodes.workspace(id) !== undefined; - })); + }) + var currentOrder = RED.nodes.getWorkspaceOrder(); + if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) { + RED.nodes.setWorkspaceOrder(newOrder); + RED.events.emit("flows:reorder",newOrder); + } workspace_tabs.order(order); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss index f9c00faac..cd3739800 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss @@ -9,19 +9,15 @@ color: transparent !important; } } - - .ace_gutter { + background: $text-editor-gutter-background; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .ace_scroller { + background: $text-editor-background; border-top-right-radius: 4px; border-bottom-right-radius: 4px; - } - - .ace_scroller { - background: $text-editor-background; color: $text-editor-color; } .ace_marker-layer .ace_active-line { @@ -37,9 +33,6 @@ .ace_gutter-active-line { background: $text-editor-gutter-active-line-background; } - .ace_gutter { - background: $text-editor-gutter-background; - } .ace_tooltip { font-family: $primary-font; line-height: 1.4em; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index 8411343c5..96be7b5ab 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -23,7 +23,7 @@ $primary-background: #f3f3f3;//#0ff; $secondary-background: #fff;//#ff0; $secondary-background-selected: #efefef;//#e9e900; $secondary-background-inactive: #f0f0f0;//#f0f000; -$secondary-background-hover: #ddd;//#dd0; +$secondary-background-hover: #e6e6e6;//#dd0; $secondary-background-disabled: #f9f9f9;//#fafa0; $tertiary-background: #f7f7f7;//#f0f; @@ -94,7 +94,7 @@ $list-item-secondary-color: $secondary-text-color; $list-item-background: $secondary-background; $list-item-background-disabled: $secondary-background-inactive; $list-item-background-hover: $secondary-background-hover; -$list-item-background-selected: $secondary-background-selected; +$list-item-background-selected: #ffebc7; // #fff1e5; $list-item-border-selected: $secondary-text-color-selected; $tab-text-color-active: $header-text-color; @@ -284,3 +284,8 @@ $debug-message-border: #eee; $debug-message-border-hover: #999; $debug-message-border-warning: #ffdf9d; $debug-message-border-error: #f99; + +$group-default-fill: none; +$group-default-fill-opacity: 1; +$group-default-stroke: #999; +$group-default-stroke-opacity: 1; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index c95fecad6..d8ef3e89c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -304,9 +304,6 @@ button.red-ui-button-small &:first-child { padding: 20px 20px 0; } - &:last-child { - padding-bottom: 20px; - } } } .red-ui-editor-type-expression-tab-content { @@ -411,6 +408,133 @@ button.red-ui-button.red-ui-editor-node-appearance-button { } } +.red-ui-group-layout-picker { + padding: 5px; + background: $primary-background; +} +.red-ui-group-layout-picker-cell-text { + position: absolute; + width: 14px; + height: 2px; + border-top: 2px solid $secondary-text-color; + border-bottom: 2px solid $secondary-text-color; + margin: 2px; + + &.red-ui-group-layout-text-pos-nw { top: 0; left: 0; } + &.red-ui-group-layout-text-pos-n { top: 0; left: calc(50% - 9px); } + &.red-ui-group-layout-text-pos-ne { top: 0; right: 0; } + &.red-ui-group-layout-text-pos-sw { bottom: 0; left: 0; } + &.red-ui-group-layout-text-pos-s { bottom: 0; left: calc(50% - 9px); } + &.red-ui-group-layout-text-pos-se { bottom: 0; right: 0; } + &.red-ui-group-layout-text-pos- { + width: 100%; + height: 100%; + border-radius: 5px; + margin: 0; + background-color: #FFF; + background-size: 100% 100%; + background-position: 0 0, 50% 50%; + background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent); + border: none; + } +} + +.red-ui-group-layout-picker button.red-ui-search-result-node { + float: none; + position: relative; + padding: 0; + margin: 5px; + width: 32px; + height: 27px; +} + +button.red-ui-group-layout-picker-none { + width: 100%; +} + +.red-ui-color-picker { + input[type="text"] { + border-radius:0; + width: 100%; + margin-bottom: 0; + border: none; + border-bottom: 1px solid $form-input-border-color; + } + small { + color: $secondary-text-color; + margin-left: 5px; + margin-right: 4px; + display: inline-block; + min-width: 35px; + text-align: right; + } + background: $primary-background; +} +.red-ui-editor-node-appearance-button { + .red-ui-search-result-node { + overflow: hidden + } +} +.red-ui-color-picker-cell { + padding: 0; + border-style: solid; + border-width: 1px; + border-color: $secondary-border-color; +} +.red-ui-color-picker-swatch { + position: absolute; + top:-1px;right:-1px;left:-1px;bottom:-1px; + border-radius: 4px; +} + +.red-ui-color-picker-cell-none { + height: 100%; + background-color: #FFF; + background-size: 100% 100%; + background-position: 0 0, 50% 50%; + background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent) +} +.red-ui-search-result-node .red-ui-color-picker-cell-none { + border-radius: 4px; + background-size: 50% 50%; + background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); +} + +.red-ui-color-picker-opacity-slider { + position:relative; + vertical-align: middle; + display: inline-block; + width: calc(100% - 50px); + height: 14px; + margin: 6px 3px 8px; + box-sizing: border-box; + background-color: white; + background-image: + linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%), + linear-gradient(-45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%); + background-size: 6px 6px; +} +.red-ui-color-picker-opacity-slider-overlay { + position: absolute; + top:0;right:0;left:0;bottom:0; + background-image:linear-gradient(90deg, transparent 0%, #f00 100%); + background-size: 100% 100%; + border: 1px solid $primary-border-color; +} + +div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { + z-Index: 10; + top: -4px; + cursor: pointer; + min-width: 0; + width: 10px; + height: 22px; + padding: 0; + border: 1px solid $primary-border-color; + border-radius: 1px; + background: $secondary-background; + box-sizing: border-box; +} .red-ui-icon-picker { select { box-sizing: border-box; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index d92d1e964..60de209cc 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -71,6 +71,48 @@ } } +.red-ui-flow-group { + &.red-ui-flow-group-hovered { + .red-ui-flow-group-outline-select { + stroke-opacity: 0.8 !important; + stroke-dasharray: 10 4 !important; + } + } + &.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) { + .red-ui-flow-group-outline-select { + stroke: $link-link-color; + } + } +} + +.red-ui-flow-group-outline { + fill: none; + stroke: $node-selected-color; + stroke-opacity: 0; + stroke-width: 12; + pointer-events: stroke; +} +.red-ui-flow-group-outline-select { + fill: none; + stroke: $node-selected-color; + pointer-events: stroke; + stroke-opacity: 0; + stroke-width: 3; +} +.red-ui-flow-group-body { + pointer-events: none; + fill: $group-default-fill; + fill-opacity: $group-default-fill-opacity; + stroke-width: 2; + stroke: $group-default-stroke; + stroke-opacity: $group-default-stroke-opacity; +} +.red-ui-flow-group-label { + @include disable-selection; +} + + + .red-ui-flow-node-unknown { stroke-dasharray:10,4; stroke: $node-border-unknown; @@ -166,6 +208,9 @@ g.red-ui-flow-node-selected { fill-opacity: 1; stroke-dasharray: none; } + .red-ui-flow-group, .red-ui-flow-group-body { + stroke-dasharray: 8, 3; + } } .red-ui-flow-node-disabled { &.red-ui-flow-node, .red-ui-flow-node { @@ -248,6 +293,7 @@ g.red-ui-flow-node-selected { .red-ui-flow-link-outline { stroke: $view-background; + stroke-opacity: 0.4; stroke-width: 5; cursor: crosshair; fill: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/library.scss b/packages/node_modules/@node-red/editor-client/src/sass/library.scss index 60014b2e6..5cff53f44 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/library.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/library.scss @@ -37,7 +37,7 @@ border-radius: 4px; font-family: $monospace-font !important; font-size: 13px !important; - height: 300px; + height: 100%; line-height: 1.3em; padding: 6px 10px; background: $clipboard-textarea-background; @@ -62,6 +62,7 @@ background: $form-input-background; &>div { height: 100%; + box-sizing: border-box; } } .red-ui-clipboard-dialog-box { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index ea4b06c52..922a31e33 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -186,6 +186,21 @@ background-size: contain; background-repeat: no-repeat; } +.red-ui-search-result-node { + &.red-ui-palette-icon-flow, + &.red-ui-palette-icon-group, + &.red-ui-palette-icon-selection { + background: none; + border-color: transparent; + .red-ui-palette-icon-container { + background: none; + } + .red-ui-palette-icon-fa { + color: $secondary-text-color; + font-size: 18px; + } + } +} .red-ui-palette-icon-fa { color: white; position: absolute; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss index 9f99db5d4..455aab891 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss @@ -22,9 +22,19 @@ // border: 1px solid red; box-sizing: border-box; } + display: flex; + flex-direction: column; + + >.red-ui-panel:first-child { + flex: 0 0 auto; + } + >.red-ui-panel:last-child { + flex: 1 1 auto; + } } .red-ui-panels-separator { + flex: 0 0 auto; border-top: 1px solid $secondary-border-color; border-bottom: 1px solid $secondary-border-color; height: 7px; @@ -37,10 +47,13 @@ .red-ui-panel { overflow: auto; height: calc(50% - 4px); + position: relative; } .red-ui-panels.red-ui-panels-horizontal { height: 100%; + flex-direction: row; + &>.red-ui-panel { vertical-align: top; display: inline-block; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss index 95097a30e..872f32024 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss @@ -150,6 +150,16 @@ .red-ui-popover a.red-ui-button, .red-ui-popover button.red-ui-button { + &:not(.primary) { + border-color: $popover-button-border-color; + background: $popover-background; + color: $popover-color !important; + } + &:not(.primary):not(.disabled):not(.ui-button-disabled):hover { + border-color: $popover-button-border-color-hover; + } + + &.primary { border-color: $popover-button-border-color; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index 9b1005f22..e08551297 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -18,7 +18,12 @@ .red-ui-editableList-container { padding: 0px; } - + padding: 0; + .red-ui-projects-dialog-box { + box-sizing: border-box; + overflow-y: auto; + padding: 25px 25px 10px 25px; + } } #red-ui-project-settings-tab-settings { overflow-y: scroll; @@ -99,6 +104,7 @@ .red-ui-projects-dialog-screen-create { min-height: 500px; button.red-ui-projects-dialog-screen-create-type { + position: relative; height: auto; padding: 10px; } @@ -169,9 +175,14 @@ .red-ui-projects-dialog-project-list-container { border: 1px solid $secondary-border-color; border-radius: 2px; + display: flex; + flex-direction: column; + .red-ui-search-container { + flex-grow: 0; + } } .red-ui-projects-dialog-project-list-inner-container { - height: 300px; + flex-grow: 1 ; overflow-y: scroll; position:relative; .red-ui-editableList-border { @@ -254,6 +265,9 @@ } } } +.red-ui-projects-dialog-screen-create-type { + position: relative; +} .red-ui-projects-dialog-screen-create-type.red-ui-button.toggle.selected:not(.disabled):not(:disabled) { color: $secondary-text-color-active !important; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index 0ec8b6525..27ebb1a04 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -24,6 +24,13 @@ top: 0px; border: 1px solid $primary-border-color; box-shadow: 0 0 10px $shadow; + background: $secondary-background; + + .red-ui-searchBox-container { + display: inline-block; + margin-right: 6px; + width: calc(100% - 30px); + } } .red-ui-type-search { @@ -87,6 +94,8 @@ } .red-ui-palette-icon { width: 15px; + position:relative; + left: -1px; } .red-ui-search-result-description { margin-left:28px; @@ -153,7 +162,7 @@ width: 30px; float:left; height: 25px; - border-radius: 5px; + border-radius: 3px; border: 1px solid $node-border; background-position: 5% 50%; background-repeat: no-repeat; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/style.scss b/packages/node_modules/@node-red/editor-client/src/sass/style.scss index 088e5c1b8..ca572ea46 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/style.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/style.scss @@ -42,6 +42,7 @@ @import "tab-config"; @import "tab-context"; @import "tab-info"; +@import "tab-help"; @import "popover"; @import "flow"; @import "palette-editor"; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss new file mode 100644 index 000000000..fe4f9fb84 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss @@ -0,0 +1,27 @@ +.red-ui-sidebar-help-stack { + // height: calc(100% - 39px); +} +.red-ui-help-search { + border-bottom: 1px solid $secondary-border-color; +} + +.red-ui-sidebar-help-toc { + .red-ui-treeList-label { + font-size: 13px; + padding: 2px 0; + overflow: hidden; + white-space: nowrap; + } + +} +#red-ui-sidebar-help-show-toc { + i.fa-angle-right { + transition: all 0.2s ease-in-out; + } + &.selected { + i.fa-angle-right { + transform: rotate(90deg); + } + } + +} diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss index bc72f7532..c98bdb6ad 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss @@ -14,9 +14,25 @@ * limitations under the License. **/ +.red-ui-sidebar-info { + height: 100%; +} .red-ui-sidebar-info hr { margin: 10px 0; } +.red-ui-info-header { + padding-left: 9px; + line-height: 21px; + cursor: default; + > * { + vertical-align: middle + } + > span { + display: inline-block; + margin-left: 5px; + } + border-bottom: 1px solid $secondary-border-color; +} table.red-ui-info-table { font-size: 14px; margin: 0 0 10px; @@ -125,6 +141,9 @@ div.red-ui-info-table { font-size: 1.296em; line-height: 1.3em; margin: 8px auto; + &.red-ui-help-title { + border-bottom: 1px solid $tertiary-border-color; + } } h2 { font-weight: 500; @@ -214,12 +233,13 @@ div.red-ui-info-table { } .red-ui-sidebar-info-stack { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow-y: scroll; + height: 100%; + // position: absolute; + // top: 0; + // bottom: 0; + // left: 0; + // right: 0; + // overflow-y: scroll; } .red-ui-help-tips { display: none; @@ -227,20 +247,23 @@ div.red-ui-info-table { left:0; right:0; bottom: 0; - height: 150px; + height: 0; + transition: height 0.2s, padding 0.2s; box-sizing: border-box; border-top: 1px solid $secondary-border-color; background-color: $secondary-background; - padding: 20px; + padding: 0; box-shadow: 0 5px 20px 0px $shadow; overflow-y: auto; } .red-ui-sidebar-info.show-tips { .red-ui-sidebar-info-stack { - bottom: 150px; + height: calc(100% - 150px); } .red-ui-help-tips { display: block; + height: 150px; + padding: 20px; } } @@ -279,3 +302,220 @@ div.red-ui-info-table { border-radius: 4px; padding: 2px 4px 2px; } + +.red-ui-info-outline,.red-ui-sidebar-help-toc { + display: flex; + flex-direction: column; + + .red-ui-treeList { + flex-grow: 1; + position: relative; + } + .red-ui-treeList-container { + position: absolute; + top: 0; + bottom: 0; + } + + .red-ui-treeList-container,.red-ui-editableList-border { + border: none; + border-radius: 0; + } + .red-ui-treeList-label { + font-size: 13px; + padding: 2px 0; + overflow: hidden; + } + .red-ui-info-outline-project { + border-bottom: 1px solid $secondary-border-color; + } + + .red-ui-info-outline-item { + display: inline-block; + padding: 0; + font-size: 13px; + border: none; + .red-ui-palette-icon-fa { + position: relative; + top: 1px; + left: 0px; + } + &:hover { + background: inherit + } + + &.red-ui-info-outline-item-flow { + .red-ui-search-result-description { + margin-left: 4px; + } + } + &.red-ui-info-outline-item-group .red-ui-search-result-node { + background: none; + border-color: transparent; + .red-ui-palette-icon-container { + background: none; + } + .red-ui-palette-icon-fa { + color: $secondary-text-color; + font-size: 18px; + } + } + &.red-ui-info-outline-item-empty { + font-style: italic; + color: $form-placeholder-color; + } + } + + .red-ui-search-result-node { + width: 24px; + height: 20px; + margin-top: 1px; + } + + .red-ui-palette-icon-container { + width: 24px; + } + .red-ui-palette-icon { + width: 20px; + } + .red-ui-search-result-description { + margin-left: 32px; + line-height: 22px; + white-space: nowrap; + } + .red-ui-search-result-node-label { + color: $secondary-text-color; + } +} +.red-ui-info-outline-item-control-spacer { + display: inline-block; + width: 23px; +} +.red-ui-info-outline-gutter { + display:none; + button { + position: absolute; + top: 1px; + left: 2px; + } + .red-ui-treeList-label:hover & { + display: inline; + } +} +.red-ui-info-outline-item-controls { + position: absolute; + top:0; + bottom: 0; + right: 0px; + padding: 2px 3px 0 1px; + text-align: right; + background: $list-item-background; + + .red-ui-treeList-label:hover & { + background: $list-item-background-hover; + } + .red-ui-treeList-label.selected & { + background: $list-item-background-selected; + } + + + &.red-ui-info-outline-item-hover-controls button { + min-width: 23px; + } + + .red-ui-treeList-label:not(:hover) &.red-ui-info-outline-item-hover-controls { + button { + border: none; + background: none; + } + } + .red-ui-info-outline-item-control-reveal, + .red-ui-info-outline-item-control-action { + display: none; + } + .red-ui-treeList-label:hover & { + .red-ui-info-outline-item-control-reveal, + .red-ui-info-outline-item-control-action { + display: inline-block; + } + } + + .fa-ban { + display: none; + } + .red-ui-info-outline-item.red-ui-info-outline-item-disabled & { + .fa-ban { + display: inline-block; + } + .fa-circle-thin { + display: none; + } + } + button { + margin-right: 3px + } +} +.red-ui-info-outline-item-disabled { + .red-ui-search-result-node { + opacity: 0.4; + } + .red-ui-info-outline-item-label { + font-style: italic; + color: $secondary-text-color-disabled; + } + .red-ui-icons-flow { + opacity: 0.4; + } +} + + + +.red-ui-icons { + display: inline-block; + width: 18px; + &:before { + white-space: pre; + content: ' ' + } + +} + +.red-ui-icons-flow { + background-image: url('images/subflow_tab.svg'); + background-repeat: no-repeat; + background-size: contain; + filter: brightness(2.5); +} + +.red-ui-info-toolbar { + min-height: 39px; + height: 39px; + box-sizing: border-box; + text-align: left; + // padding-left: 9px; + // box-sizing: border-box; + // background: $palette-header-background; + // border-bottom: 1px solid $secondary-border-color; + + .red-ui-searchBox-container { + position: absolute; + top: 6px; + right: 8px; + width: calc(100% - 150px); + max-width: 250px; + background: $palette-header-background; + input.red-ui-searchBox-input { + border: 1px solid $secondary-border-color; + border-radius: 3px; + font-size: 12px; + height: 26px; + } + input:focus.red-ui-searchBox-input { + border: 1px solid $secondary-border-color; + } + i.fa-search, i.fa-times { + top: 7px; + } + } + +} diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index 704ca10d6..ec865b116 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -51,7 +51,10 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - + .red-ui-typedInput-value-label-inactive { + background: $secondary-background-disabled; + color: $secondary-text-color-disabled; + } } } .red-ui-typedInput-options { @@ -117,7 +120,7 @@ button.red-ui-typedInput-option-trigger } &.disabled { cursor: default; - i.red-ui-typedInput-icon { + > i.red-ui-typedInput-icon { color: $secondary-text-color-disabled; } } 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 f6255eacf..2adfb89ba 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 @@ -112,7 +112,7 @@ position: absolute; bottom: 0; right:0; - zIndex: 101; + z-index: 101; border-left: 1px solid $primary-border-color; border-top: 1px solid $primary-border-color; background: $view-navigator-background; @@ -122,7 +122,7 @@ stroke-dasharray: 5,5; pointer-events: none; stroke: $secondary-border-color; - strokeWidth: 1; + stroke-width: 1; fill: $view-background; } @@ -172,3 +172,44 @@ button.red-ui-footer-button-toggle { margin-right: 0; } } + + +#red-ui-loading-progress { + position: absolute; + background: $primary-background; + top: 0; + bottom: 0; + right: 0; + left: 0; + z-index: 200; + & > div { + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, -50%); + width: 300px; + height:80px; + text-align: center; + color: $secondary-text-color; + + } +} +.red-ui-loading-bar { + box-sizing: border-box; + width: 300px; + height: 30px; + border: 2px solid $primary-border-color; + border-radius: 4px; + + > span { + display: block; + height: 100%; + background: $secondary-border-color; + transition: width 0.2s; + width: 10%; + } +} +.red-ui-loading-bar-label { + font-size: 13px; + margin-bottom: 2px; +} diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index 87ec3f86b..8aa3dc7b2 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -14,16 +14,14 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js index c0d9e0c2f..54715e131 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js @@ -20,9 +20,32 @@ module.exports = function(RED) { function InjectNode(n) { RED.nodes.createNode(this,n); - this.topic = n.topic; - this.payload = n.payload; - this.payloadType = n.payloadType; + + /* Handle legacy */ + if(!Array.isArray(n.props)){ + n.props = []; + n.props.push({ + p:'payload', + v:n.payload, + vt:n.payloadType + }); + n.props.push({ + p:'topic', + v:n.topic, + vt:'str' + }); + } else { + for (var i=0,l=n.props.length; i 2147483) { node.error(RED._("inject.errors.toolong", this)); delete node.repeat; } node.repeaterSetup = function () { - if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { - this.repeat = this.repeat * 1000; - if (RED.settings.verbose) { - this.log(RED._("inject.repeat", this)); + if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { + this.repeat = this.repeat * 1000; + if (RED.settings.verbose) { + this.log(RED._("inject.repeat", this)); + } + this.interval_id = setInterval(function() { + node.emit("input", {}); + }, this.repeat); + } else if (this.crontab) { + if (RED.settings.verbose) { + this.log(RED._("inject.crontab", this)); + } + this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true); } - this.interval_id = setInterval(function() { - node.emit("input", {}); - }, this.repeat); - } else if (this.crontab) { - if (RED.settings.verbose) { - this.log(RED._("inject.crontab", this)); - } - this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true); - } }; if (this.once) { this.onceTimeout = setTimeout( function() { - node.emit("input",{}); - node.repeaterSetup(); + node.emit("input",{}); + node.repeaterSetup(); }, this.onceDelay); } else { - node.repeaterSetup(); + node.repeaterSetup(); } - this.on("input",function(msg) { - msg.topic = this.topic; - if (this.payloadType !== 'flow' && this.payloadType !== 'global') { - try { - if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") { - msg.payload = Date.now(); - } else if (this.payloadType == null) { - msg.payload = this.payload; - } else if (this.payloadType === 'none') { - msg.payload = ""; - } else { - msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg); - } - this.send(msg); - msg = null; - } catch(err) { - this.error(err,msg); - } - } else { - RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) { - if (err) { - node.error(err,msg); - } else { - msg.payload = res; - node.send(msg); - } + this.on("input", function(msg) { + var errors = []; - }); + this.props.forEach(p => { + var property = p.p; + var value = p.v ? p.v : ''; + var valueType = p.vt ? p.vt : 'str'; + + if (!property) return; + + if (valueType === "jsonata") { + if (p.exp) { + try { + var val = RED.util.evaluateJSONataExpression(p.exp, msg); + RED.util.setMessageProperty(msg, property, val, true); + } + catch (err) { + errors.push(err.message); + } + } + return; + } + try { + RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true); + } catch (err) { + errors.push(err.toString()); + } + }); + + if (errors.length) { + node.error(errors.join('; '), msg); + } else { + node.send(msg); } }); } diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index 5e2f3ba49..d12279c74 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -1,30 +1,35 @@ - - diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 9202d96ba..78e7f2d63 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -1,22 +1,57 @@ - diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js index 65a1b4a61..a6573a787 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js @@ -57,22 +57,55 @@ module.exports = function(RED) { } } + function createVMOpt(node, kind) { + var opt = { + filename: 'Function node'+kind+':'+node.id+(node.name?' ['+node.name+']':''), // filename for stack traces + displayErrors: true + // Using the following options causes node 4/6 to not include the line number + // in the stack output. So don't use them. + // lineOffset: -11, // line number offset to be used for stack traces + // columnOffset: 0, // column number offset to be used for stack traces + }; + return opt; + } + + function updateErrorInfo(err) { + if (err.stack) { + var stack = err.stack.toString(); + var m = /^([^:]+):([^:]+):(\d+).*/.exec(stack); + if (m) { + var line = parseInt(m[3]) -1; + var kind = "body:"; + if (/setup/.exec(m[1])) { + kind = "setup:"; + } + if (/cleanup/.exec(m[1])) { + kind = "cleanup:"; + } + err.message += " ("+kind+"line "+line+")"; + } + } + } + function FunctionNode(n) { RED.nodes.createNode(this,n); var node = this; - this.name = n.name; - this.func = n.func; + node.name = n.name; + node.func = n.func; + node.ini = n.initialize ? n.initialize : ""; + node.fin = n.finalize ? n.finalize : ""; var handleNodeDoneCall = true; + // Check to see if the Function appears to call `node.done()`. If so, // we will assume it is well written and does actually call node.done(). // Otherwise, we will call node.done() after the function returns regardless. - if (/node\.done\s*\(\s*\)/.test(this.func)) { + if (/node\.done\s*\(\s*\)/.test(node.func)) { handleNodeDoneCall = false; } var functionText = "var results = null;"+ - "results = (function(msg,__send__,__done__){ "+ + "results = (async function(msg,__send__,__done__){ "+ "var __msgid__ = msg._msgid;"+ "var node = {"+ "id:__node__.id,"+ @@ -87,11 +120,13 @@ module.exports = function(RED) { "send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+ "done:__done__"+ "};\n"+ - this.func+"\n"+ + node.func+"\n"+ "})(msg,send,done);"; - this.topic = n.topic; - this.outstandingTimers = []; - this.outstandingIntervals = []; + var finScript = null; + var finOpt = null; + node.topic = n.topic; + node.outstandingTimers = []; + node.outstandingIntervals = []; var sandbox = { console:console, util:util, @@ -182,12 +217,12 @@ module.exports = function(RED) { arguments[0] = function() { sandbox.clearTimeout(timerId); try { - func.apply(this,arguments); + func.apply(node,arguments); } catch(err) { node.error(err,{}); } }; - timerId = setTimeout.apply(this,arguments); + timerId = setTimeout.apply(node,arguments); node.outstandingTimers.push(timerId); return timerId; }, @@ -203,12 +238,12 @@ module.exports = function(RED) { var timerId; arguments[0] = function() { try { - func.apply(this,arguments); + func.apply(node,arguments); } catch(err) { node.error(err,{}); } }; - timerId = setInterval.apply(this,arguments); + timerId = setInterval.apply(node,arguments); node.outstandingIntervals.push(timerId); return timerId; }, @@ -226,37 +261,48 @@ module.exports = function(RED) { sandbox.setTimeout(function(){ resolve(value); }, after); }); }; + sandbox.promisify = util.promisify; } var context = vm.createContext(sandbox); try { - this.script = vm.createScript(functionText, { - filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces - displayErrors: true - // Using the following options causes node 4/6 to not include the line number - // in the stack output. So don't use them. - // lineOffset: -11, // line number offset to be used for stack traces - // columnOffset: 0, // column number offset to be used for stack traces - }); - this.on("input", function(msg,send,done) { - try { - var start = process.hrtime(); - context.msg = msg; - context.send = send; - context.done = done; + var iniScript = null; + var iniOpt = null; + if (node.ini && (node.ini !== "")) { + var iniText = "(async function () {\n"+node.ini +"\n})();"; + iniOpt = createVMOpt(node, " setup"); + iniScript = new vm.Script(iniText, iniOpt); + } + node.script = vm.createScript(functionText, createVMOpt(node, "")); + if (node.fin && (node.fin !== "")) { + var finText = "(function () {\n"+node.fin +"\n})();"; + finOpt = createVMOpt(node, " cleanup"); + finScript = new vm.Script(finText, finOpt); + } + var promise = Promise.resolve(); + if (iniScript) { + promise = iniScript.runInContext(context, iniOpt); + } - this.script.runInContext(context); - sendResults(this,send,msg._msgid,context.results,false); + function processMessage(msg, send, done) { + var start = process.hrtime(); + context.msg = msg; + context.send = send; + context.done = done; + + node.script.runInContext(context); + context.results.then(function(results) { + sendResults(node,send,msg._msgid,results,false); if (handleNodeDoneCall) { done(); } var duration = process.hrtime(start); var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100; - this.metric("duration", msg, converted); + node.metric("duration", msg, converted); if (process.env.NODE_RED_FUNCTION_TIME) { - this.status({fill:"yellow",shape:"dot",text:""+converted}); + node.status({fill:"yellow",shape:"dot",text:""+converted}); } - } catch(err) { + }).catch(err => { if ((typeof err === "object") && err.hasOwnProperty("stack")) { //remove unwanted part var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/); @@ -294,23 +340,67 @@ module.exports = function(RED) { else { done(JSON.stringify(err)); } + }); + } + + const RESOLVING = 0; + const RESOLVED = 1; + const ERROR = 2; + var state = RESOLVING; + var messages = []; + + node.on("input", function(msg,send,done) { + if(state === RESOLVING) { + messages.push({msg:msg, send:send, done:done}); + } + else if(state === RESOLVED) { + processMessage(msg, send, done); } }); - this.on("close", function() { + node.on("close", function() { + if (finScript) { + try { + finScript.runInContext(context, finOpt); + } + catch (err) { + node.error(err); + } + } while (node.outstandingTimers.length > 0) { clearTimeout(node.outstandingTimers.pop()); } while (node.outstandingIntervals.length > 0) { clearInterval(node.outstandingIntervals.pop()); } - this.status({}); + node.status({}); }); - } catch(err) { + + promise.then(function (v) { + var msgs = messages; + messages = []; + while (msgs.length > 0) { + msgs.forEach(function (s) { + processMessage(s.msg, s.send, s.done); + }); + msgs = messages; + messages = []; + } + state = RESOLVED; + }).catch((error) => { + messages = []; + state = ERROR; + node.error(error); + }); + + } + catch(err) { // eg SyntaxError - which v8 doesn't include line number information // so we can't do better than this - this.error(err); + updateErrorInfo(err); + node.error(err); } } RED.nodes.registerType("function",FunctionNode); RED.library.register("functions"); }; + diff --git a/packages/node_modules/@node-red/nodes/core/function/16-range.html b/packages/node_modules/@node-red/nodes/core/function/16-range.html index 77d123d56..f108a99ce 100644 --- a/packages/node_modules/@node-red/nodes/core/function/16-range.html +++ b/packages/node_modules/@node-red/nodes/core/function/16-range.html @@ -1,5 +1,5 @@ - diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 4debeacde..cda1afadf 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -24,6 +24,8 @@ module.exports = function(RED) { this.op2 = n.op2 || "0"; this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; + this.second = (n.outputs == 2) ? true : false; + this.topic = n.topic || "topic"; if (this.op1type === 'val') { if (this.op1 === 'true' || this.op1 === 'false') { @@ -111,8 +113,15 @@ module.exports = function(RED) { processMessageQueue(msg); }); + var stat = function() { + var l = Object.keys(node.topics).length; + if (l === 0) { return {} } + else if (l === 1) { return {fill:"blue",shape:"dot"} } + else return {fill:"blue",shape:"dot",text:l}; + } + var processMessage = function(msg) { - var topic = msg.topic || "_none"; + var topic = RED.util.getMessageProperty(msg,node.topic) || "_none"; var promise; if (node.bytopic === "all") { topic = "_none"; } node.topics[topic] = node.topics[topic] || {}; @@ -120,7 +129,7 @@ module.exports = function(RED) { if (node.loop === true) { clearInterval(node.topics[topic].tout); } else { clearTimeout(node.topics[topic].tout); } delete node.topics[topic]; - node.status({}); + node.status(stat()); } else { if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); } @@ -189,27 +198,29 @@ module.exports = function(RED) { } promise.then(() => { if (node.op2type === "payl") { - node.send(npay[topic]); + if (node.second === true) { node.send([null,npay[topic]]); } + else { node.send(npay[topic]); } delete npay[topic]; } - else { + else { msg2.payload = node.topics[topic].m2; - node.send(msg2); + if (node.second === true) { node.send([null,msg2]); } + else { node.send(msg2); } } delete node.topics[topic]; - node.status({}); + node.status(stat()); }).catch(err => { node.error(err); }); } else { delete node.topics[topic]; - node.status({}); + node.status(stat()); } }, node.duration); } } - node.status({fill:"blue",shape:"dot",text:" "}); + node.status(stat()); if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); } }); }); @@ -245,13 +256,17 @@ module.exports = function(RED) { } } delete node.topics[topic]; - node.status({}); - node.send(msg2); + node.status(stat()); + if (node.second === true) { node.send([null,msg2]); } + else { node.send(msg2); } }).catch(err => { node.error(err); }); }, node.duration); } + // else { + // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + // } } return Promise.resolve(); } @@ -264,7 +279,7 @@ module.exports = function(RED) { delete node.topics[t]; } } - node.status({}); + node.status(stat()); }); } RED.nodes.registerType("trigger",TriggerNode); diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.html b/packages/node_modules/@node-red/nodes/core/function/90-exec.html index c6102d397..4a4e8942c 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.html +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html index 228da487b..1ece7efe0 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html index 2b22418dc..51ec15c6f 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html @@ -14,13 +14,13 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html index 69447bc60..ff515e781 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html index b17012e72..12bb3684c 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html index 09e6aef34..666889be6 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html @@ -14,7 +14,7 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html index b9231663d..7b081976c 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html index 4a28cfcab..bc6e4ad31 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html @@ -14,7 +14,7 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html index 7eb75b10a..d034f71e1 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html index 03beddb8d..24b15aa29 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html index efe82cbb1..e63c93ae5 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html @@ -14,12 +14,12 @@ limitations under the License. --> - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html index ec07a865e..fffcbcec5 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html @@ -14,13 +14,13 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html index eb7b00dff..19be36aed 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html @@ -14,7 +14,7 @@ limitations under the License. --> - + diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html index 45f658c05..3a4594ccc 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html index 4357c7f2b..cdfccd799 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - - - - - diff --git a/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html index 1c665bb8d..53c61e679 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html @@ -14,12 +14,12 @@ limitations under the License. --> - - - - - -