Compare commits

..

299 Commits

Author SHA1 Message Date
Nick O'Leary
2b9aca0c56 Add bin to release package 2015-06-16 23:39:33 +01:00
Nick O'Leary
3a17c3ee6d Merge pull request #680 from cpswan/patch-1
https link (rather than git) in dev instructions
2015-06-16 23:38:58 +01:00
Chris Swan
df09252ee0 https link (rather than git) in dev instructions 2015-06-16 23:20:10 +01:00
Nick O'Leary
f51778d417 Bump to 0.10.9 2015-06-16 22:19:31 +01:00
Nick O'Leary
119fc63794 Bump version and dependencies 2015-06-16 21:27:44 +01:00
Nick O'Leary
ee3425d3be Normalise ace editor font size 2015-06-16 20:56:09 +01:00
Nick O'Leary
31c979f30f Add deprecated node list 2015-06-16 15:32:41 +01:00
dceejay
89f2c26cd6 Give our own linked core nodes some leeway on version number
so we can apply fixes without bumping package - which was the whole point of moving them out...
2015-06-16 08:40:39 +01:00
dceejay
0dbf43d0aa Update Gruntfile so only core nodes are linted. 2015-06-15 20:38:40 +01:00
dceejay
8a6d11b191 tiny lint thing in debug node... missing ; 2015-06-15 20:38:09 +01:00
dceejay
d8eb926e2c Move out several core node to node-red-nodes ready for 0.10.8
Update package.json to match.
Part of #668
2015-06-15 20:37:36 +01:00
Nick O'Leary
21d0adbdae Handle null message in catch loop detection 2015-06-15 15:22:51 +01:00
Nick O'Leary
c5fd3a5753 Detect and prevent Catch node loops 2015-06-15 15:09:50 +01:00
Nick O'Leary
eae4e3d983 Add deploy warning for unused config nodes 2015-06-10 14:52:58 +01:00
Nick O'Leary
bb1fe8daef Allow nodesExcludes to specify node module name 2015-06-08 23:17:45 +01:00
Nick O'Leary
c5d8e09b41 Only delete node type from registry if id matches
If a node is moved out of node-red core to an npm installable
package, the first time NR runs after the move it correctly
deletes the node-red version from the registry. However it was
also removing the node constructors registered by the new
npm installed version as it wasn't checking what it was removing
came from the now-removed node.
2015-06-08 16:32:50 +01:00
dceejay
369eae3d92 remove http response function call example from info. (being deprecated) 2015-06-03 22:53:27 +01:00
Nick O'Leary
e2fa457ca2 Minimise amount of node redrawing done during drag 2015-06-03 10:05:31 +01:00
Nick O'Leary
871f764e98 Allow node icon definition to be a function 2015-06-02 22:07:45 +01:00
Nick O'Leary
f8853af902 Enabling metric/audit logging takes precedence over level 2015-06-02 16:23:38 +01:00
Nick O'Leary
4248d20f39 Add RED.util.generateId and ensure HTTP node adds proper id 2015-06-02 15:54:37 +01:00
Nick O'Leary
5cda08e7b0 Update trigger node UI 2015-05-31 20:58:28 +01:00
dceejay
7e9d96ee87 Let email node set a default filename for binary attachment
so it will send "something" if only a buffer arrives.
2015-05-31 20:27:27 +01:00
dceejay
974ac31d33 Hide right sidebar on start if screen narrower than 600px
so at last you can see something that looks like Node-RED
2015-05-31 12:58:42 +01:00
Nick O'Leary
0658b70631 Inject node not parsing 0-23 hour range correctly 2015-05-29 22:28:42 +01:00
Nick O'Leary
53258eeede Restore ability to add nodes by filename 2015-05-27 14:11:11 +01:00
dceejay
4f174308b9 Fix tcp node miscounting bytes, and check if staying connected first. 2015-05-26 20:08:58 +01:00
dceejay
98c9e40349 Add small note re escaping test to inject node. 2015-05-26 20:04:12 +01:00
dceejay
3a4756bd83 let urllib parse proxy url more cleanly in http-request node. 2015-05-23 18:55:11 +01:00
dceejay
6ccb05cb2c http-request node - relax proxy regex check to let userid:password through 2015-05-21 17:45:11 +01:00
dceejay
3c2d32b867 Correct incorrect pwm "tip" info for RPI GPIO node
Closes #648
Thanks @edent
2015-05-21 17:03:59 +01:00
dceejay
956050434f Add no_proxy to http-request node.
also make it support HTTP_PROXY and NO_PROXY (ie uppercase)
2015-05-19 11:07:54 +01:00
dceejay
38ab1550d2 change http request to use http_proxy environment rather than do it ourselves 2015-05-19 10:00:06 +01:00
dceejay
e852d1e57c remove unrequired require from Arduino node 2015-05-19 10:00:06 +01:00
Nick O'Leary
7de0216976 Add audit log entries on API calls
Closes #627
2015-05-14 14:22:56 +01:00
dceejay
911288e695 Arduino node - improved logging and port error handling. 2015-05-13 22:26:01 +01:00
dceejay
72e1f20383 Arduino - ensure port list populated on start,
also allow connect to first found board - by not specifying a port.
2015-05-13 21:59:15 +01:00
Nick O'Leary
d28a6eaf9d Add httpNodeMiddleware option
Closes #631

Enables custom middleware to be inserted in front of all HTTP In
nodes.
2015-05-13 21:25:37 +01:00
dceejay
17f3366556 Adds proxy support to http node - via settings.js 2015-05-13 13:54:47 +01:00
Nick O'Leary
0bef04ae0a Set NODE_ENV in grunt dev task to serve non-minified assets 2015-05-11 20:24:15 +01:00
dceejay
f11b906fd9 Websocket - fix broken test (or rather - fix code to pass test) 2015-05-10 10:00:44 +01:00
dceejay
518358d9dc Websocket - add reconnect capability when running as a client.
to close #643

Also adds node.status to nodes.
2015-05-10 00:12:52 +01:00
dceejay
5ffde21d83 revert change to editor change detector, and redo function node valid
code "flag" to report as non-boolean.
2015-05-08 15:31:48 +01:00
dceejay
052302b3e7 actually set valid to be false in function so .toString will work... 2015-05-07 22:02:46 +01:00
Nick O'Leary
fe1ce21114 toString values before checking for changes 2015-05-07 21:17:06 +01:00
Nick O'Leary
ce5c9da107 Ensure ace edit history is reset after initialisation 2015-05-07 21:12:46 +01:00
dceejay
cf25b2866e Allow http to accept delete properly, and put, options etc. 2015-05-07 16:45:44 +01:00
dceejay
07fd5a5f5f function node was marking changed even on no change. 2015-05-07 16:44:55 +01:00
Nick O'Leary
913fdac671 Add support for exclusive config nodes 2015-05-06 11:08:01 +01:00
Nick O'Leary
7dc838dea6 Fix click on debug message to reveal source node 2015-05-05 22:00:47 +01:00
Nick O'Leary
7112fd2a22 Partial deploy containing deleted subflow throws error 2015-05-05 13:53:40 +01:00
dceejay
56e8c143dd CONTRIBUTING - remove link to specific Issues (so we can use this file
for all the projects)
2015-05-05 12:50:46 +01:00
dceejay
7b4cbbe816 Add try catch to exec node stdout if utf8 check fails 2015-05-05 12:50:46 +01:00
Nick O'Leary
8f8ee4662d Initialise checkboxes in config editor for new nodes 2015-05-05 00:03:35 +01:00
Nick O'Leary
86013c7db4 Ensure node.credentials exists for nodes with registered creds
Fixes #639
2015-05-04 23:28:55 +01:00
Nick O'Leary
01aa3324f8 Fix lint error in user.js 2015-05-03 22:28:03 +01:00
Nick O'Leary
b9cfeee965 Navigate and submit login dialog with Enter key 2015-05-03 22:23:30 +01:00
Nick O'Leary
b3684a70b5 Handle Buffer payloads in HTTP Response node 2015-05-02 22:20:46 +01:00
Nick O'Leary
51fce9343b Ensure errors in node.receive are handled 2015-05-02 22:15:33 +01:00
Nick O'Leary
0c5c3448d0 Merge pull request #633 from scott1y/Issue#626
Added the capability to register custom loggers (for Issue #626)
2015-05-02 21:53:07 +01:00
Scott Yoshizawa
deaef3ab86 Corrected based on knolleary's comments. 2015-05-01 09:37:23 -04:00
Nick O'Leary
a443491c0c Merge pull request #630 from lostinthestory/swagger-gen
Added support for swagger doc to HTTP-In
2015-05-01 09:32:57 +01:00
Scott Yoshizawa
276d893198 Added the capability to register custom loggers (for Issue #626) 2015-04-30 17:01:22 -04:00
Nick O'Leary
653d0e71e4 Add meta tag for IE edge compatibility 2015-04-28 14:25:41 +01:00
Nick O'Leary
faa7d948a7 Update MQTT Out node status if already connected 2015-04-27 20:08:00 +01:00
Nick O'Leary
771342989e HTTP Request - handle null url
Fixes #632
2015-04-26 08:50:42 +01:00
Cody Walker
e9ce519e4b Added support for swagger doc to HTTP-In 2015-04-24 13:53:38 -05:00
Nick O'Leary
c016b102eb Add async error handler to UDP out node
Fixes #629
2015-04-24 11:17:18 +01:00
Nick O'Leary
0583c60837 Editor link filter failing for src_port == 0
Fixes #621
2015-04-22 22:53:21 +01:00
Nick O'Leary
1c1a85dcef editorTheme: allow header logo/title to be a link 2015-04-22 14:03:42 +01:00
Nick O'Leary
c71e76335b Serial Out not appending character
Fixes #622
2015-04-21 09:43:00 +01:00
Nick O'Leary
c1a32c4eb9 Merge pull request #620 from drwoods/master
fix 3 localfilesystem tests failing on Windows
2015-04-20 15:27:38 +01:00
drwoods
9ad1f769d3 localfilesystem tests failing on Windows 2015-04-20 10:13:00 -04:00
Nick O'Leary
87f8fd34b8 Ensure full deploy is visually selected by default 2015-04-20 11:17:27 +01:00
dceejay
e206d2919e More defensive on closing tcp connections,
and tidy up some lint
2015-04-19 12:13:45 +01:00
dceejay
9d809aa2ba Point Grunt to use external jshintrc 2015-04-16 11:53:39 +01:00
dceejay
8f744794e4 push opinionated .jshintrc - matches Grunt build. for starters. 2015-04-16 10:22:12 +01:00
Nick O'Leary
78ab4217be Arduino pin values should be strings not numbers 2015-04-15 09:34:03 +01:00
Nick O'Leary
d090df94c5 Arduino output - ensure string/number payloads handled
A change to tighten the type checking meant passing a string
'0' or '1' no longer matched the tests for numeric 0 or 1.
2015-04-14 23:14:13 +01:00
Nick O'Leary
937f26da41 Merge pull request #606 from drwoods/master
Ignore Eclipse project files and fix some jshint warnings
2015-04-14 21:34:23 +01:00
drwoods
98e3ff014e ignore Eclipse files and resolve some Eclipse JSHint warnings
squashed 4 commits into 1
2015-04-14 14:17:08 -04:00
dceejay
6f84526364 Remove pre-req for mkdirp package.
(fs-extra has all the features needed already)
2015-04-14 18:41:48 +01:00
dceejay
105d38c885 MQTT node - don't report missing topic if payload is missing... 2015-04-14 18:39:42 +01:00
dceejay
d7bdcd69fc fix exec missing addpayload,
improve switch null test (to include null object)
check mqtt has payload before sending
2015-04-14 17:15:56 +01:00
Nick O'Leary
87e537da90 Merge pull request #611 from node-red/themes
Editor Themes
2015-04-13 22:51:52 +01:00
Nick O'Leary
8f16695f06 Add theme tests 2015-04-13 22:15:15 +01:00
Nick O'Leary
8403f6291f Tidy deploy button css 2015-04-13 21:50:40 +01:00
Nick O'Leary
5af6ac3e80 Rename menu ids to be logically consistent 2015-04-13 16:48:38 +01:00
Nick O'Leary
0d557094b2 Move help customisation under menu settings 2015-04-13 15:32:11 +01:00
Nick O'Leary
a2aa78afd4 Add main menu customisation 2015-04-13 13:55:17 +01:00
Nick O'Leary
b0de8abb63 Customise help link label/url 2015-04-13 11:35:52 +01:00
Nick O'Leary
6ff540ed08 Customise login image 2015-04-13 10:37:30 +01:00
Nick O'Leary
2b8ed9850b Pass deployButton and userMenu theme options to ui 2015-04-13 09:48:49 +01:00
Nick O'Leary
dcd579b5e3 Some scss tidy up 2015-04-13 00:11:32 +01:00
Nick O'Leary
c9d2d301aa Add editorTheme setting 2015-04-13 00:11:11 +01:00
Nick O'Leary
1aaef598a5 Concatenate vendor js/css where possible 2015-04-13 00:10:10 +01:00
dceejay
73d1f3d0e8 Expose rest of email node header fields. 2015-04-12 00:46:03 +01:00
Nick O'Leary
e369ded6c5 Allow deploy button to be customised 2015-04-11 22:51:00 +01:00
dceejay
269846c587 Catch bad folder error in email node 2015-04-11 19:24:32 +01:00
dceejay
8dc98420db handle pure plain text messages in email node. 2015-04-11 16:48:50 +01:00
dceejay
1014abe92f Fix email node repeat send, add to, cc, bcc fields, and ability to select inbox. 2015-04-11 16:09:13 +01:00
Nick O'Leary
6927f10f8f Fix login image path 2015-04-10 15:39:29 +01:00
Nick O'Leary
49d3a7190a Update nodemonignore/watch rules 2015-04-09 22:18:37 +01:00
Nick O'Leary
a2e65b0018 Prepend copyright to built editor files 2015-04-09 21:12:39 +01:00
Nick O'Leary
f48ee01a03 Ensure MQTT nodes unsubscribe before disconnect
Fixes #609

Needed for partial deployment - the nodes assumed the
connection would always be closed when a deploy occurs.
2015-04-09 20:10:34 +01:00
Nick O'Leary
0e926c566b Add missing header scss 2015-04-09 17:26:11 +01:00
Nick O'Leary
d2c4c2c34c Split css into individual scss files 2015-04-09 14:46:29 +01:00
Nick O'Leary
c2253d1e25 Merge pull request #608 from node-red/build2
Add build process to grunt
2015-04-09 13:02:01 +01:00
Nick O'Leary
eae16b6e8c Add tests for build check 2015-04-09 12:13:46 +01:00
Nick O'Leary
868ae5b5dd Add .dist to gitignore 2015-04-09 11:37:16 +01:00
Nick O'Leary
1406503e10 Add build info to README 2015-04-09 10:22:05 +01:00
Nick O'Leary
9ca9d88546 Drop Node 0.8 from travis build due to devDependencie issues 2015-04-09 10:22:05 +01:00
Nick O'Leary
203d3f672c Add build step to Grunt file 2015-04-09 10:22:05 +01:00
dceejay
698b2688f6 Add port, ssl and allow self signed certs to IRC node 2015-04-09 09:55:34 +01:00
dceejay
be1620dd07 Fix watch node to stat file sizes ok
(and also not to when file missing)
2015-04-08 21:43:44 +01:00
Nick O'Leary
e1f0969957 Add dedicated route for editor index.html 2015-04-08 21:29:55 +01:00
Nick O'Leary
e1dd8cf2ab Restore node order in palette following async changes
The move to async loading of node files led to them appearing
out of order in the palette.
2015-04-08 20:17:24 +01:00
dceejay
8ee90777ee Move serialport (and irc) to optional dependancies so installs ok in more places.
(albeit without serialport etc... - but not many clouds have serialports)
2015-04-08 09:55:48 +01:00
Nick O'Leary
2fe9c1e55f Change node config load to be async 2015-04-07 23:46:52 +01:00
Nick O'Leary
9dd7e2e43d Refactor registry structure
Splits registry up into smaller components.

Unit tests still drive api via registry/index_spec - still
need to split them up into the currently blank _spec files
2015-04-07 16:02:15 +01:00
Nick O'Leary
5efbdf5d04 Revert removal of flow storage api
Whilst we know with the file system storage inplementation
getLibraryEntry is a suitable replacement for getFlow, this
may not be the case with other implementations.

The storage code uses the deprecated functions if they are
present - so the core code should call them and let the storage
layer decide what it calls.
2015-04-05 20:54:11 +01:00
dceejay
5be3472413 For some reason http request node icon was aligned right,
inconsistent with most other "function" nodes...
2015-04-05 17:57:06 +01:00
dceejay
a9a0953653 Slide timing window to left for trigger node test. 2015-04-04 22:27:25 +01:00
Nick O'Leary
d4ac4c44d0 Fix library test for deprecated storage api 2015-04-04 19:33:37 +01:00
Nick O'Leary
f459ff8ad0 Remove use of deprecated storage api 2015-04-04 19:25:07 +01:00
Nick O'Leary
b96ea36b70 Deprecate getAllFlows/getFlow/saveFlow storage functions
They were specialised versions of get/saveLibraryEntry that
complicated the interface.

This change removes them from localfilesystem, but the top
level module checks for their existence and uses them if
they are there - for backwards compatibility.
2015-04-03 23:05:56 +01:00
Nick O'Leary
e543cc0fed Log settings file in use on start up 2015-04-03 23:05:15 +01:00
Nick O'Leary
0b1b4df210 Update README / Remove INSTALL.md 2015-04-02 21:19:57 +01:00
Nick O'Leary
e59ffb0b19 Bump to dev version 0.10.7 2015-04-01 10:48:57 +01:00
Nick O'Leary
120c8f2c28 Bump version 0.10.6 2015-04-01 10:28:04 +01:00
Nick O'Leary
fbfc5c8a2d Add error codes to api responses 2015-03-31 22:29:42 +01:00
dceejay
31b018c80e Missed closing brace in email node fix 2015-03-31 16:39:00 +01:00
dceejay
255d708fb6 updates to serial, watch, websocket, udp, twitter, email to handle no payload. 2015-03-31 09:21:11 +01:00
dceejay
78d1da5fbc Updates to sentiment, exec and range to handle missing payload properties 2015-03-31 08:35:56 +01:00
dceejay
9c22a770ef remove superfluous console.log from debug node 2015-03-30 21:59:54 +01:00
Nick O'Leary
b201828236 Remove 'loaded' property from external node info object
and fix everything that doing this broke
2015-03-30 21:49:20 +01:00
Nick O'Leary
2a8a885271 Standardise API error response format 2015-03-30 14:16:04 +01:00
Nick O'Leary
7adefd6ee0 Add access_token expiry 2015-03-30 14:14:32 +01:00
Nick O'Leary
f967a5ecdc Fix auth on comms link and for anon user
The move to honour scope level of token broke the comms link
checking as well as the permissions checking for anon users.
2015-03-29 22:27:07 +01:00
Nick O'Leary
c8d6dc2531 Auth permission should honour the token scope 2015-03-29 21:59:48 +01:00
Nick O'Leary
216b5fba7a Increment subflow name on import of duplicate 2015-03-28 22:12:18 +00:00
Nick O'Leary
f4ec9a72d5 Increase tab max-width 2015-03-28 22:08:39 +00:00
Nick O'Leary
cf0c2825eb Expand palette category when first entry added 2015-03-28 21:46:27 +00:00
Nick O'Leary
be46c419dc Center palette label whilst being dragged 2015-03-26 20:08:25 +00:00
Nick O'Leary
62c68d06fe Merge pull request #598 from dceejay/dcjtests
new tests for sentiment, file and csv nodes
2015-03-26 16:57:04 +00:00
Nick O'Leary
4f4d8419bc Nudge palette labels to rebalance them 2015-03-26 16:55:55 +00:00
dceejay
16e17954b4 add try catch to helper shutdown,
tiny fixes for exec and trigger based on tests
2015-03-26 15:02:08 +00:00
Nick O'Leary
cc1d080a5a Remove add/removeNode by filename api
Only support add/remove by module name
2015-03-26 11:39:35 +00:00
Nick O'Leary
dd7f4f6752 Filter loaded property from /nodes endpoint 2015-03-26 11:38:51 +00:00
Nick O'Leary
9daeba02b5 Flow.registerType should indicate if type was missing or not 2015-03-26 11:37:24 +00:00
dceejay
8a96dbd121 New tests for Trigger, catch and unknown nodes 2015-03-25 21:58:26 +00:00
Nick O'Leary
2a57d0b6d0 auth/login should return empty object when insecure 2015-03-25 20:33:29 +00:00
dceejay
8a5c1bade5 new tests for sentiment, file and csv
(inc a bugfix for csv :-)

tweka of 0.8 ?

temp remove buffer should equall test from file node test

comment out failing test in file node (0.8 specific fail...)

stagger multiple writes slightly in file test
2015-03-25 14:58:57 +00:00
dceejay
fcc6943f98 Extra tests for html, xml, json and tail nodes
(and some consistent passing of missing payloads)
2015-03-24 17:43:47 +00:00
dceejay
72a9de058d tests for defaults in switch, change and range nodes. 2015-03-24 17:35:54 +00:00
Nick O'Leary
8748be28b7 Bump to 0.10.5 2015-03-23 10:56:14 +00:00
Nick O'Leary
20bdea7ae0 Increase registry test coverage 2015-03-22 22:54:52 +00:00
Nick O'Leary
e19b8d35a9 Modules not properly removed from config.json 2015-03-22 21:48:12 +00:00
Nick O'Leary
81df74dfc8 Server not waiting for settings to load 2015-03-22 20:55:38 +00:00
Nick O'Leary
153fa7478f Increase flows test coverage 2015-03-22 20:12:10 +00:00
Nick O'Leary
500e9a4010 Increase localfilesystem test coverage 2015-03-22 17:40:42 +00:00
dceejay
5352fc87ee add extra tests to debug, delay & template 2015-03-22 09:38:42 +00:00
dceejay
f07fd64ffb Make trigger have sensible defaults when dragged on. 2015-03-21 19:27:39 +00:00
Nick O'Leary
36f299c031 Improve core test coverage 2015-03-21 17:42:06 +00:00
Nick O'Leary
78cf310c58 Collapse palette category when emptied 2015-03-20 23:12:52 +00:00
Nick O'Leary
18a3d71024 Double-click on subflow palette node to open flow 2015-03-20 23:11:24 +00:00
Nick O'Leary
26db1048f9 Remove dialog close button via css not jquery 2015-03-20 23:11:03 +00:00
Nick O'Leary
b61a250d58 Debug message formatting mangling brackets 2015-03-20 22:09:58 +00:00
Nick O'Leary
1d10eba0cc Propagate changed flag to parent subflow 2015-03-20 21:20:04 +00:00
Nick O'Leary
35042132fa Make node-info properties collapsable in sidebar 2015-03-20 21:13:00 +00:00
Nick O'Leary
57c049b49f Palette filter on label as well as id 2015-03-20 11:40:34 +00:00
Nick O'Leary
7a0ce0c957 Update style of nodes in palette to match workspace 2015-03-20 11:37:47 +00:00
Nick O'Leary
eb4cadb0b5 Propagate valid flag to parent subflows 2015-03-19 23:11:55 +00:00
Nick O'Leary
ac0ca083c0 Import of subflow loses certain internal wires
Wires between subflow inputs and outputs are getting lost when
JSON is imported.
2015-03-19 22:59:09 +00:00
dceejay
9afb4a9315 reverse overide behaviour on file, http and email. Node properties now
have priority. Warn user if msg.property tries to override. 
Warning should  be removed at next major verion bump.
2015-03-19 21:25:43 +00:00
dceejay
df065e94b7 add extra tests to user_spec to test some else conditions. 2015-03-19 19:57:41 +00:00
dceejay
a9789697e7 add bcrypt as optional dependency to speed up Pi,
but not force compile on Windows.
Fix to close Issue #585
2015-03-19 11:36:48 +00:00
Nick O'Leary
2d91be8814 Disable node buttons in subflow view
Closes #592
2015-03-19 11:19:44 +00:00
Nick O'Leary
5c58b0c2f4 Revalidate all config node users after edit 2015-03-19 10:49:56 +00:00
Nick O'Leary
f0139f9808 Add multi-rule support to Change node 2015-03-18 16:20:50 +00:00
dceejay
5610a3184e small spelling mistake in deploy.js 2015-03-17 15:38:31 +00:00
Nick O'Leary
b202c73708 Allow access_token to be provided in url 2015-03-17 15:15:24 +00:00
Nick O'Leary
dd4cec84bf Add node.send/on to Function node 2015-03-17 13:40:12 +00:00
Nick O'Leary
a1dac1e290 Resize ace editor on dialog-open 2015-03-17 13:36:50 +00:00
Nick O'Leary
e199d6725e Test helper - initialise credentials with express instance 2015-03-16 21:57:31 +00:00
dceejay
aef38b945d Change http request node info to suggest {{{ rather than {{ to supress
html escape when forming urls from mustache.
2015-03-16 20:22:25 +00:00
dceejay
cd5eac2cbb Add type and size reporting to the file watch node. 2015-03-16 20:21:05 +00:00
dceejay
8fea443e71 Add error msg to rpi node 2015-03-16 17:09:13 +00:00
dceejay
2a47951e46 make sure MQTT msg has a topic 2015-03-16 17:07:46 +00:00
dceejay
5234fda266 Tidy up arduino node slightly and update settings example to work nicer
with JohnnyFive
2015-03-16 17:07:17 +00:00
Nick O'Leary
e63067cd9f Add OS X command symbol to keyboard dialog 2015-03-16 15:40:25 +00:00
Nick O'Leary
be61cf6a88 Add node.error handling to core nodes 2015-03-16 13:58:01 +00:00
Nick O'Leary
5efc89d514 Warn when leaving editor with undeployed changes 2015-03-15 23:48:02 +00:00
Nick O'Leary
9c104faff3 Use RED.nodes.filterLinks in pref to RED.nodes.eachLink 2015-03-15 23:41:16 +00:00
Nick O'Leary
cf8fe16b09 Remove direct access to RED.nodes.nodes/links
- Adds RED.nodes.filterNodes and RED.nodes.filterLinks for
   doing simply queries to find elements that match a criteria
2015-03-15 23:31:38 +00:00
Nick O'Leary
71db193675 Move user menu creation to user module 2015-03-15 23:07:57 +00:00
Nick O'Leary
9952d9451e Move deploy menu/action to own module 2015-03-15 22:54:55 +00:00
Nick O'Leary
fb738ad9fa Track dirty state in RED.nodes not RED.view
- add 'change' event on RED.nodes for tracking dirty state change
2015-03-15 21:54:36 +00:00
Nick O'Leary
46f2f752b0 Node on deleted tab not removed on partial deploy 2015-03-15 21:27:11 +00:00
Nick O'Leary
42730b8fce Move external drag/drop to clipboard module 2015-03-14 22:53:31 +00:00
Nick O'Leary
1c2be579d9 Move keyboard shortcut dialog to keyboard module 2015-03-14 22:16:07 +00:00
Nick O'Leary
51e891ff88 Move sessionStorageModule into main storageModule
Fixes #586

 - add get/saveSessions to main storage module
 - handle storage modules without those functions
 - store .session file in userDir
2015-03-13 23:37:59 +00:00
Nick O'Leary
731efe1c01 Add credential extract unit tests 2015-03-13 21:26:50 +00:00
Nick O'Leary
f77dd06e65 Partial deploy with missing type breaks flow diff
Another refactor of Flow lifecycle.
 - diffFlow made a private static function
 - applyConfig now diffConfig - which returns a diff object that
   can be passed to .stop/.start to be properly applied
2015-03-13 17:54:58 +00:00
Nick O'Leary
af20f3df64 Partial deploy with missing node type breaks deploy 2015-03-13 13:15:20 +00:00
Nick O'Leary
4078212089 Split cliboard and workspaces out of editor view 2015-03-12 23:38:37 +00:00
Nick O'Leary
7bdb3181e2 Don't reload page on enter in subflow dialog 2015-03-12 17:23:17 +00:00
Nick O'Leary
933608aec1 Disable buttons of nodes with undeployed changes 2015-03-12 13:58:53 +00:00
Nick O'Leary
1d7f06bbba Redraw unselected link when joining nodes starts 2015-03-12 13:35:39 +00:00
Nick O'Leary
f26cadab7f Minimise link redrawing 2015-03-12 13:26:31 +00:00
Nick O'Leary
eacf41a4f6 Minimise filtering of nodes on redraw 2015-03-12 11:21:05 +00:00
Nick O'Leary
ab3e64271b Move subflow handling to own module 2015-03-12 00:09:30 +00:00
Nick O'Leary
e26ea14104 Undo subflow rename not reflected in palette 2015-03-12 00:09:30 +00:00
dceejay
3967e23828 change settings to replace commented out arduino library
with johnny-five to match example in docs to make life easier.
2015-03-11 17:49:06 +00:00
dceejay
1f8c6f87c9 add don't add payload to exec node
to close #578
2015-03-11 17:43:42 +00:00
Nick O'Leary
f6203fe60a Allow a config-node be marked as not required 2015-03-09 20:42:23 +00:00
Nick O'Leary
06bf710515 Improve editor dialog auto-sizing 2015-03-09 20:41:57 +00:00
Nick O'Leary
0f3cc3196c Log-in window incorrect
fixes #583
2015-03-09 20:02:13 +00:00
dceejay
4403a00651 Revert change to http until we fully deprecate msg/node priorities
Fix to close #582
2015-03-08 18:36:35 +00:00
dceejay
9c46feb22b more tests for log and Node 2015-03-08 16:53:48 +00:00
dceejay
10277aa956 revert/redo tests for api/index, log and Node_spec 2015-03-08 15:26:47 +00:00
Nick O'Leary
ff093d98c6 Merge pull request #576 from Belphemur/function-logger
Adding an Object Node to the sandbox of a function node
2015-03-07 23:37:00 +00:00
dceejay
acc0e0875b few more tests for permissions and strategies
reset log flags at end of log test
2015-03-07 13:22:21 +00:00
dceejay
69f85bd688 boost api index, nodes index and nodes Node test coverage 2015-03-06 22:58:30 +00:00
dceejay
910d983b82 More tests for red, log, info and util. 2015-03-06 14:14:47 +00:00
dceejay
128415bc9e back out some changes to red_spec test while investigate fail on v0.8 2015-03-06 10:51:57 +00:00
dceejay
082ce798d8 slightly enhance test coverage for info and log and settings. 2015-03-06 10:18:33 +00:00
dceejay
234abd82a2 Move away from __defineGetter syntax, in red and server
Bump test coverage forwards a bit
2015-03-06 10:17:00 +00:00
dceejay
3cbc1bbb1b Add ipv6 support to udp node 2015-03-05 13:07:38 +00:00
Antoine Aflalo
0ed9f6cc4f Adding an Object node to the sandbox of a function node
Permit the user of the sandbox to log using the Function Node.
Test provided and working.

Fix Display warning message in the debug log
Before they were displayed as error instead of warning
2015-03-05 09:50:11 +02:00
Nick O'Leary
10b092a9a7 Ignore 'type' when detecting config nodes 2015-03-04 22:38:53 +00:00
Nick O'Leary
444a897410 Resort to NODE_RED_HOME if User HOME not found
Fixes #575
2015-03-04 21:47:38 +00:00
Nick O'Leary
e013afb053 Import/Export dialogs cannot be cancelled
Fixes #577
2015-03-04 21:44:14 +00:00
Nick O'Leary
34364f5627 Allow node to register multiple close handlers
Closes #573
2015-03-04 21:42:11 +00:00
Nick O'Leary
cef378d820 Add selection-changed event on RED.view 2015-03-04 13:22:32 +00:00
Nick O'Leary
a27353c166 Add onadd/onremove event handlers to node definitions 2015-03-04 13:22:29 +00:00
dceejay
bbd197c71d Note in info that MQTT node can support binary. 2015-03-03 15:56:42 +00:00
Nick O'Leary
fabf013714 Allow the main view hold keyboard focus 2015-03-02 22:55:34 +00:00
dceejay
81dcfecb4e Catch very early exit null pointer when ctrl-c hit during startup. 2015-03-02 17:32:22 +00:00
Nick O'Leary
971a62ebc9 Merge pull request #571 from knolleary/errnode
Add Catch node
2015-02-26 22:58:03 +00:00
Nick O'Leary
04f2c92ba6 Add subflow/catch node tests 2015-02-26 22:40:54 +00:00
Nick O'Leary
00d0f8cfc7 Invoke catch node only when msg is provided 2015-02-26 22:40:54 +00:00
Nick O'Leary
c5c404ea05 Update catch node icon and help text 2015-02-26 22:40:54 +00:00
Nick O'Leary
c80a44933c Add errorHandler tests 2015-02-26 22:40:54 +00:00
Nick O'Leary
5599b999ec Add catch node 2015-02-26 22:40:53 +00:00
Nick O'Leary
172cbdaa84 Merge pull request #574 from knolleary/editor
Migrate to ACE editor
2015-02-26 22:27:02 +00:00
Nick O'Leary
a3c4f12764 Bump 0.10.4 2015-02-26 21:39:30 +00:00
Nick O'Leary
bf1cd457cd Add RED.editor.createEditor utility function 2015-02-26 21:29:56 +00:00
dceejay
8af50a51ba add validation triangle to ace function editor on errors 2015-02-26 17:08:50 +00:00
Nick O'Leary
ddf31e87b2 Update core nodes to use ACE editor 2015-02-26 17:08:50 +00:00
Nick O'Leary
5adbc012f3 Add ACE editor 2015-02-26 17:08:50 +00:00
Nick O'Leary
393fc349b9 Fix saving for node-library content 2015-02-26 17:08:20 +00:00
dceejay
dfed4963ed fix big labelling issue with Pi + pins ... 2015-02-26 14:18:49 +00:00
dceejay
131adb6f4e let email node mark mail as read
(for the ones it reads)
2015-02-26 14:18:49 +00:00
dceejay
a8b3cbb683 remove unecessary require from serial node 2015-02-26 14:18:49 +00:00
Nick O'Leary
e97d5c7354 Rename node-red wrapper to node-red-pi 2015-02-26 13:41:01 +00:00
Nick O'Leary
061c44f958 Move shebang to the correct red.js 2015-02-26 13:24:38 +00:00
Nick O'Leary
f5d8433341 Add node-red-pi command 2015-02-26 11:38:05 +00:00
Nick O'Leary
f78a71e8ed Load flows file from userDir when appropriate 2015-02-26 11:30:20 +00:00
Nick O'Leary
4d48c72146 Add node-red script
Needed to allow arguments to be passed to the node
engine, which isn't possible if red.js is run with
a #! line.
2015-02-25 22:37:56 +00:00
dceejay
71ff828947 Tidy up all console.log util.log from core nodes.
Try to make log,warn,error more consistent behaviour.

Especially make sure any existing catches produce errors
2015-02-25 19:10:59 +00:00
Nick O'Leary
b6245bdef7 Remove console.log from XML test spec 2015-02-25 14:25:37 +00:00
Nick O'Leary
ce1cd1ab9c Change default data dir
Changes the default location for user data to $HOME/.node-red.
2015-02-25 14:25:01 +00:00
dceejay
54b0debb3b Re-order palette categories, move subflows to top of list. 2015-02-25 10:10:10 +00:00
Nick O'Leary
1876b56022 Fix jshint error 2015-02-24 23:06:07 +00:00
Nick O'Leary
d148a23ed6 Handle config nodes appearing out of order in flow
The editor ensures config nodes appear first in the flow file. The
code in the runtime and editor assumes this to be the case, so that
when a node is instantiated that requires a config node, it can assume
the config node already exists.

This change allows a config node to appear in the flow file after a
node that wants to use it. In both the editor and runtime, the code
now scans for config nodes and handles them first.
2015-02-24 23:04:55 +00:00
dceejay
049a5f1be6 revert small whitespace change to server start messages. 2015-02-24 22:22:16 +00:00
Nick O'Leary
f3880b7601 Fix credential pruning and start/stop log messages 2015-02-24 22:03:04 +00:00
dceejay
fbb45a8961 make udp node consistent with it's info.... (re params passed out) 2015-02-24 13:22:48 +00:00
dceejay
b8c460b825 pass original url request through http request node
(will be useful when we handle errors... ;-)
2015-02-24 13:20:33 +00:00
Nick O'Leary
63191bc641 Bump 0.10.3 2015-02-23 22:18:14 +00:00
dceejay
9f012c261a Make parser nodes errors actual errors.
(more cleanup will probably be necessary - but this is a start)
2015-02-23 19:30:29 +00:00
dceejay
dc7701ad70 Add node.js version to startup log msgs for debug. 2015-02-23 19:30:29 +00:00
Nick O'Leary
e8666827e6 Restore httpAdminAuth with deprecation warning 2015-02-23 11:39:38 +00:00
Nick O'Leary
5e2c51a741 Handle deleted tab when diffing flows 2015-02-22 22:59:26 +00:00
dceejay
51421ce657 clone msg more correctly for CSV node multiple line output 2015-02-22 21:57:06 +00:00
Nick O'Leary
339e6039e1 Add engine restriction against node 0.12 2015-02-22 21:28:28 +00:00
dceejay
43054906dc preserve other msg properties when passing through CSV node 2015-02-22 19:23:36 +00:00
Nick O'Leary
57dedcf816 Add files to .gitignore 2015-02-21 00:28:29 +00:00
Nick O'Leary
4dc21c43fa Handle strings for limit/skip args to mongo node 2015-02-20 20:02:25 +00:00
Nick O'Leary
1c86908b90 Bump 0.10.2 2015-02-20 10:07:10 +00:00
Nick O'Leary
9b26973883 Ensure activeWorkspace is set when a flow contains no tabs 2015-02-20 10:05:42 +00:00
Nick O'Leary
2585e983a5 Add test-* grunt tasks 2015-02-15 23:35:24 +00:00
Nick O'Leary
a808cb44c2 Merge pull request #563 from emiloberg/master
Fixes bug that prevented unsubscribe from WebSockets, browser side. Fixes node-red/node-red/#562
2015-02-15 21:54:48 +00:00
Nick O'Leary
edd9d2cb9c Fix Inject node handling of day selection
Fixes #564
2015-02-15 21:53:14 +00:00
Emil Öberg
33d24f79ce Fixes bug that prevented unsubscribe from WebSockets, browser side. Fixes node-red/node-red/#562 2015-02-14 22:25:25 +01:00
dceejay
cc095e4edf edit HTML node info to remove ref to jQuery. Link to CSSselect instead. 2015-02-14 19:06:35 +00:00
dceejay
07641d57ab recorrect debug logging level colour class names so sidebar is as-was.
in light of new logging levels
2015-02-14 12:14:06 +00:00
dceejay
5643c51507 Let debug node show "topic" correctly for errors in functions. 2015-02-13 21:14:54 +00:00
Nick O'Leary
ad6254c0b8 Add oneditdelete handler for config nodes
Already had the undocumented ondelete handler. Adding
this to be consistent with the other oneditXYZ handlers.
2015-02-13 11:20:08 +00:00
Nick O'Leary
d6ca421d59 Remove unecessary argument to oneditcancel 2015-02-13 11:05:45 +00:00
Nick O'Leary
79bd01f810 Remove X button on dialogs
Fixes #561

Also, oneditcancel not being honoured for regular nodes, only config nodes
2015-02-13 10:06:28 +00:00
Nick O'Leary
e357352240 Tidy up info tab handling of subflows and comments 2015-02-10 21:29:27 +00:00
dceejay
0accfade02 catch internal subflow info error in tab-info... 2015-02-10 20:15:00 +00:00
dceejay
00b7afe3ae spelling pedant alert in debug node... its not it's 2015-02-10 20:14:33 +00:00
dceejay
2e76541fa5 Update Debug node test to "unbreak" build... oops 2015-02-10 17:31:26 +00:00
dceejay
e2911078e3 tidy up tab-info so subflows show more useful information
(was broken before but no-one noticed ;-)
(and reorder fields in HTTP and XML nodes so name comes out first - ocd)
2015-02-10 16:56:07 +00:00
dceejay
c6157687c9 Move payload type label in Debug window to meta data row
rather than (object) etc at start of actual payload.
2015-02-10 16:56:07 +00:00
Nick O'Leary
87a1818486 Add missing FA font file 2015-02-09 09:48:31 +00:00
310 changed files with 14216 additions and 10893 deletions

19
.gitignore vendored
View File

@@ -1,9 +1,16 @@
node_modules
.config.json
.dist
.jshintignore
.npm
.project
.sessions.json
.settings
.tern-project
*.backup
*_cred*
coverage
credentials.json
flows*.json
flows.backup
*_cred*
nodes/node-red-nodes/
.npm
/coverage
.config.json
node_modules
public

13
.jshintrc Normal file
View File

@@ -0,0 +1,13 @@
{
"asi": true, // allow missing semicolons
"curly": true, // require braces
"eqnull": true, // ignore ==null
"forin": true, // require property filtering in "for in" loops
"immed": true, // require immediate functions to be wrapped in ( )
"nonbsp": true, // warn on unexpected whitespace breaking chars
//"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
//"unused": true, // Check for unused functions and variables
"loopfunc": true, // allow functions to be defined in loops
//"expr": true, // allow ternery operator syntax...
"sub": true // don't warn that foo['bar'] should be written as foo.bar
}

View File

@@ -1,4 +1,6 @@
.git/*
/Gruntfile.js
/.git/*
*.json
lib/*
/lib/*
*.backup
/public/*

7
.npmignore Normal file
View File

@@ -0,0 +1,7 @@
.settings
.jshintignore
.jshintrc
.project
.tern-project
.travis.yml
.git

View File

@@ -3,7 +3,6 @@ before_install:
- npm install -g npm@~1.4.18
node_js:
- "0.10"
- "0.8"
script:
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage
before_script:

View File

@@ -6,11 +6,10 @@ We welcome contributions, but request you follow these guidelines.
- [Feature requests](#feature-requests)
- [Pull-Requests](#pull-requests)
- [Contributor License Agreement](#contributor-license-agreement)
## Raising issues
Please raise any bug reports on the project's
[issue tracker](https://github.com/node-red/node-red/issues?state=open). Be sure to
Please raise any bug reports on the relevant project's issue tracker. Be sure to
search the list to see if your issue has already been raised.
A good bug report is one that make it easy for us to understand what you were
@@ -25,7 +24,6 @@ At a minimum, please include:
- Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly.
- Version of node.js - what does `node -v` say?
## Feature requests
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red).
@@ -33,15 +31,15 @@ For feature requests, please raise them on the [mailing list](https://groups.goo
## Pull-Requests
If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it may well get rejected if you haven't discussed it on
of existing code, it may well get rejected if you haven't discussed it on
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
### Contributor License Agreement
In order for us to accept pull-requests, the contributor must first complete
a Contributor License Agreement (CLA). This clarifies the intellectual
property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not
a Contributor License Agreement (CLA). This clarifies the intellectual
property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not
change your rights to use your own Contributions for any other purpose.
You can download the CLAs here:
@@ -59,13 +57,5 @@ code base. Some basic rules include:
- all files must have the Apache license in the header.
- indent with 4-spaces, no tabs. No arguments.
- opening brace on same line as `if`/`for`/`function`/etc, closing brace on its
own line.
- opening brace on same line as `if`/`for`/`function` and so on, closing brace
on its own line.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2014 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,78 +14,365 @@
* limitations under the License.
**/
var path = require("path");
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
simplemocha: {
options: {
globals: ['expect'],
timeout: 3000,
ignoreLeaks: false,
ui: 'bdd',
reporter: 'spec'
},
all: { src: ['test/**/*_spec.js'] },
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]}
pkg: grunt.file.readJSON('package.json'),
paths: {
dist: ".dist"
},
simplemocha: {
options: {
globals: ['expect'],
timeout: 3000,
ignoreLeaks: false,
ui: 'bdd',
reporter: 'spec'
},
jshint: {
options: {
// http://www.jshint.com/docs/options/
"asi": true, // allow missing semicolons
"curly": true, // require braces
"eqnull": true, // ignore ==null
"forin": true, // require property filtering in "for in" loops
"immed": true, // require immediate functions to be wrapped in ( )
"nonbsp": true, // warn on unexpected whitespace breaking chars
//"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
"loopfunc": true, // allow functions to be defined in loops
"sub": true // don't warn that foo['bar'] should be written as foo.bar
},
all: [
'Gruntfile.js',
'red.js',
'red/**/*.js',
'nodes/**/*.js',
'public/red/**/*.js'
],
core: {
files: {
src: [
'Gruntfile.js',
'red.js',
'red/**/*.js'
]
}
},
nodes: {
files: {
src: [ 'nodes/**/*.js' ]
}
},
editor: {
files: {
src: [ 'public/red/**/*.js' ]
}
},
tests: {
files: {
src: ['test/**/*.js']
},
options: {
"expr": true
}
all: { src: ['test/**/*_spec.js'] },
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]}
},
jshint: {
options: {
jshintrc:true
// http://www.jshint.com/docs/options/
//"asi": true, // allow missing semicolons
//"curly": true, // require braces
//"eqnull": true, // ignore ==null
//"forin": true, // require property filtering in "for in" loops
//"immed": true, // require immediate functions to be wrapped in ( )
//"nonbsp": true, // warn on unexpected whitespace breaking chars
////"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
//"loopfunc": true, // allow functions to be defined in loops
//"sub": true // don't warn that foo['bar'] should be written as foo.bar
},
all: [
'Gruntfile.js',
'red.js',
'red/**/*.js',
'nodes/core/*/*.js',
'editor/js/**/*.js'
],
core: {
files: {
src: [
'Gruntfile.js',
'red.js',
'red/**/*.js'
]
}
},
nodes: {
files: {
src: [ 'nodes/core/*/*.js' ]
}
},
editor: {
files: {
src: [ 'editor/js/**/*.js' ]
}
},
tests: {
files: {
src: ['test/**/*.js']
},
options: {
"expr": true
}
}
},
concat: {
options: {
separator: ";",
},
build: {
src: [
// Ensure editor source files are concatenated in
// the right order
"editor/js/main.js",
"editor/js/settings.js",
"editor/js/user.js",
"editor/js/comms.js",
"editor/js/ui/state.js",
"editor/js/nodes.js",
"editor/js/history.js",
"editor/js/validators.js",
"editor/js/ui/deploy.js",
"editor/js/ui/menu.js",
"editor/js/ui/keyboard.js",
"editor/js/ui/tabs.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
"editor/js/ui/sidebar.js",
"editor/js/ui/palette.js",
"editor/js/ui/tab-info.js",
"editor/js/ui/tab-config.js",
"editor/js/ui/editor.js",
"editor/js/ui/clipboard.js",
"editor/js/ui/library.js",
"editor/js/ui/notifications.js",
"editor/js/ui/subflow.js",
"editor/js/ui/touch/radialMenu.js"
],
dest: "public/red/red.js"
},
vendor: {
files: {
"public/vendor/vendor.js": [
"editor/vendor/jquery/js/jquery-1.11.1.min.js",
"editor/vendor/bootstrap/js/bootstrap.min.js",
"editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"editor/vendor/marked/marked.min.js",
"editor/vendor/orion/built-editor.min.js",
"editor/vendor/d3/d3.v3.min.js"
],
"public/vendor/vendor.css": [
"editor/vendor/orion/built-editor.css"
// TODO: resolve relative resource paths in
// bootstrap/FA/jquery
]
}
}
},
uglify: {
build: {
files: {
'public/red/red.min.js': 'public/red/red.js'
}
}
},
sass: {
build: {
options: {
outputStyle: 'compressed'
},
files: [{
dest: 'public/red/style.min.css',
src: 'editor/sass/style.scss'
}]
}
},
attachCopyright: {
js: {
src: [
'public/red/red.min.js'
]
},
css: {
src: [
'public/red/style.min.css'
]
}
},
clean: {
build: {
src: [
"public/red",
"public/index.html",
"public/favicon.ico",
"public/icons",
"public/vendor"
]
},
release: {
src: [
'<%= paths.dist %>'
]
}
},
watch: {
js: {
files: [
'editor/js/**/*.js'
],
tasks: ['concat','uglify','attachCopyright:js']
},
sass: {
files: [
'editor/sass/**/*.scss'
],
tasks: ['sass','attachCopyright:css']
}
},
nodemon: {
/* uses .nodemonignore */
dev: {
script: 'red.js',
options: {
args:['-v'],
ext: 'js,html',
watch: [
'red','nodes'
]
}
}
},
concurrent: {
dev: {
tasks: ['nodemon', 'watch'],
options: {
logConcurrentOutput: true
}
}
},
copy: {
build: {
files:[{
cwd: 'editor/images',
src: '**',
expand: true,
dest: 'public/red/images/'
},
{
cwd: 'editor/vendor',
src: [
'ace/**',
'bootstrap/css/**',
'bootstrap/img/**',
'jquery/css/**',
'font-awesome/**'
],
expand: true,
dest: 'public/vendor/'
},
{
cwd: 'editor/icons',
src: '**',
expand: true,
dest: 'public/icons/'
},
{
expand: true,
src: ['editor/index.html','editor/favicon.ico'],
dest: 'public/',
flatten: true
}]
},
release: {
files: [{
expand: true,
src: [
'*.md',
'LICENSE',
'package.json',
'settings.js',
'red.js',
'lib/.gitignore',
'nodes/*.demo',
'nodes/core/**',
'red/**',
'public/**',
'editor/templates/**',
'bin/**'
],
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
}]
}
},
compress: {
release: {
options: {
archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip'
},
expand: true,
cwd: '<%= paths.dist %>/',
src: ['node-red-<%= pkg.version %>/**']
}
}
});
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('default', ['jshint:core','jshint:tests','jshint:editor','simplemocha:core','simplemocha:nodes']);
grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src;
var copyright = "/**\n"+
" * Copyright 2013, 2015 IBM Corp.\n"+
" *\n"+
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
" * you may not use this file except in compliance with the License.\n"+
" * You may obtain a copy of the License at\n"+
" *\n"+
" * http://www.apache.org/licenses/LICENSE-2.0\n"+
" *\n"+
" * Unless required by applicable law or agreed to in writing, software\n"+
" * distributed under the License is distributed on an \"AS IS\" BASIS,\n"+
" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"+
" * See the License for the specific language governing permissions and\n"+
" * limitations under the License.\n"+
" **/\n";
if (files) {
for (var i=0;i<files.length;i++) {
var file = files[i];
if (!grunt.file.exists(file)) {
grunt.log.warn('File '+ file + ' not found');
return false;
} else {
var content = grunt.file.read(file);
if (content.indexOf(copyright) == -1) {
content = copyright+content;
if (!grunt.file.write(file, content)) {
return false;
}
grunt.log.writeln("Attached copyright to "+file);
} else {
grunt.log.writeln("Copyright already on "+file);
}
}
}
}
});
grunt.registerTask('setDevEnv',
'Sets NODE_ENV=development so non-minified assets are used',
function () {
process.env.NODE_ENV = 'development';
});
grunt.registerTask('default',
'Builds editor content then runs code style checks and unit tests on all components',
['build','test-core','test-editor','test-nodes']);
grunt.registerTask('test-core',
'Runs code style check and unit tests on core runtime code',
['jshint:core','simplemocha:core']);
grunt.registerTask('test-editor',
'Runs code style check on editor code',
['jshint:editor']);
grunt.registerTask('test-nodes',
'Runs unit tests on core nodes',
['simplemocha:nodes']);
grunt.registerTask('build',
'Builds editor content',
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','copy:build','attachCopyright']);
grunt.registerTask('dev',
'Developer mode: run node-red, watch for source changes and build/restart',
['build','setDevEnv','concurrent:dev']);
grunt.registerTask('release',
'Create distribution zip file',
['build','clean:release','copy:release','compress:release']);
};

View File

@@ -1,57 +0,0 @@
Node-RED Install
================
## Install node.js
You can get the latest version from <http://nodejs.org/download/>.
Or, you may want to use a version from your operating system's package manager:
<https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager>
## Get Node-RED
Clone the repository from GitHub:
$ git clone git@github.com:node-red/node-red.git
## Install the pre-requisite modules
From the top-level directory of Node-RED, run:
$ npm install
This will install the core pre-requisite modules.
## Run Node-RED
From the top-level directory, run:
$ node red.js
You can then access Node-RED at <http://localhost:1880>.
Online documentation is available at <http://nodered.org/docs>.
## Installing individual node dependencies
When Node-RED starts, it attempts to load the nodes from the `nodes/` directory.
Each will have its own set of dependencies that will need to be installed before
the node is available in the palette.
To help identify the dependencies, Node-RED logs any modules it fails to find
for a particular node. You don't have to install these unless you want or need
that node to appear.
Alternatively, a node's `.js` file can be examined to identify the modules it
explicitly requires. For example, the Twitter node is defined in
`nodes/social/27-twitter.js` and contains:
var RED = require("../../red/red");
var ntwitter = require('ntwitter');
var OAuth= require('oauth').OAuth;
Of these, `ntwitter` and `oauth` are neither built-in modules nor ones provided
by Node-RED itself. They can subsequently be installed by running:
$ npm install ntwitter oauth

View File

@@ -2,39 +2,58 @@
http://nodered.org
[![Build Status](https://travis-ci.org/node-red/node-red.png)](https://travis-ci.org/node-red/node-red) [![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.png?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
[![Build Status](https://travis-ci.org/node-red/node-red.png)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.png?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
A visual tool for wiring the Internet of Things.
![Screenshot](http://nodered.org/images/node-red-screenshot.png "Node-RED: A visual tool for wiring the Internet of Things")
![Node-RED: A visual tool for wiring the Internet of Things](http://nodered.org/images/node-red-screenshot.png)
## Quick Start
Check out [INSTALL](INSTALL.md) for full instructions on getting started.
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
started.
1. download the zip and unzip, or git clone
2. cd node-red
3. npm install
4. node red.js
5. Open <http://localhost:1880>
1. `sudo npm install -g node-red`
2. `node-red`
3. Open <http://localhost:1880>
## Getting Help
More documentation can be found [here](http://nodered.org/docs).
For further help, or general discussion, please use the [mailing list](https://groups.google.com/forum/#!forum/node-red).
For further help, or general discussion, please use the
[mailing list](https://groups.google.com/forum/#!forum/node-red).
## Browser Support
## Developers
The Node-RED editor runs in the browser. We routinely develop and test using
Chrome and Firefox. We have anecdotal evidence that it works in recent versions of IE.
If you want to run the latest code from git, here's how to get started:
We have basic support for using in mobile/tablet browsers.
1. Install grunt, the build tool
npm install -g grunt-cli
2. Clone the code:
git clone https://github.com/node-red/node-red.git
cd node-red
3. Install the node-red dependencies
npm install
4. Build the code
grunt build
5. Run
node red.js
## Contributing
Before raising a pull-request, please read our [contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
Before raising a pull-request, please read our
[contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
## Authors
@@ -48,3 +67,4 @@ For more open-source projects from IBM, head over [here](http://ibm.github.io).
## Copyright and license
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).

43
bin/node-red-pi Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
#
# Copyright 2015 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Separate out node/v8 options from node-red ones
OPTIONS=""
ARGS=""
for arg in "$@"
do
case $arg in
--userDir|--settings|--help) ARGS="$ARGS $arg";;
--*) OPTIONS="$OPTIONS $arg";;
*) ARGS="$ARGS $arg";;
esac
done
# Find the real location of this script
CURRENT_PATH=`pwd`
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
cd "`dirname "${SCRIPT_PATH}"`"
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
cd $CURRENT_PATH
# Run Node-RED
/usr/bin/env node $OPTIONS $SCRIPT_PATH/../red.js $ARGS

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 603 B

After

Width:  |  Height:  |  Size: 603 B

View File

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 609 B

View File

Before

Width:  |  Height:  |  Size: 575 B

After

Width:  |  Height:  |  Size: 575 B

View File

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 601 B

View File

Before

Width:  |  Height:  |  Size: 459 B

After

Width:  |  Height:  |  Size: 459 B

View File

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

View File

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 324 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View File

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

View File

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 457 B

View File

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 502 B

View File

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 639 B

View File

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

View File

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 671 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

View File

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 736 B

View File

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View File

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

View File

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

View File

Before

Width:  |  Height:  |  Size: 592 B

After

Width:  |  Height:  |  Size: 592 B

View File

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 509 B

View File

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View File

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 628 B

View File

Before

Width:  |  Height:  |  Size: 258 B

After

Width:  |  Height:  |  Size: 258 B

View File

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 404 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 707 B

After

Width:  |  Height:  |  Size: 707 B

View File

Before

Width:  |  Height:  |  Size: 291 B

After

Width:  |  Height:  |  Size: 291 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

View File

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 368 B

View File

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 290 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 223 B

After

Width:  |  Height:  |  Size: 223 B

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1019 B

After

Width:  |  Height:  |  Size: 1019 B

View File

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 600 B

View File

@@ -4,9 +4,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<!--
Copyright 2013, 2014 IBM Corp.
Copyright 2013, 2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -22,27 +21,22 @@
-->
<head>
<title>Node-RED</title>
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" type="text/css" href="orion/built-editor.css"/>
<link rel="stylesheet" type="text/css" href="font-awesome/css/font-awesome.min.css"/>
<link rel="stylesheet" href="style.css">
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="vendor/jquery/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="vendor/vendor.css">
<link rel="stylesheet" href="red/style.min.css">
</head>
<body spellcheck="false">
<div id="header">
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
<span class="logo"><img src="red/images/node-red.png"> <span>Node-RED</span></span>
<ul class="header-toolbar hide">
<li><span class="deploy-button-group button-group">
<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="images/deploy-full-o.png"> <span>Deploy</span></a>
<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>
</span></li>
<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
<ul>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
<img src="spin.svg" class="palette-spinner hide"/>
<img src="red/images/spin.svg" class="palette-spinner hide"/>
<div id="palette-container" class="palette-scroll">
</div>
<div id="palette-search">
@@ -81,7 +75,6 @@
<div id="notifications"></div>
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
<div id="shade"></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
@@ -107,57 +100,6 @@
</form>
</div>
<div id="node-help" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="node-help-label" aria-hidden="true">
<div class="modal-header">
<h5 id="node-help-label">Keyboard Shortcuts <span style="float: right;"><a href="http://nodered.org/docs" target="_blank">Open help in new window &raquo;</a></span></h5>
</div>
<div class="modal-body">
<table>
<tr>
<td><span class="help-key">?</span></td><td>Help</td>
<td><span class="help-key">Ctrl</span> <span class="help-key">a</span></td><td>Select all nodes</td>
</tr>
<tr>
<td><span class="help-key">Ctrl</span> <span class="help-key">Space</span></td><td>Toggle sidebar</td>
<td><span class="help-key">Shift</span> <span class="help-key">Click</span></td><td>Select all connected nodes</td>
</tr>
<tr>
<td><span class="help-key">Ctrl</span> <span class="help-key">z</span></td><td>Undo</td>
<td><span class="help-key">Ctrl</span> <span class="help-key">Click</span></td><td>Add/remove node from selection</td>
</tr>
<tr>
<td></td><td></td>
<td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td>
</tr>
<tr>
<td><span class="help-key">Ctrl</span> <span class="help-key">x</span></td><td>Cut selected nodes</td>
<td></td><td></td>
</tr>
<tr>
<td><span class="help-key">Ctrl</span> <span class="help-key">c</span></td><td>Copy selected nodes</td>
<td><span class="help-key">Ctrl</span> <span class="help-key">v</span></td><td>Paste nodes</td>
</tr>
<tr>
<td><span class="help-key">Ctrl</span> <span class="help-key">i</span></td><td>Import nodes</td>
<td><span class="help-key">Ctrl</span> <span class="help-key">e</span></td><td>Export selected nodes</td>
</tr>
<tr>
<td colspan="2"></td>
</tr>
<tr>
<td><span class="help-key">Ctrl</span> <span class="help-key">+</span></td><td>Zoom in</td>
<td><span class="help-key">Ctrl</span> <span class="help-key">-</span></td><td>Zoom out</td>
</tr>
<tr>
<td colspan="4">Mac users can use the <b>⌘ - Cmd</b> key rather than Ctrl key.</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
<div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;">
@@ -211,27 +153,13 @@
</div>
</form>
</div>
<script type="text/x-red" data-template-name="export-clipboard-dialog">
<div class="form-row">
<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>
<textarea readonly style="font-family: monospace; font-size: 12px; background:rgb(226, 229, 255); padding-left: 0.5em;" class="input-block-level" id="node-input-export" rows="5"></textarea>
</div>
<div class="form-tips">
Select the text above and copy to the clipboard with Ctrl-A Ctrl-C.
</div>
</script>
<script type="text/x-red" data-template-name="export-library-dialog">
<div class="form-row">
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
</script>
<script type="text/x-red" data-template-name="import-dialog">
<div class="form-row">
<label for="node-input-import"><i class="fa fa-clipboard"></i> Nodes:</label>
<textarea style="font-family: monospace; font-size: 12px; background:rgb(226, 229, 255); padding-left: 0.5em;" class="input-block-level" id="node-input-import" rows="5" placeholder="Paste nodes here"></textarea>
</div>
</script>
<script type="text/x-red" data-template-name="subflow">
<div class="form-row">
@@ -240,33 +168,10 @@
</div>
</script>
<script src="jquery/js/jquery-1.11.1.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
<script src="jquery/js/jquery-ui-1.10.3.custom.min.js"></script>
<script src="jquery/js/jquery.ui.touch-punch.min.js"></script>
<script src="marked/marked.min.js"></script>
<script src="orion/built-editor.min.js"></script>
<script src="d3.v3.min.js"></script>
<script src="red/main.js"></script>
<script src="red/settings.js"></script>
<script src="red/user.js"></script>
<script src="red/comms.js"></script>
<script src="red/ui/state.js"></script>
<script src="red/nodes.js"></script>
<script src="red/history.js"></script>
<script src="red/validators.js"></script>
<script src="red/ui/menu.js"></script>
<script src="red/ui/keyboard.js"></script>
<script src="red/ui/tabs.js"></script>
<script src="red/ui/view.js"></script>
<script src="red/ui/sidebar.js"></script>
<script src="red/ui/palette.js"></script>
<script src="red/ui/tab-info.js"></script>
<script src="red/ui/tab-config.js"></script>
<script src="red/ui/editor.js"></script>
<script src="red/ui/library.js"></script>
<script src="red/ui/notifications.js"></script>
<script src="red/ui/touch/radialMenu.js"></script>
<script src="vendor/vendor.js"></script>
<script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script>
<script src="red/red.min.js"></script>
</body>
</html>

View File

@@ -95,15 +95,15 @@ RED.comms = (function() {
}
function unsubscribe(topic,callback) {
if (subscriptions.topic) {
for (var i=0;i<subscriptions.topic.length;i++) {
if (subscriptions.topic[i] === callback) {
subscriptions.topic.splice(i,1);
if (subscriptions[topic]) {
for (var i=0;i<subscriptions[topic].length;i++) {
if (subscriptions[topic][i] === callback) {
subscriptions[topic].splice(i,1);
break;
}
}
if (subscriptions.topic.length === 0) {
delete subscriptions.topic;
if (subscriptions[topic].length === 0) {
delete subscriptions[topic];
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,10 +32,16 @@ RED.history = (function() {
pop: function() {
var ev = undo_history.pop();
var i;
var node;
var modifiedTabs = {};
if (ev) {
if (ev.t == 'add') {
if (ev.nodes) {
for (i=0;i<ev.nodes.length;i++) {
node = RED.nodes.node(ev.nodes[i]);
if (node.z) {
modifiedTabs[node.z] = true;
}
RED.nodes.remove(ev.nodes[i]);
}
}
@@ -47,20 +53,20 @@ RED.history = (function() {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
RED.nodes.removeWorkspace(ev.workspaces[i].id);
RED.view.removeWorkspace(ev.workspaces[i]);
RED.workspaces.remove(ev.workspaces[i]);
}
}
if (ev.subflows) {
for (i=0;i<ev.subflows.length;i++) {
RED.nodes.removeSubflow(ev.subflows[i]);
RED.view.removeWorkspace(ev.subflows[i]);
RED.workspaces.remove(ev.subflows[i]);
}
}
} else if (ev.t == "delete") {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
RED.nodes.addWorkspace(ev.workspaces[i]);
RED.view.addWorkspace(ev.workspaces[i]);
RED.workspaces.add(ev.workspaces[i]);
}
}
if (ev.subflow) {
@@ -92,22 +98,21 @@ RED.history = (function() {
}
}
if (subflow) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.inputs = subflow.in.length;
n.outputs = subflow.out.length;
while (n.outputs > n.ports.length) {
n.ports.push(n.ports.length);
}
n.resize = true;
n.dirty = true;
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
n.changed = true;
n.inputs = subflow.in.length;
n.outputs = subflow.out.length;
while (n.outputs > n.ports.length) {
n.ports.push(n.ports.length);
}
n.resize = true;
n.dirty = true;
});
}
if (ev.nodes) {
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]);
modifiedTabs[ev.nodes[i].z] = true;
}
}
if (ev.links) {
@@ -143,13 +148,11 @@ RED.history = (function() {
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
}
}
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+ev.node.id) {
n.changed = ev.changed;
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
}
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.changed = ev.changed;
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
});
RED.palette.refresh();
@@ -166,11 +169,9 @@ RED.history = (function() {
ev.node.changed = ev.changed;
} else if (ev.t == "createSubflow") {
if (ev.nodes) {
RED.nodes.eachNode(function(n) {
if (n.z === ev.subflow.id) {
n.z = ev.activeWorkspace;
n.dirty = true;
}
RED.nodes.filterNodes({z:ev.subflow.id}).forEach(function(n) {
n.z = ev.activeWorkspace;
n.dirty = true;
});
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.remove(ev.nodes[i]);
@@ -183,7 +184,7 @@ RED.history = (function() {
}
RED.nodes.removeSubflow(ev.subflow);
RED.view.removeWorkspace(ev.subflow);
RED.workspaces.remove(ev.subflow);
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
@@ -191,8 +192,17 @@ RED.history = (function() {
}
}
}
RED.view.dirty(ev.dirty);
RED.view.redraw();
Object.keys(modifiedTabs).forEach(function(id) {
var subflow = RED.nodes.subflow(id);
if (subflow) {
RED.editor.validateNode(subflow);
}
});
RED.nodes.dirty(ev.dirty);
RED.view.redraw(true);
RED.palette.refresh();
}
}
}

210
editor/js/main.js Normal file
View File

@@ -0,0 +1,210 @@
/**
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = (function() {
function loadSettings() {
RED.settings.init(loadNodeList);
}
function loadNodeList() {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'nodes',
success: function(data) {
RED.nodes.setNodeList(data);
loadNodes();
}
});
}
function loadNodes() {
$.ajax({
headers: {
"Accept":"text/html"
},
cache: false,
url: 'nodes',
success: function(data) {
$("body").append(data);
$(".palette-spinner").hide();
$(".palette-scroll").show();
$("#palette-search").show();
loadFlows();
}
});
}
function loadFlows() {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'flows',
success: function(nodes) {
RED.nodes.import(nodes);
RED.nodes.dirty(false);
RED.view.redraw(true);
RED.comms.subscribe("status/#",function(topic,msg) {
var parts = topic.split("/");
var node = RED.nodes.node(parts[1]);
if (node) {
node.status = msg;
if (statusEnabled) {
node.dirty = true;
RED.view.redraw();
}
}
});
RED.comms.subscribe("node/#",function(topic,msg) {
var i,m;
var typeList;
var info;
if (topic == "node/added") {
var addedTypes = [];
for (i=0;i<msg.length;i++) {
m = msg[i];
var id = m.id;
RED.nodes.addNodeSet(m);
addedTypes = addedTypes.concat(m.types);
$.get('nodes/'+id, function(data) {
$("body").append(data);
});
}
if (addedTypes.length) {
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(addedTypes.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
}
} else if (topic == "node/removed") {
for (i=0;i<msg.length;i++) {
m = msg[i];
info = RED.nodes.removeNodeSet(m.id);
if (info.added) {
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(m.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success");
}
}
} else if (topic == "node/enabled") {
if (msg.types) {
info = RED.nodes.getNodeSet(msg.id);
if (info.added) {
RED.nodes.enableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" enabled:"+typeList,"success");
} else {
$.get('nodes/'+msg.id, function(data) {
$("body").append(data);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
});
}
}
} else if (topic == "node/disabled") {
if (msg.types) {
RED.nodes.disableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success");
}
}
});
}
});
}
var statusEnabled = false;
function toggleStatus(state) {
statusEnabled = state;
RED.view.status(statusEnabled);
}
function loadEditor() {
RED.menu.init({id:"btn-sidemenu",
options: [
{id:"menu-item-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"menu-item-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"menu-item-import",label:"Import",options:[
{id:"menu-item-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
{id:"menu-item-import-library",label:"Library",options:[]}
]},
{id:"menu-item-export",label:"Export",disabled:true,options:[
{id:"menu-item-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:"Library",disabled:true,onselect:RED.library.export}
]},
null,
{id:"menu-item-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
null,
{id:"menu-item-subflow",label:"Subflows", options: [
{id:"menu-item-subflow-create",label:"Create subflow",onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"menu-item-workspace",label:"Workspaces",options:[
{id:"menu-item-workspace-add",label:"Add",onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
null
]},
null,
{id:"menu-item-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
{id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
}
]
});
RED.user.init();
RED.library.init();
RED.palette.init();
RED.sidebar.init();
RED.subflow.init();
RED.workspaces.init();
RED.clipboard.init();
RED.view.init();
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
RED.comms.connect();
$("#main-container").show();
$(".header-toolbar").show();
loadNodeList();
}
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = document.title+" : "+window.location.hostname;
}
ace.require("ace/ext/language_tools");
RED.settings.init(loadEditor);
});
return {
};
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,13 @@ RED.nodes = (function() {
var workspaces = {};
var subflows = {};
var dirty = false;
function setDirty(d) {
dirty = d;
eventHandler.emit("change",{dirty:dirty});
}
var registry = (function() {
var nodeList = [];
var nodeSets = {};
@@ -160,13 +167,13 @@ RED.nodes = (function() {
}
nodes.push(n);
}
if (n._def.onadd) {
n._def.onadd.call(n);
}
}
function addLink(l) {
links.push(l);
}
function addConfig(c) {
configNodes[c.id] = c;
}
function getNode(id) {
if (id in configNodes) {
@@ -183,11 +190,13 @@ RED.nodes = (function() {
function removeNode(id) {
var removedLinks = [];
var node;
if (id in configNodes) {
node = configNodes[id];
delete configNodes[id];
RED.sidebar.config.refresh();
} else {
var node = getNode(id);
node = getNode(id);
if (node) {
nodes.splice(nodes.indexOf(node),1);
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
@@ -214,6 +223,9 @@ RED.nodes = (function() {
}
}
}
if (node._def.onremove) {
node._def.onremove.call(n);
}
return removedLinks;
}
@@ -224,12 +236,6 @@ RED.nodes = (function() {
}
}
function refreshValidation() {
for (var n=0;n<nodes.length;n++) {
RED.editor.validateNode(nodes[n]);
}
}
function addWorkspace(ws) {
workspaces[ws.id] = ws;
}
@@ -254,7 +260,24 @@ RED.nodes = (function() {
return {nodes:removedNodes,links:removedLinks};
}
function addSubflow(sf) {
function addSubflow(sf, createNewIds) {
if (createNewIds) {
var subflowNames = Object.keys(subflows).map(function(sfid) {
return subflows[sfid].name;
});
subflowNames.sort();
var copyNumber = 1;
var subflowName = sf.name;
subflowNames.forEach(function(name) {
if (subflowName == name) {
copyNumber++;
subflowName = sf.name+" ("+copyNumber+")";
}
});
sf.name = subflowName;
}
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
@@ -424,16 +447,16 @@ RED.nodes = (function() {
var exportedConfigNodes = {};
var exportedSubflows = {};
for (var n=0;n<set.length;n++) {
var node = set[n].n;
var node = set[n];
if (node.type.substring(0,8) == "subflow:") {
var subflowId = node.type.substring(8);
if (!exportedSubflows[subflowId]) {
exportedSubflows[subflowId] = true;
var subflow = getSubflow(subflowId);
var subflowSet = [{n:subflow}];
var subflowSet = [subflow];
RED.nodes.eachNode(function(n) {
if (n.z == subflowId) {
subflowSet.push({n:n});
subflowSet.push(n);
}
});
var exportableSubflow = createExportableNodeSet(subflowSet);
@@ -494,279 +517,340 @@ RED.nodes = (function() {
}
function importNodes(newNodesObj,createNewIds) {
try {
var i;
var n;
var newNodes;
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
}
var i;
var n;
var newNodes;
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
}
try {
newNodes = JSON.parse(newNodesObj);
} else {
newNodes = newNodesObj;
} catch(err) {
var e = new Error("Invalid flow: "+err.message);
e.code = "NODE_RED";
throw e;
}
} else {
newNodes = newNodesObj;
}
if (!$.isArray(newNodes)) {
newNodes = [newNodes];
}
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:") {
// TODO: get this UI thing out of here! (see below as well)
if (unknownTypes.indexOf(n.type)==-1) {
unknownTypes.push(n.type);
}
//if (n.x == null && n.y == null) {
// // config node - remove it
// newNodes.splice(i,1);
// i--;
//}
}
}
if (unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
var type = "type"+(unknownTypes.length > 1?"s":"");
RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
//"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
}
var activeWorkspace = RED.view.getWorkspace();
var activeSubflow = getSubflow(activeWorkspace);
if (activeSubflow) {
for (i=0;i<newNodes.length;i++) {
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
if (m) {
var subflowId = m[1];
var err;
if (subflowId === activeSubflow.id) {
err = new Error("Cannot add subflow to itself");
}
if (subflowContains(m[1],activeSubflow.id)) {
err = new Error("Cannot add subflow - circular reference detected");
}
if (err) {
// TODO: standardise error codes
err.code = "NODE_RED";
throw err;
}
}
}
}
if (!$.isArray(newNodes)) {
newNodes = [newNodes];
}
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) {
var new_workspaces = [];
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var nid;
unknownTypes.push(n.type);
}
}
if (unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
var type = "type"+(unknownTypes.length > 1?"s":"");
RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
//"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
}
var activeWorkspace = RED.workspaces.active();
var activeSubflow = getSubflow(activeWorkspace);
if (activeSubflow) {
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type === "workspace" || n.type === "tab") {
if (n.type === "workspace") {
n.type = "tab";
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
if (m) {
var subflowId = m[1];
var err;
if (subflowId === activeSubflow.id) {
err = new Error("Cannot add subflow to itself");
}
if (defaultWorkspace == null) {
defaultWorkspace = n;
if (subflowContains(m[1],activeSubflow.id)) {
err = new Error("Cannot add subflow - circular reference detected");
}
if (createNewIds) {
nid = getID();
workspace_map[n.id] = nid;
n.id = nid;
if (err) {
// TODO: standardise error codes
err.code = "NODE_RED";
throw err;
}
addWorkspace(n);
RED.view.addWorkspace(n);
new_workspaces.push(n);
} else if (n.type === "subflow") {
subflow_map[n.id] = n;
if (createNewIds) {
nid = getID();
n.id = nid;
}
// TODO: handle createNewIds - map old to new subflow ids
n.in.forEach(function(input,i) {
input.type = "subflow";
input.direction = "in";
input.z = n.id;
input.i = i;
input.id = getID();
});
n.out.forEach(function(output,i) {
output.type = "subflow";
output.direction = "out";
output.z = n.id;
output.i = i;
output.id = getID();
});
new_subflows.push(n);
addSubflow(n);
}
}
if (defaultWorkspace == null) {
defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
addWorkspace(defaultWorkspace);
RED.view.addWorkspace(defaultWorkspace);
new_workspaces.push(defaultWorkspace);
}
var node_map = {};
var new_nodes = [];
var new_links = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
var def = registry.getNodeType(n.type);
if (def && def.category == "config") {
if (!RED.nodes.node(n.id)) {
var configNode = {id:n.id,type:n.type,users:[]};
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
}
}
var new_workspaces = [];
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var nid;
var def;
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type === "workspace" || n.type === "tab") {
if (n.type === "workspace") {
n.type = "tab";
}
if (defaultWorkspace == null) {
defaultWorkspace = n;
}
if (createNewIds) {
nid = getID();
workspace_map[n.id] = nid;
n.id = nid;
}
addWorkspace(n);
RED.workspaces.add(n);
new_workspaces.push(n);
} else if (n.type === "subflow") {
subflow_map[n.id] = n;
if (createNewIds) {
nid = getID();
n.id = nid;
}
// TODO: handle createNewIds - map old to new subflow ids
n.in.forEach(function(input,i) {
input.type = "subflow";
input.direction = "in";
input.z = n.id;
input.i = i;
input.id = getID();
});
n.out.forEach(function(output,i) {
output.type = "subflow";
output.direction = "out";
output.z = n.id;
output.i = i;
output.id = getID();
});
new_subflows.push(n);
addSubflow(n,createNewIds);
} else {
def = registry.getNodeType(n.type);
if (def && def.category == "config") {
if (!RED.nodes.node(n.id)) {
var configNode = {id:n.id,type:n.type,users:[]};
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
}
configNode.label = def.label;
configNode._def = def;
RED.nodes.add(configNode);
}
} else {
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
if (createNewIds) {
if (subflow_map[node.z]) {
node.z = subflow_map[node.z].id;
} else {
node.z = workspace_map[node.z];
if (!workspaces[node.z]) {
node.z = activeWorkspace;
}
}
node.id = getID();
configNode.label = def.label;
configNode._def = def;
RED.nodes.add(configNode);
}
}
}
}
if (defaultWorkspace == null) {
defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
addWorkspace(defaultWorkspace);
RED.workspaces.add(defaultWorkspace);
new_workspaces.push(defaultWorkspace);
activeWorkspace = RED.workspaces.active();
}
var node_map = {};
var new_nodes = [];
var new_links = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
def = registry.getNodeType(n.type);
if (!def || def.category != "config") {
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
if (createNewIds) {
if (subflow_map[node.z]) {
node.z = subflow_map[node.z].id;
} else {
node.id = n.id;
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
node.z = workspace_map[node.z];
if (!workspaces[node.z]) {
node.z = activeWorkspace;
}
}
node.type = n.type;
node._def = def;
if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
} else {
if (!node._def) {
if (node.x && node.y) {
node._def = {
color:"#fee",
defaults: {},
label: "unknown: "+n.type,
labelStyle: "node_label_italic",
outputs: n.outputs||n.wires.length
}
} else {
node._def = {
category:"config"
};
node.users = [];
}
var orig = {};
for (var p in n) {
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
orig[p] = n[p];
}
}
node._orig = orig;
node.name = n.type;
node.type = "unknown";
}
if (node._def.category != "config") {
node.inputs = n.inputs||node._def.inputs;
node.outputs = n.outputs||node._def.outputs;
for (var d2 in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d2)) {
node[d2] = n[d2];
}
}
}
node.id = getID();
} else {
node.id = n.id;
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
node.z = activeWorkspace;
}
}
node.type = n.type;
node._def = def;
if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
} else {
if (!node._def) {
if (node.x && node.y) {
node._def = {
color:"#fee",
defaults: {},
label: "unknown: "+n.type,
labelStyle: "node_label_italic",
outputs: n.outputs||n.wires.length
}
} else {
node._def = {
category:"config"
};
node.users = [];
}
var orig = {};
for (var p in n) {
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
orig[p] = n[p];
}
}
node._orig = orig;
node.name = n.type;
node.type = "unknown";
}
addNode(node);
RED.editor.validateNode(node);
node_map[n.id] = node;
if (node._def.category != "config") {
new_nodes.push(node);
node.inputs = n.inputs||node._def.inputs;
node.outputs = n.outputs||node._def.outputs;
for (var d2 in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d2)) {
node[d2] = n[d2];
}
}
}
}
addNode(node);
RED.editor.validateNode(node);
node_map[n.id] = node;
if (node._def.category != "config") {
new_nodes.push(node);
}
}
}
for (i=0;i<new_nodes.length;i++) {
n = new_nodes[i];
for (var w1=0;w1<n.wires.length;w1++) {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) {
if (wires[w2] in node_map) {
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
addLink(link);
new_links.push(link);
}
}
}
delete n.wires;
}
for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i];
n.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
addLink(link);
new_links.push(link);
});
delete input.wires;
});
n.out.forEach(function(output) {
output.wires.forEach(function(wire) {
var link;
if (wire.id == n.id) {
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
} else {
link = {source:node_map[wire.id], sourcePort:wire.port,target:output};
}
addLink(link);
new_links.push(link);
});
delete output.wires;
});
}
return [new_nodes,new_links,new_workspaces,new_subflows];
} catch(error) {
if (error.code != "NODE_RED") {
console.log(error.stack);
RED.notify("<strong>Error</strong>: "+error,"error");
} else {
RED.notify("<strong>Error</strong>: "+error.message,"error");
}
return null;
}
for (i=0;i<new_nodes.length;i++) {
n = new_nodes[i];
for (var w1=0;w1<n.wires.length;w1++) {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) {
if (wires[w2] in node_map) {
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
addLink(link);
new_links.push(link);
}
}
}
delete n.wires;
}
for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i];
n.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
addLink(link);
new_links.push(link);
});
delete input.wires;
});
n.out.forEach(function(output) {
output.wires.forEach(function(wire) {
var link;
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
} else {
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
}
addLink(link);
new_links.push(link);
});
delete output.wires;
});
}
return [new_nodes,new_links,new_workspaces,new_subflows];
}
// TODO: supports filter.z|type
function filterNodes(filter) {
var result = [];
for (var n=0;n<nodes.length;n++) {
var node = nodes[n];
if (filter.hasOwnProperty("z") && node.z !== filter.z) {
continue;
}
if (filter.hasOwnProperty("type") && node.type !== filter.type) {
continue;
}
result.push(node);
}
return result;
}
function filterLinks(filter) {
var result = [];
for (var n=0;n<links.length;n++) {
var link = links[n];
if (filter.source) {
if (filter.source.hasOwnProperty("id") && link.source.id !== filter.source.id) {
continue;
}
if (filter.source.hasOwnProperty("z") && link.source.z !== filter.source.z) {
continue;
}
}
if (filter.target) {
if (filter.target.hasOwnProperty("id") && link.target.id !== filter.target.id) {
continue;
}
if (filter.target.hasOwnProperty("z") && link.target.z !== filter.target.z) {
continue;
}
}
if (filter.hasOwnProperty("sourcePort") && link.sourcePort !== filter.sourcePort) {
continue;
}
result.push(link);
}
return result;
}
// TODO: DRY
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
return {
on: eventHandler.on,
registry:registry,
setNodeList: registry.setNodeList,
@@ -819,14 +903,24 @@ RED.nodes = (function() {
}
}
},
node: getNode,
filterNodes: filterNodes,
filterLinks: filterLinks,
import: importNodes,
refreshValidation: refreshValidation,
getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet,
id: getID,
nodes: nodes, // TODO: exposed for d3 vis
links: links // TODO: exposed for d3 vis
dirty: function(d) {
if (d == null) {
return dirty;
} else {
setDirty(d);
}
}
};
})();

View File

@@ -68,13 +68,20 @@ RED.settings = (function () {
};
var init = function (done) {
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
if (accessTokenMatch) {
var accessToken = accessTokenMatch[1];
RED.settings.set("auth-tokens",{access_token: accessToken});
window.location.search = "";
}
$.ajaxSetup({
beforeSend: function(jqXHR,settings) {
// Only attach auth header for requests to relative paths
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
var auth_tokens = RED.settings.get("auth-tokens");
if (auth_tokens) {
jqXHR.setRequestHeader("authorization","bearer "+auth_tokens.access_token);
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
}
}
}
@@ -84,7 +91,6 @@ RED.settings = (function () {
}
var load = function(done) {
$.ajax({
headers: {
"Accept": "application/json"
@@ -102,6 +108,9 @@ RED.settings = (function () {
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
window.location.search = "";
}
RED.user.login(function() { load(done); });
} else {
console.log("Unexpected error:",jqXHR.status,textStatus);
@@ -110,13 +119,30 @@ RED.settings = (function () {
});
};
function theme(property,defaultValue) {
if (!RED.settings.editorTheme) {
return defaultValue;
}
var parts = property.split(".");
var v = RED.settings.editorTheme;
try {
for (var i=0;i<parts.length;i++) {
v = v[parts[i]];
}
return v;
} catch(err) {
return defaultValue;
}
}
return {
init: init,
load: load,
set: set,
get: get,
remove: remove
remove: remove,
theme: theme
}
})
();

178
editor/js/ui/clipboard.js Normal file
View File

@@ -0,0 +1,178 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.clipboard = (function() {
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
buttons: [
{
id: "clipboard-dialog-ok",
text: "Ok",
click: function() {
if (/Import/.test(dialog.dialog("option","title"))) {
RED.view.importNodes($("#clipboard-import").val());
}
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-cancel",
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
text: "Close",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
var dialogContainer = dialog.children(".dialog-form");
var exportNodesDialog = '<div class="form-row">'+
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-tips">'+
'Select the text above and copy to the clipboard with Ctrl-C.'+
'</div>';
var importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="Paste nodes here"></textarea>'+
'</div>';
function importNodes() {
dialogContainer.empty();
dialogContainer.append($(importNodesDialog));
$("#clipboard-dialog-ok").show();
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-close").hide();
$("#clipboard-dialog-ok").button("disable");
$("#clipboard-import").keyup(function() {
var v = $(this).val();
try {
JSON.parse(v);
$(this).removeClass("input-error");
$("#clipboard-dialog-ok").button("enable");
} catch(err) {
if (v !== "") {
$(this).addClass("input-error");
}
$("#clipboard-dialog-ok").button("disable");
}
});
dialog.dialog("option","title","Import nodes").dialog("open");
}
function exportNodes() {
dialogContainer.empty();
dialogContainer.append($(exportNodesDialog));
$("#clipboard-dialog-ok").hide();
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-close").show();
var selection = RED.view.selection();
if (selection.nodes) {
var nns = RED.nodes.createExportableNodeSet(selection.nodes);
$("#clipboard-export")
.val(JSON.stringify(nns))
.focus(function() {
var textarea = $(this);
textarea.select();
textarea.mouseup(function() {
textarea.unbind("mouseup");
return false;
})
});
dialog.dialog("option","title","Export nodes to clipboard").dialog( "open" );
}
}
function hideDropTarget() {
$("#dropTarget").hide();
RED.keyboard.remove(/* ESCAPE */ 27);
}
return {
init: function() {
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
$('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'});
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
}
});
$('#dropTarget').on("dragover",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
event.preventDefault();
}
})
.on("dragleave",function(event) {
hideDropTarget();
})
.on("drop",function(event) {
var data = event.originalEvent.dataTransfer.getData("text/plain");
hideDropTarget();
RED.view.importNodes(data);
event.preventDefault();
});
},
import: importNodes,
export: exportNodes
}
})();

253
editor/js/ui/deploy.js Normal file
View File

@@ -0,0 +1,253 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.deploy = (function() {
var deploymentTypes = {
"full":{img:"red/images/deploy-full-o.png"},
"nodes":{img:"red/images/deploy-nodes-o.png"},
"flows":{img:"red/images/deploy-flows-o.png"}
}
var ignoreDeployWarnings = {
unknown: false,
unusedConfig: false,
invalid: false
}
var deploymentType = "full";
function changeDeploymentType(type) {
deploymentType = type;
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
}
/**
* options:
* type: "default" - Button with drop-down options - no further customisation available
* type: "simple" - Button without dropdown. Customisations:
* label: the text to display - default: "Deploy"
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.png"
*/
function init(options) {
options = options || {};
var type = options.type || "default";
if (type == "default") {
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#"><img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> <span>Deploy</span></a>'+
'<a id="btn-deploy-options" data-toggle="dropdown" class="deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></li>').prependTo(".header-toolbar");
RED.menu.init({id:"btn-deploy-options",
options: [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
]
});
} else if (type == "simple") {
var label = options.label || "Deploy";
var icon = 'red/images/deploy-full-o.png';
if (options.hasOwnProperty('icon')) {
icon = options.icon;
}
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
'<span>'+label+'</span></a>'+
'</span></li>').prependTo(".header-toolbar");
}
$('#btn-deploy').click(function() { save(); });
$( "#node-dialog-confirm-deploy" ).dialog({
title: "Confirm deploy",
modal: true,
autoOpen: false,
width: 550,
height: "auto",
buttons: [
{
text: "Confirm deploy",
click: function() {
var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked");
if (ignoreChecked) {
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
}
save(true);
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
create: function() {
$("#node-dialog-confirm-deploy").parent().find("div.ui-dialog-buttonpane")
.append('<div style="height:0; vertical-align: middle; display:inline-block;">'+
'<input style="vertical-align:top;" type="checkbox" id="node-dialog-confirm-deploy-hide">'+
'<label style="display:inline;" for="node-dialog-confirm-deploy-hide"> do not warn about this again</label>'+
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
'</div>');
}
});
RED.nodes.on('change',function(state) {
if (state.dirty) {
window.onbeforeunload = function() {
return "You have undeployed changes.\n\nLeaving this page will lose these changes.";
}
$("#btn-deploy").removeClass("disabled");
} else {
window.onbeforeunload = null;
$("#btn-deploy").addClass("disabled");
}
});
}
function save(force) {
if (RED.nodes.dirty()) {
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
if (!force) {
var hasUnknown = false;
var hasInvalid = false;
var hasUnusedConfig = false;
var unknownNodes = [];
RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid;
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
hasUnknown = unknownNodes.length > 0;
var unusedConfigNodes = {};
RED.nodes.eachConfig(function(node) {
if (node.users.length === 0) {
var label = "";
if (typeof node._def.label == "function") {
label = node._def.label.call(node);
} else {
label = node._def.label;
}
label = label || node.id;
unusedConfigNodes[node.type] = unusedConfigNodes[node.type] || [];
unusedConfigNodes[node.type].push(label);
hasUnusedConfig = true;
}
});
$( "#node-dialog-confirm-deploy-config" ).hide();
$( "#node-dialog-confirm-deploy-unknown" ).hide();
$( "#node-dialog-confirm-deploy-unused" ).hide();
var showWarning = false;
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("unknown");
$( "#node-dialog-confirm-deploy-unknown" ).show();
$( "#node-dialog-confirm-deploy-unknown-list" )
.html("<li>"+unknownNodes.join("</li><li>")+"</li>");
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("invalid");
$( "#node-dialog-confirm-deploy-config" ).show();
} else if (hasUnusedConfig && !ignoreDeployWarnings.unusedConfig) {
showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
$( "#node-dialog-confirm-deploy-unused" ).show();
var unusedNodeLabels = [];
var unusedTypes = Object.keys(unusedConfigNodes).sort();
unusedTypes.forEach(function(type) {
unusedConfigNodes[type].forEach(function(label) {
unusedNodeLabels.push(type+": "+label);
});
});
$( "#node-dialog-confirm-deploy-unused-list" )
.html("<li>"+unusedNodeLabels.join("</li><li>")+"</li>");
}
if (showWarning) {
$( "#node-dialog-confirm-deploy-hide" ).prop("checked",false);
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
return;
}
}
var nns = RED.nodes.createCompleteNodeSet();
$("#btn-deploy-icon").removeClass('fa-download');
$("#btn-deploy-icon").addClass('spinner');
RED.nodes.dirty(false);
$.ajax({
url:"flows",
type: "POST",
data: JSON.stringify(nns),
contentType: "application/json; charset=utf-8",
headers: {
"Node-RED-Deployment-Type":deploymentType
}
}).done(function(data,textStatus,xhr) {
RED.notify("Successfully deployed","success");
RED.nodes.eachNode(function(node) {
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if(node.credentials) {
delete node.credentials;
}
});
RED.nodes.eachConfig(function (confNode) {
if (confNode.credentials) {
delete confNode.credentials;
}
});
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
}).fail(function(xhr,textStatus,err) {
RED.nodes.dirty(true);
if (xhr.responseText) {
RED.notify("<strong>Error</strong>: "+xhr.responseJSON.message,"error");
} else {
RED.notify("<strong>Error</strong>: no response from server","error");
}
}).always(function() {
$("#btn-deploy-icon").removeClass('spinner');
$("#btn-deploy-icon").addClass('fa-download');
});
}
}
return {
init: init
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2014 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
**/
RED.editor = (function() {
var editing_node = null;
// TODO: should IMPORT/EXPORT get their own dialogs?
function getCredentialsURL(nodeType, nodeID) {
var dashedType = nodeType.replace(/\s+/g, '-');
@@ -23,21 +22,70 @@ RED.editor = (function() {
}
/**
* Validate a node
* Validate a node
* @param node - the node being validated
* @returns {boolean} whether the node is valid. Sets node.dirty if needed
*/
function validateNode(node) {
var oldValue = node.valid;
node.valid = validateNodeProperties(node, node._def.defaults, node);
if (node._def._creds) {
node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
var oldChanged = node.changed;
node.valid = true;
var subflow;
var isValid;
var hasChanged;
if (node.type.indexOf("subflow:")===0) {
subflow = RED.nodes.subflow(node.type.substring(8));
isValid = subflow.valid;
hasChanged = subflow.changed;
if (isValid === undefined) {
isValid = validateNode(subflow);
hasChanged = subflow.changed;
}
node.valid = isValid;
node.changed = hasChanged;
} else if (node._def) {
node.valid = validateNodeProperties(node, node._def.defaults, node);
if (node._def._creds) {
node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
}
} else if (node.type == "subflow") {
var subflowNodes = RED.nodes.filterNodes({z:node.id});
for (var i=0;i<subflowNodes.length;i++) {
isValid = subflowNodes[i].valid;
hasChanged = subflowNodes[i].changed;
if (isValid === undefined) {
isValid = validateNode(subflowNodes[i]);
hasChanged = subflowNodes[i].changed;
}
node.valid = node.valid && isValid;
node.changed = node.changed || hasChanged;
}
var subflowInstances = RED.nodes.filterNodes({type:"subflow:"+node.id});
var modifiedTabs = {};
for (i=0;i<subflowInstances.length;i++) {
subflowInstances[i].valid = node.valid;
subflowInstances[i].changed = node.changed;
subflowInstances[i].dirty = true;
modifiedTabs[subflowInstances[i].z] = true;
}
Object.keys(modifiedTabs).forEach(function(id) {
var subflow = RED.nodes.subflow(id);
if (subflow) {
validateNode(subflow);
}
});
}
if (oldValue != node.valid) {
if (oldValue !== node.valid || oldChanged !== node.changed) {
node.dirty = true;
subflow = RED.nodes.subflow(node.z);
if (subflow) {
validateNode(subflow);
}
}
return node.valid;
}
/**
* Validate a node's properties for the given set of property definitions
* @param node - the node being validated
@@ -75,7 +123,7 @@ RED.editor = (function() {
}
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
if (!value || value == "_ADD_") {
valid = false;
valid = definition[property].hasOwnProperty("required") && !definition[property].required;
} else {
var v = RED.nodes.node(value).valid;
valid = (v==null || v);
@@ -112,11 +160,7 @@ RED.editor = (function() {
}
}
if (node.inputs === 0) {
RED.nodes.eachLink(function(l) {
if (l.target === node) {
removedLinks.push(l);
}
});
removedLinks.concat(RED.nodes.filterLinks({target:node}));
}
for (var l=0;l<removedLinks.length;l++) {
RED.nodes.removeLink(removedLinks[l]);
@@ -124,13 +168,13 @@ RED.editor = (function() {
return removedLinks;
}
$( "#dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
width: 500,
minWidth: 500,
width: 'auto',
buttons: [
{
id: "node-dialog-ok",
@@ -139,7 +183,7 @@ RED.editor = (function() {
if (editing_node) {
var changes = {};
var changed = false;
var wasDirty = RED.view.dirty();
var wasDirty = RED.nodes.dirty();
var d;
if (editing_node._def.oneditsave) {
@@ -173,8 +217,6 @@ RED.editor = (function() {
}
}
}
}
if (editing_node._def.defaults) {
@@ -204,7 +246,6 @@ RED.editor = (function() {
configNode.users.push(editing_node);
}
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
@@ -220,35 +261,30 @@ RED.editor = (function() {
changed = changed || credsChanged;
}
var removedLinks = updateNodeProperties(editing_node);
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.view.dirty(true);
RED.nodes.dirty(true);
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
}
editing_node.dirty = true;
validateNode(editing_node);
RED.view.redraw();
} else if (RED.view.state() == RED.state.EXPORT) {
if (/library/.test($( "#dialog" ).dialog("option","title"))) {
//TODO: move this to RED.library
var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify("Saved nodes","success");
});
}
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
//TODO: move this to RED.library
var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify("Saved nodes","success");
});
}
} else if (RED.view.state() == RED.state.IMPORT) {
RED.view.importNodes($("#node-input-import").val());
}
$( this ).dialog( "close" );
}
@@ -257,6 +293,11 @@ RED.editor = (function() {
id: "node-dialog-cancel",
text: "Cancel",
click: function() {
if (editing_node && editing_node._def) {
if (editing_node._def.oneditcancel) {
editing_node._def.oneditcancel.call(editing_node);
}
}
$( this ).dialog( "close" );
}
}
@@ -267,6 +308,12 @@ RED.editor = (function() {
}
},
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
} else {
$(this).dialog('option','width',$(this).outerWidth());
}
RED.keyboard.disable();
if (editing_node) {
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
@@ -283,12 +330,12 @@ RED.editor = (function() {
RED.view.state(RED.state.DEFAULT);
}
$( this ).dialog('option','height','auto');
$( this ).dialog('option','width','500');
$( this ).dialog('option','width','auto');
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
var buttons = $( this ).dialog("option","buttons");
if (buttons.length == 3) {
$( this ).dialog("option","buttons",buttons.splice(1));
@@ -327,6 +374,32 @@ RED.editor = (function() {
input.val(label);
}
/**
* Create a config-node button for this property
* @param node - the node being edited
* @param property - the name of the field
* @param type - the type of the config-node
*/
function prepareConfigNodeButton(node,property,type) {
var input = $("#node-input-"+property);
input.val(node[property]);
input.attr("type","hidden");
var button = $("<a>",{id:"node-input-edit-"+property, class:"btn"});
input.after(button);
if (node[property]) {
button.text("edit");
} else {
button.text("add");
}
button.click(function(e) {
showEditConfigNodeDialog(property,type,input.val()||"_ADD_");
e.preventDefault();
});
}
/**
* Populate the editor dialog input field for this property
* @param node - the node being edited
@@ -395,7 +468,7 @@ RED.editor = (function() {
}
}
}
/**
* Update the node credentials from the edit form
* @param node - the node containing the credentials
@@ -419,7 +492,7 @@ RED.editor = (function() {
continue;
}
changed = true;
}
node.credentials[cred] = value;
if (value != node.credentials._[cred]) {
@@ -440,7 +513,11 @@ RED.editor = (function() {
for (var d in definition.defaults) {
if (definition.defaults.hasOwnProperty(d)) {
if (definition.defaults[d].type) {
prepareConfigNodeSelect(node,d,definition.defaults[d].type);
if (definition.defaults[d].exclusive) {
prepareConfigNodeButton(node,d,definition.defaults[d].type);
} else {
prepareConfigNodeSelect(node,d,definition.defaults[d].type);
}
} else {
preparePropertyEditor(node,d,prefix);
}
@@ -457,7 +534,7 @@ RED.editor = (function() {
}
}
}
if (definition.credentials) {
if (node.credentials) {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
@@ -487,7 +564,7 @@ RED.editor = (function() {
class: 'leftButton',
text: "Edit flow",
click: function() {
RED.view.showSubflow(id);
RED.workspaces.show(id);
$("#node-dialog-ok").click();
}
});
@@ -496,11 +573,11 @@ RED.editor = (function() {
$("#dialog-form").html($("script[data-template-name='"+type+"']").html());
$('<input type="text" style="display: none;" />').appendTo("#dialog-form");
prepareEditDialog(node,node._def,"node-input");
$( "#dialog" ).dialog("option","title","Edit "+type+" node").dialog( "open" );
}
@@ -547,6 +624,9 @@ RED.editor = (function() {
if (configTypeDef.ondelete) {
configTypeDef.ondelete.call(RED.nodes.node(configId));
}
if (configTypeDef.oneditdelete) {
configTypeDef.oneditdelete.call(RED.nodes.node(configId));
}
RED.nodes.remove(configId);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
@@ -558,7 +638,7 @@ RED.editor = (function() {
validateNode(user);
}
updateConfigNodeSelect(configProperty,configType,"");
RED.view.dirty(true);
RED.nodes.dirty(true);
$( this ).dialog( "close" );
RED.view.redraw();
}
@@ -579,29 +659,42 @@ RED.editor = (function() {
}
function updateConfigNodeSelect(name,type,value) {
var select = $("#node-input-"+name);
var node_def = RED.nodes.getType(type);
select.children().remove();
RED.nodes.eachConfig(function(config) {
if (config.type == type) {
var label = "";
if (typeof node_def.label == "function") {
label = node_def.label.call(config);
} else {
label = node_def.label;
}
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
var button = $("#node-input-edit-"+name);
if (button.length) {
if (value) {
button.text("edit");
} else {
button.text("add");
}
});
$("#node-input-"+name).val(value);
} else {
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>');
window.setTimeout(function() { select.change();},50);
var select = $("#node-input-"+name);
var node_def = RED.nodes.getType(type);
select.children().remove();
RED.nodes.eachConfig(function(config) {
if (config.type == type) {
var label = "";
if (typeof node_def.label == "function") {
label = node_def.label.call(config);
} else {
label = node_def.label;
}
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
}
});
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>');
window.setTimeout(function() { select.change();},50);
}
}
$( "#node-config-dialog" ).dialog({
modal: true,
autoOpen: false,
width: 500,
dialogClass: "ui-dialog-no-close",
minWidth: 500,
width: 'auto',
closeOnEscape: false,
buttons: [
{
@@ -615,12 +708,18 @@ RED.editor = (function() {
var configTypeDef = RED.nodes.getType(configType);
var configNode;
var d;
var input;
if (configAdding) {
configNode = {type:configType,id:configId,users:[]};
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
configNode[d] = $("#node-config-input-"+d).val();
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
configNode.label = configTypeDef.label;
@@ -631,7 +730,7 @@ RED.editor = (function() {
configNode = RED.nodes.node(configId);
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
var input = $("#node-config-input-"+d);
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
@@ -648,8 +747,12 @@ RED.editor = (function() {
configTypeDef.oneditsave.call(RED.nodes.node(configId));
}
validateNode(configNode);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
validateNode(user);
}
RED.view.dirty(true);
RED.nodes.dirty(true);
$(this).dialog("close");
}
@@ -681,11 +784,17 @@ RED.editor = (function() {
resize: function(e,ui) {
},
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.disable();
}
},
close: function(e) {
$(this).dialog('option','width','auto');
$(this).dialog('option','height','auto');
$("#dialog-config-form").html("");
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.enable();
@@ -697,8 +806,10 @@ RED.editor = (function() {
$( "#subflow-dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
width: 500,
minWidth: 500,
width: 'auto',
buttons: [
{
id: "subflow-dialog-ok",
@@ -708,19 +819,19 @@ RED.editor = (function() {
var i;
var changes = {};
var changed = false;
var wasDirty = RED.view.dirty();
var wasDirty = RED.nodes.dirty();
var newName = $("#subflow-input-name").val();
if (newName != editing_node.name) {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#btn-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
}
RED.palette.refresh();
if (changed) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
@@ -730,7 +841,7 @@ RED.editor = (function() {
});
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.view.dirty(true);
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
@@ -738,7 +849,7 @@ RED.editor = (function() {
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
@@ -758,6 +869,10 @@ RED.editor = (function() {
],
open: function(e) {
RED.keyboard.disable();
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
},
close: function(e) {
RED.keyboard.enable();
@@ -769,31 +884,60 @@ RED.editor = (function() {
editing_node = null;
}
});
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
function showEditSubflowDialog(subflow) {
editing_node = subflow;
RED.view.state(RED.state.EDITING);
$("#subflow-input-name").val(subflow.name);
var userCount = 0;
var subflowType = "subflow:"+editing_node.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
userCount++;
}
});
$("#subflow-dialog-user-count").html("There "+(userCount==1?"is":"are")+" "+userCount+" instance"+(userCount==1?" ":"s")+" of this subflow").show();
$("#subflow-dialog").dialog("option","title","Edit flow "+subflow.name).dialog( "open" );
}
return {
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties // TODO: only exposed for edit-undo
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
createEditor: function(options) {
var editor = ace.edit(options.id);
editor.setTheme("ace/theme/tomorrow");
var session = editor.getSession();
if (options.mode) {
session.setMode(options.mode);
}
if (options.foldStyle) {
session.setFoldStyle(options.foldStyle);
} else {
session.setFoldStyle('markbeginend');
}
if (options.options) {
editor.setOptions(options.options);
} else {
editor.setOptions({
enableBasicAutocompletion:true,
enableSnippets:true
});
}
editor.$blockScrolling = Infinity;
if (options.value) {
session.setValue(options.value,-1);
}
return editor;
}
}
})();

121
editor/js/ui/keyboard.js Normal file
View File

@@ -0,0 +1,121 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.keyboard = (function() {
var active = true;
var handlers = {};
d3.select(window).on("keydown",function() {
if (!active) { return; }
var handler = handlers[d3.event.keyCode];
if (handler && handler.ondown) {
if (!handler.modifiers ||
((!handler.modifiers.shift || d3.event.shiftKey) &&
(!handler.modifiers.ctrl || d3.event.ctrlKey || d3.event.metaKey) &&
(!handler.modifiers.alt || d3.event.altKey) )) {
handler.ondown();
}
}
});
d3.select(window).on("keyup",function() {
if (!active) { return; }
var handler = handlers[d3.event.keyCode];
if (handler && handler.onup) {
if (!handler.modifiers ||
((!handler.modifiers.shift || d3.event.shiftKey) &&
(!handler.modifiers.ctrl || d3.event.ctrlKey || d3.event.metaKey) &&
(!handler.modifiers.alt || d3.event.altKey) )) {
handler.onup();
}
}
});
function addHandler(key,modifiers,ondown,onup) {
var mod = modifiers;
var cbdown = ondown;
var cbup = onup;
if (typeof modifiers == "function") {
mod = {};
cbdown = modifiers;
cbup = ondown;
}
handlers[key] = {modifiers:mod, ondown:cbdown, onup:cbup};
}
function removeHandler(key) {
delete handlers[key];
}
var dialog = null;
function showKeyboardHelp() {
if (!RED.settings.theme("menu.menu-item-keyboard-shortcuts",true)) {
return;
}
if (!dialog) {
dialog = $('<div id="keyboard-help-dialog" class="hide">'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
'</table>'+
'</div>'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
'</table>'+
'</div>'+
'</div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: "800",
title:"Keyboard shortcuts",
resizable: false,
open: function() {
RED.keyboard.disable();
},
close: function() {
RED.keyboard.enable();
}
});
}
dialog.dialog("open");
}
return {
add: addHandler,
remove: removeHandler,
disable: function(){ active = false;},
enable: function(){ active = true; },
showHelp: showKeyboardHelp
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ RED.library = (function() {
var li;
var a;
var ul = document.createElement("ul");
ul.id = "btn-import-library-submenu";
ul.id = "menu-item-import-library-submenu";
ul.className = "dropdown-menu";
if (data.d) {
for (i in data.d) {
@@ -63,7 +63,7 @@ RED.library = (function() {
};
var menu = buildMenu(data,"");
//TODO: need an api in RED.menu for this
$("#btn-import-library-submenu").replaceWith(menu);
$("#menu-item-import-library-submenu").replaceWith(menu);
});
}
@@ -72,6 +72,19 @@ RED.library = (function() {
var selectedLibraryItem = null;
var libraryEditor = null;
// Orion editor has set/getText
// ACE editor has set/getValue
// normalise to set/getValue
if (options.editor.setText) {
// Orion doesn't like having pos passed in, so proxy the call to drop it
options.editor.setValue = function(text,pos) {
options.editor.setText.call(options.editor,text);
}
}
if (options.editor.getText) {
options.editor.getValue = options.editor.getText;
}
function buildFileListItem(item) {
var li = document.createElement("li");
li.onmouseover = function(e) { $(this).addClass("list-hover"); };
@@ -119,7 +132,7 @@ RED.library = (function() {
$(this).addClass("list-selected");
$.get("library/"+options.url+root+item.fn, function(data) {
selectedLibraryItem = item;
libraryEditor.setText(data);
libraryEditor.setValue(data,-1);
});
}
})();
@@ -144,7 +157,7 @@ RED.library = (function() {
$("#node-select-library").children().remove();
var bc = $("#node-dialog-library-breadcrumbs");
bc.children().first().nextAll().remove();
libraryEditor.setText('');
libraryEditor.setValue('',-1);
$.getJSON("library/"+options.url,function(data) {
$("#node-select-library").append(buildFileList("/",data));
@@ -205,14 +218,18 @@ RED.library = (function() {
e.preventDefault();
});
require(["orion/editor/edit"], function(edit) {
libraryEditor = edit({
parent:document.getElementById('node-select-library-text'),
lang:"js",
readonly: true
});
libraryEditor = ace.edit('node-select-library-text');
libraryEditor.setTheme("ace/theme/tomorrow");
if (options.mode) {
libraryEditor.getSession().setMode(options.mode);
}
libraryEditor.setOptions({
readOnly: true,
highlightActiveLine: false,
highlightGutterLine: false
});
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
libraryEditor.$blockScrolling = Infinity;
$( "#node-dialog-library-lookup" ).dialog({
title: options.type+" library",
@@ -229,7 +246,7 @@ RED.library = (function() {
var field = options.fields[i];
$("#node-input-"+field).val(selectedLibraryItem[field]);
}
options.editor.setText(libraryEditor.getText());
options.editor.setValue(libraryEditor.getValue(),-1);
}
$( this ).dialog( "close" );
}
@@ -294,19 +311,26 @@ RED.library = (function() {
//}
}
var queryArgs = [];
var data = {};
for (var i=0;i<options.fields.length;i++) {
var field = options.fields[i];
if (field == "name") {
queryArgs.push("name="+encodeURIComponent(name));
data.name = name;
} else {
queryArgs.push(encodeURIComponent(field)+"="+encodeURIComponent($("#node-input-"+field).val()));
data[field] = $("#node-input-"+field).val();
}
}
var queryString = queryArgs.join("&");
var text = options.editor.getText();
$.post("library/"+options.url+'/'+fullpath+"?"+queryString,text,function() {
RED.notify("Saved "+options.type,"success");
data.text = options.editor.getValue();
$.ajax({
url:"library/"+options.url+'/'+fullpath,
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
RED.notify("Saved "+options.type,"success");
}).fail(function(xhr,textStatus,err) {
RED.notify("Saved failed: "+xhr.responseJSON.message,"error");
});
}
$( "#node-dialog-library-save-confirm" ).dialog({
@@ -356,12 +380,36 @@ RED.library = (function() {
}
function exportFlow() {
//TODO: don't rely on the main dialog
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
$("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
$("#node-input-filename").attr('nodes',JSON.stringify(nns));
$( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" );
}
return {
init: function() {
loadFlowLibrary();
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary();
}
},
create: createUI,
loadFlowLibrary: loadFlowLibrary
loadFlowLibrary: loadFlowLibrary,
export: exportFlow
}
})();

View File

@@ -23,7 +23,14 @@ RED.menu = (function() {
function createMenuItem(opt) {
var item;
function setState() {
if (opt !== null && opt.id) {
var themeSetting = RED.settings.theme("menu."+opt.id);
if (themeSetting === false) {
return null;
}
}
function setInitialState() {
var savedStateActive = isSavedStateActive(opt.id);
if (savedStateActive) {
link.addClass("active");
@@ -99,7 +106,7 @@ RED.menu = (function() {
opt.onselect.call(opt);
}
});
setState();
setInitialState();
} else if (opt.href) {
link.attr("target","_blank").attr("href",opt.href);
} else if (!opt.options) {
@@ -113,7 +120,10 @@ RED.menu = (function() {
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
for (var i=0;i<opt.options.length;i++) {
createMenuItem(opt.options[i]).appendTo(submenu);
var li = createMenuItem(opt.options[i]);
if (li) {
li.appendTo(submenu);
}
}
}
if (opt.disabled) {
@@ -148,9 +158,16 @@ RED.menu = (function() {
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
var lastAddedSeparator = false;
for (var i=0;i<options.options.length;i++) {
var opt = options.options[i];
createMenuItem(opt).appendTo(topMenu);
if (opt !== null || !lastAddedSeparator) {
var li = createMenuItem(opt);
if (li) {
li.appendTo(topMenu);
lastAddedSeparator = (opt === null);
}
}
}
}
@@ -176,7 +193,7 @@ RED.menu = (function() {
} else {
$("#"+id).removeClass("active");
}
if (opt.onselect) {
if (opt && opt.onselect) {
opt.onselect.call(opt,state);
}
setSavedState(id, state);
@@ -198,17 +215,20 @@ RED.menu = (function() {
}
function setAction(id,action) {
menuItems[id].onselect = action;
$("#"+id).click(function() {
if ($(this).parent().hasClass("disabled")) {
return;
}
if (menuItems[id].toggle) {
setSelected(id,!isSelected(id));
} else {
menuItems[id].onselect.call(menuItems[id]);
}
});
var opt = menuItems[id];
if (opt) {
opt.onselect = action;
$("#"+id).click(function() {
if ($(this).parent().hasClass("disabled")) {
return;
}
if (menuItems[id].toggle) {
setSelected(id,!isSelected(id));
} else {
menuItems[id].onselect.call(menuItems[id]);
}
});
}
}
return {

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
RED.palette = (function() {
var exclusion = ['config','unknown','deprecated'];
var core = ['input', 'output', 'function', 'subflows', 'social', 'storage', 'analysis', 'advanced'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
function createCategoryContainer(category){
var escapedCategory = category.replace(" ","_");
@@ -98,7 +98,6 @@ RED.palette = (function() {
}
function addNodeType(nt,def) {
var nodeTypeId = escapeNodeType(nt);
if ($("#palette_node_"+nodeTypeId).length) {
return;
@@ -121,17 +120,16 @@ RED.palette = (function() {
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
}
d.innerHTML = '<div class="palette_label"></div>';
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
d.className="palette_node";
if (def.icon) {
d.style.backgroundImage = "url(icons/"+def.icon+")";
d.style.backgroundSize = "18px 27px";
if (def.align == "right") {
d.style.backgroundPosition = "95% 50%";
} else if (def.inputs > 0) {
d.style.backgroundPosition = "10% 50%";
}
var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
}
d.style.backgroundColor = def.color;
@@ -169,6 +167,7 @@ RED.palette = (function() {
container:'body'
});
$(d).click(function() {
RED.view.focus();
var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>";
$("#tab-info").html(help);
});
@@ -176,16 +175,41 @@ RED.palette = (function() {
helper: 'clone',
appendTo: 'body',
revert: true,
revertDuration: 50
revertDuration: 50,
start: function() {RED.view.focus();}
});
if (def.category == "subflows") {
$(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
}
setLabel(nt,$(d),label);
var categoryNode = $("#palette-container-"+category);
if (categoryNode.find(".palette_node").length === 1) {
if (!categoryNode.find("i").hasClass("expanded")) {
categoryNode.find(".palette-content").slideToggle();
categoryNode.find("i").toggleClass("expanded");
}
}
}
}
function removeNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
$("#palette_node_"+nodeTypeId).remove();
var paletteNode = $("#palette_node_"+nodeTypeId);
var categoryNode = paletteNode.closest(".palette-category");
paletteNode.remove();
if (categoryNode.find(".palette_node").length === 0) {
if (categoryNode.find("i").hasClass("expanded")) {
categoryNode.find(".palette-content").slideToggle();
categoryNode.find("i").toggleClass("expanded");
}
}
}
function hideNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
@@ -232,7 +256,8 @@ RED.palette = (function() {
var re = new RegExp(val,'i');
$(".palette_node").each(function(i,el) {
if (val === "" || re.test(el.id)) {
var currentLabel = $(el).find(".palette_label").text();
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
$(this).show();
} else {
$(this).hide();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,18 +51,16 @@ RED.sidebar = (function() {
sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2;
if (!RED.menu.isSelected("btn-sidebar")) {
if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true;
var newChartRight = 15;
$("#sidebar").addClass("closing");
$("#workspace").css("right",newChartRight);
$("#chart-zoom-controls").css("right",newChartRight+20);
$("#sidebar").width(0);
RED.menu.setSelected("btn-sidebar",true);
RED.view.resize();
RED.menu.setSelected("menu-item-sidebar",true);
eventHandler.emit("resize");
}
sidebarSeparator.width = $("#sidebar").width();
},
drag: function(event,ui) {
@@ -101,14 +99,12 @@ RED.sidebar = (function() {
$("#sidebar").width(newSidebarWidth);
sidebar_tabs.resize();
RED.view.resize();
eventHandler.emit("resize");
},
stop:function(event,ui) {
RED.view.resize();
if (sidebarSeparator.closing) {
$("#sidebar").removeClass("closing");
RED.menu.setSelected("btn-sidebar",false);
RED.menu.setSelected("menu-item-sidebar",false);
if ($("#sidebar").width() < 180) {
$("#sidebar").width(180);
$("#workspace").css("right",208);
@@ -117,6 +113,7 @@ RED.sidebar = (function() {
}
$("#sidebar-separator").css("left","auto");
$("#sidebar-separator").css("right",($("#sidebar").width()+13)+"px");
eventHandler.emit("resize");
}
});
@@ -127,6 +124,7 @@ RED.sidebar = (function() {
$("#main-container").removeClass("sidebar-closed");
sidebar_tabs.resize();
}
eventHandler.emit("resize");
}
function showSidebar(id) {
@@ -140,18 +138,40 @@ RED.sidebar = (function() {
}
function init () {
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();});
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("menu-item-sidebar",!RED.menu.isSelected("menu-item-sidebar"));d3.event.preventDefault();});
showSidebar();
RED.sidebar.info.show();
// hide info bar at start if screen rather narrow...
if ($(window).width() < 600) { toggleSidebar(); }
}
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
return {
init: init,
addTab: addTab,
removeTab: removeTab,
show: showSidebar,
containsTab: containsTab,
toggleSidebar: toggleSidebar
toggleSidebar: toggleSidebar,
on: eventHandler.on
}
})();

402
editor/js/ui/subflow.js Normal file
View File

@@ -0,0 +1,402 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.subflow = (function() {
function getSubflow() {
return RED.nodes.subflow(RED.workspaces.active());
}
function findAvailableSubflowIOPosition(subflow) {
var pos = {x:70,y:70};
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
var port;
if (i < subflow.out.length) {
port = subflow.out[i];
} else {
port = subflow.in[i-subflow.out.length];
}
if (port.x == pos.x && port.y == pos.y) {
pos.x += 55;
i=0;
}
}
return pos;
}
function addSubflowInput() {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow);
var newInput = {
type:"subflow",
direction:"in",
z:subflow.id,
i:subflow.in.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldInCount = subflow.in.length;
subflow.in.push(newInput);
subflow.dirty = true;
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.inputs = subflow.in.length;
RED.editor.updateNodeProperties(n);
}
});
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
inputCount: oldInCount
}
};
RED.history.push(historyEvent);
$("#workspace-subflow-add-input").toggleClass("disabled",true);
RED.view.select();
}
function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow);
var newOutput = {
type:"subflow",
direction:"out",
z:subflow.id,
i:subflow.out.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldOutCount = subflow.out.length;
subflow.out.push(newOutput);
subflow.dirty = true;
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.outputs = subflow.out.length;
RED.editor.updateNodeProperties(n);
}
});
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
outputCount: oldOutCount
}
};
RED.history.push(historyEvent);
RED.view.select();
}
function init() {
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
event.preventDefault();
});
$("#workspace-subflow-add-input").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowInput();
});
$("#workspace-subflow-add-output").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowOutput();
});
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
var removedNodes = [];
var removedLinks = [];
var startDirty = RED.nodes.dirty();
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+getSubflow().id) {
removedNodes.push(n);
}
if (n.z == getSubflow().id) {
removedNodes.push(n);
}
});
for (var i=0;i<removedNodes.length;i++) {
var rmlinks = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(rmlinks);
}
var activeSubflow = getSubflow();
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflow: activeSubflow,
dirty:startDirty
});
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
RED.view.redraw();
});
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-subflow-convert",true);
} else {
RED.menu.setDisabled("menu-item-subflow-convert",false);
}
});
}
function createSubflow() {
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
in: [],
out: []
};
RED.nodes.addSubflow(subflow);
RED.history.push({
t:'createSubflow',
subflow: subflow,
dirty:RED.nodes.dirty()
});
RED.workspaces.show(subflowId);
}
function convertToSubflow() {
var selection = RED.view.selection();
if (!selection.nodes) {
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
return;
}
var i;
var nodes = {};
var new_links = [];
var removedLinks = [];
var candidateInputs = [];
var candidateOutputs = [];
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
selection.nodes[0].y];
for (i=0;i<selection.nodes.length;i++) {
var n = selection.nodes[i];
nodes[n.id] = {n:n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.x),
Math.min(boundingBox[1],n.y),
Math.max(boundingBox[2],n.x),
Math.max(boundingBox[3],n.y)
]
}
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
RED.nodes.eachLink(function(link) {
if (nodes[link.source.id] && nodes[link.target.id]) {
// A link wholely within the selection
}
if (nodes[link.source.id] && !nodes[link.target.id]) {
// An outbound link from the selection
candidateOutputs.push(link);
removedLinks.push(link);
}
if (!nodes[link.source.id] && nodes[link.target.id]) {
// An inbound link
candidateInputs.push(link);
removedLinks.push(link);
}
});
var outputs = {};
candidateOutputs = candidateOutputs.filter(function(v) {
if (outputs[v.source.id+":"+v.sourcePort]) {
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
return false;
}
v.targets = [];
v.targets.push(v.target);
outputs[v.source.id+":"+v.sourcePort] = v;
return true;
});
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (candidateInputs.length > 1) {
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
return;
}
//if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return;
//}
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
in: candidateInputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.target.x-(v.target.w/2)-80,
y:v.target.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.target.id}]
}}),
out: candidateOutputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.source.x+(v.source.w/2)+80,
y:v.source.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.source.id,port:v.sourcePort}]
}})
};
RED.nodes.addSubflow(subflow);
var subflowInstance = {
id:RED.nodes.id(),
type:"subflow:"+subflow.id,
x: center[0],
y: center[1],
z: RED.workspaces.active(),
inputs: subflow.in.length,
outputs: subflow.out.length,
h: Math.max(30/*node_height*/,(subflow.out.length||0) * 15),
changed:true
}
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance);
candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link);
RED.nodes.addLink(link);
});
candidateOutputs.forEach(function(output,i) {
output.targets.forEach(function(target) {
var link = {source:subflowInstance, sourcePort:i, target: target};
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.out.forEach(function(output,i) {
output.wires.forEach(function(wire) {
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
new_links.push(link);
RED.nodes.addLink(link);
});
});
for (i=0;i<removedLinks.length;i++) {
RED.nodes.removeLink(removedLinks[i]);
}
for (i=0;i<selection.nodes.length;i++) {
selection.nodes[i].z = subflow.id;
}
RED.history.push({
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
subflow: subflow,
activeWorkspace: RED.workspaces.active(),
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
});
RED.editor.validateNode(subflow);
RED.nodes.dirty(true);
RED.view.redraw(true);
}
return {
init: init,
createSubflow: createSubflow,
convertToSubflow: convertToSubflow
}
})();

191
editor/js/ui/tab-info.js Normal file
View File

@@ -0,0 +1,191 @@
/**
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.sidebar.info = (function() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var content = document.createElement("div");
content.id = "tab-info";
content.style.paddingTop = "4px";
content.style.paddingLeft = "4px";
content.style.paddingRight = "4px";
var propertiesExpanded = false;
function show() {
if (!RED.sidebar.containsTab("info")) {
RED.sidebar.addTab("info",content,false);
}
RED.sidebar.show("info");
}
function jsonFilter(key,value) {
if (key === "") {
return value;
}
var t = typeof value;
if ($.isArray(value)) {
return "[array:"+value.length+"]";
} else if (t === "object") {
return "[object]"
} else if (t === "string") {
if (value.length > 30) {
return value.substring(0,30)+" ...";
}
}
return value;
}
function refresh(node) {
var table = '<table class="node-info"><tbody>';
table += '<tr class="blank"><td colspan="2">Node</td></tr>';
if (node.type != "subflow" && node.name) {
table += "<tr><td>Name</td><td>&nbsp;"+node.name+"</td></tr>";
}
table += "<tr><td>Type</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>ID</td><td>&nbsp;"+node.id+"</td></tr>";
var m = /^subflow(:(.+))?$/.exec(node.type);
if (m) {
var subflowNode;
if (m[2]) {
subflowNode = RED.nodes.subflow(m[2]);
} else {
subflowNode = node;
}
table += '<tr class="blank"><td colspan="2">Subflow</td></tr>';
var userCount = 0;
var subflowType = "subflow:"+subflowNode.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
userCount++;
}
});
table += "<tr><td>name</td><td>"+subflowNode.name+"</td></tr>";
table += "<tr><td>instances</td><td>"+userCount+"</td></tr>";
}
if (!m && node.type != "subflow" && node.type != "comment") {
table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> Properties</a></td></tr>';
if (node._def) {
for (var n in node._def.defaults) {
if (n != "name" && node._def.defaults.hasOwnProperty(n)) {
var val = node[n]||"";
var type = typeof val;
if (type === "string") {
if (val.length === 0) {
val += '<span style="font-style: italic; color: #ccc;">blank</span>';
} else {
if (val.length > 30) {
val = val.substring(0,30)+" ...";
}
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
} else if (type === "number") {
val = val.toString();
} else if ($.isArray(val)) {
val = "[<br/>";
for (var i=0;i<Math.min(node[n].length,10);i++) {
var vv = JSON.stringify(node[n][i],jsonFilter," ").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
val += "&nbsp;"+i+": "+vv+"<br/>";
}
if (node[n].length > 10) {
val += "&nbsp;... "+node[n].length+" items<br/>";
}
val += "]";
} else {
val = JSON.stringify(val,jsonFilter," ");
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
}
}
}
}
table += "</tbody></table><hr/>";
if (node.type != "comment") {
var helpText = $("script[data-help-name|='"+node.type+"']").html()||"";
table += '<div class="node-help">'+helpText+"</div>";
}
if (node._def && node._def.info) {
var info = node._def.info;
table += '<div class="node-help">'+marked(typeof info === "function" ? info.call(node) : info)+'</div>';
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
}
$("#tab-info").html(table);
$(".node-info-property-header").click(function(e) {
var icon = $(this).find("i");
if (icon.hasClass("fa-caret-right")) {
icon.removeClass("fa-caret-right");
icon.addClass("fa-caret-down");
$(".node-info-property-row").show();
propertiesExpanded = true;
} else {
icon.addClass("fa-caret-right");
icon.removeClass("fa-caret-down");
$(".node-info-property-row").hide();
propertiesExpanded = false;
}
e.preventDefault();
});
}
function clear() {
$("#tab-info").html("");
}
RED.view.on("selection-changed",function(selection) {
if (selection.nodes) {
if (selection.nodes.length == 1) {
var node = selection.nodes[0];
if (node.type === "subflow" && node.direction) {
refresh(RED.nodes.subflow(node.z));
} else {
refresh(node);
}
}
} else {
var subflow = RED.nodes.subflow(RED.workspaces.active());
if (subflow) {
refresh(subflow);
} else {
clear();
}
}
});
return {
show: show,
refresh:refresh,
clear: clear
}
})();

File diff suppressed because it is too large Load Diff

270
editor/js/ui/workspaces.js Normal file
View File

@@ -0,0 +1,270 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.workspaces = (function() {
var activeWorkspace = 0;
var workspaceIndex = 0;
function addWorkspace(ws) {
if (ws) {
workspace_tabs.addTab(ws);
workspace_tabs.resize();
} else {
var tabId = RED.nodes.id();
do {
workspaceIndex += 1;
} while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
RED.nodes.addWorkspace(ws);
workspace_tabs.addTab(ws);
workspace_tabs.activateTab(tabId);
RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
}
}
function deleteWorkspace(ws,force) {
if (workspace_tabs.count() == 1) {
return;
}
var nodes = [];
if (!force) {
nodes = RED.nodes.filterNodes({z:ws.id});
}
if (force || nodes.length === 0) {
removeWorkspace(ws);
var historyEvent = RED.nodes.removeWorkspace(ws.id);
historyEvent.t = 'delete';
historyEvent.dirty = RED.nodes.dirty();
historyEvent.workspaces = [ws];
RED.history.push(historyEvent);
RED.nodes.dirty(true);
} else {
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
$( "#node-dialog-delete-workspace-name" ).text(ws.label);
$( "#node-dialog-delete-workspace" ).dialog('open');
}
}
function showRenameWorkspaceDialog(id) {
var ws = RED.nodes.workspace(id);
$( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
if (workspace_tabs.count() == 1) {
$( "#node-dialog-rename-workspace").next().find(".leftButton")
.prop('disabled',true)
.addClass("ui-state-disabled");
} else {
$( "#node-dialog-rename-workspace").next().find(".leftButton")
.prop('disabled',false)
.removeClass("ui-state-disabled");
}
$( "#node-input-workspace-name" ).val(ws.label);
$( "#node-dialog-rename-workspace" ).dialog("open");
}
var workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
if (tab.type == "subflow") {
$("#workspace-toolbar").show();
} else {
$("#workspace-toolbar").hide();
}
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
eventHandler.emit("change",event);
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id);
} else {
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
}
},
onadd: function(tab) {
RED.menu.addItem("menu-item-workspace",{
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label,
onselect:function() {
workspace_tabs.activateTab(tab.id);
}
});
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
},
onremove: function(tab) {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
}
});
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
$( "#node-dialog-rename-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: "Rename sheet",
buttons: [
{
class: 'leftButton',
text: "Delete",
click: function() {
var workspace = $(this).dialog('option','workspace');
$( this ).dialog( "close" );
deleteWorkspace(workspace);
}
},
{
text: "Ok",
click: function() {
var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val();
if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu
}
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
$( "#node-dialog-delete-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: "Confirm delete",
buttons: [
{
text: "Ok",
click: function() {
var workspace = $(this).dialog('option','workspace');
deleteWorkspace(workspace,true);
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
function init() {
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
RED.sidebar.on("resize",workspace_tabs.resize);
RED.menu.setAction('menu-item-workspace-delete',function() {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
});
}
// TODO: DRY
var eventHandler = (function() {
var handlers = {};
return {
on: function(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
},
emit: function(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg);
}
}
}
}
})();
function removeWorkspace(ws) {
if (!ws) {
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
} else {
if (workspace_tabs.contains(ws.id)) {
workspace_tabs.removeTab(ws.id);
}
}
}
return {
init: init,
on: eventHandler.on,
add: addWorkspace,
remove: removeWorkspace,
edit: function(id) {
showRenameWorkspaceDialog(id||activeWorkspace);
},
contains: function(id) {
return workspace_tabs.contains(id);
},
count: function() {
return workspace_tabs.count();
},
active: function() {
return activeWorkspace
},
show: function(id) {
if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id);
if (sf) {
addWorkspace({type:"subflow",id:id,label:"Subflow: "+sf.name, closeable: true});
}
}
workspace_tabs.activateTab(id);
},
refresh: function() {
RED.nodes.eachSubflow(function(sf) {
if (workspace_tabs.contains(sf.id)) {
workspace_tabs.renameTab(sf.id,"Subflow: "+sf.name);
}
});
},
resize: function() {
workspace_tabs.resize();
}
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* Copyright 2014, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ RED.user = (function() {
}
var dialog = $('<div id="node-dialog-login" class="hide">'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img src="node-red-256.png"/></div>'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
'<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
'</div>'+
@@ -45,17 +45,40 @@ RED.user = (function() {
success: function(data) {
if (data.type == "credentials") {
var i=0;
if (data.image) {
$("#node-dialog-login-image").attr("src",data.image);
} else {
$("#node-dialog-login-image").attr("src","red/images/node-red-256.png");
}
for (;i<data.prompts.length;i++) {
var field = data.prompts[i];
var row = $("<div/>",{class:"form-row"});
var row = $("<div/>",{id:"rrr"+i,class:"form-row"});
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
$('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
if (i<data.prompts.length-1) {
input.keypress(
(function() {
var r = row;
return function(event) {
if (event.keyCode == 13) {
r.next("div").find("input").focus();
event.preventDefault();
}
}
})()
);
}
row.appendTo("#node-dialog-login-fields");
}
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+
'<a href="#" id="node-dialog-login-submit" tabIndex="'+(i+2)+'">Login</a></div>').appendTo("#node-dialog-login-fields");
$("#node-dialog-login-submit").button().click(function( event ) {
'<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="Login"></div>').appendTo("#node-dialog-login-fields");
$("#node-dialog-login-submit").button();
$("#node-dialog-login-fields").submit(function(event) {
$("#node-dialog-login-submit").button("option","disabled",true);
$("#node-dialog-login-failed").hide();
$(".login-spinner").show();
@@ -109,7 +132,56 @@ RED.user = (function() {
})
}
function updateUserMenu() {
$("#usermenu-submenu li").remove();
if (RED.settings.user.anonymous) {
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-login",
label:"Login",
onselect: function() {
RED.user.login({cancelable:true},function() {
RED.settings.load(function() {
RED.notify("Logged in as "+RED.settings.user.username,"success");
updateUserMenu();
});
});
}
});
} else {
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-username",
label:"<b>"+RED.settings.user.username+"</b>"
});
RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-logout",
label:"Logout",
onselect: function() {
RED.user.logout();
}
});
}
}
function init() {
if (RED.settings.user) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
.prependTo(".header-toolbar");
RED.menu.init({id:"btn-usermenu",
options: []
});
updateUserMenu();
}
}
}
return {
init: init,
login: login,
logout: logout
}

21
editor/sass/bootstrap.scss vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.popover-title { display: none; }
div.btn-group, a.btn {
@include disable-selection;
}

37
editor/sass/dragdrop.scss Normal file
View File

@@ -0,0 +1,37 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#dropTarget {
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
background: rgba(0,0,0,0.1);
display:table;
width: 100%;
height: 100%;
display: none;
}
#dropTarget div {
display: table-cell;
vertical-align: middle;
text-align: center;
font-size: 40px;
color: #fff;
}
#dropTarget div i {
font-size: 80px;
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus, .dropdown-submenu:hover>a, .dropdown-submenu:focus>a {
background: #999;
}
.dropdown-menu * .fa-check-square {
display: none;
color: #e0e0e0;
margin-left: -25px;
margin-top: 3px;
}
.dropdown-menu * a.active > .fa-check-square {
display: inline-block;
}
.dropdown-menu * .fa-square {
display: inline-block;
color: #e0e0e0;
margin-left: -25px;
margin-top: 3px;
}
.dropdown-menu * a.active > .fa-square {
display: none;
}
.dropdown-menu>li.disabled>a:hover>[class^="icon-"] {
background-image: url("vendor/bootstrap/img/glyphicons-halflings.png") !important;
}
/** Fix for unreachable dropdown menu **/
.dropdown-menu {
border-radius: 0;
width: 200px !important;
margin-left: 0px !important;
}
.dropdown-menu > li > a > i {
width: 10px;
text-align: center;
margin-left: -8px;
}
.dropdown-menu > li > a {
padding-left: 38px ;
text-indent: -8px ;
white-space: normal !important;
}
.dropdown-submenu>a:after {
display: none;
}
.dropdown-submenu>a:before {
display: block;
float: left;
width: 0;
height: 0;
margin-top: 5px;
margin-left: -30px;
border-color: transparent;
border-right-color: #e0e0e0;
border-style: solid;
border-width: 5px 5px 5px 0;
content: " ";
}
.dropdown-submenu.disabled > a:before {
border-right-color: #444;
}
.dropdown-submenu.pull-left>.dropdown-menu {
border-radius: 0;
}

76
editor/sass/editor.scss Normal file
View File

@@ -0,0 +1,76 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.dialog-form, #dialog-form, #dialog-config-form {
margin: 0;
height: 100%;
}
.input-error {
border-color: rgb(214, 97, 95) !important;
}
.leftButton {
margin-right: 200px !important;
}
.form-row {
clear: both;
margin-bottom:10px;
}
.form-row label {
display: inline-block;
width: 100px;
}
.form-row input {
width:70%;
}
input.input-append-left {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
button.input-append-right {
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
border-top-right-radius: 4px !important;
border-bottom-right-radius: 4px !important;
margin-left: -1px !important;
padding-left: 4px !important;
padding-right: 4px !important;
}
.form-tips {
background: lightgoldenrodyellow;
font-size: 12px;
padding: 8px;
border-radius: 5px;
border: 1px solid #999;
max-width: 450px;
}
.form-tips code {
border: none;
padding: auto;
}
.node-text-editor {
border:1px solid #ccc;
border-radius:5px;
overflow: hidden;
font-size: 14px !important;
font-family: monospace !important;
}

231
editor/sass/flow.scss Normal file
View File

@@ -0,0 +1,231 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.hidden {
display: none;
}
.lasso {
stroke-width: 2px;
stroke: #ff7f0e;
fill: rgba(20,125,255,0.1);
stroke-dasharray: 10 5;
}
.group-box {
stroke-width: 1px;
stroke: #aaaaaa;
fill: rgba(208, 211, 238, 0.1);
stroke-dasharray: 3 3;
}
.group-box-active {
fill: #fff;
stroke: #ff7f0e;
}
.group_label {
stroke-width: 0;
fill: #999;
font-size: 11px;
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
}
.pull-right {
margin-left: 20px;
}
#workspace {
@include component-border;
}
.node_label_italic {
font-style: italic;
}
.node_label_unknown {
font-style: italic;
fill: #e00 !important;
}
.node_label_white {
fill: #eee !important;
}
.node_label {
stroke-width: 0;
fill: #333;
font-size: 14px;
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
}
.port_label {
stroke-width: 0;
fill: #888;
font-size: 16px;
alignment-baseline: middle;
text-anchor: middle;
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
}
.function_label {
font-size: 12px;
}
.node {
stroke: #999;
cursor: move;
stroke-width: 2;
}
.node_unknown {
stroke-dasharray:10,4;
stroke: #f33;
}
.tool_arrow {
stroke-width: 1;
stroke: #999;
fill: #999;
cursor: pointer;
}
.node_tools {
fill: #ddd;
stroke: #999;
cursor: move;
stroke-width: 1;
cursor: pointer;
}
.node_tools_hovered {
stroke: #ff7f0e;
fill: #eee;
}
.node_button {
fill: inherit;
}
.port {
stroke: #999;
stroke-width: 2;
fill: #ddd;
cursor: crosshair;
}
.port_highlight {
stroke: #6DA332;
stroke-width: 3;
fill: #fff;
pointer-events:none;
fill-opacity: 0.5;
}
.node_error {
stroke: #ff0000;
stroke-width: 2;
fill: #ff7f0e;
}
.node_badge {
stroke: rgb(93, 114, 145);
stroke-width: 1;
fill: rgb(190, 209, 255);
}
.node_badge_label {
stroke-width:0;
fill: #fff;
font-size: 11px;
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
}
.node_invalid {
stroke: #ff0000;
}
.node_selected {
stroke: #ff7f0e !important;
}
.node_highlighted {
stroke: #dd1616;
stroke-width: 3;
stroke-dasharray: 10, 4;
}
.node_hovered {
}
.port_hovered {
stroke: #ff7f0e;
fill: #ff7f0e;
}
.subflowport {
stroke-dasharray: 5,5;
fill: #eee;
stroke: #999;
}
.drag_line {
stroke: #ff7f0e;
stroke-width: 5;
fill: none;
pointer-events: none;
}
.drag_line_hidden {
stroke: #ff7f0e;
stroke-width: 0;
pointer-events: none;
fill: none;
}
.link_line {
stroke: #7f7f7f;
stroke-width: 4;
fill: none;
pointer-events: none;
}
.link_subflow {
stroke: #bbb;
stroke-dasharray: 10,5;
stroke-width: 3;
}
.link_outline {
stroke: #fff;
stroke-width: 6;
cursor: crosshair;
fill: none;
pointer-events: none;
}
.link_background {
stroke: #fff;
opacity: 0;
stroke-width: 25;
cursor: crosshair;
fill: none;
}
g.link_selected path.link_line {
stroke: #ff7f0e;
}
g.link_unknown path.link_line {
stroke: #f00;
stroke-width: 2;
stroke-dasharray: 10, 4;
}

267
editor/sass/header.scss Normal file
View File

@@ -0,0 +1,267 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
$activeButton: #121212;
$deployButton: #8C101C;
$deployButtonHover: #6E0A1E;
$deployButtonActive: #4C0A17;
$deployDisabledButton: #444;
$deployDisabledButtonHover: #555;
$deployDisabledButtonActive: #444;
$headerMenuBackground: #121212;
$headerMenuItemHover: #323232;
$headerMenuItemDivider: #464646;
#header {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 40px;
background: #000;
box-sizing: border-box;
padding: 0px 0px 0px 20px;
color: #C7C7C7;
font-size: 14px;
}
span.logo {
float: left;
margin-top: 5px;
font-size: 30px;
line-height: 30px;
text-decoration: none;
span {
vertical-align: middle;
font-size: 16px !important;
}
img {
height: 18px;
}
a {
color: inherit;
&:hover {
text-decoration: none;
}
}
}
.header-toolbar {
padding: 0;
margin: 0;
list-style: none;
float: right;
> li {
display: inline-block;
padding: 0;
margin: 0;
position: relative;
}
}
.button {
@include disable-selection;
}
#header .button {
min-width: 20px;
text-align: center;
line-height: 40px;
display: inline-block;
font-size: 20px;
padding: 0px 12px;
text-decoration: none;
color: #C7C7C7;
margin: auto 5px;
vertical-align: middle;
border-left: 2px solid #000;
border-right: 2px solid #000;
&:hover {
border-color: $headerMenuItemHover;
}
}
.button-group {
display: inline-block;
margin: auto 15px;
vertical-align: middle;
clear: both;
}
.button-group > a {
display: inline-block;
float: left;
line-height: 22px;
font-size: 14px;
text-decoration: none;
padding: 4px 8px;
margin: 0;
}
.deploy-button {
background: $deployButton;
color: #eee !important;
&:hover {
background: $deployButtonHover;
}
&:active {
background: $deployButtonActive;
color: #ccc !important;
}
}
#btn-deploy {
padding: 4px 12px;
&.disabled {
cursor: default;
background: $deployDisabledButton;
color: #999 !important;
img {
opacity: 0.3;
}
&+ #btn-deploy-options {
background: $deployDisabledButton;
color: #ddd;
}
&+ #btn-deploy-options:hover {
background: $deployDisabledButtonHover;
}
&+ #btn-deploy-options:active {
background: $deployDisabledButton;
}
}
img {
margin-right: 8px;
}
}
.deploy-button-group.open {
#btn-deploy-options {
background: $activeButton !important;
}
}
#header .button {
&:active, &.active {
background: $activeButton;
}
&:focus {
outline: none;
}
}
#header li.open .button {
background: $activeButton;
border-color: $activeButton;
}
#header ul.dropdown-menu {
background: $headerMenuBackground;
width: 250px !important;
margin-top: 0;
}
#header ul.dropdown-menu li a {
color: #C7C7C7;
padding: 3px 40px;
}
#header ul.dropdown-menu li a img {
margin-right: 10px;
padding: 4px;
border: 3px solid rgba(0,0,0,0);
}
#header ul.dropdown-menu li a.active img {
border: 3px solid #777677;
}
#header ul.dropdown-menu li a span.menu-label-container {
width: 180px;
vertical-align: top;
display: inline-block;
text-indent: 0px;
}
#header ul.dropdown-menu li a span.menu-label {
font-size: 14px;
display: inline-block;
text-indent: 0px;
}
#header ul.dropdown-menu li a span.menu-sublabel {
color: #aeaeae;
font-size: 13px;
display: inline-block;
text-indent: 0px;
}
#header ul.dropdown-menu > li:hover > a,
#header ul.dropdown-menu > li:focus > a {
background: $headerMenuItemHover !important;
}
#header ul.dropdown-menu li.divider {
background: $headerMenuItemDivider;
border-bottom-color: $headerMenuItemHover;
}
#header ul.dropdown-menu li.disabled a {
color: #666;
}
#header ul.dropdown-menu > li.disabled:hover > a,
#header ul.dropdown-menu > li.disabled:focus > a {
background: none !important;
}
/* Deploy menu customisations */
#header ul#btn-deploy-options-submenu {
width: 300px !important;
}
#header ul#btn-deploy-options-submenu li a span.menu-label {
font-size: 16px;
display: inline-block;
text-indent: 0px;
}
#header ul#btn-deploy-options-submenu li a {
padding: 10px 30px;
color: #fff;
}
#header ul#btn-deploy-options-submenu li a > i.fa {
display: none !important;
}
/* User menu customisations */
#header ul#btn-usermenu-submenu li a#btn-username > .menu-label {
font-size: 16px;
color: #fff;
}

53
editor/sass/jquery.scss Normal file
View File

@@ -0,0 +1,53 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/* jQuery Theme overrides */
.ui-tabs .ui-tabs-panel {
padding: 0px;
}
.ui-autocomplete {
max-height: 250px;
overflow-x: hidden;
overflow-y: scroll;
}
.ui-dialog {
border-radius: 1px;
border: 1px solid #eee;
background: #fff;
padding: 0;
box-shadow: 2px 2px 12px rgba(0,0,0,0.2);
}
.ui-dialog .ui-dialog-content {
padding: 25px 25px 10px 25px;
}
.ui-dialog .ui-dialog-titlebar {
padding: 10px;
background: #f0f0f0;
border: none;
border-bottom: 2px solid #888;
border-radius: 0;
}
.ui-corner-all {
border-radius: 2px;
}
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default {
background: #f3f3f3;
}
.ui-dialog-no-close .ui-dialog-titlebar-close {
display: none;
}

40
editor/sass/keyboard.scss Normal file
View File

@@ -0,0 +1,40 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#keyboard-help-dialog {
font-size: 0.9em;
}
.keyboard-shortcuts {
padding: 10px;
}
.keyboard-shortcuts td {
padding: 7px 5px;
margin-bottom: 10px;
white-space: pre;
}
.keyboard-shortcuts td:first-child {
text-align: right;
padding-right: 10px;
}
.help-key {
border: 1px solid #ddd;
padding: 4px;
border-radius: 3px;
background: #f6f6f6;
font-family: Courier, monospace;
box-shadow: #999 1px 1px 1px;
}

33
editor/sass/library.scss Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#node-select-library {
overflow: hidden;
}
#node-select-library ul {
list-style: none;
padding: 0px;
margin: 2px;
}
#node-select-library li {
cursor: pointer;
}
#node-select-library li.list-selected {
background: #eee;
}
#node-select-library li.list-hover {
background: #ffffd0;
}

29
editor/sass/mixins.scss Normal file
View File

@@ -0,0 +1,29 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@mixin disable-selection {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin component-border {
border: 1px solid #000;
border-radius: 3px;
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.notification {
position: absolute;
}
#notifications {
z-index: 10000;
width: 500px;
margin-left: -250px;
left: 50%;
position: absolute;
top: 1px;
}
#notifications .alert {
box-shadow: 0 0 1px 1px;
margin-bottom: 5px;
}

200
editor/sass/palette.scss Normal file
View File

@@ -0,0 +1,200 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#palette {
position: absolute;
top: 5px;
bottom: 10px;
left:10px;
background: #f3f3f3;
width: 170px;
text-align: center;
@include disable-selection;
@include component-border;
}
.palette-scroll {
display: none;
position: absolute;
top: 0;
right: 0;
bottom: 35px;
left:0;
padding: 5px;
overflow-y: auto;
box-sizing:border-box;
}
.palette-spinner {
padding-top: 40px;
}
#palette-search {
position: absolute;
display: none;
bottom: 0;
left:0;
right:0;
overflow: hidden;
background: #f3f3f3;
text-align: center;
height: 35px;
padding: 3px;
border-top: 1px solid #999;
box-sizing:border-box;
}
#palette-search i.fa-search {
position: absolute;
pointer-events: none;
left: 4px;
top: 10px;
}
#palette-search i.fa-times {
position: absolute;
right: 6px;
top: 10px;
}
#palette-search-clear {
display: none;
color: #000;
}
#palette-search input {
border-radius: 0;
border: none;
width: 100%;
box-shadow: none;
-webkit-box-shadow: none;
padding: 3px 17px;
margin: 0px;
height: 30px;
box-sizing:border-box;
}
#palette-search input:focus {
border: none;
box-shadow: none;
-webkit-box-shadow: none;
}
.palette-category {
border: 1px solid #999;
border-radius: 3px;
margin-bottom: 5px;
}
.palette-content {
background: #fff;
border-top: 1px solid #aaa;
padding-bottom: 3px;
}
.palette-header {
background: #f3f3f3;
border-radius: 3px;
cursor: pointer;
text-align: left;
padding: 1px;
}
.palette-header i {
margin: 3px 4px 3px 3px;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
}
.palette-header i.expanded {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
}
.palette-header span {
clear: both;
}
.palette_label {
margin: 4px 0 4px 28px;
line-height: 20px;
overflow: hidden;
text-align: center;
}
.palette_label_right {
margin: 4px 28px 4px 0;
}
.palette_node {
cursor:move;
font-size:13px;
background: #ddd;
margin: 10px auto;
height: 25px;
border-radius: 6px;
border: 2px solid #999;
background-position: 5% 50%;
background-repeat: no-repeat;
width: 120px;
background-size: contain;
position: relative;
}
.palette_node:hover {
border-color: #ff7f0e;
background-color: #eee;
}
.palette_port {
position: absolute;
top:8px;
left: -5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
background:#d9d9d9;
border-radius: 3px;
width: 10px;
height: 10px;
border: 1px solid #999;
}
.palette_port_output {
left:auto;
right: -6px;
}
.palette_node:hover .palette_port {
border-color: #999;
background-color: #eee;
}
.palette_icon_container {
position: absolute;
text-align: center;
top:0;
bottom:0;
left:0;
width: 30px;
border-right: 2px solid rgba(0,0,0,0.1);
background-color: rgba(0,0,0,0.05);
}
.palette_icon_container_right {
left: auto;
right: 0;
border-right: none;
border-left: 2px solid rgba(0,0,0,0.1);
}
.palette_icon {
display: inline-block;
width: 20px;
height: 100%;
background-position: 50% 50%;
background-size: contain;
background-repeat: no-repeat;
}

60
editor/sass/sidebar.scss Normal file
View File

@@ -0,0 +1,60 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#sidebar {
position: absolute;
top: 5px;
right: 10px;
bottom: 10px;
width: 305px;
background: #fff;
box-sizing: border-box;
@include component-border;
}
#sidebar.closing {
background: #eee;
border-color: #900;
border-style: dashed;
}
#sidebar-content {
position: absolute;
top: 30px;
right: 0;
bottom: 1px;
left: 0px;
font-size: 1.2em;
overflow-y: auto;
}
#sidebar-separator {
position: absolute;
top: 5px;
right: 316px;
bottom:10px;
width: 15px;
background: url(images/grip.png) no-repeat 50% 50%;
cursor: col-resize;
}
.sidebar-closed > #sidebar { display: none; }
.sidebar-closed > #sidebar-separator { right: 0px !important; }
.sidebar-closed > #workspace { right: 15px !important; }
.sidebar-closed > #chart-zoom-controls { right: 35px !important; }

70
editor/sass/style.scss Normal file
View File

@@ -0,0 +1,70 @@
/**
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@import "mixins";
@import "jquery";
@import "bootstrap";
@import "dropdownMenu";
@import "header";
@import "palette";
@import "sidebar";
@import "workspace";
@import "workspaceToolbar";
@import "notifications";
@import "editor";
@import "library";
@import "tabs";
@import "tab-config";
@import "tab-info";
@import "flow";
@import "dragdrop";
@import "keyboard";
body {
font: 13px "Helvetica" !important;
padding-top: 100px;
background: url("images/pw_maze_white.png");
}
#main-container {
position: absolute;
top:50px; left:0; bottom: 0; right:0;
overflow:hidden;
}
i.spinner {
display: inline-block;
width: 14px;
height: 14px;
line-height: 14px;
vertical-align: text-top;
margin-top: 0px;
background: url(images/spin.svg) no-repeat 50% 50%;
background-size: contain
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
ul.tab-config-list {
list-style-type: none;
padding: 3px;
margin: 0;
@include disable-selection;
}
ul.tab-config-list li {
max-width: 400px;
font-size: 13px;
background: #f3f3f3;
margin: 10px auto;
border-radius: 3px;
border: 1px solid #ccc;
padding: 3px 8px;
}
div.tab-config-list-type {
}
div.tab-config-list-entry {
position: relative;
margin: 4px 0;
padding: 8px 4px 8px 10px;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
div.tab-config-list-entry:hover {
background: #f6f6f6;
}
div.tab-config-list-label {
}
div.tab-config-list-users {
position: absolute;
right: 3px;
top: 3px;
bottom: 3px;
line-height: 27px;
font-size: 11px;
background: #f6f6f6;
float: right;
border: 1px solid #eee;
border-radius: 3px;
padding: 1px 5px;
}

77
editor/sass/tab-info.scss Normal file
View File

@@ -0,0 +1,77 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
table.node-info {
font-size: 14px;
margin: 5px;
width: 97%;
}
table.node-info tr {
border: 1px solid #ddd;
}
table.node-info tr.blank {
border: none;
}
table.node-info tr.blank td {
padding-top: 8px;
border: none;
font-weight: bold;
padding-left: 0px;
}
table.node-info td:first-child{
color: #000;
vertical-align: top;
width: 90px;
padding: 3px;
border-right: 1px solid #ddd;
}
table.node-info td:last-child{
padding-left: 5px;
color: #666;
}
div.node-info {
margin: 5px;
}
.node-info-property-header {
color: #666;
}
.node-info-property-header:hover,
.node-info-property-header:focus {
color: #666;
text-decoration: none;
}
.node-help {
font-size: 16px;
h1 {
font-weight: normal;
font-size: 1.953em;
margin: 8px auto;
}
h2 {
font-weight: normal;
font-size: 1.563em;
margin: 8px auto;
}
h3,
h4,
h5 {
font-weight: normal;
font-size: 1.25em;
margin: 8px auto;
}
}

101
editor/sass/tabs.scss Normal file
View File

@@ -0,0 +1,101 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
ul.red-ui-tabs {
list-style-type: none;
padding:5px 2px 0px 5px;
margin: 0;
display: block;
height: 24px;
border-bottom: 1px solid #999;
background: #e3e3e3;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul.red-ui-tabs li {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
display: inline-block;
border-left: 1px solid #999;
border-top: 1px solid #999;
border-right: 1px solid #999;
border-bottom: 1px solid #999;
background: #e3e3e3;
margin: 0 5px 0 0;
height: 23px;
line-height: 17px;
max-width: 200px;
width: 14%;
overflow: hidden;
white-space: nowrap;
}
ul.red-ui-tabs li a.red-ui-tab-label {
display: block;
padding: 3px 16px;
color: #666;
}
ul.red-ui-tabs li {
position: relative;
}
ul.red-ui-tabs li a.red-ui-tab-close {
background: rgba(227,227,227,0.8);
position: absolute;
right: 2px;
top: 2px;
display: block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
padding: 0px;
border-radius: 5px;
color: #666;
}
ul.red-ui-tabs li a.red-ui-tab-close:hover {
background: #bbb !important;
}
ul.red-ui-tabs li a:hover {
text-decoration: none;
background: #f0f0f0;
}
ul.red-ui-tabs li.active {
background: #fff;
border-bottom: 1px solid #fff;
}
ul.red-ui-tabs li.active a {
color: #333;
}
ul.red-ui-tabs li.active a.red-ui-tab-close {
background: rgba(255,255,255,0.8);
}
ul.red-ui-tabs li.active a.red-ui-tab-label:hover {
background: #fff;
}
ul.red-ui-tabs li.red-ui-add-tab {
width: 25px;
border-top-right-radius: 15px;
line-height: 22px;
}
ul.red-ui-tabs li.red-ui-add-tab a {
padding: 2px 4px;
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#chart {
overflow: auto;
background: #e3e3e3;
position: absolute;
bottom:0px;
top: 30px;
left:0px;
right:0px;
}
#chart svg:focus {
outline: none;
}
#workspace {
position: absolute;
margin: 0;
top:5px;
left:190px;
bottom: 10px;
right: 330px;
overflow: hidden;
}
#chart-zoom-controls {
position: absolute;
bottom:30px; right: 350px;
}
#chart-zoom-controls {
padding-top: 3px;
text-align: right;
float: right;
}
#workspace-tabs {
margin-right: 28px;
}
#workspace-add-tab {
position: absolute;
top: 0;
right: 0;
height: 29px;
width: 28px;
border-bottom: 1px solid #999;
}
#btn-workspace-add-tab {
display: inline-block;
width: 100%;
background: #e3e3e3;
height: 100%;
line-height: 30px;
text-align: center;
color: #000;
}
#btn-workspace-add-tab:hover {
background: #efefef;
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
#workspace-toolbar {
display: none;
position: absolute;
top: 30px;
left:0;
right: 20px;
padding: 7px;
border-bottom-right-radius: 5px;
background: #f3f3f3;
}
#workspace-toolbar .button {
line-height: 18px;
display: inline-block;
font-size: 12px;
padding: 2px 8px;
text-decoration: none;
border-radius: 3px;
color: #666;
background: #f6f6f6;
vertical-align: middle;
box-shadow: 0 0 2px #888;
margin-right: 5px;
}
#workspace-toolbar .button.disabled {
box-shadow: 0 0 2px #bbb;
color: #aaa;
cursor: default;
}
#workspace-toolbar .button:not(.disabled):hover {
background: #e6e6e6;
box-shadow: 0 0 2px #666;
}
#workspace-toolbar .button:not(.disabled):active {
background: #e0e0e0;
box-shadow: 0 0 2px #444;
}

Some files were not shown because too many files have changed in this diff Show More