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..26a0606fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,15 @@ sudo: false +addons: + chrome: stable language: node_js matrix: include: + - node_js: "14" - node_js: "12" - node_js: "10" script: - ./node_modules/.bin/grunt && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage + - scripts/install-ui-test-dependencies.sh && grunt test-ui before_script: - npm install -g istanbul coveralls - node_js: "8" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d24243fb..f2993c591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,195 @@ +### 1.1.0: Milestone Release + +Editor + + - Align node labels on FF + - Fix node toggle button initial opacity + - Make color/icon/label-pos pickers keyboard navigable + - Default group label to be shown and improve toggle button + - Fix clearing group label + - Remove hardcoded css Fixes #2603 + - Fix node button mouse pointer css + - Change node linebreak handling to use "\n " + - Handle import of node with non-default number of outputs + - Improve display of focussed form element + - Fix typedInput error on empty subflow input types #2624 (@HiroyasuNishiyama) + - Update JP message catalogue for subflow input type #2471 (@HiroyasuNishiyama) + - Outliner - add empty item when last config node moved + - Update zh-CN/zh-TW translations #2626 (@JiyeYu) + - Add default shortcut for `core:show-help-tab` + - Clear outline focus on config node sidebar panel + - Tweak group margin to fit node status and look better + - Fix reparenting nodes in outliner when they change + +Runtime + + - Add developer options - permits npm run build-dev #2628 (@Steve-Mcl) + +Nodes + - Add example flows for lots of core nodes #2585 #2550 #2549 (@HiroyasuNishiyama) + - TCP: Fix tcp in node finishing packets when in streaming base64 receive mode. + - Join: Clear timeout when msg.reset received Fixes #2471 + - Switch: JSONata expr does not require msg.parts.count + - Inject: fix backwards migration of inject without topic + +#### 1.1.0-beta.3: Beta Release + +Editor + + - Fix wiring nodes from input back to output + - Fix sometimes unable to keyboard-move group to left/up + - Fix group position in outliner + - Handle unknown nodes with no icon + - Prevent node creep when switching tabs + +#### 1.1.0-beta.2: Beta Release + +Editor + + - Add UI tests to travis build #2593 #2616 #2617 #2619 (@kazuhitoyokoi) + - Add Japanese translations for outliner, jsonata and runtime #2618 (@kazuhitoyokoi) + - Fix deleting node in group after changing selection + - Fixup padding of quick-add search box + - Move config nodes under type-level hierarchy in outline + - Emit nodes:change event for config node users list modified + - Increase group margin to avoid clash with status text + - Fix event order when quick-adding node to group + - Switch RED.events.DEBUG messages to warn to get stacktraces + - Fix empty item handling for subflows/config in outliner + - Fix search indexing of group nodes + - Avoid regenerating every node label on redraw + - Fix handling of multi-line node label + - Disable merge group menu for single item or non-group item #2611 (@HiroyasuNishiyama) + - Merge pull request #2609 from node-red-hitachi/fix-remove-from-group + - Fix position of empty group with multi-line label #2612 (@HiroyasuNishiyama) + - Make treelist of subflow/config nodes initially have empty placeholder + - Fix empty placeholder not shown on remove from group #2609 (@HiroyasuNishiyama) + - Prevent conversion of circular structure #2607 (@HiroyasuNishiyama) + - Handle null status text in the editor Fixes #2606 + - Massively reduce our dependency on d3 to render the view + - EditableList/TreeList - defer adding elements to DOM + - Prevent RED.stop being called multiple times if >1 signal received + - Flag a node as removed when it is disabled + - Some performance improvements for TreeList + - Resize info/help sidebars whenever sidebar is opened + - Add search defaults to outliner searchBox + - Add search presets option to searchBox widget + - Add RED.popover.menu as a new type of menu widget + - Add support for is:XYZ search flags + - Track subflow instances on the subflow node itself + - Refresh outline filter whenever something changes Fixes #2601 + - Fix Help tab search box appearance + - Rename Node Information to Information in sidebar + - Do a sync-redraw after clearing to ensure clean state + - Make catch/status/complete/link filter case-insensitive + - Add 'add' option to touch radialMenu for quick-add dialog + - Merge branch 'dev' of https://github.com/node-red/node-red into dev + - ensure trigger node detects changes to number of outputs + - Ignore whitespace when checking function setup/close code + - Preserve event handlers when moving outliner items + - Add tooltips to outliner buttons + - Only validate nodes once they have all been imported + - Ensure configNode.users is updated properly on import + +Runtime + + - Bump node-red-admin 0.2.6 + +Nodes + + - WebSocket: Prevent charAt call on websocket listener #2610 () + - Debug: fix status to migrate old nodes to correct default mode. + - Link: Fix Link node filter Fixes #2600 + + +#### 1.1.0-beta.1: Beta Release + +Runtime + + - Allow HTTPS settings to be refreshed #2551 (@bartbutenaers) + - Add support for moment in JSONata expressions #2583 (@dxdc) + - Add httpAdminMiddleware for admin routes #2555 + - Add admin api authentication function #2479 (@KazuhiroItoh) + - Add option support for overwriting settings.js #2463 (@HiroyasuNishiyama) + - Add support for credential-stored env var in subflow #2368 + - Add node installation from other than public site #2378 (@KazuhiroItoh) + - Catch more signals to allow clean context flush on shutdown #2447 + - Add `node-red admin` command #2592 + - Move to `lodash.clonedeep` #2396 (@amodelbello) + - Tidy up unhandledRejection warning from context unit tests + - Add test cases for setMessageProperty with non-object properties + - Fix for settings.set subsequent updates #2584 (@sammachin) + - Turn off installer funding messages + - Remove unused \_info/\_type subflow env var magic values + - Add #! lines to project shell scripts #2548 + - Add nodejs14 to Travis test matrix + - Remove duplicate NLS message #2516 (@alexk111) + - Let setMessageProperty return success flag #2439 + +Editor + + - Add ability to group nodes #2493 + - Add loading progress bar #2558 + - Add Outliner to Info sidebar and add help sidebar #2556 + - Add action to toggle node label visibility #2569 + - Add show-examples-import-dialog action + - Add more consistent events in the editor #2543 + - Save the node description property to the library #2490 (@kazuhitoyokoi) + - Add credential type to TypedInput #2367 + - Scroll the view with WASD/Cursor keys when nothing selected #2381 + - Bump jquery/migrate to latest versions + - Fix editor underscore visibility on Linux systems #2579 (@ristomatti) + - Support setting title on typedInput multi-option #2586 (@Steve-Mcl) + - Projects: Allow remote branch dialog to create non-default remote branches + - Ensure auth failure on project fetch identifies the remote #2545 + - Make all dialogs handle smaller height screens better + - Add basic Array.from polyfill for IE11 + - Add some more trap form elements to workaround Chrome autofill + - [info-sidebar] Handle node/group/flows with \n in their name + - [popover] Allow hover-type popovers to contain buttons + - Modify RED.panels to use flexbox position + - Allow node edit dialog to be opened on a non-default tab + - Add createNodeIcon and getDarkerColor to RED.utils + - [search] Refactor search to use editor events to generate index + - Allow RED.notify.popover to have a position offset + - Make selected list item more distinct + - Allow node button to be clicked via api call + - Reorder initial load so projects:load event emits before any nodes:add + - Add polyfills for IE11 + - Activate project menu after initial clone #2547 (@HiroyasuNishiyama) + - Fix replacement of unknown node in workspace when module installed #2524 (@HiroyasuNishiyama) + - Fix appearance of subflow template panel #2506 (@HiroyasuNishiyama) + - Fix workspace CSS properties syntax #2487 (@bonanitech) + - Consolidate duplicate selectors #2488 (@bonanitech) + - Update message catalogue for subflow UI #2466 (@HiroyasuNishiyama) + +Nodes + + - Batch: Add reset feature to batch node #2553 (@HiroyasuNishiyama) + - Catch/Complete/Link/Status: #2588 Add compact searchBox to filter node lists + - Catch/Complete/Link/Status: Allow searchBox filter to filter on node type #2595 (@jeancarl) + - CSV: Add warn when unpaired quotes detected on input. + - CSV: allow node to only send headers once + - CSV: Allow CR and LF control chars to be a part of the value #2526 (@tmdoit) + - CSV: Add support for parsing empty strings and null values #2510 (@tmdoit) + - CSV: Update Japanese translations for CSV node #2562 (@kazuhitoyokoi) + - Debug: Add bulk-activate/deactive actions for debug node #2570 (@cinhcet) + - Debug: Show status independently of main output #2564 + - Delay: Ensure delay node rate limit timer is cleared on reset + - Function: Make the function node top-level async + - Function: Add support of initialization & finalization to function node #2498 (@HiroyasuNishiyama) + - HTTP In: Remove nodejs deprecation warning #2540 (@vladimir-kazan) + - HTTP Request: Support sending body in GET requests #2478 (@hardillb) + - Inject: Adding user definable properties to inject node #2435 (@PaulWieland) + - TCP: Allow to know particular session from status node #2413 (@dvv) + - Trigger: Add optional second output + - Trigger: Ensure trigger sends complete 2nd msg if set to send latest msg + - Trigger: Allow trigger node to use other than msg.topic to separate streams + - XML: Moved XML options documentation property from Outputs to Inputs section #2572 (@jeancarl) + - Add some core node example flows #2455 (@HiroyasuNishiyama) + - Change types from text/x-red to text/html in node html files #2425 (@kazuhitoyokoi) + + #### 1.0.6: Maintenance Release Runtime @@ -119,7 +311,7 @@ Runtime - #2332 Fix error handling of nodes with multiple input handlers - Add script to generate npm publish script - #2371 Ensure folder is present before write (e.g. flows file not in user folder) - - #2371 Handle windows UNC '\\' paths + - #2371 Handle windows UNC '\' paths - #2366 Handle logging of non-JSON encodable objects Editor diff --git a/Gruntfile.js b/Gruntfile.js index 7593fded9..8c18dc0fb 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", @@ -618,6 +623,10 @@ module.exports = function(grunt) { grunt.registerTask('build', 'Builds editor content', ['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']); + + grunt.registerTask('build-dev', + 'Developer mode: build dev version', + ['clean:build','concat:build','concat:vendor','copy:build','sass:build','setDevEnv']); grunt.registerTask('dev', 'Developer mode: run node-red, watch for source changes and build/restart', diff --git a/package.json b/package.json index 1dd0937ba..cd30686ce 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", @@ -13,6 +13,8 @@ "start": "node packages/node_modules/node-red/red.js", "test": "grunt", "build": "grunt build", + "dev": "grunt dev", + "build-dev": "grunt build-dev", "docs": "grunt docs" }, "contributors": [ @@ -24,20 +26,20 @@ } ], "dependencies": { - "ajv": "6.12.0", + "ajv": "6.12.2", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "cheerio": "0.22.0", "clone": "2.1.2", "content-type": "1.0.4", - "cookie": "0.4.0", + "cookie": "0.4.1", "cookie-parser": "1.4.5", "cors": "2.8.5", "cron": "1.7.2", "denque": "1.4.1", "express": "4.17.1", - "express-session": "1.17.0", + "express-session": "1.17.1", "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", @@ -45,16 +47,19 @@ "i18next": "15.1.2", "iconv-lite": "0.5.1", "is-utf8": "0.2.1", - "js-yaml": "3.13.1", + "js-yaml": "3.14.0", "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", + "mime": "2.4.6", + "moment-timezone": "^0.5.31", "mqtt": "2.18.8", "multer": "1.4.2", "mustache": "4.0.1", - "node-red-node-rbe": "^0.2.6", + "node-red-admin": "^0.2.6", + "node-red-node-rbe": "^0.2.9", "node-red-node-sentiment": "^0.1.6", "node-red-node-tail": "^0.1.0", "nopt": "4.0.3", @@ -66,7 +71,7 @@ "raw-body": "2.4.1", "request": "2.88.0", "semver": "6.3.0", - "uglify-js": "3.8.1", + "uglify-js": "3.9.4", "when": "3.7.8", "ws": "6.2.1", "xml2js": "0.4.23" @@ -76,7 +81,7 @@ }, "devDependencies": { "marked": "0.8.2", - "dompurify": "2.0.8", + "dompurify": "2.0.11", "grunt": "~1.0.4", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", @@ -97,14 +102,14 @@ "grunt-npm-command": "~0.1.2", "grunt-sass": "~3.1.0", "grunt-simple-mocha": "~0.4.1", - "http-proxy": "1.18.0", + "http-proxy": "1.18.1", "istanbul": "0.4.5", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", "minami": "1.2.3", "mocha": "^5.2.0", "mosca": "^2.8.3", - "node-red-node-test-helper": "^0.2.3", - "node-sass": "^4.13.1", + "node-red-node-test-helper": "^0.2.5", + "node-sass": "^4.14.1", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.1.0", 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..16f82f676 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,16 +16,16 @@ } ], "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", "cors": "2.8.5", - "express-session": "1.17.0", + "express-session": "1.17.1", "express": "4.17.1", "memorystore": "1.6.2", - "mime": "2.4.4", + "mime": "2.4.6", "mustache": "4.0.1", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", 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..309f9db89 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,15 @@ "back": "Back", "next": "Next", "clone": "Clone project", - "cont": "Continue" + "cont": "Continue", + "style": "Style", + "line": "Outline", + "fill": "Fill", + "label": "Label", + "color": "Color", + "position": "Position", + "enable": "Enable", + "disable": "Disable" }, "type": { "string": "string", @@ -28,6 +36,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 +106,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 +191,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 +208,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 +333,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", @@ -337,6 +369,7 @@ "locale": "Select UI Language", "icon": "Icon", "inputType": "Input type", + "selectType": "select types...", "inputs" : { "input": "input", "select": "select", @@ -351,7 +384,8 @@ "bool": "bool", "json": "JSON", "bin": "buffer", - "env": "env variable" + "env": "env variable", + "cred": "credential" }, "menu": { "input": "input", @@ -533,11 +567,12 @@ }, "sidebar": { "info": { - "name": "Node information", + "name": "Information", "tabName": "Name", "label": "info", "node": "Node", "type": "Type", + "group": "Group", "module": "Module", "id": "ID", "status": "Status", @@ -560,7 +595,29 @@ "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", + "triggerAction": "Trigger action", + "find": "Find in workspace", + "search": { + "configNodes": "Configuration nodes", + "unusedConfigNodes": "Unused configuration nodes", + "invalidNodes": "Invalid nodes", + "uknownNodes": "Unknown nodes", + "unusedSubflows": "Unused subflows" + } + }, + "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 +670,6 @@ "removeFromProject": "remove from project", "addToProject": "add to project", "files": "Files", - "package": "Package", "flow": "Flow", "credentials": "Credentials", "package":"Package", @@ -757,7 +813,8 @@ "bin": "buffer", "date": "timestamp", "jsonata": "expression", - "env": "env variable" + "env": "env variable", + "cred": "credential" } }, "editableList": { @@ -804,9 +861,9 @@ "expandItems": "Expand items", "collapseItems": "Collapse items", "duplicate": "Duplicate", - "error": { - "invalidJSON": "Invalid JSON: " - } + "error": { + "invalidJSON": "Invalid JSON: " + } }, "markdownEditor": { "title": "Markdown editor", @@ -978,7 +1035,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/en-US/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json index d777d1919..57ebb00a7 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json @@ -266,5 +266,9 @@ "$type": { "args": "value", "desc": "Returns the type of `value` as a string. If `value` is undefined, this will return `undefined`" + }, + "$moment": { + "args": "[str]", + "desc": "Gets a date object using the Moment library." } } 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..5422fe440 --- 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,15 @@ "back": "戻る", "next": "進む", "clone": "プロジェクトをクローン", - "cont": "続ける" + "cont": "続ける", + "style": "形式", + "line": "線", + "fill": "塗りつぶし", + "label": "ラベル", + "color": "色", + "position": "配置", + "enable": "有効", + "disable": "無効" }, "type": { "string": "文字列", @@ -28,6 +36,13 @@ "null": "null" } }, + "event": { + "loadPalette": "パレットを読み込み中", + "loadNodeCatalogs": "ノードカタログを読み込み中", + "loadNodes": "ノードを読み込み中 __count__", + "loadFlows": "フローを読み込み中", + "importFlows": "ワークスペースにフローを追加中" + }, "workspace": { "defaultName": "フロー __number__", "editFlow": "フローを編集: __name__", @@ -67,7 +82,7 @@ "settings": "設定", "userSettings": "ユーザ設定", "nodes": "ノード", - "displayStatus": "ノードの状態を表示", + "displayStatus": "ノードのステータスを表示", "displayConfig": "ノードの設定", "import": "読み込み", "export": "書き出し", @@ -91,7 +106,12 @@ "projects-new": "新規", "projects-open": "開く", "projects-settings": "設定", - "showNodeLabelDefault": "追加したノードのラベルを表示" + "showNodeLabelDefault": "追加したノードのラベルを表示", + "groups": "グループ", + "groupSelection": "選択部分をグループ化", + "ungroupSelection": "選択部分をグループ解除", + "groupMergeSelection": "選択部分をマージ", + "groupRemoveSelection": "グループから削除" } }, "actions": { @@ -171,6 +191,8 @@ "node_plural": "__count__ 個のノード", "configNode": "__count__ 個の設定ノード", "configNode_plural": "__count__ 個の設定ノード", + "group": "__count__ 個のグループ", + "group_plural": "__count__ 個のグループ", "flow": "__count__ 個のフロー", "flow_plural": "__count__ 個のフロー", "subflow": "__count__ 個のサブフロー", @@ -186,6 +208,9 @@ "nodesImported": "読み込みました:", "nodeCopied": "__count__ 個のノードをコピーしました", "nodeCopied_plural": "__count__ 個のノードをコピーしました", + "groupCopied": "__count__ 個のグループをコピーしました", + "groupCopied_plural": "__count__ 個のグループをコピーしました", + "groupStyleCopied": "グループの形式をコピーしました", "invalidFlow": "不正なフロー: __message__", "export": { "selected": "選択したフロー", @@ -308,6 +333,13 @@ "multipleInputsToSelection": "サブフローを作成できません: 複数の入力が選択されています" } }, + "group": { + "editGroup": "__name__ グループを編集", + "errors": { + "cannotCreateDiffGroups": "異なるグループのノードを使用してグループを作成することはできません", + "cannotAddSubflowPorts": "グループにサブフローの端子を追加できません" + } + }, "editor": { "configEdit": "編集", "configAdd": "追加", @@ -337,6 +369,7 @@ "locale": "UI言語の選択", "icon": "記号", "inputType": "入力形式", + "selectType": "形式選択...", "inputs": { "input": "入力", "select": "メニュー", @@ -351,7 +384,8 @@ "bool": "真偽", "json": "JSON", "bin": "バッファ", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" }, "menu": { "input": "入力", @@ -538,6 +572,7 @@ "label": "情報", "node": "ノード", "type": "型", + "group": "グループ", "module": "モジュール", "id": "ID", "status": "状態", @@ -560,7 +595,29 @@ "nodeHelp": "ノードのヘルプ", "none": "なし", "arrayItems": "__count__ 要素", - "showTips": "設定からヒントを表示できます" + "showTips": "設定からヒントを表示できます", + "outline": "アウトライン", + "empty": "空", + "globalConfig": "グローバル設定ノード", + "triggerAction": "アクションを実行", + "find": "ワークスペース内を検索", + "search": { + "configNodes": "設定ノード", + "unusedConfigNodes": "未使用の設定ノード", + "invalidNodes": "不正なノード", + "uknownNodes": "未知のノード", + "unusedSubflows": "未使用のサブフロー" + } + }, + "help": { + "name": "ヘルプ", + "label": "ヘルプ", + "search": "ヘルプを検索", + "nodeHelp": "ノードヘルプ", + "showHelp": "ヘルプを表示", + "showInOutline": "アウトラインに表示", + "showTopics": "トピックを表示", + "noHelp": "ヘルプのトピックが未選択" }, "config": { "name": "ノードの設定を表示", @@ -613,9 +670,9 @@ "removeFromProject": "プロジェクトから削除", "addToProject": "プロジェクトへ追加", "files": "ファイル", - "package": "パッケージ", "flow": "フロー", "credentials": "認証情報", + "package": "パッケージ", "packageCreate": "変更が保存された時にファイルが作成されます", "fileNotExist": "ファイルが存在しません", "selectFile": "ファイルを選択", @@ -756,7 +813,8 @@ "bin": "バッファ", "date": "日時", "jsonata": "JSONata式", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" } }, "editableList": { @@ -976,7 +1034,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/locales/ja/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json old mode 100755 new mode 100644 index 659cf66df..02973a69a --- a/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json @@ -266,5 +266,9 @@ "$type": { "args": "value", "desc": "`value` の型を文字列として返します。もし `value` が未定義の場合、 `undefined` が返されます。" + }, + "$moment": { + "args": "[str]", + "desc": "Momentライブラリを使用して日付オブジェクトを取得します。" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index 95d3fa5ee..3da4a5822 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -14,7 +14,15 @@ "back": "后退", "next": "下一个", "clone": "克隆项目", - "cont": "继续" + "cont": "继续", + "style": "风格", + "line": "大纲", + "fill": "填充", + "label": "标签", + "color": "颜色", + "position": "位置", + "enable": "启用", + "disable": "禁用" }, "type": { "string": "字符串", @@ -28,11 +36,18 @@ "null": "空" } }, + "event": { + "loadPalette": "加载控制板", + "loadNodeCatalogs": "加载节点目录", + "loadNodes": "加载 __count__ 个节点", + "loadFlows": "加载流程", + "importFlows": "往工作区中加载流程" + }, "workspace": { - "defaultName": "流程__number__", + "defaultName": "流程 __number__", "editFlow": "编辑流程: __name__", "confirmDelete": "确认删除", - "delete": "你确定想删除 '__label__'?", + "delete": "你确定要删除 __label__ ?", "dropFlowHere": "把流程放到这里", "addFlow": "添加流程", "listFlows": "流程一览", @@ -91,7 +106,12 @@ "projects-new": "新建", "projects-open": "打开", "projects-settings": "项目设定", - "showNodeLabelDefault": "显示新添加的节点的标签" + "showNodeLabelDefault": "显示新添加的节点的标签", + "groups": "组", + "groupSelection": "选择组", + "ungroupSelection": "取消选择组", + "groupMergeSelection": "合并选择", + "groupRemoveSelection": "从组中移除" } }, "actions": { @@ -101,7 +121,7 @@ "zoom-in": "放大" }, "user": { - "loggedInAs": "作为__name__登陆", + "loggedInAs": "作为 __name__ 登陆", "username": "账号", "password": "密码", "login": "登陆", @@ -127,13 +147,13 @@ "missing_flow_file": "

找不到项目流程文件。

该项目未配置流程文件。

", "missing_package_file": "

找不到项目包文件。

项目缺少package.json文件。

", "project_empty": "

该项目为空。

是否要创建一组默认的项目文件?
否则,您将必须在编辑器外部手动将文件添加到项目中。

", - "project_not_found": "

未找到项目'__project__'。

", + "project_not_found": "

未找到项目 __project__ 。

", "git_merge_conflict": "

自动合并更改失败。

修复未合并的冲突,然后提交结果。

" }, "error": "错误: __message__", "errors": { "lostConnection": "丢失与服务器的连接,重新连接...", - "lostConnectionReconnect": "丢失与服务器的连接,__time__秒后重新连接", + "lostConnectionReconnect": "丢失与服务器的连接, __time__ 秒后重新连接", "lostConnectionTry": "现在尝试", "cannotAddSubflowToItself": "无法向其自身添加子流程", "cannotAddCircularReference": "无法添加子流程 - 循环引用", @@ -167,14 +187,16 @@ "clipboard": { "clipboard": "剪贴板", "nodes": "节点", - "node": "__count__节点", - "node_plural": "__count__节点", - "configNode": "__count__配置节点", - "configNode_plural": "__count__配置节点", - "flow": "__count__流程", - "flow_plural": "__count__流程", - "subflow": "__count__子流程", - "subflow_plural": "__count__子流程", + "node": "__count__ 个节点", + "node_plural": "__count__ 个节点", + "configNode": "__count__ 个配置节点", + "configNode_plural": "__count__ 个配置节点", + "group": "__count__ 个组", + "group_plural": "__count__ 个组", + "flow": "__count__ 个流程", + "flow_plural": "__count__ 个流程", + "subflow": "__count__ 个子流程", + "subflow_plural": "__count__ 子流程", "pasteNodes": "在这里粘贴节点", "selectFile": "选择要导入的文件", "importNodes": "导入节点", @@ -184,8 +206,11 @@ "importUnrecognised_plural": "导入了无法识别的类型:", "nodesExported": "节点导出到了剪贴板", "nodesImported": "导入:", - "nodeCopied": "已复制__count__个节点", - "nodeCopied_plural": "已复制__count__个节点", + "nodeCopied": "已复制 __count__ 个节点", + "nodeCopied_plural": "已复制 __count__ 个节点", + "groupCopied": "复制 __count__ 个组", + "groupCopied_plural": "已复制 __count__ 个groups", + "groupStyleCopied": "已复制组风格", "invalidFlow": "无效的流程: __message__", "export": { "selected": "已选择的节点", @@ -204,9 +229,9 @@ "newFlow": "新流程", "errors": { "notArray": "输入的不是JSON数组", - "itemNotObject": "输入的流无效 - 项目__index__不是节点对象", + "itemNotObject": "输入的流无效 - 项目 __index__ 不是节点对象", "missingId": "输入的流无效-项 __index__ 缺少'id'属性", - "missingType": "输入的流程无效-项__index__缺少'类型'属性" + "missingType": "输入的流程无效-项 __index__ 缺少'类型'属性" } }, "copyMessagePath": "已复制路径", @@ -250,7 +275,7 @@ "conflictChecking": "检查是否可以自动合并更改", "conflictAutoMerge": "此更改不包括冲突,可以自动合并", "conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。", - "plusNMore": "+ __count__更多" + "plusNMore": "+ __count__ 更多" } }, "eventLog": { @@ -287,11 +312,11 @@ "newVersionError": "新版本不包含有效的JSON:" }, "subflow": { - "editSubflowInstance": "编辑子流实例:__name__", - "editSubflow": "编辑流程模板: __name__", + "editSubflowInstance": "编辑子流实例: __name__", + "editSubflow": "编辑流程模板: __name__", "edit": "编辑流程模板", - "subflowInstances": "这个子流程模板有__count__个实例", - "subflowInstances_plural": "这个子流程模板有__count__个实例", + "subflowInstances": "这个子流程模板有 __count__ 个实例", + "subflowInstances_plural": "这个子流程模板有 __count__ 个实例", "editSubflowProperties": "编辑属性", "input": "输入:", "output": "输出:", @@ -308,17 +333,24 @@ "multipleInputsToSelection": "无法创建子流程: 多个输入到了选择" } }, + "group": { + "editGroup": "编辑组: __name__", + "errors": { + "cannotCreateDiffGroups": "无法使用来自不同组的节点创建组", + "cannotAddSubflowPorts": "无法将子流程的端口添加到组" + } + }, "editor": { "configEdit": "编辑", "configAdd": "添加", "configUpdate": "更新", "configDelete": "删除", - "nodesUse": "__count__个节点使用此配置", - "nodesUse_plural": "__count__个节点使用此配置", - "addNewConfig": "添加新的__type__配置", - "editNode": "编辑__type__节点", - "editConfig": "编辑__type__配置", - "addNewType": "添加新的__type__节点", + "nodesUse": "__count__ 个节点使用此配置", + "nodesUse_plural": "__count__ 个节点使用此配置", + "addNewConfig": "添加新的 __type__ 配置", + "editNode": "编辑 __type__ 节点", + "editConfig": "编辑 __type__ 配置", + "addNewType": "添加新的 __type__ 节点", "nodeProperties": "节点属性", "label": "标签", "color": "颜色", @@ -337,6 +369,7 @@ "locale": "选择界面语言", "icon": "图标", "inputType": "输入类型", + "selectType": "选择类型...", "inputs": { "input": "输入", "select": "选择", @@ -351,7 +384,8 @@ "bool": "布尔", "json": "JSON", "bin": "buffer", - "env": "环境变量" + "env": "环境变量", + "cred": "证书" }, "menu": { "input": "输入", @@ -381,7 +415,7 @@ "scope": "范围", "unassigned": "未分配", "global": "全局", - "workspace": "工作组", + "workspace": "工作区", "selectAll": "选择所有节点", "selectAllConnected": "选择所有连接的节点", "addRemoveNode": "从选择中添加/删除节点", @@ -460,33 +494,33 @@ "times": { "seconds": "秒前", "minutes": "分前", - "minutesV": "__count__分前", - "hoursV": "__count__小时前", - "hoursV_plural": "__count__小时前", - "daysV": "__count__天前", - "daysV_plural": "__count__天前", - "weeksV": "__count__周前", - "weeksV_plural": "__count__周前", - "monthsV": "__count__月前", - "monthsV_plural": "__count__月前", - "yearsV": "__count__年前", - "yearsV_plural": "__count__年前", - "yearMonthsV": "__y__年, __count__月前", - "yearMonthsV_plural": "__y__年, __count__月前", - "yearsMonthsV": "__y__年, __count__月前", - "yearsMonthsV_plural": "__y__年, __count__月前" + "minutesV": "__count__ 分前", + "hoursV": "__count__ 小时前", + "hoursV_plural": "__count__ 小时前", + "daysV": "__count__ 天前", + "daysV_plural": "__count__ 天前", + "weeksV": "__count__ 周前", + "weeksV_plural": "__count__ 周前", + "monthsV": "__count__ 月前", + "monthsV_plural": "__count__ 月前", + "yearsV": "__count__ 年前", + "yearsV_plural": "__count__ 年前", + "yearMonthsV": "__y__ 年, __count__ 月前", + "yearMonthsV_plural": "__y__ 年, __count__ 月前", + "yearsMonthsV": "__y__ 年, __count__ 月前", + "yearsMonthsV_plural": "__y__ 年, __count__ 月前" }, - "nodeCount": "__label__个节点", - "nodeCount_plural": "__label__个节点", - "moduleCount": "__count__个可用模块", - "moduleCount_plural": "__count__个可用模块", + "nodeCount": "__label__ 个节点", + "nodeCount_plural": "__label__ 个节点", + "moduleCount": "__count__ 个可用模块", + "moduleCount_plural": "__count__ 个可用模块", "inuse": "使用中", "enableall": "全部启用", "disableall": "全部禁用", "enable": "启用", "disable": "禁用", "remove": "移除", - "update": "更新至__version__版本", + "update": "更新至 __version__ 版本", "updated": "已更新", "install": "安装", "installed": "已安装", @@ -498,7 +532,7 @@ "sort": "排序:", "sortAZ": "a-z顺序", "sortRecent": "日期顺序", - "more": "增加__count__个", + "more": "增加 __count__ 个", "errors": { "catalogLoadFailed": "无法加载节点目录。
查看浏览器控制台了解更多信息", "installFailed": "无法安装: __module__
__message__
查看日志了解更多信息", @@ -539,6 +573,7 @@ "label": "信息", "node": "节点", "type": "类型", + "group": "组", "module": "模组", "id": "ID", "status": "状态", @@ -560,8 +595,30 @@ "subflowDesc": "子流程描述", "nodeHelp": "节点帮助", "none": "无", - "arrayItems": "__count__个项目", - "showTips": "您可以从设置面板启用提示信息" + "arrayItems": "__count__ 个项目", + "showTips": "您可以从设置面板启用提示信息", + "outline": "大纲", + "empty": "空的", + "globalConfig": "全局配置节点", + "triggerAction": "触发动作", + "find": "在工作区中查找", + "search": { + "configNodes": "配置节点", + "unusedConfigNodes": "未使用的配置节点", + "invalidNodes": "无效的节点", + "uknownNodes": "未知的节点", + "unusedSubflows": "未使用的子流程" + } + }, + "help": { + "name": "帮助", + "label": "帮助", + "search": "搜索帮助", + "nodeHelp": "节点帮助", + "showHelp": "显示帮助", + "showInOutline": "在大纲中显示", + "showTopics": "显示主题", + "noHelp": "未选择帮助主题" }, "config": { "name": "配置节点", @@ -614,9 +671,9 @@ "removeFromProject": "从项目中删除", "addToProject": "添加到项目", "files": "文件", - "package": "包", "flow": "流程", "credentials": "证书", + "package": "包", "packageCreate": "保存更改后将创建文件", "fileNotExist": "文件不存在", "selectFile": "选择文件", @@ -628,7 +685,7 @@ "changeTheEncryptionKey": "更改加密密钥", "currentKey": "当前密钥", "newKey": "新密钥", - "credentialsAlert": "这将删除所有现有凭证", + "credentialsAlert": "这将删除所有现有证书", "versionControl": "版本控制", "branches": "分支", "noBranches": "没有分支", @@ -668,7 +725,7 @@ "copyPublicKey": "将公钥复制到剪贴板", "delete": "删除密钥", "gitConfig": "Git配置", - "deleteConfirm": "您确定要删除SSH密钥__name__吗?这不能被撤消。" + "deleteConfirm": "您确定要删除SSH密钥 __name__ 吗?这不能被撤消。" }, "versionControl": { "unstagedChanges": "未暂存的变更", @@ -722,24 +779,24 @@ "pullChanges": "拉取更改", "history": "历史", "projectHistory": "项目历史", - "daysAgo": "__count__天前", - "daysAgo_plural": "__count__天前", - "hoursAgo": "__count__小时前", - "hoursAgo_plural": "__count__小时前", - "minsAgo": "__count__分钟前", - "minsAgo_plural": "__count__分钟前", + "daysAgo": "__count__ 天前", + "daysAgo_plural": "__count__ 天前", + "hoursAgo": "__count__ 小时前", + "hoursAgo_plural": "__count__ 小时前", + "minsAgo": "__count__ 分钟前", + "minsAgo_plural": "__count__ 分钟前", "secondsAgo": "秒前", "notTracking": "您的本地分支当前未跟踪一个远程分支。", "statusUnmergedChanged": "您的仓库中有未合并的更改。您需要解决冲突并提交结果。", "repositoryUpToDate": "您的仓库是最新的。", - "commitsAhead": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。", - "commitsAhead_plural": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。", - "commitsBehind": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。", - "commitsBehind_plural": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。", - "commitsAheadAndBehind1": "您的存储库落后远程仓库__count__次提交", - "commitsAheadAndBehind1_plural": "您的存储库落后远程仓库__count__次提交", - "commitsAheadAndBehind2": "领先远程仓库__count__次提交。", - "commitsAheadAndBehind2_plural": "领先远程仓库__count__次提交。", + "commitsAhead": "您的存储库领先远程仓库 __count__ 次提交。您现在可以推送这些提交。", + "commitsAhead_plural": "您的存储库领先远程仓库 __count__ 次提交。您现在可以推送这些提交。", + "commitsBehind": "您的存储库落后远程仓库 __count__ 次提交。您现在可以拉取这些提交。", + "commitsBehind_plural": "您的存储库落后远程仓库 __count__ 次提交。您现在可以拉取这些提交。", + "commitsAheadAndBehind1": "您的存储库落后远程仓库 __count__ 次提交", + "commitsAheadAndBehind1_plural": "您的存储库落后远程仓库 __count__ 次提交", + "commitsAheadAndBehind2": "领先远程仓库 __count__ 次提交。", + "commitsAheadAndBehind2_plural": "领先远程仓库 __count__ 次提交。", "commitsAheadAndBehind3": "您必须先拉取远程提交,然后才能进行推送。", "commitsAheadAndBehind3_plural": "您必须先拉取远程提交,然后才能进行推送。", "refreshCommitHistory": "刷新提交历史", @@ -757,7 +814,8 @@ "bin": "二进制流", "date": "时间戳", "jsonata": "表达式", - "env": "环境变量" + "env": "环境变量", + "cred": "证书" } }, "editableList": { @@ -977,7 +1035,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/locales/zh-CN/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json index f27ec1f51..a9e6a7b1f 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json @@ -1,15 +1,15 @@ { "$string": { "args": "arg", - "desc": "通过以下的类型转换规则将参数*arg*转换成字符串:\n\n - 字符串不转换。\n -函数转换成空的字符串。\n - JSON的值无法用数字表示所以用无限大或者NaN(非数)表示。\n - 用’JSON.stringify’函数将其他值转换成JSON字符串。" + "desc": "通过以下的类型转换规则将参数 *arg* 转换成字符串:\n\n - 字符串不转换。\n -函数转换成空的字符串。\n - JSON的值无法用数字表示所以用无限大或者NaN(非数)表示。\n - 用 `JSON.stringify` 函数将其他值转换成JSON字符串。" }, "$length": { "args": "str", - "desc": "输出字符串’str’的字数。如果’str’不是字符串,抛出错误。" + "desc": "输出字符串 `str` 的字数。如果 `str` 不是字符串,抛出错误。" }, "$substring": { "args": "str, start[, length]", - "desc": "输出`start`位置后的的首次出现的包括`str`的子字符串。 如果`length`被指定,那么的字符串中将只包括前`length`个文字。如果`start`是负数则输出从`str`末尾开始的`length`个文字" + "desc": "输出 `start` 位置后的的首次出现的包括 `str` 的子字符串。 如果 `length` 被指定,那么的字符串中将只包括前 `length` 个文字。如果 `start` 是负数则输出从 `str` 末尾开始的 `length` 个文字" }, "$substringBefore": { "args": "str, chars", @@ -17,11 +17,11 @@ }, "$substringAfter": { "args": "str, chars", - "desc": "输出’str’中首次出现的’chars’之后的子字符串,如果’str’中不包括’chars’则输出’str’。" + "desc": "输出 `str` 中首次出现的 `chars` 之后的子字符串,如果 `str` 中不包括 `chars` 则输出 `str` 。" }, "$uppercase": { "args": "str", - "desc": "`将’str’中的所有字母变为大写后输出。" + "desc": "将 `str` 中的所有字母变为大写后输出。" }, "$lowercase": { "args": "str", @@ -29,27 +29,27 @@ }, "$trim": { "args": "str", - "desc": "将以下步骤应用于`str`来去除所有空白文字并实现标准化。\n\n – 将全部tab制表符、回车键、换行字符用空白代替。\n- 将连续的空白文字变成一个空白文字。\n- 消除开头和末尾的空白文字。\n\n如果`str`没有被指定(即在无输入参数的情况下调用本函数),将上下文的值作为`str`来使用。 如果`str` 不是字符串则抛出错误。" + "desc": "将以下步骤应用于 `str` 来去除所有空白文字并实现标准化。\n\n – 将全部tab制表符、回车键、换行字符用空白代替。\n- 将连续的空白文字变成一个空白文字。\n- 消除开头和末尾的空白文字。\n\n如果 `str` 没有被指定(即在无输入参数的情况下调用本函数),将上下文的值作为 `str` 来使用。 如果 `str` 不是字符串则抛出错误。" }, "$contains": { "args": "str, pattern", - "desc": "字符串`str` 和 `pattern`匹配的话输出`true`,不匹配的情况下输出 `false`。 不指定`str`的情况下(比如用一个参数调用本函数时)、将上下文的值作为`str`来使用。参数 `pattern`可以为字符串或正则表达。" + "desc": "字符串 `str` 和 `pattern` 匹配的话输出 `true` ,不匹配的情况下输出 `false` 。 不指定 `str` 的情况下(比如用一个参数调用本函数时)、将上下文的值作为 `str` 来使用。参数 `pattern` 可以为字符串或正则表达。" }, "$split": { "args": "str[, separator][, limit]", - "desc": "将参数`str`分解成由子字符串组成的数组。 如果`str`不是字符串抛出错误。可以省略的参数 `separator`中指定字符串`str`的分隔符。分隔符可以是文字或正则表达式。在不指定`separator`的情况下、将分隔符看作空的字符串并把`str`拆分成由单个字母组成的数组。如果`separator`不是字符串则抛出错误。在可省略的参数`limit`中指定分割后的子字符串的最大个数。超出个数的子字符串将被舍弃。如果`limit`没有被指定,`str` 将不考虑子字符串的个数而将字符串完全分隔。如果`limit`是负数则抛出错误。" + "desc": "将参数 `str` 分解成由子字符串组成的数组。 如果 `str` 不是字符串抛出错误。可以省略的参数 `separator` 中指定字符串 `str` 的分隔符。分隔符可以是文字或正则表达式。在不指定 `separator` 的情况下、将分隔符看作空的字符串并把 `str` 拆分成由单个字母组成的数组。如果 `separator` 不是字符串则抛出错误。在可省略的参数 `limit` 中指定分割后的子字符串的最大个数。超出个数的子字符串将被舍弃。如果 `limit` 没有被指定,`str` 将不考虑子字符串的个数而将字符串完全分隔。如果 `limit` 是负数则抛出错误。" }, "$join": { "args": "array[, separator]", - "desc": "用可以省略的参数 `separator`来把多个字符串连接。如果`array`不是字符串则抛出错误。 如果没有指定`separator`,则用空字符串来连接字符(即字符串之间没有`separator`)。 如果`separator`不是字符则抛出错误。" + "desc": "用可以省略的参数 `separator` 来把多个字符串连接。如果 `array` 不是字符串则抛出错误。 如果没有指定 `separator` ,则用空字符串来连接字符(即字符串之间没有 `separator` )。 如果 `separator` 不是字符则抛出错误。" }, "$match": { "args": "str, pattern [, limit]", - "desc": "对字符串`str`使用正则表达式`pattern`并输出与`str`相匹配的部分信息。" + "desc": "对字符串 `str` 使用正则表达式 `pattern` 并输出与 `str` 相匹配的部分信息。" }, "$replace": { "args": "str, pattern, replacement [, limit]", - "desc": "在字符串`str`中搜索`pattern`并用`replacement`来替换。\n\n可选参数`limit`用来指定替换次数的上限。" + "desc": "在字符串 `str` 中搜索 `pattern` 并用 `replacement` 来替换。\n\n可选参数 `limit` 用来指定替换次数的上限。" }, "$now": { "args": "", @@ -65,31 +65,31 @@ }, "$number": { "args": "arg", - "desc": "用下述的规则将参数 `arg`转换为数值。:\n\n – 数值不做转换。\n – 将字符串中合法的JSON数値表示转换成数値。\n – 其他形式的值则抛出错误。" + "desc": "用下述的规则将参数 `arg` 转换为数值。:\n\n – 数值不做转换。\n – 将字符串中合法的JSON数値表示转换成数値。\n – 其他形式的值则抛出错误。" }, "$abs": { "args": "number", - "desc": "输出参数`number`的绝对值。" + "desc": "输出参数 `number` 的绝对值。" }, "$floor": { "args": "number", - "desc": "输出比`number`的值小的最大整数。" + "desc": "输出比 `number` 的值小的最大整数。" }, "$ceil": { "args": "number", - "desc": "输出比`number`的值大的最小整数。" + "desc": "输出比 `number` 的值大的最小整数。" }, "$round": { "args": "number [, precision]", - "desc": "输出四舍五入后的参数`number`。可省略的参数 `precision`指定四舍五入后小数点下的位数。" + "desc": "输出四舍五入后的参数 `number` 。可省略的参数 `precision` 指定四舍五入后小数点下的位数。" }, "$power": { "args": "base, exponent", - "desc": "输出底数`base`的`exponent`次幂。" + "desc": "输出底数 `base` 的 `exponent` 次幂。" }, "$sqrt": { "args": "number", - "desc": "输出参数 `number`的平方根。" + "desc": "输出参数 `number` 的平方根。" }, "$random": { "args": "", @@ -97,35 +97,35 @@ }, "$millis": { "args": "", - "desc": "返回从UNIX时间 (1970年1月1日 UTC/GMT的午夜)开始到现在的毫秒数。在同一个表达式的测试中所有对`$millis()`的调用将会返回相同的值。" + "desc": "返回从UNIX时间 (1970年1月1日 UTC/GMT的午夜)开始到现在的毫秒数。在同一个表达式的测试中所有对 `$millis()` 的调用将会返回相同的值。" }, "$sum": { "args": "array", - "desc": "输出数组`array`的总和。如果`array`不是数值则抛出错误。" + "desc": "输出数组 `array` 的总和。如果 `array` 不是数值则抛出错误。" }, "$max": { "args": "array", - "desc": "输出数组`array`的最大值。如果`array`不是数值则抛出错误。" + "desc": "输出数组 `array` 的最大值。如果 `array` 不是数值则抛出错误。" }, "$min": { "args": "array", - "desc": "输出数组`array`的最小值。如果`array`不是数值则抛出错误。。" + "desc": "输出数组 `array` 的最小值。如果 `array` 不是数值则抛出错误。。" }, "$average": { "args": "array", - "desc": "输出数组`array`的平均数。如果`array`不是数值则抛出错误。。" + "desc": "输出数组 `array` 的平均数。如果 `array` 不是数值则抛出错误。。" }, "$boolean": { "args": "arg", - "desc": "用下述规则将数据转换成布尔值。:\n\n - 不转换布尔值`Boolean`。\n – 将空的字符串`string`转换为`false`\n – 将不为空的字符串`string`转换为`true`\n – 将为0的数字`number`转换成`false`\n –将不为0的数字`number`转换成`true`\n –将`null`转换成`false`\n –将空的数组`array`转换成`false`\n –如果数组`array`中含有可以转换成`true`的要素则转换成`true`\n –如果`array`中没有可转换成`true`的要素则转换成`false`\n – 空的对象`object`转换成`false`\n – 非空的对象`object`转换成`true`\n –将函数`function`转换成`false`" + "desc": "用下述规则将数据转换成布尔值。:\n\n - 不转换布尔值 `Boolean` 。\n – 将空的字符串 `string` 转换为 `false` \n – 将不为空的字符串 `string` 转换为 `true` \n – 将为0的数字 `number` 转换成 `false` \n –将不为0的数字 `number` 转换成 `true` \n –将 `null` 转换成 `false` \n –将空的数组 `array` 转换成 `false` \n –如果数组 `array` 中含有可以转换成 `true` 的要素则转换成 `true` \n –如果 `array` 中没有可转换成 `true` 的要素则转换成 `false` \n – 空的对象 `object` 转换成 `false` \n – 非空的对象 `object` 转换成 `true` \n –将函数 `function` 转换成 `false` " }, "$not": { "args": "arg", - "desc": "输出做取反运算后的布尔值。首先将`arg`转换为布尔值。" + "desc": "输出做取反运算后的布尔值。首先将 `arg` 转换为布尔值。" }, "$exists": { "args": "arg", - "desc": "如果算式`arg`的值存在则输出`true`。如果算式的值不存在(比如指向不存在区域的引用)则输出`false`。" + "desc": "如果算式 `arg` 的值存在则输出 `true` 。如果算式的值不存在(比如指向不存在区域的引用)则输出 `false` 。" }, "$count": { "args": "array", @@ -137,15 +137,15 @@ }, "$sort": { "args": "array [, function]", - "desc": "输出排序后的数组`array`。\n\n如果使用了比较函数`function`,则下述两个参数需要被指定。\n\n`function(left, right)`\n\n该比较函数是为了比较left和right两个值而被排序算法调用的。如果用户希望left的值被置于right的值之后,那么该函数必须输出布尔值`true`来表示位置交换。而在不需要位置交换时函数必须输出`false`。" + "desc": "输出排序后的数组 `array` 。\n\n如果使用了比较函数 `function` ,则下述两个参数需要被指定。\n\n `function(left, right)` \n\n该比较函数是为了比较left和right两个值而被排序算法调用的。如果用户希望left的值被置于right的值之后,那么该函数必须输出布尔值 `true` 来表示位置交换。而在不需要位置交换时函数必须输出 `false` 。" }, "$reverse": { "args": "array", - "desc": "输出倒序后的数组`array`。" + "desc": "输出倒序后的数组 `array` 。" }, "$shuffle": { "args": "array", - "desc": "输出随机排序后的数组 `array`。" + "desc": "输出随机排序后的数组 `array` 。" }, "$zip": { "args": "array, ...", @@ -157,35 +157,35 @@ }, "$lookup": { "args": "object, key", - "desc": "输出对象中与参数`key`对应的值。如果第一个参数`object`是数组,那么数组中所有的对象都将被搜索并输出这些对象中与参数`key`对应的值。" + "desc": "输出对象中与参数 `key` 对应的值。如果第一个参数 `object` 是数组,那么数组中所有的对象都将被搜索并输出这些对象中与参数 `key` 对应的值。" }, "$spread": { "args": "object", - "desc": "将对象中的键值对分隔成每个要素中只含有一个键值对的数组。如果参数`object`是数组,那么返回值的数组中包含所有对象中的键值对。" + "desc": "将对象中的键值对分隔成每个要素中只含有一个键值对的数组。如果参数 `object` 是数组,那么返回值的数组中包含所有对象中的键值对。" }, "$merge": { "args": "array<object>", - "desc": "将输入数组`objects`中所有的键值对合并到一个`object`中并返回。如果输入数组的要素中含有重复的键,则返回的`object`中将只包含数组中最后出现要素的值。如果输入数组中包括对象以外的元素,则抛出错误。" + "desc": "将输入数组 `objects` 中所有的键值对合并到一个 `object` 中并返回。如果输入数组的要素中含有重复的键,则返回的 `object` 中将只包含数组中最后出现要素的值。如果输入数组中包括对象以外的元素,则抛出错误。" }, "$sift": { "args": "object, function", - "desc": "输出参数`object`中符合`function`的键值对。\n\n`function`必须含有下述参数。\n\n`function(value [, key [, object]])`" + "desc": "输出参数 `object` 中符合 `function` 的键值对。\n\n `function` 必须含有下述参数。\n\n `function(value [, key [, object]])` " }, "$each": { "args": "object, function", - "desc": "将函数`function`应用于`object`中的所有键值对并输出由所有返回值组成的数组。" + "desc": "将函数 `function` 应用于 `object` 中的所有键值对并输出由所有返回值组成的数组。" }, "$map": { "args": "array, function", - "desc": "将函数`function`应用于数组`array`中所有的值并输出由返回值组成的数组。\n\n`function`中必须含有下述参数。\n\n`function(value [, index [, array]])`" + "desc": "将函数 `function` 应用于数组 `array` 中所有的值并输出由返回值组成的数组。\n\n `function` 中必须含有下述参数。\n\n`function(value [, index [, array]])` " }, "$filter": { "args": "array, function", - "desc": "输出数组`array`中符合函数`function`条件的值组成的数组。\n\n`function`必须包括下述参数。\n\n`function(value [, index [, array]])`" + "desc": "输出数组 `array` 中符合函数 `function` 条件的值组成的数组。\n\n `function` 必须包括下述参数。\n\n `function(value [, index [, array]])`" }, "$reduce": { "args": "array, function [, init]", - "desc": "将`function`依次应用于数组中的各要素值。 其中,前一个要素值的计算结果将参与到下一次的函数运算中。。\n\n函数`function`接受两个参数并作为中缀表示法中的操作符。\n\n可省略的参数`init`将作为运算的初始值。" + "desc": "将 `function` 依次应用于数组中的各要素值。 其中,前一个要素值的计算结果将参与到下一次的函数运算中。。\n\n函数 `function` 接受两个参数并作为中缀表示法中的操作符。\n\n可省略的参数 `init` 将作为运算的初始值。" }, "$flowContext": { "args": "string", @@ -197,7 +197,7 @@ }, "$pad": { "args": "string, width [, char]", - "desc": "根据需要,向字符串`string`的副本中填充文字使该字符串的字数达到`width`的绝对值并返回填充文字后的字符串。\n\n如果`width`的值为正,则向字符串`string`的右侧填充文字,如果`width`为负,则向字符串`string`的左侧填充文字。\n\n可选参数`char`用来指定填充的文字。如果未指定该参数,则填充空白文字。" + "desc": "根据需要,向字符串 `string` 的副本中填充文字使该字符串的字数达到 `width` 的绝对值并返回填充文字后的字符串。\n\n如果 `width` 的值为正,则向字符串 `string` 的右侧填充文字,如果 `width` 为负,则向字符串 `string` 的左侧填充文字。\n\n可选参数 `char` 用来指定填充的文字。如果未指定该参数,则填充空白文字。" }, "$fromMillis": { "args": "number", @@ -205,15 +205,15 @@ }, "$formatNumber": { "args": "number, picture [, options]", - "desc": "将`number`转换成具有`picture`所指定的数值格式的字符串。\n\n此函数的功能与XPath F&O 3.1规格中定义的XPath/XQuery函数的fn:format-number功能相一致。参数`picture`用于指定数值的转换格式,其语法与fn:format-number中的定义一致。\n\n可选的第三参数`options`用来覆盖默认的局部环境格式,如小数点分隔符。如果指定该参数,那么该参数必须是包含name/value对的对象,并且name/value对必须符合XPath F&O 3.1规格中记述的数值格式。" + "desc": "将 `number` 转换成具有 `picture` 所指定的数值格式的字符串。\n\n此函数的功能与XPath F&O 3.1规格中定义的XPath/XQuery函数的fn:format-number功能相一致。参数 `picture` 用于指定数值的转换格式,其语法与fn:format-number中的定义一致。\n\n可选的第三参数 `options` 用来覆盖默认的局部环境格式,如小数点分隔符。如果指定该参数,那么该参数必须是包含name/value对的对象,并且name/value对必须符合XPath F&O 3.1规格中记述的数值格式。" }, "$formatBase": { "args": "number [, radix]", - "desc": "将`number`变换为以参数`radix`的值为基数形式的字符串。如果不指定`radix`的值,则默认基数为10。指定的`radix`值必须在2~36之间,否则抛出错误。" + "desc": "将 `number` 变换为以参数 `radix` 的值为基数形式的字符串。如果不指定 `radix` 的值,则默认基数为10。指定的 `radix` 值必须在2~36之间,否则抛出错误。" }, "$toMillis": { "args": "timestamp", - "desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜)开始到现在的毫秒数。如果该字符串的格式不正确,则抛出错误。" + "desc": "将ISO 8601格式的字符串 `timestamp` 转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜)开始到现在的毫秒数。如果该字符串的格式不正确,则抛出错误。" }, "$env": { "args": "arg", @@ -221,7 +221,7 @@ }, "$eval": { "args": "expr [, context]", - "desc": "使用当前上下文来作为评估依据,分析并评估字符串`expr`,其中包含文字JSON或JSONata表达式。" + "desc": "使用当前上下文来作为评估依据,分析并评估字符串 `expr` ,其中包含文字JSON或JSONata表达式。" }, "$formatInteger": { "args": "number, picture", @@ -233,19 +233,19 @@ }, "$error": { "args": "[str]", - "desc": "引发错误并显示一条消息。 可选的`str`将替代$error()函数评估的默认消息。" + "desc": "引发错误并显示一条消息。 可选的 `str` 将替代$error()函数评估的默认消息。" }, "$assert": { "args": "arg, str", - "desc": "如果`arg`为真,则该函数返回。 如果arg为假,则抛出带有str的异常作为异常消息。" + "desc": "如果 `arg` 为真,则该函数返回。 如果arg为假,则抛出带有str的异常作为异常消息。" }, "$single": { "args": "array, function", - "desc": "返回满足参数function谓语的array参数中的唯一值 (比如:传递值时,函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数:`function(value [,index [,array []]])`其中value是数组的每个输入,index是该值的位置,整个数组作为第三个参数传递。" + "desc": "返回满足参数function谓语的array参数中的唯一值 (比如:传递值时,函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数: `function(value [,index [,array []]])` 其中value是数组的每个输入,index是该值的位置,整个数组作为第三个参数传递。" }, "$encodeUrl": { "args": "str", - "desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)组件进行编码。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" + "desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)组件进行编码。\n\n示例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, "$encodeUrlComponent": { "args": "str", @@ -261,10 +261,14 @@ }, "$distinct": { "args": "array", - "desc": "返回一个数组,其中重复的值已从`数组`中删除" + "desc": "返回一个数组,其中重复的值已从 `数组` 中删除" }, "$type": { "args": "value", - "desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`" + "desc": "以字符串形式返回 `值` 的类型。 如果该 `值` 未定义,则将返回 `未定义` " + }, + "$moment": { + "args": "[str]", + "desc": "使用Moment库获取日期对象。" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json index 897599bc7..644d28b12 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json @@ -14,7 +14,15 @@ "back": "返回", "next": "下一步", "clone": "複製專案", - "cont": "Continue" + "cont": "繼續", + "style": "風格", + "line": "大綱", + "fill": "填充", + "label": "標籤", + "color": "顏色", + "position": "位置", + "enable": "啟用", + "disable": "禁用" }, "type": { "string": "字符串", @@ -28,6 +36,13 @@ "null": "空" } }, + "event": { + "loadPalette": "加載控制板", + "loadNodeCatalogs": "加載節點目錄", + "loadNodes": "加載 __count__ 個節點", + "loadFlows": "加載流程", + "importFlows": "往工作區中加載流程" + }, "workspace": { "defaultName": "流程__number__", "editFlow": "編輯流程: __name__", @@ -91,7 +106,12 @@ "projects-new": "新專案", "projects-open": "開啟專案", "projects-settings": "專案設定", - "showNodeLabelDefault": "顯示新添加節點的標籤" + "showNodeLabelDefault": "顯示新添加節點的標籤", + "groups": "組", + "groupSelection": "選擇組", + "ungroupSelection": "取消選擇組", + "groupMergeSelection": "合并選擇", + "groupRemoveSelection": "從組中移除" } }, "actions": { @@ -101,7 +121,7 @@ "zoom-in": "放大" }, "user": { - "loggedInAs": "作為__name__登入", + "loggedInAs": "作為 __name__ 登入", "username": "帳號", "password": "密碼", "login": "登入", @@ -133,7 +153,7 @@ "error": "Error: __message__", "errors": { "lostConnection": "丟失與伺服器的連接,重新連接...", - "lostConnectionReconnect": "丟失與伺服器的連接,__time__秒後重新連接", + "lostConnectionReconnect": "丟失與伺服器的連接,__time__ 秒後重新連接", "lostConnectionTry": "現在嘗試", "cannotAddSubflowToItself": "無法向其自身添加子流程", "cannotAddCircularReference": "無法添加子流程 - 迴圈引用", @@ -146,7 +166,7 @@ "loaded": "已加載項目'__project__'", "updated": "已更新項目'__project__'", "pull": "已重新加載項目'__project__'", - "revert": "項目“__project__”已還原", + "revert": "項目'__project__'已還原", "merge-complete": "Git合併完成", "setupCredentials": "設定證書", "setupProjectFiles": "設置項目文件", @@ -171,6 +191,8 @@ "node_plural": "__count__ 多個節點", "configNode": "__count__ 節點組態", "configNode_plural": "__count__ 多節點組態", + "group": "__count__ 個組", + "group_plural": "__count__ 個組", "flow": "__count__ 流程", "flow_plural": "__count__ 多流程", "subflow": "__count__ 子流程", @@ -184,8 +206,11 @@ "importUnrecognised_plural": "匯入了無法識別的類型:", "nodesExported": "節點匯出到了剪貼簿", "nodesImported": "已匯入:", - "nodeCopied": "已複製__count__個節點", - "nodeCopied_plural": "已複製__count__個節點", + "nodeCopied": "已複製 __count__ 個節點", + "nodeCopied_plural": "已複製 __count__ 個節點", + "groupCopied": "複製 __count__ 個組", + "groupCopied_plural": "已複製 __count__ 個groups", + "groupStyleCopied": "已複製組風格", "invalidFlow": "無效的流程: __message__", "export": { "selected": "已選擇的節點", @@ -204,9 +229,9 @@ "newFlow": "新流程", "errors": { "notArray": "輸入的不是JSON數組", - "itemNotObject": "輸入的流程無效-項目__index__不是節點對象", - "missingId": "輸入的流程無效-項__index__缺少“ id”屬性", - "missingType": "輸入的流程無效-項__index__缺少“類型”屬性" + "itemNotObject": "輸入的流程無效-項目 __index__ 不是節點對象", + "missingId": "輸入的流程無效-項 __index__ 缺少“ id”屬性", + "missingType": "輸入的流程無效-項 __index__ 缺少“類型”屬性" } }, "copyMessagePath": "已複製路徑", @@ -250,7 +275,7 @@ "conflictChecking": "檢查是否可以自動合併更改", "conflictAutoMerge": "此更改不包括衝突,可以自動合併", "conflictManualMerge": "這些更改包括了在部署之前必須解決的衝突。", - "plusNMore": "+更多的__count__" + "plusNMore": "+更多的 __count__" } }, "eventLog": { @@ -258,8 +283,8 @@ "view": "查看日誌" }, "diff": { - "unresolvedCount": "__count__個未解決的衝突", - "unresolvedCount_plural": "__count__個未解決的衝突", + "unresolvedCount": "__count__ 個未解決的衝突", + "unresolvedCount_plural": "__count__ 個未解決的衝突", "globalNodes": "全局節點", "flowProperties": "流程屬性", "type": { @@ -269,11 +294,11 @@ "deleted": "已刪除", "flowDeleted": "已刪除流程", "flowAdded": "已添加流程", - "movedTo": "移動至__id__", - "movedFrom": "從__id__移動" + "movedTo": "移動至 __id__", + "movedFrom": "從 __id__ 移動" }, - "nodeCount": "__count__個節點", - "nodeCount_plural": "__count__個節點", + "nodeCount": "__count__ 個節點", + "nodeCount_plural": "__count__ 個節點", "local": "本地", "remote": "遠端", "reviewChanges": "查看變更", @@ -287,11 +312,11 @@ "newVersionError": "新版本不包含有效的JSON:" }, "subflow": { - "editSubflowInstance": "編輯子流程實例:__name__", - "editSubflow": "編輯流程範本: __name__", + "editSubflowInstance": "編輯子流程實例: __name__", + "editSubflow": "編輯流程範本: __name__", "edit": "編輯流程範本", - "subflowInstances": "這個子流程範本有__count__個實例", - "subflowInstances_plural": "這個子流程範本有__count__個實例", + "subflowInstances": "這個子流程範本有 __count__ 個實例", + "subflowInstances_plural": "這個子流程範本有 __count__ 個實例", "editSubflowProperties": "編輯屬性", "input": "輸入:", "output": "輸出:", @@ -308,17 +333,24 @@ "multipleInputsToSelection": "無法創建子流程: 多個輸入到了選擇" } }, + "group": { + "editGroup": "編輯組: __name__", + "errors": { + "cannotCreateDiffGroups": "無法使用來自不同組的節點創建組", + "cannotAddSubflowPorts": "無法將子流程的端口添加到組" + } + }, "editor": { "configEdit": "編輯", "configAdd": "添加", "configUpdate": "更新", "configDelete": "刪除", - "nodesUse": "__count__個節點使用此配置", - "nodesUse_plural": "__count__個節點使用此配置", - "addNewConfig": "添加新的__type__配置", - "editNode": "編輯__type__節點", - "editConfig": "編輯__type__配置", - "addNewType": "添加新的__type__節點", + "nodesUse": "__count__ 個節點使用此配置", + "nodesUse_plural": "__count__ 個節點使用此配置", + "addNewConfig": "添加新的 __type__ 配置", + "editNode": "編輯 __type__ 節點", + "editConfig": "編輯 __type__ 配置", + "addNewType": "添加新的 __type__ 節點", "nodeProperties": "節點屬性", "label": "Label", "color": "顏色", @@ -337,6 +369,7 @@ "locale": "選擇界面語言", "icon": "圖標", "inputType": "輸入類型", + "selectType": "選擇類型...", "inputs": { "input": "輸入", "select": "選擇", @@ -351,7 +384,8 @@ "bool": "布爾", "json": "JSON", "bin": "buffer", - "env": "環境變量" + "env": "環境變量", + "cred": "證書" }, "menu": { "input": "輸入", @@ -405,13 +439,13 @@ "library": "庫", "openLibrary": "打開庫...", "saveToLibrary": "保存到庫...", - "typeLibrary": "__type__型別程式庫", - "unnamedType": "無名__type__", + "typeLibrary": "__type__ 型別程式庫", + "unnamedType": "無名 __type__", "exportedToLibrary": "節點導出到庫", - "dialogSaveOverwrite": "一個叫做__libraryName__的__libraryType__已經存在,您需要覆蓋麼?", + "dialogSaveOverwrite": "一個叫做 __libraryName__ 的 __libraryType__ 已經存在,您需要覆蓋麼?", "invalidFilename": "無效的檔案名", "savedNodes": "保存的節點", - "savedType": "已保存__type__", + "savedType": "已保存 __type__", "saveFailed": "保存失敗: __message__", "newFolder": "新文件夾", "types": { @@ -460,33 +494,33 @@ "times": { "seconds": "秒前", "minutes": "分前", - "minutesV": "__count__分前", - "hoursV": "__count__小時前", - "hoursV_plural": "__count__小時前", - "daysV": "__count__天前", - "daysV_plural": "__count__天前", - "weeksV": "__count__周前", - "weeksV_plural": "__count__周前", - "monthsV": "__count__月前", - "monthsV_plural": "__count__月前", - "yearsV": "__count__年前", - "yearsV_plural": "__count__年前", - "yearMonthsV": "__y__年, __count__月前", - "yearMonthsV_plural": "__y__年, __count__月前", - "yearsMonthsV": "__y__年, __count__月前", - "yearsMonthsV_plural": "__y__年, __count__月前" + "minutesV": "__count__ 分前", + "hoursV": "__count__ 小時前", + "hoursV_plural": "__count__ 小時前", + "daysV": "__count__ 天前", + "daysV_plural": "__count__ 天前", + "weeksV": "__count__ 周前", + "weeksV_plural": "__count__ 周前", + "monthsV": "__count__ 月前", + "monthsV_plural": "__count__ 月前", + "yearsV": "__count__ 年前", + "yearsV_plural": "__count__ 年前", + "yearMonthsV": "__y__ 年, __count__ 月前", + "yearMonthsV_plural": "__y__ 年, __count__ 月前", + "yearsMonthsV": "__y__ 年, __count__ 月前", + "yearsMonthsV_plural": "__y__ 年, __count__ 月前" }, - "nodeCount": "__label__個節點", - "nodeCount_plural": "__label__個節點", - "moduleCount": "__count__個可用模組", - "moduleCount_plural": "__count__個可用模組", + "nodeCount": "__label__ 個節點", + "nodeCount_plural": "__label__ 個節點", + "moduleCount": "__count__ 個可用模組", + "moduleCount_plural": "__count__ 個可用模組", "inuse": "使用中", "enableall": "全部啟用", "disableall": "全部禁用", "enable": "啟用", "disable": "禁用", "remove": "移除", - "update": "更新至__version__版本", + "update": "更新至 __version__ 版本", "updated": "已更新", "install": "安裝", "installed": "已安裝", @@ -498,7 +532,7 @@ "sort": "排序:", "sortAZ": "a-z順序", "sortRecent": "日期順序", - "more": "增加__count__個", + "more": "增加 __count__ 個", "errors": { "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制臺瞭解更多資訊", "installFailed": "無法安裝: __module__
__message__
查看日誌瞭解更多資訊", @@ -539,6 +573,7 @@ "label": "信息", "node": "節點", "type": "類型", + "group": "組", "module": "Module", "id": "ID", "status": "狀態", @@ -560,8 +595,30 @@ "subflowDesc": "子流程描述", "nodeHelp": "節點幫助", "none": "無", - "arrayItems": "__count__個項目", - "showTips": "您可以從設置面板啟用提示資訊" + "arrayItems": "__count__ 個項目", + "showTips": "您可以從設置面板啟用提示資訊", + "outline": "大綱", + "empty": "空的", + "globalConfig": "全局配置節點", + "triggerAction": "觸發動作", + "find": "在工作區中查找", + "search": { + "configNodes": "配置節點", + "unusedConfigNodes": "未使用的配置節點", + "invalidNodes": "無效的節點", + "uknownNodes": "未知的節點", + "unusedSubflows": "未使用的子流程" + } + }, + "help": { + "name": "幫助", + "label": "幫助", + "search": "搜索幫助", + "nodeHelp": "節點幫助", + "showHelp": "顯示幫助", + "showInOutline": "在大綱中顯示", + "showTopics": "顯示主題", + "noHelp": "未選擇幫助主題" }, "config": { "name": "配置節點", @@ -614,9 +671,9 @@ "removeFromProject": "從項目中刪除", "addToProject": "添加到項目", "files": "文件", - "package": "包", "flow": "流程", "credentials": "證書", + "package": "包", "packageCreate": "保存更改後將創建文件", "fileNotExist": "文件不存在", "selectFile": "選擇文件", @@ -668,7 +725,7 @@ "copyPublicKey": "將公鑰複製到剪貼板", "delete": "刪除密鑰", "gitConfig": "Git配置", - "deleteConfirm": "您確定要刪除SSH密鑰__name__嗎? 這不能被撤消。" + "deleteConfirm": "您確定要刪除SSH密鑰 __name__ 嗎? 這不能被撤消。" }, "versionControl": { "unstagedChanges": "未暫存的更改", @@ -722,24 +779,24 @@ "pullChanges": "Pull變更", "history": "歷史", "projectHistory": "項目歷史", - "daysAgo": "__count__天前", - "daysAgo_plural": "__count__天前", - "hoursAgo": "__count__小時前", - "hoursAgo_plural": "__count__小時前", - "minsAgo": "__count__分鐘前", - "minsAgo_plural": "__count__分鐘前", + "daysAgo": "__count__ 天前", + "daysAgo_plural": "__count__ 天前", + "hoursAgo": "__count__ 小時前", + "hoursAgo_plural": "__count__ 小時前", + "minsAgo": "__count__ 分鐘前", + "minsAgo_plural": "__count__ 分鐘前", "secondsAgo": "秒前", "notTracking": "您的本地分支當前未跟蹤遠程分支。", "statusUnmergedChanged": "您的存儲庫中有未合併的更改。您需要解決衝突並提交結果。", "repositoryUpToDate": "您的存儲庫是最新的。", - "commitsAhead": "您的倉庫領先遠程倉庫__count__次提交。您現在可以push這些提交。", - "commitsAhead_plural": "您的倉庫領先遠程倉庫__count__次提交。您現在可以push這些提交。", - "commitsBehind": "您的倉庫落後遠程倉庫__count__次提交。您現在可以pull這些提交。", - "commitsBehind_plural": "您的倉庫落後遠程倉庫__count__次提交。您現在可以pull這些提交。", - "commitsAheadAndBehind1": "您的倉庫落後遠程倉庫__count__次提交", - "commitsAheadAndBehind1_plural": "您的倉庫落後遠程倉庫__count__次提交", - "commitsAheadAndBehind2": "領先遠程倉庫__count__次提交。", - "commitsAheadAndBehind2_plural": "領先遠程倉庫__count__次提交。", + "commitsAhead": "您的倉庫領先遠程倉庫 __count__ 次提交。您現在可以push這些提交。", + "commitsAhead_plural": "您的倉庫領先遠程倉庫 __count__ 次提交。您現在可以push這些提交。", + "commitsBehind": "您的倉庫落後遠程倉庫 __count__ 次提交。您現在可以pull這些提交。", + "commitsBehind_plural": "您的倉庫落後遠程倉庫 __count__ 次提交。您現在可以pull這些提交。", + "commitsAheadAndBehind1": "您的倉庫落後遠程倉庫 __count__ 次提交", + "commitsAheadAndBehind1_plural": "您的倉庫落後遠程倉庫 __count__ 次提交", + "commitsAheadAndBehind2": "領先遠程倉庫 __count__ 次提交。", + "commitsAheadAndBehind2_plural": "領先遠程倉庫 __count__ 次提交。", "commitsAheadAndBehind3": "您必須先pull遠程提交,然後再進行push。", "commitsAheadAndBehind3_plural": "您必須先pull遠程提交,然後再進行push。", "refreshCommitHistory": "刷新提交歷史", @@ -757,7 +814,8 @@ "bin": "二進位流", "date": "時間戳記", "jsonata": "expression", - "env": "env variable" + "env": "env variable", + "cred": "證書" } }, "editableList": { @@ -977,7 +1035,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/locales/zh-TW/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json index 6d99ffc6a..3765ab3dd 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json @@ -266,5 +266,9 @@ "$type": { "args": "value", "desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`" + }, + "$moment": { + "args": "[str]", + "desc": "使用Moment庫獲取日期對象。" } } 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..bd2abd8d0 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,14 +32,19 @@ } } } - function emit(evt,arg) { + function emit() { + var evt = arguments[0] + var args = Array.prototype.slice.call(arguments,1); + if (RED.events.DEBUG) { + console.warn(evt,args); + } if (handlers[evt]) { for (var i=0;i n.ports.length) { - n.ports.push(n.ports.length); - } n.resize = true; n.dirty = true; }); } + if (ev.groups) { + inverseEv.groups = []; + var groupsToAdd = {}; + ev.groups.forEach(function(g) { groupsToAdd[g.id] = g; }); + for (i=0;i n.outputs)) { n.outputs = n.wires.length; } - if (n.outputs) { - for (var i=0;i=0; i--) { + removeGroup(removedGroups[i]); + } + RED.events.emit('flows:remove',ws); } - for (n=0;ndiv:not(.node-input-env-container-row)"); var height = size.height; @@ -412,15 +470,20 @@ RED.nodes = (function() { module: "node-red" } }); + sf.instances = []; 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 +556,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 +571,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 +606,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 +655,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 +665,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 +738,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() }); @@ -353,7 +366,7 @@ var RED = (function() { var parts = topic.split("/"); var node = RED.nodes.node(parts[1]); if (node) { - if (msg.hasOwnProperty("text") && /^[a-zA-Z]/.test(msg.text)) { + if (msg.hasOwnProperty("text") && msg.text !== null && /^[a-zA-Z]/.test(msg.text)) { msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()}); } node.status = msg; @@ -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..3616bade4 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -0,0 +1,209 @@ +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'); @@ -369,6 +449,9 @@ if (opt.label) { op.text(opt.label); } + if (opt.title) { + op.prop('title', opt.title) + } if (opt.icon) { if (opt.icon.indexOf("<") === 0) { $(opt.icon).prependTo(op); @@ -635,7 +718,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 +861,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 +874,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); @@ -825,7 +913,7 @@ panel.show({ target:that.optionExpandButton, onclose:content.onclose, - align: "right" + align: "left" }); } }) 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..c59e02d0e 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 @@ -204,32 +204,28 @@ RED.editor = (function() { node.dirty = true; node.dirtyStatus = true; var removedLinks = []; - if (node.ports) { - if (outputMap) { - RED.nodes.eachLink(function(l) { - if (l.source === node && outputMap.hasOwnProperty(l.sourcePort)) { + if (outputMap) { + RED.nodes.eachLink(function(l) { + if (l.source === node) { + if (outputMap.hasOwnProperty(l.sourcePort)) { if (outputMap[l.sourcePort] === "-1") { removedLinks.push(l); } else { l.sourcePort = outputMap[l.sourcePort]; } } - }); - } - if (node.outputs < node.ports.length) { - while (node.outputs < node.ports.length) { - node.ports.pop(); } + }); + } + if (node.hasOwnProperty("__outputs")) { + if (node.outputs < node.__outputs) { RED.nodes.eachLink(function(l) { if (l.source === node && l.sourcePort >= node.outputs && removedLinks.indexOf(l) === -1) { removedLinks.push(l); } }); - } else if (node.outputs > node.ports.length) { - while (node.outputs > node.ports.length) { - node.ports.push(node.ports.length); - } } + delete node.__outputs; } node.inputs = Math.min(1,Math.max(0,parseInt(node.inputs))); if (isNaN(node.inputs)) { @@ -490,8 +486,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 +494,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 +510,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 +787,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 +827,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..39c9d81a9 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() { @@ -1613,25 +1615,16 @@ RED.projects = (function() { } function deleteProject(row,name,done) { - var cover = $('
').css({ - background:"white", - position:"absolute", - top:0,right:0,bottom:0,left:"100%", - overflow:"hidden", - padding: "5px 20px", - transition: "left 0.4s", - whitespace: "nowrap", - width:"1000px" - }).on("click", function(evt) { evt.stopPropagation(); }).appendTo(row); - $('').css({"lineHeight":"40px"}).text(RED._("projects.delete.confirm")).appendTo(cover); - $('') + var cover = $('
').on("click", function(evt) { evt.stopPropagation(); }).appendTo(row); + $('').text(RED._("projects.delete.confirm")).appendTo(cover); + $('') .appendTo(cover) .on("click", function(e) { e.stopPropagation(); cover.remove(); done(true); }); - $('') + $('') .appendTo(cover) .on("click", function(e) { e.stopPropagation(); @@ -1671,16 +1664,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 +2256,7 @@ RED.projects = (function() { } function init() { - dialog = $('
') + dialog = $('
') .appendTo("#red-ui-editor") .dialog({ modal: true, @@ -2339,6 +2343,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..1751f524e 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,11 +23,9 @@ RED.search = (function() { var visible = false; var index = {}; - var keys = []; - var results = []; + var currentResults = []; var previousActiveElement; - function indexProperty(node,label,property) { if (typeof property === 'string' || typeof property === 'number') { property = (""+property).toLowerCase(); @@ -61,53 +59,110 @@ RED.search = (function() { } for (var i=0;i + val = extractValue(val,"uses",flags); + + var hasFlags = Object.keys(flags).length > 0; val = val.trim(); - selected = -1; - results = []; - if (val.length > 0 || typeFilter) { + if (val.length > 0 || typeFilter || hasFlags) { val = val.toLowerCase(); var i; var j; var list = []; var nodes = {}; + if (flags.uses) { + keys = flags.uses; + } else { + keys = Object.keys(index); + } for (i=0;i -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 +202,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 +264,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 +299,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 +318,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 +363,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 +397,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 +924,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 +1072,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 +1131,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 +1139,328 @@ 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 = $('
').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 { + $('').text(RED._("editor.selectType")).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({ + "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 = $('
      ').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') { + var types = inputCellInput.typedInput('value'); + ui.opts.types = (types === "") ? ["str"] : types.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 +1558,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 +1587,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 +1596,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 +1634,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 +1644,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 +1700,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 +1736,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 +1758,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 +1775,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 +1825,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..db8abee2b --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -0,0 +1,334 @@ +/** + * 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({ + style: "compact", + 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); + } + resizeStack(); + } + + // 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..222aafd45 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js @@ -0,0 +1,629 @@ +RED.sidebar.info.outliner = (function() { + + var treeList; + var searchInput; + var activeSearch; + var projectInfo; + var projectInfoLabel; + var flowList; + var subflowList; + var globalConfigNodes; + + var objects = {}; + var missingParents = {}; + var configNodeTypes; + + + function getFlowData() { + var flowData = [ + { + label: RED._("menu.label.flows"), + expanded: true, + children: [] + }, + { + id: "__subflow__", + label: RED._("menu.label.subflows"), + children: [ + getEmptyItem("__subflow__") + ] + }, + { + id: "__global__", + flow: "__global__", + label: RED._("sidebar.info.globalConfig"), + types: {}, + children: [ + getEmptyItem("__global__") + ] + } + ] + + flowList = flowData[0]; + subflowList = flowData[1]; + globalConfigNodes = flowData[2]; + configNodeTypes = { __global__: globalConfigNodes}; + + 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.category === "config" && n.type !== "group") { + var userCountBadge = $('').text(n.users.length).appendTo(controls).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.search.show("uses:"+n.id); + }) + RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})}); + } + + if (n._def.button) { + var triggerButton = $('').appendTo(controls).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.view.clickNodeButton(n); + }) + RED.popover.tooltip(triggerButton,RED._("sidebar.info.triggerAction")); + } + // $('').appendTo(controls).on("click",function(evt) { + // evt.preventDefault(); + // evt.stopPropagation(); + // RED.view.reveal(n.id); + // }) + if (n.type !== 'group' && n.type !== 'subflow') { + var toggleButton = $('').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(); + } + }); + RED.popover.tooltip(toggleButton,function() { + return RED._("common.label."+(((n.type==='tab' && n.disabled) || (n.type!=='tab' && n.d))?"enable":"disable")); + }); + } 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({ + style: "compact", + delay: 500, + change: function() { + var val = $(this).val(); + var searchResults = RED.search.search(val); + if (val) { + activeSearch = 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("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) + updateSearch(); + + } + 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) + updateSearch(); + } + 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)); + } + if (empties["__subflow__"]) { + empties["__subflow__"].treeList.remove(); + delete empties["__subflow__"]; + } + subflowList.treeList.addChild(objects[sf.id]) + updateSearch(); + } + 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)); + } + }); + updateSearch(); + } + + function onNodeChange(n) { + var existingObject = objects[n.id]; + var parent = n.g||n.z||"__global__"; + + 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(" "); + } + var existingParent = existingObject.parent.id; + if (!existingParent) { + existingParent = existingObject.parent.parent.flow + } + if (parent !== existingParent) { + var parentItem = existingObject.parent; + existingObject.treeList.remove(true); + if (parentItem.children.length === 0) { + if (parentItem.config) { + // this is a config + parentItem.treeList.remove(); + // console.log("Removing",n.type,"from",parentItem.parent.id||parentItem.parent.parent.id) + + delete configNodeTypes[parentItem.parent.id||parentItem.parent.parent.id].types[n.type]; + + + if (parentItem.parent.children.length === 0) { + if (parentItem.parent.id === "__global__") { + parentItem.parent.treeList.addChild(getEmptyItem(parentItem.parent.id)); + } else { + delete configNodeTypes[parentItem.parent.parent.id]; + parentItem.parent.treeList.remove(); + if (parentItem.parent.parent.children.length === 0) { + parentItem.parent.parent.treeList.addChild(getEmptyItem(parentItem.parent.parent.id)); + } + + } + } + } else { + parentItem.treeList.addChild(getEmptyItem(parentItem.id)); + } + } + if (n._def.category === 'config' && n.type !== 'group') { + // This must be a config node that has been rescoped + createFlowConfigNode(parent,n.type); + configNodeTypes[parent].types[n.type].treeList.addChild(objects[n.id]); + } else { + // This is a node that has moved groups + if (empties[parent]) { + empties[parent].treeList.remove(); + delete empties[parent]; + } + objects[parent].treeList.addChild(existingObject) + } + + // if (parent === "__global__") { + // // Global always exists here + // if (!configNodeTypes[parent][n.type]) { + // configNodeTypes[parent][n.type] = { + // config: true, + // label: n.type, + // children: [] + // } + // globalConfigNodes.treeList.addChild(configNodeTypes[parent][n.type]) + // } + // configNodeTypes[parent][n.type].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) + + if (n._def.category === "config" && n.type !== 'group') { + existingObject.element.find(".red-ui-info-outline-item-control-users").text(n.users.length); + } + + updateSearch(); + } + function onObjectRemove(n) { + var existingObject = objects[n.id]; + existingObject.treeList.remove(); + delete objects[n.id] + + // If this is a group being removed, it may have an empty item + if (empties[n.id]) { + delete empties[n.id]; + } + var parent = existingObject.parent; + if (parent.children.length === 0) { + if (parent.config) { + // this is a config + parent.treeList.remove(); + delete configNodeTypes[parent.parent.id||n.z].types[n.type]; + if (parent.parent.children.length === 0) { + if (parent.parent.id === "__global__") { + parent.parent.treeList.addChild(getEmptyItem(parent.parent.id)); + } else { + delete configNodeTypes[n.z]; + parent.parent.treeList.remove(); + if (parent.parent.parent.children.length === 0) { + parent.parent.parent.treeList.addChild(getEmptyItem(parent.parent.parent.id)); + } + } + } + } else { + parent.treeList.addChild(getEmptyItem(parent.id)); + } + } + } + function getGutter(n) { + var span = $("",{class:"red-ui-info-outline-gutter"}); + var revealButton = $('').appendTo(span).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.view.reveal(n.id); + }) + RED.popover.tooltip(revealButton,RED._("sidebar.info.find")); + return span; + } + + function createFlowConfigNode(parent,type) { + // console.log("createFlowConfig",parent,type,configNodeTypes[parent]); + if (empties[parent]) { + empties[parent].treeList.remove(); + delete empties[parent]; + } + if (!configNodeTypes[parent]) { + // There is no 'config nodes' item in the parent flow + configNodeTypes[parent] = { + config: true, + flow: parent, + types: {}, + label: RED._("menu.label.displayConfig"), + children: [] + } + objects[parent].treeList.insertChildAt(configNodeTypes[parent],0); + // console.log("CREATED", parent) + } + if (!configNodeTypes[parent].types[type]) { + configNodeTypes[parent].types[type] = { + config: true, + label: type, + children: [] + } + configNodeTypes[parent].treeList.addChild(configNodeTypes[parent].types[type]); + // console.log("CREATED", parent,type) + } + } + 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||"__global__"; + + if (n._def.category !== "config" || n.type === 'group') { + 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 { + createFlowConfigNode(parent,n.type); + configNodeTypes[parent].types[n.type].treeList.addChild(objects[n.id]); + } + objects[n.id].element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) + updateSearch(); + } + + var updateSearchTimer; + function updateSearch() { + if (updateSearchTimer) { + clearTimeout(updateSearchTimer) + } + if (activeSearch) { + updateSearchTimer = setTimeout(function() { + searchInput.searchBox("change"); + },100); + } + } + 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..d84f47f73 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,81 @@ 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"); + RED.events.on("sidebar:resize", resizeStack); - helpSection = sections.add({ - title: RED._("sidebar.info.nodeHelp"), - collapsible: true - }); - helpSection.expand(); - helpSection.content.css("padding","6px"); + $(window).on("resize", resizeStack); + $(window).on("focus", resizeStack); - var tipContainer = $('
        ').appendTo(content); + + // Tip Box + tipContainer = $('
        ').appendTo(content); tipBox = $('
        ').appendTo(tipContainer); var tipButtons = $('
        ').appendTo(tipContainer); @@ -75,17 +141,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 +168,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 +205,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,53 +225,96 @@ 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; } - subflowUserCount = 0; var subflowType = "subflow:"+subflowNode.id; - RED.nodes.eachNode(function(n) { - if (n.type === subflowType) { - subflowUserCount++; - } - }); + subflowUserCount = subflowNode.instances.length; } + + 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 +329,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 +336,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 +367,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 +407,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 +442,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 +517,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 +531,7 @@ RED.sidebar.info = (function() { } function stopTips() { $(".red-ui-sidebar-info").removeClass('show-tips'); + resizeStack(); clearInterval(refreshTimeout); clearTimeout(startTimeout); refreshTimeout = null; @@ -448,15 +556,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/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index d41a6a426..a5e84e294 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -241,10 +241,13 @@ RED.typeSearch = (function() { $(document).off('mousedown.red-ui-type-search'); $(document).off('mouseup.red-ui-type-search'); $(document).off('click.red-ui-type-search'); + $(document).off('touchstart.red-ui-type-search'); + $(document).off('mousedown.red-ui-type-search'); setTimeout(function() { $(document).on('mousedown.red-ui-type-search',handleMouseActivity); $(document).on('mouseup.red-ui-type-search',handleMouseActivity); $(document).on('click.red-ui-type-search',handleMouseActivity); + $(document).on('touchstart.red-ui-type-search',handleMouseActivity); },200); refreshTypeList(opts); @@ -260,7 +263,9 @@ RED.typeSearch = (function() { searchResultsDiv.slideDown(300); setTimeout(function() { searchResultsDiv.find(".red-ui-editableList-container").scrollTop(0); - searchInput.trigger("focus"); + if (!opts.disableFocus) { + searchInput.trigger("focus"); + } },100); } function hide(fast) { @@ -279,6 +284,7 @@ RED.typeSearch = (function() { $(document).off('mousedown.red-ui-type-search'); $(document).off('mouseup.red-ui-type-search'); $(document).off('click.red-ui-type-search'); + $(document).off('touchstart.red-ui-type-search'); } } function getTypeLabel(type, def) { 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..e25b570b9 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,10 +100,15 @@ RED.view.tools = (function() { node.n.x += dx; node.n.y += 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); + 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); + } 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); + } } - if (minX !== 0 || minY !== 0) { for (var n = 0; n 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..8e4a75d99 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,9 +21,11 @@ * \- .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() { @@ -48,34 +50,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 +110,9 @@ RED.view = (function() { var gridLayer; var linkLayer; var dragGroupLayer; + var groupSelectLayer; var nodeLayer; + var groupLayer; var drag_lines; function init() { @@ -139,6 +151,7 @@ RED.view = (function() { } }) .on("touchend", function() { + d3.event.preventDefault(); clearTimeout(touchStartTime); touchStartTime = null; if (RED.touch.radialMenu.active()) { @@ -146,7 +159,7 @@ RED.view = (function() { } canvasMouseUp.call(this); }) - .on("touchcancel", canvasMouseUp) + .on("touchcancel", function() { d3.event.preventDefault(); canvasMouseUp.call(this); }) .on("touchstart", function() { var touch0; if (d3.event.touches.length>1) { @@ -191,6 +204,7 @@ RED.view = (function() { // .attr("class","nr-ui-view-lasso"); },touchLongPressTimeout); } + d3.event.preventDefault(); }) .on("touchmove", function(){ if (RED.touch.radialMenu.active()) { @@ -242,6 +256,7 @@ RED.view = (function() { redraw(); } } + d3.event.preventDefault(); }); // Workspace Background @@ -253,9 +268,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 +395,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 +557,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 +758,7 @@ RED.view = (function() { } function canvasMouseDown() { + if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); } var point; if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); @@ -684,7 +772,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 +785,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 +812,13 @@ RED.view = (function() { } } - function showQuickAddDialog(point,spliceLink) { + function showQuickAddDialog(point, spliceLink, targetGroup, touchTrigger) { + if (targetGroup && !targetGroup.active) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + RED.view.redraw(); + } + var ox = point[0]; var oy = point[1]; @@ -801,6 +901,7 @@ RED.view = (function() { RED.typeSearch.show({ x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), + disableFocus: touchTrigger, filter: filter, move: function(dx,dy) { if (ghostNode) { @@ -828,6 +929,10 @@ RED.view = (function() { redraw(); }, add: function(type,keepAdding) { + if (touchTrigger) { + keepAdding = false; + resetMouseVars(); + } var result = addNode(type); if (!result) { return; @@ -946,6 +1051,26 @@ RED.view = (function() { } } } + + RED.nodes.add(nn); + RED.editor.validateNode(nn); + + 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 @@ -966,12 +1091,14 @@ RED.view = (function() { historyEvent.removedLinks = [spliceLink]; } RED.history.push(historyEvent); - RED.nodes.add(nn); - RED.editor.validateNode(nn); RED.nodes.dirty(true); // 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 +1203,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 +1321,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 +1577,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 +1624,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 +1881,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 +1909,7 @@ RED.view = (function() { dirty: RED.nodes.dirty(), nodes: [], links: [], + groups: [], workspaces: [], subflows: [] } @@ -1651,6 +1929,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); @@ -1659,8 +1938,10 @@ RED.view = (function() { redraw(); } else if (moving_set.length > 0 || selected_link != null) { var result; + var node; var removedNodes = []; var removedLinks = []; + var removedGroups = []; var removedSubflowOutputs = []; var removedSubflowInputs = []; var removedSubflowStatus; @@ -1668,11 +1949,27 @@ RED.view = (function() { var startDirty = RED.nodes.dirty(); var startChanged = false; + var selectedGroups = []; if (moving_set.length > 0) { + for (var i=0;i=0; i--) { + var g = selectedGroups[i]; + removedGroups.push(g); + RED.nodes.removeGroup(g); + } if (removedSubflowOutputs.length > 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { @@ -1716,7 +2031,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 +2084,7 @@ RED.view = (function() { t:"delete", nodes:removedNodes, links:removedLinks, + groups: removedGroups, subflowOutputs:removedSubflowOutputs, subflowInputs:removedSubflowInputs, subflow: { @@ -1801,20 +2117,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]; + function calculateTextWidth(str, className) { + var result=convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i 1) { + var i=0; + for (i=0;i -1 && content[newlineIndex-1] !== '\\') { + content = content.substring(0,newlineIndex)+"..."; + } + var lines = content.split("\n"); var labelWidth = 6; var labelHeight = 12; var labelHeights = []; var lineHeight = 0; lines.forEach(function(l,i) { - var labelDimensions = calculateTextDimensions(l||" ", "red-ui-flow-port-tooltip-label", 8,0); - labelWidth = Math.max(labelWidth,labelDimensions[0] + 6); + var labelDimensions = calculateTextDimensions(l||" ", "red-ui-flow-port-tooltip-label"); + labelWidth = Math.max(labelWidth,labelDimensions[0] + 14); labelHeights.push(labelDimensions[1]); if (i === 0) { lineHeight = labelDimensions[1]; @@ -2239,7 +2640,24 @@ RED.view = (function() { port.classed("red-ui-flow-port-hovered",false); } + function prepareDrag(mouse) { + mouse_mode = RED.state.MOVING; + // Called when moving_set should be prepared to be dragged + for (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) { @@ -2266,6 +2705,12 @@ RED.view = (function() { } else if (d.type === 'link out') { direction = 0; } + } else { + if (drag_lines[0].portType === 1) { + direction = PORT_TYPE_OUTPUT; + } else { + direction = PORT_TYPE_INPUT; + } } } } @@ -2275,8 +2720,8 @@ RED.view = (function() { d3.selectAll(".red-ui-flow-port-hovered").classed("red-ui-flow-port-hovered",false); } } - function nodeMouseDown(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } focusView(); if (d3.event.button === 1) { return; @@ -2345,7 +2790,9 @@ RED.view = (function() { // } return; } + mousedown_node = d; + var now = Date.now(); clickElapsed = now-clickTime; clickTime = now; @@ -2353,11 +2800,76 @@ RED.view = (function() { dblClickPrimed = (lastClickNode == mousedown_node && d3.event.button === 0 && !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey); - lastClickNode = mousedown_node; + lastClickNode = mousedown_node; var i; - if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) { + if (!d.selected && d.g /*&& !RED.nodes.group(d.g).selected*/) { + var nodeGroup = RED.nodes.group(d.g); + + if (nodeGroup !== activeGroup && (d3.event.ctrlKey || d3.event.metaKey)) { + // Clicked on a node in a non-active group with ctrl pressed + // - exit active group + // - toggle the select state of the group + exitActiveGroup(); + groupNodeSelectPrimed = true; + if (nodeGroup.selected) { + deselectGroup(nodeGroup); + } else { + selectGroup(nodeGroup,true); + } + } else if (nodeGroup === activeGroup ) { + // Clicked on a node in the active group + if (!d3.event.ctrlKey && !d3.event.metaKey) { + // Ctrl not pressed so clear selection + deselectGroup(nodeGroup); + selectGroup(nodeGroup,false,false); + } + // Select this node + mousedown_node.selected = true; + moving_set.push({n:mousedown_node}); + } else { + // Clicked on a node in a group + // - if this group is not selected, clear current selection + // and select this group + // - if this group is not the active group, exit the active group + // and select the group + // - if this group is the active group, keep it active and + // change node selection + + // Set groupNodeSelectPrimed to true as this is a (de)select of the + // group and NOT meant to trigger going into the group - see nodeMouseUp + groupNodeSelectPrimed = !nodeGroup.selected; + var ag = activeGroup; + if (!nodeGroup.selected) { + clearSelection(); + } + if (ag) { + if (ag !== nodeGroup) { + ag.active = false; + ag.dirty = true; + } else { + activeGroup = nodeGroup; + activeGroup.active = true; + } + } else { + dblClickPrimed = false; + } + selectGroup(nodeGroup, !activeGroup); + if (activeGroup) { + mousedown_node.selected = true; + moving_set.push({n:mousedown_node}); + } + } + + + if (d3.event.button != 2) { + var mouse = d3.touches(this)[0]||d3.mouse(this); + mouse[0] += d.x-d.w/2; + mouse[1] += d.y-d.h/2; + prepareDrag(mouse); + } + } else if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) { mousedown_node.selected = false; for (i=0;i 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + function nodeMouseOut(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseOut", mouse_mode,d); } + this.parentNode.classList.remove("red-ui-flow-node-hovered"); + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + if (drag_lines.length > 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + + function portMouseDownProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchStartProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchEndProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + + function linkMouseDown(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + if (d3.event.metaKey || d3.event.ctrlKey) { + d3.select(this).classed("red-ui-flow-link-splice",true); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog(point, selected_link, clickedGroup); + } + } + function linkTouchStart(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + + var obj = d3.select(document.body); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + },touchLongPressTimeout); + d3.event.preventDefault(); + } + + function groupMouseUp(g) { + if (dblClickPrimed && mousedown_group == g && clickElapsed > 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, addToMovingSet) { + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + if (addToMovingSet !== false) { + 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; @@ -2424,7 +3254,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 +3283,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) { @@ -2464,6 +3298,10 @@ RED.view = (function() { options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}}); options.push({name:"select",onselect:function() {selectAll();}}); options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); + options.push({name:"add",onselect:function() { + chartPos = chart.offset(); + showQuickAddDialog([pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()],undefined,undefined,true) + }}); RED.touch.radialMenu.show(obj,pos,options); resetMouseVars(); @@ -2489,12 +3327,12 @@ RED.view = (function() { .text(fontAwesomeUnicode); } else { var icon = icon_group.append("image") + .style("display","none") .attr("xlink:href",iconUrl) .attr("class","red-ui-flow-node-icon") .attr("x",0) .attr("width","30") - .attr("height","30") - .style("display","none"); + .attr("height","30"); var img = new Image(); img.src = iconUrl; @@ -2522,7 +3360,37 @@ RED.view = (function() { } } + function errorBadgeMouseEnter(e) { + var d = this.__data__; + if (d.validationErrors && d.validationErrors.length > 0) { + clearTimeout(portLabelHoverTimeout); + var node = this; + portLabelHoverTimeout = setTimeout(function() { + var pos = getElementPosition(node); + portLabelHoverTimeout = null; + portLabelHover = showTooltip( + (pos[0]), + (pos[1]), + RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - "), + "top" + ); + },500); + } + } + function errorBadgeMouseLeave() { + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + } + + function redraw() { + requestAnimationFrame(_redraw); + } + + function _redraw() { eventLayer.attr("transform","scale("+scaleFactor+")"); outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); @@ -2543,32 +3411,14 @@ RED.view = (function() { // TODO: This is exactly the same set of handlers used for regular nodes - DRY .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - }) - .on("touchend", function(d) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - }); + .on("touchstart",nodeTouchStart) + .on("touchend",nodeTouchEnd) outGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);} ) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); @@ -2586,32 +3436,14 @@ RED.view = (function() { // TODO: This is exactly the same set of handlers used for regular nodes - DRY .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - }) - .on("touchend", function(d) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - }); + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);} ) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);}) .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);}); @@ -2629,32 +3461,14 @@ RED.view = (function() { // TODO: This is exactly the same set of handlers used for regular nodes - DRY .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - }) - .on("touchend", function(d) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - }); + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);} ) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); @@ -2702,515 +3516,468 @@ 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); + this.__outputs__ = []; + this.__inputs__ = []; + var node = d3.select(this); + var nodeContents = document.createDocumentFragment(); + var isLink = (d.type === "link in" || d.type === "link out") + var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; + node.attr("id",d.id); + d.h = node_height; + d.resize = true; + + if (d._def.button) { + var buttonGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + buttonGroup.__data__ = d; + buttonGroup.setAttribute("transform", "translate("+((d._def.align == "right") ? 94 : -25)+",2)"); + buttonGroup.setAttribute("class","red-ui-flow-node-button"); + node[0][0].__buttonGroup__ = buttonGroup; + + var bgBackground = document.createElementNS("http://www.w3.org/2000/svg","rect"); + bgBackground.__data__ = d; + bgBackground.setAttribute("class","red-ui-flow-node-button-background"); + bgBackground.setAttribute("rx",5); + bgBackground.setAttribute("ry",5); + bgBackground.setAttribute("width",32); + bgBackground.setAttribute("height",node_height-4); + buttonGroup.appendChild(bgBackground); + node[0][0].__buttonGroupBackground__ = bgBackground; + + var bgButton = document.createElementNS("http://www.w3.org/2000/svg","rect"); + bgButton.__data__ = d; + bgButton.setAttribute("class","red-ui-flow-node-button-button"); + bgButton.setAttribute("x", d._def.align == "right"? 11:5); + bgButton.setAttribute("y",4); + bgButton.setAttribute("rx",4); + bgButton.setAttribute("ry",4); + bgButton.setAttribute("width",16); + bgButton.setAttribute("height",node_height-12); + bgButton.setAttribute("fill", RED.utils.getNodeColor(d.type,d._def)); + d3.select(bgButton) + .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) + .on("mouseup",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}}) + .on("mouseover",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);}}) + .on("mouseout",function(d) {if (!lasso && isButtonEnabled(d)) { + var op = 1; + if (d._def.button.toggle) { + op = d[d._def.button.toggle]?1:0.2; + } + d3.select(this).attr("fill-opacity",op); + }}) + .on("click",nodeButtonClicked) + .on("touchstart",function(d) { nodeButtonClicked.call(this,d); d3.event.preventDefault();}) + buttonGroup.appendChild(bgButton); + node[0][0].__buttonGroupButton__ = bgButton; + + nodeContents.appendChild(buttonGroup); + + } + + var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); + mainRect.__data__ = d; + mainRect.setAttribute("class", "red-ui-flow-node "+(d.type == "unknown"?"red-ui-flow-node-unknown":"")); + mainRect.setAttribute("rx", 5); + mainRect.setAttribute("ry", 5); + mainRect.setAttribute("fill", RED.utils.getNodeColor(d.type,d._def)); + node[0][0].__mainRect__ = mainRect; + d3.select(mainRect) + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend",nodeTouchEnd) + .on("mouseover",nodeMouseOver) + .on("mouseout",nodeMouseOut); + nodeContents.appendChild(mainRect); + //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); + //node.append("rect").attr("class", "node-gradient-bottom").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-bottom)").style("pointer-events","none"); + + if (d._def.icon) { + var icon_url = RED.utils.getNodeIcon(d._def,d); + var icon_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + icon_groupEl.__data__ = d; + icon_groupEl.setAttribute("class","red-ui-flow-node-icon-group"+("right" == d._def.align?" red-ui-flow-node-icon-group-right":"")); + icon_groupEl.setAttribute("x",0); + icon_groupEl.setAttribute("y",0); + icon_groupEl.style["pointer-events"] = "none"; + node[0][0].__iconGroup__ = icon_groupEl; + var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","rect"); + icon_shade.setAttribute("x",0); + icon_shade.setAttribute("y",0); + icon_shade.setAttribute("class","red-ui-flow-node-icon-shade") + icon_shade.setAttribute("width",30); + icon_shade.setAttribute("height",Math.min(50,d.h-4)); + icon_groupEl.appendChild(icon_shade); + node[0][0].__iconShade__ = icon_shade; + + var icon_group = d3.select(icon_groupEl) + createIconAttributes(icon_url, icon_group, d); + + var icon_shade_border = document.createElementNS("http://www.w3.org/2000/svg","path"); + icon_shade_border.setAttribute("d","right" != d._def.align ? "M 30 1 l 0 "+(d.h-2) : "M 0 1 l 0 "+(d.h-2) ) + icon_shade_border.setAttribute("class", "red-ui-flow-node-icon-shade-border") + icon_groupEl.appendChild(icon_shade_border); + node[0][0].__iconShadeBorder__ = icon_shade_border; + + nodeContents.appendChild(icon_groupEl); + } + var text = document.createElementNS("http://www.w3.org/2000/svg","g"); + text.setAttribute("class","red-ui-flow-node-label"+(hideLabel?" hide":"")+(d._def.align?" red-ui-flow-node-label-"+d._def.align:"")); + text.setAttribute("transform","translate(38,0)"); + // text.setAttribute("dy", ".3px"); + // text.setAttribute("text-anchor",d._def.align !== "right" ? "start":"end"); + nodeContents.appendChild(text); + node[0][0].__textGroup__ = text; + + var statusEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + // statusEl.__data__ = d; + statusEl.setAttribute("class","red-ui-flow-node-status-group"); + statusEl.style.display = "none"; + node[0][0].__statusGroup__ = statusEl; + + var statusRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); + statusRect.setAttribute("class","red-ui-flow-node-status"); + statusRect.setAttribute("x",6); + statusRect.setAttribute("y",1); + statusRect.setAttribute("width",9); + statusRect.setAttribute("height",9); + statusRect.setAttribute("rx",2); + statusRect.setAttribute("ry",2); + statusRect.setAttribute("stroke-width","3"); + statusEl.appendChild(statusRect); + node[0][0].__statusShape__ = statusRect; + + var statusLabel = document.createElementNS("http://www.w3.org/2000/svg","text"); + statusLabel.setAttribute("class","red-ui-flow-node-status-label"); + statusLabel.setAttribute("x",20); + statusLabel.setAttribute("y",10); + statusEl.appendChild(statusLabel); + node[0][0].__statusLabel__ = statusLabel; + + nodeContents.appendChild(statusEl); + + + var changeBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g"); + changeBadgeG.setAttribute("class","red-ui-flow-node-changed hide"); + changeBadgeG.setAttribute("transform","translate(20, -2)"); + node[0][0].__changeBadge__ = changeBadgeG; + var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); + changeBadge.setAttribute("r",5); + changeBadgeG.appendChild(changeBadge); + nodeContents.appendChild(changeBadgeG); + + + var errorBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g"); + errorBadgeG.setAttribute("class","red-ui-flow-node-error hide"); + errorBadgeG.setAttribute("transform","translate(0, -2)"); + node[0][0].__errorBadge__ = errorBadgeG; + var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); + errorBadge.setAttribute("d","M -5,4 l 10,0 -5,-8 z"); + errorBadgeG.appendChild(errorBadge); + errorBadge.__data__ = d; + errorBadge.addEventListener("mouseenter", errorBadgeMouseEnter); + errorBadge.addEventListener("mouseleave", errorBadgeMouseLeave); + nodeContents.appendChild(errorBadgeG); + + node[0][0].appendChild(nodeContents); + }); + node.each(function(d,i) { + if (d.dirty) { + var thisNode = 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); - if (d.resize || d.w === undefined) { + dirtyNodes[d.id] = d; + //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette + + var label = RED.utils.getNodeLabel(d, d.type); + var labelParts; + if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label || this.__outputs__.length !== d.outputs) { + labelParts = getLabelParts(label, "red-ui-flow-node-label"); + this.__label__ = label; + if (labelParts.lines.length !== this.__labelLineCount__) { + d.resize = true; + } + this.__labelLineCount__ = labelParts.lines.length; + + if (hideLabel) { + d.h = Math.max(node_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, 30); + } + this.__hideLabel__ = hideLabel; + } + + if (d.resize) { + var ow = d.w; 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((labelParts.width+50+(d._def.inputs>0?7:0))/20)) ); } - } - d.h = Math.max(node_height,(d.outputs||0) * 15); - - // 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); - // badge.append("svg:text").attr("class","node_badge_label").attr("x",35).attr("y",11).attr("text-anchor","end").text(d._def.badge()); - // if (d._def.onbadgeclick) { - // badgeRect.attr("cursor","pointer") - // .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();}); - // } - // } - - if (d._def.button) { - var nodeButtonGroup = node.append("svg:g") - .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; }) - .attr("class","red-ui-flow-node-button"); - nodeButtonGroup.append("rect") - .attr("class","red-ui-flow-node-button-background") - .attr("rx",5) - .attr("ry",5) - .attr("width",32) - .attr("height",node_height-4); - nodeButtonGroup.append("rect") - .attr("class","red-ui-flow-node-button-button") - .attr("x",function(d) { return d._def.align == "right"? 11:5}) - .attr("y",4) - .attr("rx",4) - .attr("ry",4) - .attr("width",16) - .attr("height",node_height-12) - .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) - .attr("cursor","pointer") - .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) - .on("mouseup",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}}) - .on("mouseover",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);}}) - .on("mouseout",function(d) {if (!lasso && isButtonEnabled(d)) { - var op = 1; - if (d._def.button.toggle) { - op = d[d._def.button.toggle]?1:0.2; - } - d3.select(this).attr("fill-opacity",op); - }}) - .on("click",nodeButtonClicked) - .on("touchstart",nodeButtonClicked) - } - - var mainRect = node.append("rect") - .attr("class", "red-ui-flow-node") - .classed("red-ui-flow-node-unknown",function(d) { return d.type == "unknown"; }) - .attr("rx", 5) - .attr("ry", 5) - .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - }) - .on("touchend", function(d) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - }) - .on("mouseover",function(d) { - if (mouse_mode === 0 || mouse_mode === RED.state.SELECTING_NODE) { - if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions && selectNodesOptions.filter) { - if (selectNodesOptions.filter(d)) { - node.classed("red-ui-flow-node-hovered",true); - } - } else { - node.classed("red-ui-flow-node-hovered",true); - } - clearTimeout(portLabelHoverTimeout); - if (d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")) { - portLabelHoverTimeout = setTimeout(function() { - var tooltip; - if (d._def.label) { - tooltip = d._def.label; - try { - tooltip = (typeof tooltip === "function" ? tooltip.call(d) : tooltip)||""; - } catch(err) { - console.log("Definition error: "+d.type+".label",err); - tooltip = d.type; - } - } - if (tooltip !== "") { - var pos = getElementPosition(node.node()); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0] + d.w/2), - (pos[1]-1), - tooltip, - "top" - ); - } - },500); - } - } else if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - if (drag_lines.length > 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - }) - .on("mouseout",function(d) { - node.classed("red-ui-flow-node-hovered",false); - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - if (drag_lines.length > 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - }); - - //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); - //node.append("rect").attr("class", "node-gradient-bottom").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-bottom)").style("pointer-events","none"); - - if (d._def.icon) { - var icon_url = RED.utils.getNodeIcon(d._def,d); - var icon_group = node.append("g") - .attr("class","red-ui-flow-node-icon-group") - .attr("x",0).attr("y",0); - - var icon_shade = icon_group.append("rect") - .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);}); - - 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("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.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); - } - - //if (d.inputs > 0 && d._def.align == null) { - // icon_shade.attr("width",35); - // icon.attr("transform","translate(5,0)"); - // icon_shade_border.attr("transform","translate(5,0)"); - //} - //if (d._def.outputs > 0 && "right" == d._def.align) { - // icon_shade.attr("width",35); //icon.attr("x",5); - //} - - //icon.style("pointer-events","none"); - icon_group.style("pointer-events","none"); - } - var text = node.append("svg:text") - .attr("class","red-ui-flow-node-label") - .attr("x", 38) - .attr("dy", ".35em") - .attr("text-anchor","start") - .classed("hide",hideLabel); - - if (d._def.align) { - text.attr("class","red-ui-flow-node-label red-ui-flow-node-label-"+d._def.align); - if (d._def.align === "right") { - text.attr("text-anchor","end"); - } - } - - var status = node.append("svg:g").attr("class","red-ui-flow-node-status-group").style("display","none"); - var statusRect = status.append("rect").attr("class","red-ui-flow-node-status") - .attr("x",6).attr("y",1).attr("width",9).attr("height",9) - .attr("rx",2).attr("ry",2).attr("stroke-width","3"); - var statusLabel = status.append("svg:text") - .attr("class","red-ui-flow-node-status-label") - .attr("x",20).attr("y",10); - - node.append("g").attr("class","red-ui-flow-node-changed hide").attr("transform","translate(20, -2)").append("circle").attr("r",5); - var nodeErrorButton = node.append("g").attr("class","red-ui-flow-node-error hide").attr("transform","translate(0, -2)").append("path").attr("d","M -5,4 l 10,0 -5,-8 z"); - nodeErrorButton.on("mouseenter", function() { - if (d.validationErrors && d.validationErrors.length > 0) { - clearTimeout(portLabelHoverTimeout); - portLabelHoverTimeout = setTimeout(function() { - var pos = getElementPosition(nodeErrorButton.node()); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0]), - (pos[1]), - RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - "), - "top" - ); - },500); - } - }).on("mouseleave", function() { - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - }); - }); - - node.each(function(d,i) { - if (d.dirty) { - var isLink = (d.type === "link in" || d.type === "link out") - var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; - dirtyNodes[d.id] = d; - //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette - if (d.resize) { - var l = RED.utils.getNodeLabel(d); - var ow = d.w; - 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((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); + if (ow !== undefined) { d.x += (d.w-ow)/2; - d.resize = false; } - 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; }) + d.resize = false; + } - //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) + ")"; }); - if (mouse_mode != RED.state.MOVING_ACTIVE) { - thisNode.classed("red-ui-flow-node-selected",function(d) { return 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; }) - ; - if ((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) { - thisNode.selectAll(".red-ui-flow-node-icon-group").classed("red-ui-flow-node-icon-group-right", true); - thisNode.selectAll(".red-ui-flow-node-label").classed("red-ui-flow-node-label-right", true).attr("text-anchor", "end"); - } else { - thisNode.selectAll(".red-ui-flow-node-icon-group").classed("red-ui-flow-node-icon-group-right", false); - thisNode.selectAll(".red-ui-flow-node-label").classed("red-ui-flow-node-label-right", false).attr("text-anchor", "start"); + //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); + this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + if (mouse_mode != RED.state.MOVING_ACTIVE) { + this.classList.toggle("red-ui-flow-node-disabled", d.d === true); + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + this.__mainRect__.setAttribute("width", d.w) + this.__mainRect__.setAttribute("height", d.h) + this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); + + if (labelParts) { + // The label has changed + var sa = labelParts.lines; + var sn = labelParts.lines.length; + var textLines = this.__textGroup__.childNodes; + while(textLines.length > sn) { + textLines[textLines.length-1].remove(); } - thisNode.selectAll(".red-ui-flow-node-icon-group").attr("transform", function (d) { return "translate(0, 0)"; }); - thisNode.selectAll(".red-ui-flow-node-label").attr("x", function (d) { return 38; }); - thisNode.selectAll(".red-ui-flow-node-icon-group-right").attr("transform", function(d){return "translate("+(d.w-30)+",0)"}); - thisNode.selectAll(".red-ui-flow-node-label-right").attr("x", function(d){return d.w-38}); - //thisNode.selectAll(".red-ui-flow-node-icon-right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?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)}); - - var inputPorts = thisNode.selectAll(".red-ui-flow-port-input"); - if ((!isLink || (showAllLinkPorts === -1 && !activeLinkNodes[d.id])) && d.inputs === 0 && !inputPorts.empty()) { - inputPorts.remove(); - } else if (((isLink && (showAllLinkPorts===PORT_TYPE_INPUT||activeLinkNodes[d.id]))|| d.inputs === 1) && inputPorts.empty()) { - var inputGroup = thisNode.append("g").attr("class","red-ui-flow-port-input"); - var inputGroupPorts; - - if (d.type === "link in") { - inputGroupPorts = inputGroup.append("circle") - .attr("cx",-1).attr("cy",5) - .attr("r",5) - .attr("class","red-ui-flow-port red-ui-flow-link-port") - } else { - inputGroupPorts = inputGroup.append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + for (var i=0; i numOutputs) { + var port = this.__outputs__.pop(); + port.remove(); + } + for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { + var portGroup; + if (portIndex === this.__outputs__.length) { + portGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + portGroup.setAttribute("class","red-ui-flow-port-output"); + var portPort; + if (d.type === "link out") { + portPort = document.createElementNS("http://www.w3.org/2000/svg","circle"); + portPort.setAttribute("cx",11); + portPort.setAttribute("cy",5); + portPort.setAttribute("r",5); + portPort.setAttribute("class","red-ui-flow-port red-ui-flow-link-port"); } else { - current_url = faIcon.attr("xlink:href"); + portPort = document.createElementNS("http://www.w3.org/2000/svg","rect"); + portPort.setAttribute("rx",3); + portPort.setAttribute("ry",3); + portPort.setAttribute("width",10); + portPort.setAttribute("height",10); + portPort.setAttribute("class","red-ui-flow-port"); } - var new_url = RED.utils.getNodeIcon(d._def,d); - if (new_url !== current_url) { - if (!icon.empty()) { - icon.remove(); - } else { - faIcon.remove(); - } - var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); - createIconAttributes(new_url, iconGroup, d); + portGroup.appendChild(portPort); + portPort.__data__ = this.__data__; + portPort.__portType__ = PORT_TYPE_OUTPUT; + portPort.__portIndex__ = portIndex; + portPort.addEventListener("mousedown", portMouseDownProxy); + portPort.addEventListener("touchstart", portTouchStartProxy); + portPort.addEventListener("mouseup", portMouseUpProxy); + portPort.addEventListener("touchend", portTouchEndProxy); + portPort.addEventListener("mouseover", portMouseOverProxy); + portPort.addEventListener("mouseout", portMouseOutProxy); + + this.appendChild(portGroup); + this.__outputs__.push(portGroup); + } else { + portGroup = this.__outputs__[portIndex]; + } + var x = d.w - 5; + var y = (d.h/2)-((numOutputs-1)/2)*13; + portGroup.setAttribute("transform","translate("+x+","+((y+13*portIndex)-5)+")") + } + if (d._def.icon) { + var icon = thisNode.select(".red-ui-flow-node-icon"); + var faIcon = thisNode.select(".fa-lg"); + var current_url; + if (!icon.empty()) { + current_url = icon.attr("xlink:href"); + } else { + current_url = faIcon.attr("xlink:href"); + } + var new_url = RED.utils.getNodeIcon(d._def,d); + if (new_url !== current_url) { + if (!icon.empty()) { + icon.remove(); + } else { + faIcon.remove(); } + var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); + createIconAttributes(new_url, iconGroup, d); + icon = thisNode.select(".red-ui-flow-node-icon"); + faIcon = thisNode.select(".fa-lg"); } - 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); }); + icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); + this.__iconShade__.setAttribute("height", d.h ); + this.__iconShadeBorder__.setAttribute("d", + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + ); + faIcon.attr("y",(d.h+13)/2); + } + this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); + this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); + this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); + this.__errorBadge__.classList.toggle("hide", d.valid); - 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; }); + thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { + var port = d3.select(this); + port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) + }); - thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { - var port = d3.select(this); - port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) - }); + if (d._def.button) { + var buttonEnabled = isButtonEnabled(d); + this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - 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;}); + var x = d._def.align == "right"?d.w-6:-25; + if (d._def.button.toggle && !d[d._def.button.toggle]) { + x = x - (d._def.align == "right"?8:-8); + } + this.__buttonGroup__.setAttribute("transform", "translate("+x+",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("transform",function(d){ - var x = d._def.align == "right"?d.w-6:-25; - if (d._def.button.toggle && !d[d._def.button.toggle]) { - x = x - (d._def.align == "right"?8:-8); - } - return "translate("+x+",2)"; - }); - thisNode.selectAll(".red-ui-flow-node-button rect").attr("fill-opacity",function(d){ - if (d._def.button.toggle) { - return d[d._def.button.toggle]?1:0.2; - } - return 1; - }); + if (d._def.button.toggle) { + this.__buttonGroupButton__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) + this.__buttonGroupBackground__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) + } - if (d._def.button && (typeof d._def.button.visible === "function")) { // is defined and a function... + if (typeof d._def.button.visible === "function") { // is defined and a function... if (d._def.button.visible.call(d) === false) { - thisNode.selectAll(".red-ui-flow-node-button").style("display","none"); + this.__buttonGroup__.style.display = "none"; } else { - thisNode.selectAll(".red-ui-flow-node-button").style("display","inherit"); + this.__buttonGroup__.style.display = "inherit"; } } - - // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); - // thisNode.selectAll("text.node_badge_label").text(function(d,i) { - // if (d._def.badge) { - // if (typeof d._def.badge == "function") { - // try { - // return d._def.badge.call(d); - // } catch(err) { - // console.log("Definition error: "+d.type+".badge",err); - // return ""; - // } - // } else { - // return d._def.badge; - // } - // } - // return ""; - // }); } - - if (d.dirtyStatus) { - if (!showStatus || !d.status) { - thisNode.selectAll(".red-ui-flow-node-status-group").style("display","none"); - } else { - thisNode.selectAll(".red-ui-flow-node-status-group").style("display","inline"); - var fill = status_colours[d.status.fill]; // Only allow our colours for now - if (d.status.shape == null && fill == null) { - thisNode.selectAll(".red-ui-flow-node-status").style("display","none"); - thisNode.selectAll(".red-ui-flow-node-status-group").attr("transform","translate(-14,"+(d.h+3)+")"); - } else { - thisNode.selectAll(".red-ui-flow-node-status-group").attr("transform","translate(3,"+(d.h+3)+")"); - var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; - thisNode.selectAll(".red-ui-flow-node-status").style("display","inline").attr("class","red-ui-flow-node-status "+statusClass); - } - if (d.status.hasOwnProperty('text')) { - thisNode.selectAll(".red-ui-flow-node-status-label").text(d.status.text); - } else { - thisNode.selectAll(".red-ui-flow-node-status-label").text(""); - } - } - delete d.dirtyStatus; - } - - d.dirty = false; + // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); + // thisNode.selectAll("text.node_badge_label").text(function(d,i) { + // if (d._def.badge) { + // if (typeof d._def.badge == "function") { + // try { + // return d._def.badge.call(d); + // } catch(err) { + // console.log("Definition error: "+d.type+".badge",err); + // return ""; + // } + // } else { + // return d._def.badge; + // } + // } + // return ""; + // }); } - }); + if (d.dirtyStatus) { + if (!showStatus || !d.status) { + this.__statusGroup__.style.display = "none"; + } else { + this.__statusGroup__.style.display = "inline"; + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + this.__statusShape__.style.display = "none"; + this.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); + } else { + this.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); + var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; + this.__statusShape__.style.display = "inline"; + this.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); + } + if (d.status.hasOwnProperty('text')) { + this.__statusLabel__.textContent = d.status.text; + } else { + this.__statusLabel__.textContent = ""; + } + } + delete d.dirtyStatus; + } + 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; + } + } + } + } + }); var link = linkLayer.selectAll(".red-ui-flow-link").data( activeLinks, function(d) { @@ -3221,89 +3988,66 @@ RED.view = (function() { linkEnter.each(function(d,i) { var l = d3.select(this); - d.added = true; - l.append("svg:path").attr("class","red-ui-flow-link-background red-ui-flow-link-path") - .classed("red-ui-flow-link-link", function(d) { return d.link }) - .on("mousedown",function(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - mousedown_link = d; - clearSelection(); - selected_link = mousedown_link; - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); - if (d3.event.metaKey || d3.event.ctrlKey) { - l.classed("red-ui-flow-link-splice",true); - showQuickAddDialog(d3.mouse(this), selected_link); - } - }) - .on("touchstart",function(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - mousedown_link = d; - clearSelection(); - selected_link = mousedown_link; - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); + var pathContents = document.createDocumentFragment(); - var obj = d3.select(document.body); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - touchStartTime = setTimeout(function() { - touchStartTime = null; - showTouchMenu(obj,pos); - },touchLongPressTimeout); - }) - l.append("svg:path").attr("class","red-ui-flow-link-outline red-ui-flow-link-path"); - l.append("svg:path").attr("class","red-ui-flow-link-line red-ui-flow-link-path") - .classed("red-ui-flow-link-link", function(d) { return d.link }) - .classed("red-ui-flow-subflow-link", function(d) { return !d.link && activeSubflow }); + d.added = true; + var pathBack = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathBack.__data__ = d; + pathBack.setAttribute("class","red-ui-flow-link-background red-ui-flow-link-path"+(d.link?" red-ui-flow-link-link":"")); + this.__pathBack__ = pathBack; + pathContents.appendChild(pathBack); + d3.select(pathBack) + .on("mousedown",linkMouseDown) + .on("touchstart",linkTouchStart) + + var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathOutline.__data__ = d; + pathOutline.setAttribute("class","red-ui-flow-link-outline red-ui-flow-link-path"); + this.__pathOutline__ = pathOutline; + pathContents.appendChild(pathOutline); + + var pathLine = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathLine.__data__ = d; + pathLine.setAttribute("class","red-ui-flow-link-line red-ui-flow-link-path"+ + (d.link?" red-ui-flow-link-link":(activeSubflow?" red-ui-flow-subflow-link":""))); + this.__pathLine__ = pathLine; + pathContents.appendChild(pathLine); + + l[0][0].appendChild(pathContents); }); link.exit().remove(); - var links = linkLayer.selectAll(".red-ui-flow-link-path"); - links.each(function(d) { + link.each(function(d) { var link = d3.select(this); if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { - if (/red-ui-flow-link-line/.test(link.attr('class'))) { - link.classed("red-ui-flow-subflow-link", function(d) { return !d.link && activeSubflow }); + var numOutputs = d.source.outputs || 1; + var sourcePort = d.sourcePort || 0; + var y = -((numOutputs-1)/2)*13 +13*sourcePort; + d.x1 = d.source.x+d.source.w/2; + d.y1 = d.source.y+y; + d.x2 = d.target.x-d.target.w/2; + d.y2 = d.target.y; + + // return "M "+d.x1+" "+d.y1+ + // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ + // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ + // d.x2+" "+d.y2; + var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); + if (/NaN/.test(path)) { + path = "" } - link.attr("d",function(d){ - var numOutputs = d.source.outputs || 1; - var sourcePort = d.sourcePort || 0; - var y = -((numOutputs-1)/2)*13 +13*sourcePort; - d.x1 = d.source.x+d.source.w/2; - d.y1 = d.source.y+y; - d.x2 = d.target.x-d.target.w/2; - d.y2 = d.target.y; - - // return "M "+d.x1+" "+d.y1+ - // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ - // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ - // d.x2+" "+d.y2; - var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); - if (/NaN/.test(path)) { - return "" - } - return path; - }); - link.classed("red-ui-flow-node-disabled", function(d) { return d.source.d || d.target.d; }); + this.__pathBack__.setAttribute("d",path); + this.__pathOutline__.setAttribute("d",path); + this.__pathLine__.setAttribute("d",path); + this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); + this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); } - }) + this.classList.toggle("red-ui-flow-link-selected", !!(d===selected_link||d.selected)); - link.classed("red-ui-flow-link-selected", function(d) { return d === selected_link || d.selected; }); - link.classed("red-ui-flow-link-unknown",function(d) { + var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); + this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) delete d.added; - return d.target.type == "unknown" || d.source.type == "unknown" - }); + }) var offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow").data( activeFlowLinks, function(d) { @@ -3334,24 +4078,24 @@ RED.view = (function() { var y = -(flows.length-1)*h/2; var linkGroups = g.selectAll(".red-ui-flow-link-group").data(flows); var enterLinkGroups = linkGroups.enter().append("g").attr("class","red-ui-flow-link-group") - .on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',true)}) - .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',false)}) - .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) - .on('mouseup', function(f) { - if (mouse_mode !== 0) { - return - } - d3.event.stopPropagation(); - var targets = d.links[f]; - RED.workspaces.show(f); - targets.forEach(function(n) { - n.selected = true; - n.dirty = true; - moving_set.push({n:n}); - }); - updateSelection(); - redraw(); + .on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',true)}) + .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',false)}) + .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) + .on('mouseup', function(f) { + if (mouse_mode !== 0) { + return + } + d3.event.stopPropagation(); + var targets = d.links[f]; + RED.workspaces.show(f); + targets.forEach(function(n) { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); }); + updateSelection(); + redraw(); + }); enterLinkGroups.each(function(f) { var linkG = d3.select(this); linkG.append("svg:path") @@ -3423,6 +4167,200 @@ 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; + var margin = 26; + d.nodes.forEach(function(n) { + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-margin); + maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+margin); + } else { + minX = Math.min(minX,n.x-margin) + minY = Math.min(minY,n.y-margin) + maxX = Math.max(maxX,n.x+n.w+margin) + maxY = Math.max(maxY,n.y+n.h+margin) + } + }); + + 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) { + var labelParts = getLabelParts(d.name||"","red-ui-flow-group-label"); + d.minWidth = labelParts.width + 8; + d.labels = labelParts.lines; + } else { + d.minWidth = 40; + d.labels = []; + } + } + d.w = Math.max(d.minWidth,d.w); + if (d.style.label && d.labels.length > 0) { + var labelPos = d.style["label-position"] || "nw"; + var h = (d.labels.length-1) * 16; + if (labelPos[0] === "s") { + h += 8; + } + d.h += h; + if (labelPos[0] === "n") { + if (d.nodes.length > 0) { + 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; + }); + } else { + g.selectAll(".red-ui-flow-group-label-text").remove(); + } + } + + delete dirtyGroups[d.id]; + delete d.dirty; + } + }) } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( @@ -3436,7 +4374,6 @@ RED.view = (function() { if (d3.event) { d3.event.preventDefault(); } - } function focusView() { @@ -3474,29 +4411,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 +4597,25 @@ RED.view = (function() { var historyEvents = []; for (var i=0;i 0) { @@ -3670,7 +4630,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 +4674,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 +4699,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 +4738,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 +4813,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 +4865,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/base.scss b/packages/node_modules/@node-red/editor-client/src/sass/base.scss index 2aca0b8e1..391ddf83f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/base.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/base.scss @@ -67,6 +67,9 @@ text-decoration: none; color: $primary-text-color; } + a:focus { + outline: 1px solid $form-input-focus-color; + } p { margin: 0 0 10px; 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..d8ffd9402 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; @@ -129,7 +129,7 @@ $workspace-button-color-primary: #eee; $workspace-button-background-primary: #AD1625; $workspace-button-background-primary-hover: #6E0A1E; -$workspace-button-color-focus-outline: $form-input-border-color; +$workspace-button-color-focus-outline: $form-input-focus-color; $shade-color: rgba(160,160,160,0.5); @@ -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/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 658094571..bb91846bb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -175,3 +175,38 @@ .red-ui-menu-dropdown-submenu.disabled > a:before { border-right-color: $menuCaret; } + + +// Menu NG +ul.red-ui-menu:not(.red-ui-menu-dropdown) { + font-family: $primary-font; + font-size: 12px; + list-style-type: none; + padding: 0; + margin: 0; + li a { + display: block; + padding: 4px 8px 4px 16px; + clear: both; + font-weight: normal; + line-height: 20px; + color: $menuColor; + white-space: nowrap; + text-decoration: none; + + &:hover,&:focus { + color: $menuHoverColor; + text-decoration: none; + background-color: $menuHoverBackground; + border: none; + outline: none; + } + } + &.red-ui-menu-compact { + font-size: 12px; + li a { + line-height: 16px; + } + } + +} \ No newline at end of file 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..4e0aca45b 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: $secondary-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; @@ -591,6 +715,9 @@ button.red-ui-button.red-ui-editor-node-appearance-button { button.red-ui-toggleButton.toggle { text-align: left; + i { + min-width: 15px; + } } @@ -704,8 +831,18 @@ span.red-ui-editor-subflow-env-lang-icon { right: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; - } + +.red-ui-editor-subflow-env-input-type { + background: $secondary-background; + height: 100%; + box-sizing: border-box; +} +.red-ui-editor-subflow-env-input-type-placeholder { + color: $tertiary-text-color; + padding-left: 4px; +} + // .red-ui-editor-subflow-ui-grid { // width: 100%; // .red-ui-editableList-container { 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..0d41e43f6 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 @@ -35,13 +35,21 @@ pointer-events: none; -webkit-touch-callout: none; @include disable-selection; + + .red-ui-flow-node-label-text { + dominant-baseline: middle; + } + + &.red-ui-flow-node-label-right .red-ui-flow-node-label-text { + text-anchor: end; + } } .red-ui-flow-port-label { stroke-width: 0; fill: $secondary-text-color; font-size: 16px; - alignment-baseline: middle; + dominant-baseline: middle; text-anchor: middle; pointer-events: none; -webkit-touch-callout: none; @@ -71,6 +79,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; @@ -103,6 +153,15 @@ .red-ui-flow-node-button { fill: inherit; + &.red-ui-flow-node-button-disabled { + opacity: 0.4; + .red-ui-flow-node-button-button { + cursor: default; + } + } +} +.red-ui-flow-node-button-button { + cursor: pointer; } .red-ui-flow-node-button-background { fill: $node-background-placeholder; @@ -166,6 +225,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 +310,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..e42125c23 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,29 @@ } } } +.red-ui-projects-dialog-project-list-entry-delete-confirm { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 100%; + width: 1000px; + overflow: hidden; + padding: 5px 20px; + background: $secondary-background; + transition: left 0.4s; + white-space: nowrap; + > span { + line-height: 40px; + } + button { + margin-left: 20px; + } +} + +.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..b2710dfad 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,17 @@ 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: 100%; + } + + &:not(.red-ui-type-search) .red-ui-searchBox-container { + width: calc(100% - 30px); + } } .red-ui-type-search { @@ -39,6 +50,7 @@ border: 1px dashed $primary-border-color; border-bottom: none; padding: 0; + width: 100%; } .red-ui-search-results-container { display: none; @@ -87,6 +99,8 @@ } .red-ui-palette-icon { width: 15px; + position:relative; + left: -1px; } .red-ui-search-result-description { margin-left:28px; @@ -153,7 +167,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-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index f88cbfd73..787669fd7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -20,6 +20,10 @@ height: 100%; overflow-y:auto; @include disable-selection; + + &:focus { + outline: none; + } } ul.red-ui-sidebar-node-config-list { 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..d401e0c70 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,208 @@ 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% - 130px); + max-width: 250px; + background: $palette-header-background; + } + +} diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss index d0cd631fc..b925e5212 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss @@ -18,23 +18,32 @@ .red-ui-searchBox-container { position: relative; i { + position: absolute; + top: 9px; font-size: 10px; color: $secondary-text-color; } i.fa-search { - position: absolute; pointer-events: none; left: 8px; - top: 9px; + } + i.fa-caret-down { + position: absolute; + right: 6px; + font-size: 12px; } i.fa-times { position: absolute; - right: 5px; - top: 9px; + right: 4px; } + form.red-ui-searchBox-form { margin: 0; } + a.red-ui-searchBox-opts:hover { + color: $workspace-button-color-hover; + background: $workspace-button-background-hover; + } input.red-ui-searchBox-input { border-radius: 0; border: none; @@ -57,9 +66,12 @@ right: 0; top: 0; bottom: 0; - width: 20px; + width: 16px; + } + a.red-ui-searchBox-clear { display: none; } + .red-ui-searchBox-resultCount { position: absolute; right: 18px; @@ -70,4 +82,36 @@ font-size: 9px; border-radius: 4px; } + &.red-ui-searchBox-has-options { + a.red-ui-searchBox-clear { + right: 16px; + } + .red-ui-searchBox-resultCount { + right: 33px; + } + input.red-ui-searchBox-input { + padding-right: 33px; + } + } } +.red-ui-searchBox-compact { + + input:focus.red-ui-searchBox-input { + outline: 1px solid $form-input-focus-color; + } + + + input.red-ui-searchBox-input,input:focus.red-ui-searchBox-input { + border: 1px solid $secondary-border-color; + border-radius: 3px; + font-size: 12px; + height: 26px; + } + i.fa-times,i.fa-search, i.fa-caret-down { + top: 7px; + } + .red-ui-searchBox-resultCount { + top: 3px; + padding: 0 6px; + } +} \ No newline at end of file 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/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 49a02c206..3d1af605c 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -191,6 +191,7 @@ '$merge':{ args:[ 'array' ]}, '$millis':{ args:[ ]}, '$min':{ args:[ 'array' ]}, + '$moment':{ args:[ ]}, '$not':{ args:[ 'arg' ]}, '$now':{ args:[ ]}, '$number':{ args:[ 'arg' ]}, 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..b5ba345bd 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 @@ - - @@ -47,6 +49,24 @@ }); var candidateNodes = RED.nodes.filterNodes({type:targetType}); + var search = $("#node-input-link-target-filter").searchBox({ + style: "compact", + delay: 300, + change: function() { + var val = $(this).val().trim().toLowerCase(); + if (val === "") { + treeList.treeList("filter", null); + search.searchBox("count",""); + } else { + var count = treeList.treeList("filter", function(item) { + return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1) + }); + search.searchBox("count",count+" / "+candidateNodes.length); + } + } + }); + + var flows = []; var flowMap = {}; @@ -83,7 +103,8 @@ id: n.id, node: n, label: n.name||n.id, - selected: isChecked + selected: isChecked, + checkbox: true }) } }); diff --git a/packages/node_modules/@node-red/nodes/core/common/90-comment.html b/packages/node_modules/@node-red/nodes/core/common/90-comment.html index 76c4547c0..cca7cd51d 100644 --- a/packages/node_modules/@node-red/nodes/core/common/90-comment.html +++ b/packages/node_modules/@node-red/nodes/core/common/90-comment.html @@ -1,5 +1,5 @@ - 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..f0e5c7d68 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..a876ed741 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.trim() : ""; + node.fin = n.finalize ? n.finalize.trim() : ""; 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/10-switch.js b/packages/node_modules/@node-red/nodes/core/function/10-switch.js index 1761853a6..2d91e4b8d 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-switch.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-switch.js @@ -253,7 +253,7 @@ module.exports = function(RED) { for (var i=0; i + @@ -74,6 +82,7 @@ category: 'function', color:"#E6E0F8", defaults: { + name: {value:""}, op1: {value:"1", validate: RED.validators.typedInput("op1type")}, op2: {value:"0", validate: RED.validators.typedInput("op2type")}, op1type: {value:"val"}, @@ -82,8 +91,9 @@ extend: {value:"false"}, units: {value:"ms"}, reset: {value:""}, - bytopic: {value: "all"}, - name: {value:""} + bytopic: {value:"all"}, + topic: {value:"topic",required:true}, + outputs: {value:1} }, inputs:1, outputs:1, @@ -103,19 +113,48 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { + var that = this; + if (this.topic === undefined) { $("#node-input-topic").val("topic"); } + $("#node-input-topic").typedInput({default:'msg',types:['msg']}); + $("#node-input-bytopic").on("change", function() { + if ($("#node-input-bytopic").val() === "all") { + $("#node-stream-topic").hide(); + } else { + $("#node-stream-topic").show(); + } + }); + + if (this.outputs == 2) { $("#node-input-second").prop('checked', true) } + else { $("#node-input-second").prop('checked', false) } + + $("#node-input-second").change(function() { + if ($("#node-input-second").is(":checked")) { + $("#node-input-outputs").val(2); + } + else { + $("#node-input-outputs").val(1); + } + }); $("#node-then-type").on("change", function() { if ($(this).val() == "block") { $(".node-type-wait").hide(); $(".node-type-duration").hide(); + $("#node-second-output").hide(); + $("#node-input-second").prop('checked', false); + $("#node-input-outputs").val(1); } else if ($(this).val() == "loop") { if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); } $(".node-type-wait").hide(); $(".node-type-duration").show(); + $("#node-second-output").hide(); + $("#node-input-second").prop('checked', false); + $("#node-input-outputs").val(1); } else { if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); } $(".node-type-wait").show(); $(".node-type-duration").show(); + $("#node-second-output").show(); } }); @@ -177,7 +216,7 @@ } if ($("#node-then-type").val() == "loop") { $("#node-input-duration").val($("#node-input-duration").val() * -1); - } + } } }); 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..636117d71 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. --> - - - - - -